]> git.sesse.net Git - vlc/blob - modules/gui/macosx/Windows.m
macosx: implement vout actions handling for multiple vout windows
[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 - (void)resizeWindow
426 {
427     if ([[VLCMainWindow sharedInstance] fullscreen])
428         return;
429
430     NSSize windowMinSize = [self minSize];
431     NSRect screenFrame = [[self screen] visibleFrame];
432
433     NSPoint topleftbase = NSMakePoint(0, [self frame].size.height);
434     NSPoint topleftscreen = [self convertBaseToScreen: topleftbase];
435
436     unsigned int i_width = nativeVideoSize.width;
437     unsigned int i_height = nativeVideoSize.height;
438     if (i_width < windowMinSize.width)
439         i_width = windowMinSize.width;
440     if (i_height < f_min_video_height)
441         i_height = f_min_video_height;
442
443     /* Calculate the window's new size */
444     NSRect new_frame;
445     new_frame.size.width = [self frame].size.width - [o_video_view frame].size.width + i_width;
446     new_frame.size.height = [self frame].size.height - [o_video_view frame].size.height + i_height;
447     new_frame.origin.x = topleftscreen.x;
448     new_frame.origin.y = topleftscreen.y - new_frame.size.height;
449
450     /* make sure the window doesn't exceed the screen size the window is on */
451     if (new_frame.size.width > screenFrame.size.width) {
452         new_frame.size.width = screenFrame.size.width;
453         new_frame.origin.x = screenFrame.origin.x;
454     }
455     if (new_frame.size.height > screenFrame.size.height) {
456         new_frame.size.height = screenFrame.size.height;
457         new_frame.origin.y = screenFrame.origin.y;
458     }
459     if (new_frame.origin.y < screenFrame.origin.y)
460         new_frame.origin.y = screenFrame.origin.y;
461
462     CGFloat right_screen_point = screenFrame.origin.x + screenFrame.size.width;
463     CGFloat right_window_point = new_frame.origin.x + new_frame.size.width;
464     if (right_window_point > right_screen_point)
465         new_frame.origin.x -= (right_window_point - right_screen_point);
466
467     [[self animator] setFrame:new_frame display:YES];
468 }
469
470 - (void)setNativeVideoSize:(NSSize)size
471 {
472     nativeVideoSize = size;
473
474     if (var_InheritBool(VLCIntf, "macosx-video-autoresize") && !var_InheritBool(VLCIntf, "video-wallpaper"))
475         [self resizeWindow];
476 }
477
478 - (NSSize)windowWillResize:(NSWindow *)window toSize:(NSSize)proposedFrameSize
479 {
480     if (![[VLCMain sharedInstance] activeVideoPlayback] || nativeVideoSize.width == 0. || nativeVideoSize.height == 0. || window != self)
481         return proposedFrameSize;
482
483     // needed when entering lion fullscreen mode
484     if ([[VLCMainWindow sharedInstance] fullscreen])
485         return proposedFrameSize;
486
487     if ([[VLCCoreInteraction sharedInstance] aspectRatioIsLocked]) {
488         NSRect videoWindowFrame = [self frame];
489         NSRect viewRect = [o_video_view convertRect:[o_video_view bounds] toView: nil];
490         NSRect contentRect = [self contentRectForFrameRect:videoWindowFrame];
491         float marginy = viewRect.origin.y + videoWindowFrame.size.height - contentRect.size.height;
492         float marginx = contentRect.size.width - viewRect.size.width;
493         if (o_titlebar_view && b_dark_interface)
494             marginy += [o_titlebar_view frame].size.height;
495
496         proposedFrameSize.height = (proposedFrameSize.width - marginx) * nativeVideoSize.height / nativeVideoSize.width + marginy;
497     }
498
499     return proposedFrameSize;
500 }
501
502 #pragma mark -
503 #pragma mark Fullscreen Logic
504
505 - (void)lockFullscreenAnimation
506 {
507     [o_animation_lock lock];
508 }
509
510 - (void)unlockFullscreenAnimation
511 {
512     [o_animation_lock unlock];
513 }
514
515 - (void)enterFullscreen
516 {
517     NSMutableDictionary *dict1, *dict2;
518     NSScreen *screen;
519     NSRect screen_rect;
520     NSRect rect;
521     BOOL blackout_other_displays = var_InheritBool(VLCIntf, "macosx-black");
522
523     screen = [NSScreen screenWithDisplayID:(CGDirectDisplayID)var_InheritInteger(VLCIntf, "macosx-vdev")];
524     [self lockFullscreenAnimation];
525
526     if (!screen) {
527         msg_Dbg(VLCIntf, "chosen screen isn't present, using current screen for fullscreen mode");
528         screen = [self screen];
529     }
530     if (!screen) {
531         msg_Dbg(VLCIntf, "Using deepest screen");
532         screen = [NSScreen deepestScreen];
533     }
534
535     screen_rect = [screen frame];
536
537     if (o_controls_bar)
538         [o_controls_bar setFullscreenState:YES];
539
540     [[VLCMainWindow sharedInstance] recreateHideMouseTimer];
541
542     if (blackout_other_displays)
543         [screen blackoutOtherScreens];
544
545     /* Make sure we don't see the window flashes in float-on-top mode */
546     i_originalLevel = [self level];
547     [self setLevel:NSNormalWindowLevel];
548
549     /* Only create the o_fullscreen_window if we are not in the middle of the zooming animation */
550     if (!o_fullscreen_window) {
551         /* We can't change the styleMask of an already created NSWindow, so we create another window, and do eye catching stuff */
552
553         rect = [[o_video_view superview] convertRect: [o_video_view frame] toView: nil]; /* Convert to Window base coord */
554         rect.origin.x += [self frame].origin.x;
555         rect.origin.y += [self frame].origin.y;
556         o_fullscreen_window = [[VLCWindow alloc] initWithContentRect:rect styleMask: NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:YES];
557         [o_fullscreen_window setBackgroundColor: [NSColor blackColor]];
558         [o_fullscreen_window setCanBecomeKeyWindow: YES];
559         [o_fullscreen_window setCanBecomeMainWindow: YES];
560
561         if (![self isVisible] || [self alphaValue] == 0.0) {
562             /* We don't animate if we are not visible, instead we
563              * simply fade the display */
564             CGDisplayFadeReservationToken token;
565
566             if (blackout_other_displays) {
567                 CGAcquireDisplayFadeReservation(kCGMaxDisplayReservationInterval, &token);
568                 CGDisplayFade(token, 0.5, kCGDisplayBlendNormal, kCGDisplayBlendSolidColor, 0, 0, 0, YES);
569             }
570
571             if ([screen mainScreen])
572                 [NSApp setPresentationOptions:(NSApplicationPresentationAutoHideDock | NSApplicationPresentationAutoHideMenuBar)];
573
574             [[o_video_view superview] replaceSubview:o_video_view with:o_temp_view];
575             [o_temp_view setFrame:[o_video_view frame]];
576             [o_fullscreen_window setContentView:o_video_view];
577
578             [o_fullscreen_window makeKeyAndOrderFront:self];
579             [o_fullscreen_window orderFront:self animate:YES];
580
581             [o_fullscreen_window setFrame:screen_rect display:YES animate:YES];
582             [o_fullscreen_window setLevel:NSNormalWindowLevel];
583
584             if (blackout_other_displays) {
585                 CGDisplayFade(token, 0.3, kCGDisplayBlendSolidColor, kCGDisplayBlendNormal, 0, 0, 0, NO);
586                 CGReleaseDisplayFadeReservation(token);
587             }
588
589             /* Will release the lock */
590             [self hasBecomeFullscreen];
591
592             return;
593         }
594
595         /* Make sure we don't see the o_video_view disappearing of the screen during this operation */
596         NSDisableScreenUpdates();
597         [[o_video_view superview] replaceSubview:o_video_view with:o_temp_view];
598         [o_temp_view setFrame:[o_video_view frame]];
599         [o_fullscreen_window setContentView:o_video_view];
600         [o_fullscreen_window makeKeyAndOrderFront:self];
601         NSEnableScreenUpdates();
602     }
603
604     /* We are in fullscreen (and no animation is running) */
605     if ([[VLCMainWindow sharedInstance] fullscreen]) {
606         /* Make sure we are hidden */
607         [self orderOut: self];
608
609         [self unlockFullscreenAnimation];
610         return;
611     }
612
613     if (o_fullscreen_anim1) {
614         [o_fullscreen_anim1 stopAnimation];
615         [o_fullscreen_anim1 release];
616     }
617     if (o_fullscreen_anim2) {
618         [o_fullscreen_anim2 stopAnimation];
619         [o_fullscreen_anim2 release];
620     }
621
622     if ([screen mainScreen])
623         [NSApp setPresentationOptions:(NSApplicationPresentationAutoHideDock | NSApplicationPresentationAutoHideMenuBar)];
624
625     dict1 = [[NSMutableDictionary alloc] initWithCapacity:2];
626     dict2 = [[NSMutableDictionary alloc] initWithCapacity:3];
627
628     [dict1 setObject:self forKey:NSViewAnimationTargetKey];
629     [dict1 setObject:NSViewAnimationFadeOutEffect forKey:NSViewAnimationEffectKey];
630
631     [dict2 setObject:o_fullscreen_window forKey:NSViewAnimationTargetKey];
632     [dict2 setObject:[NSValue valueWithRect:[o_fullscreen_window frame]] forKey:NSViewAnimationStartFrameKey];
633     [dict2 setObject:[NSValue valueWithRect:screen_rect] forKey:NSViewAnimationEndFrameKey];
634
635     /* Strategy with NSAnimation allocation:
636      - Keep at most 2 animation at a time
637      - leaveFullscreen/enterFullscreen are the only responsible for releasing and alloc-ing
638      */
639     o_fullscreen_anim1 = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObject:dict1]];
640     o_fullscreen_anim2 = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObject:dict2]];
641
642     [dict1 release];
643     [dict2 release];
644
645     [o_fullscreen_anim1 setAnimationBlockingMode: NSAnimationNonblocking];
646     [o_fullscreen_anim1 setDuration: 0.3];
647     [o_fullscreen_anim1 setFrameRate: 30];
648     [o_fullscreen_anim2 setAnimationBlockingMode: NSAnimationNonblocking];
649     [o_fullscreen_anim2 setDuration: 0.2];
650     [o_fullscreen_anim2 setFrameRate: 30];
651
652     [o_fullscreen_anim2 setDelegate: self];
653     [o_fullscreen_anim2 startWhenAnimation: o_fullscreen_anim1 reachesProgress: 1.0];
654
655     [o_fullscreen_anim1 startAnimation];
656     /* fullscreenAnimation will be unlocked when animation ends */
657 }
658
659 - (void)hasBecomeFullscreen
660 {
661     if ([[o_video_view subviews] count] > 0)
662         [o_fullscreen_window makeFirstResponder: [[o_video_view subviews] objectAtIndex:0]];
663
664     [o_fullscreen_window makeKeyWindow];
665     [o_fullscreen_window setAcceptsMouseMovedEvents: YES];
666
667     /* tell the fspanel to move itself to front next time it's triggered */
668     [[[VLCMainWindow sharedInstance] fsPanel] setVoutWasUpdated: o_fullscreen_window];
669     [[[VLCMainWindow sharedInstance] fsPanel] setActive: nil];
670
671     if ([self isVisible])
672         [self orderOut: self];
673
674     [[VLCMainWindow sharedInstance] setFullscreen:YES];
675     [self unlockFullscreenAnimation];
676 }
677
678 - (void)leaveFullscreen
679 {
680     [self leaveFullscreenAndFadeOut: NO];
681 }
682
683 - (void)leaveFullscreenAndFadeOut: (BOOL)fadeout
684 {
685     NSMutableDictionary *dict1, *dict2;
686     NSRect frame;
687     BOOL blackout_other_displays = var_InheritBool(VLCIntf, "macosx-black");
688
689     [self lockFullscreenAnimation];
690
691     if (o_controls_bar)
692         [o_controls_bar setFullscreenState:NO];
693
694     /* We always try to do so */
695     [NSScreen unblackoutScreens];
696
697     vout_thread_t *p_vout = getVoutForActiveWindow();
698     if (p_vout) {
699         if (var_GetBool(p_vout, "video-on-top"))
700             [[o_video_view window] setLevel: NSStatusWindowLevel];
701         else
702             [[o_video_view window] setLevel: NSNormalWindowLevel];
703         vlc_object_release(p_vout);
704     }
705     [[o_video_view window] makeKeyAndOrderFront: nil];
706
707     /* Don't do anything if o_fullscreen_window is already closed */
708     if (!o_fullscreen_window) {
709         [self unlockFullscreenAnimation];
710         return;
711     }
712
713     if (fadeout) {
714         /* We don't animate if we are not visible, instead we
715          * simply fade the display */
716         CGDisplayFadeReservationToken token;
717
718         if (blackout_other_displays) {
719             CGAcquireDisplayFadeReservation(kCGMaxDisplayReservationInterval, &token);
720             CGDisplayFade(token, 0.3, kCGDisplayBlendNormal, kCGDisplayBlendSolidColor, 0, 0, 0, YES);
721         }
722
723         [[[VLCMainWindow sharedInstance] fsPanel] setNonActive: nil];
724         [NSApp setPresentationOptions: NSApplicationPresentationDefault];
725
726         /* Will release the lock */
727         [self hasEndedFullscreen];
728
729         /* Our window is hidden, and might be faded. We need to workaround that, so note it
730          * here */
731         b_window_is_invisible = YES;
732
733         if (blackout_other_displays) {
734             CGDisplayFade(token, 0.5, kCGDisplayBlendSolidColor, kCGDisplayBlendNormal, 0, 0, 0, NO);
735             CGReleaseDisplayFadeReservation(token);
736         }
737
738         return;
739     }
740
741     [self setAlphaValue: 0.0];
742     [self orderFront: self];
743     [[o_video_view window] orderFront: self];
744
745     [[[VLCMainWindow sharedInstance] fsPanel] setNonActive: nil];
746     [NSApp setPresentationOptions:(NSApplicationPresentationDefault)];
747
748     if (o_fullscreen_anim1) {
749         [o_fullscreen_anim1 stopAnimation];
750         [o_fullscreen_anim1 release];
751     }
752     if (o_fullscreen_anim2) {
753         [o_fullscreen_anim2 stopAnimation];
754         [o_fullscreen_anim2 release];
755     }
756
757     frame = [[o_temp_view superview] convertRect: [o_temp_view frame] toView: nil]; /* Convert to Window base coord */
758     frame.origin.x += [self frame].origin.x;
759     frame.origin.y += [self frame].origin.y;
760
761     dict2 = [[NSMutableDictionary alloc] initWithCapacity:2];
762     [dict2 setObject:self forKey:NSViewAnimationTargetKey];
763     [dict2 setObject:NSViewAnimationFadeInEffect forKey:NSViewAnimationEffectKey];
764
765     o_fullscreen_anim2 = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObjects:dict2, nil]];
766     [dict2 release];
767
768     [o_fullscreen_anim2 setAnimationBlockingMode: NSAnimationNonblocking];
769     [o_fullscreen_anim2 setDuration: 0.3];
770     [o_fullscreen_anim2 setFrameRate: 30];
771
772     [o_fullscreen_anim2 setDelegate: self];
773
774     dict1 = [[NSMutableDictionary alloc] initWithCapacity:3];
775
776     [dict1 setObject:o_fullscreen_window forKey:NSViewAnimationTargetKey];
777     [dict1 setObject:[NSValue valueWithRect:[o_fullscreen_window frame]] forKey:NSViewAnimationStartFrameKey];
778     [dict1 setObject:[NSValue valueWithRect:frame] forKey:NSViewAnimationEndFrameKey];
779
780     o_fullscreen_anim1 = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObjects:dict1, nil]];
781     [dict1 release];
782
783     [o_fullscreen_anim1 setAnimationBlockingMode: NSAnimationNonblocking];
784     [o_fullscreen_anim1 setDuration: 0.2];
785     [o_fullscreen_anim1 setFrameRate: 30];
786     [o_fullscreen_anim2 startWhenAnimation: o_fullscreen_anim1 reachesProgress: 1.0];
787
788     /* Make sure o_fullscreen_window is the frontmost window */
789     [o_fullscreen_window orderFront: self];
790
791     [o_fullscreen_anim1 startAnimation];
792     /* fullscreenAnimation will be unlocked when animation ends */
793 }
794
795 - (void)hasEndedFullscreen
796 {
797     [[VLCMainWindow sharedInstance] setFullscreen:NO];
798     
799     /* This function is private and should be only triggered at the end of the fullscreen change animation */
800     /* Make sure we don't see the o_video_view disappearing of the screen during this operation */
801     NSDisableScreenUpdates();
802     [o_video_view retain];
803     [o_video_view removeFromSuperviewWithoutNeedingDisplay];
804     [[o_temp_view superview] replaceSubview:o_temp_view with:o_video_view];
805     [o_video_view release];
806     [o_video_view setFrame:[o_temp_view frame]];
807     if ([[o_video_view subviews] count] > 0)
808         [self makeFirstResponder: [[o_video_view subviews] objectAtIndex:0]];
809
810     [super makeKeyAndOrderFront:self]; /* our version (in main window) contains a workaround */
811
812     [o_fullscreen_window orderOut: self];
813     NSEnableScreenUpdates();
814
815     [o_fullscreen_window release];
816     o_fullscreen_window = nil;
817     [self setLevel:i_originalLevel];
818     [self setAlphaValue: config_GetFloat(VLCIntf, "macosx-opaqueness")];
819
820     // if we quit fullscreen because there is no video anymore, make sure non-embedded window is not visible
821     if (![[VLCMain sharedInstance] activeVideoPlayback] && [self class] != [VLCMainWindow class])
822         [self orderOut: self];
823
824     [self unlockFullscreenAnimation];
825 }
826
827 - (void)animationDidEnd:(NSAnimation*)animation
828 {
829     NSArray *viewAnimations;
830     if (o_makekey_anim == animation) {
831         [o_makekey_anim release];
832         return;
833     }
834     if ([animation currentValue] < 1.0)
835         return;
836
837     /* Fullscreen ended or started (we are a delegate only for leaveFullscreen's/enterFullscren's anim2) */
838     viewAnimations = [o_fullscreen_anim2 viewAnimations];
839     if ([viewAnimations count] >=1 &&
840         [[[viewAnimations objectAtIndex: 0] objectForKey: NSViewAnimationEffectKey] isEqualToString:NSViewAnimationFadeInEffect]) {
841         /* Fullscreen ended */
842         [self hasEndedFullscreen];
843     } else
844     /* Fullscreen started */
845         [self hasBecomeFullscreen];
846 }
847
848 #pragma mark -
849 #pragma mark Accessibility stuff
850
851 - (NSArray *)accessibilityAttributeNames
852 {
853     if (!b_dark_interface || !o_titlebar_view)
854         return [super accessibilityAttributeNames];
855
856     static NSMutableArray *attributes = nil;
857     if (attributes == nil) {
858         attributes = [[super accessibilityAttributeNames] mutableCopy];
859         NSArray *appendAttributes = [NSArray arrayWithObjects: NSAccessibilitySubroleAttribute,
860                                      NSAccessibilityCloseButtonAttribute,
861                                      NSAccessibilityMinimizeButtonAttribute,
862                                      NSAccessibilityZoomButtonAttribute,
863                                      nil];
864
865         for(NSString *attribute in appendAttributes) {
866             if (![attributes containsObject:attribute])
867                 [attributes addObject:attribute];
868         }
869     }
870     return attributes;
871 }
872
873 - (id)accessibilityAttributeValue: (NSString*)o_attribute_name
874 {
875     if (b_dark_interface && o_titlebar_view) {
876         VLCMainWindowTitleView *o_tbv = o_titlebar_view;
877
878         if ([o_attribute_name isEqualTo: NSAccessibilitySubroleAttribute])
879             return NSAccessibilityStandardWindowSubrole;
880
881         if ([o_attribute_name isEqualTo: NSAccessibilityCloseButtonAttribute])
882             return [[o_tbv closeButton] cell];
883
884         if ([o_attribute_name isEqualTo: NSAccessibilityMinimizeButtonAttribute])
885             return [[o_tbv minimizeButton] cell];
886
887         if ([o_attribute_name isEqualTo: NSAccessibilityZoomButtonAttribute])
888             return [[o_tbv zoomButton] cell];
889     }
890
891     return [super accessibilityAttributeValue: o_attribute_name];
892 }
893
894 @end