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