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