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