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