Mac OS X gui: Rework VLCVoutWindow behaviour when macosx-black is set. (Pointed by...
[vlc.git] / modules / gui / macosx / misc.m
1 /*****************************************************************************
2  * misc.m: code not specific to vlc
3  *****************************************************************************
4  * Copyright (C) 2003-2007 the VideoLAN team
5  * $Id$
6  *
7  * Authors: Jon Lech Johansen <jon-vl@nanocrew.net>
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22  *****************************************************************************/
23
24 #include <Cocoa/Cocoa.h>
25
26 #include "intf.h"                                          /* VLCApplication */
27 #include "misc.h"
28 #include "playlist.h"
29 #include "controls.h"
30
31 /*****************************************************************************
32  * NSAnimation (VLCAdditions)
33  *
34  *  Missing extension to NSAnimation
35  *****************************************************************************/
36
37 @implementation NSAnimation (VLCAdditions)
38 /* fake class attributes  */
39 static NSMapTable *VLCAdditions_userInfo = NULL;
40
41 + (void)load
42 {
43     /* init our fake object attribute */
44     VLCAdditions_userInfo = NSCreateMapTable(NSNonRetainedObjectMapKeyCallBacks, NSObjectMapValueCallBacks, 16);
45 }
46
47 - (void)dealloc
48 {
49     NSMapRemove(VLCAdditions_userInfo, self);
50     [super dealloc];
51 }
52
53 - (void)setUserInfo: (void *)userInfo
54 {
55     NSMapInsert(VLCAdditions_userInfo, self, (void*)userInfo);
56 }
57
58 - (void *)userInfo
59 {
60     return NSMapGet(VLCAdditions_userInfo, self);
61 }
62 @end
63
64 /*****************************************************************************
65  * NSScreen (VLCAdditions)
66  *
67  *  Missing extension to NSScreen
68  *****************************************************************************/
69
70 @implementation NSScreen (VLCAdditions)
71
72 static NSMutableArray *blackoutWindows = NULL;
73
74 + (void)load
75 {
76     /* init our fake object attribute */
77     blackoutWindows = [[NSMutableArray alloc] initWithCapacity:1];
78 }
79
80 + (NSScreen *)screenWithDisplayID: (CGDirectDisplayID)displayID
81 {
82     int i;
83     
84     for( i = 0; i < [[NSScreen screens] count]; i++ )
85     {
86         NSScreen *screen = [[NSScreen screens] objectAtIndex: i];
87         if([screen displayID] == displayID)
88             return screen;
89     }
90     return nil;
91 }
92
93 - (BOOL)isMainScreen
94 {
95     return ([self displayID] == [[[NSScreen screens] objectAtIndex:0] displayID]);
96 }
97
98 - (BOOL)isScreen: (NSScreen*)screen
99 {
100     return ([self displayID] == [screen displayID]);
101 }
102
103 - (CGDirectDisplayID)displayID
104 {
105     return (CGDirectDisplayID)_screenNumber;
106 }
107
108 - (void)blackoutOtherScreens
109 {
110     unsigned int i;
111
112     /* Free our previous blackout window (follow blackoutWindow alloc strategy) */
113     [blackoutWindows makeObjectsPerformSelector:@selector(close)];
114     [blackoutWindows removeAllObjects];
115
116     
117     for(i = 0; i < [[NSScreen screens] count]; i++)
118     {
119         NSScreen *screen = [[NSScreen screens] objectAtIndex: i];
120         VLCWindow *blackoutWindow;
121         NSRect screen_rect;
122         
123         if([self isScreen: screen])
124             continue;
125
126         screen_rect = [screen frame];
127         screen_rect.origin.x = screen_rect.origin.y = 0.0f;
128
129         /* blackoutWindow alloc strategy
130             - The NSMutableArray blackoutWindows has the blackoutWindow references
131             - blackoutOtherDisplays is responsible for alloc/releasing its Windows
132         */
133         blackoutWindow = [[VLCWindow alloc] initWithContentRect: screen_rect styleMask: NSBorderlessWindowMask
134                 backing: NSBackingStoreBuffered defer: NO screen: screen];
135         [blackoutWindow setBackgroundColor:[NSColor blackColor]];
136         [blackoutWindow setLevel: NSFloatingWindowLevel]; /* Disappear when Expose is triggered */
137         
138         [blackoutWindow orderFront: self animate: YES];
139
140         [blackoutWindows addObject: blackoutWindow];
141         [blackoutWindow release];
142     }
143 }
144
145 + (void)unblackoutScreens
146 {
147     unsigned int i;
148
149     for(i = 0; i < [blackoutWindows count]; i++)
150     {
151         VLCWindow *blackoutWindow = [blackoutWindows objectAtIndex: i];
152         [blackoutWindow closeAndAnimate: YES];
153     }
154 }
155
156 @end
157
158 /*****************************************************************************
159  * VLCWindow
160  *
161  *  Missing extension to NSWindow
162  *****************************************************************************/
163
164 @implementation VLCWindow
165 - (id)initWithContentRect:(NSRect)contentRect styleMask:(unsigned int)styleMask
166     backing:(NSBackingStoreType)backingType defer:(BOOL)flag
167 {
168     self = [super initWithContentRect:contentRect styleMask:styleMask backing:backingType defer:flag];
169     if( self )
170         b_isset_canBecomeKeyWindow = NO;
171     return self;
172 }
173 - (void)setCanBecomeKeyWindow: (BOOL)canBecomeKey
174 {
175     b_isset_canBecomeKeyWindow = YES;
176     b_canBecomeKeyWindow = canBecomeKey;
177 }
178
179 - (BOOL)canBecomeKeyWindow
180 {
181     if(b_isset_canBecomeKeyWindow)
182         return b_canBecomeKeyWindow;
183
184     return [super canBecomeKeyWindow];
185 }
186
187 - (void)closeAndAnimate: (BOOL)animate
188 {
189     NSInvocation *invoc;
190     
191     if (!animate || MACOS_VERSION < 10.4f)
192     {
193         [super close];
194         return;
195     }
196
197     invoc = [NSInvocation invocationWithMethodSignature:[super methodSignatureForSelector:@selector(close)]];
198     [invoc setTarget: (id)super];
199
200     if (![self isVisible] || [self alphaValue] == 0.0)
201     {
202         [super close];
203         return;
204     }
205
206     [self orderOut: self animate: YES callback: invoc];
207 }
208
209 - (void)orderOut: (id)sender animate: (BOOL)animate
210 {
211     NSInvocation *invoc = [NSInvocation invocationWithMethodSignature:[super methodSignatureForSelector:@selector(orderOut:)]];
212     [invoc setTarget: (id)super];
213     [invoc setArgument: sender atIndex: 0];
214     [self orderOut: sender animate: animate callback: invoc];
215 }
216
217 - (void)orderOut: (id)sender animate: (BOOL)animate callback:(NSInvocation *)callback
218 {
219     NSViewAnimation *anim;
220     NSViewAnimation *current_anim;
221     NSMutableDictionary *dict;
222
223     if (!animate || MACOS_VERSION < 10.4f)
224     {
225         [self orderOut: sender];
226         return;
227     }
228
229     dict = [[NSMutableDictionary alloc] initWithCapacity:2];
230
231     [dict setObject:self forKey:NSViewAnimationTargetKey];
232
233     [dict setObject:NSViewAnimationFadeOutEffect forKey:NSViewAnimationEffectKey];
234     anim = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObjects:dict, nil]];
235     [dict release];
236
237     [anim setAnimationBlockingMode:NSAnimationNonblocking];
238     [anim setDuration:0.9];
239     [anim setFrameRate:30];
240     [anim setUserInfo: callback];
241
242     @synchronized(self) {
243         current_anim = self->animation;
244
245         if ([[[current_anim viewAnimations] objectAtIndex:0] objectForKey: NSViewAnimationEffectKey] == NSViewAnimationFadeOutEffect && [current_anim isAnimating])
246         {
247             [anim release];
248         }
249         else
250         {
251             if (current_anim)
252             {
253                 [current_anim stopAnimation];
254                 [anim setCurrentProgress:1.0-[current_anim currentProgress]];
255                 [current_anim release];
256             }
257             else
258                 [anim setCurrentProgress:1.0 - [self alphaValue]];
259             self->animation = anim;
260             [self setDelegate: self];
261             [anim startAnimation];
262         }
263     }
264 }
265
266 - (void)orderFront: (id)sender animate: (BOOL)animate
267 {
268     NSViewAnimation *anim;
269     NSViewAnimation *current_anim;
270     NSMutableDictionary *dict;
271     
272     if (!animate || MACOS_VERSION < 10.4f)
273     {
274         [super orderFront: sender];
275         [self setAlphaValue: 1.0];
276         return;
277     }
278
279     if (![self isVisible])
280     {
281         [self setAlphaValue: 0.0];
282         [super orderFront: sender];
283     }
284     else if ([self alphaValue] == 1.0)
285     {
286         [super orderFront: self];
287         return;
288     }
289
290     dict = [[NSMutableDictionary alloc] initWithCapacity:2];
291
292     [dict setObject:self forKey:NSViewAnimationTargetKey];
293     
294     [dict setObject:NSViewAnimationFadeInEffect forKey:NSViewAnimationEffectKey];
295     anim = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObjects:dict, nil]];
296     [dict release];
297     
298     [anim setAnimationBlockingMode:NSAnimationNonblocking];
299     [anim setDuration:0.5];
300     [anim setFrameRate:30];
301
302     @synchronized(self) {
303         current_anim = self->animation;
304
305         if ([[[current_anim viewAnimations] objectAtIndex:0] objectForKey: NSViewAnimationEffectKey] == NSViewAnimationFadeInEffect && [current_anim isAnimating])
306         {
307             [anim release];
308         }
309         else
310         {
311             if (current_anim)
312             {
313                 [current_anim stopAnimation];
314                 [anim setCurrentProgress:1.0 - [current_anim currentProgress]];
315                 [current_anim release];
316             }
317             else
318                 [anim setCurrentProgress:[self alphaValue]];
319             self->animation = anim;
320             [self setDelegate: self];
321             [self orderFront: sender];
322             [anim startAnimation];
323         }
324     }
325 }
326
327 - (void)animationDidEnd:(NSAnimation*)anim
328 {
329     if ([self alphaValue] <= 0.0)
330     {
331         NSInvocation * invoc;
332         [super orderOut: nil];
333         [self setAlphaValue: 1.0];
334         if ((invoc = [anim userInfo]))
335             [invoc invoke];
336     }
337 }
338 @end
339
340 /*****************************************************************************
341  * VLCControllerWindow
342  *****************************************************************************/
343
344 @implementation VLCControllerWindow
345
346 - (id)initWithContentRect:(NSRect)contentRect styleMask:(unsigned int)styleMask
347     backing:(NSBackingStoreType)backingType defer:(BOOL)flag
348 {
349     self = [super initWithContentRect:contentRect styleMask:styleMask //& ~NSTitledWindowMask
350     backing:backingType defer:flag];
351
352     [[VLCMain sharedInstance] updateTogglePlaylistState];
353
354     return( self );
355 }
356
357 - (BOOL)performKeyEquivalent:(NSEvent *)o_event
358 {
359     /* We indeed want to prioritize Cocoa key equivalent against libvlc,
360        so we perform the menu equivalent now. */
361     if([[NSApp mainMenu] performKeyEquivalent:o_event])
362         return TRUE;
363
364     return [[VLCMain sharedInstance] hasDefinedShortcutKey:o_event] ||
365            [(VLCControls *)[[VLCMain sharedInstance] getControls] keyEvent:o_event];
366 }
367
368 @end
369
370
371
372 /*****************************************************************************
373  * VLCControllerView
374  *****************************************************************************/
375
376 @implementation VLCControllerView
377
378 - (void)dealloc
379 {
380     [self unregisterDraggedTypes];
381     [super dealloc];
382 }
383
384 #if GC_ENABLED
385 - (void)finalize
386 {
387     /* dealloc doesn't get called on 10.5 if GC is enabled, so we need to provide the basic functionality here */
388     [self unregisterDraggedTypes];
389     [super finalize];
390 }
391 #endif
392
393 - (void)awakeFromNib
394 {
395     [self registerForDraggedTypes:[NSArray arrayWithObjects:NSTIFFPboardType, 
396         NSFilenamesPboardType, nil]];
397 }
398
399 - (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
400 {
401     if ((NSDragOperationGeneric & [sender draggingSourceOperationMask]) 
402                 == NSDragOperationGeneric)
403     {
404         return NSDragOperationGeneric;
405     }
406     else
407     {
408         return NSDragOperationNone;
409     }
410 }
411
412 - (BOOL)prepareForDragOperation:(id <NSDraggingInfo>)sender
413 {
414     return YES;
415 }
416
417 - (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
418 {
419     NSPasteboard *o_paste = [sender draggingPasteboard];
420     NSArray *o_types = [NSArray arrayWithObjects: NSFilenamesPboardType, nil];
421     NSString *o_desired_type = [o_paste availableTypeFromArray:o_types];
422     NSData *o_carried_data = [o_paste dataForType:o_desired_type];
423
424     if( o_carried_data )
425     {
426         if ([o_desired_type isEqualToString:NSFilenamesPboardType])
427         {
428             int i;
429             NSArray *o_array = [NSArray array];
430             NSArray *o_values = [[o_paste propertyListForType: NSFilenamesPboardType]
431                         sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)];
432
433             for( i = 0; i < (int)[o_values count]; i++)
434             {
435                 NSDictionary *o_dic;
436                 o_dic = [NSDictionary dictionaryWithObject:[o_values objectAtIndex:i] forKey:@"ITEM_URL"];
437                 o_array = [o_array arrayByAddingObject: o_dic];
438             }
439             [(VLCPlaylist *)[[VLCMain sharedInstance] getPlaylist] appendArray: o_array atPos: -1 enqueue:NO];
440             return YES;
441         }
442     }
443     [self setNeedsDisplay:YES];
444     return YES;
445 }
446
447 - (void)concludeDragOperation:(id <NSDraggingInfo>)sender
448 {
449     [self setNeedsDisplay:YES];
450 }
451
452 @end
453
454 /*****************************************************************************
455  * VLBrushedMetalImageView
456  *****************************************************************************/
457
458 @implementation VLBrushedMetalImageView
459
460 - (BOOL)mouseDownCanMoveWindow
461 {
462     return YES;
463 }
464
465 - (void)dealloc
466 {
467     [self unregisterDraggedTypes];
468     [super dealloc];
469 }
470
471 #if GC_ENABLED
472 - (void)finalize
473 {
474     /* dealloc doesn't get called on 10.5 if GC is enabled, so we need to provide the basic functionality here */
475     [self unregisterDraggedTypes];
476     [super finalize];
477 }
478 #endif
479
480 - (void)awakeFromNib
481 {
482     [self registerForDraggedTypes:[NSArray arrayWithObjects:NSTIFFPboardType, 
483         NSFilenamesPboardType, nil]];
484 }
485
486 - (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
487 {
488     if ((NSDragOperationGeneric & [sender draggingSourceOperationMask]) 
489                 == NSDragOperationGeneric)
490     {
491         return NSDragOperationGeneric;
492     }
493     else
494     {
495         return NSDragOperationNone;
496     }
497 }
498
499 - (BOOL)prepareForDragOperation:(id <NSDraggingInfo>)sender
500 {
501     return YES;
502 }
503
504 - (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
505 {
506     NSPasteboard *o_paste = [sender draggingPasteboard];
507     NSArray *o_types = [NSArray arrayWithObjects: NSFilenamesPboardType, nil];
508     NSString *o_desired_type = [o_paste availableTypeFromArray:o_types];
509     NSData *o_carried_data = [o_paste dataForType:o_desired_type];
510     BOOL b_autoplay = config_GetInt( VLCIntf, "macosx-autoplay" );
511
512     if( o_carried_data )
513     {
514         if ([o_desired_type isEqualToString:NSFilenamesPboardType])
515         {
516             int i;
517             NSArray *o_array = [NSArray array];
518             NSArray *o_values = [[o_paste propertyListForType: NSFilenamesPboardType]
519                         sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)];
520
521             for( i = 0; i < (int)[o_values count]; i++)
522             {
523                 NSDictionary *o_dic;
524                 o_dic = [NSDictionary dictionaryWithObject:[o_values objectAtIndex:i] forKey:@"ITEM_URL"];
525                 o_array = [o_array arrayByAddingObject: o_dic];
526             }
527             if( b_autoplay )
528                 [[[VLCMain sharedInstance] getPlaylist] appendArray: o_array atPos: -1 enqueue:NO];
529             else
530                 [[[VLCMain sharedInstance] getPlaylist] appendArray: o_array atPos: -1 enqueue:YES];
531             return YES;
532         }
533     }
534     [self setNeedsDisplay:YES];
535     return YES;
536 }
537
538 - (void)concludeDragOperation:(id <NSDraggingInfo>)sender
539 {
540     [self setNeedsDisplay:YES];
541 }
542
543 @end
544
545
546 /*****************************************************************************
547  * MPSlider
548  *****************************************************************************/
549 @implementation MPSlider
550
551 void _drawKnobInRect(NSRect knobRect)
552 {
553     // Center knob in given rect
554     knobRect.origin.x += (int)((float)(knobRect.size.width - 7)/2.0);
555     knobRect.origin.y += (int)((float)(knobRect.size.height - 7)/2.0);
556     
557     // Draw diamond
558     NSRectFillUsingOperation(NSMakeRect(knobRect.origin.x + 3, knobRect.origin.y + 6, 1, 1), NSCompositeSourceOver);
559     NSRectFillUsingOperation(NSMakeRect(knobRect.origin.x + 2, knobRect.origin.y + 5, 3, 1), NSCompositeSourceOver);
560     NSRectFillUsingOperation(NSMakeRect(knobRect.origin.x + 1, knobRect.origin.y + 4, 5, 1), NSCompositeSourceOver);
561     NSRectFillUsingOperation(NSMakeRect(knobRect.origin.x + 0, knobRect.origin.y + 3, 7, 1), NSCompositeSourceOver);
562     NSRectFillUsingOperation(NSMakeRect(knobRect.origin.x + 1, knobRect.origin.y + 2, 5, 1), NSCompositeSourceOver);
563     NSRectFillUsingOperation(NSMakeRect(knobRect.origin.x + 2, knobRect.origin.y + 1, 3, 1), NSCompositeSourceOver);
564     NSRectFillUsingOperation(NSMakeRect(knobRect.origin.x + 3, knobRect.origin.y + 0, 1, 1), NSCompositeSourceOver);
565 }
566
567 void _drawFrameInRect(NSRect frameRect)
568 {
569     // Draw frame
570     NSRectFillUsingOperation(NSMakeRect(frameRect.origin.x, frameRect.origin.y, frameRect.size.width, 1), NSCompositeSourceOver);
571     NSRectFillUsingOperation(NSMakeRect(frameRect.origin.x, frameRect.origin.y + frameRect.size.height-1, frameRect.size.width, 1), NSCompositeSourceOver);
572     NSRectFillUsingOperation(NSMakeRect(frameRect.origin.x, frameRect.origin.y, 1, frameRect.size.height), NSCompositeSourceOver);
573     NSRectFillUsingOperation(NSMakeRect(frameRect.origin.x+frameRect.size.width-1, frameRect.origin.y, 1, frameRect.size.height), NSCompositeSourceOver);
574 }
575
576 - (void)drawRect:(NSRect)rect
577 {
578     // Draw default to make sure the slider behaves correctly
579     [[NSGraphicsContext currentContext] saveGraphicsState];
580     NSRectClip(NSZeroRect);
581     [super drawRect:rect];
582     [[NSGraphicsContext currentContext] restoreGraphicsState];
583     
584     // Full size
585     rect = [self bounds];
586     int diff = (int)(([[self cell] knobThickness] - 7.0)/2.0) - 1;
587     rect.origin.x += diff-1;
588     rect.origin.y += diff;
589     rect.size.width -= 2*diff-2;
590     rect.size.height -= 2*diff;
591     
592     // Draw dark
593     NSRect knobRect = [[self cell] knobRectFlipped:NO];
594     [[[NSColor blackColor] colorWithAlphaComponent:0.6] set];
595     _drawFrameInRect(rect);
596     _drawKnobInRect(knobRect);
597     
598     // Draw shadow
599     [[[NSColor blackColor] colorWithAlphaComponent:0.1] set];
600     rect.origin.x++;
601     rect.origin.y++;
602     knobRect.origin.x++;
603     knobRect.origin.y++;
604     _drawFrameInRect(rect);
605     _drawKnobInRect(knobRect);
606 }
607
608 @end
609
610
611 /*****************************************************************************
612  * ITSlider
613  *****************************************************************************/
614
615 @implementation ITSlider
616
617 - (void)awakeFromNib
618 {
619     if ([[self cell] class] != [ITSliderCell class]) {
620         // replace cell
621         NSSliderCell *oldCell = [self cell];
622         NSSliderCell *newCell = [[[ITSliderCell alloc] init] autorelease];
623         [newCell setTag:[oldCell tag]];
624         [newCell setTarget:[oldCell target]];
625         [newCell setAction:[oldCell action]];
626         [newCell setControlSize:[oldCell controlSize]];
627         [newCell setType:[oldCell type]];
628         [newCell setState:[oldCell state]]; 
629         [newCell setAllowsTickMarkValuesOnly:[oldCell allowsTickMarkValuesOnly]];
630         [newCell setAltIncrementValue:[oldCell altIncrementValue]];
631         [newCell setControlTint:[oldCell controlTint]];
632         [newCell setKnobThickness:[oldCell knobThickness]];
633         [newCell setMaxValue:[oldCell maxValue]];
634         [newCell setMinValue:[oldCell minValue]];
635         [newCell setDoubleValue:[oldCell doubleValue]];
636         [newCell setNumberOfTickMarks:[oldCell numberOfTickMarks]];
637         [newCell setEditable:[oldCell isEditable]];
638         [newCell setEnabled:[oldCell isEnabled]];
639         [newCell setEntryType:[oldCell entryType]];
640         [newCell setHighlighted:[oldCell isHighlighted]];
641         [newCell setTickMarkPosition:[oldCell tickMarkPosition]];
642         [self setCell:newCell];
643     }
644 }
645
646 @end
647
648 /*****************************************************************************
649  * ITSliderCell
650  *****************************************************************************/
651 @implementation ITSliderCell
652
653 - (id)init
654 {
655     self = [super init];
656     _knobOff = [[NSImage imageNamed:@"volumeslider_normal"] retain];
657     _knobOn = [[NSImage imageNamed:@"volumeslider_blue"] retain];
658     b_mouse_down = FALSE;
659     return self;
660 }
661
662 - (void)dealloc
663 {
664     [_knobOff release];
665     [_knobOn release];
666     [super dealloc];
667 }
668
669 - (void)drawKnob:(NSRect)knob_rect
670 {
671     NSImage *knob;
672
673     if( b_mouse_down )
674         knob = _knobOn;
675     else
676         knob = _knobOff;
677
678     [[self controlView] lockFocus];
679     [knob compositeToPoint:NSMakePoint( knob_rect.origin.x + 1,
680         knob_rect.origin.y + knob_rect.size.height -2 )  
681         operation:NSCompositeSourceOver];
682     [[self controlView] unlockFocus];
683 }
684
685 - (void)stopTracking:(NSPoint)lastPoint at:(NSPoint)stopPoint inView: 
686         (NSView *)controlView mouseIsUp:(BOOL)flag
687 {
688     b_mouse_down = NO;
689     [self drawKnob];
690     [super stopTracking:lastPoint at:stopPoint inView:controlView mouseIsUp:flag];
691 }
692
693 - (BOOL)startTrackingAt:(NSPoint)startPoint inView:(NSView *)controlView
694 {
695     b_mouse_down = YES;
696     [self drawKnob];
697     return [super startTrackingAt:startPoint inView:controlView];
698 }
699
700 @end
701