permissions_bubble_view.cc revision e5d81f57cb97b3b6b7fccc9c5610d21eb81db09d
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/website_settings/permission_bubble_request.h"
9#include "grit/generated_resources.h"
10#include "grit/ui_resources.h"
11#include "ui/base/l10n/l10n_util.h"
12#include "ui/base/models/combobox_model.h"
13#include "ui/base/resource/resource_bundle.h"
14#include "ui/gfx/text_constants.h"
15#include "ui/views/bubble/bubble_delegate.h"
16#include "ui/views/controls/button/checkbox.h"
17#include "ui/views/controls/button/label_button.h"
18#include "ui/views/controls/button/label_button_border.h"
19#include "ui/views/controls/button/menu_button.h"
20#include "ui/views/controls/button/menu_button_listener.h"
21#include "ui/views/controls/combobox/combobox.h"
22#include "ui/views/controls/combobox/combobox_listener.h"
23#include "ui/views/controls/label.h"
24#include "ui/views/layout/box_layout.h"
25#include "ui/views/layout/grid_layout.h"
26
27namespace {
28
29// Spacing constant for outer margin. This is added to the
30// bubble margin itself to equalize the margins at 20px.
31const int kBubbleOuterMargin = 12;
32
33// Spacing between major items should be 10px.
34const int kItemMajorSpacing = 10;
35
36// Button border size, draws inside the spacing distance.
37const int kButtonBorderSize = 2;
38
39}  // namespace
40
41// Model for an Allow/Block combobox control. Each combobox has a separate
42// model, which remembers the index of the permission it is associated with.
43class PermissionComboboxModel : public ui::ComboboxModel {
44 public:
45  enum Item {
46    STATE_ALLOW = 0,
47    STATE_BLOCK = 1
48  };
49
50  PermissionComboboxModel(int index, Item default_item);
51  virtual ~PermissionComboboxModel() {}
52
53  virtual int GetItemCount() const OVERRIDE;
54  virtual base::string16 GetItemAt(int index) OVERRIDE;
55  virtual int GetDefaultIndex() const OVERRIDE;
56
57  // Return the item index this combobox is the model for.
58  int index() { return index_; }
59
60 private:
61  int index_;
62  int default_item_;
63};
64
65PermissionComboboxModel::PermissionComboboxModel(int index, Item default_item)
66    : index_(index), default_item_(default_item) {}
67
68int PermissionComboboxModel::GetItemCount() const {
69  return 2;  // 'allow' and 'block'
70}
71
72base::string16 PermissionComboboxModel::GetItemAt(int index) {
73  if (index == 0)
74    return l10n_util::GetStringUTF16(IDS_PERMISSION_ALLOW);
75  else
76    return l10n_util::GetStringUTF16(IDS_PERMISSION_DENY);
77}
78
79int PermissionComboboxModel::GetDefaultIndex() const {
80  return default_item_;
81}
82
83class CustomizeDenyComboboxModel : public ui::ComboboxModel {
84 public:
85  enum Item {
86    INDEX_DENY = 0,
87    INDEX_CUSTOMIZE = 1
88  };
89
90  CustomizeDenyComboboxModel() {}
91  virtual ~CustomizeDenyComboboxModel() {}
92
93  virtual int GetItemCount() const OVERRIDE;
94  virtual base::string16 GetItemAt(int index) OVERRIDE;
95  virtual int GetDefaultIndex() const OVERRIDE;
96};
97
98int CustomizeDenyComboboxModel::GetItemCount() const {
99  return 2;
100}
101
102base::string16 CustomizeDenyComboboxModel::GetItemAt(int index) {
103  if (index == INDEX_DENY)
104    return l10n_util::GetStringUTF16(IDS_PERMISSION_DENY);
105  else
106    return l10n_util::GetStringUTF16(IDS_PERMISSION_CUSTOMIZE);
107}
108
109int CustomizeDenyComboboxModel::GetDefaultIndex() const {
110  return INDEX_DENY;
111}
112
113///////////////////////////////////////////////////////////////////////////////
114// View implementation for the permissions bubble.
115
116class PermissionsBubbleDelegateView : public views::BubbleDelegateView,
117                                      public views::ButtonListener,
118                                      public views::ComboboxListener {
119 public:
120  PermissionsBubbleDelegateView(
121      views::View* anchor,
122      PermissionBubbleViewViews* owner,
123      const std::vector<PermissionBubbleRequest*>& requests,
124      const std::vector<bool>& accept_state,
125      bool customization_mode);
126  virtual ~PermissionsBubbleDelegateView();
127
128  void Close();
129  void SizeToContents();
130
131  // BubbleDelegateView:
132  virtual bool ShouldShowCloseButton() const OVERRIDE;
133  virtual bool ShouldShowWindowTitle() const OVERRIDE;
134  virtual base::string16 GetWindowTitle() const OVERRIDE;
135  virtual void OnWidgetDestroying(views::Widget* widget) OVERRIDE;
136
137  // ButtonListener:
138  virtual void ButtonPressed(views::Button* button,
139                             const ui::Event& event) OVERRIDE;
140
141  // ComboboxListener:
142  virtual void OnPerformAction(views::Combobox* combobox) OVERRIDE;
143
144 private:
145  PermissionBubbleViewViews* owner_;
146  views::Button* allow_;
147  views::Button* deny_;
148  views::Combobox* deny_combobox_;
149  base::string16 title_;
150  std::string hostname_;
151
152  DISALLOW_COPY_AND_ASSIGN(PermissionsBubbleDelegateView);
153};
154
155PermissionsBubbleDelegateView::PermissionsBubbleDelegateView(
156    views::View* anchor,
157    PermissionBubbleViewViews* owner,
158    const std::vector<PermissionBubbleRequest*>& requests,
159    const std::vector<bool>& accept_state,
160    bool customization_mode)
161    : views::BubbleDelegateView(anchor, views::BubbleBorder::TOP_LEFT),
162      owner_(owner),
163      allow_(NULL),
164      deny_(NULL),
165      deny_combobox_(NULL) {
166  DCHECK(!requests.empty());
167
168  RemoveAllChildViews(true);
169  set_close_on_esc(false);
170  set_close_on_deactivate(false);
171
172  SetLayoutManager(new views::BoxLayout(
173      views::BoxLayout::kVertical, kBubbleOuterMargin, 0, kItemMajorSpacing));
174
175  ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
176
177  // TODO(gbillock): account for different requests from different hosts.
178  hostname_ = requests[0]->GetRequestingHostname().host();
179
180  ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
181  for (size_t index = 0; index < requests.size(); index++) {
182    DCHECK(index < accept_state.size());
183    // The row is laid out containing a leading-aligned label area and a
184    // trailing column which will be filled during customization with a
185    // combobox.
186    views::View* row = new views::View();
187    views::GridLayout* row_layout = new views::GridLayout(row);
188    row->SetLayoutManager(row_layout);
189    views::ColumnSet* columns = row_layout->AddColumnSet(0);
190    columns->AddColumn(views::GridLayout::LEADING, views::GridLayout::FILL,
191                       0, views::GridLayout::USE_PREF, 0, 0);
192    columns->AddColumn(views::GridLayout::TRAILING, views::GridLayout::FILL,
193                       100, views::GridLayout::USE_PREF, 0, 0);
194    row_layout->StartRow(0, 0);
195
196    views::View* label_container = new views::View();
197    label_container->SetLayoutManager(
198        new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0, 5));
199    views::ImageView* icon = new views::ImageView();
200    icon->SetImage(bundle.GetImageSkiaNamed(requests.at(index)->GetIconID()));
201    label_container->AddChildView(icon);
202    views::Label* label =
203        new views::Label(requests.at(index)->GetMessageTextFragment());
204    label->SetFontList(rb.GetFontList(ui::ResourceBundle::MediumFont));
205    label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
206    label_container->AddChildView(label);
207    row_layout->AddView(label_container);
208
209    if (customization_mode) {
210      views::Combobox* combobox = new views::Combobox(
211          new PermissionComboboxModel(index,
212              accept_state[index] ? PermissionComboboxModel::STATE_ALLOW
213                                  : PermissionComboboxModel::STATE_BLOCK));
214      combobox->set_listener(this);
215      row_layout->AddView(combobox);
216    } else {
217      row_layout->AddView(new views::View());
218    }
219
220    AddChildView(row);
221  }
222
223  views::View* button_row = new views::View();
224  views::GridLayout* button_layout = new views::GridLayout(button_row);
225  views::ColumnSet* columns = button_layout->AddColumnSet(0);
226  button_row->SetLayoutManager(button_layout);
227  AddChildView(button_row);
228
229  // Customization case: just an "OK" button
230  if (customization_mode) {
231    columns->AddColumn(views::GridLayout::TRAILING, views::GridLayout::FILL,
232                       100, views::GridLayout::USE_PREF, 0, 0);
233    button_layout->StartRow(0, 0);
234    views::LabelButton* ok_button =
235        new views::LabelButton(this, l10n_util::GetStringUTF16(IDS_OK));
236    ok_button->SetStyle(views::Button::STYLE_BUTTON);
237    button_layout->AddView(ok_button);
238    allow_ = ok_button;
239
240    button_layout->AddPaddingRow(0, kBubbleOuterMargin);
241    return;
242  }
243
244  // No customization: lay out the Deny/Allow buttons.
245
246  columns->AddColumn(views::GridLayout::TRAILING, views::GridLayout::FILL,
247                     100, views::GridLayout::USE_PREF, 0, 0);
248  columns->AddPaddingColumn(0, kItemMajorSpacing - (2*kButtonBorderSize));
249  columns->AddColumn(views::GridLayout::TRAILING, views::GridLayout::FILL,
250                     0, views::GridLayout::USE_PREF, 0, 0);
251  button_layout->StartRow(0, 0);
252
253  base::string16 allow_text = l10n_util::GetStringUTF16(IDS_PERMISSION_ALLOW);
254  views::LabelButton* allow_button = new views::LabelButton(this, allow_text);
255  allow_button->SetStyle(views::Button::STYLE_BUTTON);
256  allow_button->SetFontList(rb.GetFontList(ui::ResourceBundle::MediumFont));
257  button_layout->AddView(allow_button);
258  allow_ = allow_button;
259
260  // Deny button is a regular button when there's only one option, and a
261  // STYLE_ACTION Combobox when there are more than one option and
262  // customization is an option.
263  base::string16 deny_text = l10n_util::GetStringUTF16(IDS_PERMISSION_DENY);
264  if (requests.size() == 1) {
265    views::LabelButton* deny_button = new views::LabelButton(this, deny_text);
266    deny_button->SetStyle(views::Button::STYLE_BUTTON);
267    deny_button->SetFontList(rb.GetFontList(ui::ResourceBundle::MediumFont));
268    button_layout->AddView(deny_button);
269    deny_ = deny_button;
270  } else {
271    views::Combobox* deny_combobox = new views::Combobox(
272        new CustomizeDenyComboboxModel());
273    deny_combobox->set_listener(this);
274    deny_combobox->SetStyle(views::Combobox::STYLE_ACTION);
275    button_layout->AddView(deny_combobox);
276    deny_combobox_ = deny_combobox;
277  }
278
279  button_layout->AddPaddingRow(0, kBubbleOuterMargin);
280}
281
282PermissionsBubbleDelegateView::~PermissionsBubbleDelegateView() {
283  if (owner_)
284    owner_->Closing();
285}
286
287void PermissionsBubbleDelegateView::Close() {
288  owner_ = NULL;
289  GetWidget()->Close();
290}
291
292bool PermissionsBubbleDelegateView::ShouldShowCloseButton() const {
293  return true;
294}
295
296bool PermissionsBubbleDelegateView::ShouldShowWindowTitle() const {
297  return true;
298}
299
300base::string16 PermissionsBubbleDelegateView::GetWindowTitle() const {
301  if (!title_.empty()) {
302    return title_;
303  }
304
305  return l10n_util::GetStringFUTF16(IDS_PERMISSIONS_BUBBLE_PROMPT,
306                                    base::UTF8ToUTF16(hostname_));
307}
308
309void PermissionsBubbleDelegateView::SizeToContents() {
310  BubbleDelegateView::SizeToContents();
311}
312
313void PermissionsBubbleDelegateView::OnWidgetDestroying(views::Widget* widget) {
314  views::BubbleDelegateView::OnWidgetDestroying(widget);
315  if (owner_) {
316    owner_->Closing();
317    owner_ = NULL;
318  }
319}
320
321void PermissionsBubbleDelegateView::ButtonPressed(views::Button* button,
322                                                  const ui::Event& event) {
323  if (!owner_)
324    return;
325
326  if (button == allow_)
327    owner_->Accept();
328  else if (button == deny_)
329    owner_->Deny();
330}
331
332void PermissionsBubbleDelegateView::OnPerformAction(
333    views::Combobox* combobox) {
334  if (combobox == deny_combobox_) {
335    if (combobox->selected_index() ==
336        CustomizeDenyComboboxModel::INDEX_CUSTOMIZE)
337      owner_->SetCustomizationMode();
338    else if (combobox->selected_index() ==
339             CustomizeDenyComboboxModel::INDEX_DENY)
340      owner_->Deny();
341    return;
342  }
343
344  PermissionComboboxModel* model =
345      static_cast<PermissionComboboxModel*>(combobox->model());
346  owner_->Toggle(model->index(), combobox->selected_index() == 0);
347}
348
349//////////////////////////////////////////////////////////////////////////////
350// PermissionBubbleViewViews
351
352PermissionBubbleViewViews::PermissionBubbleViewViews(views::View* anchor_view)
353    : anchor_view_(anchor_view),
354      delegate_(NULL),
355      bubble_delegate_(NULL) {}
356
357PermissionBubbleViewViews::~PermissionBubbleViewViews() {
358  if (delegate_)
359    delegate_->SetView(NULL);
360}
361
362void PermissionBubbleViewViews::SetDelegate(Delegate* delegate) {
363  delegate_ = delegate;
364}
365
366void PermissionBubbleViewViews::Show(
367    const std::vector<PermissionBubbleRequest*>& requests,
368    const std::vector<bool>& values,
369    bool customization_mode) {
370  if (bubble_delegate_ != NULL)
371    bubble_delegate_->Close();
372
373  bubble_delegate_ =
374      new PermissionsBubbleDelegateView(anchor_view_, this,
375                                        requests, values, customization_mode);
376  views::BubbleDelegateView::CreateBubble(bubble_delegate_)->Show();
377  bubble_delegate_->SizeToContents();
378}
379
380bool PermissionBubbleViewViews::CanAcceptRequestUpdate() {
381  return !(bubble_delegate_ && bubble_delegate_->IsMouseHovered());
382}
383
384void PermissionBubbleViewViews::Hide() {
385  if (bubble_delegate_) {
386    bubble_delegate_->Close();
387    bubble_delegate_ = NULL;
388  }
389}
390
391void PermissionBubbleViewViews::Closing() {
392  if (bubble_delegate_)
393    bubble_delegate_ = NULL;
394  if (delegate_)
395    delegate_->Closing();
396}
397
398void PermissionBubbleViewViews::Toggle(int index, bool value) {
399  if (delegate_)
400    delegate_->ToggleAccept(index, value);
401}
402
403void PermissionBubbleViewViews::Accept() {
404  if (delegate_)
405    delegate_->Accept();
406}
407
408void PermissionBubbleViewViews::Deny() {
409  if (delegate_)
410    delegate_->Deny();
411}
412
413void PermissionBubbleViewViews::SetCustomizationMode() {
414  if (delegate_)
415    delegate_->SetCustomizationMode();
416}
417