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