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