blob: 9e86a81c0141750b39a8315af535117870dc91b5 [file] [log] [blame]
// Copyright 2013 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/extensions/extension_installed_bubble_view.h"
#include <algorithm>
#include <string>
#include "base/macros.h"
#include "base/metrics/user_metrics_action.h"
#include "chrome/browser/extensions/extension_action_manager.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/chrome_pages.h"
#include "chrome/browser/ui/singleton_tabs.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/browser/ui/views/location_bar/location_bar_view.h"
#include "chrome/browser/ui/views/location_bar/page_action_with_badge_view.h"
#include "chrome/browser/ui/views/sync/bubble_sync_promo_view.h"
#include "chrome/browser/ui/views/toolbar/app_menu_button.h"
#include "chrome/browser/ui/views/toolbar/browser_actions_container.h"
#include "chrome/browser/ui/views/toolbar/toolbar_view.h"
#include "chrome/common/url_constants.h"
#include "chrome/grit/chromium_strings.h"
#include "chrome/grit/generated_resources.h"
#include "components/bubble/bubble_controller.h"
#include "components/bubble/bubble_ui.h"
#include "content/public/browser/user_metrics.h"
#include "extensions/common/extension.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/gfx/render_text.h"
#include "ui/gfx/text_elider.h"
#include "ui/resources/grit/ui_resources.h"
#include "ui/views/bubble/bubble_frame_view.h"
#include "ui/views/controls/button/image_button.h"
#include "ui/views/controls/image_view.h"
#include "ui/views/controls/label.h"
#include "ui/views/controls/link.h"
#include "ui/views/controls/link_listener.h"
#include "ui/views/layout/box_layout.h"
#include "ui/views/layout/fill_layout.h"
#include "ui/views/layout/grid_layout.h"
#include "ui/views/layout/layout_constants.h"
using extensions::Extension;
namespace {
const int kIconSize = 43;
const int kRightColumnWidth = 285;
views::Label* CreateLabel(const base::string16& text,
const gfx::FontList& font) {
views::Label* label = new views::Label(text, font);
label->SetMultiLine(true);
label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
return label;
}
class HeadingAndCloseButtonView : public views::View {
public:
HeadingAndCloseButtonView(views::Label* heading, views::LabelButton* close)
: heading_(heading), close_(close) {
AddChildView(heading_);
AddChildView(close_);
}
~HeadingAndCloseButtonView() override {}
void Layout() override {
gfx::Size close_size = close_->GetPreferredSize();
gfx::Size view_size = size();
// Close button is in the upper right and always gets its full desired size.
close_->SetBounds(view_size.width() - close_size.width(),
0,
close_size.width(),
close_size.height());
// The heading takes up the remaining room (modulo padding).
heading_->SetBounds(
0,
0,
view_size.width() - close_size.width() -
views::kUnrelatedControlHorizontalSpacing,
view_size.height());
}
gfx::Size GetPreferredSize() const override {
gfx::Size heading_size = heading_->GetPreferredSize();
gfx::Size close_size = close_->GetPreferredSize();
return gfx::Size(kRightColumnWidth,
std::max(heading_size.height(), close_size.height()));
}
int GetHeightForWidth(int width) const override {
gfx::Size close_size = close_->GetPreferredSize();
int heading_width = width - views::kUnrelatedControlHorizontalSpacing -
close_size.width();
return std::max(heading_->GetHeightForWidth(heading_width),
close_size.height());
}
private:
views::Label* heading_;
views::LabelButton* close_;
DISALLOW_COPY_AND_ASSIGN(HeadingAndCloseButtonView);
};
} // namespace
ExtensionInstalledBubbleView::ExtensionInstalledBubbleView(
ExtensionInstalledBubble* bubble,
BubbleReference bubble_reference)
: bubble_(bubble),
bubble_reference_(bubble_reference),
extension_(bubble->extension()),
browser_(bubble->browser()),
type_(bubble->type()),
anchor_position_(bubble->anchor_position()),
close_(nullptr),
manage_shortcut_(nullptr) {}
ExtensionInstalledBubbleView::~ExtensionInstalledBubbleView() {}
void ExtensionInstalledBubbleView::UpdateAnchorView() {
BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser_);
views::View* reference_view = nullptr;
switch (anchor_position_) {
case ExtensionInstalledBubble::ANCHOR_BROWSER_ACTION: {
BrowserActionsContainer* container =
browser_view->GetToolbarView()->browser_actions();
// Hitting this DCHECK means |ShouldShow| failed.
DCHECK(!container->animating());
reference_view = container->GetViewForId(extension_->id());
// If the view is not visible then it is in the chevron, so point the
// install bubble to the chevron instead. If this is an incognito window,
// both could be invisible.
if (!reference_view || !reference_view->visible()) {
reference_view = container->chevron();
if (!reference_view || !reference_view->visible())
reference_view = nullptr; // fall back to app menu below.
}
break;
}
case ExtensionInstalledBubble::ANCHOR_PAGE_ACTION: {
LocationBarView* location_bar_view = browser_view->GetLocationBarView();
ExtensionAction* page_action =
extensions::ExtensionActionManager::Get(browser_->profile())
->GetPageAction(*extension_);
location_bar_view->SetPreviewEnabledPageAction(page_action,
true); // preview_enabled
reference_view = location_bar_view->GetPageActionView(page_action);
DCHECK(reference_view);
break;
}
case ExtensionInstalledBubble::ANCHOR_OMNIBOX: {
LocationBarView* location_bar_view = browser_view->GetLocationBarView();
reference_view = location_bar_view;
DCHECK(reference_view);
break;
}
case ExtensionInstalledBubble::ANCHOR_APP_MENU:
// Will be caught below.
break;
}
// Default case.
if (!reference_view)
reference_view = browser_view->GetToolbarView()->app_menu_button();
SetAnchorView(reference_view);
}
views::View* ExtensionInstalledBubbleView::CreateFootnoteView() {
if (!(bubble_->options() & ExtensionInstalledBubble::SIGN_IN_PROMO))
return nullptr;
return new BubbleSyncPromoView(this,
IDS_EXTENSION_INSTALLED_SYNC_PROMO_LINK_NEW,
IDS_EXTENSION_INSTALLED_SYNC_PROMO_NEW);
}
void ExtensionInstalledBubbleView::WindowClosing() {
if (anchor_position_ == ExtensionInstalledBubble::ANCHOR_PAGE_ACTION) {
BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser_);
browser_view->GetLocationBarView()->SetPreviewEnabledPageAction(
extensions::ExtensionActionManager::Get(browser_->profile())
->GetPageAction(*extension_),
false); // preview_enabled
}
}
gfx::Rect ExtensionInstalledBubbleView::GetAnchorRect() const {
// For omnibox keyword bubbles, move the arrow to point to the left edge
// of the omnibox, just to the right of the icon.
if (type_ == ExtensionInstalledBubble::OMNIBOX_KEYWORD) {
const LocationBarView* location_bar_view =
BrowserView::GetBrowserViewForBrowser(browser_)->GetLocationBarView();
return gfx::Rect(location_bar_view->GetOmniboxViewOrigin(),
gfx::Size(0, location_bar_view->omnibox_view()->height()));
}
return views::BubbleDelegateView::GetAnchorRect();
}
void ExtensionInstalledBubbleView::OnWidgetClosing(views::Widget* widget) {
if (bubble_reference_) {
DCHECK_EQ(widget, GetWidget());
// A more specific close reason should already be recorded.
// This is the catch-all close reason for this bubble.
bubble_reference_->CloseBubble(BUBBLE_CLOSE_FOCUS_LOST);
}
}
void ExtensionInstalledBubbleView::OnWidgetActivationChanged(
views::Widget* widget,
bool active) {
if (!active && bubble_reference_ && widget == GetWidget())
bubble_reference_->CloseBubble(BUBBLE_CLOSE_FOCUS_LOST);
}
bool ExtensionInstalledBubbleView::AcceleratorPressed(
const ui::Accelerator& accelerator) {
if (!close_on_esc() || accelerator.key_code() != ui::VKEY_ESCAPE)
return false;
DCHECK(bubble_reference_);
bool did_close = bubble_reference_->CloseBubble(BUBBLE_CLOSE_USER_DISMISSED);
DCHECK(did_close);
return true;
}
void ExtensionInstalledBubbleView::OnSignInLinkClicked() {
GetWidget()->Close();
chrome::ShowBrowserSignin(
browser_,
signin_metrics::AccessPoint::ACCESS_POINT_EXTENSION_INSTALL_BUBBLE);
}
void ExtensionInstalledBubbleView::ButtonPressed(views::Button* sender,
const ui::Event& event) {
DCHECK_EQ(sender, close_);
GetWidget()->Close();
}
void ExtensionInstalledBubbleView::LinkClicked(views::Link* source,
int event_flags) {
DCHECK_EQ(manage_shortcut_, source);
GetWidget()->Close();
std::string configure_url = chrome::kChromeUIExtensionsURL;
configure_url += chrome::kExtensionConfigureCommandsSubPage;
chrome::NavigateParams params(
chrome::GetSingletonTabNavigateParams(browser_, GURL(configure_url)));
chrome::Navigate(&params);
}
void ExtensionInstalledBubbleView::InitLayout() {
// The Extension Installed bubble takes on various forms, depending on the
// type of extension installed. In general, though, they are all similar:
//
// -------------------------
// | | Heading [X] |
// | Icon | Info |
// | | Extra info |
// -------------------------
//
// Icon and Heading are always shown (as well as the close button).
// Info is shown for browser actions, page actions and Omnibox keyword
// extensions and might list keyboard shorcut for the former two types.
// Extra info is...
// ... for other types, either a description of how to manage the extension
// or a link to configure the keybinding shortcut (if one exists).
// Extra info can include a promo for signing into sync.
const ExtensionInstalledBubble& bubble = *bubble_;
// The number of rows in the content section of the bubble.
int main_content_row_count = 1;
if (bubble.options() & ExtensionInstalledBubble::HOW_TO_USE)
++main_content_row_count;
if (bubble.options() & ExtensionInstalledBubble::SHOW_KEYBINDING)
++main_content_row_count;
if (bubble.options() & ExtensionInstalledBubble::HOW_TO_MANAGE)
++main_content_row_count;
views::GridLayout* layout = new views::GridLayout(this);
SetLayoutManager(layout);
const int cs_id = 0;
views::ColumnSet* main_cs = layout->AddColumnSet(cs_id);
// Icon column.
main_cs->AddColumn(views::GridLayout::CENTER, views::GridLayout::LEADING, 0,
views::GridLayout::USE_PREF, 0, 0);
main_cs->AddPaddingColumn(0, views::kUnrelatedControlHorizontalSpacing);
// Heading column.
main_cs->AddColumn(views::GridLayout::LEADING, views::GridLayout::LEADING, 0,
views::GridLayout::FIXED, kRightColumnWidth, 0);
ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
const gfx::FontList& font_list = rb.GetFontList(ui::ResourceBundle::BaseFont);
const SkBitmap& bitmap = bubble.icon();
// Add the icon (for all options).
// Scale down to 43x43, but allow smaller icons (don't scale up).
gfx::Size size(bitmap.width(), bitmap.height());
if (size.width() > kIconSize || size.height() > kIconSize)
size = gfx::Size(kIconSize, kIconSize);
views::ImageView* icon = new views::ImageView();
icon->SetImageSize(size);
icon->SetImage(gfx::ImageSkia::CreateFrom1xBitmap(bitmap));
layout->StartRow(0, cs_id);
layout->AddView(icon, 1, main_content_row_count);
// Add the heading (for all options).
base::string16 extension_name = base::UTF8ToUTF16(extension_->name());
base::i18n::AdjustStringForLocaleDirection(&extension_name);
views::Label* heading =
CreateLabel(l10n_util::GetStringFUTF16(IDS_EXTENSION_INSTALLED_HEADING,
extension_name),
rb.GetFontList(ui::ResourceBundle::MediumFont));
close_ = views::BubbleFrameView::CreateCloseButton(this);
HeadingAndCloseButtonView* heading_and_close =
new HeadingAndCloseButtonView(heading, close_);
layout->AddView(heading_and_close);
layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
auto add_content_view = [&layout, &cs_id](views::View* view) {
layout->StartRow(0, cs_id);
// Skip the icon column.
layout->SkipColumns(1);
layout->AddView(view);
layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
};
if (bubble.options() & ExtensionInstalledBubble::HOW_TO_USE) {
add_content_view(CreateLabel(bubble.GetHowToUseDescription(), font_list));
}
if (bubble.options() & ExtensionInstalledBubble::SHOW_KEYBINDING) {
manage_shortcut_ = new views::Link(
l10n_util::GetStringUTF16(IDS_EXTENSION_INSTALLED_MANAGE_SHORTCUTS));
manage_shortcut_->set_listener(this);
manage_shortcut_->SetUnderline(false);
add_content_view(manage_shortcut_);
}
if (bubble.options() & ExtensionInstalledBubble::HOW_TO_MANAGE) {
add_content_view(CreateLabel(
l10n_util::GetStringUTF16(IDS_EXTENSION_INSTALLED_MANAGE_INFO),
font_list));
}
}
// Views specific implementation.
bool ExtensionInstalledBubble::ShouldShow() {
if (anchor_position() == ANCHOR_BROWSER_ACTION) {
BrowserActionsContainer* container =
BrowserView::GetBrowserViewForBrowser(browser())
->GetToolbarView()
->browser_actions();
return !container->animating();
}
return true;
}
class ExtensionInstalledBubbleUi : public BubbleUi {
public:
explicit ExtensionInstalledBubbleUi(ExtensionInstalledBubble* bubble);
~ExtensionInstalledBubbleUi() override;
private:
// BubbleUi:
void Show(BubbleReference bubble_reference) override;
void Close() override;
void UpdateAnchorPosition() override;
ExtensionInstalledBubble* bubble_;
ExtensionInstalledBubbleView* delegate_view_;
DISALLOW_COPY_AND_ASSIGN(ExtensionInstalledBubbleUi);
};
// Implemented here to create the platform specific instance of the BubbleUi.
scoped_ptr<BubbleUi> ExtensionInstalledBubble::BuildBubbleUi() {
return make_scoped_ptr(new ExtensionInstalledBubbleUi(this));
}
ExtensionInstalledBubbleUi::ExtensionInstalledBubbleUi(
ExtensionInstalledBubble* bubble)
: bubble_(bubble), delegate_view_(nullptr) {
DCHECK(bubble_);
}
ExtensionInstalledBubbleUi::~ExtensionInstalledBubbleUi() {}
void ExtensionInstalledBubbleUi::Show(BubbleReference bubble_reference) {
// Owned by widget.
delegate_view_ = new ExtensionInstalledBubbleView(bubble_, bubble_reference);
delegate_view_->UpdateAnchorView();
delegate_view_->set_arrow(bubble_->type() == bubble_->OMNIBOX_KEYWORD
? views::BubbleBorder::TOP_LEFT
: views::BubbleBorder::TOP_RIGHT);
delegate_view_->InitLayout();
views::BubbleDelegateView::CreateBubble(delegate_view_)->Show();
content::RecordAction(
base::UserMetricsAction("Signin_Impression_FromExtensionInstallBubble"));
}
void ExtensionInstalledBubbleUi::Close() {
if (delegate_view_) {
delegate_view_->GetWidget()->Close();
delegate_view_ = nullptr;
}
}
void ExtensionInstalledBubbleUi::UpdateAnchorPosition() {
DCHECK(delegate_view_);
delegate_view_->UpdateAnchorView();
}