]> git.sesse.net Git - vlc/blob - modules/gui/macosx/Windows.m
macosx: simplify ea98fcfc
[vlc] / modules / gui / macosx / Windows.m
1 /*****************************************************************************
2  * Windows.m: MacOS X interface module
3  *****************************************************************************
4  * Copyright (C) 2012 VLC authors and VideoLAN
5  * $Id$
6  *
7  * Authors: Felix Paul Kühne <fkuehne -at- videolan -dot- org>
8  *          David Fuhrmann <david dot fuhrmann at googlemail dot com>
9  *
10  * This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 2 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
23  *****************************************************************************/
24
25 #import "Windows.h"
26 #import "intf.h"
27 #import "CoreInteraction.h"
28 #import "ControlsBar.h"
29 #import "VideoView.h"
30
31 /*****************************************************************************
32  * VLCWindow
33  *
34  *  Missing extension to NSWindow
35  *****************************************************************************/
36
37 @implementation VLCWindow
38 - (id)initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)styleMask
39                   backing:(NSBackingStoreType)backingType defer:(BOOL)flag
40 {
41     self = [super initWithContentRect:contentRect styleMask:styleMask backing:backingType defer:flag];
42     if (self) {
43         /* we don't want this window to be restored on relaunch */
44         if (!OSX_SNOW_LEOPARD)
45             [self setRestorable:NO];
46     }
47     return self;
48 }
49
50 - (void)setCanBecomeKeyWindow: (BOOL)canBecomeKey
51 {
52     b_isset_canBecomeKeyWindow = YES;
53     b_canBecomeKeyWindow = canBecomeKey;
54 }
55
56 - (BOOL)canBecomeKeyWindow
57 {
58     if (b_isset_canBecomeKeyWindow)
59         return b_canBecomeKeyWindow;
60
61     return [super canBecomeKeyWindow];
62 }
63
64 - (void)setCanBecomeMainWindow: (BOOL)canBecomeMain
65 {
66     b_isset_canBecomeMainWindow = YES;
67     b_canBecomeMainWindow = canBecomeMain;
68 }
69
70 - (BOOL)canBecomeMainWindow
71 {
72     if (b_isset_canBecomeMainWindow)
73         return b_canBecomeMainWindow;
74
75     return [super canBecomeMainWindow];
76 }
77
78 - (void)closeAndAnimate: (BOOL)animate
79 {
80     NSInvocation *invoc;
81
82     if (!animate) {
83         [super close];
84         return;
85     }
86
87     invoc = [NSInvocation invocationWithMethodSignature:[super methodSignatureForSelector:@selector(close)]];
88     [invoc setTarget: self];
89
90     if (![self isVisible] || [self alphaValue] == 0.0) {
91         [super close];
92         return;
93     }
94
95     [self orderOut: self animate: YES callback: invoc];
96 }
97
98 - (void)orderOut: (id)sender animate: (BOOL)animate
99 {
100     NSInvocation *invoc = [NSInvocation invocationWithMethodSignature:[super methodSignatureForSelector:@selector(orderOut:)]];
101     [invoc setTarget: self];
102     [invoc setArgument: sender atIndex: 0];
103     [self orderOut: sender animate: animate callback: invoc];
104 }
105
106 - (void)orderOut: (id)sender animate: (BOOL)animate callback:(NSInvocation *)callback
107 {
108     NSViewAnimation *anim;
109     NSViewAnimation *current_anim;
110     NSMutableDictionary *dict;
111
112     if (!animate) {
113         [self orderOut: sender];
114         return;
115     }
116
117     dict = [[NSMutableDictionary alloc] initWithCapacity:2];
118
119     [dict setObject:self forKey:NSViewAnimationTargetKey];
120
121     [dict setObject:NSViewAnimationFadeOutEffect forKey:NSViewAnimationEffectKey];
122     anim = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObjects:dict, nil]];
123     [dict release];
124
125     [anim setAnimationBlockingMode:NSAnimationNonblocking];
126     [anim setDuration:0.9];
127     [anim setFrameRate:30];
128     [anim setUserInfo: callback];
129
130     @synchronized(self) {
131         current_anim = self->o_current_animation;
132
133         if ([[[current_anim viewAnimations] objectAtIndex:0] objectForKey: NSViewAnimationEffectKey] == NSViewAnimationFadeOutEffect && [current_anim isAnimating]) {
134             [anim release];
135         } else {
136             if (current_anim) {
137                 [current_anim stopAnimation];
138                 [anim setCurrentProgress:1.0 - [current_anim currentProgress]];
139                 [current_anim release];
140             }
141             else
142                 [anim setCurrentProgress:1.0 - [self alphaValue]];
143             self->o_current_animation = anim;
144             [anim startAnimation];
145         }
146     }
147 }
148
149 - (void)orderFront: (id)sender animate: (BOOL)animate
150 {
151     NSViewAnimation *anim;
152     NSViewAnimation *current_anim;
153     NSMutableDictionary *dict;
154
155     if (!animate) {
156         [super orderFront: sender];
157         [self setAlphaValue: 1.0];
158         return;
159     }
160
161     if (![self isVisible]) {
162         [self setAlphaValue: 0.0];
163         [super orderFront: sender];
164     }
165     else if ([self alphaValue] == 1.0) {
166         [super orderFront: self];
167         return;
168     }
169
170     dict = [[NSMutableDictionary alloc] initWithCapacity:2];
171
172     [dict setObject:self forKey:NSViewAnimationTargetKey];
173
174     [dict setObject:NSViewAnimationFadeInEffect forKey:NSViewAnimationEffectKey];
175     anim = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObjects:dict, nil]];
176     [dict release];
177
178     [anim setAnimationBlockingMode:NSAnimationNonblocking];
179     [anim setDuration:0.5];
180     [anim setFrameRate:30];
181
182     @synchronized(self) {
183         current_anim = self->o_current_animation;
184
185         if ([[[current_anim viewAnimations] objectAtIndex:0] objectForKey: NSViewAnimationEffectKey] == NSViewAnimationFadeInEffect && [current_anim isAnimating]) {
186             [anim release];
187         } else {
188             if (current_anim) {
189                 [current_anim stopAnimation];
190                 [anim setCurrentProgress:1.0 - [current_anim currentProgress]];
191                 [current_anim release];
192             }
193             else
194                 [anim setCurrentProgress:[self alphaValue]];
195             self->o_current_animation = anim;
196             [self orderFront: sender];
197             [anim startAnimation];
198         }
199     }
200 }
201
202 - (void)animationDidEnd:(NSAnimation*)anim
203 {
204     if ([self alphaValue] <= 0.0) {
205         NSInvocation * invoc;
206         [super orderOut: nil];
207         [self setAlphaValue: 1.0];
208         if ((invoc = [anim userInfo]))
209             [invoc invoke];
210     }
211 }
212
213 - (VLCVoutView *)videoView
214 {
215     if ([[self contentView] class] == [VLCVoutView class])
216         return (VLCVoutView *)[self contentView];
217
218     return nil;
219 }
220
221
222 @end
223
224
225 /*****************************************************************************
226  * VLCVideoWindowCommon
227  *
228  *  Common code for main window, detached window and extra video window
229  *****************************************************************************/
230
231 @interface VLCVideoWindowCommon (Internal)
232 - (void)customZoom:(id)sender;
233 - (void)hasBecomeFullscreen;
234 - (void)leaveFullscreenAndFadeOut:(BOOL)fadeout;
235 - (void)hasEndedFullscreen;
236 @end
237
238 @implementation VLCVideoWindowCommon
239
240 @synthesize videoView=o_video_view;
241 @synthesize controlsBar=o_controls_bar;
242
243 #pragma mark -
244 #pragma mark Init
245
246 - (id)initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)styleMask
247                   backing:(NSBackingStoreType)backingType defer:(BOOL)flag
248 {
249     b_dark_interface = config_GetInt(VLCIntf, "macosx-interfacestyle");
250
251     if (b_dark_interface) {
252         styleMask = NSBorderlessWindowMask;
253 #ifdef MAC_OS_X_VERSION_10_7
254         if (!OSX_SNOW_LEOPARD)
255             styleMask |= NSResizableWindowMask;
256 #endif
257     }
258
259     self = [super initWithContentRect:contentRect styleMask:styleMask
260                               backing:backingType defer:flag];
261
262     /* we want to be moveable regardless of our style */
263     [self setMovableByWindowBackground: YES];
264     [self setCanBecomeKeyWindow:YES];
265
266     o_temp_view = [[NSView alloc] init];
267     [o_temp_view setAutoresizingMask:NSViewHeightSizable | NSViewWidthSizable];
268
269     return self;
270 }
271
272 - (void)dealloc
273 {
274     [o_temp_view release];
275     [super dealloc];
276 }
277
278 - (void)setTitle:(NSString *)title
279 {
280     if (!title || [title length] < 1)
281         return;
282
283     if (b_dark_interface && o_titlebar_view)
284         [o_titlebar_view setWindowTitle: title];
285
286     [super setTitle: title];
287 }
288
289 #pragma mark -
290 #pragma mark zoom / minimize / close
291
292 - (BOOL)validateMenuItem:(NSMenuItem *)menuItem
293 {
294     SEL s_menuAction = [menuItem action];
295
296     if ((s_menuAction == @selector(performClose:)) || (s_menuAction == @selector(performMiniaturize:)) || (s_menuAction == @selector(performZoom:)))
297         return YES;
298
299     return [super validateMenuItem:menuItem];
300 }
301
302 - (BOOL)windowShouldClose:(id)sender
303 {
304     return YES;
305 }
306
307 - (void)performClose:(id)sender
308 {
309     if (!([self styleMask] & NSTitledWindowMask)) {
310         [[NSNotificationCenter defaultCenter] postNotificationName:NSWindowWillCloseNotification object:self];
311
312         [self orderOut: sender];
313     } else
314         [super performClose: sender];
315 }
316
317 - (void)performMiniaturize:(id)sender
318 {
319     if (!([self styleMask] & NSTitledWindowMask))
320         [self miniaturize: sender];
321     else
322         [super performMiniaturize: sender];
323 }
324
325 - (void)performZoom:(id)sender
326 {
327     if (!([self styleMask] & NSTitledWindowMask))
328         [self customZoom: sender];
329     else
330         [super performZoom: sender];
331 }
332
333 - (void)zoom:(id)sender
334 {
335     if (!([self styleMask] & NSTitledWindowMask))
336         [self customZoom: sender];
337     else
338         [super zoom: sender];
339 }
340
341 /**
342  * Given a proposed frame rectangle, return a modified version
343  * which will fit inside the screen.
344  *
345  * This method is based upon NSWindow.m, part of the GNUstep GUI Library, licensed under LGPLv2+.
346  *    Authors:  Scott Christley <scottc@net-community.com>, Venkat Ajjanagadde <venkat@ocbi.com>,
347  *              Felipe A. Rodriguez <far@ix.netcom.com>, Richard Frith-Macdonald <richard@brainstorm.co.uk>
348  *    Copyright (C) 1996 Free Software Foundation, Inc.
349  */
350 - (NSRect) customConstrainFrameRect: (NSRect)frameRect toScreen: (NSScreen*)screen
351 {
352     NSRect screenRect = [screen visibleFrame];
353     float difference;
354
355     /* Move top edge of the window inside the screen */
356     difference = NSMaxY (frameRect) - NSMaxY (screenRect);
357     if (difference > 0) {
358         frameRect.origin.y -= difference;
359     }
360
361     /* If the window is resizable, resize it (if needed) so that the
362      bottom edge is on the screen or can be on the screen when the user moves
363      the window */
364     difference = NSMaxY (screenRect) - NSMaxY (frameRect);
365     if (_styleMask & NSResizableWindowMask) {
366         float difference2;
367
368         difference2 = screenRect.origin.y - frameRect.origin.y;
369         difference2 -= difference;
370         // Take in account the space between the top of window and the top of the
371         // screen which can be used to move the bottom of the window on the screen
372         if (difference2 > 0) {
373             frameRect.size.height -= difference2;
374             frameRect.origin.y += difference2;
375         }
376
377         /* Ensure that resizing doesn't makewindow smaller than minimum */
378         difference2 = [self minSize].height - frameRect.size.height;
379         if (difference2 > 0) {
380             frameRect.size.height += difference2;
381             frameRect.origin.y -= difference2;
382         }
383     }
384
385     return frameRect;
386 }
387
388 #define DIST 3
389
390 /**
391  Zooms the receiver.   This method calls the delegate method
392  windowShouldZoom:toFrame: to determine if the window should
393  be allowed to zoom to full screen.
394  *
395  * This method is based upon NSWindow.m, part of the GNUstep GUI Library, licensed under LGPLv2+.
396  *    Authors:  Scott Christley <scottc@net-community.com>, Venkat Ajjanagadde <venkat@ocbi.com>,
397  *              Felipe A. Rodriguez <far@ix.netcom.com>, Richard Frith-Macdonald <richard@brainstorm.co.uk>
398  *    Copyright (C) 1996 Free Software Foundation, Inc.
399  */
400 - (void) customZoom: (id)sender
401 {
402     NSRect maxRect = [[self screen] visibleFrame];
403     NSRect currentFrame = [self frame];
404
405     if ([[self delegate] respondsToSelector: @selector(windowWillUseStandardFrame:defaultFrame:)]) {
406         maxRect = [[self delegate] windowWillUseStandardFrame: self defaultFrame: maxRect];
407     }
408
409     maxRect = [self customConstrainFrameRect: maxRect toScreen: [self screen]];
410
411     // Compare the new frame with the current one
412     if ((abs(NSMaxX(maxRect) - NSMaxX(currentFrame)) < DIST)
413         && (abs(NSMaxY(maxRect) - NSMaxY(currentFrame)) < DIST)
414         && (abs(NSMinX(maxRect) - NSMinX(currentFrame)) < DIST)
415         && (abs(NSMinY(maxRect) - NSMinY(currentFrame)) < DIST)) {
416         // Already in zoomed mode, reset user frame, if stored
417         if ([self frameAutosaveName] != nil) {
418             [self setFrame: previousSavedFrame display: YES animate: YES];
419             [self saveFrameUsingName: [self frameAutosaveName]];
420         }
421         return;
422     }
423
424     if ([self frameAutosaveName] != nil) {
425         [self saveFrameUsingName: [self frameAutosaveName]];
426         previousSavedFrame = [self frame];
427     }
428
429     [self setFrame: maxRect display: YES animate: YES];
430 }
431
432 #pragma mark -
433 #pragma mark Video window resizing logic
434
435 - (NSRect)getWindowRectForProposedVideoViewSize:(NSSize)size
436 {
437     NSSize windowMinSize = [self minSize];
438     NSRect screenFrame = [[self screen] visibleFrame];
439
440     NSPoint topleftbase = NSMakePoint(0, [self frame].size.height);
441     NSPoint topleftscreen = [self convertBaseToScreen: topleftbase];
442
443     unsigned int i_width = size.width;
444     unsigned int i_height = size.height;
445     if (i_width < windowMinSize.width)
446         i_width = windowMinSize.width;
447     if (i_height < f_min_video_height)
448         i_height = f_min_video_height;
449
450     /* Calculate the window's new size */
451     NSRect new_frame;
452     new_frame.size.width = [self frame].size.width - [o_video_view frame].size.width + i_width;
453     new_frame.size.height = [self frame].size.height - [o_video_view frame].size.height + i_height;
454     new_frame.origin.x = topleftscreen.x;
455     new_frame.origin.y = topleftscreen.y - new_frame.size.height;
456
457     /* make sure the window doesn't exceed the screen size the window is on */
458     if (new_frame.size.width > screenFrame.size.width) {
459         new_frame.size.width = screenFrame.size.width;
460         new_frame.origin.x = screenFrame.origin.x;
461     }
462     if (new_frame.size.height > screenFrame.size.height) {
463         new_frame.size.height = screenFrame.size.height;
464         new_frame.origin.y = screenFrame.origin.y;
465     }
466     if (new_frame.origin.y < screenFrame.origin.y)
467         new_frame.origin.y = screenFrame.origin.y;
468
469     CGFloat right_screen_point = screenFrame.origin.x + screenFrame.size.width;
470     CGFloat right_window_point = new_frame.origin.x + new_frame.size.width;
471     if (right_window_point > right_screen_point)
472         new_frame.origin.x -= (right_window_point - right_screen_point);
473
474     return new_frame;
475 }
476
477 - (void)resizeWindow
478 {
479     if ([[VLCMainWindow sharedInstance] fullscreen])
480         return;
481
482     NSRect window_rect = [self getWindowRectForProposedVideoViewSize:nativeVideoSize];
483     [[self animator] setFrame:window_rect display:YES];
484 }
485
486 - (void)setNativeVideoSize:(NSSize)size
487 {
488     nativeVideoSize = size;
489
490     if (var_InheritBool(VLCIntf, "macosx-video-autoresize") && !var_InheritBool(VLCIntf, "video-wallpaper"))
491         [self resizeWindow];
492 }
493
494 - (NSSize)windowWillResize:(NSWindow *)window toSize:(NSSize)proposedFrameSize
495 {
496     if (![[VLCMain sharedInstance] activeVideoPlayback] || nativeVideoSize.width == 0. || nativeVideoSize.height == 0. || window != self)
497         return proposedFrameSize;
498
499     // needed when entering lion fullscreen mode
500     if ([[VLCMainWindow sharedInstance] fullscreen])
501         return proposedFrameSize;
502
503     if ([[VLCCoreInteraction sharedInstance] aspectRatioIsLocked]) {
504         NSRect videoWindowFrame = [self frame];
505         NSRect viewRect = [o_video_view convertRect:[o_video_view bounds] toView: nil];
506         NSRect contentRect = [self contentRectForFrameRect:videoWindowFrame];
507         float marginy = viewRect.origin.y + videoWindowFrame.size.height - contentRect.size.height;
508         float marginx = contentRect.size.width - viewRect.size.width;
509         if (o_titlebar_view && b_dark_interface)
510             marginy += [o_titlebar_view frame].size.height;
511
512         proposedFrameSize.height = (proposedFrameSize.width - marginx) * nativeVideoSize.height / nativeVideoSize.width + marginy;
513     }
514
515     return proposedFrameSize;
516 }
517
518 #pragma mark -
519 #pragma mark Fullscreen Logic
520
521 - (void)lockFullscreenAnimation
522 {
523     [o_animation_lock lock];
524 }
525
526 - (void)unlockFullscreenAnimation
527 {
528     [o_animation_lock unlock];
529 }
530
531 - (void)enterFullscreen
532 {
533     NSMutableDictionary *dict1, *dict2;
534     NSScreen *screen;
535     NSRect screen_rect;
536     NSRect rect;
537     BOOL blackout_other_displays = var_InheritBool(VLCIntf, "macosx-black");
538
539     screen = [NSScreen screenWithDisplayID:(CGDirectDisplayID)var_InheritInteger(VLCIntf, "macosx-vdev")];
540     [self lockFullscreenAnimation];
541
542     if (!screen) {
543         msg_Dbg(VLCIntf, "chosen screen isn't present, using current screen for fullscreen mode");
544         screen = [self screen];
545     }
546     if (!screen) {
547         msg_Dbg(VLCIntf, "Using deepest screen");
548         screen = [NSScreen deepestScreen];
549     }
550
551     screen_rect = [screen frame];
552
553     if (o_controls_bar)
554         [o_controls_bar setFullscreenState:YES];
555     [[[VLCMainWindow sharedInstance] controlsBar] setFullscreenState:YES];
556
557     [[VLCMainWindow sharedInstance] recreateHideMouseTimer];
558
559     if (blackout_other_displays)
560         [screen blackoutOtherScreens];
561
562     /* Make sure we don't see the window flashes in float-on-top mode */
563     i_originalLevel = [self level];
564     [self setLevel:NSNormalWindowLevel];
565
566     /* Only create the o_fullscreen_window if we are not in the middle of the zooming animation */
567     if (!o_fullscreen_window) {
568         /* We can't change the styleMask of an already created NSWindow, so we create another window, and do eye catching stuff */
569
570         rect = [[o_video_view superview] convertRect: [o_video_view frame] toView: nil]; /* Convert to Window base coord */
571         rect.origin.x += [self frame].origin.x;
572         rect.origin.y += [self frame].origin.y;
573         o_fullscreen_window = [[VLCWindow alloc] initWithContentRect:rect styleMask: NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:YES];
574         [o_fullscreen_window setBackgroundColor: [NSColor blackColor]];
575         [o_fullscreen_window setCanBecomeKeyWindow: YES];
576         [o_fullscreen_window setCanBecomeMainWindow: YES];
577
578         if (![self isVisible] || [self alphaValue] == 0.0) {
579             /* We don't animate if we are not visible, instead we
580              * simply fade the display */
581             CGDisplayFadeReservationToken token;
582
583             if (blackout_other_displays) {
584                 CGAcquireDisplayFadeReservation(kCGMaxDisplayReservationInterval, &token);
585                 CGDisplayFade(token, 0.5, kCGDisplayBlendNormal, kCGDisplayBlendSolidColor, 0, 0, 0, YES);
586             }
587
588             NSApplicationPresentationOptions presentationOpts = [NSApp presentationOptions];
589             if ([screen hasMenuBar])
590                 presentationOpts |= NSApplicationPresentationAutoHideMenuBar;
591             if ([screen hasMenuBar] || [screen hasDock])
592                 presentationOpts |= NSApplicationPresentationAutoHideDock;
593             [NSApp setPresentationOptions:presentationOpts];
594
595             [[o_video_view superview] replaceSubview:o_video_view with:o_temp_view];
596             [o_temp_view setFrame:[o_video_view frame]];
597             [o_fullscreen_window setContentView:o_video_view];
598
599             [o_fullscreen_window makeKeyAndOrderFront:self];
600             [o_fullscreen_window orderFront:self animate:YES];
601
602             [o_fullscreen_window setFrame:screen_rect display:YES animate:YES];
603             [o_fullscreen_window setLevel:NSNormalWindowLevel];
604
605             if (blackout_other_displays) {
606                 CGDisplayFade(token, 0.3, kCGDisplayBlendSolidColor, kCGDisplayBlendNormal, 0, 0, 0, NO);
607                 CGReleaseDisplayFadeReservation(token);
608             }
609
610             /* Will release the lock */
611             [self hasBecomeFullscreen];
612
613             return;
614         }
615
616         /* Make sure we don't see the o_video_view disappearing of the screen during this operation */
617         NSDisableScreenUpdates();
618         [[o_video_view superview] replaceSubview:o_video_view with:o_temp_view];
619         [o_temp_view setFrame:[o_video_view frame]];
620         [o_fullscreen_window setContentView:o_video_view];
621         [o_fullscreen_window makeKeyAndOrderFront:self];
622         NSEnableScreenUpdates();
623     }
624
625     /* We are in fullscreen (and no animation is running) */
626     if ([[VLCMainWindow sharedInstance] fullscreen]) {
627         /* Make sure we are hidden */
628         [self orderOut: self];
629
630         [self unlockFullscreenAnimation];
631         return;
632     }
633
634     if (o_fullscreen_anim1) {
635         [o_fullscreen_anim1 stopAnimation];
636         [o_fullscreen_anim1 release];
637     }
638     if (o_fullscreen_anim2) {
639         [o_fullscreen_anim2 stopAnimation];
640         [o_fullscreen_anim2 release];
641     }
642
643     NSApplicationPresentationOptions presentationOpts = [NSApp presentationOptions];
644     if ([screen hasMenuBar])
645         presentationOpts |= NSApplicationPresentationAutoHideMenuBar;
646     if ([screen hasMenuBar] || [screen hasDock])
647         presentationOpts |= NSApplicationPresentationAutoHideDock;
648     [NSApp setPresentationOptions:presentationOpts];
649
650     dict1 = [[NSMutableDictionary alloc] initWithCapacity:2];
651     dict2 = [[NSMutableDictionary alloc] initWithCapacity:3];
652
653     [dict1 setObject:self forKey:NSViewAnimationTargetKey];
654     [dict1 setObject:NSViewAnimationFadeOutEffect forKey:NSViewAnimationEffectKey];
655
656     [dict2 setObject:o_fullscreen_window forKey:NSViewAnimationTargetKey];
657     [dict2 setObject:[NSValue valueWithRect:[o_fullscreen_window frame]] forKey:NSViewAnimationStartFrameKey];
658     [dict2 setObject:[NSValue valueWithRect:screen_rect] forKey:NSViewAnimationEndFrameKey];
659
660     /* Strategy with NSAnimation allocation:
661      - Keep at most 2 animation at a time
662      - leaveFullscreen/enterFullscreen are the only responsible for releasing and alloc-ing
663      */
664     o_fullscreen_anim1 = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObject:dict1]];
665     o_fullscreen_anim2 = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObject:dict2]];
666
667     [dict1 release];
668     [dict2 release];
669
670     [o_fullscreen_anim1 setAnimationBlockingMode: NSAnimationNonblocking];
671     [o_fullscreen_anim1 setDuration: 0.3];
672     [o_fullscreen_anim1 setFrameRate: 30];
673     [o_fullscreen_anim2 setAnimationBlockingMode: NSAnimationNonblocking];
674     [o_fullscreen_anim2 setDuration: 0.2];
675     [o_fullscreen_anim2 setFrameRate: 30];
676
677     [o_fullscreen_anim2 setDelegate: self];
678     [o_fullscreen_anim2 startWhenAnimation: o_fullscreen_anim1 reachesProgress: 1.0];
679
680     [o_fullscreen_anim1 startAnimation];
681     /* fullscreenAnimation will be unlocked when animation ends */
682 }
683
684 - (void)hasBecomeFullscreen
685 {
686     if ([[o_video_view subviews] count] > 0)
687         [o_fullscreen_window makeFirstResponder: [[o_video_view subviews] objectAtIndex:0]];
688
689     [o_fullscreen_window makeKeyWindow];
690     [o_fullscreen_window setAcceptsMouseMovedEvents: YES];
691
692     /* tell the fspanel to move itself to front next time it's triggered */
693     [[[VLCMainWindow sharedInstance] fsPanel] setVoutWasUpdated: o_fullscreen_window];
694     [[[VLCMainWindow sharedInstance] fsPanel] setActive: nil];
695
696     if ([self isVisible])
697         [self orderOut: self];
698
699     [[VLCMainWindow sharedInstance] setFullscreen:YES];
700     [self unlockFullscreenAnimation];
701 }
702
703 - (void)leaveFullscreen
704 {
705     [self leaveFullscreenAndFadeOut: NO];
706 }
707
708 - (void)leaveFullscreenAndFadeOut: (BOOL)fadeout
709 {
710     NSMutableDictionary *dict1, *dict2;
711     NSRect frame;
712     BOOL blackout_other_displays = var_InheritBool(VLCIntf, "macosx-black");
713
714     [self lockFullscreenAnimation];
715
716     if (o_controls_bar)
717         [o_controls_bar setFullscreenState:NO];
718     [[[VLCMainWindow sharedInstance] controlsBar] setFullscreenState:NO];
719
720     /* We always try to do so */
721     [NSScreen unblackoutScreens];
722
723     vout_thread_t *p_vout = getVoutForActiveWindow();
724     if (p_vout) {
725         if (var_GetBool(p_vout, "video-on-top"))
726             [[o_video_view window] setLevel: NSStatusWindowLevel];
727         else
728             [[o_video_view window] setLevel: NSNormalWindowLevel];
729         vlc_object_release(p_vout);
730     }
731     [[o_video_view window] makeKeyAndOrderFront: nil];
732
733     /* Don't do anything if o_fullscreen_window is already closed */
734     if (!o_fullscreen_window) {
735         [self unlockFullscreenAnimation];
736         return;
737     }
738
739     if (fadeout) {
740         /* We don't animate if we are not visible, instead we
741          * simply fade the display */
742         CGDisplayFadeReservationToken token;
743
744         if (blackout_other_displays) {
745             CGAcquireDisplayFadeReservation(kCGMaxDisplayReservationInterval, &token);
746             CGDisplayFade(token, 0.3, kCGDisplayBlendNormal, kCGDisplayBlendSolidColor, 0, 0, 0, YES);
747         }
748
749         [[[VLCMainWindow sharedInstance] fsPanel] setNonActive: nil];
750         [NSApp setPresentationOptions: NSApplicationPresentationDefault];
751
752         /* Will release the lock */
753         [self hasEndedFullscreen];
754
755         /* Our window is hidden, and might be faded. We need to workaround that, so note it
756          * here */
757         b_window_is_invisible = YES;
758
759         if (blackout_other_displays) {
760             CGDisplayFade(token, 0.5, kCGDisplayBlendSolidColor, kCGDisplayBlendNormal, 0, 0, 0, NO);
761             CGReleaseDisplayFadeReservation(token);
762         }
763
764         return;
765     }
766
767     [self setAlphaValue: 0.0];
768     [self orderFront: self];
769     [[o_video_view window] orderFront: self];
770
771     [[[VLCMainWindow sharedInstance] fsPanel] setNonActive: nil];
772     [NSApp setPresentationOptions:(NSApplicationPresentationDefault)];
773
774     if (o_fullscreen_anim1) {
775         [o_fullscreen_anim1 stopAnimation];
776         [o_fullscreen_anim1 release];
777     }
778     if (o_fullscreen_anim2) {
779         [o_fullscreen_anim2 stopAnimation];
780         [o_fullscreen_anim2 release];
781     }
782
783     frame = [[o_temp_view superview] convertRect: [o_temp_view frame] toView: nil]; /* Convert to Window base coord */
784     frame.origin.x += [self frame].origin.x;
785     frame.origin.y += [self frame].origin.y;
786
787     dict2 = [[NSMutableDictionary alloc] initWithCapacity:2];
788     [dict2 setObject:self forKey:NSViewAnimationTargetKey];
789     [dict2 setObject:NSViewAnimationFadeInEffect forKey:NSViewAnimationEffectKey];
790
791     o_fullscreen_anim2 = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObjects:dict2, nil]];
792     [dict2 release];
793
794     [o_fullscreen_anim2 setAnimationBlockingMode: NSAnimationNonblocking];
795     [o_fullscreen_anim2 setDuration: 0.3];
796     [o_fullscreen_anim2 setFrameRate: 30];
797
798     [o_fullscreen_anim2 setDelegate: self];
799
800     dict1 = [[NSMutableDictionary alloc] initWithCapacity:3];
801
802     [dict1 setObject:o_fullscreen_window forKey:NSViewAnimationTargetKey];
803     [dict1 setObject:[NSValue valueWithRect:[o_fullscreen_window frame]] forKey:NSViewAnimationStartFrameKey];
804     [dict1 setObject:[NSValue valueWithRect:frame] forKey:NSViewAnimationEndFrameKey];
805
806     o_fullscreen_anim1 = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObjects:dict1, nil]];
807     [dict1 release];
808
809     [o_fullscreen_anim1 setAnimationBlockingMode: NSAnimationNonblocking];
810     [o_fullscreen_anim1 setDuration: 0.2];
811     [o_fullscreen_anim1 setFrameRate: 30];
812     [o_fullscreen_anim2 startWhenAnimation: o_fullscreen_anim1 reachesProgress: 1.0];
813
814     /* Make sure o_fullscreen_window is the frontmost window */
815     [o_fullscreen_window orderFront: self];
816
817     [o_fullscreen_anim1 startAnimation];
818     /* fullscreenAnimation will be unlocked when animation ends */
819 }
820
821 - (void)hasEndedFullscreen
822 {
823     [[VLCMainWindow sharedInstance] setFullscreen:NO];
824
825     /* This function is private and should be only triggered at the end of the fullscreen change animation */
826     /* Make sure we don't see the o_video_view disappearing of the screen during this operation */
827     NSDisableScreenUpdates();
828     [o_video_view retain];
829     [o_video_view removeFromSuperviewWithoutNeedingDisplay];
830     [[o_temp_view superview] replaceSubview:o_temp_view with:o_video_view];
831     [o_video_view release];
832     [o_video_view setFrame:[o_temp_view frame]];
833     if ([[o_video_view subviews] count] > 0)
834         [self makeFirstResponder: [[o_video_view subviews] objectAtIndex:0]];
835
836     [super makeKeyAndOrderFront:self]; /* our version (in main window) contains a workaround */
837
838     [o_fullscreen_window orderOut: self];
839     NSEnableScreenUpdates();
840
841     [o_fullscreen_window release];
842     o_fullscreen_window = nil;
843     [self setLevel:i_originalLevel];
844     [self setAlphaValue: config_GetFloat(VLCIntf, "macosx-opaqueness")];
845
846     // if we quit fullscreen because there is no video anymore, make sure non-embedded window is not visible
847     if (![[VLCMain sharedInstance] activeVideoPlayback] && [self class] != [VLCMainWindow class])
848         [self orderOut: self];
849
850     [self unlockFullscreenAnimation];
851 }
852
853 - (void)animationDidEnd:(NSAnimation*)animation
854 {
855     NSArray *viewAnimations;
856     if (o_makekey_anim == animation) {
857         [o_makekey_anim release];
858         return;
859     }
860     if ([animation currentValue] < 1.0)
861         return;
862
863     /* Fullscreen ended or started (we are a delegate only for leaveFullscreen's/enterFullscren's anim2) */
864     viewAnimations = [o_fullscreen_anim2 viewAnimations];
865     if ([viewAnimations count] >=1 &&
866         [[[viewAnimations objectAtIndex: 0] objectForKey: NSViewAnimationEffectKey] isEqualToString:NSViewAnimationFadeInEffect]) {
867         /* Fullscreen ended */
868         [self hasEndedFullscreen];
869     } else
870     /* Fullscreen started */
871         [self hasBecomeFullscreen];
872 }
873
874 #pragma mark -
875 #pragma mark Accessibility stuff
876
877 - (NSArray *)accessibilityAttributeNames
878 {
879     if (!b_dark_interface || !o_titlebar_view)
880         return [super accessibilityAttributeNames];
881
882     static NSMutableArray *attributes = nil;
883     if (attributes == nil) {
884         attributes = [[super accessibilityAttributeNames] mutableCopy];
885         NSArray *appendAttributes = [NSArray arrayWithObjects: NSAccessibilitySubroleAttribute,
886                                      NSAccessibilityCloseButtonAttribute,
887                                      NSAccessibilityMinimizeButtonAttribute,
888                                      NSAccessibilityZoomButtonAttribute,
889                                      nil];
890
891         for(NSString *attribute in appendAttributes) {
892             if (![attributes containsObject:attribute])
893                 [attributes addObject:attribute];
894         }
895     }
896     return attributes;
897 }
898
899 - (id)accessibilityAttributeValue: (NSString*)o_attribute_name
900 {
901     if (b_dark_interface && o_titlebar_view) {
902         VLCMainWindowTitleView *o_tbv = o_titlebar_view;
903
904         if ([o_attribute_name isEqualTo: NSAccessibilitySubroleAttribute])
905             return NSAccessibilityStandardWindowSubrole;
906
907         if ([o_attribute_name isEqualTo: NSAccessibilityCloseButtonAttribute])
908             return [[o_tbv closeButton] cell];
909
910         if ([o_attribute_name isEqualTo: NSAccessibilityMinimizeButtonAttribute])
911             return [[o_tbv minimizeButton] cell];
912
913         if ([o_attribute_name isEqualTo: NSAccessibilityZoomButtonAttribute])
914             return [[o_tbv zoomButton] cell];
915     }
916
917     return [super accessibilityAttributeValue: o_attribute_name];
918 }
919
920 @end