create_application_shortcut_view.cc revision 5821806d5e7f356e8fa4b058a389a808ea183019
1// Copyright (c) 2012 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/create_application_shortcut_view.h" 6 7#include <algorithm> 8 9#include "base/bind.h" 10#include "base/bind_helpers.h" 11#include "base/utf_string_conversions.h" 12#include "base/win/windows_version.h" 13#include "chrome/browser/extensions/tab_helper.h" 14#include "chrome/browser/favicon/favicon_tab_helper.h" 15#include "chrome/browser/prefs/pref_service.h" 16#include "chrome/browser/profiles/profile.h" 17#include "chrome/browser/ui/browser.h" 18#include "chrome/browser/ui/browser_commands.h" 19#include "chrome/browser/ui/browser_finder.h" 20#include "chrome/browser/ui/web_applications/web_app_ui.h" 21#include "chrome/browser/ui/webui/extensions/extension_icon_source.h" 22#include "chrome/common/chrome_constants.h" 23#include "chrome/common/extensions/extension.h" 24#include "chrome/common/extensions/extension_resource.h" 25#include "chrome/common/pref_names.h" 26#include "content/public/browser/web_contents.h" 27#include "content/public/browser/web_contents_delegate.h" 28#include "grit/generated_resources.h" 29#include "grit/locale_settings.h" 30#include "grit/theme_resources.h" 31#include "net/base/load_flags.h" 32#include "net/url_request/url_request.h" 33#include "third_party/skia/include/core/SkBitmap.h" 34#include "third_party/skia/include/core/SkPaint.h" 35#include "third_party/skia/include/core/SkRect.h" 36#include "ui/base/l10n/l10n_util.h" 37#include "ui/base/resource/resource_bundle.h" 38#include "ui/gfx/canvas.h" 39#include "ui/gfx/codec/png_codec.h" 40#include "ui/views/controls/button/checkbox.h" 41#include "ui/views/controls/image_view.h" 42#include "ui/views/controls/label.h" 43#include "ui/views/layout/grid_layout.h" 44#include "ui/views/layout/layout_constants.h" 45#include "ui/views/widget/widget.h" 46#include "ui/views/window/dialog_client_view.h" 47 48namespace { 49 50const int kAppIconSize = 32; 51 52// AppInfoView shows the application icon and title. 53class AppInfoView : public views::View { 54 public: 55 AppInfoView(const string16& title, 56 const string16& description, 57 const SkBitmap& icon); 58 59 // Updates the title/description of the web app. 60 void UpdateText(const string16& title, const string16& description); 61 62 // Updates the icon of the web app. 63 void UpdateIcon(const gfx::Image& image); 64 65 // Overridden from views::View: 66 virtual void OnPaint(gfx::Canvas* canvas); 67 68 private: 69 // Initializes the controls 70 void Init(const string16& title, 71 const string16& description, const SkBitmap& icon); 72 73 // Creates or updates description label. 74 void PrepareDescriptionLabel(const string16& description); 75 76 // Sets up layout manager. 77 void SetupLayout(); 78 79 views::ImageView* icon_; 80 views::Label* title_; 81 views::Label* description_; 82}; 83 84AppInfoView::AppInfoView(const string16& title, 85 const string16& description, 86 const SkBitmap& icon) 87 : icon_(NULL), 88 title_(NULL), 89 description_(NULL) { 90 Init(title, description, icon); 91} 92 93void AppInfoView::Init(const string16& title_text, 94 const string16& description_text, 95 const SkBitmap& icon) { 96 icon_ = new views::ImageView(); 97 icon_->SetImage(gfx::ImageSkia(icon)); 98 icon_->SetImageSize(gfx::Size(kAppIconSize, kAppIconSize)); 99 100 title_ = new views::Label(title_text); 101 title_->SetMultiLine(true); 102 title_->SetHorizontalAlignment(gfx::ALIGN_LEFT); 103 title_->SetFont(ui::ResourceBundle::GetSharedInstance().GetFont( 104 ui::ResourceBundle::BaseFont).DeriveFont(0, gfx::Font::BOLD)); 105 106 if (!description_text.empty()) { 107 PrepareDescriptionLabel(description_text); 108 } 109 110 SetupLayout(); 111} 112 113void AppInfoView::PrepareDescriptionLabel(const string16& description) { 114 DCHECK(!description.empty()); 115 116 const size_t kMaxLength = 200; 117 const string16 kEllipsis(ASCIIToUTF16(" ... ")); 118 119 string16 text = description; 120 if (text.length() > kMaxLength) { 121 text = text.substr(0, kMaxLength); 122 text += kEllipsis; 123 } 124 125 if (description_) { 126 description_->SetText(text); 127 } else { 128 description_ = new views::Label(text); 129 description_->SetMultiLine(true); 130 description_->SetHorizontalAlignment(gfx::ALIGN_LEFT); 131 } 132} 133 134void AppInfoView::SetupLayout() { 135 views::GridLayout* layout = views::GridLayout::CreatePanel(this); 136 SetLayoutManager(layout); 137 138 static const int kColumnSetId = 0; 139 views::ColumnSet* column_set = layout->AddColumnSet(kColumnSetId); 140 column_set->AddColumn(views::GridLayout::CENTER, views::GridLayout::LEADING, 141 20.0f, views::GridLayout::FIXED, 142 kAppIconSize, kAppIconSize); 143 column_set->AddColumn(views::GridLayout::FILL, views::GridLayout::CENTER, 144 80.0f, views::GridLayout::USE_PREF, 0, 0); 145 146 layout->StartRow(0, kColumnSetId); 147 layout->AddView(icon_, 1, description_ ? 2 : 1); 148 layout->AddView(title_); 149 150 if (description_) { 151 layout->StartRow(0, kColumnSetId); 152 layout->SkipColumns(1); 153 layout->AddView(description_); 154 } 155} 156 157void AppInfoView::UpdateText(const string16& title, 158 const string16& description) { 159 title_->SetText(title); 160 PrepareDescriptionLabel(description); 161 162 SetupLayout(); 163} 164 165void AppInfoView::UpdateIcon(const gfx::Image& image) { 166 if (!image.IsEmpty()) 167 icon_->SetImage(image.ToImageSkia()); 168} 169 170void AppInfoView::OnPaint(gfx::Canvas* canvas) { 171 gfx::Rect bounds = GetLocalBounds(); 172 173 SkRect border_rect = { 174 SkIntToScalar(bounds.x()), 175 SkIntToScalar(bounds.y()), 176 SkIntToScalar(bounds.right()), 177 SkIntToScalar(bounds.bottom()) 178 }; 179 180 SkPaint border_paint; 181 border_paint.setAntiAlias(true); 182 border_paint.setARGB(0xFF, 0xC8, 0xC8, 0xC8); 183 184 canvas->sk_canvas()->drawRoundRect(border_rect, SkIntToScalar(2), 185 SkIntToScalar(2), border_paint); 186 187 SkRect inner_rect = { 188 border_rect.fLeft + SkDoubleToScalar(0.5), 189 border_rect.fTop + SkDoubleToScalar(0.5), 190 border_rect.fRight - SkDoubleToScalar(0.5), 191 border_rect.fBottom - SkDoubleToScalar(0.5), 192 }; 193 194 SkPaint inner_paint; 195 inner_paint.setAntiAlias(true); 196 inner_paint.setARGB(0xFF, 0xF8, 0xF8, 0xF8); 197 canvas->sk_canvas()->drawRoundRect(inner_rect, SkDoubleToScalar(1.5), 198 SkDoubleToScalar(1.5), inner_paint); 199} 200 201} // namespace 202 203namespace chrome { 204 205void ShowCreateWebAppShortcutsDialog(gfx::NativeWindow parent_window, 206 content::WebContents* web_contents) { 207 views::Widget::CreateWindowWithParent( 208 new CreateUrlApplicationShortcutView(web_contents), 209 parent_window)->Show(); 210} 211 212void ShowCreateChromeAppShortcutsDialog(gfx::NativeWindow parent_window, 213 Profile* profile, 214 const extensions::Extension* app) { 215 views::Widget::CreateWindowWithParent( 216 new CreateChromeApplicationShortcutView(profile, app), 217 parent_window)->Show(); 218} 219 220} // namespace chrome 221 222class CreateUrlApplicationShortcutView::IconDownloadCallbackFunctor { 223 public: 224 explicit IconDownloadCallbackFunctor(CreateUrlApplicationShortcutView* owner) 225 : owner_(owner) { 226 } 227 228 void Run(int download_id, bool errored, const SkBitmap& image) { 229 if (owner_) 230 owner_->OnIconDownloaded(errored, image); 231 delete this; 232 } 233 234 void Cancel() { 235 owner_ = NULL; 236 } 237 238 private: 239 CreateUrlApplicationShortcutView* owner_; 240}; 241 242CreateApplicationShortcutView::CreateApplicationShortcutView(Profile* profile) 243 : profile_(profile), 244 app_info_(NULL), 245 create_shortcuts_label_(NULL), 246 desktop_check_box_(NULL), 247 menu_check_box_(NULL), 248 quick_launch_check_box_(NULL) {} 249 250CreateApplicationShortcutView::~CreateApplicationShortcutView() {} 251 252void CreateApplicationShortcutView::InitControls() { 253 // Create controls 254 app_info_ = new AppInfoView(shortcut_info_.title, shortcut_info_.description, 255 shortcut_info_.favicon.IsEmpty() ? SkBitmap() : 256 *shortcut_info_.favicon.ToSkBitmap()); 257 create_shortcuts_label_ = new views::Label( 258 l10n_util::GetStringUTF16(IDS_CREATE_SHORTCUTS_LABEL)); 259 create_shortcuts_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT); 260 261 desktop_check_box_ = AddCheckbox( 262 l10n_util::GetStringUTF16(IDS_CREATE_SHORTCUTS_DESKTOP_CHKBOX), 263 profile_->GetPrefs()->GetBoolean(prefs::kWebAppCreateOnDesktop)); 264 265 menu_check_box_ = NULL; 266 quick_launch_check_box_ = NULL; 267 268#if defined(OS_WIN) 269 // Do not allow creating shortcuts on the Start Screen for Windows 8. 270 if (base::win::GetVersion() < base::win::VERSION_WIN8) { 271 menu_check_box_ = AddCheckbox( 272 l10n_util::GetStringUTF16(IDS_CREATE_SHORTCUTS_START_MENU_CHKBOX), 273 profile_->GetPrefs()->GetBoolean(prefs::kWebAppCreateInAppsMenu)); 274 } 275 276 quick_launch_check_box_ = AddCheckbox( 277 (base::win::GetVersion() >= base::win::VERSION_WIN7) ? 278 l10n_util::GetStringUTF16(IDS_PIN_TO_TASKBAR_CHKBOX) : 279 l10n_util::GetStringUTF16( 280 IDS_CREATE_SHORTCUTS_QUICK_LAUNCH_BAR_CHKBOX), 281 profile_->GetPrefs()->GetBoolean(prefs::kWebAppCreateInQuickLaunchBar)); 282#elif defined(OS_POSIX) 283 menu_check_box_ = AddCheckbox( 284 l10n_util::GetStringUTF16(IDS_CREATE_SHORTCUTS_MENU_CHKBOX), 285 profile_->GetPrefs()->GetBoolean(prefs::kWebAppCreateInAppsMenu)); 286#endif 287 288 // Layout controls 289 views::GridLayout* layout = views::GridLayout::CreatePanel(this); 290 SetLayoutManager(layout); 291 292 static const int kHeaderColumnSetId = 0; 293 views::ColumnSet* column_set = layout->AddColumnSet(kHeaderColumnSetId); 294 column_set->AddColumn(views::GridLayout::FILL, views::GridLayout::CENTER, 295 100.0f, views::GridLayout::FIXED, 0, 0); 296 297 static const int kTableColumnSetId = 1; 298 column_set = layout->AddColumnSet(kTableColumnSetId); 299 column_set->AddPaddingColumn(0, views::kPanelHorizIndentation); 300 column_set->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, 301 100.0f, views::GridLayout::USE_PREF, 0, 0); 302 303 layout->StartRow(0, kHeaderColumnSetId); 304 layout->AddView(app_info_); 305 306 layout->AddPaddingRow(0, views::kPanelSubVerticalSpacing); 307 layout->StartRow(0, kHeaderColumnSetId); 308 layout->AddView(create_shortcuts_label_); 309 310 layout->AddPaddingRow(0, views::kLabelToControlVerticalSpacing); 311 layout->StartRow(0, kTableColumnSetId); 312 layout->AddView(desktop_check_box_); 313 314 if (menu_check_box_ != NULL) { 315 layout->AddPaddingRow(0, views::kRelatedControlSmallVerticalSpacing); 316 layout->StartRow(0, kTableColumnSetId); 317 layout->AddView(menu_check_box_); 318 } 319 320 if (quick_launch_check_box_ != NULL) { 321 layout->AddPaddingRow(0, views::kRelatedControlSmallVerticalSpacing); 322 layout->StartRow(0, kTableColumnSetId); 323 layout->AddView(quick_launch_check_box_); 324 } 325} 326 327gfx::Size CreateApplicationShortcutView::GetPreferredSize() { 328 // TODO(evanm): should this use IDS_CREATE_SHORTCUTS_DIALOG_WIDTH_CHARS? 329 static const int kDialogWidth = 360; 330 int height = GetLayoutManager()->GetPreferredHeightForWidth(this, 331 kDialogWidth); 332 return gfx::Size(kDialogWidth, height); 333} 334 335string16 CreateApplicationShortcutView::GetDialogButtonLabel( 336 ui::DialogButton button) const { 337 if (button == ui::DIALOG_BUTTON_OK) 338 return l10n_util::GetStringUTF16(IDS_CREATE_SHORTCUTS_COMMIT); 339 return string16(); 340} 341 342bool CreateApplicationShortcutView::IsDialogButtonEnabled( 343 ui::DialogButton button) const { 344 if (button == ui::DIALOG_BUTTON_OK) 345 return desktop_check_box_->checked() || 346 ((menu_check_box_ != NULL) && 347 menu_check_box_->checked()) || 348 ((quick_launch_check_box_ != NULL) && 349 quick_launch_check_box_->checked()); 350 351 return true; 352} 353 354bool CreateApplicationShortcutView::CanResize() const { 355 return false; 356} 357 358bool CreateApplicationShortcutView::CanMaximize() const { 359 return false; 360} 361 362ui::ModalType CreateApplicationShortcutView::GetModalType() const { 363 return ui::MODAL_TYPE_WINDOW; 364} 365 366string16 CreateApplicationShortcutView::GetWindowTitle() const { 367 return l10n_util::GetStringUTF16(IDS_CREATE_SHORTCUTS_TITLE); 368} 369 370bool CreateApplicationShortcutView::Accept() { 371 if (!IsDialogButtonEnabled(ui::DIALOG_BUTTON_OK)) 372 return false; 373 374 shortcut_info_.create_on_desktop = desktop_check_box_->checked(); 375 shortcut_info_.create_in_applications_menu = menu_check_box_ == NULL ? false : 376 menu_check_box_->checked(); 377 378#if defined(OS_WIN) 379 shortcut_info_.create_in_quick_launch_bar = quick_launch_check_box_ == NULL ? 380 NULL : quick_launch_check_box_->checked(); 381#elif defined(OS_POSIX) 382 // Create shortcut in Mac dock or as Linux (gnome/kde) application launcher 383 // are not implemented yet. 384 shortcut_info_.create_in_quick_launch_bar = false; 385#endif 386 387 web_app::CreateShortcuts(shortcut_info_); 388 return true; 389} 390 391 392views::View* CreateApplicationShortcutView::GetContentsView() { 393 return this; 394} 395 396views::Checkbox* CreateApplicationShortcutView::AddCheckbox( 397 const string16& text, bool checked) { 398 views::Checkbox* checkbox = new views::Checkbox(text); 399 checkbox->SetChecked(checked); 400 checkbox->set_listener(this); 401 return checkbox; 402} 403 404void CreateApplicationShortcutView::ButtonPressed(views::Button* sender, 405 const ui::Event& event) { 406 if (sender == desktop_check_box_) { 407 profile_->GetPrefs()->SetBoolean(prefs::kWebAppCreateOnDesktop, 408 desktop_check_box_->checked()); 409 } else if (sender == menu_check_box_) { 410 profile_->GetPrefs()->SetBoolean(prefs::kWebAppCreateInAppsMenu, 411 menu_check_box_->checked()); 412 } else if (sender == quick_launch_check_box_) { 413 profile_->GetPrefs()->SetBoolean(prefs::kWebAppCreateInQuickLaunchBar, 414 quick_launch_check_box_->checked()); 415 } 416 417 // When no checkbox is checked we should not have the action button enabled. 418 GetDialogClientView()->UpdateDialogButtons(); 419} 420 421CreateUrlApplicationShortcutView::CreateUrlApplicationShortcutView( 422 content::WebContents* web_contents) 423 : CreateApplicationShortcutView( 424 Profile::FromBrowserContext(web_contents->GetBrowserContext())), 425 web_contents_(web_contents), 426 pending_download_(NULL) { 427 428 web_app::GetShortcutInfoForTab(web_contents_, &shortcut_info_); 429 const WebApplicationInfo& app_info = 430 extensions::TabHelper::FromWebContents(web_contents_)->web_app_info(); 431 if (!app_info.icons.empty()) { 432 web_app::GetIconsInfo(app_info, &unprocessed_icons_); 433 FetchIcon(); 434 } 435 436 InitControls(); 437} 438 439CreateUrlApplicationShortcutView::~CreateUrlApplicationShortcutView() { 440 if (pending_download_) 441 pending_download_->Cancel(); 442} 443 444bool CreateUrlApplicationShortcutView::Accept() { 445 if (!CreateApplicationShortcutView::Accept()) 446 return false; 447 448 extensions::TabHelper::FromWebContents(web_contents_)-> 449 SetAppIcon(shortcut_info_.favicon.IsEmpty() 450 ? SkBitmap() 451 : *shortcut_info_.favicon.ToSkBitmap()); 452 Browser* browser = browser::FindBrowserWithWebContents(web_contents_); 453 if (browser) 454 chrome::ConvertTabToAppWindow(browser, web_contents_); 455 return true; 456} 457 458void CreateUrlApplicationShortcutView::FetchIcon() { 459 // There should only be fetch job at a time. 460 DCHECK(pending_download_ == NULL); 461 462 if (unprocessed_icons_.empty()) // No icons to fetch. 463 return; 464 465 pending_download_ = new IconDownloadCallbackFunctor(this); 466 DCHECK(pending_download_); 467 468 FaviconTabHelper::FromWebContents(web_contents_)-> 469 DownloadImage(unprocessed_icons_.back().url, 470 std::max(unprocessed_icons_.back().width, 471 unprocessed_icons_.back().height), 472 history::FAVICON, 473 base::Bind(&IconDownloadCallbackFunctor::Run, 474 base::Unretained(pending_download_))); 475 476 unprocessed_icons_.pop_back(); 477} 478 479void CreateUrlApplicationShortcutView::OnIconDownloaded(bool errored, 480 const SkBitmap& image) { 481 pending_download_ = NULL; 482 483 if (!errored && !image.isNull()) { 484 shortcut_info_.favicon = gfx::Image(image); 485 static_cast<AppInfoView*>(app_info_)->UpdateIcon(shortcut_info_.favicon); 486 } else { 487 FetchIcon(); 488 } 489} 490 491CreateChromeApplicationShortcutView::CreateChromeApplicationShortcutView( 492 Profile* profile, 493 const extensions::Extension* app) : 494 CreateApplicationShortcutView(profile), 495 app_(app), 496 ALLOW_THIS_IN_INITIALIZER_LIST(tracker_(this)) { 497 shortcut_info_.extension_id = app_->id(); 498 shortcut_info_.url = GURL(app_->launch_web_url()); 499 shortcut_info_.title = UTF8ToUTF16(app_->name()); 500 shortcut_info_.description = UTF8ToUTF16(app_->description()); 501 shortcut_info_.extension_path = app_->path(); 502 shortcut_info_.profile_path = profile->GetPath(); 503 504 // The icon will be resized to |max_size|. 505 const gfx::Size max_size(kAppIconSize, kAppIconSize); 506 507 // Look for an icon. If there is no icon at the ideal size, 508 // we will resize whatever we can get. Making a large icon smaller 509 // is prefered to making a small icon larger, so look for a larger 510 // icon first: 511 ExtensionResource icon_resource = app_->GetIconResource( 512 kAppIconSize, 513 ExtensionIconSet::MATCH_BIGGER); 514 515 // If no icon exists that is the desired size or larger, get the 516 // largest icon available: 517 if (icon_resource.empty()) { 518 icon_resource = app_->GetIconResource( 519 kAppIconSize, 520 ExtensionIconSet::MATCH_SMALLER); 521 } 522 523 InitControls(); 524 525 // tracker_.LoadImage() can call OnImageLoaded() before it returns if the 526 // image is cached. This is very rare. app_info_ must be initialized 527 // when OnImageLoaded() is called, so we check it here. 528 CHECK(app_info_); 529 tracker_.LoadImage(app_, 530 icon_resource, 531 max_size, 532 ImageLoadingTracker::DONT_CACHE); 533} 534 535CreateChromeApplicationShortcutView::~CreateChromeApplicationShortcutView() {} 536 537// Called by tracker_ when the app's icon is loaded. 538void CreateChromeApplicationShortcutView::OnImageLoaded( 539 const gfx::Image& image, 540 const std::string& extension_id, 541 int index) { 542 if (image.IsEmpty()) { 543 shortcut_info_.favicon = ui::ResourceBundle::GetSharedInstance(). 544 GetImageNamed(IDR_APP_DEFAULT_ICON); 545 } else { 546 shortcut_info_.favicon = image; 547 } 548 549 CHECK(app_info_); 550 static_cast<AppInfoView*>(app_info_)->UpdateIcon(shortcut_info_.favicon); 551} 552