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