ftp.nice.ch/pub/next/science/mathematics/HippoDraw.2.0.s.tar.gz#/HippoDraw/Hippo.bproj/Draw.subproj/Scribble.m

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

#import "draw.h"

/*
 * This line is just a stub to get genstrings to generate
 * a .strings file entry for the name of this type of Graphic.
 * The name is used in the Undo New <Whatever> menu item.
 *
 * NXLocalString("Scribble", NULL, "Name of the tool that draws scribbles, i.e., the %s of the New %s operation.")
 */

@implementation Scribble : Graphic

static NXPoint lastPoint;	/* used in creating only */

+ initialize
/*
 * This bumps the class version so that we can compatibly read
 * old Graphic objects out of an archive.
 */
{
    [Scribble setVersion:1];
    return self;
}

+ cursor
/*
 * A Scribble uses a pencil as its cursor.
 * Since Draw is a subproject in Hippo.bundle, we have
 * to find the tiff file. -- pfkeb
 */
{
    NXBundle		*bundle;
    NXImage		*image;
    NXPoint 		spot;
    static NXCursor 	*cursor = nil;
    char		buffer[MAXPATHLEN+1];

    if (!cursor) {
        bundle = [NXBundle bundleForClass:[self class]];
        if ( [bundle getPath:buffer forResource:"pencil" ofType:"tiff"] ) {
	    image = [[NXImage allocFromZone:[self zone]] initFromFile:buffer];
	    cursor = [NXCursor newFromImage:image];
  	    spot.x = 0.0; spot.y = 15.0;
	    [cursor setHotSpot:&spot];
	}
    }

    return cursor ? cursor : [super cursor];
}

- free
{
    NX_FREE(points);
    NX_FREE(userPathOps);
    return [super free];
}

- allocateChunk
/*
 * The Scribble's storage is allocated in chunks.
 * This allocates another chunk.
 */
{
    int i, newSize;

    newSize = length + CHUNK_SIZE;
    if (points) {
	NX_ZONEREALLOC([self zone], points, float, newSize << 1);
	NX_ZONEREALLOC([self zone], userPathOps, char, newSize);
    } else {
	NX_ZONEMALLOC([self zone], points, float, newSize << 1);
	NX_ZONEMALLOC([self zone], userPathOps, char, newSize);
    }
    for (i = newSize - 1; i >= length; i--) {
	userPathOps[i] = dps_rlineto;
    }

    return self;
}

- (float)naturalAspectRatio
/*
 * The Scribble's natural aspect ratio is the one it was created with.
 */
{
    return (gFlags.initialized ? ((bbox[2]-bbox[0])/(bbox[3]-bbox[1])) : 0.0);
}

- (int)moveCorner:(int)corner to:(const NXPoint *)point constrain:(BOOL)flag
/*
 * After the Scribble is created (gFlags.initialized == YES), this method
 * just returns super's implementation.  During creation, every time the
 * "corner" is moved, a new line segment is added to the Scribble and
 * the bounding box is expanded if necessary.
 */
{
    float *p;

    if (gFlags.initialized) {
	return [super moveCorner:corner to:point constrain:flag];
    }

    if (!(point->x - lastPoint.x || point->y - lastPoint.y)) return corner;

    length++;

    if (!(length % CHUNK_SIZE)) [self allocateChunk];

    p = points + (length << 1);
    *p++ = point->x - lastPoint.x;
    *p = point->y - lastPoint.y;
    lastPoint = *point;

    bbox[2] = MAX(point->x, bbox[2]);
    bbox[0] = MIN(point->x, bbox[0]);
    bbox[3] = MAX(point->y, bbox[3]);
    bbox[1] = MIN(point->y, bbox[1]);

    bounds.origin.x = bbox[0];
    bounds.origin.y = bbox[1];
    bounds.size.width = bbox[2] - bbox[0];
    bounds.size.height = bbox[3] - bbox[1];

    return corner;
}


- (BOOL)create:(NXEvent *)event in:view
/*
 * Before creating, an initial chunk is initialized, and the userPathOps
 * are initialized.  The lastPoint is also remembered as the start point.
 * After the Scribble is created, the initialized flag is set.
 */
{
    NXPoint p;

    [self allocateChunk];
    userPathOps[0] = dps_moveto;    
    p = event->location;
    [view convertPoint:&p fromView:nil];
    [view grid:&p];
    points[0] = p.x;
    points[1] = p.y;
    lastPoint = p;
    bbox[0] = bbox[2] = p.x;
    bbox[1] = bbox[3] = p.y;
    bounds.origin = p;
    bounds.size.width = bounds.size.height = 0.0;

    if ([super create:event in:view]) {
	gFlags.initialized = YES;
	return YES;
    }

    return NO;
}


