permissions_bubble_view.cc revision 1320f92c476a1ad9d19dba2a48c72b75566198e9
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. 4 5#include "chrome/browser/ui/views/website_settings/permissions_bubble_view.h" 6 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" 30 31namespace { 32 33// Spacing constant for outer margin. This is added to the 34// bubble margin itself to equalize the margins at 13px. 35const int kBubbleOuterMargin = 5; 36 37// Spacing between major items should be 9px. 38const int kItemMajorSpacing = 9; 39 40// Button border size, draws inside the spacing distance. 41const int kButtonBorderSize = 2; 42 43// (Square) pixel size of icon. 44const int kIconSize = 18; 45 46// Number of pixels to indent the permission request labels. 47const int kPermissionIndentSpacing = 12; 48 49} // namespace 50 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? 55class PermissionCombobox : public views::MenuButton, 56 public views::MenuButtonListener { 57 public: 58 // Get notifications when the selection changes. 59 class Listener { 60 public: 61 virtual void PermissionSelectionChanged(int index, bool allowed) = 0; 62 }; 63 64 PermissionCombobox(Listener* listener, 65 int index, 66 const GURL& url, 67 ContentSetting setting); 68 virtual ~PermissionCombobox(); 69 70 int index() const { return index_; } 71 72 virtual void GetAccessibleState(ui::AXViewState* state) OVERRIDE; 73 74 // MenuButtonListener: 75 virtual void OnMenuButtonClicked(View* source, 76 const gfx::Point& point) OVERRIDE; 77 78 // Callback when a permission's setting is changed. 79 void PermissionChanged(const WebsiteSettingsUI::PermissionInfo& permission); 80 81 private: 82 int index_; 83 Listener* listener_; 84 scoped_ptr<PermissionMenuModel> model_; 85 scoped_ptr<views::MenuRunner> menu_runner_; 86}; 87 88PermissionCombobox::PermissionCombobox(Listener* listener, 89 int index, 90 const GURL& url, 91 ContentSetting setting) 92 : MenuButton(NULL, base::string16(), this, true), 93 index_(index), 94 listener_(listener), 95 model_(new PermissionMenuModel( 96 url, 97 setting, 98 base::Bind(&PermissionCombobox::PermissionChanged, 99 base::Unretained(this)))) { 100 SetText(model_->GetLabelAt(model_->GetIndexOfCommandId(setting))); 101 SizeToPreferredSize(); 102} 103 104PermissionCombobox::~PermissionCombobox() {} 105 106void PermissionCombobox::GetAccessibleState(ui::AXViewState* state) { 107 MenuButton::GetAccessibleState(state); 108 state->value = GetText(); 109} 110 111void PermissionCombobox::OnMenuButtonClicked(View* source, 112 const gfx::Point& point) { 113 menu_runner_.reset( 114 new views::MenuRunner(model_.get(), views::MenuRunner::HAS_MNEMONICS)); 115 116 gfx::Point p(point); 117 p.Offset(-source->width(), 0); 118 if (menu_runner_->RunMenuAt(source->GetWidget()->GetTopLevelWidget(), 119 this, 120 gfx::Rect(p, gfx::Size()), 121 views::MENU_ANCHOR_TOPLEFT, 122 ui::MENU_SOURCE_NONE) == 123 views::MenuRunner::MENU_DELETED) { 124 return; 125 } 126} 127 128void PermissionCombobox::PermissionChanged( 129 const WebsiteSettingsUI::PermissionInfo& permission) { 130 SetText(model_->GetLabelAt(model_->GetIndexOfCommandId(permission.setting))); 131 SizeToPreferredSize(); 132 133 listener_->PermissionSelectionChanged( 134 index_, permission.setting == CONTENT_SETTING_ALLOW); 135} 136 137// A combobox originating on the Allow button allowing for customization 138// of permissions. 139class CustomizeAllowComboboxModel : public ui::ComboboxModel { 140 public: 141 enum Item { 142 INDEX_ALLOW = 0, 143 INDEX_CUSTOMIZE = 1 144 }; 145 146 CustomizeAllowComboboxModel() {} 147 virtual ~CustomizeAllowComboboxModel() {} 148 149 virtual int GetItemCount() const OVERRIDE; 150 virtual base::string16 GetItemAt(int index) OVERRIDE; 151 virtual int GetDefaultIndex() const OVERRIDE; 152}; 153 154int CustomizeAllowComboboxModel::GetItemCount() const { 155 return 2; 156} 157 158base::string16 CustomizeAllowComboboxModel::GetItemAt(int index) { 159 if (index == INDEX_ALLOW) 160 return l10n_util::GetStringUTF16(IDS_PERMISSION_ALLOW); 161 else 162 return l10n_util::GetStringUTF16(IDS_PERMISSION_CUSTOMIZE); 163} 164 165int CustomizeAllowComboboxModel::GetDefaultIndex() const { 166 return INDEX_ALLOW; 167} 168 169/////////////////////////////////////////////////////////////////////////////// 170// View implementation for the permissions bubble. 171class PermissionsBubbleDelegateView : public views::BubbleDelegateView, 172 public views::ButtonListener, 173 public views::ComboboxListener, 174 public PermissionCombobox::Listener { 175 public: 176 PermissionsBubbleDelegateView( 177 views::View* anchor, 178 PermissionBubbleViewViews* owner, 179 const std::vector<PermissionBubbleRequest*>& requests, 180 const std::vector<bool>& accept_state, 181 bool customization_mode); 182 virtual ~PermissionsBubbleDelegateView(); 183 184 void Close(); 185 void SizeToContents(); 186 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; 193 194 // ButtonListener: 195 virtual void ButtonPressed(views::Button* button, 196 const ui::Event& event) OVERRIDE; 197 198 // ComboboxListener: 199 virtual void OnPerformAction(views::Combobox* combobox) OVERRIDE; 200 201 // PermissionCombobox::Listener: 202 virtual void PermissionSelectionChanged(int index, bool allowed) OVERRIDE; 203 204 private: 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_; 212 213 DISALLOW_COPY_AND_ASSIGN(PermissionsBubbleDelegateView); 214}; 215 216PermissionsBubbleDelegateView::PermissionsBubbleDelegateView( 217 views::View* anchor, 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), 223 owner_(owner), 224 allow_(NULL), 225 deny_(NULL), 226 allow_combobox_(NULL) { 227 DCHECK(!requests.empty()); 228 229 RemoveAllChildViews(true); 230 customize_comboboxes_.clear(); 231 set_close_on_esc(false); 232 set_close_on_deactivate(false); 233 234 SetLayoutManager(new views::BoxLayout( 235 views::BoxLayout::kVertical, kBubbleOuterMargin, 0, kItemMajorSpacing)); 236 237 // TODO(gbillock): support other languages than English. 238 hostname_ = net::FormatUrl(requests[0]->GetRequestingHostname(), 239 "en", 240 net::kFormatUrlOmitUsernamePassword | 241 net::kFormatUrlOmitTrailingSlashOnBareHostname, 242 net::UnescapeRule::SPACES, NULL, NULL, NULL); 243 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 249 // combobox. 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); 259 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); 274 275 if (customization_mode) { 276 PermissionCombobox* combobox = new PermissionCombobox( 277 this, 278 index, 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); 283 } else { 284 row_layout->AddView(new views::View()); 285 } 286 287 AddChildView(row); 288 } 289 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); 295 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); 305 allow_ = ok_button; 306 307 button_layout->AddPaddingRow(0, kBubbleOuterMargin); 308 return; 309 } 310 311 // No customization: lay out the Deny/Allow buttons. 312 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); 319 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. 323 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; 330 } else { 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; 337 } 338 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); 343 deny_ = deny_button; 344 345 button_layout->AddPaddingRow(0, kBubbleOuterMargin); 346} 347 348PermissionsBubbleDelegateView::~PermissionsBubbleDelegateView() { 349 if (owner_) 350 owner_->Closing(); 351} 352 353void PermissionsBubbleDelegateView::Close() { 354 owner_ = NULL; 355 GetWidget()->Close(); 356} 357 358bool PermissionsBubbleDelegateView::ShouldShowCloseButton() const { 359 return true; 360} 361 362bool PermissionsBubbleDelegateView::ShouldShowWindowTitle() const { 363 return true; 364} 365 366const gfx::FontList& PermissionsBubbleDelegateView::GetTitleFontList() const { 367 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); 368 return rb.GetFontList(ui::ResourceBundle::BaseFont); 369} 370 371base::string16 PermissionsBubbleDelegateView::GetWindowTitle() const { 372 return l10n_util::GetStringFUTF16(IDS_PERMISSIONS_BUBBLE_PROMPT, 373 hostname_); 374} 375 376void PermissionsBubbleDelegateView::SizeToContents() { 377 BubbleDelegateView::SizeToContents(); 378} 379 380void PermissionsBubbleDelegateView::OnWidgetDestroying(views::Widget* widget) { 381 views::BubbleDelegateView::OnWidgetDestroying(widget); 382 if (owner_) { 383 owner_->Closing(); 384 owner_ = NULL; 385 } 386} 387 388void PermissionsBubbleDelegateView::ButtonPressed(views::Button* button, 389 const ui::Event& event) { 390 if (!owner_) 391 return; 392 393 if (button == allow_) 394 owner_->Accept(); 395 else if (button == deny_) 396 owner_->Deny(); 397} 398 399void PermissionsBubbleDelegateView::PermissionSelectionChanged( 400 int index, bool allowed) { 401 owner_->Toggle(index, allowed); 402} 403 404void 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) 412 owner_->Accept(); 413 } 414} 415 416////////////////////////////////////////////////////////////////////////////// 417// PermissionBubbleViewViews 418 419PermissionBubbleViewViews::PermissionBubbleViewViews(views::View* anchor_view) 420 : anchor_view_(anchor_view), 421 delegate_(NULL), 422 bubble_delegate_(NULL) {} 423 424PermissionBubbleViewViews::~PermissionBubbleViewViews() { 425 if (delegate_) 426 delegate_->SetView(NULL); 427} 428 429void PermissionBubbleViewViews::SetDelegate(Delegate* delegate) { 430 delegate_ = delegate; 431} 432 433void 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(); 439 440 bubble_delegate_ = 441 new PermissionsBubbleDelegateView(anchor_view_, this, 442 requests, values, customization_mode); 443 views::BubbleDelegateView::CreateBubble(bubble_delegate_)->Show(); 444 bubble_delegate_->SizeToContents(); 445} 446 447bool PermissionBubbleViewViews::CanAcceptRequestUpdate() { 448 return !(bubble_delegate_ && bubble_delegate_->IsMouseHovered()); 449} 450 451void PermissionBubbleViewViews::Hide() { 452 if (bubble_delegate_) { 453 bubble_delegate_->Close(); 454 bubble_delegate_ = NULL; 455 } 456} 457 458bool PermissionBubbleViewViews::IsVisible() { 459 return bubble_delegate_ != NULL; 460} 461 462void PermissionBubbleViewViews::Closing() { 463 if (bubble_delegate_) 464 bubble_delegate_ = NULL; 465 if (delegate_) 466 delegate_->Closing(); 467} 468 469void PermissionBubbleViewViews::Toggle(int index, bool value) { 470 if (delegate_) 471 delegate_->ToggleAccept(index, value); 472} 473 474void PermissionBubbleViewViews::Accept() { 475 if (delegate_) 476 delegate_->Accept(); 477} 478 479void PermissionBubbleViewViews::Deny() { 480 if (delegate_) 481 delegate_->Deny(); 482} 483 484void PermissionBubbleViewViews::SetCustomizationMode() { 485 if (delegate_) 486 delegate_->SetCustomizationMode(); 487} 488