1// Copyright (c) 2011 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "chrome/browser/ui/gtk/content_setting_bubble_gtk.h"
6
7#include <set>
8#include <string>
9#include <vector>
10
11#include "base/i18n/rtl.h"
12#include "base/utf_string_conversions.h"
13#include "chrome/browser/blocked_content_container.h"
14#include "chrome/browser/content_setting_bubble_model.h"
15#include "chrome/browser/content_settings/host_content_settings_map.h"
16#include "chrome/browser/profiles/profile.h"
17#include "chrome/browser/ui/gtk/gtk_chrome_link_button.h"
18#include "chrome/browser/ui/gtk/gtk_theme_service.h"
19#include "chrome/browser/ui/gtk/gtk_util.h"
20#include "chrome/common/content_settings.h"
21#include "content/browser/tab_contents/tab_contents.h"
22#include "content/common/notification_source.h"
23#include "content/common/notification_type.h"
24#include "grit/app_resources.h"
25#include "grit/generated_resources.h"
26#include "ui/base/l10n/l10n_util.h"
27#include "ui/base/text/text_elider.h"
28#include "ui/gfx/gtk_util.h"
29#include "webkit/plugins/npapi/plugin_list.h"
30
31namespace {
32
33// Padding between content and edge of info bubble.
34const int kContentBorder = 7;
35
36// The maximum width of a title entry in the content box. We elide anything
37// longer than this.
38const int kMaxLinkPixelSize = 500;
39
40std::string BuildElidedText(const std::string& input) {
41  return UTF16ToUTF8(ui::ElideText(
42      UTF8ToUTF16(input),
43      gfx::Font(),
44      kMaxLinkPixelSize,
45      false));
46}
47
48}  // namespace
49
50ContentSettingBubbleGtk::ContentSettingBubbleGtk(
51    GtkWidget* anchor,
52    InfoBubbleGtkDelegate* delegate,
53    ContentSettingBubbleModel* content_setting_bubble_model,
54    Profile* profile,
55    TabContents* tab_contents)
56    : anchor_(anchor),
57      profile_(profile),
58      tab_contents_(tab_contents),
59      delegate_(delegate),
60      content_setting_bubble_model_(content_setting_bubble_model),
61      info_bubble_(NULL) {
62  registrar_.Add(this, NotificationType::TAB_CONTENTS_DESTROYED,
63                 Source<TabContents>(tab_contents));
64  BuildBubble();
65}
66
67ContentSettingBubbleGtk::~ContentSettingBubbleGtk() {
68}
69
70void ContentSettingBubbleGtk::Close() {
71  if (info_bubble_)
72    info_bubble_->Close();
73}
74
75void ContentSettingBubbleGtk::InfoBubbleClosing(InfoBubbleGtk* info_bubble,
76                                                bool closed_by_escape) {
77  delegate_->InfoBubbleClosing(info_bubble, closed_by_escape);
78  delete this;
79}
80
81void ContentSettingBubbleGtk::Observe(NotificationType type,
82                                      const NotificationSource& source,
83                                      const NotificationDetails& details) {
84  DCHECK(type == NotificationType::TAB_CONTENTS_DESTROYED);
85  DCHECK(source == Source<TabContents>(tab_contents_));
86  tab_contents_ = NULL;
87}
88
89void ContentSettingBubbleGtk::BuildBubble() {
90  GtkThemeService* theme_provider = GtkThemeService::GetFrom(profile_);
91
92  GtkWidget* bubble_content = gtk_vbox_new(FALSE, gtk_util::kControlSpacing);
93  gtk_container_set_border_width(GTK_CONTAINER(bubble_content), kContentBorder);
94
95  const ContentSettingBubbleModel::BubbleContent& content =
96      content_setting_bubble_model_->bubble_content();
97  if (!content.title.empty()) {
98    // Add the content label.
99    GtkWidget* label = gtk_label_new(content.title.c_str());
100    gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
101    gtk_box_pack_start(GTK_BOX(bubble_content), label, FALSE, FALSE, 0);
102  }
103
104  const std::set<std::string>& plugins = content.resource_identifiers;
105  if (!plugins.empty()) {
106    GtkWidget* list_content = gtk_vbox_new(FALSE, gtk_util::kControlSpacing);
107
108    for (std::set<std::string>::const_iterator it = plugins.begin();
109        it != plugins.end(); ++it) {
110      std::string name = UTF16ToUTF8(
111          webkit::npapi::PluginList::Singleton()->GetPluginGroupName(*it));
112      if (name.empty())
113        name = *it;
114
115      GtkWidget* label = gtk_label_new(BuildElidedText(name).c_str());
116      GtkWidget* label_box = gtk_hbox_new(FALSE, 0);
117      gtk_box_pack_start(GTK_BOX(label_box), label, FALSE, FALSE, 0);
118
119      gtk_box_pack_start(GTK_BOX(list_content),
120                         label_box,
121                         FALSE, FALSE, 0);
122    }
123    gtk_box_pack_start(GTK_BOX(bubble_content), list_content, FALSE, FALSE,
124                       gtk_util::kControlSpacing);
125  }
126
127  if (content_setting_bubble_model_->content_type() ==
128      CONTENT_SETTINGS_TYPE_POPUPS) {
129    const std::vector<ContentSettingBubbleModel::PopupItem>& popup_items =
130        content.popup_items;
131    GtkWidget* table = gtk_table_new(popup_items.size(), 2, FALSE);
132    int row = 0;
133    for (std::vector<ContentSettingBubbleModel::PopupItem>::const_iterator
134         i(popup_items.begin()); i != popup_items.end(); ++i, ++row) {
135      GtkWidget* image = gtk_image_new();
136      if (!i->bitmap.empty()) {
137        GdkPixbuf* icon_pixbuf = gfx::GdkPixbufFromSkBitmap(&i->bitmap);
138        gtk_image_set_from_pixbuf(GTK_IMAGE(image), icon_pixbuf);
139        g_object_unref(icon_pixbuf);
140
141        // We stuff the image in an event box so we can trap mouse clicks on the
142        // image (and launch the popup).
143        GtkWidget* event_box = gtk_event_box_new();
144        gtk_container_add(GTK_CONTAINER(event_box), image);
145
146        popup_icons_[event_box] = i -popup_items.begin();
147        g_signal_connect(event_box, "button_press_event",
148                         G_CALLBACK(OnPopupIconButtonPressThunk), this);
149        gtk_table_attach(GTK_TABLE(table), event_box, 0, 1, row, row + 1,
150                         GTK_FILL, GTK_FILL, gtk_util::kControlSpacing / 2,
151                         gtk_util::kControlSpacing / 2);
152      }
153
154      GtkWidget* button = gtk_chrome_link_button_new(
155          BuildElidedText(i->title).c_str());
156      popup_links_[button] = i -popup_items.begin();
157      g_signal_connect(button, "clicked", G_CALLBACK(OnPopupLinkClickedThunk),
158                       this);
159      gtk_table_attach(GTK_TABLE(table), button, 1, 2, row, row + 1,
160                       GTK_FILL, GTK_FILL, gtk_util::kControlSpacing / 2,
161                       gtk_util::kControlSpacing / 2);
162    }
163
164    gtk_box_pack_start(GTK_BOX(bubble_content), table, FALSE, FALSE, 0);
165  }
166
167  if (content_setting_bubble_model_->content_type() !=
168      CONTENT_SETTINGS_TYPE_COOKIES) {
169    const ContentSettingBubbleModel::RadioGroup& radio_group =
170        content.radio_group;
171    for (ContentSettingBubbleModel::RadioItems::const_iterator i =
172         radio_group.radio_items.begin();
173         i != radio_group.radio_items.end(); ++i) {
174      std::string elided = BuildElidedText(*i);
175      GtkWidget* radio =
176          radio_group_gtk_.empty() ?
177              gtk_radio_button_new_with_label(NULL, elided.c_str()) :
178              gtk_radio_button_new_with_label_from_widget(
179                  GTK_RADIO_BUTTON(radio_group_gtk_[0]),
180                  elided.c_str());
181      gtk_box_pack_start(GTK_BOX(bubble_content), radio, FALSE, FALSE, 0);
182      if (i - radio_group.radio_items.begin() == radio_group.default_item) {
183        // We must set the default value before we attach the signal handlers
184        // or pain occurs.
185        gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radio), TRUE);
186      }
187      radio_group_gtk_.push_back(radio);
188    }
189    for (std::vector<GtkWidget*>::const_iterator i = radio_group_gtk_.begin();
190         i != radio_group_gtk_.end(); ++i) {
191      // We can attach signal handlers now that all defaults are set.
192      g_signal_connect(*i, "toggled", G_CALLBACK(OnRadioToggledThunk), this);
193    }
194  }
195
196  for (std::vector<ContentSettingBubbleModel::DomainList>::const_iterator i =
197       content.domain_lists.begin();
198       i != content.domain_lists.end(); ++i) {
199    // Put each list into its own vbox to allow spacing between lists.
200    GtkWidget* list_content = gtk_vbox_new(FALSE, gtk_util::kControlSpacing);
201
202    GtkWidget* label = gtk_label_new(BuildElidedText(i->title).c_str());
203    gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
204    GtkWidget* label_box = gtk_hbox_new(FALSE, 0);
205    gtk_box_pack_start(GTK_BOX(label_box), label, FALSE, FALSE, 0);
206    gtk_box_pack_start(GTK_BOX(list_content), label_box, FALSE, FALSE, 0);
207    for (std::set<std::string>::const_iterator j = i->hosts.begin();
208         j != i->hosts.end(); ++j) {
209      gtk_box_pack_start(GTK_BOX(list_content),
210                         gtk_util::IndentWidget(gtk_util::CreateBoldLabel(*j)),
211                         FALSE, FALSE, 0);
212    }
213    gtk_box_pack_start(GTK_BOX(bubble_content), list_content, FALSE, FALSE,
214                       gtk_util::kControlSpacing);
215  }
216
217  if (!content.custom_link.empty()) {
218    GtkWidget* custom_link_box = gtk_hbox_new(FALSE, 0);
219    GtkWidget* custom_link = NULL;
220    if (content.custom_link_enabled) {
221      custom_link = gtk_chrome_link_button_new(content.custom_link.c_str());
222      g_signal_connect(custom_link, "clicked",
223                       G_CALLBACK(OnCustomLinkClickedThunk), this);
224    } else {
225      custom_link = gtk_label_new(content.custom_link.c_str());
226      gtk_misc_set_alignment(GTK_MISC(custom_link), 0, 0.5);
227    }
228    DCHECK(custom_link);
229    gtk_box_pack_start(GTK_BOX(custom_link_box), custom_link, FALSE, FALSE, 0);
230    gtk_box_pack_start(GTK_BOX(bubble_content), custom_link_box,
231                       FALSE, FALSE, 0);
232  }
233
234  gtk_box_pack_start(GTK_BOX(bubble_content), gtk_hseparator_new(),
235                     FALSE, FALSE, 0);
236
237  GtkWidget* bottom_box = gtk_hbox_new(FALSE, 0);
238
239  GtkWidget* manage_link =
240      gtk_chrome_link_button_new(content.manage_link.c_str());
241  g_signal_connect(manage_link, "clicked", G_CALLBACK(OnManageLinkClickedThunk),
242                   this);
243  gtk_box_pack_start(GTK_BOX(bottom_box), manage_link, FALSE, FALSE, 0);
244
245  GtkWidget* button = gtk_button_new_with_label(
246      l10n_util::GetStringUTF8(IDS_DONE).c_str());
247  g_signal_connect(button, "clicked", G_CALLBACK(OnCloseButtonClickedThunk),
248                   this);
249  gtk_box_pack_end(GTK_BOX(bottom_box), button, FALSE, FALSE, 0);
250
251  gtk_box_pack_start(GTK_BOX(bubble_content), bottom_box, FALSE, FALSE, 0);
252  gtk_widget_grab_focus(bottom_box);
253  gtk_widget_grab_focus(button);
254
255  InfoBubbleGtk::ArrowLocationGtk arrow_location =
256      !base::i18n::IsRTL() ?
257      InfoBubbleGtk::ARROW_LOCATION_TOP_RIGHT :
258      InfoBubbleGtk::ARROW_LOCATION_TOP_LEFT;
259  info_bubble_ = InfoBubbleGtk::Show(
260      anchor_,
261      NULL,
262      bubble_content,
263      arrow_location,
264      true,  // match_system_theme
265      true,  // grab_input
266      theme_provider,
267      this);
268}
269
270void ContentSettingBubbleGtk::OnPopupIconButtonPress(
271    GtkWidget* icon_event_box,
272    GdkEventButton* event) {
273  PopupMap::iterator i(popup_icons_.find(icon_event_box));
274  DCHECK(i != popup_icons_.end());
275  content_setting_bubble_model_->OnPopupClicked(i->second);
276  // The views interface implicitly closes because of the launching of a new
277  // window; we need to do that explicitly.
278  Close();
279}
280
281void ContentSettingBubbleGtk::OnPopupLinkClicked(GtkWidget* button) {
282  PopupMap::iterator i(popup_links_.find(button));
283  DCHECK(i != popup_links_.end());
284  content_setting_bubble_model_->OnPopupClicked(i->second);
285  // The views interface implicitly closes because of the launching of a new
286  // window; we need to do that explicitly.
287  Close();
288}
289
290void ContentSettingBubbleGtk::OnRadioToggled(GtkWidget* widget) {
291  for (ContentSettingBubbleGtk::RadioGroupGtk::const_iterator i =
292       radio_group_gtk_.begin();
293       i != radio_group_gtk_.end(); ++i) {
294    if (widget == *i) {
295      content_setting_bubble_model_->OnRadioClicked(
296          i - radio_group_gtk_.begin());
297      return;
298    }
299  }
300  NOTREACHED() << "unknown radio toggled";
301}
302
303void ContentSettingBubbleGtk::OnCloseButtonClicked(GtkWidget *button) {
304  Close();
305}
306
307void ContentSettingBubbleGtk::OnCustomLinkClicked(GtkWidget* button) {
308  content_setting_bubble_model_->OnCustomLinkClicked();
309  Close();
310}
311
312void ContentSettingBubbleGtk::OnManageLinkClicked(GtkWidget* button) {
313  content_setting_bubble_model_->OnManageLinkClicked();
314  Close();
315}
316