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.
5 #include "chrome/browser/ui/views/content_setting_bubble_contents.h"
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/base/resource/resource_bundle.h"
28 #include "ui/gfx/font_list.h"
29 #include "ui/gfx/text_utils.h"
30 #include "ui/views/controls/button/label_button.h"
31 #include "ui/views/controls/button/menu_button.h"
32 #include "ui/views/controls/button/radio_button.h"
33 #include "ui/views/controls/image_view.h"
34 #include "ui/views/controls/label.h"
35 #include "ui/views/controls/link.h"
36 #include "ui/views/controls/menu/menu.h"
37 #include "ui/views/controls/menu/menu_config.h"
38 #include "ui/views/controls/menu/menu_runner.h"
39 #include "ui/views/controls/separator.h"
40 #include "ui/views/layout/grid_layout.h"
41 #include "ui/views/layout/layout_constants.h"
44 #include "ui/base/cursor/cursor.h"
49 // If we don't clamp the maximum width, then very long URLs and titles can make
50 // the bubble arbitrarily wide.
51 const int kMaxContentsWidth = 500;
53 // When we have multiline labels, we should set a minimum width lest we get very
54 // narrow bubbles with lots of line-wrapping.
55 const int kMinMultiLineContentsWidth = 250;
57 // The minimum width of the media menu buttons.
58 const int kMinMediaMenuButtonWidth = 150;
62 using content::PluginService;
63 using content::WebContents;
66 // ContentSettingBubbleContents::Favicon --------------------------------------
68 class ContentSettingBubbleContents::Favicon : public views::ImageView {
70 Favicon(const gfx::Image& image,
71 ContentSettingBubbleContents* parent,
76 // views::View overrides:
77 virtual bool OnMousePressed(const ui::MouseEvent& event) OVERRIDE;
78 virtual void OnMouseReleased(const ui::MouseEvent& event) OVERRIDE;
79 virtual gfx::NativeCursor GetCursor(const ui::MouseEvent& event) OVERRIDE;
81 ContentSettingBubbleContents* parent_;
85 ContentSettingBubbleContents::Favicon::Favicon(
86 const gfx::Image& image,
87 ContentSettingBubbleContents* parent,
91 SetImage(image.AsImageSkia());
94 ContentSettingBubbleContents::Favicon::~Favicon() {
97 bool ContentSettingBubbleContents::Favicon::OnMousePressed(
98 const ui::MouseEvent& event) {
99 return event.IsLeftMouseButton() || event.IsMiddleMouseButton();
102 void ContentSettingBubbleContents::Favicon::OnMouseReleased(
103 const ui::MouseEvent& event) {
104 if ((event.IsLeftMouseButton() || event.IsMiddleMouseButton()) &&
105 HitTestPoint(event.location())) {
106 parent_->LinkClicked(link_, event.flags());
110 gfx::NativeCursor ContentSettingBubbleContents::Favicon::GetCursor(
111 const ui::MouseEvent& event) {
112 #if defined(USE_AURA)
113 return ui::kCursorHand;
114 #elif defined(OS_WIN)
115 static HCURSOR g_hand_cursor = LoadCursor(NULL, IDC_HAND);
116 return g_hand_cursor;
121 // ContentSettingBubbleContents::MediaMenuParts -------------------------------
123 struct ContentSettingBubbleContents::MediaMenuParts {
124 explicit MediaMenuParts(content::MediaStreamType type);
127 content::MediaStreamType type;
128 scoped_ptr<ui::SimpleMenuModel> menu_model;
131 DISALLOW_COPY_AND_ASSIGN(MediaMenuParts);
134 ContentSettingBubbleContents::MediaMenuParts::MediaMenuParts(
135 content::MediaStreamType type)
138 ContentSettingBubbleContents::MediaMenuParts::~MediaMenuParts() {}
140 // ContentSettingBubbleContents -----------------------------------------------
142 ContentSettingBubbleContents::ContentSettingBubbleContents(
143 ContentSettingBubbleModel* content_setting_bubble_model,
144 content::WebContents* web_contents,
145 views::View* anchor_view,
146 views::BubbleBorder::Arrow arrow)
147 : content::WebContentsObserver(web_contents),
148 BubbleDelegateView(anchor_view, arrow),
149 content_setting_bubble_model_(content_setting_bubble_model),
152 learn_more_link_(NULL),
153 close_button_(NULL) {
154 // Compensate for built-in vertical padding in the anchor view's image.
155 set_anchor_view_insets(gfx::Insets(5, 0, 5, 0));
158 ContentSettingBubbleContents::~ContentSettingBubbleContents() {
159 STLDeleteValues(&media_menus_);
162 gfx::Size ContentSettingBubbleContents::GetPreferredSize() const {
163 gfx::Size preferred_size(views::View::GetPreferredSize());
164 int preferred_width =
165 (!content_setting_bubble_model_->bubble_content().domain_lists.empty() &&
166 (kMinMultiLineContentsWidth > preferred_size.width())) ?
167 kMinMultiLineContentsWidth : preferred_size.width();
168 preferred_size.set_width(std::min(preferred_width, kMaxContentsWidth));
169 return preferred_size;
172 void ContentSettingBubbleContents::UpdateMenuLabel(
173 content::MediaStreamType type,
174 const std::string& label) {
175 for (MediaMenuPartsMap::const_iterator it = media_menus_.begin();
176 it != media_menus_.end(); ++it) {
177 if (it->second->type == type) {
178 it->first->SetText(base::UTF8ToUTF16(label));
185 void ContentSettingBubbleContents::Init() {
186 using views::GridLayout;
188 GridLayout* layout = new views::GridLayout(this);
189 SetLayoutManager(layout);
191 const int kSingleColumnSetId = 0;
192 views::ColumnSet* column_set = layout->AddColumnSet(kSingleColumnSetId);
193 column_set->AddColumn(GridLayout::LEADING, GridLayout::FILL, 1,
194 GridLayout::USE_PREF, 0, 0);
195 column_set->AddPaddingColumn(0, views::kRelatedControlHorizontalSpacing);
196 column_set->AddColumn(GridLayout::LEADING, GridLayout::FILL, 1,
197 GridLayout::USE_PREF, 0, 0);
199 const ContentSettingBubbleModel::BubbleContent& bubble_content =
200 content_setting_bubble_model_->bubble_content();
201 bool bubble_content_empty = true;
203 if (!bubble_content.title.empty()) {
204 views::Label* title_label = new views::Label(base::UTF8ToUTF16(
205 bubble_content.title));
206 title_label->SetMultiLine(true);
207 title_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
208 layout->StartRow(0, kSingleColumnSetId);
209 layout->AddView(title_label);
210 bubble_content_empty = false;
213 if (!bubble_content.learn_more_link.empty()) {
215 new views::Link(base::UTF8ToUTF16(bubble_content.learn_more_link));
216 learn_more_link_->set_listener(this);
217 learn_more_link_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
218 layout->AddView(learn_more_link_);
219 bubble_content_empty = false;
222 if (content_setting_bubble_model_->content_type() ==
223 CONTENT_SETTINGS_TYPE_POPUPS) {
224 const int kPopupColumnSetId = 2;
225 views::ColumnSet* popup_column_set =
226 layout->AddColumnSet(kPopupColumnSetId);
227 popup_column_set->AddColumn(GridLayout::LEADING, GridLayout::FILL, 0,
228 GridLayout::USE_PREF, 0, 0);
229 popup_column_set->AddPaddingColumn(
230 0, views::kRelatedControlHorizontalSpacing);
231 popup_column_set->AddColumn(GridLayout::LEADING, GridLayout::FILL, 1,
232 GridLayout::USE_PREF, 0, 0);
234 for (std::vector<ContentSettingBubbleModel::PopupItem>::const_iterator
235 i(bubble_content.popup_items.begin());
236 i != bubble_content.popup_items.end(); ++i) {
237 if (!bubble_content_empty)
238 layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
239 layout->StartRow(0, kPopupColumnSetId);
241 views::Link* link = new views::Link(base::UTF8ToUTF16(i->title));
242 link->set_listener(this);
243 link->SetElideBehavior(gfx::ELIDE_MIDDLE);
244 popup_links_[link] = i - bubble_content.popup_items.begin();
245 layout->AddView(new Favicon(i->image, this, link));
246 layout->AddView(link);
247 bubble_content_empty = false;
251 const int indented_kSingleColumnSetId = 3;
252 // Insert a column set with greater indent.
253 views::ColumnSet* indented_single_column_set =
254 layout->AddColumnSet(indented_kSingleColumnSetId);
255 indented_single_column_set->AddPaddingColumn(0, views::kCheckboxIndent);
256 indented_single_column_set->AddColumn(GridLayout::LEADING, GridLayout::FILL,
257 1, GridLayout::USE_PREF, 0, 0);
259 const ContentSettingBubbleModel::RadioGroup& radio_group =
260 bubble_content.radio_group;
261 if (!radio_group.radio_items.empty()) {
262 if (!bubble_content_empty)
263 layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
264 for (ContentSettingBubbleModel::RadioItems::const_iterator i(
265 radio_group.radio_items.begin());
266 i != radio_group.radio_items.end(); ++i) {
267 views::RadioButton* radio =
268 new views::RadioButton(base::UTF8ToUTF16(*i), 0);
269 radio->SetEnabled(bubble_content.radio_group_enabled);
270 radio->set_listener(this);
271 radio_group_.push_back(radio);
272 layout->StartRow(0, indented_kSingleColumnSetId);
273 layout->AddView(radio);
274 bubble_content_empty = false;
276 DCHECK(!radio_group_.empty());
277 // Now that the buttons have been added to the view hierarchy, it's safe
278 // to call SetChecked() on them.
279 radio_group_[radio_group.default_item]->SetChecked(true);
282 // Layout code for the media device menus.
283 if (content_setting_bubble_model_->content_type() ==
284 CONTENT_SETTINGS_TYPE_MEDIASTREAM) {
285 const int kMediaMenuColumnSetId = 2;
286 views::ColumnSet* menu_column_set =
287 layout->AddColumnSet(kMediaMenuColumnSetId);
288 menu_column_set->AddPaddingColumn(0, views::kCheckboxIndent);
289 menu_column_set->AddColumn(GridLayout::LEADING, GridLayout::FILL, 0,
290 GridLayout::USE_PREF, 0, 0);
291 menu_column_set->AddPaddingColumn(
292 0, views::kRelatedControlHorizontalSpacing);
293 menu_column_set->AddColumn(GridLayout::LEADING, GridLayout::FILL, 1,
294 GridLayout::USE_PREF, 0, 0);
296 for (ContentSettingBubbleModel::MediaMenuMap::const_iterator i(
297 bubble_content.media_menus.begin());
298 i != bubble_content.media_menus.end(); ++i) {
299 if (!bubble_content_empty)
300 layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
301 layout->StartRow(0, kMediaMenuColumnSetId);
303 views::Label* label =
304 new views::Label(base::UTF8ToUTF16(i->second.label));
305 label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
307 views::MenuButton* menu_button = new views::MenuButton(
308 NULL, base::UTF8ToUTF16((i->second.selected_device.name)),
310 menu_button->SetStyle(views::Button::STYLE_BUTTON);
311 menu_button->SetHorizontalAlignment(gfx::ALIGN_LEFT);
312 menu_button->set_animate_on_state_change(false);
314 MediaMenuParts* menu_view = new MediaMenuParts(i->first);
315 menu_view->menu_model.reset(new ContentSettingMediaMenuModel(
317 content_setting_bubble_model_.get(),
318 base::Bind(&ContentSettingBubbleContents::UpdateMenuLabel,
319 base::Unretained(this))));
320 media_menus_[menu_button] = menu_view;
322 if (!menu_view->menu_model->GetItemCount()) {
323 // Show a "None available" title and grey out the menu when there are
324 // no available devices.
325 menu_button->SetText(
326 l10n_util::GetStringUTF16(IDS_MEDIA_MENU_NO_DEVICE_TITLE));
327 menu_button->SetEnabled(false);
330 // Disable the device selection when the website is managing the devices
332 if (i->second.disabled)
333 menu_button->SetEnabled(false);
335 layout->AddView(label);
336 layout->AddView(menu_button);
338 bubble_content_empty = false;
342 UpdateMenuButtonSizes(GetNativeTheme());
344 const gfx::FontList& domain_font =
345 ui::ResourceBundle::GetSharedInstance().GetFontList(
346 ui::ResourceBundle::BoldFont);
347 for (std::vector<ContentSettingBubbleModel::DomainList>::const_iterator i(
348 bubble_content.domain_lists.begin());
349 i != bubble_content.domain_lists.end(); ++i) {
350 layout->StartRow(0, kSingleColumnSetId);
351 views::Label* section_title = new views::Label(base::UTF8ToUTF16(i->title));
352 section_title->SetMultiLine(true);
353 section_title->SetHorizontalAlignment(gfx::ALIGN_LEFT);
354 layout->AddView(section_title, 1, 1, GridLayout::FILL, GridLayout::LEADING);
355 for (std::set<std::string>::const_iterator j = i->hosts.begin();
356 j != i->hosts.end(); ++j) {
357 layout->StartRow(0, indented_kSingleColumnSetId);
358 layout->AddView(new views::Label(base::UTF8ToUTF16(*j), domain_font));
360 bubble_content_empty = false;
363 if (!bubble_content.custom_link.empty()) {
365 new views::Link(base::UTF8ToUTF16(bubble_content.custom_link));
366 custom_link_->SetEnabled(bubble_content.custom_link_enabled);
367 custom_link_->set_listener(this);
368 if (!bubble_content_empty)
369 layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
370 layout->StartRow(0, kSingleColumnSetId);
371 layout->AddView(custom_link_);
372 bubble_content_empty = false;
375 const int kDoubleColumnSetId = 1;
376 views::ColumnSet* double_column_set =
377 layout->AddColumnSet(kDoubleColumnSetId);
378 if (!bubble_content_empty) {
379 layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
380 layout->StartRow(0, kSingleColumnSetId);
381 layout->AddView(new views::Separator(views::Separator::HORIZONTAL), 1, 1,
382 GridLayout::FILL, GridLayout::FILL);
383 layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
386 double_column_set->AddColumn(GridLayout::LEADING, GridLayout::CENTER, 1,
387 GridLayout::USE_PREF, 0, 0);
388 double_column_set->AddPaddingColumn(
389 0, views::kUnrelatedControlHorizontalSpacing);
390 double_column_set->AddColumn(GridLayout::TRAILING, GridLayout::CENTER, 0,
391 GridLayout::USE_PREF, 0, 0);
393 layout->StartRow(0, kDoubleColumnSetId);
395 new views::Link(base::UTF8ToUTF16(bubble_content.manage_link));
396 manage_link_->set_listener(this);
397 layout->AddView(manage_link_);
400 new views::LabelButton(this, l10n_util::GetStringUTF16(IDS_DONE));
401 close_button_->SetStyle(views::Button::STYLE_BUTTON);
402 layout->AddView(close_button_);
405 void ContentSettingBubbleContents::DidNavigateMainFrame(
406 const content::LoadCommittedDetails& details,
407 const content::FrameNavigateParams& params) {
408 // Content settings are based on the main frame, so if it switches then
410 content_setting_bubble_model_->OnDoneClicked();
411 GetWidget()->Close();
414 void ContentSettingBubbleContents::OnNativeThemeChanged(
415 const ui::NativeTheme* theme) {
416 views::BubbleDelegateView::OnNativeThemeChanged(theme);
417 UpdateMenuButtonSizes(theme);
420 void ContentSettingBubbleContents::ButtonPressed(views::Button* sender,
421 const ui::Event& event) {
422 RadioGroup::const_iterator i(
423 std::find(radio_group_.begin(), radio_group_.end(), sender));
424 if (i != radio_group_.end()) {
425 content_setting_bubble_model_->OnRadioClicked(i - radio_group_.begin());
428 DCHECK_EQ(sender, close_button_);
429 content_setting_bubble_model_->OnDoneClicked();
430 GetWidget()->Close();
433 void ContentSettingBubbleContents::LinkClicked(views::Link* source,
435 if (source == learn_more_link_) {
436 content_setting_bubble_model_->OnLearnMoreLinkClicked();
437 GetWidget()->Close();
440 if (source == custom_link_) {
441 content_setting_bubble_model_->OnCustomLinkClicked();
442 GetWidget()->Close();
445 if (source == manage_link_) {
446 GetWidget()->Close();
447 content_setting_bubble_model_->OnManageLinkClicked();
448 // CAREFUL: Showing the settings window activates it, which deactivates the
449 // info bubble, which causes it to close, which deletes us.
453 PopupLinks::const_iterator i(popup_links_.find(source));
454 DCHECK(i != popup_links_.end());
455 content_setting_bubble_model_->OnPopupClicked(i->second);
458 void ContentSettingBubbleContents::OnMenuButtonClicked(
460 const gfx::Point& point) {
461 MediaMenuPartsMap::iterator j(media_menus_.find(
462 static_cast<views::MenuButton*>(source)));
463 DCHECK(j != media_menus_.end());
464 menu_runner_.reset(new views::MenuRunner(j->second->menu_model.get(),
465 views::MenuRunner::HAS_MNEMONICS));
467 gfx::Point screen_location;
468 views::View::ConvertPointToScreen(j->first, &screen_location);
470 menu_runner_->RunMenuAt(source->GetWidget(),
472 gfx::Rect(screen_location, j->first->size()),
473 views::MENU_ANCHOR_TOPLEFT,
474 ui::MENU_SOURCE_NONE));
477 void ContentSettingBubbleContents::UpdateMenuButtonSizes(
478 const ui::NativeTheme* theme) {
479 const views::MenuConfig config = views::MenuConfig(theme);
480 const int margins = config.item_left_margin + config.check_width +
481 config.label_to_arrow_padding + config.arrow_width +
482 config.arrow_to_edge_padding;
484 // The preferred media menu size sort of copies the logic in
485 // MenuItemView::CalculateDimensions(). When this was using TextButton, it
486 // completely coincidentally matched the logic in MenuItemView. We now need
487 // to redo this manually.
489 for (MediaMenuPartsMap::const_iterator i = media_menus_.begin();
490 i != media_menus_.end(); ++i) {
491 for (int j = 0; j < i->second->menu_model->GetItemCount(); ++j) {
492 int string_width = gfx::GetStringWidth(
493 i->second->menu_model->GetLabelAt(j),
496 menu_width = std::max(menu_width, string_width);
500 // Make sure the width is at least kMinMediaMenuButtonWidth. The
501 // maximum width will be clamped by kMaxContentsWidth of the view.
502 menu_width = std::max(kMinMediaMenuButtonWidth, menu_width + margins);
504 for (MediaMenuPartsMap::const_iterator i = media_menus_.begin();
505 i != media_menus_.end(); ++i) {
506 i->first->SetMinSize(gfx::Size(menu_width, 0));
507 i->first->SetMaxSize(gfx::Size(menu_width, 0));