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