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.