Home | History | Annotate | Download | only in gtk
      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