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