• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
31 namespace {
32 
33 // Padding between content and edge of info bubble.
34 const int kContentBorder = 7;
35 
36 // The maximum width of a title entry in the content box. We elide anything
37 // longer than this.
38 const int kMaxLinkPixelSize = 500;
39 
BuildElidedText(const std::string & input)40 std::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 
ContentSettingBubbleGtk(GtkWidget * anchor,InfoBubbleGtkDelegate * delegate,ContentSettingBubbleModel * content_setting_bubble_model,Profile * profile,TabContents * tab_contents)50 ContentSettingBubbleGtk::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 
~ContentSettingBubbleGtk()67 ContentSettingBubbleGtk::~ContentSettingBubbleGtk() {
68 }
69 
Close()70 void ContentSettingBubbleGtk::Close() {
71   if (info_bubble_)
72     info_bubble_->Close();
73 }
74 
InfoBubbleClosing(InfoBubbleGtk * info_bubble,bool closed_by_escape)75 void ContentSettingBubbleGtk::InfoBubbleClosing(InfoBubbleGtk* info_bubble,
76                                                 bool closed_by_escape) {
77   delegate_->InfoBubbleClosing(info_bubble, closed_by_escape);
78   delete this;
79 }
80 
Observe(NotificationType type,const NotificationSource & source,const NotificationDetails & details)81 void 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 
BuildBubble()89 void 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 
OnPopupIconButtonPress(GtkWidget * icon_event_box,GdkEventButton * event)270 void 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 
OnPopupLinkClicked(GtkWidget * button)281 void 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 
OnRadioToggled(GtkWidget * widget)290 void 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 
OnCloseButtonClicked(GtkWidget * button)303 void ContentSettingBubbleGtk::OnCloseButtonClicked(GtkWidget *button) {
304   Close();
305 }
306 
OnCustomLinkClicked(GtkWidget * button)307 void ContentSettingBubbleGtk::OnCustomLinkClicked(GtkWidget* button) {
308   content_setting_bubble_model_->OnCustomLinkClicked();
309   Close();
310 }
311 
OnManageLinkClicked(GtkWidget * button)312 void ContentSettingBubbleGtk::OnManageLinkClicked(GtkWidget* button) {
313   content_setting_bubble_model_->OnManageLinkClicked();
314   Close();
315 }
316