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. 39 const 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. 43 const int kMinMultiLineContentsWidth = 250; 44 45 class 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) 68 HCURSOR ContentSettingBubbleContents::Favicon::g_hand_cursor = NULL; 69 #endif 70 71 ContentSettingBubbleContents::Favicon::Favicon( 72 const SkBitmap& image, 73 ContentSettingBubbleContents* parent, 74 views::Link* link) 75 : parent_(parent), 76 link_(link) { 77 SetImage(image); 78 } 79 80 ContentSettingBubbleContents::Favicon::~Favicon() { 81 } 82 83 bool ContentSettingBubbleContents::Favicon::OnMousePressed( 84 const views::MouseEvent& event) { 85 return event.IsLeftMouseButton() || event.IsMiddleMouseButton(); 86 } 87 88 void 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 96 gfx::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 108 ContentSettingBubbleContents::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 123 ContentSettingBubbleContents::~ContentSettingBubbleContents() { 124 } 125 126 gfx::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 136 void ContentSettingBubbleContents::ViewHierarchyChanged(bool is_add, 137 View* parent, 138 View* child) { 139 if (is_add && (child == this)) 140 InitControlLayout(); 141 } 142 143 void 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 161 void 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 182 void 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 190 void 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