Infobar material design refresh: layout
[chromium-blink-merge.git] / chrome / browser / ui / views / website_settings / permissions_bubble_view.cc
blobf587d5588f333ef6f3223666278d971041c33f5b
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/prefs/pref_service.h"
8 #include "base/strings/string16.h"
9 #include "chrome/browser/profiles/profile.h"
10 #include "chrome/browser/ui/browser.h"
11 #include "chrome/browser/ui/views/exclusive_access_bubble_views.h"
12 #include "chrome/browser/ui/views/frame/browser_view.h"
13 #include "chrome/browser/ui/views/frame/top_container_view.h"
14 #include "chrome/browser/ui/views/location_bar/location_bar_view.h"
15 #include "chrome/browser/ui/views/location_bar/location_icon_view.h"
16 #include "chrome/browser/ui/views/website_settings/permission_selector_view.h"
17 #include "chrome/browser/ui/views/website_settings/permission_selector_view_observer.h"
18 #include "chrome/browser/ui/website_settings/permission_bubble_request.h"
19 #include "chrome/common/pref_names.h"
20 #include "chrome/grit/generated_resources.h"
21 #include "components/url_formatter/url_formatter.h"
22 #include "ui/accessibility/ax_view_state.h"
23 #include "ui/base/l10n/l10n_util.h"
24 #include "ui/base/models/combobox_model.h"
25 #include "ui/base/resource/resource_bundle.h"
26 #include "ui/gfx/paint_vector_icon.h"
27 #include "ui/gfx/text_constants.h"
28 #include "ui/gfx/vector_icons_public.h"
29 #include "ui/native_theme/common_theme.h"
30 #include "ui/native_theme/native_theme.h"
31 #include "ui/views/bubble/bubble_delegate.h"
32 #include "ui/views/bubble/bubble_frame_view.h"
33 #include "ui/views/controls/button/checkbox.h"
34 #include "ui/views/controls/button/label_button.h"
35 #include "ui/views/controls/button/label_button_border.h"
36 #include "ui/views/controls/button/menu_button.h"
37 #include "ui/views/controls/button/menu_button_listener.h"
38 #include "ui/views/controls/combobox/combobox.h"
39 #include "ui/views/controls/combobox/combobox_listener.h"
40 #include "ui/views/controls/label.h"
41 #include "ui/views/controls/menu/menu_runner.h"
42 #include "ui/views/layout/box_layout.h"
43 #include "ui/views/layout/grid_layout.h"
45 namespace {
47 // Spacing constant for outer margin. This is added to the
48 // bubble margin itself to equalize the margins at 13px.
49 const int kBubbleOuterMargin = 5;
51 // Spacing between major items should be 9px.
52 const int kItemMajorSpacing = 9;
54 // Button border size, draws inside the spacing distance.
55 const int kButtonBorderSize = 2;
57 // (Square) pixel size of icon.
58 const int kIconSize = 18;
60 // Number of pixels to indent the permission request labels.
61 const int kPermissionIndentSpacing = 12;
63 } // namespace
65 // This class is a MenuButton which is given a PermissionMenuModel. It
66 // shows the current checked item in the menu model, and notifies its listener
67 // about any updates to the state of the selection.
68 // TODO(gbillock): refactor PermissionMenuButton to work like this and re-use?
69 class PermissionCombobox : public views::MenuButton,
70 public views::MenuButtonListener {
71 public:
72 // Get notifications when the selection changes.
73 class Listener {
74 public:
75 virtual void PermissionSelectionChanged(int index, bool allowed) = 0;
78 PermissionCombobox(Listener* listener,
79 int index,
80 const GURL& url,
81 ContentSetting setting);
82 ~PermissionCombobox() override;
84 int index() const { return index_; }
86 void GetAccessibleState(ui::AXViewState* state) override;
88 // MenuButtonListener:
89 void OnMenuButtonClicked(View* source, const gfx::Point& point) override;
91 // Callback when a permission's setting is changed.
92 void PermissionChanged(const WebsiteSettingsUI::PermissionInfo& permission);
94 private:
95 int index_;
96 Listener* listener_;
97 scoped_ptr<PermissionMenuModel> model_;
98 scoped_ptr<views::MenuRunner> menu_runner_;
101 PermissionCombobox::PermissionCombobox(Listener* listener,
102 int index,
103 const GURL& url,
104 ContentSetting setting)
105 : MenuButton(nullptr, base::string16(), this, true),
106 index_(index),
107 listener_(listener),
108 model_(new PermissionMenuModel(
109 url,
110 setting,
111 base::Bind(&PermissionCombobox::PermissionChanged,
112 base::Unretained(this)))) {
113 SetText(model_->GetLabelAt(model_->GetIndexOfCommandId(setting)));
114 SizeToPreferredSize();
117 PermissionCombobox::~PermissionCombobox() {}
119 void PermissionCombobox::GetAccessibleState(ui::AXViewState* state) {
120 MenuButton::GetAccessibleState(state);
121 state->value = GetText();
124 void PermissionCombobox::OnMenuButtonClicked(View* source,
125 const gfx::Point& point) {
126 menu_runner_.reset(
127 new views::MenuRunner(model_.get(), views::MenuRunner::HAS_MNEMONICS));
129 gfx::Point p(point);
130 p.Offset(-source->width(), 0);
131 if (menu_runner_->RunMenuAt(source->GetWidget()->GetTopLevelWidget(),
132 this,
133 gfx::Rect(p, gfx::Size()),
134 views::MENU_ANCHOR_TOPLEFT,
135 ui::MENU_SOURCE_NONE) ==
136 views::MenuRunner::MENU_DELETED) {
137 return;
141 void PermissionCombobox::PermissionChanged(
142 const WebsiteSettingsUI::PermissionInfo& permission) {
143 SetText(model_->GetLabelAt(model_->GetIndexOfCommandId(permission.setting)));
144 SizeToPreferredSize();
146 listener_->PermissionSelectionChanged(
147 index_, permission.setting == CONTENT_SETTING_ALLOW);
150 ///////////////////////////////////////////////////////////////////////////////
151 // View implementation for the permissions bubble.
152 class PermissionsBubbleDelegateView : public views::BubbleDelegateView,
153 public views::ButtonListener,
154 public PermissionCombobox::Listener {
155 public:
156 PermissionsBubbleDelegateView(
157 views::View* anchor_view,
158 views::BubbleBorder::Arrow anchor_arrow,
159 PermissionBubbleViewViews* owner,
160 const std::string& languages,
161 const std::vector<PermissionBubbleRequest*>& requests,
162 const std::vector<bool>& accept_state);
163 ~PermissionsBubbleDelegateView() override;
165 void Close();
166 void SizeToContents();
168 // BubbleDelegateView:
169 bool ShouldShowCloseButton() const override;
170 bool ShouldShowWindowTitle() const override;
171 const gfx::FontList& GetTitleFontList() const override;
172 base::string16 GetWindowTitle() const override;
173 void OnWidgetDestroying(views::Widget* widget) override;
174 void GetAccessibleState(ui::AXViewState* state) override;
176 // ButtonListener:
177 void ButtonPressed(views::Button* button, const ui::Event& event) override;
179 // PermissionCombobox::Listener:
180 void PermissionSelectionChanged(int index, bool allowed) override;
182 // Updates the anchor's arrow and view. Also repositions the bubble so it's
183 // displayed in the correct location.
184 void UpdateAnchor(views::View* anchor_view,
185 views::BubbleBorder::Arrow anchor_arrow);
187 private:
188 PermissionBubbleViewViews* owner_;
189 views::Button* allow_;
190 views::Button* deny_;
191 base::string16 hostname_;
192 scoped_ptr<PermissionMenuModel> menu_button_model_;
193 std::vector<PermissionCombobox*> customize_comboboxes_;
195 DISALLOW_COPY_AND_ASSIGN(PermissionsBubbleDelegateView);
198 PermissionsBubbleDelegateView::PermissionsBubbleDelegateView(
199 views::View* anchor_view,
200 views::BubbleBorder::Arrow anchor_arrow,
201 PermissionBubbleViewViews* owner,
202 const std::string& languages,
203 const std::vector<PermissionBubbleRequest*>& requests,
204 const std::vector<bool>& accept_state)
205 : views::BubbleDelegateView(anchor_view, anchor_arrow),
206 owner_(owner),
207 allow_(nullptr),
208 deny_(nullptr) {
209 DCHECK(!requests.empty());
211 RemoveAllChildViews(true);
212 customize_comboboxes_.clear();
213 set_close_on_esc(true);
214 set_close_on_deactivate(false);
216 SetLayoutManager(new views::BoxLayout(
217 views::BoxLayout::kVertical, kBubbleOuterMargin, 0, kItemMajorSpacing));
219 hostname_ = url_formatter::FormatUrl(
220 requests[0]->GetRequestingHostname(), languages,
221 url_formatter::kFormatUrlOmitUsernamePassword |
222 url_formatter::kFormatUrlOmitTrailingSlashOnBareHostname,
223 net::UnescapeRule::SPACES, nullptr, nullptr, nullptr);
225 ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
226 for (size_t index = 0; index < requests.size(); index++) {
227 DCHECK(index < accept_state.size());
228 // The row is laid out containing a leading-aligned label area and a
229 // trailing column which will be filled if there are multiple permission
230 // requests.
231 views::View* row = new views::View();
232 views::GridLayout* row_layout = new views::GridLayout(row);
233 row->SetLayoutManager(row_layout);
234 views::ColumnSet* columns = row_layout->AddColumnSet(0);
235 columns->AddColumn(views::GridLayout::LEADING, views::GridLayout::FILL,
236 0, views::GridLayout::USE_PREF, 0, 0);
237 columns->AddColumn(views::GridLayout::TRAILING, views::GridLayout::FILL,
238 100, views::GridLayout::USE_PREF, 0, 0);
239 row_layout->StartRow(0, 0);
241 views::View* label_container = new views::View();
242 label_container->SetLayoutManager(
243 new views::BoxLayout(views::BoxLayout::kHorizontal,
244 kPermissionIndentSpacing,
245 0, kBubbleOuterMargin));
246 views::ImageView* icon = new views::ImageView();
247 gfx::VectorIconId vector_id = requests[index]->GetVectorIconId();
248 if (vector_id != gfx::VectorIconId::VECTOR_ICON_NONE) {
249 SkColor grey;
250 ui::CommonThemeGetSystemColor(ui::NativeTheme::kColorId_ChromeIconGrey,
251 &grey);
252 icon->SetImage(gfx::CreateVectorIcon(vector_id, kIconSize, grey));
253 } else {
254 icon->SetImage(bundle.GetImageSkiaNamed(requests.at(index)->GetIconId()));
255 icon->SetImageSize(gfx::Size(kIconSize, kIconSize));
257 icon->SetTooltipText(base::string16()); // Redundant with the text fragment
258 label_container->AddChildView(icon);
259 views::Label* label =
260 new views::Label(requests.at(index)->GetMessageTextFragment());
261 label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
262 label_container->AddChildView(label);
263 row_layout->AddView(label_container);
265 if (requests.size() > 1) {
266 PermissionCombobox* combobox = new PermissionCombobox(
267 this,
268 index,
269 requests[index]->GetRequestingHostname(),
270 accept_state[index] ? CONTENT_SETTING_ALLOW : CONTENT_SETTING_BLOCK);
271 row_layout->AddView(combobox);
272 customize_comboboxes_.push_back(combobox);
273 } else {
274 row_layout->AddView(new views::View());
277 AddChildView(row);
280 views::View* button_row = new views::View();
281 views::GridLayout* button_layout = new views::GridLayout(button_row);
282 views::ColumnSet* columns = button_layout->AddColumnSet(0);
283 button_row->SetLayoutManager(button_layout);
284 AddChildView(button_row);
286 // For multiple permissions: just an "OK" button.
287 if (requests.size() > 1) {
288 columns->AddColumn(views::GridLayout::TRAILING, views::GridLayout::FILL,
289 100, views::GridLayout::USE_PREF, 0, 0);
290 button_layout->StartRowWithPadding(0, 0, 0, 4);
291 views::LabelButton* ok_button =
292 new views::LabelButton(this, l10n_util::GetStringUTF16(IDS_OK));
293 ok_button->SetStyle(views::Button::STYLE_BUTTON);
294 button_layout->AddView(ok_button);
295 allow_ = ok_button;
297 button_layout->AddPaddingRow(0, kBubbleOuterMargin);
298 return;
301 // For a single permission: lay out the Deny/Allow buttons.
302 columns->AddColumn(views::GridLayout::TRAILING, views::GridLayout::FILL,
303 100, views::GridLayout::USE_PREF, 0, 0);
304 columns->AddPaddingColumn(0, kItemMajorSpacing - (2*kButtonBorderSize));
305 columns->AddColumn(views::GridLayout::TRAILING, views::GridLayout::FILL,
306 0, views::GridLayout::USE_PREF, 0, 0);
307 button_layout->StartRow(0, 0);
309 base::string16 allow_text = l10n_util::GetStringUTF16(IDS_PERMISSION_ALLOW);
310 views::LabelButton* allow_button = new views::LabelButton(this, allow_text);
311 allow_button->SetStyle(views::Button::STYLE_BUTTON);
312 button_layout->AddView(allow_button);
313 allow_ = allow_button;
315 base::string16 deny_text = l10n_util::GetStringUTF16(IDS_PERMISSION_DENY);
316 views::LabelButton* deny_button = new views::LabelButton(this, deny_text);
317 deny_button->SetStyle(views::Button::STYLE_BUTTON);
318 button_layout->AddView(deny_button);
319 deny_ = deny_button;
321 button_layout->AddPaddingRow(0, kBubbleOuterMargin);
324 PermissionsBubbleDelegateView::~PermissionsBubbleDelegateView() {
325 if (owner_)
326 owner_->Closing();
329 void PermissionsBubbleDelegateView::Close() {
330 owner_ = nullptr;
331 GetWidget()->Close();
334 bool PermissionsBubbleDelegateView::ShouldShowCloseButton() const {
335 return true;
338 bool PermissionsBubbleDelegateView::ShouldShowWindowTitle() const {
339 return true;
342 const gfx::FontList& PermissionsBubbleDelegateView::GetTitleFontList() const {
343 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
344 return rb.GetFontList(ui::ResourceBundle::BaseFont);
347 base::string16 PermissionsBubbleDelegateView::GetWindowTitle() const {
348 return l10n_util::GetStringFUTF16(IDS_PERMISSIONS_BUBBLE_PROMPT,
349 hostname_);
352 void PermissionsBubbleDelegateView::SizeToContents() {
353 BubbleDelegateView::SizeToContents();
356 void PermissionsBubbleDelegateView::OnWidgetDestroying(views::Widget* widget) {
357 views::BubbleDelegateView::OnWidgetDestroying(widget);
358 if (owner_) {
359 owner_->Closing();
360 owner_ = nullptr;
364 void PermissionsBubbleDelegateView::GetAccessibleState(ui::AXViewState* state) {
365 views::BubbleDelegateView::GetAccessibleState(state);
366 state->role = ui::AX_ROLE_ALERT_DIALOG;
369 void PermissionsBubbleDelegateView::ButtonPressed(views::Button* button,
370 const ui::Event& event) {
371 if (!owner_)
372 return;
374 if (button == allow_)
375 owner_->Accept();
376 else if (button == deny_)
377 owner_->Deny();
380 void PermissionsBubbleDelegateView::PermissionSelectionChanged(
381 int index, bool allowed) {
382 owner_->Toggle(index, allowed);
385 void PermissionsBubbleDelegateView::UpdateAnchor(
386 views::View* anchor_view,
387 views::BubbleBorder::Arrow anchor_arrow) {
388 if (GetAnchorView() == anchor_view && arrow() == anchor_arrow)
389 return;
391 set_arrow(anchor_arrow);
393 // Update the border in the bubble: will either add or remove the arrow.
394 views::BubbleFrameView* frame =
395 views::BubbleDelegateView::GetBubbleFrameView();
396 views::BubbleBorder::Arrow adjusted_arrow = anchor_arrow;
397 if (base::i18n::IsRTL())
398 adjusted_arrow = views::BubbleBorder::horizontal_mirror(adjusted_arrow);
399 frame->SetBubbleBorder(scoped_ptr<views::BubbleBorder>(
400 new views::BubbleBorder(adjusted_arrow, shadow(), color())));
402 // Reposition the bubble based on the updated arrow and view.
403 SetAnchorView(anchor_view);
406 //////////////////////////////////////////////////////////////////////////////
407 // PermissionBubbleViewViews
409 PermissionBubbleViewViews::PermissionBubbleViewViews(Browser* browser)
410 : browser_(browser),
411 delegate_(nullptr),
412 bubble_delegate_(nullptr) {
413 DCHECK(browser);
416 PermissionBubbleViewViews::~PermissionBubbleViewViews() {
419 // static
420 scoped_ptr<PermissionBubbleView> PermissionBubbleView::Create(
421 Browser* browser) {
422 return make_scoped_ptr(new PermissionBubbleViewViews(browser));
425 views::View* PermissionBubbleViewViews::GetAnchorView() {
426 BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser_);
428 if (browser_->SupportsWindowFeature(Browser::FEATURE_LOCATIONBAR))
429 return browser_view->GetLocationBarView()->location_icon_view();
431 if (browser_view->IsFullscreenBubbleVisible())
432 return browser_view->exclusive_access_bubble()->GetView();
434 return browser_view->top_container();
437 views::BubbleBorder::Arrow PermissionBubbleViewViews::GetAnchorArrow() {
438 if (browser_->SupportsWindowFeature(Browser::FEATURE_LOCATIONBAR))
439 return views::BubbleBorder::TOP_LEFT;
440 return views::BubbleBorder::NONE;
443 void PermissionBubbleViewViews::SetDelegate(Delegate* delegate) {
444 delegate_ = delegate;
447 void PermissionBubbleViewViews::Show(
448 const std::vector<PermissionBubbleRequest*>& requests,
449 const std::vector<bool>& values) {
450 if (bubble_delegate_)
451 bubble_delegate_->Close();
453 bubble_delegate_ =
454 new PermissionsBubbleDelegateView(
455 GetAnchorView(), GetAnchorArrow(), this,
456 browser_->profile()->GetPrefs()->GetString(prefs::kAcceptLanguages),
457 requests, values);
459 // Set |parent_window| because some valid anchors can become hidden.
460 views::Widget* widget = views::Widget::GetWidgetForNativeWindow(
461 browser_->window()->GetNativeWindow());
462 bubble_delegate_->set_parent_window(widget->GetNativeView());
464 views::BubbleDelegateView::CreateBubble(bubble_delegate_)->Show();
465 bubble_delegate_->SizeToContents();
468 bool PermissionBubbleViewViews::CanAcceptRequestUpdate() {
469 return !(bubble_delegate_ && bubble_delegate_->IsMouseHovered());
472 void PermissionBubbleViewViews::Hide() {
473 if (bubble_delegate_) {
474 bubble_delegate_->Close();
475 bubble_delegate_ = nullptr;
479 bool PermissionBubbleViewViews::IsVisible() {
480 return bubble_delegate_ != nullptr;
483 void PermissionBubbleViewViews::UpdateAnchorPosition() {
484 if (IsVisible())
485 bubble_delegate_->UpdateAnchor(GetAnchorView(), GetAnchorArrow());
488 gfx::NativeWindow PermissionBubbleViewViews::GetNativeWindow() {
489 if (bubble_delegate_ && bubble_delegate_->GetWidget())
490 return bubble_delegate_->GetWidget()->GetNativeWindow();
491 return nullptr;
494 void PermissionBubbleViewViews::Closing() {
495 if (bubble_delegate_)
496 bubble_delegate_ = nullptr;
497 if (delegate_)
498 delegate_->Closing();
501 void PermissionBubbleViewViews::Toggle(int index, bool value) {
502 if (delegate_)
503 delegate_->ToggleAccept(index, value);
506 void PermissionBubbleViewViews::Accept() {
507 if (delegate_)
508 delegate_->Accept();
511 void PermissionBubbleViewViews::Deny() {
512 if (delegate_)
513 delegate_->Deny();