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