permissions_bubble_view.cc revision 5c02ac1a9c1b504631c0a3d2b6e737b5d738bae1
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(source->GetWidget()->GetTopLevelWidget(), 112 this, 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) { 118 return; 119 } 120} 121 122void PermissionCombobox::PermissionChanged( 123 const WebsiteSettingsUI::PermissionInfo& permission) { 124 SetText(model_->GetLabelAt(model_->GetIndexOfCommandId(permission.setting))); 125 SizeToPreferredSize(); 126 127 listener_->PermissionSelectionChanged( 128 index_, permission.setting == CONTENT_SETTING_ALLOW); 129} 130 131// A combobox originating on the Allow button allowing for customization 132// of permissions. 133class CustomizeAllowComboboxModel : public ui::ComboboxModel { 134 public: 135 enum Item { 136 INDEX_ALLOW = 0, 137 INDEX_CUSTOMIZE = 1 138 }; 139 140 CustomizeAllowComboboxModel() {} 141 virtual ~CustomizeAllowComboboxModel() {} 142 143 virtual int GetItemCount() const OVERRIDE; 144 virtual base::string16 GetItemAt(int index) OVERRIDE; 145 virtual int GetDefaultIndex() const OVERRIDE; 146}; 147 148int CustomizeAllowComboboxModel::GetItemCount() const { 149 return 2; 150} 151 152base::string16 CustomizeAllowComboboxModel::GetItemAt(int index) { 153 if (index == INDEX_ALLOW) 154 return l10n_util::GetStringUTF16(IDS_PERMISSION_ALLOW); 155 else 156 return l10n_util::GetStringUTF16(IDS_PERMISSION_CUSTOMIZE); 157} 158 159int CustomizeAllowComboboxModel::GetDefaultIndex() const { 160 return INDEX_ALLOW; 161} 162 163/////////////////////////////////////////////////////////////////////////////// 164// View implementation for the permissions bubble. 165 166class PermissionsBubbleDelegateView : public views::BubbleDelegateView, 167 public views::ButtonListener, 168 public views::ComboboxListener, 169 public PermissionCombobox::Listener { 170 public: 171 PermissionsBubbleDelegateView( 172 views::View* anchor, 173 PermissionBubbleViewViews* owner, 174 const std::vector<PermissionBubbleRequest*>& requests, 175 const std::vector<bool>& accept_state, 176 bool customization_mode); 177 virtual ~PermissionsBubbleDelegateView(); 178 179 void Close(); 180 void SizeToContents(); 181 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; 187 188 // ButtonListener: 189 virtual void ButtonPressed(views::Button* button, 190 const ui::Event& event) OVERRIDE; 191 192 // ComboboxListener: 193 virtual void OnPerformAction(views::Combobox* combobox) OVERRIDE; 194 195 // PermissionCombobox::Listener: 196 virtual void PermissionSelectionChanged(int index, bool allowed) OVERRIDE; 197 198 private: 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_; 207 208 DISALLOW_COPY_AND_ASSIGN(PermissionsBubbleDelegateView); 209}; 210 211PermissionsBubbleDelegateView::PermissionsBubbleDelegateView( 212 views::View* anchor, 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), 218 owner_(owner), 219 allow_(NULL), 220 deny_(NULL), 221 allow_combobox_(NULL) { 222 DCHECK(!requests.empty()); 223 224 RemoveAllChildViews(true); 225 customize_comboboxes_.clear(); 226 set_close_on_esc(false); 227 set_close_on_deactivate(false); 228 set_move_with_anchor(true); 229 230 SetLayoutManager(new views::BoxLayout( 231 views::BoxLayout::kVertical, kBubbleOuterMargin, 0, kItemMajorSpacing)); 232 233 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); 234 235 // TODO(gbillock): account for different requests from different hosts. 236 hostname_ = requests[0]->GetRequestingHostname().host(); 237 238 ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance(); 239 for (size_t index = 0; index < requests.size(); index++) { 240 DCHECK(index < accept_state.size()); 241 // The row is laid out containing a leading-aligned label area and a 242 // trailing column which will be filled during customization with a 243 // combobox. 244 views::View* row = new views::View(); 245 views::GridLayout* row_layout = new views::GridLayout(row); 246 row->SetLayoutManager(row_layout); 247 views::ColumnSet* columns = row_layout->AddColumnSet(0); 248 columns->AddColumn(views::GridLayout::LEADING, views::GridLayout::FILL, 249 0, views::GridLayout::USE_PREF, 0, 0); 250 columns->AddColumn(views::GridLayout::TRAILING, views::GridLayout::FILL, 251 100, views::GridLayout::USE_PREF, 0, 0); 252 row_layout->StartRow(0, 0); 253 254 views::View* label_container = new views::View(); 255 label_container->SetLayoutManager( 256 new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0, 5)); 257 views::ImageView* icon = new views::ImageView(); 258 icon->SetImage(bundle.GetImageSkiaNamed(requests.at(index)->GetIconID())); 259 label_container->AddChildView(icon); 260 views::Label* label = 261 new views::Label(requests.at(index)->GetMessageTextFragment()); 262 label->SetFontList(rb.GetFontList(ui::ResourceBundle::MediumFont)); 263 label->SetHorizontalAlignment(gfx::ALIGN_LEFT); 264 label_container->AddChildView(label); 265 row_layout->AddView(label_container); 266 267 if (customization_mode) { 268 PermissionCombobox* combobox = new PermissionCombobox( 269 this, 270 index, 271 requests[index]->GetRequestingHostname(), 272 accept_state[index] ? CONTENT_SETTING_ALLOW : CONTENT_SETTING_BLOCK); 273 row_layout->AddView(combobox); 274 customize_comboboxes_.push_back(combobox); 275 } else { 276 row_layout->AddView(new views::View()); 277 } 278 279 AddChildView(row); 280 } 281 282 views::View* button_row = new views::View(); 283 views::GridLayout* button_layout = new views::GridLayout(button_row); 284 views::ColumnSet* columns = button_layout->AddColumnSet(0); 285 button_row->SetLayoutManager(button_layout); 286 AddChildView(button_row); 287 288 // Customization case: just an "OK" button 289 if (customization_mode) { 290 columns->AddColumn(views::GridLayout::TRAILING, views::GridLayout::FILL, 291 100, views::GridLayout::USE_PREF, 0, 0); 292 button_layout->StartRow(0, 0); 293 views::LabelButton* ok_button = 294 new views::LabelButton(this, l10n_util::GetStringUTF16(IDS_OK)); 295 ok_button->SetStyle(views::Button::STYLE_BUTTON); 296 button_layout->AddView(ok_button); 297 allow_ = ok_button; 298 299 button_layout->AddPaddingRow(0, kBubbleOuterMargin); 300 return; 301 } 302 303 // No customization: lay out the Deny/Allow buttons. 304 305 columns->AddColumn(views::GridLayout::TRAILING, views::GridLayout::FILL, 306 100, views::GridLayout::USE_PREF, 0, 0); 307 columns->AddPaddingColumn(0, kItemMajorSpacing - (2*kButtonBorderSize)); 308 columns->AddColumn(views::GridLayout::TRAILING, views::GridLayout::FILL, 309 0, views::GridLayout::USE_PREF, 0, 0); 310 button_layout->StartRow(0, 0); 311 312 // Allow button is a regular button when there's only one option, and a 313 // STYLE_ACTION Combobox when there are more than one option and 314 // customization is an option. 315 316 base::string16 allow_text = l10n_util::GetStringUTF16(IDS_PERMISSION_ALLOW); 317 if (requests.size() == 1) { 318 views::LabelButton* allow_button = new views::LabelButton(this, allow_text); 319 allow_button->SetStyle(views::Button::STYLE_BUTTON); 320 allow_button->SetFontList(rb.GetFontList(ui::ResourceBundle::MediumFont)); 321 button_layout->AddView(allow_button); 322 allow_ = allow_button; 323 } else { 324 views::Combobox* allow_combobox = new views::Combobox( 325 new CustomizeAllowComboboxModel()); 326 allow_combobox->set_listener(this); 327 allow_combobox->SetStyle(views::Combobox::STYLE_ACTION); 328 button_layout->AddView(allow_combobox); 329 allow_combobox_ = allow_combobox; 330 } 331 332 base::string16 deny_text = l10n_util::GetStringUTF16(IDS_PERMISSION_DENY); 333 views::LabelButton* deny_button = new views::LabelButton(this, deny_text); 334 deny_button->SetStyle(views::Button::STYLE_BUTTON); 335 deny_button->SetFontList(rb.GetFontList(ui::ResourceBundle::MediumFont)); 336 button_layout->AddView(deny_button); 337 deny_ = deny_button; 338 339 button_layout->AddPaddingRow(0, kBubbleOuterMargin); 340} 341 342PermissionsBubbleDelegateView::~PermissionsBubbleDelegateView() { 343 if (owner_) 344 owner_->Closing(); 345} 346 347void PermissionsBubbleDelegateView::Close() { 348 owner_ = NULL; 349 GetWidget()->Close(); 350} 351 352bool PermissionsBubbleDelegateView::ShouldShowCloseButton() const { 353 return true; 354} 355 356bool PermissionsBubbleDelegateView::ShouldShowWindowTitle() const { 357 return true; 358} 359 360base::string16 PermissionsBubbleDelegateView::GetWindowTitle() const { 361 if (!title_.empty()) { 362 return title_; 363 } 364 365 return l10n_util::GetStringFUTF16(IDS_PERMISSIONS_BUBBLE_PROMPT, 366 base::UTF8ToUTF16(hostname_)); 367} 368 369void PermissionsBubbleDelegateView::SizeToContents() { 370 BubbleDelegateView::SizeToContents(); 371} 372 373void PermissionsBubbleDelegateView::OnWidgetDestroying(views::Widget* widget) { 374 views::BubbleDelegateView::OnWidgetDestroying(widget); 375 if (owner_) { 376 owner_->Closing(); 377 owner_ = NULL; 378 } 379} 380 381void PermissionsBubbleDelegateView::ButtonPressed(views::Button* button, 382 const ui::Event& event) { 383 if (!owner_) 384 return; 385 386 if (button == allow_) 387 owner_->Accept(); 388 else if (button == deny_) 389 owner_->Deny(); 390} 391 392void PermissionsBubbleDelegateView::PermissionSelectionChanged( 393 int index, bool allowed) { 394 owner_->Toggle(index, allowed); 395} 396 397void PermissionsBubbleDelegateView::OnPerformAction( 398 views::Combobox* combobox) { 399 if (combobox == allow_combobox_) { 400 if (combobox->selected_index() == 401 CustomizeAllowComboboxModel::INDEX_CUSTOMIZE) 402 owner_->SetCustomizationMode(); 403 else if (combobox->selected_index() == 404 CustomizeAllowComboboxModel::INDEX_ALLOW) 405 owner_->Accept(); 406 } 407} 408 409////////////////////////////////////////////////////////////////////////////// 410// PermissionBubbleViewViews 411 412PermissionBubbleViewViews::PermissionBubbleViewViews(views::View* anchor_view) 413 : anchor_view_(anchor_view), 414 delegate_(NULL), 415 bubble_delegate_(NULL) {} 416 417PermissionBubbleViewViews::~PermissionBubbleViewViews() { 418 if (delegate_) 419 delegate_->SetView(NULL); 420} 421 422void PermissionBubbleViewViews::SetDelegate(Delegate* delegate) { 423 delegate_ = delegate; 424} 425 426void PermissionBubbleViewViews::Show( 427 const std::vector<PermissionBubbleRequest*>& requests, 428 const std::vector<bool>& values, 429 bool customization_mode) { 430 if (bubble_delegate_ != NULL) 431 bubble_delegate_->Close(); 432 433 bubble_delegate_ = 434 new PermissionsBubbleDelegateView(anchor_view_, this, 435 requests, values, customization_mode); 436 views::BubbleDelegateView::CreateBubble(bubble_delegate_)->Show(); 437 bubble_delegate_->SizeToContents(); 438} 439 440bool PermissionBubbleViewViews::CanAcceptRequestUpdate() { 441 return !(bubble_delegate_ && bubble_delegate_->IsMouseHovered()); 442} 443 444void PermissionBubbleViewViews::Hide() { 445 if (bubble_delegate_) { 446 bubble_delegate_->Close(); 447 bubble_delegate_ = NULL; 448 } 449} 450 451void PermissionBubbleViewViews::Closing() { 452 if (bubble_delegate_) 453 bubble_delegate_ = NULL; 454 if (delegate_) 455 delegate_->Closing(); 456} 457 458void PermissionBubbleViewViews::Toggle(int index, bool value) { 459 if (delegate_) 460 delegate_->ToggleAccept(index, value); 461} 462 463void PermissionBubbleViewViews::Accept() { 464 if (delegate_) 465 delegate_->Accept(); 466} 467 468void PermissionBubbleViewViews::Deny() { 469 if (delegate_) 470 delegate_->Deny(); 471} 472 473void PermissionBubbleViewViews::SetCustomizationMode() { 474 if (delegate_) 475 delegate_->SetCustomizationMode(); 476} 477