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.
5 #define _USE_MATH_DEFINES // For VC++ to get M_PI. This has to be first.
7 #include "chrome/browser/download/download_shelf.h"
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"
35 using content::DownloadItem
;
39 // Delay before we show a transient download.
40 const int64 kDownloadShowDelayInSeconds
= 2;
42 // Get the opacity based on |animation_progress|, with values in [0.0, 1.0].
43 // Range of return value is [0, 255].
44 int GetOpacity(double animation_progress
) {
45 DCHECK(animation_progress
>= 0 && animation_progress
<= 1);
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
);
57 DownloadShelf::DownloadShelf()
58 : should_show_on_unhide_(false),
60 weak_ptr_factory_(this) {
63 DownloadShelf::~DownloadShelf() {}
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
) {
74 big_progress_icon_size
= kBigProgressIconSize
;
78 return big_progress_icon_size
;
82 int DownloadShelf::GetBigProgressIconOffset() {
83 return (GetBigProgressIconSize() - kBigIconSize
) / 2;
86 // Download progress painting --------------------------------------------------
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
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
;
97 void DownloadShelf::PaintCustomDownloadProgress(
99 const gfx::ImageSkia
& background_image
,
100 const gfx::ImageSkia
& foreground_image
,
102 const gfx::Rect
& bounds
,
105 // Draw the background progress image.
106 canvas
->DrawImageInt(background_image
,
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
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
);
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%).
129 if (sweep_angle
< static_cast<float>(kMaxDegrees
- 1)) {
131 oval
.set(SkIntToScalar(bounds
.x()),
132 SkIntToScalar(bounds
.y()),
133 SkIntToScalar(bounds
.x() + image_size
),
134 SkIntToScalar(bounds
.y() + image_size
));
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));
142 // gfx::Canvas::ClipPath does not provide for anti-aliasing.
143 canvas
->sk_canvas()->clipPath(path
, SkRegion::kIntersect_Op
, true);
146 canvas
->DrawImageInt(foreground_image
,
153 void DownloadShelf::PaintDownloadProgress(
155 const BoundsAdjusterCallback
& rtl_mirror
,
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());
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
;
179 const int kProgressIconSize
=
180 (size
== BIG
) ? kBigProgressIconSize
: kSmallProgressIconSize
;
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());
187 // Mirror the positions if necessary.
188 rtl_mirror
.Run(&bounds
);
190 // Draw the background progress image.
191 canvas
->DrawImageInt(*background
,
195 PaintCustomDownloadProgress(canvas
,
205 void DownloadShelf::PaintDownloadComplete(
207 const BoundsAdjusterCallback
& rtl_mirror
,
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
);
219 gfx::ImageSkia
* complete
= (size
== BIG
) ? g_foreground_32
: g_foreground_16
;
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
);
226 // Start at full opacity, then loop back and forth five times before ending
228 canvas
->DrawImageInt(*complete
, complete_bounds
.x(), complete_bounds
.y(),
229 GetOpacity(animation_progress
));
233 void DownloadShelf::PaintDownloadInterrupted(
235 const BoundsAdjusterCallback
& rtl_mirror
,
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
);
247 gfx::ImageSkia
* complete
= (size
== BIG
) ? g_foreground_32
: g_foreground_16
;
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
);
254 // Start at zero opacity, then loop back and forth five times before ending
256 canvas
->DrawImageInt(*complete
, complete_bounds
.x(), complete_bounds
.y(),
257 GetOpacity(1.0 - animation_progress
));
260 void DownloadShelf::AddDownload(DownloadItem
* 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(
268 base::Bind(&DownloadShelf::ShowDownloadById
,
269 weak_ptr_factory_
.GetWeakPtr(),
271 GetTransientDownloadShowDelay());
273 ShowDownload(download
);
277 void DownloadShelf::Show() {
279 should_show_on_unhide_
= true;
285 void DownloadShelf::Close(CloseReason reason
) {
287 should_show_on_unhide_
= false;
293 void DownloadShelf::Hide() {
298 should_show_on_unhide_
= true;
303 void DownloadShelf::Unhide() {
307 if (should_show_on_unhide_
) {
308 should_show_on_unhide_
= false;
313 base::TimeDelta
DownloadShelf::GetTransientDownloadShowDelay() {
314 return base::TimeDelta::FromSeconds(kDownloadShowDelayInSeconds
);
317 content::DownloadManager
* DownloadShelf::GetDownloadManager() {
318 return content::BrowserContext::GetDownloadManager(browser()->profile());
321 void DownloadShelf::ShowDownload(DownloadItem
* download
) {
322 if (download
->GetState() == DownloadItem::COMPLETE
&&
323 DownloadItemModel(download
).ShouldRemoveFromShelfWhenComplete())
325 if (!DownloadServiceFactory::GetForBrowserContext(
326 download
->GetBrowserContext())->IsShelfEnabled())
332 DoAddDownload(download
);
334 // browser() can be NULL for tests.
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() &&
348 platform_util::IsVisible(shelf_tab
->GetNativeView()) &&
349 gfx::Animation::ShouldRenderRichAnimation()) {
350 DownloadStartedAnimation::Show(shelf_tab
);
354 void DownloadShelf::ShowDownloadById(int32 download_id
) {
355 content::DownloadManager
* download_manager
= GetDownloadManager();
356 if (!download_manager
)
359 DownloadItem
* download
= download_manager
->GetDownload(download_id
);
363 ShowDownload(download
);