普通文本  |  382行  |  12.99 KB

// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/ui/views/page_info_bubble_view.h"

#include "base/utf_string_conversions.h"
#include "chrome/browser/google/google_util.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/browser/ui/views/bubble/bubble.h"
#include "chrome/browser/ui/views/toolbar_view.h"
#include "chrome/common/url_constants.h"
#include "content/browser/cert_store.h"
#include "content/browser/certificate_viewer.h"
#include "grit/generated_resources.h"
#include "grit/locale_settings.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/gfx/image.h"
#include "views/controls/image_view.h"
#include "views/controls/label.h"
#include "views/controls/separator.h"
#include "views/layout/grid_layout.h"
#include "views/widget/widget.h"
#include "views/window/window.h"

namespace {

// Layout constants.
const int kHGapToBorder = 11;
const int kVGapToImage = 10;
const int kVGapToHeadline = 7;
const int kHGapImageToDescription = 6;
const int kTextPaddingRight = 10;
const int kPaddingBelowSeparator = 4;
const int kPaddingAboveSeparator = 13;
const int kIconHorizontalOffset = 27;
const int kIconVerticalOffset = -7;

// The duration of the animation that resizes the bubble once the async
// information is provided through the ModelChanged event.
const int kPageInfoSlideDuration = 300;

// A section contains an image that shows a status (good or bad), a title, an
// optional head-line (in bold) and a description.
class Section : public views::View,
                public views::LinkController {
 public:
  Section(PageInfoBubbleView* owner,
          const PageInfoModel::SectionInfo& section_info,
          const SkBitmap* status_icon,
          bool show_cert);
  virtual ~Section();

  // views::View methods:
  virtual int GetHeightForWidth(int w);
  virtual void Layout();

  // views::LinkController methods:
  virtual void LinkActivated(views::Link* source, int event_flags);

 private:
  // Calculate the layout if |compute_bounds_only|, otherwise does Layout also.
  gfx::Size LayoutItems(bool compute_bounds_only, int width);

  // The view that owns this Section object.
  PageInfoBubbleView* owner_;

  // The information this view represents.
  PageInfoModel::SectionInfo info_;

  views::ImageView* status_image_;
  views::Label* headline_label_;
  views::Label* description_label_;
  views::Link* link_;

  DISALLOW_COPY_AND_ASSIGN(Section);
};

}  // namespace

////////////////////////////////////////////////////////////////////////////////
// PageInfoBubbleView

Bubble* PageInfoBubbleView::bubble_ = NULL;

PageInfoBubbleView::PageInfoBubbleView(gfx::NativeWindow parent_window,
                                       Profile* profile,
                                       const GURL& url,
                                       const NavigationEntry::SSLStatus& ssl,
                                       bool show_history)
    : ALLOW_THIS_IN_INITIALIZER_LIST(model_(profile, url, ssl,
                                            show_history, this)),
      parent_window_(parent_window),
      cert_id_(ssl.cert_id()),
      help_center_link_(NULL),
      ALLOW_THIS_IN_INITIALIZER_LIST(resize_animation_(this)),
      animation_start_height_(0) {
  if (bubble_)
    bubble_->Close();
  if (cert_id_ > 0) {
    scoped_refptr<net::X509Certificate> cert;
    CertStore::GetInstance()->RetrieveCert(cert_id_, &cert);
    // When running with fake certificate (Chrome Frame), we have no os
    // certificate, so there is no cert to show. Don't bother showing the cert
    // info link in that case.
    if (!cert.get() || !cert->os_cert_handle())
      cert_id_ = 0;
  }
  LayoutSections();
}

PageInfoBubbleView::~PageInfoBubbleView() {
}

void PageInfoBubbleView::ShowCertDialog() {
  ShowCertificateViewerByID(parent_window_, cert_id_);
}

