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