]> git.sesse.net Git - vlc/blob - modules/gui/macosx/Windows.m
macosx: Fix issue where window vanishes from screen if fullscreen is toggled fast...
[vlc] / modules / gui / macosx / Windows.m
1 /*****************************************************************************
2  * Windows.m: MacOS X interface module
3  *****************************************************************************
4  * Copyright (C) 2012-2013 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
39 @synthesize hasActiveVideo=b_has_active_video;
40 @synthesize fullscreen=b_fullscreen;
41
42 - (id)initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)styleMask
43                   backing:(NSBackingStoreType)backingType defer:(BOOL)flag
44 {
45     self = [super initWithContentRect:contentRect styleMask:styleMask backing:backingType defer:flag];
46     if (self) {
47         /* we don't want this window to be restored on relaunch */
48         if (!OSX_SNOW_LEOPARD)
49             [self setRestorable:NO];
50     }
51     return self;
52 }
53
54 - (void)setCanBecomeKeyWindow: (BOOL)canBecomeKey
55 {
56     b_isset_canBecomeKeyWindow = YES;
57     b_canBecomeKeyWindow = canBecomeKey;
58 }
59
60 - (BOOL)canBecomeKeyWindow
61 {
62     if (b_isset_canBecomeKeyWindow)
63         return b_canBecomeKeyWindow;
64
65     return [super canBecomeKeyWindow];
66 }
67
68 - (void)setCanBecomeMainWindow: (BOOL)canBecomeMain
69 {
70     b_isset_canBecomeMainWindow = YES;
71     b_canBecomeMainWindow = canBecomeMain;
72 }
73
74 - (BOOL)canBecomeMainWindow
75 {
76     if (b_isset_canBecomeMainWindow)
77         return b_canBecomeMainWindow;
78
79     return [super canBecomeMainWindow];
80 }
81
82 - (void)closeAndAnimate: (BOOL)animate
83 {
84     NSInvocation *invoc;
85
86     if (!animate) {
87         [super close];
88         return;
89     }
90
91     // TODO this callback stuff does not work and is not needed
92     invoc = [[[NSInvocation alloc] init] autorelease];
93     [invoc setSelector:@selector(close)];
94     [invoc setTarget: self];
95
96     if (![self isVisible] || [self alphaValue] == 0.0) {
97         [super close];
98         return;
99     }
100
101     [self orderOut: self animate: YES callback: invoc];
102 }
103
104 - (void)orderOut: (id)sender animate: (BOOL)animate
105 {
106     NSInvocation *invoc = [[[NSInvocation alloc] init] autorelease];
107     [invoc setSelector:@selector(orderOut:)];
108     [invoc setTarget: self];
109     [invoc setArgument: sender atIndex: 2];
110     [self orderOut: sender animate: animate callback: invoc];
111 }
112
113 - (void)orderOut: (id)sender animate: (BOOL)animate callback:(NSInvocation *)callback
114 {
115     NSViewAnimation *anim;
116     NSViewAnimation *current_anim;
117     NSMutableDictionary *dict;
118
119     if (!animate) {
120         [self orderOut: sender];
121         return;
122     }
123
124     dict = [[NSMutableDictionary alloc] initWithCapacity:2];
125
126     [dict setObject:self forKey:NSViewAnimationTargetKey];
127
128     [dict setObject:NSViewAnimationFadeOutEffect forKey:NSViewAnimationEffectKey];
129     anim = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObject:dict]];
130     [dict release];
131
132     [anim setAnimationBlockingMode:NSAnimationNonblocking];
133     [anim setDuration:0.9];
134     [anim setFrameRate:30];
135     [anim setUserInfo:callback];
136     [anim setDelegate:self];
137
138     @synchronized(self) {
139         current_anim = self->o_current_animation;
140
141         if ([[[current_anim viewAnimations] objectAtIndex:0] objectForKey: NSViewAnimationEffectKey] == NSViewAnimationFadeOutEffect && [current_anim isAnimating]) {
142             [anim release];
143         } else {
144             if (current_anim) {
145                 [current_anim stopAnimation];
146                 [anim setCurrentProgress:1.0 - [current_anim currentProgress]];
147                 [current_anim release];
148             }
149             else
150                 [anim setCurrentProgress:1.0 - [self alphaValue]];
151             self->o_current_animation = anim;
152             [anim startAnimation];
153         }
154     }
155 }
156
157 - (void)orderFront: (id)sender animate: (BOOL)animate
158 {
159     NSViewAnimation *anim;
160     NSViewAnimation *current_anim;
161     NSMutableDictionary *dict;
162
163     if (!animate) {
164         [super orderFront: sender];
165         [self setAlphaValue: 1.0];
166         return;
167     }
168
169     if (![self isVisible]) {
170         [self setAlphaValue: 0.0];
171         [super orderFront: sender];
172     }
173     else if ([self alphaValue] == 1.0) {
174         [super orderFront: self];
175         return;
176     }
177
178     dict = [[NSMutableDictionary alloc] initWithCapacity:2];
179
180     [dict setObject:self forKey:NSViewAnimationTargetKey];
181
182     [dict setObject:NSViewAnimationFadeInEffect forKey:NSViewAnimationEffectKey];
183     anim = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObject:dict]];
184     [dict release];
185
186     [anim setAnimationBlockingMode:NSAnimationNonblocking];
187     [anim setDuration:0.5];
188     [anim setFrameRate:30];
189     [anim setDelegate:self];
190
191     @synchronized(self) {
192         current_anim = self->o_current_animation;
193
194         if ([[[current_anim viewAnimations] objectAtIndex:0] objectForKey: NSViewAnimationEffectKey] == NSViewAnimationFadeInEffect && [current_anim isAnimating]) {
195             [anim release];
196         } else {
197             if (current_anim) {
198                 [current_anim stopAnimation];
199                 [anim setCurrentProgress:1.0 - [current_anim currentProgress]];
200                 [current_anim release];
201             }
202             else
203                 [anim setCurrentProgress:[self alphaValue]];
204             self->o_current_animation = anim;
205             [self orderFront: sender];
206             [anim startAnimation];
207         }
208     }
209 }
210
211 - (void)animationDidEnd:(NSAnimation*)anim
212 {
213     if ([self alphaValue] <= 0.0) {
214         NSInvocation * invoc;
215         [super orderOut: nil];
216         [self setAlphaValue: 1.0];
217         if ((invoc = [anim userInfo])) {
218             [invoc invoke];
219         }
220     }
221 }
222
223 - (VLCVoutView *)videoView
224 {
225     if ([[self contentView] class] == [VLCVoutView class])
226         return (VLCVoutView *)[self contentView];
227
228     return nil;
229 }
230
231
232 @end
233
234
235 /*****************************************************************************
236  * VLCVideoWindowCommon
237  *
238  *  Common code for main window, detached window and extra video window
239  *****************************************************************************/
240
241 @interface VLCVideoWindowCommon (Internal)
242 - (void)customZoom:(id)sender;
243 - (void)hasBecomeFullscreen;
244 - (void)leaveFullscreenAndFadeOut:(BOOL)fadeout;
245 - (void)hasEndedFullscreen;
246 @end
247
248 @implementation VLCVideoWindowCommon
249
250 @synthesize videoView=o_video_view;
251 @synthesize controlsBar=o_controls_bar;
252 @synthesize inFullscreenTransition=b_in_fullscreen_transition;
253
254 #pragma mark -
255 #pragma mark Init
256
257 - (id)initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)styleMask
258                   backing:(NSBackingStoreType)backingType defer:(BOOL)flag
259 {
260     b_dark_interface = config_GetInt(VLCIntf, "macosx-interfacestyle");
261
262     if (b_dark_interface) {
263         styleMask = NSBorderlessWindowMask;
264 #ifdef MAC_OS_X_VERSION_10_7
265         if (!OSX_SNOW_LEOPARD)
266             styleMask |= NSResizableWindowMask;
267 #endif
268     }
269
270     self = [super initWithContentRect:contentRect styleMask:styleMask
271                               backing:backingType defer:flag];
272
273     /* we want to be moveable regardless of our style */
274     [self setMovableByWindowBackground: YES];
275     [self setCanBecomeKeyWindow:YES];
276
277     o_temp_view = [[NSView alloc] init];
278     [o_temp_view setAutoresizingMask:NSViewHeightSizable | NSViewWidthSizable];
279
280     return self;
281 }
282
283 - (void)dealloc
284 {
285     [o_temp_view release];
286     [super dealloc];
287 }
288
289 - (void)awakeFromNib
290 {
291     BOOL b_nativeFullscreenMode = NO;
292 #ifdef MAC_OS_X_VERSION_10_7
293     if (!OSX_SNOW_LEOPARD)
294         b_nativeFullscreenMode = var_InheritBool(VLCIntf, "macosx-nativefullscreenmode");
295 #endif
296
297     if (b_nativeFullscreenMode) {
298         [self setCollectionBehavior: NSWindowCollectionBehaviorFullScreenPrimary];
299     } else {
300         [o_titlebar_view setFullscreenButtonHidden: YES];
301     }
302
303     [super awakeFromNib];
304 }
305
306 - (void)setTitle:(NSString *)title
307 {
308     if (!title || [title length] < 1)
309         return;
310
311     if (b_dark_interface && o_titlebar_view)
312         [o_titlebar_view setWindowTitle: title];
313
314     [super setTitle: title];
315 }
316
317 #pragma mark -
318 #pragma mark zoom / minimize / close
319
320 - (BOOL)validateMenuItem:(NSMenuItem *)menuItem
321 {
322     SEL s_menuAction = [menuItem action];
323
324     if ((s_menuAction == @selector(performClose:)) || (s_menuAction == @selector(performMiniaturize:)) || (s_menuAction == @selector(performZoom:)))
325         return YES;
326
327     return [super validateMenuItem:menuItem];
328 }
329
330 - (BOOL)windowShouldClose:(id)sender
331 {
332     return YES;
333 }
334
335 - (void)performClose:(id)sender
336 {
337     if (!([self styleMask] & NSTitledWindowMask)) {
338         [[NSNotificationCenter defaultCenter] postNotificationName:NSWindowWillCloseNotification object:self];
339
340         [self close];
341     } else
342         [super performClose: sender];
343 }
344
345 - (void)performMiniaturize:(id)sender
346 {
347     if (!([self styleMask] & NSTitledWindowMask))
348         [self miniaturize: sender];
349     else
350         [super performMiniaturize: sender];
351 }
352
353 - (void)performZoom:(id)sender
354 {
355     if (!([self styleMask] & NSTitledWindowMask))
356         [self customZoom: sender];
357     else
358         [super performZoom: sender];
359 }
360
361 - (void)zoom:(id)sender
362 {
363     if (!([self styleMask] & NSTitledWindowMask))
364         [self customZoom: sender];
365     else
366         [super zoom: sender];
367 }
368
369 /**
370  * Given a proposed frame rectangle, return a modified version
371  * which will fit inside the screen.
372  *
373  * This method is based upon NSWindow.m, part of the GNUstep GUI Library, licensed under LGPLv2+.
374  *    Authors:  Scott Christley <scottc@net-community.com>, Venkat Ajjanagadde <venkat@ocbi.com>,
375  *              Felipe A. Rodriguez <far@ix.netcom.com>, Richard Frith-Macdonald <richard@brainstorm.co.uk>
376  *    Copyright (C) 1996 Free Software Foundation, Inc.
377  */
378 - (NSRect) customConstrainFrameRect: (NSRect)frameRect toScreen: (NSScreen*)screen
379 {
380     NSRect screenRect = [screen visibleFrame];
381     float difference;
382
383     /* Move top edge of the window inside the screen */
384     difference = NSMaxY (frameRect) - NSMaxY (screenRect);
385     if (difference > 0) {
386         frameRect.origin.y -= difference;
387     }
388
389     /* If the window is resizable, resize it (if needed) so that the
390      bottom edge is on the screen or can be on the screen when the user moves
391      the window */
392     difference = NSMaxY (screenRect) - NSMaxY (frameRect);
393     if (_styleMask & NSResizableWindowMask) {
394         float difference2;
395
396         difference2 = screenRect.origin.y - frameRect.origin.y;
397         difference2 -= difference;
398         // Take in account the space between the top of window and the top of the
399         // screen which can be used to move the bottom of the window on the screen
400         if (difference2 > 0) {
401             frameRect.size.height -= difference2;
402             frameRect.origin.y += difference2;
403         }
404
405         /* Ensure that resizing doesn't makewindow smaller than minimum */
406         difference2 = [self minSize].height - frameRect.size.height;
407         if (difference2 > 0) {
408             frameRect.size.height += difference2;
409             frameRect.origin.y -= difference2;
410         }
411     }
412
413     return frameRect;
414 }
415
416 #define DIST 3
417
418 /**
419  Zooms the receiver.   This method calls the delegate method
420  windowShouldZoom:toFrame: to determine if the window should
421  be allowed to zoom to full screen.
422  *
423  * This method is based upon NSWindow.m, part of the GNUstep GUI Library, licensed under LGPLv2+.
424  *    Authors:  Scott Christley <scottc@net-community.com>, Venkat Ajjanagadde <venkat@ocbi.com>,
425  *              Felipe A. Rodriguez <far@ix.netcom.com>, Richard Frith-Macdonald <richard@brainstorm.co.uk>
426  *    Copyright (C) 1996 Free Software Foundation, Inc.
427  */
428 - (void) customZoom: (id)sender
429 {
430     NSRect maxRect = [[self screen] visibleFrame];
431     NSRect currentFrame = [self frame];
432
433     if ([[self delegate] respondsToSelector: @selector(windowWillUseStandardFrame:defaultFrame:)]) {
434         maxRect = [[self delegate] windowWillUseStandardFrame: self defaultFrame: maxRect];
435     }
436
437     maxRect = [self customConstrainFrameRect: maxRect toScreen: [self screen]];
438
439     // Compare the new frame with the current one
440     if ((abs(NSMaxX(maxRect) - NSMaxX(currentFrame)) < DIST)
441         && (abs(NSMaxY(maxRect) - NSMaxY(currentFrame)) < DIST)
442         && (abs(NSMinX(maxRect) - NSMinX(currentFrame)) < DIST)
443         && (abs(NSMinY(maxRect) - NSMinY(currentFrame)) < DIST)) {
444         // Already in zoomed mode, reset user frame, if stored
445         if ([self frameAutosaveName] != nil) {
446             [self setFrame: previousSavedFrame display: YES animate: YES];
447             [self saveFrameUsingName: [self frameAutosaveName]];
448         }
449         return;
450     }
451
452     if ([self frameAutosaveName] != nil) {
453         [self saveFrameUsingName: [self frameAutosaveName]];
454         previousSavedFrame = [self frame];
455     }
456
457     [self setFrame: maxRect display: YES animate: YES];
458 }
459
460 #pragma mark -
461 #pragma mark Video window resizing logic
462
463 - (void)setWindowLevel:(NSInteger)i_state
464 {
465     if (var_InheritBool(VLCIntf, "video-wallpaper") || [self level] < NSNormalWindowLevel)
466         return;
467
468     if (!b_fullscreen && !b_in_fullscreen_transition)
469         [self setLevel: i_state];
470
471     // save it for restore if window is currently minimized or in fullscreen
472     i_originalLevel = i_state;
473 }
474
475 - (NSRect)getWindowRectForProposedVideoViewSize:(NSSize)size
476 {
477     NSSize windowMinSize = [self minSize];
478     NSRect screenFrame = [[self screen] visibleFrame];
479
480     NSPoint topleftbase = NSMakePoint(0, [self frame].size.height);
481     NSPoint topleftscreen = [self convertBaseToScreen: topleftbase];
482
483     unsigned int i_width = size.width;
484     unsigned int i_height = size.height;
485     if (i_width < windowMinSize.width)
486         i_width = windowMinSize.width;
487     if (i_height < f_min_video_height)
488         i_height = f_min_video_height;
489
490     /* Calculate the window's new size */
491     NSRect new_frame;
492     new_frame.size.width = [self frame].size.width - [o_video_view frame].size.width + i_width;
493     new_frame.size.height = [self frame].size.height - [o_video_view frame].size.height + i_height;
494     new_frame.origin.x = topleftscreen.x;
495     new_frame.origin.y = topleftscreen.y - new_frame.size.height;
496
497     /* make sure the window doesn't exceed the screen size the window is on */
498     if (new_frame.size.width > screenFrame.size.width) {
499         new_frame.size.width = screenFrame.size.width;
500         new_frame.origin.x = screenFrame.origin.x;
501     }
502     if (new_frame.size.height > screenFrame.size.height) {
503         new_frame.size.height = screenFrame.size.height;
504         new_frame.origin.y = screenFrame.origin.y;
505     }
506     if (new_frame.origin.y < screenFrame.origin.y)
507         new_frame.origin.y = screenFrame.origin.y;
508
509     CGFloat right_screen_point = screenFrame.origin.x + screenFrame.size.width;
510     CGFloat right_window_point = new_frame.origin.x + new_frame.size.width;
511     if (right_window_point > right_screen_point)
512         new_frame.origin.x -= (right_window_point - right_screen_point);
513
514     return new_frame;
515 }
516
517 - (void)resizeWindow
518 {
519     // VOUT_WINDOW_SET_SIZE is triggered when exiting fullscreen. This event is ignored here
520     // to avoid interference with the animation.
521     if ([self fullscreen] || b_in_fullscreen_transition)
522         return;
523
524     NSRect window_rect = [self getWindowRectForProposedVideoViewSize:nativeVideoSize];
525     [[self animator] setFrame:window_rect display:YES];
526 }
527
528 - (void)setNativeVideoSize:(NSSize)size
529 {
530     nativeVideoSize = size;
531
532     if (var_InheritBool(VLCIntf, "macosx-video-autoresize") && !var_InheritBool(VLCIntf, "video-wallpaper"))
533         [self resizeWindow];
534 }
535
536 - (NSSize)windowWillResize:(NSWindow *)window toSize:(NSSize)proposedFrameSize
537 {
538     if (![[VLCMain sharedInstance] activeVideoPlayback] || nativeVideoSize.width == 0. || nativeVideoSize.height == 0. || window != self)
539         return proposedFrameSize;
540
541     // needed when entering lion fullscreen mode
542     if (b_in_fullscreen_transition || [self fullscreen])
543         return proposedFrameSize;
544
545     if ([[VLCCoreInteraction sharedInstance] aspectRatioIsLocked]) {
546         NSRect videoWindowFrame = [self frame];
547         NSRect viewRect = [o_video_view convertRect:[o_video_view bounds] toView: nil];
548         NSRect contentRect = [self contentRectForFrameRect:videoWindowFrame];
549         float marginy = viewRect.origin.y + videoWindowFrame.size.height - contentRect.size.height;
550         float marginx = contentRect.size.width - viewRect.size.width;
551         if (o_titlebar_view && b_dark_interface)
552             marginy += [o_titlebar_view frame].size.height;
553
554         proposedFrameSize.height = (proposedFrameSize.width - marginx) * nativeVideoSize.height / nativeVideoSize.width + marginy;
555     }
556
557     return proposedFrameSize;
558 }
559
560 - (void)windowWillMiniaturize:(NSNotification *)notification
561 {
562     // Set level to normal as a workaround for Mavericks bug causing window
563     // to vanish from screen, see radar://15473716
564     i_originalLevel = [self level];
565     [self setLevel: NSNormalWindowLevel];
566 }
567
568 - (void)windowDidDeminiaturize:(NSNotification *)notification
569 {
570     [self setLevel: i_originalLevel];
571 }
572
573 #pragma mark -
574 #pragma mark Mouse cursor handling
575
576 //  NSTimer selectors require this function signature as per Apple's docs
577 - (void)hideMouseCursor:(NSTimer *)timer
578 {
579     [NSCursor setHiddenUntilMouseMoves: YES];
580 }
581
582 - (void)recreateHideMouseTimer
583 {
584     if (t_hide_mouse_timer != nil) {
585         [t_hide_mouse_timer invalidate];
586         [t_hide_mouse_timer release];
587     }
588
589     t_hide_mouse_timer = [NSTimer scheduledTimerWithTimeInterval:2
590                                                           target:self
591                                                         selector:@selector(hideMouseCursor:)
592                                                         userInfo:nil
593                                                          repeats:NO];
594     [t_hide_mouse_timer retain];
595 }
596
597 //  Called automatically if window's acceptsMouseMovedEvents property is true
598 - (void)mouseMoved:(NSEvent *)theEvent
599 {
600     if (b_fullscreen)
601         [self recreateHideMouseTimer];
602
603     [super mouseMoved: theEvent];
604 }
605
606 #pragma mark -
607 #pragma mark Lion native fullscreen handling
608
609 - (void)becomeKeyWindow
610 {
611     [super becomeKeyWindow];
612
613     // change fspanel state for the case when multiple windows are in fullscreen
614     if ([self hasActiveVideo] && [self fullscreen])
615         [[[VLCMainWindow sharedInstance] fsPanel] setActive:nil];
616     else
617         [[[VLCMainWindow sharedInstance] fsPanel] setNonActive:nil];
618 }
619
620 - (void)resignKeyWindow
621 {
622     [super resignKeyWindow];
623
624     [[[VLCMainWindow sharedInstance] fsPanel] setNonActive:nil];
625 }
626
627 - (void)windowWillEnterFullScreen:(NSNotification *)notification
628 {
629     // workaround, see #6668
630     [NSApp setPresentationOptions:(NSApplicationPresentationFullScreen | NSApplicationPresentationAutoHideDock | NSApplicationPresentationAutoHideMenuBar)];
631
632     i_originalLevel = [self level];
633     // b_fullscreen and b_in_fullscreen_transition must not be true yet
634     [[[VLCMain sharedInstance] voutController] updateWindowLevelForHelperWindows: NSNormalWindowLevel];
635     [self setLevel:NSNormalWindowLevel];
636
637     b_in_fullscreen_transition = YES;
638
639     var_SetBool(pl_Get(VLCIntf), "fullscreen", true);
640
641     if ([self hasActiveVideo]) {
642         vout_thread_t *p_vout = getVoutForActiveWindow();
643         if (p_vout) {
644             var_SetBool(p_vout, "fullscreen", true);
645             vlc_object_release(p_vout);
646         }
647     }
648
649     if ([self hasActiveVideo])
650         [[VLCMainWindow sharedInstance] recreateHideMouseTimer];
651
652     if (b_dark_interface) {
653         [o_titlebar_view removeFromSuperviewWithoutNeedingDisplay];
654
655         NSRect winrect;
656         CGFloat f_titleBarHeight = [o_titlebar_view frame].size.height;
657         winrect = [self frame];
658
659         winrect.size.height = winrect.size.height - f_titleBarHeight;
660         [self setFrame: winrect display:NO animate:NO];
661     }
662
663     [o_video_view setFrame: [[self contentView] frame]];
664     if (![o_video_view isHidden]) {
665         [[o_controls_bar bottomBarView] setHidden: YES];
666     }
667     
668
669     [self setMovableByWindowBackground: NO];
670 }
671
672 - (void)windowDidEnterFullScreen:(NSNotification *)notification
673 {
674     // Indeed, we somehow can have an "inactive" fullscreen (but a visible window!).
675     // But this creates some problems when leaving fs over remote intfs, so activate app here.
676     [NSApp activateIgnoringOtherApps:YES];
677
678     [self setFullscreen: YES];
679     b_in_fullscreen_transition = NO;
680
681     if ([self hasActiveVideo]) {
682         [[[VLCMainWindow sharedInstance] fsPanel] setVoutWasUpdated: self];
683         if (![o_video_view isHidden])
684             [[[VLCMainWindow sharedInstance] fsPanel] setActive: nil];
685     }
686
687     NSArray *subviews = [[self videoView] subviews];
688     NSUInteger count = [subviews count];
689
690     for (NSUInteger x = 0; x < count; x++) {
691         if ([[subviews objectAtIndex:x] respondsToSelector:@selector(reshape)])
692             [[subviews objectAtIndex:x] reshape];
693     }
694
695 }
696
697 - (void)windowWillExitFullScreen:(NSNotification *)notification
698 {
699     b_in_fullscreen_transition = YES;
700     [self setFullscreen: NO];
701
702     var_SetBool(pl_Get(VLCIntf), "fullscreen", false);
703
704     if ([self hasActiveVideo]) {
705         vout_thread_t *p_vout = getVoutForActiveWindow();
706         if (p_vout) {
707             var_SetBool(p_vout, "fullscreen", false);
708             vlc_object_release(p_vout);
709         }
710     }
711
712     [NSCursor setHiddenUntilMouseMoves: NO];
713     [[[VLCMainWindow sharedInstance] fsPanel] setNonActive: nil];
714
715
716     if (b_dark_interface) {
717         NSRect winrect;
718         CGFloat f_titleBarHeight = [o_titlebar_view frame].size.height;
719
720         winrect = [o_video_view frame];
721         winrect.size.height -= f_titleBarHeight;
722         [o_video_view setFrame: winrect];
723
724         winrect = [self frame];
725         [o_titlebar_view setFrame: NSMakeRect(0, winrect.size.height - f_titleBarHeight,
726                                               winrect.size.width, f_titleBarHeight)];
727         [[self contentView] addSubview: o_titlebar_view];
728
729         winrect.size.height = winrect.size.height + f_titleBarHeight;
730         [self setFrame: winrect display:NO animate:NO];
731     }
732
733     NSRect videoViewFrame = [o_video_view frame];
734     videoViewFrame.origin.y += [o_controls_bar height];
735     videoViewFrame.size.height -= [o_controls_bar height];
736     [o_video_view setFrame: videoViewFrame];
737
738     if (![o_video_view isHidden]) {
739         [[o_controls_bar bottomBarView] setHidden: NO];
740     }
741
742     [self setMovableByWindowBackground: YES];
743 }
744
745 - (void)windowDidExitFullScreen:(NSNotification *)notification
746 {
747     b_in_fullscreen_transition = NO;
748
749     [[[VLCMain sharedInstance] voutController] updateWindowLevelForHelperWindows: i_originalLevel];
750     [self setLevel:i_originalLevel];
751 }
752
753 #pragma mark -
754 #pragma mark Fullscreen Logic
755
756 - (void)enterFullscreen
757 {
758     NSMutableDictionary *dict1, *dict2;
759     NSScreen *screen;
760     NSRect screen_rect;
761     NSRect rect;
762     BOOL blackout_other_displays = var_InheritBool(VLCIntf, "macosx-black");
763
764     screen = [NSScreen screenWithDisplayID:(CGDirectDisplayID)var_InheritInteger(VLCIntf, "macosx-vdev")];
765
766     if (!screen) {
767         msg_Dbg(VLCIntf, "chosen screen isn't present, using current screen for fullscreen mode");
768         screen = [self screen];
769     }
770     if (!screen) {
771         msg_Dbg(VLCIntf, "Using deepest screen");
772         screen = [NSScreen deepestScreen];
773     }
774
775     screen_rect = [screen frame];
776
777     if (o_controls_bar)
778         [o_controls_bar setFullscreenState:YES];
779     [[[VLCMainWindow sharedInstance] controlsBar] setFullscreenState:YES];
780
781     [[VLCMainWindow sharedInstance] recreateHideMouseTimer];
782
783     if (blackout_other_displays)
784         [screen blackoutOtherScreens];
785
786     /* Make sure we don't see the window flashes in float-on-top mode */
787     i_originalLevel = [self level];
788     // b_fullscreen must not be true yet
789     [[[VLCMain sharedInstance] voutController] updateWindowLevelForHelperWindows: NSNormalWindowLevel];
790     [self setLevel:NSNormalWindowLevel];
791
792     /* Only create the o_fullscreen_window if we are not in the middle of the zooming animation */
793     if (!o_fullscreen_window) {
794         /* We can't change the styleMask of an already created NSWindow, so we create another window, and do eye catching stuff */
795
796         rect = [[o_video_view superview] convertRect: [o_video_view frame] toView: nil]; /* Convert to Window base coord */
797         rect.origin.x += [self frame].origin.x;
798         rect.origin.y += [self frame].origin.y;
799         o_fullscreen_window = [[VLCWindow alloc] initWithContentRect:rect styleMask: NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:YES];
800         [o_fullscreen_window setBackgroundColor: [NSColor blackColor]];
801         [o_fullscreen_window setCanBecomeKeyWindow: YES];
802         [o_fullscreen_window setCanBecomeMainWindow: YES];
803         [o_fullscreen_window setHasActiveVideo: YES];
804         [o_fullscreen_window setFullscreen: YES];
805
806         if (![self isVisible] || [self alphaValue] == 0.0) {
807             /* We don't animate if we are not visible, instead we
808              * simply fade the display */
809             CGDisplayFadeReservationToken token;
810
811             if (blackout_other_displays) {
812                 CGAcquireDisplayFadeReservation(kCGMaxDisplayReservationInterval, &token);
813                 CGDisplayFade(token, 0.5, kCGDisplayBlendNormal, kCGDisplayBlendSolidColor, 0, 0, 0, YES);
814             }
815
816             [screen setFullscreenPresentationOptions];
817
818             [o_video_view retain];
819             [[o_video_view superview] replaceSubview:o_video_view with:o_temp_view];
820             [o_temp_view setFrame:[o_video_view frame]];
821             [o_fullscreen_window setContentView:o_video_view];
822             [o_video_view release];
823
824             [o_fullscreen_window makeKeyAndOrderFront:self];
825             [o_fullscreen_window orderFront:self animate:YES];
826
827             [o_fullscreen_window setFrame:screen_rect display:YES animate:YES];
828             [o_fullscreen_window setLevel:NSNormalWindowLevel];
829
830             if (blackout_other_displays) {
831                 CGDisplayFade(token, 0.3, kCGDisplayBlendSolidColor, kCGDisplayBlendNormal, 0, 0, 0, NO);
832                 CGReleaseDisplayFadeReservation(token);
833             }
834
835             /* Will release the lock */
836             [self hasBecomeFullscreen];
837
838             return;
839         }
840
841         /* Make sure video view gets visible in case the playlist was visible before */
842         b_video_view_was_hidden = [o_video_view isHidden];
843         [o_video_view setHidden: NO];
844
845         /* Make sure we don't see the o_video_view disappearing of the screen during this operation */
846         NSDisableScreenUpdates();
847         [o_video_view retain];
848         [[o_video_view superview] replaceSubview:o_video_view with:o_temp_view];
849         [o_temp_view setFrame:[o_video_view frame]];
850         [o_fullscreen_window setContentView:o_video_view];
851         [o_video_view release];
852         [o_fullscreen_window makeKeyAndOrderFront:self];
853         NSEnableScreenUpdates();
854     }
855
856     /* We are in fullscreen (and no animation is running) */
857     if ([self fullscreen]) {
858         /* Make sure we are hidden */
859         [self orderOut: self];
860
861         return;
862     }
863
864     if (o_fullscreen_anim1) {
865         [o_fullscreen_anim1 stopAnimation];
866         [o_fullscreen_anim1 release];
867     }
868     if (o_fullscreen_anim2) {
869         [o_fullscreen_anim2 stopAnimation];
870         [o_fullscreen_anim2 release];
871     }
872
873     [screen setFullscreenPresentationOptions];
874
875     dict1 = [[NSMutableDictionary alloc] initWithCapacity:2];
876     dict2 = [[NSMutableDictionary alloc] initWithCapacity:3];
877
878     [dict1 setObject:self forKey:NSViewAnimationTargetKey];
879     [dict1 setObject:NSViewAnimationFadeOutEffect forKey:NSViewAnimationEffectKey];
880
881     [dict2 setObject:o_fullscreen_window forKey:NSViewAnimationTargetKey];
882     [dict2 setObject:[NSValue valueWithRect:[o_fullscreen_window frame]] forKey:NSViewAnimationStartFrameKey];
883     [dict2 setObject:[NSValue valueWithRect:screen_rect] forKey:NSViewAnimationEndFrameKey];
884
885     /* Strategy with NSAnimation allocation:
886      - Keep at most 2 animation at a time
887      - leaveFullscreen/enterFullscreen are the only responsible for releasing and alloc-ing
888      */
889     o_fullscreen_anim1 = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObject:dict1]];
890     o_fullscreen_anim2 = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObject:dict2]];
891
892     [dict1 release];
893     [dict2 release];
894
895     [o_fullscreen_anim1 setAnimationBlockingMode: NSAnimationNonblocking];
896     [o_fullscreen_anim1 setDuration: 0.3];
897     [o_fullscreen_anim1 setFrameRate: 30];
898     [o_fullscreen_anim2 setAnimationBlockingMode: NSAnimationNonblocking];
899     [o_fullscreen_anim2 setDuration: 0.2];
900     [o_fullscreen_anim2 setFrameRate: 30];
901
902     [o_fullscreen_anim2 setDelegate: self];
903     [o_fullscreen_anim2 startWhenAnimation: o_fullscreen_anim1 reachesProgress: 1.0];
904
905     [o_fullscreen_anim1 startAnimation];
906     /* fullscreenAnimation will be unlocked when animation ends */
907
908     b_in_fullscreen_transition = YES;
909 }
910
911 - (void)hasBecomeFullscreen
912 {
913     if ([[o_video_view subviews] count] > 0)
914         [o_fullscreen_window makeFirstResponder: [[o_video_view subviews] objectAtIndex:0]];
915
916     [o_fullscreen_window makeKeyWindow];
917     [o_fullscreen_window setAcceptsMouseMovedEvents: YES];
918
919     /* tell the fspanel to move itself to front next time it's triggered */
920     [[[VLCMainWindow sharedInstance] fsPanel] setVoutWasUpdated: o_fullscreen_window];
921     [[[VLCMainWindow sharedInstance] fsPanel] setActive: nil];
922
923     if ([self isVisible])
924         [self orderOut: self];
925
926     b_in_fullscreen_transition = NO;
927     [self setFullscreen:YES];
928 }
929
930 - (void)leaveFullscreen
931 {
932     [self leaveFullscreenAndFadeOut: NO];
933 }
934
935 - (void)leaveFullscreenAndFadeOut: (BOOL)fadeout
936 {
937     NSMutableDictionary *dict1, *dict2;
938     NSRect frame;
939     BOOL blackout_other_displays = var_InheritBool(VLCIntf, "macosx-black");
940
941     if (o_controls_bar)
942         [o_controls_bar setFullscreenState:NO];
943     [[[VLCMainWindow sharedInstance] controlsBar] setFullscreenState:NO];
944
945     /* We always try to do so */
946     [NSScreen unblackoutScreens];
947
948     [[o_video_view window] makeKeyAndOrderFront: nil];
949
950     /* Don't do anything if o_fullscreen_window is already closed */
951     if (!o_fullscreen_window) {
952         return;
953     }
954
955     if (fadeout) {
956         /* We don't animate if we are not visible, instead we
957          * simply fade the display */
958         CGDisplayFadeReservationToken token;
959
960         if (blackout_other_displays) {
961             CGAcquireDisplayFadeReservation(kCGMaxDisplayReservationInterval, &token);
962             CGDisplayFade(token, 0.3, kCGDisplayBlendNormal, kCGDisplayBlendSolidColor, 0, 0, 0, YES);
963         }
964
965         [[[VLCMainWindow sharedInstance] fsPanel] setNonActive: nil];
966         [[o_fullscreen_window screen] setNonFullscreenPresentationOptions];
967
968         /* Will release the lock */
969         [self hasEndedFullscreen];
970
971         /* Our window is hidden, and might be faded. We need to workaround that, so note it
972          * here */
973         b_window_is_invisible = YES;
974
975         if (blackout_other_displays) {
976             CGDisplayFade(token, 0.5, kCGDisplayBlendSolidColor, kCGDisplayBlendNormal, 0, 0, 0, NO);
977             CGReleaseDisplayFadeReservation(token);
978         }
979
980         return;
981     }
982
983     b_in_fullscreen_transition = YES;
984
985     [self setAlphaValue: 0.0];
986     [self orderFront: self];
987     [[o_video_view window] orderFront: self];
988
989     [[[VLCMainWindow sharedInstance] fsPanel] setNonActive: nil];
990     [[o_fullscreen_window screen] setNonFullscreenPresentationOptions];
991
992     if (o_fullscreen_anim1) {
993         [o_fullscreen_anim1 stopAnimation];
994         [o_fullscreen_anim1 release];
995     }
996     if (o_fullscreen_anim2) {
997         [o_fullscreen_anim2 stopAnimation];
998         [o_fullscreen_anim2 release];
999     }
1000
1001     frame = [[o_temp_view superview] convertRect: [o_temp_view frame] toView: nil]; /* Convert to Window base coord */
1002     frame.origin.x += [self frame].origin.x;
1003     frame.origin.y += [self frame].origin.y;
1004
1005     dict2 = [[NSMutableDictionary alloc] initWithCapacity:2];
1006     [dict2 setObject:self forKey:NSViewAnimationTargetKey];
1007     [dict2 setObject:NSViewAnimationFadeInEffect forKey:NSViewAnimationEffectKey];
1008
1009     o_fullscreen_anim2 = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObject:dict2]];
1010     [dict2 release];
1011
1012     [o_fullscreen_anim2 setAnimationBlockingMode: NSAnimationNonblocking];
1013     [o_fullscreen_anim2 setDuration: 0.3];
1014     [o_fullscreen_anim2 setFrameRate: 30];
1015
1016     [o_fullscreen_anim2 setDelegate: self];
1017
1018     dict1 = [[NSMutableDictionary alloc] initWithCapacity:3];
1019
1020     [dict1 setObject:o_fullscreen_window forKey:NSViewAnimationTargetKey];
1021     [dict1 setObject:[NSValue valueWithRect:[o_fullscreen_window frame]] forKey:NSViewAnimationStartFrameKey];
1022     [dict1 setObject:[NSValue valueWithRect:frame] forKey:NSViewAnimationEndFrameKey];
1023
1024     o_fullscreen_anim1 = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObject:dict1]];
1025     [dict1 release];
1026
1027     [o_fullscreen_anim1 setAnimationBlockingMode: NSAnimationNonblocking];
1028     [o_fullscreen_anim1 setDuration: 0.2];
1029     [o_fullscreen_anim1 setFrameRate: 30];
1030     [o_fullscreen_anim2 startWhenAnimation: o_fullscreen_anim1 reachesProgress: 1.0];
1031
1032     /* Make sure o_fullscreen_window is the frontmost window */
1033     [o_fullscreen_window orderFront: self];
1034
1035     [o_fullscreen_anim1 startAnimation];
1036     /* fullscreenAnimation will be unlocked when animation ends */
1037 }
1038
1039 - (void)hasEndedFullscreen
1040 {
1041     [self setFullscreen:NO];
1042     b_in_fullscreen_transition = NO;
1043
1044     /* This function is private and should be only triggered at the end of the fullscreen change animation */
1045     /* Make sure we don't see the o_video_view disappearing of the screen during this operation */
1046     NSDisableScreenUpdates();
1047     [o_video_view retain];
1048     [o_video_view removeFromSuperviewWithoutNeedingDisplay];
1049     [[o_temp_view superview] replaceSubview:o_temp_view with:o_video_view];
1050     [o_video_view release];
1051     [o_video_view setFrame:[o_temp_view frame]];
1052     if ([[o_video_view subviews] count] > 0)
1053         [self makeFirstResponder: [[o_video_view subviews] objectAtIndex:0]];
1054
1055     [o_video_view setHidden: b_video_view_was_hidden];
1056
1057     [super makeKeyAndOrderFront:self]; /* our version (in main window) contains a workaround */
1058
1059     [o_fullscreen_window orderOut: self];
1060     NSEnableScreenUpdates();
1061
1062     [o_fullscreen_window release];
1063     o_fullscreen_window = nil;
1064
1065     [[[VLCMain sharedInstance] voutController] updateWindowLevelForHelperWindows: i_originalLevel];
1066     [self setLevel:i_originalLevel];
1067
1068     [self setAlphaValue: config_GetFloat(VLCIntf, "macosx-opaqueness")];
1069
1070     // if we quit fullscreen because there is no video anymore, make sure non-embedded window is not visible
1071     if (![[VLCMain sharedInstance] activeVideoPlayback] && [self class] != [VLCMainWindow class])
1072         [self orderOut: self];
1073 }
1074
1075 - (void)animationDidEnd:(NSAnimation*)animation
1076 {
1077     NSArray *viewAnimations;
1078     if (o_makekey_anim == animation) {
1079         [o_makekey_anim release];
1080         return;
1081     }
1082     if ([animation currentValue] < 1.0)
1083         return;
1084
1085     /* Fullscreen ended or started (we are a delegate only for leaveFullscreen's/enterFullscren's anim2) */
1086     viewAnimations = [o_fullscreen_anim2 viewAnimations];
1087     if ([viewAnimations count] >=1 &&
1088         [[[viewAnimations objectAtIndex:0] objectForKey: NSViewAnimationEffectKey] isEqualToString:NSViewAnimationFadeInEffect]) {
1089         /* Fullscreen ended */
1090         [self hasEndedFullscreen];
1091     } else
1092     /* Fullscreen started */
1093         [self hasBecomeFullscreen];
1094 }
1095
1096 - (void)orderOut:(id)sender
1097 {
1098     [super orderOut:sender];
1099
1100     /*
1101      * TODO reimplement leaveFullscreenAndFadeOut:YES, or remove code
1102      * and the hack below
1103     
1104     if (![NSStringFromClass([self class]) isEqualToString:@"VLCMainWindow"]) {
1105         [self leaveFullscreenAndFadeOut:YES];
1106     }
1107      */
1108 }
1109
1110 - (void)makeKeyAndOrderFront: (id)sender
1111 {
1112     /* Hack
1113      * when we exit fullscreen and fade out, we may endup in
1114      * having a window that is faded. We can't have it fade in unless we
1115      * animate again. */
1116
1117     if (!b_window_is_invisible) {
1118         /* Make sure we don't do it too much */
1119         [super makeKeyAndOrderFront: sender];
1120         return;
1121     }
1122
1123     [super setAlphaValue:0.0f];
1124     [super makeKeyAndOrderFront: sender];
1125
1126     NSMutableDictionary * dict = [[NSMutableDictionary alloc] initWithCapacity:2];
1127     [dict setObject:self forKey:NSViewAnimationTargetKey];
1128     [dict setObject:NSViewAnimationFadeInEffect forKey:NSViewAnimationEffectKey];
1129
1130     o_makekey_anim = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObject:dict]];
1131     [dict release];
1132
1133     [o_makekey_anim setAnimationBlockingMode: NSAnimationNonblocking];
1134     [o_makekey_anim setDuration: 0.1];
1135     [o_makekey_anim setFrameRate: 30];
1136     [o_makekey_anim setDelegate: self];
1137
1138     [o_makekey_anim startAnimation];
1139     b_window_is_invisible = NO;
1140
1141     /* fullscreenAnimation will be unlocked when animation ends */
1142 }
1143
1144
1145 #pragma mark -
1146 #pragma mark Accessibility stuff
1147
1148 - (NSArray *)accessibilityAttributeNames
1149 {
1150     if (!b_dark_interface || !o_titlebar_view)
1151         return [super accessibilityAttributeNames];
1152
1153     static NSMutableArray *attributes = nil;
1154     if (attributes == nil) {
1155         attributes = [[super accessibilityAttributeNames] mutableCopy];
1156         NSArray *appendAttributes = [NSArray arrayWithObjects:NSAccessibilitySubroleAttribute,
1157                                      NSAccessibilityCloseButtonAttribute,
1158                                      NSAccessibilityMinimizeButtonAttribute,
1159                                      NSAccessibilityZoomButtonAttribute, nil];
1160
1161         for(NSString *attribute in appendAttributes) {
1162             if (![attributes containsObject:attribute])
1163                 [attributes addObject:attribute];
1164         }
1165     }
1166     return attributes;
1167 }
1168
1169 - (id)accessibilityAttributeValue: (NSString*)o_attribute_name
1170 {
1171     if (b_dark_interface && o_titlebar_view) {
1172         VLCMainWindowTitleView *o_tbv = o_titlebar_view;
1173
1174         if ([o_attribute_name isEqualTo: NSAccessibilitySubroleAttribute])
1175             return NSAccessibilityStandardWindowSubrole;
1176
1177         if ([o_attribute_name isEqualTo: NSAccessibilityCloseButtonAttribute])
1178             return [[o_tbv closeButton] cell];
1179
1180         if ([o_attribute_name isEqualTo: NSAccessibilityMinimizeButtonAttribute])
1181             return [[o_tbv minimizeButton] cell];
1182
1183         if ([o_attribute_name isEqualTo: NSAccessibilityZoomButtonAttribute])
1184             return [[o_tbv zoomButton] cell];
1185     }
1186
1187     return [super accessibilityAttributeValue: o_attribute_name];
1188 }
1189
1190 @end