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