macosx: clean code of objectAtIndex
[vlc/vlc-acra.git] / modules / gui / macosx / Windows.m
blobdc15ad9391e2e379b8d6b65c2b9921130b169c70
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  *****************************************************************************/
25 #import "Windows.h"
26 #import "intf.h"
27 #import "CoreInteraction.h"
28 #import "ControlsBar.h"
29 #import "VideoView.h"
31 /*****************************************************************************
32  * VLCWindow
33  *
34  *  Missing extension to NSWindow
35  *****************************************************************************/
37 @implementation VLCWindow
39 @synthesize hasActiveVideo=b_has_active_video;
40 @synthesize fullscreen=b_fullscreen;
42 - (id)initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)styleMask
43                   backing:(NSBackingStoreType)backingType defer:(BOOL)flag
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;
54 - (void)setCanBecomeKeyWindow: (BOOL)canBecomeKey
56     b_isset_canBecomeKeyWindow = YES;
57     b_canBecomeKeyWindow = canBecomeKey;
60 - (BOOL)canBecomeKeyWindow
62     if (b_isset_canBecomeKeyWindow)
63         return b_canBecomeKeyWindow;
65     return [super canBecomeKeyWindow];
68 - (void)setCanBecomeMainWindow: (BOOL)canBecomeMain
70     b_isset_canBecomeMainWindow = YES;
71     b_canBecomeMainWindow = canBecomeMain;
74 - (BOOL)canBecomeMainWindow
76     if (b_isset_canBecomeMainWindow)
77         return b_canBecomeMainWindow;
79     return [super canBecomeMainWindow];
82 - (void)closeAndAnimate: (BOOL)animate
84     NSInvocation *invoc;
86     if (!animate) {
87         [super close];
88         return;
89     }
91     invoc = [NSInvocation invocationWithMethodSignature:[super methodSignatureForSelector:@selector(close)]];
92     [invoc setTarget: self];
94     if (![self isVisible] || [self alphaValue] == 0.0) {
95         [super close];
96         return;
97     }
99     [self orderOut: self animate: YES callback: invoc];
102 - (void)orderOut: (id)sender animate: (BOOL)animate
104     NSInvocation *invoc = [NSInvocation invocationWithMethodSignature:[super methodSignatureForSelector:@selector(orderOut:)]];
105     [invoc setTarget: self];
106     [invoc setArgument: sender atIndex: 0];
107     [self orderOut: sender animate: animate callback: invoc];
110 - (void)orderOut: (id)sender animate: (BOOL)animate callback:(NSInvocation *)callback
112     NSViewAnimation *anim;
113     NSViewAnimation *current_anim;
114     NSMutableDictionary *dict;
116     if (!animate) {
117         [self orderOut: sender];
118         return;
119     }
121     dict = [[NSMutableDictionary alloc] initWithCapacity:2];
123     [dict setObject:self forKey:NSViewAnimationTargetKey];
125     [dict setObject:NSViewAnimationFadeOutEffect forKey:NSViewAnimationEffectKey];
126     anim = [[NSViewAnimation alloc] initWithViewAnimations:@[dict]];
127     [dict release];
129     [anim setAnimationBlockingMode:NSAnimationNonblocking];
130     [anim setDuration:0.9];
131     [anim setFrameRate:30];
132     [anim setUserInfo: callback];
134     @synchronized(self) {
135         current_anim = self->o_current_animation;
137         if ([[current_anim viewAnimations][0] objectForKey: NSViewAnimationEffectKey] == NSViewAnimationFadeOutEffect && [current_anim isAnimating]) {
138             [anim release];
139         } else {
140             if (current_anim) {
141                 [current_anim stopAnimation];
142                 [anim setCurrentProgress:1.0 - [current_anim currentProgress]];
143                 [current_anim release];
144             }
145             else
146                 [anim setCurrentProgress:1.0 - [self alphaValue]];
147             self->o_current_animation = anim;
148             [anim startAnimation];
149         }
150     }
153 - (void)orderFront: (id)sender animate: (BOOL)animate
155     NSViewAnimation *anim;
156     NSViewAnimation *current_anim;
157     NSMutableDictionary *dict;
159     if (!animate) {
160         [super orderFront: sender];
161         [self setAlphaValue: 1.0];
162         return;
163     }
165     if (![self isVisible]) {
166         [self setAlphaValue: 0.0];
167         [super orderFront: sender];
168     }
169     else if ([self alphaValue] == 1.0) {
170         [super orderFront: self];
171         return;
172     }
174     dict = [[NSMutableDictionary alloc] initWithCapacity:2];
176     [dict setObject:self forKey:NSViewAnimationTargetKey];
178     [dict setObject:NSViewAnimationFadeInEffect forKey:NSViewAnimationEffectKey];
179     anim = [[NSViewAnimation alloc] initWithViewAnimations:@[dict]];
180     [dict release];
182     [anim setAnimationBlockingMode:NSAnimationNonblocking];
183     [anim setDuration:0.5];
184     [anim setFrameRate:30];
186     @synchronized(self) {
187         current_anim = self->o_current_animation;
189         if ([[current_anim viewAnimations][0] objectForKey: NSViewAnimationEffectKey] == NSViewAnimationFadeInEffect && [current_anim isAnimating]) {
190             [anim release];
191         } else {
192             if (current_anim) {
193                 [current_anim stopAnimation];
194                 [anim setCurrentProgress:1.0 - [current_anim currentProgress]];
195                 [current_anim release];
196             }
197             else
198                 [anim setCurrentProgress:[self alphaValue]];
199             self->o_current_animation = anim;
200             [self orderFront: sender];
201             [anim startAnimation];
202         }
203     }
206 - (void)animationDidEnd:(NSAnimation*)anim
208     if ([self alphaValue] <= 0.0) {
209         NSInvocation * invoc;
210         [super orderOut: nil];
211         [self setAlphaValue: 1.0];
212         if ((invoc = [anim userInfo]))
213             [invoc invoke];
214     }
217 - (VLCVoutView *)videoView
219     if ([[self contentView] class] == [VLCVoutView class])
220         return (VLCVoutView *)[self contentView];
222     return nil;
226 @end
229 /*****************************************************************************
230  * VLCVideoWindowCommon
232  *  Common code for main window, detached window and extra video window
233  *****************************************************************************/
235 @interface VLCVideoWindowCommon (Internal)
236 - (void)customZoom:(id)sender;
237 - (void)hasBecomeFullscreen;
238 - (void)leaveFullscreenAndFadeOut:(BOOL)fadeout;
239 - (void)hasEndedFullscreen;
240 @end
242 @implementation VLCVideoWindowCommon
244 @synthesize videoView=o_video_view;
245 @synthesize controlsBar=o_controls_bar;
246 @synthesize enteringFullscreenTransition=b_entering_fullscreen_transition;
248 #pragma mark -
249 #pragma mark Init
251 - (id)initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)styleMask
252                   backing:(NSBackingStoreType)backingType defer:(BOOL)flag
254     b_dark_interface = config_GetInt(VLCIntf, "macosx-interfacestyle");
256     if (b_dark_interface) {
257         styleMask = NSBorderlessWindowMask;
258 #ifdef MAC_OS_X_VERSION_10_7
259         if (!OSX_SNOW_LEOPARD)
260             styleMask |= NSResizableWindowMask;
261 #endif
262     }
264     self = [super initWithContentRect:contentRect styleMask:styleMask
265                               backing:backingType defer:flag];
267     /* we want to be moveable regardless of our style */
268     [self setMovableByWindowBackground: YES];
269     [self setCanBecomeKeyWindow:YES];
271     o_temp_view = [[NSView alloc] init];
272     [o_temp_view setAutoresizingMask:NSViewHeightSizable | NSViewWidthSizable];
274     return self;
277 - (void)dealloc
279     [o_temp_view release];
280     [super dealloc];
283 - (void)awakeFromNib
285     BOOL b_nativeFullscreenMode = NO;
286 #ifdef MAC_OS_X_VERSION_10_7
287     if (!OSX_SNOW_LEOPARD)
288         b_nativeFullscreenMode = var_InheritBool(VLCIntf, "macosx-nativefullscreenmode");
289 #endif
291     if (b_nativeFullscreenMode) {
292         [self setCollectionBehavior: NSWindowCollectionBehaviorFullScreenPrimary];
293     } else {
294         [o_titlebar_view setFullscreenButtonHidden: YES];
295     }
297     [super awakeFromNib];
300 - (void)setTitle:(NSString *)title
302     if (!title || [title length] < 1)
303         return;
305     if (b_dark_interface && o_titlebar_view)
306         [o_titlebar_view setWindowTitle: title];
308     [super setTitle: title];
311 #pragma mark -
312 #pragma mark zoom / minimize / close
314 - (BOOL)validateMenuItem:(NSMenuItem *)menuItem
316     SEL s_menuAction = [menuItem action];
318     if ((s_menuAction == @selector(performClose:)) || (s_menuAction == @selector(performMiniaturize:)) || (s_menuAction == @selector(performZoom:)))
319         return YES;
321     return [super validateMenuItem:menuItem];
324 - (BOOL)windowShouldClose:(id)sender
326     return YES;
329 - (void)performClose:(id)sender
331     if (!([self styleMask] & NSTitledWindowMask)) {
332         [[NSNotificationCenter defaultCenter] postNotificationName:NSWindowWillCloseNotification object:self];
334         [self orderOut: sender];
335     } else
336         [super performClose: sender];
339 - (void)performMiniaturize:(id)sender
341     if (!([self styleMask] & NSTitledWindowMask))
342         [self miniaturize: sender];
343     else
344         [super performMiniaturize: sender];
347 - (void)performZoom:(id)sender
349     if (!([self styleMask] & NSTitledWindowMask))
350         [self customZoom: sender];
351     else
352         [super performZoom: sender];
355 - (void)zoom:(id)sender
357     if (!([self styleMask] & NSTitledWindowMask))
358         [self customZoom: sender];
359     else
360         [super zoom: sender];
364  * Given a proposed frame rectangle, return a modified version
365  * which will fit inside the screen.
367  * This method is based upon NSWindow.m, part of the GNUstep GUI Library, licensed under LGPLv2+.
368  *    Authors:  Scott Christley <scottc@net-community.com>, Venkat Ajjanagadde <venkat@ocbi.com>,
369  *              Felipe A. Rodriguez <far@ix.netcom.com>, Richard Frith-Macdonald <richard@brainstorm.co.uk>
370  *    Copyright (C) 1996 Free Software Foundation, Inc.
371  */
372 - (NSRect) customConstrainFrameRect: (NSRect)frameRect toScreen: (NSScreen*)screen
374     NSRect screenRect = [screen visibleFrame];
375     float difference;
377     /* Move top edge of the window inside the screen */
378     difference = NSMaxY (frameRect) - NSMaxY (screenRect);
379     if (difference > 0) {
380         frameRect.origin.y -= difference;
381     }
383     /* If the window is resizable, resize it (if needed) so that the
384      bottom edge is on the screen or can be on the screen when the user moves
385      the window */
386     difference = NSMaxY (screenRect) - NSMaxY (frameRect);
387     if (_styleMask & NSResizableWindowMask) {
388         float difference2;
390         difference2 = screenRect.origin.y - frameRect.origin.y;
391         difference2 -= difference;
392         // Take in account the space between the top of window and the top of the
393         // screen which can be used to move the bottom of the window on the screen
394         if (difference2 > 0) {
395             frameRect.size.height -= difference2;
396             frameRect.origin.y += difference2;
397         }
399         /* Ensure that resizing doesn't makewindow smaller than minimum */
400         difference2 = [self minSize].height - frameRect.size.height;
401         if (difference2 > 0) {
402             frameRect.size.height += difference2;
403             frameRect.origin.y -= difference2;
404         }
405     }
407     return frameRect;
410 #define DIST 3
413  Zooms the receiver.   This method calls the delegate method
414  windowShouldZoom:toFrame: to determine if the window should
415  be allowed to zoom to full screen.
417  * This method is based upon NSWindow.m, part of the GNUstep GUI Library, licensed under LGPLv2+.
418  *    Authors:  Scott Christley <scottc@net-community.com>, Venkat Ajjanagadde <venkat@ocbi.com>,
419  *              Felipe A. Rodriguez <far@ix.netcom.com>, Richard Frith-Macdonald <richard@brainstorm.co.uk>
420  *    Copyright (C) 1996 Free Software Foundation, Inc.
421  */
422 - (void) customZoom: (id)sender
424     NSRect maxRect = [[self screen] visibleFrame];
425     NSRect currentFrame = [self frame];
427     if ([[self delegate] respondsToSelector: @selector(windowWillUseStandardFrame:defaultFrame:)]) {
428         maxRect = [[self delegate] windowWillUseStandardFrame: self defaultFrame: maxRect];
429     }
431     maxRect = [self customConstrainFrameRect: maxRect toScreen: [self screen]];
433     // Compare the new frame with the current one
434     if ((abs(NSMaxX(maxRect) - NSMaxX(currentFrame)) < DIST)
435         && (abs(NSMaxY(maxRect) - NSMaxY(currentFrame)) < DIST)
436         && (abs(NSMinX(maxRect) - NSMinX(currentFrame)) < DIST)
437         && (abs(NSMinY(maxRect) - NSMinY(currentFrame)) < DIST)) {
438         // Already in zoomed mode, reset user frame, if stored
439         if ([self frameAutosaveName] != nil) {
440             [self setFrame: previousSavedFrame display: YES animate: YES];
441             [self saveFrameUsingName: [self frameAutosaveName]];
442         }
443         return;
444     }
446     if ([self frameAutosaveName] != nil) {
447         [self saveFrameUsingName: [self frameAutosaveName]];
448         previousSavedFrame = [self frame];
449     }
451     [self setFrame: maxRect display: YES animate: YES];
454 #pragma mark -
455 #pragma mark Video window resizing logic
457 - (void)setWindowLevel:(NSInteger)i_state
459     if (var_InheritBool(VLCIntf, "video-wallpaper") || [self level] < NSNormalWindowLevel)
460         return;
462     [self setLevel: i_state];
466 - (NSRect)getWindowRectForProposedVideoViewSize:(NSSize)size
468     NSSize windowMinSize = [self minSize];
469     NSRect screenFrame = [[self screen] visibleFrame];
471     NSPoint topleftbase = NSMakePoint(0, [self frame].size.height);
472     NSPoint topleftscreen = [self convertBaseToScreen: topleftbase];
474     unsigned int i_width = size.width;
475     unsigned int i_height = size.height;
476     if (i_width < windowMinSize.width)
477         i_width = windowMinSize.width;
478     if (i_height < f_min_video_height)
479         i_height = f_min_video_height;
481     /* Calculate the window's new size */
482     NSRect new_frame;
483     new_frame.size.width = [self frame].size.width - [o_video_view frame].size.width + i_width;
484     new_frame.size.height = [self frame].size.height - [o_video_view frame].size.height + i_height;
485     new_frame.origin.x = topleftscreen.x;
486     new_frame.origin.y = topleftscreen.y - new_frame.size.height;
488     /* make sure the window doesn't exceed the screen size the window is on */
489     if (new_frame.size.width > screenFrame.size.width) {
490         new_frame.size.width = screenFrame.size.width;
491         new_frame.origin.x = screenFrame.origin.x;
492     }
493     if (new_frame.size.height > screenFrame.size.height) {
494         new_frame.size.height = screenFrame.size.height;
495         new_frame.origin.y = screenFrame.origin.y;
496     }
497     if (new_frame.origin.y < screenFrame.origin.y)
498         new_frame.origin.y = screenFrame.origin.y;
500     CGFloat right_screen_point = screenFrame.origin.x + screenFrame.size.width;
501     CGFloat right_window_point = new_frame.origin.x + new_frame.size.width;
502     if (right_window_point > right_screen_point)
503         new_frame.origin.x -= (right_window_point - right_screen_point);
505     return new_frame;
508 - (void)resizeWindow
510     if ([self fullscreen])
511         return;
513     NSRect window_rect = [self getWindowRectForProposedVideoViewSize:nativeVideoSize];
514     [[self animator] setFrame:window_rect display:YES];
517 - (void)setNativeVideoSize:(NSSize)size
519     nativeVideoSize = size;
521     if (var_InheritBool(VLCIntf, "macosx-video-autoresize") && !var_InheritBool(VLCIntf, "video-wallpaper"))
522         [self resizeWindow];
525 - (NSSize)windowWillResize:(NSWindow *)window toSize:(NSSize)proposedFrameSize
527     if (![[VLCMain sharedInstance] activeVideoPlayback] || nativeVideoSize.width == 0. || nativeVideoSize.height == 0. || window != self)
528         return proposedFrameSize;
530     // needed when entering lion fullscreen mode
531     if (b_entering_fullscreen_transition || [self fullscreen])
532         return proposedFrameSize;
534     if ([[VLCCoreInteraction sharedInstance] aspectRatioIsLocked]) {
535         NSRect videoWindowFrame = [self frame];
536         NSRect viewRect = [o_video_view convertRect:[o_video_view bounds] toView: nil];
537         NSRect contentRect = [self contentRectForFrameRect:videoWindowFrame];
538         float marginy = viewRect.origin.y + videoWindowFrame.size.height - contentRect.size.height;
539         float marginx = contentRect.size.width - viewRect.size.width;
540         if (o_titlebar_view && b_dark_interface)
541             marginy += [o_titlebar_view frame].size.height;
543         proposedFrameSize.height = (proposedFrameSize.width - marginx) * nativeVideoSize.height / nativeVideoSize.width + marginy;
544     }
546     return proposedFrameSize;
550 #pragma mark -
551 #pragma mark Mouse cursor handling
553 //  NSTimer selectors require this function signature as per Apple's docs
554 - (void)hideMouseCursor:(NSTimer *)timer
556     [NSCursor setHiddenUntilMouseMoves: YES];
559 - (void)recreateHideMouseTimer
561     if (t_hide_mouse_timer != nil) {
562         [t_hide_mouse_timer invalidate];
563         [t_hide_mouse_timer release];
564     }
566     t_hide_mouse_timer = [NSTimer scheduledTimerWithTimeInterval:2
567                                                           target:self
568                                                         selector:@selector(hideMouseCursor:)
569                                                         userInfo:nil
570                                                          repeats:NO];
571     [t_hide_mouse_timer retain];
574 //  Called automatically if window's acceptsMouseMovedEvents property is true
575 - (void)mouseMoved:(NSEvent *)theEvent
577     if (b_fullscreen)
578         [self recreateHideMouseTimer];
580     [super mouseMoved: theEvent];
583 #pragma mark -
584 #pragma mark Lion native fullscreen handling
586 - (void)becomeKeyWindow
588     [super becomeKeyWindow];
590     // change fspanel state for the case when multiple windows are in fullscreen
591     if ([self hasActiveVideo] && [self fullscreen])
592         [[[VLCMainWindow sharedInstance] fsPanel] setActive:nil];
593     else
594         [[[VLCMainWindow sharedInstance] fsPanel] setNonActive:nil];
597 - (void)resignKeyWindow
599     [super resignKeyWindow];
601     [[[VLCMainWindow sharedInstance] fsPanel] setNonActive:nil];
604 - (void)windowWillEnterFullScreen:(NSNotification *)notification
606     // workaround, see #6668
607     [NSApp setPresentationOptions:(NSApplicationPresentationFullScreen | NSApplicationPresentationAutoHideDock | NSApplicationPresentationAutoHideMenuBar)];
609     b_entering_fullscreen_transition = YES;
611     var_SetBool(pl_Get(VLCIntf), "fullscreen", true);
613     if ([self hasActiveVideo]) {
614         vout_thread_t *p_vout = getVoutForActiveWindow();
615         if (p_vout) {
616             var_SetBool(p_vout, "fullscreen", true);
617             vlc_object_release(p_vout);
618         }
619     }
621     if ([self hasActiveVideo])
622         [[VLCMainWindow sharedInstance] recreateHideMouseTimer];
624     i_originalLevel = [self level];
625     [[[VLCMain sharedInstance] voutController] updateWindowLevelForHelperWindows: NSNormalWindowLevel];
626     [self setLevel:NSNormalWindowLevel];
628     if (b_dark_interface) {
629         [o_titlebar_view removeFromSuperviewWithoutNeedingDisplay];
631         NSRect winrect;
632         CGFloat f_titleBarHeight = [o_titlebar_view frame].size.height;
633         winrect = [self frame];
635         winrect.size.height = winrect.size.height - f_titleBarHeight;
636         [self setFrame: winrect display:NO animate:NO];
637     }
639     [o_video_view setFrame: [[self contentView] frame]];
640     if (![o_video_view isHidden]) {
641         [[o_controls_bar bottomBarView] setHidden: YES];
642     }
643     
645     [self setMovableByWindowBackground: NO];
648 - (void)windowDidEnterFullScreen:(NSNotification *)notification
650     // Indeed, we somehow can have an "inactive" fullscreen (but a visible window!).
651     // But this creates some problems when leaving fs over remote intfs, so activate app here.
652     [NSApp activateIgnoringOtherApps:YES];
654     [self setFullscreen: YES];
655     b_entering_fullscreen_transition = NO;
657     if ([self hasActiveVideo]) {
658         [[[VLCMainWindow sharedInstance] fsPanel] setVoutWasUpdated: self];
659         if (![o_video_view isHidden])
660             [[[VLCMainWindow sharedInstance] fsPanel] setActive: nil];
661     }
663     NSArray *subviews = [[self videoView] subviews];
664     NSUInteger count = [subviews count];
666     for (NSUInteger x = 0; x < count; x++) {
667         if ([subviews[x] respondsToSelector:@selector(reshape)])
668             [subviews[x] reshape];
669     }
673 - (void)windowWillExitFullScreen:(NSNotification *)notification
675     [self setFullscreen: NO];
677     var_SetBool(pl_Get(VLCIntf), "fullscreen", false);
679     if ([self hasActiveVideo]) {
680         vout_thread_t *p_vout = getVoutForActiveWindow();
681         if (p_vout) {
682             var_SetBool(p_vout, "fullscreen", false);
683             vlc_object_release(p_vout);
684         }
685     }
687     [NSCursor setHiddenUntilMouseMoves: NO];
688     [[[VLCMainWindow sharedInstance] fsPanel] setNonActive: nil];
689     
690     [[[VLCMain sharedInstance] voutController] updateWindowLevelForHelperWindows: i_originalLevel];
691     [self setLevel:i_originalLevel];
693     if (b_dark_interface) {
694         NSRect winrect;
695         CGFloat f_titleBarHeight = [o_titlebar_view frame].size.height;
697         winrect = [o_video_view frame];
698         winrect.size.height -= f_titleBarHeight;
699         [o_video_view setFrame: winrect];
701         winrect = [self frame];
702         [o_titlebar_view setFrame: NSMakeRect(0, winrect.size.height - f_titleBarHeight,
703                                               winrect.size.width, f_titleBarHeight)];
704         [[self contentView] addSubview: o_titlebar_view];
706         winrect.size.height = winrect.size.height + f_titleBarHeight;
707         [self setFrame: winrect display:NO animate:NO];
708     }
710     NSRect videoViewFrame = [o_video_view frame];
711     videoViewFrame.origin.y += [o_controls_bar height];
712     videoViewFrame.size.height -= [o_controls_bar height];
713     [o_video_view setFrame: videoViewFrame];
715     if (![o_video_view isHidden]) {
716         [[o_controls_bar bottomBarView] setHidden: NO];
717     }
718     
719     [self setMovableByWindowBackground: YES];
722 #pragma mark -
723 #pragma mark Fullscreen Logic
725 - (void)lockFullscreenAnimation
727     [o_animation_lock lock];
730 - (void)unlockFullscreenAnimation
732     [o_animation_lock unlock];
735 - (void)enterFullscreen
737     NSMutableDictionary *dict1, *dict2;
738     NSScreen *screen;
739     NSRect screen_rect;
740     NSRect rect;
741     BOOL blackout_other_displays = var_InheritBool(VLCIntf, "macosx-black");
743     screen = [NSScreen screenWithDisplayID:(CGDirectDisplayID)var_InheritInteger(VLCIntf, "macosx-vdev")];
744     [self lockFullscreenAnimation];
746     if (!screen) {
747         msg_Dbg(VLCIntf, "chosen screen isn't present, using current screen for fullscreen mode");
748         screen = [self screen];
749     }
750     if (!screen) {
751         msg_Dbg(VLCIntf, "Using deepest screen");
752         screen = [NSScreen deepestScreen];
753     }
755     screen_rect = [screen frame];
757     if (o_controls_bar)
758         [o_controls_bar setFullscreenState:YES];
759     [[[VLCMainWindow sharedInstance] controlsBar] setFullscreenState:YES];
761     [[VLCMainWindow sharedInstance] recreateHideMouseTimer];
763     if (blackout_other_displays)
764         [screen blackoutOtherScreens];
766     /* Make sure we don't see the window flashes in float-on-top mode */
767     i_originalLevel = [self level];
768     [[[VLCMain sharedInstance] voutController] updateWindowLevelForHelperWindows: NSNormalWindowLevel];
769     [self setLevel:NSNormalWindowLevel];
772     /* Only create the o_fullscreen_window if we are not in the middle of the zooming animation */
773     if (!o_fullscreen_window) {
774         /* We can't change the styleMask of an already created NSWindow, so we create another window, and do eye catching stuff */
776         rect = [[o_video_view superview] convertRect: [o_video_view frame] toView: nil]; /* Convert to Window base coord */
777         rect.origin.x += [self frame].origin.x;
778         rect.origin.y += [self frame].origin.y;
779         o_fullscreen_window = [[VLCWindow alloc] initWithContentRect:rect styleMask: NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:YES];
780         [o_fullscreen_window setBackgroundColor: [NSColor blackColor]];
781         [o_fullscreen_window setCanBecomeKeyWindow: YES];
782         [o_fullscreen_window setCanBecomeMainWindow: YES];
783         [o_fullscreen_window setHasActiveVideo: YES];
784         [o_fullscreen_window setFullscreen: YES];
786         if (![self isVisible] || [self alphaValue] == 0.0) {
787             /* We don't animate if we are not visible, instead we
788              * simply fade the display */
789             CGDisplayFadeReservationToken token;
791             if (blackout_other_displays) {
792                 CGAcquireDisplayFadeReservation(kCGMaxDisplayReservationInterval, &token);
793                 CGDisplayFade(token, 0.5, kCGDisplayBlendNormal, kCGDisplayBlendSolidColor, 0, 0, 0, YES);
794             }
796             [screen setFullscreenPresentationOptions];
798             [[o_video_view superview] replaceSubview:o_video_view with:o_temp_view];
799             [o_temp_view setFrame:[o_video_view frame]];
800             [o_fullscreen_window setContentView:o_video_view];
802             [o_fullscreen_window makeKeyAndOrderFront:self];
803             [o_fullscreen_window orderFront:self animate:YES];
805             [o_fullscreen_window setFrame:screen_rect display:YES animate:YES];
806             [o_fullscreen_window setLevel:NSNormalWindowLevel];
808             if (blackout_other_displays) {
809                 CGDisplayFade(token, 0.3, kCGDisplayBlendSolidColor, kCGDisplayBlendNormal, 0, 0, 0, NO);
810                 CGReleaseDisplayFadeReservation(token);
811             }
813             /* Will release the lock */
814             [self hasBecomeFullscreen];
816             return;
817         }
819         /* Make sure we don't see the o_video_view disappearing of the screen during this operation */
820         NSDisableScreenUpdates();
821         [[o_video_view superview] replaceSubview:o_video_view with:o_temp_view];
822         [o_temp_view setFrame:[o_video_view frame]];
823         [o_fullscreen_window setContentView:o_video_view];
824         [o_fullscreen_window makeKeyAndOrderFront:self];
825         NSEnableScreenUpdates();
826     }
828     /* We are in fullscreen (and no animation is running) */
829     if ([self fullscreen]) {
830         /* Make sure we are hidden */
831         [self orderOut: self];
833         [self unlockFullscreenAnimation];
834         return;
835     }
837     if (o_fullscreen_anim1) {
838         [o_fullscreen_anim1 stopAnimation];
839         [o_fullscreen_anim1 release];
840     }
841     if (o_fullscreen_anim2) {
842         [o_fullscreen_anim2 stopAnimation];
843         [o_fullscreen_anim2 release];
844     }
846     [screen setFullscreenPresentationOptions];
848     dict1 = [[NSMutableDictionary alloc] initWithCapacity:2];
849     dict2 = [[NSMutableDictionary alloc] initWithCapacity:3];
851     [dict1 setObject:self forKey:NSViewAnimationTargetKey];
852     [dict1 setObject:NSViewAnimationFadeOutEffect forKey:NSViewAnimationEffectKey];
854     [dict2 setObject:o_fullscreen_window forKey:NSViewAnimationTargetKey];
855     [dict2 setObject:[NSValue valueWithRect:[o_fullscreen_window frame]] forKey:NSViewAnimationStartFrameKey];
856     [dict2 setObject:[NSValue valueWithRect:screen_rect] forKey:NSViewAnimationEndFrameKey];
858     /* Strategy with NSAnimation allocation:
859      - Keep at most 2 animation at a time
860      - leaveFullscreen/enterFullscreen are the only responsible for releasing and alloc-ing
861      */
862     o_fullscreen_anim1 = [[NSViewAnimation alloc] initWithViewAnimations:@[dict1]];
863     o_fullscreen_anim2 = [[NSViewAnimation alloc] initWithViewAnimations:@[dict2]];
865     [dict1 release];
866     [dict2 release];
868     [o_fullscreen_anim1 setAnimationBlockingMode: NSAnimationNonblocking];
869     [o_fullscreen_anim1 setDuration: 0.3];
870     [o_fullscreen_anim1 setFrameRate: 30];
871     [o_fullscreen_anim2 setAnimationBlockingMode: NSAnimationNonblocking];
872     [o_fullscreen_anim2 setDuration: 0.2];
873     [o_fullscreen_anim2 setFrameRate: 30];
875     [o_fullscreen_anim2 setDelegate: self];
876     [o_fullscreen_anim2 startWhenAnimation: o_fullscreen_anim1 reachesProgress: 1.0];
878     [o_fullscreen_anim1 startAnimation];
879     /* fullscreenAnimation will be unlocked when animation ends */
882 - (void)hasBecomeFullscreen
884     if ([[o_video_view subviews] count] > 0)
885         [o_fullscreen_window makeFirstResponder: [o_video_view subviews][0]];
887     [o_fullscreen_window makeKeyWindow];
888     [o_fullscreen_window setAcceptsMouseMovedEvents: YES];
890     /* tell the fspanel to move itself to front next time it's triggered */
891     [[[VLCMainWindow sharedInstance] fsPanel] setVoutWasUpdated: o_fullscreen_window];
892     [[[VLCMainWindow sharedInstance] fsPanel] setActive: nil];
894     if ([self isVisible])
895         [self orderOut: self];
897     [self setFullscreen:YES];
898     [self unlockFullscreenAnimation];
901 - (void)leaveFullscreen
903     [self leaveFullscreenAndFadeOut: NO];
906 - (void)leaveFullscreenAndFadeOut: (BOOL)fadeout
908     NSMutableDictionary *dict1, *dict2;
909     NSRect frame;
910     BOOL blackout_other_displays = var_InheritBool(VLCIntf, "macosx-black");
912     [self lockFullscreenAnimation];
914     if (o_controls_bar)
915         [o_controls_bar setFullscreenState:NO];
916     [[[VLCMainWindow sharedInstance] controlsBar] setFullscreenState:NO];
918     /* We always try to do so */
919     [NSScreen unblackoutScreens];
921     [[o_video_view window] makeKeyAndOrderFront: nil];
923     /* Don't do anything if o_fullscreen_window is already closed */
924     if (!o_fullscreen_window) {
925         [self unlockFullscreenAnimation];
926         return;
927     }
929     if (fadeout) {
930         /* We don't animate if we are not visible, instead we
931          * simply fade the display */
932         CGDisplayFadeReservationToken token;
934         if (blackout_other_displays) {
935             CGAcquireDisplayFadeReservation(kCGMaxDisplayReservationInterval, &token);
936             CGDisplayFade(token, 0.3, kCGDisplayBlendNormal, kCGDisplayBlendSolidColor, 0, 0, 0, YES);
937         }
939         [[[VLCMainWindow sharedInstance] fsPanel] setNonActive: nil];
940         [[o_fullscreen_window screen] setNonFullscreenPresentationOptions];
942         /* Will release the lock */
943         [self hasEndedFullscreen];
945         /* Our window is hidden, and might be faded. We need to workaround that, so note it
946          * here */
947         b_window_is_invisible = YES;
949         if (blackout_other_displays) {
950             CGDisplayFade(token, 0.5, kCGDisplayBlendSolidColor, kCGDisplayBlendNormal, 0, 0, 0, NO);
951             CGReleaseDisplayFadeReservation(token);
952         }
954         return;
955     }
957     [self setAlphaValue: 0.0];
958     [self orderFront: self];
959     [[o_video_view window] orderFront: self];
961     [[[VLCMainWindow sharedInstance] fsPanel] setNonActive: nil];
962     [[o_fullscreen_window screen] setNonFullscreenPresentationOptions];
964     if (o_fullscreen_anim1) {
965         [o_fullscreen_anim1 stopAnimation];
966         [o_fullscreen_anim1 release];
967     }
968     if (o_fullscreen_anim2) {
969         [o_fullscreen_anim2 stopAnimation];
970         [o_fullscreen_anim2 release];
971     }
973     frame = [[o_temp_view superview] convertRect: [o_temp_view frame] toView: nil]; /* Convert to Window base coord */
974     frame.origin.x += [self frame].origin.x;
975     frame.origin.y += [self frame].origin.y;
977     dict2 = [[NSMutableDictionary alloc] initWithCapacity:2];
978     [dict2 setObject:self forKey:NSViewAnimationTargetKey];
979     [dict2 setObject:NSViewAnimationFadeInEffect forKey:NSViewAnimationEffectKey];
981     o_fullscreen_anim2 = [[NSViewAnimation alloc] initWithViewAnimations:@[dict2]];
982     [dict2 release];
984     [o_fullscreen_anim2 setAnimationBlockingMode: NSAnimationNonblocking];
985     [o_fullscreen_anim2 setDuration: 0.3];
986     [o_fullscreen_anim2 setFrameRate: 30];
988     [o_fullscreen_anim2 setDelegate: self];
990     dict1 = [[NSMutableDictionary alloc] initWithCapacity:3];
992     [dict1 setObject:o_fullscreen_window forKey:NSViewAnimationTargetKey];
993     [dict1 setObject:[NSValue valueWithRect:[o_fullscreen_window frame]] forKey:NSViewAnimationStartFrameKey];
994     [dict1 setObject:[NSValue valueWithRect:frame] forKey:NSViewAnimationEndFrameKey];
996     o_fullscreen_anim1 = [[NSViewAnimation alloc] initWithViewAnimations:@[dict1]];
997     [dict1 release];
999     [o_fullscreen_anim1 setAnimationBlockingMode: NSAnimationNonblocking];
1000     [o_fullscreen_anim1 setDuration: 0.2];
1001     [o_fullscreen_anim1 setFrameRate: 30];
1002     [o_fullscreen_anim2 startWhenAnimation: o_fullscreen_anim1 reachesProgress: 1.0];
1004     /* Make sure o_fullscreen_window is the frontmost window */
1005     [o_fullscreen_window orderFront: self];
1007     [o_fullscreen_anim1 startAnimation];
1008     /* fullscreenAnimation will be unlocked when animation ends */
1011 - (void)hasEndedFullscreen
1013     [self setFullscreen:NO];
1015     /* This function is private and should be only triggered at the end of the fullscreen change animation */
1016     /* Make sure we don't see the o_video_view disappearing of the screen during this operation */
1017     NSDisableScreenUpdates();
1018     [o_video_view retain];
1019     [o_video_view removeFromSuperviewWithoutNeedingDisplay];
1020     [[o_temp_view superview] replaceSubview:o_temp_view with:o_video_view];
1021     [o_video_view release];
1022     [o_video_view setFrame:[o_temp_view frame]];
1023     if ([[o_video_view subviews] count] > 0)
1024         [self makeFirstResponder: [o_video_view subviews][0]];
1026     [super makeKeyAndOrderFront:self]; /* our version (in main window) contains a workaround */
1028     [o_fullscreen_window orderOut: self];
1029     NSEnableScreenUpdates();
1031     [o_fullscreen_window release];
1032     o_fullscreen_window = nil;
1033     
1034     [[[VLCMain sharedInstance] voutController] updateWindowLevelForHelperWindows: i_originalLevel];
1035     [self setLevel:i_originalLevel];
1036     [self setAlphaValue: config_GetFloat(VLCIntf, "macosx-opaqueness")];
1038     // if we quit fullscreen because there is no video anymore, make sure non-embedded window is not visible
1039     if (![[VLCMain sharedInstance] activeVideoPlayback] && [self class] != [VLCMainWindow class])
1040         [self orderOut: self];
1042     [self unlockFullscreenAnimation];
1045 - (void)animationDidEnd:(NSAnimation*)animation
1047     NSArray *viewAnimations;
1048     if (o_makekey_anim == animation) {
1049         [o_makekey_anim release];
1050         return;
1051     }
1052     if ([animation currentValue] < 1.0)
1053         return;
1055     /* Fullscreen ended or started (we are a delegate only for leaveFullscreen's/enterFullscren's anim2) */
1056     viewAnimations = [o_fullscreen_anim2 viewAnimations];
1057     if ([viewAnimations count] >=1 &&
1058         [[viewAnimations[0] objectForKey: NSViewAnimationEffectKey] isEqualToString:NSViewAnimationFadeInEffect]) {
1059         /* Fullscreen ended */
1060         [self hasEndedFullscreen];
1061     } else
1062     /* Fullscreen started */
1063         [self hasBecomeFullscreen];
1066 #pragma mark -
1067 #pragma mark Accessibility stuff
1069 - (NSArray *)accessibilityAttributeNames
1071     if (!b_dark_interface || !o_titlebar_view)
1072         return [super accessibilityAttributeNames];
1074     static NSMutableArray *attributes = nil;
1075     if (attributes == nil) {
1076         attributes = [[super accessibilityAttributeNames] mutableCopy];
1077         NSArray *appendAttributes = @[NSAccessibilitySubroleAttribute,
1078                                       NSAccessibilityCloseButtonAttribute,
1079                                       NSAccessibilityMinimizeButtonAttribute,
1080                                       NSAccessibilityZoomButtonAttribute];
1082         for(NSString *attribute in appendAttributes) {
1083             if (![attributes containsObject:attribute])
1084                 [attributes addObject:attribute];
1085         }
1086     }
1087     return attributes;
1090 - (id)accessibilityAttributeValue: (NSString*)o_attribute_name
1092     if (b_dark_interface && o_titlebar_view) {
1093         VLCMainWindowTitleView *o_tbv = o_titlebar_view;
1095         if ([o_attribute_name isEqualTo: NSAccessibilitySubroleAttribute])
1096             return NSAccessibilityStandardWindowSubrole;
1098         if ([o_attribute_name isEqualTo: NSAccessibilityCloseButtonAttribute])
1099             return [[o_tbv closeButton] cell];
1101         if ([o_attribute_name isEqualTo: NSAccessibilityMinimizeButtonAttribute])
1102             return [[o_tbv minimizeButton] cell];
1104         if ([o_attribute_name isEqualTo: NSAccessibilityZoomButtonAttribute])
1105             return [[o_tbv zoomButton] cell];
1106     }
1108     return [super accessibilityAttributeValue: o_attribute_name];
1111 @end