]> git.sesse.net Git - vlc/blob - modules/gui/macosx/embeddedwindow.m
* forward-port [23884]
[vlc] / modules / gui / macosx / embeddedwindow.m
1 /*****************************************************************************
2  * embeddedwindow.m: MacOS X interface module
3  *****************************************************************************
4  * Copyright (C) 2005-2007 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 screens] objectAtIndex: var_GetInteger( p_vout, "video-device" )];
156  
157     [self lockFullscreenAnimation];
158
159     if (!screen)
160     {
161         msg_Dbg( p_vout, "chosen screen isn't present, using current screen for fullscreen mode" );
162         screen = [self screen];
163     }
164     
165     vlc_object_release( p_vout );
166
167     screen_rect = [screen frame];
168
169     [o_btn_fullscreen setState: YES];
170
171     [NSCursor setHiddenUntilMouseMoves: YES];
172  
173     if (blackout_other_displays)
174         [screen blackoutOtherScreens]; /* We should do something like [screen blackoutOtherScreens]; */
175
176     /* Only create the o_fullscreen_window if we are not in the middle of the zooming animation */
177     if (!o_fullscreen_window)
178     {
179         /* We can't change the styleMask of an already created NSWindow, so we create an other window, and do eye catching stuff */
180
181         rect = [[o_view superview] convertRect: [o_view frame] toView: nil]; /* Convert to Window base coord */
182         rect.origin.x += [self frame].origin.x;
183         rect.origin.y += [self frame].origin.y;
184         o_fullscreen_window = [[VLCWindow alloc] initWithContentRect:rect styleMask: NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:YES];
185         [o_fullscreen_window setBackgroundColor: [NSColor blackColor]];
186         [o_fullscreen_window setCanBecomeKeyWindow: YES];
187
188         if (![self isVisible] || [self alphaValue] == 0.0 || MACOS_VERSION < 10.4f)
189         {
190             /* We don't animate if we are not visible or if we are running on
191              * Mac OS X <10.4 which doesn't support NSAnimation, instead we
192              * simply fade the display */
193             CGDisplayFadeReservationToken token;
194  
195             [o_fullscreen_window setFrame:screen_rect display:NO];
196  
197             CGAcquireDisplayFadeReservation(kCGMaxDisplayReservationInterval, &token);
198             CGDisplayFade( token, 0.3, kCGDisplayBlendNormal, kCGDisplayBlendSolidColor, 0, 0, 0, YES );
199  
200             if ([screen isMainScreen])
201                 SetSystemUIMode( kUIModeAllHidden, kUIOptionAutoShowMenuBar);
202  
203             [[self contentView] replaceSubview:o_view with:o_temp_view];
204             [o_temp_view setFrame:[o_view frame]];
205             [o_fullscreen_window setContentView:o_view];
206             [o_fullscreen_window makeKeyAndOrderFront:self];
207             [self orderOut: self];
208
209             CGDisplayFade( token, 0.5, kCGDisplayBlendSolidColor, kCGDisplayBlendNormal, 0, 0, 0, NO );
210             CGReleaseDisplayFadeReservation( token);
211
212             /* Will release the lock */
213             [self hasBecomeFullscreen];
214
215             return;
216         }
217  
218         /* Make sure we don't see the o_view disappearing of the screen during this operation */
219         DisableScreenUpdates();
220         [[self contentView] replaceSubview:o_view with:o_temp_view];
221         [o_temp_view setFrame:[o_view frame]];
222         [o_fullscreen_window setContentView:o_view];
223         [o_fullscreen_window makeKeyAndOrderFront:self];
224         EnableScreenUpdates();
225     }
226
227     if (MACOS_VERSION < 10.4f)
228     {
229         /* We were already fullscreen nothing to do when NSAnimation
230          * is not supported */
231         b_animation_lock_alreadylocked = NO;
232         [self unlockFullscreenAnimation];
233         return;
234     }
235
236     /* We are in fullscreen (and no animation is running) */
237     if (b_fullscreen)
238     {
239         /* Make sure we are hidden */
240         [super orderOut: self];
241         b_animation_lock_alreadylocked = NO;
242         [self unlockFullscreenAnimation];
243         return;
244     }
245
246     if (o_fullscreen_anim1)
247     {
248         [o_fullscreen_anim1 stopAnimation];
249         [o_fullscreen_anim1 release];
250     }
251     if (o_fullscreen_anim2)
252     {
253         [o_fullscreen_anim2 stopAnimation];
254         [o_fullscreen_anim2 release];
255     }
256  
257      /* This is a recursive lock. If we are already in the middle of an animation we
258      * unlock it. We don't add an extra locking here, because enter/leavefullscreen
259      * are executed always in the same thread */
260     if (b_animation_lock_alreadylocked)
261         [self unlockFullscreenAnimation];
262     b_animation_lock_alreadylocked = YES;
263
264     if ([screen isMainScreen])
265         SetSystemUIMode( kUIModeAllHidden, kUIOptionAutoShowMenuBar);
266
267     dict1 = [[NSMutableDictionary alloc] initWithCapacity:2];
268     dict2 = [[NSMutableDictionary alloc] initWithCapacity:3];
269
270     [dict1 setObject:self forKey:NSViewAnimationTargetKey];
271     [dict1 setObject:NSViewAnimationFadeOutEffect forKey:NSViewAnimationEffectKey];
272
273     [dict2 setObject:o_fullscreen_window forKey:NSViewAnimationTargetKey];
274     [dict2 setObject:[NSValue valueWithRect:[o_fullscreen_window frame]] forKey:NSViewAnimationStartFrameKey];
275     [dict2 setObject:[NSValue valueWithRect:screen_rect] forKey:NSViewAnimationEndFrameKey];
276
277     /* Strategy with NSAnimation allocation:
278         - Keep at most 2 animation at a time
279         - leaveFullscreen/enterFullscreen are the only responsible for releasing and alloc-ing
280     */
281     o_fullscreen_anim1 = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObjects:dict1, nil]];
282     o_fullscreen_anim2 = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObjects:dict2, nil]];
283
284     [dict1 release];
285     [dict2 release];
286
287     [o_fullscreen_anim1 setAnimationBlockingMode: NSAnimationNonblocking];
288     [o_fullscreen_anim1 setDuration: 0.3];
289     [o_fullscreen_anim1 setFrameRate: 30];
290     [o_fullscreen_anim2 setAnimationBlockingMode: NSAnimationNonblocking];
291     [o_fullscreen_anim2 setDuration: 0.2];
292     [o_fullscreen_anim2 setFrameRate: 30];
293
294     [o_fullscreen_anim2 setDelegate: self];
295     [o_fullscreen_anim2 startWhenAnimation: o_fullscreen_anim1 reachesProgress: 1.0];
296
297     [o_fullscreen_anim1 startAnimation];
298     /* fullscreenAnimation will be unlocked when animation ends */
299 }
300
301 - (void)hasBecomeFullscreen
302 {
303     [o_fullscreen_window makeFirstResponder: [[[VLCMain sharedInstance] getControls] getVoutView]];
304
305     [o_fullscreen_window makeKeyWindow];
306     [o_fullscreen_window setAcceptsMouseMovedEvents: TRUE];
307
308     /* tell the fspanel to move itself to front next time it's triggered */
309     [[[[VLCMain sharedInstance] getControls] getFSPanel] setVoutWasUpdated: (int)[[o_fullscreen_window screen] displayID]];
310  
311     [super orderOut: self];
312
313     [[[[VLCMain sharedInstance] getControls] getFSPanel] setActive: nil];
314     b_fullscreen = YES;
315     [self unlockFullscreenAnimation];
316 }
317
318 - (void)leaveFullscreen
319 {
320     [self leaveFullscreenAndFadeOut: NO];
321 }
322
323 - (void)leaveFullscreenAndFadeOut: (BOOL)fadeout
324 {
325     NSMutableDictionary *dict1, *dict2;
326     NSRect frame;
327
328     [self lockFullscreenAnimation];
329
330     b_fullscreen = NO;
331     [o_btn_fullscreen setState: NO];
332
333     /* We always try to do so */
334     [NSScreen unblackoutScreens];
335
336     /* Don't do anything if o_fullscreen_window is already closed */
337     if (!o_fullscreen_window)
338     {
339         b_animation_lock_alreadylocked = NO;
340         [self unlockFullscreenAnimation];
341         return;
342     }
343
344     if (fadeout || MACOS_VERSION < 10.4f)
345     {
346         /* We don't animate if we are not visible or if we are running on
347         * Mac OS X <10.4 which doesn't support NSAnimation, instead we
348         * simply fade the display */
349         CGDisplayFadeReservationToken token;
350
351         CGAcquireDisplayFadeReservation(kCGMaxDisplayReservationInterval, &token);
352         CGDisplayFade( token, 0.3, kCGDisplayBlendNormal, kCGDisplayBlendSolidColor, 0, 0, 0, YES );
353
354         [[[[VLCMain sharedInstance] getControls] getFSPanel] setNonActive: nil];
355         SetSystemUIMode( kUIModeNormal, kUIOptionAutoShowMenuBar);
356
357         /* Will release the lock */
358         [self hasEndedFullscreen];
359
360         CGDisplayFade( token, 0.5, kCGDisplayBlendSolidColor, kCGDisplayBlendNormal, 0, 0, 0, NO );
361         CGReleaseDisplayFadeReservation( token);
362         return;
363     }
364
365     [self setAlphaValue: 0.0];
366     [self orderFront: self];
367
368     [[[[VLCMain sharedInstance] getControls] getFSPanel] setNonActive: nil];
369     SetSystemUIMode( kUIModeNormal, kUIOptionAutoShowMenuBar);
370
371     if (o_fullscreen_anim1)
372     {
373         [o_fullscreen_anim1 stopAnimation];
374         [o_fullscreen_anim1 release];
375     }
376     if (o_fullscreen_anim2)
377     {
378         [o_fullscreen_anim2 stopAnimation];
379         [o_fullscreen_anim2 release];
380     }
381
382     /* This is a recursive lock. If we are already in the middle of an animation we
383      * unlock it. We don't add an extra locking here, because enter/leavefullscreen
384      * are executed always in the same thread */
385     if (b_animation_lock_alreadylocked)
386         [self unlockFullscreenAnimation];
387     b_animation_lock_alreadylocked = YES;
388
389     frame = [[o_temp_view superview] convertRect: [o_temp_view frame] toView: nil]; /* Convert to Window base coord */
390     frame.origin.x += [self frame].origin.x;
391     frame.origin.y += [self frame].origin.y;
392
393     dict2 = [[NSMutableDictionary alloc] initWithCapacity:2];
394     [dict2 setObject:self forKey:NSViewAnimationTargetKey];
395     [dict2 setObject:NSViewAnimationFadeInEffect forKey:NSViewAnimationEffectKey];
396
397     o_fullscreen_anim2 = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObjects:dict2, nil]];
398     [dict2 release];
399
400     [o_fullscreen_anim2 setAnimationBlockingMode: NSAnimationNonblocking];
401     [o_fullscreen_anim2 setDuration: 0.3];
402     [o_fullscreen_anim2 setFrameRate: 30];
403
404     [o_fullscreen_anim2 setDelegate: self];
405
406     dict1 = [[NSMutableDictionary alloc] initWithCapacity:3];
407
408     [dict1 setObject:o_fullscreen_window forKey:NSViewAnimationTargetKey];
409     [dict1 setObject:[NSValue valueWithRect:[o_fullscreen_window frame]] forKey:NSViewAnimationStartFrameKey];
410     [dict1 setObject:[NSValue valueWithRect:frame] forKey:NSViewAnimationEndFrameKey];
411
412     o_fullscreen_anim1 = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObjects:dict1, nil]];
413     [dict1 release];
414
415     [o_fullscreen_anim1 setAnimationBlockingMode: NSAnimationNonblocking];
416     [o_fullscreen_anim1 setDuration: 0.2];
417     [o_fullscreen_anim1 setFrameRate: 30];
418     [o_fullscreen_anim2 startWhenAnimation: o_fullscreen_anim1 reachesProgress: 1.0];
419
420     /* Make sure o_fullscreen_window is the frontmost window */
421     [o_fullscreen_window orderFront: self];
422
423     [o_fullscreen_anim1 startAnimation];
424     /* fullscreenAnimation will be unlocked when animation ends */
425 }
426
427 - (void)hasEndedFullscreen
428 {
429     /* This function is private and should be only triggered at the end of the fullscreen change animation */
430     /* Make sure we don't see the o_view disappearing of the screen during this operation */
431     DisableScreenUpdates();
432     [o_view retain];
433     [o_view removeFromSuperviewWithoutNeedingDisplay];
434     [[self contentView] replaceSubview:o_temp_view with:o_view];
435     [o_view release];
436     [o_view setFrame:[o_temp_view frame]];
437     [self makeFirstResponder: o_view];
438     if ([self isVisible])
439         [self makeKeyAndOrderFront:self];
440     [o_fullscreen_window orderOut: self];
441     EnableScreenUpdates();
442
443     [o_fullscreen_window release];
444     o_fullscreen_window = nil;
445     b_animation_lock_alreadylocked = NO;
446     [self unlockFullscreenAnimation];
447 }
448
449 - (void)animationDidEnd:(NSAnimation*)animation
450 {
451     NSArray *viewAnimations;
452
453     if ([animation currentValue] < 1.0)
454         return;
455
456     /* Fullscreen ended or started (we are a delegate only for leaveFullscreen's/enterFullscren's anim2) */
457     viewAnimations = [o_fullscreen_anim2 viewAnimations];
458     if ([viewAnimations count] >=1 &&
459         [[[viewAnimations objectAtIndex: 0] objectForKey: NSViewAnimationEffectKey] isEqualToString:NSViewAnimationFadeInEffect])
460     {
461         /* Fullscreen ended */
462         [self hasEndedFullscreen];
463     }
464     else
465     {
466         /* Fullscreen started */
467         [self hasBecomeFullscreen];
468     }
469 }
470
471 - (void)orderOut: (id)sender
472 {
473     [super orderOut: sender];
474     /* Make sure we leave fullscreen */
475     [self leaveFullscreenAndFadeOut: YES];
476 }
477
478 /* Make sure setFrame gets executed on main thread especially if we are animating.
479  * (Thus we won't block the video output thread) */
480 - (void)setFrame:(NSRect)frame display:(BOOL)display animate:(BOOL)animate
481 {
482     struct { NSRect frame; BOOL display; BOOL animate;} args;
483     NSData *packedargs;
484
485     args.frame = frame;
486     args.display = display;
487     args.animate = animate;
488
489     packedargs = [NSData dataWithBytes:&args length:sizeof(args)];
490
491     [self performSelectorOnMainThread:@selector(setFrameOnMainThread:)
492                     withObject: packedargs waitUntilDone: YES];
493 }
494
495 - (void)setFrameOnMainThread:(NSData*)packedargs
496 {
497     struct args { NSRect frame; BOOL display; BOOL animate; } * args = (struct args*)[packedargs bytes];
498
499     [super setFrame: args->frame display: args->display animate:args->animate];
500 }
501 @end