]> git.sesse.net Git - vlc/blob - modules/gui/macosx/embeddedwindow.m
d7cd72cc26c29de2c4ec2e237ff8b2954ee9f12c
[vlc] / modules / gui / macosx / embeddedwindow.m
1 /*****************************************************************************
2  * embeddedwindow.m: MacOS X interface module
3  *****************************************************************************
4  * Copyright (C) 2005-2008 the VideoLAN team
5  * $Id$
6  *
7  * Authors: Benjamin Pracht <bigben at videolan dot org>
8  *          Felix Paul Kühne <fkuehne at videolan dot org>
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 /*****************************************************************************
26  * Preamble
27  *****************************************************************************/
28
29 /* DisableScreenUpdates, SetSystemUIMode, ... */
30 #import <QuickTime/QuickTime.h>
31
32 #import "intf.h"
33 #import "controls.h"
34 #import "vout.h"
35 #import "embeddedwindow.h"
36 #import "fspanel.h"
37
38 /*****************************************************************************
39  * VLCEmbeddedWindow Implementation
40  *****************************************************************************/
41
42 @implementation VLCEmbeddedWindow
43
44 - (void)awakeFromNib
45 {
46     [self setDelegate: self];
47
48     [o_btn_backward setToolTip: _NS("Rewind")];
49     [o_btn_forward setToolTip: _NS("Fast Forward")];
50     [o_btn_fullscreen setToolTip: _NS("Fullscreen")];
51     [o_btn_play setToolTip: _NS("Play")];
52     [o_slider setToolTip: _NS("Position")];
53
54     o_img_play = [NSImage imageNamed: @"play_embedded"];
55     o_img_pause = [NSImage imageNamed: @"pause_embedded"];
56     [self controlTintChanged];
57     [[NSNotificationCenter defaultCenter] addObserver: self
58                                              selector: @selector( controlTintChanged )
59                                                  name: NSControlTintDidChangeNotification
60                                                object: nil];
61
62     /* Useful to save o_view frame in fullscreen mode */
63     o_temp_view = [[NSView alloc] init];
64     [o_temp_view setAutoresizingMask:NSViewHeightSizable | NSViewWidthSizable];
65
66     o_fullscreen_window = nil;
67     o_fullscreen_anim1 = o_fullscreen_anim2 = nil;
68
69     /* Not fullscreen when we wake up */
70     [o_btn_fullscreen setState: NO];
71     b_fullscreen = NO;
72
73     /* Make sure setVisible: returns NO */
74     [self orderOut:self];
75 }
76
77 - (void)controlTintChanged
78 {
79     BOOL b_playing = NO;
80     if( [o_btn_play alternateImage] == o_img_play_pressed )
81         b_playing = YES;
82     
83     if( [NSColor currentControlTint] == NSGraphiteControlTint )
84     {
85         o_img_play_pressed = [NSImage imageNamed: @"play_embedded_graphite"];
86         o_img_pause_pressed = [NSImage imageNamed: @"pause_embedded_graphite"];
87         [o_btn_backward setAlternateImage: [NSImage imageNamed: @"skip_previous_embedded_graphite"]];
88         [o_btn_forward setAlternateImage: [NSImage imageNamed: @"skip_forward_embedded_graphite"]];
89         [o_btn_fullscreen setAlternateImage: [NSImage imageNamed: @"fullscreen_graphite"]];
90     }
91     else
92     {
93         o_img_play_pressed = [NSImage imageNamed: @"play_embedded_blue"];
94         o_img_pause_pressed = [NSImage imageNamed: @"pause_embedded_blue"];
95         [o_btn_backward setAlternateImage: [NSImage imageNamed: @"skip_previous_embedded_blue"]];
96         [o_btn_forward setAlternateImage: [NSImage imageNamed: @"skip_forward_embedded_blue"]];
97         [o_btn_fullscreen setAlternateImage: [NSImage imageNamed: @"fullscreen_blue"]];
98     }
99     
100     if( b_playing )
101         [o_btn_play setAlternateImage: o_img_play_pressed];
102     else
103         [o_btn_play setAlternateImage: o_img_pause_pressed];
104 }
105
106 - (void)dealloc
107 {
108     [[NSNotificationCenter defaultCenter] removeObserver: self];
109     [o_img_play release];
110     [o_img_play_pressed release];
111     [o_img_pause release];
112     [o_img_pause_pressed release];
113     
114     [super dealloc];
115 }
116
117 - (void)setTime:(NSString *)o_arg_time position:(float)f_position
118 {
119     [o_time setStringValue: o_arg_time];
120     [o_slider setFloatValue: f_position];
121 }
122
123 - (void)playStatusUpdated:(int)i_status
124 {
125     if( i_status == PLAYING_S )
126     {
127         [o_btn_play setImage: o_img_pause];
128         [o_btn_play setAlternateImage: o_img_pause_pressed];
129         [o_btn_play setToolTip: _NS("Pause")];
130     }
131     else
132     {
133         [o_btn_play setImage: o_img_play];
134         [o_btn_play setAlternateImage: o_img_play_pressed];
135         [o_btn_play setToolTip: _NS("Play")];
136     }
137 }
138
139 - (void)setSeekable:(BOOL)b_seekable
140 {
141     [o_btn_forward setEnabled: b_seekable];
142     [o_btn_backward setEnabled: b_seekable];
143     [o_slider setEnabled: b_seekable];
144 }
145
146 - (BOOL)windowShouldZoom:(NSWindow *)sender toFrame:(NSRect)newFrame
147 {
148     [self setFrame: newFrame display: YES animate: YES];
149     return YES;
150 }
151
152 - (BOOL)windowShouldClose:(id)sender
153 {
154     playlist_t * p_playlist = pl_Yield( VLCIntf );
155
156     playlist_Stop( p_playlist );
157     vlc_object_release( p_playlist );
158     return YES;
159 }
160
161 - (NSView *)mainView
162 {
163     if (o_fullscreen_window)
164         return o_temp_view;
165     else
166         return o_view;
167 }
168
169 /*****************************************************************************
170  * Fullscreen support
171  */
172
173 - (BOOL)isFullscreen
174 {
175     return b_fullscreen;
176 }
177
178 - (void)lockFullscreenAnimation
179 {
180     [o_animation_lock lock];
181 }
182
183 - (void)unlockFullscreenAnimation
184 {
185     [o_animation_lock unlock];
186 }
187
188 - (void)enterFullscreen
189 {
190     NSMutableDictionary *dict1, *dict2;
191     NSScreen *screen;
192     NSRect screen_rect;
193     NSRect rect;
194     vout_thread_t *p_vout = vlc_object_find( VLCIntf, VLC_OBJECT_VOUT, FIND_ANYWHERE );
195     BOOL blackout_other_displays = config_GetInt( VLCIntf, "macosx-black" );
196
197     screen = [NSScreen screenWithDisplayID:(CGDirectDisplayID)var_GetInteger( p_vout, "video-device" )]; 
198  
199     [self lockFullscreenAnimation];
200
201     if (!screen)
202     {
203         msg_Dbg( p_vout, "chosen screen isn't present, using current screen for fullscreen mode" );
204         screen = [self screen];
205     }
206     if (!screen)
207     {
208         msg_Dbg( p_vout, "Using deepest screen" );
209         screen = [NSScreen deepestScreen];
210     }
211
212     vlc_object_release( p_vout );
213
214     screen_rect = [screen frame];
215
216     [o_btn_fullscreen setState: YES];
217
218     [NSCursor setHiddenUntilMouseMoves: YES];
219  
220     if( blackout_other_displays )        
221         [screen blackoutOtherScreens];
222
223     /* Only create the o_fullscreen_window if we are not in the middle of the zooming animation */
224     if (!o_fullscreen_window)
225     {
226         /* We can't change the styleMask of an already created NSWindow, so we create an other window, and do eye catching stuff */
227
228         rect = [[o_view superview] convertRect: [o_view frame] toView: nil]; /* Convert to Window base coord */
229         rect.origin.x += [self frame].origin.x;
230         rect.origin.y += [self frame].origin.y;
231         o_fullscreen_window = [[VLCWindow alloc] initWithContentRect:rect styleMask: NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:YES];
232         [o_fullscreen_window setBackgroundColor: [NSColor blackColor]];
233         [o_fullscreen_window setCanBecomeKeyWindow: YES];
234
235         if (![self isVisible] || [self alphaValue] == 0.0 || MACOS_VERSION < 10.4f)
236         {
237             /* We don't animate if we are not visible or if we are running on
238              * Mac OS X <10.4 which doesn't support NSAnimation, instead we
239              * simply fade the display */
240             CGDisplayFadeReservationToken token;
241  
242             CGAcquireDisplayFadeReservation(kCGMaxDisplayReservationInterval, &token);
243             CGDisplayFade( token, 0.5, kCGDisplayBlendNormal, kCGDisplayBlendSolidColor, 0, 0, 0, YES );
244  
245             if ([screen isMainScreen])
246                 SetSystemUIMode( kUIModeAllHidden, kUIOptionAutoShowMenuBar);
247  
248             [[self contentView] replaceSubview:o_view with:o_temp_view];
249             [o_temp_view setFrame:[o_view frame]];
250             [o_fullscreen_window setContentView:o_view];
251
252             [o_fullscreen_window makeKeyAndOrderFront:self];
253
254             [o_fullscreen_window makeKeyAndOrderFront:self];
255             [o_fullscreen_window orderFront:self animate:YES];
256
257             [o_fullscreen_window setFrame:screen_rect display:YES];
258
259             CGDisplayFade( token, 0.3, kCGDisplayBlendSolidColor, kCGDisplayBlendNormal, 0, 0, 0, NO );
260             CGReleaseDisplayFadeReservation( token);
261
262             /* Will release the lock */
263             [self hasBecomeFullscreen];
264
265             return;
266         }
267  
268         /* Make sure we don't see the o_view disappearing of the screen during this operation */
269         DisableScreenUpdates();
270         [[self contentView] replaceSubview:o_view with:o_temp_view];
271         [o_temp_view setFrame:[o_view frame]];
272         [o_fullscreen_window setContentView:o_view];
273         [o_fullscreen_window makeKeyAndOrderFront:self];
274         EnableScreenUpdates();
275     }
276
277     if (MACOS_VERSION < 10.4f)
278     {
279         /* We were already fullscreen nothing to do when NSAnimation
280          * is not supported */
281         [self unlockFullscreenAnimation];
282         return;
283     }
284
285     /* We are in fullscreen (and no animation is running) */
286     if (b_fullscreen)
287     {
288         /* Make sure we are hidden */
289         [super orderOut: self];
290         [self unlockFullscreenAnimation];
291         return;
292     }
293
294     if (o_fullscreen_anim1)
295     {
296         [o_fullscreen_anim1 stopAnimation];
297         [o_fullscreen_anim1 release];
298     }
299     if (o_fullscreen_anim2)
300     {
301         [o_fullscreen_anim2 stopAnimation];
302         [o_fullscreen_anim2 release];
303     }
304  
305     if ([screen isMainScreen])
306         SetSystemUIMode( kUIModeAllHidden, kUIOptionAutoShowMenuBar);
307
308     dict1 = [[NSMutableDictionary alloc] initWithCapacity:2];
309     dict2 = [[NSMutableDictionary alloc] initWithCapacity:3];
310
311     [dict1 setObject:self forKey:NSViewAnimationTargetKey];
312     [dict1 setObject:NSViewAnimationFadeOutEffect forKey:NSViewAnimationEffectKey];
313
314     [dict2 setObject:o_fullscreen_window forKey:NSViewAnimationTargetKey];
315     [dict2 setObject:[NSValue valueWithRect:[o_fullscreen_window frame]] forKey:NSViewAnimationStartFrameKey];
316     [dict2 setObject:[NSValue valueWithRect:screen_rect] forKey:NSViewAnimationEndFrameKey];
317
318     /* Strategy with NSAnimation allocation:
319         - Keep at most 2 animation at a time
320         - leaveFullscreen/enterFullscreen are the only responsible for releasing and alloc-ing
321     */
322     o_fullscreen_anim1 = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObjects:dict1, nil]];
323     o_fullscreen_anim2 = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObjects:dict2, nil]];
324
325     [dict1 release];
326     [dict2 release];
327
328     [o_fullscreen_anim1 setAnimationBlockingMode: NSAnimationNonblocking];
329     [o_fullscreen_anim1 setDuration: 0.3];
330     [o_fullscreen_anim1 setFrameRate: 30];
331     [o_fullscreen_anim2 setAnimationBlockingMode: NSAnimationNonblocking];
332     [o_fullscreen_anim2 setDuration: 0.2];
333     [o_fullscreen_anim2 setFrameRate: 30];
334
335     [o_fullscreen_anim2 setDelegate: self];
336     [o_fullscreen_anim2 startWhenAnimation: o_fullscreen_anim1 reachesProgress: 1.0];
337
338     [o_fullscreen_anim1 startAnimation];
339     /* fullscreenAnimation will be unlocked when animation ends */
340 }
341
342 - (void)hasBecomeFullscreen
343 {
344     [o_fullscreen_window makeFirstResponder: [[[VLCMain sharedInstance] getControls] getVoutView]];
345
346     [o_fullscreen_window makeKeyWindow];
347     [o_fullscreen_window setAcceptsMouseMovedEvents: TRUE];
348
349     /* tell the fspanel to move itself to front next time it's triggered */
350     [[[[VLCMain sharedInstance] getControls] getFSPanel] setVoutWasUpdated: (int)[[o_fullscreen_window screen] displayID]];
351
352     if([self isVisible])
353         [super orderOut: self];
354
355     [[[[VLCMain sharedInstance] getControls] getFSPanel] setActive: nil];
356
357     b_fullscreen = YES;
358     [self unlockFullscreenAnimation];
359 }
360
361 - (void)leaveFullscreen
362 {
363     [self leaveFullscreenAndFadeOut: NO];
364 }
365
366 - (void)leaveFullscreenAndFadeOut: (BOOL)fadeout
367 {
368     NSMutableDictionary *dict1, *dict2;
369     NSRect frame;
370
371     [self lockFullscreenAnimation];
372
373     b_fullscreen = NO;
374     [o_btn_fullscreen setState: NO];
375
376     /* We always try to do so */
377     [NSScreen unblackoutScreens];
378
379     /* Don't do anything if o_fullscreen_window is already closed */
380     if (!o_fullscreen_window)
381     {
382         [self unlockFullscreenAnimation];
383         return;
384     }
385
386     if (fadeout || MACOS_VERSION < 10.4f)
387     {
388         /* We don't animate if we are not visible or if we are running on
389         * Mac OS X <10.4 which doesn't support NSAnimation, instead we
390         * simply fade the display */
391         CGDisplayFadeReservationToken token;
392
393         CGAcquireDisplayFadeReservation(kCGMaxDisplayReservationInterval, &token);
394         CGDisplayFade( token, 0.3, kCGDisplayBlendNormal, kCGDisplayBlendSolidColor, 0, 0, 0, YES );
395
396         [[[[VLCMain sharedInstance] getControls] getFSPanel] setNonActive: nil];
397         SetSystemUIMode( kUIModeNormal, kUIOptionAutoShowMenuBar);
398
399         /* Will release the lock */
400         [self hasEndedFullscreen];
401
402         CGDisplayFade( token, 0.5, kCGDisplayBlendSolidColor, kCGDisplayBlendNormal, 0, 0, 0, NO );
403         CGReleaseDisplayFadeReservation( token);
404         return;
405     }
406
407     [self setAlphaValue: 0.0];
408     [self orderFront: self];
409
410     [[[[VLCMain sharedInstance] getControls] getFSPanel] setNonActive: nil];
411     SetSystemUIMode( kUIModeNormal, kUIOptionAutoShowMenuBar);
412
413     if (o_fullscreen_anim1)
414     {
415         [o_fullscreen_anim1 stopAnimation];
416         [o_fullscreen_anim1 release];
417     }
418     if (o_fullscreen_anim2)
419     {
420         [o_fullscreen_anim2 stopAnimation];
421         [o_fullscreen_anim2 release];
422     }
423
424     frame = [[o_temp_view superview] convertRect: [o_temp_view frame] toView: nil]; /* Convert to Window base coord */
425     frame.origin.x += [self frame].origin.x;
426     frame.origin.y += [self frame].origin.y;
427
428     dict2 = [[NSMutableDictionary alloc] initWithCapacity:2];
429     [dict2 setObject:self forKey:NSViewAnimationTargetKey];
430     [dict2 setObject:NSViewAnimationFadeInEffect forKey:NSViewAnimationEffectKey];
431
432     o_fullscreen_anim2 = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObjects:dict2, nil]];
433     [dict2 release];
434
435     [o_fullscreen_anim2 setAnimationBlockingMode: NSAnimationNonblocking];
436     [o_fullscreen_anim2 setDuration: 0.3];
437     [o_fullscreen_anim2 setFrameRate: 30];
438
439     [o_fullscreen_anim2 setDelegate: self];
440
441     dict1 = [[NSMutableDictionary alloc] initWithCapacity:3];
442
443     [dict1 setObject:o_fullscreen_window forKey:NSViewAnimationTargetKey];
444     [dict1 setObject:[NSValue valueWithRect:[o_fullscreen_window frame]] forKey:NSViewAnimationStartFrameKey];
445     [dict1 setObject:[NSValue valueWithRect:frame] forKey:NSViewAnimationEndFrameKey];
446
447     o_fullscreen_anim1 = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObjects:dict1, nil]];
448     [dict1 release];
449
450     [o_fullscreen_anim1 setAnimationBlockingMode: NSAnimationNonblocking];
451     [o_fullscreen_anim1 setDuration: 0.2];
452     [o_fullscreen_anim1 setFrameRate: 30];
453     [o_fullscreen_anim2 startWhenAnimation: o_fullscreen_anim1 reachesProgress: 1.0];
454
455     /* Make sure o_fullscreen_window is the frontmost window */
456     [o_fullscreen_window orderFront: self];
457
458     [o_fullscreen_anim1 startAnimation];
459     /* fullscreenAnimation will be unlocked when animation ends */
460 }
461
462 - (void)hasEndedFullscreen
463 {
464     /* This function is private and should be only triggered at the end of the fullscreen change animation */
465     /* Make sure we don't see the o_view disappearing of the screen during this operation */
466     DisableScreenUpdates();
467     [o_view retain];
468     [o_view removeFromSuperviewWithoutNeedingDisplay];
469     [[self contentView] replaceSubview:o_temp_view with:o_view];
470     [o_view release];
471     [o_view setFrame:[o_temp_view frame]];
472     [self makeFirstResponder: o_view];
473     if ([self isVisible])
474         [super makeKeyAndOrderFront:self]; /* our version contains a workaround */
475     [o_fullscreen_window orderOut: self];
476     EnableScreenUpdates();
477
478     [o_fullscreen_window release];
479     o_fullscreen_window = nil;
480     [self unlockFullscreenAnimation];
481 }
482
483 - (void)animationDidEnd:(NSAnimation*)animation
484 {
485     NSArray *viewAnimations;
486
487     if ([animation currentValue] < 1.0)
488         return;
489
490     /* Fullscreen ended or started (we are a delegate only for leaveFullscreen's/enterFullscren's anim2) */
491     viewAnimations = [o_fullscreen_anim2 viewAnimations];
492     if ([viewAnimations count] >=1 &&
493         [[[viewAnimations objectAtIndex: 0] objectForKey: NSViewAnimationEffectKey] isEqualToString:NSViewAnimationFadeInEffect])
494     {
495         /* Fullscreen ended */
496         [self hasEndedFullscreen];
497     }
498     else
499     {
500         /* Fullscreen started */
501         [self hasBecomeFullscreen];
502     }
503 }
504
505 - (void)orderOut: (id)sender
506 {
507     [super orderOut: sender];
508
509     /* Make sure we leave fullscreen */
510     [self leaveFullscreenAndFadeOut: YES];
511 }
512
513 - (void)makeKeyAndOrderFront: (id)sender
514 {
515     /* Hack
516      * when we exit fullscreen and fade out, we may endup in
517      * having a window that is faded. We can't have it fade in unless we
518      * animate again. */
519
520     [super setAlphaValue:0.0f];
521     [super makeKeyAndOrderFront: sender];
522
523     NSMutableDictionary * dict = [[[NSMutableDictionary alloc] initWithCapacity:2] autorelease];
524     [dict setObject:self forKey:NSViewAnimationTargetKey];
525     [dict setObject:NSViewAnimationFadeInEffect forKey:NSViewAnimationEffectKey];
526
527     NSViewAnimation * anim = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObject:dict]];
528
529     [anim setAnimationBlockingMode: NSAnimationNonblocking];
530     [anim setDuration: 0.1];
531     [anim setFrameRate: 30];
532
533     [anim startAnimation];
534     /* fullscreenAnimation will be unlocked when animation ends */
535 }
536
537
538
539 /* Make sure setFrame gets executed on main thread especially if we are animating.
540  * (Thus we won't block the video output thread) */
541 - (void)setFrame:(NSRect)frame display:(BOOL)display animate:(BOOL)animate
542 {
543     struct { NSRect frame; BOOL display; BOOL animate;} args;
544     NSData *packedargs;
545
546     args.frame = frame;
547     args.display = display;
548     args.animate = animate;
549
550     packedargs = [NSData dataWithBytes:&args length:sizeof(args)];
551
552     [self performSelectorOnMainThread:@selector(setFrameOnMainThread:)
553                     withObject: packedargs waitUntilDone: YES];
554 }
555
556 - (void)setFrameOnMainThread:(NSData*)packedargs
557 {
558     struct args { NSRect frame; BOOL display; BOOL animate; } * args = (struct args*)[packedargs bytes];
559
560     [super setFrame: args->frame display: args->display animate:args->animate];
561 }
562 @end