ftp.nice.ch/pub/next/developer/resources/classes/misckit/MiscKit.1.10.0.s.gnutar.gz#/MiscKit/Palettes/MiscCircularSlider/MiscCircularSlider.subproj/MiscCSCell.m

This is MiscCSCell.m in view mode; [Download] [Up]

//
//	MiscCircularSliderCell.m -- a SliderCell for circular sliders
//		Written by Vince DeMarco and Don Yacktman
//		Copyright (c) 1994 by Vince DeMarco and Don Yacktman.
//				Version 1.0  All rights reserved.
//		This notice may not be removed from this source code.
//
//	This object is included in the MiscKit by permission from the author
//	and its use is governed by the MiscKit license, found in the file
//	"LICENSE.rtf" in the MiscKit distribution.  Please refer to that file
//	for a list of all applicable permissions and restrictions.
//	

// This was created by modifying Vince's CircularSliderCell class and
// merging it with Don's RotationSliderCell class.  Here's the info
// from the original files.
/*
 *    Filename:	CircularSliderCell.m 
 *    Created :	Sun Oct 18 16:56:38 1992 
 *    Author  :	Vince DeMarco
 *		<vince@whatnxt.cuc.ab.ca>
 *    LastEditDate was "Wed Feb  3 17:30:28 1993"
 */
//	RotationSliderCell.m -- written by Don Yacktman
//	Copyright 1993 by Don Yacktman.  All rights reserved.

//	Modified by Laurent Daudelin on Wed Sep 7 12:09:55 1994
//	to add support for lower and upper stop values, and
//	to support for knob width adjustment for the MISC_SHUTTLEWHEEL_STYLE slider.

//  Modified by Carl Lindberg   Wed Apr 19 02:01:15 EDT 1995
//  Added support for border types
//  Added support for background color
//  Fixed boundary-check bug, fixed bug when view is taller than it is wide
//  Reorganized the code to something approaching sanity
//  Redid drawing of knobImage, no more glitchiness on larger sliders/knob widths.
//    (Unless you get really, really big :-) )

//  Modified more by Carl Lindberg Mon Jun 12 00:36:31 EDT 1995
//  Added support for allowing which angle minValue is shown at
//       (they don't have to "start" on the right side anymore)
//  Added support for specifying which direction (clockwise or 
//       clockwise) that values increase in.  Before, this was
//       counterclockwise.
//  Changed the pswrap to the pie-style one to accomodate above fixes
//  The pie-style one previously gave strange values; doing the
//       above modifications fixed this

#import "MiscCircularSliderCell.h"
#import "MiscCSWraps.h"
#import <math.h>
#import <appkit/appkit.h>

@interface MiscCircularSliderCell(private)
- _cacheImages;
@end

// I changed how the knobImage gets drawn for slider-style a bit, so 
// naturally I have a new set of fudges... :-)   -Carl

#define _KNOBSIZE_FUDGE			(- 0.1)
#define _KNOBPOS_RADIUS_FUDGE	(-  .15)
#define _KNOBPOS_X_FUDGE		(  1.3)
#define _KNOBPOS_Y_FUDGE		(- 0.5)

// These are still used, though
#define _KNOB_WIDTH_FUDGE (5.0 / 8.0)
#define _KNOB_Y_FUDGE (- 1.0)
#define _KNOB_X_FUDGE (  1.0)
#define _BEZELS_FUDGE (  4.0)
#define MISC_SLIDERCELL_CURRENT_VERSION 4

#define MISC_CSDEFAULTCOLOR NXConvertRGBAToColor(0.0, 0.0, 0.0, 0.0)  //transparent

@implementation MiscCircularSliderCell

// returns angle in 0-360 range
inline static float in360(float ang)
{	
	while (ang < 0.0) ang += 360.0;
	while (ang >= 360.0) ang -= 360.0;
	return ang;
}

