1 // Copyright (c) 2012 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.
5 #include "chrome/browser/ui/gtk/content_setting_bubble_gtk.h"
11 #include "base/bind.h"
12 #include "base/i18n/rtl.h"
13 #include "base/stl_util.h"
14 #include "base/strings/utf_string_conversions.h"
15 #include "chrome/browser/content_settings/host_content_settings_map.h"
16 #include "chrome/browser/plugins/plugin_finder.h"
17 #include "chrome/browser/plugins/plugin_metadata.h"
18 #include "chrome/browser/profiles/profile.h"
19 #include "chrome/browser/ui/content_settings/content_setting_bubble_model.h"
20 #include "chrome/browser/ui/content_settings/content_setting_media_menu_model.h"
21 #include "chrome/browser/ui/gtk/gtk_chrome_link_button.h"
22 #include "chrome/browser/ui/gtk/gtk_theme_service.h"
23 #include "chrome/browser/ui/gtk/gtk_util.h"
24 #include "chrome/common/content_settings.h"
25 #include "content/public/browser/plugin_service.h"
26 #include "content/public/browser/web_contents.h"
27 #include "grit/generated_resources.h"
28 #include "grit/ui_resources.h"
29 #include "ui/base/gtk/gtk_hig_constants.h"
30 #include "ui/base/l10n/l10n_util.h"
31 #include "ui/base/models/simple_menu_model.h"
32 #include "ui/gfx/font_list.h"
33 #include "ui/gfx/gtk_util.h"
34 #include "ui/gfx/text_elider.h"
36 using content::PluginService
;
37 using content::WebContents
;
41 // The maximum width of a title entry in the content box. We elide anything
43 const int kMaxLinkPixelSize
= 500;
45 // The minimum and maximum width of the media menu buttons.
46 const int kMinMediaMenuButtonWidth
= 100;
47 const int kMaxMediaMenuButtonWidth
= 600;
49 std::string
BuildElidedText(const std::string
& input
) {
50 return base::UTF16ToUTF8(gfx::ElideText(
51 base::UTF8ToUTF16(input
),
59 ContentSettingBubbleGtk::ContentSettingBubbleGtk(
61 BubbleDelegateGtk
* delegate
,
62 ContentSettingBubbleModel
* content_setting_bubble_model
,
67 content_setting_bubble_model_(content_setting_bubble_model
),
72 ContentSettingBubbleGtk::~ContentSettingBubbleGtk() {
75 void ContentSettingBubbleGtk::Close() {
76 STLDeleteValues(&media_menus_
);
82 void ContentSettingBubbleGtk::UpdateMenuLabel(content::MediaStreamType type
,
83 const std::string
& label
) {
84 GtkMediaMenuMap::const_iterator it
= media_menus_
.begin();
85 for (; it
!= media_menus_
.end(); ++it
) {
86 if (it
->second
->type
== type
) {
87 gtk_label_set_text(GTK_LABEL(it
->second
->label
.get()), label
.c_str());
94 void ContentSettingBubbleGtk::BubbleClosing(BubbleGtk
* bubble
,
95 bool closed_by_escape
) {
96 delegate_
->BubbleClosing(bubble
, closed_by_escape
);
100 void ContentSettingBubbleGtk::BuildBubble() {
101 GtkThemeService
* theme_provider
= GtkThemeService::GetFrom(profile_
);
103 GtkWidget
* bubble_content
= gtk_vbox_new(FALSE
, ui::kControlSpacing
);
104 gtk_container_set_border_width(GTK_CONTAINER(bubble_content
),
105 ui::kContentAreaBorder
);
107 const ContentSettingBubbleModel::BubbleContent
& content
=
108 content_setting_bubble_model_
->bubble_content();
109 if (!content
.title
.empty()) {
110 // Add the content label.
111 GtkWidget
* label
= theme_provider
->BuildLabel(content
.title
.c_str(),
113 gtk_misc_set_alignment(GTK_MISC(label
), 0, 0.5);
114 gtk_box_pack_start(GTK_BOX(bubble_content
), label
, FALSE
, FALSE
, 0);
117 if (content_setting_bubble_model_
->content_type() ==
118 CONTENT_SETTINGS_TYPE_POPUPS
) {
119 const std::vector
<ContentSettingBubbleModel::PopupItem
>& popup_items
=
121 GtkWidget
* table
= gtk_table_new(popup_items
.size(), 2, FALSE
);
123 for (std::vector
<ContentSettingBubbleModel::PopupItem
>::const_iterator
124 i(popup_items
.begin()); i
!= popup_items
.end(); ++i
, ++row
) {
125 GtkWidget
* image
= gtk_image_new();
126 if (!i
->image
.IsEmpty()) {
127 GdkPixbuf
* icon_pixbuf
= i
->image
.ToGdkPixbuf();
128 gtk_image_set_from_pixbuf(GTK_IMAGE(image
), icon_pixbuf
);
130 // We stuff the image in an event box so we can trap mouse clicks on the
131 // image (and launch the popup).
132 GtkWidget
* event_box
= gtk_event_box_new();
133 gtk_container_add(GTK_CONTAINER(event_box
), image
);
135 popup_icons_
[event_box
] = i
-popup_items
.begin();
136 g_signal_connect(event_box
, "button_press_event",
137 G_CALLBACK(OnPopupIconButtonPressThunk
), this);
138 gtk_table_attach(GTK_TABLE(table
), event_box
, 0, 1, row
, row
+ 1,
139 GTK_FILL
, GTK_FILL
, ui::kControlSpacing
/ 2,
140 ui::kControlSpacing
/ 2);
143 GtkWidget
* button
= gtk_chrome_link_button_new(
144 BuildElidedText(i
->title
).c_str());
145 popup_links_
[button
] = i
-popup_items
.begin();
146 g_signal_connect(button
, "clicked", G_CALLBACK(OnPopupLinkClickedThunk
),
148 gtk_table_attach(GTK_TABLE(table
), button
, 1, 2, row
, row
+ 1,
149 GTK_FILL
, GTK_FILL
, ui::kControlSpacing
/ 2,
150 ui::kControlSpacing
/ 2);
153 gtk_box_pack_start(GTK_BOX(bubble_content
), table
, FALSE
, FALSE
, 0);
156 const ContentSettingBubbleModel::RadioGroup
& radio_group
=
158 for (ContentSettingBubbleModel::RadioItems::const_iterator i
=
159 radio_group
.radio_items
.begin();
160 i
!= radio_group
.radio_items
.end(); ++i
) {
161 std::string elided
= BuildElidedText(*i
);
162 GtkWidget
* radio
= radio_group_gtk_
.empty() ?
163 gtk_radio_button_new(NULL
) :
164 gtk_radio_button_new_from_widget(GTK_RADIO_BUTTON(radio_group_gtk_
[0]));
166 theme_provider
->BuildLabel(elided
.c_str(), ui::kGdkBlack
);
167 gtk_container_add(GTK_CONTAINER(radio
), label
);
168 gtk_box_pack_start(GTK_BOX(bubble_content
), radio
, FALSE
, FALSE
, 0);
169 if (i
- radio_group
.radio_items
.begin() == radio_group
.default_item
) {
170 // We must set the default value before we attach the signal handlers
172 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radio
), TRUE
);
174 if (!content
.radio_group_enabled
)
175 gtk_widget_set_sensitive(radio
, FALSE
);
176 radio_group_gtk_
.push_back(radio
);
178 for (std::vector
<GtkWidget
*>::const_iterator i
= radio_group_gtk_
.begin();
179 i
!= radio_group_gtk_
.end(); ++i
) {
180 // We can attach signal handlers now that all defaults are set.
181 g_signal_connect(*i
, "toggled", G_CALLBACK(OnRadioToggledThunk
), this);
184 // Layout code for the media device menus.
185 if (content_setting_bubble_model_
->content_type() ==
186 CONTENT_SETTINGS_TYPE_MEDIASTREAM
) {
187 GtkWidget
* table
= gtk_table_new(content
.media_menus
.size(), 2, FALSE
);
190 for (ContentSettingBubbleModel::MediaMenuMap::const_iterator
i(
191 content
.media_menus
.begin());
192 i
!= content
.media_menus
.end();
194 GtkWidget
* label
= theme_provider
->BuildLabel(
195 i
->second
.label
.c_str(), ui::kGdkBlack
);
196 gtk_table_attach(GTK_TABLE(table
), gtk_util::LeftAlignMisc(label
), 0, 1,
197 row
, row
+ 1, GTK_FILL
, GTK_FILL
,
198 ui::kControlSpacing
/ 2, ui::kControlSpacing
/ 2);
200 // Build up the gtk menu button.
201 MediaMenuGtk
* gtk_menu
= new MediaMenuGtk(i
->first
);
203 gtk_label_new(i
->second
.selected_device
.name
.c_str()));
204 GtkWidget
* button
= gtk_button_new();
205 GtkWidget
* button_content
= gtk_hbox_new(FALSE
, 0);
206 gtk_box_pack_start(GTK_BOX(button_content
),
207 gtk_util::LeftAlignMisc(gtk_menu
->label
.get()),
209 GtkWidget
* arrow
= gtk_arrow_new(GTK_ARROW_DOWN
, GTK_SHADOW_NONE
);
210 gtk_box_pack_end(GTK_BOX(button_content
), arrow
, FALSE
, FALSE
, 0);
212 gtk_container_add(GTK_CONTAINER(button
), button_content
);
214 // Store the gtk menu to the map.
215 gtk_menu
->menu_model
.reset(new ContentSettingMediaMenuModel(
217 content_setting_bubble_model_
.get(),
218 base::Bind(&ContentSettingBubbleGtk::UpdateMenuLabel
,
219 base::Unretained(this))));
220 gtk_menu
->menu
.reset(new MenuGtk(NULL
, gtk_menu
->menu_model
.get()));
221 media_menus_
[button
] = gtk_menu
;
223 if (!gtk_menu
->menu_model
->GetItemCount()) {
224 // Show a "None available" title and grey out the menu when there is
225 // no available device.
228 l10n_util::GetStringUTF8(IDS_MEDIA_MENU_NO_DEVICE_TITLE
));
229 gtk_widget_set_sensitive(button
, FALSE
);
232 // Disable the device selection when the website is managing the devices
234 if (i
->second
.disabled
)
235 gtk_widget_set_sensitive(button
, FALSE
);
237 // Use the longest width of the menus as the width of the menu buttons.
238 GtkRequisition menu_req
;
239 gtk_widget_size_request(gtk_menu
->menu
->widget(), &menu_req
);
240 menu_width
= std::max(menu_width
, menu_req
.width
);
242 g_signal_connect(button
, "clicked",
243 G_CALLBACK(OnMenuButtonClickedThunk
), this);
244 gtk_table_attach(GTK_TABLE(table
), button
, 1, 2, row
, row
+ 1,
245 GTK_FILL
, GTK_FILL
, ui::kControlSpacing
* 2,
246 ui::kControlSpacing
/ 2);
249 // Make sure the width is within [kMinMediaMenuButtonWidth,
250 // kMaxMediaMenuButtonWidth].
251 menu_width
= std::max(kMinMediaMenuButtonWidth
, menu_width
);
252 menu_width
= std::min(kMaxMediaMenuButtonWidth
, menu_width
);
254 // Set all the menu buttons to the width we calculated above.
255 for (GtkMediaMenuMap::const_iterator i
= media_menus_
.begin();
256 i
!= media_menus_
.end(); ++i
)
257 gtk_widget_set_size_request(i
->first
, menu_width
, -1);
259 gtk_box_pack_start(GTK_BOX(bubble_content
), table
, FALSE
, FALSE
, 0);
262 for (std::vector
<ContentSettingBubbleModel::DomainList
>::const_iterator i
=
263 content
.domain_lists
.begin();
264 i
!= content
.domain_lists
.end(); ++i
) {
265 // Put each list into its own vbox to allow spacing between lists.
266 GtkWidget
* list_content
= gtk_vbox_new(FALSE
, ui::kControlSpacing
);
268 GtkWidget
* label
= theme_provider
->BuildLabel(
269 BuildElidedText(i
->title
).c_str(), ui::kGdkBlack
);
270 gtk_label_set_line_wrap(GTK_LABEL(label
), TRUE
);
271 GtkWidget
* label_box
= gtk_hbox_new(FALSE
, 0);
272 gtk_box_pack_start(GTK_BOX(label_box
), label
, FALSE
, FALSE
, 0);
273 gtk_box_pack_start(GTK_BOX(list_content
), label_box
, FALSE
, FALSE
, 0);
274 for (std::set
<std::string
>::const_iterator j
= i
->hosts
.begin();
275 j
!= i
->hosts
.end(); ++j
) {
276 gtk_box_pack_start(GTK_BOX(list_content
),
277 gtk_util::IndentWidget(gtk_util::CreateBoldLabel(*j
)),
280 gtk_box_pack_start(GTK_BOX(bubble_content
), list_content
, FALSE
, FALSE
,
281 ui::kControlSpacing
);
284 if (!content
.custom_link
.empty()) {
285 GtkWidget
* custom_link_box
= gtk_hbox_new(FALSE
, 0);
286 GtkWidget
* custom_link
= NULL
;
287 if (content
.custom_link_enabled
) {
289 theme_provider
->BuildChromeLinkButton(content
.custom_link
.c_str());
290 g_signal_connect(custom_link
, "clicked",
291 G_CALLBACK(OnCustomLinkClickedThunk
), this);
293 custom_link
= theme_provider
->BuildLabel(content
.custom_link
.c_str(),
295 gtk_misc_set_alignment(GTK_MISC(custom_link
), 0, 0.5);
298 gtk_box_pack_start(GTK_BOX(custom_link_box
), custom_link
, FALSE
, FALSE
, 0);
299 gtk_box_pack_start(GTK_BOX(bubble_content
), custom_link_box
,
303 gtk_box_pack_start(GTK_BOX(bubble_content
), gtk_hseparator_new(),
306 GtkWidget
* bottom_box
= gtk_hbox_new(FALSE
, 0);
308 GtkWidget
* manage_link
=
309 theme_provider
->BuildChromeLinkButton(content
.manage_link
.c_str());
310 g_signal_connect(manage_link
, "clicked", G_CALLBACK(OnManageLinkClickedThunk
),
312 gtk_box_pack_start(GTK_BOX(bottom_box
), manage_link
, FALSE
, FALSE
, 0);
315 gtk_button_new_with_label(l10n_util::GetStringUTF8(IDS_DONE
).c_str());
316 g_signal_connect(button
, "clicked", G_CALLBACK(OnCloseButtonClickedThunk
),
318 gtk_box_pack_end(GTK_BOX(bottom_box
), button
, FALSE
, FALSE
, 0);
320 gtk_box_pack_start(GTK_BOX(bubble_content
), bottom_box
, FALSE
, FALSE
, 0);
321 gtk_widget_grab_focus(button
);
323 bubble_
= BubbleGtk::Show(anchor_
,
326 BubbleGtk::ANCHOR_TOP_RIGHT
,
327 BubbleGtk::MATCH_SYSTEM_THEME
|
328 BubbleGtk::POPUP_WINDOW
|
329 BubbleGtk::GRAB_INPUT
,
334 void ContentSettingBubbleGtk::OnPopupIconButtonPress(
335 GtkWidget
* icon_event_box
,
336 GdkEventButton
* event
) {
337 PopupMap::iterator
i(popup_icons_
.find(icon_event_box
));
338 DCHECK(i
!= popup_icons_
.end());
339 content_setting_bubble_model_
->OnPopupClicked(i
->second
);
340 // The views interface implicitly closes because of the launching of a new
341 // window; we need to do that explicitly.
345 void ContentSettingBubbleGtk::OnPopupLinkClicked(GtkWidget
* button
) {
346 PopupMap::iterator
i(popup_links_
.find(button
));
347 DCHECK(i
!= popup_links_
.end());
348 content_setting_bubble_model_
->OnPopupClicked(i
->second
);
349 // The views interface implicitly closes because of the launching of a new
350 // window; we need to do that explicitly.
354 void ContentSettingBubbleGtk::OnRadioToggled(GtkWidget
* widget
) {
355 for (ContentSettingBubbleGtk::RadioGroupGtk::const_iterator i
=
356 radio_group_gtk_
.begin();
357 i
!= radio_group_gtk_
.end(); ++i
) {
359 content_setting_bubble_model_
->OnRadioClicked(
360 i
- radio_group_gtk_
.begin());
364 NOTREACHED() << "unknown radio toggled";
367 void ContentSettingBubbleGtk::OnCloseButtonClicked(GtkWidget
* button
) {
368 content_setting_bubble_model_
->OnDoneClicked();
372 void ContentSettingBubbleGtk::OnCustomLinkClicked(GtkWidget
* button
) {
373 content_setting_bubble_model_
->OnCustomLinkClicked();
377 void ContentSettingBubbleGtk::OnManageLinkClicked(GtkWidget
* button
) {
378 content_setting_bubble_model_
->OnManageLinkClicked();
382 void ContentSettingBubbleGtk::OnMenuButtonClicked(GtkWidget
* button
) {
383 GtkMediaMenuMap::iterator
i(media_menus_
.find(button
));
384 DCHECK(i
!= media_menus_
.end());
385 i
->second
->menu
->PopupForWidget(button
, 1, gtk_get_current_event_time());
388 ContentSettingBubbleGtk::MediaMenuGtk::MediaMenuGtk(
389 content::MediaStreamType type
)
392 ContentSettingBubbleGtk::MediaMenuGtk::~MediaMenuGtk() {}