- add sources.
[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 #if !defined(MAC_OS_X_VERSION_10_7) || \
17     MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_7
18 enum {
19   NSWindowCollectionBehaviorFullScreenAuxiliary = 1 << 8
20 };
21 #endif  // MAC_OS_X_VERSION_10_7
22
23 ////////////////////////////////////////////////////////////////////////////////
24
25 @interface MCPopupController (Private)
26 - (void)notificationSwipeStarted;
27 - (void)notificationSwipeMoved:(CGFloat)amount;
28 - (void)notificationSwipeEnded:(BOOL)ended complete:(BOOL)isComplete;
29 @end
30
31 // Window Subclass /////////////////////////////////////////////////////////////
32
33 @interface MCPopupWindow : NSPanel {
34   // The cumulative X and Y scrollingDeltas since the -scrollWheel: event began.
35   NSPoint totalScrollDelta_;
36 }
37 @end
38
39 @implementation MCPopupWindow
40
41 - (void)scrollWheel:(NSEvent*)event {
42   // Gesture swiping only exists on 10.7+.
43   if (![event respondsToSelector:@selector(phase)])
44     return;
45
46   NSEventPhase phase = [event phase];
47   BOOL shouldTrackSwipe = NO;
48
49   if (phase == NSEventPhaseBegan) {
50     totalScrollDelta_ = NSZeroPoint;
51   } else if (phase == NSEventPhaseChanged) {
52     shouldTrackSwipe = YES;
53     totalScrollDelta_.x += [event scrollingDeltaX];
54     totalScrollDelta_.y += [event scrollingDeltaY];
55   }
56
57   // Only allow horizontal scrolling.
58   if (std::abs(totalScrollDelta_.x) < std::abs(totalScrollDelta_.y))
59     return;
60
61   if (shouldTrackSwipe) {
62     MCPopupController* controller =
63         base::mac::ObjCCastStrict<MCPopupController>([self windowController]);
64     BOOL directionInverted = [event isDirectionInvertedFromDevice];
65
66     auto handler = ^(CGFloat gestureAmount, NSEventPhase phase,
67                      BOOL isComplete, BOOL* stop) {
68         // The swipe direction should match the direction the user's fingers
69         // are moving, not the interpreted scroll direction.
70         if (directionInverted)
71           gestureAmount *= -1;
72
73         if (phase == NSEventPhaseBegan) {
74           [controller notificationSwipeStarted];
75           return;
76         }
77
78         [controller notificationSwipeMoved:gestureAmount];
79
80         BOOL ended = phase == NSEventPhaseEnded;
81         if (ended || isComplete)
82           [controller notificationSwipeEnded:ended complete:isComplete];
83     };
84     [event trackSwipeEventWithOptions:NSEventSwipeTrackingLockDirection
85              dampenAmountThresholdMin:-1
86                                   max:1
87                          usingHandler:handler];
88   }
89 }
90
91 @end
92
93 ////////////////////////////////////////////////////////////////////////////////
94
95 @implementation MCPopupController
96
97 - (id)initWithNotification:(const message_center::Notification*)notification
98              messageCenter:(message_center::MessageCenter*)messageCenter
99            popupCollection:(MCPopupCollection*)popupCollection {
100   base::scoped_nsobject<MCPopupWindow> window(
101       [[MCPopupWindow alloc] initWithContentRect:ui::kWindowSizeDeterminedLater
102                                        styleMask:NSBorderlessWindowMask |
103                                                  NSNonactivatingPanelMask
104                                          backing:NSBackingStoreBuffered
105                                            defer:YES]);
106   if ((self = [super initWithWindow:window])) {
107     messageCenter_ = messageCenter;
108     popupCollection_ = popupCollection;
109     notificationController_.reset(
110         [[MCNotificationController alloc] initWithNotification:notification
111                                                  messageCenter:messageCenter_]);
112     isClosing_ = NO;
113     bounds_ = [[notificationController_ view] frame];
114
115     [window setReleasedWhenClosed:NO];
116
117     [window setLevel:NSFloatingWindowLevel];
118     [window setExcludedFromWindowsMenu:YES];
119     [window setCollectionBehavior:
120         NSWindowCollectionBehaviorIgnoresCycle |
121         NSWindowCollectionBehaviorFullScreenAuxiliary];
122
123     [window setHasShadow:YES];
124     [window setContentView:[notificationController_ view]];
125
126     trackingArea_.reset(
127         [[CrTrackingArea alloc] initWithRect:NSZeroRect
128                                      options:NSTrackingInVisibleRect |
129                                              NSTrackingMouseEnteredAndExited |
130                                              NSTrackingActiveAlways
131                                        owner:self
132                                     userInfo:nil]);
133     [[window contentView] addTrackingArea:trackingArea_.get()];
134   }
135   return self;
136 }
137
138 - (void)close {
139   if (boundsAnimation_) {
140     [boundsAnimation_ stopAnimation];
141     [boundsAnimation_ setDelegate:nil];
142     boundsAnimation_.reset();
143   }
144   if (trackingArea_.get())
145     [[[self window] contentView] removeTrackingArea:trackingArea_.get()];
146   [super close];
147   [self performSelectorOnMainThread:@selector(release)
148                          withObject:nil
149                       waitUntilDone:NO
150                               modes:@[ NSDefaultRunLoopMode ]];
151 }
152
153 - (MCNotificationController*)notificationController {
154   return notificationController_.get();
155 }
156
157 - (const message_center::Notification*)notification {
158   return [notificationController_ notification];
159 }
160
161 - (const std::string&)notificationID {
162   return [notificationController_ notificationID];
163 }
164
165 // Private /////////////////////////////////////////////////////////////////////
166
167 - (void)notificationSwipeStarted {
168   originalFrame_ = [[self window] frame];
169   swipeGestureEnded_ = NO;
170 }
171
172 - (void)notificationSwipeMoved:(CGFloat)amount {
173   NSWindow* window = [self window];
174
175   [window setAlphaValue:1.0 - std::abs(amount)];
176   NSRect frame = [window frame];
177   CGFloat originalMin = NSMinX(originalFrame_);
178   frame.origin.x = originalMin + (NSMidX(originalFrame_) - originalMin) *
179                    -amount;
180   [window setFrame:frame display:YES];
181 }
182
183 - (void)notificationSwipeEnded:(BOOL)ended complete:(BOOL)isComplete {
184   swipeGestureEnded_ |= ended;
185   if (swipeGestureEnded_ && isComplete) {
186     messageCenter_->RemoveNotification([self notificationID], /*by_user=*/true);
187     [popupCollection_ onPopupAnimationEnded:[self notificationID]];
188   }
189 }
190
191 - (void)animationDidEnd:(NSAnimation*)animation {
192   if (animation != boundsAnimation_.get())
193     return;
194   boundsAnimation_.reset();
195
196   [popupCollection_ onPopupAnimationEnded:[self notificationID]];
197
198   if (isClosing_)
199     [self close];
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