void PageInfoBubbleView::LayoutSections() {
  // Remove all the existing sections.
  RemoveAllChildViews(true);

  views::GridLayout* layout = new views::GridLayout(this);
  SetLayoutManager(layout);
  views::ColumnSet* columns = layout->AddColumnSet(0);
  columns->AddColumn(views::GridLayout::FILL,  // Horizontal resize.
                     views::GridLayout::FILL,  // Vertical resize.
                     1,   // Resize weight.
                     views::GridLayout::USE_PREF,  // Size type.
                     0,   // Ignored for USE_PREF.
                     0);  // Minimum size.
  // Add a column set for aligning the text when it has no icons (such as the
  // help center link).
  columns = layout->AddColumnSet(1);
  columns->AddPaddingColumn(
      0, kHGapToBorder + kIconHorizontalOffset + kHGapImageToDescription);
  columns->AddColumn(views::GridLayout::LEADING,  // Horizontal resize.
                     views::GridLayout::FILL,     // Vertical resize.
                     1,   // Resize weight.
                     views::GridLayout::USE_PREF,  // Size type.
                     0,   // Ignored for USE_PREF.
                     0);  // Minimum size.

  int count = model_.GetSectionCount();
  for (int i = 0; i < count; ++i) {
    PageInfoModel::SectionInfo info = model_.GetSectionInfo(i);
    layout->StartRow(0, 0);
    const SkBitmap* icon = *model_.GetIconImage(info.icon_id);
    layout->AddView(new Section(this, info, icon, cert_id_ > 0));

    // Add separator after all sections.
    layout->AddPaddingRow(0, kPaddingAboveSeparator);
    layout->StartRow(0, 0);
    layout->AddView(new views::Separator());
    layout->AddPaddingRow(0, kPaddingBelowSeparator);
  }

  // Then add the help center link at the bottom.
  layout->StartRow(0, 1);
  help_center_link_ = new views::Link(
      UTF16ToWide(l10n_util::GetStringUTF16(IDS_PAGE_INFO_HELP_CENTER_LINK)));
  help_center_link_->SetController(this);
  layout->AddView(help_center_link_);
}

gfx::Size PageInfoBubbleView::GetPreferredSize() {
  gfx::Size size(views::Window::GetLocalizedContentsSize(
      IDS_PAGEINFOBUBBLE_WIDTH_CHARS, IDS_PAGEINFOBUBBLE_HEIGHT_LINES));
  size.set_height(0);

  int count = model_.GetSectionCount();
  for (int i = 0; i < count; ++i) {
    PageInfoModel::SectionInfo info = model_.GetSectionInfo(i);
    const SkBitmap* icon = *model_.GetIconImage(info.icon_id);
    Section section(this, info, icon, cert_id_ > 0);
    size.Enlarge(0, section.GetHeightForWidth(size.width()));
  }

  // Calculate how much space the separators take up (with padding).
  views::Separator separator;
  gfx::Size separator_size = separator.GetPreferredSize();
  gfx::Size separator_plus_padding(0, separator_size.height() +
                                      kPaddingAboveSeparator +
                                      kPaddingBelowSeparator);

  // Account for the separators and padding within sections.
  size.Enlarge(0, (count - 1) * separator_plus_padding.height());

  // Account for the Help Center link and the separator above it.
  gfx::Size link_size = help_center_link_->GetPreferredSize();
  size.Enlarge(0, separator_plus_padding.height() +
                  link_size.height());

  if (!resize_animation_.is_animating())
    return size;

  // We are animating from animation_start_height_ to size.
  int target_height = animation_start_height_ + static_cast<int>(
      (size.height() - animation_start_height_) *
      resize_animation_.GetCurrentValue());
  size.set_height(target_height);
  return size;
}

void PageInfoBubbleView::ModelChanged() {
  animation_start_height_ = bounds().height();
  LayoutSections();
  resize_animation_.SetSlideDuration(kPageInfoSlideDuration);
  resize_animation_.Show();
}

void PageInfoBubbleView::BubbleClosing(Bubble* bubble, bool closed_by_escape) {
  resize_animation_.Reset();
  bubble_ = NULL;
}

bool PageInfoBubbleView::CloseOnEscape() {
  return true;
}

bool PageInfoBubbleView::FadeInOnShow() {
  return false;
}

std::wstring PageInfoBubbleView::accessible_name() {
  return L"PageInfoBubble";
}

void PageInfoBubbleView::LinkActivated(views::Link* source, int event_flags) {
  // We want to make sure the info bubble closes once the link is activated.  So
  // we close it explicitly rather than relying on a side-effect of opening a
  // new tab (see http://crosbug.com/10186).
  bubble_->Close();

  GURL url = google_util::AppendGoogleLocaleParam(
      GURL(chrome::kPageInfoHelpCenterURL));
  Browser* browser = BrowserList::GetLastActive();
  browser->OpenURL(url, GURL(), NEW_FOREGROUND_TAB, PageTransition::LINK);
}

void PageInfoBubbleView::AnimationEnded(const ui::Animation* animation) {
  bubble_->SizeToContents();
}

void PageInfoBubbleView::AnimationProgressed(const ui::Animation* animation) {
  bubble_->SizeToContents();
}

////////////////////////////////////////////////////////////////////////////////
// Section

