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 35using content::DownloadItem; 36 37namespace { 38 39// Delay before we show a transient download. 40const 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]. 44int 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 57DownloadShelf::DownloadShelf() 58 : should_show_on_unhide_(false), 59 is_hidden_(false), 60 weak_ptr_factory_(this) { 61} 62 63DownloadShelf::~DownloadShelf() {} 64 65// static 66int 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 82int 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. 91gfx::ImageSkia* g_foreground_16 = NULL; 92gfx::ImageSkia* g_background_16 = NULL; 93gfx::ImageSkia* g_foreground_32 = NULL; 94gfx::ImageSkia* g_background_32 = NULL; 95 96// static 97void 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 153void 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 205void 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 233void 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 260void 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 277void DownloadShelf::Show() { 278 if (is_hidden_) { 279 should_show_on_unhide_ = true; 280 return; 281 } 282 DoShow(); 283} 284 285void DownloadShelf::Close(CloseReason reason) { 286 if (is_hidden_) { 287 should_show_on_unhide_ = false; 288 return; 289 } 290 DoClose(reason); 291} 292 293void 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 303void 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 313base::TimeDelta DownloadShelf::GetTransientDownloadShowDelay() { 314 return base::TimeDelta::FromSeconds(kDownloadShowDelayInSeconds); 315} 316 317content::DownloadManager* DownloadShelf::GetDownloadManager() { 318 return content::BrowserContext::GetDownloadManager(browser()->profile()); 319} 320 321void 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 354void 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