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