ftp.nice.ch/pub/next/graphics/apps/EnvelopeEd.1.04b.I.bs.tar.gz#/EnvelopeEd1.04b/Source/EnvelopeView.m

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

//////////////////////////////////////////////////////////////
//
// EnvelopeView.m -- Implementation for the EnvelopeView class
// Copyright 1991-94 Fernando Lopez Lezcano All Rights Reserved
//
//////////////////////////////////////////////////////////////

// Revision History:

// 8/25/92 Add parsing of exponential format numbers
// 8/21/92 Memory allocation bug on allocateTemp and allocateDraw
// 8/20/92 Added drawSegments option.

#import "EnvelopeView.h"
#import "Controller.h"

#import <stdlib.h>
#import <ctype.h>
#import <objc/zone.h>
#import <defaults/defaults.h>
#import <appkit/Application.h>
#import <musickit/musickit.h>            // for envelope class stuff
#import <dpsclient/wraps.h>              // for PSxxx stuff
#import <appkit/Form.h>
#import <appkit/Button.h>
#import <appkit/Panel.h>
#import <appkit/NXCursor.h>
#import <appkit/NXImage.h>

#import <appkit/Pasteboard.h>

#define    STYLE_MK       '0'
#define    STYLE_pX_Yp    '1'
#define    STYLE_pXcYp    '2'
#define    STYLE_X_Y      '3'
#define    STYLE_XcY      '4'
#define    STYLE_X_Y_Zc   '5'
#define    STYLE_XcYcZ_   '6'
#define	STYLE_ppX_Yp    '7'

#define TEST 1

#define KNOBSIZE 8
#define MAXNUMSEGS 8

#define WIDTH bounds.size.width
#define HEIGHT bounds.size.height

#define WHITE 1.0
#define BLACK 0.0
#define DKGRAY (1.0/3.0)
#define LTGRAY (2.0/3.0)
#define TRANSP 2.0

#define DRAG_MASK (NX_MOUSEUPMASK|NX_MOUSEDRAGGEDMASK)

//===================================================================
// Envelope auxiliar routines and macros
//===================================================================

//-------------------------------------------------------------------
// Draw a knob in the display

#define DRAWKNOB(x,y) PScompositerect(x-KNOBSIZE/2,y-KNOBSIZE/2,KNOBSIZE, KNOBSIZE,NX_SOVER)

//-------------------------------------------------------------------
// Translate from x/y envelope values to display pixel coordinates

#define xToPix(x) (((x-xMin)/(xMax-xMin))*WIDTH)
#define yToPix(y) (((y-yMin)/(yMax-yMin))*HEIGHT)

//-------------------------------------------------------------------
// Translate from display pixel coordinates to x/y envelope values

#define pixToX(xPix) (xPix/WIDTH*(xMax-xMin)+xMin)
#define pixToY(yPix) (yPix/HEIGHT*(yMax-yMin)+yMin)

//-------------------------------------------------------------------
// Shorthand to access envelope object instance variables

#define pointCount [theEnvelope pointCount]
#define stickyPoint [theEnvelope stickPoint]
#define xValues [theEnvelope xArray]
#define yValues [theEnvelope yArray]
#define sValues [theEnvelope smoothingArray]

//-------------------------------------------------------------------
// Temporary copy arrays for envelope copy operations

typedef struct _env {
    double *x;                    // x array for adding or removing points
    double *y;                    // y array
    double *s;                    // smoothing array
    int max;                      // currently allocated length of arrays
} Env;

static Env * temp=NULL;           // only one copy for each envelope object

// Allocate or reallocate arrays to "size" elements

void allocateTemp(int size)
{
    int newsize=size;
    if (newsize<64) newsize=64;
    if (temp==NULL)    {                   // initial creation of arrays
        temp=malloc(sizeof(Env));
        temp->x=malloc(sizeof(double)*newsize);
        temp->y=malloc(sizeof(double)*newsize);
        temp->s=malloc(sizeof(double)*newsize);
        temp->max=newsize;
    }
    else if (size>temp->max) {            // grow to double size each time
        if (size>temp->max*2) 
            temp->max=size;
	else 
	    temp->max*=2;
        temp->x=realloc(temp->x,sizeof(double)*temp->max);
        temp->y=realloc(temp->y,sizeof(double)*temp->max);
        temp->s=realloc(temp->s,sizeof(double)*temp->max);
    }
}

// Free allocated memory

void freeTemp()
{
    if (temp!=NULL) {
        free(temp->x);
        free(temp->y);
        free(temp->s);
        free(temp);
        temp=NULL;
    }
}

//-------------------------------------------------------------------
// Temporary arrays for drawing operations

typedef struct _draw {
    int num;                    // number of points in draw arrays (x,y,p)
    int *p;                     // point number array
    double *x;                  // x array of drawing coordinates
    double *y;                  // y array
    double *yr;                 // real y coordinate of each point 
    int max;                    // currently allocated length
} Draw;

// Each time the envelope is drawn in the screen the calculated positions
// of all intermediate points are stored in the x and y arrays. The point
// number is also stored in the p array. This arrays are then used to erase
// the lines by drawing them in white.

static Draw * draw=NULL;

// Allocate or reallocate arrays to "size" elements

void allocateDraw(int size)
{
    int newsize=size;
    if (newsize<64) newsize=64;
    if (draw==NULL)    {
        draw=malloc(sizeof(Draw));
        draw->x=malloc(sizeof(double)*newsize*MAXNUMSEGS);
        draw->y=malloc(sizeof(double)*newsize*MAXNUMSEGS);
        draw->p=malloc(sizeof(int)*newsize*MAXNUMSEGS);
        draw->yr=malloc(sizeof(double)*newsize);
        draw->max=newsize;
        draw->num=0;
    }
    else if (size>draw->max) {
        if (size>draw->max*2) 
            draw->max=size;
	else 
	    draw->max*=2;
        draw->x=realloc(draw->x,sizeof(double)*draw->max*MAXNUMSEGS);
        draw->y=realloc(draw->y,sizeof(double)*draw->max*MAXNUMSEGS);
        draw->p=realloc(draw->p,sizeof(int)*draw->max*MAXNUMSEGS);
        draw->yr=realloc(draw->yr,sizeof(double)*draw->max);
    }
}

// Free allocated memory

void freeDraw()
{
    if (draw!=NULL) {
        free(draw->x);
        free(draw->y);
        free(draw->p);
        free(draw->yr);
        free(draw);
        draw=NULL;
    }
}

