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