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, ¢er);
backgroundColor = NXReadColor(stream);
break;
case 3:
NXReadTypes(stream, "iccffffci",
&style,
&jumpToMousePoint,
&hidden,
&radius,
&sliderPathWidth,
&lowerStopValue,
&upperStopValue,
&useBoundaries,
&borderType);
NXReadPoint(stream, ¢er);
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, ¢er);
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, ¢er);
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, ¢er);
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, ¢er);
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.