content_setting_bubble_contents.cc revision 3551c9c881056c480085172ff9840cab31610854
15738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project// Copyright (c) 2012 The Chromium Authors. All rights reserved.
25738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project// Use of this source code is governed by a BSD-style license that can be
35738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project// found in the LICENSE file.
45738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project
55738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project#include "chrome/browser/ui/views/content_setting_bubble_contents.h"
65738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project
75738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project#include <algorithm>
85738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project#include <set>
95738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project#include <string>
105738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project#include <vector>
115738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project
125738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project#include "base/bind.h"
135738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project#include "base/stl_util.h"
145738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project#include "base/strings/utf_string_conversions.h"
155738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project#include "chrome/browser/content_settings/host_content_settings_map.h"
165738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project#include "chrome/browser/plugins/plugin_finder.h"
175738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project#include "chrome/browser/plugins/plugin_metadata.h"
185738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project#include "chrome/browser/ui/content_settings/content_setting_bubble_model.h"
195738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project#include "chrome/browser/ui/content_settings/content_setting_media_menu_model.h"
205738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project#include "chrome/browser/ui/views/browser_dialogs.h"
215738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project#include "content/public/browser/notification_source.h"
225738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project#include "content/public/browser/notification_types.h"
235738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project#include "content/public/browser/plugin_service.h"
245cd8bff2dd0337cb52bf48f312e3d2d55a8882fbMike J. Chen#include "content/public/browser/web_contents.h"
255738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project#include "grit/generated_resources.h"
265738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project#include "grit/theme_resources.h"
275738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project#include "ui/base/l10n/l10n_util.h"
285738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project#include "ui/base/models/simple_menu_model.h"
295738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project#include "ui/views/controls/button/label_button.h"
305738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project#include "ui/views/controls/button/menu_button.h"
315738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project#include "ui/views/controls/button/radio_button.h"
325738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project#include "ui/views/controls/image_view.h"
335738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project#include "ui/views/controls/label.h"
345738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project#include "ui/views/controls/link.h"
355738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project#include "ui/views/controls/menu/menu.h"
365738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project#include "ui/views/controls/menu/menu_runner.h"
375738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project#include "ui/views/controls/separator.h"
385738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project#include "ui/views/layout/grid_layout.h"
395738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project#include "ui/views/layout/layout_constants.h"
4066432dcffe211e317d35978283a04a96d5620bdfMatthew Xie
4166432dcffe211e317d35978283a04a96d5620bdfMatthew Xie#if defined(USE_AURA)
4266432dcffe211e317d35978283a04a96d5620bdfMatthew Xie#include "ui/base/cursor/cursor.h"
4366432dcffe211e317d35978283a04a96d5620bdfMatthew Xie#endif
4466432dcffe211e317d35978283a04a96d5620bdfMatthew Xie
455738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Projectnamespace {
465738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project
475738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project// If we don't clamp the maximum width, then very long URLs and titles can make
485738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project// the bubble arbitrarily wide.
495738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Projectconst int kMaxContentsWidth = 500;
505738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project
515738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project// When we have multiline labels, we should set a minimum width lest we get very
525738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project// narrow bubbles with lots of line-wrapping.
535738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Projectconst int kMinMultiLineContentsWidth = 250;
545738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project
555738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project// The minimum width of the media menu buttons.
565738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Projectconst int kMinMediaMenuButtonWidth = 100;
575738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project
585738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project}  // namespace
595738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project
605738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Projectusing content::PluginService;
615738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Projectusing content::WebContents;
625738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project
635738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project
645738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project// ContentSettingBubbleContents::Favicon --------------------------------------
655738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project
665738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Projectclass ContentSettingBubbleContents::Favicon : public views::ImageView {
675738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project public:
685738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project  Favicon(const gfx::Image& image,
695738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project          ContentSettingBubbleContents* parent,
705738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project          views::Link* link);
715738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project  virtual ~Favicon();
725738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project
735738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project private:
745738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project  // views::View overrides:
755738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project  virtual bool OnMousePressed(const ui::MouseEvent& event) OVERRIDE;
765738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project  virtual void OnMouseReleased(const ui::MouseEvent& event) OVERRIDE;
7766432dcffe211e317d35978283a04a96d5620bdfMatthew Xie  virtual gfx::NativeCursor GetCursor(const ui::MouseEvent& event) OVERRIDE;
7866432dcffe211e317d35978283a04a96d5620bdfMatthew Xie
7966432dcffe211e317d35978283a04a96d5620bdfMatthew Xie  ContentSettingBubbleContents* parent_;
8066432dcffe211e317d35978283a04a96d5620bdfMatthew Xie  views::Link* link_;
815738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project};
825738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project
835738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source ProjectContentSettingBubbleContents::Favicon::Favicon(
845738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project    const gfx::Image& image,
8566432dcffe211e317d35978283a04a96d5620bdfMatthew Xie    ContentSettingBubbleContents* parent,
8666432dcffe211e317d35978283a04a96d5620bdfMatthew Xie    views::Link* link)
8766432dcffe211e317d35978283a04a96d5620bdfMatthew Xie    : parent_(parent),
885738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project      link_(link) {
8966432dcffe211e317d35978283a04a96d5620bdfMatthew Xie  SetImage(image.AsImageSkia());
905738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project}
91e8c3d75b75493911ebf0f99c83676359657178f7Sharvil Nanavati
9266432dcffe211e317d35978283a04a96d5620bdfMatthew XieContentSettingBubbleContents::Favicon::~Favicon() {
9366432dcffe211e317d35978283a04a96d5620bdfMatthew Xie}
9466432dcffe211e317d35978283a04a96d5620bdfMatthew Xie
9566432dcffe211e317d35978283a04a96d5620bdfMatthew Xiebool ContentSettingBubbleContents::Favicon::OnMousePressed(
9666432dcffe211e317d35978283a04a96d5620bdfMatthew Xie    const ui::MouseEvent& event) {
9766432dcffe211e317d35978283a04a96d5620bdfMatthew Xie  return event.IsLeftMouseButton() || event.IsMiddleMouseButton();
9866432dcffe211e317d35978283a04a96d5620bdfMatthew Xie}
9966432dcffe211e317d35978283a04a96d5620bdfMatthew Xie
10066432dcffe211e317d35978283a04a96d5620bdfMatthew Xievoid ContentSettingBubbleContents::Favicon::OnMouseReleased(
10166432dcffe211e317d35978283a04a96d5620bdfMatthew Xie    const ui::MouseEvent& event) {
10266432dcffe211e317d35978283a04a96d5620bdfMatthew Xie  if ((event.IsLeftMouseButton() || event.IsMiddleMouseButton()) &&
10366432dcffe211e317d35978283a04a96d5620bdfMatthew Xie     HitTestPoint(event.location())) {
10466432dcffe211e317d35978283a04a96d5620bdfMatthew Xie    parent_->LinkClicked(link_, event.flags());
10566432dcffe211e317d35978283a04a96d5620bdfMatthew Xie  }
10666432dcffe211e317d35978283a04a96d5620bdfMatthew Xie}
10766432dcffe211e317d35978283a04a96d5620bdfMatthew Xie
10866432dcffe211e317d35978283a04a96d5620bdfMatthew Xiegfx::NativeCursor ContentSettingBubbleContents::Favicon::GetCursor(
10966432dcffe211e317d35978283a04a96d5620bdfMatthew Xie    const ui::MouseEvent& event) {
11066432dcffe211e317d35978283a04a96d5620bdfMatthew Xie#if defined(USE_AURA)
11166432dcffe211e317d35978283a04a96d5620bdfMatthew Xie  return ui::kCursorHand;
11266432dcffe211e317d35978283a04a96d5620bdfMatthew Xie#elif defined(OS_WIN)
11366432dcffe211e317d35978283a04a96d5620bdfMatthew Xie  static HCURSOR g_hand_cursor = LoadCursor(NULL, IDC_HAND);
11466432dcffe211e317d35978283a04a96d5620bdfMatthew Xie  return g_hand_cursor;
11566432dcffe211e317d35978283a04a96d5620bdfMatthew Xie#endif
11666432dcffe211e317d35978283a04a96d5620bdfMatthew Xie}
11766432dcffe211e317d35978283a04a96d5620bdfMatthew Xie
11866432dcffe211e317d35978283a04a96d5620bdfMatthew Xie
11966432dcffe211e317d35978283a04a96d5620bdfMatthew Xie// ContentSettingBubbleContents::MediaMenuParts -------------------------------
12066432dcffe211e317d35978283a04a96d5620bdfMatthew Xie
12166432dcffe211e317d35978283a04a96d5620bdfMatthew Xiestruct ContentSettingBubbleContents::MediaMenuParts {
12266432dcffe211e317d35978283a04a96d5620bdfMatthew Xie  explicit MediaMenuParts(content::MediaStreamType type);
12366432dcffe211e317d35978283a04a96d5620bdfMatthew Xie  ~MediaMenuParts();
1245738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project
1255738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project  content::MediaStreamType type;
1265738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project  scoped_ptr<ui::SimpleMenuModel> menu_model;
1275738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project
1285738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project private:
1295738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project  DISALLOW_COPY_AND_ASSIGN(MediaMenuParts);
1305738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project};
1315738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project
1325738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source ProjectContentSettingBubbleContents::MediaMenuParts::MediaMenuParts(
1335738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project    content::MediaStreamType type)
1345738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project    : type(type) {}
1355738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project
1365738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source ProjectContentSettingBubbleContents::MediaMenuParts::~MediaMenuParts() {}
1375738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project
1385738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project
1395738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project// ContentSettingBubbleContents -----------------------------------------------
1405738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project
1415738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source ProjectContentSettingBubbleContents::ContentSettingBubbleContents(
142e8c3d75b75493911ebf0f99c83676359657178f7Sharvil Nanavati    ContentSettingBubbleModel* content_setting_bubble_model,
1435738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project    WebContents* web_contents,
1445738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project    views::View* anchor_view,
1455738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project    views::BubbleBorder::Arrow arrow)
1465738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project    : BubbleDelegateView(anchor_view, arrow),
1475738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project      content_setting_bubble_model_(content_setting_bubble_model),
1485738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project      web_contents_(web_contents),
1495738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project      custom_link_(NULL),
1505738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project      manage_link_(NULL),
1515738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project      close_button_(NULL) {
1525738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project  // Compensate for built-in vertical padding in the anchor view's image.
1535738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project  set_anchor_view_insets(gfx::Insets(5, 0, 5, 0));
1545738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project
1555738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project  registrar_.Add(this, content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
1565738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project                 content::Source<WebContents>(web_contents));
1575738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project}
158e8c3d75b75493911ebf0f99c83676359657178f7Sharvil Nanavati
1595738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source ProjectContentSettingBubbleContents::~ContentSettingBubbleContents() {
1605738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project  STLDeleteValues(&media_menus_);
1615738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project}
1625738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project
1635738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Projectgfx::Size ContentSettingBubbleContents::GetPreferredSize() {
1645738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project  gfx::Size preferred_size(views::View::GetPreferredSize());
1655738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project  int preferred_width =
1665738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project      (!content_setting_bubble_model_->bubble_content().domain_lists.empty() &&
1675738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project       (kMinMultiLineContentsWidth > preferred_size.width())) ?
1685738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project      kMinMultiLineContentsWidth : preferred_size.width();
1695738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project  preferred_size.set_width(std::min(preferred_width, kMaxContentsWidth));
1705738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project  return preferred_size;
1715738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project}
1725738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project
1735738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Projectvoid ContentSettingBubbleContents::UpdateMenuLabel(
1745738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project    content::MediaStreamType type,
1755738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project    const std::string& label) {
1765cd8bff2dd0337cb52bf48f312e3d2d55a8882fbMike J. Chen  for (MediaMenuPartsMap::const_iterator it = media_menus_.begin();
177e8c3d75b75493911ebf0f99c83676359657178f7Sharvil Nanavati       it != media_menus_.end(); ++it) {
1785738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project    if (it->second->type == type) {
1795738f83aeb59361a0a2eda2460113f6dc919427The Android Open Source Project      it->first->SetText(UTF8ToUTF16(label));
180      return;
181    }
182  }
183  NOTREACHED();
184}
185
186void ContentSettingBubbleContents::Init() {
187  using views::GridLayout;
188
189  GridLayout* layout = new views::GridLayout(this);
190  SetLayoutManager(layout);
191
192  const int kSingleColumnSetId = 0;
193  views::ColumnSet* column_set = layout->AddColumnSet(kSingleColumnSetId);
194  column_set->AddColumn(GridLayout::LEADING, GridLayout::FILL, 1,
195                        GridLayout::USE_PREF, 0, 0);
196
197  const ContentSettingBubbleModel::BubbleContent& bubble_content =
198      content_setting_bubble_model_->bubble_content();
199  bool bubble_content_empty = true;
200
201  if (!bubble_content.title.empty()) {
202    views::Label* title_label = new views::Label(UTF8ToUTF16(
203        bubble_content.title));
204    title_label->SetMultiLine(true);
205    title_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
206    layout->StartRow(0, kSingleColumnSetId);
207    layout->AddView(title_label);
208    bubble_content_empty = false;
209  }
210
211  const std::set<std::string>& plugins = bubble_content.resource_identifiers;
212  if (!plugins.empty()) {
213    if (!bubble_content_empty)
214      layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
215    PluginFinder* finder = PluginFinder::GetInstance();
216    for (std::set<std::string>::const_iterator i(plugins.begin());
217         i != plugins.end(); ++i) {
218      string16 name = finder->FindPluginNameWithIdentifier(*i);
219      layout->StartRow(0, kSingleColumnSetId);
220      layout->AddView(new views::Label(name));
221      bubble_content_empty = false;
222    }
223  }
224
225  if (content_setting_bubble_model_->content_type() ==
226      CONTENT_SETTINGS_TYPE_POPUPS) {
227    const int kPopupColumnSetId = 2;
228    views::ColumnSet* popup_column_set =
229        layout->AddColumnSet(kPopupColumnSetId);
230    popup_column_set->AddColumn(GridLayout::LEADING, GridLayout::FILL, 0,
231                                GridLayout::USE_PREF, 0, 0);
232    popup_column_set->AddPaddingColumn(
233        0, views::kRelatedControlHorizontalSpacing);
234    popup_column_set->AddColumn(GridLayout::LEADING, GridLayout::FILL, 1,
235                                GridLayout::USE_PREF, 0, 0);
236
237    for (std::vector<ContentSettingBubbleModel::PopupItem>::const_iterator
238         i(bubble_content.popup_items.begin());
239         i != bubble_content.popup_items.end(); ++i) {
240      if (!bubble_content_empty)
241        layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
242      layout->StartRow(0, kPopupColumnSetId);
243
244      views::Link* link = new views::Link(UTF8ToUTF16(i->title));
245      link->set_listener(this);
246      link->SetElideBehavior(views::Label::ELIDE_IN_MIDDLE);
247      popup_links_[link] = i - bubble_content.popup_items.begin();
248      layout->AddView(new Favicon(i->image, this, link));
249      layout->AddView(link);
250      bubble_content_empty = false;
251    }
252  }
253
254  const int indented_kSingleColumnSetId = 3;
255  // Insert a column set with greater indent.
256  views::ColumnSet* indented_single_column_set =
257      layout->AddColumnSet(indented_kSingleColumnSetId);
258  indented_single_column_set->AddPaddingColumn(0, views::kCheckboxIndent);
259  indented_single_column_set->AddColumn(GridLayout::LEADING, GridLayout::FILL,
260                                        1, GridLayout::USE_PREF, 0, 0);
261
262  const ContentSettingBubbleModel::RadioGroup& radio_group =
263      bubble_content.radio_group;
264  if (!radio_group.radio_items.empty()) {
265    if (!bubble_content_empty)
266      layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
267    for (ContentSettingBubbleModel::RadioItems::const_iterator i(
268         radio_group.radio_items.begin());
269         i != radio_group.radio_items.end(); ++i) {
270      views::RadioButton* radio = new views::RadioButton(UTF8ToUTF16(*i), 0);
271      radio->SetEnabled(bubble_content.radio_group_enabled);
272      radio->set_listener(this);
273      radio_group_.push_back(radio);
274      layout->StartRow(0, indented_kSingleColumnSetId);
275      layout->AddView(radio);
276      bubble_content_empty = false;
277    }
278    DCHECK(!radio_group_.empty());
279    // Now that the buttons have been added to the view hierarchy, it's safe
280    // to call SetChecked() on them.
281    radio_group_[radio_group.default_item]->SetChecked(true);
282  }
283
284  // Layout code for the media device menus.
285  if (content_setting_bubble_model_->content_type() ==
286      CONTENT_SETTINGS_TYPE_MEDIASTREAM) {
287    const int kMediaMenuColumnSetId = 2;
288    views::ColumnSet* menu_column_set =
289        layout->AddColumnSet(kMediaMenuColumnSetId);
290    menu_column_set->AddPaddingColumn(0, views::kCheckboxIndent);
291    menu_column_set->AddColumn(GridLayout::LEADING, GridLayout::FILL, 0,
292                               GridLayout::USE_PREF, 0, 0);
293    menu_column_set->AddPaddingColumn(
294        0, views::kRelatedControlHorizontalSpacing);
295    menu_column_set->AddColumn(GridLayout::LEADING, GridLayout::FILL, 1,
296                               GridLayout::USE_PREF, 0, 0);
297
298    int menu_width = 0;
299    for (ContentSettingBubbleModel::MediaMenuMap::const_iterator i(
300         bubble_content.media_menus.begin());
301         i != bubble_content.media_menus.end(); ++i) {
302      if (!bubble_content_empty)
303        layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
304      layout->StartRow(0, kMediaMenuColumnSetId);
305
306      views::Label* label = new views::Label(UTF8ToUTF16(i->second.label));
307      label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
308
309      views::MenuButton* menu_button = new views::MenuButton(
310          NULL, UTF8ToUTF16((i->second.selected_device.name)), this, true);
311      menu_button->set_alignment(views::TextButton::ALIGN_LEFT);
312      menu_button->set_border(
313          new views::TextButtonNativeThemeBorder(menu_button));
314      menu_button->set_animate_on_state_change(false);
315
316      MediaMenuParts* menu_view = new MediaMenuParts(i->first);
317      menu_view->menu_model.reset(new ContentSettingMediaMenuModel(
318          i->first,
319          content_setting_bubble_model_.get(),
320          base::Bind(&ContentSettingBubbleContents::UpdateMenuLabel,
321                     base::Unretained(this))));
322      media_menus_[menu_button] = menu_view;
323
324      if (!menu_view->menu_model->GetItemCount()) {
325        // Show a "None available" title and grey out the menu when there is no
326        // available device.
327        menu_button->SetText(
328            l10n_util::GetStringUTF16(IDS_MEDIA_MENU_NO_DEVICE_TITLE));
329        menu_button->SetEnabled(false);
330      }
331
332      // Use the longest width of the menus as the width of the menu buttons.
333      menu_width = std::max(menu_width,
334                            GetPreferredMediaMenuWidth(
335                                menu_button, menu_view->menu_model.get()));
336
337      layout->AddView(label);
338      layout->AddView(menu_button);
339
340      bubble_content_empty = false;
341    }
342
343    // Make sure the width is at least kMinMediaMenuButtonWidth. The
344    // maximum width will be clamped by kMaxContentsWidth of the view.
345    menu_width = std::max(kMinMediaMenuButtonWidth, menu_width);
346
347    // Set all the menu buttons to the width we calculated above.
348    for (MediaMenuPartsMap::const_iterator i = media_menus_.begin();
349         i != media_menus_.end(); ++i) {
350      i->first->set_min_width(menu_width);
351      i->first->set_max_width(menu_width);
352    }
353  }
354
355  gfx::Font domain_font =
356      views::Label().font().DeriveFont(0, gfx::Font::BOLD);
357  for (std::vector<ContentSettingBubbleModel::DomainList>::const_iterator i(
358       bubble_content.domain_lists.begin());
359       i != bubble_content.domain_lists.end(); ++i) {
360    layout->StartRow(0, kSingleColumnSetId);
361    views::Label* section_title = new views::Label(UTF8ToUTF16(i->title));
362    section_title->SetMultiLine(true);
363    section_title->SetHorizontalAlignment(gfx::ALIGN_LEFT);
364    layout->AddView(section_title, 1, 1, GridLayout::FILL, GridLayout::LEADING);
365    for (std::set<std::string>::const_iterator j = i->hosts.begin();
366         j != i->hosts.end(); ++j) {
367      layout->StartRow(0, indented_kSingleColumnSetId);
368      layout->AddView(new views::Label(UTF8ToUTF16(*j), domain_font));
369    }
370    bubble_content_empty = false;
371  }
372
373  if (!bubble_content.custom_link.empty()) {
374    custom_link_ = new views::Link(UTF8ToUTF16(bubble_content.custom_link));
375    custom_link_->SetEnabled(bubble_content.custom_link_enabled);
376    custom_link_->set_listener(this);
377    if (!bubble_content_empty)
378      layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
379    layout->StartRow(0, kSingleColumnSetId);
380    layout->AddView(custom_link_);
381    bubble_content_empty = false;
382  }
383
384  if (!bubble_content_empty) {
385    layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
386    layout->StartRow(0, kSingleColumnSetId);
387    layout->AddView(new views::Separator(views::Separator::HORIZONTAL), 1, 1,
388                    GridLayout::FILL, GridLayout::FILL);
389    layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
390  }
391
392  const int kDoubleColumnSetId = 1;
393  views::ColumnSet* double_column_set =
394      layout->AddColumnSet(kDoubleColumnSetId);
395  double_column_set->AddColumn(GridLayout::LEADING, GridLayout::CENTER, 1,
396                        GridLayout::USE_PREF, 0, 0);
397  double_column_set->AddPaddingColumn(
398      0, views::kUnrelatedControlHorizontalSpacing);
399  double_column_set->AddColumn(GridLayout::TRAILING, GridLayout::CENTER, 0,
400                        GridLayout::USE_PREF, 0, 0);
401
402  layout->StartRow(0, kDoubleColumnSetId);
403  manage_link_ = new views::Link(UTF8ToUTF16(bubble_content.manage_link));
404  manage_link_->set_listener(this);
405  layout->AddView(manage_link_);
406  if (content_setting_bubble_model_->content_type() !=
407      CONTENT_SETTINGS_TYPE_SAVE_PASSWORD) {
408    close_button_ =
409        new views::LabelButton(this, l10n_util::GetStringUTF16(IDS_DONE));
410    close_button_->SetStyle(views::Button::STYLE_NATIVE_TEXTBUTTON);
411    layout->AddView(close_button_);
412  }
413}
414
415void ContentSettingBubbleContents::ButtonPressed(views::Button* sender,
416                                                 const ui::Event& event) {
417  if (sender == close_button_) {
418    content_setting_bubble_model_->OnDoneClicked();
419    StartFade(false);
420    return;
421  }
422
423  for (RadioGroup::const_iterator i(radio_group_.begin());
424       i != radio_group_.end(); ++i) {
425    if (sender == *i) {
426      content_setting_bubble_model_->OnRadioClicked(i - radio_group_.begin());
427      return;
428    }
429  }
430  NOTREACHED() << "unknown radio";
431}
432
433void ContentSettingBubbleContents::LinkClicked(views::Link* source,
434                                               int event_flags) {
435  if (source == custom_link_) {
436    content_setting_bubble_model_->OnCustomLinkClicked();
437    StartFade(false);
438    return;
439  }
440  if (source == manage_link_) {
441    StartFade(false);
442    content_setting_bubble_model_->OnManageLinkClicked();
443    // CAREFUL: Showing the settings window activates it, which deactivates the
444    // info bubble, which causes it to close, which deletes us.
445    return;
446  }
447
448  PopupLinks::const_iterator i(popup_links_.find(source));
449  DCHECK(i != popup_links_.end());
450  content_setting_bubble_model_->OnPopupClicked(i->second);
451}
452
453void ContentSettingBubbleContents::OnMenuButtonClicked(
454    views::View* source,
455    const gfx::Point& point) {
456  MediaMenuPartsMap::iterator i(media_menus_.find(
457      static_cast<views::MenuButton*>(source)));
458  DCHECK(i != media_menus_.end());
459
460  menu_runner_.reset(new views::MenuRunner(i->second->menu_model.get()));
461
462  gfx::Point screen_location;
463  views::View::ConvertPointToScreen(i->first, &screen_location);
464  ignore_result(menu_runner_->RunMenuAt(
465      source->GetWidget(),
466      i->first,
467      gfx::Rect(screen_location, i->first->size()),
468      views::MenuItemView::TOPLEFT,
469      ui::MENU_SOURCE_NONE,
470      views::MenuRunner::HAS_MNEMONICS));
471}
472
473void ContentSettingBubbleContents::Observe(
474    int type,
475    const content::NotificationSource& source,
476    const content::NotificationDetails& details) {
477  DCHECK_EQ(content::NOTIFICATION_WEB_CONTENTS_DESTROYED, type);
478  DCHECK(source == content::Source<WebContents>(web_contents_));
479  web_contents_ = NULL;
480}
481
482int ContentSettingBubbleContents::GetPreferredMediaMenuWidth(
483    views::MenuButton* button,
484    ui::SimpleMenuModel* menu_model) {
485  string16 title = button->text();
486
487  int width = button->GetPreferredSize().width();
488  for (int i = 0; i < menu_model->GetItemCount(); ++i) {
489    button->SetText(menu_model->GetLabelAt(i));
490    width = std::max(width, button->GetPreferredSize().width());
491  }
492
493  // Recover the title for the menu button.
494  button->SetText(title);
495  return width;
496}
497