Section::Section(PageInfoBubbleView* owner,
                 const PageInfoModel::SectionInfo& section_info,
                 const SkBitmap* state_icon,
                 bool show_cert)
    : owner_(owner),
      info_(section_info),
      status_image_(NULL),
      link_(NULL) {
  if (state_icon) {
    status_image_ = new views::ImageView();
    status_image_->SetImage(*state_icon);
    AddChildView(status_image_);
  }

  headline_label_ = new views::Label(UTF16ToWideHack(info_.headline));
  headline_label_->SetFont(
      headline_label_->font().DeriveFont(0, gfx::Font::BOLD));
  headline_label_->SetHorizontalAlignment(views::Label::ALIGN_LEFT);
  AddChildView(headline_label_);

  description_label_ = new views::Label(UTF16ToWideHack(info_.description));
  description_label_->SetMultiLine(true);
  description_label_->SetHorizontalAlignment(views::Label::ALIGN_LEFT);
  // Allow linebreaking in the middle of words if necessary, so that extremely
  // long hostnames (longer than one line) will still be completely shown.
  description_label_->SetAllowCharacterBreak(true);
  AddChildView(description_label_);

  if (info_.type == PageInfoModel::SECTION_INFO_IDENTITY && show_cert) {
    link_ = new views::Link(
        UTF16ToWide(l10n_util::GetStringUTF16(IDS_PAGEINFO_CERT_INFO_BUTTON)));
    link_->SetController(this);
    AddChildView(link_);
  }
}

Section::~Section() {
}

int Section::GetHeightForWidth(int width) {
  return LayoutItems(true, width).height();
}

void Section::Layout() {
  LayoutItems(false, width());
}

void Section::LinkActivated(views::Link* source, int event_flags) {
  owner_->ShowCertDialog();
}

gfx::Size Section::LayoutItems(bool compute_bounds_only, int width) {
  int x = kHGapToBorder;
  int y = kVGapToImage;

  // Layout the image, head-line and description.
  gfx::Size size;
  if (status_image_) {
      size = status_image_->GetPreferredSize();
    if (!compute_bounds_only)
      status_image_->SetBounds(x, y, size.width(), size.height());
  }
  int image_height = y + size.height();
  x += size.width() + kHGapImageToDescription;
  int w = width - x - kTextPaddingRight;
  y = kVGapToHeadline;
  if (!headline_label_->GetText().empty()) {
    size = headline_label_->GetPreferredSize();
    if (!compute_bounds_only)
      headline_label_->SetBounds(x, y, w > 0 ? w : 0, size.height());
    y += size.height();
  } else {
    if (!compute_bounds_only)
      headline_label_->SetBounds(x, y, 0, 0);
  }
  if (w > 0) {
    int height = description_label_->GetHeightForWidth(w);
    if (!compute_bounds_only)
      description_label_->SetBounds(x, y, w, height);
    y += height;
  } else {
    if (!compute_bounds_only)
      description_label_->SetBounds(x, y, 0, 0);
  }
  if (info_.type == PageInfoModel::SECTION_INFO_IDENTITY && link_) {
    size = link_->GetPreferredSize();
    if (!compute_bounds_only)
      link_->SetBounds(x, y, size.width(), size.height());
    y += size.height();
  }

  // Make sure the image is not truncated if the text doesn't contain much.
  y = std::max(y, image_height);
  return gfx::Size(width, y);
}

namespace browser {

void ShowPageInfoBubble(gfx::NativeWindow parent,
                        Profile* profile,
                        const GURL& url,
                        const NavigationEntry::SSLStatus& ssl,
                        bool show_history) {
  // Find where to point the bubble at.
  BrowserView* browser_view =
      BrowserView::GetBrowserViewForNativeWindow(parent);
  gfx::Point point;
  if (base::i18n::IsRTL()) {
    int width = browser_view->toolbar()->location_bar()->width();
    point = gfx::Point(width - kIconHorizontalOffset, 0);
  }
  point.Offset(0, kIconVerticalOffset);
  views::View::ConvertPointToScreen(browser_view->toolbar()->location_bar(),
                                    &point);
  gfx::Rect bounds = browser_view->toolbar()->location_bar()->bounds();
  bounds.set_origin(point);
  bounds.set_width(kIconHorizontalOffset);

  // Show the bubble. If the bubble already exist - it will be closed first.
  PageInfoBubbleView* page_info_bubble =
      new PageInfoBubbleView(parent, profile, url, ssl, show_history);
  Bubble* bubble =
      Bubble::Show(browser_view->GetWidget(), bounds,
                   BubbleBorder::TOP_LEFT,
                   page_info_bubble, page_info_bubble);
  page_info_bubble->set_bubble(bubble);
}

}