[Metrics] Make MetricsStateManager take a callback param to check if UMA is enabled.
[chromium-blink-merge.git] / chrome / browser / ui / cocoa / info_bubble_window.mm
blob4822d19cc82aa9061c9fb8daa620172cfffbfbd9
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"
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
27 @interface InfoBubbleWindow (Private)
28 - (void)appIsTerminating;
29 - (void)finishCloseAfterAnimation;
30 @end
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   }
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   }
54  private:
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 {
69  @private
70   InfoBubbleWindow* window_;  // Weak. Window to close.
72 - (id)initWithWindow:(InfoBubbleWindow*)window;
73 @end
75 @implementation InfoBubbleWindowCloser
77 - (id)initWithWindow:(InfoBubbleWindow*)window {
78   if ((self = [super init])) {
79     window_ = window;
80   }
81   return self;
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   }
92 @end
95 @implementation InfoBubbleWindow
97 @synthesize allowedAnimations = allowedAnimations_;
98 @synthesize canBecomeKeyWindow = canBecomeKeyWindow_;
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));
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];
133   }
134   return self;
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_;
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;
152 - (void)close {
153   // Block the window from receiving events while it fades out.
154   closing_ = YES;
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   }
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 {
187   if (closing_)
188     [super close];
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];
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];
219   } else {
220     [super orderWindow:orderingMode relativeTo:otherWindowNumber];
221   }
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];
231 - (BOOL)isClosing {
232   return closing_;
235 @end