// Given a "raw" angle (the angle if not clockwise and
// startangle at 0), calculates the correct angle based
// on the current values of isClockwise and startAngle.
//  Used with currentangle() to get actual angle of
// given value.
float convertAngle(float ang, BOOL isClockwise, float startAngle)
{
	ang = in360(ang);
	if (isClockwise) ang = startAngle-ang;
	else ang = startAngle+ang;
	return in360(ang);
}

// Inverse of convertAngle().  Given the actual angle, uses
// values of startAngle and isClockwise and startAngle to
// convert to a "raw" angle (0-360, with start at 0), which then can be
// mapped against minValue and maxValue to compute the value
// of the current angle.  Used by position().
float convertAngle2(float ang, BOOL isClockwise, float startAngle)
{
	ang = in360(ang);
	ang -= startAngle;
	if (isClockwise) ang = (360-ang);
	return in360(ang);
}

inline static float angle(float x, float y)
{ // this is a little cleaner than Vince's original routine.  :-)
	double result = atan2(y, x) * (180 / M_PI);
	return in360(result); // keep in 0-360 degree range
}

inline static void xycoord(float angle, float radius,float *x, float *y)
{ // the inverse of the above; given angle and radius, determines x & y
    *x = radius * (float)cos((M_PI / 180) * angle);
    *y = radius * (float)sin((M_PI / 180) * angle);
}

// Convert between a 0-360 degree angle and the slider's position.  The
// angle is used as the parameter to pass to the pswrap that does the drawing.
// These two #defines are inverses of each other.
#define currentangle(pos, min, max) \
	(convertAngle((360.0 * ((pos) - (min)) / ((max) - (min))),isClockwise,startAngle))
#define position(angle, min, max) \
	((convertAngle2(angle, isClockwise,startAngle)) * ((max) - (min)) / 360.0 + (min))

static float distBetween(NXPoint a, NXPoint b)
{ return (float)hypot((double)(a.x-b.x),(double)(a.y-b.y)); }

+ initialize 
{
    if (self == [MiscCircularSliderCell class]) {
	      [MiscCircularSliderCell setVersion:MISC_SLIDERCELL_CURRENT_VERSION];
    }
    return self;
}

- init
{
    self = [super init];
    minValue = 0.0;
	value = 45.0;
    maxValue = 360.0;
	radius = 50.0; sliderPathWidth = 10.0;
	lowerStopValue = minValue;
	upperStopValue = maxValue;
	center.x = radius;
	center.y = radius;
	style = MISC_SLIDER_STYLE;
	jumpToMousePoint = YES;
    hidden = NO;
	backgroundColor = MISC_CSDEFAULTCOLOR;
	borderType = MISC_CSNONE;
	startAngle = 0;
	isClockwise = NO;
	[self _cacheImages];
    return self;
}

- awake
{
	id ret = [super awake];
	[self _cacheImages];
	return ret;
}

#define _SETUPIMAGE PSsetgray(0.0); PSsetalpha(0.0); \
		NXRectFill(&frame); PSsetalpha(1.0);

