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