//===================================================================
@implementation EnvelopeView
//===================================================================

//-------------------------------------------------------------------
// - resetCursorRects sets a new cursor of the view. The message first
// initializes the two cursor shapes if needed and the proceeds to
// set the default cursor for the view to a cross.

- resetCursorRects
{
    NXRect visible;    
    NXPoint spot;
    
    if (!theCross) {
        theCross=[NXCursor newFromImage:[NXImage newFromSection:"cross.tiff"]];
        spot.x = 7.0; 
        spot.y = 7.0;
        [theCross setHotSpot:&spot];
    }
    if (!theFilledCross) {
        theFilledCross=[NXCursor newFromImage:[NXImage newFromSection:"crossfill.tiff"]];
        spot.x = 7.0; 
        spot.y = 7.0;
        [theFilledCross setHotSpot:&spot];
    }
    if ([self getVisibleRect:&visible]) {
        [self addCursorRect:&visible cursor: theCross];
    }
    return self;
}

//-------------------------------------------------------------------
// - selectPoint:n
// Selects point number n in the envelope and broadcasts the change
// to the controller object.

- selectPoint:(int)n
{
    selected=n;
    [self display];
    [theController update: self];
    return self;
}

//-------------------------------------------------------------------
// - initFrame:  -- initialize a new Envelope View object

NXDefaultsVector envelopeDefaults={
        {"defaultSmoothing", "1.0" },
        {"showSmoothing", "YES" },
        {"drawSegments", "YES" },
    };

- initFrame:(const NXRect *)frameRect
{
    static double xs[]={0.0,1.0};
    static double ys[]={0.0,0.0};
    static double ss[]={1.0,1.0};

    malloc_debug(0xFFFF);
    
    defaultSmooth=atof(NXGetDefaultValue("EnvelopeEd","defaultSmoothing"));
    ss[0]=ss[1]=defaultSmooth;
    self = [super initFrame: frameRect];
    
    theEnvelope=[[[Envelope alloc] init]
        setPointCount: 2 
        xArray: xs
        orSamplingPeriod: 1.0
        yArray: ys
        smoothingArray: ss
        orDefaultSmoothing: defaultSmooth];
    [theEnvelope setStickPoint: MAXINT];    // init new envelope to default values
    
    allocateDraw(64);                        // arrays for drawing and erasing
    allocateTemp(64);                        // temporary arrays for move operations
    userPath = newUserPath();                // allocate a user path for drawing
    
    xMin=yMin=0.0;                            // max and min limits of display
    xMax=yMax=1.0;
    xSnap=ySnap=0.001;                        // initial Snap off in both directions
    
    envColour=BLACK;
    if (strcasecmp(NXGetDefaultValue("EnvelopeEd","showSmoothing"),"YES")==0) 
        showSmooth=-1;
    if (strcasecmp(NXGetDefaultValue("EnvelopeEd","drawSegments"),"YES")==0) 
        drawSegments=-1;
    else showSmooth=0;
    [self selectPoint: 0];                    // select first point and display
    return self;
}

//-------------------------------------------------------------------
// - free  -- Release the allocated memory when object is freed

-free
{
    [theEnvelope free];
    freeUserPath(userPath);
    freeTemp();
    freeDraw();
    return [super free]; 
}

//-------------------------------------------------------------------
// - controllerIs:  Receive the object id of the controller of the view

- controllerIs:sender
{
    theController=sender;
    return self;
}

//-------------------------------------------------------------------
// - insertPointAt:p
// Finds the place in the envelope where the point p should be inserted.
// Returns the point number at the insertion or -1 if an error is
// detected. 

- (int) insertPointAt:(NXPoint)p
{
    int point;

    point=0;
    while (point<=pointCount-1) {
        if (xValues[point]>p.x)
            return point;
        point++;
    }
    return pointCount;
}

//-------------------------------------------------------------------
// - (int) hitKnobAt: p border: delta 
// Test if the user hits a knob when clicking the mouse at point p. 
// The boundaries of the zone are delta pixels wide. Returns the
// number of the hit point or -1 if no hit.

- (int) hitKnobAt:(NXPoint)p border:(float)delta;
{
    int point;
    float kx, ky, dx, dy;
    
    dx=delta/WIDTH*(xMax-xMin);
    dy=delta/HEIGHT*(yMax-yMin);
    for (point=0; point<pointCount; point++) {
        kx=xValues[point];
        ky=yValues[point];
        if (p.x<kx-dx)
            break;
        if (p.x<=kx+dx && p.x>=kx-dx &&
            p.y<=ky+dy && p.y>=ky-dy)
            return point;
    }
    return -1;
}

//-------------------------------------------------------------------
// drawKnobs(from,to,lcolour,hcolour) draws a set of knobs starting at 
// point from and ending at point to. Also highlights the selected
// knob if necessary. The second colour argument specifies the colour
// of the highlighted knob.

- drawKnobsFrom:(int)from to:(int)to in:(float)lcolour :(float)hcolour
{
    int point;
    
    PSsetgray(lcolour);
    for (point=from; point<=to; point++) {
        if (point!=selected)
            DRAWKNOB(xToPix(xValues[point]),yToPix(yValues[point]));
    }
    if (selected>=from && selected<=to && hcolour<=WHITE) {
        PSsetgray(hcolour);
        DRAWKNOB(xToPix(xValues[selected]),yToPix(yValues[selected]));
    }
    return self;
}

//-------------------------------------------------------------------
// drawSegments(from,to,colour) draws line segments starting at point
// from and ending at point to. It also takes care of the sticky point
// marker. Due to problems with rounding each time a path is drawn the
// moves are recorded in a set of arrays. This path is used to "erase"
// the segment when a point is moved.

- recordMovex:(double)x y:(double)y p:(int)n draw:(int)state
{
    double xp, yp;
    
    draw->p[draw->num]=n;
    draw->x[draw->num]=xp=xToPix(x);
    draw->y[draw->num++]=yp=yToPix(y);
    if (state==0)
        UPmoveto(userPath,xp,yp);
    else
        UPlineto(userPath,xp,yp);
    return self;
}

