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