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