- drawSegmentsFrom:(int)from to:(int)to in:(float)colour
{
    int point, seg;
    double 
        incx, incy,
        deltax, deltay, 
        xi, xf, x, 
        yi, yf, y,
        smooth, numsegs;
        
    // If drawing colour is white then use erase arrays to remove 
    // the previously drawn segments
    
    if (colour==WHITE) {
        PSsetgray(colour);
        beginUserPath(userPath, NO);
        for (point=0; 
             draw->p[point]<=from && point<draw->num; 
             point++);
        if (point-1>=0) point--;
        UPmoveto(userPath,draw->x[point],draw->y[point]);
        for (; 
             draw->p[point]<=to && point<draw->num; 
             point++)
            UPlineto(userPath,draw->x[point],draw->y[point]);
        endUserPath(userPath, dps_ustroke);
        sendUserPath(userPath);
        return self;
    }

    // Else construct path for next erase and draw segments
    // first draw x and y axis if necessary
    
    draw->num=0;
    if (xMin!=0 || xMax!=0) {
        PSsetgray(LTGRAY);
        beginUserPath(userPath, NO);
        if (xMin!=0) {
            [self recordMovex: 0 y: yMin p: from draw: 0];
            [self recordMovex: 0 y: yMax p: from draw: 1];    
        }
        if (yMin!=0) {
            [self recordMovex: xMin y: 0 p: from draw: 0];
            [self recordMovex: xMax y: 0 p: from draw: 1];
        }
        endUserPath(userPath, dps_ustroke);
        sendUserPath(userPath);
    }
    
    // and then draw the segments that interconnect the breakpoints.
    
    if (drawSegments==0) return self;
    
    draw->num=0;
    PSsetgray(colour);
    beginUserPath(userPath, NO);
    [self recordMovex: xValues[from] y: yValues[from] p: from draw: 0];
    yi=draw->yr[from]=yValues[from];
    for (point=from+1; point<=to; point++) {
    	
        xi=xValues[point-1];
        xf=x=xValues[point];
        yf=y=yValues[point];
        deltax=xf-xi;
        deltay=yf-yi;
        numsegs=rint((deltax*WIDTH/(xMax-xMin))/3);        // at least 3 pixels per segment
        if (numsegs>MAXNUMSEGS) numsegs=MAXNUMSEGS;        // but no more than MAXNUMSEGS...
        if (numsegs<2) numsegs=2;                          // or less than 2
        if (showSmooth!=0 && deltax!=0 && deltay!=0) {
            smooth=sValues[point];
            incx=deltax/numsegs;                           // effective deltax is always
            if (smooth<0.01) incx*=0.01;                   // determined by the time
            else if (smooth<1.0) incx*=smooth;             // constant of the segment
            incy=deltay/numsegs;
            for (seg=0, x=xi+incx; seg<numsegs-1; seg++, x+=incx) {
                y=yi+(deltay*(1-exp(-5.5262*(x-xi)/(deltax*smooth))));
                [self recordMovex: x y: y p: point draw: 1];
            }
            y=yi+(deltay*(1-exp(-5.5262/smooth)));         // last segment to xf directly
            [self recordMovex: xf y: y p: point draw: 1];
        }
        else [self recordMovex: x y: y p: point draw: 1];
        if (point==stickyPoint) {
            [self recordMovex: xValues[point] y: 0 p: point draw: 1];
            [self recordMovex: xValues[point] y: y p: point draw: 0];
        }
        yi=draw->yr[point]=y;
    }
    endUserPath(userPath, dps_ustroke);
    sendUserPath(userPath);
    return self;
}

//-------------------------------------------------------------------
// - drawSelf:: - Draw the envelope object in the view

- drawSelf:(NXRect *)rects :(int)rectCount
{
    NXEraseRect(&bounds);                        // clear the view
    PSsetlinewidth(0.0);
    if (theEnvelope!=NULL) {
        [self drawKnobsFrom:0 to:pointCount-1 in:LTGRAY:envColour];
        [self drawSegmentsFrom: 0 to:pointCount-1 in: envColour];
    }
    return self;
}
    
//-------------------------------------------------------------------
// - eraseSelectedKnob Draw the envelope again without drawing the 
// selected knob. This is used just before the modal loop for draging
// a point is entered. In the loop the cursor itself acts as the knob.

- eraseSelectedKnob
{
    NXEraseRect(&bounds);                        // clear the view
    PSsetlinewidth(0.0);
    if (theEnvelope!=NULL) {
        [self drawKnobsFrom:0 to:pointCount-1 in:LTGRAY:TRANSP];
        [self drawSegmentsFrom: 0 to:pointCount-1 in: envColour];
    }
    return self;
}
    
//-------------------------------------------------------------------
// movePoint:to: Move point n to a new location.
// This is called from within the drag modal loop so it tries to be
// quick. Each time it erases the old knobs and segments (up to two)
// and then draws the new knobs and segments. This last step can draw
// any number of segments depending on how close the other points are
// to the selected point (the problem is that the knob can erase 
// small portions of the segments that are lying outside of the area
// bounded by the selected point and its two enclosing points).

- (int) movePoint:(int)n to: (NXPoint)p;
{
    int left,right,                                // limits for x movement of point
        drawFrom,drawTo;                           // start and end points for drawing lines
    
    if (n==0) {                                    // determine limits for erase and draw
        drawFrom=0; 
        drawTo=1;
    } 
    else
    if (n==pointCount-1) {
        drawFrom=n-1; 
        drawTo=n;
    } 
    else {
        drawFrom=n-1; 
        drawTo=n+1;
    }
    left=drawFrom;
    right=drawTo;
    
    if (showSmooth!=0) {                          // if showing smoothing draw points...
        while(drawFrom-1>=0 &&                    // ...that have smoothing greater that 1
            sValues[drawFrom]>1.0)
            drawFrom--;
        while(drawTo+1<pointCount &&
            sValues[drawTo]>1.0)
            drawTo++;
    }
    if (n!=0 && p.x<xValues[left])                // force selected point to be within
        p.x=xValues[left];                        // neighbouring points
    if (n!=pointCount-1 && p.x>xValues[right]) 
        p.x=xValues[right];
    if (xSnap!=0 && 
        xValues[right]-xValues[left]>2*xSnap)
        p.x=rint(p.x/xSnap)*xSnap;                // snap into x grid
    if (p.y>yMax) p.y=yMax;                       // clip y values to max and min
    if (p.y<yMin) p.y=yMin;
    if (ySnap!=0) 
        p.y=rint(p.y/ySnap)*ySnap;                // snap into y grid
    
    if (n>1)                                      // avoid 3 points with the same x value
        if (p.x==xValues[n-1] && 
            p.x==xValues[n-2])
            p.x=xValues[n-1]+(1/WIDTH*(xMax-xMin));
    if (n<(pointCount-2))
        if (p.x==xValues[n+1] && 
            p.x==xValues[n+2])
            p.x=xValues[n+1]-(1/WIDTH*(xMax-xMin));

    [self drawKnobsFrom:drawFrom                  // erase old knobs and segments
        to:drawTo in:WHITE:TRANSP];
    [self drawSegmentsFrom:drawFrom 
        to:drawTo in:WHITE];

    xValues[n]=p.x;
    yValues[n]=p.y;                               // update coordinates in arrays
    
    [self drawKnobsFrom:drawFrom                  // draw new knobs and segments
        to:drawTo in:LTGRAY:TRANSP];
    [self drawSegmentsFrom:drawFrom 
        to:drawTo in:envColour];

    [[self window] flushWindow];
    return n;
}

