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