- _cacheImages
{	// builds the cached slider, etc., if necessary.  (ie, size changed
	// or no cache created yet... *****
	NXSize knobSize, bezelSize = { radius * 2, radius * 2 };
	NXRect frame = { { 0.0, 0.0 }, bezelSize };

    // don't cache for other styles -Carl
  	if (style != MISC_SLIDER_STYLE) return self;
		
	if (disabledBezelImage) [disabledBezelImage free];
	disabledBezelImage = [[NXImage alloc] initSize:&bezelSize];
	[disabledBezelImage lockFocus];
	PSgsave(); _SETUPIMAGE;
	MiscCSDrawBezel(0, 0, radius, sliderPathWidth, 0.666);
	PSgrestore();
	[disabledBezelImage unlockFocus];

	if (enabledBezelImage) [enabledBezelImage free];
	enabledBezelImage = [[NXImage alloc] initSize:&bezelSize];
	[enabledBezelImage lockFocus];
	PSgsave(); _SETUPIMAGE;
	MiscCSDrawBezel(0, 0, radius, sliderPathWidth, 0.5);
	PSgrestore();
	[enabledBezelImage unlockFocus];

	knobSize.width  = sliderPathWidth + _KNOBSIZE_FUDGE;
	knobSize.height = sliderPathWidth + _KNOBSIZE_FUDGE;
	if (knobImage) [knobImage free];
	knobImage = [[NXImage alloc] initSize:&knobSize];
	[knobImage lockFocus];
	PSgsave(); _SETUPIMAGE;
	MiscCSDrawKnob(0,0, sliderPathWidth + _KNOBSIZE_FUDGE);
	PSgrestore();
	[knobImage unlockFocus];
	return self;
}

- setSliderPathWidth:(float)val
{
	if (val == sliderPathWidth) return self;
	sliderPathWidth = val;
	[self _cacheImages];
	return self;
}

- (float)sliderPathWidth
{
    return sliderPathWidth;
}

- setSliderStyle:(MiscCircularSliderStyle)aBOOL
{
	if (aBOOL == style) return self;
	style = aBOOL;
	[self _cacheImages];
    return self;
}

- (MiscCircularSliderStyle)sliderStyle
{
    return style;
}

- setJumpToMousePoint:(BOOL)aBOOL
{
    jumpToMousePoint = aBOOL;
    return self;
}

- (BOOL)jumpToMousePoint
{
    return jumpToMousePoint;
}

- setHidden:(BOOL)flag
{
    hidden = flag;
    return self;
}

- (BOOL)hidden
{
    return hidden;
}

- setUpperStopValue:(float)aValue
{
	upperStopValue = aValue;
	return self;
}

- (float)upperStopValue
{
	return upperStopValue;
}

- setLowerStopValue:(float)aValue
{
	lowerStopValue = aValue;
	return self;
}

- (float)lowerStopValue
{
	return lowerStopValue;
}

- setUseBoundaries:(BOOL)flag
{
	useBoundaries = flag;
	return self;
}

- (BOOL)useBoundaries
{
	return useBoundaries;
}

- read:(NXTypedStream *)stream
{
//  Looks like version 1 added upper/lower stop values, version 2 added an
//  option to turn that feature off.
//  Version 3 adds border type and a background color.
//  Version 4 adds startAngle  -Carl
    [super read:stream];
	switch (NXTypedStreamClassVersion(stream, "MiscCircularSliderCell")) {
 		case MISC_SLIDERCELL_CURRENT_VERSION:
			NXReadTypes(stream, "iccffffcifi",
				&style,
				&jumpToMousePoint,
				&hidden,
				&radius,
				&sliderPathWidth,
				&lowerStopValue,
				&upperStopValue,
				&useBoundaries,
				&borderType,
				&startAngle,
				&isClockwise);
			NXReadPoint(stream, &center);
			backgroundColor = NXReadColor(stream);
			break;
 		case 3:
			NXReadTypes(stream, "iccffffci",
				&style,
				&jumpToMousePoint,
				&hidden,
				&radius,
				&sliderPathWidth,
				&lowerStopValue,
				&upperStopValue,
				&useBoundaries,
				&borderType);
			NXReadPoint(stream, &center);
			backgroundColor = NXReadColor(stream);
			startAngle = (style == MISC_PIECHART_STYLE) ? 90 : 0;
			isClockwise = (style == MISC_PIECHART_STYLE) ? YES : NO;
			break;
		case 2:
			NXReadTypes(stream, "iccffffc",
				&style,
				&jumpToMousePoint,
				&hidden,
				&radius,
				&sliderPathWidth,
				&lowerStopValue,
				&upperStopValue,
				&useBoundaries);
			NXReadPoint(stream, &center);
			borderType = MISC_CSNONE;
			backgroundColor = MISC_CSDEFAULTCOLOR;
			startAngle = (style == MISC_PIECHART_STYLE) ? 90 : 0;
			isClockwise = (style == MISC_PIECHART_STYLE) ? YES : NO;
			break;
		case 1:
			NXReadTypes(stream, "iccffff",
				&style,
				&jumpToMousePoint,
				&hidden,
				&radius,
				&sliderPathWidth,
				&lowerStopValue,
				&upperStopValue);
    		NXReadPoint(stream, &center);
			useBoundaries = NO;
			borderType = MISC_CSNONE;
			backgroundColor = MISC_CSDEFAULTCOLOR;
			startAngle = (style == MISC_PIECHART_STYLE) ? 90 : 0;
			isClockwise = (style == MISC_PIECHART_STYLE) ? YES : NO;
			break;
		case 0:        /* read code for old version */
			NXReadTypes(stream, "iccff",
				&style, &jumpToMousePoint, &hidden, &radius, &sliderPathWidth);
		    NXReadPoint(stream, &center);
			upperStopValue = maxValue;
			lowerStopValue = minValue;
			useBoundaries = NO;
			borderType = MISC_CSNONE;
			backgroundColor = MISC_CSDEFAULTCOLOR;
			startAngle = (style == MISC_PIECHART_STYLE) ? 90 : 0;
			isClockwise = (style == MISC_PIECHART_STYLE) ? YES : NO;
			break;
 	}
    return self;
}

