Home | History | Annotate | Download | only in extensions
      1 // Copyright 2014 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/extensions/bookmark_app_bubble_view.h"
      6 
      7 #include "base/strings/string16.h"
      8 #include "base/strings/utf_string_conversions.h"
      9 #include "chrome/browser/extensions/app_icon_loader_impl.h"
     10 #include "chrome/browser/extensions/bookmark_app_helper.h"
     11 #include "chrome/browser/extensions/extension_service.h"
     12 #include "chrome/browser/extensions/launch_util.h"
     13 #include "chrome/browser/profiles/profile.h"
     14 #include "chrome/common/extensions/extension_constants.h"
     15 #include "chrome/grit/generated_resources.h"
     16 #include "extensions/browser/extension_prefs.h"
     17 #include "extensions/browser/extension_registry.h"
     18 #include "extensions/browser/extension_system.h"
     19 #include "extensions/browser/pref_names.h"
     20 #include "extensions/browser/uninstall_reason.h"
     21 #include "extensions/common/constants.h"
     22 #include "ui/base/l10n/l10n_util.h"
     23 #include "ui/base/resource/resource_bundle.h"
     24 #include "ui/events/keycodes/keyboard_codes.h"
     25 #include "ui/views/controls/button/checkbox.h"
     26 #include "ui/views/controls/button/label_button.h"
     27 #include "ui/views/controls/image_view.h"
     28 #include "ui/views/controls/label.h"
     29 #include "ui/views/controls/textfield/textfield.h"
     30 #include "ui/views/layout/grid_layout.h"
     31 #include "ui/views/layout/layout_constants.h"
     32 #include "ui/views/widget/widget.h"
     33 
     34 using views::ColumnSet;
     35 using views::GridLayout;
     36 
     37 namespace {
     38 
     39 // Minimum width of the the bubble.
     40 const int kMinBubbleWidth = 300;
     41 // Minimum width of the the textfield.
     42 const int kMinTextfieldWidth = 200;
     43 // Size of the icon.
     44 const int kIconSize = extension_misc::EXTENSION_ICON_MEDIUM;
     45 
     46 ExtensionService* GetExtensionService(Profile* profile) {
     47   return extensions::ExtensionSystem::Get(profile)->extension_service();
     48 }
     49 
     50 }  // namespace
     51 
     52 BookmarkAppBubbleView* BookmarkAppBubbleView::bookmark_app_bubble_ = NULL;
     53 
     54 BookmarkAppBubbleView::~BookmarkAppBubbleView() {
     55 }
     56 
     57 // static
     58 void BookmarkAppBubbleView::ShowBubble(views::View* anchor_view,
     59                                        Profile* profile,
     60                                        const WebApplicationInfo& web_app_info,
     61                                        const std::string& extension_id) {
     62   if (bookmark_app_bubble_ != NULL)
     63     return;
     64 
     65   bookmark_app_bubble_ = new BookmarkAppBubbleView(
     66       anchor_view, profile, web_app_info, extension_id);
     67   views::BubbleDelegateView::CreateBubble(bookmark_app_bubble_)->Show();
     68   // Select the entire title textfield contents when the bubble is first shown.
     69   bookmark_app_bubble_->title_tf_->SelectAll(true);
     70   bookmark_app_bubble_->SetArrowPaintType(views::BubbleBorder::PAINT_NONE);
     71 }
     72 
     73 BookmarkAppBubbleView::BookmarkAppBubbleView(
     74     views::View* anchor_view,
     75     Profile* profile,
     76     const WebApplicationInfo& web_app_info,
     77     const std::string& extension_id)
     78     : BubbleDelegateView(anchor_view, views::BubbleBorder::TOP_RIGHT),
     79       profile_(profile),
     80       web_app_info_(web_app_info),
     81       extension_id_(extension_id),
     82       add_button_(NULL),
     83       cancel_button_(NULL),
     84       open_as_tab_checkbox_(NULL),
     85       title_tf_(NULL),
     86       remove_app_(true),
     87       app_icon_loader_(new extensions::AppIconLoaderImpl(profile,
     88                                                          kIconSize,
     89                                                          this)) {
     90   const SkColor background_color = GetNativeTheme()->GetSystemColor(
     91       ui::NativeTheme::kColorId_DialogBackground);
     92   set_arrow(views::BubbleBorder::TOP_CENTER);
     93   set_color(background_color);
     94   set_background(views::Background::CreateSolidBackground(background_color));
     95   set_margins(gfx::Insets(views::kPanelVertMargin, 0, 0, 0));
     96 }
     97 
     98 void BookmarkAppBubbleView::Init() {
     99   views::Label* title_label = new views::Label(
    100       l10n_util::GetStringUTF16(IDS_BOOKMARK_APP_BUBBLE_TITLE));
    101   ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance();
    102   title_label->SetFontList(rb->GetFontList(ui::ResourceBundle::MediumFont));
    103   title_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
    104 
    105   add_button_ =
    106       new views::LabelButton(this, l10n_util::GetStringUTF16(IDS_ADD));
    107   add_button_->SetStyle(views::Button::STYLE_BUTTON);
    108   add_button_->SetIsDefault(true);
    109 
    110   cancel_button_ =
    111       new views::LabelButton(this, l10n_util::GetStringUTF16(IDS_CANCEL));
    112   cancel_button_->SetStyle(views::Button::STYLE_BUTTON);
    113 
    114   GridLayout* layout = new GridLayout(this);
    115   SetLayoutManager(layout);
    116 
    117   // Column sets used in the layout of the bubble.
    118   enum ColumnSetID {
    119     TITLE_COLUMN_SET_ID,
    120     TITLE_TEXT_COLUMN_SET_ID,
    121     CONTENT_COLUMN_SET_ID
    122   };
    123 
    124   // The column layout used for the title and checkbox.
    125   ColumnSet* cs = layout->AddColumnSet(TITLE_COLUMN_SET_ID);
    126   cs->AddPaddingColumn(0, views::kButtonHEdgeMarginNew);
    127   cs->AddColumn(
    128       GridLayout::LEADING, GridLayout::CENTER, 0, GridLayout::USE_PREF, 0, 0);
    129   cs->AddPaddingColumn(0, views::kButtonHEdgeMarginNew);
    130 
    131   // The column layout used for the icon and text box.
    132   cs = layout->AddColumnSet(TITLE_TEXT_COLUMN_SET_ID);
    133   cs->AddPaddingColumn(0, views::kButtonHEdgeMarginNew);
    134   cs->AddColumn(GridLayout::LEADING,
    135                 GridLayout::CENTER,
    136                 0,
    137                 GridLayout::USE_PREF,
    138                 0,
    139                 0);
    140   cs->AddPaddingColumn(0, views::kButtonHEdgeMarginNew);
    141   cs->AddColumn(GridLayout::FILL,
    142                 GridLayout::CENTER,
    143                 1,
    144                 GridLayout::USE_PREF,
    145                 0,
    146                 kMinTextfieldWidth);
    147   cs->AddPaddingColumn(0, views::kButtonHEdgeMarginNew);
    148 
    149   // The column layout used for the row with buttons.
    150   cs = layout->AddColumnSet(CONTENT_COLUMN_SET_ID);
    151   cs->AddPaddingColumn(0, views::kButtonHEdgeMarginNew);
    152   cs->AddColumn(
    153       GridLayout::LEADING, GridLayout::CENTER, 0, GridLayout::USE_PREF, 0, 0);
    154   cs->AddPaddingColumn(1, views::kUnrelatedControlHorizontalSpacing);
    155   cs->AddColumn(
    156       GridLayout::LEADING, GridLayout::CENTER, 0, GridLayout::USE_PREF, 0, 0);
    157   cs->AddPaddingColumn(0, views::kRelatedButtonHSpacing);
    158   cs->AddColumn(
    159       GridLayout::LEADING, GridLayout::CENTER, 0, GridLayout::USE_PREF, 0, 0);
    160   cs->AddPaddingColumn(0, views::kButtonHEdgeMarginNew);
    161 
    162   layout->StartRow(0, TITLE_COLUMN_SET_ID);
    163   layout->AddView(title_label);
    164   layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
    165 
    166   const extensions::Extension* extension =
    167       extensions::ExtensionRegistry::Get(profile_)->GetExtensionById(
    168           extension_id_, extensions::ExtensionRegistry::EVERYTHING);
    169 
    170   layout->StartRow(0, TITLE_TEXT_COLUMN_SET_ID);
    171   icon_image_view_ = new views::ImageView();
    172   icon_image_view_->SetImageSize(gfx::Size(kIconSize, kIconSize));
    173   layout->AddView(icon_image_view_);
    174   app_icon_loader_->FetchImage(extension_id_);
    175 
    176   title_tf_ = new views::Textfield();
    177   title_tf_->SetText(extension ? base::UTF8ToUTF16(extension->name())
    178                                : web_app_info_.title);
    179   layout->AddView(title_tf_);
    180   layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
    181 
    182   layout->StartRow(0, CONTENT_COLUMN_SET_ID);
    183   open_as_tab_checkbox_ = new views::Checkbox(
    184       l10n_util::GetStringUTF16(IDS_BOOKMARK_APP_BUBBLE_OPEN_AS_TAB));
    185   open_as_tab_checkbox_->SetChecked(
    186       profile_->GetPrefs()->GetInteger(
    187           extensions::pref_names::kBookmarkAppCreationLaunchType) ==
    188               extensions::LAUNCH_TYPE_REGULAR);
    189   layout->AddView(open_as_tab_checkbox_);
    190   layout->AddView(add_button_);
    191   layout->AddView(cancel_button_);
    192   layout->AddPaddingRow(0, views::kUnrelatedControlVerticalSpacing);
    193 
    194   AddAccelerator(ui::Accelerator(ui::VKEY_RETURN, ui::EF_NONE));
    195 }
    196 
    197 views::View* BookmarkAppBubbleView::GetInitiallyFocusedView() {
    198   return title_tf_;
    199 }
    200 
    201 void BookmarkAppBubbleView::WindowClosing() {
    202   // We have to reset |bookmark_app_bubble_| here, not in our destructor,
    203   // because we'll be destroyed asynchronously and the shown state will be
    204   // checked before then.
    205   DCHECK_EQ(bookmark_app_bubble_, this);
    206   bookmark_app_bubble_ = NULL;
    207 
    208   if (remove_app_) {
    209     GetExtensionService(profile_)
    210         ->UninstallExtension(extension_id_,
    211                              extensions::UNINSTALL_REASON_INSTALL_CANCELED,
    212                              base::Bind(&base::DoNothing),
    213                              NULL);
    214   } else {
    215     ApplyEdits();
    216   }
    217 }
    218 
    219 bool BookmarkAppBubbleView::AcceleratorPressed(
    220     const ui::Accelerator& accelerator) {
    221   if (accelerator.key_code() == ui::VKEY_RETURN) {
    222     HandleButtonPressed(add_button_);
    223   }
    224 
    225   return BubbleDelegateView::AcceleratorPressed(accelerator);
    226 }
    227 
    228 gfx::Size BookmarkAppBubbleView::GetMinimumSize() const {
    229   gfx::Size size(views::BubbleDelegateView::GetPreferredSize());
    230   size.SetToMax(gfx::Size(kMinBubbleWidth, 0));
    231   return size;
    232 }
    233 
    234 void BookmarkAppBubbleView::ButtonPressed(views::Button* sender,
    235                                           const ui::Event& event) {
    236   HandleButtonPressed(sender);
    237 }
    238 
    239 void BookmarkAppBubbleView::SetAppImage(const std::string& id,
    240                                         const gfx::ImageSkia& image) {
    241   DCHECK_EQ(extension_id_, id);
    242   icon_image_view_->SetImage(image);
    243 }
    244 
    245 void BookmarkAppBubbleView::HandleButtonPressed(views::Button* sender) {
    246   // Unset |remove_app_| so we don't delete the bookmark after the window
    247   // closes.
    248   if (sender == add_button_)
    249     remove_app_ = false;
    250 
    251   GetWidget()->Close();
    252 }
    253 
    254 void BookmarkAppBubbleView::ApplyEdits() {
    255   // Set the launch type based on the checkbox.
    256   extensions::LaunchType launch_type = open_as_tab_checkbox_->checked()
    257       ? extensions::LAUNCH_TYPE_REGULAR
    258       : extensions::LAUNCH_TYPE_WINDOW;
    259   profile_->GetPrefs()->SetInteger(
    260           extensions::pref_names::kBookmarkAppCreationLaunchType, launch_type);
    261   extensions::SetLaunchType(GetExtensionService(profile_),
    262                             extension_id_,
    263                             launch_type);
    264 
    265   const extensions::Extension* extension =
    266       extensions::ExtensionRegistry::Get(profile_)->GetExtensionById(
    267           extension_id_, extensions::ExtensionRegistry::EVERYTHING);
    268   if (extension && base::UTF8ToUTF16(extension->name()) == title_tf_->text())
    269     return;
    270 
    271   // Reinstall the app with an updated name.
    272   WebApplicationInfo install_info(web_app_info_);
    273   install_info.title = title_tf_->text();
    274 
    275   extensions::CreateOrUpdateBookmarkApp(GetExtensionService(profile_),
    276                                         install_info);
    277 }
    278