]> git.sesse.net Git - vlc/blob - modules/gui/macosx/Windows.m
macosx: only show fspanel when needed, additional fixed for lion fullscreen behavior
[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 Lion native fullscreen handling
551
552 - (void)becomeKeyWindow
553 {
554     [super becomeKeyWindow];
555
556     // change fspanel state for the case when multiple windows are in fullscreen
557     if ([self hasActiveVideo] && [self fullscreen])
558         [[[VLCMainWindow sharedInstance] fsPanel] setActive:nil];
559     else
560         [[[VLCMainWindow sharedInstance] fsPanel] setNonActive:nil];
561 }
562
563 - (void)windowWillEnterFullScreen:(NSNotification *)notification
564 {
565     // workaround, see #6668
566     [NSApp setPresentationOptions:(NSApplicationPresentationFullScreen | NSApplicationPresentationAutoHideDock | NSApplicationPresentationAutoHideMenuBar)];
567
568     [self setFullscreen: YES];
569
570     var_SetBool(pl_Get(VLCIntf), "fullscreen", true);
571
572     if ([self hasActiveVideo]) {
573         vout_thread_t *p_vout = getVoutForActiveWindow();
574         if (p_vout) {
575             var_SetBool(p_vout, "fullscreen", true);
576             vlc_object_release(p_vout);
577         }
578     }
579
580     [o_video_view setFrame: [[self contentView] frame]];
581
582     [[VLCMainWindow sharedInstance] recreateHideMouseTimer];
583     i_originalLevel = [self level];
584     [[[VLCMain sharedInstance] voutController] updateWindowLevelForHelperWindows: NSNormalWindowLevel];
585     [self setLevel:NSNormalWindowLevel];
586
587     if (b_dark_interface) {
588         [o_titlebar_view removeFromSuperviewWithoutNeedingDisplay];
589
590         NSRect winrect;
591         CGFloat f_titleBarHeight = [o_titlebar_view frame].size.height;
592         winrect = [self frame];
593
594         winrect.size.height = winrect.size.height - f_titleBarHeight;
595         [self setFrame: winrect display:NO animate:NO];
596     }
597
598     // TODO fix bottom bar status when vout just not visible, but there
599     if (![o_video_view isHidden])
600         [[o_controls_bar bottomBarView] setHidden: YES];
601
602     [self setMovableByWindowBackground: NO];
603 }
604
605 - (void)windowDidEnterFullScreen:(NSNotification *)notification
606 {
607     // Indeed, we somehow can have an "inactive" fullscreen (but a visible window!).
608     // But this creates some problems when leaving fs over remote intfs, so activate app here.
609     [NSApp activateIgnoringOtherApps:YES];
610
611     if ([self hasActiveVideo]) {
612         [[[VLCMainWindow sharedInstance] fsPanel] setVoutWasUpdated: self];
613         [[[VLCMainWindow sharedInstance] fsPanel] setActive: nil];
614     }
615
616     NSArray *subviews = [[self videoView] subviews];
617     NSUInteger count = [subviews count];
618
619     for (NSUInteger x = 0; x < count; x++) {
620         if ([[subviews objectAtIndex:x] respondsToSelector:@selector(reshape)])
621             [[subviews objectAtIndex:x] reshape];
622     }
623
624 }
625
626 - (void)windowWillExitFullScreen:(NSNotification *)notification
627 {
628     [self setFullscreen: NO];
629
630     var_SetBool(pl_Get(VLCIntf), "fullscreen", false);
631
632     if ([self hasActiveVideo]) {
633         vout_thread_t *p_vout = getVoutForActiveWindow();
634         if (p_vout) {
635             var_SetBool(p_vout, "fullscreen", false);
636             vlc_object_release(p_vout);
637         }
638     }
639
640     [NSCursor setHiddenUntilMouseMoves: NO];
641     [[[VLCMainWindow sharedInstance] fsPanel] setNonActive: nil];
642     
643     [[[VLCMain sharedInstance] voutController] updateWindowLevelForHelperWindows: i_originalLevel];
644     [self setLevel:i_originalLevel];
645
646     if (b_dark_interface) {
647         NSRect winrect;
648         CGFloat f_titleBarHeight = [o_titlebar_view frame].size.height;
649         winrect = [self frame];
650
651         [o_titlebar_view setFrame: NSMakeRect(0, winrect.size.height - f_titleBarHeight,
652                                               winrect.size.width, f_titleBarHeight)];
653         [[self contentView] addSubview: o_titlebar_view];
654
655         winrect.size.height = winrect.size.height + f_titleBarHeight;
656         [self setFrame: winrect display:NO animate:NO];
657         winrect = [o_video_view frame];
658         winrect.size.height -= f_titleBarHeight;
659         [o_video_view setFrame: winrect];
660     }
661
662     NSRect videoViewFrame = [o_video_view frame];
663     videoViewFrame.origin.y = [[o_controls_bar bottomBarView] frame].size.height;
664     videoViewFrame.size.height -= [[o_controls_bar bottomBarView] frame].size.height;
665     [o_video_view setFrame: videoViewFrame];
666     [[o_controls_bar bottomBarView] setHidden: NO];
667     
668     [self setMovableByWindowBackground: YES];
669 }
670
671 #pragma mark -
672 #pragma mark Fullscreen Logic
673
674 - (void)lockFullscreenAnimation
675 {
676     [o_animation_lock lock];
677 }
678
679 - (void)unlockFullscreenAnimation
680 {
681     [o_animation_lock unlock];
682 }
683
684 - (void)enterFullscreen
685 {
686     NSMutableDictionary *dict1, *dict2;
687     NSScreen *screen;
688     NSRect screen_rect;
689     NSRect rect;
690     BOOL blackout_other_displays = var_InheritBool(VLCIntf, "macosx-black");
691
692     screen = [NSScreen screenWithDisplayID:(CGDirectDisplayID)var_InheritInteger(VLCIntf, "macosx-vdev")];
693     [self lockFullscreenAnimation];
694
695     if (!screen) {
696         msg_Dbg(VLCIntf, "chosen screen isn't present, using current screen for fullscreen mode");
697         screen = [self screen];
698     }
699     if (!screen) {
700         msg_Dbg(VLCIntf, "Using deepest screen");
701         screen = [NSScreen deepestScreen];
702     }
703
704     screen_rect = [screen frame];
705
706     if (o_controls_bar)
707         [o_controls_bar setFullscreenState:YES];
708     [[[VLCMainWindow sharedInstance] controlsBar] setFullscreenState:YES];
709
710     [[VLCMainWindow sharedInstance] recreateHideMouseTimer];
711
712     if (blackout_other_displays)
713         [screen blackoutOtherScreens];
714
715     /* Make sure we don't see the window flashes in float-on-top mode */
716     i_originalLevel = [self level];
717     [[[VLCMain sharedInstance] voutController] updateWindowLevelForHelperWindows: NSNormalWindowLevel];
718     [self setLevel:NSNormalWindowLevel];
719
720
721     /* Only create the o_fullscreen_window if we are not in the middle of the zooming animation */
722     if (!o_fullscreen_window) {
723         /* We can't change the styleMask of an already created NSWindow, so we create another window, and do eye catching stuff */
724
725         rect = [[o_video_view superview] convertRect: [o_video_view frame] toView: nil]; /* Convert to Window base coord */
726         rect.origin.x += [self frame].origin.x;
727         rect.origin.y += [self frame].origin.y;
728         o_fullscreen_window = [[VLCWindow alloc] initWithContentRect:rect styleMask: NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:YES];
729         [o_fullscreen_window setBackgroundColor: [NSColor blackColor]];
730         [o_fullscreen_window setCanBecomeKeyWindow: YES];
731         [o_fullscreen_window setCanBecomeMainWindow: YES];
732         [o_fullscreen_window setHasActiveVideo: YES];
733         [o_fullscreen_window setFullscreen: YES];
734
735         if (![self isVisible] || [self alphaValue] == 0.0) {
736             /* We don't animate if we are not visible, instead we
737              * simply fade the display */
738             CGDisplayFadeReservationToken token;
739
740             if (blackout_other_displays) {
741                 CGAcquireDisplayFadeReservation(kCGMaxDisplayReservationInterval, &token);
742                 CGDisplayFade(token, 0.5, kCGDisplayBlendNormal, kCGDisplayBlendSolidColor, 0, 0, 0, YES);
743             }
744
745             NSApplicationPresentationOptions presentationOpts = [NSApp presentationOptions];
746             if ([screen hasMenuBar])
747                 presentationOpts |= NSApplicationPresentationAutoHideMenuBar;
748             if ([screen hasMenuBar] || [screen hasDock])
749                 presentationOpts |= NSApplicationPresentationAutoHideDock;
750             [NSApp setPresentationOptions:presentationOpts];
751
752             [[o_video_view superview] replaceSubview:o_video_view with:o_temp_view];
753             [o_temp_view setFrame:[o_video_view frame]];
754             [o_fullscreen_window setContentView:o_video_view];
755
756             [o_fullscreen_window makeKeyAndOrderFront:self];
757             [o_fullscreen_window orderFront:self animate:YES];
758
759             [o_fullscreen_window setFrame:screen_rect display:YES animate:YES];
760             [o_fullscreen_window setLevel:NSNormalWindowLevel];
761
762             if (blackout_other_displays) {
763                 CGDisplayFade(token, 0.3, kCGDisplayBlendSolidColor, kCGDisplayBlendNormal, 0, 0, 0, NO);
764                 CGReleaseDisplayFadeReservation(token);
765             }
766
767             /* Will release the lock */
768             [self hasBecomeFullscreen];
769
770             return;
771         }
772
773         /* Make sure we don't see the o_video_view disappearing of the screen during this operation */
774         NSDisableScreenUpdates();
775         [[o_video_view superview] replaceSubview:o_video_view with:o_temp_view];
776         [o_temp_view setFrame:[o_video_view frame]];
777         [o_fullscreen_window setContentView:o_video_view];
778         [o_fullscreen_window makeKeyAndOrderFront:self];
779         NSEnableScreenUpdates();
780     }
781
782     /* We are in fullscreen (and no animation is running) */
783     if ([self fullscreen]) {
784         /* Make sure we are hidden */
785         [self orderOut: self];
786
787         [self unlockFullscreenAnimation];
788         return;
789     }
790
791     if (o_fullscreen_anim1) {
792         [o_fullscreen_anim1 stopAnimation];
793         [o_fullscreen_anim1 release];
794     }
795     if (o_fullscreen_anim2) {
796         [o_fullscreen_anim2 stopAnimation];
797         [o_fullscreen_anim2 release];
798     }
799
800     NSApplicationPresentationOptions presentationOpts = [NSApp presentationOptions];
801     if ([screen hasMenuBar])
802         presentationOpts |= NSApplicationPresentationAutoHideMenuBar;
803     if ([screen hasMenuBar] || [screen hasDock])
804         presentationOpts |= NSApplicationPresentationAutoHideDock;
805     [NSApp setPresentationOptions:presentationOpts];
806
807     dict1 = [[NSMutableDictionary alloc] initWithCapacity:2];
808     dict2 = [[NSMutableDictionary alloc] initWithCapacity:3];
809
810     [dict1 setObject:self forKey:NSViewAnimationTargetKey];
811     [dict1 setObject:NSViewAnimationFadeOutEffect forKey:NSViewAnimationEffectKey];
812
813     [dict2 setObject:o_fullscreen_window forKey:NSViewAnimationTargetKey];
814     [dict2 setObject:[NSValue valueWithRect:[o_fullscreen_window frame]] forKey:NSViewAnimationStartFrameKey];
815     [dict2 setObject:[NSValue valueWithRect:screen_rect] forKey:NSViewAnimationEndFrameKey];
816
817     /* Strategy with NSAnimation allocation:
818      - Keep at most 2 animation at a time
819      - leaveFullscreen/enterFullscreen are the only responsible for releasing and alloc-ing
820      */
821     o_fullscreen_anim1 = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObject:dict1]];
822     o_fullscreen_anim2 = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObject:dict2]];
823
824     [dict1 release];
825     [dict2 release];
826
827     [o_fullscreen_anim1 setAnimationBlockingMode: NSAnimationNonblocking];
828     [o_fullscreen_anim1 setDuration: 0.3];
829     [o_fullscreen_anim1 setFrameRate: 30];
830     [o_fullscreen_anim2 setAnimationBlockingMode: NSAnimationNonblocking];
831     [o_fullscreen_anim2 setDuration: 0.2];
832     [o_fullscreen_anim2 setFrameRate: 30];
833
834     [o_fullscreen_anim2 setDelegate: self];
835     [o_fullscreen_anim2 startWhenAnimation: o_fullscreen_anim1 reachesProgress: 1.0];
836
837     [o_fullscreen_anim1 startAnimation];
838     /* fullscreenAnimation will be unlocked when animation ends */
839 }
840
841 - (void)hasBecomeFullscreen
842 {
843     if ([[o_video_view subviews] count] > 0)
844         [o_fullscreen_window makeFirstResponder: [[o_video_view subviews] objectAtIndex:0]];
845
846     [o_fullscreen_window makeKeyWindow];
847     [o_fullscreen_window setAcceptsMouseMovedEvents: YES];
848
849     /* tell the fspanel to move itself to front next time it's triggered */
850     [[[VLCMainWindow sharedInstance] fsPanel] setVoutWasUpdated: o_fullscreen_window];
851     [[[VLCMainWindow sharedInstance] fsPanel] setActive: nil];
852
853     if ([self isVisible])
854         [self orderOut: self];
855
856     [self setFullscreen:YES];
857     [self unlockFullscreenAnimation];
858 }
859
860 - (void)leaveFullscreen
861 {
862     [self leaveFullscreenAndFadeOut: NO];
863 }
864
865 - (void)leaveFullscreenAndFadeOut: (BOOL)fadeout
866 {
867     NSMutableDictionary *dict1, *dict2;
868     NSRect frame;
869     BOOL blackout_other_displays = var_InheritBool(VLCIntf, "macosx-black");
870
871     [self lockFullscreenAnimation];
872
873     if (o_controls_bar)
874         [o_controls_bar setFullscreenState:NO];
875     [[[VLCMainWindow sharedInstance] controlsBar] setFullscreenState:NO];
876
877     /* We always try to do so */
878     [NSScreen unblackoutScreens];
879
880     [[o_video_view window] makeKeyAndOrderFront: nil];
881
882     /* Don't do anything if o_fullscreen_window is already closed */
883     if (!o_fullscreen_window) {
884         [self unlockFullscreenAnimation];
885         return;
886     }
887
888     if (fadeout) {
889         /* We don't animate if we are not visible, instead we
890          * simply fade the display */
891         CGDisplayFadeReservationToken token;
892
893         if (blackout_other_displays) {
894             CGAcquireDisplayFadeReservation(kCGMaxDisplayReservationInterval, &token);
895             CGDisplayFade(token, 0.3, kCGDisplayBlendNormal, kCGDisplayBlendSolidColor, 0, 0, 0, YES);
896         }
897
898         [[[VLCMainWindow sharedInstance] fsPanel] setNonActive: nil];
899         [NSApp setPresentationOptions: NSApplicationPresentationDefault];
900
901         /* Will release the lock */
902         [self hasEndedFullscreen];
903
904         /* Our window is hidden, and might be faded. We need to workaround that, so note it
905          * here */
906         b_window_is_invisible = YES;
907
908         if (blackout_other_displays) {
909             CGDisplayFade(token, 0.5, kCGDisplayBlendSolidColor, kCGDisplayBlendNormal, 0, 0, 0, NO);
910             CGReleaseDisplayFadeReservation(token);
911         }
912
913         return;
914     }
915
916     [self setAlphaValue: 0.0];
917     [self orderFront: self];
918     [[o_video_view window] orderFront: self];
919
920     [[[VLCMainWindow sharedInstance] fsPanel] setNonActive: nil];
921     [NSApp setPresentationOptions:(NSApplicationPresentationDefault)];
922
923     if (o_fullscreen_anim1) {
924         [o_fullscreen_anim1 stopAnimation];
925         [o_fullscreen_anim1 release];
926     }
927     if (o_fullscreen_anim2) {
928         [o_fullscreen_anim2 stopAnimation];
929         [o_fullscreen_anim2 release];
930     }
931
932     frame = [[o_temp_view superview] convertRect: [o_temp_view frame] toView: nil]; /* Convert to Window base coord */
933     frame.origin.x += [self frame].origin.x;
934     frame.origin.y += [self frame].origin.y;
935
936     dict2 = [[NSMutableDictionary alloc] initWithCapacity:2];
937     [dict2 setObject:self forKey:NSViewAnimationTargetKey];
938     [dict2 setObject:NSViewAnimationFadeInEffect forKey:NSViewAnimationEffectKey];
939
940     o_fullscreen_anim2 = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObjects:dict2, nil]];
941     [dict2 release];
942
943     [o_fullscreen_anim2 setAnimationBlockingMode: NSAnimationNonblocking];
944     [o_fullscreen_anim2 setDuration: 0.3];
945     [o_fullscreen_anim2 setFrameRate: 30];
946
947     [o_fullscreen_anim2 setDelegate: self];
948
949     dict1 = [[NSMutableDictionary alloc] initWithCapacity:3];
950
951     [dict1 setObject:o_fullscreen_window forKey:NSViewAnimationTargetKey];
952     [dict1 setObject:[NSValue valueWithRect:[o_fullscreen_window frame]] forKey:NSViewAnimationStartFrameKey];
953     [dict1 setObject:[NSValue valueWithRect:frame] forKey:NSViewAnimationEndFrameKey];
954
955     o_fullscreen_anim1 = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObjects:dict1, nil]];
956     [dict1 release];
957
958     [o_fullscreen_anim1 setAnimationBlockingMode: NSAnimationNonblocking];
959     [o_fullscreen_anim1 setDuration: 0.2];
960     [o_fullscreen_anim1 setFrameRate: 30];
961     [o_fullscreen_anim2 startWhenAnimation: o_fullscreen_anim1 reachesProgress: 1.0];
962
963     /* Make sure o_fullscreen_window is the frontmost window */
964     [o_fullscreen_window orderFront: self];
965
966     [o_fullscreen_anim1 startAnimation];
967     /* fullscreenAnimation will be unlocked when animation ends */
968 }
969
970 - (void)hasEndedFullscreen
971 {
972     [self setFullscreen:NO];
973
974     /* This function is private and should be only triggered at the end of the fullscreen change animation */
975     /* Make sure we don't see the o_video_view disappearing of the screen during this operation */
976     NSDisableScreenUpdates();
977     [o_video_view retain];
978     [o_video_view removeFromSuperviewWithoutNeedingDisplay];
979     [[o_temp_view superview] replaceSubview:o_temp_view with:o_video_view];
980     [o_video_view release];
981     [o_video_view setFrame:[o_temp_view frame]];
982     if ([[o_video_view subviews] count] > 0)
983         [self makeFirstResponder: [[o_video_view subviews] objectAtIndex:0]];
984
985     [super makeKeyAndOrderFront:self]; /* our version (in main window) contains a workaround */
986
987     [o_fullscreen_window orderOut: self];
988     NSEnableScreenUpdates();
989
990     [o_fullscreen_window release];
991     o_fullscreen_window = nil;
992     
993     [[[VLCMain sharedInstance] voutController] updateWindowLevelForHelperWindows: i_originalLevel];
994     [self setLevel:i_originalLevel];
995     [self setAlphaValue: config_GetFloat(VLCIntf, "macosx-opaqueness")];
996
997     // if we quit fullscreen because there is no video anymore, make sure non-embedded window is not visible
998     if (![[VLCMain sharedInstance] activeVideoPlayback] && [self class] != [VLCMainWindow class])
999         [self orderOut: self];
1000
1001     [self unlockFullscreenAnimation];
1002 }
1003
1004 - (void)animationDidEnd:(NSAnimation*)animation
1005 {
1006     NSArray *viewAnimations;
1007     if (o_makekey_anim == animation) {
1008         [o_makekey_anim release];
1009         return;
1010     }
1011     if ([animation currentValue] < 1.0)
1012         return;
1013
1014     /* Fullscreen ended or started (we are a delegate only for leaveFullscreen's/enterFullscren's anim2) */
1015     viewAnimations = [o_fullscreen_anim2 viewAnimations];
1016     if ([viewAnimations count] >=1 &&
1017         [[[viewAnimations objectAtIndex: 0] objectForKey: NSViewAnimationEffectKey] isEqualToString:NSViewAnimationFadeInEffect]) {
1018         /* Fullscreen ended */
1019         [self hasEndedFullscreen];
1020     } else
1021     /* Fullscreen started */
1022         [self hasBecomeFullscreen];
1023 }
1024
1025 #pragma mark -
1026 #pragma mark Accessibility stuff
1027
1028 - (NSArray *)accessibilityAttributeNames
1029 {
1030     if (!b_dark_interface || !o_titlebar_view)
1031         return [super accessibilityAttributeNames];
1032
1033     static NSMutableArray *attributes = nil;
1034     if (attributes == nil) {
1035         attributes = [[super accessibilityAttributeNames] mutableCopy];
1036         NSArray *appendAttributes = [NSArray arrayWithObjects: NSAccessibilitySubroleAttribute,
1037                                      NSAccessibilityCloseButtonAttribute,
1038                                      NSAccessibilityMinimizeButtonAttribute,
1039                                      NSAccessibilityZoomButtonAttribute,
1040                                      nil];
1041
1042         for(NSString *attribute in appendAttributes) {
1043             if (![attributes containsObject:attribute])
1044                 [attributes addObject:attribute];
1045         }
1046     }
1047     return attributes;
1048 }
1049
1050 - (id)accessibilityAttributeValue: (NSString*)o_attribute_name
1051 {
1052     if (b_dark_interface && o_titlebar_view) {
1053         VLCMainWindowTitleView *o_tbv = o_titlebar_view;
1054
1055         if ([o_attribute_name isEqualTo: NSAccessibilitySubroleAttribute])
1056             return NSAccessibilityStandardWindowSubrole;
1057
1058         if ([o_attribute_name isEqualTo: NSAccessibilityCloseButtonAttribute])
1059             return [[o_tbv closeButton] cell];
1060
1061         if ([o_attribute_name isEqualTo: NSAccessibilityMinimizeButtonAttribute])
1062             return [[o_tbv minimizeButton] cell];
1063
1064         if ([o_attribute_name isEqualTo: NSAccessibilityZoomButtonAttribute])
1065             return [[o_tbv zoomButton] cell];
1066     }
1067
1068     return [super accessibilityAttributeValue: o_attribute_name];
1069 }
1070
1071 @end