Home | History | Annotate | Download | only in download
      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 #include "chrome/browser/download/download_started_animation.h"
      6 
      7 #include <math.h>
      8 
      9 #include <gtk/gtk.h>
     10 
     11 #include "base/message_loop/message_loop.h"
     12 #include "content/public/browser/notification_details.h"
     13 #include "content/public/browser/notification_observer.h"
     14 #include "content/public/browser/notification_registrar.h"
     15 #include "content/public/browser/notification_source.h"
     16 #include "content/public/browser/notification_types.h"
     17 #include "content/public/browser/web_contents.h"
     18 #include "content/public/browser/web_contents_view.h"
     19 #include "grit/theme_resources.h"
     20 #include "ui/base/animation/linear_animation.h"
     21 #include "ui/base/resource/resource_bundle.h"
     22 #include "ui/gfx/image/image.h"
     23 #include "ui/gfx/rect.h"
     24 
     25 using content::WebContents;
     26 
     27 namespace {
     28 
     29 // How long to spend moving downwards and fading out after waiting.
     30 const int kMoveTimeMs = 600;
     31 
     32 // The animation framerate.
     33 const int kFrameRateHz = 60;
     34 
     35 // What fraction of the frame height to move downward from the frame center.
     36 // Note that setting this greater than 0.5 will mean moving past the bottom of
     37 // the frame.
     38 const double kMoveFraction = 1.0 / 3.0;
     39 
     40 class DownloadStartedAnimationGtk : public ui::LinearAnimation,
     41                                     public content::NotificationObserver {
     42  public:
     43   explicit DownloadStartedAnimationGtk(WebContents* web_contents);
     44 
     45   // DownloadStartedAnimation will delete itself, but this is public so
     46   // that we can use DeleteSoon().
     47   virtual ~DownloadStartedAnimationGtk();
     48 
     49  private:
     50   // Move the arrow to wherever it should currently be.
     51   void Reposition();
     52 
     53   // Shut down cleanly.
     54   void Close();
     55 
     56   // Animation implementation.
     57   virtual void AnimateToState(double state) OVERRIDE;
     58 
     59   // NotificationObserver
     60   virtual void Observe(int type,
     61                        const content::NotificationSource& source,
     62                        const content::NotificationDetails& details) OVERRIDE;
     63 
     64   // The top level window that floats over the browser and displays the
     65   // image.
     66   GtkWidget* popup_;
     67 
     68   // Dimensions of the image.
     69   int width_;
     70   int height_;
     71 
     72   // The content area holding us.
     73   WebContents* web_contents_;
     74 
     75   // The content area at the start of the animation. We store this so that the
     76   // download shelf's resizing of the content area doesn't cause the animation
     77   // to move around. This means that once started, the animation won't move
     78   // with the parent window, but it's so fast that this shouldn't cause too
     79   // much heartbreak.
     80   gfx::Rect web_contents_bounds_;
     81 
     82   // A scoped container for notification registries.
     83   content::NotificationRegistrar registrar_;
     84 
     85   DISALLOW_COPY_AND_ASSIGN(DownloadStartedAnimationGtk);
     86 };
     87 
     88 DownloadStartedAnimationGtk::DownloadStartedAnimationGtk(
     89     WebContents* web_contents)
     90     : ui::LinearAnimation(kMoveTimeMs, kFrameRateHz, NULL),
     91       popup_(NULL),
     92       web_contents_(web_contents) {
     93   static GdkPixbuf* kDownloadImage = NULL;
     94   if (!kDownloadImage) {
     95     ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
     96     kDownloadImage = rb.GetNativeImageNamed(
     97         IDR_DOWNLOAD_ANIMATION_BEGIN).ToGdkPixbuf();
     98   }
     99 
    100   width_ = gdk_pixbuf_get_width(kDownloadImage);
    101   height_ = gdk_pixbuf_get_height(kDownloadImage);
    102 
    103   // If we're too small to show the download image, then don't bother -
    104   // the shelf will be enough.
    105   web_contents_->GetView()->GetContainerBounds(&web_contents_bounds_);
    106   if (web_contents_bounds_.height() < height_)
    107     return;
    108 
    109   registrar_.Add(
    110       this,
    111       content::NOTIFICATION_WEB_CONTENTS_VISIBILITY_CHANGED,
    112       content::Source<WebContents>(web_contents_));
    113   registrar_.Add(
    114       this,
    115       content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
    116       content::Source<WebContents>(web_contents_));
    117 
    118   // TODO(estade): don't show up on the wrong virtual desktop.
    119 
    120   popup_ = gtk_window_new(GTK_WINDOW_POPUP);
    121   GtkWidget* image = gtk_image_new_from_pixbuf(kDownloadImage);
    122   gtk_container_add(GTK_CONTAINER(popup_), image);
    123 
    124   // Set the shape of the window to that of the arrow. Areas with
    125   // opacity less than 0xff (i.e. <100% opacity) will be transparent.
    126   GdkBitmap* mask = gdk_pixmap_new(NULL, width_, height_, 1);
    127   gdk_pixbuf_render_threshold_alpha(kDownloadImage, mask,
    128                                     0, 0,
    129                                     0, 0, -1, -1,
    130                                     0xff);
    131   gtk_widget_shape_combine_mask(popup_, mask, 0, 0);
    132   g_object_unref(mask);
    133 
    134   Reposition();
    135   gtk_widget_show_all(popup_);
    136   // Make sure our window has focus, is brought to the top, etc.
    137   gtk_window_present(GTK_WINDOW(popup_));
    138 
    139   Start();
    140 }
    141 
    142 DownloadStartedAnimationGtk::~DownloadStartedAnimationGtk() {
    143 }
    144 
    145 void DownloadStartedAnimationGtk::Reposition() {
    146   if (!web_contents_)
    147     return;
    148 
    149   DCHECK(popup_);
    150 
    151   // Align the image with the bottom left of the web contents (so that it
    152   // points to the newly created download).
    153   gtk_window_move(GTK_WINDOW(popup_),
    154       web_contents_bounds_.x(),
    155       static_cast<int>(web_contents_bounds_.bottom() -
    156           height_ - height_ * (1 - GetCurrentValue())));
    157 }
    158 
    159 void DownloadStartedAnimationGtk::Close() {
    160   if (!web_contents_)
    161     return;
    162 
    163   DCHECK(popup_);
    164 
    165   registrar_.Remove(
    166       this,
    167       content::NOTIFICATION_WEB_CONTENTS_VISIBILITY_CHANGED,
    168       content::Source<WebContents>(web_contents_));
    169   registrar_.Remove(
    170       this,
    171       content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
    172       content::Source<WebContents>(web_contents_));
    173 
    174   web_contents_ = NULL;
    175   gtk_widget_destroy(popup_);
    176   base::MessageLoop::current()->DeleteSoon(FROM_HERE, this);
    177 }
    178 
    179 void DownloadStartedAnimationGtk::AnimateToState(double state) {
    180   if (!web_contents_)
    181     return;
    182 
    183   if (state >= 1.0) {
    184     Close();
    185   } else {
    186     Reposition();
    187 
    188     // Start at zero, peak halfway and end at zero.
    189     double opacity = std::min(1.0 - pow(GetCurrentValue() - 0.5, 2) * 4.0,
    190                               static_cast<double>(1.0));
    191 
    192     // This only works when there's a compositing manager running. Oh well.
    193     gtk_window_set_opacity(GTK_WINDOW(popup_), opacity);
    194   }
    195 }
    196 
    197 void DownloadStartedAnimationGtk::Observe(
    198     int type,
    199     const content::NotificationSource& source,
    200     const content::NotificationDetails& details) {
    201   if (type == content::NOTIFICATION_WEB_CONTENTS_VISIBILITY_CHANGED) {
    202     bool visible = *content::Details<bool>(details).ptr();
    203     if (visible)
    204       return;
    205   }
    206   Close();
    207 }
    208 
    209 }  // namespace
    210 
    211 // static
    212 void DownloadStartedAnimation::Show(WebContents* web_contents) {
    213   // The animation will delete itself.
    214   new DownloadStartedAnimationGtk(web_contents);
    215 }
    216