Upstream version 11.40.277.0
[platform/framework/web/crosswalk.git] / src / ui / message_center / cocoa / popup_controller.mm
1 // Copyright (c) 2013 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 "ui/message_center/cocoa/popup_controller.h"
6
7 #include <cmath>
8
9 #import "base/mac/foundation_util.h"
10 #import "base/mac/sdk_forward_declarations.h"
11 #import "ui/base/cocoa/window_size_constants.h"
12 #import "ui/message_center/cocoa/notification_controller.h"
13 #import "ui/message_center/cocoa/popup_collection.h"
14 #include "ui/message_center/message_center.h"
15
16 ////////////////////////////////////////////////////////////////////////////////
17
18 @interface MCPopupController (Private)
19 - (void)notificationSwipeStarted;
20 - (void)notificationSwipeMoved:(CGFloat)amount;
21 - (void)notificationSwipeEnded:(BOOL)ended complete:(BOOL)isComplete;
22 @end
23
24 // Window Subclass /////////////////////////////////////////////////////////////
25
26 @interface MCPopupWindow : NSPanel {
27   // The cumulative X and Y scrollingDeltas since the -scrollWheel: event began.
28   NSPoint totalScrollDelta_;
29 }
30 @end
31
32 @implementation MCPopupWindow
33
34 - (void)scrollWheel:(NSEvent*)event {
35   // Gesture swiping only exists on 10.7+.
36   if (![event respondsToSelector:@selector(phase)])
37     return;
38
39   NSEventPhase phase = [event phase];
40   BOOL shouldTrackSwipe = NO;
41
42   if (phase == NSEventPhaseBegan) {
43     totalScrollDelta_ = NSZeroPoint;
44   } else if (phase == NSEventPhaseChanged) {
45     shouldTrackSwipe = YES;
46     totalScrollDelta_.x += [event scrollingDeltaX];
47     totalScrollDelta_.y += [event scrollingDeltaY];
48   }
49
50   // Only allow horizontal scrolling.
51   if (std::abs(totalScrollDelta_.x) < std::abs(totalScrollDelta_.y))
52     return;
53
54   if (shouldTrackSwipe) {
55     MCPopupController* controller =
56         base::mac::ObjCCastStrict<MCPopupController>([self windowController]);
57     BOOL directionInverted = [event isDirectionInvertedFromDevice];
58
59     auto handler = ^(CGFloat gestureAmount, NSEventPhase phase,
60                      BOOL isComplete, BOOL* stop) {
61         // The swipe direction should match the direction the user's fingers
62         // are moving, not the interpreted scroll direction.
63         if (directionInverted)
64           gestureAmount *= -1;
65
66         if (phase == NSEventPhaseBegan) {
67           [controller notificationSwipeStarted];
68           return;
69         }
70
71         [controller notificationSwipeMoved:gestureAmount];
72
73         BOOL ended = phase == NSEventPhaseEnded;
74         if (ended || isComplete)
75           [controller notificationSwipeEnded:ended complete:isComplete];
76     };
77     [event trackSwipeEventWithOptions:NSEventSwipeTrackingLockDirection
78              dampenAmountThresholdMin:-1
79                                   max:1
80                          usingHandler:handler];
81   }
82 }
83
84 @end
85
86 ////////////////////////////////////////////////////////////////////////////////
87
88 @implementation MCPopupController
89
90 - (id)initWithNotification:(const message_center::Notification*)notification
91              messageCenter:(message_center::MessageCenter*)messageCenter
92            popupCollection:(MCPopupCollection*)popupCollection {
93   base::scoped_nsobject<MCPopupWindow> window(
94       [[MCPopupWindow alloc] initWithContentRect:ui::kWindowSizeDeterminedLater
95                                        styleMask:NSBorderlessWindowMask |
96                                                  NSNonactivatingPanelMask
97                                          backing:NSBackingStoreBuffered
98                                            defer:YES]);
99   if ((self = [super initWithWindow:window])) {
100     messageCenter_ = messageCenter;
101     popupCollection_ = popupCollection;
102     notificationController_.reset(
103         [[MCNotificationController alloc] initWithNotification:notification
104                                                  messageCenter:messageCenter_]);
105     isClosing_ = NO;
106     bounds_ = [[notificationController_ view] frame];
107
108     [window setReleasedWhenClosed:NO];
109
110     [window setLevel:NSFloatingWindowLevel];
111     [window setExcludedFromWindowsMenu:YES];
112     [window setCollectionBehavior:
113         NSWindowCollectionBehaviorIgnoresCycle |
114         NSWindowCollectionBehaviorFullScreenAuxiliary];
115
116     [window setHasShadow:YES];
117     [window setContentView:[notificationController_ view]];
118
119     trackingArea_.reset(
120         [[CrTrackingArea alloc] initWithRect:NSZeroRect
121                                      options:NSTrackingInVisibleRect |
122                                              NSTrackingMouseEnteredAndExited |
123                                              NSTrackingActiveAlways
124                                        owner:self
125                                     userInfo:nil]);
126     [[window contentView] addTrackingArea:trackingArea_.get()];
127   }
128   return self;
129 }
130
131 - (void)close {
132   if (boundsAnimation_) {
133     [boundsAnimation_ stopAnimation];
134     [boundsAnimation_ setDelegate:nil];
135     boundsAnimation_.reset();
136   }
137   if (trackingArea_.get())
138     [[[self window] contentView] removeTrackingArea:trackingArea_.get()];
139   [super close];
140   [self performSelectorOnMainThread:@selector(release)
141                          withObject:nil
142                       waitUntilDone:NO
143                               modes:@[ NSDefaultRunLoopMode ]];
144 }
145
146 - (MCNotificationController*)notificationController {
147   return notificationController_.get();
148 }
149
150 - (const message_center::Notification*)notification {
151   return [notificationController_ notification];
152 }
153
154 - (const std::string&)notificationID {
155   return [notificationController_ notificationID];
156 }
157
158 // Private /////////////////////////////////////////////////////////////////////
159
160 - (void)notificationSwipeStarted {
161   originalFrame_ = [[self window] frame];
162   swipeGestureEnded_ = NO;
163 }
164
165 - (void)notificationSwipeMoved:(CGFloat)amount {
166   NSWindow* window = [self window];
167
168   [window setAlphaValue:1.0 - std::abs(amount)];
169   NSRect frame = [window frame];
170   CGFloat originalMin = NSMinX(originalFrame_);
171   frame.origin.x = originalMin + (NSMidX(originalFrame_) - originalMin) *
172                    -amount;
173   [window setFrame:frame display:YES];
174 }
175
176 - (void)notificationSwipeEnded:(BOOL)ended complete:(BOOL)isComplete {
177   swipeGestureEnded_ |= ended;
178   if (swipeGestureEnded_ && isComplete) {
179     messageCenter_->RemoveNotification([self notificationID], /*by_user=*/true);
180     [popupCollection_ onPopupAnimationEnded:[self notificationID]];
181   }
182 }
183
184 - (void)animationDidEnd:(NSAnimation*)animation {
185   if (animation != boundsAnimation_.get())
186     return;
187   boundsAnimation_.reset();
188
189   [popupCollection_ onPopupAnimationEnded:[self notificationID]];
190
191   if (isClosing_)
192     [self close];
193 }
194
195 - (void)animationDidStop:(NSAnimation*)animation {
196   // We can arrive here if animation was stopped in [self close] call.
197   boundsAnimation_.reset();
198
199   [popupCollection_ onPopupAnimationEnded:[self notificationID]];
200 }
201
202 - (void)showWithAnimation:(NSRect)newBounds {
203   bounds_ = newBounds;
204   NSRect startBounds = newBounds;
205   startBounds.origin.x += startBounds.size.width;
206   [[self window] setFrame:startBounds display:NO];
207   [[self window] setAlphaValue:0];
208   [self showWindow:nil];
209
210   // Slide-in and fade-in simultaneously.
211   NSDictionary* animationDict = @{
212     NSViewAnimationTargetKey : [self window],
213     NSViewAnimationEndFrameKey : [NSValue valueWithRect:newBounds],
214     NSViewAnimationEffectKey : NSViewAnimationFadeInEffect
215   };
216   DCHECK(!boundsAnimation_);
217   boundsAnimation_.reset([[NSViewAnimation alloc]
218       initWithViewAnimations:[NSArray arrayWithObject:animationDict]]);
219   [boundsAnimation_ setDuration:[popupCollection_ popupAnimationDuration]];
220   [boundsAnimation_ setDelegate:self];
221   [boundsAnimation_ startAnimation];
222 }
223
224 - (void)closeWithAnimation {
225   if (isClosing_)
226     return;
227
228   isClosing_ = YES;
229
230   // If the notification was swiped closed, do not animate it as the
231   // notification has already faded out.
232   if (swipeGestureEnded_) {
233     [self close];
234     return;
235   }
236
237   NSDictionary* animationDict = @{
238     NSViewAnimationTargetKey : [self window],
239     NSViewAnimationEffectKey : NSViewAnimationFadeOutEffect
240   };
241   DCHECK(!boundsAnimation_);
242   boundsAnimation_.reset([[NSViewAnimation alloc]
243       initWithViewAnimations:[NSArray arrayWithObject:animationDict]]);
244   [boundsAnimation_ setDuration:[popupCollection_ popupAnimationDuration]];
245   [boundsAnimation_ setDelegate:self];
246   [boundsAnimation_ startAnimation];
247 }
248
249 - (void)markPopupCollectionGone {
250   popupCollection_ = nil;
251 }
252
253 - (NSRect)bounds {
254   return bounds_;
255 }
256
257 - (void)setBounds:(NSRect)newBounds {
258   if (isClosing_ || NSEqualRects(bounds_ , newBounds))
259     return;
260   bounds_ = newBounds;
261
262   NSDictionary* animationDict = @{
263     NSViewAnimationTargetKey :   [self window],
264     NSViewAnimationEndFrameKey : [NSValue valueWithRect:newBounds]
265   };
266   DCHECK(!boundsAnimation_);
267   boundsAnimation_.reset([[NSViewAnimation alloc]
268       initWithViewAnimations:[NSArray arrayWithObject:animationDict]]);
269   [boundsAnimation_ setDuration:[popupCollection_ popupAnimationDuration]];
270   [boundsAnimation_ setDelegate:self];
271   [boundsAnimation_ startAnimation];
272 }
273
274 - (void)mouseEntered:(NSEvent*)event {
275   messageCenter_->PausePopupTimers();
276 }
277
278 - (void)mouseExited:(NSEvent*)event {
279   messageCenter_->RestartPopupTimers();
280 }
281
282 @end