9bfee0255f9d081c1cc3d2a4dd7052aa13e71fbe
[platform/framework/web/crosswalk.git] / src / chrome / browser / ui / views / content_setting_bubble_contents.cc
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/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"
40
41 #if defined(USE_AURA)
42 #include "ui/base/cursor/cursor.h"
43 #endif
44
45 namespace {
46
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;
50
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;
54
55 // The minimum width of the media menu buttons.
56 const int kMinMediaMenuButtonWidth = 100;
57
58 }  // namespace
59
60 using content::PluginService;
61 using content::WebContents;
62
63
64 // ContentSettingBubbleContents::Favicon --------------------------------------
65
66 class ContentSettingBubbleContents::Favicon : public views::ImageView {
67  public:
68   Favicon(const gfx::Image& image,
69           ContentSettingBubbleContents* parent,
70           views::Link* link);
71   virtual ~Favicon();
72
73  private:
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;
78
79   ContentSettingBubbleContents* parent_;
80   views::Link* link_;
81 };
82
83 ContentSettingBubbleContents::Favicon::Favicon(
84     const gfx::Image& image,
85     ContentSettingBubbleContents* parent,
86     views::Link* link)
87     : parent_(parent),
88       link_(link) {
89   SetImage(image.AsImageSkia());
90 }
91
92 ContentSettingBubbleContents::Favicon::~Favicon() {
93 }
94
95 bool ContentSettingBubbleContents::Favicon::OnMousePressed(
96     const ui::MouseEvent& event) {
97   return event.IsLeftMouseButton() || event.IsMiddleMouseButton();
98 }
99
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());
105   }
106 }
107
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;
115 #endif
116 }
117
118
119 // ContentSettingBubbleContents::MediaMenuParts -------------------------------
120
121 struct ContentSettingBubbleContents::MediaMenuParts {
122   explicit MediaMenuParts(content::MediaStreamType type);
123   ~MediaMenuParts();
124
125   content::MediaStreamType type;
126   scoped_ptr<ui::SimpleMenuModel> menu_model;
127
128  private:
129   DISALLOW_COPY_AND_ASSIGN(MediaMenuParts);
130 };
131
132 ContentSettingBubbleContents::MediaMenuParts::MediaMenuParts(
133     content::MediaStreamType type)
134     : type(type) {}
135
136 ContentSettingBubbleContents::MediaMenuParts::~MediaMenuParts() {}
137
138 // ContentSettingBubbleContents -----------------------------------------------
139
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),
146       custom_link_(NULL),
147       manage_link_(NULL),
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));
151 }
152
153 ContentSettingBubbleContents::~ContentSettingBubbleContents() {
154   STLDeleteValues(&media_menus_);
155 }
156
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;
165 }
166
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));
174       return;
175     }
176   }
177   NOTREACHED();
178 }
179
180 void ContentSettingBubbleContents::Init() {
181   using views::GridLayout;
182
183   GridLayout* layout = new views::GridLayout(this);
184   SetLayoutManager(layout);
185
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);
190
191   const ContentSettingBubbleModel::BubbleContent& bubble_content =
192       content_setting_bubble_model_->bubble_content();
193   bool bubble_content_empty = true;
194
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;
203   }
204
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);
216
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);
223
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;
231     }
232   }
233
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);
241
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;
258     }
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);
263   }
264
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);
278
279     int menu_width = 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);
286
287       views::Label* label =
288           new views::Label(base::UTF8ToUTF16(i->second.label));
289       label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
290
291       views::MenuButton* menu_button = new views::MenuButton(
292           NULL, base::UTF8ToUTF16((i->second.selected_device.name)),
293           this, true);
294       menu_button->set_alignment(views::TextButton::ALIGN_LEFT);
295       menu_button->SetBorder(scoped_ptr<views::Border>(
296           new views::TextButtonNativeThemeBorder(menu_button)));
297       menu_button->set_animate_on_state_change(false);
298
299       MediaMenuParts* menu_view = new MediaMenuParts(i->first);
300       menu_view->menu_model.reset(new ContentSettingMediaMenuModel(
301           i->first,
302           content_setting_bubble_model_.get(),
303           base::Bind(&ContentSettingBubbleContents::UpdateMenuLabel,
304                      base::Unretained(this))));
305       media_menus_[menu_button] = menu_view;
306
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);
313       }
314
315       // Disable the device selection when the website is managing the devices
316       // itself.
317       if (i->second.disabled)
318         menu_button->SetEnabled(false);
319
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()));
324
325       layout->AddView(label);
326       layout->AddView(menu_button);
327
328       bubble_content_empty = false;
329     }
330
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);
334
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);
340     }
341   }
342
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));
358     }
359     bubble_content_empty = false;
360   }
361
362   if (!bubble_content.custom_link.empty()) {
363     custom_link_ =
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;
372   }
373
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);
383     }
384
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);
391
392     layout->StartRow(0, kDoubleColumnSetId);
393     manage_link_ =
394         new views::Link(base::UTF8ToUTF16(bubble_content.manage_link));
395     manage_link_->set_listener(this);
396     layout->AddView(manage_link_);
397
398     close_button_ =
399         new views::LabelButton(this, l10n_util::GetStringUTF16(IDS_DONE));
400     close_button_->SetStyle(views::Button::STYLE_BUTTON);
401     layout->AddView(close_button_);
402 }
403
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());
410     return;
411   }
412   DCHECK_EQ(sender, close_button_);
413   content_setting_bubble_model_->OnDoneClicked();
414   GetWidget()->Close();
415 }
416
417 void ContentSettingBubbleContents::LinkClicked(views::Link* source,
418                                                int event_flags) {
419   if (source == custom_link_) {
420     content_setting_bubble_model_->OnCustomLinkClicked();
421     GetWidget()->Close();
422     return;
423   }
424   if (source == manage_link_) {
425     GetWidget()->Close();
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.
429     return;
430   }
431
432   PopupLinks::const_iterator i(popup_links_.find(source));
433   DCHECK(i != popup_links_.end());
434   content_setting_bubble_model_->OnPopupClicked(i->second);
435 }
436
437 void ContentSettingBubbleContents::OnMenuButtonClicked(
438     views::View* source,
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()));
444
445     gfx::Point screen_location;
446     views::View::ConvertPointToScreen(j->first, &screen_location);
447     ignore_result(
448         menu_runner_->RunMenuAt(source->GetWidget(),
449                                 j->first,
450                                 gfx::Rect(screen_location, j->first->size()),
451                                 views::MENU_ANCHOR_TOPLEFT,
452                                 ui::MENU_SOURCE_NONE,
453                                 views::MenuRunner::HAS_MNEMONICS));
454 }
455
456 int ContentSettingBubbleContents::GetPreferredMediaMenuWidth(
457     views::MenuButton* button,
458     ui::SimpleMenuModel* menu_model) {
459   base::string16 title = button->text();
460
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());
465   }
466
467   // Recover the title for the menu button.
468   button->SetText(title);
469   return width;
470 }