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.
5 #include "chrome/browser/ui/views/website_settings/permissions_bubble_view.h"
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"
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;
38 // Spacing between major items should be 9px.
39 const int kItemMajorSpacing = 9;
41 // Button border size, draws inside the spacing distance.
42 const int kButtonBorderSize = 2;
44 // (Square) pixel size of icon.
45 const int kIconSize = 18;
47 // Number of pixels to indent the permission request labels.
48 const int kPermissionIndentSpacing = 12;
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 {
59 // Get notifications when the selection changes.
62 virtual void PermissionSelectionChanged(int index, bool allowed) = 0;
65 PermissionCombobox(Listener* listener,
68 ContentSetting setting);
69 virtual ~PermissionCombobox();
71 int index() const { return index_; }
73 virtual void GetAccessibleState(ui::AXViewState* state) OVERRIDE;
75 // MenuButtonListener:
76 virtual void OnMenuButtonClicked(View* source,
77 const gfx::Point& point) OVERRIDE;
79 // Callback when a permission's setting is changed.
80 void PermissionChanged(const WebsiteSettingsUI::PermissionInfo& permission);
85 scoped_ptr<PermissionMenuModel> model_;
86 scoped_ptr<views::MenuRunner> menu_runner_;
89 PermissionCombobox::PermissionCombobox(Listener* listener,
92 ContentSetting setting)
93 : MenuButton(NULL, base::string16(), this, true),
96 model_(new PermissionMenuModel(
99 base::Bind(&PermissionCombobox::PermissionChanged,
100 base::Unretained(this)))) {
101 SetText(model_->GetLabelAt(model_->GetIndexOfCommandId(setting)));
102 SizeToPreferredSize();
105 PermissionCombobox::~PermissionCombobox() {}
107 void PermissionCombobox::GetAccessibleState(ui::AXViewState* state) {
108 MenuButton::GetAccessibleState(state);
109 state->value = GetText();
112 void PermissionCombobox::OnMenuButtonClicked(View* source,
113 const gfx::Point& point) {
115 new views::MenuRunner(model_.get(), views::MenuRunner::HAS_MNEMONICS));
118 p.Offset(-source->width(), 0);
119 if (menu_runner_->RunMenuAt(source->GetWidget()->GetTopLevelWidget(),
121 gfx::Rect(p, gfx::Size()),
122 views::MENU_ANCHOR_TOPLEFT,
123 ui::MENU_SOURCE_NONE) ==
124 views::MenuRunner::MENU_DELETED) {
129 void PermissionCombobox::PermissionChanged(
130 const WebsiteSettingsUI::PermissionInfo& permission) {
131 SetText(model_->GetLabelAt(model_->GetIndexOfCommandId(permission.setting)));
132 SizeToPreferredSize();
134 listener_->PermissionSelectionChanged(
135 index_, permission.setting == CONTENT_SETTING_ALLOW);
138 // A combobox originating on the Allow button allowing for customization
140 class CustomizeAllowComboboxModel : public ui::ComboboxModel {
147 CustomizeAllowComboboxModel() {}
148 virtual ~CustomizeAllowComboboxModel() {}
150 virtual int GetItemCount() const OVERRIDE;
151 virtual base::string16 GetItemAt(int index) OVERRIDE;
152 virtual int GetDefaultIndex() const OVERRIDE;
155 int CustomizeAllowComboboxModel::GetItemCount() const {
159 base::string16 CustomizeAllowComboboxModel::GetItemAt(int index) {
160 if (index == INDEX_ALLOW)
161 return l10n_util::GetStringUTF16(IDS_PERMISSION_ALLOW);
163 return l10n_util::GetStringUTF16(IDS_PERMISSION_CUSTOMIZE);
166 int CustomizeAllowComboboxModel::GetDefaultIndex() const {
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 {
177 PermissionsBubbleDelegateView(
179 PermissionBubbleViewViews* owner,
180 const std::vector<PermissionBubbleRequest*>& requests,
181 const std::vector<bool>& accept_state,
182 bool customization_mode);
183 virtual ~PermissionsBubbleDelegateView();
186 void SizeToContents();
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;
196 virtual void ButtonPressed(views::Button* button,
197 const ui::Event& event) OVERRIDE;
200 virtual void OnPerformAction(views::Combobox* combobox) OVERRIDE;
202 // PermissionCombobox::Listener:
203 virtual void PermissionSelectionChanged(int index, bool allowed) OVERRIDE;
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_;
214 DISALLOW_COPY_AND_ASSIGN(PermissionsBubbleDelegateView);
217 PermissionsBubbleDelegateView::PermissionsBubbleDelegateView(
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),
227 allow_combobox_(NULL) {
228 DCHECK(!requests.empty());
230 RemoveAllChildViews(true);
231 customize_comboboxes_.clear();
232 set_close_on_esc(false);
233 set_close_on_deactivate(false);
235 SetLayoutManager(new views::BoxLayout(
236 views::BoxLayout::kVertical, kBubbleOuterMargin, 0, kItemMajorSpacing));
238 // TODO(gbillock): support other languages than English.
239 hostname_ = net::FormatUrl(requests[0]->GetRequestingHostname(),
241 net::kFormatUrlOmitUsernamePassword |
242 net::kFormatUrlOmitTrailingSlashOnBareHostname,
243 net::UnescapeRule::SPACES, NULL, NULL, NULL);
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
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);
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);
276 if (customization_mode) {
277 PermissionCombobox* combobox = new PermissionCombobox(
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);
285 row_layout->AddView(new views::View());
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);
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);
308 button_layout->AddPaddingRow(0, kBubbleOuterMargin);
312 // No customization: lay out the Deny/Allow buttons.
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);
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.
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;
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;
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);
346 button_layout->AddPaddingRow(0, kBubbleOuterMargin);
349 PermissionsBubbleDelegateView::~PermissionsBubbleDelegateView() {
354 void PermissionsBubbleDelegateView::Close() {
356 GetWidget()->Close();
359 bool PermissionsBubbleDelegateView::ShouldShowCloseButton() const {
363 bool PermissionsBubbleDelegateView::ShouldShowWindowTitle() const {
367 const gfx::FontList& PermissionsBubbleDelegateView::GetTitleFontList() const {
368 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
369 return rb.GetFontList(ui::ResourceBundle::BaseFont);
372 base::string16 PermissionsBubbleDelegateView::GetWindowTitle() const {
373 return l10n_util::GetStringFUTF16(IDS_PERMISSIONS_BUBBLE_PROMPT,
377 void PermissionsBubbleDelegateView::SizeToContents() {
378 BubbleDelegateView::SizeToContents();
381 void PermissionsBubbleDelegateView::OnWidgetDestroying(views::Widget* widget) {
382 views::BubbleDelegateView::OnWidgetDestroying(widget);
389 void PermissionsBubbleDelegateView::ButtonPressed(views::Button* button,
390 const ui::Event& event) {
394 if (button == allow_)
396 else if (button == deny_)
400 void PermissionsBubbleDelegateView::PermissionSelectionChanged(
401 int index, bool allowed) {
402 owner_->Toggle(index, allowed);
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)
417 //////////////////////////////////////////////////////////////////////////////
418 // PermissionBubbleViewViews
420 PermissionBubbleViewViews::PermissionBubbleViewViews(views::View* anchor_view)
421 : anchor_view_(anchor_view),
423 bubble_delegate_(NULL) {}
425 PermissionBubbleViewViews::~PermissionBubbleViewViews() {
427 delegate_->SetView(NULL);
430 void PermissionBubbleViewViews::SetDelegate(Delegate* delegate) {
431 delegate_ = delegate;
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();
442 new PermissionsBubbleDelegateView(anchor_view_, this,
443 requests, values, customization_mode);
444 views::BubbleDelegateView::CreateBubble(bubble_delegate_)->Show();
445 bubble_delegate_->SizeToContents();
448 bool PermissionBubbleViewViews::CanAcceptRequestUpdate() {
449 return !(bubble_delegate_ && bubble_delegate_->IsMouseHovered());
452 void PermissionBubbleViewViews::Hide() {
453 if (bubble_delegate_) {
454 bubble_delegate_->Close();
455 bubble_delegate_ = NULL;
459 bool PermissionBubbleViewViews::IsVisible() {
460 return bubble_delegate_ != NULL;
463 void PermissionBubbleViewViews::Closing() {
464 if (bubble_delegate_)
465 bubble_delegate_ = NULL;
467 delegate_->Closing();
470 void PermissionBubbleViewViews::Toggle(int index, bool value) {
472 delegate_->ToggleAccept(index, value);
475 void PermissionBubbleViewViews::Accept() {
480 void PermissionBubbleViewViews::Deny() {
485 void PermissionBubbleViewViews::SetCustomizationMode() {
487 delegate_->SetCustomizationMode();