]> git.sesse.net Git - vlc/blob - modules/gui/macosx/Windows.m
macosx: move the resizing code to VLCVideoWindowCommon as its related to vout
[vlc] / modules / gui / macosx / Windows.m
1 /*****************************************************************************
2  * Windows.m: MacOS X interface module
3  *****************************************************************************
4  * Copyright (C) 2012 VLC authors and VideoLAN
5  * $Id$
6  *
7  * Authors: Felix Paul Kühne <fkuehne -at- videolan -dot- org>
8  *          David Fuhrmann <david dot fuhrmann at googlemail dot com>
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 "Windows.h"
26 #import "intf.h"
27 #import "CoreInteraction.h"
28
29 /*****************************************************************************
30  * VLCWindow
31  *
32  *  Missing extension to NSWindow
33  *****************************************************************************/
34
35 @implementation VLCWindow
36 - (id)initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)styleMask
37                   backing:(NSBackingStoreType)backingType defer:(BOOL)flag
38 {
39     self = [super initWithContentRect:contentRect styleMask:styleMask backing:backingType defer:flag];
40     if (self) {
41         /* we don't want this window to be restored on relaunch */
42         if (!OSX_SNOW_LEOPARD)
43             [self setRestorable:NO];
44     }
45     return self;
46 }
47
48 - (void)setCanBecomeKeyWindow: (BOOL)canBecomeKey
49 {
50     b_isset_canBecomeKeyWindow = YES;
51     b_canBecomeKeyWindow = canBecomeKey;
52 }
53
54 - (BOOL)canBecomeKeyWindow
55 {
56     if (b_isset_canBecomeKeyWindow)
57         return b_canBecomeKeyWindow;
58
59     return [super canBecomeKeyWindow];
60 }
61
62 - (void)setCanBecomeMainWindow: (BOOL)canBecomeMain
63 {
64     b_isset_canBecomeMainWindow = YES;
65     b_canBecomeMainWindow = canBecomeMain;
66 }
67
68 - (BOOL)canBecomeMainWindow
69 {
70     if (b_isset_canBecomeMainWindow)
71         return b_canBecomeMainWindow;
72
73     return [super canBecomeMainWindow];
74 }
75
76 - (void)closeAndAnimate: (BOOL)animate
77 {
78     NSInvocation *invoc;
79
80     if (!animate) {
81         [super close];
82         return;
83     }
84
85     invoc = [NSInvocation invocationWithMethodSignature:[super methodSignatureForSelector:@selector(close)]];
86     [invoc setTarget: self];
87
88     if (![self isVisible] || [self alphaValue] == 0.0) {
89         [super close];
90         return;
91     }
92
93     [self orderOut: self animate: YES callback: invoc];
94 }
95
96 - (void)orderOut: (id)sender animate: (BOOL)animate
97 {
98     NSInvocation *invoc = [NSInvocation invocationWithMethodSignature:[super methodSignatureForSelector:@selector(orderOut:)]];
99     [invoc setTarget: self];
100     [invoc setArgument: sender atIndex: 0];
101     [self orderOut: sender animate: animate callback: invoc];
102 }
103
104 - (void)orderOut: (id)sender animate: (BOOL)animate callback:(NSInvocation *)callback
105 {
106     NSViewAnimation *anim;
107     NSViewAnimation *current_anim;
108     NSMutableDictionary *dict;
109
110     if (!animate) {
111         [self orderOut: sender];
112         return;
113     }
114
115     dict = [[NSMutableDictionary alloc] initWithCapacity:2];
116
117     [dict setObject:self forKey:NSViewAnimationTargetKey];
118
119     [dict setObject:NSViewAnimationFadeOutEffect forKey:NSViewAnimationEffectKey];
120     anim = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObjects:dict, nil]];
121     [dict release];
122
123     [anim setAnimationBlockingMode:NSAnimationNonblocking];
124     [anim setDuration:0.9];
125     [anim setFrameRate:30];
126     [anim setUserInfo: callback];
127
128     @synchronized(self) {
129         current_anim = self->o_current_animation;
130
131         if ([[[current_anim viewAnimations] objectAtIndex:0] objectForKey: NSViewAnimationEffectKey] == NSViewAnimationFadeOutEffect && [current_anim isAnimating]) {
132             [anim release];
133         } else {
134             if (current_anim) {
135                 [current_anim stopAnimation];
136                 [anim setCurrentProgress:1.0 - [current_anim currentProgress]];
137                 [current_anim release];
138             }
139             else
140                 [anim setCurrentProgress:1.0 - [self alphaValue]];
141             self->o_current_animation = anim;
142             [anim startAnimation];
143         }
144     }
145 }
146
147 - (void)orderFront: (id)sender animate: (BOOL)animate
148 {
149     NSViewAnimation *anim;
150     NSViewAnimation *current_anim;
151     NSMutableDictionary *dict;
152
153     if (!animate) {
154         [super orderFront: sender];
155         [self setAlphaValue: 1.0];
156         return;
157     }
158
159     if (![self isVisible]) {
160         [self setAlphaValue: 0.0];
161         [super orderFront: sender];
162     }
163     else if ([self alphaValue] == 1.0) {
164         [super orderFront: self];
165         return;
166     }
167
168     dict = [[NSMutableDictionary alloc] initWithCapacity:2];
169
170     [dict setObject:self forKey:NSViewAnimationTargetKey];
171
172     [dict setObject:NSViewAnimationFadeInEffect forKey:NSViewAnimationEffectKey];
173     anim = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObjects:dict, nil]];
174     [dict release];
175
176     [anim setAnimationBlockingMode:NSAnimationNonblocking];
177     [anim setDuration:0.5];
178     [anim setFrameRate:30];
179
180     @synchronized(self) {
181         current_anim = self->o_current_animation;
182
183         if ([[[current_anim viewAnimations] objectAtIndex:0] objectForKey: NSViewAnimationEffectKey] == NSViewAnimationFadeInEffect && [current_anim isAnimating]) {
184             [anim release];
185         } else {
186             if (current_anim) {
187                 [current_anim stopAnimation];
188                 [anim setCurrentProgress:1.0 - [current_anim currentProgress]];
189                 [current_anim release];
190             }
191             else
192                 [anim setCurrentProgress:[self alphaValue]];
193             self->o_current_animation = anim;
194             [self orderFront: sender];
195             [anim startAnimation];
196         }
197     }
198 }
199
200 - (void)animationDidEnd:(NSAnimation*)anim
201 {
202     if ([self alphaValue] <= 0.0) {
203         NSInvocation * invoc;
204         [super orderOut: nil];
205         [self setAlphaValue: 1.0];
206         if ((invoc = [anim userInfo]))
207             [invoc invoke];
208     }
209 }
210
211 @end
212
213
214 /*****************************************************************************
215  * VLCVideoWindowCommon
216  *
217  *  Common code for main window, detached window and extra video window
218  *****************************************************************************/
219
220 @implementation VLCVideoWindowCommon
221
222 @synthesize videoView=o_video_view;
223 @synthesize controlsBar=o_controls_bar;
224
225 #pragma mark -
226 #pragma mark Init
227
228 - (id)initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)styleMask
229                   backing:(NSBackingStoreType)backingType defer:(BOOL)flag
230 {
231     b_dark_interface = config_GetInt(VLCIntf, "macosx-interfacestyle");
232
233     if (b_dark_interface) {
234         styleMask = NSBorderlessWindowMask;
235 #ifdef MAC_OS_X_VERSION_10_7
236         if (!OSX_SNOW_LEOPARD)
237             styleMask |= NSResizableWindowMask;
238 #endif
239     }
240
241     self = [super initWithContentRect:contentRect styleMask:styleMask
242                               backing:backingType defer:flag];
243
244     /* we want to be moveable regardless of our style */
245     [self setMovableByWindowBackground: YES];
246     [self setCanBecomeKeyWindow:YES];
247
248     return self;
249 }
250
251 - (void)setTitle:(NSString *)title
252 {
253     if (b_dark_interface && o_titlebar_view)
254         [o_titlebar_view setWindowTitle: title];
255
256     [super setTitle: title];
257 }
258
259 #pragma mark -
260 #pragma mark zoom / minimize / close
261
262 - (BOOL)validateMenuItem:(NSMenuItem *)menuItem
263 {
264     SEL s_menuAction = [menuItem action];
265
266     if ((s_menuAction == @selector(performClose:)) || (s_menuAction == @selector(performMiniaturize:)) || (s_menuAction == @selector(performZoom:)))
267         return YES;
268
269     return [super validateMenuItem:menuItem];
270 }
271
272 - (BOOL)windowShouldClose:(id)sender
273 {
274     return YES;
275 }
276
277 - (void)performClose:(id)sender
278 {
279     if (!([self styleMask] & NSTitledWindowMask)) {
280         [[NSNotificationCenter defaultCenter] postNotificationName:NSWindowWillCloseNotification object:self];
281
282         [self orderOut: sender];
283     } else
284         [super performClose: sender];
285 }
286
287 - (void)performMiniaturize:(id)sender
288 {
289     if (!([self styleMask] & NSTitledWindowMask))
290         [self miniaturize: sender];
291     else
292         [super performMiniaturize: sender];
293 }
294
295 - (void)performZoom:(id)sender
296 {
297     if (!([self styleMask] & NSTitledWindowMask))
298         [self customZoom: sender];
299     else
300         [super performZoom: sender];
301 }
302
303 - (void)zoom:(id)sender
304 {
305     if (!([self styleMask] & NSTitledWindowMask))
306         [self customZoom: sender];
307     else
308         [super zoom: sender];
309 }
310
311 /**
312  * Given a proposed frame rectangle, return a modified version
313  * which will fit inside the screen.
314  *
315  * This method is based upon NSWindow.m, part of the GNUstep GUI Library, licensed under LGPLv2+.
316  *    Authors:  Scott Christley <scottc@net-community.com>, Venkat Ajjanagadde <venkat@ocbi.com>,
317  *              Felipe A. Rodriguez <far@ix.netcom.com>, Richard Frith-Macdonald <richard@brainstorm.co.uk>
318  *    Copyright (C) 1996 Free Software Foundation, Inc.
319  */
320 - (NSRect) customConstrainFrameRect: (NSRect)frameRect toScreen: (NSScreen*)screen
321 {
322     NSRect screenRect = [screen visibleFrame];
323     float difference;
324
325     /* Move top edge of the window inside the screen */
326     difference = NSMaxY (frameRect) - NSMaxY (screenRect);
327     if (difference > 0) {
328         frameRect.origin.y -= difference;
329     }
330
331     /* If the window is resizable, resize it (if needed) so that the
332      bottom edge is on the screen or can be on the screen when the user moves
333      the window */
334     difference = NSMaxY (screenRect) - NSMaxY (frameRect);
335     if (_styleMask & NSResizableWindowMask) {
336         float difference2;
337
338         difference2 = screenRect.origin.y - frameRect.origin.y;
339         difference2 -= difference;
340         // Take in account the space between the top of window and the top of the
341         // screen which can be used to move the bottom of the window on the screen
342         if (difference2 > 0) {
343             frameRect.size.height -= difference2;
344             frameRect.origin.y += difference2;
345         }
346
347         /* Ensure that resizing doesn't makewindow smaller than minimum */
348         difference2 = [self minSize].height - frameRect.size.height;
349         if (difference2 > 0) {
350             frameRect.size.height += difference2;
351             frameRect.origin.y -= difference2;
352         }
353     }
354
355     return frameRect;
356 }
357
358 #define DIST 3
359
360 /**
361  Zooms the receiver.   This method calls the delegate method
362  windowShouldZoom:toFrame: to determine if the window should
363  be allowed to zoom to full screen.
364  *
365  * This method is based upon NSWindow.m, part of the GNUstep GUI Library, licensed under LGPLv2+.
366  *    Authors:  Scott Christley <scottc@net-community.com>, Venkat Ajjanagadde <venkat@ocbi.com>,
367  *              Felipe A. Rodriguez <far@ix.netcom.com>, Richard Frith-Macdonald <richard@brainstorm.co.uk>
368  *    Copyright (C) 1996 Free Software Foundation, Inc.
369  */
370 - (void) customZoom: (id)sender
371 {
372     NSRect maxRect = [[self screen] visibleFrame];
373     NSRect currentFrame = [self frame];
374
375     if ([[self delegate] respondsToSelector: @selector(windowWillUseStandardFrame:defaultFrame:)]) {
376         maxRect = [[self delegate] windowWillUseStandardFrame: self defaultFrame: maxRect];
377     }
378
379     maxRect = [self customConstrainFrameRect: maxRect toScreen: [self screen]];
380
381     // Compare the new frame with the current one
382     if ((abs(NSMaxX(maxRect) - NSMaxX(currentFrame)) < DIST)
383         && (abs(NSMaxY(maxRect) - NSMaxY(currentFrame)) < DIST)
384         && (abs(NSMinX(maxRect) - NSMinX(currentFrame)) < DIST)
385         && (abs(NSMinY(maxRect) - NSMinY(currentFrame)) < DIST)) {
386         // Already in zoomed mode, reset user frame, if stored
387         if ([self frameAutosaveName] != nil) {
388             [self setFrame: previousSavedFrame display: YES animate: YES];
389             [self saveFrameUsingName: [self frameAutosaveName]];
390         }
391         return;
392     }
393
394     if ([self frameAutosaveName] != nil) {
395         [self saveFrameUsingName: [self frameAutosaveName]];
396         previousSavedFrame = [self frame];
397     }
398
399     [self setFrame: maxRect display: YES animate: YES];
400 }
401
402 #pragma mark -
403 #pragma mark Video window resizing logic
404
405 - (void)resizeWindow
406 {
407     if ([[VLCMainWindow sharedInstance] fullscreen])
408         return;
409
410     NSSize windowMinSize = [self minSize];
411     NSRect screenFrame = [[self screen] visibleFrame];
412
413     NSPoint topleftbase = NSMakePoint(0, [self frame].size.height);
414     NSPoint topleftscreen = [self convertBaseToScreen: topleftbase];
415
416     unsigned int i_width = nativeVideoSize.width;
417     unsigned int i_height = nativeVideoSize.height;
418     if (i_width < windowMinSize.width)
419         i_width = windowMinSize.width;
420     if (i_height < f_min_video_height)
421         i_height = f_min_video_height;
422
423     /* Calculate the window's new size */
424     NSRect new_frame;
425     new_frame.size.width = [self frame].size.width - [o_video_view frame].size.width + i_width;
426     new_frame.size.height = [self frame].size.height - [o_video_view frame].size.height + i_height;
427     new_frame.origin.x = topleftscreen.x;
428     new_frame.origin.y = topleftscreen.y - new_frame.size.height;
429
430     /* make sure the window doesn't exceed the screen size the window is on */
431     if (new_frame.size.width > screenFrame.size.width) {
432         new_frame.size.width = screenFrame.size.width;
433         new_frame.origin.x = screenFrame.origin.x;
434     }
435     if (new_frame.size.height > screenFrame.size.height) {
436         new_frame.size.height = screenFrame.size.height;
437         new_frame.origin.y = screenFrame.origin.y;
438     }
439     if (new_frame.origin.y < screenFrame.origin.y)
440         new_frame.origin.y = screenFrame.origin.y;
441
442     CGFloat right_screen_point = screenFrame.origin.x + screenFrame.size.width;
443     CGFloat right_window_point = new_frame.origin.x + new_frame.size.width;
444     if (right_window_point > right_screen_point)
445         new_frame.origin.x -= (right_window_point - right_screen_point);
446
447     [[self animator] setFrame:new_frame display:YES];
448 }
449
450 - (void)setNativeVideoSize:(NSSize)size
451 {
452     nativeVideoSize = size;
453
454     if (var_InheritBool(VLCIntf, "macosx-video-autoresize") && !var_InheritBool(VLCIntf, "video-wallpaper"))
455         [self resizeWindow];
456 }
457
458 - (NSSize)windowWillResize:(NSWindow *)window toSize:(NSSize)proposedFrameSize
459 {
460     if (![[VLCMain sharedInstance] activeVideoPlayback] || nativeVideoSize.width == 0. || nativeVideoSize.height == 0. || window != self)
461         return proposedFrameSize;
462
463     // needed when entering lion fullscreen mode
464     if ([[VLCMainWindow sharedInstance] fullscreen])
465         return proposedFrameSize;
466
467     if ([[VLCCoreInteraction sharedInstance] aspectRatioIsLocked]) {
468         NSRect videoWindowFrame = [self frame];
469         NSRect viewRect = [o_video_view convertRect:[o_video_view bounds] toView: nil];
470         NSRect contentRect = [self contentRectForFrameRect:videoWindowFrame];
471         float marginy = viewRect.origin.y + videoWindowFrame.size.height - contentRect.size.height;
472         float marginx = contentRect.size.width - viewRect.size.width;
473         if (o_titlebar_view && b_dark_interface)
474             marginy += [o_titlebar_view frame].size.height;
475
476         proposedFrameSize.height = (proposedFrameSize.width - marginx) * nativeVideoSize.height / nativeVideoSize.width + marginy;
477     }
478
479     return proposedFrameSize;
480 }
481
482 #pragma mark -
483 #pragma mark Accessibility stuff
484
485 - (NSArray *)accessibilityAttributeNames
486 {
487     if (!b_dark_interface || !o_titlebar_view)
488         return [super accessibilityAttributeNames];
489
490     static NSMutableArray *attributes = nil;
491     if (attributes == nil) {
492         attributes = [[super accessibilityAttributeNames] mutableCopy];
493         NSArray *appendAttributes = [NSArray arrayWithObjects: NSAccessibilitySubroleAttribute,
494                                      NSAccessibilityCloseButtonAttribute,
495                                      NSAccessibilityMinimizeButtonAttribute,
496                                      NSAccessibilityZoomButtonAttribute,
497                                      nil];
498
499         for(NSString *attribute in appendAttributes) {
500             if (![attributes containsObject:attribute])
501                 [attributes addObject:attribute];
502         }
503     }
504     return attributes;
505 }
506
507 - (id)accessibilityAttributeValue: (NSString*)o_attribute_name
508 {
509     if (b_dark_interface && o_titlebar_view) {
510         VLCMainWindowTitleView *o_tbv = o_titlebar_view;
511
512         if ([o_attribute_name isEqualTo: NSAccessibilitySubroleAttribute])
513             return NSAccessibilityStandardWindowSubrole;
514
515         if ([o_attribute_name isEqualTo: NSAccessibilityCloseButtonAttribute])
516             return [[o_tbv closeButton] cell];
517
518         if ([o_attribute_name isEqualTo: NSAccessibilityMinimizeButtonAttribute])
519             return [[o_tbv minimizeButton] cell];
520
521         if ([o_attribute_name isEqualTo: NSAccessibilityZoomButtonAttribute])
522             return [[o_tbv zoomButton] cell];
523     }
524
525     return [super accessibilityAttributeValue: o_attribute_name];
526 }
527
528 @end