• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
AppInfoView(const string16 & title,const string16 & description,const SkBitmap & icon)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 
Init(const string16 & title_text,const string16 & description_text,const SkBitmap & icon)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 
PrepareDescriptionLabel(const string16 & description)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 
SetupLayout()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 
UpdateText(const string16 & title,const string16 & description)147 void AppInfoView::UpdateText(const string16& title,
148                              const string16& description) {
149   title_->SetText(UTF16ToWide(title));
150   PrepareDescriptionLabel(description);
151 
152   SetupLayout();
153 }
154 
UpdateIcon(const SkBitmap & new_icon)155 void AppInfoView::UpdateIcon(const SkBitmap& new_icon) {
156   DCHECK(icon_ != NULL);
157 
158   icon_->SetImage(new_icon);
159 }
160 
OnPaint(gfx::Canvas * canvas)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 
ShowCreateWebAppShortcutsDialog(gfx::NativeWindow parent_window,TabContentsWrapper * tab_contents)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 
ShowCreateChromeAppShortcutsDialog(gfx::NativeWindow parent_window,Profile * profile,const Extension * app)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:
IconDownloadCallbackFunctor(CreateUrlApplicationShortcutView * owner)213   explicit IconDownloadCallbackFunctor(CreateUrlApplicationShortcutView* owner)
214       : owner_(owner) {
215   }
216 
Run(int download_id,bool errored,const SkBitmap & image)217   void Run(int download_id, bool errored, const SkBitmap& image) {
218     if (owner_)
219       owner_->OnIconDownloaded(errored, image);
220     delete this;
221   }
222 
Cancel()223   void Cancel() {
224     owner_ = NULL;
225   }
226 
227  private:
228   CreateUrlApplicationShortcutView* owner_;
229 };
230 
CreateApplicationShortcutView(Profile * profile)231 CreateApplicationShortcutView::CreateApplicationShortcutView(Profile* profile)
232     : profile_(profile) {}
233 
~CreateApplicationShortcutView()234 CreateApplicationShortcutView::~CreateApplicationShortcutView() {}
235 
InitControls()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 
GetPreferredSize()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 
GetDialogButtonLabel(MessageBoxFlags::DialogButton button) const315 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 
IsDialogButtonEnabled(MessageBoxFlags::DialogButton button) const324 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 
CanResize() const336 bool CreateApplicationShortcutView::CanResize() const {
337   return false;
338 }
339 
CanMaximize() const340 bool CreateApplicationShortcutView::CanMaximize() const {
341   return false;
342 }
343 
IsAlwaysOnTop() const344 bool CreateApplicationShortcutView::IsAlwaysOnTop() const {
345   return false;
346 }
347 
HasAlwaysOnTopMenu() const348 bool CreateApplicationShortcutView::HasAlwaysOnTopMenu() const {
349   return false;
350 }
351 
IsModal() const352 bool CreateApplicationShortcutView::IsModal() const {
353   return true;
354 }
355 
GetWindowTitle() const356 std::wstring CreateApplicationShortcutView::GetWindowTitle() const {
357   return UTF16ToWide(l10n_util::GetStringUTF16(IDS_CREATE_SHORTCUTS_TITLE));
358 }
359 
Accept()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 
GetContentsView()384 views::View* CreateApplicationShortcutView::GetContentsView() {
385   return this;
386 }
387 
AddCheckbox(const std::wstring & text,bool checked)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 
ButtonPressed(views::Button * sender,const views::Event & event)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 
CreateUrlApplicationShortcutView(TabContentsWrapper * tab_contents)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 
~CreateUrlApplicationShortcutView()429 CreateUrlApplicationShortcutView::~CreateUrlApplicationShortcutView() {
430   if (pending_download_)
431     pending_download_->Cancel();
432 }
433 
Accept()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 
FetchIcon()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 
OnIconDownloaded(bool errored,const SkBitmap & image)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 
CreateChromeApplicationShortcutView(Profile * profile,const Extension * app)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 
~CreateChromeApplicationShortcutView()517 CreateChromeApplicationShortcutView::~CreateChromeApplicationShortcutView() {}
518 
519 // Called by tracker_ when the app's icon is loaded.
OnImageLoaded(SkBitmap * image,const ExtensionResource & resource,int index)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