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