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 // This file contains the Mac implementation the download animation, displayed
      6 // at the start of a download. The animation produces an arrow pointing
      7 // downwards and animates towards the bottom of the window where the new
      8 // download appears in the download shelf.
      9 
     10 #include "chrome/browser/download/download_started_animation.h"
     11 
     12 #import <QuartzCore/QuartzCore.h>
     13 
     14 #include "chrome/browser/tab_contents/tab_contents_view_mac.h"
     15 #import "chrome/browser/ui/cocoa/animatable_image.h"
     16 #include "content/browser/tab_contents/tab_contents.h"
     17 #include "content/common/notification_details.h"
     18 #include "content/common/notification_registrar.h"
     19 #include "content/common/notification_source.h"
     20 #include "grit/theme_resources.h"
     21 #import "third_party/GTM/AppKit/GTMNSAnimation+Duration.h"
     22 #include "third_party/skia/include/utils/mac/SkCGUtils.h"
     23 #include "ui/base/resource/resource_bundle.h"
     24 #include "ui/gfx/image.h"
     25 
     26 class DownloadAnimationTabObserver;
     27 
     28 // A class for managing the Core Animation download animation.
     29 // Should be instantiated using +startAnimationWithTabContents:.
     30 @interface DownloadStartedAnimationMac : NSObject {
     31  @private
     32   // The observer for the TabContents we are drawing on.
     33   scoped_ptr<DownloadAnimationTabObserver> observer_;
     34   CGFloat imageWidth_;
     35   AnimatableImage* animation_;
     36 };
     37 
     38 + (void)startAnimationWithTabContents:(TabContents*)tabContents;
     39 
     40 // Called by the Observer if the tab is hidden or closed.
     41 - (void)closeAnimation;
     42 
     43 @end
     44 
     45 // A helper class to monitor tab hidden and closed notifications. If we receive
     46 // such a notification, we stop the animation.
     47 class DownloadAnimationTabObserver : public NotificationObserver {
     48  public:
     49   DownloadAnimationTabObserver(DownloadStartedAnimationMac* owner,
     50                                TabContents* tab_contents)
     51       : owner_(owner),
     52         tab_contents_(tab_contents) {
     53     registrar_.Add(this,
     54                    NotificationType::TAB_CONTENTS_HIDDEN,
     55                    Source<TabContents>(tab_contents_));
     56     registrar_.Add(this,
     57                    NotificationType::TAB_CONTENTS_DESTROYED,
     58                    Source<TabContents>(tab_contents_));
     59   }
     60 
     61   // Runs when a tab is hidden or destroyed. Let our owner know we should end
     62   // the animation.
     63   void Observe(NotificationType type,
     64                const NotificationSource& source,
     65                const NotificationDetails& details) {
     66     // This ends up deleting us.
     67     [owner_ closeAnimation];
     68   }
     69 
     70  private:
     71   // The object we need to inform when we get a notification. Weak.
     72   DownloadStartedAnimationMac* owner_;
     73 
     74   // The tab we are observing. Weak.
     75   TabContents* tab_contents_;
     76 
     77   // Used for registering to receive notifications and automatic clean up.
     78   NotificationRegistrar registrar_;
     79 
     80   DISALLOW_COPY_AND_ASSIGN(DownloadAnimationTabObserver);
     81 };
     82 
     83 @implementation DownloadStartedAnimationMac
     84 
     85 - (id)initWithTabContents:(TabContents*)tabContents {
     86   if ((self = [super init])) {
     87     // Load the image of the download arrow.
     88     ResourceBundle& bundle = ResourceBundle::GetSharedInstance();
     89     NSImage* image = bundle.GetNativeImageNamed(IDR_DOWNLOAD_ANIMATION_BEGIN);
     90 
     91     // Figure out the positioning in the current tab. Try to position the layer
     92     // against the left edge, and three times the download image's height from
     93     // the bottom of the tab, assuming there is enough room. If there isn't
     94     // enough, don't show the animation and let the shelf speak for itself.
     95     gfx::Rect bounds;
     96     tabContents->GetContainerBounds(&bounds);
     97     imageWidth_ = [image size].width;
     98     CGFloat imageHeight = [image size].height;
     99 
    100     // Sanity check the size in case there's no room to display the animation.
    101     if (bounds.height() < imageHeight) {
    102       [self release];
    103       return nil;
    104     }
    105 
    106     NSView* tabContentsView = tabContents->GetNativeView();
    107     NSWindow* parentWindow = [tabContentsView window];
    108     if (!parentWindow) {
    109       // The tab is no longer frontmost.
    110       [self release];
    111       return nil;
    112     }
    113 
    114     NSPoint origin = [tabContentsView frame].origin;
    115     origin = [tabContentsView convertPoint:origin toView:nil];
    116     origin = [parentWindow convertBaseToScreen:origin];
    117 
    118     // Create the animation object to assist in animating and fading.
    119     CGFloat animationHeight = MIN(bounds.height(), 4 * imageHeight);
    120     NSRect frame = NSMakeRect(origin.x, origin.y, imageWidth_, animationHeight);
    121     animation_ = [[AnimatableImage alloc] initWithImage:image
    122                                          animationFrame:frame];
    123     [parentWindow addChildWindow:animation_ ordered:NSWindowAbove];
    124 
    125     animationHeight = MIN(bounds.height(), 3 * imageHeight);
    126     [animation_ setStartFrame:CGRectMake(0, animationHeight,
    127                                          imageWidth_, imageHeight)];
    128     [animation_ setEndFrame:CGRectMake(0, imageHeight,
    129                                        imageWidth_, imageHeight)];
    130     [animation_ setStartOpacity:1.0];
    131     [animation_ setEndOpacity:0.4];
    132     [animation_ setDuration:0.6];
    133 
    134     observer_.reset(new DownloadAnimationTabObserver(self, tabContents));
    135 
    136     // Set up to get notified about resize events on the parent window.
    137     NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
    138     [center addObserver:self
    139                selector:@selector(parentWindowChanged:)
    140                    name:NSWindowDidResizeNotification
    141                  object:parentWindow];
    142     // When the animation window closes, it needs to be removed from the
    143     // parent window.
    144     [center addObserver:self
    145                selector:@selector(windowWillClose:)
    146                    name:NSWindowWillCloseNotification
    147                 object:animation_];
    148   }
    149   return self;
    150 }
    151 
    152 - (void)dealloc {
    153   [[NSNotificationCenter defaultCenter] removeObserver:self];
    154   [super dealloc];
    155 }
    156 
    157 // Called when the parent window is resized.
    158 - (void)parentWindowChanged:(NSNotification*)notification {
    159   NSWindow* parentWindow = [animation_ parentWindow];
    160   DCHECK([[notification object] isEqual:parentWindow]);
    161   NSRect parentFrame = [parentWindow frame];
    162   NSRect frame = parentFrame;
    163   frame.size.width = MIN(imageWidth_, NSWidth(parentFrame));
    164   [animation_ setFrame:frame display:YES];
    165 }
    166 
    167 - (void)closeAnimation {
    168   [animation_ close];
    169 }
    170 
    171 // When the animation closes, release self.
    172 - (void)windowWillClose:(NSNotification*)notification {
    173   DCHECK([[notification object] isEqual:animation_]);
    174   [[animation_ parentWindow] removeChildWindow:animation_];
    175   [self release];
    176 }
    177 
    178 + (void)startAnimationWithTabContents:(TabContents*)contents {
    179   // Will be deleted when the animation window closes.
    180   DownloadStartedAnimationMac* controller =
    181       [[self alloc] initWithTabContents:contents];
    182   // The initializer can return nil.
    183   if (!controller)
    184     return;
    185 
    186   // The |animation_| releases itself when done.
    187   [controller->animation_ startAnimation];
    188 }
    189 
    190 @end
    191 
    192 void DownloadStartedAnimation::Show(TabContents* tab_contents) {
    193   DCHECK(tab_contents);
    194 
    195   // Will be deleted when the animation is complete.
    196   [DownloadStartedAnimationMac startAnimationWithTabContents:tab_contents];
    197 }
    198