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/page_info_bubble_view.h" 6 7 #include "base/utf_string_conversions.h" 8 #include "chrome/browser/google/google_util.h" 9 #include "chrome/browser/ui/browser_list.h" 10 #include "chrome/browser/ui/views/frame/browser_view.h" 11 #include "chrome/browser/ui/views/bubble/bubble.h" 12 #include "chrome/browser/ui/views/toolbar_view.h" 13 #include "chrome/common/url_constants.h" 14 #include "content/browser/cert_store.h" 15 #include "content/browser/certificate_viewer.h" 16 #include "grit/generated_resources.h" 17 #include "grit/locale_settings.h" 18 #include "ui/base/l10n/l10n_util.h" 19 #include "ui/gfx/image.h" 20 #include "views/controls/image_view.h" 21 #include "views/controls/label.h" 22 #include "views/controls/separator.h" 23 #include "views/layout/grid_layout.h" 24 #include "views/widget/widget.h" 25 #include "views/window/window.h" 26 27 namespace { 28 29 // Layout constants. 30 const int kHGapToBorder = 11; 31 const int kVGapToImage = 10; 32 const int kVGapToHeadline = 7; 33 const int kHGapImageToDescription = 6; 34 const int kTextPaddingRight = 10; 35 const int kPaddingBelowSeparator = 4; 36 const int kPaddingAboveSeparator = 13; 37 const int kIconHorizontalOffset = 27; 38 const int kIconVerticalOffset = -7; 39 40 // The duration of the animation that resizes the bubble once the async 41 // information is provided through the ModelChanged event. 42 const int kPageInfoSlideDuration = 300; 43 44 // A section contains an image that shows a status (good or bad), a title, an 45 // optional head-line (in bold) and a description. 46 class Section : public views::View, 47 public views::LinkController { 48 public: 49 Section(PageInfoBubbleView* owner, 50 const PageInfoModel::SectionInfo& section_info, 51 const SkBitmap* status_icon, 52 bool show_cert); 53 virtual ~Section(); 54 55 // views::View methods: 56 virtual int GetHeightForWidth(int w); 57 virtual void Layout(); 58 59 // views::LinkController methods: 60 virtual void LinkActivated(views::Link* source, int event_flags); 61 62 private: 63 // Calculate the layout if |compute_bounds_only|, otherwise does Layout also. 64 gfx::Size LayoutItems(bool compute_bounds_only, int width); 65 66 // The view that owns this Section object. 67 PageInfoBubbleView* owner_; 68 69 // The information this view represents. 70 PageInfoModel::SectionInfo info_; 71 72 views::ImageView* status_image_; 73 views::Label* headline_label_; 74 views::Label* description_label_; 75 views::Link* link_; 76 77 DISALLOW_COPY_AND_ASSIGN(Section); 78 }; 79 80 } // namespace 81 82 //////////////////////////////////////////////////////////////////////////////// 83 // PageInfoBubbleView 84 85 Bubble* PageInfoBubbleView::bubble_ = NULL; 86 87 PageInfoBubbleView::PageInfoBubbleView(gfx::NativeWindow parent_window, 88 Profile* profile, 89 const GURL& url, 90 const NavigationEntry::SSLStatus& ssl, 91 bool show_history) 92 : ALLOW_THIS_IN_INITIALIZER_LIST(model_(profile, url, ssl, 93 show_history, this)), 94 parent_window_(parent_window), 95 cert_id_(ssl.cert_id()), 96 help_center_link_(NULL), 97 ALLOW_THIS_IN_INITIALIZER_LIST(resize_animation_(this)), 98 animation_start_height_(0) { 99 if (bubble_) 100 bubble_->Close(); 101 if (cert_id_ > 0) { 102 scoped_refptr<net::X509Certificate> cert; 103 CertStore::GetInstance()->RetrieveCert(cert_id_, &cert); 104 // When running with fake certificate (Chrome Frame), we have no os 105 // certificate, so there is no cert to show. Don't bother showing the cert 106 // info link in that case. 107 if (!cert.get() || !cert->os_cert_handle()) 108 cert_id_ = 0; 109 } 110 LayoutSections(); 111 } 112 113 PageInfoBubbleView::~PageInfoBubbleView() { 114 } 115 116 void PageInfoBubbleView::ShowCertDialog() { 117 ShowCertificateViewerByID(parent_window_, cert_id_); 118 } 119 120 void PageInfoBubbleView::LayoutSections() { 121 // Remove all the existing sections. 122 RemoveAllChildViews(true); 123 124 views::GridLayout* layout = new views::GridLayout(this); 125 SetLayoutManager(layout); 126 views::ColumnSet* columns = layout->AddColumnSet(0); 127 columns->AddColumn(views::GridLayout::FILL, // Horizontal resize. 128 views::GridLayout::FILL, // Vertical resize. 129 1, // Resize weight. 130 views::GridLayout::USE_PREF, // Size type. 131 0, // Ignored for USE_PREF. 132 0); // Minimum size. 133 // Add a column set for aligning the text when it has no icons (such as the 134 // help center link). 135 columns = layout->AddColumnSet(1); 136 columns->AddPaddingColumn( 137 0, kHGapToBorder + kIconHorizontalOffset + kHGapImageToDescription); 138 columns->AddColumn(views::GridLayout::LEADING, // Horizontal resize. 139 views::GridLayout::FILL, // Vertical resize. 140 1, // Resize weight. 141 views::GridLayout::USE_PREF, // Size type. 142 0, // Ignored for USE_PREF. 143 0); // Minimum size. 144 145 int count = model_.GetSectionCount(); 146 for (int i = 0; i < count; ++i) { 147 PageInfoModel::SectionInfo info = model_.GetSectionInfo(i); 148 layout->StartRow(0, 0); 149 const SkBitmap* icon = *model_.GetIconImage(info.icon_id); 150 layout->AddView(new Section(this, info, icon, cert_id_ > 0)); 151 152 // Add separator after all sections. 153 layout->AddPaddingRow(0, kPaddingAboveSeparator); 154 layout->StartRow(0, 0); 155 layout->AddView(new views::Separator()); 156 layout->AddPaddingRow(0, kPaddingBelowSeparator); 157 } 158 159 // Then add the help center link at the bottom. 160 layout->StartRow(0, 1); 161 help_center_link_ = new views::Link( 162 UTF16ToWide(l10n_util::GetStringUTF16(IDS_PAGE_INFO_HELP_CENTER_LINK))); 163 help_center_link_->SetController(this); 164 layout->AddView(help_center_link_); 165 } 166 167 gfx::Size PageInfoBubbleView::GetPreferredSize() { 168 gfx::Size size(views::Window::GetLocalizedContentsSize( 169 IDS_PAGEINFOBUBBLE_WIDTH_CHARS, IDS_PAGEINFOBUBBLE_HEIGHT_LINES)); 170 size.set_height(0); 171 172 int count = model_.GetSectionCount(); 173 for (int i = 0; i < count; ++i) { 174 PageInfoModel::SectionInfo info = model_.GetSectionInfo(i); 175 const SkBitmap* icon = *model_.GetIconImage(info.icon_id); 176 Section section(this, info, icon, cert_id_ > 0); 177 size.Enlarge(0, section.GetHeightForWidth(size.width())); 178 } 179 180 // Calculate how much space the separators take up (with padding). 181 views::Separator separator; 182 gfx::Size separator_size = separator.GetPreferredSize(); 183 gfx::Size separator_plus_padding(0, separator_size.height() + 184 kPaddingAboveSeparator + 185 kPaddingBelowSeparator); 186 187 // Account for the separators and padding within sections. 188 size.Enlarge(0, (count - 1) * separator_plus_padding.height()); 189 190 // Account for the Help Center link and the separator above it. 191 gfx::Size link_size = help_center_link_->GetPreferredSize(); 192 size.Enlarge(0, separator_plus_padding.height() + 193 link_size.height()); 194 195 if (!resize_animation_.is_animating()) 196 return size; 197 198 // We are animating from animation_start_height_ to size. 199 int target_height = animation_start_height_ + static_cast<int>( 200 (size.height() - animation_start_height_) * 201 resize_animation_.GetCurrentValue()); 202 size.set_height(target_height); 203 return size; 204 } 205 206 void PageInfoBubbleView::ModelChanged() { 207 animation_start_height_ = bounds().height(); 208 LayoutSections(); 209 resize_animation_.SetSlideDuration(kPageInfoSlideDuration); 210 resize_animation_.Show(); 211 } 212 213 void PageInfoBubbleView::BubbleClosing(Bubble* bubble, bool closed_by_escape) { 214 resize_animation_.Reset(); 215 bubble_ = NULL; 216 } 217 218 bool PageInfoBubbleView::CloseOnEscape() { 219 return true; 220 } 221 222 bool PageInfoBubbleView::FadeInOnShow() { 223 return false; 224 } 225 226 std::wstring PageInfoBubbleView::accessible_name() { 227 return L"PageInfoBubble"; 228 } 229 230 void PageInfoBubbleView::LinkActivated(views::Link* source, int event_flags) { 231 // We want to make sure the info bubble closes once the link is activated. So 232 // we close it explicitly rather than relying on a side-effect of opening a 233 // new tab (see http://crosbug.com/10186). 234 bubble_->Close(); 235 236 GURL url = google_util::AppendGoogleLocaleParam( 237 GURL(chrome::kPageInfoHelpCenterURL)); 238 Browser* browser = BrowserList::GetLastActive(); 239 browser->OpenURL(url, GURL(), NEW_FOREGROUND_TAB, PageTransition::LINK); 240 } 241 242 void PageInfoBubbleView::AnimationEnded(const ui::Animation* animation) { 243 bubble_->SizeToContents(); 244 } 245 246 void PageInfoBubbleView::AnimationProgressed(const ui::Animation* animation) { 247 bubble_->SizeToContents(); 248 } 249 250 //////////////////////////////////////////////////////////////////////////////// 251 // Section 252 253 Section::Section(PageInfoBubbleView* owner, 254 const PageInfoModel::SectionInfo& section_info, 255 const SkBitmap* state_icon, 256 bool show_cert) 257 : owner_(owner), 258 info_(section_info), 259 status_image_(NULL), 260 link_(NULL) { 261 if (state_icon) { 262 status_image_ = new views::ImageView(); 263 status_image_->SetImage(*state_icon); 264 AddChildView(status_image_); 265 } 266 267 headline_label_ = new views::Label(UTF16ToWideHack(info_.headline)); 268 headline_label_->SetFont( 269 headline_label_->font().DeriveFont(0, gfx::Font::BOLD)); 270 headline_label_->SetHorizontalAlignment(views::Label::ALIGN_LEFT); 271 AddChildView(headline_label_); 272 273 description_label_ = new views::Label(UTF16ToWideHack(info_.description)); 274 description_label_->SetMultiLine(true); 275 description_label_->SetHorizontalAlignment(views::Label::ALIGN_LEFT); 276 // Allow linebreaking in the middle of words if necessary, so that extremely 277 // long hostnames (longer than one line) will still be completely shown. 278 description_label_->SetAllowCharacterBreak(true); 279 AddChildView(description_label_); 280 281 if (info_.type == PageInfoModel::SECTION_INFO_IDENTITY && show_cert) { 282 link_ = new views::Link( 283 UTF16ToWide(l10n_util::GetStringUTF16(IDS_PAGEINFO_CERT_INFO_BUTTON))); 284 link_->SetController(this); 285 AddChildView(link_); 286 } 287 } 288 289 Section::~Section() { 290 } 291 292 int Section::GetHeightForWidth(int width) { 293 return LayoutItems(true, width).height(); 294 } 295 296 void Section::Layout() { 297 LayoutItems(false, width()); 298 } 299 300 void Section::LinkActivated(views::Link* source, int event_flags) { 301 owner_->ShowCertDialog(); 302 } 303 304 gfx::Size Section::LayoutItems(bool compute_bounds_only, int width) { 305 int x = kHGapToBorder; 306 int y = kVGapToImage; 307 308 // Layout the image, head-line and description. 309 gfx::Size size; 310 if (status_image_) { 311 size = status_image_->GetPreferredSize(); 312 if (!compute_bounds_only) 313 status_image_->SetBounds(x, y, size.width(), size.height()); 314 } 315 int image_height = y + size.height(); 316 x += size.width() + kHGapImageToDescription; 317 int w = width - x - kTextPaddingRight; 318 y = kVGapToHeadline; 319 if (!headline_label_->GetText().empty()) { 320 size = headline_label_->GetPreferredSize(); 321 if (!compute_bounds_only) 322 headline_label_->SetBounds(x, y, w > 0 ? w : 0, size.height()); 323 y += size.height(); 324 } else { 325 if (!compute_bounds_only) 326 headline_label_->SetBounds(x, y, 0, 0); 327 } 328 if (w > 0) { 329 int height = description_label_->GetHeightForWidth(w); 330 if (!compute_bounds_only) 331 description_label_->SetBounds(x, y, w, height); 332 y += height; 333 } else { 334 if (!compute_bounds_only) 335 description_label_->SetBounds(x, y, 0, 0); 336 } 337 if (info_.type == PageInfoModel::SECTION_INFO_IDENTITY && link_) { 338 size = link_->GetPreferredSize(); 339 if (!compute_bounds_only) 340 link_->SetBounds(x, y, size.width(), size.height()); 341 y += size.height(); 342 } 343 344 // Make sure the image is not truncated if the text doesn't contain much. 345 y = std::max(y, image_height); 346 return gfx::Size(width, y); 347 } 348 349 namespace browser { 350 351 void ShowPageInfoBubble(gfx::NativeWindow parent, 352 Profile* profile, 353 const GURL& url, 354 const NavigationEntry::SSLStatus& ssl, 355 bool show_history) { 356 // Find where to point the bubble at. 357 BrowserView* browser_view = 358 BrowserView::GetBrowserViewForNativeWindow(parent); 359 gfx::Point point; 360 if (base::i18n::IsRTL()) { 361 int width = browser_view->toolbar()->location_bar()->width(); 362 point = gfx::Point(width - kIconHorizontalOffset, 0); 363 } 364 point.Offset(0, kIconVerticalOffset); 365 views::View::ConvertPointToScreen(browser_view->toolbar()->location_bar(), 366 &point); 367 gfx::Rect bounds = browser_view->toolbar()->location_bar()->bounds(); 368 bounds.set_origin(point); 369 bounds.set_width(kIconHorizontalOffset); 370 371 // Show the bubble. If the bubble already exist - it will be closed first. 372 PageInfoBubbleView* page_info_bubble = 373 new PageInfoBubbleView(parent, profile, url, ssl, show_history); 374 Bubble* bubble = 375 Bubble::Show(browser_view->GetWidget(), bounds, 376 BubbleBorder::TOP_LEFT, 377 page_info_bubble, page_info_bubble); 378 page_info_bubble->set_bubble(bubble); 379 } 380 381 } 382