blob: 744f635147947171d3ca956c0ec71a20fdde7b55 [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ui/views/website_settings/permissions_bubble_view.h"
#include "base/strings/string16.h"
#include "chrome/browser/ui/views/website_settings/permission_selector_view.h"
#include "chrome/browser/ui/views/website_settings/permission_selector_view_observer.h"
#include "chrome/browser/ui/website_settings/permission_bubble_request.h"
#include "grit/generated_resources.h"
#include "grit/ui_resources.h"
#include "net/base/net_util.h"
#include "ui/accessibility/ax_view_state.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/models/combobox_model.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/gfx/text_constants.h"
#include "ui/views/bubble/bubble_delegate.h"
#include "ui/views/controls/button/checkbox.h"
#include "ui/views/controls/button/label_button.h"
#include "ui/views/controls/button/label_button_border.h"
#include "ui/views/controls/button/menu_button.h"
#include "ui/views/controls/button/menu_button_listener.h"
#include "ui/views/controls/combobox/combobox.h"
#include "ui/views/controls/combobox/combobox_listener.h"
#include "ui/views/controls/label.h"
#include "ui/views/controls/menu/menu_runner.h"
#include "ui/views/layout/box_layout.h"
#include "ui/views/layout/grid_layout.h"
namespace {
// Spacing constant for outer margin. This is added to the
// bubble margin itself to equalize the margins at 13px.
const int kBubbleOuterMargin = 5;
// Spacing between major items should be 9px.
const int kItemMajorSpacing = 9;
// Button border size, draws inside the spacing distance.
const int kButtonBorderSize = 2;
// (Square) pixel size of icon.
const int kIconSize = 18;
// Number of pixels to indent the permission request labels.
const int kPermissionIndentSpacing = 12;
} // namespace
// This class is a MenuButton which is given a PermissionMenuModel. It
// shows the current checked item in the menu model, and notifies its listener
// about any updates to the state of the selection.
// TODO: refactor PermissionMenuButton to work like this and re-use?
class PermissionCombobox : public views::MenuButton,
public views::MenuButtonListener {
public:
// Get notifications when the selection changes.
class Listener {
public:
virtual void PermissionSelectionChanged(int index, bool allowed) = 0;
};
PermissionCombobox(Listener* listener,
int index,
const GURL& url,
ContentSetting setting);
virtual ~PermissionCombobox();
int index() const { return index_; }
virtual void GetAccessibleState(ui::AXViewState* state) OVERRIDE;
// MenuButtonListener:
virtual void OnMenuButtonClicked(View* source,
const gfx::Point& point) OVERRIDE;
// Callback when a permission's setting is changed.
void PermissionChanged(const WebsiteSettingsUI::PermissionInfo& permission);
private:
int index_;
Listener* listener_;
scoped_ptr<PermissionMenuModel> model_;
scoped_ptr<views::MenuRunner> menu_runner_;
};
PermissionCombobox::PermissionCombobox(Listener* listener,
int index,
const GURL& url,
ContentSetting setting)
: MenuButton(NULL, base::string16(), this, true),
index_(index),
listener_(listener),
model_(new PermissionMenuModel(
url,
setting,
base::Bind(&PermissionCombobox::PermissionChanged,
base::Unretained(this)))) {
SetText(model_->GetLabelAt(model_->GetIndexOfCommandId(setting)));
SizeToPreferredSize();
}
PermissionCombobox::~PermissionCombobox() {}
void PermissionCombobox::GetAccessibleState(ui::AXViewState* state) {
MenuButton::GetAccessibleState(state);
state->value = GetText();
}
void PermissionCombobox::OnMenuButtonClicked(View* source,
const gfx::Point& point) {
menu_runner_.reset(
new views::MenuRunner(model_.get(), views::MenuRunner::HAS_MNEMONICS));
gfx::Point p(point);
p.Offset(-source->width(), 0);
if (menu_runner_->RunMenuAt(source->GetWidget()->GetTopLevelWidget(),
this,
gfx::Rect(p, gfx::Size()),
views::MENU_ANCHOR_TOPLEFT,
ui::MENU_SOURCE_NONE) ==
views::MenuRunner::MENU_DELETED) {
return;
}
}
void PermissionCombobox::PermissionChanged(
const WebsiteSettingsUI::PermissionInfo& permission) {
SetText(model_->GetLabelAt(model_->GetIndexOfCommandId(permission.setting)));
SizeToPreferredSize();
listener_->PermissionSelectionChanged(
index_, permission.setting == CONTENT_SETTING_ALLOW);
}
// A combobox originating on the Allow button allowing for customization
// of permissions.
class CustomizeAllowComboboxModel : public ui::ComboboxModel {
public:
enum Item {
INDEX_ALLOW = 0,
INDEX_CUSTOMIZE = 1
};
CustomizeAllowComboboxModel() {}
virtual ~CustomizeAllowComboboxModel() {}
virtual int GetItemCount() const OVERRIDE;
virtual base::string16 GetItemAt(int index) OVERRIDE;
virtual int GetDefaultIndex() const OVERRIDE;
};
int CustomizeAllowComboboxModel::GetItemCount() const {
return 2;
}
base::string16 CustomizeAllowComboboxModel::GetItemAt(int index) {
if (index == INDEX_ALLOW)
return l10n_util::GetStringUTF16(IDS_PERMISSION_ALLOW);
else
return l10n_util::GetStringUTF16(IDS_PERMISSION_CUSTOMIZE);
}
int CustomizeAllowComboboxModel::GetDefaultIndex() const {
return INDEX_ALLOW;
}
///////////////////////////////////////////////////////////////////////////////
// View implementation for the permissions bubble.
class PermissionsBubbleDelegateView : public views::BubbleDelegateView,
public views::ButtonListener,
public views::ComboboxListener,
public PermissionCombobox::Listener {
public:
PermissionsBubbleDelegateView(
views::View* anchor,
PermissionBubbleViewViews* owner,
const std::vector<PermissionBubbleRequest*>& requests,
const std::vector<bool>& accept_state,
bool customization_mode);
virtual ~PermissionsBubbleDelegateView();
void Close();
void SizeToContents();
// BubbleDelegateView:
virtual bool ShouldShowCloseButton() const OVERRIDE;
virtual bool ShouldShowWindowTitle() const OVERRIDE;
virtual const gfx::FontList& GetTitleFontList() const OVERRIDE;
virtual base::string16 GetWindowTitle() const OVERRIDE;
virtual void OnWidgetDestroying(views::Widget* widget) OVERRIDE;
// ButtonListener:
virtual void ButtonPressed(views::Button* button,
const ui::Event& event) OVERRIDE;
// ComboboxListener:
virtual void OnPerformAction(views::Combobox* combobox) OVERRIDE;
// PermissionCombobox::Listener:
virtual void PermissionSelectionChanged(int index, bool allowed) OVERRIDE;
private:
PermissionBubbleViewViews* owner_;
views::Button* allow_;
views::Button* deny_;
views::Combobox* allow_combobox_;
base::string16 hostname_;
scoped_ptr<PermissionMenuModel> menu_button_model_;
std::vector<PermissionCombobox*> customize_comboboxes_;
DISALLOW_COPY_AND_ASSIGN(PermissionsBubbleDelegateView);
};
PermissionsBubbleDelegateView::PermissionsBubbleDelegateView(
views::View* anchor,
PermissionBubbleViewViews* owner,
const std::vector<PermissionBubbleRequest*>& requests,
const std::vector<bool>& accept_state,
bool customization_mode)
: views::BubbleDelegateView(anchor, views::BubbleBorder::TOP_LEFT),
owner_(owner),
allow_(NULL),
deny_(NULL),
allow_combobox_(NULL) {
DCHECK(!requests.empty());
RemoveAllChildViews(true);
customize_comboboxes_.clear();
set_close_on_esc(false);
set_close_on_deactivate(false);
SetLayoutManager(new views::BoxLayout(
views::BoxLayout::kVertical, kBubbleOuterMargin, 0, kItemMajorSpacing));
// TODO(gbillock): support other languages than English.
hostname_ = net::FormatUrl(requests[0]->GetRequestingHostname(),
"en",
net::kFormatUrlOmitUsernamePassword |
net::kFormatUrlOmitTrailingSlashOnBareHostname,
net::UnescapeRule::SPACES, NULL, NULL, NULL);
ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
for (size_t index = 0; index < requests.size(); index++) {
DCHECK(index < accept_state.size());
// The row is laid out containing a leading-aligned label area and a
// trailing column which will be filled during customization with a
// combobox.
views::View* row = new views::View();
views::GridLayout* row_layout = new views::GridLayout(row);
row->SetLayoutManager(row_layout);
views::ColumnSet* columns = row_layout->AddColumnSet(0);
columns->AddColumn(views::GridLayout::LEADING, views::GridLayout::FILL,
0, views::GridLayout::USE_PREF, 0, 0);
columns->AddColumn(views::GridLayout::TRAILING, views::GridLayout::FILL,
100, views::GridLayout::USE_PREF, 0, 0);
row_layout->StartRow(0, 0);
views::View* label_container = new views::View();
label_container->SetLayoutManager(
new views::BoxLayout(views::BoxLayout::kHorizontal,
kPermissionIndentSpacing,
0, kBubbleOuterMargin));
views::ImageView* icon = new views::ImageView();
icon->SetImage(bundle.GetImageSkiaNamed(requests.at(index)->GetIconID()));
icon->SetImageSize(gfx::Size(kIconSize, kIconSize));
label_container->AddChildView(icon);
views::Label* label =
new views::Label(requests.at(index)->GetMessageTextFragment());
label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
label_container->AddChildView(label);
row_layout->AddView(label_container);
if (customization_mode) {
PermissionCombobox* combobox = new PermissionCombobox(
this,
index,
requests[index]->GetRequestingHostname(),
accept_state[index] ? CONTENT_SETTING_ALLOW : CONTENT_SETTING_BLOCK);
row_layout->AddView(combobox);
customize_comboboxes_.push_back(combobox);
} else {
row_layout->AddView(new views::View());
}
AddChildView(row);
}
views::View* button_row = new views::View();
views::GridLayout* button_layout = new views::GridLayout(button_row);
views::ColumnSet* columns = button_layout->AddColumnSet(0);
button_row->SetLayoutManager(button_layout);
AddChildView(button_row);
// Customization case: just an "OK" button
if (customization_mode) {
columns->AddColumn(views::GridLayout::TRAILING, views::GridLayout::FILL,
100, views::GridLayout::USE_PREF, 0, 0);
button_layout->StartRow(0, 0);
views::LabelButton* ok_button =
new views::LabelButton(this, l10n_util::GetStringUTF16(IDS_OK));
ok_button->SetStyle(views::Button::STYLE_BUTTON);
button_layout->AddView(ok_button);
allow_ = ok_button;
button_layout->AddPaddingRow(0, kBubbleOuterMargin);
return;
}
// No customization: lay out the Deny/Allow buttons.
columns->AddColumn(views::GridLayout::TRAILING, views::GridLayout::FILL,
100, views::GridLayout::USE_PREF, 0, 0);
columns->AddPaddingColumn(0, kItemMajorSpacing - (2*kButtonBorderSize));
columns->AddColumn(views::GridLayout::TRAILING, views::GridLayout::FILL,
0, views::GridLayout::USE_PREF, 0, 0);
button_layout->StartRow(0, 0);
// Allow button is a regular button when there's only one option, and a
// STYLE_ACTION Combobox when there are more than one option and
// customization is an option.
base::string16 allow_text = l10n_util::GetStringUTF16(IDS_PERMISSION_ALLOW);
if (requests.size() == 1) {
views::LabelButton* allow_button = new views::LabelButton(this, allow_text);
allow_button->SetStyle(views::Button::STYLE_BUTTON);
button_layout->AddView(allow_button);
allow_ = allow_button;
} else {
views::Combobox* allow_combobox = new views::Combobox(
new CustomizeAllowComboboxModel());
allow_combobox->set_listener(this);
allow_combobox->SetStyle(views::Combobox::STYLE_ACTION);
button_layout->AddView(allow_combobox);
allow_combobox_ = allow_combobox;
}
base::string16 deny_text = l10n_util::GetStringUTF16(IDS_PERMISSION_DENY);
views::LabelButton* deny_button = new views::LabelButton(this, deny_text);
deny_button->SetStyle(views::Button::STYLE_BUTTON);
button_layout->AddView(deny_button);
deny_ = deny_button;
button_layout->AddPaddingRow(0, kBubbleOuterMargin);
}
PermissionsBubbleDelegateView::~PermissionsBubbleDelegateView() {
if (owner_)
owner_->Closing();
}
void PermissionsBubbleDelegateView::Close() {
owner_ = NULL;
GetWidget()->Close();
}
bool PermissionsBubbleDelegateView::ShouldShowCloseButton() const {
return true;
}
bool PermissionsBubbleDelegateView::ShouldShowWindowTitle() const {
return true;
}
const gfx::FontList& PermissionsBubbleDelegateView::GetTitleFontList() const {
ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
return rb.GetFontList(ui::ResourceBundle::BaseFont);
}
base::string16 PermissionsBubbleDelegateView::GetWindowTitle() const {
return l10n_util::GetStringFUTF16(IDS_PERMISSIONS_BUBBLE_PROMPT,
hostname_);
}
void PermissionsBubbleDelegateView::SizeToContents() {
BubbleDelegateView::SizeToContents();
}
void PermissionsBubbleDelegateView::OnWidgetDestroying(views::Widget* widget) {
views::BubbleDelegateView::OnWidgetDestroying(widget);
if (owner_) {
owner_->Closing();
owner_ = NULL;
}
}
void PermissionsBubbleDelegateView::ButtonPressed(views::Button* button,
const ui::Event& event) {
if (!owner_)
return;
if (button == allow_)
owner_->Accept();
else if (button == deny_)
owner_->Deny();
}
void PermissionsBubbleDelegateView::PermissionSelectionChanged(
int index, bool allowed) {
owner_->Toggle(index, allowed);
}
void PermissionsBubbleDelegateView::OnPerformAction(
views::Combobox* combobox) {
if (combobox == allow_combobox_) {
if (combobox->selected_index() ==
CustomizeAllowComboboxModel::INDEX_CUSTOMIZE)
owner_->SetCustomizationMode();
else if (combobox->selected_index() ==
CustomizeAllowComboboxModel::INDEX_ALLOW)
owner_->Accept();
}
}
//////////////////////////////////////////////////////////////////////////////
// PermissionBubbleViewViews
PermissionBubbleViewViews::PermissionBubbleViewViews(views::View* anchor_view)
: anchor_view_(anchor_view),
delegate_(NULL),
bubble_delegate_(NULL) {}
PermissionBubbleViewViews::~PermissionBubbleViewViews() {
if (delegate_)
delegate_->SetView(NULL);
}
void PermissionBubbleViewViews::SetDelegate(Delegate* delegate) {
delegate_ = delegate;
}
void PermissionBubbleViewViews::Show(
const std::vector<PermissionBubbleRequest*>& requests,
const std::vector<bool>& values,
bool customization_mode) {
if (bubble_delegate_ != NULL)
bubble_delegate_->Close();
bubble_delegate_ =
new PermissionsBubbleDelegateView(anchor_view_, this,
requests, values, customization_mode);
views::BubbleDelegateView::CreateBubble(bubble_delegate_)->Show();
bubble_delegate_->SizeToContents();
}
bool PermissionBubbleViewViews::CanAcceptRequestUpdate() {
return !(bubble_delegate_ && bubble_delegate_->IsMouseHovered());
}
void PermissionBubbleViewViews::Hide() {
if (bubble_delegate_) {
bubble_delegate_->Close();
bubble_delegate_ = NULL;
}
}
void PermissionBubbleViewViews::Closing() {
if (bubble_delegate_)
bubble_delegate_ = NULL;
if (delegate_)
delegate_->Closing();
}
void PermissionBubbleViewViews::Toggle(int index, bool value) {
if (delegate_)
delegate_->ToggleAccept(index, value);
}
void PermissionBubbleViewViews::Accept() {
if (delegate_)
delegate_->Accept();
}
void PermissionBubbleViewViews::Deny() {
if (delegate_)
delegate_->Deny();
}
void PermissionBubbleViewViews::SetCustomizationMode() {
if (delegate_)
delegate_->SetCustomizationMode();
}