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