permissions_bubble_view.cc revision 0529e5d033099cbfc42635f6f6183833b09dff6e
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/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" 30 31namespace { 32 33// Spacing constant for outer margin. This is added to the 34// bubble margin itself to equalize the margins at 20px. 35const int kBubbleOuterMargin = 12; 36 37// Spacing between major items should be 10px. 38const int kItemMajorSpacing = 10; 39 40// Button border size, draws inside the spacing distance. 41const int kButtonBorderSize = 2; 42 43} // namespace 44 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? 49class PermissionCombobox : public views::MenuButton, 50 public views::MenuButtonListener { 51 public: 52 // Get notifications when the selection changes. 53 class Listener { 54 public: 55 virtual void PermissionSelectionChanged(int index, bool allowed) = 0; 56 }; 57 58 PermissionCombobox(Listener* listener, 59 int index, 60 const GURL& url, 61 ContentSetting setting); 62 virtual ~PermissionCombobox(); 63 64 int index() const { return index_; } 65 66 virtual void GetAccessibleState(ui::AXViewState* state) OVERRIDE; 67 68 // MenuButtonListener: 69 virtual void OnMenuButtonClicked(View* source, 70 const gfx::Point& point) OVERRIDE; 71 72 // Callback when a permission's setting is changed. 73 void PermissionChanged(const WebsiteSettingsUI::PermissionInfo& permission); 74 75 private: 76 int index_; 77 Listener* listener_; 78 scoped_ptr<PermissionMenuModel> model_; 79 scoped_ptr<views::MenuRunner> menu_runner_; 80}; 81 82PermissionCombobox::PermissionCombobox(Listener* listener, 83 int index, 84 const GURL& url, 85 ContentSetting setting) 86 : MenuButton(NULL, base::string16(), this, true), 87 index_(index), 88 listener_(listener), 89 model_(new PermissionMenuModel( 90 url, 91 setting, 92 base::Bind(&PermissionCombobox::PermissionChanged, 93 base::Unretained(this)))) { 94 SetText(model_->GetLabelAt(model_->GetIndexOfCommandId(setting))); 95 SizeToPreferredSize(); 96} 97 98PermissionCombobox::~PermissionCombobox() {} 99 100void PermissionCombobox::GetAccessibleState(ui::AXViewState* state) { 101 MenuButton::GetAccessibleState(state); 102 state->value = text(); 103} 104 105void PermissionCombobox::OnMenuButtonClicked(View* source, 106 const gfx::Point& point) { 107 menu_runner_.reset(new views::MenuRunner(model_.get())); 108 109 gfx::Point p(point); 110 p.Offset(-source->width(), 0); 111 if (menu_runner_->RunMenuAt( 112 source->GetWidget()->GetTopLevelWidget(), 113 this, 114 gfx::Rect(p, gfx::Size()), 115 views::MenuItemView::TOPLEFT, 116 ui::MENU_SOURCE_NONE, 117 views::MenuRunner::HAS_MNEMONICS) == views::MenuRunner::MENU_DELETED) 118 return; 119} 120 121void PermissionCombobox::PermissionChanged( 122 const WebsiteSettingsUI::PermissionInfo& permission) { 123 SetText(model_->GetLabelAt(model_->GetIndexOfCommandId(permission.setting))); 124 SizeToPreferredSize(); 125 126 listener_->PermissionSelectionChanged( 127 index_, permission.setting == CONTENT_SETTING_ALLOW); 128} 129 130// A combobox originating on the Allow button allowing for customization 131// of permissions. 132class CustomizeAllowComboboxModel : public ui::ComboboxModel { 133 public: 134 enum Item { 135 INDEX_ALLOW = 0, 136 INDEX_CUSTOMIZE = 1 137 }; 138 139 CustomizeAllowComboboxModel() {} 140 virtual ~CustomizeAllowComboboxModel() {} 141 142 virtual int GetItemCount() const OVERRIDE; 143 virtual base::string16 GetItemAt(int index) OVERRIDE; 144 virtual int GetDefaultIndex() const OVERRIDE; 145}; 146 147int CustomizeAllowComboboxModel::GetItemCount() const { 148 return 2; 149} 150 151base::string16 CustomizeAllowComboboxModel::GetItemAt(int index) { 152 if (index == INDEX_ALLOW) 153 return l10n_util::GetStringUTF16(IDS_PERMISSION_ALLOW); 154 else 155 return l10n_util::GetStringUTF16(IDS_PERMISSION_CUSTOMIZE); 156} 157 158int CustomizeAllowComboboxModel::GetDefaultIndex() const { 159 return INDEX_ALLOW; 160} 161 162/////////////////////////////////////////////////////////////////////////////// 163// View implementation for the permissions bubble. 164 165class PermissionsBubbleDelegateView : public views::BubbleDelegateView, 166 public views::ButtonListener, 167 public views::ComboboxListener, 168 public PermissionCombobox::Listener { 169 public: 170 PermissionsBubbleDelegateView( 171 views::View* anchor, 172 PermissionBubbleViewViews* owner, 173 const std::vector<PermissionBubbleRequest*>& requests, 174 const std::vector<bool>& accept_state, 175 bool customization_mode); 176 virtual ~PermissionsBubbleDelegateView(); 177 178 void Close(); 179 void SizeToContents(); 180 181 // BubbleDelegateView: 182 virtual bool ShouldShowCloseButton() const OVERRIDE; 183 virtual bool ShouldShowWindowTitle() const OVERRIDE; 184 virtual base::string16 GetWindowTitle() const OVERRIDE; 185 virtual void OnWidgetDestroying(views::Widget* widget) OVERRIDE; 186 187 // ButtonListener: 188 virtual void ButtonPressed(views::Button* button, 189 const ui::Event& event) OVERRIDE; 190 191 // ComboboxListener: 192 virtual void OnPerformAction(views::Combobox* combobox) OVERRIDE; 193 194 // PermissionCombobox::Listener: 195 virtual void PermissionSelectionChanged(int index, bool allowed) OVERRIDE; 196 197 private: 198 PermissionBubbleViewViews* owner_; 199 views::Button* allow_; 200 views::Button* deny_; 201 views::Combobox* allow_combobox_; 202 base::string16 title_; 203 std::string hostname_; 204 scoped_ptr<PermissionMenuModel> menu_button_model_; 205 std::vector<PermissionCombobox*> customize_comboboxes_; 206 207 DISALLOW_COPY_AND_ASSIGN(PermissionsBubbleDelegateView); 208}; 209 210PermissionsBubbleDelegateView::PermissionsBubbleDelegateView( 211 views::View* anchor, 212 PermissionBubbleViewViews* owner, 213 const std::vector<PermissionBubbleRequest*>& requests, 214 const std::vector<bool>& accept_state, 215 bool customization_mode) 216 : views::BubbleDelegateView(anchor, views::BubbleBorder::TOP_LEFT), 217 owner_(owner), 218 allow_(NULL), 219 deny_(NULL), 220 allow_combobox_(NULL) { 221 DCHECK(!requests.empty()); 222 223 RemoveAllChildViews(true); 224 customize_comboboxes_.clear(); 225 set_close_on_esc(false); 226 set_close_on_deactivate(false); 227 228 SetLayoutManager(new views::BoxLayout( 229 views::BoxLayout::kVertical, kBubbleOuterMargin, 0, kItemMajorSpacing)); 230 231 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); 232 233 // TODO(gbillock): account for different requests from different hosts. 234 hostname_ = requests[0]->GetRequestingHostname().host(); 235 236 ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance(); 237 for (size_t index = 0; index < requests.size(); index++) { 238 DCHECK(index < accept_state.size()); 239 // The row is laid out containing a leading-aligned label area and a 240 // trailing column which will be filled during customization with a 241 // combobox. 242 views::View* row = new views::View(); 243 views::GridLayout* row_layout = new views::GridLayout(row); 244 row->SetLayoutManager(row_layout); 245 views::ColumnSet* columns = row_layout->AddColumnSet(0); 246 columns->AddColumn(views::GridLayout::LEADING, views::GridLayout::FILL, 247 0, views::GridLayout::USE_PREF, 0, 0); 248 columns->AddColumn(views::GridLayout::TRAILING, views::GridLayout::FILL, 249 100, views::GridLayout::USE_PREF, 0, 0); 250 row_layout->StartRow(0, 0); 251 252 views::View* label_container = new views::View(); 253 label_container->SetLayoutManager( 254 new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0, 5)); 255 views::ImageView* icon = new views::ImageView(); 256 icon->SetImage(bundle.GetImageSkiaNamed(requests.at(index)->GetIconID())); 257 label_container->AddChildView(icon); 258 views::Label* label = 259 new views::Label(requests.at(index)->GetMessageTextFragment()); 260 label->SetFontList(rb.GetFontList(ui::ResourceBundle::MediumFont)); 261 label->SetHorizontalAlignment(gfx::ALIGN_LEFT); 262 label_container->AddChildView(label); 263 row_layout->AddView(label_container); 264 265 if (customization_mode) { 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()); 275 } 276 277 AddChildView(row); 278 } 279 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); 285 286 // Customization case: just an "OK" button 287 if (customization_mode) { 288 columns->AddColumn(views::GridLayout::TRAILING, views::GridLayout::FILL, 289 100, views::GridLayout::USE_PREF, 0, 0); 290 button_layout->StartRow(0, 0); 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; 296 297 button_layout->AddPaddingRow(0, kBubbleOuterMargin); 298 return; 299 } 300 301 // No customization: lay out the Deny/Allow buttons. 302 303 columns->AddColumn(views::GridLayout::TRAILING, views::GridLayout::FILL, 304 100, views::GridLayout::USE_PREF, 0, 0); 305 columns->AddPaddingColumn(0, kItemMajorSpacing - (2*kButtonBorderSize)); 306 columns->AddColumn(views::GridLayout::TRAILING, views::GridLayout::FILL, 307 0, views::GridLayout::USE_PREF, 0, 0); 308 button_layout->StartRow(0, 0); 309 310 // Allow button is a regular button when there's only one option, and a 311 // STYLE_ACTION Combobox when there are more than one option and 312 // customization is an option. 313 314 base::string16 allow_text = l10n_util::GetStringUTF16(IDS_PERMISSION_ALLOW); 315 if (requests.size() == 1) { 316 views::LabelButton* allow_button = new views::LabelButton(this, allow_text); 317 allow_button->SetStyle(views::Button::STYLE_BUTTON); 318 allow_button->SetFontList(rb.GetFontList(ui::ResourceBundle::MediumFont)); 319 button_layout->AddView(allow_button); 320 allow_ = allow_button; 321 } else { 322 views::Combobox* allow_combobox = new views::Combobox( 323 new CustomizeAllowComboboxModel()); 324 allow_combobox->set_listener(this); 325 allow_combobox->SetStyle(views::Combobox::STYLE_ACTION); 326 button_layout->AddView(allow_combobox); 327 allow_combobox_ = allow_combobox; 328 } 329 330 base::string16 deny_text = l10n_util::GetStringUTF16(IDS_PERMISSION_DENY); 331 views::LabelButton* deny_button = new views::LabelButton(this, deny_text); 332 deny_button->SetStyle(views::Button::STYLE_BUTTON); 333 deny_button->SetFontList(rb.GetFontList(ui::ResourceBundle::MediumFont)); 334 button_layout->AddView(deny_button); 335 deny_ = deny_button; 336 337 button_layout->AddPaddingRow(0, kBubbleOuterMargin); 338} 339 340PermissionsBubbleDelegateView::~PermissionsBubbleDelegateView() { 341 if (owner_) 342 owner_->Closing(); 343} 344 345void PermissionsBubbleDelegateView::Close() { 346 owner_ = NULL; 347 GetWidget()->Close(); 348} 349 350bool PermissionsBubbleDelegateView::ShouldShowCloseButton() const { 351 return true; 352} 353 354bool PermissionsBubbleDelegateView::ShouldShowWindowTitle() const { 355 return true; 356} 357 358base::string16 PermissionsBubbleDelegateView::GetWindowTitle() const { 359 if (!title_.empty()) { 360 return title_; 361 } 362 363 return l10n_util::GetStringFUTF16(IDS_PERMISSIONS_BUBBLE_PROMPT, 364 base::UTF8ToUTF16(hostname_)); 365} 366 367void PermissionsBubbleDelegateView::SizeToContents() { 368 BubbleDelegateView::SizeToContents(); 369} 370 371void PermissionsBubbleDelegateView::OnWidgetDestroying(views::Widget* widget) { 372 views::BubbleDelegateView::OnWidgetDestroying(widget); 373 if (owner_) { 374 owner_->Closing(); 375 owner_ = NULL; 376 } 377} 378 379void PermissionsBubbleDelegateView::ButtonPressed(views::Button* button, 380 const ui::Event& event) { 381 if (!owner_) 382 return; 383 384 if (button == allow_) 385 owner_->Accept(); 386 else if (button == deny_) 387 owner_->Deny(); 388} 389 390void PermissionsBubbleDelegateView::PermissionSelectionChanged( 391 int index, bool allowed) { 392 owner_->Toggle(index, allowed); 393} 394 395void PermissionsBubbleDelegateView::OnPerformAction( 396 views::Combobox* combobox) { 397 if (combobox == allow_combobox_) { 398 if (combobox->selected_index() == 399 CustomizeAllowComboboxModel::INDEX_CUSTOMIZE) 400 owner_->SetCustomizationMode(); 401 else if (combobox->selected_index() == 402 CustomizeAllowComboboxModel::INDEX_ALLOW) 403 owner_->Accept(); 404 } 405} 406 407////////////////////////////////////////////////////////////////////////////// 408// PermissionBubbleViewViews 409 410PermissionBubbleViewViews::PermissionBubbleViewViews(views::View* anchor_view) 411 : anchor_view_(anchor_view), 412 delegate_(NULL), 413 bubble_delegate_(NULL) {} 414 415PermissionBubbleViewViews::~PermissionBubbleViewViews() { 416 if (delegate_) 417 delegate_->SetView(NULL); 418} 419 420void PermissionBubbleViewViews::SetDelegate(Delegate* delegate) { 421 delegate_ = delegate; 422} 423 424void PermissionBubbleViewViews::Show( 425 const std::vector<PermissionBubbleRequest*>& requests, 426 const std::vector<bool>& values, 427 bool customization_mode) { 428 if (bubble_delegate_ != NULL) 429 bubble_delegate_->Close(); 430 431 bubble_delegate_ = 432 new PermissionsBubbleDelegateView(anchor_view_, this, 433 requests, values, customization_mode); 434 views::BubbleDelegateView::CreateBubble(bubble_delegate_)->Show(); 435 bubble_delegate_->SizeToContents(); 436} 437 438bool PermissionBubbleViewViews::CanAcceptRequestUpdate() { 439 return !(bubble_delegate_ && bubble_delegate_->IsMouseHovered()); 440} 441 442void PermissionBubbleViewViews::Hide() { 443 if (bubble_delegate_) { 444 bubble_delegate_->Close(); 445 bubble_delegate_ = NULL; 446 } 447} 448 449void PermissionBubbleViewViews::Closing() { 450 if (bubble_delegate_) 451 bubble_delegate_ = NULL; 452 if (delegate_) 453 delegate_->Closing(); 454} 455 456void PermissionBubbleViewViews::Toggle(int index, bool value) { 457 if (delegate_) 458 delegate_->ToggleAccept(index, value); 459} 460 461void PermissionBubbleViewViews::Accept() { 462 if (delegate_) 463 delegate_->Accept(); 464} 465 466void PermissionBubbleViewViews::Deny() { 467 if (delegate_) 468 delegate_->Deny(); 469} 470 471void PermissionBubbleViewViews::SetCustomizationMode() { 472 if (delegate_) 473 delegate_->SetCustomizationMode(); 474} 475