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