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