]> git.sesse.net Git - vlc/blob - modules/gui/macosx/Windows.m
macosx: ensure that video view is always visible in fullscreen window (close #9526)
[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 orderOut: sender];
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     [self setLevel: i_state];
469
470 }
471
472 - (NSRect)getWindowRectForProposedVideoViewSize:(NSSize)size
473 {
474     NSSize windowMinSize = [self minSize];
475     NSRect screenFrame = [[self screen] visibleFrame];
476
477     NSPoint topleftbase = NSMakePoint(0, [self frame].size.height);
478     NSPoint topleftscreen = [self convertBaseToScreen: topleftbase];
479
480     unsigned int i_width = size.width;
481     unsigned int i_height = size.height;
482     if (i_width < windowMinSize.width)
483         i_width = windowMinSize.width;
484     if (i_height < f_min_video_height)
485         i_height = f_min_video_height;
486
487     /* Calculate the window's new size */
488     NSRect new_frame;
489     new_frame.size.width = [self frame].size.width - [o_video_view frame].size.width + i_width;
490     new_frame.size.height = [self frame].size.height - [o_video_view frame].size.height + i_height;
491     new_frame.origin.x = topleftscreen.x;
492     new_frame.origin.y = topleftscreen.y - new_frame.size.height;
493
494     /* make sure the window doesn't exceed the screen size the window is on */
495     if (new_frame.size.width > screenFrame.size.width) {
496         new_frame.size.width = screenFrame.size.width;
497         new_frame.origin.x = screenFrame.origin.x;
498     }
499     if (new_frame.size.height > screenFrame.size.height) {
500         new_frame.size.height = screenFrame.size.height;
501         new_frame.origin.y = screenFrame.origin.y;
502     }
503     if (new_frame.origin.y < screenFrame.origin.y)
504         new_frame.origin.y = screenFrame.origin.y;
505
506     CGFloat right_screen_point = screenFrame.origin.x + screenFrame.size.width;
507     CGFloat right_window_point = new_frame.origin.x + new_frame.size.width;
508     if (right_window_point > right_screen_point)
509         new_frame.origin.x -= (right_window_point - right_screen_point);
510
511     return new_frame;
512 }
513
514 - (void)resizeWindow
515 {
516     if ([self fullscreen])
517         return;
518
519     NSRect window_rect = [self getWindowRectForProposedVideoViewSize:nativeVideoSize];
520     [[self animator] setFrame:window_rect display:YES];
521 }
522
523 - (void)setNativeVideoSize:(NSSize)size
524 {
525     nativeVideoSize = size;
526
527     if (var_InheritBool(VLCIntf, "macosx-video-autoresize") && !var_InheritBool(VLCIntf, "video-wallpaper"))
528         [self resizeWindow];
529 }
530
531 - (NSSize)windowWillResize:(NSWindow *)window toSize:(NSSize)proposedFrameSize
532 {
533     if (![[VLCMain sharedInstance] activeVideoPlayback] || nativeVideoSize.width == 0. || nativeVideoSize.height == 0. || window != self)
534         return proposedFrameSize;
535
536     // needed when entering lion fullscreen mode
537     if (b_entering_fullscreen_transition || [self fullscreen])
538         return proposedFrameSize;
539
540     if ([[VLCCoreInteraction sharedInstance] aspectRatioIsLocked]) {
541         NSRect videoWindowFrame = [self frame];
542         NSRect viewRect = [o_video_view convertRect:[o_video_view bounds] toView: nil];
543         NSRect contentRect = [self contentRectForFrameRect:videoWindowFrame];
544         float marginy = viewRect.origin.y + videoWindowFrame.size.height - contentRect.size.height;
545         float marginx = contentRect.size.width - viewRect.size.width;
546         if (o_titlebar_view && b_dark_interface)
547             marginy += [o_titlebar_view frame].size.height;
548
549         proposedFrameSize.height = (proposedFrameSize.width - marginx) * nativeVideoSize.height / nativeVideoSize.width + marginy;
550     }
551
552     return proposedFrameSize;
553 }
554
555
556 #pragma mark -
557 #pragma mark Mouse cursor handling
558
559 //  NSTimer selectors require this function signature as per Apple's docs
560 - (void)hideMouseCursor:(NSTimer *)timer
561 {
562     [NSCursor setHiddenUntilMouseMoves: YES];
563 }
564
565 - (void)recreateHideMouseTimer
566 {
567     if (t_hide_mouse_timer != nil) {
568         [t_hide_mouse_timer invalidate];
569         [t_hide_mouse_timer release];
570     }
571
572     t_hide_mouse_timer = [NSTimer scheduledTimerWithTimeInterval:2
573                                                           target:self
574                                                         selector:@selector(hideMouseCursor:)
575                                                         userInfo:nil
576                                                          repeats:NO];
577     [t_hide_mouse_timer retain];
578 }
579
580 //  Called automatically if window's acceptsMouseMovedEvents property is true
581 - (void)mouseMoved:(NSEvent *)theEvent
582 {
583     if (b_fullscreen)
584         [self recreateHideMouseTimer];
585
586     [super mouseMoved: theEvent];
587 }
588
589 #pragma mark -
590 #pragma mark Lion native fullscreen handling
591
592 - (void)becomeKeyWindow
593 {
594     [super becomeKeyWindow];
595
596     // change fspanel state for the case when multiple windows are in fullscreen
597     if ([self hasActiveVideo] && [self fullscreen])
598         [[[VLCMainWindow sharedInstance] fsPanel] setActive:nil];
599     else
600         [[[VLCMainWindow sharedInstance] fsPanel] setNonActive:nil];
601 }
602
603 - (void)resignKeyWindow
604 {
605     [super resignKeyWindow];
606
607     [[[VLCMainWindow sharedInstance] fsPanel] setNonActive:nil];
608 }
609
610 - (void)windowWillEnterFullScreen:(NSNotification *)notification
611 {
612     // workaround, see #6668
613     [NSApp setPresentationOptions:(NSApplicationPresentationFullScreen | NSApplicationPresentationAutoHideDock | NSApplicationPresentationAutoHideMenuBar)];
614
615     b_entering_fullscreen_transition = YES;
616
617     var_SetBool(pl_Get(VLCIntf), "fullscreen", true);
618
619     if ([self hasActiveVideo]) {
620         vout_thread_t *p_vout = getVoutForActiveWindow();
621         if (p_vout) {
622             var_SetBool(p_vout, "fullscreen", true);
623             vlc_object_release(p_vout);
624         }
625     }
626
627     if ([self hasActiveVideo])
628         [[VLCMainWindow sharedInstance] recreateHideMouseTimer];
629
630     i_originalLevel = [self level];
631     [[[VLCMain sharedInstance] voutController] updateWindowLevelForHelperWindows: NSNormalWindowLevel];
632     [self setLevel:NSNormalWindowLevel];
633
634     if (b_dark_interface) {
635         [o_titlebar_view removeFromSuperviewWithoutNeedingDisplay];
636
637         NSRect winrect;
638         CGFloat f_titleBarHeight = [o_titlebar_view frame].size.height;
639         winrect = [self frame];
640
641         winrect.size.height = winrect.size.height - f_titleBarHeight;
642         [self setFrame: winrect display:NO animate:NO];
643     }
644
645     [o_video_view setFrame: [[self contentView] frame]];
646     if (![o_video_view isHidden]) {
647         [[o_controls_bar bottomBarView] setHidden: YES];
648     }
649     
650
651     [self setMovableByWindowBackground: NO];
652 }
653
654 - (void)windowDidEnterFullScreen:(NSNotification *)notification
655 {
656     // Indeed, we somehow can have an "inactive" fullscreen (but a visible window!).
657     // But this creates some problems when leaving fs over remote intfs, so activate app here.
658     [NSApp activateIgnoringOtherApps:YES];
659
660     [self setFullscreen: YES];
661     b_entering_fullscreen_transition = NO;
662
663     if ([self hasActiveVideo]) {
664         [[[VLCMainWindow sharedInstance] fsPanel] setVoutWasUpdated: self];
665         if (![o_video_view isHidden])
666             [[[VLCMainWindow sharedInstance] fsPanel] setActive: nil];
667     }
668
669     NSArray *subviews = [[self videoView] subviews];
670     NSUInteger count = [subviews count];
671
672     for (NSUInteger x = 0; x < count; x++) {
673         if ([[subviews objectAtIndex:x] respondsToSelector:@selector(reshape)])
674             [[subviews objectAtIndex:x] reshape];
675     }
676
677 }
678
679 - (void)windowWillExitFullScreen:(NSNotification *)notification
680 {
681     [self setFullscreen: NO];
682
683     var_SetBool(pl_Get(VLCIntf), "fullscreen", false);
684
685     if ([self hasActiveVideo]) {
686         vout_thread_t *p_vout = getVoutForActiveWindow();
687         if (p_vout) {
688             var_SetBool(p_vout, "fullscreen", false);
689             vlc_object_release(p_vout);
690         }
691     }
692
693     [NSCursor setHiddenUntilMouseMoves: NO];
694     [[[VLCMainWindow sharedInstance] fsPanel] setNonActive: nil];
695
696     [[[VLCMain sharedInstance] voutController] updateWindowLevelForHelperWindows: i_originalLevel];
697     [self setLevel:i_originalLevel];
698
699     if (b_dark_interface) {
700         NSRect winrect;
701         CGFloat f_titleBarHeight = [o_titlebar_view frame].size.height;
702
703         winrect = [o_video_view frame];
704         winrect.size.height -= f_titleBarHeight;
705         [o_video_view setFrame: winrect];
706
707         winrect = [self frame];
708         [o_titlebar_view setFrame: NSMakeRect(0, winrect.size.height - f_titleBarHeight,
709                                               winrect.size.width, f_titleBarHeight)];
710         [[self contentView] addSubview: o_titlebar_view];
711
712         winrect.size.height = winrect.size.height + f_titleBarHeight;
713         [self setFrame: winrect display:NO animate:NO];
714     }
715
716     NSRect videoViewFrame = [o_video_view frame];
717     videoViewFrame.origin.y += [o_controls_bar height];
718     videoViewFrame.size.height -= [o_controls_bar height];
719     [o_video_view setFrame: videoViewFrame];
720
721     if (![o_video_view isHidden]) {
722         [[o_controls_bar bottomBarView] setHidden: NO];
723     }
724
725     [self setMovableByWindowBackground: YES];
726 }
727
728 #pragma mark -
729 #pragma mark Fullscreen Logic
730
731 - (void)enterFullscreen
732 {
733     NSMutableDictionary *dict1, *dict2;
734     NSScreen *screen;
735     NSRect screen_rect;
736     NSRect rect;
737     BOOL blackout_other_displays = var_InheritBool(VLCIntf, "macosx-black");
738
739     screen = [NSScreen screenWithDisplayID:(CGDirectDisplayID)var_InheritInteger(VLCIntf, "macosx-vdev")];
740
741     if (!screen) {
742         msg_Dbg(VLCIntf, "chosen screen isn't present, using current screen for fullscreen mode");
743         screen = [self screen];
744     }
745     if (!screen) {
746         msg_Dbg(VLCIntf, "Using deepest screen");
747         screen = [NSScreen deepestScreen];
748     }
749
750     screen_rect = [screen frame];
751
752     if (o_controls_bar)
753         [o_controls_bar setFullscreenState:YES];
754     [[[VLCMainWindow sharedInstance] controlsBar] setFullscreenState:YES];
755
756     [[VLCMainWindow sharedInstance] recreateHideMouseTimer];
757
758     if (blackout_other_displays)
759         [screen blackoutOtherScreens];
760
761     /* Make sure we don't see the window flashes in float-on-top mode */
762     i_originalLevel = [self level];
763     [[[VLCMain sharedInstance] voutController] updateWindowLevelForHelperWindows: NSNormalWindowLevel];
764     [self setLevel:NSNormalWindowLevel];
765
766
767     /* Only create the o_fullscreen_window if we are not in the middle of the zooming animation */
768     if (!o_fullscreen_window) {
769         /* We can't change the styleMask of an already created NSWindow, so we create another window, and do eye catching stuff */
770
771         rect = [[o_video_view superview] convertRect: [o_video_view frame] toView: nil]; /* Convert to Window base coord */
772         rect.origin.x += [self frame].origin.x;
773         rect.origin.y += [self frame].origin.y;
774         o_fullscreen_window = [[VLCWindow alloc] initWithContentRect:rect styleMask: NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:YES];
775         [o_fullscreen_window setBackgroundColor: [NSColor blackColor]];
776         [o_fullscreen_window setCanBecomeKeyWindow: YES];
777         [o_fullscreen_window setCanBecomeMainWindow: YES];
778         [o_fullscreen_window setHasActiveVideo: YES];
779         [o_fullscreen_window setFullscreen: YES];
780
781         if (![self isVisible] || [self alphaValue] == 0.0) {
782             /* We don't animate if we are not visible, instead we
783              * simply fade the display */
784             CGDisplayFadeReservationToken token;
785
786             if (blackout_other_displays) {
787                 CGAcquireDisplayFadeReservation(kCGMaxDisplayReservationInterval, &token);
788                 CGDisplayFade(token, 0.5, kCGDisplayBlendNormal, kCGDisplayBlendSolidColor, 0, 0, 0, YES);
789             }
790
791             [screen setFullscreenPresentationOptions];
792
793             [o_video_view retain];
794             [[o_video_view superview] replaceSubview:o_video_view with:o_temp_view];
795             [o_temp_view setFrame:[o_video_view frame]];
796             [o_fullscreen_window setContentView:o_video_view];
797             [o_video_view release];
798
799             [o_fullscreen_window makeKeyAndOrderFront:self];
800             [o_fullscreen_window orderFront:self animate:YES];
801
802             [o_fullscreen_window setFrame:screen_rect display:YES animate:YES];
803             [o_fullscreen_window setLevel:NSNormalWindowLevel];
804
805             if (blackout_other_displays) {
806                 CGDisplayFade(token, 0.3, kCGDisplayBlendSolidColor, kCGDisplayBlendNormal, 0, 0, 0, NO);
807                 CGReleaseDisplayFadeReservation(token);
808             }
809
810             /* Will release the lock */
811             [self hasBecomeFullscreen];
812
813             return;
814         }
815
816         /* Make sure video view gets visible in case the playlist was visible before */
817         b_video_view_was_hidden = [o_video_view isHidden];
818         [o_video_view setHidden: NO];
819
820         /* Make sure we don't see the o_video_view disappearing of the screen during this operation */
821         NSDisableScreenUpdates();
822         [o_video_view retain];
823         [[o_video_view superview] replaceSubview:o_video_view with:o_temp_view];
824         [o_temp_view setFrame:[o_video_view frame]];
825         [o_fullscreen_window setContentView:o_video_view];
826         [o_video_view release];
827         [o_fullscreen_window makeKeyAndOrderFront:self];
828         NSEnableScreenUpdates();
829     }
830
831     /* We are in fullscreen (and no animation is running) */
832     if ([self fullscreen]) {
833         /* Make sure we are hidden */
834         [self orderOut: self];
835
836         return;
837     }
838
839     if (o_fullscreen_anim1) {
840         [o_fullscreen_anim1 stopAnimation];
841         [o_fullscreen_anim1 release];
842     }
843     if (o_fullscreen_anim2) {
844         [o_fullscreen_anim2 stopAnimation];
845         [o_fullscreen_anim2 release];
846     }
847
848     [screen setFullscreenPresentationOptions];
849
850     dict1 = [[NSMutableDictionary alloc] initWithCapacity:2];
851     dict2 = [[NSMutableDictionary alloc] initWithCapacity:3];
852
853     [dict1 setObject:self forKey:NSViewAnimationTargetKey];
854     [dict1 setObject:NSViewAnimationFadeOutEffect forKey:NSViewAnimationEffectKey];
855
856     [dict2 setObject:o_fullscreen_window forKey:NSViewAnimationTargetKey];
857     [dict2 setObject:[NSValue valueWithRect:[o_fullscreen_window frame]] forKey:NSViewAnimationStartFrameKey];
858     [dict2 setObject:[NSValue valueWithRect:screen_rect] forKey:NSViewAnimationEndFrameKey];
859
860     /* Strategy with NSAnimation allocation:
861      - Keep at most 2 animation at a time
862      - leaveFullscreen/enterFullscreen are the only responsible for releasing and alloc-ing
863      */
864     o_fullscreen_anim1 = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObject:dict1]];
865     o_fullscreen_anim2 = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObject:dict2]];
866
867     [dict1 release];
868     [dict2 release];
869
870     [o_fullscreen_anim1 setAnimationBlockingMode: NSAnimationNonblocking];
871     [o_fullscreen_anim1 setDuration: 0.3];
872     [o_fullscreen_anim1 setFrameRate: 30];
873     [o_fullscreen_anim2 setAnimationBlockingMode: NSAnimationNonblocking];
874     [o_fullscreen_anim2 setDuration: 0.2];
875     [o_fullscreen_anim2 setFrameRate: 30];
876
877     [o_fullscreen_anim2 setDelegate: self];
878     [o_fullscreen_anim2 startWhenAnimation: o_fullscreen_anim1 reachesProgress: 1.0];
879
880     [o_fullscreen_anim1 startAnimation];
881     /* fullscreenAnimation will be unlocked when animation ends */
882 }
883
884 - (void)hasBecomeFullscreen
885 {
886     if ([[o_video_view subviews] count] > 0)
887         [o_fullscreen_window makeFirstResponder: [[o_video_view subviews] objectAtIndex:0]];
888
889     [o_fullscreen_window makeKeyWindow];
890     [o_fullscreen_window setAcceptsMouseMovedEvents: YES];
891
892     /* tell the fspanel to move itself to front next time it's triggered */
893     [[[VLCMainWindow sharedInstance] fsPanel] setVoutWasUpdated: o_fullscreen_window];
894     [[[VLCMainWindow sharedInstance] fsPanel] setActive: nil];
895
896     if ([self isVisible])
897         [self orderOut: self];
898
899     [self setFullscreen:YES];
900 }
901
902 - (void)leaveFullscreen
903 {
904     [self leaveFullscreenAndFadeOut: NO];
905 }
906
907 - (void)leaveFullscreenAndFadeOut: (BOOL)fadeout
908 {
909     NSMutableDictionary *dict1, *dict2;
910     NSRect frame;
911     BOOL blackout_other_displays = var_InheritBool(VLCIntf, "macosx-black");
912
913     if (o_controls_bar)
914         [o_controls_bar setFullscreenState:NO];
915     [[[VLCMainWindow sharedInstance] controlsBar] setFullscreenState:NO];
916
917     /* We always try to do so */
918     [NSScreen unblackoutScreens];
919
920     [[o_video_view window] makeKeyAndOrderFront: nil];
921
922     /* Don't do anything if o_fullscreen_window is already closed */
923     if (!o_fullscreen_window) {
924         return;
925     }
926
927     if (fadeout) {
928         /* We don't animate if we are not visible, instead we
929          * simply fade the display */
930         CGDisplayFadeReservationToken token;
931
932         if (blackout_other_displays) {
933             CGAcquireDisplayFadeReservation(kCGMaxDisplayReservationInterval, &token);
934             CGDisplayFade(token, 0.3, kCGDisplayBlendNormal, kCGDisplayBlendSolidColor, 0, 0, 0, YES);
935         }
936
937         [[[VLCMainWindow sharedInstance] fsPanel] setNonActive: nil];
938         [[o_fullscreen_window screen] setNonFullscreenPresentationOptions];
939
940         /* Will release the lock */
941         [self hasEndedFullscreen];
942
943         /* Our window is hidden, and might be faded. We need to workaround that, so note it
944          * here */
945         b_window_is_invisible = YES;
946
947         if (blackout_other_displays) {
948             CGDisplayFade(token, 0.5, kCGDisplayBlendSolidColor, kCGDisplayBlendNormal, 0, 0, 0, NO);
949             CGReleaseDisplayFadeReservation(token);
950         }
951
952         return;
953     }
954
955     [self setAlphaValue: 0.0];
956     [self orderFront: self];
957     [[o_video_view window] orderFront: self];
958
959     [[[VLCMainWindow sharedInstance] fsPanel] setNonActive: nil];
960     [[o_fullscreen_window screen] setNonFullscreenPresentationOptions];
961
962     if (o_fullscreen_anim1) {
963         [o_fullscreen_anim1 stopAnimation];
964         [o_fullscreen_anim1 release];
965     }
966     if (o_fullscreen_anim2) {
967         [o_fullscreen_anim2 stopAnimation];
968         [o_fullscreen_anim2 release];
969     }
970
971     frame = [[o_temp_view superview] convertRect: [o_temp_view frame] toView: nil]; /* Convert to Window base coord */
972     frame.origin.x += [self frame].origin.x;
973     frame.origin.y += [self frame].origin.y;
974
975     dict2 = [[NSMutableDictionary alloc] initWithCapacity:2];
976     [dict2 setObject:self forKey:NSViewAnimationTargetKey];
977     [dict2 setObject:NSViewAnimationFadeInEffect forKey:NSViewAnimationEffectKey];
978
979     o_fullscreen_anim2 = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObject:dict2]];
980     [dict2 release];
981
982     [o_fullscreen_anim2 setAnimationBlockingMode: NSAnimationNonblocking];
983     [o_fullscreen_anim2 setDuration: 0.3];
984     [o_fullscreen_anim2 setFrameRate: 30];
985
986     [o_fullscreen_anim2 setDelegate: self];
987
988     dict1 = [[NSMutableDictionary alloc] initWithCapacity:3];
989
990     [dict1 setObject:o_fullscreen_window forKey:NSViewAnimationTargetKey];
991     [dict1 setObject:[NSValue valueWithRect:[o_fullscreen_window frame]] forKey:NSViewAnimationStartFrameKey];
992     [dict1 setObject:[NSValue valueWithRect:frame] forKey:NSViewAnimationEndFrameKey];
993
994     o_fullscreen_anim1 = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObject:dict1]];
995     [dict1 release];
996
997     [o_fullscreen_anim1 setAnimationBlockingMode: NSAnimationNonblocking];
998     [o_fullscreen_anim1 setDuration: 0.2];
999     [o_fullscreen_anim1 setFrameRate: 30];
1000     [o_fullscreen_anim2 startWhenAnimation: o_fullscreen_anim1 reachesProgress: 1.0];
1001
1002     /* Make sure o_fullscreen_window is the frontmost window */
1003     [o_fullscreen_window orderFront: self];
1004
1005     [o_fullscreen_anim1 startAnimation];
1006     /* fullscreenAnimation will be unlocked when animation ends */
1007 }
1008
1009 - (void)hasEndedFullscreen
1010 {
1011     [self setFullscreen:NO];
1012
1013     /* This function is private and should be only triggered at the end of the fullscreen change animation */
1014     /* Make sure we don't see the o_video_view disappearing of the screen during this operation */
1015     NSDisableScreenUpdates();
1016     [o_video_view retain];
1017     [o_video_view removeFromSuperviewWithoutNeedingDisplay];
1018     [[o_temp_view superview] replaceSubview:o_temp_view with:o_video_view];
1019     [o_video_view release];
1020     [o_video_view setFrame:[o_temp_view frame]];
1021     if ([[o_video_view subviews] count] > 0)
1022         [self makeFirstResponder: [[o_video_view subviews] objectAtIndex:0]];
1023
1024     [o_video_view setHidden: b_video_view_was_hidden];
1025
1026     [super makeKeyAndOrderFront:self]; /* our version (in main window) contains a workaround */
1027
1028     [o_fullscreen_window orderOut: self];
1029     NSEnableScreenUpdates();
1030
1031     [o_fullscreen_window release];
1032     o_fullscreen_window = nil;
1033
1034     [[[VLCMain sharedInstance] voutController] updateWindowLevelForHelperWindows: i_originalLevel];
1035     [self setLevel:i_originalLevel];
1036     [self setAlphaValue: config_GetFloat(VLCIntf, "macosx-opaqueness")];
1037
1038     // if we quit fullscreen because there is no video anymore, make sure non-embedded window is not visible
1039     if (![[VLCMain sharedInstance] activeVideoPlayback] && [self class] != [VLCMainWindow class])
1040         [self orderOut: self];
1041 }
1042
1043 - (void)animationDidEnd:(NSAnimation*)animation
1044 {
1045     NSArray *viewAnimations;
1046     if (o_makekey_anim == animation) {
1047         [o_makekey_anim release];
1048         return;
1049     }
1050     if ([animation currentValue] < 1.0)
1051         return;
1052
1053     /* Fullscreen ended or started (we are a delegate only for leaveFullscreen's/enterFullscren's anim2) */
1054     viewAnimations = [o_fullscreen_anim2 viewAnimations];
1055     if ([viewAnimations count] >=1 &&
1056         [[[viewAnimations objectAtIndex:0] objectForKey: NSViewAnimationEffectKey] isEqualToString:NSViewAnimationFadeInEffect]) {
1057         /* Fullscreen ended */
1058         [self hasEndedFullscreen];
1059     } else
1060     /* Fullscreen started */
1061         [self hasBecomeFullscreen];
1062 }
1063
1064 - (void)orderOut:(id)sender
1065 {
1066     [super orderOut:sender];
1067
1068     /*
1069      * TODO reimplement leaveFullscreenAndFadeOut:YES, or remove code
1070      * and the hack below
1071     
1072     if (![NSStringFromClass([self class]) isEqualToString:@"VLCMainWindow"]) {
1073         [self leaveFullscreenAndFadeOut:YES];
1074     }
1075      */
1076 }
1077
1078 - (void)makeKeyAndOrderFront: (id)sender
1079 {
1080     /* Hack
1081      * when we exit fullscreen and fade out, we may endup in
1082      * having a window that is faded. We can't have it fade in unless we
1083      * animate again. */
1084
1085     if (!b_window_is_invisible) {
1086         /* Make sure we don't do it too much */
1087         [super makeKeyAndOrderFront: sender];
1088         return;
1089     }
1090
1091     [super setAlphaValue:0.0f];
1092     [super makeKeyAndOrderFront: sender];
1093
1094     NSMutableDictionary * dict = [[NSMutableDictionary alloc] initWithCapacity:2];
1095     [dict setObject:self forKey:NSViewAnimationTargetKey];
1096     [dict setObject:NSViewAnimationFadeInEffect forKey:NSViewAnimationEffectKey];
1097
1098     o_makekey_anim = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObject:dict]];
1099     [dict release];
1100
1101     [o_makekey_anim setAnimationBlockingMode: NSAnimationNonblocking];
1102     [o_makekey_anim setDuration: 0.1];
1103     [o_makekey_anim setFrameRate: 30];
1104     [o_makekey_anim setDelegate: self];
1105
1106     [o_makekey_anim startAnimation];
1107     b_window_is_invisible = NO;
1108
1109     /* fullscreenAnimation will be unlocked when animation ends */
1110 }
1111
1112
1113 #pragma mark -
1114 #pragma mark Accessibility stuff
1115
1116 - (NSArray *)accessibilityAttributeNames
1117 {
1118     if (!b_dark_interface || !o_titlebar_view)
1119         return [super accessibilityAttributeNames];
1120
1121     static NSMutableArray *attributes = nil;
1122     if (attributes == nil) {
1123         attributes = [[super accessibilityAttributeNames] mutableCopy];
1124         NSArray *appendAttributes = [NSArray arrayWithObjects:NSAccessibilitySubroleAttribute,
1125                                      NSAccessibilityCloseButtonAttribute,
1126                                      NSAccessibilityMinimizeButtonAttribute,
1127                                      NSAccessibilityZoomButtonAttribute, nil];
1128
1129         for(NSString *attribute in appendAttributes) {
1130             if (![attributes containsObject:attribute])
1131                 [attributes addObject:attribute];
1132         }
1133     }
1134     return attributes;
1135 }
1136
1137 - (id)accessibilityAttributeValue: (NSString*)o_attribute_name
1138 {
1139     if (b_dark_interface && o_titlebar_view) {
1140         VLCMainWindowTitleView *o_tbv = o_titlebar_view;
1141
1142         if ([o_attribute_name isEqualTo: NSAccessibilitySubroleAttribute])
1143             return NSAccessibilityStandardWindowSubrole;
1144
1145         if ([o_attribute_name isEqualTo: NSAccessibilityCloseButtonAttribute])
1146             return [[o_tbv closeButton] cell];
1147
1148         if ([o_attribute_name isEqualTo: NSAccessibilityMinimizeButtonAttribute])
1149             return [[o_tbv minimizeButton] cell];
1150
1151         if ([o_attribute_name isEqualTo: NSAccessibilityZoomButtonAttribute])
1152             return [[o_tbv zoomButton] cell];
1153     }
1154
1155     return [super accessibilityAttributeValue: o_attribute_name];
1156 }
1157
1158 @end