• 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 "content/public/browser/web_contents_view.h"
28 #include "grit/locale_settings.h"
29 #include "grit/theme_resources.h"
30 #include "ui/base/l10n/l10n_util.h"
31 #include "ui/base/resource/resource_bundle.h"
32 #include "ui/gfx/animation/animation.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].
GetOpacity(double animation_progress)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 
DownloadShelf()62 DownloadShelf::DownloadShelf()
63     : should_show_on_unhide_(false),
64       is_hidden_(false),
65       weak_ptr_factory_(this) {
66 }
67 
~DownloadShelf()68 DownloadShelf::~DownloadShelf() {}
69 
70 // static
GetBigProgressIconSize()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
GetBigProgressIconOffset()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
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)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
PaintDownloadProgress(gfx::Canvas * canvas,views::View * containing_view,int origin_x,int origin_y,int start_angle,int percent_done,PaintDownloadProgressSize size)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
PaintDownloadComplete(gfx::Canvas * canvas,views::View * containing_view,int origin_x,int origin_y,double animation_progress,PaintDownloadProgressSize size)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
PaintDownloadInterrupted(gfx::Canvas * canvas,views::View * containing_view,int origin_x,int origin_y,double animation_progress,PaintDownloadProgressSize size)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 
AddDownload(DownloadItem * download)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 
Show()292 void DownloadShelf::Show() {
293   if (is_hidden_) {
294     should_show_on_unhide_ = true;
295     return;
296   }
297   DoShow();
298 }
299 
Close(CloseReason reason)300 void DownloadShelf::Close(CloseReason reason) {
301   if (is_hidden_) {
302     should_show_on_unhide_ = false;
303     return;
304   }
305   DoClose(reason);
306 }
307 
Hide()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 
Unhide()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 
GetTransientDownloadShowDelay()328 base::TimeDelta DownloadShelf::GetTransientDownloadShowDelay() {
329   return base::TimeDelta::FromSeconds(kDownloadShowDelayInSeconds);
330 }
331 
GetDownloadManager()332 content::DownloadManager* DownloadShelf::GetDownloadManager() {
333   return content::BrowserContext::GetDownloadManager(browser()->profile());
334 }
335 
ShowDownload(DownloadItem * download)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       gfx::Animation::ShouldRenderRichAnimation()) {
365     DownloadStartedAnimation::Show(shelf_tab);
366   }
367 }
368 
ShowDownloadById(int32 download_id)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