]> git.sesse.net Git - vlc/blob - modules/gui/macosx/Windows.m
macosx: simplify one setting and fix a typo regarding extra video window
[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 controlsBar=o_controls_bar;
223
224 #pragma mark -
225 #pragma mark Init
226
227 - (id)initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)styleMask
228                   backing:(NSBackingStoreType)backingType defer:(BOOL)flag
229 {
230     b_dark_interface = config_GetInt(VLCIntf, "macosx-interfacestyle");
231
232     if (b_dark_interface) {
233         styleMask = NSBorderlessWindowMask;
234 #ifdef MAC_OS_X_VERSION_10_7
235         if (!OSX_SNOW_LEOPARD)
236             styleMask |= NSResizableWindowMask;
237 #endif
238     }
239
240     self = [super initWithContentRect:contentRect styleMask:styleMask
241                               backing:backingType defer:flag];
242
243     /* we want to be moveable regardless of our style */
244     [self setMovableByWindowBackground: YES];
245     [self setCanBecomeKeyWindow:YES];
246
247     return self;
248 }
249
250 - (void)setTitle:(NSString *)title
251 {
252     if (b_dark_interface && o_titlebar_view)
253         [o_titlebar_view setWindowTitle: title];
254
255     [super setTitle: title];
256 }
257
258 #pragma mark -
259 #pragma mark zoom / minimize / close
260
261 - (BOOL)validateMenuItem:(NSMenuItem *)menuItem
262 {
263     SEL s_menuAction = [menuItem action];
264
265     if ((s_menuAction == @selector(performClose:)) || (s_menuAction == @selector(performMiniaturize:)) || (s_menuAction == @selector(performZoom:)))
266         return YES;
267
268     return [super validateMenuItem:menuItem];
269 }
270
271 - (BOOL)windowShouldClose:(id)sender
272 {
273     return YES;
274 }
275
276 - (void)performClose:(id)sender
277 {
278     if (!([self styleMask] & NSTitledWindowMask)) {
279         [[NSNotificationCenter defaultCenter] postNotificationName:NSWindowWillCloseNotification object:self];
280
281         [self orderOut: sender];
282     } else
283         [super performClose: sender];
284 }
285
286 - (void)performMiniaturize:(id)sender
287 {
288     if (!([self styleMask] & NSTitledWindowMask))
289         [self miniaturize: sender];
290     else
291         [super performMiniaturize: sender];
292 }
293
294 - (void)performZoom:(id)sender
295 {
296     if (!([self styleMask] & NSTitledWindowMask))
297         [self customZoom: sender];
298     else
299         [super performZoom: sender];
300 }
301
302 - (void)zoom:(id)sender
303 {
304     if (!([self styleMask] & NSTitledWindowMask))
305         [self customZoom: sender];
306     else
307         [super zoom: sender];
308 }
309
310 /**
311  * Given a proposed frame rectangle, return a modified version
312  * which will fit inside the screen.
313  *
314  * This method is based upon NSWindow.m, part of the GNUstep GUI Library, licensed under LGPLv2+.
315  *    Authors:  Scott Christley <scottc@net-community.com>, Venkat Ajjanagadde <venkat@ocbi.com>,
316  *              Felipe A. Rodriguez <far@ix.netcom.com>, Richard Frith-Macdonald <richard@brainstorm.co.uk>
317  *    Copyright (C) 1996 Free Software Foundation, Inc.
318  */
319 - (NSRect) customConstrainFrameRect: (NSRect)frameRect toScreen: (NSScreen*)screen
320 {
321     NSRect screenRect = [screen visibleFrame];
322     float difference;
323
324     /* Move top edge of the window inside the screen */
325     difference = NSMaxY (frameRect) - NSMaxY (screenRect);
326     if (difference > 0) {
327         frameRect.origin.y -= difference;
328     }
329
330     /* If the window is resizable, resize it (if needed) so that the
331      bottom edge is on the screen or can be on the screen when the user moves
332      the window */
333     difference = NSMaxY (screenRect) - NSMaxY (frameRect);
334     if (_styleMask & NSResizableWindowMask) {
335         float difference2;
336
337         difference2 = screenRect.origin.y - frameRect.origin.y;
338         difference2 -= difference;
339         // Take in account the space between the top of window and the top of the
340         // screen which can be used to move the bottom of the window on the screen
341         if (difference2 > 0) {
342             frameRect.size.height -= difference2;
343             frameRect.origin.y += difference2;
344         }
345
346         /* Ensure that resizing doesn't makewindow smaller than minimum */
347         difference2 = [self minSize].height - frameRect.size.height;
348         if (difference2 > 0) {
349             frameRect.size.height += difference2;
350             frameRect.origin.y -= difference2;
351         }
352     }
353
354     return frameRect;
355 }
356
357 #define DIST 3
358
359 /**
360  Zooms the receiver.   This method calls the delegate method
361  windowShouldZoom:toFrame: to determine if the window should
362  be allowed to zoom to full screen.
363  *
364  * This method is based upon NSWindow.m, part of the GNUstep GUI Library, licensed under LGPLv2+.
365  *    Authors:  Scott Christley <scottc@net-community.com>, Venkat Ajjanagadde <venkat@ocbi.com>,
366  *              Felipe A. Rodriguez <far@ix.netcom.com>, Richard Frith-Macdonald <richard@brainstorm.co.uk>
367  *    Copyright (C) 1996 Free Software Foundation, Inc.
368  */
369 - (void) customZoom: (id)sender
370 {
371     NSRect maxRect = [[self screen] visibleFrame];
372     NSRect currentFrame = [self frame];
373
374     if ([[self delegate] respondsToSelector: @selector(windowWillUseStandardFrame:defaultFrame:)]) {
375         maxRect = [[self delegate] windowWillUseStandardFrame: self defaultFrame: maxRect];
376     }
377
378     maxRect = [self customConstrainFrameRect: maxRect toScreen: [self screen]];
379
380     // Compare the new frame with the current one
381     if ((abs(NSMaxX(maxRect) - NSMaxX(currentFrame)) < DIST)
382         && (abs(NSMaxY(maxRect) - NSMaxY(currentFrame)) < DIST)
383         && (abs(NSMinX(maxRect) - NSMinX(currentFrame)) < DIST)
384         && (abs(NSMinY(maxRect) - NSMinY(currentFrame)) < DIST)) {
385         // Already in zoomed mode, reset user frame, if stored
386         if ([self frameAutosaveName] != nil) {
387             [self setFrame: previousSavedFrame display: YES animate: YES];
388             [self saveFrameUsingName: [self frameAutosaveName]];
389         }
390         return;
391     }
392
393     if ([self frameAutosaveName] != nil) {
394         [self saveFrameUsingName: [self frameAutosaveName]];
395         previousSavedFrame = [self frame];
396     }
397
398     [self setFrame: maxRect display: YES animate: YES];
399 }
400
401 #pragma mark -
402 #pragma mark Accessibility stuff
403
404 - (NSArray *)accessibilityAttributeNames
405 {
406     if (!b_dark_interface || !o_titlebar_view)
407         return [super accessibilityAttributeNames];
408
409     static NSMutableArray *attributes = nil;
410     if (attributes == nil) {
411         attributes = [[super accessibilityAttributeNames] mutableCopy];
412         NSArray *appendAttributes = [NSArray arrayWithObjects: NSAccessibilitySubroleAttribute,
413                                      NSAccessibilityCloseButtonAttribute,
414                                      NSAccessibilityMinimizeButtonAttribute,
415                                      NSAccessibilityZoomButtonAttribute,
416                                      nil];
417
418         for(NSString *attribute in appendAttributes) {
419             if (![attributes containsObject:attribute])
420                 [attributes addObject:attribute];
421         }
422     }
423     return attributes;
424 }
425
426 - (id)accessibilityAttributeValue: (NSString*)o_attribute_name
427 {
428     if (b_dark_interface && o_titlebar_view) {
429         VLCMainWindowTitleView *o_tbv = o_titlebar_view;
430
431         if ([o_attribute_name isEqualTo: NSAccessibilitySubroleAttribute])
432             return NSAccessibilityStandardWindowSubrole;
433
434         if ([o_attribute_name isEqualTo: NSAccessibilityCloseButtonAttribute])
435             return [[o_tbv closeButton] cell];
436
437         if ([o_attribute_name isEqualTo: NSAccessibilityMinimizeButtonAttribute])
438             return [[o_tbv minimizeButton] cell];
439
440         if ([o_attribute_name isEqualTo: NSAccessibilityZoomButtonAttribute])
441             return [[o_tbv zoomButton] cell];
442     }
443
444     return [super accessibilityAttributeValue: o_attribute_name];
445 }
446
447 @end