]> git.sesse.net Git - vlc/blob - modules/gui/macosx/Windows.m
macosx: fix fullscreen button state in mainwindow
[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     [[[VLCMainWindow sharedInstance] controlsBar] setFullscreenState:YES];
546
547     [[VLCMainWindow sharedInstance] recreateHideMouseTimer];
548
549     if (blackout_other_displays)
550         [screen blackoutOtherScreens];
551
552     /* Make sure we don't see the window flashes in float-on-top mode */
553     i_originalLevel = [self level];
554     [self setLevel:NSNormalWindowLevel];
555
556     /* Only create the o_fullscreen_window if we are not in the middle of the zooming animation */
557     if (!o_fullscreen_window) {
558         /* We can't change the styleMask of an already created NSWindow, so we create another window, and do eye catching stuff */
559
560         rect = [[o_video_view superview] convertRect: [o_video_view frame] toView: nil]; /* Convert to Window base coord */
561         rect.origin.x += [self frame].origin.x;
562         rect.origin.y += [self frame].origin.y;
563         o_fullscreen_window = [[VLCWindow alloc] initWithContentRect:rect styleMask: NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:YES];
564         [o_fullscreen_window setBackgroundColor: [NSColor blackColor]];
565         [o_fullscreen_window setCanBecomeKeyWindow: YES];
566         [o_fullscreen_window setCanBecomeMainWindow: YES];
567
568         if (![self isVisible] || [self alphaValue] == 0.0) {
569             /* We don't animate if we are not visible, instead we
570              * simply fade the display */
571             CGDisplayFadeReservationToken token;
572
573             if (blackout_other_displays) {
574                 CGAcquireDisplayFadeReservation(kCGMaxDisplayReservationInterval, &token);
575                 CGDisplayFade(token, 0.5, kCGDisplayBlendNormal, kCGDisplayBlendSolidColor, 0, 0, 0, YES);
576             }
577
578             if ([screen mainScreen])
579                 [NSApp setPresentationOptions:(NSApplicationPresentationAutoHideDock | NSApplicationPresentationAutoHideMenuBar)];
580
581             [[o_video_view superview] replaceSubview:o_video_view with:o_temp_view];
582             [o_temp_view setFrame:[o_video_view frame]];
583             [o_fullscreen_window setContentView:o_video_view];
584
585             [o_fullscreen_window makeKeyAndOrderFront:self];
586             [o_fullscreen_window orderFront:self animate:YES];
587
588             [o_fullscreen_window setFrame:screen_rect display:YES animate:YES];
589             [o_fullscreen_window setLevel:NSNormalWindowLevel];
590
591             if (blackout_other_displays) {
592                 CGDisplayFade(token, 0.3, kCGDisplayBlendSolidColor, kCGDisplayBlendNormal, 0, 0, 0, NO);
593                 CGReleaseDisplayFadeReservation(token);
594             }
595
596             /* Will release the lock */
597             [self hasBecomeFullscreen];
598
599             return;
600         }
601
602         /* Make sure we don't see the o_video_view disappearing of the screen during this operation */
603         NSDisableScreenUpdates();
604         [[o_video_view superview] replaceSubview:o_video_view with:o_temp_view];
605         [o_temp_view setFrame:[o_video_view frame]];
606         [o_fullscreen_window setContentView:o_video_view];
607         [o_fullscreen_window makeKeyAndOrderFront:self];
608         NSEnableScreenUpdates();
609     }
610
611     /* We are in fullscreen (and no animation is running) */
612     if ([[VLCMainWindow sharedInstance] fullscreen]) {
613         /* Make sure we are hidden */
614         [self orderOut: self];
615
616         [self unlockFullscreenAnimation];
617         return;
618     }
619
620     if (o_fullscreen_anim1) {
621         [o_fullscreen_anim1 stopAnimation];
622         [o_fullscreen_anim1 release];
623     }
624     if (o_fullscreen_anim2) {
625         [o_fullscreen_anim2 stopAnimation];
626         [o_fullscreen_anim2 release];
627     }
628
629     if ([screen mainScreen])
630         [NSApp setPresentationOptions:(NSApplicationPresentationAutoHideDock | NSApplicationPresentationAutoHideMenuBar)];
631
632     dict1 = [[NSMutableDictionary alloc] initWithCapacity:2];
633     dict2 = [[NSMutableDictionary alloc] initWithCapacity:3];
634
635     [dict1 setObject:self forKey:NSViewAnimationTargetKey];
636     [dict1 setObject:NSViewAnimationFadeOutEffect forKey:NSViewAnimationEffectKey];
637
638     [dict2 setObject:o_fullscreen_window forKey:NSViewAnimationTargetKey];
639     [dict2 setObject:[NSValue valueWithRect:[o_fullscreen_window frame]] forKey:NSViewAnimationStartFrameKey];
640     [dict2 setObject:[NSValue valueWithRect:screen_rect] forKey:NSViewAnimationEndFrameKey];
641
642     /* Strategy with NSAnimation allocation:
643      - Keep at most 2 animation at a time
644      - leaveFullscreen/enterFullscreen are the only responsible for releasing and alloc-ing
645      */
646     o_fullscreen_anim1 = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObject:dict1]];
647     o_fullscreen_anim2 = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObject:dict2]];
648
649     [dict1 release];
650     [dict2 release];
651
652     [o_fullscreen_anim1 setAnimationBlockingMode: NSAnimationNonblocking];
653     [o_fullscreen_anim1 setDuration: 0.3];
654     [o_fullscreen_anim1 setFrameRate: 30];
655     [o_fullscreen_anim2 setAnimationBlockingMode: NSAnimationNonblocking];
656     [o_fullscreen_anim2 setDuration: 0.2];
657     [o_fullscreen_anim2 setFrameRate: 30];
658
659     [o_fullscreen_anim2 setDelegate: self];
660     [o_fullscreen_anim2 startWhenAnimation: o_fullscreen_anim1 reachesProgress: 1.0];
661
662     [o_fullscreen_anim1 startAnimation];
663     /* fullscreenAnimation will be unlocked when animation ends */
664 }
665
666 - (void)hasBecomeFullscreen
667 {
668     if ([[o_video_view subviews] count] > 0)
669         [o_fullscreen_window makeFirstResponder: [[o_video_view subviews] objectAtIndex:0]];
670
671     [o_fullscreen_window makeKeyWindow];
672     [o_fullscreen_window setAcceptsMouseMovedEvents: YES];
673
674     /* tell the fspanel to move itself to front next time it's triggered */
675     [[[VLCMainWindow sharedInstance] fsPanel] setVoutWasUpdated: o_fullscreen_window];
676     [[[VLCMainWindow sharedInstance] fsPanel] setActive: nil];
677
678     if ([self isVisible])
679         [self orderOut: self];
680
681     [[VLCMainWindow sharedInstance] setFullscreen:YES];
682     [self unlockFullscreenAnimation];
683 }
684
685 - (void)leaveFullscreen
686 {
687     [self leaveFullscreenAndFadeOut: NO];
688 }
689
690 - (void)leaveFullscreenAndFadeOut: (BOOL)fadeout
691 {
692     NSMutableDictionary *dict1, *dict2;
693     NSRect frame;
694     BOOL blackout_other_displays = var_InheritBool(VLCIntf, "macosx-black");
695
696     [self lockFullscreenAnimation];
697
698     if (o_controls_bar)
699         [o_controls_bar setFullscreenState:NO];
700     [[[VLCMainWindow sharedInstance] controlsBar] setFullscreenState:NO];
701
702     /* We always try to do so */
703     [NSScreen unblackoutScreens];
704
705     vout_thread_t *p_vout = getVoutForActiveWindow();
706     if (p_vout) {
707         if (var_GetBool(p_vout, "video-on-top"))
708             [[o_video_view window] setLevel: NSStatusWindowLevel];
709         else
710             [[o_video_view window] setLevel: NSNormalWindowLevel];
711         vlc_object_release(p_vout);
712     }
713     [[o_video_view window] makeKeyAndOrderFront: nil];
714
715     /* Don't do anything if o_fullscreen_window is already closed */
716     if (!o_fullscreen_window) {
717         [self unlockFullscreenAnimation];
718         return;
719     }
720
721     if (fadeout) {
722         /* We don't animate if we are not visible, instead we
723          * simply fade the display */
724         CGDisplayFadeReservationToken token;
725
726         if (blackout_other_displays) {
727             CGAcquireDisplayFadeReservation(kCGMaxDisplayReservationInterval, &token);
728             CGDisplayFade(token, 0.3, kCGDisplayBlendNormal, kCGDisplayBlendSolidColor, 0, 0, 0, YES);
729         }
730
731         [[[VLCMainWindow sharedInstance] fsPanel] setNonActive: nil];
732         [NSApp setPresentationOptions: NSApplicationPresentationDefault];
733
734         /* Will release the lock */
735         [self hasEndedFullscreen];
736
737         /* Our window is hidden, and might be faded. We need to workaround that, so note it
738          * here */
739         b_window_is_invisible = YES;
740
741         if (blackout_other_displays) {
742             CGDisplayFade(token, 0.5, kCGDisplayBlendSolidColor, kCGDisplayBlendNormal, 0, 0, 0, NO);
743             CGReleaseDisplayFadeReservation(token);
744         }
745
746         return;
747     }
748
749     [self setAlphaValue: 0.0];
750     [self orderFront: self];
751     [[o_video_view window] orderFront: self];
752
753     [[[VLCMainWindow sharedInstance] fsPanel] setNonActive: nil];
754     [NSApp setPresentationOptions:(NSApplicationPresentationDefault)];
755
756     if (o_fullscreen_anim1) {
757         [o_fullscreen_anim1 stopAnimation];
758         [o_fullscreen_anim1 release];
759     }
760     if (o_fullscreen_anim2) {
761         [o_fullscreen_anim2 stopAnimation];
762         [o_fullscreen_anim2 release];
763     }
764
765     frame = [[o_temp_view superview] convertRect: [o_temp_view frame] toView: nil]; /* Convert to Window base coord */
766     frame.origin.x += [self frame].origin.x;
767     frame.origin.y += [self frame].origin.y;
768
769     dict2 = [[NSMutableDictionary alloc] initWithCapacity:2];
770     [dict2 setObject:self forKey:NSViewAnimationTargetKey];
771     [dict2 setObject:NSViewAnimationFadeInEffect forKey:NSViewAnimationEffectKey];
772
773     o_fullscreen_anim2 = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObjects:dict2, nil]];
774     [dict2 release];
775
776     [o_fullscreen_anim2 setAnimationBlockingMode: NSAnimationNonblocking];
777     [o_fullscreen_anim2 setDuration: 0.3];
778     [o_fullscreen_anim2 setFrameRate: 30];
779
780     [o_fullscreen_anim2 setDelegate: self];
781
782     dict1 = [[NSMutableDictionary alloc] initWithCapacity:3];
783
784     [dict1 setObject:o_fullscreen_window forKey:NSViewAnimationTargetKey];
785     [dict1 setObject:[NSValue valueWithRect:[o_fullscreen_window frame]] forKey:NSViewAnimationStartFrameKey];
786     [dict1 setObject:[NSValue valueWithRect:frame] forKey:NSViewAnimationEndFrameKey];
787
788     o_fullscreen_anim1 = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObjects:dict1, nil]];
789     [dict1 release];
790
791     [o_fullscreen_anim1 setAnimationBlockingMode: NSAnimationNonblocking];
792     [o_fullscreen_anim1 setDuration: 0.2];
793     [o_fullscreen_anim1 setFrameRate: 30];
794     [o_fullscreen_anim2 startWhenAnimation: o_fullscreen_anim1 reachesProgress: 1.0];
795
796     /* Make sure o_fullscreen_window is the frontmost window */
797     [o_fullscreen_window orderFront: self];
798
799     [o_fullscreen_anim1 startAnimation];
800     /* fullscreenAnimation will be unlocked when animation ends */
801 }
802
803 - (void)hasEndedFullscreen
804 {
805     [[VLCMainWindow sharedInstance] setFullscreen:NO];
806     
807     /* This function is private and should be only triggered at the end of the fullscreen change animation */
808     /* Make sure we don't see the o_video_view disappearing of the screen during this operation */
809     NSDisableScreenUpdates();
810     [o_video_view retain];
811     [o_video_view removeFromSuperviewWithoutNeedingDisplay];
812     [[o_temp_view superview] replaceSubview:o_temp_view with:o_video_view];
813     [o_video_view release];
814     [o_video_view setFrame:[o_temp_view frame]];
815     if ([[o_video_view subviews] count] > 0)
816         [self makeFirstResponder: [[o_video_view subviews] objectAtIndex:0]];
817
818     [super makeKeyAndOrderFront:self]; /* our version (in main window) contains a workaround */
819
820     [o_fullscreen_window orderOut: self];
821     NSEnableScreenUpdates();
822
823     [o_fullscreen_window release];
824     o_fullscreen_window = nil;
825     [self setLevel:i_originalLevel];
826     [self setAlphaValue: config_GetFloat(VLCIntf, "macosx-opaqueness")];
827
828     // if we quit fullscreen because there is no video anymore, make sure non-embedded window is not visible
829     if (![[VLCMain sharedInstance] activeVideoPlayback] && [self class] != [VLCMainWindow class])
830         [self orderOut: self];
831
832     [self unlockFullscreenAnimation];
833 }
834
835 - (void)animationDidEnd:(NSAnimation*)animation
836 {
837     NSArray *viewAnimations;
838     if (o_makekey_anim == animation) {
839         [o_makekey_anim release];
840         return;
841     }
842     if ([animation currentValue] < 1.0)
843         return;
844
845     /* Fullscreen ended or started (we are a delegate only for leaveFullscreen's/enterFullscren's anim2) */
846     viewAnimations = [o_fullscreen_anim2 viewAnimations];
847     if ([viewAnimations count] >=1 &&
848         [[[viewAnimations objectAtIndex: 0] objectForKey: NSViewAnimationEffectKey] isEqualToString:NSViewAnimationFadeInEffect]) {
849         /* Fullscreen ended */
850         [self hasEndedFullscreen];
851     } else
852     /* Fullscreen started */
853         [self hasBecomeFullscreen];
854 }
855
856 #pragma mark -
857 #pragma mark Accessibility stuff
858
859 - (NSArray *)accessibilityAttributeNames
860 {
861     if (!b_dark_interface || !o_titlebar_view)
862         return [super accessibilityAttributeNames];
863
864     static NSMutableArray *attributes = nil;
865     if (attributes == nil) {
866         attributes = [[super accessibilityAttributeNames] mutableCopy];
867         NSArray *appendAttributes = [NSArray arrayWithObjects: NSAccessibilitySubroleAttribute,
868                                      NSAccessibilityCloseButtonAttribute,
869                                      NSAccessibilityMinimizeButtonAttribute,
870                                      NSAccessibilityZoomButtonAttribute,
871                                      nil];
872
873         for(NSString *attribute in appendAttributes) {
874             if (![attributes containsObject:attribute])
875                 [attributes addObject:attribute];
876         }
877     }
878     return attributes;
879 }
880
881 - (id)accessibilityAttributeValue: (NSString*)o_attribute_name
882 {
883     if (b_dark_interface && o_titlebar_view) {
884         VLCMainWindowTitleView *o_tbv = o_titlebar_view;
885
886         if ([o_attribute_name isEqualTo: NSAccessibilitySubroleAttribute])
887             return NSAccessibilityStandardWindowSubrole;
888
889         if ([o_attribute_name isEqualTo: NSAccessibilityCloseButtonAttribute])
890             return [[o_tbv closeButton] cell];
891
892         if ([o_attribute_name isEqualTo: NSAccessibilityMinimizeButtonAttribute])
893             return [[o_tbv minimizeButton] cell];
894
895         if ([o_attribute_name isEqualTo: NSAccessibilityZoomButtonAttribute])
896             return [[o_tbv zoomButton] cell];
897     }
898
899     return [super accessibilityAttributeValue: o_attribute_name];
900 }
901
902 @end