//-------------------------------------------------------------------
// addPoint:  -- Adds a point to the envelope
// Returns number of new point, -1 on error

- (int) addPoint:(NXPoint)p
{
    int point, newp, oldp, newpc, newSticky;

    if (p.x<xMin || p.x>=xMax ||                 // check point is within bounds
        p.y<yMin || p.y>yMax)
        return -1;
    if (ySnap!=0) p.y=rint(p.y/ySnap)*ySnap;     // snap into y grid

    if ((point=[self insertPointAt:p])<0)        // and that we get a valid point number
        return -1;

    newpc=pointCount+1;
    allocateTemp(newpc);
    allocateDraw(newpc);

    draw->num=0;                                 // clear erase path
    newSticky=[theEnvelope stickPoint];
    if (newSticky!=MAXINT && stickyPoint>point) 
        newSticky++;                             // adjust value of stick point
    
    for (newp=oldp=0; newp<newpc; newp++) {
        if (newp==point) {
            temp->x[newp]=p.x;                   // set values of new point
            temp->y[newp]=p.y;
            draw->yr[newp]=p.y;
            temp->s[newp]=defaultSmooth;
        }
        else {
            temp->x[newp]=xValues[oldp];         // copy old values into new arrays
            temp->y[newp]=yValues[oldp];
            draw->yr[newp]=draw->yr[oldp];
            if (sValues!=NULL)
                temp->s[newp]=sValues[oldp];
            else
                temp->s[newp]=defaultSmooth;
            oldp++;
        }
    }
    [theEnvelope                                 // redefine the old envelope object
        setPointCount: newpc
        xArray: temp->x
        orSamplingPeriod: 1.0
        yArray: temp->y
        smoothingArray: temp->s
        orDefaultSmoothing: defaultSmooth];
    [theEnvelope setStickPoint: newSticky];

    return point;
}

//-------------------------------------------------------------------
// removePoint: -- Remove a point from the envelope
// Returns the new number of points or -1 on error

- (int) removePoint:(int)n
{
    int oldp, newp, newpc, newSticky;
    
    if (pointCount<3) return -1;                // leave always at least two breaks
    
    newpc=pointCount-1;
    allocateTemp(newpc);
    allocateDraw(newpc);
    
    draw->num=0;
    for (oldp=newp=0; newp<newpc; newp++,oldp++) {
        if (oldp==n) oldp++;                    // copy arrays skipping deleted element
        temp->x[newp]=xValues[oldp];
        temp->y[newp]=yValues[oldp];
        draw->yr[newp]=draw->yr[oldp];
        if (sValues!=NULL)
            temp->s[newp]=sValues[oldp];
    }
    newSticky=[theEnvelope stickPoint];
    if (newSticky!=MAXINT) {
        if (newSticky==n)
            newSticky=MAXINT;                   // remove stick point with point
        else if (newSticky>n)
            newSticky--;                        // or adjust value if higher
    }
    [theEnvelope
        setPointCount: newpc
        xArray: temp->x
        orSamplingPeriod: 1.0
        yArray: temp->y
        smoothingArray: temp->s
        orDefaultSmoothing: defaultSmooth];
    [theEnvelope setStickPoint: newSticky];     // update envelope object

    [self selectPoint: n-1];                    // and select previous point
    return pointCount;
}

//-------------------------------------------------------------------
// mouseDown:  -- Responds to a mousedown event
// The following is the behaviour of the mouse:
// hit a knob:            --> drag the point
// hit with shift:        --> delete envelope breakpoint
// hit with alternate:    --> toggle sticky point at breakpoint
// no hit with shift:    --> create a new envelope breakpoint

- mouseDown:(NXEvent *)event
{
    NXPoint ep, p;
    int hitpt;                // Point to move/remove
    int oldMask;

     ep=event->location;
    [self convertPoint:&ep fromView:nil];
    p.x=pixToX(ep.x);
    p.y=pixToY(ep.y);                                    // convert from pixels to x/y
    hitpt=[self hitKnobAt: p border: KNOBSIZE/2];        // see if it is a breakpoint    
    if (event->flags & NX_SHIFTMASK) {                   // with shift key down...
        if (hitpt>=0) {
            if ([self removePoint: hitpt] > 0)           // hit --> remove point
                hitpt=-1;
        }
        else hitpt=[self addPoint: p];                   // no hit --> add point
    }
    if ((event->flags&NX_ALTERNATEMASK)&&(hitpt>=0)) {   // hit plus alternate...
        if (stickyPoint==hitpt)
            [theEnvelope setStickPoint: MAXINT];                
        else
            [theEnvelope setStickPoint: hitpt];
        [self selectPoint: hitpt];
        hitpt=-1;
    }
    
    if (hitpt >= 0) {                                    // Move hitpt as mouse drags
        [self selectPoint: hitpt];                       // select and redraw image
        oldMask = [[self window] addToEventMask:DRAG_MASK];
        
        [self lockFocus];
        [theFilledCross push];                           // use cursor = cross+knob
        [self eraseSelectedKnob];
        while (event->type != NX_MOUSEUP) {
             ep=event->location;
            [self convertPoint:&ep fromView:nil];
            if ([NXApp peekNextEvent: DRAG_MASK into: event]==NULL) {
                p.x=pixToX(ep.x);
                p.y=pixToY(ep.y);
                [self movePoint:hitpt to:p];
                [theController updateCoords: self at: hitpt];
                NXPing();
            }
            event = [NXApp getNextEvent:DRAG_MASK];
        }
        [NXCursor pop];                                   // return to crosshair cursor
        [self unlockFocus];
    }
    [self display];
    return self;
}

//===================================================================
// Messages received from window to change first responder status
//===================================================================

//-------------------------------------------------------------------
// highlight

- highlight
{
    envColour=BLACK;
    [self display];
    return self;
}

//-------------------------------------------------------------------
// dim

