MacOSX/VLC_app: port the fullscreen zoom and the fspanel from gui/macosx to VLC_app.
[vlc.git] / extras / MacOSX / VLC_app / Sources / VLCBrowsableVideoView.m
1 /*****************************************************************************
2  * VLCBrowsableVideoView.h: VideoView subclasses that allow fullScreen
3  * browsing
4  *****************************************************************************
5  * Copyright (C) 2007 Pierre d'Herbemont
6  * Copyright (C) 2007 the VideoLAN team
7  * $Id$
8  *
9  * Authors: Pierre d'Herbemont <pdherbemont # videolan.org>
10  *
11  * This program is free software; you can redistribute it and/or modify
12  * it under the terms of the GNU General Public License as published by
13  * the Free Software Foundation; either version 2 of the License, or
14  * (at your option) any later version.
15  *
16  * This program is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19  * GNU General Public License for more details.
20  *
21  * You should have received a copy of the GNU General Public License
22  * along with this program; if not, write to the Free Software
23  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
24  *****************************************************************************/
25
26 /* DisableScreenUpdates, SetSystemUIMode, ... */
27 #import <QuickTime/QuickTime.h>
28
29 #import "VLCBrowsableVideoView.h"
30 #import "VLCAppAdditions.h"
31
32 /* TODO: We may want to clean up the private functions a bit... */
33
34 @interface VLCBrowsableVideoView ()
35 /* Property */
36 @property (readwrite, retain) id selectedObject;
37 @end
38
39 @interface VLCBrowsableVideoView (Private)
40
41 /* Methods */
42 + (CAScrollLayer *)menuLayer;
43 + (CALayer *)backLayer;
44
45 - (void)loadItemsAtIndexPath:(NSIndexPath *)path inLayer:(CALayer *)layer;
46 - (void)changeSelectedIndex:(NSInteger)i;
47 - (void)changeSelectedPath:(NSIndexPath *)newPath withSelectedIndex:(NSUInteger)newIndex;
48
49 - (void)displayEmptyView;
50 @end
51
52
53 @interface VLCBrowsableVideoView (FullScreenTransition)
54 - (void)hasEndedFullScreen;
55 - (void)hasBecomeFullScreen;
56
57 - (void)enterFullScreen:(NSScreen *)screen;
58 - (void)leaveFullScreen;
59 - (void)leaveFullScreenAndFadeOut: (BOOL)fadeout;
60
61 @end
62
63 /******************************************************************************
64  * VLCBrowsableVideoView
65  */
66 @implementation VLCBrowsableVideoView
67
68 /* Property */
69 @synthesize nodeKeyPath;
70 @synthesize contentKeyPath;
71 @synthesize selectedObject;
72 @synthesize target;
73 @synthesize action;
74
75 - (NSArray *)itemsTree {
76     return itemsTree;
77 }
78
79 - (void)setItemsTree:(NSArray *)newItemsTree
80 {
81     [itemsTree release];
82     itemsTree = [newItemsTree retain];
83     [self changeSelectedPath:[[[NSIndexPath alloc] init] autorelease] withSelectedIndex:0];
84 }
85
86 - (BOOL)fullScreen
87 {
88     return [self isInFullScreenMode];
89 }
90
91 - (void)setFullScreen:(BOOL)newFullScreen
92 {
93     if( newFullScreen == self.fullScreen )
94         return;
95     
96     if( newFullScreen )
97     {
98         [self enterFullScreenMode:[[self window] screen] withOptions:
99                     [NSDictionary dictionaryWithObject: [NSNumber numberWithInt:1]
100                                                 forKey: NSFullScreenModeWindowLevel]];
101     }
102     else
103     {
104         [self exitFullScreenModeWithOptions:nil];
105     }
106 }
107
108 /* Initializer */
109 - (void)awakeFromNib
110 {
111     // FIXME: do that in -initWithFrame:
112     [self setWantsLayer:YES];
113     menuDisplayed = NO;
114     displayedItems = NSMakeRange( -1, 0 );
115     selectedIndex = -1;
116     selectionLayer = backLayer = nil;
117     menuLayer = nil;
118     selectedPath = [[NSIndexPath alloc] init];
119     tempFullScreenView = [[NSView alloc] init];
120     fullScreen = NO;
121     /* Observe our bindings */
122     //[self displayMenu];
123     //[self changeSelectedIndex:0];
124 }
125
126 - (void)dealloc
127 {
128     [tempFullScreenView release];
129     [selectedPath release];
130     [super dealloc];
131 }
132
133 /* Hiding/Displaying the menu */
134
135 - (void)hideMenu
136 {
137     if( !menuDisplayed )
138         return; /* Nothing to do */
139
140     [menuLayer removeFromSuperlayer];
141     [selectionLayer removeFromSuperlayer];
142     [backLayer removeFromSuperlayer];
143     //[menuLayer autorelease]; /* Need gc for that */
144     //[selectionLayer autorelease];
145     //[backLayer autorelease];
146     selectionLayer = backLayer = nil;
147     menuLayer = nil;
148     menuDisplayed = NO;
149 }
150
151 - (void)displayMenu
152 {
153     if( menuDisplayed || !self.itemsTree )
154         return; /* Nothing to do */
155
156     if( !menuLayer )
157     {
158         CALayer * rootLayer = [self layer];
159         rootLayer.backgroundColor = CGColorGetConstantColor(kCGColorBlack);
160         rootLayer.layoutManager = [CAConstraintLayoutManager layoutManager];
161         menuLayer = [VLCBrowsableVideoView menuLayer];
162         [self loadItemsAtIndexPath: selectedPath inLayer: menuLayer];
163     }
164     if( !backLayer )
165     {
166         backLayer = [[VLCBrowsableVideoView backLayer] retain];
167     }
168     [[self layer] addSublayer:backLayer];
169     [[self layer] addSublayer:menuLayer];
170
171     [[self layer] setNeedsLayout];
172     [[self layer] setNeedsDisplay];
173
174     menuDisplayed = YES;
175     [self changeSelectedPath:selectedPath withSelectedIndex:selectedIndex];
176 }
177
178 - (void)toggleMenu
179 {
180     if( menuDisplayed )
181         [self hideMenu];
182     else
183         [self displayMenu];
184 }
185
186
187 /* Event handling */
188
189 - (BOOL)acceptsFirstResponder
190 {
191     return YES;
192 }
193
194 -(void)moveUp:(id)sender
195 {
196     [self changeSelectedIndex:selectedIndex-1];
197 }
198
199 -(void)moveDown:(id)sender
200 {
201     [self changeSelectedIndex:selectedIndex+1];
202 }
203
204 - (void)mouseDown:(NSEvent *)theEvent
205 {
206     if([theEvent clickCount] != 2)
207         return;
208
209     self.fullScreen = !self.fullScreen;
210 }
211
212 - (void)keyDown:(NSEvent *)theEvent
213 {
214     if(([[theEvent charactersIgnoringModifiers] characterAtIndex:0] == 13) && menuDisplayed)
215     {
216         [self changeSelectedPath:[selectedPath indexPathByAddingIndex:selectedIndex] withSelectedIndex:0];
217     }
218     else if([[theEvent charactersIgnoringModifiers] characterAtIndex:0] ==  NSLeftArrowFunctionKey && menuDisplayed)
219     {
220         if( [selectedPath length] > 0 )
221             [self changeSelectedPath:[selectedPath indexPathByRemovingLastIndex] withSelectedIndex:[selectedPath lastIndex]];
222         else
223             [self hideMenu];
224     }
225     else if(!menuDisplayed)
226     {
227         [self displayMenu];
228     }
229     else
230         [super keyDown: theEvent];
231
232 }
233
234 - (void)enterFullScreenMode:(NSScreen *)screen withOptions:(NSDictionary *)options
235 {
236     [self enterFullScreen: screen];
237 }
238
239 - (void)exitFullScreenModeWithOptions:(NSDictionary *)options
240 {
241     [self leaveFullScreen];
242
243 }
244
245 - (BOOL)isInFullScreenMode
246 {
247     return fullScreen;
248 }
249
250 @end
251
252 /******************************************************************************
253  * VLCBrowsableVideoView (Private)
254  */
255 @implementation VLCBrowsableVideoView (Private)
256 + (CAScrollLayer *)menuLayer
257 {
258     CAScrollLayer * layer = [CAScrollLayer layer];
259     layer.scrollMode = kCAScrollVertically;
260             
261     [layer addConstraint:[CAConstraint constraintWithAttribute:kCAConstraintMaxY
262                                        relativeTo:@"superlayer" attribute:kCAConstraintMaxY]];
263     [layer addConstraint:[CAConstraint constraintWithAttribute:kCAConstraintMaxX
264                                        relativeTo:@"superlayer" attribute:kCAConstraintMaxX]];
265     [layer addConstraint:[CAConstraint constraintWithAttribute:kCAConstraintMinX
266                                        relativeTo:@"superlayer" attribute:kCAConstraintMinX]];
267     [layer addConstraint:[CAConstraint constraintWithAttribute:kCAConstraintMinY
268                                        relativeTo:@"superlayer" attribute:kCAConstraintMinY]];
269     return layer;
270 }
271
272 + (CALayer *)backLayer
273 {
274     CALayer * layer = [CALayer layer];
275             
276     [layer addConstraint:[CAConstraint constraintWithAttribute:kCAConstraintMaxY
277                                        relativeTo:@"superlayer" attribute:kCAConstraintMaxY]];
278     [layer addConstraint:[CAConstraint constraintWithAttribute:kCAConstraintMaxX
279                                        relativeTo:@"superlayer" attribute:kCAConstraintMaxX]];
280     [layer addConstraint:[CAConstraint constraintWithAttribute:kCAConstraintMinX
281                                        relativeTo:@"superlayer" attribute:kCAConstraintMinX]];
282     [layer addConstraint:[CAConstraint constraintWithAttribute:kCAConstraintMinY
283                                        relativeTo:@"superlayer" attribute:kCAConstraintMinY]];
284
285     layer.opacity = 1.0;
286     layer.backgroundColor = CGColorCreateGenericRGB(0., 0., 0., .5);
287     return layer;
288 }
289
290
291 - (void)loadItemsAtIndexPath:(NSIndexPath *)path inLayer:(CALayer *)layer
292 {
293     const CGFloat height=70.0;
294     const CGFloat fontSize=48.0;
295     NSArray * items = [self.itemsTree objectAtIndexPath:path withNodeKeyPath:self.nodeKeyPath]; 
296     int i;
297
298     for( i = 0; i < [items count]; i++ )
299     {
300         CATextLayer *menuItemLayer=[CATextLayer layer];
301         id item = [items objectAtIndex: i];
302         menuItemLayer.string = self.contentKeyPath ? [item valueForKeyPath:self.contentKeyPath] : @"No content Key path set";
303         menuItemLayer.font = @"BankGothic-Light";
304         menuItemLayer.fontSize = fontSize;
305         menuItemLayer.foregroundColor = CGColorCreateGenericRGB(1.0,1.0,1.0,1.0);
306         menuItemLayer.shadowColor = CGColorCreateGenericRGB(0.0,0.0,0.0,1.0);
307         menuItemLayer.shadowOpacity = 0.7;
308         menuItemLayer.shadowRadius = 2.0;
309
310         menuItemLayer.frame = CGRectMake( 40., height*(-i) + layer.visibleRect.size.height, 500.0f,70.);
311         [layer addSublayer: menuItemLayer];
312     }
313
314 /*    for(i=0; i < [[layer sublayers] count]; i++)
315         NSLog(@"%d, %@", i, [[[layer sublayers] objectAtIndex: i] string]);
316     NSLog(@"---");*/
317 }
318
319 - (void)changeSelectedIndex:(NSInteger)i
320 {
321     BOOL justCreatedSelectionLayer = NO;
322     if( !menuDisplayed )
323     {
324         selectedIndex = i;
325         return;
326     }
327
328     if( !selectionLayer )
329     {
330         justCreatedSelectionLayer = YES;
331         /* Rip-off from Apple's Sample code */
332         selectionLayer=[[CALayer layer] retain];
333         
334         selectionLayer.borderWidth=2.0;
335         selectionLayer.borderColor=CGColorCreateGenericRGB(1.0f,1.0f,1.0f,1.0f);
336         selectionLayer.backgroundColor=CGColorCreateGenericRGB(.9f,1.0f,1.0f,.1f);
337         
338         CIFilter *filter = [CIFilter filterWithName:@"CIBloom"];
339         [filter setDefaults];
340         [filter setValue:[NSNumber numberWithFloat:5.0] forKey:@"inputRadius"];
341         
342         [filter setName:@"pulseFilter"];
343         
344         [selectionLayer setFilters:[NSArray arrayWithObject:filter]];
345         
346         CABasicAnimation* pulseAnimation = [CABasicAnimation animation];
347         
348         pulseAnimation.keyPath = @"filters.pulseFilter.inputIntensity";
349         
350         pulseAnimation.fromValue = [NSNumber numberWithFloat: 0.0];
351         pulseAnimation.toValue = [NSNumber numberWithFloat: 3.0];
352         
353         pulseAnimation.duration = 2.0;
354         pulseAnimation.repeatCount = 1e100f;
355         pulseAnimation.autoreverses = YES;
356         
357         pulseAnimation.timingFunction = [CAMediaTimingFunction functionWithName:
358                                          kCAMediaTimingFunctionEaseInEaseOut];
359         
360         [selectionLayer addAnimation:pulseAnimation forKey:@"pulseAnimation"];
361         [[self layer] addSublayer:selectionLayer];
362     }
363     NSArray * items = [self.itemsTree objectAtIndexPath:selectedPath withNodeKeyPath:self.nodeKeyPath];
364     if( [items count] <= 0 )
365         return;
366     if( i >= [items count] ) i = [items count] - 1;
367     if( i < 0 ) i = 0;
368
369     CALayer * layer = [[menuLayer sublayers] objectAtIndex: i];
370     CGRect frame = layer.frame;
371     if( i == 0 )
372     {
373         frame.origin.y -= [self layer].bounds.size.height - frame.size.height;
374         frame.size.height = [self layer].bounds.size.height;
375     }
376     [(CAScrollLayer*)menuLayer scrollToRect:frame];
377
378     if( !justCreatedSelectionLayer ) /* Get around an artifact on first launch */
379         [CATransaction flush]; /* Make sure we get the "right" layer.frame */
380
381     frame = [[self layer] convertRect:layer.frame fromLayer:[layer superlayer]];
382     frame.size.width += 200.;
383     frame.origin.x -= 100.f;
384     selectionLayer.frame = frame;
385     
386     selectionLayer.cornerRadius = selectionLayer.bounds.size.height / 2.;
387     selectedIndex = i;
388 }
389
390 - (void)changeSelectedPath:(NSIndexPath *)newPath withSelectedIndex:(NSUInteger)newIndex
391 {
392     if( menuDisplayed )
393     {
394         id object = [itemsTree objectAtIndexPath:newPath withNodeKeyPath:nodeKeyPath];
395         /* Make sure we are in a node */
396         if( ![object isKindOfClass:[NSArray class]] )
397         {
398             self.selectedObject = object;
399             if( !self.target || !self.action )
400             {
401                 [NSException raise:@"VLCBrowsableVideoViewNoActionSpecified" format:@"*** Exception [%@]: No action specified.", [self class]];
402                 return;
403             }
404             void (*method)(id, SEL, id) = (void (*)(id, SEL, id))[self.target methodForSelector: self.action];
405
406             method( self.target, self.action, self);
407
408             [self hideMenu];
409             return;
410         }
411         
412         /* Make sure the node isn't empty */
413         if( ![object count] )
414         {
415             [self displayEmptyView];
416         }
417         else
418         {
419             CALayer * newMenuLayer = [VLCBrowsableVideoView menuLayer];
420             if( menuLayer )
421                 newMenuLayer.bounds = menuLayer.bounds; /* Get around some artifacts */
422             [self loadItemsAtIndexPath:newPath inLayer:newMenuLayer];
423             if( menuLayer )
424                 [[self layer] replaceSublayer:menuLayer with:newMenuLayer];
425             else
426                 [[self layer] addSublayer:newMenuLayer];
427             //[menuLayer autorelease]; /* warn: we need gc for that */
428             menuLayer = [newMenuLayer retain];
429         }
430     }
431     [selectedPath release];
432     selectedPath = [newPath retain];
433     [self changeSelectedIndex:newIndex];
434 }
435
436 - (void)displayEmptyView
437 {
438     CALayer * layer = [CALayer layer];
439     layer.layoutManager = [CAConstraintLayoutManager layoutManager];
440     [layer addConstraint:[CAConstraint constraintWithAttribute:kCAConstraintMaxY
441                                        relativeTo:@"superlayer" attribute:kCAConstraintMaxY]];
442     [layer addConstraint:[CAConstraint constraintWithAttribute:kCAConstraintMaxX
443                                        relativeTo:@"superlayer" attribute:kCAConstraintMaxX]];
444     [layer addConstraint:[CAConstraint constraintWithAttribute:kCAConstraintMinX
445                                        relativeTo:@"superlayer" attribute:kCAConstraintMinX]];
446     [layer addConstraint:[CAConstraint constraintWithAttribute:kCAConstraintMinY
447                                        relativeTo:@"superlayer" attribute:kCAConstraintMinY]];
448         
449     CATextLayer *menuItemLayer=[CATextLayer layer];
450     menuItemLayer.string = @"Empty";
451     menuItemLayer.font = @"BankGothic-Light";
452     menuItemLayer.fontSize = 48.f;
453     menuItemLayer.foregroundColor = CGColorCreateGenericRGB(1.0,1.0,1.0,1.0);
454     menuItemLayer.shadowColor = CGColorCreateGenericRGB(0.0,0.0,0.0,1.0);
455     menuItemLayer.shadowOpacity = 0.7;
456     menuItemLayer.shadowRadius = 2.0;
457
458     [menuItemLayer addConstraint:[CAConstraint constraintWithAttribute:kCAConstraintMidX
459                                        relativeTo:@"superlayer" attribute:kCAConstraintMidX]];
460     [menuItemLayer addConstraint:[CAConstraint constraintWithAttribute:kCAConstraintMidY
461                                        relativeTo:@"superlayer" attribute:kCAConstraintMidY]];
462     [layer addSublayer:menuItemLayer];
463
464     if( menuLayer )
465         [[self layer] replaceSublayer:menuLayer with:layer];
466     else
467         [[self layer] addSublayer:layer];
468     [selectionLayer removeFromSuperlayer];
469     //[selectionLayer autorelease] /* need gc */
470     //[menuLayer autorelease] /* need gc */
471     menuLayer = layer;
472     selectionLayer = nil;
473 }
474
475
476 @end
477
478 @implementation VLCBrowsableVideoView (FullScreenTransition)
479
480 - (void)enterFullScreen:(NSScreen *)screen
481 {
482     NSMutableDictionary *dict1,*dict2;
483     NSRect screenRect;
484     NSRect aRect;
485             
486     screenRect = [screen frame];
487         
488     [NSCursor setHiddenUntilMouseMoves: YES];
489     
490     /* Only create the o_fullScreen_window if we are not in the middle of the zooming animation */
491     if (!fullScreenWindow)
492     {
493         /* We can't change the styleMask of an already created NSWindow, so we create an other window, and do eye catching stuff */
494         
495         aRect = [[self superview] convertRect: [self frame] toView: nil]; /* Convert to Window base coord */
496         aRect.origin.x += [[self window] frame].origin.x;
497         aRect.origin.y += [[self window] frame].origin.y;
498         fullScreenWindow = [[VLCWindow alloc] initWithContentRect:aRect styleMask: NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:YES];
499         [fullScreenWindow setBackgroundColor: [NSColor blackColor]];
500         [fullScreenWindow setCanBecomeKeyWindow: YES];
501
502         if (![[self window] isVisible] || [[self window] alphaValue] == 0.0 || [self isHiddenOrHasHiddenAncestor] )
503         {
504             /* We don't animate if we are not visible or if we are running on
505              * Mac OS X <10.4 which doesn't support NSAnimation, instead we
506              * simply fade the display */
507             CGDisplayFadeReservationToken token;
508  
509             [fullScreenWindow setFrame:screenRect display:NO];
510  
511             CGAcquireDisplayFadeReservation(kCGMaxDisplayReservationInterval, &token);
512             CGDisplayFade( token, 0.3, kCGDisplayBlendNormal, kCGDisplayBlendSolidColor, 0, 0, 0, YES );
513  
514             if ([screen isMainScreen])
515                 SetSystemUIMode( kUIModeAllHidden, kUIOptionAutoShowMenuBar);
516  
517             [self retain];
518             [[self superview] replaceSubview:self with:tempFullScreenView];
519             [tempFullScreenView setFrame:[self frame]];
520             [fullScreenWindow setContentView:self];
521             [fullScreenWindow makeKeyAndOrderFront:self];
522             [self release];
523             [[tempFullScreenView window] orderOut: self];
524
525             CGDisplayFade( token, 0.5, kCGDisplayBlendSolidColor, kCGDisplayBlendNormal, 0, 0, 0, NO );
526             CGReleaseDisplayFadeReservation( token);
527
528             [self hasBecomeFullScreen];
529
530             return;
531         }
532  
533         /* Make sure we don't see the o_view disappearing of the screen during this operation */
534         DisableScreenUpdates();
535         [self retain]; /* Removing from a view, make sure we won't be released */
536         /* Make sure our layer won't disappear */
537         CALayer * layer = [[self layer] retain];
538         id alayoutManager = layer.layoutManager;
539         [[self superview] replaceSubview:self with:tempFullScreenView];
540         [tempFullScreenView setFrame:[self frame]];
541         [fullScreenWindow setContentView:self];
542         [self setWantsLayer:YES];
543         [self setLayer:layer];
544         layer.layoutManager = alayoutManager;
545
546         [fullScreenWindow makeKeyAndOrderFront:self];
547         EnableScreenUpdates();
548     }
549
550     /* We are in fullScreen (and no animation is running) */
551     if (fullScreen)
552     {
553         /* Make sure we are hidden */
554         [[tempFullScreenView window] orderOut: self];
555         return;
556     }
557
558     if (fullScreenAnim1)
559     {
560         [fullScreenAnim1 stopAnimation];
561         [fullScreenAnim1 release];
562     }
563     if (fullScreenAnim2)
564     {
565         [fullScreenAnim2 stopAnimation];
566         [fullScreenAnim2 release];
567     }
568  
569     if ([screen isMainScreen])
570         SetSystemUIMode( kUIModeAllHidden, kUIOptionAutoShowMenuBar);
571
572     dict1 = [[NSMutableDictionary alloc] initWithCapacity:2];
573     dict2 = [[NSMutableDictionary alloc] initWithCapacity:3];
574
575     [dict1 setObject:[tempFullScreenView window] forKey:NSViewAnimationTargetKey];
576     [dict1 setObject:NSViewAnimationFadeOutEffect forKey:NSViewAnimationEffectKey];
577
578     [dict2 setObject:fullScreenWindow forKey:NSViewAnimationTargetKey];
579     [dict2 setObject:[NSValue valueWithRect:[fullScreenWindow frame]] forKey:NSViewAnimationStartFrameKey];
580     [dict2 setObject:[NSValue valueWithRect:screenRect] forKey:NSViewAnimationEndFrameKey];
581
582     /* Strategy with NSAnimation allocation:
583         - Keep at most 2 animation at a time
584         - leaveFullScreen/enterFullScreen are the only responsible for releasing and alloc-ing
585     */
586     fullScreenAnim1 = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObjects:dict1, nil]];
587     fullScreenAnim2 = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObjects:dict2, nil]];
588
589     [dict1 release];
590     [dict2 release];
591
592     [fullScreenAnim1 setAnimationBlockingMode: NSAnimationNonblocking];
593     [fullScreenAnim1 setDuration: 0.3];
594     [fullScreenAnim1 setFrameRate: 30];
595     [fullScreenAnim2 setAnimationBlockingMode: NSAnimationNonblocking];
596     [fullScreenAnim2 setDuration: 0.3];
597     [fullScreenAnim2 setFrameRate: 30];
598
599     [fullScreenAnim2 setDelegate: self];
600     [fullScreenAnim2 startWhenAnimation: fullScreenAnim1 reachesProgress: 1.0];
601
602     [fullScreenAnim1 startAnimation];
603 }
604
605 - (void)hasBecomeFullScreen
606 {
607     [fullScreenWindow makeFirstResponder: self];
608
609     [fullScreenWindow makeKeyWindow];
610     [fullScreenWindow setAcceptsMouseMovedEvents: TRUE];
611  
612     [[tempFullScreenView window] orderOut: self];
613     [self willChangeValueForKey:@"fullScreen"];
614     fullScreen = YES;
615     [self didChangeValueForKey:@"fullScreen"];
616 }
617
618 - (void)leaveFullScreen
619 {
620     [self leaveFullScreenAndFadeOut: NO];
621 }
622
623 - (void)leaveFullScreenAndFadeOut: (BOOL)fadeout
624 {
625     NSMutableDictionary *dict1, *dict2;
626     NSRect frame;
627
628     [self willChangeValueForKey:@"fullScreen"];
629     fullScreen = NO;
630     [self didChangeValueForKey:@"fullScreen"];
631
632     /* Don't do anything if o_fullScreen_window is already closed */
633     if (!fullScreenWindow)
634         return;
635
636     if (fadeout || [tempFullScreenView isHiddenOrHasHiddenAncestor])
637     {
638         /* We don't animate if we are not visible or if we are running on
639         * Mac OS X <10.4 which doesn't support NSAnimation, instead we
640         * simply fade the display */
641         CGDisplayFadeReservationToken token;
642
643         CGAcquireDisplayFadeReservation(kCGMaxDisplayReservationInterval, &token);
644         CGDisplayFade( token, 0.3, kCGDisplayBlendNormal, kCGDisplayBlendSolidColor, 0, 0, 0, YES );
645
646         SetSystemUIMode( kUIModeNormal, kUIOptionAutoShowMenuBar);
647
648         [self hasEndedFullScreen];
649
650         CGDisplayFade( token, 0.5, kCGDisplayBlendSolidColor, kCGDisplayBlendNormal, 0, 0, 0, NO );
651         CGReleaseDisplayFadeReservation( token);
652         return;
653     }
654
655     [[tempFullScreenView window] setAlphaValue: 0.0];
656     [[tempFullScreenView window] orderFront: self];
657
658     SetSystemUIMode( kUIModeNormal, kUIOptionAutoShowMenuBar);
659
660     if (fullScreenAnim1)
661     {
662         [fullScreenAnim1 stopAnimation];
663         [fullScreenAnim1 release];
664     }
665     if (fullScreenAnim2)
666     {
667         [fullScreenAnim2 stopAnimation];
668         [fullScreenAnim2 release];
669     }
670
671     frame = [[tempFullScreenView superview] convertRect: [tempFullScreenView frame] toView: nil]; /* Convert to Window base coord */
672     frame.origin.x += [tempFullScreenView window].frame.origin.x;
673     frame.origin.y += [tempFullScreenView window].frame.origin.y;
674
675     dict2 = [[NSMutableDictionary alloc] initWithCapacity:2];
676     [dict2 setObject:[tempFullScreenView window] forKey:NSViewAnimationTargetKey];
677     [dict2 setObject:NSViewAnimationFadeInEffect forKey:NSViewAnimationEffectKey];
678
679     fullScreenAnim2 = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObjects:dict2, nil]];
680     [dict2 release];
681
682     [fullScreenAnim2 setAnimationBlockingMode: NSAnimationNonblocking];
683     [fullScreenAnim2 setDuration: 0.3];
684     [fullScreenAnim2 setFrameRate: 30];
685
686     [fullScreenAnim2 setDelegate: self];
687
688     dict1 = [[NSMutableDictionary alloc] initWithCapacity:3];
689
690     [dict1 setObject:fullScreenWindow forKey:NSViewAnimationTargetKey];
691     [dict1 setObject:[NSValue valueWithRect:[fullScreenWindow frame]] forKey:NSViewAnimationStartFrameKey];
692     [dict1 setObject:[NSValue valueWithRect:frame] forKey:NSViewAnimationEndFrameKey];
693
694     fullScreenAnim1 = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObjects:dict1, nil]];
695     [dict1 release];
696
697     [fullScreenAnim1 setAnimationBlockingMode: NSAnimationNonblocking];
698     [fullScreenAnim1 setDuration: 0.2];
699     [fullScreenAnim1 setFrameRate: 30];
700     [fullScreenAnim2 startWhenAnimation: fullScreenAnim1 reachesProgress: 1.0];
701
702     /* Make sure o_fullScreen_window is the frontmost window */
703     [fullScreenWindow orderFront: self];
704
705     [fullScreenAnim1 startAnimation];
706 }
707
708 - (void)hasEndedFullScreen
709 {
710     /* This function is private and should be only triggered at the end of the fullScreen change animation */
711     /* Make sure we don't see the o_view disappearing of the screen during this operation */
712     DisableScreenUpdates();
713     [self retain];
714     /* Make sure we don't loose the layer */
715     CALayer * layer = [[self layer] retain];
716     id alayoutManager = layer.layoutManager;
717     [self removeFromSuperviewWithoutNeedingDisplay];
718     [[tempFullScreenView superview] replaceSubview:tempFullScreenView with:self];
719     [self release];
720     [self setWantsLayer:YES];
721     [self setLayer:layer];
722     layer.layoutManager = alayoutManager;
723
724     [self setFrame:[tempFullScreenView frame]];
725     [[self window] makeFirstResponder: self];
726     if ([[self window] isVisible])
727         [[self window] makeKeyAndOrderFront:self];
728     [fullScreenWindow orderOut: self];
729     EnableScreenUpdates();
730
731     [fullScreenWindow release];
732     fullScreenWindow = nil;
733 }
734
735 - (void)animationDidEnd:(NSAnimation*)animation
736 {
737     NSArray *viewAnimations;
738
739     if ([animation currentValue] < 1.0)
740         return;
741
742     /* FullScreen ended or started (we are a delegate only for leaveFullScreen's/enterFullscren's anim2) */
743     viewAnimations = [fullScreenAnim2 viewAnimations];
744     if ([viewAnimations count] >=1 &&
745         [[[viewAnimations objectAtIndex: 0] objectForKey: NSViewAnimationEffectKey] isEqualToString:NSViewAnimationFadeInEffect])
746     {
747         /* FullScreen ended */
748         [self hasEndedFullScreen];
749     }
750     else
751     {
752         /* FullScreen started */
753         [self hasBecomeFullScreen];
754     }
755 }
756
757 @end
758