]> git.sesse.net Git - vlc/blob - modules/gui/macosx/misc.m
macosx: add scroll wheel support for progress and volume bar
[vlc] / modules / gui / macosx / misc.m
1 /*****************************************************************************
2  * misc.m: code not specific to vlc
3  *****************************************************************************
4  * Copyright (C) 2003-2012 VLC authors and VideoLAN
5  * $Id$
6  *
7  * Authors: Jon Lech Johansen <jon-vl@nanocrew.net>
8  *          Felix Paul Kühne <fkuehne at videolan dot 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 "misc.h"
26 #import "intf.h"                                          /* VLCApplication */
27 #import "MainWindow.h"
28 #import "controls.h"
29 #import "CoreInteraction.h"
30 #import <CoreAudio/CoreAudio.h>
31 #import <vlc_keys.h>
32
33
34 /*****************************************************************************
35  * NSSound (VLCAdditions)
36  *
37  * added code to change the system volume, needed for the apple remote code
38  * this is simplified code, which won't let you set the exact volume
39  * (that's what the audio output is for after all), but just the system volume
40  * in steps of 1/16 (matching the default AR or volume key implementation).
41  *****************************************************************************/
42
43 @implementation NSSound (VLCAdditions)
44
45 + (float)systemVolumeForChannel:(int)channel
46 {
47     AudioDeviceID i_device;
48     float f_volume;
49     OSStatus err;
50     UInt32 i_size;
51
52     i_size = sizeof( i_device );
53     AudioObjectPropertyAddress deviceAddress = { kAudioHardwarePropertyDefaultOutputDevice, kAudioDevicePropertyScopeOutput, kAudioObjectPropertyElementMaster };
54     err = AudioObjectGetPropertyData( kAudioObjectSystemObject, &deviceAddress, 0, NULL, &i_size, &i_device );
55     if (err != noErr) {
56         msg_Warn( VLCIntf, "couldn't get main audio output device" );
57         return .0;
58     }
59
60     AudioObjectPropertyAddress propertyAddress = { kAudioDevicePropertyVolumeScalar, kAudioDevicePropertyScopeOutput, channel };
61     i_size = sizeof( f_volume );
62     err = AudioObjectGetPropertyData(i_device, &propertyAddress, 0, NULL, &i_size, &f_volume);
63     if (err != noErr) {
64         msg_Warn( VLCIntf, "couldn't get volume value" );
65         return .0;
66     }
67
68     return f_volume;
69 }
70
71 + (bool)setSystemVolume:(float)f_volume forChannel:(int)i_channel
72 {
73     /* the following code will fail on S/PDIF devices. there is an easy work-around, but we'd like to match the OS behavior */
74
75     AudioDeviceID i_device;
76     OSStatus err;
77     UInt32 i_size;
78     Boolean b_writeable;
79
80     i_size = sizeof( i_device );
81     AudioObjectPropertyAddress deviceAddress = { kAudioHardwarePropertyDefaultOutputDevice, kAudioDevicePropertyScopeOutput, kAudioObjectPropertyElementMaster };
82     err = AudioObjectGetPropertyData( kAudioObjectSystemObject, &deviceAddress, 0, NULL, &i_size, &i_device );
83     if (err != noErr) {
84         msg_Warn( VLCIntf, "couldn't get main audio output device" );
85         return NO;
86     }
87
88     AudioObjectPropertyAddress propertyAddress = { kAudioDevicePropertyVolumeScalar, kAudioDevicePropertyScopeOutput, i_channel };
89     i_size = sizeof( f_volume );
90     err = AudioObjectIsPropertySettable( i_device, &propertyAddress, &b_writeable );
91     if (err != noErr || !b_writeable ) {
92         msg_Warn( VLCIntf, "we can't set the main audio devices' volume" );
93         return NO;
94     }
95     err = AudioObjectSetPropertyData(i_device, &propertyAddress, 0, NULL, i_size, &f_volume);
96
97     return YES;
98 }
99
100 + (void)increaseSystemVolume
101 {
102     float f_volume = [NSSound systemVolumeForChannel:1]; // we trust that mono is always available and that all channels got the same volume
103     f_volume += .0625; // 1/16 to match the OS
104     bool b_returned = YES;
105
106     /* since core audio doesn't provide a reasonable way to see how many channels we got, let's see how long we can do this */
107     for (NSUInteger x = 1; b_returned ; x++)
108         b_returned = [NSSound setSystemVolume: f_volume forChannel:x];
109 }
110
111 + (void)decreaseSystemVolume
112 {
113     float f_volume = [NSSound systemVolumeForChannel:1]; // we trust that mono is always available and that all channels got the same volume
114     f_volume -= .0625; // 1/16 to match the OS
115     bool b_returned = YES;
116
117     /* since core audio doesn't provide a reasonable way to see how many channels we got, let's see how long we can do this */
118     for (NSUInteger x = 1; b_returned ; x++)
119         b_returned = [NSSound setSystemVolume: f_volume forChannel:x];
120 }
121
122 @end
123
124 /*****************************************************************************
125  * NSAnimation (VLCAdditions)
126  *
127  *  Missing extension to NSAnimation
128  *****************************************************************************/
129
130 @implementation NSAnimation (VLCAdditions)
131 /* fake class attributes  */
132 static NSMapTable *VLCAdditions_userInfo = NULL;
133
134 + (void)load
135 {
136     /* init our fake object attribute */
137     VLCAdditions_userInfo = NSCreateMapTable(NSNonRetainedObjectMapKeyCallBacks, NSObjectMapValueCallBacks, 16);
138 }
139
140 - (void)dealloc
141 {
142     NSMapRemove(VLCAdditions_userInfo, self);
143     [super dealloc];
144 }
145
146 - (void)setUserInfo: (void *)userInfo
147 {
148     NSMapInsert(VLCAdditions_userInfo, self, (void*)userInfo);
149 }
150
151 - (void *)userInfo
152 {
153     return NSMapGet(VLCAdditions_userInfo, self);
154 }
155 @end
156
157 /*****************************************************************************
158  * NSScreen (VLCAdditions)
159  *
160  *  Missing extension to NSScreen
161  *****************************************************************************/
162
163 @implementation NSScreen (VLCAdditions)
164
165 static NSMutableArray *blackoutWindows = NULL;
166
167 + (void)load
168 {
169     /* init our fake object attribute */
170     blackoutWindows = [[NSMutableArray alloc] initWithCapacity:1];
171 }
172
173 + (NSScreen *)screenWithDisplayID: (CGDirectDisplayID)displayID
174 {
175     NSUInteger count = [[NSScreen screens] count];
176
177     for ( NSUInteger i = 0; i < count; i++ ) {
178         NSScreen *screen = [[NSScreen screens] objectAtIndex: i];
179         if ([screen displayID] == displayID)
180             return screen;
181     }
182     return nil;
183 }
184
185 - (BOOL)mainScreen
186 {
187     return ([self displayID] == [[[NSScreen screens] objectAtIndex:0] displayID]);
188 }
189
190 - (BOOL)isScreen: (NSScreen*)screen
191 {
192     return ([self displayID] == [screen displayID]);
193 }
194
195 - (CGDirectDisplayID)displayID
196 {
197     return (CGDirectDisplayID)[[[self deviceDescription] objectForKey: @"NSScreenNumber"] intValue];
198 }
199
200 - (void)blackoutOtherScreens
201 {
202     /* Free our previous blackout window (follow blackoutWindow alloc strategy) */
203     [blackoutWindows makeObjectsPerformSelector:@selector(close)];
204     [blackoutWindows removeAllObjects];
205
206     NSUInteger screenCount = [[NSScreen screens] count];
207     for (NSUInteger i = 0; i < screenCount; i++) {
208         NSScreen *screen = [[NSScreen screens] objectAtIndex: i];
209         VLCWindow *blackoutWindow;
210         NSRect screen_rect;
211
212         if ([self isScreen: screen])
213             continue;
214
215         screen_rect = [screen frame];
216         screen_rect.origin.x = screen_rect.origin.y = 0;
217
218         /* blackoutWindow alloc strategy
219             - The NSMutableArray blackoutWindows has the blackoutWindow references
220             - blackoutOtherDisplays is responsible for alloc/releasing its Windows
221         */
222         blackoutWindow = [[VLCWindow alloc] initWithContentRect: screen_rect styleMask: NSBorderlessWindowMask
223                 backing: NSBackingStoreBuffered defer: NO screen: screen];
224         [blackoutWindow setBackgroundColor:[NSColor blackColor]];
225         [blackoutWindow setLevel: NSFloatingWindowLevel]; /* Disappear when Expose is triggered */
226
227         [blackoutWindow displayIfNeeded];
228         [blackoutWindow orderFront: self animate: YES];
229
230         [blackoutWindows addObject: blackoutWindow];
231         [blackoutWindow release];
232
233         if ( [screen mainScreen] )
234             [NSApp setPresentationOptions:(NSApplicationPresentationAutoHideDock | NSApplicationPresentationAutoHideMenuBar)];
235     }
236 }
237
238 + (void)unblackoutScreens
239 {
240     NSUInteger blackoutWindowCount = [blackoutWindows count];
241
242     for (NSUInteger i = 0; i < blackoutWindowCount; i++) {
243         VLCWindow *blackoutWindow = [blackoutWindows objectAtIndex: i];
244         [blackoutWindow closeAndAnimate: YES];
245     }
246
247     [NSApp setPresentationOptions:(NSApplicationPresentationDefault)];
248 }
249
250 @end
251
252 /*****************************************************************************
253  * VLCWindow
254  *
255  *  Missing extension to NSWindow
256  *****************************************************************************/
257
258 @implementation VLCWindow
259 - (id)initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)styleMask
260     backing:(NSBackingStoreType)backingType defer:(BOOL)flag
261 {
262     self = [super initWithContentRect:contentRect styleMask:styleMask backing:backingType defer:flag];
263     if ( self ) {
264         b_isset_canBecomeKeyWindow = NO;
265         /* we don't want this window to be restored on relaunch */
266         if (!OSX_SNOW_LEOPARD)
267             [self setRestorable:NO];
268     }
269     return self;
270 }
271
272 - (void)setCanBecomeKeyWindow: (BOOL)canBecomeKey
273 {
274     b_isset_canBecomeKeyWindow = YES;
275     b_canBecomeKeyWindow = canBecomeKey;
276 }
277
278 - (BOOL)canBecomeKeyWindow
279 {
280     if (b_isset_canBecomeKeyWindow)
281         return b_canBecomeKeyWindow;
282
283     return [super canBecomeKeyWindow];
284 }
285
286 - (void)setCanBecomeMainWindow: (BOOL)canBecomeMain
287 {
288     b_isset_canBecomeMainWindow = YES;
289     b_canBecomeMainWindow = canBecomeMain;
290 }
291
292 - (BOOL)canBecomeMainWindow
293 {
294     if (b_isset_canBecomeMainWindow)
295         return b_canBecomeMainWindow;
296
297     return [super canBecomeMainWindow];
298 }
299
300 - (void)closeAndAnimate: (BOOL)animate
301 {
302     NSInvocation *invoc;
303
304     if (!animate) {
305         [super close];
306         return;
307     }
308
309     invoc = [NSInvocation invocationWithMethodSignature:[super methodSignatureForSelector:@selector(close)]];
310     [invoc setTarget: self];
311
312     if (![self isVisible] || [self alphaValue] == 0.0) {
313         [super close];
314         return;
315     }
316
317     [self orderOut: self animate: YES callback: invoc];
318 }
319
320 - (void)orderOut: (id)sender animate: (BOOL)animate
321 {
322     NSInvocation *invoc = [NSInvocation invocationWithMethodSignature:[super methodSignatureForSelector:@selector(orderOut:)]];
323     [invoc setTarget: self];
324     [invoc setArgument: sender atIndex: 0];
325     [self orderOut: sender animate: animate callback: invoc];
326 }
327
328 - (void)orderOut: (id)sender animate: (BOOL)animate callback:(NSInvocation *)callback
329 {
330     NSViewAnimation *anim;
331     NSViewAnimation *current_anim;
332     NSMutableDictionary *dict;
333
334     if (!animate) {
335         [self orderOut: sender];
336         return;
337     }
338
339     dict = [[NSMutableDictionary alloc] initWithCapacity:2];
340
341     [dict setObject:self forKey:NSViewAnimationTargetKey];
342
343     [dict setObject:NSViewAnimationFadeOutEffect forKey:NSViewAnimationEffectKey];
344     anim = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObjects:dict, nil]];
345     [dict release];
346
347     [anim setAnimationBlockingMode:NSAnimationNonblocking];
348     [anim setDuration:0.9];
349     [anim setFrameRate:30];
350     [anim setUserInfo: callback];
351
352     @synchronized(self) {
353         current_anim = self->animation;
354
355         if ([[[current_anim viewAnimations] objectAtIndex:0] objectForKey: NSViewAnimationEffectKey] == NSViewAnimationFadeOutEffect && [current_anim isAnimating]) {
356             [anim release];
357         } else {
358             if (current_anim) {
359                 [current_anim stopAnimation];
360                 [anim setCurrentProgress:1.0-[current_anim currentProgress]];
361                 [current_anim release];
362             }
363             else
364                 [anim setCurrentProgress:1.0 - [self alphaValue]];
365             self->animation = anim;
366             [self setDelegate: self];
367             [anim startAnimation];
368         }
369     }
370 }
371
372 - (void)orderFront: (id)sender animate: (BOOL)animate
373 {
374     NSViewAnimation *anim;
375     NSViewAnimation *current_anim;
376     NSMutableDictionary *dict;
377
378     if (!animate) {
379         [super orderFront: sender];
380         [self setAlphaValue: 1.0];
381         return;
382     }
383
384     if (![self isVisible]) {
385         [self setAlphaValue: 0.0];
386         [super orderFront: sender];
387     }
388     else if ([self alphaValue] == 1.0) {
389         [super orderFront: self];
390         return;
391     }
392
393     dict = [[NSMutableDictionary alloc] initWithCapacity:2];
394
395     [dict setObject:self forKey:NSViewAnimationTargetKey];
396
397     [dict setObject:NSViewAnimationFadeInEffect forKey:NSViewAnimationEffectKey];
398     anim = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObjects:dict, nil]];
399     [dict release];
400
401     [anim setAnimationBlockingMode:NSAnimationNonblocking];
402     [anim setDuration:0.5];
403     [anim setFrameRate:30];
404
405     @synchronized(self) {
406         current_anim = self->animation;
407
408         if ([[[current_anim viewAnimations] objectAtIndex:0] objectForKey: NSViewAnimationEffectKey] == NSViewAnimationFadeInEffect && [current_anim isAnimating]) {
409             [anim release];
410         } else {
411             if (current_anim) {
412                 [current_anim stopAnimation];
413                 [anim setCurrentProgress:1.0 - [current_anim currentProgress]];
414                 [current_anim release];
415             }
416             else
417                 [anim setCurrentProgress:[self alphaValue]];
418             self->animation = anim;
419             [self setDelegate: self];
420             [self orderFront: sender];
421             [anim startAnimation];
422         }
423     }
424 }
425
426 - (void)animationDidEnd:(NSAnimation*)anim
427 {
428     if ([self alphaValue] <= 0.0) {
429         NSInvocation * invoc;
430         [super orderOut: nil];
431         [self setAlphaValue: 1.0];
432         if ((invoc = [anim userInfo]))
433             [invoc invoke];
434     }
435 }
436
437 - (IBAction)fullscreen:(id)sender
438 {
439     [[VLCCoreInteraction sharedInstance] toggleFullscreen];
440 }
441
442 @end
443
444 /*****************************************************************************
445  * VLBrushedMetalImageView
446  *****************************************************************************/
447
448 @implementation VLBrushedMetalImageView
449
450 - (BOOL)mouseDownCanMoveWindow
451 {
452     return YES;
453 }
454
455 - (void)dealloc
456 {
457     [self unregisterDraggedTypes];
458     [super dealloc];
459 }
460
461 - (void)awakeFromNib
462 {
463     [self registerForDraggedTypes:[NSArray arrayWithObject: NSFilenamesPboardType]];
464     [self setImageScaling: NSScaleToFit];
465     [self setImageFrameStyle: NSImageFrameNone];
466     [self setImageAlignment: NSImageAlignCenter];
467 }
468
469 - (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
470 {
471     if ((NSDragOperationGeneric & [sender draggingSourceOperationMask]) == NSDragOperationGeneric)
472         return NSDragOperationGeneric;
473
474     return NSDragOperationNone;
475 }
476
477 - (BOOL)prepareForDragOperation:(id <NSDraggingInfo>)sender
478 {
479     return YES;
480 }
481
482 - (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
483 {
484     BOOL b_returned;
485     b_returned = [[VLCCoreInteraction sharedInstance] performDragOperation: sender];
486
487     [self setNeedsDisplay:YES];
488     return b_returned;
489 }
490
491 - (void)concludeDragOperation:(id <NSDraggingInfo>)sender
492 {
493     [self setNeedsDisplay:YES];
494 }
495
496 @end
497
498
499 /*****************************************************************************
500  * MPSlider
501  *****************************************************************************/
502 @implementation MPSlider
503
504 void _drawKnobInRect(NSRect knobRect)
505 {
506     // Center knob in given rect
507     knobRect.origin.x += (int)((float)(knobRect.size.width - 7)/2.0);
508     knobRect.origin.y += (int)((float)(knobRect.size.height - 7)/2.0);
509
510     // Draw diamond
511     NSRectFillUsingOperation(NSMakeRect(knobRect.origin.x + 3, knobRect.origin.y + 6, 1, 1), NSCompositeSourceOver);
512     NSRectFillUsingOperation(NSMakeRect(knobRect.origin.x + 2, knobRect.origin.y + 5, 3, 1), NSCompositeSourceOver);
513     NSRectFillUsingOperation(NSMakeRect(knobRect.origin.x + 1, knobRect.origin.y + 4, 5, 1), NSCompositeSourceOver);
514     NSRectFillUsingOperation(NSMakeRect(knobRect.origin.x + 0, knobRect.origin.y + 3, 7, 1), NSCompositeSourceOver);
515     NSRectFillUsingOperation(NSMakeRect(knobRect.origin.x + 1, knobRect.origin.y + 2, 5, 1), NSCompositeSourceOver);
516     NSRectFillUsingOperation(NSMakeRect(knobRect.origin.x + 2, knobRect.origin.y + 1, 3, 1), NSCompositeSourceOver);
517     NSRectFillUsingOperation(NSMakeRect(knobRect.origin.x + 3, knobRect.origin.y + 0, 1, 1), NSCompositeSourceOver);
518 }
519
520 void _drawFrameInRect(NSRect frameRect)
521 {
522     // Draw frame
523     NSRectFillUsingOperation(NSMakeRect(frameRect.origin.x, frameRect.origin.y, frameRect.size.width, 1), NSCompositeSourceOver);
524     NSRectFillUsingOperation(NSMakeRect(frameRect.origin.x, frameRect.origin.y + frameRect.size.height-1, frameRect.size.width, 1), NSCompositeSourceOver);
525     NSRectFillUsingOperation(NSMakeRect(frameRect.origin.x, frameRect.origin.y, 1, frameRect.size.height), NSCompositeSourceOver);
526     NSRectFillUsingOperation(NSMakeRect(frameRect.origin.x+frameRect.size.width-1, frameRect.origin.y, 1, frameRect.size.height), NSCompositeSourceOver);
527 }
528
529 - (void)drawRect:(NSRect)rect
530 {
531     // Draw default to make sure the slider behaves correctly
532     [[NSGraphicsContext currentContext] saveGraphicsState];
533     NSRectClip(NSZeroRect);
534     [super drawRect:rect];
535     [[NSGraphicsContext currentContext] restoreGraphicsState];
536
537     // Full size
538     rect = [self bounds];
539     int diff = (int)(([[self cell] knobThickness] - 7.0)/2.0) - 1;
540     rect.origin.x += diff-1;
541     rect.origin.y += diff;
542     rect.size.width -= 2*diff-2;
543     rect.size.height -= 2*diff;
544
545     // Draw dark
546     NSRect knobRect = [[self cell] knobRectFlipped:NO];
547     [[[NSColor blackColor] colorWithAlphaComponent:0.6] set];
548     _drawFrameInRect(rect);
549     _drawKnobInRect(knobRect);
550
551     // Draw shadow
552     [[[NSColor blackColor] colorWithAlphaComponent:0.1] set];
553     rect.origin.x++;
554     rect.origin.y++;
555     knobRect.origin.x++;
556     knobRect.origin.y++;
557     _drawFrameInRect(rect);
558     _drawKnobInRect(knobRect);
559 }
560
561 @end
562
563 /*****************************************************************************
564  * ProgressView
565  *****************************************************************************/
566
567 @implementation VLCProgressView : NSView
568
569 - (void)scrollWheel:(NSEvent *)o_event
570 {
571     intf_thread_t * p_intf = VLCIntf;
572     CGFloat f_deltaY = [o_event deltaY];
573     CGFloat f_deltaX = [o_event deltaX];
574
575     if (!OSX_SNOW_LEOPARD && [o_event isDirectionInvertedFromDevice])
576         f_deltaX = -f_deltaX; // optimisation, actually double invertion of f_deltaY here
577     else
578         f_deltaY = -f_deltaY;
579
580     // positive for left / down, negative otherwise
581     CGFloat f_delta = f_deltaX + f_deltaY;
582     CGFloat f_abs;
583     int i_vlckey;
584
585     if (f_delta > 0.0f) {
586         i_vlckey = ACTIONID_JUMP_BACKWARD_EXTRASHORT;
587         f_abs = f_delta;
588     }
589     else {
590         i_vlckey = ACTIONID_JUMP_FORWARD_EXTRASHORT;
591         f_abs = -f_delta;
592     }
593
594     for (NSUInteger i = 0; i < (int)(f_abs/4.+1.) && f_abs > 0.05 ; i++)
595         var_SetInteger( p_intf->p_libvlc, "key-action", i_vlckey );
596 }
597
598 - (BOOL)acceptsFirstResponder
599 {
600     return YES;
601 }
602
603 @end
604
605 /*****************************************************************************
606  * TimeLineSlider
607  *****************************************************************************/
608
609 @implementation TimeLineSlider
610
611 - (void)awakeFromNib
612 {
613     if (config_GetInt( VLCIntf, "macosx-interfacestyle" )) {
614         o_knob_img = [NSImage imageNamed:@"progression-knob_dark"];
615         b_dark = YES;
616     } else {
617         o_knob_img = [NSImage imageNamed:@"progression-knob"];
618         b_dark = NO;
619     }
620     img_rect.size = [o_knob_img size];
621     img_rect.origin.x = img_rect.origin.y = 0;
622 }
623
624 - (void)dealloc
625 {
626     [o_knob_img release];
627     [super dealloc];
628 }
629
630 - (CGFloat)knobPosition
631 {
632     NSRect knobRect = [[self cell] knobRectFlipped:NO];
633     knobRect.origin.x += knobRect.size.width / 2;
634     return knobRect.origin.x;
635 }
636
637 - (void)drawKnobInRect:(NSRect)knobRect
638 {
639     knobRect.origin.x += (knobRect.size.width - img_rect.size.width) / 2;
640     knobRect.size.width = img_rect.size.width;
641     knobRect.size.height = img_rect.size.height;
642     [o_knob_img drawInRect:knobRect fromRect:img_rect operation:NSCompositeSourceOver fraction:1];
643 }
644
645 - (void)drawRect:(NSRect)rect
646 {
647     [[[VLCMain sharedInstance] mainWindow] drawFancyGradientEffectForTimeSlider];
648     msleep( 10000 ); //wait for the gradient to draw completely
649
650     /* Draw default to make sure the slider behaves correctly */
651     [[NSGraphicsContext currentContext] saveGraphicsState];
652     NSRectClip(NSZeroRect);
653     [super drawRect:rect];
654     [[NSGraphicsContext currentContext] restoreGraphicsState];
655
656     NSRect knobRect = [[self cell] knobRectFlipped:NO];
657     if (b_dark)
658         knobRect.origin.y+=2;
659     else
660         knobRect.origin.y+=1;
661     [self drawKnobInRect: knobRect];
662 }
663
664 @end
665
666 /*****************************************************************************
667  * ITSlider
668  *****************************************************************************/
669
670 @implementation ITSlider
671
672 - (void)awakeFromNib
673 {
674     BOOL b_dark = config_GetInt( VLCIntf, "macosx-interfacestyle" );
675     if (b_dark)
676         img = [NSImage imageNamed:@"volume-slider-knob_dark"];
677     else
678         img = [NSImage imageNamed:@"volume-slider-knob"];
679
680     image_rect.size = [img size];
681     image_rect.origin.x = 0;
682
683     if (b_dark)
684         image_rect.origin.y = -1;
685     else
686         image_rect.origin.y = 0;
687 }
688
689 - (void)drawKnobInRect:(NSRect)knobRect
690 {
691     knobRect.origin.x += (knobRect.size.width - image_rect.size.width) / 2;
692     knobRect.size.width = image_rect.size.width;
693     knobRect.size.height = image_rect.size.height;
694     [img drawInRect:knobRect fromRect:image_rect operation:NSCompositeSourceOver fraction:1];
695 }
696
697 - (void)drawRect:(NSRect)rect
698 {
699     /* Draw default to make sure the slider behaves correctly */
700     [[NSGraphicsContext currentContext] saveGraphicsState];
701     NSRectClip(NSZeroRect);
702     [super drawRect:rect];
703     [[NSGraphicsContext currentContext] restoreGraphicsState];
704
705     NSRect knobRect = [[self cell] knobRectFlipped:NO];
706     knobRect.origin.y+=2;
707     [self drawKnobInRect: knobRect];
708 }
709
710 - (void)scrollWheel:(NSEvent *)o_event
711 {
712     intf_thread_t * p_intf = VLCIntf;
713     CGFloat f_deltaY = [o_event deltaY];
714     CGFloat f_deltaX = [o_event deltaX];
715
716     if (!OSX_SNOW_LEOPARD && [o_event isDirectionInvertedFromDevice])
717         f_deltaX = -f_deltaX; // optimisation, actually double invertion of f_deltaY here
718     else
719         f_deltaY = -f_deltaY;
720
721     // positive for left / down, negative otherwise
722     CGFloat f_delta = f_deltaX + f_deltaY;
723     CGFloat f_abs;
724     int i_vlckey;
725
726     if (f_delta > 0.0f) {
727         i_vlckey = ACTIONID_VOL_DOWN;
728         f_abs = f_delta;
729     }
730     else {
731         i_vlckey = ACTIONID_VOL_UP;
732         f_abs = -f_delta;
733     }
734
735     for (NSUInteger i = 0; i < (int)(f_abs/4.+1.) && f_abs > 0.05 ; i++)
736         var_SetInteger( p_intf->p_libvlc, "key-action", i_vlckey );
737 }
738
739 @end
740
741 /*****************************************************************************
742  * VLCTimeField implementation
743  *****************************************************************************
744  * we need this to catch our click-event in the controller window
745  *****************************************************************************/
746
747 @implementation VLCTimeField
748 + (void)initialize{
749     NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
750     NSDictionary *appDefaults = [NSDictionary dictionaryWithObject:@"NO" forKey:@"DisplayTimeAsTimeRemaining"];
751
752     [defaults registerDefaults:appDefaults];
753 }
754
755 - (void)awakeFromNib
756 {
757     NSColor *o_string_color;
758     if (!config_GetInt( VLCIntf, "macosx-interfacestyle"))
759         o_string_color = [NSColor colorWithCalibratedRed:0.229 green:0.229 blue:0.229 alpha:100.0];
760     else
761         o_string_color = [NSColor colorWithCalibratedRed:0.64 green:0.64 blue:0.64 alpha:100.0];
762
763     textAlignment = NSCenterTextAlignment;
764     o_string_attributes_dict = [[NSDictionary dictionaryWithObjectsAndKeys: o_string_color, NSForegroundColorAttributeName, [NSFont titleBarFontOfSize:10.0], NSFontAttributeName, nil] retain];
765 }
766
767 - (void)setAlignment:(NSTextAlignment)alignment
768 {
769     textAlignment = alignment;
770     [self setStringValue:[self stringValue]];
771 }
772
773 - (void)dealloc
774 {
775     [o_string_shadow release];
776     [o_string_attributes_dict release];
777     [super dealloc];
778 }
779
780 - (void)setStringValue:(NSString *)string
781 {
782     if (!o_string_shadow) {
783         o_string_shadow = [[NSShadow alloc] init];
784         [o_string_shadow setShadowColor: [NSColor colorWithCalibratedWhite:1.0 alpha:0.5]];
785         [o_string_shadow setShadowOffset:NSMakeSize(0.0, -1.5)];
786         [o_string_shadow setShadowBlurRadius:0.0];
787     }
788
789     NSMutableAttributedString *o_attributed_string = [[NSMutableAttributedString alloc] initWithString:string attributes: o_string_attributes_dict];
790     NSUInteger i_stringLength = [string length];
791
792     [o_attributed_string addAttribute: NSShadowAttributeName value: o_string_shadow range: NSMakeRange(0, i_stringLength)];
793     [o_attributed_string setAlignment: textAlignment range: NSMakeRange(0, i_stringLength)];
794     [self setAttributedStringValue: o_attributed_string];
795     [o_attributed_string release];
796 }
797
798 - (void)mouseDown: (NSEvent *)ourEvent
799 {
800     if ( [ourEvent clickCount] > 1 )
801         [[[VLCMain sharedInstance] controls] goToSpecificTime: nil];
802     else
803     {
804         if ([[NSUserDefaults standardUserDefaults] boolForKey:@"DisplayTimeAsTimeRemaining"])
805             [[NSUserDefaults standardUserDefaults] setObject:@"NO" forKey:@"DisplayTimeAsTimeRemaining"];
806         else
807             [[NSUserDefaults standardUserDefaults] setObject:@"YES" forKey:@"DisplayTimeAsTimeRemaining"];
808     }
809 }
810
811 - (BOOL)timeRemaining
812 {
813     return [[NSUserDefaults standardUserDefaults] boolForKey:@"DisplayTimeAsTimeRemaining"];
814 }
815 @end
816
817 /*****************************************************************************
818  * VLCMainWindowSplitView implementation
819  * comment 1 + 2 taken from NSSplitView.h (10.7 SDK)
820  *****************************************************************************/
821 @implementation VLCMainWindowSplitView : NSSplitView
822 /* Return the color of the dividers that the split view is drawing between subviews. The default implementation of this method returns [NSColor clearColor] for the thick divider style. It will also return [NSColor clearColor] for the thin divider style when the split view is in a textured window. All other thin dividers are drawn with a color that looks good between two white panes. You can override this method to change the color of dividers.
823  */
824 - (NSColor *)dividerColor
825 {
826     return [NSColor colorWithCalibratedRed:.60 green:.60 blue:.60 alpha:1.];
827 }
828
829 /* Return the thickness of the dividers that the split view is drawing between subviews. The default implementation returns a value that depends on the divider style. You can override this method to change the size of dividers.
830  */
831 - (CGFloat)dividerThickness
832 {
833     return 1.0;
834 }
835 @end
836
837 /*****************************************************************************
838  * VLCThreePartImageView interface
839  *****************************************************************************/
840 @implementation VLCThreePartImageView
841
842 - (void)dealloc
843 {
844     [o_left_img release];
845     [o_middle_img release];
846     [o_right_img release];
847
848     [super dealloc];
849 }
850
851 - (void)setImagesLeft:(NSImage *)left middle: (NSImage *)middle right:(NSImage *)right
852 {
853     if (o_left_img)
854         [o_left_img release];
855     if (o_middle_img)
856         [o_middle_img release];
857     if (o_right_img)
858         [o_right_img release];
859
860     o_left_img = [left retain];
861     o_middle_img = [middle retain];
862     o_right_img = [right retain];
863 }
864
865 - (void)drawRect:(NSRect)rect
866 {
867     NSRect bnds = [self bounds];
868     NSDrawThreePartImage( bnds, o_left_img, o_middle_img, o_right_img, NO, NSCompositeSourceOver, 1, NO );
869 }
870
871 @end
872
873 @implementation VLCThreePartDropView
874
875 - (BOOL)mouseDownCanMoveWindow
876 {
877     return YES;
878 }
879
880 - (void)dealloc
881 {
882     [self unregisterDraggedTypes];
883     [super dealloc];
884 }
885
886 - (void)awakeFromNib
887 {
888     [self registerForDraggedTypes:[NSArray arrayWithObject: NSFilenamesPboardType]];
889 }
890
891 - (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
892 {
893     if ((NSDragOperationGeneric & [sender draggingSourceOperationMask]) == NSDragOperationGeneric)
894         return NSDragOperationGeneric;
895
896     return NSDragOperationNone;
897 }
898
899 - (BOOL)prepareForDragOperation:(id <NSDraggingInfo>)sender
900 {
901     return YES;
902 }
903
904 - (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
905 {
906     BOOL b_returned;
907     b_returned = [[VLCCoreInteraction sharedInstance] performDragOperation: sender];
908
909     [self setNeedsDisplay:YES];
910     return YES;
911 }
912
913 - (void)concludeDragOperation:(id <NSDraggingInfo>)sender
914 {
915     [self setNeedsDisplay:YES];
916 }
917
918 @end