- dim
{
    envColour=DKGRAY;
    [self display];
    return self;
}

//-------------------------------------------------------------------
// acceptsFirstResponder: 

- (BOOL) acceptsFirstResponder
{
    return YES;
}

//-------------------------------------------------------------------
// becomeFirstResponder: 

- becomeFirstResponder
{
    if (theController!=NULL)
        [theController update: self];
    envColour=BLACK;
    [self display];
    return self;
}

//-------------------------------------------------------------------
// resignFirstResponder: 

- resignFirstResponder
{
    return self;
}

//===================================================================
// Pasteboard interface methods
//===================================================================

//-------------------------------------------------------------------
// copy: -- Copy the current envelope to the pasteboard

- copy:sender
{
    int point;                  // current point
     char select;               // number representing the style
    const char *style;          // string representing the style
    const char *types[1];
    int num=0;
    char *s;                    // output string
    int max;                    // max number of chars in the output string
    char tmp[128];              // temporary buffer for conversions

    max=1024;
    s=malloc(max*sizeof(char));                  // allocate array for output string
    
    if (theController==NULL)                     // get current selected style
        style="MusicKit";
    else {
        style=[theController getStyle];
        if (style==NULL) style="MusicKit";
    }
    select=STYLE_MK;
    if (strcmp(style,"MusicKit")==0) select=STYLE_MK;
    if (strcmp(style,"(x y ...)")==0) select=STYLE_pX_Yp;
    if (strcmp(style,"(x,y ...)")==0) select=STYLE_pXcYp;
    if (strcmp(style,"x y ...")==0) select=STYLE_X_Y;
    if (strcmp(style,"x,y ...")==0) select=STYLE_XcY;
    if (strcmp(style,"x,y,z ...")==0) select=STYLE_XcYcZ_;
    if (strcmp(style,"x y z,...")==0) select=STYLE_X_Y_Zc;
    if (strcmp(style,"((x y)...)")==0) select=STYLE_ppX_Yp;
    switch(select)
        {
        case STYLE_MK:
            strcpy(s,"[");
            for (point=0; point<pointCount-1; point++) {
                sprintf(tmp,"(%5.3f,%5.3f",xValues[point],yValues[point]);
                strcat(s,tmp);
                if (sValues[point]!=defaultSmooth) {
                    sprintf(tmp,",%5.3f",sValues[point]);
                    strcat(s,tmp);
                }
                strcat(s,")");
                if (stickyPoint==point) strcat(s,"|");
                if (strlen(s)>max-128) {
                    max+=1024;
                    s=realloc(s,max*sizeof(char));
                }
            }
            sprintf(tmp,"(%5.3f,%5.3f",xValues[pointCount-1],yValues[pointCount-1]);
            strcat(s,tmp);
            if (sValues[point]!=defaultSmooth) {
                sprintf(tmp,",%5.3f",sValues[point]);
                strcat(s,tmp);
            }
            strcat(s,")");
            if (stickyPoint==point) strcat(s,"|");
            strcat(s,"]");
            break;

        case STYLE_pX_Yp:
            strcpy(s,"(");
            for (point=0; point<pointCount-1; point++) {
                sprintf(tmp,"%5.3f %5.3f ",xValues[point],yValues[point]);
                strcat(s,tmp);
                if (strlen(s)>max-128) {
                    max+=1024;
                    s=realloc(s,max*sizeof(char));
                }
            }
            sprintf(tmp,"%5.3f %5.3f)",xValues[pointCount-1],yValues[pointCount-1]); 
            strcat(s,tmp);
            break;
            
        case STYLE_pXcYp:
            strcpy(s,"(");
            for (point=0; point<pointCount-1; point++) {
                sprintf(tmp,"%5.3f,%5.3f,",xValues[point],yValues[point]);
                strcat(s,tmp);
                if (strlen(s)>max-128) {
                    max+=1024;
                    s=realloc(s,max*sizeof(char));
                }
            }
            sprintf(tmp,"%5.3f,%5.3f)",xValues[pointCount-1],yValues[pointCount-1]); 
            strcat(s,tmp);
            break;

        case STYLE_X_Y:
            strcpy(s,"");
            for (point=0; point<pointCount-1; point++) {
                sprintf(tmp,"%5.3f %5.3f ",xValues[point],yValues[point]);
                strcat(s,tmp);
                if (strlen(s)>max-128) {
                    max+=1024;
                    s=realloc(s,max*sizeof(char));
                }
            }
            sprintf(tmp,"%5.3f %5.3f",xValues[pointCount-1],yValues[pointCount-1]); 
            strcat(s,tmp);
            break;
            
        case STYLE_XcY:
            strcpy(s,"");
            for (point=0; point<pointCount-1; point++) {
                sprintf(tmp,"%5.3f,%5.3f,",xValues[point],yValues[point]);
                strcat(s,tmp);
                if (strlen(s)>max-128) {
                    max+=1024;
                    s=realloc(s,max*sizeof(char));
                }
            }
            sprintf(tmp,"%5.3f,%5.3f",xValues[pointCount-1],yValues[pointCount-1]); 
            strcat(s,tmp);
            break;

        case STYLE_XcYcZ_:
            strcpy(s,"");
            for (point=0; point<pointCount-1; point++) {
                sprintf(tmp,"%5.3f,%5.3f,%5.3f ",xValues[point],yValues[point],sValues[point]);
                strcat(s,tmp);
                if (strlen(s)>max-128) {
                    max+=1024;
                    s=realloc(s,max*sizeof(char));
                }
            }
            sprintf(tmp,"%5.3f,%5.3f,%5.3f",xValues[pointCount-1],yValues[pointCount-1],sValues[pointCount-1]);
            strcat(s,tmp);
            break;

        case STYLE_X_Y_Zc:
            strcpy(s,"");
            for (point=0; point<pointCount-1; point++) {
                sprintf(tmp,"%5.3f %5.3f %5.3f,",xValues[point],yValues[point],sValues[point]);
                strcat(s,tmp);
                if (strlen(s)>max-128) {
                    max+=1024;
                    s=realloc(s,max*sizeof(char));
                }
            }
            sprintf(tmp,"%5.3f %5.3f %5.3f",xValues[pointCount-1],yValues[pointCount-1],sValues[pointCount-1]);
            strcat(s,tmp);
            break;

        case STYLE_ppX_Yp:
            strcpy(s,"(");
            for (point=0; point<pointCount-1; point++) {
                sprintf(tmp,"(%5.3f %5.3f)",xValues[point],yValues[point]);
                strcat(s,tmp);
                if (strlen(s)>max-128) {
                    max+=1024;
                    s=realloc(s,max*sizeof(char));
                }
            }
            sprintf(tmp,"(%5.3f %5.3f))",xValues[pointCount-1],yValues[pointCount-1]); 
            strcat(s,tmp);
            break;

        }
    types[num++] = NXAsciiPboardType;
    [[Pasteboard new] declareTypes: types num: num owner: [self class]];
    [[Pasteboard new] writeType: NXAsciiPboardType data:s length:strlen(s)];
    free(s);
    return self;
}

