This is CubicSliderCell.m in view mode; [Download] [Up]
/* * * * CubicSlider 1.3, subclassed by Raymond Lutz mai 1993, * class description in the accompaining * file CubicSliderCell.rtf * */ #import <dpsclient/psops.h> #import <appkit/publicWraps.h> #import <math.h> #import <appkit/graphics.h> #import <appkit/Application.h> #import <appkit/Form.h> #import <appkit/Button.h> #import <appkit/ButtonCell.h> #import <appkit/Box.h> #import <appkit/Text.h> #import <appkit/Font.h> #import "CubicSliderCell.h" #import <objc/objc.h> #define ONETHIRD (double)0.3333333333333 #define MINEDITDEFAULT YES #define MAXEDITDEFAULT YES #define CUBICDEFAULT YES #define INAFEWSECS (float)0.001 #define XFORM (float)15 #define WFORM (float)52 #define XLEFTBUTT (float)0 #define XRIGHTBUTT (float)70 #define WIDTHBUTT (float)15 #define WTOTAL (float)85 // _______________________________________ SSSSSTTTTRRRREEETTTTCCCHH _ _ _ _ _ _ _ _ _ _ _ _ _ _ > @implementation CubicSliderCell {} - init { id retVal; retVal = [super init]; [self calcNewFct]; [self setCubic: CUBICDEFAULT]; [self setMinEditable: MINEDITDEFAULT]; [self setMaxEditable: MAXEDITDEFAULT]; [self clearAbsoluteMaxValue]; [self clearAbsoluteMinValue]; yFlat=value; return retVal; } - (BOOL) maxIsEditable; { return maxIsEditable; } - (BOOL) minIsEditable; { return minIsEditable; } - setMinEditable:(BOOL) aFlag; { minIsEditable = aFlag; return self; } - setMaxEditable:(BOOL) aFlag; { maxIsEditable = aFlag; return self; } - (BOOL) absoluteMaxValueSet; { return absoluteMaxValueSet; } - (BOOL) absoluteMinValueSet; { return absoluteMinValueSet; } - clearAbsoluteMaxValue; { absoluteMaxValueSet = NO; absoluteMaxValue = maxValue; return self; } - clearAbsoluteMinValue; { absoluteMinValueSet = NO; absoluteMinValue = minValue; return self; } - setAbsoluteMaxValue:(double) aDouble; { absoluteMaxValueSet = YES; absoluteMaxValue = aDouble; if (maxValue > absoluteMaxValue) [self setMaxValue: absoluteMaxValue]; return self; } - setAbsoluteMinValue:(double) aDouble; { absoluteMinValueSet = YES; absoluteMinValue = aDouble; if (minValue < absoluteMinValue) [self setMinValue: absoluteMinValue]; return self; } - (double) absoluteMaxValue; { return absoluteMaxValue; } - (double) absoluteMinValue; { return absoluteMinValue; } - setMinValue: (double) val { id retval; double bufCubicVal = [self doubleValue]; retval=[super setMinValue:val]; if (absoluteMinValueSet && val < absoluteMinValue) [self setAbsoluteMinValue: val]; if (cubic) [self setDoubleValue: bufCubicVal]; return retval; } - setMaxValue: (double) val { id retval; retval=[super setMaxValue:val]; if (absoluteMaxValueSet && val > absoluteMaxValue) [self setAbsoluteMaxValue: val]; if (cubic) [self calcNewFct]; return retval; } - setTarget:anObject { target = anObject; return self; } - setAction:(SEL)anAction { action = anAction; return self; } - newCover { id font = [Font newFont: "Helvetica" size: 8.0]; NXRect knobButtonFrame, quadButtonFrame; NXRect formFrame, minMaxViewFrame; NXSetRect(&formFrame, XFORM,0,WFORM,16); form=[[[[Form alloc] initFrame: &formFrame] setFont: font] setBackgroundGray:NX_LTGRAY]; [form addEntry:" " tag:0 target:self action:@selector(takeFormValueForNew)]; [[form setTextAlignment:NX_RIGHTALIGNED] sizeToFit]; NXSetRect(&knobButtonFrame, 0,0, WIDTHBUTT,16); knobButton=[[Button alloc] initFrame: &knobButtonFrame icon:"NXscrollMenuDown" tag: 0 target: self action: @selector(takeCurrentSliderValueForNew) key: '\0' enabled:YES]; [[knobButton setFont:font] setAlignment: NX_CENTERED]; NXSetRect(&quadButtonFrame, 0,0, WIDTHBUTT,16); quadButton=[[Button alloc] initFrame: &quadButtonFrame title: "<" tag: 0 target: self action: @selector(quadRange) key: '\0' enabled:YES]; [[quadButton setFont:font] setAlignment: NX_CENTERED]; NXSetRect(&minMaxViewFrame, 0,0, WTOTAL,16); minMaxView=[[View alloc] initFrame: &minMaxViewFrame]; cover=[[[Button alloc] initFrame: &minMaxViewFrame title: "" tag: 0 target: self action: (SEL)0 key: '\0' enabled:YES] setBordered: NX_NONE]; [[cover cell] setHighlightsBy: NX_NONE]; [minMaxView addSubview:knobButton]; [minMaxView addSubview:quadButton]; [minMaxView addSubview:form]; [cover addSubview:minMaxView]; return self; } - drawSelf:(const NXRect *)cellFrame inView:controlView { if (editing) { PSsetgray(NX_LTGRAY); NXRectFill(cellFrame); [minMaxView display]; return self; } else return [super drawSelf: cellFrame inView:controlView]; } - drawInside:(const NXRect *)cellFrame inView:controlView { if (editing) { PSsetgray(NX_LTGRAY); NXRectFill(cellFrame); [minMaxView display]; return self; } else return [super drawInside: cellFrame inView:controlView]; } - (BOOL)trackMouse:(NXEvent *)theEvent inRect:(const NXRect *)cellFrame ofView:controlView { NXRect knobFrame; BOOL retval; BOOL isFlipped = NO; BOOL onTheKnob; int AltKeyPressed = (theEvent->flags & NX_ALTERNATEMASK); int nClicks = theEvent->data.mouse.click; NXPoint aPoint = theEvent->location; float mouseX ; float leftKnobSide,rightKnobSide,midKnob; BOOL dbleClick = (nClicks == 2); [controlView convertPoint:&aPoint fromView:nil]; mouseX = aPoint.x; [self getKnobRect:&knobFrame flipped:isFlipped]; leftKnobSide = NX_X(&knobFrame); rightKnobSide = NX_MAXX(&knobFrame); midKnob = (rightKnobSide+leftKnobSide)/2; onTheKnob = (leftKnobSide <= mouseX && mouseX <= rightKnobSide); if (AltKeyPressed) { if (dbleClick) { [self flipCubicFlag]; [self notifyTargets]; } return NO; // don't track } if (dbleClick && onTheKnob && previousClickOnTheKnob) { extremumType = (mouseX > midKnob); if (extremumType && maxIsEditable || !extremumType && minIsEditable) { // enter editing mode here [self popFormAndButtons]; } else NXBeep(); // no editing: beep. return NO; // don't track, we pop (don't we?) } previousClickOnTheKnob = onTheKnob; if (nClicks > 2) return NO; // prevents knob to reappear over retval=[super trackMouse:theEvent inRect:cellFrame ofView:controlView]; if (upTarget) [controlView sendAction:upAction to:upTarget]; return retval; } - (BOOL)startTrackingAt:(const NXPoint *)startPoint inView:controlView { mouseIsUp=NO; return [super startTrackingAt: startPoint inView:controlView]; } - stopTracking:(NXPoint *)lastPoint at:(NXPoint *)endPoint inView:controlView mouseIsUp:(BOOL)flag { mouseIsUp = flag; [self doubleValue]; yFlat = [self doubleValue]; // records the chosen value if (cubic && [super isContinuous]) [self calcNewFct]; return self; } - setCubic: (BOOL) aFlag { cubic = aFlag; return self; } - flipCubicFlag; { cubic = !cubic; return self; } - (BOOL) isCubic; { return cubic; } - (BOOL) mouseIsUp { return mouseIsUp; } - setEnabled: (BOOL) aFlag { if (editing) { [quadButton setIcon: aFlag ? "NXscrollMenuRight":"NXscrollMenuRightD"]; [knobButton setIcon: aFlag ? "NXscrollMenuDown":"NXscrollMenuDownD"]; [quadButton setEnabled:aFlag]; [knobButton setEnabled:aFlag]; [form setEnabled:aFlag]; } return [super setEnabled:aFlag]; } - setIntValue:(int) anInt { yFlat = anInt; if (cubic) [self calcNewFct]; else [super setIntValue: anInt]; return self; } - setFloatValue:(float) aFloat { yFlat = aFloat; if (cubic) [self calcNewFct]; else [super setFloatValue:aFloat]; return self; } - setDoubleValue:(double)aValue { yFlat = aValue; if (cubic) [self calcNewFct]; else [super setDoubleValue:aValue]; return self; } - (float) floatValue { float aFloat=[self doubleValue]; return aFloat; } - (int) intValue { int anInt=[self doubleValue]; return anInt; } - (double) doubleValue /* * It's here that we modify the true slider value to be non-linear, * that is, pass it through a simple cubic polynomial of the form * y=x^3, adequately x and y translated to have the flat region around the * present (seen from the target) slider value and scaled to passes by * the points (minValue,minValue) and (maxValue,maxValue). Those parameters, * A, xFlat and yFlat are recalculated in calcNewFct on each mouseUp. * */ { double doubleValue = cubic ? (A*pow((value - xFlat),3.0) + yFlat):value; doubleValue = (doubleValue > maxValue) ? maxValue: doubleValue; doubleValue = (doubleValue < minValue) ? minValue: doubleValue; return doubleValue; } - (int) linearIntValue; { return [super intValue]; } - (float) linearFloatValue; { return [super floatValue]; } - (double) linearDoubleValue; { return [super doubleValue]; } - setUpTarget:anObject action:(SEL)anAction { upTarget = anObject; upAction = anAction; return self; } - setUpTarget:anObject { upTarget = anObject; return self; } - setUpAction:(SEL)anAction { upAction = anAction; return self; } - upTarget; { return upTarget; } - (SEL)upAction; { return upAction; } // inner work methods - calcNewFct /* * Before looking at this, see (double)doubleValue above. * * Here, given minValue, maxValue and the chosen slider value * we calculate the cubic parameters so that the function * * y = A*(x - xFlat)^3 + yFlat * * [1] passes through (minValue,minValue) * [2] passes through (maxValue,maxValue) * [3] has its flatest point at y = "chosen slider value" * */ { double epsilon; // a twice used quantity /* * The expression for A has an inf/inf indetermination when the knob * hits the min or max value, so there are TWO ways of determine it. */ if (yFlat > (minValue + maxValue)/2.0) { // here yFlat -> maxValue and epsilon -> 0 if (yFlat > maxValue) yFlat = maxValue; epsilon = pow((maxValue - yFlat) /(yFlat - minValue), ONETHIRD); xFlat = (maxValue + epsilon *minValue)/(1.0 + epsilon); } else { // here yFlat -> minValue and still epsilon -> 0 if (yFlat < minValue) yFlat = minValue; epsilon = pow((yFlat - minValue) /(maxValue - yFlat), ONETHIRD); xFlat = (minValue + epsilon * maxValue)/(1.0 + epsilon); } A=(maxValue-minValue)/(pow((maxValue-xFlat),3.0)+pow((xFlat-minValue),3.0)); [super setDoubleValue: xFlat]; return self; } - popFormAndButtons { float rangeFraction; NXRect frame; BOOL doubleIsOk,knobIsOK; NXSetRect(&frame , trackRect.origin.x-1, trackRect.origin.y-1, trackRect.size.width+2,trackRect.size.height+2); if (NX_WIDTH(&frame) >= WTOTAL) { // the slider is big enough to pop, do it editing = YES; if (!cover) [self newCover]; [cover setFrame: &frame]; if (extremumType) { [[form setTitle: "max" at:0] setFloatValue: maxValue at:0]; knobIsOK = (yFlat != minValue); doubleIsOk = !absoluteMaxValueSet || (maxValue != absoluteMaxValue); [quadButton setIcon: doubleIsOk ? "NXscrollMenuRight":"NXscrollMenuRightD"]; } else { [[form setTitle: "min" at:0] setFloatValue: minValue at:0] ; knobIsOK = (yFlat != maxValue); doubleIsOk = !absoluteMaxValueSet || (minValue != absoluteMinValue); [quadButton setIcon: doubleIsOk ? "NXscrollMenuLeft":"NXscrollMenuLeftD"]; } [knobButton setEnabled: knobIsOK]; [knobButton setIcon: knobIsOK ? "NXscrollMenuDown":"NXscrollMenuDownD"]; [quadButton setEnabled: doubleIsOk]; // the following is there so the right button will fall under the mouse at the right time... // depending on wheter or not the knob hit the side and min or max editing // [minMaxView setFrame:&frame]; rangeFraction = (maxValue - minValue) / 30; if (extremumType) { [minMaxView moveTo: NX_WIDTH(&frame) - WTOTAL:0]; if (value >= maxValue - rangeFraction) { [quadButton moveTo: XRIGHTBUTT :0]; [knobButton moveTo: XLEFTBUTT :0]; } else { [quadButton moveTo: XLEFTBUTT :0]; [knobButton moveTo: XRIGHTBUTT :0]; } } else { [minMaxView moveTo: 0:0]; if (value <= minValue + rangeFraction) { [quadButton moveTo: XLEFTBUTT:0]; [knobButton moveTo: XRIGHTBUTT:0]; } else { [quadButton moveTo: XRIGHTBUTT:0]; [knobButton moveTo: XLEFTBUTT:0]; } } // We're ready: pop! [[self controlView] addSubview: cover]; [[self controlView] updateCell: self]; [form selectTextAt:0]; } return self; } - takeCurrentSliderValueForNew { newExtremumValue = yFlat; [form setDoubleValue:yFlat at:0]; [self popCellAndMakeChangeSoon]; return self; } - quadRange { newExtremumValue = extremumType ? 4*maxValue - 3*minValue : 4*minValue - 3*maxValue ; [form setDoubleValue:newExtremumValue at:0]; [self popCellAndMakeChangeSoon]; return self; } - takeFormValueForNew { newExtremumValue = [form floatValueAt:0]; [self popCellAndMakeChangeSoon]; return self; } void IllBeBack (teNum, now, cubicSlider) DPSTimedEntry teNum; double now; id cubicSlider; { [[cubicSlider popCellAndMakeChangeNow] stopTimedEntry]; return; } - popCellAndMakeChangeSoon { if (!teNum) teNum = DPSAddTimedEntry(INAFEWSECS, &IllBeBack, self, NX_BASETHRESHOLD); return self; } /* * Why this TimedEntry? because we have buttons which actions have as effect * to eventually zap themselves from the view hierarchy... but since they send their * message _before_ finishing to draw they are still lockfocused... we must * let them redraw and zap them later. */ - popCellAndMakeChangeNow { double oldValue; [cover removeFromSuperview]; // it's OK now: we're later editing = NO; [[self controlView] updateCell:self]; if (extremumType) { if (newExtremumValue < minValue) {NXBeep(); return self;} if (absoluteMaxValueSet && newExtremumValue > absoluteMaxValue) { newExtremumValue = absoluteMaxValue; NXBeep();} if (yFlat > newExtremumValue ) [self setDoubleValue:newExtremumValue]; //narrowing oldValue = maxValue; [self setMaxValue: newExtremumValue]; if (newExtremumValue != oldValue) [self notifyTargets]; } else { if (newExtremumValue > maxValue) {NXBeep(); return self;} if (absoluteMinValueSet && newExtremumValue < absoluteMinValue) { newExtremumValue = absoluteMinValue; NXBeep();} if (yFlat < newExtremumValue ) [self setDoubleValue:newExtremumValue]; //narrowing oldValue = minValue; [self setMinValue: newExtremumValue]; if (newExtremumValue != oldValue) [self notifyTargets]; } return self; } - notifyTargets { if ([target respondsTo: @selector(takeCubicSliderStatusFrom:)]) [target takeCubicSliderStatusFrom:self]; if (upTarget && [upTarget respondsTo: @selector(takeCubicSliderStatusFrom:)]) [upTarget takeCubicSliderStatusFrom:self]; return self; return self; } - stopTimedEntry { if (teNum) DPSRemoveTimedEntry (teNum); teNum = (DPSTimedEntry)0; return self; } - read:(NXTypedStream*)stream { [super read:stream]; upTarget = NXReadObject(stream); NXReadTypes(stream, "@iiiiiddddd", &upAction, &cubic, &absoluteMinValueSet, &absoluteMaxValueSet, &minIsEditable, &maxIsEditable, &absoluteMinValue, &absoluteMaxValue, &A, &yFlat, &xFlat); return self; } - write:(NXTypedStream*)stream { [super write:stream]; NXWriteObjectReference(stream, upTarget); NXWriteTypes(stream, "@iiiiiddddd", &upAction, &cubic, &absoluteMinValueSet, &absoluteMaxValueSet, &minIsEditable, &maxIsEditable, &absoluteMinValue, &absoluteMaxValue, &A, &yFlat, &xFlat); return self; } - (const char*)inspectorName { NXEvent *e = [NXApp currentEvent]; if (e->flags & NX_ALTERNATEMASK) return "SliderInspector"; return "CubicSliderInspector"; } -free { [minMaxView free]; return [super free]; } @end
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.