permissions_bubble_view.cc revision e5d81f57cb97b3b6b7fccc9c5610d21eb81db09d
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/website_settings/permission_bubble_request.h" 9#include "grit/generated_resources.h" 10#include "grit/ui_resources.h" 11#include "ui/base/l10n/l10n_util.h" 12#include "ui/base/models/combobox_model.h" 13#include "ui/base/resource/resource_bundle.h" 14#include "ui/gfx/text_constants.h" 15#include "ui/views/bubble/bubble_delegate.h" 16#include "ui/views/controls/button/checkbox.h" 17#include "ui/views/controls/button/label_button.h" 18#include "ui/views/controls/button/label_button_border.h" 19#include "ui/views/controls/button/menu_button.h" 20#include "ui/views/controls/button/menu_button_listener.h" 21#include "ui/views/controls/combobox/combobox.h" 22#include "ui/views/controls/combobox/combobox_listener.h" 23#include "ui/views/controls/label.h" 24#include "ui/views/layout/box_layout.h" 25#include "ui/views/layout/grid_layout.h" 26 27namespace { 28 29// Spacing constant for outer margin. This is added to the 30// bubble margin itself to equalize the margins at 20px. 31const int kBubbleOuterMargin = 12; 32 33// Spacing between major items should be 10px. 34const int kItemMajorSpacing = 10; 35 36// Button border size, draws inside the spacing distance. 37const int kButtonBorderSize = 2; 38 39} // namespace 40 41// Model for an Allow/Block combobox control. Each combobox has a separate 42// model, which remembers the index of the permission it is associated with. 43class PermissionComboboxModel : public ui::ComboboxModel { 44 public: 45 enum Item { 46 STATE_ALLOW = 0, 47 STATE_BLOCK = 1 48 }; 49 50 PermissionComboboxModel(int index, Item default_item); 51 virtual ~PermissionComboboxModel() {} 52 53 virtual int GetItemCount() const OVERRIDE; 54 virtual base::string16 GetItemAt(int index) OVERRIDE; 55 virtual int GetDefaultIndex() const OVERRIDE; 56 57 // Return the item index this combobox is the model for. 58 int index() { return index_; } 59 60 private: 61 int index_; 62 int default_item_; 63}; 64 65PermissionComboboxModel::PermissionComboboxModel(int index, Item default_item) 66 : index_(index), default_item_(default_item) {} 67 68int PermissionComboboxModel::GetItemCount() const { 69 return 2; // 'allow' and 'block' 70} 71 72base::string16 PermissionComboboxModel::GetItemAt(int index) { 73 if (index == 0) 74 return l10n_util::GetStringUTF16(IDS_PERMISSION_ALLOW); 75 else 76 return l10n_util::GetStringUTF16(IDS_PERMISSION_DENY); 77} 78 79int PermissionComboboxModel::GetDefaultIndex() const { 80 return default_item_; 81} 82 83class CustomizeDenyComboboxModel : public ui::ComboboxModel { 84 public: 85 enum Item { 86 INDEX_DENY = 0, 87 INDEX_CUSTOMIZE = 1 88 }; 89 90 CustomizeDenyComboboxModel() {} 91 virtual ~CustomizeDenyComboboxModel() {} 92 93 virtual int GetItemCount() const OVERRIDE; 94 virtual base::string16 GetItemAt(int index) OVERRIDE; 95 virtual int GetDefaultIndex() const OVERRIDE; 96}; 97 98int CustomizeDenyComboboxModel::GetItemCount() const { 99 return 2; 100} 101 102base::string16 CustomizeDenyComboboxModel::GetItemAt(int index) { 103 if (index == INDEX_DENY) 104 return l10n_util::GetStringUTF16(IDS_PERMISSION_DENY); 105 else 106 return l10n_util::GetStringUTF16(IDS_PERMISSION_CUSTOMIZE); 107} 108 109int CustomizeDenyComboboxModel::GetDefaultIndex() const { 110 return INDEX_DENY; 111} 112 113/////////////////////////////////////////////////////////////////////////////// 114// View implementation for the permissions bubble. 115 116class PermissionsBubbleDelegateView : public views::BubbleDelegateView, 117 public views::ButtonListener, 118 public views::ComboboxListener { 119 public: 120 PermissionsBubbleDelegateView( 121 views::View* anchor, 122 PermissionBubbleViewViews* owner, 123 const std::vector<PermissionBubbleRequest*>& requests, 124 const std::vector<bool>& accept_state, 125 bool customization_mode); 126 virtual ~PermissionsBubbleDelegateView(); 127 128 void Close(); 129 void SizeToContents(); 130 131 // BubbleDelegateView: 132 virtual bool ShouldShowCloseButton() const OVERRIDE; 133 virtual bool ShouldShowWindowTitle() const OVERRIDE; 134 virtual base::string16 GetWindowTitle() const OVERRIDE; 135 virtual void OnWidgetDestroying(views::Widget* widget) OVERRIDE; 136 137 // ButtonListener: 138 virtual void ButtonPressed(views::Button* button, 139 const ui::Event& event) OVERRIDE; 140 141 // ComboboxListener: 142 virtual void OnPerformAction(views::Combobox* combobox) OVERRIDE; 143 144 private: 145 PermissionBubbleViewViews* owner_; 146 views::Button* allow_; 147 views::Button* deny_; 148 views::Combobox* deny_combobox_; 149 base::string16 title_; 150 std::string hostname_; 151 152 DISALLOW_COPY_AND_ASSIGN(PermissionsBubbleDelegateView); 153}; 154 155PermissionsBubbleDelegateView::PermissionsBubbleDelegateView( 156 views::View* anchor, 157 PermissionBubbleViewViews* owner, 158 const std::vector<PermissionBubbleRequest*>& requests, 159 const std::vector<bool>& accept_state, 160 bool customization_mode) 161 : views::BubbleDelegateView(anchor, views::BubbleBorder::TOP_LEFT), 162 owner_(owner), 163 allow_(NULL), 164 deny_(NULL), 165 deny_combobox_(NULL) { 166 DCHECK(!requests.empty()); 167 168 RemoveAllChildViews(true); 169 set_close_on_esc(false); 170 set_close_on_deactivate(false); 171 172 SetLayoutManager(new views::BoxLayout( 173 views::BoxLayout::kVertical, kBubbleOuterMargin, 0, kItemMajorSpacing)); 174 175 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); 176 177 // TODO(gbillock): account for different requests from different hosts. 178 hostname_ = requests[0]->GetRequestingHostname().host(); 179 180 ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance(); 181 for (size_t index = 0; index < requests.size(); index++) { 182 DCHECK(index < accept_state.size()); 183 // The row is laid out containing a leading-aligned label area and a 184 // trailing column which will be filled during customization with a 185 // combobox. 186 views::View* row = new views::View(); 187 views::GridLayout* row_layout = new views::GridLayout(row); 188 row->SetLayoutManager(row_layout); 189 views::ColumnSet* columns = row_layout->AddColumnSet(0); 190 columns->AddColumn(views::GridLayout::LEADING, views::GridLayout::FILL, 191 0, views::GridLayout::USE_PREF, 0, 0); 192 columns->AddColumn(views::GridLayout::TRAILING, views::GridLayout::FILL, 193 100, views::GridLayout::USE_PREF, 0, 0); 194 row_layout->StartRow(0, 0); 195 196 views::View* label_container = new views::View(); 197 label_container->SetLayoutManager( 198 new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0, 5)); 199 views::ImageView* icon = new views::ImageView(); 200 icon->SetImage(bundle.GetImageSkiaNamed(requests.at(index)->GetIconID())); 201 label_container->AddChildView(icon); 202 views::Label* label = 203 new views::Label(requests.at(index)->GetMessageTextFragment()); 204 label->SetFontList(rb.GetFontList(ui::ResourceBundle::MediumFont)); 205 label->SetHorizontalAlignment(gfx::ALIGN_LEFT); 206 label_container->AddChildView(label); 207 row_layout->AddView(label_container); 208 209 if (customization_mode) { 210 views::Combobox* combobox = new views::Combobox( 211 new PermissionComboboxModel(index, 212 accept_state[index] ? PermissionComboboxModel::STATE_ALLOW 213 : PermissionComboboxModel::STATE_BLOCK)); 214 combobox->set_listener(this); 215 row_layout->AddView(combobox); 216 } else { 217 row_layout->AddView(new views::View()); 218 } 219 220 AddChildView(row); 221 } 222 223 views::View* button_row = new views::View(); 224 views::GridLayout* button_layout = new views::GridLayout(button_row); 225 views::ColumnSet* columns = button_layout->AddColumnSet(0); 226 button_row->SetLayoutManager(button_layout); 227 AddChildView(button_row); 228 229 // Customization case: just an "OK" button 230 if (customization_mode) { 231 columns->AddColumn(views::GridLayout::TRAILING, views::GridLayout::FILL, 232 100, views::GridLayout::USE_PREF, 0, 0); 233 button_layout->StartRow(0, 0); 234 views::LabelButton* ok_button = 235 new views::LabelButton(this, l10n_util::GetStringUTF16(IDS_OK)); 236 ok_button->SetStyle(views::Button::STYLE_BUTTON); 237 button_layout->AddView(ok_button); 238 allow_ = ok_button; 239 240 button_layout->AddPaddingRow(0, kBubbleOuterMargin); 241 return; 242 } 243 244 // No customization: lay out the Deny/Allow buttons. 245 246 columns->AddColumn(views::GridLayout::TRAILING, views::GridLayout::FILL, 247 100, views::GridLayout::USE_PREF, 0, 0); 248 columns->AddPaddingColumn(0, kItemMajorSpacing - (2*kButtonBorderSize)); 249 columns->AddColumn(views::GridLayout::TRAILING, views::GridLayout::FILL, 250 0, views::GridLayout::USE_PREF, 0, 0); 251 button_layout->StartRow(0, 0); 252 253 base::string16 allow_text = l10n_util::GetStringUTF16(IDS_PERMISSION_ALLOW); 254 views::LabelButton* allow_button = new views::LabelButton(this, allow_text); 255 allow_button->SetStyle(views::Button::STYLE_BUTTON); 256 allow_button->SetFontList(rb.GetFontList(ui::ResourceBundle::MediumFont)); 257 button_layout->AddView(allow_button); 258 allow_ = allow_button; 259 260 // Deny button is a regular button when there's only one option, and a 261 // STYLE_ACTION Combobox when there are more than one option and 262 // customization is an option. 263 base::string16 deny_text = l10n_util::GetStringUTF16(IDS_PERMISSION_DENY); 264 if (requests.size() == 1) { 265 views::LabelButton* deny_button = new views::LabelButton(this, deny_text); 266 deny_button->SetStyle(views::Button::STYLE_BUTTON); 267 deny_button->SetFontList(rb.GetFontList(ui::ResourceBundle::MediumFont)); 268 button_layout->AddView(deny_button); 269 deny_ = deny_button; 270 } else { 271 views::Combobox* deny_combobox = new views::Combobox( 272 new CustomizeDenyComboboxModel()); 273 deny_combobox->set_listener(this); 274 deny_combobox->SetStyle(views::Combobox::STYLE_ACTION); 275 button_layout->AddView(deny_combobox); 276 deny_combobox_ = deny_combobox; 277 } 278 279 button_layout->AddPaddingRow(0, kBubbleOuterMargin); 280} 281 282PermissionsBubbleDelegateView::~PermissionsBubbleDelegateView() { 283 if (owner_) 284 owner_->Closing(); 285} 286 287void PermissionsBubbleDelegateView::Close() { 288 owner_ = NULL; 289 GetWidget()->Close(); 290} 291 292bool PermissionsBubbleDelegateView::ShouldShowCloseButton() const { 293 return true; 294} 295 296bool PermissionsBubbleDelegateView::ShouldShowWindowTitle() const { 297 return true; 298} 299 300base::string16 PermissionsBubbleDelegateView::GetWindowTitle() const { 301 if (!title_.empty()) { 302 return title_; 303 } 304 305 return l10n_util::GetStringFUTF16(IDS_PERMISSIONS_BUBBLE_PROMPT, 306 base::UTF8ToUTF16(hostname_)); 307} 308 309void PermissionsBubbleDelegateView::SizeToContents() { 310 BubbleDelegateView::SizeToContents(); 311} 312 313void PermissionsBubbleDelegateView::OnWidgetDestroying(views::Widget* widget) { 314 views::BubbleDelegateView::OnWidgetDestroying(widget); 315 if (owner_) { 316 owner_->Closing(); 317 owner_ = NULL; 318 } 319} 320 321void PermissionsBubbleDelegateView::ButtonPressed(views::Button* button, 322 const ui::Event& event) { 323 if (!owner_) 324 return; 325 326 if (button == allow_) 327 owner_->Accept(); 328 else if (button == deny_) 329 owner_->Deny(); 330} 331 332void PermissionsBubbleDelegateView::OnPerformAction( 333 views::Combobox* combobox) { 334 if (combobox == deny_combobox_) { 335 if (combobox->selected_index() == 336 CustomizeDenyComboboxModel::INDEX_CUSTOMIZE) 337 owner_->SetCustomizationMode(); 338 else if (combobox->selected_index() == 339 CustomizeDenyComboboxModel::INDEX_DENY) 340 owner_->Deny(); 341 return; 342 } 343 344 PermissionComboboxModel* model = 345 static_cast<PermissionComboboxModel*>(combobox->model()); 346 owner_->Toggle(model->index(), combobox->selected_index() == 0); 347} 348 349////////////////////////////////////////////////////////////////////////////// 350// PermissionBubbleViewViews 351 352PermissionBubbleViewViews::PermissionBubbleViewViews(views::View* anchor_view) 353 : anchor_view_(anchor_view), 354 delegate_(NULL), 355 bubble_delegate_(NULL) {} 356 357PermissionBubbleViewViews::~PermissionBubbleViewViews() { 358 if (delegate_) 359 delegate_->SetView(NULL); 360} 361 362void PermissionBubbleViewViews::SetDelegate(Delegate* delegate) { 363 delegate_ = delegate; 364} 365 366void PermissionBubbleViewViews::Show( 367 const std::vector<PermissionBubbleRequest*>& requests, 368 const std::vector<bool>& values, 369 bool customization_mode) { 370 if (bubble_delegate_ != NULL) 371 bubble_delegate_->Close(); 372 373 bubble_delegate_ = 374 new PermissionsBubbleDelegateView(anchor_view_, this, 375 requests, values, customization_mode); 376 views::BubbleDelegateView::CreateBubble(bubble_delegate_)->Show(); 377 bubble_delegate_->SizeToContents(); 378} 379 380bool PermissionBubbleViewViews::CanAcceptRequestUpdate() { 381 return !(bubble_delegate_ && bubble_delegate_->IsMouseHovered()); 382} 383 384void PermissionBubbleViewViews::Hide() { 385 if (bubble_delegate_) { 386 bubble_delegate_->Close(); 387 bubble_delegate_ = NULL; 388 } 389} 390 391void PermissionBubbleViewViews::Closing() { 392 if (bubble_delegate_) 393 bubble_delegate_ = NULL; 394 if (delegate_) 395 delegate_->Closing(); 396} 397 398void PermissionBubbleViewViews::Toggle(int index, bool value) { 399 if (delegate_) 400 delegate_->ToggleAccept(index, value); 401} 402 403void PermissionBubbleViewViews::Accept() { 404 if (delegate_) 405 delegate_->Accept(); 406} 407 408void PermissionBubbleViewViews::Deny() { 409 if (delegate_) 410 delegate_->Deny(); 411} 412 413void PermissionBubbleViewViews::SetCustomizationMode() { 414 if (delegate_) 415 delegate_->SetCustomizationMode(); 416} 417