//-------------------------------------------------------------------
// paste: -- Paste the current pasteboard contents into the view. The
// method parses the text representation of the envelope automatically
// deciding on the type of envelope received. The internal representation
// is a standard MusicKit envelope object.

// Get next token from a string (symbol or number)

#define NUMBER 0

char *data;

// Parse the data stream in tokens representing symbols and numbers

int token(char *t)
{
    char c;
    int i;
    
    while (((t[0]=c=*data++) == ' ')||(c == '\t')||(c == '\n'));
    t[1]='\0';
    if (c=='\0') {
        data--;
        return EOF;
    }
    if (!isdigit(c) && c!= '.' && c!= '-')
        return c;        
    i=0;                            // collect a number stream
    if (c=='-')
        t[++i]=c=*data++;
    if (isdigit(c))
        while (isdigit(t[++i]=c=*data++));
    if (c == '.')
        while (isdigit(t[++i]=c=*data++));
	
    if ((c == 'e')||(c == 'E')) {
        t[++i]=c=*data++;
    	if (c=='-')
            t[++i]=c=*data++;
    	if (isdigit(c))
            while (isdigit(t[++i]=c=*data++));
    }
    
    t[i]='\0';
    if (c!='\0')
        data--;
    return NUMBER;
}

- paste:sender
{
    char *prs, *orig, tk[1024];
    int   symb, length;
    const NXAtom *pastetypes;
    int sticky, point;

    pastetypes = [[Pasteboard new] types];    
    if ([[Pasteboard new] 
        readType: NXAsciiPboardType 
        data:&prs 
        length:&length]) {                            // if ASCII in pasteboard...
	
        data=orig=calloc(length+16,sizeof(char));     // copy data to local buffer
        strncpy(data,prs,length);
        
        point=0;                                      // start by converting point 0
        sticky=MAXINT;                                // no sticky point by default
        allocateTemp(64);                             // 64 points long by default
        
        if ((symb=token(tk))=='(') {
	    if (symb=token(tk)=='(') {
	    
    // parse a list of lists type envelope
    // accepted syntax: "((x0 y0)...(xn yn))" or "((x0,y0)...(xn,yn))"

            while((symb!=')')&&(symb!=-1)) {
	        symb=token(tk);                           // should be "x" component
                if (symb!=NUMBER) {                   // must be x component
                    NXRunAlertPanel("Error",
                    "Expected x component at:\n\"%s\"\nin:\n\"%s\"",
                    NULL,NULL,"Continue",data-1,orig);
                    break;
                }
                else {
                    temp->x[point]=atof(tk);           // convert "x" component!
                    temp->y[point]=0.0;                // and set defaults
                    temp->s[point]=defaultSmooth;
                }
		symb=token(tk);
                if (symb!=NUMBER) {
                    NXRunAlertPanel("Error",
                    "Expected y component at:\n\"%s\"\nin:\n\"%s\"",
                    NULL,NULL,"Continue",data-1,orig);
                    break;
                }
                else {
                    temp->y[point]=atof(tk);          // convert "y" component
                    point++;                          // count a complete envelope node
                    allocateTemp(point+1);
                }
                if (symb=token(tk)!=')') {
                    NXRunAlertPanel("Error",
                    "Expected closing parenthesis at:\n\"%s\"\nin:\n\"%s\"",
                    NULL,NULL,"Continue",data-1,orig);
                    break;
                }
                else symb=token(tk);
            }
            showSmooth=0;                             // only MK shows smoothing by default
	    }	
	else {
	
    // parse a CLM type envelope
    // accepted syntax: "(x0 y0 ... xn yn)" or "(x0,y0, ... xn,yn)"
            
            while((symb!=')')&&(symb!=-1)) {
                if (symb==',') symb=token(tk);        // ignore commas between xy pairs
                if (symb!=NUMBER) {                   // must be x component
                    NXRunAlertPanel("Error",
                    "Expected x component at:\n\"%s\"\nin:\n\"%s\"",
                    NULL,NULL,"Continue",data-1,orig);
                    break;
                }
                else {
                    temp->x[point]=atof(tk);           // convert "x" component!
                    temp->y[point]=0.0;                // and set defaults
                    temp->s[point]=defaultSmooth;
                }
                if ((symb=token(tk))==',')             // ignore commas between values
                    symb=token(tk);
                if (symb!=NUMBER) {
                    NXRunAlertPanel("Error",
                    "Expected y component at:\n\"%s\"\nin:\n\"%s\"",
                    NULL,NULL,"Continue",data-1,orig);
                    break;
                }
                else {
                    temp->y[point]=atof(tk);          // convert "y" component
                    point++;                          // count a complete envelope node
                    allocateTemp(point+1);
                }
                symb=token(tk);
            }
            showSmooth=0;                             // only MK shows smoothing by default
        }
	}
        else if (symb=='[')    {    
        
    // parse a MusicKit envelope
    // uses normal MusicKit syntax
            
            symb=token(tk);                            // should be starting '(' or '|'
            while((symb=='(')||(symb=='|')) {
                if (symb=='|') {
                    sticky=point-1;                    // last point was the sticky point
                    symb=token(tk);                    // should be '(' or the end
                    continue;
                }
                if ((symb=token(tk))!=NUMBER) {        // break if not a number
                    NXRunAlertPanel("Error",
                    "Expected x component at:\n\"%s\"\nin:\n\"%s\"",
                    NULL,NULL,"Continue",data-1,orig);
                    break;
                }
                else {
                    temp->x[point]=atof(tk);           // convert "x" component!
                    temp->y[point]=0.0;
                    temp->s[point]=defaultSmooth;
                }
                if ((symb=token(tk))==',')             // skip comma but also accept a space
                    symb=token(tk);
                if (symb!=NUMBER) {                    // break if not a number
                    NXRunAlertPanel("Error",
                    "Expected y component at:\n\"%s\"\nin:\n\"%s\"",
                    NULL,NULL,"Continue",data-1,orig);
                    break;
                }
                else {
                    temp->y[point]=atof(tk);           // convert "y" component
                }
                if ((symb=token(tk))==',')             // is there a smoothing component?
                    if ((symb=token(tk))==NUMBER) {    // if number...
                        temp->s[point]=atof(tk);       // convert smoothing component
                        symb=token(tk);
                    }
                point++;                               // count a complete envelope node
                allocateTemp(point+1);

                if (symb!=')') {                       // must be point's closing parenthesis
                    NXRunAlertPanel("Error",
                    "Expected a ')' at:\n\"%s\"\nin:\n\"%s\"",
                    NULL,NULL,"Continue",data-1,orig);
                    break;
                }
                symb=token(tk);
            }
            showSmooth=-1;
        }
        else if (symb==NUMBER)    {

    // parse a x-y-<z> pair type envelope
    // accepted syntax: "x0 y0 ... xn yn" or "x0,y0, ... xn,yn" for xy pairs
    // accepted syntax: "x0,y0,z0 x1,y1,z1 ..." or " x0 y0 z0,x1 y1 z1, ..." for xyz sets
            
            int commas=0;
            double last_value=0.0;
            int pending=0;

            while(symb!=-1) {
                if (pending==0) {
                    if (symb!=NUMBER) {                  // not a number!
                        NXRunAlertPanel("Error",
                        "Expected x component at:\n\"%s\"\nin:\n\"%s\"",
                        NULL,NULL,"Continue",data-1,orig);
                        break;
                    }
                    else {
                        temp->x[point]=atof(tk);        // convert "x" component!
                        temp->y[point]=0;
                        temp->s[point]=defaultSmooth;
                    }
                    if ((symb=token(tk))==',') {
                        commas++;
                        symb=token(tk);
                    }
                }
                else {
                    temp->x[point]=last_value;
                    temp->y[point]=0.0;
                    temp->s[point]=defaultSmooth;
                    pending=0;
                }
                if (symb!=NUMBER) {
                    NXRunAlertPanel("Error",
                    "Expected y component at:\n\"%s\"\nin:\n\"%s\"",
                    NULL,NULL,"Continue",data-1,orig);
                    break;
                }
                else temp->y[point]=atof(tk);        // convert "y" component
                if ((symb=token(tk))==',') {
                    commas++;
                    symb=token(tk);
                }
                if (symb==-1) {
                    point++;
                    allocateTemp(point+1);
                    break;
                }
                else {
                    if (symb!=NUMBER) {
                        NXRunAlertPanel("Error",
                        "Expected x or z component at:\n\"%s\"\nin:\n\"%s\"",
                        NULL,NULL,"Continue",data-1,orig);
                        break;
                    }
                    else last_value=atof(tk);
                    if ((((symb=token(tk))!=',')&&(commas==2)) ||
                         ((symb==',')&&(commas==0))) {
                        temp->s[point]=last_value;
                        pending=0;
                        commas=0;
                    }
                    else {
                        pending=1;
                        commas=1;
                    }
                    point++;
                    allocateTemp(point+1);
                }
                if (symb==',') symb=token(tk);
            }
            showSmooth=0;                            // only MK shows smoothing by default
        }
        else
            NXRunAlertPanel(
                "Error",
                "The envelope must start with '[','(' or a number:\n\"%s\"",
                NULL,NULL,"Continue",orig);
        
        vm_deallocate(task_self(),(vm_address_t)prs, length);
        
        if (point<2) {                                // if envelope has less than 2 nodes
            NXRunAlertPanel(
                "Error",
                "Less than 2 legal points in envelope:\n\"%s\"",
                NULL,NULL,"Continue",orig);
        }
        else {
            [theEnvelope
                setPointCount: point
                xArray: temp->x
                orSamplingPeriod: 1.0
                yArray: temp->y
                smoothingArray: temp->s
                orDefaultSmoothing: defaultSmooth];
            [theEnvelope setStickPoint: sticky];      // update envelope object
            allocateDraw(temp->max); //(pointCount);                 // resize drawing arrays
            [self scaleLimits];                       // define drawing limits
        }
        if (selected<pointCount)
            [self selectPoint: selected];
        else
            [self selectPoint: 0];                    // display and update controller
        free(orig);                                   // free local copy of data
    }
    return self;
}

