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.
5 #import "chrome/browser/ui/cocoa/info_bubble_window.h"
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/google_toolbox_for_mac/src/AppKit/GTMNSAnimation+Duration.h"
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();
27 @interface InfoBubbleWindow (Private)
28 - (void)appIsTerminating;
29 - (void)finishCloseAfterAnimation;
32 // A helper class to proxy app notifications to the window.
33 class AppNotificationBridge : public content::NotificationObserver {
35 explicit AppNotificationBridge(InfoBubbleWindow* owner) : owner_(owner) {
36 registrar_.Add(this, chrome::NOTIFICATION_APP_TERMINATING,
37 content::NotificationService::AllSources());
40 // Overridden from content::NotificationObserver.
43 const content::NotificationSource& source,
44 const content::NotificationDetails& details) OVERRIDE {
46 case chrome::NOTIFICATION_APP_TERMINATING:
47 [owner_ appIsTerminating];
50 NOTREACHED() << L"Unexpected notification";
55 // The object we need to inform when we get a notification. Weak. Owns us.
56 InfoBubbleWindow* owner_;
58 // Used for registering to receive notifications and automatic clean up.
59 content::NotificationRegistrar registrar_;
61 DISALLOW_COPY_AND_ASSIGN(AppNotificationBridge);
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 {
70 InfoBubbleWindow* window_; // Weak. Window to close.
72 - (id)initWithWindow:(InfoBubbleWindow*)window;
75 @implementation InfoBubbleWindowCloser
77 - (id)initWithWindow:(InfoBubbleWindow*)window {
78 if ((self = [super init])) {
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];
95 @implementation InfoBubbleWindow
97 @synthesize allowedAnimations = allowedAnimations_;
98 @synthesize canBecomeKeyWindow = canBecomeKeyWindow_;
100 - (id)initWithContentRect:(NSRect)contentRect
101 styleMask:(NSUInteger)aStyle
102 backing:(NSBackingStoreType)bufferingType
104 if ((self = [super initWithContentRect:contentRect
105 styleMask:NSBorderlessWindowMask
106 backing:bufferingType
108 [self setBackgroundColor:[NSColor clearColor]];
109 [self setExcludedFromWindowsMenu:YES];
111 [self setHasShadow:YES];
112 canBecomeKeyWindow_ = YES;
113 allowedAnimations_ = info_bubble::kAnimateOrderIn |
114 info_bubble::kAnimateOrderOut;
115 notificationBridge_.reset(new AppNotificationBridge(self));
117 // Start invisible. Will be made visible when ordered front.
118 [self setAlphaValue:0.0];
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];
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_;
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 {
153 // Block the window from receiving events while it fades out.
156 if ((allowedAnimations_ & info_bubble::kAnimateOrderOut) == 0) {
157 [self finishCloseAfterAnimation];
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];
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.
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];
184 // Called by InfoBubbleWindowCloser when the window is to be really closed
185 // after the fading animation is complete.
186 - (void)finishCloseAfterAnimation {
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
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];
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];
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];
220 [super orderWindow:orderingMode relativeTo:otherWindowNumber];
224 // If the window is currently animating a close, block all UI events to the
226 - (void)sendEvent:(NSEvent*)theEvent {
228 [super sendEvent:theEvent];