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