MacOSX/Framework/VLCVideoView.m: Fix a potential segfault.
[vlc.git] / extras / MacOSX / Framework / Sources / VLCVideoView.m
1 /*****************************************************************************
2  * VLCVideoView.h: VLC.framework VLCVideoView implementation
3  *****************************************************************************
4  * Copyright (C) 2007 Pierre d'Herbemont
5  * Copyright (C) 2007 the VideoLAN team
6  * $Id$
7  *
8  * Authors: Pierre d'Herbemont <pdherbemont # videolan.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 #import "VLCVideoView.h"
26 #import "VLCLibrary.h"
27 #import "VLCEventManager.h"
28
29 /* Libvlc */
30 #include <vlc/vlc.h>
31 #include <vlc/libvlc.h>
32
33 #import <QuartzCore/QuartzCore.h>
34
35 /* Notifications */
36 NSString * VLCVideoViewEnteredFullScreen    = @"VLCVideoViewEnteredFullScreen";
37 NSString * VLCVideoViewLeftFullScreen       = @"VLCVideoViewLeftFullScreen";
38
39 /******************************************************************************
40  * Soon deprecated stuff 
41  */
42
43 /* This is a forward reference to VLCOpenGLVoutView specified in minimal_macosx
44    library.  We could get rid of this, but it prevents warnings from the 
45    compiler. (Scheduled to deletion) */
46 @interface VLCOpenGLVoutView : NSView
47 - (void)detachFromVout;
48 @end
49
50 /* Depreacted methods */
51 @interface VLCVideoView (Deprecated)
52 - (void)setStretchesVideo:(BOOL)value;
53 - (BOOL)stretchesVideo;
54
55 - (void)addVoutSubview:(NSView *)aView;  /* (Scheduled to deletion) */
56 - (void)removeVoutSubview:(NSView *)aView;  /* (Scheduled to deletion) */
57 @end
58
59 /******************************************************************************
60  * VLCVideoView (Private) 
61  */
62
63 @interface VLCVideoView (Private)
64 /* Method */
65 - (void)addVoutLayer:(CALayer *)aLayer;
66 @end
67
68
69
70 /******************************************************************************
71  * Interface & Implementation VLCConstraintLayoutManager
72  *
73  * Manage the size of the video layer 
74  */
75 @interface VLCConstraintLayoutManager : CAConstraintLayoutManager
76 {
77     CGSize originalVideoSize;
78     BOOL  fillScreenEntirely;
79 }
80 @property BOOL  fillScreenEntirely;
81 @property CGSize originalVideoSize;
82 @end
83
84 @implementation VLCConstraintLayoutManager 
85 @synthesize  fillScreenEntirely;
86 @synthesize  originalVideoSize;
87 - (id)init
88 {
89     if( self = [super init] )
90     {
91         self.originalVideoSize = CGSizeMake(0., 0.);
92         self.fillScreenEntirely = NO;
93     }
94     return self;
95
96 }
97 + (id)layoutManager
98 {
99     return [[[VLCConstraintLayoutManager alloc] init] autorelease];
100 }
101 - (void)layoutSublayersOfLayer:(CALayer *)layer
102 {
103     /* Called by CA, when our rootLayer needs layout */
104     [super layoutSublayersOfLayer:layer];
105
106     /* After having done everything normally resize the vlcopengllayer */
107     if( [[layer sublayers] count] > 0 && [[[[layer sublayers] objectAtIndex:0] name] isEqualToString:@"vlcopengllayer"])
108     {
109         CALayer * videolayer = [[layer sublayers] objectAtIndex:0];
110         CGRect bounds = layer.bounds;
111         float new_height = (bounds.size.width * originalVideoSize.height) / originalVideoSize.width;
112
113         if( fillScreenEntirely )
114         {
115             if( bounds.size.height > new_height )
116                 bounds.size.height = new_height;
117             else
118                 bounds.size.width = (bounds.size.height * originalVideoSize.width) / originalVideoSize.height;
119         }
120         else
121         {
122             if( bounds.size.height > new_height )
123                 bounds.size.width = (bounds.size.height * originalVideoSize.width) / originalVideoSize.height;
124             else
125                 bounds.size.height = new_height;
126         }
127
128         bounds.origin = CGPointMake( 0.0, 0.0 );
129         videolayer.bounds = bounds;
130         videolayer.position = CGPointMake((layer.bounds.size.width - layer.bounds.origin.x)/2, (layer.bounds.size.height - layer.bounds.origin.y)/2);
131     }
132 }
133 @end
134
135 /******************************************************************************
136  * Implementation VLCVideoView 
137  */
138
139 @implementation VLCVideoView
140
141 - (BOOL)fillScreen
142 {
143     return [layoutManager fillScreenEntirely];
144 }
145 - (void)setFillScreen:(BOOL)fillScreen
146 {
147     [layoutManager setFillScreenEntirely:fillScreen];
148     [[self layer] setNeedsLayout];
149 }
150
151 - (BOOL)fullScreen
152 {
153     return fullScreen;
154 }
155
156 - (void)setFullScreen:(BOOL)newFullScreen
157 {
158     if( newFullScreen )
159     {
160         fullScreen = YES;
161         [self enterFullscreen];
162     }
163     else
164     {
165         fullScreen = NO;
166         [self leaveFullscreen];
167     }
168 }
169
170
171 - (id)initWithFrame:(NSRect)rect
172 {
173     if (self = [super initWithFrame:rect]) 
174     {
175         delegate = nil;
176         [self setBackColor:[NSColor blackColor]];
177         [self setStretchesVideo:NO];
178         [self setAutoresizesSubviews:YES];
179         [self setFillScreen: NO];
180         layoutManager = [[VLCConstraintLayoutManager layoutManager] retain];
181     }
182     return self;
183 }
184
185 - (void)dealloc
186 {
187     [layoutManager release];
188     delegate = nil;
189     [backColor release];
190     [super dealloc];
191 }
192
193 - (void)setDelegate:(id)value
194 {
195     delegate = value;
196 }
197
198 - (id)delegate
199 {
200     return delegate;
201 }
202
203 - (void)setBackColor:(NSColor *)value
204 {
205     if (backColor != value)
206     {
207         [backColor release];
208         backColor = [value retain];
209     }
210 }
211
212 - (NSColor *)backColor
213 {
214     return backColor;
215 }
216
217 /* This is a LibVLC notification that we're about to enter into full screen,
218    there is no other place where I can see where we can trap this event */
219 - (void)enterFullscreen
220 {
221     // Go ahead and send a notification to the world we're going into full screen
222     [[VLCEventManager sharedManager] callOnMainThreadDelegateOfObject:self 
223                                                    withDelegateMethod:nil 
224                                                  withNotificationName:VLCVideoViewEnteredFullScreen];
225     
226     [super enterFullScreenMode:[[self window] screen] withOptions:nil];
227     if( !self.fullScreen ) self.fullScreen = YES;
228 }
229
230 /* This is a LibVLC notification that we're about to enter leaving full screen,
231    there is no other place where I can see where we can trap this event */
232 - (void)leaveFullscreen
233 {
234     // Go ahead and send a notification to the world we're leaving full screen
235     [[VLCEventManager sharedManager] callOnMainThreadDelegateOfObject:self 
236                                                    withDelegateMethod:nil 
237                                                  withNotificationName:VLCVideoViewLeftFullScreen];
238     
239     // There is nothing else to do, as this object strictly displays the video feed
240     [super exitFullScreenModeWithOptions:nil];
241     if( self.fullScreen ) self.fullScreen = NO;
242 }
243
244 - (void)drawRect:(NSRect)aRect
245 {
246     [self lockFocus];
247     [backColor set];
248     NSRectFill(aRect);
249     [self unlockFocus];
250 }
251
252 - (BOOL)isOpaque
253 {
254     return YES;
255 }
256
257 - (void)mouseDown:(NSEvent *)theEvent
258 {
259     if([theEvent clickCount] != 2)
260         return;
261     if(self.fullScreen)
262         [self leaveFullscreen];
263     else
264         [self enterFullscreen];
265 }
266 @end
267
268 /******************************************************************************
269  * Implementation VLCVideoView  (Private)
270  */
271
272 @implementation VLCVideoView (Private)
273
274 /* This is called by the libvlc module 'opengllayer' as soon as there is one 
275  * vout available
276  */
277 - (void)addVoutLayer:(CALayer *)aLayer
278 {
279     [CATransaction begin];
280     [self setWantsLayer: YES];
281         CALayer * rootLayer = [self layer];
282
283     aLayer.name = @"vlcopengllayer";
284
285     [layoutManager setOriginalVideoSize:aLayer.bounds.size];
286     [rootLayer setLayoutManager:layoutManager];
287     [rootLayer insertSublayer:aLayer atIndex:0];
288
289     [aLayer setNeedsLayout];
290     [aLayer setNeedsDisplay];
291     [rootLayer setNeedsDisplay];
292     [rootLayer layoutIfNeeded];
293     [CATransaction commit];
294 }
295
296 @end
297
298 /******************************************************************************
299  * Implementation VLCVideoView  (Deprecated)
300  */
301
302 @implementation VLCVideoView (Deprecated)
303
304 - (void)setStretchesVideo:(BOOL)value
305 {
306     stretchesVideo = value;
307 }
308
309 - (BOOL)stretchesVideo
310 {
311     return stretchesVideo;
312 }
313
314 /* This is called by the libvlc module 'minimal_macosx' as soon as there is one 
315  * vout available
316  */
317 - (void)addVoutSubview:(NSView *)aView /* (Scheduled to deletion) */
318 {
319     /* This is where the real video comes from */
320     if( [[self subviews] count] )
321     {
322         /* XXX: This is a hack until core gets fixed */
323         int i;
324         for( i = 0; i < [[self subviews] count]; i++ )
325         {
326             [[[self subviews] objectAtIndex:i] detachFromVout];
327             [[[self subviews] objectAtIndex:i] retain];
328             [[[self subviews] objectAtIndex:i] removeFromSuperview];
329         }
330     }
331
332     [aView setFrame:[self bounds]];
333     
334     [self addSubview:aView];
335
336     // TODO: Should we let the media player specify these values?
337     [aView setAutoresizingMask:NSViewHeightSizable | NSViewWidthSizable];
338 }
339
340 - (void)removeVoutSubview:(NSView *)view /* (Scheduled to deletion) */
341 {
342     // Should we do something?  I don't know, however the protocol requires
343     // this to be implemented
344 }
345 @end