- write:(NXTypedStream *)stream
{
    [super write:stream];
    NXWriteTypes(stream, "iccffffcifi",
		&style,
		&jumpToMousePoint,
		&hidden,
		&radius,
		&sliderPathWidth,
		&lowerStopValue,
		&upperStopValue,
		&useBoundaries,
		&borderType,                        /*NEW for version 3*/
		&startAngle,                        /*NEW for version 4*/
		&isClockwise);                      /*NEW for version 4*/
    NXWritePoint(stream, &center);
	NXWriteColor(stream, backgroundColor);  /*NEW for version 3*/
    return self;
}

// overridden methods here:
- calcCellSize:(NXSize *)theSize inRect:(const NXRect *)aRect
{
	theSize->width = NX_WIDTH(aRect);
	theSize->height = NX_HEIGHT(aRect);

	if (style != MISC_SLIDER_STYLE) return self;
	// the "slider looking" style should always be a perfect circle.
	if (theSize->height < theSize->width) theSize->width = theSize->height;
	else theSize->height = theSize->width;
	return self;
}

#define _FINDXYFROMPOINT(z) \
        if (imFlipped) \
          y = NX_HEIGHT(&_lastViewFrame) - (z)->y - center.y; \
        else y = (z)->y - center.y; \
        x = (z)->x - center.x;

- (BOOL)startTrackingAt:(const NXPoint *)startPoint inView:controlView
{ // make sure we're within the slider's "hot" area, a circle (or ring)
	float x, y, rad, ir;

	_FINDXYFROMPOINT(startPoint);
	_offsetAng = angle(x, y) - currentangle(value, minValue, maxValue);
	rad = x * x + y * y;
	if (rad > radius * radius) return NO; // outside
	ir = radius - sliderPathWidth - 3.0;
	// If we are the slider style, don't respond to a click on the inner
	// circle.  The second condition checks if the knob width is larger
	// than the radius, meaning there is no inner circle and no reason
	// to disallow the click.  -Carl
	if ((style==MISC_SLIDER_STYLE) && (radius > sliderPathWidth) && (rad < ir*ir))
		return NO;
	return [super startTrackingAt:startPoint inView:controlView];  //return YES?
}

