Home | History | Annotate | Download | only in download
      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 #define _USE_MATH_DEFINES  // For VC++ to get M_PI. This has to be first.
      6 
      7 #include "chrome/browser/download/download_shelf.h"
      8 
      9 #include <cmath>
     10 
     11 #include "base/bind.h"
     12 #include "base/callback.h"
     13 #include "base/message_loop/message_loop.h"
     14 #include "base/strings/string_number_conversions.h"
     15 #include "chrome/browser/download/download_item_model.h"
     16 #include "chrome/browser/download/download_service.h"
     17 #include "chrome/browser/download/download_service_factory.h"
     18 #include "chrome/browser/download/download_started_animation.h"
     19 #include "chrome/browser/platform_util.h"
     20 #include "chrome/browser/profiles/profile.h"
     21 #include "chrome/browser/ui/browser.h"
     22 #include "chrome/browser/ui/tabs/tab_strip_model.h"
     23 #include "content/public/browser/browser_context.h"
     24 #include "content/public/browser/download_item.h"
     25 #include "content/public/browser/download_manager.h"
     26 #include "content/public/browser/web_contents.h"
     27 #include "content/public/browser/web_contents_view.h"
     28 #include "grit/locale_settings.h"
     29 #include "grit/theme_resources.h"
     30 #include "ui/base/animation/animation.h"
     31 #include "ui/base/l10n/l10n_util.h"
     32 #include "ui/base/resource/resource_bundle.h"
     33 #include "ui/gfx/canvas.h"
     34 #include "ui/gfx/image/image_skia.h"
     35 
     36 #if defined(TOOLKIT_VIEWS)
     37 #include "ui/views/view.h"
     38 #endif
     39 
     40 using content::DownloadItem;
     41 
     42 namespace {
     43 
     44 // Delay before we show a transient download.
     45 const int64 kDownloadShowDelayInSeconds = 2;
     46 
     47 // Get the opacity based on |animation_progress|, with values in [0.0, 1.0].
     48 // Range of return value is [0, 255].
     49 int GetOpacity(double animation_progress) {
     50   DCHECK(animation_progress >= 0 && animation_progress <= 1);
     51 
     52   // How many times to cycle the complete animation. This should be an odd
     53   // number so that the animation ends faded out.
     54   static const int kCompleteAnimationCycles = 5;
     55   double temp = animation_progress * kCompleteAnimationCycles * M_PI + M_PI_2;
     56   temp = sin(temp) / 2 + 0.5;
     57   return static_cast<int>(255.0 * temp);
     58 }
     59 
     60 } // namespace
     61 
     62 DownloadShelf::DownloadShelf()
     63     : should_show_on_unhide_(false),
     64       is_hidden_(false),
     65       weak_ptr_factory_(this) {
     66 }
     67 
     68 DownloadShelf::~DownloadShelf() {}
     69 
     70 // static
     71 int DownloadShelf::GetBigProgressIconSize() {
     72   static int big_progress_icon_size = 0;
     73   if (big_progress_icon_size == 0) {
     74     base::string16 locale_size_str =
     75         l10n_util::GetStringUTF16(IDS_DOWNLOAD_BIG_PROGRESS_SIZE);
     76     bool rc = base::StringToInt(locale_size_str, &big_progress_icon_size);
     77     if (!rc || big_progress_icon_size < kBigProgressIconSize) {
     78       NOTREACHED();
     79       big_progress_icon_size = kBigProgressIconSize;
     80     }
     81   }
     82 
     83   return big_progress_icon_size;
     84 }
     85 
     86 // static
     87 int DownloadShelf::GetBigProgressIconOffset() {
     88   return (GetBigProgressIconSize() - kBigIconSize) / 2;
     89 }
     90 
     91 // Download progress painting --------------------------------------------------
     92 
     93 // Common images used for download progress animations. We load them once the
     94 // first time we do a progress paint, then reuse them as they are always the
     95 // same.
     96 gfx::ImageSkia* g_foreground_16 = NULL;
     97 gfx::ImageSkia* g_background_16 = NULL;
     98 gfx::ImageSkia* g_foreground_32 = NULL;
     99 gfx::ImageSkia* g_background_32 = NULL;
    100 
    101 // static
    102 void DownloadShelf::PaintCustomDownloadProgress(
    103     gfx::Canvas* canvas,
    104     const gfx::ImageSkia& background_image,
    105     const gfx::ImageSkia& foreground_image,
    106     int image_size,
    107     const gfx::Rect& bounds,
    108     int start_angle,
    109     int percent_done) {
    110   // Draw the background progress image.
    111   canvas->DrawImageInt(background_image,
    112                        bounds.x(),
    113                        bounds.y());
    114 
    115   // Layer the foreground progress image in an arc proportional to the download
    116   // progress. The arc grows clockwise, starting in the midnight position, as
    117   // the download progresses. However, if the download does not have known total
    118   // size (the server didn't give us one), then we just spin an arc around until
    119   // we're done.
    120   float sweep_angle = 0.0;
    121   float start_pos = static_cast<float>(kStartAngleDegrees);
    122   if (percent_done < 0) {
    123     sweep_angle = kUnknownAngleDegrees;
    124     start_pos = static_cast<float>(start_angle);
    125   } else if (percent_done > 0) {
    126     sweep_angle = static_cast<float>(kMaxDegrees / 100.0 * percent_done);
    127   }
    128 
    129   // Set up an arc clipping region for the foreground image. Don't bother using
    130   // a clipping region if it would round to 360 (really 0) degrees, since that
    131   // would eliminate the foreground completely and be quite confusing (it would
    132   // look like 0% complete when it should be almost 100%).
    133   canvas->Save();
    134   if (sweep_angle < static_cast<float>(kMaxDegrees - 1)) {
    135     SkRect oval;
    136     oval.set(SkIntToScalar(bounds.x()),
    137              SkIntToScalar(bounds.y()),
    138              SkIntToScalar(bounds.x() + image_size),
    139              SkIntToScalar(bounds.y() + image_size));
    140     SkPath path;
    141     path.arcTo(oval,
    142                SkFloatToScalar(start_pos),
    143                SkFloatToScalar(sweep_angle), false);
    144     path.lineTo(SkIntToScalar(bounds.x() + image_size / 2),
    145                 SkIntToScalar(bounds.y() + image_size / 2));
    146 
    147     // gfx::Canvas::ClipPath does not provide for anti-aliasing.
    148     canvas->sk_canvas()->clipPath(path, SkRegion::kIntersect_Op, true);
    149   }
    150 
    151   canvas->DrawImageInt(foreground_image,
    152                        bounds.x(),
    153                        bounds.y());
    154   canvas->Restore();
    155 }
    156 
    157 // static
    158 void DownloadShelf::PaintDownloadProgress(gfx::Canvas* canvas,
    159 #if defined(TOOLKIT_VIEWS)
    160                                           views::View* containing_view,
    161 #endif
    162                                           int origin_x,
    163                                           int origin_y,
    164                                           int start_angle,
    165                                           int percent_done,
    166                                           PaintDownloadProgressSize size) {
    167   // Load up our common images.
    168   if (!g_background_16) {
    169     ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
    170     g_foreground_16 = rb.GetImageSkiaNamed(IDR_DOWNLOAD_PROGRESS_FOREGROUND_16);
    171     g_background_16 = rb.GetImageSkiaNamed(IDR_DOWNLOAD_PROGRESS_BACKGROUND_16);
    172     g_foreground_32 = rb.GetImageSkiaNamed(IDR_DOWNLOAD_PROGRESS_FOREGROUND_32);
    173     g_background_32 = rb.GetImageSkiaNamed(IDR_DOWNLOAD_PROGRESS_BACKGROUND_32);
    174     DCHECK_EQ(g_foreground_16->width(), g_background_16->width());
    175     DCHECK_EQ(g_foreground_16->height(), g_background_16->height());
    176     DCHECK_EQ(g_foreground_32->width(), g_background_32->width());
    177     DCHECK_EQ(g_foreground_32->height(), g_background_32->height());
    178   }
    179 
    180   gfx::ImageSkia* background =
    181       (size == BIG) ? g_background_32 : g_background_16;
    182   gfx::ImageSkia* foreground =
    183       (size == BIG) ? g_foreground_32 : g_foreground_16;
    184 
    185   const int kProgressIconSize =
    186       (size == BIG) ? kBigProgressIconSize : kSmallProgressIconSize;
    187 
    188   // We start by storing the bounds of the images so that it is easy to mirror
    189   // the bounds if the UI layout is RTL.
    190   gfx::Rect bounds(origin_x, origin_y,
    191                    background->width(), background->height());
    192 
    193 #if defined(TOOLKIT_VIEWS)
    194   // Mirror the positions if necessary.
    195   int mirrored_x = containing_view->GetMirroredXForRect(bounds);
    196   bounds.set_x(mirrored_x);
    197 #endif
    198 
    199   // Draw the background progress image.
    200   canvas->DrawImageInt(*background,
    201                        bounds.x(),
    202                        bounds.y());
    203 
    204   PaintCustomDownloadProgress(canvas,
    205                               *background,
    206                               *foreground,
    207                               kProgressIconSize,
    208                               bounds,
    209                               start_angle,
    210                               percent_done);
    211 }
    212 
    213 // static
    214 void DownloadShelf::PaintDownloadComplete(gfx::Canvas* canvas,
    215 #if defined(TOOLKIT_VIEWS)
    216                                           views::View* containing_view,
    217 #endif
    218                                           int origin_x,
    219                                           int origin_y,
    220                                           double animation_progress,
    221                                           PaintDownloadProgressSize size) {
    222   // Load up our common images.
    223   if (!g_foreground_16) {
    224     ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
    225     g_foreground_16 = rb.GetImageSkiaNamed(IDR_DOWNLOAD_PROGRESS_FOREGROUND_16);
    226     g_foreground_32 = rb.GetImageSkiaNamed(IDR_DOWNLOAD_PROGRESS_FOREGROUND_32);
    227   }
    228 
    229   gfx::ImageSkia* complete = (size == BIG) ? g_foreground_32 : g_foreground_16;
    230 
    231   gfx::Rect complete_bounds(origin_x, origin_y,
    232                             complete->width(), complete->height());
    233 #if defined(TOOLKIT_VIEWS)
    234   // Mirror the positions if necessary.
    235   complete_bounds.set_x(containing_view->GetMirroredXForRect(complete_bounds));
    236 #endif
    237 
    238   // Start at full opacity, then loop back and forth five times before ending
    239   // at zero opacity.
    240   canvas->DrawImageInt(*complete, complete_bounds.x(), complete_bounds.y(),
    241                        GetOpacity(animation_progress));
    242 }
    243 
    244 // static
    245 void DownloadShelf::PaintDownloadInterrupted(gfx::Canvas* canvas,
    246 #if defined(TOOLKIT_VIEWS)
    247                                              views::View* containing_view,
    248 #endif
    249                                              int origin_x,
    250                                              int origin_y,
    251                                              double animation_progress,
    252                                              PaintDownloadProgressSize size) {
    253   // Load up our common images.
    254   if (!g_foreground_16) {
    255     ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
    256     g_foreground_16 = rb.GetImageSkiaNamed(IDR_DOWNLOAD_PROGRESS_FOREGROUND_16);
    257     g_foreground_32 = rb.GetImageSkiaNamed(IDR_DOWNLOAD_PROGRESS_FOREGROUND_32);
    258   }
    259 
    260   gfx::ImageSkia* complete = (size == BIG) ? g_foreground_32 : g_foreground_16;
    261 
    262   gfx::Rect complete_bounds(origin_x, origin_y,
    263                             complete->width(), complete->height());
    264 #if defined(TOOLKIT_VIEWS)
    265   // Mirror the positions if necessary.
    266   complete_bounds.set_x(containing_view->GetMirroredXForRect(complete_bounds));
    267 #endif
    268 
    269   // Start at zero opacity, then loop back and forth five times before ending
    270   // at full opacity.
    271   canvas->DrawImageInt(*complete, complete_bounds.x(), complete_bounds.y(),
    272                        GetOpacity(1.0 - animation_progress));
    273 }
    274 
    275 void DownloadShelf::AddDownload(DownloadItem* download) {
    276   DCHECK(download);
    277   if (DownloadItemModel(download).ShouldRemoveFromShelfWhenComplete()) {
    278     // If we are going to remove the download from the shelf upon completion,
    279     // wait a few seconds to see if it completes quickly. If it's a small
    280     // download, then the user won't have time to interact with it.
    281     base::MessageLoop::current()->PostDelayedTask(
    282         FROM_HERE,
    283         base::Bind(&DownloadShelf::ShowDownloadById,
    284                    weak_ptr_factory_.GetWeakPtr(),
    285                    download->GetId()),
    286         GetTransientDownloadShowDelay());
    287   } else {
    288     ShowDownload(download);
    289   }
    290 }
    291 
    292 void DownloadShelf::Show() {
    293   if (is_hidden_) {
    294     should_show_on_unhide_ = true;
    295     return;
    296   }
    297   DoShow();
    298 }
    299 
    300 void DownloadShelf::Close(CloseReason reason) {
    301   if (is_hidden_) {
    302     should_show_on_unhide_ = false;
    303     return;
    304   }
    305   DoClose(reason);
    306 }
    307 
    308 void DownloadShelf::Hide() {
    309   if (is_hidden_)
    310     return;
    311   is_hidden_ = true;
    312   if (IsShowing()) {
    313     should_show_on_unhide_ = true;
    314     DoClose(AUTOMATIC);
    315   }
    316 }
    317 
    318 void DownloadShelf::Unhide() {
    319   if (!is_hidden_)
    320     return;
    321   is_hidden_ = false;
    322   if (should_show_on_unhide_) {
    323     should_show_on_unhide_ = false;
    324     DoShow();
    325   }
    326 }
    327 
    328 base::TimeDelta DownloadShelf::GetTransientDownloadShowDelay() {
    329   return base::TimeDelta::FromSeconds(kDownloadShowDelayInSeconds);
    330 }
    331 
    332 content::DownloadManager* DownloadShelf::GetDownloadManager() {
    333   return content::BrowserContext::GetDownloadManager(browser()->profile());
    334 }
    335 
    336 void DownloadShelf::ShowDownload(DownloadItem* download) {
    337   if (download->GetState() == DownloadItem::COMPLETE &&
    338       DownloadItemModel(download).ShouldRemoveFromShelfWhenComplete())
    339     return;
    340   if (!DownloadServiceFactory::GetForBrowserContext(
    341         download->GetBrowserContext())->IsShelfEnabled())
    342     return;
    343 
    344   if (is_hidden_)
    345     Unhide();
    346   Show();
    347   DoAddDownload(download);
    348 
    349   // browser() can be NULL for tests.
    350   if (!browser())
    351     return;
    352 
    353   // Show the download started animation if:
    354   // - Download started animation is enabled for this download. It is disabled
    355   //   for "Save As" downloads and extension installs, for example.
    356   // - The browser has an active visible WebContents. (browser isn't minimized,
    357   //   or running under a test etc.)
    358   // - Rich animations are enabled.
    359   content::WebContents* shelf_tab =
    360       browser()->tab_strip_model()->GetActiveWebContents();
    361   if (DownloadItemModel(download).ShouldShowDownloadStartedAnimation() &&
    362       shelf_tab &&
    363       platform_util::IsVisible(shelf_tab->GetView()->GetNativeView()) &&
    364       ui::Animation::ShouldRenderRichAnimation()) {
    365     DownloadStartedAnimation::Show(shelf_tab);
    366   }
    367 }
    368 
    369 void DownloadShelf::ShowDownloadById(int32 download_id) {
    370   content::DownloadManager* download_manager = GetDownloadManager();
    371   if (!download_manager)
    372     return;
    373 
    374   DownloadItem* download = download_manager->GetDownload(download_id);
    375   if (!download)
    376     return;
    377 
    378   ShowDownload(download);
    379 }
    380