Home | History | Annotate | Download | only in website_settings
      1 // Copyright 2014 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/views/website_settings/permissions_bubble_view.h"
      6 
      7 #include "base/strings/string16.h"
      8 #include "chrome/browser/ui/views/website_settings/permission_selector_view.h"
      9 #include "chrome/browser/ui/views/website_settings/permission_selector_view_observer.h"
     10 #include "chrome/browser/ui/website_settings/permission_bubble_request.h"
     11 #include "chrome/grit/generated_resources.h"
     12 #include "net/base/net_util.h"
     13 #include "ui/accessibility/ax_view_state.h"
     14 #include "ui/base/l10n/l10n_util.h"
     15 #include "ui/base/models/combobox_model.h"
     16 #include "ui/base/resource/resource_bundle.h"
     17 #include "ui/gfx/text_constants.h"
     18 #include "ui/views/bubble/bubble_delegate.h"
     19 #include "ui/views/controls/button/checkbox.h"
     20 #include "ui/views/controls/button/label_button.h"
     21 #include "ui/views/controls/button/label_button_border.h"
     22 #include "ui/views/controls/button/menu_button.h"
     23 #include "ui/views/controls/button/menu_button_listener.h"
     24 #include "ui/views/controls/combobox/combobox.h"
     25 #include "ui/views/controls/combobox/combobox_listener.h"
     26 #include "ui/views/controls/label.h"
     27 #include "ui/views/controls/menu/menu_runner.h"
     28 #include "ui/views/layout/box_layout.h"
     29 #include "ui/views/layout/grid_layout.h"
     30 
     31 namespace {
     32 
     33 // Spacing constant for outer margin. This is added to the
     34 // bubble margin itself to equalize the margins at 13px.
     35 const int kBubbleOuterMargin = 5;
     36 
     37 // Spacing between major items should be 9px.
     38 const int kItemMajorSpacing = 9;
     39 
     40 // Button border size, draws inside the spacing distance.
     41 const int kButtonBorderSize = 2;
     42 
     43 // (Square) pixel size of icon.
     44 const int kIconSize = 18;
     45 
     46 // Number of pixels to indent the permission request labels.
     47 const int kPermissionIndentSpacing = 12;
     48 
     49 }  // namespace
     50 
     51 // This class is a MenuButton which is given a PermissionMenuModel. It
     52 // shows the current checked item in the menu model, and notifies its listener
     53 // about any updates to the state of the selection.
     54 // TODO: refactor PermissionMenuButton to work like this and re-use?
     55 class PermissionCombobox : public views::MenuButton,
     56                            public views::MenuButtonListener {
     57  public:
     58   // Get notifications when the selection changes.
     59   class Listener {
     60    public:
     61     virtual void PermissionSelectionChanged(int index, bool allowed) = 0;
     62   };
     63 
     64   PermissionCombobox(Listener* listener,
     65                      int index,
     66                      const GURL& url,
     67                      ContentSetting setting);
     68   virtual ~PermissionCombobox();
     69 
     70   int index() const { return index_; }
     71 
     72   virtual void GetAccessibleState(ui::AXViewState* state) OVERRIDE;
     73 
     74   // MenuButtonListener:
     75   virtual void OnMenuButtonClicked(View* source,
     76                                    const gfx::Point& point) OVERRIDE;
     77 
     78   // Callback when a permission's setting is changed.
     79   void PermissionChanged(const WebsiteSettingsUI::PermissionInfo& permission);
     80 
     81  private:
     82   int index_;
     83   Listener* listener_;
     84   scoped_ptr<PermissionMenuModel> model_;
     85   scoped_ptr<views::MenuRunner> menu_runner_;
     86 };
     87 
     88 PermissionCombobox::PermissionCombobox(Listener* listener,
     89                                        int index,
     90                                        const GURL& url,
     91                                        ContentSetting setting)
     92     : MenuButton(NULL, base::string16(), this, true),
     93       index_(index),
     94       listener_(listener),
     95       model_(new PermissionMenuModel(
     96           url,
     97           setting,
     98           base::Bind(&PermissionCombobox::PermissionChanged,
     99                      base::Unretained(this)))) {
    100   SetText(model_->GetLabelAt(model_->GetIndexOfCommandId(setting)));
    101   SizeToPreferredSize();
    102 }
    103 
    104 PermissionCombobox::~PermissionCombobox() {}
    105 
    106 void PermissionCombobox::GetAccessibleState(ui::AXViewState* state) {
    107   MenuButton::GetAccessibleState(state);
    108   state->value = GetText();
    109 }
    110 
    111 void PermissionCombobox::OnMenuButtonClicked(View* source,
    112                                              const gfx::Point& point) {
    113   menu_runner_.reset(
    114       new views::MenuRunner(model_.get(), views::MenuRunner::HAS_MNEMONICS));
    115 
    116   gfx::Point p(point);
    117   p.Offset(-source->width(), 0);
    118   if (menu_runner_->RunMenuAt(source->GetWidget()->GetTopLevelWidget(),
    119                               this,
    120                               gfx::Rect(p, gfx::Size()),
    121                               views::MENU_ANCHOR_TOPLEFT,
    122                               ui::MENU_SOURCE_NONE) ==
    123       views::MenuRunner::MENU_DELETED) {
    124     return;
    125   }
    126 }
    127 
    128 void PermissionCombobox::PermissionChanged(
    129     const WebsiteSettingsUI::PermissionInfo& permission) {
    130   SetText(model_->GetLabelAt(model_->GetIndexOfCommandId(permission.setting)));
    131   SizeToPreferredSize();
    132 
    133   listener_->PermissionSelectionChanged(
    134       index_, permission.setting == CONTENT_SETTING_ALLOW);
    135 }
    136 
    137 // A combobox originating on the Allow button allowing for customization
    138 // of permissions.
    139 class CustomizeAllowComboboxModel : public ui::ComboboxModel {
    140  public:
    141   enum Item {
    142     INDEX_ALLOW = 0,
    143     INDEX_CUSTOMIZE = 1
    144   };
    145 
    146   CustomizeAllowComboboxModel() {}
    147   virtual ~CustomizeAllowComboboxModel() {}
    148 
    149   virtual int GetItemCount() const OVERRIDE;
    150   virtual base::string16 GetItemAt(int index) OVERRIDE;
    151   virtual int GetDefaultIndex() const OVERRIDE;
    152 };
    153 
    154 int CustomizeAllowComboboxModel::GetItemCount() const {
    155   return 2;
    156 }
    157 
    158 base::string16 CustomizeAllowComboboxModel::GetItemAt(int index) {
    159   if (index == INDEX_ALLOW)
    160     return l10n_util::GetStringUTF16(IDS_PERMISSION_ALLOW);
    161   else
    162     return l10n_util::GetStringUTF16(IDS_PERMISSION_CUSTOMIZE);
    163 }
    164 
    165 int CustomizeAllowComboboxModel::GetDefaultIndex() const {
    166   return INDEX_ALLOW;
    167 }
    168 
    169 ///////////////////////////////////////////////////////////////////////////////
    170 // View implementation for the permissions bubble.
    171 class PermissionsBubbleDelegateView : public views::BubbleDelegateView,
    172                                       public views::ButtonListener,
    173                                       public views::ComboboxListener,
    174                                       public PermissionCombobox::Listener {
    175  public:
    176   PermissionsBubbleDelegateView(
    177       views::View* anchor,
    178       PermissionBubbleViewViews* owner,
    179       const std::vector<PermissionBubbleRequest*>& requests,
    180       const std::vector<bool>& accept_state,
    181       bool customization_mode);
    182   virtual ~PermissionsBubbleDelegateView();
    183 
    184   void Close();
    185   void SizeToContents();
    186 
    187   // BubbleDelegateView:
    188   virtual bool ShouldShowCloseButton() const OVERRIDE;
    189   virtual bool ShouldShowWindowTitle() const OVERRIDE;
    190   virtual const gfx::FontList& GetTitleFontList() const OVERRIDE;
    191   virtual base::string16 GetWindowTitle() const OVERRIDE;
    192   virtual void OnWidgetDestroying(views::Widget* widget) OVERRIDE;
    193 
    194   // ButtonListener:
    195   virtual void ButtonPressed(views::Button* button,
    196                              const ui::Event& event) OVERRIDE;
    197 
    198   // ComboboxListener:
    199   virtual void OnPerformAction(views::Combobox* combobox) OVERRIDE;
    200 
    201   // PermissionCombobox::Listener:
    202   virtual void PermissionSelectionChanged(int index, bool allowed) OVERRIDE;
    203 
    204  private:
    205   PermissionBubbleViewViews* owner_;
    206   views::Button* allow_;
    207   views::Button* deny_;
    208   views::Combobox* allow_combobox_;
    209   base::string16 hostname_;
    210   scoped_ptr<PermissionMenuModel> menu_button_model_;
    211   std::vector<PermissionCombobox*> customize_comboboxes_;
    212 
    213   DISALLOW_COPY_AND_ASSIGN(PermissionsBubbleDelegateView);
    214 };
    215 
    216 PermissionsBubbleDelegateView::PermissionsBubbleDelegateView(
    217     views::View* anchor,
    218     PermissionBubbleViewViews* owner,
    219     const std::vector<PermissionBubbleRequest*>& requests,
    220     const std::vector<bool>& accept_state,
    221     bool customization_mode)
    222     : views::BubbleDelegateView(anchor, views::BubbleBorder::TOP_LEFT),
    223       owner_(owner),
    224       allow_(NULL),
    225       deny_(NULL),
    226       allow_combobox_(NULL) {
    227   DCHECK(!requests.empty());
    228 
    229   RemoveAllChildViews(true);
    230   customize_comboboxes_.clear();
    231   set_close_on_esc(false);
    232   set_close_on_deactivate(false);
    233 
    234   SetLayoutManager(new views::BoxLayout(
    235       views::BoxLayout::kVertical, kBubbleOuterMargin, 0, kItemMajorSpacing));
    236 
    237   // TODO(gbillock): support other languages than English.
    238   hostname_ = net::FormatUrl(requests[0]->GetRequestingHostname(),
    239                              "en",
    240                              net::kFormatUrlOmitUsernamePassword |
    241                              net::kFormatUrlOmitTrailingSlashOnBareHostname,
    242                              net::UnescapeRule::SPACES, NULL, NULL, NULL);
    243 
    244   ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
    245   for (size_t index = 0; index < requests.size(); index++) {
    246     DCHECK(index < accept_state.size());
    247     // The row is laid out containing a leading-aligned label area and a
    248     // trailing column which will be filled during customization with a
    249     // combobox.
    250     views::View* row = new views::View();
    251     views::GridLayout* row_layout = new views::GridLayout(row);
    252     row->SetLayoutManager(row_layout);
    253     views::ColumnSet* columns = row_layout->AddColumnSet(0);
    254     columns->AddColumn(views::GridLayout::LEADING, views::GridLayout::FILL,
    255                        0, views::GridLayout::USE_PREF, 0, 0);
    256     columns->AddColumn(views::GridLayout::TRAILING, views::GridLayout::FILL,
    257                        100, views::GridLayout::USE_PREF, 0, 0);
    258     row_layout->StartRow(0, 0);
    259 
    260     views::View* label_container = new views::View();
    261     label_container->SetLayoutManager(
    262         new views::BoxLayout(views::BoxLayout::kHorizontal,
    263                              kPermissionIndentSpacing,
    264                              0, kBubbleOuterMargin));
    265     views::ImageView* icon = new views::ImageView();
    266     icon->SetImage(bundle.GetImageSkiaNamed(requests.at(index)->GetIconID()));
    267     icon->SetImageSize(gfx::Size(kIconSize, kIconSize));
    268     label_container->AddChildView(icon);
    269     views::Label* label =
    270         new views::Label(requests.at(index)->GetMessageTextFragment());
    271     label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
    272     label_container->AddChildView(label);
    273     row_layout->AddView(label_container);
    274 
    275     if (customization_mode) {
    276       PermissionCombobox* combobox = new PermissionCombobox(
    277           this,
    278           index,
    279           requests[index]->GetRequestingHostname(),
    280           accept_state[index] ? CONTENT_SETTING_ALLOW : CONTENT_SETTING_BLOCK);
    281       row_layout->AddView(combobox);
    282       customize_comboboxes_.push_back(combobox);
    283     } else {
    284       row_layout->AddView(new views::View());
    285     }
    286 
    287     AddChildView(row);
    288   }
    289 
    290   views::View* button_row = new views::View();
    291   views::GridLayout* button_layout = new views::GridLayout(button_row);
    292   views::ColumnSet* columns = button_layout->AddColumnSet(0);
    293   button_row->SetLayoutManager(button_layout);
    294   AddChildView(button_row);
    295 
    296   // Customization case: just an "OK" button
    297   if (customization_mode) {
    298     columns->AddColumn(views::GridLayout::TRAILING, views::GridLayout::FILL,
    299                        100, views::GridLayout::USE_PREF, 0, 0);
    300     button_layout->StartRow(0, 0);
    301     views::LabelButton* ok_button =
    302         new views::LabelButton(this, l10n_util::GetStringUTF16(IDS_OK));
    303     ok_button->SetStyle(views::Button::STYLE_BUTTON);
    304     button_layout->AddView(ok_button);
    305     allow_ = ok_button;
    306 
    307     button_layout->AddPaddingRow(0, kBubbleOuterMargin);
    308     return;
    309   }
    310 
    311   // No customization: lay out the Deny/Allow buttons.
    312 
    313   columns->AddColumn(views::GridLayout::TRAILING, views::GridLayout::FILL,
    314                      100, views::GridLayout::USE_PREF, 0, 0);
    315   columns->AddPaddingColumn(0, kItemMajorSpacing - (2*kButtonBorderSize));
    316   columns->AddColumn(views::GridLayout::TRAILING, views::GridLayout::FILL,
    317                      0, views::GridLayout::USE_PREF, 0, 0);
    318   button_layout->StartRow(0, 0);
    319 
    320   // Allow button is a regular button when there's only one option, and a
    321   // STYLE_ACTION Combobox when there are more than one option and
    322   // customization is an option.
    323 
    324   base::string16 allow_text = l10n_util::GetStringUTF16(IDS_PERMISSION_ALLOW);
    325   if (requests.size() == 1) {
    326     views::LabelButton* allow_button = new views::LabelButton(this, allow_text);
    327     allow_button->SetStyle(views::Button::STYLE_BUTTON);
    328     button_layout->AddView(allow_button);
    329     allow_ = allow_button;
    330   } else {
    331     views::Combobox* allow_combobox = new views::Combobox(
    332         new CustomizeAllowComboboxModel());
    333     allow_combobox->set_listener(this);
    334     allow_combobox->SetStyle(views::Combobox::STYLE_ACTION);
    335     button_layout->AddView(allow_combobox);
    336     allow_combobox_ = allow_combobox;
    337   }
    338 
    339   base::string16 deny_text = l10n_util::GetStringUTF16(IDS_PERMISSION_DENY);
    340   views::LabelButton* deny_button = new views::LabelButton(this, deny_text);
    341   deny_button->SetStyle(views::Button::STYLE_BUTTON);
    342   button_layout->AddView(deny_button);
    343   deny_ = deny_button;
    344 
    345   button_layout->AddPaddingRow(0, kBubbleOuterMargin);
    346 }
    347 
    348 PermissionsBubbleDelegateView::~PermissionsBubbleDelegateView() {
    349   if (owner_)
    350     owner_->Closing();
    351 }
    352 
    353 void PermissionsBubbleDelegateView::Close() {
    354   owner_ = NULL;
    355   GetWidget()->Close();
    356 }
    357 
    358 bool PermissionsBubbleDelegateView::ShouldShowCloseButton() const {
    359   return true;
    360 }
    361 
    362 bool PermissionsBubbleDelegateView::ShouldShowWindowTitle() const {
    363   return true;
    364 }
    365 
    366 const gfx::FontList& PermissionsBubbleDelegateView::GetTitleFontList() const {
    367   ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
    368   return rb.GetFontList(ui::ResourceBundle::BaseFont);
    369 }
    370 
    371 base::string16 PermissionsBubbleDelegateView::GetWindowTitle() const {
    372   return l10n_util::GetStringFUTF16(IDS_PERMISSIONS_BUBBLE_PROMPT,
    373                                     hostname_);
    374 }
    375 
    376 void PermissionsBubbleDelegateView::SizeToContents() {
    377   BubbleDelegateView::SizeToContents();
    378 }
    379 
    380 void PermissionsBubbleDelegateView::OnWidgetDestroying(views::Widget* widget) {
    381   views::BubbleDelegateView::OnWidgetDestroying(widget);
    382   if (owner_) {
    383     owner_->Closing();
    384     owner_ = NULL;
    385   }
    386 }
    387 
    388 void PermissionsBubbleDelegateView::ButtonPressed(views::Button* button,
    389                                                   const ui::Event& event) {
    390   if (!owner_)
    391     return;
    392 
    393   if (button == allow_)
    394     owner_->Accept();
    395   else if (button == deny_)
    396     owner_->Deny();
    397 }
    398 
    399 void PermissionsBubbleDelegateView::PermissionSelectionChanged(
    400     int index, bool allowed) {
    401   owner_->Toggle(index, allowed);
    402 }
    403 
    404 void PermissionsBubbleDelegateView::OnPerformAction(
    405     views::Combobox* combobox) {
    406   if (combobox == allow_combobox_) {
    407     if (combobox->selected_index() ==
    408         CustomizeAllowComboboxModel::INDEX_CUSTOMIZE)
    409       owner_->SetCustomizationMode();
    410     else if (combobox->selected_index() ==
    411              CustomizeAllowComboboxModel::INDEX_ALLOW)
    412       owner_->Accept();
    413   }
    414 }
    415 
    416 //////////////////////////////////////////////////////////////////////////////
    417 // PermissionBubbleViewViews
    418 
    419 PermissionBubbleViewViews::PermissionBubbleViewViews(views::View* anchor_view)
    420     : anchor_view_(anchor_view),
    421       delegate_(NULL),
    422       bubble_delegate_(NULL) {}
    423 
    424 PermissionBubbleViewViews::~PermissionBubbleViewViews() {
    425   if (delegate_)
    426     delegate_->SetView(NULL);
    427 }
    428 
    429 void PermissionBubbleViewViews::SetDelegate(Delegate* delegate) {
    430   delegate_ = delegate;
    431 }
    432 
    433 void PermissionBubbleViewViews::Show(
    434     const std::vector<PermissionBubbleRequest*>& requests,
    435     const std::vector<bool>& values,
    436     bool customization_mode) {
    437   if (bubble_delegate_ != NULL)
    438     bubble_delegate_->Close();
    439 
    440   bubble_delegate_ =
    441       new PermissionsBubbleDelegateView(anchor_view_, this,
    442                                         requests, values, customization_mode);
    443   views::BubbleDelegateView::CreateBubble(bubble_delegate_)->Show();
    444   bubble_delegate_->SizeToContents();
    445 }
    446 
    447 bool PermissionBubbleViewViews::CanAcceptRequestUpdate() {
    448   return !(bubble_delegate_ && bubble_delegate_->IsMouseHovered());
    449 }
    450 
    451 void PermissionBubbleViewViews::Hide() {
    452   if (bubble_delegate_) {
    453     bubble_delegate_->Close();
    454     bubble_delegate_ = NULL;
    455   }
    456 }
    457 
    458 bool PermissionBubbleViewViews::IsVisible() {
    459   return bubble_delegate_ != NULL;
    460 }
    461 
    462 void PermissionBubbleViewViews::Closing() {
    463   if (bubble_delegate_)
    464     bubble_delegate_ = NULL;
    465   if (delegate_)
    466     delegate_->Closing();
    467 }
    468 
    469 void PermissionBubbleViewViews::Toggle(int index, bool value) {
    470   if (delegate_)
    471     delegate_->ToggleAccept(index, value);
    472 }
    473 
    474 void PermissionBubbleViewViews::Accept() {
    475   if (delegate_)
    476     delegate_->Accept();
    477 }
    478 
    479 void PermissionBubbleViewViews::Deny() {
    480   if (delegate_)
    481     delegate_->Deny();
    482 }
    483 
    484 void PermissionBubbleViewViews::SetCustomizationMode() {
    485   if (delegate_)
    486     delegate_->SetCustomizationMode();
    487 }
    488