]> git.sesse.net Git - vlc/blob - modules/gui/macosx/Windows.m
macosx: only hide dock and menu bar when necessary
[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 @implementation VLCVideoWindowCommon
232
233 @synthesize videoView=o_video_view;
234 @synthesize controlsBar=o_controls_bar;
235
236 #pragma mark -
237 #pragma mark Init
238
239 - (id)initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)styleMask
240                   backing:(NSBackingStoreType)backingType defer:(BOOL)flag
241 {
242     b_dark_interface = config_GetInt(VLCIntf, "macosx-interfacestyle");
243
244     if (b_dark_interface) {
245         styleMask = NSBorderlessWindowMask;
246 #ifdef MAC_OS_X_VERSION_10_7
247         if (!OSX_SNOW_LEOPARD)
248             styleMask |= NSResizableWindowMask;
249 #endif
250     }
251
252     self = [super initWithContentRect:contentRect styleMask:styleMask
253                               backing:backingType defer:flag];
254
255     /* we want to be moveable regardless of our style */
256     [self setMovableByWindowBackground: YES];
257     [self setCanBecomeKeyWindow:YES];
258
259     o_temp_view = [[NSView alloc] init];
260     [o_temp_view setAutoresizingMask:NSViewHeightSizable | NSViewWidthSizable];
261
262     return self;
263 }
264
265 - (void)dealloc
266 {
267     [o_temp_view release];
268     [super dealloc];
269 }
270
271 - (void)setTitle:(NSString *)title
272 {
273     if (!title || [title length] < 1)
274         return;
275
276     if (b_dark_interface && o_titlebar_view)
277         [o_titlebar_view setWindowTitle: title];
278
279     [super setTitle: title];
280 }
281
282 #pragma mark -
283 #pragma mark zoom / minimize / close
284
285 - (BOOL)validateMenuItem:(NSMenuItem *)menuItem
286 {
287     SEL s_menuAction = [menuItem action];
288
289     if ((s_menuAction == @selector(performClose:)) || (s_menuAction == @selector(performMiniaturize:)) || (s_menuAction == @selector(performZoom:)))
290         return YES;
291
292     return [super validateMenuItem:menuItem];
293 }
294
295 - (BOOL)windowShouldClose:(id)sender
296 {
297     return YES;
298 }
299
300 - (void)performClose:(id)sender
301 {
302     if (!([self styleMask] & NSTitledWindowMask)) {
303         [[NSNotificationCenter defaultCenter] postNotificationName:NSWindowWillCloseNotification object:self];
304
305         [self orderOut: sender];
306     } else
307         [super performClose: sender];
308 }
309
310 - (void)performMiniaturize:(id)sender
311 {
312     if (!([self styleMask] & NSTitledWindowMask))
313         [self miniaturize: sender];
314     else
315         [super performMiniaturize: sender];
316 }
317
318 - (void)performZoom:(id)sender
319 {
320     if (!([self styleMask] & NSTitledWindowMask))
321         [self customZoom: sender];
322     else
323         [super performZoom: sender];
324 }
325
326 - (void)zoom:(id)sender
327 {
328     if (!([self styleMask] & NSTitledWindowMask))
329         [self customZoom: sender];
330     else
331         [super zoom: sender];
332 }
333
334 /**
335  * Given a proposed frame rectangle, return a modified version
336  * which will fit inside the screen.
337  *
338  * This method is based upon NSWindow.m, part of the GNUstep GUI Library, licensed under LGPLv2+.
339  *    Authors:  Scott Christley <scottc@net-community.com>, Venkat Ajjanagadde <venkat@ocbi.com>,
340  *              Felipe A. Rodriguez <far@ix.netcom.com>, Richard Frith-Macdonald <richard@brainstorm.co.uk>
341  *    Copyright (C) 1996 Free Software Foundation, Inc.
342  */
343 - (NSRect) customConstrainFrameRect: (NSRect)frameRect toScreen: (NSScreen*)screen
344 {
345     NSRect screenRect = [screen visibleFrame];
346     float difference;
347
348     /* Move top edge of the window inside the screen */
349     difference = NSMaxY (frameRect) - NSMaxY (screenRect);
350     if (difference > 0) {
351         frameRect.origin.y -= difference;
352     }
353
354     /* If the window is resizable, resize it (if needed) so that the
355      bottom edge is on the screen or can be on the screen when the user moves
356      the window */
357     difference = NSMaxY (screenRect) - NSMaxY (frameRect);
358     if (_styleMask & NSResizableWindowMask) {
359         float difference2;
360
361         difference2 = screenRect.origin.y - frameRect.origin.y;
362         difference2 -= difference;
363         // Take in account the space between the top of window and the top of the
364         // screen which can be used to move the bottom of the window on the screen
365         if (difference2 > 0) {
366             frameRect.size.height -= difference2;
367             frameRect.origin.y += difference2;
368         }
369
370         /* Ensure that resizing doesn't makewindow smaller than minimum */
371         difference2 = [self minSize].height - frameRect.size.height;
372         if (difference2 > 0) {
373             frameRect.size.height += difference2;
374             frameRect.origin.y -= difference2;
375         }
376     }
377
378     return frameRect;
379 }
380
381 #define DIST 3
382
383 /**
384  Zooms the receiver.   This method calls the delegate method
385  windowShouldZoom:toFrame: to determine if the window should
386  be allowed to zoom to full screen.
387  *
388  * This method is based upon NSWindow.m, part of the GNUstep GUI Library, licensed under LGPLv2+.
389  *    Authors:  Scott Christley <scottc@net-community.com>, Venkat Ajjanagadde <venkat@ocbi.com>,
390  *              Felipe A. Rodriguez <far@ix.netcom.com>, Richard Frith-Macdonald <richard@brainstorm.co.uk>
391  *    Copyright (C) 1996 Free Software Foundation, Inc.
392  */
393 - (void) customZoom: (id)sender
394 {
395     NSRect maxRect = [[self screen] visibleFrame];
396     NSRect currentFrame = [self frame];
397
398     if ([[self delegate] respondsToSelector: @selector(windowWillUseStandardFrame:defaultFrame:)]) {
399         maxRect = [[self delegate] windowWillUseStandardFrame: self defaultFrame: maxRect];
400     }
401
402     maxRect = [self customConstrainFrameRect: maxRect toScreen: [self screen]];
403
404     // Compare the new frame with the current one
405     if ((abs(NSMaxX(maxRect) - NSMaxX(currentFrame)) < DIST)
406         && (abs(NSMaxY(maxRect) - NSMaxY(currentFrame)) < DIST)
407         && (abs(NSMinX(maxRect) - NSMinX(currentFrame)) < DIST)
408         && (abs(NSMinY(maxRect) - NSMinY(currentFrame)) < DIST)) {
409         // Already in zoomed mode, reset user frame, if stored
410         if ([self frameAutosaveName] != nil) {
411             [self setFrame: previousSavedFrame display: YES animate: YES];
412             [self saveFrameUsingName: [self frameAutosaveName]];
413         }
414         return;
415     }
416
417     if ([self frameAutosaveName] != nil) {
418         [self saveFrameUsingName: [self frameAutosaveName]];
419         previousSavedFrame = [self frame];
420     }
421
422     [self setFrame: maxRect display: YES animate: YES];
423 }
424
425 #pragma mark -
426 #pragma mark Video window resizing logic
427
428 - (NSRect)getWindowRectForProposedVideoViewSize:(NSSize)size
429 {
430     NSSize windowMinSize = [self minSize];
431     NSRect screenFrame = [[self screen] visibleFrame];
432
433     NSPoint topleftbase = NSMakePoint(0, [self frame].size.height);
434     NSPoint topleftscreen = [self convertBaseToScreen: topleftbase];
435
436     unsigned int i_width = size.width;
437     unsigned int i_height = size.height;
438     if (i_width < windowMinSize.width)
439         i_width = windowMinSize.width;
440     if (i_height < f_min_video_height)
441         i_height = f_min_video_height;
442
443     /* Calculate the window's new size */
444     NSRect new_frame;
445     new_frame.size.width = [self frame].size.width - [o_video_view frame].size.width + i_width;
446     new_frame.size.height = [self frame].size.height - [o_video_view frame].size.height + i_height;
447     new_frame.origin.x = topleftscreen.x;
448     new_frame.origin.y = topleftscreen.y - new_frame.size.height;
449
450     /* make sure the window doesn't exceed the screen size the window is on */
451     if (new_frame.size.width > screenFrame.size.width) {
452         new_frame.size.width = screenFrame.size.width;
453         new_frame.origin.x = screenFrame.origin.x;
454     }
455     if (new_frame.size.height > screenFrame.size.height) {
456         new_frame.size.height = screenFrame.size.height;
457         new_frame.origin.y = screenFrame.origin.y;
458     }
459     if (new_frame.origin.y < screenFrame.origin.y)
460         new_frame.origin.y = screenFrame.origin.y;
461
462     CGFloat right_screen_point = screenFrame.origin.x + screenFrame.size.width;
463     CGFloat right_window_point = new_frame.origin.x + new_frame.size.width;
464     if (right_window_point > right_screen_point)
465         new_frame.origin.x -= (right_window_point - right_screen_point);
466
467     return new_frame;
468 }
469
470 - (void)resizeWindow
471 {
472     if ([[VLCMainWindow sharedInstance] fullscreen])
473         return;
474
475     NSRect window_rect = [self getWindowRectForProposedVideoViewSize:nativeVideoSize];
476     [[self animator] setFrame:window_rect display:YES];
477 }
478
479 - (void)setNativeVideoSize:(NSSize)size
480 {
481     nativeVideoSize = size;
482
483     if (var_InheritBool(VLCIntf, "macosx-video-autoresize") && !var_InheritBool(VLCIntf, "video-wallpaper"))
484         [self resizeWindow];
485 }
486
487 - (NSSize)windowWillResize:(NSWindow *)window toSize:(NSSize)proposedFrameSize
488 {
489     if (![[VLCMain sharedInstance] activeVideoPlayback] || nativeVideoSize.width == 0. || nativeVideoSize.height == 0. || window != self)
490         return proposedFrameSize;
491
492     // needed when entering lion fullscreen mode
493     if ([[VLCMainWindow sharedInstance] fullscreen])
494         return proposedFrameSize;
495
496     if ([[VLCCoreInteraction sharedInstance] aspectRatioIsLocked]) {
497         NSRect videoWindowFrame = [self frame];
498         NSRect viewRect = [o_video_view convertRect:[o_video_view bounds] toView: nil];
499         NSRect contentRect = [self contentRectForFrameRect:videoWindowFrame];
500         float marginy = viewRect.origin.y + videoWindowFrame.size.height - contentRect.size.height;
501         float marginx = contentRect.size.width - viewRect.size.width;
502         if (o_titlebar_view && b_dark_interface)
503             marginy += [o_titlebar_view frame].size.height;
504
505         proposedFrameSize.height = (proposedFrameSize.width - marginx) * nativeVideoSize.height / nativeVideoSize.width + marginy;
506     }
507
508     return proposedFrameSize;
509 }
510
511 #pragma mark -
512 #pragma mark Fullscreen Logic
513
514 - (void)lockFullscreenAnimation
515 {
516     [o_animation_lock lock];
517 }
518
519 - (void)unlockFullscreenAnimation
520 {
521     [o_animation_lock unlock];
522 }
523
524 - (void)enterFullscreen
525 {
526     NSMutableDictionary *dict1, *dict2;
527     NSScreen *screen;
528     NSRect screen_rect;
529     NSRect rect;
530     BOOL blackout_other_displays = var_InheritBool(VLCIntf, "macosx-black");
531
532     screen = [NSScreen screenWithDisplayID:(CGDirectDisplayID)var_InheritInteger(VLCIntf, "macosx-vdev")];
533     [self lockFullscreenAnimation];
534
535     if (!screen) {
536         msg_Dbg(VLCIntf, "chosen screen isn't present, using current screen for fullscreen mode");
537         screen = [self screen];
538     }
539     if (!screen) {
540         msg_Dbg(VLCIntf, "Using deepest screen");
541         screen = [NSScreen deepestScreen];
542     }
543
544     screen_rect = [screen frame];
545
546     if (o_controls_bar)
547         [o_controls_bar setFullscreenState:YES];
548     [[[VLCMainWindow sharedInstance] controlsBar] setFullscreenState:YES];
549
550     [[VLCMainWindow sharedInstance] recreateHideMouseTimer];
551
552     if (blackout_other_displays)
553         [screen blackoutOtherScreens];
554
555     /* Make sure we don't see the window flashes in float-on-top mode */
556     i_originalLevel = [self level];
557     [self setLevel:NSNormalWindowLevel];
558
559     /* Only create the o_fullscreen_window if we are not in the middle of the zooming animation */
560     if (!o_fullscreen_window) {
561         /* We can't change the styleMask of an already created NSWindow, so we create another window, and do eye catching stuff */
562
563         rect = [[o_video_view superview] convertRect: [o_video_view frame] toView: nil]; /* Convert to Window base coord */
564         rect.origin.x += [self frame].origin.x;
565         rect.origin.y += [self frame].origin.y;
566         o_fullscreen_window = [[VLCWindow alloc] initWithContentRect:rect styleMask: NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:YES];
567         [o_fullscreen_window setBackgroundColor: [NSColor blackColor]];
568         [o_fullscreen_window setCanBecomeKeyWindow: YES];
569         [o_fullscreen_window setCanBecomeMainWindow: YES];
570
571         if (![self isVisible] || [self alphaValue] == 0.0) {
572             /* We don't animate if we are not visible, instead we
573              * simply fade the display */
574             CGDisplayFadeReservationToken token;
575
576             if (blackout_other_displays) {
577                 CGAcquireDisplayFadeReservation(kCGMaxDisplayReservationInterval, &token);
578                 CGDisplayFade(token, 0.5, kCGDisplayBlendNormal, kCGDisplayBlendSolidColor, 0, 0, 0, YES);
579             }
580
581             NSApplicationPresentationOptions presentationOpts = [NSApp presentationOptions];
582             if ([screen hasMenuBar])
583                 presentationOpts |= NSApplicationPresentationAutoHideMenuBar;
584             if ([screen hasMenuBar] || [screen hasDock])
585                 presentationOpts |= NSApplicationPresentationAutoHideDock;
586             [NSApp setPresentationOptions:presentationOpts];
587
588             [[o_video_view superview] replaceSubview:o_video_view with:o_temp_view];
589             [o_temp_view setFrame:[o_video_view frame]];
590             [o_fullscreen_window setContentView:o_video_view];
591
592             [o_fullscreen_window makeKeyAndOrderFront:self];
593             [o_fullscreen_window orderFront:self animate:YES];
594
595             [o_fullscreen_window setFrame:screen_rect display:YES animate:YES];
596             [o_fullscreen_window setLevel:NSNormalWindowLevel];
597
598             if (blackout_other_displays) {
599                 CGDisplayFade(token, 0.3, kCGDisplayBlendSolidColor, kCGDisplayBlendNormal, 0, 0, 0, NO);
600                 CGReleaseDisplayFadeReservation(token);
601             }
602
603             /* Will release the lock */
604             [self hasBecomeFullscreen];
605
606             return;
607         }
608
609         /* Make sure we don't see the o_video_view disappearing of the screen during this operation */
610         NSDisableScreenUpdates();
611         [[o_video_view superview] replaceSubview:o_video_view with:o_temp_view];
612         [o_temp_view setFrame:[o_video_view frame]];
613         [o_fullscreen_window setContentView:o_video_view];
614         [o_fullscreen_window makeKeyAndOrderFront:self];
615         NSEnableScreenUpdates();
616     }
617
618     /* We are in fullscreen (and no animation is running) */
619     if ([[VLCMainWindow sharedInstance] fullscreen]) {
620         /* Make sure we are hidden */
621         [self orderOut: self];
622
623         [self unlockFullscreenAnimation];
624         return;
625     }
626
627     if (o_fullscreen_anim1) {
628         [o_fullscreen_anim1 stopAnimation];
629         [o_fullscreen_anim1 release];
630     }
631     if (o_fullscreen_anim2) {
632         [o_fullscreen_anim2 stopAnimation];
633         [o_fullscreen_anim2 release];
634     }
635
636     NSApplicationPresentationOptions presentationOpts = [NSApp presentationOptions];
637     if ([screen hasMenuBar])
638         presentationOpts |= NSApplicationPresentationAutoHideMenuBar;
639     if ([screen hasMenuBar] || [screen hasDock])
640         presentationOpts |= NSApplicationPresentationAutoHideDock;
641     [NSApp setPresentationOptions:presentationOpts];
642
643     dict1 = [[NSMutableDictionary alloc] initWithCapacity:2];
644     dict2 = [[NSMutableDictionary alloc] initWithCapacity:3];
645
646     [dict1 setObject:self forKey:NSViewAnimationTargetKey];
647     [dict1 setObject:NSViewAnimationFadeOutEffect forKey:NSViewAnimationEffectKey];
648
649     [dict2 setObject:o_fullscreen_window forKey:NSViewAnimationTargetKey];
650     [dict2 setObject:[NSValue valueWithRect:[o_fullscreen_window frame]] forKey:NSViewAnimationStartFrameKey];
651     [dict2 setObject:[NSValue valueWithRect:screen_rect] forKey:NSViewAnimationEndFrameKey];
652
653     /* Strategy with NSAnimation allocation:
654      - Keep at most 2 animation at a time
655      - leaveFullscreen/enterFullscreen are the only responsible for releasing and alloc-ing
656      */
657     o_fullscreen_anim1 = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObject:dict1]];
658     o_fullscreen_anim2 = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObject:dict2]];
659
660     [dict1 release];
661     [dict2 release];
662
663     [o_fullscreen_anim1 setAnimationBlockingMode: NSAnimationNonblocking];
664     [o_fullscreen_anim1 setDuration: 0.3];
665     [o_fullscreen_anim1 setFrameRate: 30];
666     [o_fullscreen_anim2 setAnimationBlockingMode: NSAnimationNonblocking];
667     [o_fullscreen_anim2 setDuration: 0.2];
668     [o_fullscreen_anim2 setFrameRate: 30];
669
670     [o_fullscreen_anim2 setDelegate: self];
671     [o_fullscreen_anim2 startWhenAnimation: o_fullscreen_anim1 reachesProgress: 1.0];
672
673     [o_fullscreen_anim1 startAnimation];
674     /* fullscreenAnimation will be unlocked when animation ends */
675 }
676
677 - (void)hasBecomeFullscreen
678 {
679     if ([[o_video_view subviews] count] > 0)
680         [o_fullscreen_window makeFirstResponder: [[o_video_view subviews] objectAtIndex:0]];
681
682     [o_fullscreen_window makeKeyWindow];
683     [o_fullscreen_window setAcceptsMouseMovedEvents: YES];
684
685     /* tell the fspanel to move itself to front next time it's triggered */
686     [[[VLCMainWindow sharedInstance] fsPanel] setVoutWasUpdated: o_fullscreen_window];
687     [[[VLCMainWindow sharedInstance] fsPanel] setActive: nil];
688
689     if ([self isVisible])
690         [self orderOut: self];
691
692     [[VLCMainWindow sharedInstance] setFullscreen:YES];
693     [self unlockFullscreenAnimation];
694 }
695
696 - (void)leaveFullscreen
697 {
698     [self leaveFullscreenAndFadeOut: NO];
699 }
700
701 - (void)leaveFullscreenAndFadeOut: (BOOL)fadeout
702 {
703     NSMutableDictionary *dict1, *dict2;
704     NSRect frame;
705     BOOL blackout_other_displays = var_InheritBool(VLCIntf, "macosx-black");
706
707     [self lockFullscreenAnimation];
708
709     if (o_controls_bar)
710         [o_controls_bar setFullscreenState:NO];
711     [[[VLCMainWindow sharedInstance] controlsBar] setFullscreenState:NO];
712
713     /* We always try to do so */
714     [NSScreen unblackoutScreens];
715
716     vout_thread_t *p_vout = getVoutForActiveWindow();
717     if (p_vout) {
718         if (var_GetBool(p_vout, "video-on-top"))
719             [[o_video_view window] setLevel: NSStatusWindowLevel];
720         else
721             [[o_video_view window] setLevel: NSNormalWindowLevel];
722         vlc_object_release(p_vout);
723     }
724     [[o_video_view window] makeKeyAndOrderFront: nil];
725
726     /* Don't do anything if o_fullscreen_window is already closed */
727     if (!o_fullscreen_window) {
728         [self unlockFullscreenAnimation];
729         return;
730     }
731
732     if (fadeout) {
733         /* We don't animate if we are not visible, instead we
734          * simply fade the display */
735         CGDisplayFadeReservationToken token;
736
737         if (blackout_other_displays) {
738             CGAcquireDisplayFadeReservation(kCGMaxDisplayReservationInterval, &token);
739             CGDisplayFade(token, 0.3, kCGDisplayBlendNormal, kCGDisplayBlendSolidColor, 0, 0, 0, YES);
740         }
741
742         [[[VLCMainWindow sharedInstance] fsPanel] setNonActive: nil];
743         [NSApp setPresentationOptions: NSApplicationPresentationDefault];
744
745         /* Will release the lock */
746         [self hasEndedFullscreen];
747
748         /* Our window is hidden, and might be faded. We need to workaround that, so note it
749          * here */
750         b_window_is_invisible = YES;
751
752         if (blackout_other_displays) {
753             CGDisplayFade(token, 0.5, kCGDisplayBlendSolidColor, kCGDisplayBlendNormal, 0, 0, 0, NO);
754             CGReleaseDisplayFadeReservation(token);
755         }
756
757         return;
758     }
759
760     [self setAlphaValue: 0.0];
761     [self orderFront: self];
762     [[o_video_view window] orderFront: self];
763
764     [[[VLCMainWindow sharedInstance] fsPanel] setNonActive: nil];
765     [NSApp setPresentationOptions:(NSApplicationPresentationDefault)];
766
767     if (o_fullscreen_anim1) {
768         [o_fullscreen_anim1 stopAnimation];
769         [o_fullscreen_anim1 release];
770     }
771     if (o_fullscreen_anim2) {
772         [o_fullscreen_anim2 stopAnimation];
773         [o_fullscreen_anim2 release];
774     }
775
776     frame = [[o_temp_view superview] convertRect: [o_temp_view frame] toView: nil]; /* Convert to Window base coord */
777     frame.origin.x += [self frame].origin.x;
778     frame.origin.y += [self frame].origin.y;
779
780     dict2 = [[NSMutableDictionary alloc] initWithCapacity:2];
781     [dict2 setObject:self forKey:NSViewAnimationTargetKey];
782     [dict2 setObject:NSViewAnimationFadeInEffect forKey:NSViewAnimationEffectKey];
783
784     o_fullscreen_anim2 = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObjects:dict2, nil]];
785     [dict2 release];
786
787     [o_fullscreen_anim2 setAnimationBlockingMode: NSAnimationNonblocking];
788     [o_fullscreen_anim2 setDuration: 0.3];
789     [o_fullscreen_anim2 setFrameRate: 30];
790
791     [o_fullscreen_anim2 setDelegate: self];
792
793     dict1 = [[NSMutableDictionary alloc] initWithCapacity:3];
794
795     [dict1 setObject:o_fullscreen_window forKey:NSViewAnimationTargetKey];
796     [dict1 setObject:[NSValue valueWithRect:[o_fullscreen_window frame]] forKey:NSViewAnimationStartFrameKey];
797     [dict1 setObject:[NSValue valueWithRect:frame] forKey:NSViewAnimationEndFrameKey];
798
799     o_fullscreen_anim1 = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObjects:dict1, nil]];
800     [dict1 release];
801
802     [o_fullscreen_anim1 setAnimationBlockingMode: NSAnimationNonblocking];
803     [o_fullscreen_anim1 setDuration: 0.2];
804     [o_fullscreen_anim1 setFrameRate: 30];
805     [o_fullscreen_anim2 startWhenAnimation: o_fullscreen_anim1 reachesProgress: 1.0];
806
807     /* Make sure o_fullscreen_window is the frontmost window */
808     [o_fullscreen_window orderFront: self];
809
810     [o_fullscreen_anim1 startAnimation];
811     /* fullscreenAnimation will be unlocked when animation ends */
812 }
813
814 - (void)hasEndedFullscreen
815 {
816     [[VLCMainWindow sharedInstance] setFullscreen:NO];
817
818     /* This function is private and should be only triggered at the end of the fullscreen change animation */
819     /* Make sure we don't see the o_video_view disappearing of the screen during this operation */
820     NSDisableScreenUpdates();
821     [o_video_view retain];
822     [o_video_view removeFromSuperviewWithoutNeedingDisplay];
823     [[o_temp_view superview] replaceSubview:o_temp_view with:o_video_view];
824     [o_video_view release];
825     [o_video_view setFrame:[o_temp_view frame]];
826     if ([[o_video_view subviews] count] > 0)
827         [self makeFirstResponder: [[o_video_view subviews] objectAtIndex:0]];
828
829     [super makeKeyAndOrderFront:self]; /* our version (in main window) contains a workaround */
830
831     [o_fullscreen_window orderOut: self];
832     NSEnableScreenUpdates();
833
834     [o_fullscreen_window release];
835     o_fullscreen_window = nil;
836     [self setLevel:i_originalLevel];
837     [self setAlphaValue: config_GetFloat(VLCIntf, "macosx-opaqueness")];
838
839     // if we quit fullscreen because there is no video anymore, make sure non-embedded window is not visible
840     if (![[VLCMain sharedInstance] activeVideoPlayback] && [self class] != [VLCMainWindow class])
841         [self orderOut: self];
842
843     [self unlockFullscreenAnimation];
844 }
845
846 - (void)animationDidEnd:(NSAnimation*)animation
847 {
848     NSArray *viewAnimations;
849     if (o_makekey_anim == animation) {
850         [o_makekey_anim release];
851         return;
852     }
853     if ([animation currentValue] < 1.0)
854         return;
855
856     /* Fullscreen ended or started (we are a delegate only for leaveFullscreen's/enterFullscren's anim2) */
857     viewAnimations = [o_fullscreen_anim2 viewAnimations];
858     if ([viewAnimations count] >=1 &&
859         [[[viewAnimations objectAtIndex: 0] objectForKey: NSViewAnimationEffectKey] isEqualToString:NSViewAnimationFadeInEffect]) {
860         /* Fullscreen ended */
861         [self hasEndedFullscreen];
862     } else
863     /* Fullscreen started */
864         [self hasBecomeFullscreen];
865 }
866
867 #pragma mark -
868 #pragma mark Accessibility stuff
869
870 - (NSArray *)accessibilityAttributeNames
871 {
872     if (!b_dark_interface || !o_titlebar_view)
873         return [super accessibilityAttributeNames];
874
875     static NSMutableArray *attributes = nil;
876     if (attributes == nil) {
877         attributes = [[super accessibilityAttributeNames] mutableCopy];
878         NSArray *appendAttributes = [NSArray arrayWithObjects: NSAccessibilitySubroleAttribute,
879                                      NSAccessibilityCloseButtonAttribute,
880                                      NSAccessibilityMinimizeButtonAttribute,
881                                      NSAccessibilityZoomButtonAttribute,
882                                      nil];
883
884         for(NSString *attribute in appendAttributes) {
885             if (![attributes containsObject:attribute])
886                 [attributes addObject:attribute];
887         }
888     }
889     return attributes;
890 }
891
892 - (id)accessibilityAttributeValue: (NSString*)o_attribute_name
893 {
894     if (b_dark_interface && o_titlebar_view) {
895         VLCMainWindowTitleView *o_tbv = o_titlebar_view;
896
897         if ([o_attribute_name isEqualTo: NSAccessibilitySubroleAttribute])
898             return NSAccessibilityStandardWindowSubrole;
899
900         if ([o_attribute_name isEqualTo: NSAccessibilityCloseButtonAttribute])
901             return [[o_tbv closeButton] cell];
902
903         if ([o_attribute_name isEqualTo: NSAccessibilityMinimizeButtonAttribute])
904             return [[o_tbv minimizeButton] cell];
905
906         if ([o_attribute_name isEqualTo: NSAccessibilityZoomButtonAttribute])
907             return [[o_tbv zoomButton] cell];
908     }
909
910     return [super accessibilityAttributeValue: o_attribute_name];
911 }
912
913 @end