- (BOOL)continueTracking:(const NXPoint *)lastPoint
		at:(const NXPoint *)currentPoint inView:controlView
{
	float x, y, oldx, oldy, curAng, oldAng;

	if (hidden) return NO;

	// get the current angle of the mouse pointer
	_FINDXYFROMPOINT(currentPoint);
	curAng = angle(x, y);
	if (!jumpToMousePoint) {
		curAng -= _offsetAng;
		if (curAng < 0) curAng += 360.0;
	}
	// get the last angle of the mouse pointer
	oldAng = currentangle(value, minValue, maxValue);

	value = position(curAng, minValue, maxValue);
	
	// Do the boundary check; if we are outside those bounds, set
	// the current value to be the bound the mouse is closest to. -Carl
	if (useBoundaries && ((value > upperStopValue) || (value < lowerStopValue))) {
		NXPoint upoint, lpoint, vpoint;
		xycoord(currentangle(lowerStopValue,minValue,maxValue),radius,&lpoint.x,&lpoint.y);
		xycoord(currentangle(upperStopValue,minValue,maxValue),radius,&upoint.x,&upoint.y);
		xycoord(curAng,radius,&vpoint.x,&vpoint.y);
		
		if (distBetween(vpoint,lpoint) < distBetween(vpoint,upoint)) {
			value = lowerStopValue;
			curAng = currentangle(lowerStopValue, minValue, maxValue);
		}
		else {
			value = upperStopValue;
			curAng = currentangle(upperStopValue, minValue, maxValue);
		}
	}
	
	[[controlView window] disableFlushWindow];
	PSgsave();
	if (imFlipped) {
		PSscale(1.0, -1.0);
		PStranslate(0.0, -NX_HEIGHT(&_lastViewFrame));
	}
	if (style == MISC_SLIDER_STYLE) {
		// Need to erase the old knob, which is done be re-compositing just that
		// section.  The other styles do their own erasing in -drawKnob.
		// Have to do this here, because it is only here we have the info
		// on where the last knob position was, which the other styles don't need.
		NXRect eraseRect; NXPoint imgPoint;
		id bezel = (cFlags1.disabled ? disabledBezelImage : enabledBezelImage);
		xycoord(oldAng, radius - sliderPathWidth / 2 - 2, &oldx, &oldy);
		oldx += center.x - sliderPathWidth * _KNOB_WIDTH_FUDGE + _KNOB_X_FUDGE;
		oldy += center.y - sliderPathWidth * _KNOB_WIDTH_FUDGE + _KNOB_Y_FUDGE;
		NXSetRect(&eraseRect, oldx, oldy,
				sliderPathWidth * 1.5 + 2, sliderPathWidth * 1.5 + 2);
		NXIntegralRect(&eraseRect);
		NXInsetRect(&eraseRect, -4.0, -4.0);
		imgPoint.x = NX_X(&eraseRect) + NX_X(&_lastFrame);
		imgPoint.y = NX_Y(&eraseRect) + NX_Y(&_lastFrame);
		[bezel composite:NX_SOVER fromRect:&eraseRect toPoint:&(imgPoint)];
	}
	PSgrestore();

	[self drawKnob:&_lastFrame];  // -drawKnob ignores its given rect; use as dummy.
	[[[controlView window] reenableFlushWindow] flushWindow];
	NXPing();
	return YES;
}


// The basic drawing methods...

#define _COPYRECT(x, y) NXSetRect((x), \
		NX_X(y), NX_Y(y), NX_WIDTH(y), NX_HEIGHT(y));

// This should draw the whole slider.  In here, we draw the
// border, then the background color second (so to wipe out
// any gray drawn by the border functions).  We then inset
// the given rect a bit to account for the border, and call
// -drawBarInside, which will draw the rest (via -drawKnob).
// Hidden will just draw background color and return.
// The reason I draw to a temporary NXImage here is so that
// a background color with alpha in it will draw correctly.
// When I drew directly to the view, the background would
// start out as black, then NX_SOVER-like draw the background
// color.  Fully transparent would end up black, and semi-
// transparent would be much darker than you would expect.
// This was the way I found to make it work.  A bit slower,
// perhaps, but this method shouldn't get called all _that_
// often. :-)  -Carl

