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