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