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/views/controls/button/label_button.h"
30 #include "ui/views/controls/button/menu_button.h"
31 #include "ui/views/controls/button/radio_button.h"
32 #include "ui/views/controls/image_view.h"
33 #include "ui/views/controls/label.h"
34 #include "ui/views/controls/link.h"
35 #include "ui/views/controls/menu/menu.h"
36 #include "ui/views/controls/menu/menu_runner.h"
37 #include "ui/views/controls/separator.h"
38 #include "ui/views/layout/grid_layout.h"
39 #include "ui/views/layout/layout_constants.h"
42 #include "ui/base/cursor/cursor.h"
47 // If we don't clamp the maximum width, then very long URLs and titles can make
48 // the bubble arbitrarily wide.
49 const int kMaxContentsWidth
= 500;
51 // When we have multiline labels, we should set a minimum width lest we get very
52 // narrow bubbles with lots of line-wrapping.
53 const int kMinMultiLineContentsWidth
= 250;
55 // The minimum width of the media menu buttons.
56 const int kMinMediaMenuButtonWidth
= 100;
60 using content::PluginService
;
61 using content::WebContents
;
64 // ContentSettingBubbleContents::Favicon --------------------------------------
66 class ContentSettingBubbleContents::Favicon
: public views::ImageView
{
68 Favicon(const gfx::Image
& image
,
69 ContentSettingBubbleContents
* parent
,
74 // views::View overrides:
75 virtual bool OnMousePressed(const ui::MouseEvent
& event
) OVERRIDE
;
76 virtual void OnMouseReleased(const ui::MouseEvent
& event
) OVERRIDE
;
77 virtual gfx::NativeCursor
GetCursor(const ui::MouseEvent
& event
) OVERRIDE
;
79 ContentSettingBubbleContents
* parent_
;
83 ContentSettingBubbleContents::Favicon::Favicon(
84 const gfx::Image
& image
,
85 ContentSettingBubbleContents
* parent
,
89 SetImage(image
.AsImageSkia());
92 ContentSettingBubbleContents::Favicon::~Favicon() {
95 bool ContentSettingBubbleContents::Favicon::OnMousePressed(
96 const ui::MouseEvent
& event
) {
97 return event
.IsLeftMouseButton() || event
.IsMiddleMouseButton();
100 void ContentSettingBubbleContents::Favicon::OnMouseReleased(
101 const ui::MouseEvent
& event
) {
102 if ((event
.IsLeftMouseButton() || event
.IsMiddleMouseButton()) &&
103 HitTestPoint(event
.location())) {
104 parent_
->LinkClicked(link_
, event
.flags());
108 gfx::NativeCursor
ContentSettingBubbleContents::Favicon::GetCursor(
109 const ui::MouseEvent
& event
) {
110 #if defined(USE_AURA)
111 return ui::kCursorHand
;
112 #elif defined(OS_WIN)
113 static HCURSOR g_hand_cursor
= LoadCursor(NULL
, IDC_HAND
);
114 return g_hand_cursor
;
119 // ContentSettingBubbleContents::MediaMenuParts -------------------------------
121 struct ContentSettingBubbleContents::MediaMenuParts
{
122 explicit MediaMenuParts(content::MediaStreamType type
);
125 content::MediaStreamType type
;
126 scoped_ptr
<ui::SimpleMenuModel
> menu_model
;
129 DISALLOW_COPY_AND_ASSIGN(MediaMenuParts
);
132 ContentSettingBubbleContents::MediaMenuParts::MediaMenuParts(
133 content::MediaStreamType type
)
136 ContentSettingBubbleContents::MediaMenuParts::~MediaMenuParts() {}
138 // ContentSettingBubbleContents -----------------------------------------------
140 ContentSettingBubbleContents::ContentSettingBubbleContents(
141 ContentSettingBubbleModel
* content_setting_bubble_model
,
142 views::View
* anchor_view
,
143 views::BubbleBorder::Arrow arrow
)
144 : BubbleDelegateView(anchor_view
, arrow
),
145 content_setting_bubble_model_(content_setting_bubble_model
),
148 close_button_(NULL
) {
149 // Compensate for built-in vertical padding in the anchor view's image.
150 set_anchor_view_insets(gfx::Insets(5, 0, 5, 0));
153 ContentSettingBubbleContents::~ContentSettingBubbleContents() {
154 STLDeleteValues(&media_menus_
);
157 gfx::Size
ContentSettingBubbleContents::GetPreferredSize() {
158 gfx::Size
preferred_size(views::View::GetPreferredSize());
159 int preferred_width
=
160 (!content_setting_bubble_model_
->bubble_content().domain_lists
.empty() &&
161 (kMinMultiLineContentsWidth
> preferred_size
.width())) ?
162 kMinMultiLineContentsWidth
: preferred_size
.width();
163 preferred_size
.set_width(std::min(preferred_width
, kMaxContentsWidth
));
164 return preferred_size
;
167 void ContentSettingBubbleContents::UpdateMenuLabel(
168 content::MediaStreamType type
,
169 const std::string
& label
) {
170 for (MediaMenuPartsMap::const_iterator it
= media_menus_
.begin();
171 it
!= media_menus_
.end(); ++it
) {
172 if (it
->second
->type
== type
) {
173 it
->first
->SetText(base::UTF8ToUTF16(label
));
180 void ContentSettingBubbleContents::Init() {
181 using views::GridLayout
;
183 GridLayout
* layout
= new views::GridLayout(this);
184 SetLayoutManager(layout
);
186 const int kSingleColumnSetId
= 0;
187 views::ColumnSet
* column_set
= layout
->AddColumnSet(kSingleColumnSetId
);
188 column_set
->AddColumn(GridLayout::LEADING
, GridLayout::FILL
, 1,
189 GridLayout::USE_PREF
, 0, 0);
191 const ContentSettingBubbleModel::BubbleContent
& bubble_content
=
192 content_setting_bubble_model_
->bubble_content();
193 bool bubble_content_empty
= true;
195 if (!bubble_content
.title
.empty()) {
196 views::Label
* title_label
= new views::Label(base::UTF8ToUTF16(
197 bubble_content
.title
));
198 title_label
->SetMultiLine(true);
199 title_label
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
200 layout
->StartRow(0, kSingleColumnSetId
);
201 layout
->AddView(title_label
);
202 bubble_content_empty
= false;
205 if (content_setting_bubble_model_
->content_type() ==
206 CONTENT_SETTINGS_TYPE_POPUPS
) {
207 const int kPopupColumnSetId
= 2;
208 views::ColumnSet
* popup_column_set
=
209 layout
->AddColumnSet(kPopupColumnSetId
);
210 popup_column_set
->AddColumn(GridLayout::LEADING
, GridLayout::FILL
, 0,
211 GridLayout::USE_PREF
, 0, 0);
212 popup_column_set
->AddPaddingColumn(
213 0, views::kRelatedControlHorizontalSpacing
);
214 popup_column_set
->AddColumn(GridLayout::LEADING
, GridLayout::FILL
, 1,
215 GridLayout::USE_PREF
, 0, 0);
217 for (std::vector
<ContentSettingBubbleModel::PopupItem
>::const_iterator
218 i(bubble_content
.popup_items
.begin());
219 i
!= bubble_content
.popup_items
.end(); ++i
) {
220 if (!bubble_content_empty
)
221 layout
->AddPaddingRow(0, views::kRelatedControlVerticalSpacing
);
222 layout
->StartRow(0, kPopupColumnSetId
);
224 views::Link
* link
= new views::Link(base::UTF8ToUTF16(i
->title
));
225 link
->set_listener(this);
226 link
->SetElideBehavior(views::Label::ELIDE_IN_MIDDLE
);
227 popup_links_
[link
] = i
- bubble_content
.popup_items
.begin();
228 layout
->AddView(new Favicon(i
->image
, this, link
));
229 layout
->AddView(link
);
230 bubble_content_empty
= false;
234 const int indented_kSingleColumnSetId
= 3;
235 // Insert a column set with greater indent.
236 views::ColumnSet
* indented_single_column_set
=
237 layout
->AddColumnSet(indented_kSingleColumnSetId
);
238 indented_single_column_set
->AddPaddingColumn(0, views::kCheckboxIndent
);
239 indented_single_column_set
->AddColumn(GridLayout::LEADING
, GridLayout::FILL
,
240 1, GridLayout::USE_PREF
, 0, 0);
242 const ContentSettingBubbleModel::RadioGroup
& radio_group
=
243 bubble_content
.radio_group
;
244 if (!radio_group
.radio_items
.empty()) {
245 if (!bubble_content_empty
)
246 layout
->AddPaddingRow(0, views::kRelatedControlVerticalSpacing
);
247 for (ContentSettingBubbleModel::RadioItems::const_iterator
i(
248 radio_group
.radio_items
.begin());
249 i
!= radio_group
.radio_items
.end(); ++i
) {
250 views::RadioButton
* radio
=
251 new views::RadioButton(base::UTF8ToUTF16(*i
), 0);
252 radio
->SetEnabled(bubble_content
.radio_group_enabled
);
253 radio
->set_listener(this);
254 radio_group_
.push_back(radio
);
255 layout
->StartRow(0, indented_kSingleColumnSetId
);
256 layout
->AddView(radio
);
257 bubble_content_empty
= false;
259 DCHECK(!radio_group_
.empty());
260 // Now that the buttons have been added to the view hierarchy, it's safe
261 // to call SetChecked() on them.
262 radio_group_
[radio_group
.default_item
]->SetChecked(true);
265 // Layout code for the media device menus.
266 if (content_setting_bubble_model_
->content_type() ==
267 CONTENT_SETTINGS_TYPE_MEDIASTREAM
) {
268 const int kMediaMenuColumnSetId
= 2;
269 views::ColumnSet
* menu_column_set
=
270 layout
->AddColumnSet(kMediaMenuColumnSetId
);
271 menu_column_set
->AddPaddingColumn(0, views::kCheckboxIndent
);
272 menu_column_set
->AddColumn(GridLayout::LEADING
, GridLayout::FILL
, 0,
273 GridLayout::USE_PREF
, 0, 0);
274 menu_column_set
->AddPaddingColumn(
275 0, views::kRelatedControlHorizontalSpacing
);
276 menu_column_set
->AddColumn(GridLayout::LEADING
, GridLayout::FILL
, 1,
277 GridLayout::USE_PREF
, 0, 0);
280 for (ContentSettingBubbleModel::MediaMenuMap::const_iterator
i(
281 bubble_content
.media_menus
.begin());
282 i
!= bubble_content
.media_menus
.end(); ++i
) {
283 if (!bubble_content_empty
)
284 layout
->AddPaddingRow(0, views::kRelatedControlVerticalSpacing
);
285 layout
->StartRow(0, kMediaMenuColumnSetId
);
287 views::Label
* label
=
288 new views::Label(base::UTF8ToUTF16(i
->second
.label
));
289 label
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
291 views::MenuButton
* menu_button
= new views::MenuButton(
292 NULL
, base::UTF8ToUTF16((i
->second
.selected_device
.name
)),
294 menu_button
->set_alignment(views::TextButton::ALIGN_LEFT
);
295 menu_button
->set_border(
296 new views::TextButtonNativeThemeBorder(menu_button
));
297 menu_button
->set_animate_on_state_change(false);
299 MediaMenuParts
* menu_view
= new MediaMenuParts(i
->first
);
300 menu_view
->menu_model
.reset(new ContentSettingMediaMenuModel(
302 content_setting_bubble_model_
.get(),
303 base::Bind(&ContentSettingBubbleContents::UpdateMenuLabel
,
304 base::Unretained(this))));
305 media_menus_
[menu_button
] = menu_view
;
307 if (!menu_view
->menu_model
->GetItemCount()) {
308 // Show a "None available" title and grey out the menu when there are
309 // no available devices.
310 menu_button
->SetText(
311 l10n_util::GetStringUTF16(IDS_MEDIA_MENU_NO_DEVICE_TITLE
));
312 menu_button
->SetEnabled(false);
315 // Disable the device selection when the website is managing the devices
317 if (i
->second
.disabled
)
318 menu_button
->SetEnabled(false);
320 // Use the longest width of the menus as the width of the menu buttons.
321 menu_width
= std::max(menu_width
,
322 GetPreferredMediaMenuWidth(
323 menu_button
, menu_view
->menu_model
.get()));
325 layout
->AddView(label
);
326 layout
->AddView(menu_button
);
328 bubble_content_empty
= false;
331 // Make sure the width is at least kMinMediaMenuButtonWidth. The
332 // maximum width will be clamped by kMaxContentsWidth of the view.
333 menu_width
= std::max(kMinMediaMenuButtonWidth
, menu_width
);
335 // Set all the menu buttons to the width we calculated above.
336 for (MediaMenuPartsMap::const_iterator i
= media_menus_
.begin();
337 i
!= media_menus_
.end(); ++i
) {
338 i
->first
->set_min_width(menu_width
);
339 i
->first
->set_max_width(menu_width
);
343 const gfx::FontList
& domain_font
=
344 ui::ResourceBundle::GetSharedInstance().GetFontList(
345 ui::ResourceBundle::BoldFont
);
346 for (std::vector
<ContentSettingBubbleModel::DomainList
>::const_iterator
i(
347 bubble_content
.domain_lists
.begin());
348 i
!= bubble_content
.domain_lists
.end(); ++i
) {
349 layout
->StartRow(0, kSingleColumnSetId
);
350 views::Label
* section_title
= new views::Label(base::UTF8ToUTF16(i
->title
));
351 section_title
->SetMultiLine(true);
352 section_title
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
353 layout
->AddView(section_title
, 1, 1, GridLayout::FILL
, GridLayout::LEADING
);
354 for (std::set
<std::string
>::const_iterator j
= i
->hosts
.begin();
355 j
!= i
->hosts
.end(); ++j
) {
356 layout
->StartRow(0, indented_kSingleColumnSetId
);
357 layout
->AddView(new views::Label(base::UTF8ToUTF16(*j
), domain_font
));
359 bubble_content_empty
= false;
362 if (!bubble_content
.custom_link
.empty()) {
364 new views::Link(base::UTF8ToUTF16(bubble_content
.custom_link
));
365 custom_link_
->SetEnabled(bubble_content
.custom_link_enabled
);
366 custom_link_
->set_listener(this);
367 if (!bubble_content_empty
)
368 layout
->AddPaddingRow(0, views::kRelatedControlVerticalSpacing
);
369 layout
->StartRow(0, kSingleColumnSetId
);
370 layout
->AddView(custom_link_
);
371 bubble_content_empty
= false;
374 const int kDoubleColumnSetId
= 1;
375 views::ColumnSet
* double_column_set
=
376 layout
->AddColumnSet(kDoubleColumnSetId
);
377 if (!bubble_content_empty
) {
378 layout
->AddPaddingRow(0, views::kRelatedControlVerticalSpacing
);
379 layout
->StartRow(0, kSingleColumnSetId
);
380 layout
->AddView(new views::Separator(views::Separator::HORIZONTAL
), 1, 1,
381 GridLayout::FILL
, GridLayout::FILL
);
382 layout
->AddPaddingRow(0, views::kRelatedControlVerticalSpacing
);
385 double_column_set
->AddColumn(GridLayout::LEADING
, GridLayout::CENTER
, 1,
386 GridLayout::USE_PREF
, 0, 0);
387 double_column_set
->AddPaddingColumn(
388 0, views::kUnrelatedControlHorizontalSpacing
);
389 double_column_set
->AddColumn(GridLayout::TRAILING
, GridLayout::CENTER
, 0,
390 GridLayout::USE_PREF
, 0, 0);
392 layout
->StartRow(0, kDoubleColumnSetId
);
394 new views::Link(base::UTF8ToUTF16(bubble_content
.manage_link
));
395 manage_link_
->set_listener(this);
396 layout
->AddView(manage_link_
);
399 new views::LabelButton(this, l10n_util::GetStringUTF16(IDS_DONE
));
400 close_button_
->SetStyle(views::Button::STYLE_BUTTON
);
401 layout
->AddView(close_button_
);
404 void ContentSettingBubbleContents::ButtonPressed(views::Button
* sender
,
405 const ui::Event
& event
) {
406 RadioGroup::const_iterator
i(
407 std::find(radio_group_
.begin(), radio_group_
.end(), sender
));
408 if (i
!= radio_group_
.end()) {
409 content_setting_bubble_model_
->OnRadioClicked(i
- radio_group_
.begin());
412 DCHECK_EQ(sender
, close_button_
);
413 content_setting_bubble_model_
->OnDoneClicked();
417 void ContentSettingBubbleContents::LinkClicked(views::Link
* source
,
419 if (source
== custom_link_
) {
420 content_setting_bubble_model_
->OnCustomLinkClicked();
424 if (source
== manage_link_
) {
426 content_setting_bubble_model_
->OnManageLinkClicked();
427 // CAREFUL: Showing the settings window activates it, which deactivates the
428 // info bubble, which causes it to close, which deletes us.
432 PopupLinks::const_iterator
i(popup_links_
.find(source
));
433 DCHECK(i
!= popup_links_
.end());
434 content_setting_bubble_model_
->OnPopupClicked(i
->second
);
437 void ContentSettingBubbleContents::OnMenuButtonClicked(
439 const gfx::Point
& point
) {
440 MediaMenuPartsMap::iterator
j(media_menus_
.find(
441 static_cast<views::MenuButton
*>(source
)));
442 DCHECK(j
!= media_menus_
.end());
443 menu_runner_
.reset(new views::MenuRunner(j
->second
->menu_model
.get()));
445 gfx::Point screen_location
;
446 views::View::ConvertPointToScreen(j
->first
, &screen_location
);
447 ignore_result(menu_runner_
->RunMenuAt(
450 gfx::Rect(screen_location
, j
->first
->size()),
451 views::MenuItemView::TOPLEFT
,
452 ui::MENU_SOURCE_NONE
,
453 views::MenuRunner::HAS_MNEMONICS
));
456 int ContentSettingBubbleContents::GetPreferredMediaMenuWidth(
457 views::MenuButton
* button
,
458 ui::SimpleMenuModel
* menu_model
) {
459 base::string16 title
= button
->text();
461 int width
= button
->GetPreferredSize().width();
462 for (int i
= 0; i
< menu_model
->GetItemCount(); ++i
) {
463 button
->SetText(menu_model
->GetLabelAt(i
));
464 width
= std::max(width
, button
->GetPreferredSize().width());
467 // Recover the title for the menu button.
468 button
->SetText(title
);