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