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