BookmarkManager: Fix 'new folder text field size changes on clicking it' issue.
[chromium-blink-merge.git] / chrome / browser / ui / views / content_setting_bubble_contents.cc
blob54e273abdfe6254e10c5d002fc88f0b15b1b022d
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"
7 #include <algorithm>
8 #include <set>
9 #include <string>
10 #include <vector>
12 #include "base/bind.h"
13 #include "base/stl_util.h"
14 #include "base/strings/utf_string_conversions.h"
15 #include "chrome/browser/plugins/plugin_finder.h"
16 #include "chrome/browser/plugins/plugin_metadata.h"
17 #include "chrome/browser/ui/content_settings/content_setting_bubble_model.h"
18 #include "chrome/browser/ui/content_settings/content_setting_media_menu_model.h"
19 #include "chrome/browser/ui/views/browser_dialogs.h"
20 #include "chrome/grit/generated_resources.h"
21 #include "components/content_settings/core/browser/host_content_settings_map.h"
22 #include "content/public/browser/plugin_service.h"
23 #include "content/public/browser/web_contents.h"
24 #include "ui/base/cursor/cursor.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_config.h"
37 #include "ui/views/controls/menu/menu_runner.h"
38 #include "ui/views/controls/separator.h"
39 #include "ui/views/layout/grid_layout.h"
40 #include "ui/views/layout/layout_constants.h"
41 #include "ui/views/native_cursor.h"
43 namespace {
45 // If we don't clamp the maximum width, then very long URLs and titles can make
46 // the bubble arbitrarily wide.
47 const int kMaxContentsWidth = 500;
49 // When we have multiline labels, we should set a minimum width lest we get very
50 // narrow bubbles with lots of line-wrapping.
51 const int kMinMultiLineContentsWidth = 250;
53 // The minimum width of the media menu buttons.
54 const int kMinMediaMenuButtonWidth = 150;
56 } // namespace
58 using content::PluginService;
59 using content::WebContents;
62 // ContentSettingBubbleContents::Favicon --------------------------------------
64 class ContentSettingBubbleContents::Favicon : public views::ImageView {
65 public:
66 Favicon(const gfx::Image& image,
67 ContentSettingBubbleContents* parent,
68 views::Link* link);
69 ~Favicon() override;
71 private:
72 // views::View overrides:
73 bool OnMousePressed(const ui::MouseEvent& event) override;
74 void OnMouseReleased(const ui::MouseEvent& event) override;
75 gfx::NativeCursor GetCursor(const ui::MouseEvent& event) override;
77 ContentSettingBubbleContents* parent_;
78 views::Link* link_;
81 ContentSettingBubbleContents::Favicon::Favicon(
82 const gfx::Image& image,
83 ContentSettingBubbleContents* parent,
84 views::Link* link)
85 : parent_(parent),
86 link_(link) {
87 SetImage(image.AsImageSkia());
90 ContentSettingBubbleContents::Favicon::~Favicon() {
93 bool ContentSettingBubbleContents::Favicon::OnMousePressed(
94 const ui::MouseEvent& event) {
95 return event.IsLeftMouseButton() || event.IsMiddleMouseButton();
98 void ContentSettingBubbleContents::Favicon::OnMouseReleased(
99 const ui::MouseEvent& event) {
100 if ((event.IsLeftMouseButton() || event.IsMiddleMouseButton()) &&
101 HitTestPoint(event.location())) {
102 parent_->LinkClicked(link_, event.flags());
106 gfx::NativeCursor ContentSettingBubbleContents::Favicon::GetCursor(
107 const ui::MouseEvent& event) {
108 return views::GetNativeHandCursor();
112 // ContentSettingBubbleContents::MediaMenuParts -------------------------------
114 struct ContentSettingBubbleContents::MediaMenuParts {
115 explicit MediaMenuParts(content::MediaStreamType type);
116 ~MediaMenuParts();
118 content::MediaStreamType type;
119 scoped_ptr<ui::SimpleMenuModel> menu_model;
121 private:
122 DISALLOW_COPY_AND_ASSIGN(MediaMenuParts);
125 ContentSettingBubbleContents::MediaMenuParts::MediaMenuParts(
126 content::MediaStreamType type)
127 : type(type) {}
129 ContentSettingBubbleContents::MediaMenuParts::~MediaMenuParts() {}
131 // ContentSettingBubbleContents -----------------------------------------------
133 ContentSettingBubbleContents::ContentSettingBubbleContents(
134 ContentSettingBubbleModel* content_setting_bubble_model,
135 content::WebContents* web_contents,
136 views::View* anchor_view,
137 views::BubbleBorder::Arrow arrow)
138 : content::WebContentsObserver(web_contents),
139 BubbleDelegateView(anchor_view, arrow),
140 content_setting_bubble_model_(content_setting_bubble_model),
141 custom_link_(NULL),
142 manage_link_(NULL),
143 learn_more_link_(NULL),
144 close_button_(NULL) {
145 // Compensate for built-in vertical padding in the anchor view's image.
146 set_anchor_view_insets(gfx::Insets(5, 0, 5, 0));
149 ContentSettingBubbleContents::~ContentSettingBubbleContents() {
150 STLDeleteValues(&media_menus_);
153 gfx::Size ContentSettingBubbleContents::GetPreferredSize() const {
154 gfx::Size preferred_size(views::View::GetPreferredSize());
155 int preferred_width =
156 (!content_setting_bubble_model_->bubble_content().domain_lists.empty() &&
157 (kMinMultiLineContentsWidth > preferred_size.width())) ?
158 kMinMultiLineContentsWidth : preferred_size.width();
159 preferred_size.set_width(std::min(preferred_width, kMaxContentsWidth));
160 return preferred_size;
163 void ContentSettingBubbleContents::UpdateMenuLabel(
164 content::MediaStreamType type,
165 const std::string& label) {
166 for (MediaMenuPartsMap::const_iterator it = media_menus_.begin();
167 it != media_menus_.end(); ++it) {
168 if (it->second->type == type) {
169 it->first->SetText(base::UTF8ToUTF16(label));
170 return;
173 NOTREACHED();
176 void ContentSettingBubbleContents::Init() {
177 using views::GridLayout;
179 GridLayout* layout = new views::GridLayout(this);
180 SetLayoutManager(layout);
182 const int kSingleColumnSetId = 0;
183 views::ColumnSet* column_set = layout->AddColumnSet(kSingleColumnSetId);
184 column_set->AddColumn(GridLayout::LEADING, GridLayout::FILL, 1,
185 GridLayout::USE_PREF, 0, 0);
186 column_set->AddPaddingColumn(0, views::kRelatedControlHorizontalSpacing);
187 column_set->AddColumn(GridLayout::LEADING, GridLayout::FILL, 1,
188 GridLayout::USE_PREF, 0, 0);
190 const ContentSettingBubbleModel::BubbleContent& bubble_content =
191 content_setting_bubble_model_->bubble_content();
192 bool bubble_content_empty = true;
194 if (!bubble_content.title.empty()) {
195 views::Label* title_label = new views::Label(base::UTF8ToUTF16(
196 bubble_content.title));
197 title_label->SetMultiLine(true);
198 title_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
199 layout->StartRow(0, kSingleColumnSetId);
200 layout->AddView(title_label);
201 bubble_content_empty = false;
204 if (!bubble_content.learn_more_link.empty()) {
205 learn_more_link_ =
206 new views::Link(base::UTF8ToUTF16(bubble_content.learn_more_link));
207 learn_more_link_->set_listener(this);
208 learn_more_link_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
209 layout->AddView(learn_more_link_);
210 bubble_content_empty = false;
213 // Layout for the item list (blocked plugins and popups).
214 if (!bubble_content.list_items.empty()) {
215 const int kItemListColumnSetId = 2;
216 views::ColumnSet* item_list_column_set =
217 layout->AddColumnSet(kItemListColumnSetId);
218 item_list_column_set->AddColumn(GridLayout::LEADING, GridLayout::FILL, 0,
219 GridLayout::USE_PREF, 0, 0);
220 item_list_column_set->AddPaddingColumn(
221 0, views::kRelatedControlHorizontalSpacing);
222 item_list_column_set->AddColumn(GridLayout::LEADING, GridLayout::FILL, 1,
223 GridLayout::USE_PREF, 0, 0);
225 int row = 0;
226 for (const ContentSettingBubbleModel::ListItem& list_item :
227 bubble_content.list_items) {
228 if (!bubble_content_empty)
229 layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
230 layout->StartRow(0, kItemListColumnSetId);
231 if (list_item.has_link) {
232 views::Link* link = new views::Link(base::UTF8ToUTF16(list_item.title));
233 link->set_listener(this);
234 link->SetElideBehavior(gfx::ELIDE_MIDDLE);
235 list_item_links_[link] = row;
236 layout->AddView(new Favicon(list_item.image, this, link));
237 layout->AddView(link);
238 } else {
239 views::ImageView* icon = new views::ImageView();
240 icon->SetImage(list_item.image.AsImageSkia());
241 layout->AddView(icon);
242 layout->AddView(new views::Label(base::UTF8ToUTF16(list_item.title)));
244 row++;
245 bubble_content_empty = false;
249 const int indented_kSingleColumnSetId = 3;
250 // Insert a column set with greater indent.
251 views::ColumnSet* indented_single_column_set =
252 layout->AddColumnSet(indented_kSingleColumnSetId);
253 indented_single_column_set->AddPaddingColumn(0, views::kCheckboxIndent);
254 indented_single_column_set->AddColumn(GridLayout::LEADING, GridLayout::FILL,
255 1, GridLayout::USE_PREF, 0, 0);
257 const ContentSettingBubbleModel::RadioGroup& radio_group =
258 bubble_content.radio_group;
259 if (!radio_group.radio_items.empty()) {
260 if (!bubble_content_empty)
261 layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
262 for (ContentSettingBubbleModel::RadioItems::const_iterator i(
263 radio_group.radio_items.begin());
264 i != radio_group.radio_items.end(); ++i) {
265 views::RadioButton* radio =
266 new views::RadioButton(base::UTF8ToUTF16(*i), 0);
267 radio->SetEnabled(bubble_content.radio_group_enabled);
268 radio->set_listener(this);
269 radio_group_.push_back(radio);
270 layout->StartRow(0, indented_kSingleColumnSetId);
271 layout->AddView(radio);
272 bubble_content_empty = false;
274 DCHECK(!radio_group_.empty());
275 // Now that the buttons have been added to the view hierarchy, it's safe
276 // to call SetChecked() on them.
277 radio_group_[radio_group.default_item]->SetChecked(true);
280 // Layout code for the media device menus.
281 if (content_setting_bubble_model_->content_type() ==
282 CONTENT_SETTINGS_TYPE_MEDIASTREAM) {
283 const int kMediaMenuColumnSetId = 4;
284 views::ColumnSet* menu_column_set =
285 layout->AddColumnSet(kMediaMenuColumnSetId);
286 menu_column_set->AddPaddingColumn(0, views::kCheckboxIndent);
287 menu_column_set->AddColumn(GridLayout::LEADING, GridLayout::FILL, 0,
288 GridLayout::USE_PREF, 0, 0);
289 menu_column_set->AddPaddingColumn(
290 0, views::kRelatedControlHorizontalSpacing);
291 menu_column_set->AddColumn(GridLayout::LEADING, GridLayout::FILL, 1,
292 GridLayout::USE_PREF, 0, 0);
294 for (ContentSettingBubbleModel::MediaMenuMap::const_iterator i(
295 bubble_content.media_menus.begin());
296 i != bubble_content.media_menus.end(); ++i) {
297 if (!bubble_content_empty)
298 layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
299 layout->StartRow(0, kMediaMenuColumnSetId);
301 views::Label* label =
302 new views::Label(base::UTF8ToUTF16(i->second.label));
303 label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
305 views::MenuButton* menu_button = new views::MenuButton(
306 NULL, base::UTF8ToUTF16((i->second.selected_device.name)),
307 this, true);
308 menu_button->SetStyle(views::Button::STYLE_BUTTON);
309 menu_button->SetHorizontalAlignment(gfx::ALIGN_LEFT);
310 menu_button->set_animate_on_state_change(false);
312 MediaMenuParts* menu_view = new MediaMenuParts(i->first);
313 menu_view->menu_model.reset(new ContentSettingMediaMenuModel(
314 i->first,
315 content_setting_bubble_model_.get(),
316 base::Bind(&ContentSettingBubbleContents::UpdateMenuLabel,
317 base::Unretained(this))));
318 media_menus_[menu_button] = menu_view;
320 if (!menu_view->menu_model->GetItemCount()) {
321 // Show a "None available" title and grey out the menu when there are
322 // no available devices.
323 menu_button->SetText(
324 l10n_util::GetStringUTF16(IDS_MEDIA_MENU_NO_DEVICE_TITLE));
325 menu_button->SetEnabled(false);
328 // Disable the device selection when the website is managing the devices
329 // itself.
330 if (i->second.disabled)
331 menu_button->SetEnabled(false);
333 layout->AddView(label);
334 layout->AddView(menu_button);
336 bubble_content_empty = false;
340 UpdateMenuButtonSizes(GetNativeTheme());
342 const gfx::FontList& domain_font =
343 ui::ResourceBundle::GetSharedInstance().GetFontList(
344 ui::ResourceBundle::BoldFont);
345 for (std::vector<ContentSettingBubbleModel::DomainList>::const_iterator i(
346 bubble_content.domain_lists.begin());
347 i != bubble_content.domain_lists.end(); ++i) {
348 layout->StartRow(0, kSingleColumnSetId);
349 views::Label* section_title = new views::Label(base::UTF8ToUTF16(i->title));
350 section_title->SetMultiLine(true);
351 section_title->SetHorizontalAlignment(gfx::ALIGN_LEFT);
352 layout->AddView(section_title, 1, 1, GridLayout::FILL, GridLayout::LEADING);
353 for (std::set<std::string>::const_iterator j = i->hosts.begin();
354 j != i->hosts.end(); ++j) {
355 layout->StartRow(0, indented_kSingleColumnSetId);
356 layout->AddView(new views::Label(base::UTF8ToUTF16(*j), domain_font));
358 bubble_content_empty = false;
361 if (!bubble_content.custom_link.empty()) {
362 custom_link_ =
363 new views::Link(base::UTF8ToUTF16(bubble_content.custom_link));
364 custom_link_->SetEnabled(bubble_content.custom_link_enabled);
365 custom_link_->set_listener(this);
366 if (!bubble_content_empty)
367 layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
368 layout->StartRow(0, kSingleColumnSetId);
369 layout->AddView(custom_link_);
370 bubble_content_empty = false;
373 const int kDoubleColumnSetId = 1;
374 views::ColumnSet* double_column_set =
375 layout->AddColumnSet(kDoubleColumnSetId);
376 if (!bubble_content_empty) {
377 layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
378 layout->StartRow(0, kSingleColumnSetId);
379 layout->AddView(new views::Separator(views::Separator::HORIZONTAL), 1, 1,
380 GridLayout::FILL, GridLayout::FILL);
381 layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
384 double_column_set->AddColumn(GridLayout::LEADING, GridLayout::CENTER, 1,
385 GridLayout::USE_PREF, 0, 0);
386 double_column_set->AddPaddingColumn(
387 0, views::kUnrelatedControlHorizontalSpacing);
388 double_column_set->AddColumn(GridLayout::TRAILING, GridLayout::CENTER, 0,
389 GridLayout::USE_PREF, 0, 0);
391 layout->StartRow(0, kDoubleColumnSetId);
392 manage_link_ =
393 new views::Link(base::UTF8ToUTF16(bubble_content.manage_link));
394 manage_link_->set_listener(this);
395 layout->AddView(manage_link_);
397 close_button_ =
398 new views::LabelButton(this, l10n_util::GetStringUTF16(IDS_DONE));
399 close_button_->SetStyle(views::Button::STYLE_BUTTON);
400 layout->AddView(close_button_);
403 void ContentSettingBubbleContents::DidNavigateMainFrame(
404 const content::LoadCommittedDetails& details,
405 const content::FrameNavigateParams& params) {
406 // Content settings are based on the main frame, so if it switches then
407 // close up shop.
408 content_setting_bubble_model_->OnDoneClicked();
409 GetWidget()->Close();
412 void ContentSettingBubbleContents::OnNativeThemeChanged(
413 const ui::NativeTheme* theme) {
414 views::BubbleDelegateView::OnNativeThemeChanged(theme);
415 UpdateMenuButtonSizes(theme);
418 void ContentSettingBubbleContents::ButtonPressed(views::Button* sender,
419 const ui::Event& event) {
420 RadioGroup::const_iterator i(
421 std::find(radio_group_.begin(), radio_group_.end(), sender));
422 if (i != radio_group_.end()) {
423 content_setting_bubble_model_->OnRadioClicked(i - radio_group_.begin());
424 return;
426 DCHECK_EQ(sender, close_button_);
427 content_setting_bubble_model_->OnDoneClicked();
428 GetWidget()->Close();
431 void ContentSettingBubbleContents::LinkClicked(views::Link* source,
432 int event_flags) {
433 if (source == learn_more_link_) {
434 content_setting_bubble_model_->OnLearnMoreLinkClicked();
435 GetWidget()->Close();
436 return;
438 if (source == custom_link_) {
439 content_setting_bubble_model_->OnCustomLinkClicked();
440 GetWidget()->Close();
441 return;
443 if (source == manage_link_) {
444 GetWidget()->Close();
445 content_setting_bubble_model_->OnManageLinkClicked();
446 // CAREFUL: Showing the settings window activates it, which deactivates the
447 // info bubble, which causes it to close, which deletes us.
448 return;
451 ListItemLinks::const_iterator i(list_item_links_.find(source));
452 DCHECK(i != list_item_links_.end());
453 content_setting_bubble_model_->OnListItemClicked(i->second);
456 void ContentSettingBubbleContents::OnMenuButtonClicked(
457 views::View* source,
458 const gfx::Point& point) {
459 MediaMenuPartsMap::iterator j(media_menus_.find(
460 static_cast<views::MenuButton*>(source)));
461 DCHECK(j != media_menus_.end());
462 menu_runner_.reset(new views::MenuRunner(j->second->menu_model.get(),
463 views::MenuRunner::HAS_MNEMONICS));
465 gfx::Point screen_location;
466 views::View::ConvertPointToScreen(j->first, &screen_location);
467 ignore_result(
468 menu_runner_->RunMenuAt(source->GetWidget(),
469 j->first,
470 gfx::Rect(screen_location, j->first->size()),
471 views::MENU_ANCHOR_TOPLEFT,
472 ui::MENU_SOURCE_NONE));
475 void ContentSettingBubbleContents::UpdateMenuButtonSizes(
476 const ui::NativeTheme* theme) {
477 const views::MenuConfig config = views::MenuConfig(theme);
478 const int margins = config.item_left_margin + config.check_width +
479 config.label_to_arrow_padding + config.arrow_width +
480 config.arrow_to_edge_padding;
482 // The preferred media menu size sort of copies the logic in
483 // MenuItemView::CalculateDimensions(). When this was using TextButton, it
484 // completely coincidentally matched the logic in MenuItemView. We now need
485 // to redo this manually.
486 int menu_width = 0;
487 for (MediaMenuPartsMap::const_iterator i = media_menus_.begin();
488 i != media_menus_.end(); ++i) {
489 for (int j = 0; j < i->second->menu_model->GetItemCount(); ++j) {
490 int string_width = gfx::GetStringWidth(
491 i->second->menu_model->GetLabelAt(j),
492 config.font_list);
494 menu_width = std::max(menu_width, string_width);
498 // Make sure the width is at least kMinMediaMenuButtonWidth. The
499 // maximum width will be clamped by kMaxContentsWidth of the view.
500 menu_width = std::max(kMinMediaMenuButtonWidth, menu_width + margins);
502 for (MediaMenuPartsMap::const_iterator i = media_menus_.begin();
503 i != media_menus_.end(); ++i) {
504 i->first->SetMinSize(gfx::Size(menu_width, 0));
505 i->first->SetMaxSize(gfx::Size(menu_width, 0));