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/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"
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;
37 // Spacing between major items should be 10px.
38 const int kItemMajorSpacing
= 10;
40 // Button border size, draws inside the spacing distance.
41 const int kButtonBorderSize
= 2;
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
{
52 // Get notifications when the selection changes.
55 virtual void PermissionSelectionChanged(int index
, bool allowed
) = 0;
58 PermissionCombobox(Listener
* listener
,
61 ContentSetting setting
);
62 virtual ~PermissionCombobox();
64 int index() const { return index_
; }
66 virtual void GetAccessibleState(ui::AXViewState
* state
) OVERRIDE
;
68 // MenuButtonListener:
69 virtual void OnMenuButtonClicked(View
* source
,
70 const gfx::Point
& point
) OVERRIDE
;
72 // Callback when a permission's setting is changed.
73 void PermissionChanged(const WebsiteSettingsUI::PermissionInfo
& permission
);
78 scoped_ptr
<PermissionMenuModel
> model_
;
79 scoped_ptr
<views::MenuRunner
> menu_runner_
;
82 PermissionCombobox::PermissionCombobox(Listener
* listener
,
85 ContentSetting setting
)
86 : MenuButton(NULL
, base::string16(), this, true),
89 model_(new PermissionMenuModel(
92 base::Bind(&PermissionCombobox::PermissionChanged
,
93 base::Unretained(this)))) {
94 SetText(model_
->GetLabelAt(model_
->GetIndexOfCommandId(setting
)));
95 SizeToPreferredSize();
98 PermissionCombobox::~PermissionCombobox() {}
100 void PermissionCombobox::GetAccessibleState(ui::AXViewState
* state
) {
101 MenuButton::GetAccessibleState(state
);
102 state
->value
= text();
105 void PermissionCombobox::OnMenuButtonClicked(View
* source
,
106 const gfx::Point
& point
) {
107 menu_runner_
.reset(new views::MenuRunner(model_
.get()));
110 p
.Offset(-source
->width(), 0);
111 if (menu_runner_
->RunMenuAt(source
->GetWidget()->GetTopLevelWidget(),
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
) {
122 void PermissionCombobox::PermissionChanged(
123 const WebsiteSettingsUI::PermissionInfo
& permission
) {
124 SetText(model_
->GetLabelAt(model_
->GetIndexOfCommandId(permission
.setting
)));
125 SizeToPreferredSize();
127 listener_
->PermissionSelectionChanged(
128 index_
, permission
.setting
== CONTENT_SETTING_ALLOW
);
131 // A combobox originating on the Allow button allowing for customization
133 class CustomizeAllowComboboxModel
: public ui::ComboboxModel
{
140 CustomizeAllowComboboxModel() {}
141 virtual ~CustomizeAllowComboboxModel() {}
143 virtual int GetItemCount() const OVERRIDE
;
144 virtual base::string16
GetItemAt(int index
) OVERRIDE
;
145 virtual int GetDefaultIndex() const OVERRIDE
;
148 int CustomizeAllowComboboxModel::GetItemCount() const {
152 base::string16
CustomizeAllowComboboxModel::GetItemAt(int index
) {
153 if (index
== INDEX_ALLOW
)
154 return l10n_util::GetStringUTF16(IDS_PERMISSION_ALLOW
);
156 return l10n_util::GetStringUTF16(IDS_PERMISSION_CUSTOMIZE
);
159 int CustomizeAllowComboboxModel::GetDefaultIndex() const {
163 ///////////////////////////////////////////////////////////////////////////////
164 // View implementation for the permissions bubble.
166 class PermissionsBubbleDelegateView
: public views::BubbleDelegateView
,
167 public views::ButtonListener
,
168 public views::ComboboxListener
,
169 public PermissionCombobox::Listener
{
171 PermissionsBubbleDelegateView(
173 PermissionBubbleViewViews
* owner
,
174 const std::vector
<PermissionBubbleRequest
*>& requests
,
175 const std::vector
<bool>& accept_state
,
176 bool customization_mode
);
177 virtual ~PermissionsBubbleDelegateView();
180 void SizeToContents();
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
;
189 virtual void ButtonPressed(views::Button
* button
,
190 const ui::Event
& event
) OVERRIDE
;
193 virtual void OnPerformAction(views::Combobox
* combobox
) OVERRIDE
;
195 // PermissionCombobox::Listener:
196 virtual void PermissionSelectionChanged(int index
, bool allowed
) OVERRIDE
;
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_
;
208 DISALLOW_COPY_AND_ASSIGN(PermissionsBubbleDelegateView
);
211 PermissionsBubbleDelegateView::PermissionsBubbleDelegateView(
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
),
221 allow_combobox_(NULL
) {
222 DCHECK(!requests
.empty());
224 RemoveAllChildViews(true);
225 customize_comboboxes_
.clear();
226 set_close_on_esc(false);
227 set_close_on_deactivate(false);
229 SetLayoutManager(new views::BoxLayout(
230 views::BoxLayout::kVertical
, kBubbleOuterMargin
, 0, kItemMajorSpacing
));
232 ui::ResourceBundle
& rb
= ui::ResourceBundle::GetSharedInstance();
234 // TODO(gbillock): account for different requests from different hosts.
235 hostname_
= requests
[0]->GetRequestingHostname().host();
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
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);
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
);
266 if (customization_mode
) {
267 PermissionCombobox
* combobox
= new PermissionCombobox(
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
);
275 row_layout
->AddView(new views::View());
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
);
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
);
298 button_layout
->AddPaddingRow(0, kBubbleOuterMargin
);
302 // No customization: lay out the Deny/Allow buttons.
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);
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.
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
;
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
;
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
);
338 button_layout
->AddPaddingRow(0, kBubbleOuterMargin
);
341 PermissionsBubbleDelegateView::~PermissionsBubbleDelegateView() {
346 void PermissionsBubbleDelegateView::Close() {
348 GetWidget()->Close();
351 bool PermissionsBubbleDelegateView::ShouldShowCloseButton() const {
355 bool PermissionsBubbleDelegateView::ShouldShowWindowTitle() const {
359 base::string16
PermissionsBubbleDelegateView::GetWindowTitle() const {
360 if (!title_
.empty()) {
364 return l10n_util::GetStringFUTF16(IDS_PERMISSIONS_BUBBLE_PROMPT
,
365 base::UTF8ToUTF16(hostname_
));
368 void PermissionsBubbleDelegateView::SizeToContents() {
369 BubbleDelegateView::SizeToContents();
372 void PermissionsBubbleDelegateView::OnWidgetDestroying(views::Widget
* widget
) {
373 views::BubbleDelegateView::OnWidgetDestroying(widget
);
380 void PermissionsBubbleDelegateView::ButtonPressed(views::Button
* button
,
381 const ui::Event
& event
) {
385 if (button
== allow_
)
387 else if (button
== deny_
)
391 void PermissionsBubbleDelegateView::PermissionSelectionChanged(
392 int index
, bool allowed
) {
393 owner_
->Toggle(index
, allowed
);
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
)
408 //////////////////////////////////////////////////////////////////////////////
409 // PermissionBubbleViewViews
411 PermissionBubbleViewViews::PermissionBubbleViewViews(views::View
* anchor_view
)
412 : anchor_view_(anchor_view
),
414 bubble_delegate_(NULL
) {}
416 PermissionBubbleViewViews::~PermissionBubbleViewViews() {
418 delegate_
->SetView(NULL
);
421 void PermissionBubbleViewViews::SetDelegate(Delegate
* delegate
) {
422 delegate_
= delegate
;
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();
433 new PermissionsBubbleDelegateView(anchor_view_
, this,
434 requests
, values
, customization_mode
);
435 views::BubbleDelegateView::CreateBubble(bubble_delegate_
)->Show();
436 bubble_delegate_
->SizeToContents();
439 bool PermissionBubbleViewViews::CanAcceptRequestUpdate() {
440 return !(bubble_delegate_
&& bubble_delegate_
->IsMouseHovered());
443 void PermissionBubbleViewViews::Hide() {
444 if (bubble_delegate_
) {
445 bubble_delegate_
->Close();
446 bubble_delegate_
= NULL
;
450 void PermissionBubbleViewViews::Closing() {
451 if (bubble_delegate_
)
452 bubble_delegate_
= NULL
;
454 delegate_
->Closing();
457 void PermissionBubbleViewViews::Toggle(int index
, bool value
) {
459 delegate_
->ToggleAccept(index
, value
);
462 void PermissionBubbleViewViews::Accept() {
467 void PermissionBubbleViewViews::Deny() {
472 void PermissionBubbleViewViews::SetCustomizationMode() {
474 delegate_
->SetCustomizationMode();