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 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 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 67 ContentSettingBubbleGtk::~ContentSettingBubbleGtk() { 68 } 69 70 void ContentSettingBubbleGtk::Close() { 71 if (info_bubble_) 72 info_bubble_->Close(); 73 } 74 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 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 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 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 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 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 303 void ContentSettingBubbleGtk::OnCloseButtonClicked(GtkWidget *button) { 304 Close(); 305 } 306 307 void ContentSettingBubbleGtk::OnCustomLinkClicked(GtkWidget* button) { 308 content_setting_bubble_model_->OnCustomLinkClicked(); 309 Close(); 310 } 311 312 void ContentSettingBubbleGtk::OnManageLinkClicked(GtkWidget* button) { 313 content_setting_bubble_model_->OnManageLinkClicked(); 314 Close(); 315 } 316