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