//===================================================================
// Messages received from controller to change parameters
//===================================================================

//-------------------------------------------------------------------
// setPointTo: set current point to n (as a side effect updates values
// of x and y coordinates on the controller object
 
- setPointTo: (int)point
{
    if (point>=pointCount) point=pointCount-1;
    if (point<0) point=0;
    [self selectPoint: point];
    [theController update: self];
    return self;
}

//-------------------------------------------------------------------
// nextPoint go to the next point in the envelope if possible
 
- nextPoint
{
    int next;
    
    next=selected+1;
    if (next>=pointCount) next=selected;
    if (next<0) next=0;
    if (next!=selected) {
        [self selectPoint:next];
        [theController update: self];
    }
    return self;
}

//-------------------------------------------------------------------
// previousPoint go to the previous point in the envelope if possible
 
- previousPoint
{
    int previous;
    
    previous=selected-1;
    if (previous>=pointCount) previous=selected;
    if (previous<0) previous=0;
    if (previous!=selected) {
        [self selectPoint:previous];
        [theController update: self];
    }
    return self;
}

//-------------------------------------------------------------------
// setXAt:to: changes value of x coordinate of point n
 
- setXAt: (int)n to: (float)coord
{
    if (n!=0 && coord<xValues[n-1])             // force selected point to be within
        coord=xValues[n-1];                     // enclosing points
    if (n!=pointCount-1 && coord>xValues[n+1]) 
        coord=xValues[n+1];
    if (coord!=xValues[n]) {                    // if x changed update display panel
        xValues[n]=coord;
        [self display];
        [theController update: self];
    }
    return self;
}

//-------------------------------------------------------------------
// setYAt:to: changes value of y coordinate of point n
 
- setYAt: (int)n to: (float)coord
{
    if (coord>yMax) coord=yMax;                // clip y values to max and min
    if (coord<yMin) coord=yMin;
    
    if (coord!=yValues[n]) {                   // if y changed update display panel
        yValues[n]=coord;
        [self display];
        [theController update: self];
    }
    return self;
}

//-------------------------------------------------------------------
// setYrAt:to: changes value of real y coordinate of point n
 
