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]. 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 61 DownloadShelf::DownloadShelf() 62 : should_show_on_unhide_(false), 63 is_hidden_(false), 64 weak_ptr_factory_(this) { 65 } 66 67 DownloadShelf::~DownloadShelf() {} 68 69 // static 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 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 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 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 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 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 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 291 void DownloadShelf::Show() { 292 if (is_hidden_) { 293 should_show_on_unhide_ = true; 294 return; 295 } 296 DoShow(); 297 } 298 299 void DownloadShelf::Close(CloseReason reason) { 300 if (is_hidden_) { 301 should_show_on_unhide_ = false; 302 return; 303 } 304 DoClose(reason); 305 } 306 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 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 327 base::TimeDelta DownloadShelf::GetTransientDownloadShowDelay() { 328 return base::TimeDelta::FromSeconds(kDownloadShowDelayInSeconds); 329 } 330 331 content::DownloadManager* DownloadShelf::GetDownloadManager() { 332 return content::BrowserContext::GetDownloadManager(browser()->profile()); 333 } 334 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 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