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