| // 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 <algorithm> |
| #include <string> |
| |
| #include "base/macros.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/metrics/user_metrics_action.h" |
| #include "chrome/browser/extensions/extension_action_manager.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/browser/ui/chrome_pages.h" |
| #include "chrome/browser/ui/extensions/extension_installed_bubble.h" |
| #include "chrome/browser/ui/singleton_tabs.h" |
| #include "chrome/browser/ui/sync/bubble_sync_promo_delegate.h" |
| #include "chrome/browser/ui/views/frame/browser_view.h" |
| #include "chrome/browser/ui/views/harmony/layout_delegate.h" |
| #include "chrome/browser/ui/views/location_bar/location_bar_view.h" |
| #include "chrome/browser/ui/views/location_bar/location_icon_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/gfx/image/image_skia_operations.h" |
| #include "ui/views/bubble/bubble_dialog_delegate.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" |
| |
| using extensions::Extension; |
| |
| namespace { |
| |
| const int kIconSize = 43; |
| |
| const int kRightColumnWidth = 285; |
| |
| views::Label* CreateLabel(const base::string16& text) { |
| views::Label* label = new views::Label(text); |
| label->SetMultiLine(true); |
| label->SetHorizontalAlignment(gfx::ALIGN_LEFT); |
| label->SizeToFit(kRightColumnWidth); |
| return label; |
| } |
| |
| // Provides feedback to the user upon successful installation of an |
| // extension. Depending on the type of extension, the Bubble will |
| // point to: |
| // OMNIBOX_KEYWORD-> The omnibox. |
| // BROWSER_ACTION -> The browserAction icon in the toolbar. |
| // PAGE_ACTION -> A preview of the pageAction icon in the location |
| // bar which is shown while the Bubble is shown. |
| // GENERIC -> The app menu. This case includes pageActions that don't |
| // specify a default icon. |
| class ExtensionInstalledBubbleView : public BubbleSyncPromoDelegate, |
| public views::BubbleDialogDelegateView, |
| public views::LinkListener { |
| public: |
| explicit ExtensionInstalledBubbleView(ExtensionInstalledBubble* bubble); |
| ~ExtensionInstalledBubbleView() override; |
| |
| // Recalculate the anchor position for this bubble. |
| void UpdateAnchorView(); |
| |
| void CloseBubble(); |
| |
| private: |
| Browser* browser() { return controller_->browser(); } |
| |
| // views::BubbleDialogDelegateView: |
| base::string16 GetWindowTitle() const override; |
| gfx::ImageSkia GetWindowIcon() override; |
| bool ShouldShowWindowIcon() const override; |
| bool ShouldShowCloseButton() const override; |
| View* CreateFootnoteView() override; |
| int GetDialogButtons() const override; |
| void Init() override; |
| |
| // BubbleSyncPromoDelegate: |
| void OnSignInLinkClicked() override; |
| |
| // views::LinkListener: |
| void LinkClicked(views::Link* source, int event_flags) override; |
| |
| // Gets the size of the icon, capped at kIconSize. |
| gfx::Size GetIconSize() const; |
| |
| ExtensionInstalledBubble* controller_; |
| |
| // The shortcut to open the manage shortcuts page. |
| views::Link* manage_shortcut_; |
| |
| DISALLOW_COPY_AND_ASSIGN(ExtensionInstalledBubbleView); |
| }; |
| |
| ExtensionInstalledBubbleView::ExtensionInstalledBubbleView( |
| ExtensionInstalledBubble* controller) |
| : BubbleDialogDelegateView(nullptr, |
| controller->anchor_position() == |
| ExtensionInstalledBubble::ANCHOR_OMNIBOX |
| ? views::BubbleBorder::TOP_LEFT |
| : views::BubbleBorder::TOP_RIGHT), |
| controller_(controller), |
| manage_shortcut_(nullptr) {} |
| |
| ExtensionInstalledBubbleView::~ExtensionInstalledBubbleView() {} |
| |
| void ExtensionInstalledBubbleView::UpdateAnchorView() { |
| BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser()); |
| |
| views::View* reference_view = nullptr; |
| switch (controller_->anchor_position()) { |
| case ExtensionInstalledBubble::ANCHOR_ACTION: { |
| BrowserActionsContainer* container = |
| browser_view->toolbar()->browser_actions(); |
| // Hitting this DCHECK means |ShouldShow| failed. |
| DCHECK(!container->animating()); |
| |
| reference_view = container->GetViewForId(controller_->extension()->id()); |
| break; |
| } |
| case ExtensionInstalledBubble::ANCHOR_OMNIBOX: { |
| reference_view = browser_view->GetLocationBarView()->location_icon_view(); |
| break; |
| } |
| case ExtensionInstalledBubble::ANCHOR_APP_MENU: |
| // Will be caught below. |
| break; |
| } |
| |
| // Default case. |
| if (!reference_view || !reference_view->visible()) |
| reference_view = browser_view->toolbar()->app_menu_button(); |
| SetAnchorView(reference_view); |
| } |
| |
| void ExtensionInstalledBubbleView::CloseBubble() { |
| if (GetWidget()->IsClosed()) |
| return; |
| GetWidget()->Close(); |
| } |
| |
| base::string16 ExtensionInstalledBubbleView::GetWindowTitle() const { |
| // Add the heading (for all options). |
| base::string16 extension_name = |
| base::UTF8ToUTF16(controller_->extension()->name()); |
| base::i18n::AdjustStringForLocaleDirection(&extension_name); |
| return l10n_util::GetStringFUTF16(IDS_EXTENSION_INSTALLED_HEADING, |
| extension_name); |
| } |
| |
| gfx::ImageSkia ExtensionInstalledBubbleView::GetWindowIcon() { |
| const SkBitmap& bitmap = controller_->icon(); |
| return gfx::ImageSkiaOperations::CreateResizedImage( |
| gfx::ImageSkia::CreateFrom1xBitmap(bitmap), |
| skia::ImageOperations::RESIZE_BEST, GetIconSize()); |
| } |
| |
| bool ExtensionInstalledBubbleView::ShouldShowWindowIcon() const { |
| return true; |
| } |
| |
| views::View* ExtensionInstalledBubbleView::CreateFootnoteView() { |
| if (!(controller_->options() & ExtensionInstalledBubble::SIGN_IN_PROMO)) |
| return nullptr; |
| |
| return new BubbleSyncPromoView(this, |
| IDS_EXTENSION_INSTALLED_SYNC_PROMO_LINK_NEW, |
| IDS_EXTENSION_INSTALLED_SYNC_PROMO_NEW); |
| } |
| |
| int ExtensionInstalledBubbleView::GetDialogButtons() const { |
| return ui::DIALOG_BUTTON_NONE; |
| } |
| |
| bool ExtensionInstalledBubbleView::ShouldShowCloseButton() const { |
| return true; |
| } |
| |
| void ExtensionInstalledBubbleView::Init() { |
| UpdateAnchorView(); |
| |
| // The Extension Installed bubble takes on various forms, depending on the |
| // type of extension installed. In general, though, they are all similar: |
| // |
| // ------------------------- |
| // | Icon | Title (x) | |
| // | Info | |
| // | Extra info | |
| // ------------------------- |
| // |
| // Icon and Title 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. |
| |
| LayoutDelegate* layout_delegate = LayoutDelegate::Get(); |
| std::unique_ptr<views::BoxLayout> layout(new views::BoxLayout( |
| views::BoxLayout::kVertical, 0, 0, |
| layout_delegate->GetMetric( |
| LayoutDelegate::Metric::RELATED_CONTROL_VERTICAL_SPACING))); |
| layout->set_minimum_cross_axis_size(kRightColumnWidth); |
| // Indent by the size of the icon. |
| layout->set_inside_border_insets(gfx::Insets( |
| 0, |
| GetIconSize().width() + |
| layout_delegate->GetMetric( |
| LayoutDelegate::Metric::UNRELATED_CONTROL_HORIZONTAL_SPACING), |
| 0, 0)); |
| layout->set_cross_axis_alignment( |
| views::BoxLayout::CROSS_AXIS_ALIGNMENT_START); |
| SetLayoutManager(layout.release()); |
| |
| if (controller_->options() & ExtensionInstalledBubble::HOW_TO_USE) |
| AddChildView(CreateLabel(controller_->GetHowToUseDescription())); |
| |
| if (controller_->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); |
| AddChildView(manage_shortcut_); |
| } |
| |
| if (controller_->options() & ExtensionInstalledBubble::HOW_TO_MANAGE) { |
| AddChildView(CreateLabel( |
| l10n_util::GetStringUTF16(IDS_EXTENSION_INSTALLED_MANAGE_INFO))); |
| } |
| } |
| |
| void ExtensionInstalledBubbleView::OnSignInLinkClicked() { |
| chrome::ShowBrowserSignin( |
| browser(), |
| signin_metrics::AccessPoint::ACCESS_POINT_EXTENSION_INSTALL_BUBBLE); |
| CloseBubble(); |
| } |
| |
| void ExtensionInstalledBubbleView::LinkClicked(views::Link* source, |
| int event_flags) { |
| DCHECK_EQ(manage_shortcut_, source); |
| |
| std::string configure_url = chrome::kChromeUIExtensionsURL; |
| configure_url += chrome::kExtensionConfigureCommandsSubPage; |
| chrome::NavigateParams params( |
| chrome::GetSingletonTabNavigateParams(browser(), GURL(configure_url))); |
| chrome::Navigate(¶ms); |
| CloseBubble(); |
| } |
| |
| gfx::Size ExtensionInstalledBubbleView::GetIconSize() const { |
| const SkBitmap& bitmap = controller_->icon(); |
| // Scale down to 43x43, but allow smaller icons (don't scale up). |
| gfx::Size size(bitmap.width(), bitmap.height()); |
| return size.width() > kIconSize || size.height() > kIconSize |
| ? gfx::Size(kIconSize, kIconSize) |
| : size; |
| } |
| |
| // NB: This bubble is using the temporarily-deprecated bubble manager interface |
| // BubbleUi. Do not copy this pattern. |
| class ExtensionInstalledBubbleUi : public BubbleUi, |
| public views::WidgetObserver { |
| public: |
| explicit ExtensionInstalledBubbleUi(ExtensionInstalledBubble* bubble); |
| ~ExtensionInstalledBubbleUi() override; |
| |
| // BubbleUi: |
| void Show(BubbleReference bubble_reference) override; |
| void Close() override; |
| void UpdateAnchorPosition() override; |
| |
| // WidgetObserver: |
| void OnWidgetClosing(views::Widget* widget) override; |
| |
| private: |
| ExtensionInstalledBubble* bubble_; |
| ExtensionInstalledBubbleView* bubble_view_; |
| |
| DISALLOW_COPY_AND_ASSIGN(ExtensionInstalledBubbleUi); |
| }; |
| |
| ExtensionInstalledBubbleUi::ExtensionInstalledBubbleUi( |
| ExtensionInstalledBubble* bubble) |
| : bubble_(bubble), bubble_view_(nullptr) { |
| DCHECK(bubble_); |
| } |
| |
| ExtensionInstalledBubbleUi::~ExtensionInstalledBubbleUi() { |
| if (bubble_view_) |
| bubble_view_->GetWidget()->RemoveObserver(this); |
| } |
| |
| void ExtensionInstalledBubbleUi::Show(BubbleReference /*bubble_reference*/) { |
| bubble_view_ = new ExtensionInstalledBubbleView(bubble_); |
| |
| views::BubbleDialogDelegateView::CreateBubble(bubble_view_)->Show(); |
| bubble_view_->GetWidget()->AddObserver(this); |
| content::RecordAction( |
| base::UserMetricsAction("Signin_Impression_FromExtensionInstallBubble")); |
| } |
| |
| void ExtensionInstalledBubbleUi::Close() { |
| if (bubble_view_) |
| bubble_view_->CloseBubble(); |
| } |
| |
| void ExtensionInstalledBubbleUi::UpdateAnchorPosition() { |
| DCHECK(bubble_view_); |
| bubble_view_->UpdateAnchorView(); |
| } |
| |
| void ExtensionInstalledBubbleUi::OnWidgetClosing(views::Widget* widget) { |
| widget->RemoveObserver(this); |
| bubble_view_ = nullptr; |
| } |
| |
| } // namespace |
| |
| // Views specific implementation. |
| bool ExtensionInstalledBubble::ShouldShow() { |
| if (anchor_position() == ANCHOR_ACTION) { |
| BrowserActionsContainer* container = |
| BrowserView::GetBrowserViewForBrowser(browser()) |
| ->toolbar() |
| ->browser_actions(); |
| return !container->animating(); |
| } |
| return true; |
| } |
| |
| // Implemented here to create the platform specific instance of the BubbleUi. |
| std::unique_ptr<BubbleUi> ExtensionInstalledBubble::BuildBubbleUi() { |
| return base::WrapUnique(new ExtensionInstalledBubbleUi(this)); |
| } |