]> git.sesse.net Git - vlc/blob - modules/gui/macosx/Windows.m
macosx: use custom animation for native fullscreen to continue video in animation
[vlc] / modules / gui / macosx / Windows.m
1 /*****************************************************************************
2  * Windows.m: MacOS X interface module
3  *****************************************************************************
4  * Copyright (C) 2012-2014 VLC authors and VideoLAN
5  * $Id$
6  *
7  * Authors: Felix Paul Kühne <fkuehne -at- videolan -dot- org>
8  *          David Fuhrmann <david dot fuhrmann at googlemail dot com>
9  *
10  * This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 2 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
23  *****************************************************************************/
24
25 #import "Windows.h"
26 #import "intf.h"
27 #import "CoreInteraction.h"
28 #import "ControlsBar.h"
29 #import "VideoView.h"
30 #import "CompatibilityFixes.h"
31
32 /*****************************************************************************
33  * VLCWindow
34  *
35  *  Missing extension to NSWindow
36  *****************************************************************************/
37
38 @implementation VLCWindow
39
40 @synthesize hasActiveVideo=b_has_active_video;
41 @synthesize fullscreen=b_fullscreen;
42
43 - (id)initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)styleMask
44                   backing:(NSBackingStoreType)backingType defer:(BOOL)flag
45 {
46     self = [super initWithContentRect:contentRect styleMask:styleMask backing:backingType defer:flag];
47     if (self) {
48         /* we don't want this window to be restored on relaunch */
49         if (!OSX_SNOW_LEOPARD)
50             [self setRestorable:NO];
51     }
52     return self;
53 }
54
55 - (void)setCanBecomeKeyWindow: (BOOL)canBecomeKey
56 {
57     b_isset_canBecomeKeyWindow = YES;
58     b_canBecomeKeyWindow = canBecomeKey;
59 }
60
61 - (BOOL)canBecomeKeyWindow
62 {
63     if (b_isset_canBecomeKeyWindow)
64         return b_canBecomeKeyWindow;
65
66     return [super canBecomeKeyWindow];
67 }
68
69 - (void)setCanBecomeMainWindow: (BOOL)canBecomeMain
70 {
71     b_isset_canBecomeMainWindow = YES;
72     b_canBecomeMainWindow = canBecomeMain;
73 }
74
75 - (BOOL)canBecomeMainWindow
76 {
77     if (b_isset_canBecomeMainWindow)
78         return b_canBecomeMainWindow;
79
80     return [super canBecomeMainWindow];
81 }
82
83 - (void)closeAndAnimate: (BOOL)animate
84 {
85     NSInvocation *invoc;
86
87     if (!animate) {
88         [super close];
89         return;
90     }
91
92     // TODO this callback stuff does not work and is not needed
93     invoc = [[[NSInvocation alloc] init] autorelease];
94     [invoc setSelector:@selector(close)];
95     [invoc setTarget: self];
96
97     if (![self isVisible] || [self alphaValue] == 0.0) {
98         [super close];
99         return;
100     }
101
102     [self orderOut: self animate: YES callback: invoc];
103 }
104
105 - (void)orderOut: (id)sender animate: (BOOL)animate
106 {
107     NSInvocation *invoc = [[[NSInvocation alloc] init] autorelease];
108     [invoc setSelector:@selector(orderOut:)];
109     [invoc setTarget: self];
110     [invoc setArgument: sender atIndex: 2];
111     [self orderOut: sender animate: animate callback: invoc];
112 }
113
114 - (void)orderOut: (id)sender animate: (BOOL)animate callback:(NSInvocation *)callback
115 {
116     NSViewAnimation *anim;
117     NSViewAnimation *current_anim;
118     NSMutableDictionary *dict;
119
120     if (!animate) {
121         [self orderOut: sender];
122         return;
123     }
124
125     dict = [[NSMutableDictionary alloc] initWithCapacity:2];
126
127     [dict setObject:self forKey:NSViewAnimationTargetKey];
128
129     [dict setObject:NSViewAnimationFadeOutEffect forKey:NSViewAnimationEffectKey];
130     anim = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObject:dict]];
131     [dict release];
132
133     [anim setAnimationBlockingMode:NSAnimationNonblocking];
134     [anim setDuration:0.9];
135     [anim setFrameRate:30];
136     [anim setUserInfo:callback];
137     [anim setDelegate:self];
138
139     @synchronized(self) {
140         current_anim = self->o_current_animation;
141
142         if ([[[current_anim viewAnimations] objectAtIndex:0] objectForKey: NSViewAnimationEffectKey] == NSViewAnimationFadeOutEffect && [current_anim isAnimating]) {
143             [anim release];
144         } else {
145             if (current_anim) {
146                 [current_anim stopAnimation];
147                 [anim setCurrentProgress:1.0 - [current_anim currentProgress]];
148                 [current_anim release];
149             }
150             else
151                 [anim setCurrentProgress:1.0 - [self alphaValue]];
152             self->o_current_animation = anim;
153             [anim startAnimation];
154         }
155     }
156 }
157
158 - (void)orderFront: (id)sender animate: (BOOL)animate
159 {
160     NSViewAnimation *anim;
161     NSViewAnimation *current_anim;
162     NSMutableDictionary *dict;
163
164     if (!animate) {
165         [super orderFront: sender];
166         [self setAlphaValue: 1.0];
167         return;
168     }
169
170     if (![self isVisible]) {
171         [self setAlphaValue: 0.0];
172         [super orderFront: sender];
173     }
174     else if ([self alphaValue] == 1.0) {
175         [super orderFront: self];
176         return;
177     }
178
179     dict = [[NSMutableDictionary alloc] initWithCapacity:2];
180
181     [dict setObject:self forKey:NSViewAnimationTargetKey];
182
183     [dict setObject:NSViewAnimationFadeInEffect forKey:NSViewAnimationEffectKey];
184     anim = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObject:dict]];
185     [dict release];
186
187     [anim setAnimationBlockingMode:NSAnimationNonblocking];
188     [anim setDuration:0.5];
189     [anim setFrameRate:30];
190     [anim setDelegate:self];
191
192     @synchronized(self) {
193         current_anim = self->o_current_animation;
194
195         if ([[[current_anim viewAnimations] objectAtIndex:0] objectForKey: NSViewAnimationEffectKey] == NSViewAnimationFadeInEffect && [current_anim isAnimating]) {
196             [anim release];
197         } else {
198             if (current_anim) {
199                 [current_anim stopAnimation];
200                 [anim setCurrentProgress:1.0 - [current_anim currentProgress]];
201                 [current_anim release];
202             }
203             else
204                 [anim setCurrentProgress:[self alphaValue]];
205             self->o_current_animation = anim;
206             [self orderFront: sender];
207             [anim startAnimation];
208         }
209     }
210 }
211
212 - (void)animationDidEnd:(NSAnimation*)anim
213 {
214     if ([self alphaValue] <= 0.0) {
215         NSInvocation * invoc;
216         [super orderOut: nil];
217         [self setAlphaValue: 1.0];
218         if ((invoc = [anim userInfo])) {
219             [invoc invoke];
220         }
221     }
222 }
223
224 - (VLCVoutView *)videoView
225 {
226     if ([[self contentView] class] == [VLCVoutView class])
227         return (VLCVoutView *)[self contentView];
228
229     return nil;
230 }
231
232
233 @end
234
235
236 /*****************************************************************************
237  * VLCVideoWindowCommon
238  *
239  *  Common code for main window, detached window and extra video window
240  *****************************************************************************/
241
242 @interface VLCVideoWindowCommon (Internal)
243 - (void)customZoom:(id)sender;
244 - (void)hasBecomeFullscreen;
245 - (void)leaveFullscreenAndFadeOut:(BOOL)fadeout;
246 - (void)hasEndedFullscreen;
247 @end
248
249 @implementation VLCVideoWindowCommon
250
251 @synthesize videoView=o_video_view;
252 @synthesize controlsBar=o_controls_bar;
253 @synthesize inFullscreenTransition=b_in_fullscreen_transition;
254
255 #pragma mark -
256 #pragma mark Init
257
258 - (id)initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)styleMask
259                   backing:(NSBackingStoreType)backingType defer:(BOOL)flag
260 {
261     b_dark_interface = config_GetInt(VLCIntf, "macosx-interfacestyle");
262
263     if (b_dark_interface) {
264         styleMask = NSBorderlessWindowMask;
265 #ifdef MAC_OS_X_VERSION_10_7
266         if (!OSX_SNOW_LEOPARD)
267             styleMask |= NSResizableWindowMask;
268 #endif
269     }
270
271     self = [super initWithContentRect:contentRect styleMask:styleMask
272                               backing:backingType defer:flag];
273
274     /* we want to be moveable regardless of our style */
275     [self setMovableByWindowBackground: YES];
276     [self setCanBecomeKeyWindow:YES];
277
278     o_temp_view = [[NSView alloc] init];
279     [o_temp_view setAutoresizingMask:NSViewHeightSizable | NSViewWidthSizable];
280
281     return self;
282 }
283
284 - (void)dealloc
285 {
286     [o_temp_view release];
287     [super dealloc];
288 }
289
290 - (void)awakeFromNib
291 {
292     BOOL b_nativeFullscreenMode = NO;
293 #ifdef MAC_OS_X_VERSION_10_7
294     if (!OSX_SNOW_LEOPARD)
295         b_nativeFullscreenMode = var_InheritBool(VLCIntf, "macosx-nativefullscreenmode");
296 #endif
297
298     if (b_nativeFullscreenMode) {
299         [self setCollectionBehavior: NSWindowCollectionBehaviorFullScreenPrimary];
300     } else {
301         [o_titlebar_view setFullscreenButtonHidden: YES];
302     }
303
304     [super awakeFromNib];
305 }
306
307 - (void)setTitle:(NSString *)title
308 {
309     if (!title || [title length] < 1)
310         return;
311
312     if (b_dark_interface && o_titlebar_view)
313         [o_titlebar_view setWindowTitle: title];
314
315     [super setTitle: title];
316 }
317
318 #pragma mark -
319 #pragma mark zoom / minimize / close
320
321 - (BOOL)validateMenuItem:(NSMenuItem *)menuItem
322 {
323     SEL s_menuAction = [menuItem action];
324
325     if ((s_menuAction == @selector(performClose:)) || (s_menuAction == @selector(performMiniaturize:)) || (s_menuAction == @selector(performZoom:)))
326         return YES;
327
328     return [super validateMenuItem:menuItem];
329 }
330
331 - (BOOL)windowShouldClose:(id)sender
332 {
333     return YES;
334 }
335
336 - (void)performClose:(id)sender
337 {
338     if (!([self styleMask] & NSTitledWindowMask)) {
339         [[NSNotificationCenter defaultCenter] postNotificationName:NSWindowWillCloseNotification object:self];
340
341         [self close];
342     } else
343         [super performClose: sender];
344 }
345
346 - (void)performMiniaturize:(id)sender
347 {
348     if (!([self styleMask] & NSTitledWindowMask))
349         [self miniaturize: sender];
350     else
351         [super performMiniaturize: sender];
352 }
353
354 - (void)performZoom:(id)sender
355 {
356     if (!([self styleMask] & NSTitledWindowMask))
357         [self customZoom: sender];
358     else
359         [super performZoom: sender];
360 }
361
362 - (void)zoom:(id)sender
363 {
364     if (!([self styleMask] & NSTitledWindowMask))
365         [self customZoom: sender];
366     else
367         [super zoom: sender];
368 }
369
370 /**
371  * Given a proposed frame rectangle, return a modified version
372  * which will fit inside the screen.
373  *
374  * This method is based upon NSWindow.m, part of the GNUstep GUI Library, licensed under LGPLv2+.
375  *    Authors:  Scott Christley <scottc@net-community.com>, Venkat Ajjanagadde <venkat@ocbi.com>,
376  *              Felipe A. Rodriguez <far@ix.netcom.com>, Richard Frith-Macdonald <richard@brainstorm.co.uk>
377  *    Copyright (C) 1996 Free Software Foundation, Inc.
378  */
379 - (NSRect) customConstrainFrameRect: (NSRect)frameRect toScreen: (NSScreen*)screen
380 {
381     NSRect screenRect = [screen visibleFrame];
382     float difference;
383
384     /* Move top edge of the window inside the screen */
385     difference = NSMaxY (frameRect) - NSMaxY (screenRect);
386     if (difference > 0) {
387         frameRect.origin.y -= difference;
388     }
389
390     /* If the window is resizable, resize it (if needed) so that the
391      bottom edge is on the screen or can be on the screen when the user moves
392      the window */
393     difference = NSMaxY (screenRect) - NSMaxY (frameRect);
394     if (_styleMask & NSResizableWindowMask) {
395         float difference2;
396
397         difference2 = screenRect.origin.y - frameRect.origin.y;
398         difference2 -= difference;
399         // Take in account the space between the top of window and the top of the
400         // screen which can be used to move the bottom of the window on the screen
401         if (difference2 > 0) {
402             frameRect.size.height -= difference2;
403             frameRect.origin.y += difference2;
404         }
405
406         /* Ensure that resizing doesn't makewindow smaller than minimum */
407         difference2 = [self minSize].height - frameRect.size.height;
408         if (difference2 > 0) {
409             frameRect.size.height += difference2;
410             frameRect.origin.y -= difference2;
411         }
412     }
413
414     return frameRect;
415 }
416
417 #define DIST 3
418
419 /**
420  Zooms the receiver.   This method calls the delegate method
421  windowShouldZoom:toFrame: to determine if the window should
422  be allowed to zoom to full screen.
423  *
424  * This method is based upon NSWindow.m, part of the GNUstep GUI Library, licensed under LGPLv2+.
425  *    Authors:  Scott Christley <scottc@net-community.com>, Venkat Ajjanagadde <venkat@ocbi.com>,
426  *              Felipe A. Rodriguez <far@ix.netcom.com>, Richard Frith-Macdonald <richard@brainstorm.co.uk>
427  *    Copyright (C) 1996 Free Software Foundation, Inc.
428  */
429 - (void) customZoom: (id)sender
430 {
431     NSRect maxRect = [[self screen] visibleFrame];
432     NSRect currentFrame = [self frame];
433
434     if ([[self delegate] respondsToSelector: @selector(windowWillUseStandardFrame:defaultFrame:)]) {
435         maxRect = [[self delegate] windowWillUseStandardFrame: self defaultFrame: maxRect];
436     }
437
438     maxRect = [self customConstrainFrameRect: maxRect toScreen: [self screen]];
439
440     // Compare the new frame with the current one
441     if ((abs(NSMaxX(maxRect) - NSMaxX(currentFrame)) < DIST)
442         && (abs(NSMaxY(maxRect) - NSMaxY(currentFrame)) < DIST)
443         && (abs(NSMinX(maxRect) - NSMinX(currentFrame)) < DIST)
444         && (abs(NSMinY(maxRect) - NSMinY(currentFrame)) < DIST)) {
445         // Already in zoomed mode, reset user frame, if stored
446         if ([self frameAutosaveName] != nil) {
447             [self setFrame: previousSavedFrame display: YES animate: YES];
448             [self saveFrameUsingName: [self frameAutosaveName]];
449         }
450         return;
451     }
452
453     if ([self frameAutosaveName] != nil) {
454         [self saveFrameUsingName: [self frameAutosaveName]];
455         previousSavedFrame = [self frame];
456     }
457
458     [self setFrame: maxRect display: YES animate: YES];
459 }
460
461 #pragma mark -
462 #pragma mark Video window resizing logic
463
464 - (void)setWindowLevel:(NSInteger)i_state
465 {
466     if (var_InheritBool(VLCIntf, "video-wallpaper") || [self level] < NSNormalWindowLevel)
467         return;
468
469     if (!b_fullscreen && !b_in_fullscreen_transition)
470         [self setLevel: i_state];
471
472     // save it for restore if window is currently minimized or in fullscreen
473     i_originalLevel = i_state;
474 }
475
476 - (NSRect)getWindowRectForProposedVideoViewSize:(NSSize)size
477 {
478     NSSize windowMinSize = [self minSize];
479     NSRect screenFrame = [[self screen] visibleFrame];
480
481     NSPoint topleftbase = NSMakePoint(0, [self frame].size.height);
482     NSPoint topleftscreen = [self convertBaseToScreen: topleftbase];
483
484     unsigned int i_width = size.width;
485     unsigned int i_height = size.height;
486     if (i_width < windowMinSize.width)
487         i_width = windowMinSize.width;
488     if (i_height < f_min_video_height)
489         i_height = f_min_video_height;
490
491     /* Calculate the window's new size */
492     NSRect new_frame;
493     new_frame.size.width = [self frame].size.width - [o_video_view frame].size.width + i_width;
494     new_frame.size.height = [self frame].size.height - [o_video_view frame].size.height + i_height;
495     new_frame.origin.x = topleftscreen.x;
496     new_frame.origin.y = topleftscreen.y - new_frame.size.height;
497
498     /* make sure the window doesn't exceed the screen size the window is on */
499     if (new_frame.size.width > screenFrame.size.width) {
500         new_frame.size.width = screenFrame.size.width;
501         new_frame.origin.x = screenFrame.origin.x;
502     }
503     if (new_frame.size.height > screenFrame.size.height) {
504         new_frame.size.height = screenFrame.size.height;
505         new_frame.origin.y = screenFrame.origin.y;
506     }
507     if (new_frame.origin.y < screenFrame.origin.y)
508         new_frame.origin.y = screenFrame.origin.y;
509
510     CGFloat right_screen_point = screenFrame.origin.x + screenFrame.size.width;
511     CGFloat right_window_point = new_frame.origin.x + new_frame.size.width;
512     if (right_window_point > right_screen_point)
513         new_frame.origin.x -= (right_window_point - right_screen_point);
514
515     return new_frame;
516 }
517
518 - (void)resizeWindow
519 {
520     // VOUT_WINDOW_SET_SIZE is triggered when exiting fullscreen. This event is ignored here
521     // to avoid interference with the animation.
522     if ([self fullscreen] || b_in_fullscreen_transition)
523         return;
524
525     NSRect window_rect = [self getWindowRectForProposedVideoViewSize:nativeVideoSize];
526     [[self animator] setFrame:window_rect display:YES];
527 }
528
529 - (void)setNativeVideoSize:(NSSize)size
530 {
531     nativeVideoSize = size;
532
533     if (var_InheritBool(VLCIntf, "macosx-video-autoresize") && !var_InheritBool(VLCIntf, "video-wallpaper"))
534         [self resizeWindow];
535 }
536
537 - (NSSize)windowWillResize:(NSWindow *)window toSize:(NSSize)proposedFrameSize
538 {
539     if (![[VLCMain sharedInstance] activeVideoPlayback] || nativeVideoSize.width == 0. || nativeVideoSize.height == 0. || window != self)
540         return proposedFrameSize;
541
542     // needed when entering lion fullscreen mode
543     if (b_in_fullscreen_transition || [self fullscreen])
544         return proposedFrameSize;
545
546     if ([[VLCCoreInteraction sharedInstance] aspectRatioIsLocked]) {
547         NSRect videoWindowFrame = [self frame];
548         NSRect viewRect = [o_video_view convertRect:[o_video_view bounds] toView: nil];
549         NSRect contentRect = [self contentRectForFrameRect:videoWindowFrame];
550         float marginy = viewRect.origin.y + videoWindowFrame.size.height - contentRect.size.height;
551         float marginx = contentRect.size.width - viewRect.size.width;
552         if (o_titlebar_view && b_dark_interface)
553             marginy += [o_titlebar_view frame].size.height;
554
555         proposedFrameSize.height = (proposedFrameSize.width - marginx) * nativeVideoSize.height / nativeVideoSize.width + marginy;
556     }
557
558     return proposedFrameSize;
559 }
560
561 - (void)windowWillMiniaturize:(NSNotification *)notification
562 {
563     // Set level to normal as a workaround for Mavericks bug causing window
564     // to vanish from screen, see radar://15473716
565     i_originalLevel = [self level];
566     [self setLevel: NSNormalWindowLevel];
567 }
568
569 - (void)windowDidDeminiaturize:(NSNotification *)notification
570 {
571     [self setLevel: i_originalLevel];
572 }
573
574 #pragma mark -
575 #pragma mark Mouse cursor handling
576
577 //  NSTimer selectors require this function signature as per Apple's docs
578 - (void)hideMouseCursor:(NSTimer *)timer
579 {
580     [NSCursor setHiddenUntilMouseMoves: YES];
581 }
582
583 - (void)recreateHideMouseTimer
584 {
585     if (t_hide_mouse_timer != nil) {
586         [t_hide_mouse_timer invalidate];
587         [t_hide_mouse_timer release];
588     }
589
590     t_hide_mouse_timer = [NSTimer scheduledTimerWithTimeInterval:2
591                                                           target:self
592                                                         selector:@selector(hideMouseCursor:)
593                                                         userInfo:nil
594                                                          repeats:NO];
595     [t_hide_mouse_timer retain];
596 }
597
598 //  Called automatically if window's acceptsMouseMovedEvents property is true
599 - (void)mouseMoved:(NSEvent *)theEvent
600 {
601     if (b_fullscreen)
602         [self recreateHideMouseTimer];
603
604     [super mouseMoved: theEvent];
605 }
606
607 #pragma mark -
608 #pragma mark Lion native fullscreen handling
609
610 - (void)becomeKeyWindow
611 {
612     [super becomeKeyWindow];
613
614     // change fspanel state for the case when multiple windows are in fullscreen
615     if ([self hasActiveVideo] && [self fullscreen])
616         [[[VLCMainWindow sharedInstance] fsPanel] setActive:nil];
617     else
618         [[[VLCMainWindow sharedInstance] fsPanel] setNonActive:nil];
619 }
620
621 - (void)resignKeyWindow
622 {
623     [super resignKeyWindow];
624
625     [[[VLCMainWindow sharedInstance] fsPanel] setNonActive:nil];
626 }
627
628 -(NSArray*)customWindowsToEnterFullScreenForWindow:(NSWindow *)window
629 {
630     if (window == self) {
631         return [NSArray arrayWithObject:window];
632     }
633
634     return nil;
635 }
636
637 - (NSArray*)customWindowsToExitFullScreenForWindow:(NSWindow*)window
638 {
639     if (window == self) {
640         return [NSArray arrayWithObject:window];
641     }
642
643     return nil;
644 }
645
646 - (void)window:window startCustomAnimationToEnterFullScreenWithDuration:(NSTimeInterval)duration
647 {
648     [window setStyleMask:([window styleMask] | NSFullScreenWindowMask)];
649
650     NSScreen *screen = [window screen];
651     NSRect screenFrame = [screen frame];
652
653     [NSAnimationContext runAnimationGroup:^(NSAnimationContext *context) {
654         [context setDuration:0.5 * duration];
655         [[window animator] setFrame:screenFrame display:YES];
656     } completionHandler:nil];
657 }
658
659 - (void)window:window startCustomAnimationToExitFullScreenWithDuration:(NSTimeInterval)duration
660 {
661     [window setStyleMask:([window styleMask] & ~NSFullScreenWindowMask)];
662     [[window animator] setFrame:frameBeforeLionFullscreen display:YES animate:YES];
663
664     [NSAnimationContext runAnimationGroup:^(NSAnimationContext *context) {
665         [context setDuration:0.5 * duration];
666         [[window animator] setFrame:frameBeforeLionFullscreen display:YES animate:YES];
667     } completionHandler:nil];
668 }
669
670 - (void)windowWillEnterFullScreen:(NSNotification *)notification
671 {
672     // workaround, see #6668
673     [NSApp setPresentationOptions:(NSApplicationPresentationFullScreen | NSApplicationPresentationAutoHideDock | NSApplicationPresentationAutoHideMenuBar)];
674
675     i_originalLevel = [self level];
676     // b_fullscreen and b_in_fullscreen_transition must not be true yet
677     [[[VLCMain sharedInstance] voutController] updateWindowLevelForHelperWindows: NSNormalWindowLevel];
678     [self setLevel:NSNormalWindowLevel];
679
680     b_in_fullscreen_transition = YES;
681
682     var_SetBool(pl_Get(VLCIntf), "fullscreen", true);
683
684     frameBeforeLionFullscreen = [self frame];
685
686     if ([self hasActiveVideo]) {
687         vout_thread_t *p_vout = getVoutForActiveWindow();
688         if (p_vout) {
689             var_SetBool(p_vout, "fullscreen", true);
690             vlc_object_release(p_vout);
691         }
692     }
693
694     if ([self hasActiveVideo])
695         [[VLCMainWindow sharedInstance] recreateHideMouseTimer];
696
697     if (b_dark_interface) {
698         [o_titlebar_view removeFromSuperviewWithoutNeedingDisplay];
699
700         NSRect winrect;
701         CGFloat f_titleBarHeight = [o_titlebar_view frame].size.height;
702         winrect = [self frame];
703
704         winrect.size.height = winrect.size.height - f_titleBarHeight;
705         [self setFrame: winrect display:NO animate:NO];
706     }
707
708     [o_video_view setFrame: [[self contentView] frame]];
709     if (![o_video_view isHidden]) {
710         [[o_controls_bar bottomBarView] setHidden: YES];
711     }
712
713     [self setMovableByWindowBackground: NO];
714 }
715
716 - (void)windowDidEnterFullScreen:(NSNotification *)notification
717 {
718     // Indeed, we somehow can have an "inactive" fullscreen (but a visible window!).
719     // But this creates some problems when leaving fs over remote intfs, so activate app here.
720     [NSApp activateIgnoringOtherApps:YES];
721
722     [self setFullscreen: YES];
723     b_in_fullscreen_transition = NO;
724
725     if ([self hasActiveVideo]) {
726         [[[VLCMainWindow sharedInstance] fsPanel] setVoutWasUpdated: self];
727         if (![o_video_view isHidden])
728             [[[VLCMainWindow sharedInstance] fsPanel] setActive: nil];
729     }
730
731     NSArray *subviews = [[self videoView] subviews];
732     NSUInteger count = [subviews count];
733
734     for (NSUInteger x = 0; x < count; x++) {
735         if ([[subviews objectAtIndex:x] respondsToSelector:@selector(reshape)])
736             [[subviews objectAtIndex:x] reshape];
737     }
738
739 }
740
741 - (void)windowWillExitFullScreen:(NSNotification *)notification
742 {
743     b_in_fullscreen_transition = YES;
744     [self setFullscreen: NO];
745
746     var_SetBool(pl_Get(VLCIntf), "fullscreen", false);
747
748     if ([self hasActiveVideo]) {
749         vout_thread_t *p_vout = getVoutForActiveWindow();
750         if (p_vout) {
751             var_SetBool(p_vout, "fullscreen", false);
752             vlc_object_release(p_vout);
753         }
754     }
755
756     [NSCursor setHiddenUntilMouseMoves: NO];
757     [[[VLCMainWindow sharedInstance] fsPanel] setNonActive: nil];
758
759
760     if (b_dark_interface) {
761         NSRect winrect;
762         CGFloat f_titleBarHeight = [o_titlebar_view frame].size.height;
763
764         winrect = [o_video_view frame];
765         winrect.size.height -= f_titleBarHeight;
766         [o_video_view setFrame: winrect];
767
768         winrect = [self frame];
769         [o_titlebar_view setFrame: NSMakeRect(0, winrect.size.height - f_titleBarHeight,
770                                               winrect.size.width, f_titleBarHeight)];
771         [[self contentView] addSubview: o_titlebar_view];
772
773         winrect.size.height = winrect.size.height + f_titleBarHeight;
774         [self setFrame: winrect display:NO animate:NO];
775     }
776
777     NSRect videoViewFrame = [o_video_view frame];
778     videoViewFrame.origin.y += [o_controls_bar height];
779     videoViewFrame.size.height -= [o_controls_bar height];
780     [o_video_view setFrame: videoViewFrame];
781
782     if (![o_video_view isHidden]) {
783         [[o_controls_bar bottomBarView] setHidden: NO];
784     }
785
786     [self setMovableByWindowBackground: YES];
787 }
788
789 - (void)windowDidExitFullScreen:(NSNotification *)notification
790 {
791     b_in_fullscreen_transition = NO;
792
793     [[[VLCMain sharedInstance] voutController] updateWindowLevelForHelperWindows: i_originalLevel];
794     [self setLevel:i_originalLevel];
795 }
796
797 #pragma mark -
798 #pragma mark Fullscreen Logic
799
800 - (void)enterFullscreen
801 {
802     NSMutableDictionary *dict1, *dict2;
803     NSScreen *screen;
804     NSRect screen_rect;
805     NSRect rect;
806     BOOL blackout_other_displays = var_InheritBool(VLCIntf, "macosx-black");
807
808     screen = [NSScreen screenWithDisplayID:(CGDirectDisplayID)var_InheritInteger(VLCIntf, "macosx-vdev")];
809
810     if (!screen) {
811         msg_Dbg(VLCIntf, "chosen screen isn't present, using current screen for fullscreen mode");
812         screen = [self screen];
813     }
814     if (!screen) {
815         msg_Dbg(VLCIntf, "Using deepest screen");
816         screen = [NSScreen deepestScreen];
817     }
818
819     screen_rect = [screen frame];
820
821     if (o_controls_bar)
822         [o_controls_bar setFullscreenState:YES];
823     [[[VLCMainWindow sharedInstance] controlsBar] setFullscreenState:YES];
824
825     [[VLCMainWindow sharedInstance] recreateHideMouseTimer];
826
827     if (blackout_other_displays)
828         [screen blackoutOtherScreens];
829
830     /* Make sure we don't see the window flashes in float-on-top mode */
831     i_originalLevel = [self level];
832     // b_fullscreen must not be true yet
833     [[[VLCMain sharedInstance] voutController] updateWindowLevelForHelperWindows: NSNormalWindowLevel];
834     [self setLevel:NSNormalWindowLevel];
835
836     /* Only create the o_fullscreen_window if we are not in the middle of the zooming animation */
837     if (!o_fullscreen_window) {
838         /* We can't change the styleMask of an already created NSWindow, so we create another window, and do eye catching stuff */
839
840         rect = [[o_video_view superview] convertRect: [o_video_view frame] toView: nil]; /* Convert to Window base coord */
841         rect.origin.x += [self frame].origin.x;
842         rect.origin.y += [self frame].origin.y;
843         o_fullscreen_window = [[VLCWindow alloc] initWithContentRect:rect styleMask: NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:YES];
844         [o_fullscreen_window setBackgroundColor: [NSColor blackColor]];
845         [o_fullscreen_window setCanBecomeKeyWindow: YES];
846         [o_fullscreen_window setCanBecomeMainWindow: YES];
847         [o_fullscreen_window setHasActiveVideo: YES];
848         [o_fullscreen_window setFullscreen: YES];
849
850         if (![self isVisible] || [self alphaValue] == 0.0) {
851             /* We don't animate if we are not visible, instead we
852              * simply fade the display */
853             CGDisplayFadeReservationToken token;
854
855             if (blackout_other_displays) {
856                 CGAcquireDisplayFadeReservation(kCGMaxDisplayReservationInterval, &token);
857                 CGDisplayFade(token, 0.5, kCGDisplayBlendNormal, kCGDisplayBlendSolidColor, 0, 0, 0, YES);
858             }
859
860             [screen setFullscreenPresentationOptions];
861
862             [o_video_view retain];
863             [[o_video_view superview] replaceSubview:o_video_view with:o_temp_view];
864             [o_temp_view setFrame:[o_video_view frame]];
865             [o_fullscreen_window setContentView:o_video_view];
866             [o_video_view release];
867
868             [o_fullscreen_window makeKeyAndOrderFront:self];
869             [o_fullscreen_window orderFront:self animate:YES];
870
871             [o_fullscreen_window setFrame:screen_rect display:YES animate:YES];
872             [o_fullscreen_window setLevel:NSNormalWindowLevel];
873
874             if (blackout_other_displays) {
875                 CGDisplayFade(token, 0.3, kCGDisplayBlendSolidColor, kCGDisplayBlendNormal, 0, 0, 0, NO);
876                 CGReleaseDisplayFadeReservation(token);
877             }
878
879             /* Will release the lock */
880             [self hasBecomeFullscreen];
881
882             return;
883         }
884
885         /* Make sure video view gets visible in case the playlist was visible before */
886         b_video_view_was_hidden = [o_video_view isHidden];
887         [o_video_view setHidden: NO];
888
889         /* Make sure we don't see the o_video_view disappearing of the screen during this operation */
890         NSDisableScreenUpdates();
891         [o_video_view retain];
892         [[o_video_view superview] replaceSubview:o_video_view with:o_temp_view];
893         [o_temp_view setFrame:[o_video_view frame]];
894         [o_fullscreen_window setContentView:o_video_view];
895         [o_video_view release];
896         [o_fullscreen_window makeKeyAndOrderFront:self];
897         NSEnableScreenUpdates();
898     }
899
900     /* We are in fullscreen (and no animation is running) */
901     if ([self fullscreen]) {
902         /* Make sure we are hidden */
903         [self orderOut: self];
904
905         return;
906     }
907
908     if (o_fullscreen_anim1) {
909         [o_fullscreen_anim1 stopAnimation];
910         [o_fullscreen_anim1 release];
911     }
912     if (o_fullscreen_anim2) {
913         [o_fullscreen_anim2 stopAnimation];
914         [o_fullscreen_anim2 release];
915     }
916
917     [screen setFullscreenPresentationOptions];
918
919     dict1 = [[NSMutableDictionary alloc] initWithCapacity:2];
920     dict2 = [[NSMutableDictionary alloc] initWithCapacity:3];
921
922     [dict1 setObject:self forKey:NSViewAnimationTargetKey];
923     [dict1 setObject:NSViewAnimationFadeOutEffect forKey:NSViewAnimationEffectKey];
924
925     [dict2 setObject:o_fullscreen_window forKey:NSViewAnimationTargetKey];
926     [dict2 setObject:[NSValue valueWithRect:[o_fullscreen_window frame]] forKey:NSViewAnimationStartFrameKey];
927     [dict2 setObject:[NSValue valueWithRect:screen_rect] forKey:NSViewAnimationEndFrameKey];
928
929     /* Strategy with NSAnimation allocation:
930      - Keep at most 2 animation at a time
931      - leaveFullscreen/enterFullscreen are the only responsible for releasing and alloc-ing
932      */
933     o_fullscreen_anim1 = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObject:dict1]];
934     o_fullscreen_anim2 = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObject:dict2]];
935
936     [dict1 release];
937     [dict2 release];
938
939     [o_fullscreen_anim1 setAnimationBlockingMode: NSAnimationNonblocking];
940     [o_fullscreen_anim1 setDuration: 0.3];
941     [o_fullscreen_anim1 setFrameRate: 30];
942     [o_fullscreen_anim2 setAnimationBlockingMode: NSAnimationNonblocking];
943     [o_fullscreen_anim2 setDuration: 0.2];
944     [o_fullscreen_anim2 setFrameRate: 30];
945
946     [o_fullscreen_anim2 setDelegate: self];
947     [o_fullscreen_anim2 startWhenAnimation: o_fullscreen_anim1 reachesProgress: 1.0];
948
949     [o_fullscreen_anim1 startAnimation];
950     /* fullscreenAnimation will be unlocked when animation ends */
951
952     b_in_fullscreen_transition = YES;
953 }
954
955 - (void)hasBecomeFullscreen
956 {
957     if ([[o_video_view subviews] count] > 0)
958         [o_fullscreen_window makeFirstResponder: [[o_video_view subviews] objectAtIndex:0]];
959
960     [o_fullscreen_window makeKeyWindow];
961     [o_fullscreen_window setAcceptsMouseMovedEvents: YES];
962
963     /* tell the fspanel to move itself to front next time it's triggered */
964     [[[VLCMainWindow sharedInstance] fsPanel] setVoutWasUpdated: o_fullscreen_window];
965     [[[VLCMainWindow sharedInstance] fsPanel] setActive: nil];
966
967     if ([self isVisible])
968         [self orderOut: self];
969
970     b_in_fullscreen_transition = NO;
971     [self setFullscreen:YES];
972 }
973
974 - (void)leaveFullscreen
975 {
976     [self leaveFullscreenAndFadeOut: NO];
977 }
978
979 - (void)leaveFullscreenAndFadeOut: (BOOL)fadeout
980 {
981     NSMutableDictionary *dict1, *dict2;
982     NSRect frame;
983     BOOL blackout_other_displays = var_InheritBool(VLCIntf, "macosx-black");
984
985     if (o_controls_bar)
986         [o_controls_bar setFullscreenState:NO];
987     [[[VLCMainWindow sharedInstance] controlsBar] setFullscreenState:NO];
988
989     /* We always try to do so */
990     [NSScreen unblackoutScreens];
991
992     [[o_video_view window] makeKeyAndOrderFront: nil];
993
994     /* Don't do anything if o_fullscreen_window is already closed */
995     if (!o_fullscreen_window) {
996         return;
997     }
998
999     if (fadeout) {
1000         /* We don't animate if we are not visible, instead we
1001          * simply fade the display */
1002         CGDisplayFadeReservationToken token;
1003
1004         if (blackout_other_displays) {
1005             CGAcquireDisplayFadeReservation(kCGMaxDisplayReservationInterval, &token);
1006             CGDisplayFade(token, 0.3, kCGDisplayBlendNormal, kCGDisplayBlendSolidColor, 0, 0, 0, YES);
1007         }
1008
1009         [[[VLCMainWindow sharedInstance] fsPanel] setNonActive: nil];
1010         [[o_fullscreen_window screen] setNonFullscreenPresentationOptions];
1011
1012         /* Will release the lock */
1013         [self hasEndedFullscreen];
1014
1015         /* Our window is hidden, and might be faded. We need to workaround that, so note it
1016          * here */
1017         b_window_is_invisible = YES;
1018
1019         if (blackout_other_displays) {
1020             CGDisplayFade(token, 0.5, kCGDisplayBlendSolidColor, kCGDisplayBlendNormal, 0, 0, 0, NO);
1021             CGReleaseDisplayFadeReservation(token);
1022         }
1023
1024         return;
1025     }
1026
1027     b_in_fullscreen_transition = YES;
1028
1029     [self setAlphaValue: 0.0];
1030     [self orderFront: self];
1031     [[o_video_view window] orderFront: self];
1032
1033     [[[VLCMainWindow sharedInstance] fsPanel] setNonActive: nil];
1034     [[o_fullscreen_window screen] setNonFullscreenPresentationOptions];
1035
1036     if (o_fullscreen_anim1) {
1037         [o_fullscreen_anim1 stopAnimation];
1038         [o_fullscreen_anim1 release];
1039     }
1040     if (o_fullscreen_anim2) {
1041         [o_fullscreen_anim2 stopAnimation];
1042         [o_fullscreen_anim2 release];
1043     }
1044
1045     frame = [[o_temp_view superview] convertRect: [o_temp_view frame] toView: nil]; /* Convert to Window base coord */
1046     frame.origin.x += [self frame].origin.x;
1047     frame.origin.y += [self frame].origin.y;
1048
1049     dict2 = [[NSMutableDictionary alloc] initWithCapacity:2];
1050     [dict2 setObject:self forKey:NSViewAnimationTargetKey];
1051     [dict2 setObject:NSViewAnimationFadeInEffect forKey:NSViewAnimationEffectKey];
1052
1053     o_fullscreen_anim2 = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObject:dict2]];
1054     [dict2 release];
1055
1056     [o_fullscreen_anim2 setAnimationBlockingMode: NSAnimationNonblocking];
1057     [o_fullscreen_anim2 setDuration: 0.3];
1058     [o_fullscreen_anim2 setFrameRate: 30];
1059
1060     [o_fullscreen_anim2 setDelegate: self];
1061
1062     dict1 = [[NSMutableDictionary alloc] initWithCapacity:3];
1063
1064     [dict1 setObject:o_fullscreen_window forKey:NSViewAnimationTargetKey];
1065     [dict1 setObject:[NSValue valueWithRect:[o_fullscreen_window frame]] forKey:NSViewAnimationStartFrameKey];
1066     [dict1 setObject:[NSValue valueWithRect:frame] forKey:NSViewAnimationEndFrameKey];
1067
1068     o_fullscreen_anim1 = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObject:dict1]];
1069     [dict1 release];
1070
1071     [o_fullscreen_anim1 setAnimationBlockingMode: NSAnimationNonblocking];
1072     [o_fullscreen_anim1 setDuration: 0.2];
1073     [o_fullscreen_anim1 setFrameRate: 30];
1074     [o_fullscreen_anim2 startWhenAnimation: o_fullscreen_anim1 reachesProgress: 1.0];
1075
1076     /* Make sure o_fullscreen_window is the frontmost window */
1077     [o_fullscreen_window orderFront: self];
1078
1079     [o_fullscreen_anim1 startAnimation];
1080     /* fullscreenAnimation will be unlocked when animation ends */
1081 }
1082
1083 - (void)hasEndedFullscreen
1084 {
1085     [self setFullscreen:NO];
1086     b_in_fullscreen_transition = NO;
1087
1088     /* This function is private and should be only triggered at the end of the fullscreen change animation */
1089     /* Make sure we don't see the o_video_view disappearing of the screen during this operation */
1090     NSDisableScreenUpdates();
1091     [o_video_view retain];
1092     [o_video_view removeFromSuperviewWithoutNeedingDisplay];
1093     [[o_temp_view superview] replaceSubview:o_temp_view with:o_video_view];
1094     [o_video_view release];
1095     [o_video_view setFrame:[o_temp_view frame]];
1096     if ([[o_video_view subviews] count] > 0)
1097         [self makeFirstResponder: [[o_video_view subviews] objectAtIndex:0]];
1098
1099     [o_video_view setHidden: b_video_view_was_hidden];
1100
1101     [super makeKeyAndOrderFront:self]; /* our version (in main window) contains a workaround */
1102
1103     [o_fullscreen_window orderOut: self];
1104     NSEnableScreenUpdates();
1105
1106     [o_fullscreen_window release];
1107     o_fullscreen_window = nil;
1108
1109     [[[VLCMain sharedInstance] voutController] updateWindowLevelForHelperWindows: i_originalLevel];
1110     [self setLevel:i_originalLevel];
1111
1112     [self setAlphaValue: config_GetFloat(VLCIntf, "macosx-opaqueness")];
1113
1114     // if we quit fullscreen because there is no video anymore, make sure non-embedded window is not visible
1115     if (![[VLCMain sharedInstance] activeVideoPlayback] && [self class] != [VLCMainWindow class])
1116         [self orderOut: self];
1117 }
1118
1119 - (void)animationDidEnd:(NSAnimation*)animation
1120 {
1121     NSArray *viewAnimations;
1122     if (o_makekey_anim == animation) {
1123         [o_makekey_anim release];
1124         return;
1125     }
1126     if ([animation currentValue] < 1.0)
1127         return;
1128
1129     /* Fullscreen ended or started (we are a delegate only for leaveFullscreen's/enterFullscren's anim2) */
1130     viewAnimations = [o_fullscreen_anim2 viewAnimations];
1131     if ([viewAnimations count] >=1 &&
1132         [[[viewAnimations objectAtIndex:0] objectForKey: NSViewAnimationEffectKey] isEqualToString:NSViewAnimationFadeInEffect]) {
1133         /* Fullscreen ended */
1134         [self hasEndedFullscreen];
1135     } else
1136     /* Fullscreen started */
1137         [self hasBecomeFullscreen];
1138 }
1139
1140 - (void)orderOut:(id)sender
1141 {
1142     [super orderOut:sender];
1143
1144     /*
1145      * TODO reimplement leaveFullscreenAndFadeOut:YES, or remove code
1146      * and the hack below
1147     
1148     if (![NSStringFromClass([self class]) isEqualToString:@"VLCMainWindow"]) {
1149         [self leaveFullscreenAndFadeOut:YES];
1150     }
1151      */
1152 }
1153
1154 - (void)makeKeyAndOrderFront: (id)sender
1155 {
1156     /* Hack
1157      * when we exit fullscreen and fade out, we may endup in
1158      * having a window that is faded. We can't have it fade in unless we
1159      * animate again. */
1160
1161     if (!b_window_is_invisible) {
1162         /* Make sure we don't do it too much */
1163         [super makeKeyAndOrderFront: sender];
1164         return;
1165     }
1166
1167     [super setAlphaValue:0.0f];
1168     [super makeKeyAndOrderFront: sender];
1169
1170     NSMutableDictionary * dict = [[NSMutableDictionary alloc] initWithCapacity:2];
1171     [dict setObject:self forKey:NSViewAnimationTargetKey];
1172     [dict setObject:NSViewAnimationFadeInEffect forKey:NSViewAnimationEffectKey];
1173
1174     o_makekey_anim = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObject:dict]];
1175     [dict release];
1176
1177     [o_makekey_anim setAnimationBlockingMode: NSAnimationNonblocking];
1178     [o_makekey_anim setDuration: 0.1];
1179     [o_makekey_anim setFrameRate: 30];
1180     [o_makekey_anim setDelegate: self];
1181
1182     [o_makekey_anim startAnimation];
1183     b_window_is_invisible = NO;
1184
1185     /* fullscreenAnimation will be unlocked when animation ends */
1186 }
1187
1188
1189 #pragma mark -
1190 #pragma mark Accessibility stuff
1191
1192 - (NSArray *)accessibilityAttributeNames
1193 {
1194     if (!b_dark_interface || !o_titlebar_view)
1195         return [super accessibilityAttributeNames];
1196
1197     static NSMutableArray *attributes = nil;
1198     if (attributes == nil) {
1199         attributes = [[super accessibilityAttributeNames] mutableCopy];
1200         NSArray *appendAttributes = [NSArray arrayWithObjects:NSAccessibilitySubroleAttribute,
1201                                      NSAccessibilityCloseButtonAttribute,
1202                                      NSAccessibilityMinimizeButtonAttribute,
1203                                      NSAccessibilityZoomButtonAttribute, nil];
1204
1205         for(NSString *attribute in appendAttributes) {
1206             if (![attributes containsObject:attribute])
1207                 [attributes addObject:attribute];
1208         }
1209     }
1210     return attributes;
1211 }
1212
1213 - (id)accessibilityAttributeValue: (NSString*)o_attribute_name
1214 {
1215     if (b_dark_interface && o_titlebar_view) {
1216         VLCMainWindowTitleView *o_tbv = o_titlebar_view;
1217
1218         if ([o_attribute_name isEqualTo: NSAccessibilitySubroleAttribute])
1219             return NSAccessibilityStandardWindowSubrole;
1220
1221         if ([o_attribute_name isEqualTo: NSAccessibilityCloseButtonAttribute])
1222             return [[o_tbv closeButton] cell];
1223
1224         if ([o_attribute_name isEqualTo: NSAccessibilityMinimizeButtonAttribute])
1225             return [[o_tbv minimizeButton] cell];
1226
1227         if ([o_attribute_name isEqualTo: NSAccessibilityZoomButtonAttribute])
1228             return [[o_tbv zoomButton] cell];
1229     }
1230
1231     return [super accessibilityAttributeValue: o_attribute_name];
1232 }
1233
1234 @end