Home | History | Annotate | Download | only in views
      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