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 "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"
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;
37 // Spacing between major items should be 9px.
38 const int kItemMajorSpacing = 9;
40 // Button border size, draws inside the spacing distance.
41 const int kButtonBorderSize = 2;
43 // (Square) pixel size of icon.
44 const int kIconSize = 18;
46 // Number of pixels to indent the permission request labels.
47 const int kPermissionIndentSpacing = 12;
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 {
58 // Get notifications when the selection changes.
61 virtual void PermissionSelectionChanged(int index, bool allowed) = 0;
64 PermissionCombobox(Listener* listener,
67 ContentSetting setting);
68 virtual ~PermissionCombobox();
70 int index() const { return index_; }
72 virtual void GetAccessibleState(ui::AXViewState* state) OVERRIDE;
74 // MenuButtonListener:
75 virtual void OnMenuButtonClicked(View* source,
76 const gfx::Point& point) OVERRIDE;
78 // Callback when a permission's setting is changed.
79 void PermissionChanged(const WebsiteSettingsUI::PermissionInfo& permission);
84 scoped_ptr<PermissionMenuModel> model_;
85 scoped_ptr<views::MenuRunner> menu_runner_;
88 PermissionCombobox::PermissionCombobox(Listener* listener,
91 ContentSetting setting)
92 : MenuButton(NULL, base::string16(), this, true),
95 model_(new PermissionMenuModel(
98 base::Bind(&PermissionCombobox::PermissionChanged,
99 base::Unretained(this)))) {
100 SetText(model_->GetLabelAt(model_->GetIndexOfCommandId(setting)));
101 SizeToPreferredSize();
104 PermissionCombobox::~PermissionCombobox() {}
106 void PermissionCombobox::GetAccessibleState(ui::AXViewState* state) {
107 MenuButton::GetAccessibleState(state);
108 state->value = GetText();
111 void PermissionCombobox::OnMenuButtonClicked(View* source,
112 const gfx::Point& point) {
114 new views::MenuRunner(model_.get(), views::MenuRunner::HAS_MNEMONICS));
117 p.Offset(-source->width(), 0);
118 if (menu_runner_->RunMenuAt(source->GetWidget()->GetTopLevelWidget(),
120 gfx::Rect(p, gfx::Size()),
121 views::MENU_ANCHOR_TOPLEFT,
122 ui::MENU_SOURCE_NONE) ==
123 views::MenuRunner::MENU_DELETED) {
128 void PermissionCombobox::PermissionChanged(
129 const WebsiteSettingsUI::PermissionInfo& permission) {
130 SetText(model_->GetLabelAt(model_->GetIndexOfCommandId(permission.setting)));
131 SizeToPreferredSize();
133 listener_->PermissionSelectionChanged(
134 index_, permission.setting == CONTENT_SETTING_ALLOW);
137 // A combobox originating on the Allow button allowing for customization
139 class CustomizeAllowComboboxModel : public ui::ComboboxModel {
146 CustomizeAllowComboboxModel() {}
147 virtual ~CustomizeAllowComboboxModel() {}
149 virtual int GetItemCount() const OVERRIDE;
150 virtual base::string16 GetItemAt(int index) OVERRIDE;
151 virtual int GetDefaultIndex() const OVERRIDE;
154 int CustomizeAllowComboboxModel::GetItemCount() const {
158 base::string16 CustomizeAllowComboboxModel::GetItemAt(int index) {
159 if (index == INDEX_ALLOW)
160 return l10n_util::GetStringUTF16(IDS_PERMISSION_ALLOW);
162 return l10n_util::GetStringUTF16(IDS_PERMISSION_CUSTOMIZE);
165 int CustomizeAllowComboboxModel::GetDefaultIndex() const {
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 {
176 PermissionsBubbleDelegateView(
178 PermissionBubbleViewViews* owner,
179 const std::vector<PermissionBubbleRequest*>& requests,
180 const std::vector<bool>& accept_state,
181 bool customization_mode);
182 virtual ~PermissionsBubbleDelegateView();
185 void SizeToContents();
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;
195 virtual void ButtonPressed(views::Button* button,
196 const ui::Event& event) OVERRIDE;
199 virtual void OnPerformAction(views::Combobox* combobox) OVERRIDE;
201 // PermissionCombobox::Listener:
202 virtual void PermissionSelectionChanged(int index, bool allowed) OVERRIDE;
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_;
213 DISALLOW_COPY_AND_ASSIGN(PermissionsBubbleDelegateView);
216 PermissionsBubbleDelegateView::PermissionsBubbleDelegateView(
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),
226 allow_combobox_(NULL) {
227 DCHECK(!requests.empty());
229 RemoveAllChildViews(true);
230 customize_comboboxes_.clear();
231 set_close_on_esc(false);
232 set_close_on_deactivate(false);
234 SetLayoutManager(new views::BoxLayout(
235 views::BoxLayout::kVertical, kBubbleOuterMargin, 0, kItemMajorSpacing));
237 // TODO(gbillock): support other languages than English.
238 hostname_ = net::FormatUrl(requests[0]->GetRequestingHostname(),
240 net::kFormatUrlOmitUsernamePassword |
241 net::kFormatUrlOmitTrailingSlashOnBareHostname,
242 net::UnescapeRule::SPACES, NULL, NULL, NULL);
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
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);
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);
275 if (customization_mode) {
276 PermissionCombobox* combobox = new PermissionCombobox(
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);
284 row_layout->AddView(new views::View());
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);
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);
307 button_layout->AddPaddingRow(0, kBubbleOuterMargin);
311 // No customization: lay out the Deny/Allow buttons.
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);
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.
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;
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;
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);
345 button_layout->AddPaddingRow(0, kBubbleOuterMargin);
348 PermissionsBubbleDelegateView::~PermissionsBubbleDelegateView() {
353 void PermissionsBubbleDelegateView::Close() {
355 GetWidget()->Close();
358 bool PermissionsBubbleDelegateView::ShouldShowCloseButton() const {
362 bool PermissionsBubbleDelegateView::ShouldShowWindowTitle() const {
366 const gfx::FontList& PermissionsBubbleDelegateView::GetTitleFontList() const {
367 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
368 return rb.GetFontList(ui::ResourceBundle::BaseFont);
371 base::string16 PermissionsBubbleDelegateView::GetWindowTitle() const {
372 return l10n_util::GetStringFUTF16(IDS_PERMISSIONS_BUBBLE_PROMPT,
376 void PermissionsBubbleDelegateView::SizeToContents() {
377 BubbleDelegateView::SizeToContents();
380 void PermissionsBubbleDelegateView::OnWidgetDestroying(views::Widget* widget) {
381 views::BubbleDelegateView::OnWidgetDestroying(widget);
388 void PermissionsBubbleDelegateView::ButtonPressed(views::Button* button,
389 const ui::Event& event) {
393 if (button == allow_)
395 else if (button == deny_)
399 void PermissionsBubbleDelegateView::PermissionSelectionChanged(
400 int index, bool allowed) {
401 owner_->Toggle(index, allowed);
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)
416 //////////////////////////////////////////////////////////////////////////////
417 // PermissionBubbleViewViews
419 PermissionBubbleViewViews::PermissionBubbleViewViews(views::View* anchor_view)
420 : anchor_view_(anchor_view),
422 bubble_delegate_(NULL) {}
424 PermissionBubbleViewViews::~PermissionBubbleViewViews() {
426 delegate_->SetView(NULL);
429 void PermissionBubbleViewViews::SetDelegate(Delegate* delegate) {
430 delegate_ = delegate;
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();
441 new PermissionsBubbleDelegateView(anchor_view_, this,
442 requests, values, customization_mode);
443 views::BubbleDelegateView::CreateBubble(bubble_delegate_)->Show();
444 bubble_delegate_->SizeToContents();
447 bool PermissionBubbleViewViews::CanAcceptRequestUpdate() {
448 return !(bubble_delegate_ && bubble_delegate_->IsMouseHovered());
451 void PermissionBubbleViewViews::Hide() {
452 if (bubble_delegate_) {
453 bubble_delegate_->Close();
454 bubble_delegate_ = NULL;
458 bool PermissionBubbleViewViews::IsVisible() {
459 return bubble_delegate_ != NULL;
462 void PermissionBubbleViewViews::Closing() {
463 if (bubble_delegate_)
464 bubble_delegate_ = NULL;
466 delegate_->Closing();
469 void PermissionBubbleViewViews::Toggle(int index, bool value) {
471 delegate_->ToggleAccept(index, value);
474 void PermissionBubbleViewViews::Accept() {
479 void PermissionBubbleViewViews::Deny() {
484 void PermissionBubbleViewViews::SetCustomizationMode() {
486 delegate_->SetCustomizationMode();