]> git.sesse.net Git - vlc/blob - projects/macosx/vlc_app/Sources/VLCBrowsableVideoView.m
d34d210d7d5956470675aefa899a4e3522d79564
[vlc] / projects / 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, 2009 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 #import "VLCMediaListLayer.h"
32 #import "VLCMainWindowController.h"
33
34 /* TODO: We may want to clean up the private functions a bit... */
35
36 @interface VLCBrowsableVideoView ()
37 /* Property */
38 @property (readwrite, retain) id selectedObject;
39 @end
40
41 @interface VLCBrowsableVideoView (Private)
42
43 /* Methods */
44 + (CAScrollLayer *)menuLayer;
45 + (CALayer *)backLayer;
46
47 - (void)loadItemsAtIndexPath:(NSIndexPath *)path inLayer:(CALayer *)layer;
48 - (void)changeSelectedIndex:(NSInteger)i;
49 - (void)changeSelectedPath:(NSIndexPath *)newPath withSelectedIndex:(NSUInteger)newIndex;
50
51 - (void)displayEmptyView;
52 @end
53
54
55 @interface VLCBrowsableVideoView (FullScreenTransition)
56 - (void)hasEndedFullScreen;
57 - (void)hasBecomeFullScreen;
58
59 - (void)enterFullScreen:(NSScreen *)screen;
60 - (void)leaveFullScreen;
61 - (void)leaveFullScreenAndFadeOut: (BOOL)fadeout;
62
63 @end
64
65 #pragma mark -
66 /******************************************************************************
67  * VLCBrowsableVideoView
68  */
69 @implementation VLCBrowsableVideoView
70
71 /* Property */
72 @synthesize nodeKeyPath;
73 @synthesize contentKeyPath;
74 @synthesize selectedObject;
75 @synthesize target;
76 @synthesize action;
77 @synthesize videoLayer;
78
79 - (NSArray *)itemsTree {
80     return itemsTree;
81 }
82
83 - (void)setItemsTree:(NSArray *)newItemsTree
84 {
85     [itemsTree release];
86     itemsTree = [newItemsTree retain];
87     [self changeSelectedPath:[[[NSIndexPath alloc] init] autorelease] withSelectedIndex:0];
88 }
89
90 - (BOOL)fullScreen
91 {
92     return [self isInFullScreenMode];
93 }
94
95 - (void)setFullScreen:(BOOL)newFullScreen
96 {
97     if( newFullScreen == self.fullScreen )
98         return;
99     
100     if( newFullScreen )
101     {
102         [self enterFullScreenMode:[[self window] screen] withOptions:
103                     [NSDictionary dictionaryWithObject: [NSNumber numberWithInt:1]
104                                                 forKey: NSFullScreenModeWindowLevel]];
105     }
106     else
107     {
108         [self exitFullScreenModeWithOptions:nil];
109     }
110 }
111
112 - (BOOL)hasVideo
113 {
114     return videoLayer.hasVideo;
115 }
116
117 /* Binded to VideoLayer's hasVideo */
118 - (void)setHasVideo:(BOOL)hasVideo
119 {
120     if( hasVideo )
121     {
122         [CATransaction begin];
123         [videoLayer removeFromSuperlayer];
124         [self.layer addSublayer:videoLayer];
125         videoLayer.frame = [self layer].bounds;
126         [videoLayer setAutoresizingMask:kCALayerWidthSizable|kCALayerHeightSizable];
127         [mediaListLayer removeFromSuperlayer];
128         [CATransaction commit];
129     }
130     else
131     {
132         [CATransaction begin];
133         [mediaListLayer removeFromSuperlayer];
134         [self.layer addSublayer:mediaListLayer];
135         mediaListLayer.frame = [self layer].bounds;
136         [mediaListLayer setAutoresizingMask:kCALayerWidthSizable|kCALayerHeightSizable];
137         [videoLayer removeFromSuperlayer];
138         [CATransaction commit];
139
140     }
141     [[self layer] setNeedsDisplay];
142     [self setNeedsDisplay:YES];
143 }
144
145 /* Initializer */
146 - (void)awakeFromNib
147 {
148     // FIXME: do that in -initWithFrame:
149     [self setWantsLayer:YES];
150     menuDisplayed = NO;
151     displayedItems = NSMakeRange( -1, 0 );
152     selectedIndex = -1;
153     selectionLayer = backLayer = nil;
154     menuLayer = nil;
155     selectedPath = [[NSIndexPath alloc] init];
156     tempFullScreenView = [[NSView alloc] init];
157     fullScreen = NO;
158
159     videoLayer = [[VLCVideoLayer layer] retain];
160     [videoLayer addObserver:self forKeyPath:@"hasVideo" options:NSKeyValueObservingOptionInitial|NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];
161     
162     [videoLayer setDelegate:self];
163     NSAssert( mainWindowController, @"No mainWindowController" );
164     [mainWindowController.mediaPlayer setVideoLayer: videoLayer];
165     mediaListLayer = [[VLCMediaListLayer layerWithMediaArrayController:mainWindowController.mediaArrayController] retain];
166     [self registerForDraggedTypes:[NSArray arrayWithObjects:NSFilenamesPboardType, NSURLPboardType, @"VLCMediaURLType", nil]];
167     [mainWindowController.mediaArrayController setSelectsInsertedObjects:YES];
168     [mainWindowController.mediaArrayController setAvoidsEmptySelection:YES];
169     [[self layer] addSublayer:mediaListLayer];
170     mediaListLayer.frame = [self layer].bounds;
171     [mediaListLayer setAutoresizingMask:kCALayerWidthSizable|kCALayerHeightSizable];
172
173     [[self layer] setNeedsDisplay];
174
175 }
176
177 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
178 {
179     if([keyPath isEqualToString:@"hasVideo"])
180     {
181         [self setHasVideo:[object hasVideo]];
182         return;
183     }
184     [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
185 }
186
187 - (void)dealloc
188 {
189     /* Previously registered in */
190     [videoLayer removeObserver:self forKeyPath:@"hasVideo"];
191
192     [mediaListLayer release];
193     [videoLayer release];
194     [tempFullScreenView release];
195     [selectedPath release];
196     [super dealloc];
197 }
198
199 #pragma mark -
200 /* Drag and drop */
201
202 - (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
203 {
204     NSPasteboard *pboard;
205  
206     pboard = [sender draggingPasteboard];
207  
208     if ( [[pboard types] containsObject:NSFilenamesPboardType] &&
209         ![mainWindowController.mediaArrayController.contentMediaList isReadOnly] )
210     {
211         self.layer.borderColor = CGColorCreateGenericGray(0.5, 0.5);
212         self.layer.cornerRadius = 10.f;
213         self.layer.borderWidth = 10.0;
214         return NSDragOperationCopy;
215     }
216     return NSDragOperationNone;
217 }
218
219 - (void)draggingEnded:(id < NSDraggingInfo >)sender
220 {
221     [CATransaction begin];
222     [CATransaction setValue:[NSNumber numberWithFloat:0.1] forKey:kCATransactionAnimationDuration];
223     self.layer.borderWidth = 0.;
224     [CATransaction commit];
225     [CATransaction begin];
226     [mainWindowController.mediaArrayController setFilterPredicate:nil];
227     [mainWindowController.mediaArrayController setSelectionIndex:[mainWindowController.mediaArrayController.contentMediaList count] - 1];
228     [CATransaction commit];
229 }
230
231 - (void)draggingExited:(id < NSDraggingInfo >)sender
232 {
233     self.layer.borderWidth = 0.;
234 }
235
236 - (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
237 {
238     NSPasteboard *pboard;
239     NSDragOperation sourceDragMask;
240  
241     sourceDragMask = [sender draggingSourceOperationMask];
242     pboard = [sender draggingPasteboard];
243  
244     if ( [[pboard types] containsObject:NSFilenamesPboardType] )
245     {
246         NSArray *files = [pboard propertyListForType:NSFilenamesPboardType];
247         VLCMediaList * mediaList = mainWindowController.mediaArrayController.contentMediaList;
248         if( [mediaList isReadOnly] )
249             return NO;
250
251         [CATransaction begin];
252         for( NSString * filePath in files )
253             [mediaList addMedia:[VLCMedia mediaWithPath:filePath]];
254         [CATransaction commit];
255     }
256     return YES;
257 }
258
259 - (void)showDrag
260 {
261
262 }
263
264 #pragma mark -
265 /* Hiding/Displaying the menu */
266
267 - (void)hideMenu
268 {
269     if( !menuDisplayed )
270         return; /* Nothing to do */
271
272     [menuLayer removeFromSuperlayer];
273     [selectionLayer removeFromSuperlayer];
274     [backLayer removeFromSuperlayer];
275     //[menuLayer autorelease]; /* Need gc for that */
276     //[selectionLayer autorelease];
277     //[backLayer autorelease];
278     selectionLayer = backLayer = nil;
279     menuLayer = nil;
280     menuDisplayed = NO;
281     [self setNeedsDisplay:YES];
282 }
283
284 - (void)displayMenu
285 {
286     if( menuDisplayed || !self.itemsTree )
287         return; /* Nothing to do */
288
289     if( !menuLayer )
290     {
291         CALayer * rootLayer = [self layer];
292         rootLayer.backgroundColor = CGColorGetConstantColor(kCGColorBlack);
293         rootLayer.layoutManager = [CAConstraintLayoutManager layoutManager];
294         menuLayer = [VLCBrowsableVideoView menuLayer];
295         [self loadItemsAtIndexPath: selectedPath inLayer: menuLayer];
296     }
297     if( !backLayer )
298     {
299         backLayer = [[VLCBrowsableVideoView backLayer] retain];
300     }
301     [[self layer] addSublayer:backLayer];
302     [[self layer] addSublayer:menuLayer];
303
304     [[self layer] setNeedsLayout];
305     [[self layer] setNeedsDisplay];
306
307     menuDisplayed = YES;
308     [self changeSelectedPath:selectedPath withSelectedIndex:selectedIndex];
309 }
310
311 - (void)toggleMenu
312 {
313     if( menuDisplayed )
314         [self hideMenu];
315     else
316         [self displayMenu];
317 }
318
319 - (IBAction)backToMediaListView:(id)sender
320 {
321     [mainWindowController.mediaPlayer stop];
322     [self setHasVideo: NO];
323 }
324
325 #pragma mark -
326 /* drawRect */
327
328 - (void)drawRect:(NSRect)rect
329 {
330     if( [self hasVideo] )
331     {
332         [[NSColor blackColor] set];
333         NSRectFill(rect);
334         return;
335     }
336     NSColor * topGradient = [NSColor colorWithCalibratedWhite:.0f alpha:1.0];
337     NSColor * bottomGradient   = [NSColor colorWithCalibratedWhite:0.35f alpha:1.0];
338         NSGradient * gradient = [[NSGradient alloc] initWithColorsAndLocations:bottomGradient, 0.f, topGradient, 0.65f, topGradient, 1.f, nil];
339     [gradient drawInRect:self.bounds angle:100.0];
340 }
341
342 #pragma mark -
343 /* Event handling */
344
345 - (BOOL)acceptsFirstResponder
346 {
347     return YES;
348 }
349
350 -(void)moveUp:(id)sender
351 {
352     [self changeSelectedIndex:selectedIndex-1];
353 }
354
355 -(void)moveDown:(id)sender
356 {
357     [self changeSelectedIndex:selectedIndex+1];
358 }
359
360 - (void)mouseDown:(NSEvent *)theEvent
361 {
362     if([theEvent clickCount] == 1)
363     {
364         NSRect rect1 = [self bounds];
365         NSRect rect2 = [self bounds];
366         rect1.origin.x += [self bounds].size.width * 4./5.;
367         rect1.size.width /= 5.;
368         rect2.size.width /= 5.;
369         if(NSPointInRect([self convertPoint:[theEvent locationInWindow] fromView:nil], rect1))
370         {
371             [mainWindowController.mediaArrayController selectNext:self];
372         }
373         else if(NSPointInRect([self convertPoint:[theEvent locationInWindow] fromView:nil], rect2))
374         {
375             [mainWindowController.mediaArrayController selectPrevious:self];
376         }
377         return;
378     }
379     if([theEvent clickCount] == 2)
380     {
381         [mainWindowController mediaListViewItemDoubleClicked:self];
382         return;
383     }
384     if([theEvent clickCount] == 3)
385     {
386         self.fullScreen = !self.fullScreen;
387     }
388 }
389
390 - (void)keyDown:(NSEvent *)theEvent
391 {
392     if(([[theEvent charactersIgnoringModifiers] characterAtIndex:0] == 13) && menuDisplayed)
393     {
394         [self changeSelectedPath:[selectedPath indexPathByAddingIndex:selectedIndex] withSelectedIndex:0];
395     }
396     else if([[theEvent charactersIgnoringModifiers] characterAtIndex:0] ==  NSLeftArrowFunctionKey && menuDisplayed)
397     {
398         if( [selectedPath length] > 0 )
399             [self changeSelectedPath:[selectedPath indexPathByRemovingLastIndex] withSelectedIndex:[selectedPath lastIndex]];
400         else
401             [self hideMenu];
402     }
403     else if(!menuDisplayed && [[theEvent charactersIgnoringModifiers] characterAtIndex:0] ==  NSRightArrowFunctionKey)
404     {
405         [self displayMenu];
406     }
407     else
408         [super keyDown: theEvent];
409
410 }
411
412 - (void)enterFullScreenMode:(NSScreen *)screen withOptions:(NSDictionary *)options
413 {
414     [self enterFullScreen: screen];
415 }
416
417 - (void)exitFullScreenModeWithOptions:(NSDictionary *)options
418 {
419     [self leaveFullScreen];
420
421 }
422
423 - (BOOL)isInFullScreenMode
424 {
425     return fullScreen;
426 }
427
428 @end
429
430 #pragma mark -
431 /******************************************************************************
432  * VLCBrowsableVideoView (Private)
433  */
434
435 @implementation VLCBrowsableVideoView (Private)
436 + (CAScrollLayer *)menuLayer
437 {
438     CAScrollLayer * layer = [CAScrollLayer layer];
439     layer.scrollMode = kCAScrollVertically;
440             
441     [layer addConstraint:[CAConstraint constraintWithAttribute:kCAConstraintMaxY
442                                        relativeTo:@"superlayer" attribute:kCAConstraintMaxY]];
443     [layer addConstraint:[CAConstraint constraintWithAttribute:kCAConstraintMaxX
444                                        relativeTo:@"superlayer" attribute:kCAConstraintMaxX]];
445     [layer addConstraint:[CAConstraint constraintWithAttribute:kCAConstraintMinX
446                                        relativeTo:@"superlayer" attribute:kCAConstraintMinX]];
447     [layer addConstraint:[CAConstraint constraintWithAttribute:kCAConstraintMinY
448                                        relativeTo:@"superlayer" attribute:kCAConstraintMinY]];
449     return layer;
450 }
451
452 + (CALayer *)backLayer
453 {
454     CALayer * layer = [CALayer layer];
455             
456     [layer addConstraint:[CAConstraint constraintWithAttribute:kCAConstraintMaxY
457                                        relativeTo:@"superlayer" attribute:kCAConstraintMaxY]];
458     [layer addConstraint:[CAConstraint constraintWithAttribute:kCAConstraintMaxX
459                                        relativeTo:@"superlayer" attribute:kCAConstraintMaxX]];
460     [layer addConstraint:[CAConstraint constraintWithAttribute:kCAConstraintMinX
461                                        relativeTo:@"superlayer" attribute:kCAConstraintMinX]];
462     [layer addConstraint:[CAConstraint constraintWithAttribute:kCAConstraintMinY
463                                        relativeTo:@"superlayer" attribute:kCAConstraintMinY]];
464
465     layer.opacity = 1.0;
466     layer.backgroundColor = CGColorCreateGenericRGB(0., 0., 0., .5);
467     return layer;
468 }
469
470
471 - (void)loadItemsAtIndexPath:(NSIndexPath *)path inLayer:(CALayer *)layer
472 {
473     const CGFloat height=70.0;
474     const CGFloat fontSize=48.0;
475     NSArray * items = [self.itemsTree objectAtIndexPath:path withNodeKeyPath:self.nodeKeyPath]; 
476     int i;
477
478     for( i = 0; i < [items count]; i++ )
479     {
480         CATextLayer *menuItemLayer=[CATextLayer layer];
481         id item = [items objectAtIndex: i];
482         menuItemLayer.string = self.contentKeyPath ? [item valueForKeyPath:self.contentKeyPath] : @"No content Key path set";
483         menuItemLayer.font = @"BankGothic-Light";
484         menuItemLayer.fontSize = fontSize;
485         menuItemLayer.foregroundColor = CGColorCreateGenericRGB(1.0,1.0,1.0,1.0);
486         menuItemLayer.shadowColor = CGColorCreateGenericRGB(0.0,0.0,0.0,1.0);
487         menuItemLayer.shadowOpacity = 0.7;
488         menuItemLayer.shadowRadius = 2.0;
489
490         menuItemLayer.frame = CGRectMake( 40., height*(-i) + layer.visibleRect.size.height, 500.0f,70.);
491         [layer addSublayer: menuItemLayer];
492     }
493
494 /*    for(i=0; i < [[layer sublayers] count]; i++)
495         NSLog(@"%d, %@", i, [[[layer sublayers] objectAtIndex: i] string]);
496     NSLog(@"---");*/
497 }
498
499 - (void)changeSelectedIndex:(NSInteger)i
500 {
501     BOOL justCreatedSelectionLayer = NO;
502     if( !menuDisplayed )
503     {
504         selectedIndex = i;
505         return;
506     }
507
508     if( !selectionLayer )
509     {
510         justCreatedSelectionLayer = YES;
511         /* Rip-off from Apple's Sample code */
512         selectionLayer=[[CALayer layer] retain];
513         
514         selectionLayer.borderWidth=2.0;
515         selectionLayer.borderColor=CGColorCreateGenericRGB(1.0f,1.0f,1.0f,1.0f);
516         selectionLayer.backgroundColor=CGColorCreateGenericRGB(.9f,1.0f,1.0f,.1f);
517         
518         CIFilter *filter = [CIFilter filterWithName:@"CIBloom"];
519         [filter setDefaults];
520         [filter setValue:[NSNumber numberWithFloat:5.0] forKey:@"inputRadius"];
521         
522         [filter setName:@"pulseFilter"];
523         
524         [selectionLayer setFilters:[NSArray arrayWithObject:filter]];
525         
526         CABasicAnimation* pulseAnimation = [CABasicAnimation animation];
527         
528         pulseAnimation.keyPath = @"filters.pulseFilter.inputIntensity";
529         
530         pulseAnimation.fromValue = [NSNumber numberWithFloat: 0.0];
531         pulseAnimation.toValue = [NSNumber numberWithFloat: 3.0];
532         
533         pulseAnimation.duration = 2.0;
534         pulseAnimation.repeatCount = 1e100f;
535         pulseAnimation.autoreverses = YES;
536         
537         pulseAnimation.timingFunction = [CAMediaTimingFunction functionWithName:
538                                          kCAMediaTimingFunctionEaseInEaseOut];
539         
540         [selectionLayer addAnimation:pulseAnimation forKey:@"pulseAnimation"];
541         [[self layer] addSublayer:selectionLayer];
542     }
543     NSArray * items = [self.itemsTree objectAtIndexPath:selectedPath withNodeKeyPath:self.nodeKeyPath];
544     if( [items count] <= 0 )
545         return;
546     if( i >= [items count] ) i = [items count] - 1;
547     if( i < 0 ) i = 0;
548
549     CALayer * layer = [[menuLayer sublayers] objectAtIndex: i];
550     CGRect frame = layer.frame;
551     if( i == 0 )
552     {
553         frame.origin.y -= [self layer].bounds.size.height - frame.size.height;
554         frame.size.height = [self layer].bounds.size.height;
555     }
556     [(CAScrollLayer*)menuLayer scrollToRect:frame];
557
558     if( !justCreatedSelectionLayer ) /* Get around an artifact on first launch */
559         [CATransaction flush]; /* Make sure we get the "right" layer.frame */
560
561     frame = [[self layer] convertRect:layer.frame fromLayer:[layer superlayer]];
562     frame.size.width += 200.;
563     frame.origin.x -= 100.f;
564     selectionLayer.frame = frame;
565     
566     selectionLayer.cornerRadius = selectionLayer.bounds.size.height / 2.;
567     selectedIndex = i;
568 }
569
570 - (void)changeSelectedPath:(NSIndexPath *)newPath withSelectedIndex:(NSUInteger)newIndex
571 {
572     if( menuDisplayed )
573     {
574         id object = [itemsTree objectAtIndexPath:newPath withNodeKeyPath:nodeKeyPath];
575         /* Make sure we are in a node */
576         if( ![object isKindOfClass:[NSArray class]] )
577         {
578             self.selectedObject = object;
579             if( !self.target || !self.action )
580             {
581                 [NSException raise:@"VLCBrowsableVideoViewNoActionSpecified" format:@"*** Exception [%@]: No action specified.", [self class]];
582                 return;
583             }
584             void (*method)(id, SEL, id) = (void (*)(id, SEL, id))[self.target methodForSelector: self.action];
585
586             method( self.target, self.action, self);
587
588             [self hideMenu];
589             return;
590         }
591         
592         /* Make sure the node isn't empty */
593         if( ![object count] )
594         {
595             [self displayEmptyView];
596         }
597         else
598         {
599             CALayer * newMenuLayer = [VLCBrowsableVideoView menuLayer];
600             if( menuLayer )
601                 newMenuLayer.bounds = menuLayer.bounds; /* Get around some artifacts */
602             [self loadItemsAtIndexPath:newPath inLayer:newMenuLayer];
603             if( menuLayer )
604                 [[self layer] replaceSublayer:menuLayer with:newMenuLayer];
605             else
606                 [[self layer] addSublayer:newMenuLayer];
607             //[menuLayer autorelease]; /* warn: we need gc for that */
608             menuLayer = [newMenuLayer retain];
609         }
610     }
611     [selectedPath release];
612     selectedPath = [newPath retain];
613     [self changeSelectedIndex:newIndex];
614 }
615
616 - (void)displayEmptyView
617 {
618     CALayer * layer = [CALayer layer];
619     layer.layoutManager = [CAConstraintLayoutManager layoutManager];
620     [layer addConstraint:[CAConstraint constraintWithAttribute:kCAConstraintMaxY
621                                        relativeTo:@"superlayer" attribute:kCAConstraintMaxY]];
622     [layer addConstraint:[CAConstraint constraintWithAttribute:kCAConstraintMaxX
623                                        relativeTo:@"superlayer" attribute:kCAConstraintMaxX]];
624     [layer addConstraint:[CAConstraint constraintWithAttribute:kCAConstraintMinX
625                                        relativeTo:@"superlayer" attribute:kCAConstraintMinX]];
626     [layer addConstraint:[CAConstraint constraintWithAttribute:kCAConstraintMinY
627                                        relativeTo:@"superlayer" attribute:kCAConstraintMinY]];
628         
629     CATextLayer *menuItemLayer=[CATextLayer layer];
630     menuItemLayer.string = @"Empty";
631     menuItemLayer.font = @"BankGothic-Light";
632     menuItemLayer.fontSize = 48.f;
633     menuItemLayer.foregroundColor = CGColorCreateGenericRGB(1.0,1.0,1.0,1.0);
634     menuItemLayer.shadowColor = CGColorCreateGenericRGB(0.0,0.0,0.0,1.0);
635     menuItemLayer.shadowOpacity = 0.7;
636     menuItemLayer.shadowRadius = 2.0;
637
638     [menuItemLayer addConstraint:[CAConstraint constraintWithAttribute:kCAConstraintMidX
639                                        relativeTo:@"superlayer" attribute:kCAConstraintMidX]];
640     [menuItemLayer addConstraint:[CAConstraint constraintWithAttribute:kCAConstraintMidY
641                                        relativeTo:@"superlayer" attribute:kCAConstraintMidY]];
642     [layer addSublayer:menuItemLayer];
643
644     if( menuLayer )
645         [[self layer] replaceSublayer:menuLayer with:layer];
646     else
647         [[self layer] addSublayer:layer];
648     [selectionLayer removeFromSuperlayer];
649     //[selectionLayer autorelease] /* need gc */
650     //[menuLayer autorelease] /* need gc */
651     menuLayer = layer;
652     selectionLayer = nil;
653 }
654
655
656 @end
657
658 #pragma mark -
659
660 @implementation VLCBrowsableVideoView (FullScreenTransition)
661
662 - (void)enterFullScreen:(NSScreen *)screen
663 {
664     NSMutableDictionary *dict1,*dict2;
665     NSRect screenRect;
666     NSRect aRect;
667             
668     screenRect = [screen frame];
669         
670     [NSCursor setHiddenUntilMouseMoves: YES];
671     
672     /* Only create the o_fullScreen_window if we are not in the middle of the zooming animation */
673     if (!fullScreenWindow)
674     {
675         /* We can't change the styleMask of an already created NSWindow, so we create an other window, and do eye catching stuff */
676         
677         aRect = [[self superview] convertRect: [self frame] toView: nil]; /* Convert to Window base coord */
678         aRect.origin.x += [[self window] frame].origin.x;
679         aRect.origin.y += [[self window] frame].origin.y;
680         fullScreenWindow = [[VLCWindow alloc] initWithContentRect:aRect styleMask: NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:YES];
681         [fullScreenWindow setBackgroundColor: [NSColor blackColor]];
682         [fullScreenWindow setCanBecomeKeyWindow: YES];
683
684         if (![[self window] isVisible] || [[self window] alphaValue] == 0.0 || [self isHiddenOrHasHiddenAncestor] )
685         {
686             /* We don't animate if we are not visible, instead we
687              * simply fade the display */
688             CGDisplayFadeReservationToken token;
689  
690             [fullScreenWindow setFrame:screenRect display:NO];
691  
692             CGAcquireDisplayFadeReservation(kCGMaxDisplayReservationInterval, &token);
693             CGDisplayFade( token, 0.3, kCGDisplayBlendNormal, kCGDisplayBlendSolidColor, 0, 0, 0, YES );
694  
695             if ([screen isMainScreen])
696                 SetSystemUIMode( kUIModeAllHidden, kUIOptionAutoShowMenuBar);
697  
698             [self retain];
699             [[self superview] replaceSubview:self with:tempFullScreenView];
700             [tempFullScreenView setFrame:[self frame]];
701             [fullScreenWindow setContentView:self];
702             [fullScreenWindow makeKeyAndOrderFront:self];
703             [self release];
704             [[tempFullScreenView window] orderOut: self];
705
706             CGDisplayFade( token, 0.5, kCGDisplayBlendSolidColor, kCGDisplayBlendNormal, 0, 0, 0, NO );
707             CGReleaseDisplayFadeReservation( token);
708
709             [self hasBecomeFullScreen];
710
711             return;
712         }
713  
714         /* Make sure we don't see the o_view disappearing of the screen during this operation */
715         DisableScreenUpdates();
716         [self retain]; /* Removing from a view, make sure we won't be released */
717         /* Make sure our layer won't disappear */
718         CALayer * layer = [[self layer] retain];
719         id alayoutManager = layer.layoutManager;
720         [[self superview] replaceSubview:self with:tempFullScreenView];
721         [tempFullScreenView setFrame:[self frame]];
722         [fullScreenWindow setContentView:self];
723         [self setWantsLayer:YES];
724         [self setLayer:layer];
725         layer.layoutManager = alayoutManager;
726
727         [fullScreenWindow makeKeyAndOrderFront:self];
728         EnableScreenUpdates();
729     }
730
731     /* We are in fullScreen (and no animation is running) */
732     if (fullScreen)
733     {
734         /* Make sure we are hidden */
735         [[tempFullScreenView window] orderOut: self];
736         return;
737     }
738
739     if (fullScreenAnim1)
740     {
741         [fullScreenAnim1 stopAnimation];
742         [fullScreenAnim1 release];
743     }
744     if (fullScreenAnim2)
745     {
746         [fullScreenAnim2 stopAnimation];
747         [fullScreenAnim2 release];
748     }
749  
750     if ([screen isMainScreen])
751         SetSystemUIMode( kUIModeAllHidden, kUIOptionAutoShowMenuBar);
752
753     dict1 = [[NSMutableDictionary alloc] initWithCapacity:2];
754     dict2 = [[NSMutableDictionary alloc] initWithCapacity:3];
755
756     [dict1 setObject:[tempFullScreenView window] forKey:NSViewAnimationTargetKey];
757     [dict1 setObject:NSViewAnimationFadeOutEffect forKey:NSViewAnimationEffectKey];
758
759     [dict2 setObject:fullScreenWindow forKey:NSViewAnimationTargetKey];
760     [dict2 setObject:[NSValue valueWithRect:[fullScreenWindow frame]] forKey:NSViewAnimationStartFrameKey];
761     [dict2 setObject:[NSValue valueWithRect:screenRect] forKey:NSViewAnimationEndFrameKey];
762
763     /* Strategy with NSAnimation allocation:
764         - Keep at most 2 animation at a time
765         - leaveFullScreen/enterFullScreen are the only responsible for releasing and alloc-ing
766     */
767     fullScreenAnim1 = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObjects:dict1, nil]];
768     fullScreenAnim2 = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObjects:dict2, nil]];
769
770     [dict1 release];
771     [dict2 release];
772
773     [fullScreenAnim1 setAnimationBlockingMode: NSAnimationNonblocking];
774     [fullScreenAnim1 setDuration: 0.3];
775     [fullScreenAnim1 setFrameRate: 30];
776     [fullScreenAnim2 setAnimationBlockingMode: NSAnimationNonblocking];
777     [fullScreenAnim2 setDuration: 0.3];
778     [fullScreenAnim2 setFrameRate: 30];
779
780     [fullScreenAnim2 setDelegate: self];
781     [fullScreenAnim2 startWhenAnimation: fullScreenAnim1 reachesProgress: 1.0];
782
783     [fullScreenAnim1 startAnimation];
784 }
785
786 - (void)hasBecomeFullScreen
787 {
788     [fullScreenWindow makeFirstResponder: self];
789
790     [fullScreenWindow makeKeyWindow];
791     [fullScreenWindow setAcceptsMouseMovedEvents: TRUE];
792  
793     [[tempFullScreenView window] orderOut: self];
794     [self willChangeValueForKey:@"fullScreen"];
795     fullScreen = YES;
796     [self didChangeValueForKey:@"fullScreen"];
797 }
798
799 - (void)leaveFullScreen
800 {
801     [self leaveFullScreenAndFadeOut: NO];
802 }
803
804 - (void)leaveFullScreenAndFadeOut: (BOOL)fadeout
805 {
806     NSMutableDictionary *dict1, *dict2;
807     NSRect frame;
808
809     [self willChangeValueForKey:@"fullScreen"];
810     fullScreen = NO;
811     [self didChangeValueForKey:@"fullScreen"];
812
813     /* Don't do anything if o_fullScreen_window is already closed */
814     if (!fullScreenWindow)
815         return;
816
817     if (fadeout || [tempFullScreenView isHiddenOrHasHiddenAncestor])
818     {
819         /* We don't animate if we are not visible, instead we
820         * simply fade the display */
821         CGDisplayFadeReservationToken token;
822
823         CGAcquireDisplayFadeReservation(kCGMaxDisplayReservationInterval, &token);
824         CGDisplayFade( token, 0.3, kCGDisplayBlendNormal, kCGDisplayBlendSolidColor, 0, 0, 0, YES );
825
826         SetSystemUIMode( kUIModeNormal, kUIOptionAutoShowMenuBar);
827
828         [self hasEndedFullScreen];
829
830         CGDisplayFade( token, 0.5, kCGDisplayBlendSolidColor, kCGDisplayBlendNormal, 0, 0, 0, NO );
831         CGReleaseDisplayFadeReservation( token);
832         return;
833     }
834
835     [[tempFullScreenView window] setAlphaValue: 0.0];
836     [[tempFullScreenView window] orderFront: self];
837
838     SetSystemUIMode( kUIModeNormal, kUIOptionAutoShowMenuBar);
839
840     if (fullScreenAnim1)
841     {
842         [fullScreenAnim1 stopAnimation];
843         [fullScreenAnim1 release];
844     }
845     if (fullScreenAnim2)
846     {
847         [fullScreenAnim2 stopAnimation];
848         [fullScreenAnim2 release];
849     }
850
851     frame = [[tempFullScreenView superview] convertRect: [tempFullScreenView frame] toView: nil]; /* Convert to Window base coord */
852     frame.origin.x += [tempFullScreenView window].frame.origin.x;
853     frame.origin.y += [tempFullScreenView window].frame.origin.y;
854
855     dict2 = [[NSMutableDictionary alloc] initWithCapacity:2];
856     [dict2 setObject:[tempFullScreenView window] forKey:NSViewAnimationTargetKey];
857     [dict2 setObject:NSViewAnimationFadeInEffect forKey:NSViewAnimationEffectKey];
858
859     fullScreenAnim2 = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObjects:dict2, nil]];
860     [dict2 release];
861
862     [fullScreenAnim2 setAnimationBlockingMode: NSAnimationNonblocking];
863     [fullScreenAnim2 setDuration: 0.3];
864     [fullScreenAnim2 setFrameRate: 30];
865
866     [fullScreenAnim2 setDelegate: self];
867
868     dict1 = [[NSMutableDictionary alloc] initWithCapacity:3];
869
870     [dict1 setObject:fullScreenWindow forKey:NSViewAnimationTargetKey];
871     [dict1 setObject:[NSValue valueWithRect:[fullScreenWindow frame]] forKey:NSViewAnimationStartFrameKey];
872     [dict1 setObject:[NSValue valueWithRect:frame] forKey:NSViewAnimationEndFrameKey];
873
874     fullScreenAnim1 = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObjects:dict1, nil]];
875     [dict1 release];
876
877     [fullScreenAnim1 setAnimationBlockingMode: NSAnimationNonblocking];
878     [fullScreenAnim1 setDuration: 0.2];
879     [fullScreenAnim1 setFrameRate: 30];
880     [fullScreenAnim2 startWhenAnimation: fullScreenAnim1 reachesProgress: 1.0];
881
882     /* Make sure o_fullScreen_window is the frontmost window */
883     [fullScreenWindow orderFront: self];
884
885     [fullScreenAnim1 startAnimation];
886 }
887
888 - (void)hasEndedFullScreen
889 {
890     /* This function is private and should be only triggered at the end of the fullScreen change animation */
891     /* Make sure we don't see the o_view disappearing of the screen during this operation */
892     DisableScreenUpdates();
893     [self retain];
894     /* Make sure we don't loose the layer */
895     CALayer * layer = [[self layer] retain];
896     id alayoutManager = layer.layoutManager;
897     [self removeFromSuperviewWithoutNeedingDisplay];
898     [[tempFullScreenView superview] replaceSubview:tempFullScreenView with:self];
899     [self release];
900     [self setWantsLayer:YES];
901     [self setLayer:layer];
902     layer.layoutManager = alayoutManager;
903
904     [self setFrame:[tempFullScreenView frame]];
905     [[self window] makeFirstResponder: self];
906     if ([[self window] isVisible])
907         [[self window] makeKeyAndOrderFront:self];
908     [fullScreenWindow orderOut: self];
909     EnableScreenUpdates();
910
911     [fullScreenWindow release];
912     fullScreenWindow = nil;
913 }
914
915 - (void)animationDidEnd:(NSAnimation*)animation
916 {
917     NSArray *viewAnimations;
918
919     if ([animation currentValue] < 1.0)
920         return;
921
922     /* FullScreen ended or started (we are a delegate only for leaveFullScreen's/enterFullscren's anim2) */
923     viewAnimations = [fullScreenAnim2 viewAnimations];
924     if ([viewAnimations count] >=1 &&
925         [[[viewAnimations objectAtIndex: 0] objectForKey: NSViewAnimationEffectKey] isEqualToString:NSViewAnimationFadeInEffect])
926     {
927         /* FullScreen ended */
928         [self hasEndedFullScreen];
929     }
930     else
931     {
932         /* FullScreen started */
933         [self hasBecomeFullScreen];
934     }
935 }
936
937 @end
938