| OLD | NEW |
| (Empty) |
| 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. | |
| 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/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" | |
| 35 | |
| 36 using content::PluginService; | |
| 37 using content::WebContents; | |
| 38 | |
| 39 namespace { | |
| 40 | |
| 41 // The maximum width of a title entry in the content box. We elide anything | |
| 42 // longer than this. | |
| 43 const int kMaxLinkPixelSize = 500; | |
| 44 | |
| 45 // The minimum and maximum width of the media menu buttons. | |
| 46 const int kMinMediaMenuButtonWidth = 100; | |
| 47 const int kMaxMediaMenuButtonWidth = 600; | |
| 48 | |
| 49 std::string BuildElidedText(const std::string& input) { | |
| 50 return base::UTF16ToUTF8(gfx::ElideText( | |
| 51 base::UTF8ToUTF16(input), | |
| 52 gfx::FontList(), | |
| 53 kMaxLinkPixelSize, | |
| 54 gfx::ELIDE_AT_END)); | |
| 55 } | |
| 56 | |
| 57 } // namespace | |
| 58 | |
| 59 ContentSettingBubbleGtk::ContentSettingBubbleGtk( | |
| 60 GtkWidget* anchor, | |
| 61 BubbleDelegateGtk* delegate, | |
| 62 ContentSettingBubbleModel* content_setting_bubble_model, | |
| 63 Profile* profile) | |
| 64 : anchor_(anchor), | |
| 65 profile_(profile), | |
| 66 delegate_(delegate), | |
| 67 content_setting_bubble_model_(content_setting_bubble_model), | |
| 68 bubble_(NULL) { | |
| 69 BuildBubble(); | |
| 70 } | |
| 71 | |
| 72 ContentSettingBubbleGtk::~ContentSettingBubbleGtk() { | |
| 73 } | |
| 74 | |
| 75 void ContentSettingBubbleGtk::Close() { | |
| 76 STLDeleteValues(&media_menus_); | |
| 77 | |
| 78 if (bubble_) | |
| 79 bubble_->Close(); | |
| 80 } | |
| 81 | |
| 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()); | |
| 88 return; | |
| 89 } | |
| 90 } | |
| 91 NOTREACHED(); | |
| 92 } | |
| 93 | |
| 94 void ContentSettingBubbleGtk::BubbleClosing(BubbleGtk* bubble, | |
| 95 bool closed_by_escape) { | |
| 96 delegate_->BubbleClosing(bubble, closed_by_escape); | |
| 97 delete this; | |
| 98 } | |
| 99 | |
| 100 void ContentSettingBubbleGtk::BuildBubble() { | |
| 101 GtkThemeService* theme_provider = GtkThemeService::GetFrom(profile_); | |
| 102 | |
| 103 GtkWidget* bubble_content = gtk_vbox_new(FALSE, ui::kControlSpacing); | |
| 104 gtk_container_set_border_width(GTK_CONTAINER(bubble_content), | |
| 105 ui::kContentAreaBorder); | |
| 106 | |
| 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(), | |
| 112 ui::kGdkBlack); | |
| 113 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); | |
| 114 gtk_box_pack_start(GTK_BOX(bubble_content), label, FALSE, FALSE, 0); | |
| 115 } | |
| 116 | |
| 117 if (content_setting_bubble_model_->content_type() == | |
| 118 CONTENT_SETTINGS_TYPE_POPUPS) { | |
| 119 const std::vector<ContentSettingBubbleModel::PopupItem>& popup_items = | |
| 120 content.popup_items; | |
| 121 GtkWidget* table = gtk_table_new(popup_items.size(), 2, FALSE); | |
| 122 int row = 0; | |
| 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); | |
| 129 | |
| 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); | |
| 134 | |
| 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); | |
| 141 } | |
| 142 | |
| 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), | |
| 147 this); | |
| 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); | |
| 151 } | |
| 152 | |
| 153 gtk_box_pack_start(GTK_BOX(bubble_content), table, FALSE, FALSE, 0); | |
| 154 } | |
| 155 | |
| 156 const ContentSettingBubbleModel::RadioGroup& radio_group = | |
| 157 content.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])); | |
| 165 GtkWidget* label = | |
| 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 | |
| 171 // or pain occurs. | |
| 172 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radio), TRUE); | |
| 173 } | |
| 174 if (!content.radio_group_enabled) | |
| 175 gtk_widget_set_sensitive(radio, FALSE); | |
| 176 radio_group_gtk_.push_back(radio); | |
| 177 } | |
| 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); | |
| 182 } | |
| 183 | |
| 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); | |
| 188 int menu_width = 0; | |
| 189 int row = 0; | |
| 190 for (ContentSettingBubbleModel::MediaMenuMap::const_iterator i( | |
| 191 content.media_menus.begin()); | |
| 192 i != content.media_menus.end(); | |
| 193 ++i, ++row) { | |
| 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); | |
| 199 | |
| 200 // Build up the gtk menu button. | |
| 201 MediaMenuGtk* gtk_menu = new MediaMenuGtk(i->first); | |
| 202 gtk_menu->label.Own( | |
| 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()), | |
| 208 FALSE, FALSE, 0); | |
| 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); | |
| 211 | |
| 212 gtk_container_add(GTK_CONTAINER(button), button_content); | |
| 213 | |
| 214 // Store the gtk menu to the map. | |
| 215 gtk_menu->menu_model.reset(new ContentSettingMediaMenuModel( | |
| 216 gtk_menu->type, | |
| 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; | |
| 222 | |
| 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. | |
| 226 UpdateMenuLabel( | |
| 227 gtk_menu->type, | |
| 228 l10n_util::GetStringUTF8(IDS_MEDIA_MENU_NO_DEVICE_TITLE)); | |
| 229 gtk_widget_set_sensitive(button, FALSE); | |
| 230 } | |
| 231 | |
| 232 // Disable the device selection when the website is managing the devices | |
| 233 // itself. | |
| 234 if (i->second.disabled) | |
| 235 gtk_widget_set_sensitive(button, FALSE); | |
| 236 | |
| 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); | |
| 241 | |
| 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); | |
| 247 } | |
| 248 | |
| 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); | |
| 253 | |
| 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); | |
| 258 | |
| 259 gtk_box_pack_start(GTK_BOX(bubble_content), table, FALSE, FALSE, 0); | |
| 260 } | |
| 261 | |
| 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); | |
| 267 | |
| 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)), | |
| 278 FALSE, FALSE, 0); | |
| 279 } | |
| 280 gtk_box_pack_start(GTK_BOX(bubble_content), list_content, FALSE, FALSE, | |
| 281 ui::kControlSpacing); | |
| 282 } | |
| 283 | |
| 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) { | |
| 288 custom_link = | |
| 289 theme_provider->BuildChromeLinkButton(content.custom_link.c_str()); | |
| 290 g_signal_connect(custom_link, "clicked", | |
| 291 G_CALLBACK(OnCustomLinkClickedThunk), this); | |
| 292 } else { | |
| 293 custom_link = theme_provider->BuildLabel(content.custom_link.c_str(), | |
| 294 ui::kGdkBlack); | |
| 295 gtk_misc_set_alignment(GTK_MISC(custom_link), 0, 0.5); | |
| 296 } | |
| 297 DCHECK(custom_link); | |
| 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, | |
| 300 FALSE, FALSE, 0); | |
| 301 } | |
| 302 | |
| 303 gtk_box_pack_start(GTK_BOX(bubble_content), gtk_hseparator_new(), | |
| 304 FALSE, FALSE, 0); | |
| 305 | |
| 306 GtkWidget* bottom_box = gtk_hbox_new(FALSE, 0); | |
| 307 | |
| 308 GtkWidget* manage_link = | |
| 309 theme_provider->BuildChromeLinkButton(content.manage_link.c_str()); | |
| 310 g_signal_connect(manage_link, "clicked", G_CALLBACK(OnManageLinkClickedThunk), | |
| 311 this); | |
| 312 gtk_box_pack_start(GTK_BOX(bottom_box), manage_link, FALSE, FALSE, 0); | |
| 313 | |
| 314 GtkWidget* button = | |
| 315 gtk_button_new_with_label(l10n_util::GetStringUTF8(IDS_DONE).c_str()); | |
| 316 g_signal_connect(button, "clicked", G_CALLBACK(OnCloseButtonClickedThunk), | |
| 317 this); | |
| 318 gtk_box_pack_end(GTK_BOX(bottom_box), button, FALSE, FALSE, 0); | |
| 319 | |
| 320 gtk_box_pack_start(GTK_BOX(bubble_content), bottom_box, FALSE, FALSE, 0); | |
| 321 gtk_widget_grab_focus(button); | |
| 322 | |
| 323 bubble_ = BubbleGtk::Show(anchor_, | |
| 324 NULL, | |
| 325 bubble_content, | |
| 326 BubbleGtk::ANCHOR_TOP_RIGHT, | |
| 327 BubbleGtk::MATCH_SYSTEM_THEME | | |
| 328 BubbleGtk::POPUP_WINDOW | | |
| 329 BubbleGtk::GRAB_INPUT, | |
| 330 theme_provider, | |
| 331 this); | |
| 332 } | |
| 333 | |
| 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. | |
| 342 Close(); | |
| 343 } | |
| 344 | |
| 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. | |
| 351 Close(); | |
| 352 } | |
| 353 | |
| 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) { | |
| 358 if (widget == *i) { | |
| 359 content_setting_bubble_model_->OnRadioClicked( | |
| 360 i - radio_group_gtk_.begin()); | |
| 361 return; | |
| 362 } | |
| 363 } | |
| 364 NOTREACHED() << "unknown radio toggled"; | |
| 365 } | |
| 366 | |
| 367 void ContentSettingBubbleGtk::OnCloseButtonClicked(GtkWidget* button) { | |
| 368 content_setting_bubble_model_->OnDoneClicked(); | |
| 369 Close(); | |
| 370 } | |
| 371 | |
| 372 void ContentSettingBubbleGtk::OnCustomLinkClicked(GtkWidget* button) { | |
| 373 content_setting_bubble_model_->OnCustomLinkClicked(); | |
| 374 Close(); | |
| 375 } | |
| 376 | |
| 377 void ContentSettingBubbleGtk::OnManageLinkClicked(GtkWidget* button) { | |
| 378 content_setting_bubble_model_->OnManageLinkClicked(); | |
| 379 Close(); | |
| 380 } | |
| 381 | |
| 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()); | |
| 386 } | |
| 387 | |
| 388 ContentSettingBubbleGtk::MediaMenuGtk::MediaMenuGtk( | |
| 389 content::MediaStreamType type) | |
| 390 : type(type) {} | |
| 391 | |
| 392 ContentSettingBubbleGtk::MediaMenuGtk::~MediaMenuGtk() {} | |
| OLD | NEW |