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.