Home | History | Annotate | Download | only in cocoa
      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 #import "chrome/browser/ui/cocoa/info_bubble_window.h"
      6 
      7 #include "base/basictypes.h"
      8 #include "base/logging.h"
      9 #include "base/mac/scoped_nsobject.h"
     10 #include "chrome/browser/chrome_notification_types.h"
     11 #include "content/public/browser/notification_observer.h"
     12 #include "content/public/browser/notification_registrar.h"
     13 #include "content/public/browser/notification_service.h"
     14 #import "third_party/GTM/AppKit/GTMNSAnimation+Duration.h"
     15 
     16 namespace {
     17 const CGFloat kOrderInSlideOffset = 10;
     18 const NSTimeInterval kOrderInAnimationDuration = 0.075;
     19 const NSTimeInterval kOrderOutAnimationDuration = 0.15;
     20 // The minimum representable time interval.  This can be used as the value
     21 // passed to +[NSAnimationContext setDuration:] to stop an in-progress
     22 // animation as quickly as possible.
     23 const NSTimeInterval kMinimumTimeInterval =
     24     std::numeric_limits<NSTimeInterval>::min();
     25 }  // namespace
     26 
     27 @interface InfoBubbleWindow (Private)
     28 - (void)appIsTerminating;
     29 - (void)finishCloseAfterAnimation;
     30 @end
     31 
     32 // A helper class to proxy app notifications to the window.
     33 class AppNotificationBridge : public content::NotificationObserver {
     34  public:
     35   explicit AppNotificationBridge(InfoBubbleWindow* owner) : owner_(owner) {
     36     registrar_.Add(this, chrome::NOTIFICATION_APP_TERMINATING,
     37                    content::NotificationService::AllSources());
     38   }
     39 
     40   // Overridden from content::NotificationObserver.
     41   virtual void Observe(
     42       int type,
     43       const content::NotificationSource& source,
     44       const content::NotificationDetails& details) OVERRIDE {
     45     switch (type) {
     46       case chrome::NOTIFICATION_APP_TERMINATING:
     47         [owner_ appIsTerminating];
     48         break;
     49       default:
     50         NOTREACHED() << L"Unexpected notification";
     51     }
     52   }
     53 
     54  private:
     55   // The object we need to inform when we get a notification. Weak. Owns us.
     56   InfoBubbleWindow* owner_;
     57 
     58   // Used for registering to receive notifications and automatic clean up.
     59   content::NotificationRegistrar registrar_;
     60 
     61   DISALLOW_COPY_AND_ASSIGN(AppNotificationBridge);
     62 };
     63 
     64 // A delegate object for watching the alphaValue animation on InfoBubbleWindows.
     65 // An InfoBubbleWindow instance cannot be the delegate for its own animation
     66 // because CAAnimations retain their delegates, and since the InfoBubbleWindow
     67 // retains its animations a retain loop would be formed.
     68 @interface InfoBubbleWindowCloser : NSObject {
     69  @private
     70   InfoBubbleWindow* window_;  // Weak. Window to close.
     71 }
     72 - (id)initWithWindow:(InfoBubbleWindow*)window;
     73 @end
     74 
     75 @implementation InfoBubbleWindowCloser
     76 
     77 - (id)initWithWindow:(InfoBubbleWindow*)window {
     78   if ((self = [super init])) {
     79     window_ = window;
     80   }
     81   return self;
     82 }
     83 
     84 // Callback for the alpha animation. Closes window_ if appropriate.
     85 - (void)animationDidStop:(CAAnimation*)anim finished:(BOOL)flag {
     86   // When alpha reaches zero, close window_.
     87   if ([window_ alphaValue] == 0.0) {
     88     [window_ finishCloseAfterAnimation];
     89   }
     90 }
     91 
     92 @end
     93 
     94 
     95 @implementation InfoBubbleWindow
     96 
     97 @synthesize allowedAnimations = allowedAnimations_;
     98 @synthesize canBecomeKeyWindow = canBecomeKeyWindow_;
     99 
    100 - (id)initWithContentRect:(NSRect)contentRect
    101                 styleMask:(NSUInteger)aStyle
    102                   backing:(NSBackingStoreType)bufferingType
    103                     defer:(BOOL)flag {
    104   if ((self = [super initWithContentRect:contentRect
    105                                styleMask:NSBorderlessWindowMask
    106                                  backing:bufferingType
    107                                    defer:flag])) {
    108     [self setBackgroundColor:[NSColor clearColor]];
    109     [self setExcludedFromWindowsMenu:YES];
    110     [self setOpaque:NO];
    111     [self setHasShadow:YES];
    112     canBecomeKeyWindow_ = YES;
    113     allowedAnimations_ = info_bubble::kAnimateOrderIn |
    114                          info_bubble::kAnimateOrderOut;
    115     notificationBridge_.reset(new AppNotificationBridge(self));
    116 
    117     // Start invisible. Will be made visible when ordered front.
    118     [self setAlphaValue:0.0];
    119 
    120     // Set up alphaValue animation so that self is delegate for the animation.
    121     // Setting up the delegate is required so that the
    122     // animationDidStop:finished: callback can be handled.
    123     // Notice that only the alphaValue Animation is replaced in case
    124     // superclasses set up animations.
    125     CAAnimation* alphaAnimation = [CABasicAnimation animation];
    126     base::scoped_nsobject<InfoBubbleWindowCloser> delegate(
    127         [[InfoBubbleWindowCloser alloc] initWithWindow:self]);
    128     [alphaAnimation setDelegate:delegate];
    129     NSMutableDictionary* animations =
    130         [NSMutableDictionary dictionaryWithDictionary:[self animations]];
    131     [animations setObject:alphaAnimation forKey:@"alphaValue"];
    132     [self setAnimations:animations];
    133   }
    134   return self;
    135 }
    136 
    137 // According to
    138 // http://www.cocoabuilder.com/archive/message/cocoa/2006/6/19/165953,
    139 // NSBorderlessWindowMask windows cannot become key or main. In this
    140 // case, this is not necessarily a desired behavior. As an example, the
    141 // bubble could have buttons.
    142 - (BOOL)canBecomeKeyWindow {
    143   return canBecomeKeyWindow_;
    144 }
    145 
    146 // Lets the traffic light buttons on the browser window keep their "active"
    147 // state while an info bubble is open. Only has an effect on 10.7.
    148 - (BOOL)_sharesParentKeyState {
    149   return YES;
    150 }
    151 
    152 - (void)close {
    153   // Block the window from receiving events while it fades out.
    154   closing_ = YES;
    155 
    156   if ((allowedAnimations_ & info_bubble::kAnimateOrderOut) == 0) {
    157     [self finishCloseAfterAnimation];
    158   } else {
    159     // Apply animations to hide self.
    160     [NSAnimationContext beginGrouping];
    161     [[NSAnimationContext currentContext]
    162         gtm_setDuration:kOrderOutAnimationDuration
    163               eventMask:NSLeftMouseUpMask];
    164     [[self animator] setAlphaValue:0.0];
    165     [NSAnimationContext endGrouping];
    166   }
    167 }
    168 
    169 // If the app is terminating but the window is still fading out, cancel the
    170 // animation and close the window to prevent it from leaking.
    171 // See http://crbug.com/37717
    172 - (void)appIsTerminating {
    173   if ((allowedAnimations_ & info_bubble::kAnimateOrderOut) == 0)
    174     return;  // The close has already happened with no Core Animation.
    175 
    176   // Cancel the current animation so that it closes immediately, triggering
    177   // |finishCloseAfterAnimation|.
    178   [NSAnimationContext beginGrouping];
    179   [[NSAnimationContext currentContext] setDuration:kMinimumTimeInterval];
    180   [[self animator] setAlphaValue:0.0];
    181   [NSAnimationContext endGrouping];
    182 }
    183 
    184 // Called by InfoBubbleWindowCloser when the window is to be really closed
    185 // after the fading animation is complete.
    186 - (void)finishCloseAfterAnimation {
    187   if (closing_)
    188     [super close];
    189 }
    190 
    191 // Adds animation for info bubbles being ordered to the front.
    192 - (void)orderWindow:(NSWindowOrderingMode)orderingMode
    193          relativeTo:(NSInteger)otherWindowNumber {
    194   // According to the documentation '0' is the otherWindowNumber when the window
    195   // is ordered front.
    196   if (orderingMode == NSWindowAbove && otherWindowNumber == 0) {
    197     // Order self appropriately assuming that its alpha is zero as set up
    198     // in the designated initializer.
    199     [super orderWindow:orderingMode relativeTo:otherWindowNumber];
    200 
    201     // Set up frame so it can be adjust down by a few pixels.
    202     NSRect frame = [self frame];
    203     NSPoint newOrigin = frame.origin;
    204     newOrigin.y += kOrderInSlideOffset;
    205     [self setFrameOrigin:newOrigin];
    206 
    207     // Apply animations to show and move self.
    208     [NSAnimationContext beginGrouping];
    209     // The star currently triggers on mouse down, not mouse up.
    210     NSTimeInterval duration =
    211         (allowedAnimations_ & info_bubble::kAnimateOrderIn)
    212             ? kOrderInAnimationDuration : kMinimumTimeInterval;
    213     [[NSAnimationContext currentContext]
    214         gtm_setDuration:duration
    215               eventMask:NSLeftMouseUpMask | NSLeftMouseDownMask];
    216     [[self animator] setAlphaValue:1.0];
    217     [[self animator] setFrame:frame display:YES];
    218     [NSAnimationContext endGrouping];
    219   } else {
    220     [super orderWindow:orderingMode relativeTo:otherWindowNumber];
    221   }
    222 }
    223 
    224 // If the window is currently animating a close, block all UI events to the
    225 // window.
    226 - (void)sendEvent:(NSEvent*)theEvent {
    227   if (!closing_)
    228     [super sendEvent:theEvent];
    229 }
    230 
    231 - (BOOL)isClosing {
    232   return closing_;
    233 }
    234 
    235 @end
    236