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