]> git.sesse.net Git - vlc/blob - modules/gui/macosx/MainWindowTitle.m
macosx: allow the user to minimize the main window by double-clicking the window...
[vlc] / modules / gui / macosx / MainWindowTitle.m
1 /*****************************************************************************
2  * MainWindowTitle.m: MacOS X interface module
3  *****************************************************************************
4  * Copyright (C) 2011-2012 Felix Paul Kühne
5  * $Id$
6  *
7  * Authors: Felix Paul Kühne <fkuehne -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 #import <vlc_common.h>
25 #import "intf.h"
26 #import "MainWindowTitle.h"
27 #import "CoreInteraction.h"
28 #import "CompatibilityFixes.h"
29
30 /*****************************************************************************
31  * VLCMainWindowTitleView
32  *
33  * this is our title bar, which can do anything a title should do
34  * it relies on the VLCWindowButtonCell to display the correct traffic light
35  * states, since we can't capture the mouse-moved events here correctly
36  *****************************************************************************/
37
38 @implementation VLCMainWindowTitleView
39 - (id)init
40 {
41     o_window_title_attributes_dict = [[NSDictionary dictionaryWithObjectsAndKeys: [NSColor whiteColor], NSForegroundColorAttributeName, [NSFont titleBarFontOfSize:12.0], NSFontAttributeName, nil] retain];
42
43     return [super init];
44 }
45
46 - (void)dealloc
47 {
48     [[NSNotificationCenter defaultCenter] removeObserver: self];
49
50     [o_red_img release];
51     [o_red_over_img release];
52     [o_red_on_img release];
53     [o_yellow_img release];
54     [o_yellow_over_img release];
55     [o_yellow_on_img release];
56     [o_green_img release];
57     [o_green_over_img release];
58     [o_green_on_img release];
59
60     [o_window_title_shadow release];
61     [o_window_title_attributes_dict release];
62
63     [super dealloc];
64 }
65
66 - (void)awakeFromNib
67 {
68     [self setAutoresizesSubviews: YES];
69     [self setImagesLeft:[NSImage imageNamed:@"topbar-dark-left"] middle: [NSImage imageNamed:@"topbar-dark-center-fill"] right:[NSImage imageNamed:@"topbar-dark-right"]];
70
71     [self loadButtonIcons];
72     [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(controlTintChanged:) name: NSControlTintDidChangeNotification object: nil];
73
74     [o_red_btn setImage: o_red_img];
75     [o_red_btn setAlternateImage: o_red_on_img];
76     [[o_red_btn cell] setShowsBorderOnlyWhileMouseInside: YES];
77     [[o_red_btn cell] setTag: 0];
78     [o_yellow_btn setImage: o_yellow_img];
79     [o_yellow_btn setAlternateImage: o_yellow_on_img];
80     [[o_yellow_btn cell] setShowsBorderOnlyWhileMouseInside: YES];
81     [[o_yellow_btn cell] setTag: 1];
82     [o_green_btn setImage: o_green_img];
83     [o_green_btn setAlternateImage: o_green_on_img];
84     [[o_green_btn cell] setShowsBorderOnlyWhileMouseInside: YES];
85     [[o_green_btn cell] setTag: 2];
86     [o_fullscreen_btn setImage: [NSImage imageNamed:@"window-fullscreen"]];
87     [o_fullscreen_btn setAlternateImage: [NSImage imageNamed:@"window-fullscreen-on"]];
88     [[o_fullscreen_btn cell] setShowsBorderOnlyWhileMouseInside: YES];
89     [[o_fullscreen_btn cell] setTag: 3];
90 }
91
92 - (void)controlTintChanged:(NSNotification *)notification
93 {
94     [o_red_img release];
95     [o_red_over_img release];
96     [o_red_on_img release];
97     [o_yellow_img release];
98     [o_yellow_over_img release];
99     [o_yellow_on_img release];
100     [o_green_img release];
101     [o_green_over_img release];
102     [o_green_on_img release];
103
104     [self loadButtonIcons];
105
106     [o_red_btn setNeedsDisplay];
107     [o_yellow_btn setNeedsDisplay];
108     [o_green_btn setNeedsDisplay];
109 }
110
111 - (void)loadButtonIcons
112 {
113     if (OSX_LION)
114     {
115         if( [NSColor currentControlTint] == NSBlueControlTint )
116         {
117             o_red_img = [[NSImage imageNamed:@"lion-window-close"] retain];
118             o_red_over_img = [[NSImage imageNamed:@"lion-window-close-over"] retain];
119             o_red_on_img = [[NSImage imageNamed:@"lion-window-close-on"] retain];
120             o_yellow_img = [[NSImage imageNamed:@"lion-window-minimize"] retain];
121             o_yellow_over_img = [[NSImage imageNamed:@"lion-window-minimize-over"] retain];
122             o_yellow_on_img = [[NSImage imageNamed:@"lion-window-minimize-on"] retain];
123             o_green_img = [[NSImage imageNamed:@"lion-window-zoom"] retain];
124             o_green_over_img = [[NSImage imageNamed:@"lion-window-zoom-over"] retain];
125             o_green_on_img = [[NSImage imageNamed:@"lion-window-zoom-on"] retain];
126         } else {
127             o_red_img = [[NSImage imageNamed:@"lion-window-close-graphite"] retain];
128             o_red_over_img = [[NSImage imageNamed:@"lion-window-close-over-graphite"] retain];
129             o_red_on_img = [[NSImage imageNamed:@"lion-window-close-on-graphite"] retain];
130             o_yellow_img = [[NSImage imageNamed:@"lion-window-minimize-graphite"] retain];
131             o_yellow_over_img = [[NSImage imageNamed:@"lion-window-minimize-over-graphite"] retain];
132             o_yellow_on_img = [[NSImage imageNamed:@"lion-window-minimize-on-graphite"] retain];
133             o_green_img = [[NSImage imageNamed:@"lion-window-zoom-graphite"] retain];
134             o_green_over_img = [[NSImage imageNamed:@"lion-window-zoom-over-graphite"] retain];
135             o_green_on_img = [[NSImage imageNamed:@"lion-window-zoom-on-graphite"] retain];
136         }
137     } else {
138         if( [NSColor currentControlTint] == NSBlueControlTint )
139         {
140             o_red_img = [[NSImage imageNamed:@"snowleo-window-close"] retain];
141             o_red_over_img = [[NSImage imageNamed:@"snowleo-window-close-over"] retain];
142             o_red_on_img = [[NSImage imageNamed:@"snowleo-window-close-on"] retain];
143             o_yellow_img = [[NSImage imageNamed:@"snowleo-window-minimize"] retain];
144             o_yellow_over_img = [[NSImage imageNamed:@"snowleo-window-minimize-over"] retain];
145             o_yellow_on_img = [[NSImage imageNamed:@"snowleo-window-minimize-on"] retain];
146             o_green_img = [[NSImage imageNamed:@"snowleo-window-zoom"] retain];
147             o_green_over_img = [[NSImage imageNamed:@"snowleo-window-zoom-over"] retain];
148             o_green_on_img = [[NSImage imageNamed:@"snowleo-window-zoom-on"] retain];
149         } else {
150             o_red_img = [[NSImage imageNamed:@"snowleo-window-close-graphite"] retain];
151             o_red_over_img = [[NSImage imageNamed:@"snowleo-window-close-over-graphite"] retain];
152             o_red_on_img = [[NSImage imageNamed:@"snowleo-window-close-on-graphite"] retain];
153             o_yellow_img = [[NSImage imageNamed:@"snowleo-window-minimize-graphite"] retain];
154             o_yellow_over_img = [[NSImage imageNamed:@"snowleo-window-minimize-over-graphite"] retain];
155             o_yellow_on_img = [[NSImage imageNamed:@"snowleo-window-minimize-on-graphite"] retain];
156             o_green_img = [[NSImage imageNamed:@"snowleo-window-zoom-graphite"] retain];
157             o_green_over_img = [[NSImage imageNamed:@"snowleo-window-zoom-over-graphite"] retain];
158             o_green_on_img = [[NSImage imageNamed:@"snowleo-window-zoom-on-graphite"] retain];
159         }
160     }
161 }
162
163 - (BOOL)mouseDownCanMoveWindow
164 {
165     return YES;
166 }
167
168 - (IBAction)buttonAction:(id)sender
169 {
170     if (sender == o_red_btn)
171         [[self window] performClose: sender];
172     else if (sender == o_yellow_btn)
173         [[self window] miniaturize: sender];
174     else if (sender == o_green_btn)
175         [[self window] performZoom: sender];
176     else if (sender == o_fullscreen_btn)
177     {
178         // set fs directly to true, as the vars can be already true in some configs
179         var_SetBool( pl_Get( VLCIntf ), "fullscreen", true );
180
181         vout_thread_t *p_vout = getVout();
182         if( p_vout )
183         {
184             var_SetBool( p_vout, "fullscreen", true );
185             vlc_object_release( p_vout );
186         }
187     }
188     else
189         msg_Err( VLCIntf, "unknown button action sender" );
190
191     [self setWindowButtonOver: NO];
192     [self setWindowFullscreenButtonOver: NO];
193 }
194
195 - (void)setWindowTitle:(NSString *)title
196 {
197     if (!o_window_title_shadow)
198     {
199         o_window_title_shadow = [[NSShadow alloc] init];
200         [o_window_title_shadow setShadowColor:[NSColor colorWithCalibratedWhite:1.0 alpha:0.5]];
201         [o_window_title_shadow setShadowOffset:NSMakeSize(0.0, -1.5)];
202         [o_window_title_shadow setShadowBlurRadius:0.5];
203         [o_window_title_shadow retain];
204     }
205
206     NSMutableAttributedString *o_attributed_title = [[NSMutableAttributedString alloc] initWithString:title attributes: o_window_title_attributes_dict];
207     NSUInteger i_titleLength = [title length];
208
209     [o_attributed_title addAttribute:NSShadowAttributeName value:o_window_title_shadow range:NSMakeRange(0, i_titleLength)];
210     [o_attributed_title setAlignment: NSCenterTextAlignment range:NSMakeRange(0, i_titleLength)];
211     [o_title_lbl setAttributedStringValue:o_attributed_title];
212     [o_attributed_title release];
213 }
214
215 - (void)setFullscreenButtonHidden:(BOOL)b_value
216 {
217     [o_fullscreen_btn setHidden: b_value];
218 }
219
220 - (void)setWindowButtonOver:(BOOL)b_value
221 {
222     if( b_value )
223     {
224         [o_red_btn setImage: o_red_over_img];
225         [o_yellow_btn setImage: o_yellow_over_img];
226         [o_green_btn setImage: o_green_over_img];
227     }
228     else
229     {
230         [o_red_btn setImage: o_red_img];
231         [o_yellow_btn setImage: o_yellow_img];
232         [o_green_btn setImage: o_green_img];
233     }
234 }
235
236 - (void)setWindowFullscreenButtonOver:(BOOL)b_value
237 {
238     if (b_value)
239         [o_fullscreen_btn setImage: [NSImage imageNamed:@"window-fullscreen-over"]];
240     else
241         [o_fullscreen_btn setImage: [NSImage imageNamed:@"window-fullscreen"]];
242 }
243
244 - (void)mouseUp:(NSEvent *)event
245 {
246     if ([event clickCount] == 2) {
247         //Get settings from "System Preferences" >  "Appearance" > "Double-click on windows title bar to minimize"
248         NSString *const MDAppleMiniaturizeOnDoubleClickKey = @"AppleMiniaturizeOnDoubleClick";
249         NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
250         [userDefaults addSuiteNamed:NSGlobalDomain];
251
252         if ([[userDefaults objectForKey:MDAppleMiniaturizeOnDoubleClickKey] boolValue])
253             [[self window] miniaturize:self];
254     }
255 }
256 @end
257
258 /*****************************************************************************
259  * VLCWindowButtonCell
260  *
261  * since the title bar cannot fetch these mouse events (the more top-level
262  * NSButton is unable fetch them as well), we are using a subclass of the
263  * button cell to do so. It's set in the nib for the respective objects.
264  *****************************************************************************/
265
266 @implementation VLCWindowButtonCell
267
268 - (void)mouseEntered:(NSEvent *)theEvent
269 {
270     if ([self tag] == 3)
271         [(VLCMainWindowTitleView *)[[self controlView] superview] setWindowFullscreenButtonOver: YES];
272     else
273         [(VLCMainWindowTitleView *)[[self controlView] superview] setWindowButtonOver: YES];
274 }
275
276 - (void)mouseExited:(NSEvent *)theEvent
277 {
278     if ([self tag] == 3)
279         [(VLCMainWindowTitleView *)[[self controlView] superview] setWindowFullscreenButtonOver: NO];
280     else
281         [(VLCMainWindowTitleView *)[[self controlView] superview] setWindowButtonOver: NO];
282 }
283
284 /* accessibility stuff */
285 - (NSArray*)accessibilityAttributeNames {
286     NSArray *theAttributeNames = [super accessibilityAttributeNames];
287     id theControlView = [self controlView];
288     return ([theControlView respondsToSelector: @selector(extendedAccessibilityAttributeNames:)] ? [theControlView extendedAccessibilityAttributeNames: theAttributeNames] : theAttributeNames); // ask the cell's control view (i.e., the button) for additional attribute values
289 }
290
291 - (id)accessibilityAttributeValue: (NSString*)theAttributeName {
292     id theControlView = [self controlView];
293     if ([theControlView respondsToSelector: @selector(extendedAccessibilityAttributeValue:)]) {
294         id theValue = [theControlView extendedAccessibilityAttributeValue: theAttributeName];
295         if (theValue) {
296             return theValue; // if this is an extended attribute value we added, return that -- otherwise, fall back to super's implementation
297         }
298     }
299     return [super accessibilityAttributeValue: theAttributeName];
300 }
301
302 - (BOOL)accessibilityIsAttributeSettable: (NSString*)theAttributeName {
303     id theControlView = [self controlView];
304     if ([theControlView respondsToSelector: @selector(extendedAccessibilityIsAttributeSettable:)]) {
305         NSNumber *theValue = [theControlView extendedAccessibilityIsAttributeSettable: theAttributeName];
306         if (theValue) {
307             return [theValue boolValue]; // same basic strategy we use in -accessibilityAttributeValue:
308         }
309     }
310     return [super accessibilityIsAttributeSettable: theAttributeName];
311 }
312
313 @end
314
315
316 /*****************************************************************************
317  * VLCResizeControl
318  *
319  * For Leopard and Snow Leopard, we need to emulate the resize control on the
320  * bottom right of the window, since it is gone by using the borderless window
321  * mask. A proper fix would be Lion-only.
322  *****************************************************************************/
323
324 @implementation VLCResizeControl
325
326 - (void)mouseDown:(NSEvent *)theEvent {
327     BOOL keepOn = YES;
328
329     while (keepOn) {
330         theEvent = [[self window] nextEventMatchingMask: NSLeftMouseUpMask |
331                     NSLeftMouseDraggedMask];
332
333         switch ([theEvent type]) {
334             case NSLeftMouseDragged:
335             {
336                 NSRect windowFrame = [[self window] frame];
337                 CGFloat deltaX, deltaY, oldOriginY;
338                 deltaX = [theEvent deltaX];
339                 deltaY = [theEvent deltaY];
340                 oldOriginY = windowFrame.origin.y;
341
342                 windowFrame.origin.y = (oldOriginY + windowFrame.size.height) - (windowFrame.size.height + deltaY);
343                 windowFrame.size.width += deltaX;
344                 windowFrame.size.height += deltaY;
345
346                 NSSize winMinSize = [self window].minSize;
347                 if (windowFrame.size.width < winMinSize.width)
348                     windowFrame.size.width = winMinSize.width;
349
350                 if (windowFrame.size.height < winMinSize.height)
351                 {
352                     windowFrame.size.height = winMinSize.height;
353                     windowFrame.origin.y = oldOriginY;
354                 }
355
356                 [[self window] setFrame: windowFrame display: YES animate: NO];
357                 break;
358             }
359                 break;
360             case NSLeftMouseUp:
361                 keepOn = NO;
362                 break;
363             default:
364                 /* Ignore any other kind of event. */
365                 break;
366         }
367
368     };
369
370     return;
371 }
372
373 @end
374
375 /*****************************************************************************
376  * VLCColorView
377  *
378  * since we are using a clear window color when using the black window
379  * style, some filling is needed behind the video and some other elements
380  *****************************************************************************/
381
382 @implementation VLCColorView
383
384 - (void)drawRect:(NSRect)rect {
385     [[NSColor blackColor] setFill];
386     NSRectFill(rect);
387 }
388
389 @end
390
391 /*****************************************************************************
392  * custom window buttons to support the accessibility stuff
393  *****************************************************************************/
394
395 @implementation VLCCustomWindowButtonPrototype
396 + (Class)cellClass {
397     return [VLCWindowButtonCell class];
398 }
399
400 - (NSArray*)extendedAccessibilityAttributeNames: (NSArray*)theAttributeNames {
401     return ([theAttributeNames containsObject: NSAccessibilitySubroleAttribute] ? theAttributeNames : [theAttributeNames arrayByAddingObject: NSAccessibilitySubroleAttribute]); // run-of-the-mill button cells don't usually have a Subrole attribute, so we add that attribute
402 }
403
404 - (id)extendedAccessibilityAttributeValue: (NSString*)theAttributeName {
405     return nil;
406 }
407
408 - (NSNumber*)extendedAccessibilityIsAttributeSettable: (NSString*)theAttributeName {
409     return ([theAttributeName isEqualToString: NSAccessibilitySubroleAttribute] ? [NSNumber numberWithBool: NO] : nil); // make the Subrole attribute we added non-settable
410 }
411
412 - (void)accessibilityPerformAction: (NSString*)theActionName {
413     if ([theActionName isEqualToString: NSAccessibilityPressAction]) {
414         if ([self isEnabled]) {
415             [self performClick: nil];
416         }
417     } else {
418         [super accessibilityPerformAction: theActionName];
419     }
420 }
421
422 @end
423
424 @implementation VLCCustomWindowCloseButton
425 - (id)extendedAccessibilityAttributeValue: (NSString*)theAttributeName {
426     return ([theAttributeName isEqualToString: NSAccessibilitySubroleAttribute] ? NSAccessibilityCloseButtonAttribute : nil);
427 }
428
429 @end
430
431
432 @implementation VLCCustomWindowMinimizeButton
433 - (id)extendedAccessibilityAttributeValue: (NSString*)theAttributeName {
434     return ([theAttributeName isEqualToString: NSAccessibilitySubroleAttribute] ? NSAccessibilityMinimizeButtonAttribute : nil);
435 }
436
437 @end
438
439
440 @implementation VLCCustomWindowZoomButton
441 - (id)extendedAccessibilityAttributeValue: (NSString*)theAttributeName {
442     return ([theAttributeName isEqualToString: NSAccessibilitySubroleAttribute] ? NSAccessibilityZoomButtonAttribute : nil);
443 }
444
445 @end
446
447
448 @implementation VLCCustomWindowFullscreenButton
449 #ifdef MAC_OS_X_VERSION_10_7
450 - (id)extendedAccessibilityAttributeValue: (NSString*)theAttributeName {
451     return ([theAttributeName isEqualToString: NSAccessibilitySubroleAttribute] ? NSAccessibilityFullScreenButtonAttribute : nil);
452 }
453 #endif
454
455 @end