content_setting_bubble_contents.cc revision a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7
1// Copyright (c) 2012 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/content_setting_bubble_contents.h" 6 7#include <algorithm> 8#include <set> 9#include <string> 10#include <vector> 11 12#include "base/bind.h" 13#include "base/stl_util.h" 14#include "base/strings/utf_string_conversions.h" 15#include "chrome/browser/content_settings/host_content_settings_map.h" 16#include "chrome/browser/plugins/plugin_finder.h" 17#include "chrome/browser/plugins/plugin_metadata.h" 18#include "chrome/browser/ui/content_settings/content_setting_bubble_model.h" 19#include "chrome/browser/ui/content_settings/content_setting_media_menu_model.h" 20#include "chrome/browser/ui/views/browser_dialogs.h" 21#include "content/public/browser/plugin_service.h" 22#include "content/public/browser/web_contents.h" 23#include "grit/generated_resources.h" 24#include "grit/theme_resources.h" 25#include "ui/base/l10n/l10n_util.h" 26#include "ui/base/models/simple_menu_model.h" 27#include "ui/views/controls/button/label_button.h" 28#include "ui/views/controls/button/menu_button.h" 29#include "ui/views/controls/button/radio_button.h" 30#include "ui/views/controls/image_view.h" 31#include "ui/views/controls/label.h" 32#include "ui/views/controls/link.h" 33#include "ui/views/controls/menu/menu.h" 34#include "ui/views/controls/menu/menu_runner.h" 35#include "ui/views/controls/separator.h" 36#include "ui/views/layout/grid_layout.h" 37#include "ui/views/layout/layout_constants.h" 38 39#if defined(USE_AURA) 40#include "ui/base/cursor/cursor.h" 41#endif 42 43namespace { 44 45// If we don't clamp the maximum width, then very long URLs and titles can make 46// the bubble arbitrarily wide. 47const int kMaxContentsWidth = 500; 48 49// When we have multiline labels, we should set a minimum width lest we get very 50// narrow bubbles with lots of line-wrapping. 51const int kMinMultiLineContentsWidth = 250; 52 53// The minimum width of the media menu buttons. 54const int kMinMediaMenuButtonWidth = 100; 55 56} // namespace 57 58using content::PluginService; 59using content::WebContents; 60 61 62// ContentSettingBubbleContents::Favicon -------------------------------------- 63 64class ContentSettingBubbleContents::Favicon : public views::ImageView { 65 public: 66 Favicon(const gfx::Image& image, 67 ContentSettingBubbleContents* parent, 68 views::Link* link); 69 virtual ~Favicon(); 70 71 private: 72 // views::View overrides: 73 virtual bool OnMousePressed(const ui::MouseEvent& event) OVERRIDE; 74 virtual void OnMouseReleased(const ui::MouseEvent& event) OVERRIDE; 75 virtual gfx::NativeCursor GetCursor(const ui::MouseEvent& event) OVERRIDE; 76 77 ContentSettingBubbleContents* parent_; 78 views::Link* link_; 79}; 80 81ContentSettingBubbleContents::Favicon::Favicon( 82 const gfx::Image& image, 83 ContentSettingBubbleContents* parent, 84 views::Link* link) 85 : parent_(parent), 86 link_(link) { 87 SetImage(image.AsImageSkia()); 88} 89 90ContentSettingBubbleContents::Favicon::~Favicon() { 91} 92 93bool ContentSettingBubbleContents::Favicon::OnMousePressed( 94 const ui::MouseEvent& event) { 95 return event.IsLeftMouseButton() || event.IsMiddleMouseButton(); 96} 97 98void ContentSettingBubbleContents::Favicon::OnMouseReleased( 99 const ui::MouseEvent& event) { 100 if ((event.IsLeftMouseButton() || event.IsMiddleMouseButton()) && 101 HitTestPoint(event.location())) { 102 parent_->LinkClicked(link_, event.flags()); 103 } 104} 105 106gfx::NativeCursor ContentSettingBubbleContents::Favicon::GetCursor( 107 const ui::MouseEvent& event) { 108#if defined(USE_AURA) 109 return ui::kCursorHand; 110#elif defined(OS_WIN) 111 static HCURSOR g_hand_cursor = LoadCursor(NULL, IDC_HAND); 112 return g_hand_cursor; 113#endif 114} 115 116 117// ContentSettingBubbleContents::MediaMenuParts ------------------------------- 118 119struct ContentSettingBubbleContents::MediaMenuParts { 120 explicit MediaMenuParts(content::MediaStreamType type); 121 ~MediaMenuParts(); 122 123 content::MediaStreamType type; 124 scoped_ptr<ui::SimpleMenuModel> menu_model; 125 126 private: 127 DISALLOW_COPY_AND_ASSIGN(MediaMenuParts); 128}; 129 130ContentSettingBubbleContents::MediaMenuParts::MediaMenuParts( 131 content::MediaStreamType type) 132 : type(type) {} 133 134ContentSettingBubbleContents::MediaMenuParts::~MediaMenuParts() {} 135 136// ContentSettingBubbleContents ----------------------------------------------- 137 138ContentSettingBubbleContents::ContentSettingBubbleContents( 139 ContentSettingBubbleModel* content_setting_bubble_model, 140 views::View* anchor_view, 141 views::BubbleBorder::Arrow arrow) 142 : BubbleDelegateView(anchor_view, arrow), 143 content_setting_bubble_model_(content_setting_bubble_model), 144 custom_link_(NULL), 145 manage_link_(NULL), 146 close_button_(NULL) { 147 // Compensate for built-in vertical padding in the anchor view's image. 148 set_anchor_view_insets(gfx::Insets(5, 0, 5, 0)); 149} 150 151ContentSettingBubbleContents::~ContentSettingBubbleContents() { 152 STLDeleteValues(&media_menus_); 153} 154 155gfx::Size ContentSettingBubbleContents::GetPreferredSize() { 156 gfx::Size preferred_size(views::View::GetPreferredSize()); 157 int preferred_width = 158 (!content_setting_bubble_model_->bubble_content().domain_lists.empty() && 159 (kMinMultiLineContentsWidth > preferred_size.width())) ? 160 kMinMultiLineContentsWidth : preferred_size.width(); 161 preferred_size.set_width(std::min(preferred_width, kMaxContentsWidth)); 162 return preferred_size; 163} 164 165void ContentSettingBubbleContents::UpdateMenuLabel( 166 content::MediaStreamType type, 167 const std::string& label) { 168 for (MediaMenuPartsMap::const_iterator it = media_menus_.begin(); 169 it != media_menus_.end(); ++it) { 170 if (it->second->type == type) { 171 it->first->SetText(UTF8ToUTF16(label)); 172 return; 173 } 174 } 175 NOTREACHED(); 176} 177 178void ContentSettingBubbleContents::Init() { 179 using views::GridLayout; 180 181 GridLayout* layout = new views::GridLayout(this); 182 SetLayoutManager(layout); 183 184 const int kSingleColumnSetId = 0; 185 views::ColumnSet* column_set = layout->AddColumnSet(kSingleColumnSetId); 186 column_set->AddColumn(GridLayout::LEADING, GridLayout::FILL, 1, 187 GridLayout::USE_PREF, 0, 0); 188 189 const ContentSettingBubbleModel::BubbleContent& bubble_content = 190 content_setting_bubble_model_->bubble_content(); 191 bool bubble_content_empty = true; 192 193 if (!bubble_content.title.empty()) { 194 views::Label* title_label = new views::Label(UTF8ToUTF16( 195 bubble_content.title)); 196 title_label->SetMultiLine(true); 197 title_label->SetHorizontalAlignment(gfx::ALIGN_LEFT); 198 layout->StartRow(0, kSingleColumnSetId); 199 layout->AddView(title_label); 200 bubble_content_empty = false; 201 } 202 203 if (content_setting_bubble_model_->content_type() == 204 CONTENT_SETTINGS_TYPE_POPUPS) { 205 const int kPopupColumnSetId = 2; 206 views::ColumnSet* popup_column_set = 207 layout->AddColumnSet(kPopupColumnSetId); 208 popup_column_set->AddColumn(GridLayout::LEADING, GridLayout::FILL, 0, 209 GridLayout::USE_PREF, 0, 0); 210 popup_column_set->AddPaddingColumn( 211 0, views::kRelatedControlHorizontalSpacing); 212 popup_column_set->AddColumn(GridLayout::LEADING, GridLayout::FILL, 1, 213 GridLayout::USE_PREF, 0, 0); 214 215 for (std::vector<ContentSettingBubbleModel::PopupItem>::const_iterator 216 i(bubble_content.popup_items.begin()); 217 i != bubble_content.popup_items.end(); ++i) { 218 if (!bubble_content_empty) 219 layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing); 220 layout->StartRow(0, kPopupColumnSetId); 221 222 views::Link* link = new views::Link(UTF8ToUTF16(i->title)); 223 link->set_listener(this); 224 link->SetElideBehavior(views::Label::ELIDE_IN_MIDDLE); 225 popup_links_[link] = i - bubble_content.popup_items.begin(); 226 layout->AddView(new Favicon(i->image, this, link)); 227 layout->AddView(link); 228 bubble_content_empty = false; 229 } 230 } 231 232 const int indented_kSingleColumnSetId = 3; 233 // Insert a column set with greater indent. 234 views::ColumnSet* indented_single_column_set = 235 layout->AddColumnSet(indented_kSingleColumnSetId); 236 indented_single_column_set->AddPaddingColumn(0, views::kCheckboxIndent); 237 indented_single_column_set->AddColumn(GridLayout::LEADING, GridLayout::FILL, 238 1, GridLayout::USE_PREF, 0, 0); 239 240 const ContentSettingBubbleModel::RadioGroup& radio_group = 241 bubble_content.radio_group; 242 if (!radio_group.radio_items.empty()) { 243 if (!bubble_content_empty) 244 layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing); 245 for (ContentSettingBubbleModel::RadioItems::const_iterator i( 246 radio_group.radio_items.begin()); 247 i != radio_group.radio_items.end(); ++i) { 248 views::RadioButton* radio = new views::RadioButton(UTF8ToUTF16(*i), 0); 249 radio->SetEnabled(bubble_content.radio_group_enabled); 250 radio->set_listener(this); 251 radio_group_.push_back(radio); 252 layout->StartRow(0, indented_kSingleColumnSetId); 253 layout->AddView(radio); 254 bubble_content_empty = false; 255 } 256 DCHECK(!radio_group_.empty()); 257 // Now that the buttons have been added to the view hierarchy, it's safe 258 // to call SetChecked() on them. 259 radio_group_[radio_group.default_item]->SetChecked(true); 260 } 261 262 // Layout code for the media device menus. 263 if (content_setting_bubble_model_->content_type() == 264 CONTENT_SETTINGS_TYPE_MEDIASTREAM) { 265 const int kMediaMenuColumnSetId = 2; 266 views::ColumnSet* menu_column_set = 267 layout->AddColumnSet(kMediaMenuColumnSetId); 268 menu_column_set->AddPaddingColumn(0, views::kCheckboxIndent); 269 menu_column_set->AddColumn(GridLayout::LEADING, GridLayout::FILL, 0, 270 GridLayout::USE_PREF, 0, 0); 271 menu_column_set->AddPaddingColumn( 272 0, views::kRelatedControlHorizontalSpacing); 273 menu_column_set->AddColumn(GridLayout::LEADING, GridLayout::FILL, 1, 274 GridLayout::USE_PREF, 0, 0); 275 276 int menu_width = 0; 277 for (ContentSettingBubbleModel::MediaMenuMap::const_iterator i( 278 bubble_content.media_menus.begin()); 279 i != bubble_content.media_menus.end(); ++i) { 280 if (!bubble_content_empty) 281 layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing); 282 layout->StartRow(0, kMediaMenuColumnSetId); 283 284 views::Label* label = new views::Label(UTF8ToUTF16(i->second.label)); 285 label->SetHorizontalAlignment(gfx::ALIGN_LEFT); 286 287 views::MenuButton* menu_button = new views::MenuButton( 288 NULL, UTF8ToUTF16((i->second.selected_device.name)), this, true); 289 menu_button->set_alignment(views::TextButton::ALIGN_LEFT); 290 menu_button->set_border( 291 new views::TextButtonNativeThemeBorder(menu_button)); 292 menu_button->set_animate_on_state_change(false); 293 294 MediaMenuParts* menu_view = new MediaMenuParts(i->first); 295 menu_view->menu_model.reset(new ContentSettingMediaMenuModel( 296 i->first, 297 content_setting_bubble_model_.get(), 298 base::Bind(&ContentSettingBubbleContents::UpdateMenuLabel, 299 base::Unretained(this)))); 300 media_menus_[menu_button] = menu_view; 301 302 if (!menu_view->menu_model->GetItemCount()) { 303 // Show a "None available" title and grey out the menu when there are 304 // no available devices. 305 menu_button->SetText( 306 l10n_util::GetStringUTF16(IDS_MEDIA_MENU_NO_DEVICE_TITLE)); 307 menu_button->SetEnabled(false); 308 } 309 310 // Disable the device selection when the website is managing the devices 311 // itself. 312 if (i->second.disabled) 313 menu_button->SetEnabled(false); 314 315 // Use the longest width of the menus as the width of the menu buttons. 316 menu_width = std::max(menu_width, 317 GetPreferredMediaMenuWidth( 318 menu_button, menu_view->menu_model.get())); 319 320 layout->AddView(label); 321 layout->AddView(menu_button); 322 323 bubble_content_empty = false; 324 } 325 326 // Make sure the width is at least kMinMediaMenuButtonWidth. The 327 // maximum width will be clamped by kMaxContentsWidth of the view. 328 menu_width = std::max(kMinMediaMenuButtonWidth, menu_width); 329 330 // Set all the menu buttons to the width we calculated above. 331 for (MediaMenuPartsMap::const_iterator i = media_menus_.begin(); 332 i != media_menus_.end(); ++i) { 333 i->first->set_min_width(menu_width); 334 i->first->set_max_width(menu_width); 335 } 336 } 337 338 gfx::Font domain_font = 339 views::Label().font().DeriveFont(0, gfx::Font::BOLD); 340 for (std::vector<ContentSettingBubbleModel::DomainList>::const_iterator i( 341 bubble_content.domain_lists.begin()); 342 i != bubble_content.domain_lists.end(); ++i) { 343 layout->StartRow(0, kSingleColumnSetId); 344 views::Label* section_title = new views::Label(UTF8ToUTF16(i->title)); 345 section_title->SetMultiLine(true); 346 section_title->SetHorizontalAlignment(gfx::ALIGN_LEFT); 347 layout->AddView(section_title, 1, 1, GridLayout::FILL, GridLayout::LEADING); 348 for (std::set<std::string>::const_iterator j = i->hosts.begin(); 349 j != i->hosts.end(); ++j) { 350 layout->StartRow(0, indented_kSingleColumnSetId); 351 layout->AddView(new views::Label(UTF8ToUTF16(*j), domain_font)); 352 } 353 bubble_content_empty = false; 354 } 355 356 if (!bubble_content.custom_link.empty()) { 357 custom_link_ = new views::Link(UTF8ToUTF16(bubble_content.custom_link)); 358 custom_link_->SetEnabled(bubble_content.custom_link_enabled); 359 custom_link_->set_listener(this); 360 if (!bubble_content_empty) 361 layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing); 362 layout->StartRow(0, kSingleColumnSetId); 363 layout->AddView(custom_link_); 364 bubble_content_empty = false; 365 } 366 367 const int kDoubleColumnSetId = 1; 368 views::ColumnSet* double_column_set = 369 layout->AddColumnSet(kDoubleColumnSetId); 370 if (!bubble_content_empty) { 371 layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing); 372 layout->StartRow(0, kSingleColumnSetId); 373 layout->AddView(new views::Separator(views::Separator::HORIZONTAL), 1, 1, 374 GridLayout::FILL, GridLayout::FILL); 375 layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing); 376 } 377 378 double_column_set->AddColumn(GridLayout::LEADING, GridLayout::CENTER, 1, 379 GridLayout::USE_PREF, 0, 0); 380 double_column_set->AddPaddingColumn( 381 0, views::kUnrelatedControlHorizontalSpacing); 382 double_column_set->AddColumn(GridLayout::TRAILING, GridLayout::CENTER, 0, 383 GridLayout::USE_PREF, 0, 0); 384 385 layout->StartRow(0, kDoubleColumnSetId); 386 manage_link_ = new views::Link(UTF8ToUTF16(bubble_content.manage_link)); 387 manage_link_->set_listener(this); 388 layout->AddView(manage_link_); 389 390 close_button_ = 391 new views::LabelButton(this, l10n_util::GetStringUTF16(IDS_DONE)); 392 close_button_->SetStyle(views::Button::STYLE_NATIVE_TEXTBUTTON); 393 layout->AddView(close_button_); 394} 395 396void ContentSettingBubbleContents::ButtonPressed(views::Button* sender, 397 const ui::Event& event) { 398 RadioGroup::const_iterator i( 399 std::find(radio_group_.begin(), radio_group_.end(), sender)); 400 if (i != radio_group_.end()) { 401 content_setting_bubble_model_->OnRadioClicked(i - radio_group_.begin()); 402 return; 403 } 404 DCHECK_EQ(sender, close_button_); 405 content_setting_bubble_model_->OnDoneClicked(); 406 StartFade(false); 407} 408 409void ContentSettingBubbleContents::LinkClicked(views::Link* source, 410 int event_flags) { 411 if (source == custom_link_) { 412 content_setting_bubble_model_->OnCustomLinkClicked(); 413 StartFade(false); 414 return; 415 } 416 if (source == manage_link_) { 417 StartFade(false); 418 content_setting_bubble_model_->OnManageLinkClicked(); 419 // CAREFUL: Showing the settings window activates it, which deactivates the 420 // info bubble, which causes it to close, which deletes us. 421 return; 422 } 423 424 PopupLinks::const_iterator i(popup_links_.find(source)); 425 DCHECK(i != popup_links_.end()); 426 content_setting_bubble_model_->OnPopupClicked(i->second); 427} 428 429void ContentSettingBubbleContents::OnMenuButtonClicked( 430 views::View* source, 431 const gfx::Point& point) { 432 MediaMenuPartsMap::iterator j(media_menus_.find( 433 static_cast<views::MenuButton*>(source))); 434 DCHECK(j != media_menus_.end()); 435 menu_runner_.reset(new views::MenuRunner(j->second->menu_model.get())); 436 437 gfx::Point screen_location; 438 views::View::ConvertPointToScreen(j->first, &screen_location); 439 ignore_result(menu_runner_->RunMenuAt( 440 source->GetWidget(), 441 j->first, 442 gfx::Rect(screen_location, j->first->size()), 443 views::MenuItemView::TOPLEFT, 444 ui::MENU_SOURCE_NONE, 445 views::MenuRunner::HAS_MNEMONICS)); 446} 447 448int ContentSettingBubbleContents::GetPreferredMediaMenuWidth( 449 views::MenuButton* button, 450 ui::SimpleMenuModel* menu_model) { 451 base::string16 title = button->text(); 452 453 int width = button->GetPreferredSize().width(); 454 for (int i = 0; i < menu_model->GetItemCount(); ++i) { 455 button->SetText(menu_model->GetLabelAt(i)); 456 width = std::max(width, button->GetPreferredSize().width()); 457 } 458 459 // Recover the title for the menu button. 460 button->SetText(title); 461 return width; 462} 463