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]. 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 gfx::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