Home | History | Annotate | Download | only in views
      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