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