- drawSelf:(const NXRect *)cellFrame inView:controlView
{
	NXRect ourRect;
	NXPoint pt = cellFrame->origin;	
	NXImage *img = [[NXImage alloc] initSize:&(cellFrame->size)];

	_COPYRECT(&ourRect,cellFrame);
	_COPYRECT(&_lastViewFrame,cellFrame);
	if ([controlView isFlipped]) imFlipped = YES;

	[img lockFocus];
	
	if (!hidden) switch (borderType) {
		case MISC_CSGROOVE:
			NXDrawGroove(&ourRect, &ourRect);
			NXInsetRect(&ourRect,2.0,2.0);
			break;
		case MISC_CSBEZEL:
			NXDrawGrayBezel(&ourRect, &ourRect);
			NXInsetRect(&ourRect,2.0,2.0);
			break;
		case MISC_CSLINE:
			PSsetgray(0.0);
			NXFrameRect(&ourRect);
			NXInsetRect(&ourRect,1.0,1.0);
			break;
		case MISC_CSBUTTON:
			NXDrawButton(&ourRect, &ourRect);
			NXInsetRect(&ourRect,2.0,2.0);
			break;
		case MISC_CSNONE:
		default:break;
	}
	NXSetColor(backgroundColor); NXRectFill(&ourRect);
	[img unlockFocus];
	if (imFlipped) pt.y += NX_HEIGHT(cellFrame);
	[img composite:NX_SOVER toPoint:&pt];
	[img free];

	// I want to put this code inside of -drawBarInside, but
	// when I do, it thinks enabledBezelImage is a Window class
	// object!!  [I get a "Window does not respond to
	// -composite:fromPoint:" runtime error in the switch
	// statement in -drawBarInside.]  I am *clueless* as to
	// why that's true.  It works if I put this code here, though.
	if (!NXEqualRect(&_lastFrame, &ourRect)) {
		_COPYRECT(&_lastFrame, &ourRect);
		radius = MIN(NX_WIDTH(&ourRect), NX_HEIGHT(&ourRect)) / 2;
		center.x = NX_X(&ourRect) + radius;
		center.y = NX_Y(&ourRect) + radius;
		[self _cacheImages];
	}

	[self drawInside:&ourRect inView:controlView];
	return self;
}


// This method should basically draw the slider, sans border and
// background color, inside cellFrame.  It resets some instance
// vars, draws whatever slider background is necessary, then calls
// -drawKnob to do the rest.
- drawBarInside:(const NXRect *)cellFrame flipped:(BOOL)flipped
{
	NXImage *bezel = (cFlags1.disabled ? disabledBezelImage : enabledBezelImage);
	float r4 = radius - _BEZELS_FUDGE;

	// reset some instance vars....

/*  //WHY DOESN'T THIS WORK HERE!?!?!?!?
	if (!NXEqualRect(&_lastFrame, cellFrame)) {
		_COPYRECT(&_lastFrame, cellFrame);
		radius = MIN(NX_WIDTH(cellFrame), NX_HEIGHT(cellFrame)) / 2;
		center.x = NX_X(cellFrame) + radius;
		center.y = NX_Y(cellFrame) + radius;
		[self _cacheImages];
	}
 */
	if (hidden) return self;
	
	PSgsave();
	if (flipped) {
		PSscale(1.0, -1.0);
		PStranslate(0.0, -NX_HEIGHT(&_lastViewFrame));
		imFlipped = YES;
	}
	switch(style) {
		case MISC_SLIDER_STYLE:
			[bezel composite:NX_SOVER toPoint:&(cellFrame->origin)];
			break;
		case MISC_PIECHART_STYLE:
			if (cFlags1.disabled) break;	//otherwise do the background below
		case MISC_DIAL_STYLE:
		case MISC_SHUTTLEWHEEL_STYLE:
			MiscCSDrawBackground(center.x, center.y, r4, radius, radius);
			break;
	}

	PSgrestore();
	[self drawKnob:cellFrame];	//-drawKnob ignores given rect, use as dummy.
	return self;
}


