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