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 38namespace { 39 40const int kAppIconSize = 32; 41 42// AppInfoView shows the application icon and title. 43class 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 74AppInfoView::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 83void 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 103void 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 124void 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 147void AppInfoView::UpdateText(const string16& title, 148 const string16& description) { 149 title_->SetText(UTF16ToWide(title)); 150 PrepareDescriptionLabel(description); 151 152 SetupLayout(); 153} 154 155void AppInfoView::UpdateIcon(const SkBitmap& new_icon) { 156 DCHECK(icon_ != NULL); 157 158 icon_->SetImage(new_icon); 159} 160 161void 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 194namespace browser { 195 196void 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 202void 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 211class 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 231CreateApplicationShortcutView::CreateApplicationShortcutView(Profile* profile) 232 : profile_(profile) {} 233 234CreateApplicationShortcutView::~CreateApplicationShortcutView() {} 235 236void 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 307gfx::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 315std::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 324bool 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 336bool CreateApplicationShortcutView::CanResize() const { 337 return false; 338} 339 340bool CreateApplicationShortcutView::CanMaximize() const { 341 return false; 342} 343 344bool CreateApplicationShortcutView::IsAlwaysOnTop() const { 345 return false; 346} 347 348bool CreateApplicationShortcutView::HasAlwaysOnTopMenu() const { 349 return false; 350} 351 352bool CreateApplicationShortcutView::IsModal() const { 353 return true; 354} 355 356std::wstring CreateApplicationShortcutView::GetWindowTitle() const { 357 return UTF16ToWide(l10n_util::GetStringUTF16(IDS_CREATE_SHORTCUTS_TITLE)); 358} 359 360bool 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 384views::View* CreateApplicationShortcutView::GetContentsView() { 385 return this; 386} 387 388views::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 396void 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 412CreateUrlApplicationShortcutView::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 429CreateUrlApplicationShortcutView::~CreateUrlApplicationShortcutView() { 430 if (pending_download_) 431 pending_download_->Cancel(); 432} 433 434bool 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 446void 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 466void 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 478CreateChromeApplicationShortcutView::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 517CreateChromeApplicationShortcutView::~CreateChromeApplicationShortcutView() {} 518 519// Called by tracker_ when the app's icon is loaded. 520void 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