// This method will draw the filled piepiece/line/knob of the slider.
// It actually ignores the knobRect parameter; it is only there because
// this is the correct method to override.
- drawKnob:(const NXRect *)knobRect
{
	NXPoint pos;
	float ang, r4 = radius - _BEZELS_FUDGE;
	NXSize knobSize;

	if (hidden) return self;

	PSgsave();
	if (imFlipped) {
		PSscale(1.0, -1.0);
		PStranslate(0.0, -NX_HEIGHT(&_lastViewFrame));
	}
	ang = currentangle(value, minValue, maxValue);
	switch (style) {
		case MISC_SLIDER_STYLE:
			// Composite the knobImage to the right spot. 
			[knobImage getSize:&knobSize];
			xycoord(ang, (radius-2) - (sliderPathWidth / 2) + _KNOBPOS_RADIUS_FUDGE, &pos.x, &pos.y);
			pos.x += center.x - knobSize.width / 2.0 + _KNOBPOS_X_FUDGE;
			pos.y += center.y - knobSize.height / 2.0 + _KNOBPOS_Y_FUDGE;
			[knobImage composite:NX_SOVER toPoint:&pos];
			break;
		case MISC_PIECHART_STYLE: {
			// With this style, I want the gray area to start with startAngle and go to
			// the current angle.  Which one to start with depends on whether it's clockwise.
			// So, just set the two params based on if it's clockwise.
			float startang = (isClockwise)? ang:startAngle;
			float finishang= (isClockwise)? startAngle:ang;

			if (cFlags1.disabled) {
				MiscCSPieChartDisabled(center.x, center.y, r4, radius, radius, startang, finishang);
			} else {
				MiscCSPieChart(center.x, center.y, r4, radius, radius, startang, finishang);
			}
			break;
		}
		case MISC_DIAL_STYLE:
			MiscCSControlDial(center.x, center.y, r4, radius, radius,
					ang, (cFlags1.disabled ? 0.333 : 0.0));
			break;
		case MISC_SHUTTLEWHEEL_STYLE:
			if (cFlags1.disabled) {
				MiscCSControlKnobDisabled(center.x, center.y, r4, radius, radius);
			} else {
				// Removed the following function call to allow adjustment of the
				// control know width by the following one. -Laurent Daudelin.
				//MiscCSControlKnob(center.x, center.y, r4, radius, radius, ang);
				MiscCSDrawShuttleKnob(center.x, center.y, radius, sliderPathWidth, ang);
			}
			break;
	}
	PSgrestore();
	return self;
}


// I think this is what the superclass does anyways, but let's make sure
- drawInside:(const NXRect *)cellFrame inView:controlView
{
	[self drawBarInside:cellFrame flipped:imFlipped];
	return self;
}

- getKnobRect:(NXRect *)knobRect flipped:(BOOL)flipped
{
	[super getKnobRect:knobRect flipped:flipped];
	return self;
}

- setBorderType:(int)type
{
	borderType = type;
	return self;
}

- (int)borderType
{
	return borderType;
}

- setBackgroundColor:(NXColor)aColor
{
	backgroundColor = aColor;
	return self;
}

- (NXColor)backgroundColor
{
	return backgroundColor;
}

- (float)startAngle
{
	return startAngle;
}

- setStartAngle:(float)ang
{
	startAngle = in360(ang);
	return self;
}

- (BOOL)isClockwise
{
	return isClockwise;
}

- setClockwise:(BOOL)clockwise
{
	isClockwise = clockwise;
	return self;
}

@end

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.