]> git.sesse.net Git - vlc/blob - modules/gui/macosx/misc.m
678c91746ad5c880bfa60d58bd686fa0b388ee99
[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 + (void)load
169 {
170     /* init our fake object attribute */
171     blackoutWindows = [[NSMutableArray alloc] initWithCapacity:1];
172 }
173
174 + (NSScreen *)screenWithDisplayID: (CGDirectDisplayID)displayID
175 {
176     NSUInteger count = [[NSScreen screens] count];
177
178     for ( NSUInteger i = 0; i < count; i++ ) {
179         NSScreen *screen = [[NSScreen screens] objectAtIndex:i];
180         if ([screen displayID] == displayID)
181             return screen;
182     }
183     return nil;
184 }
185
186 - (BOOL)hasMenuBar
187 {
188     return ([self displayID] == [[[NSScreen screens] objectAtIndex:0] displayID]);
189 }
190
191 - (BOOL)hasDock
192 {
193     NSRect screen_frame = [self frame];
194     NSRect screen_visible_frame = [self visibleFrame];
195     CGFloat f_menu_bar_thickness = [self hasMenuBar] ? [[NSStatusBar systemStatusBar] thickness] : 0.0;
196
197     BOOL b_found_dock = NO;
198     if (screen_visible_frame.size.width < screen_frame.size.width)
199         b_found_dock = YES;
200     else if (screen_visible_frame.size.height + f_menu_bar_thickness < screen_frame.size.height)
201         b_found_dock = YES;
202
203     return b_found_dock;
204 }
205
206 - (BOOL)isScreen: (NSScreen*)screen
207 {
208     return ([self displayID] == [screen displayID]);
209 }
210
211 - (CGDirectDisplayID)displayID
212 {
213     return (CGDirectDisplayID)[[[self deviceDescription] objectForKey: @"NSScreenNumber"] intValue];
214 }
215
216 - (void)blackoutOtherScreens
217 {
218     /* Free our previous blackout window (follow blackoutWindow alloc strategy) */
219     [blackoutWindows makeObjectsPerformSelector:@selector(close)];
220     [blackoutWindows removeAllObjects];
221
222     NSUInteger screenCount = [[NSScreen screens] count];
223     for (NSUInteger i = 0; i < screenCount; i++) {
224         NSScreen *screen = [[NSScreen screens] objectAtIndex:i];
225         VLCWindow *blackoutWindow;
226         NSRect screen_rect;
227
228         if ([self isScreen: screen])
229             continue;
230
231         screen_rect = [screen frame];
232         screen_rect.origin.x = screen_rect.origin.y = 0;
233
234         /* blackoutWindow alloc strategy
235             - The NSMutableArray blackoutWindows has the blackoutWindow references
236             - blackoutOtherDisplays is responsible for alloc/releasing its Windows
237         */
238         blackoutWindow = [[VLCWindow alloc] initWithContentRect: screen_rect styleMask: NSBorderlessWindowMask
239                 backing: NSBackingStoreBuffered defer: NO screen: screen];
240         [blackoutWindow setBackgroundColor:[NSColor blackColor]];
241         [blackoutWindow setLevel: NSFloatingWindowLevel]; /* Disappear when Expose is triggered */
242
243         [blackoutWindow displayIfNeeded];
244         [blackoutWindow orderFront: self animate: YES];
245
246         [blackoutWindows addObject: blackoutWindow];
247         [blackoutWindow release];
248
249         [screen setFullscreenPresentationOptions];
250     }
251 }
252
253 + (void)unblackoutScreens
254 {
255     NSUInteger blackoutWindowCount = [blackoutWindows count];
256
257     for (NSUInteger i = 0; i < blackoutWindowCount; i++) {
258         VLCWindow *blackoutWindow = [blackoutWindows objectAtIndex:i];
259         [[blackoutWindow screen] setNonFullscreenPresentationOptions];
260         [blackoutWindow closeAndAnimate: YES];
261     }
262 }
263
264 - (void)setFullscreenPresentationOptions
265 {
266     NSApplicationPresentationOptions presentationOpts = [NSApp presentationOptions];
267     if ([self hasMenuBar])
268         presentationOpts |= NSApplicationPresentationAutoHideMenuBar;
269     if ([self hasMenuBar] || [self hasDock])
270         presentationOpts |= NSApplicationPresentationAutoHideDock;
271     [NSApp setPresentationOptions:presentationOpts];
272 }
273
274 - (void)setNonFullscreenPresentationOptions
275 {
276     NSApplicationPresentationOptions presentationOpts = [NSApp presentationOptions];
277     if ([self hasMenuBar])
278         presentationOpts &= (~NSApplicationPresentationAutoHideMenuBar);
279     if ([self hasMenuBar] || [self hasDock])
280         presentationOpts &= (~NSApplicationPresentationAutoHideDock);
281     [NSApp setPresentationOptions:presentationOpts];
282 }
283
284 @end
285
286 /*****************************************************************************
287  * VLBrushedMetalImageView
288  *****************************************************************************/
289
290 @implementation VLBrushedMetalImageView
291
292 - (BOOL)mouseDownCanMoveWindow
293 {
294     return YES;
295 }
296
297 - (void)dealloc
298 {
299     [self unregisterDraggedTypes];
300     [super dealloc];
301 }
302
303 - (void)awakeFromNib
304 {
305     [self registerForDraggedTypes:@[NSFilenamesPboardType]];
306     [self setImageScaling: NSScaleToFit];
307     [self setImageFrameStyle: NSImageFrameNone];
308     [self setImageAlignment: NSImageAlignCenter];
309 }
310
311 - (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
312 {
313     if ((NSDragOperationGeneric & [sender draggingSourceOperationMask]) == NSDragOperationGeneric)
314         return NSDragOperationGeneric;
315
316     return NSDragOperationNone;
317 }
318
319 - (BOOL)prepareForDragOperation:(id <NSDraggingInfo>)sender
320 {
321     return YES;
322 }
323
324 - (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
325 {
326     BOOL b_returned;
327     b_returned = [[VLCCoreInteraction sharedInstance] performDragOperation: sender];
328
329     [self setNeedsDisplay:YES];
330     return b_returned;
331 }
332
333 - (void)concludeDragOperation:(id <NSDraggingInfo>)sender
334 {
335     [self setNeedsDisplay:YES];
336 }
337
338 @end
339
340
341 /*****************************************************************************
342  * MPSlider
343  *****************************************************************************/
344 @implementation MPSlider
345
346 void _drawKnobInRect(NSRect knobRect)
347 {
348     // Center knob in given rect
349     knobRect.origin.x += (int)((float)(knobRect.size.width - 7)/2.0);
350     knobRect.origin.y += (int)((float)(knobRect.size.height - 7)/2.0);
351
352     // Draw diamond
353     NSRectFillUsingOperation(NSMakeRect(knobRect.origin.x + 3, knobRect.origin.y + 6, 1, 1), NSCompositeSourceOver);
354     NSRectFillUsingOperation(NSMakeRect(knobRect.origin.x + 2, knobRect.origin.y + 5, 3, 1), NSCompositeSourceOver);
355     NSRectFillUsingOperation(NSMakeRect(knobRect.origin.x + 1, knobRect.origin.y + 4, 5, 1), NSCompositeSourceOver);
356     NSRectFillUsingOperation(NSMakeRect(knobRect.origin.x + 0, knobRect.origin.y + 3, 7, 1), NSCompositeSourceOver);
357     NSRectFillUsingOperation(NSMakeRect(knobRect.origin.x + 1, knobRect.origin.y + 2, 5, 1), NSCompositeSourceOver);
358     NSRectFillUsingOperation(NSMakeRect(knobRect.origin.x + 2, knobRect.origin.y + 1, 3, 1), NSCompositeSourceOver);
359     NSRectFillUsingOperation(NSMakeRect(knobRect.origin.x + 3, knobRect.origin.y + 0, 1, 1), NSCompositeSourceOver);
360 }
361
362 void _drawFrameInRect(NSRect frameRect)
363 {
364     // Draw frame
365     NSRectFillUsingOperation(NSMakeRect(frameRect.origin.x, frameRect.origin.y, frameRect.size.width, 1), NSCompositeSourceOver);
366     NSRectFillUsingOperation(NSMakeRect(frameRect.origin.x, frameRect.origin.y + frameRect.size.height-1, frameRect.size.width, 1), NSCompositeSourceOver);
367     NSRectFillUsingOperation(NSMakeRect(frameRect.origin.x, frameRect.origin.y, 1, frameRect.size.height), NSCompositeSourceOver);
368     NSRectFillUsingOperation(NSMakeRect(frameRect.origin.x+frameRect.size.width-1, frameRect.origin.y, 1, frameRect.size.height), NSCompositeSourceOver);
369 }
370
371 - (void)drawRect:(NSRect)rect
372 {
373     // Draw default to make sure the slider behaves correctly
374     [[NSGraphicsContext currentContext] saveGraphicsState];
375     NSRectClip(NSZeroRect);
376     [super drawRect:rect];
377     [[NSGraphicsContext currentContext] restoreGraphicsState];
378
379     // Full size
380     rect = [self bounds];
381     int diff = (int)(([[self cell] knobThickness] - 7.0)/2.0) - 1;
382     rect.origin.x += diff-1;
383     rect.origin.y += diff;
384     rect.size.width -= 2*diff-2;
385     rect.size.height -= 2*diff;
386
387     // Draw dark
388     NSRect knobRect = [[self cell] knobRectFlipped:NO];
389     [[[NSColor blackColor] colorWithAlphaComponent:0.6] set];
390     _drawFrameInRect(rect);
391     _drawKnobInRect(knobRect);
392
393     // Draw shadow
394     [[[NSColor blackColor] colorWithAlphaComponent:0.1] set];
395     rect.origin.x++;
396     rect.origin.y++;
397     knobRect.origin.x++;
398     knobRect.origin.y++;
399     _drawFrameInRect(rect);
400     _drawKnobInRect(knobRect);
401 }
402
403 @end
404
405 /*****************************************************************************
406  * ProgressView
407  *****************************************************************************/
408
409 @implementation VLCProgressView : NSView
410
411 - (void)scrollWheel:(NSEvent *)o_event
412 {
413     intf_thread_t * p_intf = VLCIntf;
414     BOOL b_forward = NO;
415     CGFloat f_deltaY = [o_event deltaY];
416     CGFloat f_deltaX = [o_event deltaX];
417
418     if (!OSX_SNOW_LEOPARD && [o_event isDirectionInvertedFromDevice])
419         f_deltaX = -f_deltaX; // optimisation, actually double invertion of f_deltaY here
420     else
421         f_deltaY = -f_deltaY;
422
423     // positive for left / down, negative otherwise
424     CGFloat f_delta = f_deltaX + f_deltaY;
425     CGFloat f_abs;
426     int i_vlckey;
427
428     if (f_delta > 0.0f)
429         f_abs = f_delta;
430     else {
431         b_forward = YES;
432         f_abs = -f_delta;
433     }
434
435     for (NSUInteger i = 0; i < (int)(f_abs/4.+1.) && f_abs > 0.05 ; i++) {
436         if (b_forward)
437             [[VLCCoreInteraction sharedInstance] forwardExtraShort];
438         else
439             [[VLCCoreInteraction sharedInstance] backwardExtraShort];
440     }
441 }
442
443 - (BOOL)acceptsFirstResponder
444 {
445     return YES;
446 }
447
448 @end
449
450 /*****************************************************************************
451  * TimeLineSlider
452  *****************************************************************************/
453
454 @implementation TimeLineSlider
455
456 - (void)awakeFromNib
457 {
458     if (config_GetInt( VLCIntf, "macosx-interfacestyle" )) {
459         o_knob_img = [NSImage imageNamed:@"progression-knob_dark"];
460         b_dark = YES;
461     } else {
462         o_knob_img = [NSImage imageNamed:@"progression-knob"];
463         b_dark = NO;
464     }
465     img_rect.size = [o_knob_img size];
466     img_rect.origin.x = img_rect.origin.y = 0;
467 }
468
469 - (void)dealloc
470 {
471     [o_knob_img release];
472     [super dealloc];
473 }
474
475 - (CGFloat)knobPosition
476 {
477     NSRect knobRect = [[self cell] knobRectFlipped:NO];
478     knobRect.origin.x += knobRect.size.width / 2;
479     return knobRect.origin.x;
480 }
481
482 - (void)drawKnobInRect:(NSRect)knobRect
483 {
484     knobRect.origin.x += (knobRect.size.width - img_rect.size.width) / 2;
485     knobRect.size.width = img_rect.size.width;
486     knobRect.size.height = img_rect.size.height;
487     [o_knob_img drawInRect:knobRect fromRect:img_rect operation:NSCompositeSourceOver fraction:1];
488 }
489
490 - (void)drawRect:(NSRect)rect
491 {
492     [[(VLCVideoWindowCommon *)[self window] controlsBar] drawFancyGradientEffectForTimeSlider];
493     msleep(10000); //wait for the gradient to draw completely
494
495     /* Draw default to make sure the slider behaves correctly */
496     [[NSGraphicsContext currentContext] saveGraphicsState];
497     NSRectClip(NSZeroRect);
498     [super drawRect:rect];
499     [[NSGraphicsContext currentContext] restoreGraphicsState];
500
501     NSRect knobRect = [[self cell] knobRectFlipped:NO];
502     knobRect.origin.y+=1;
503     [self drawKnobInRect: knobRect];
504 }
505
506 @end
507
508 /*****************************************************************************
509  * VLCVolumeSliderCommon
510  *****************************************************************************/
511
512 @implementation VLCVolumeSliderCommon : NSSlider
513
514 @synthesize usesBrightArtwork = _usesBrightArtwork;
515
516 - (void)scrollWheel:(NSEvent *)o_event
517 {
518     intf_thread_t * p_intf = VLCIntf;
519     BOOL b_up = NO;
520     CGFloat f_deltaY = [o_event deltaY];
521     CGFloat f_deltaX = [o_event deltaX];
522
523     if (!OSX_SNOW_LEOPARD && [o_event isDirectionInvertedFromDevice])
524         f_deltaX = -f_deltaX; // optimisation, actually double invertion of f_deltaY here
525     else
526         f_deltaY = -f_deltaY;
527
528     // positive for left / down, negative otherwise
529     CGFloat f_delta = f_deltaX + f_deltaY;
530     CGFloat f_abs;
531     int i_vlckey;
532
533     if (f_delta > 0.0f)
534         f_abs = f_delta;
535     else {
536         b_up = YES;
537         f_abs = -f_delta;
538     }
539
540     for (NSUInteger i = 0; i < (int)(f_abs/4.+1.) && f_abs > 0.05 ; i++) {
541         if (b_up)
542             [[VLCCoreInteraction sharedInstance] volumeUp];
543         else
544             [[VLCCoreInteraction sharedInstance] volumeDown];
545     }
546 }
547
548 - (void)drawFullVolumeMarker
549 {
550     CGFloat maxAudioVol = self.maxValue / AOUT_VOLUME_DEFAULT;
551     if (maxAudioVol < 1.)
552         return;
553
554     NSColor *drawingColor;
555     // for bright artwork, a black color is used and vice versa
556     if (_usesBrightArtwork)
557         drawingColor = [[NSColor blackColor] colorWithAlphaComponent:.4];
558     else
559         drawingColor = [[NSColor whiteColor] colorWithAlphaComponent:.4];
560
561     NSBezierPath* bezierPath = [NSBezierPath bezierPath];
562     [self drawFullVolBezierPath:bezierPath];
563     [bezierPath closePath];
564
565     bezierPath.lineWidth = 1.;
566     [drawingColor setStroke];
567     [bezierPath stroke];
568 }
569
570 - (CGFloat)fullVolumePos
571 {
572     CGFloat maxAudioVol = self.maxValue / AOUT_VOLUME_DEFAULT;
573     CGFloat sliderRange = [self frame].size.width - [self knobThickness];
574     CGFloat sliderOrigin = [self knobThickness] / 2.;
575
576     return 1. / maxAudioVol * sliderRange + sliderOrigin;
577 }
578
579 - (void)drawFullVolBezierPath:(NSBezierPath*)bezierPath
580 {
581     CGFloat fullVolPos = [self fullVolumePos];
582     [bezierPath moveToPoint:NSMakePoint(fullVolPos, [self frame].size.height - 3.)];
583     [bezierPath lineToPoint:NSMakePoint(fullVolPos, 2.)];
584 }
585
586 @end
587
588 @implementation VolumeSliderCell
589
590 - (BOOL)continueTracking:(NSPoint)lastPoint at:(NSPoint)currentPoint inView:(NSView *)controlView
591 {
592     VLCVolumeSliderCommon *o_slider = (VLCVolumeSliderCommon *)controlView;
593     CGFloat fullVolumePos = [o_slider fullVolumePos] + 2.;
594
595     CGPoint snapToPoint = currentPoint;
596     if (ABS(fullVolumePos - currentPoint.x) <= 4.)
597         snapToPoint.x = fullVolumePos;
598
599     return [super continueTracking:lastPoint at:snapToPoint inView:controlView];
600 }
601
602 @end
603
604 /*****************************************************************************
605  * ITSlider
606  *****************************************************************************/
607
608 @implementation ITSlider
609
610 - (void)awakeFromNib
611 {
612     BOOL b_dark = config_GetInt( VLCIntf, "macosx-interfacestyle" );
613     if (b_dark)
614         img = [NSImage imageNamed:@"volume-slider-knob_dark"];
615     else
616         img = [NSImage imageNamed:@"volume-slider-knob"];
617
618     image_rect.size = [img size];
619     image_rect.origin.x = 0;
620
621     if (b_dark)
622         image_rect.origin.y = -1;
623     else
624         image_rect.origin.y = 0;
625 }
626
627 - (void)drawKnobInRect:(NSRect)knobRect
628 {
629     knobRect.origin.x += (knobRect.size.width - image_rect.size.width) / 2;
630     knobRect.size.width = image_rect.size.width;
631     knobRect.size.height = image_rect.size.height;
632     [img drawInRect:knobRect fromRect:image_rect operation:NSCompositeSourceOver fraction:1];
633 }
634
635 - (void)drawRect:(NSRect)rect
636 {
637     /* Draw default to make sure the slider behaves correctly */
638     [[NSGraphicsContext currentContext] saveGraphicsState];
639     NSRectClip(NSZeroRect);
640     [super drawRect:rect];
641     [[NSGraphicsContext currentContext] restoreGraphicsState];
642
643     [self drawFullVolumeMarker];
644
645     NSRect knobRect = [[self cell] knobRectFlipped:NO];
646     knobRect.origin.y+=2;
647     [self drawKnobInRect: knobRect];
648 }
649
650 @end
651
652 /*****************************************************************************
653  * VLCTimeField implementation
654  *****************************************************************************
655  * we need this to catch our click-event in the controller window
656  *****************************************************************************/
657
658 @implementation VLCTimeField
659 + (void)initialize{
660     NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
661     NSDictionary *appDefaults = [NSDictionary dictionaryWithObjectsAndKeys:
662                                  @"NO", @"DisplayTimeAsTimeRemaining",
663                                  @"YES", @"DisplayFullscreenTimeAsTimeRemaining",
664                                  nil];
665
666     [defaults registerDefaults:appDefaults];
667 }
668
669 - (id)initWithFrame:(NSRect)frameRect
670 {
671     if (self = [super initWithFrame:frameRect]) {
672         textAlignment = NSCenterTextAlignment;
673         o_remaining_identifier = @"";
674     }
675
676     return self;
677 }
678
679 - (void)setRemainingIdentifier:(NSString *)o_string
680 {
681     o_remaining_identifier = o_string;
682     b_time_remaining = [[NSUserDefaults standardUserDefaults] boolForKey:o_remaining_identifier];
683 }
684
685 - (void)setAlignment:(NSTextAlignment)alignment
686 {
687     textAlignment = alignment;
688     [self setStringValue:[self stringValue]];
689 }
690
691 - (void)dealloc
692 {
693     [o_string_shadow release];
694     [super dealloc];
695 }
696
697 - (void)setStringValue:(NSString *)string
698 {
699     if (!o_string_shadow) {
700         o_string_shadow = [[NSShadow alloc] init];
701         [o_string_shadow setShadowColor: [NSColor colorWithCalibratedWhite:1.0 alpha:0.5]];
702         [o_string_shadow setShadowOffset:NSMakeSize(0.0, -1.0)];
703         [o_string_shadow setShadowBlurRadius:0.0];
704     }
705
706     NSMutableAttributedString *o_attributed_string = [[NSMutableAttributedString alloc] initWithString:string attributes: nil];
707     NSUInteger i_stringLength = [string length];
708
709     [o_attributed_string addAttribute: NSShadowAttributeName value: o_string_shadow range: NSMakeRange(0, i_stringLength)];
710     [o_attributed_string setAlignment: textAlignment range: NSMakeRange(0, i_stringLength)];
711     [self setAttributedStringValue: o_attributed_string];
712     [o_attributed_string release];
713 }
714
715 - (void)mouseDown: (NSEvent *)ourEvent
716 {
717     if ( [ourEvent clickCount] > 1 )
718         [[[VLCMain sharedInstance] controls] goToSpecificTime: nil];
719     else
720     {
721         if (![o_remaining_identifier isEqualToString: @""]) {
722             if ([[NSUserDefaults standardUserDefaults] boolForKey:o_remaining_identifier]) {
723                 [[NSUserDefaults standardUserDefaults] setObject:@"NO" forKey:o_remaining_identifier];
724                 b_time_remaining = NO;
725             } else {
726                 [[NSUserDefaults standardUserDefaults] setObject:@"YES" forKey:o_remaining_identifier];
727                 b_time_remaining = YES;
728             }
729         } else {
730             b_time_remaining = !b_time_remaining;
731             [[NSUserDefaults standardUserDefaults] setObject:(b_time_remaining ? @"YES" : @"NO") forKey:o_remaining_identifier];
732         }
733     }
734 }
735
736 - (BOOL)timeRemaining
737 {
738     if (![o_remaining_identifier isEqualToString: @""])
739         return [[NSUserDefaults standardUserDefaults] boolForKey:o_remaining_identifier];
740     else
741         return b_time_remaining;
742 }
743
744 @end
745
746 /*****************************************************************************
747  * VLCMainWindowSplitView implementation
748  * comment 1 + 2 taken from NSSplitView.h (10.7 SDK)
749  *****************************************************************************/
750 @implementation VLCMainWindowSplitView : NSSplitView
751 /* 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.
752  */
753 - (NSColor *)dividerColor
754 {
755     return [NSColor colorWithCalibratedRed:.60 green:.60 blue:.60 alpha:1.];
756 }
757
758 /* 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.
759  */
760 - (CGFloat)dividerThickness
761 {
762     return 1.0;
763 }
764 @end
765
766 /*****************************************************************************
767  * VLCThreePartImageView interface
768  *****************************************************************************/
769 @implementation VLCThreePartImageView
770
771 - (void)dealloc
772 {
773     [o_left_img release];
774     [o_middle_img release];
775     [o_right_img release];
776
777     [super dealloc];
778 }
779
780 - (void)setImagesLeft:(NSImage *)left middle: (NSImage *)middle right:(NSImage *)right
781 {
782     if (o_left_img)
783         [o_left_img release];
784     if (o_middle_img)
785         [o_middle_img release];
786     if (o_right_img)
787         [o_right_img release];
788
789     o_left_img = [left retain];
790     o_middle_img = [middle retain];
791     o_right_img = [right retain];
792 }
793
794 - (void)drawRect:(NSRect)rect
795 {
796     NSRect bnds = [self bounds];
797     NSDrawThreePartImage( bnds, o_left_img, o_middle_img, o_right_img, NO, NSCompositeSourceOver, 1, NO );
798 }
799
800 @end
801
802 @implementation VLCThreePartDropView
803
804 - (BOOL)mouseDownCanMoveWindow
805 {
806     return YES;
807 }
808
809 - (void)dealloc
810 {
811     [self unregisterDraggedTypes];
812     [super dealloc];
813 }
814
815 - (void)awakeFromNib
816 {
817     [self registerForDraggedTypes:@[NSFilenamesPboardType]];
818 }
819
820 - (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
821 {
822     if ((NSDragOperationGeneric & [sender draggingSourceOperationMask]) == NSDragOperationGeneric)
823         return NSDragOperationGeneric;
824
825     return NSDragOperationNone;
826 }
827
828 - (BOOL)prepareForDragOperation:(id <NSDraggingInfo>)sender
829 {
830     return YES;
831 }
832
833 - (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
834 {
835     BOOL b_returned;
836     b_returned = [[VLCCoreInteraction sharedInstance] performDragOperation: sender];
837
838     [self setNeedsDisplay:YES];
839     return YES;
840 }
841
842 - (void)concludeDragOperation:(id <NSDraggingInfo>)sender
843 {
844     [self setNeedsDisplay:YES];
845 }
846
847 @end
848
849 @implementation PositionFormatter
850
851 - (id)init
852 {
853     self = [super init];
854     NSMutableCharacterSet *nonNumbers = [[[NSCharacterSet decimalDigitCharacterSet] invertedSet] mutableCopy];
855     [nonNumbers removeCharactersInString:@":"];
856     o_forbidden_characters = [nonNumbers copy];
857     [nonNumbers release];
858
859     return self;
860 }
861
862 - (void)dealloc
863 {
864     [o_forbidden_characters release];
865     [super dealloc];
866 }
867
868 - (NSString*)stringForObjectValue:(id)obj
869 {
870     return obj;
871 }
872
873 - (BOOL)getObjectValue:(id*)obj forString:(NSString*)string errorDescription:(NSString**)error
874 {
875     *obj = [[string copy] autorelease];
876     return YES;
877 }
878
879 - (bool)isPartialStringValid:(NSString*)partialString newEditingString:(NSString**)newString errorDescription:(NSString**)error
880 {
881     if ([partialString rangeOfCharacterFromSet:o_forbidden_characters options:NSLiteralSearch].location != NSNotFound) {
882         return NO;
883     } else {
884         return YES;
885     }
886 }
887
888
889 @end
890
891 @implementation NSView (EnableSubviews)
892
893 - (void)enableSubviews:(BOOL)b_enable
894 {
895     for (NSView *o_view in [self subviews]) {
896         [o_view enableSubviews:b_enable];
897
898         // enable NSControl
899         if ([o_view respondsToSelector:@selector(setEnabled:)]) {
900             [(NSControl *)o_view setEnabled:b_enable];
901         }
902         // also "enable / disable" text views
903         if ([o_view respondsToSelector:@selector(setTextColor:)]) {
904             if (b_enable == NO) {
905                 [(NSTextField *)o_view setTextColor:[NSColor disabledControlTextColor]];
906             } else {
907                 [(NSTextField *)o_view setTextColor:[NSColor controlTextColor]];
908             }
909         }
910
911     }
912 }
913
914 @end