- draw
/*
 * The Scribble is drawn simply by scaling appropriately from its
 * initial bounding box and drawing the user path.
 */
{
    NXCoord x, y;
    NXPoint p1, p2;
    int i, count, coords;
    float angle, sx, sy, tx, ty;

    if (bounds.size.width < 1.0 || bounds.size.height < 1.0) return self;

    if (length && (bbox[2] - bbox[0]) && (bbox[3] - bbox[1])) {
	sx = bounds.size.width / (bbox[2] - bbox[0]);
	sy = bounds.size.height / (bbox[3] - bbox[1]);
	tx = (bounds.origin.x +
	      ((points[0]-bbox[0]) / (bbox[2]-bbox[0]) * bounds.size.width)) -
		points[0] * sx;
	ty = (bounds.origin.y +
	      ((points[1]-bbox[1]) / (bbox[3]-bbox[1]) * bounds.size.height)) -
		points[1] * sy;
	if (gFlags.arrow && ![self fill] && (sx != 1.0 || sy != 1.0 || tx || ty)) {
	    PSgsave();
	}
	if ([self fill]) {
	    PSgsave();
	    PStranslate(tx, ty);
	    PSscale(sx, sy);
	    [self setFillColor];
	    DPSDoUserPath(points, (length + 1) << 1, dps_float,
			  userPathOps, length + 1, bbox,
			  gFlags.eofill ? dps_ueofill : dps_ufill);
	    PSgrestore();
	}
	if (!gFlags.nooutline) {
	    PStranslate(tx, ty);
	    PSscale(sx, sy);
	    [self setLineColor];
	    DPSDoUserPath(points, (length + 1) << 1, dps_float,
			  userPathOps, length + 1, bbox, dps_ustroke);
	}
	if (gFlags.arrow && ![self fill]) {
	    if (sx != 1.0 || sy != 1.0 || tx || ty) {
		PSgrestore();
		[self setLineColor];
	    }
	    if (gFlags.arrow != ARROW_AT_END) {
		i = 0;
		p1.x = points[i++];
		p1.y = points[i++];
		p2 = p1;
		p2.x += points[i++];
		p2.y += points[i++];
		count = length - 1;
		while (hypot((p1.x-p2.x)*sx,(p1.y-p2.y)*sy) < 7.0 && count--) {
		    p2.x += points[i++];
		    p2.y += points[i++];
		}
		angle = atan2((p1.y-p2.y)*sy, (p1.x-p2.x)*sx);
		angle = (angle / 3.1415) * 180.0;
		x = bounds.origin.x + (p1.x - bbox[0]) * sx;
		y = bounds.origin.y + (p1.y - bbox[1]) * sy;
		PSArrow(x, y, angle);
	    }
	    if (gFlags.arrow != ARROW_AT_START) {
		i = 0;
		coords = (length + 1) << 1;
		p1.x = points[i++];
		p1.y = points[i++];
		while (i < coords) {
		    p1.x += points[i++];
		    p1.y += points[i++];
		}
		p2 = p1;
		i = coords;
		p2.y -= points[--i];
		p2.x -= points[--i];
		count = length - 1;
		while (hypot((p2.x-p1.x)*sx,(p2.y-p1.y)*sy) < 7.0 && count--) {
		    p2.y -= points[--i];
		    p2.x -= points[--i];
		}
		angle = atan2((p1.y-p2.y)*sy, (p1.x-p2.x)*sx);
		angle = (angle / 3.1415) * 180.0;
		x = bounds.origin.x + (p1.x - bbox[0]) * sx;
		y = bounds.origin.y + (p1.y - bbox[1]) * sy;
		PSArrow(x, y, angle);
	    }
	}
    }

    return self;
}

- write:(NXTypedStream *)stream
/*
 * The Scribble is written out by writing its length (in segments), its
 * bounding box, and all the points.
 */
{
    int i, numFloats;

    [super write:stream];

    NXWriteTypes(stream,"iffff",&length,&bbox[0],&bbox[1],&bbox[2],&bbox[3]);

    numFloats = (length + 1) << 1;
    for (i = 0; i < numFloats; i++) {
	NXWriteTypes(stream, "f", &points[i]);
    }

    return self;
}

- read:(NXTypedStream *)stream
{
    int i;
    float *p;

    [super read:stream];

    NXReadTypes(stream,"iffff",&length,&bbox[0],&bbox[1],&bbox[2],&bbox[3]);

    NX_ZONEMALLOC([self zone], points, float, (length + 1) << 1);
    NX_ZONEMALLOC([self zone], userPathOps, char, length + 1);

    p = points;
    for (i = 0; i <= length; i++) {
	NXReadTypes(stream, "f", p++);
	NXReadTypes(stream, "f", p++);
	userPathOps[i] = dps_rlineto;
    }
    userPathOps[0] = dps_moveto;

    return self;
}

@end

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