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