Home | History | Annotate | Download | only in views
      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 #include <cmath>
      9 
     10 #include "base/bind.h"
     11 #include "base/bind_helpers.h"
     12 #include "base/prefs/pref_service.h"
     13 #include "base/strings/utf_string_conversions.h"
     14 #include "base/win/windows_version.h"
     15 #include "chrome/browser/extensions/tab_helper.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/views/constrained_window_views.h"
     21 #include "chrome/browser/ui/webui/extensions/extension_icon_source.h"
     22 #include "chrome/browser/web_applications/web_app.h"
     23 #include "chrome/common/chrome_constants.h"
     24 #include "chrome/common/pref_names.h"
     25 #include "components/favicon_base/select_favicon_frames.h"
     26 #include "content/public/browser/render_view_host.h"
     27 #include "content/public/browser/render_widget_host_view.h"
     28 #include "content/public/browser/web_contents.h"
     29 #include "extensions/common/extension.h"
     30 #include "grit/chromium_strings.h"
     31 #include "grit/generated_resources.h"
     32 #include "grit/locale_settings.h"
     33 #include "grit/theme_resources.h"
     34 #include "net/base/load_flags.h"
     35 #include "net/url_request/url_request.h"
     36 #include "skia/ext/image_operations.h"
     37 #include "third_party/skia/include/core/SkBitmap.h"
     38 #include "third_party/skia/include/core/SkPaint.h"
     39 #include "third_party/skia/include/core/SkRect.h"
     40 #include "ui/base/l10n/l10n_util.h"
     41 #include "ui/base/layout.h"
     42 #include "ui/base/resource/resource_bundle.h"
     43 #include "ui/gfx/canvas.h"
     44 #include "ui/gfx/codec/png_codec.h"
     45 #include "ui/gfx/image/image_family.h"
     46 #include "ui/gfx/image/image_skia.h"
     47 #include "ui/views/controls/button/checkbox.h"
     48 #include "ui/views/controls/image_view.h"
     49 #include "ui/views/controls/label.h"
     50 #include "ui/views/layout/grid_layout.h"
     51 #include "ui/views/layout/layout_constants.h"
     52 #include "ui/views/widget/widget.h"
     53 #include "ui/views/window/dialog_client_view.h"
     54 #include "url/gurl.h"
     55 
     56 namespace {
     57 
     58 const int kIconPreviewSizePixels = 32;
     59 
     60 // AppInfoView shows the application icon and title.
     61 class AppInfoView : public views::View {
     62  public:
     63   AppInfoView(const base::string16& title,
     64               const base::string16& description,
     65               const gfx::ImageFamily& icon);
     66 
     67   // Updates the title/description of the web app.
     68   void UpdateText(const base::string16& title,
     69                   const base::string16& description);
     70 
     71   // Updates the icon of the web app.
     72   void UpdateIcon(const gfx::ImageFamily& image);
     73 
     74   // Overridden from views::View:
     75   virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE;
     76 
     77  private:
     78   // Initializes the controls
     79   void Init(const base::string16& title,
     80             const base::string16& description, const gfx::ImageFamily& icon);
     81 
     82   // Creates or updates description label.
     83   void PrepareDescriptionLabel(const base::string16& description);
     84 
     85   // Sets up layout manager.
     86   void SetupLayout();
     87 
     88   views::ImageView* icon_;
     89   views::Label* title_;
     90   views::Label* description_;
     91 };
     92 
     93 AppInfoView::AppInfoView(const base::string16& title,
     94                          const base::string16& description,
     95                          const gfx::ImageFamily& icon)
     96     : icon_(NULL),
     97       title_(NULL),
     98       description_(NULL) {
     99   Init(title, description, icon);
    100 }
    101 
    102 void AppInfoView::Init(const base::string16& title_text,
    103                        const base::string16& description_text,
    104                        const gfx::ImageFamily& icon) {
    105   icon_ = new views::ImageView();
    106   UpdateIcon(icon);
    107   icon_->SetImageSize(gfx::Size(kIconPreviewSizePixels,
    108                                 kIconPreviewSizePixels));
    109 
    110   title_ = new views::Label(
    111       title_text,
    112       ui::ResourceBundle::GetSharedInstance().GetFontList(
    113           ui::ResourceBundle::BoldFont));
    114   title_->SetMultiLine(true);
    115   title_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
    116 
    117   PrepareDescriptionLabel(description_text);
    118 
    119   SetupLayout();
    120 }
    121 
    122 void AppInfoView::PrepareDescriptionLabel(const base::string16& description) {
    123   // Do not make space for the description if it is empty.
    124   if (description.empty())
    125     return;
    126 
    127   const size_t kMaxLength = 200;
    128   const base::string16 kEllipsis(base::ASCIIToUTF16(" ... "));
    129 
    130   base::string16 text = description;
    131   if (text.length() > kMaxLength) {
    132     text = text.substr(0, kMaxLength);
    133     text += kEllipsis;
    134   }
    135 
    136   if (description_) {
    137     description_->SetText(text);
    138   } else {
    139     description_ = new views::Label(text);
    140     description_->SetMultiLine(true);
    141     description_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
    142   }
    143 }
    144 
    145 void AppInfoView::SetupLayout() {
    146   views::GridLayout* layout = views::GridLayout::CreatePanel(this);
    147   SetLayoutManager(layout);
    148 
    149   static const int kColumnSetId = 0;
    150   views::ColumnSet* column_set = layout->AddColumnSet(kColumnSetId);
    151   column_set->AddColumn(views::GridLayout::CENTER, views::GridLayout::LEADING,
    152                         20.0f, views::GridLayout::FIXED,
    153                         kIconPreviewSizePixels, kIconPreviewSizePixels);
    154   column_set->AddColumn(views::GridLayout::FILL, views::GridLayout::CENTER,
    155                         80.0f, views::GridLayout::USE_PREF, 0, 0);
    156 
    157   layout->StartRow(0, kColumnSetId);
    158   layout->AddView(icon_, 1, description_ ? 2 : 1);
    159   layout->AddView(title_);
    160 
    161   if (description_) {
    162     layout->StartRow(0, kColumnSetId);
    163     layout->SkipColumns(1);
    164     layout->AddView(description_);
    165   }
    166 }
    167 
    168 void AppInfoView::UpdateText(const base::string16& title,
    169                              const base::string16& description) {
    170   title_->SetText(title);
    171   PrepareDescriptionLabel(description);
    172 
    173   SetupLayout();
    174 }
    175 
    176 void AppInfoView::UpdateIcon(const gfx::ImageFamily& image) {
    177   // Get the icon closest to the desired preview size.
    178   const gfx::Image* icon = image.GetBest(kIconPreviewSizePixels,
    179                                          kIconPreviewSizePixels);
    180   if (!icon || icon->IsEmpty())
    181     // The family has no icons. Leave the image blank.
    182     return;
    183   const SkBitmap& bitmap = *icon->ToSkBitmap();
    184   if (bitmap.width() == kIconPreviewSizePixels &&
    185       bitmap.height() == kIconPreviewSizePixels) {
    186     icon_->SetImage(gfx::ImageSkia::CreateFrom1xBitmap(bitmap));
    187   } else {
    188     // Resize the image to the desired size.
    189     SkBitmap resized_bitmap = skia::ImageOperations::Resize(
    190         bitmap, skia::ImageOperations::RESIZE_LANCZOS3,
    191         kIconPreviewSizePixels, kIconPreviewSizePixels);
    192 
    193     icon_->SetImage(gfx::ImageSkia::CreateFrom1xBitmap(resized_bitmap));
    194   }
    195 }
    196 
    197 void AppInfoView::OnPaint(gfx::Canvas* canvas) {
    198   gfx::Rect bounds = GetLocalBounds();
    199 
    200   SkRect border_rect = {
    201     SkIntToScalar(bounds.x()),
    202     SkIntToScalar(bounds.y()),
    203     SkIntToScalar(bounds.right()),
    204     SkIntToScalar(bounds.bottom())
    205   };
    206 
    207   SkPaint border_paint;
    208   border_paint.setAntiAlias(true);
    209   border_paint.setARGB(0xFF, 0xC8, 0xC8, 0xC8);
    210 
    211   canvas->sk_canvas()->drawRoundRect(border_rect, SkIntToScalar(2),
    212                                      SkIntToScalar(2), border_paint);
    213 
    214   SkRect inner_rect = {
    215     border_rect.fLeft + SkDoubleToScalar(0.5),
    216     border_rect.fTop + SkDoubleToScalar(0.5),
    217     border_rect.fRight - SkDoubleToScalar(0.5),
    218     border_rect.fBottom - SkDoubleToScalar(0.5),
    219   };
    220 
    221   SkPaint inner_paint;
    222   inner_paint.setAntiAlias(true);
    223   inner_paint.setARGB(0xFF, 0xF8, 0xF8, 0xF8);
    224   canvas->sk_canvas()->drawRoundRect(inner_rect, SkDoubleToScalar(1.5),
    225                                      SkDoubleToScalar(1.5), inner_paint);
    226 }
    227 
    228 }  // namespace
    229 
    230 namespace chrome {
    231 
    232 void ShowCreateWebAppShortcutsDialog(gfx::NativeWindow parent_window,
    233                                      content::WebContents* web_contents) {
    234   CreateBrowserModalDialogViews(
    235       new CreateUrlApplicationShortcutView(web_contents),
    236       parent_window)->Show();
    237 }
    238 
    239 void ShowCreateChromeAppShortcutsDialog(
    240     gfx::NativeWindow parent_window,
    241     Profile* profile,
    242     const extensions::Extension* app,
    243     const base::Callback<void(bool)>& close_callback) {
    244   CreateBrowserModalDialogViews(
    245       new CreateChromeApplicationShortcutView(profile, app, close_callback),
    246       parent_window)->Show();
    247 }
    248 
    249 }  // namespace chrome
    250 
    251 CreateApplicationShortcutView::CreateApplicationShortcutView(Profile* profile)
    252     : profile_(profile),
    253       app_info_(NULL),
    254       create_shortcuts_label_(NULL),
    255       desktop_check_box_(NULL),
    256       menu_check_box_(NULL),
    257       quick_launch_check_box_(NULL) {}
    258 
    259 CreateApplicationShortcutView::~CreateApplicationShortcutView() {}
    260 
    261 void CreateApplicationShortcutView::InitControls(DialogLayout dialog_layout) {
    262   if (dialog_layout == DIALOG_LAYOUT_URL_SHORTCUT) {
    263     app_info_ = new AppInfoView(shortcut_info_.title,
    264                                 shortcut_info_.description,
    265                                 shortcut_info_.favicon);
    266   }
    267   create_shortcuts_label_ = new views::Label(
    268       l10n_util::GetStringUTF16(IDS_CREATE_SHORTCUTS_LABEL));
    269   create_shortcuts_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
    270 
    271   desktop_check_box_ = AddCheckbox(
    272       l10n_util::GetStringUTF16(IDS_CREATE_SHORTCUTS_DESKTOP_CHKBOX),
    273       profile_->GetPrefs()->GetBoolean(prefs::kWebAppCreateOnDesktop));
    274 
    275   menu_check_box_ = NULL;
    276   quick_launch_check_box_ = NULL;
    277 
    278 #if defined(OS_WIN)
    279   // Do not allow creating shortcuts on the Start Screen for Windows 8.
    280   if (base::win::GetVersion() < base::win::VERSION_WIN8) {
    281     menu_check_box_ = AddCheckbox(
    282         l10n_util::GetStringUTF16(IDS_CREATE_SHORTCUTS_START_MENU_CHKBOX),
    283         profile_->GetPrefs()->GetBoolean(prefs::kWebAppCreateInAppsMenu));
    284   }
    285 
    286   quick_launch_check_box_ = AddCheckbox(
    287       (base::win::GetVersion() >= base::win::VERSION_WIN7) ?
    288         l10n_util::GetStringUTF16(IDS_PIN_TO_TASKBAR_CHKBOX) :
    289         l10n_util::GetStringUTF16(
    290             IDS_CREATE_SHORTCUTS_QUICK_LAUNCH_BAR_CHKBOX),
    291       profile_->GetPrefs()->GetBoolean(prefs::kWebAppCreateInQuickLaunchBar));
    292 #elif defined(OS_POSIX)
    293   menu_check_box_ = AddCheckbox(
    294       l10n_util::GetStringUTF16(IDS_CREATE_SHORTCUTS_MENU_CHKBOX),
    295       profile_->GetPrefs()->GetBoolean(prefs::kWebAppCreateInAppsMenu));
    296 #endif
    297 
    298   // Layout controls
    299   views::GridLayout* layout = views::GridLayout::CreatePanel(this);
    300   SetLayoutManager(layout);
    301 
    302   static const int kHeaderColumnSetId = 0;
    303   views::ColumnSet* column_set = layout->AddColumnSet(kHeaderColumnSetId);
    304   column_set->AddColumn(views::GridLayout::FILL, views::GridLayout::CENTER,
    305                         100.0f, views::GridLayout::FIXED, 0, 0);
    306 
    307   static const int kTableColumnSetId = 1;
    308   column_set = layout->AddColumnSet(kTableColumnSetId);
    309   column_set->AddPaddingColumn(0, views::kPanelHorizIndentation);
    310   column_set->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL,
    311                         100.0f, views::GridLayout::USE_PREF, 0, 0);
    312 
    313   if (app_info_) {
    314     layout->StartRow(0, kHeaderColumnSetId);
    315     layout->AddView(app_info_);
    316     layout->AddPaddingRow(0, views::kPanelSubVerticalSpacing);
    317   }
    318 
    319   layout->StartRow(0, kHeaderColumnSetId);
    320   layout->AddView(create_shortcuts_label_);
    321 
    322   layout->AddPaddingRow(0, views::kLabelToControlVerticalSpacing);
    323   layout->StartRow(0, kTableColumnSetId);
    324   layout->AddView(desktop_check_box_);
    325 
    326   if (menu_check_box_ != NULL) {
    327     layout->AddPaddingRow(0, views::kRelatedControlSmallVerticalSpacing);
    328     layout->StartRow(0, kTableColumnSetId);
    329     layout->AddView(menu_check_box_);
    330   }
    331 
    332   if (quick_launch_check_box_ != NULL) {
    333     layout->AddPaddingRow(0, views::kRelatedControlSmallVerticalSpacing);
    334     layout->StartRow(0, kTableColumnSetId);
    335     layout->AddView(quick_launch_check_box_);
    336   }
    337 }
    338 
    339 gfx::Size CreateApplicationShortcutView::GetPreferredSize() const {
    340   // TODO(evanm): should this use IDS_CREATE_SHORTCUTS_DIALOG_WIDTH_CHARS?
    341   static const int kDialogWidth = 360;
    342   int height = GetLayoutManager()->GetPreferredHeightForWidth(this,
    343       kDialogWidth);
    344   return gfx::Size(kDialogWidth, height);
    345 }
    346 
    347 base::string16 CreateApplicationShortcutView::GetDialogButtonLabel(
    348     ui::DialogButton button) const {
    349   if (button == ui::DIALOG_BUTTON_OK)
    350     return l10n_util::GetStringUTF16(IDS_CREATE_SHORTCUTS_COMMIT);
    351   return views::DialogDelegateView::GetDialogButtonLabel(button);
    352 }
    353 
    354 bool CreateApplicationShortcutView::IsDialogButtonEnabled(
    355     ui::DialogButton button) const {
    356   if (button == ui::DIALOG_BUTTON_OK)
    357     return desktop_check_box_->checked() ||
    358            ((menu_check_box_ != NULL) &&
    359             menu_check_box_->checked()) ||
    360            ((quick_launch_check_box_ != NULL) &&
    361             quick_launch_check_box_->checked());
    362 
    363   return true;
    364 }
    365 
    366 ui::ModalType CreateApplicationShortcutView::GetModalType() const {
    367   return ui::MODAL_TYPE_WINDOW;
    368 }
    369 
    370 base::string16 CreateApplicationShortcutView::GetWindowTitle() const {
    371   return l10n_util::GetStringUTF16(IDS_CREATE_SHORTCUTS_TITLE);
    372 }
    373 
    374 bool CreateApplicationShortcutView::Accept() {
    375   if (!IsDialogButtonEnabled(ui::DIALOG_BUTTON_OK))
    376     return false;
    377 
    378   web_app::ShortcutLocations creation_locations;
    379   creation_locations.on_desktop = desktop_check_box_->checked();
    380   if (menu_check_box_ != NULL && menu_check_box_->checked()) {
    381     creation_locations.applications_menu_location =
    382         create_in_chrome_apps_subdir_ ?
    383             web_app::APP_MENU_LOCATION_SUBDIR_CHROMEAPPS :
    384             web_app::APP_MENU_LOCATION_ROOT;
    385   }
    386 
    387 #if defined(OS_WIN)
    388   creation_locations.in_quick_launch_bar = quick_launch_check_box_ == NULL ?
    389       NULL : quick_launch_check_box_->checked();
    390 #elif defined(OS_POSIX)
    391   // Create shortcut in Mac dock or as Linux (gnome/kde) application launcher
    392   // are not implemented yet.
    393   creation_locations.in_quick_launch_bar = false;
    394 #endif
    395 
    396   web_app::CreateShortcutsForShortcutInfo(
    397       web_app::SHORTCUT_CREATION_BY_USER,
    398       creation_locations,
    399       shortcut_info_);
    400   return true;
    401 }
    402 
    403 views::Checkbox* CreateApplicationShortcutView::AddCheckbox(
    404     const base::string16& text, bool checked) {
    405   views::Checkbox* checkbox = new views::Checkbox(text);
    406   checkbox->SetChecked(checked);
    407   checkbox->set_listener(this);
    408   return checkbox;
    409 }
    410 
    411 void CreateApplicationShortcutView::ButtonPressed(views::Button* sender,
    412                                                   const ui::Event& event) {
    413   if (sender == desktop_check_box_) {
    414     profile_->GetPrefs()->SetBoolean(prefs::kWebAppCreateOnDesktop,
    415                                      desktop_check_box_->checked());
    416   } else if (sender == menu_check_box_) {
    417     profile_->GetPrefs()->SetBoolean(prefs::kWebAppCreateInAppsMenu,
    418                                      menu_check_box_->checked());
    419   } else if (sender == quick_launch_check_box_) {
    420     profile_->GetPrefs()->SetBoolean(prefs::kWebAppCreateInQuickLaunchBar,
    421                                      quick_launch_check_box_->checked());
    422   }
    423 
    424   // When no checkbox is checked we should not have the action button enabled.
    425   GetDialogClientView()->UpdateDialogButtons();
    426 }
    427 
    428 CreateUrlApplicationShortcutView::CreateUrlApplicationShortcutView(
    429     content::WebContents* web_contents)
    430     : CreateApplicationShortcutView(
    431           Profile::FromBrowserContext(web_contents->GetBrowserContext())),
    432       web_contents_(web_contents),
    433       pending_download_id_(-1),
    434       weak_ptr_factory_(this)  {
    435 
    436   web_app::GetShortcutInfoForTab(web_contents_, &shortcut_info_);
    437   const WebApplicationInfo& app_info =
    438       extensions::TabHelper::FromWebContents(web_contents_)->web_app_info();
    439   if (!app_info.icons.empty()) {
    440     web_app::GetIconsInfo(app_info, &unprocessed_icons_);
    441     FetchIcon();
    442   }
    443 
    444   // Create URL app shortcuts in the top-level menu.
    445   create_in_chrome_apps_subdir_ = false;
    446 
    447   InitControls(DIALOG_LAYOUT_URL_SHORTCUT);
    448 }
    449 
    450 CreateUrlApplicationShortcutView::~CreateUrlApplicationShortcutView() {
    451 }
    452 
    453 bool CreateUrlApplicationShortcutView::Accept() {
    454   if (!CreateApplicationShortcutView::Accept())
    455     return false;
    456 
    457   // Get the smallest icon in the icon family (should have only 1).
    458   const gfx::Image* icon = shortcut_info_.favicon.GetBest(0, 0);
    459   SkBitmap bitmap = icon ? icon->AsBitmap() : SkBitmap();
    460   extensions::TabHelper::FromWebContents(web_contents_)->SetAppIcon(bitmap);
    461   Browser* browser = chrome::FindBrowserWithWebContents(web_contents_);
    462   if (browser)
    463     chrome::ConvertTabToAppWindow(browser, web_contents_);
    464   return true;
    465 }
    466 
    467 void CreateUrlApplicationShortcutView::FetchIcon() {
    468   // There should only be fetch job at a time.
    469   DCHECK_EQ(-1, pending_download_id_);
    470 
    471   if (unprocessed_icons_.empty())  // No icons to fetch.
    472     return;
    473 
    474   int preferred_size = std::max(unprocessed_icons_.back().width,
    475                                 unprocessed_icons_.back().height);
    476   pending_download_id_ = web_contents_->DownloadImage(
    477       unprocessed_icons_.back().url,
    478       true,  // is a favicon
    479       0,  // no maximum size
    480       base::Bind(&CreateUrlApplicationShortcutView::DidDownloadFavicon,
    481                  weak_ptr_factory_.GetWeakPtr(),
    482                  preferred_size));
    483 
    484   unprocessed_icons_.pop_back();
    485 }
    486 
    487 void CreateUrlApplicationShortcutView::DidDownloadFavicon(
    488     int requested_size,
    489     int id,
    490     int http_status_code,
    491     const GURL& image_url,
    492     const std::vector<SkBitmap>& bitmaps,
    493     const std::vector<gfx::Size>& original_bitmap_sizes) {
    494   if (id != pending_download_id_)
    495     return;
    496   pending_download_id_ = -1;
    497 
    498   gfx::ImageSkia image_skia = CreateFaviconImageSkia(
    499       bitmaps,
    500       original_bitmap_sizes,
    501       requested_size,
    502       NULL);
    503   if (!image_skia.isNull()) {
    504     shortcut_info_.favicon.Add(image_skia);
    505     static_cast<AppInfoView*>(app_info_)->UpdateIcon(shortcut_info_.favicon);
    506   } else {
    507     FetchIcon();
    508   }
    509 }
    510 
    511 CreateChromeApplicationShortcutView::CreateChromeApplicationShortcutView(
    512     Profile* profile,
    513     const extensions::Extension* app,
    514     const base::Callback<void(bool)>& close_callback)
    515         : CreateApplicationShortcutView(profile),
    516           close_callback_(close_callback),
    517           weak_ptr_factory_(this) {
    518   // Place Chrome app shortcuts in the "Chrome Apps" submenu.
    519   create_in_chrome_apps_subdir_ = true;
    520 
    521   InitControls(DIALOG_LAYOUT_APP_SHORTCUT);
    522 
    523   // Get shortcut information and icon; they are needed for creating the
    524   // shortcut.
    525   web_app::UpdateShortcutInfoAndIconForApp(
    526       app,
    527       profile,
    528       base::Bind(&CreateChromeApplicationShortcutView::OnShortcutInfoLoaded,
    529                  weak_ptr_factory_.GetWeakPtr()));
    530 }
    531 
    532 CreateChromeApplicationShortcutView::~CreateChromeApplicationShortcutView() {}
    533 
    534 bool CreateChromeApplicationShortcutView::Accept() {
    535   if (!close_callback_.is_null())
    536     close_callback_.Run(true);
    537   return CreateApplicationShortcutView::Accept();
    538 }
    539 
    540 bool CreateChromeApplicationShortcutView::Cancel() {
    541   if (!close_callback_.is_null())
    542     close_callback_.Run(false);
    543   return CreateApplicationShortcutView::Cancel();
    544 }
    545 
    546 // Called when the app's ShortcutInfo (with icon) is loaded.
    547 void CreateChromeApplicationShortcutView::OnShortcutInfoLoaded(
    548     const web_app::ShortcutInfo& shortcut_info) {
    549   shortcut_info_ = shortcut_info;
    550 }
    551