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