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