1// Copyright (c) 2011 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#if defined(OS_LINUX)
8#include <gdk/gdk.h>
9#endif
10
11#include "base/utf_string_conversions.h"
12#include "chrome/browser/blocked_content_container.h"
13#include "chrome/browser/content_setting_bubble_model.h"
14#include "chrome/browser/content_settings/host_content_settings_map.h"
15#include "chrome/browser/plugin_updater.h"
16#include "chrome/browser/profiles/profile.h"
17#include "chrome/browser/ui/views/browser_dialogs.h"
18#include "chrome/browser/ui/views/bubble/bubble.h"
19#include "content/browser/tab_contents/tab_contents.h"
20#include "content/common/notification_source.h"
21#include "content/common/notification_type.h"
22#include "grit/generated_resources.h"
23#include "ui/base/l10n/l10n_util.h"
24#include "views/controls/button/native_button.h"
25#include "views/controls/button/radio_button.h"
26#include "views/controls/image_view.h"
27#include "views/controls/label.h"
28#include "views/controls/separator.h"
29#include "views/layout/grid_layout.h"
30#include "views/layout/layout_constants.h"
31#include "webkit/glue/plugins/plugin_list.h"
32
33#if defined(OS_LINUX)
34#include "ui/gfx/gtk_util.h"
35#endif
36
37// If we don't clamp the maximum width, then very long URLs and titles can make
38// the bubble arbitrarily wide.
39const int kMaxContentsWidth = 500;
40
41// When we have multiline labels, we should set a minimum width lest we get very
42// narrow bubbles with lots of line-wrapping.
43const int kMinMultiLineContentsWidth = 250;
44
45class ContentSettingBubbleContents::Favicon : public views::ImageView {
46 public:
47  Favicon(const SkBitmap& image,
48          ContentSettingBubbleContents* parent,
49          views::Link* link);
50  virtual ~Favicon();
51
52 private:
53#if defined(OS_WIN)
54  static HCURSOR g_hand_cursor;
55#endif
56
57  // views::View overrides:
58  virtual bool OnMousePressed(const views::MouseEvent& event) OVERRIDE;
59  virtual void OnMouseReleased(const views::MouseEvent& event) OVERRIDE;
60  virtual gfx::NativeCursor GetCursorForPoint(ui::EventType event_type,
61                                              const gfx::Point& p) OVERRIDE;
62
63  ContentSettingBubbleContents* parent_;
64  views::Link* link_;
65};
66
67#if defined(OS_WIN)
68HCURSOR ContentSettingBubbleContents::Favicon::g_hand_cursor = NULL;
69#endif
70
71ContentSettingBubbleContents::Favicon::Favicon(
72    const SkBitmap& image,
73    ContentSettingBubbleContents* parent,
74    views::Link* link)
75    : parent_(parent),
76      link_(link) {
77  SetImage(image);
78}
79
80ContentSettingBubbleContents::Favicon::~Favicon() {
81}
82
83bool ContentSettingBubbleContents::Favicon::OnMousePressed(
84    const views::MouseEvent& event) {
85  return event.IsLeftMouseButton() || event.IsMiddleMouseButton();
86}
87
88void ContentSettingBubbleContents::Favicon::OnMouseReleased(
89    const views::MouseEvent& event) {
90  if ((event.IsLeftMouseButton() || event.IsMiddleMouseButton()) &&
91     HitTest(event.location())) {
92    parent_->LinkActivated(link_, event.flags());
93  }
94}
95
96gfx::NativeCursor ContentSettingBubbleContents::Favicon::GetCursorForPoint(
97    ui::EventType event_type,
98    const gfx::Point& p) {
99#if defined(OS_WIN)
100  if (!g_hand_cursor)
101    g_hand_cursor = LoadCursor(NULL, IDC_HAND);
102  return g_hand_cursor;
103#elif defined(OS_LINUX)
104  return gfx::GetCursor(GDK_HAND2);
105#endif
106}
107
108ContentSettingBubbleContents::ContentSettingBubbleContents(
109    ContentSettingBubbleModel* content_setting_bubble_model,
110    Profile* profile,
111    TabContents* tab_contents)
112    : content_setting_bubble_model_(content_setting_bubble_model),
113      profile_(profile),
114      tab_contents_(tab_contents),
115      bubble_(NULL),
116      custom_link_(NULL),
117      manage_link_(NULL),
118      close_button_(NULL) {
119  registrar_.Add(this, NotificationType::TAB_CONTENTS_DESTROYED,
120                 Source<TabContents>(tab_contents));
121}
122
123ContentSettingBubbleContents::~ContentSettingBubbleContents() {
124}
125
126gfx::Size ContentSettingBubbleContents::GetPreferredSize() {
127  gfx::Size preferred_size(views::View::GetPreferredSize());
128  int preferred_width =
129      (!content_setting_bubble_model_->bubble_content().domain_lists.empty() &&
130       (kMinMultiLineContentsWidth > preferred_size.width())) ?
131      kMinMultiLineContentsWidth : preferred_size.width();
132  preferred_size.set_width(std::min(preferred_width, kMaxContentsWidth));
133  return preferred_size;
134}
135
136void ContentSettingBubbleContents::ViewHierarchyChanged(bool is_add,
137                                                        View* parent,
138                                                        View* child) {
139  if (is_add && (child == this))
140    InitControlLayout();
141}
142
143void ContentSettingBubbleContents::ButtonPressed(views::Button* sender,
144                                                 const views::Event& event) {
145  if (sender == close_button_) {
146    bubble_->set_fade_away_on_close(true);
147    bubble_->Close();  // CAREFUL: This deletes us.
148    return;
149  }
150
151  for (RadioGroup::const_iterator i = radio_group_.begin();
152       i != radio_group_.end(); ++i) {
153    if (sender == *i) {
154      content_setting_bubble_model_->OnRadioClicked(i - radio_group_.begin());
155      return;
156    }
157  }
158  NOTREACHED() << "unknown radio";
159}
160
161void ContentSettingBubbleContents::LinkActivated(views::Link* source,
162                                                 int event_flags) {
163  if (source == custom_link_) {
164    content_setting_bubble_model_->OnCustomLinkClicked();
165    bubble_->set_fade_away_on_close(true);
166    bubble_->Close();  // CAREFUL: This deletes us.
167    return;
168  }
169  if (source == manage_link_) {
170    bubble_->set_fade_away_on_close(true);
171    content_setting_bubble_model_->OnManageLinkClicked();
172    // CAREFUL: Showing the settings window activates it, which deactivates the
173    // info bubble, which causes it to close, which deletes us.
174    return;
175  }
176
177  PopupLinks::const_iterator i(popup_links_.find(source));
178  DCHECK(i != popup_links_.end());
179  content_setting_bubble_model_->OnPopupClicked(i->second);
180}
181
182void ContentSettingBubbleContents::Observe(NotificationType type,
183                                           const NotificationSource& source,
184                                           const NotificationDetails& details) {
185  DCHECK(type == NotificationType::TAB_CONTENTS_DESTROYED);
186  DCHECK(source == Source<TabContents>(tab_contents_));
187  tab_contents_ = NULL;
188}
189
190void ContentSettingBubbleContents::InitControlLayout() {
191  using views::GridLayout;
192
193  GridLayout* layout = new views::GridLayout(this);
194  SetLayoutManager(layout);
195
196  const int single_column_set_id = 0;
197  views::ColumnSet* column_set = layout->AddColumnSet(single_column_set_id);
198  column_set->AddColumn(GridLayout::LEADING, GridLayout::FILL, 1,
199                        GridLayout::USE_PREF, 0, 0);
200
201  const ContentSettingBubbleModel::BubbleContent& bubble_content =
202      content_setting_bubble_model_->bubble_content();
203  bool bubble_content_empty = true;
204
205  if (!bubble_content.title.empty()) {
206    views::Label* title_label = new views::Label(UTF8ToWide(
207        bubble_content.title));
208    layout->StartRow(0, single_column_set_id);
209    layout->AddView(title_label);
210    bubble_content_empty = false;
211  }
212
213  const std::set<std::string>& plugins = bubble_content.resource_identifiers;
214  if (!plugins.empty()) {
215    if (!bubble_content_empty)
216      layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
217    for (std::set<std::string>::const_iterator it = plugins.begin();
218        it != plugins.end(); ++it) {
219      std::wstring name = UTF16ToWide(
220          NPAPI::PluginList::Singleton()->GetPluginGroupName(*it));
221      if (name.empty())
222        name = UTF8ToWide(*it);
223      layout->StartRow(0, single_column_set_id);
224      layout->AddView(new views::Label(name));
225      bubble_content_empty = false;
226    }
227  }
228
229  if (content_setting_bubble_model_->content_type() ==
230      CONTENT_SETTINGS_TYPE_POPUPS) {
231    const int popup_column_set_id = 2;
232    views::ColumnSet* popup_column_set =
233        layout->AddColumnSet(popup_column_set_id);
234    popup_column_set->AddColumn(GridLayout::LEADING, GridLayout::FILL, 0,
235                                GridLayout::USE_PREF, 0, 0);
236    popup_column_set->AddPaddingColumn(
237        0, views::kRelatedControlHorizontalSpacing);
238    popup_column_set->AddColumn(GridLayout::LEADING, GridLayout::FILL, 1,
239                                GridLayout::USE_PREF, 0, 0);
240
241    for (std::vector<ContentSettingBubbleModel::PopupItem>::const_iterator
242         i(bubble_content.popup_items.begin());
243         i != bubble_content.popup_items.end(); ++i) {
244      if (!bubble_content_empty)
245        layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
246      layout->StartRow(0, popup_column_set_id);
247
248      views::Link* link = new views::Link(UTF8ToWide(i->title));
249      link->SetController(this);
250      link->SetElideInMiddle(true);
251      popup_links_[link] = i - bubble_content.popup_items.begin();
252      layout->AddView(new Favicon((*i).bitmap, this, link));
253      layout->AddView(link);
254      bubble_content_empty = false;
255    }
256  }
257
258  const ContentSettingBubbleModel::RadioGroup& radio_group =
259      bubble_content.radio_group;
260  if (!radio_group.radio_items.empty()) {
261    for (ContentSettingBubbleModel::RadioItems::const_iterator i =
262         radio_group.radio_items.begin();
263         i != radio_group.radio_items.end(); ++i) {
264      views::RadioButton* radio = new views::RadioButton(UTF8ToWide(*i), 0);
265      radio->set_listener(this);
266      radio_group_.push_back(radio);
267      if (!bubble_content_empty)
268        layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
269      layout->StartRow(0, single_column_set_id);
270      layout->AddView(radio);
271      bubble_content_empty = false;
272    }
273    DCHECK(!radio_group_.empty());
274    // Now that the buttons have been added to the view hierarchy, it's safe
275    // to call SetChecked() on them.
276    radio_group_[radio_group.default_item]->SetChecked(true);
277  }
278
279  gfx::Font domain_font =
280      views::Label().font().DeriveFont(0, gfx::Font::BOLD);
281  const int indented_single_column_set_id = 3;
282  // Insert a column set to indent the domain list.
283  views::ColumnSet* indented_single_column_set =
284      layout->AddColumnSet(indented_single_column_set_id);
285  indented_single_column_set->AddPaddingColumn(
286      0, views::kPanelHorizIndentation);
287  indented_single_column_set->AddColumn(GridLayout::LEADING, GridLayout::FILL,
288                                        1, GridLayout::USE_PREF, 0, 0);
289  for (std::vector<ContentSettingBubbleModel::DomainList>::const_iterator i =
290       bubble_content.domain_lists.begin();
291       i != bubble_content.domain_lists.end(); ++i) {
292    layout->StartRow(0, single_column_set_id);
293    views::Label* section_title = new views::Label(UTF8ToWide(i->title));
294    section_title->SetMultiLine(true);
295    section_title->SetHorizontalAlignment(views::Label::ALIGN_LEFT);
296    layout->AddView(section_title, 1, 1, GridLayout::FILL, GridLayout::LEADING);
297    for (std::set<std::string>::const_iterator j = i->hosts.begin();
298         j != i->hosts.end(); ++j) {
299      layout->StartRow(0, indented_single_column_set_id);
300      layout->AddView(new views::Label(UTF8ToWide(*j), domain_font));
301    }
302    bubble_content_empty = false;
303  }
304
305  if (!bubble_content.custom_link.empty()) {
306    custom_link_ = new views::Link(UTF8ToWide(bubble_content.custom_link));
307    custom_link_->SetEnabled(bubble_content.custom_link_enabled);
308    custom_link_->SetController(this);
309    if (!bubble_content_empty)
310      layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
311    layout->StartRow(0, single_column_set_id);
312    layout->AddView(custom_link_);
313    bubble_content_empty = false;
314  }
315
316  if (!bubble_content_empty) {
317    layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
318    layout->StartRow(0, single_column_set_id);
319    layout->AddView(new views::Separator, 1, 1,
320                    GridLayout::FILL, GridLayout::FILL);
321    layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
322  }
323
324  const int double_column_set_id = 1;
325  views::ColumnSet* double_column_set =
326      layout->AddColumnSet(double_column_set_id);
327  double_column_set->AddColumn(GridLayout::LEADING, GridLayout::CENTER, 1,
328                        GridLayout::USE_PREF, 0, 0);
329  double_column_set->AddPaddingColumn(
330      0, views::kUnrelatedControlHorizontalSpacing);
331  double_column_set->AddColumn(GridLayout::TRAILING, GridLayout::CENTER, 0,
332                        GridLayout::USE_PREF, 0, 0);
333
334  layout->StartRow(0, double_column_set_id);
335  manage_link_ = new views::Link(UTF8ToWide(bubble_content.manage_link));
336  manage_link_->SetController(this);
337  layout->AddView(manage_link_);
338
339  close_button_ =
340      new views::NativeButton(this,
341                              UTF16ToWide(l10n_util::GetStringUTF16(IDS_DONE)));
342  layout->AddView(close_button_);
343}
344