// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #define _USE_MATH_DEFINES // For VC++ to get M_PI. This has to be first. #include "chrome/browser/download/download_shelf.h" #include <cmath> #include "base/bind.h" #include "base/callback.h" #include "base/message_loop/message_loop.h" #include "base/strings/string_number_conversions.h" #include "chrome/browser/download/download_item_model.h" #include "chrome/browser/download/download_service.h" #include "chrome/browser/download/download_service_factory.h" #include "chrome/browser/download/download_started_animation.h" #include "chrome/browser/platform_util.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/tabs/tab_strip_model.h" #include "content/public/browser/browser_context.h" #include "content/public/browser/download_item.h" #include "content/public/browser/download_manager.h" #include "content/public/browser/web_contents.h" #include "grit/locale_settings.h" #include "grit/theme_resources.h" #include "ui/base/l10n/l10n_util.h" #include "ui/base/resource/resource_bundle.h" #include "ui/gfx/animation/animation.h" #include "ui/gfx/canvas.h" #include "ui/gfx/image/image_skia.h" #if defined(TOOLKIT_VIEWS) #include "ui/views/view.h" #endif using content::DownloadItem; namespace { // Delay before we show a transient download. const int64 kDownloadShowDelayInSeconds = 2; // Get the opacity based on |animation_progress|, with values in [0.0, 1.0]. // Range of return value is [0, 255]. int GetOpacity(double animation_progress) { DCHECK(animation_progress >= 0 && animation_progress <= 1); // How many times to cycle the complete animation. This should be an odd // number so that the animation ends faded out. static const int kCompleteAnimationCycles = 5; double temp = animation_progress * kCompleteAnimationCycles * M_PI + M_PI_2; temp = sin(temp) / 2 + 0.5; return static_cast<int>(255.0 * temp); } } // namespace DownloadShelf::DownloadShelf() : should_show_on_unhide_(false), is_hidden_(false), weak_ptr_factory_(this) { } DownloadShelf::~DownloadShelf() {} // static int DownloadShelf::GetBigProgressIconSize() { static int big_progress_icon_size = 0; if (big_progress_icon_size == 0) { base::string16 locale_size_str = l10n_util::GetStringUTF16(IDS_DOWNLOAD_BIG_PROGRESS_SIZE); bool rc = base::StringToInt(locale_size_str, &big_progress_icon_size); if (!rc || big_progress_icon_size < kBigProgressIconSize) { NOTREACHED(); big_progress_icon_size = kBigProgressIconSize; } } return big_progress_icon_size; } // static int DownloadShelf::GetBigProgressIconOffset() { return (GetBigProgressIconSize() - kBigIconSize) / 2; } // Download progress painting -------------------------------------------------- // Common images used for download progress animations. We load them once the // first time we do a progress paint, then reuse them as they are always the // same. gfx::ImageSkia* g_foreground_16 = NULL; gfx::ImageSkia* g_background_16 = NULL; gfx::ImageSkia* g_foreground_32 = NULL; gfx::ImageSkia* g_background_32 = NULL; // static void DownloadShelf::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) { // Draw the background progress image. canvas->DrawImageInt(background_image, bounds.x(), bounds.y()); // Layer the foreground progress image in an arc proportional to the download // progress. The arc grows clockwise, starting in the midnight position, as // the download progresses. However, if the download does not have known total // size (the server didn't give us one), then we just spin an arc around until // we're done. float sweep_angle = 0.0; float start_pos = static_cast<float>(kStartAngleDegrees); if (percent_done < 0) { sweep_angle = kUnknownAngleDegrees; start_pos = static_cast<float>(start_angle); } else if (percent_done > 0) { sweep_angle = static_cast<float>(kMaxDegrees / 100.0 * percent_done); } // Set up an arc clipping region for the foreground image. Don't bother using // a clipping region if it would round to 360 (really 0) degrees, since that // would eliminate the foreground completely and be quite confusing (it would // look like 0% complete when it should be almost 100%). canvas->Save(); if (sweep_angle < static_cast<float>(kMaxDegrees - 1)) { SkRect oval; oval.set(SkIntToScalar(bounds.x()), SkIntToScalar(bounds.y()), SkIntToScalar(bounds.x() + image_size), SkIntToScalar(bounds.y() + image_size)); SkPath path; path.arcTo(oval, SkFloatToScalar(start_pos), SkFloatToScalar(sweep_angle), false); path.lineTo(SkIntToScalar(bounds.x() + image_size / 2), SkIntToScalar(bounds.y() + image_size / 2)); // gfx::Canvas::ClipPath does not provide for anti-aliasing. canvas->sk_canvas()->clipPath(path, SkRegion::kIntersect_Op, true); } canvas->DrawImageInt(foreground_image, bounds.x(), bounds.y()); canvas->Restore(); } // static void DownloadShelf::PaintDownloadProgress(gfx::Canvas* canvas, #if defined(TOOLKIT_VIEWS) views::View* containing_view, #endif int origin_x, int origin_y, int start_angle, int percent_done, PaintDownloadProgressSize size) { // Load up our common images. if (!g_background_16) { ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); g_foreground_16 = rb.GetImageSkiaNamed(IDR_DOWNLOAD_PROGRESS_FOREGROUND_16); g_background_16 = rb.GetImageSkiaNamed(IDR_DOWNLOAD_PROGRESS_BACKGROUND_16); g_foreground_32 = rb.GetImageSkiaNamed(IDR_DOWNLOAD_PROGRESS_FOREGROUND_32); g_background_32 = rb.GetImageSkiaNamed(IDR_DOWNLOAD_PROGRESS_BACKGROUND_32); DCHECK_EQ(g_foreground_16->width(), g_background_16->width()); DCHECK_EQ(g_foreground_16->height(), g_background_16->height()); DCHECK_EQ(g_foreground_32->width(), g_background_32->width()); DCHECK_EQ(g_foreground_32->height(), g_background_32->height()); } gfx::ImageSkia* background = (size == BIG) ? g_background_32 : g_background_16; gfx::ImageSkia* foreground = (size == BIG) ? g_foreground_32 : g_foreground_16; const int kProgressIconSize = (size == BIG) ? kBigProgressIconSize : kSmallProgressIconSize; // We start by storing the bounds of the images so that it is easy to mirror // the bounds if the UI layout is RTL. gfx::Rect bounds(origin_x, origin_y, background->width(), background->height()); #if defined(TOOLKIT_VIEWS) // Mirror the positions if necessary. int mirrored_x = containing_view->GetMirroredXForRect(bounds); bounds.set_x(mirrored_x); #endif // Draw the background progress image. canvas->DrawImageInt(*background, bounds.x(), bounds.y()); PaintCustomDownloadProgress(canvas, *background, *foreground, kProgressIconSize, bounds, start_angle, percent_done); } // static void DownloadShelf::PaintDownloadComplete(gfx::Canvas* canvas, #if defined(TOOLKIT_VIEWS) views::View* containing_view, #endif int origin_x, int origin_y, double animation_progress, PaintDownloadProgressSize size) { // Load up our common images. if (!g_foreground_16) { ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); g_foreground_16 = rb.GetImageSkiaNamed(IDR_DOWNLOAD_PROGRESS_FOREGROUND_16); g_foreground_32 = rb.GetImageSkiaNamed(IDR_DOWNLOAD_PROGRESS_FOREGROUND_32); } gfx::ImageSkia* complete = (size == BIG) ? g_foreground_32 : g_foreground_16; gfx::Rect complete_bounds(origin_x, origin_y, complete->width(), complete->height()); #if defined(TOOLKIT_VIEWS) // Mirror the positions if necessary. complete_bounds.set_x(containing_view->GetMirroredXForRect(complete_bounds)); #endif // Start at full opacity, then loop back and forth five times before ending // at zero opacity. canvas->DrawImageInt(*complete, complete_bounds.x(), complete_bounds.y(), GetOpacity(animation_progress)); } // static void DownloadShelf::PaintDownloadInterrupted(gfx::Canvas* canvas, #if defined(TOOLKIT_VIEWS) views::View* containing_view, #endif int origin_x, int origin_y, double animation_progress, PaintDownloadProgressSize size) { // Load up our common images. if (!g_foreground_16) { ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); g_foreground_16 = rb.GetImageSkiaNamed(IDR_DOWNLOAD_PROGRESS_FOREGROUND_16); g_foreground_32 = rb.GetImageSkiaNamed(IDR_DOWNLOAD_PROGRESS_FOREGROUND_32); } gfx::ImageSkia* complete = (size == BIG) ? g_foreground_32 : g_foreground_16; gfx::Rect complete_bounds(origin_x, origin_y, complete->width(), complete->height()); #if defined(TOOLKIT_VIEWS) // Mirror the positions if necessary. complete_bounds.set_x(containing_view->GetMirroredXForRect(complete_bounds)); #endif // Start at zero opacity, then loop back and forth five times before ending // at full opacity. canvas->DrawImageInt(*complete, complete_bounds.x(), complete_bounds.y(), GetOpacity(1.0 - animation_progress)); } void DownloadShelf::AddDownload(DownloadItem* download) { DCHECK(download); if (DownloadItemModel(download).ShouldRemoveFromShelfWhenComplete()) { // If we are going to remove the download from the shelf upon completion, // wait a few seconds to see if it completes quickly. If it's a small // download, then the user won't have time to interact with it. base::MessageLoop::current()->PostDelayedTask( FROM_HERE, base::Bind(&DownloadShelf::ShowDownloadById, weak_ptr_factory_.GetWeakPtr(), download->GetId()), GetTransientDownloadShowDelay()); } else { ShowDownload(download); } } void DownloadShelf::Show() { if (is_hidden_) { should_show_on_unhide_ = true; return; } DoShow(); } void DownloadShelf::Close(CloseReason reason) { if (is_hidden_) { should_show_on_unhide_ = false; return; } DoClose(reason); } void DownloadShelf::Hide() { if (is_hidden_) return; is_hidden_ = true; if (IsShowing()) { should_show_on_unhide_ = true; DoClose(AUTOMATIC); } } void DownloadShelf::Unhide() { if (!is_hidden_) return; is_hidden_ = false; if (should_show_on_unhide_) { should_show_on_unhide_ = false; DoShow(); } } base::TimeDelta DownloadShelf::GetTransientDownloadShowDelay() { return base::TimeDelta::FromSeconds(kDownloadShowDelayInSeconds); } content::DownloadManager* DownloadShelf::GetDownloadManager() { return content::BrowserContext::GetDownloadManager(browser()->profile()); } void DownloadShelf::ShowDownload(DownloadItem* download) { if (download->GetState() == DownloadItem::COMPLETE && DownloadItemModel(download).ShouldRemoveFromShelfWhenComplete()) return; if (!DownloadServiceFactory::GetForBrowserContext( download->GetBrowserContext())->IsShelfEnabled()) return; if (is_hidden_) Unhide(); Show(); DoAddDownload(download); // browser() can be NULL for tests. if (!browser()) return; // Show the download started animation if: // - Download started animation is enabled for this download. It is disabled // for "Save As" downloads and extension installs, for example. // - The browser has an active visible WebContents. (browser isn't minimized, // or running under a test etc.) // - Rich animations are enabled. content::WebContents* shelf_tab = browser()->tab_strip_model()->GetActiveWebContents(); if (DownloadItemModel(download).ShouldShowDownloadStartedAnimation() && shelf_tab && platform_util::IsVisible(shelf_tab->GetNativeView()) && gfx::Animation::ShouldRenderRichAnimation()) { DownloadStartedAnimation::Show(shelf_tab); } } void DownloadShelf::ShowDownloadById(int32 download_id) { content::DownloadManager* download_manager = GetDownloadManager(); if (!download_manager) return; DownloadItem* download = download_manager->GetDownload(download_id); if (!download) return; ShowDownload(download); }