- setYrAt: (int)n to: (float)coord
{
    double y;
    
    if (coord>yMax) coord=yMax;               // clip y values to max and min
    if (coord<yMin) coord=yMin;
    
    if (n==0) return self;                    // no sense to change this in first point
    
    draw->yr[n]=coord;
    y=(coord-yValues[n-1]*exp(-5.5262/sValues[n]))/(1-exp(-5.5262/sValues[n]));
    [self setYAt: n to: y];
    return self;
}

//-------------------------------------------------------------------
// setSmoothAt:to: changes value of smoothing of point n
 
- setSmoothAt: (int)n to: (float)value
{
    if (value!=sValues[n]) {                  // if smoothing changed update panel
        sValues[n]=value;
        [self display];
        [theController update: self];
    }
    return self;
}

//-------------------------------------------------------------------
// setXMinTo: changes minimum value of x component of envelope
 
- setXMinTo: (float)coord
{
    if (coord<xMax) {
        xMin=coord;
        [self display];
    }
    else if (theController!=NULL)
        [theController update: self];
    return self;
}

//-------------------------------------------------------------------
// setXMaxTo: changes maximun value of x component of envelope
 
- setXMaxTo: (float)coord
{
    if (coord>xMin) {
        xMax=coord;
        [self display];
    }
    else if (theController!=NULL)
        [theController update: self];
    return self;
}

//-------------------------------------------------------------------
// setXLimitsTo:: changes max and min values of x component
 
- setXLimitsTo: (float)min : (float)max
{
    xMin=min;
    xMax=max;
    [self display];
    return self;
}

//-------------------------------------------------------------------
// setYMinTo: changes minimum value of y component of envelope
 
- setYMinTo: (float)coord
{
    if (coord<yMax) {
        yMin=coord;
        [self display];
    }
    else if (theController!=NULL)
        [theController update: self];
    return self;
}

//-------------------------------------------------------------------
// setYMaxTo: changes maximun value of y component of envelope
 
- setYMaxTo: (float)coord
{
    if (coord>yMax) {
        yMax=coord;
        [self display];
    }
    else if (theController!=NULL)
        [theController update: self];
    return self;
}

//-------------------------------------------------------------------
// setXSnapTo: changes value of x Snap
 
- setXSnapTo: (float)coord
{
    xSnap=coord;
    [self display];
    return self;
}

//-------------------------------------------------------------------
// setYSnapTo: changes value of y Snap
 
- setYSnapTo: (float)coord
{
    ySnap=coord;
    [self display];
    return self;
}

//-------------------------------------------------------------------
// setStickyAt:To: sets point to be the sticky point of envelope
 
- setStickyAt:(int) point To: (int)state
{
    if (state==0)
        [theEnvelope setStickPoint: MAXINT];
    else
        [theEnvelope setStickPoint: point];
    [theController update: self];
    [self display];
    return self;
}

//-------------------------------------------------------------------
// setShowSmooth: sets type of graphics to be used
 
- setShowSmooth: (int)state
{
    showSmooth=state;
    [self display];
    [theController update: self];
    return self;
}

//-------------------------------------------------------------------
// setDrawSegments: choose to draw segments or only points
 
- setDrawSegments: (int)state
{
    drawSegments=state;
    [self display];
    [theController update: self];
    return self;
}

//-------------------------------------------------------------------
// scaleLimits computes the max and min bounds of the view
 
- scaleLimits
{
    double xmin, xmax, ymin, ymax;
    int point;
    
    xmin=xmax=xValues[0];
    ymin=ymax=yValues[0];
    for (point=1; point<pointCount; point++) {
        if (xValues[point]<xmin) xmin=xValues[point];
        if (xValues[point]>xmax) xmax=xValues[point];
        if (yValues[point]<ymin) ymin=yValues[point];
        if (yValues[point]>ymax) ymax=yValues[point];
    }
    xMax=xmax;
    xMin=xmin;
    if (ymax>=0.0 && ymax<=10.0)
        yMax=rint(ymax*10+1.0)/10;
    else
        yMax=rint(ymax+1.0);
    if (ymin<=0.0 && ymin>=-1.0)
        yMin=rint(ymin*10-1.0)/10;
    else
        yMin=rint(ymin-1.0);
    if (ymin==0) yMin=ymin;
    
    [self display];
    return self;
}

//===================================================================
// Messages received from controller to query for envelope values
//===================================================================

//-------------------------------------------------------------------
// (int)getPoint Return the selected point

- (int)getPoint
{
    return selected;
}

//-------------------------------------------------------------------
// (float)getX:(int)i Return value of x component of point i

- (float)getX:(int)i
{
    if (i>=pointCount) i=pointCount-1;
    if (i<0) i=0;
    return xValues[i];
}

//-------------------------------------------------------------------
// (float)getY:(int)i Return value of y component of point i

- (float)getY:(int)i
{
    if (i>=pointCount) i=pointCount-1;
    if (i<0) i=0;
    return yValues[i];
}

//-------------------------------------------------------------------
// (float)getYr:(int)i Return value of real y component of point i

- (float)getYr:(int)i
{
    if (i>=pointCount) i=pointCount-1;
    if (i<0) i=0;
    return draw->yr[i];
}

//-------------------------------------------------------------------
// (float)getSmoothing:(int)i Return value of smoothing of point i

- (float)getSmoothing:(int)i
{
    if (i>=pointCount) i=pointCount-1;
    if (i<0) i=0;
    return sValues[i];
}

//-------------------------------------------------------------------
// (int)getSticky:(int)i Return value of stickyness of point i

- (int)getSticky:(int)i
{
    if (i>=pointCount) i=pointCount-1;
    if (i<0) i=0;
    if (stickyPoint==i)
        return 1;
    else
        return 0;
}

//-------------------------------------------------------------------
// (float)getXMax 

- (float) getXMax { return xMax; }

//-------------------------------------------------------------------
// (float)getXMin 

- (float) getXMin { return xMin; }

//-------------------------------------------------------------------
// (float)getYMax 

- (float) getYMax { return yMax; }

//-------------------------------------------------------------------
// (float)getYMin 

- (float) getYMin { return yMin; }

//-------------------------------------------------------------------
// (float)getXSnap 

- (float) getXSnap { return xSnap; }

//-------------------------------------------------------------------
// (float)getYSnap 

- (float) getYSnap { return ySnap; }

//-------------------------------------------------------------------
// (int)getShowSmooth 

- (int)getShowSmooth
{
    return showSmooth;
}

//-------------------------------------------------------------------
// (int)getDrawSegments 

- (int)getDrawSegments
{
    return drawSegments;
}

@end

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