ftp.nice.ch/pub/next/tools/screen/backspace/NickSpace.NIHS.bs.tar.gz#/NickSpaceView.BackModule/NickSpaceView.m

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

#import "NickSpaceView.h"
#import "NickSpaceWraps.h"
#import <appkit/appkit.h>
#import <dpsclient/wraps.h>
#import <ansi/string.h>
#import <bsd/libc.h>
#import <stdio.h>

@implementation NickSpaceView

- calcNext
{
	int i, j;
	BOOL tryingLeft, tryingRight;  // with respect to the CURRENT ORIENTATION!!

	
	for (i=0;i<trailCount;i++) {
		if ((!trails[i].dead && trails[i].maxLength > trails[i].currentLength) ||
			  trails[i].currentLength<=1) 
			continue;
		
		if (trails[i].tailOrient == UP || trails[i].tailOrient == DOWN)
			VERTEDGE(trails[i].tailEdge.row,trails[i].tailEdge.col) = 0;
		else
			HOREDGE(trails[i].tailEdge.row,trails[i].tailEdge.col) = 0;
		
		trails[i].currentLength--;
		
		/* update tail edges */	
		switch (trails[i].tailOrient) {
		
		case UP:
			if (VERTEDGE(trails[i].tailEdge.row+1,trails[i].tailEdge.col))
				trails[i].tailEdge.row++;
			else if (HOREDGE(trails[i].tailEdge.row,trails[i].tailEdge.col))
				trails[i].tailOrient = LEFT;
			else if (HOREDGE(trails[i].tailEdge.row,trails[i].tailEdge.col+1)) {
				trails[i].tailEdge.col++;
				trails[i].tailOrient = RIGHT;
			}
			break;
			
		case DOWN:
			if (VERTEDGE(trails[i].tailEdge.row-1,trails[i].tailEdge.col))
				trails[i].tailEdge.row--;
			else if (HOREDGE(trails[i].tailEdge.row-1,trails[i].tailEdge.col)) {
				trails[i].tailEdge.row--;
				trails[i].tailOrient = LEFT;
			}
			else if (HOREDGE(trails[i].tailEdge.row-1,trails[i].tailEdge.col+1)) {
				trails[i].tailEdge.row--;
				trails[i].tailEdge.col++;
				trails[i].tailOrient = RIGHT;
			}
			break;
			
		case RIGHT:
			if (HOREDGE(trails[i].tailEdge.row,trails[i].tailEdge.col+1))
				trails[i].tailEdge.col++;
			else if (VERTEDGE(trails[i].tailEdge.row,trails[i].tailEdge.col))
				trails[i].tailOrient = DOWN;
			else if (VERTEDGE(trails[i].tailEdge.row+1,trails[i].tailEdge.col)) {
				trails[i].tailEdge.row++;
				trails[i].tailOrient = UP;
			}
			break;

		case LEFT:
			if (HOREDGE(trails[i].tailEdge.row,trails[i].tailEdge.col-1))
				trails[i].tailEdge.col--;
			else if (VERTEDGE(trails[i].tailEdge.row,trails[i].tailEdge.col-1)) {
				trails[i].tailEdge.col--;
				trails[i].tailOrient = DOWN;
			}
			else if (VERTEDGE(trails[i].tailEdge.row+1,trails[i].tailEdge.col-1)) {
				trails[i].tailEdge.row++;
				trails[i].tailEdge.col--;
				trails[i].tailOrient = UP;
			}
			break;
		}
	}
			
	/* update head edges */
	for (i=0;i<trailCount;i++) {
		if (firstTime) {
			trails[i].headEdge = trails[i].tailEdge;
			trails[i].tailOrient = trails[i].headOrient;
		} else {
			trails[i].dead = NO;
			switch (trails[i].headOrient) {
				
			case UP: 
				if (trails[i].headEdge.row < horCount - 1 &&
				    !VERTEDGE(trails[i].headEdge.row + 2,trails[i].headEdge.col) &&
					!HOREDGE(trails[i].headEdge.row + 1,trails[i].headEdge.col) &&
					!HOREDGE(trails[i].headEdge.row + 1,trails[i].headEdge.col + 1))
						trails[i].headEdge.row++; /* continue UP */
				else {
					tryingLeft = (BOOL)random()%2;
					for (j=0;j<2;j++) {
						if (tryingLeft) {
							if (trails[i].headEdge.col > 0 &&
							 	!VERTEDGE(trails[i].headEdge.row,trails[i].headEdge.col-1) &&
								!VERTEDGE(trails[i].headEdge.row+1,trails[i].headEdge.col-1) &&
								!HOREDGE(trails[i].headEdge.row,trails[i].headEdge.col-1)) {
									trails[i].headOrient = LEFT;
									break;
							}	
						} else {
							if (trails[i].headEdge.col < vertCount - 1 &&
								!VERTEDGE(trails[i].headEdge.row,trails[i].headEdge.col+1) &&
								!VERTEDGE(trails[i].headEdge.row+1,trails[i].headEdge.col+1) &&
								!HOREDGE(trails[i].headEdge.row,trails[i].headEdge.col+2)) {
									trails[i].headEdge.col++;
									trails[i].headOrient = RIGHT;
									break;
							}
						}
					if (j==1)
						trails[i].dead = YES;
					else
						tryingLeft = 1 - tryingLeft;
					}
				}
				break;
					
			case DOWN: 
				if (trails[i].headEdge.row > 1 &&
				    !VERTEDGE(trails[i].headEdge.row - 2,trails[i].headEdge.col) &&
					!HOREDGE(trails[i].headEdge.row - 2,trails[i].headEdge.col) &&
					!HOREDGE(trails[i].headEdge.row - 2,trails[i].headEdge.col + 1))
						trails[i].headEdge.row--; /* continue DOWN */
				else {
					tryingRight = (BOOL)random()%2;
					for (j=0;j<2;j++) {
						if (tryingRight) {
							if (trails[i].headEdge.col > 0 &&
							 	!VERTEDGE(trails[i].headEdge.row,trails[i].headEdge.col-1) &&
								!VERTEDGE(trails[i].headEdge.row-1,trails[i].headEdge.col-1) &&
								!HOREDGE(trails[i].headEdge.row-1,trails[i].headEdge.col-1)) {
									trails[i].headEdge.row--;
									trails[i].headOrient = LEFT;
									break;
							}	
						} else {
							if (trails[i].headEdge.col < vertCount - 1 &&
								!VERTEDGE(trails[i].headEdge.row,trails[i].headEdge.col+1) &&
								!VERTEDGE(trails[i].headEdge.row-1,trails[i].headEdge.col+1) &&
								!HOREDGE(trails[i].headEdge.row-1,trails[i].headEdge.col+2)) {
									trails[i].headEdge.col++;
									trails[i].headEdge.row--;
									trails[i].headOrient = RIGHT;
									break;
							}
						}
					if (j==1)
						trails[i].dead = YES;
					else
						tryingRight = 1 - tryingRight;
					}
				}
				break;
				
			case RIGHT:
				if (trails[i].headEdge.col < vertCount - 1 &&
					!HOREDGE(trails[i].headEdge.row,trails[i].headEdge.col+2) &&
					!VERTEDGE(trails[i].headEdge.row,trails[i].headEdge.col + 1) &&
					!VERTEDGE(trails[i].headEdge.row + 1,trails[i].headEdge.col + 1))
						trails[i].headEdge.col++; // continue RIGHT
				else {
					tryingRight = (BOOL)random()%2;
					for (j=0;j<2;j++) {
						if (tryingRight) {
							if (trails[i].headEdge.row > 0 && 
								!HOREDGE(trails[i].headEdge.row-1,trails[i].headEdge.col) &&
								!HOREDGE(trails[i].headEdge.row-1,trails[i].headEdge.col+1) &&
								!VERTEDGE(trails[i].headEdge.row-1,trails[i].headEdge.col)) {
									trails[i].headOrient = DOWN;
									break;
							}
						} else {
							if (trails[i].headEdge.row < horCount - 1 &&
								!HOREDGE(trails[i].headEdge.row+1,trails[i].headEdge.col) &&
								!HOREDGE(trails[i].headEdge.row+1,trails[i].headEdge.col+1) &&
								!VERTEDGE(trails[i].headEdge.row+2,trails[i].headEdge.col)) {
									trails[i].headEdge.row++;
									trails[i].headOrient = UP;
									break;
								}
						}
					if (j==1)
						trails[i].dead = YES;
					else
						tryingRight = 1 - tryingRight;
					}	
				}
				break;
				
			case LEFT:
				if (trails[i].headEdge.col > 1 &&
					!HOREDGE(trails[i].headEdge.row,trails[i].headEdge.col-2) &&
					!VERTEDGE(trails[i].headEdge.row+1,trails[i].headEdge.col-2) &&
					!VERTEDGE(trails[i].headEdge.row,trails[i].headEdge.col-2))
						trails[i].headEdge.col--; // continue LEFT
				else {
					tryingLeft = (BOOL)random()%2;
					for (j=0;j<2;j++) {
						if (tryingLeft) {
							if (trails[i].headEdge.row > 0 && 
								!HOREDGE(trails[i].headEdge.row-1,trails[i].headEdge.col) &&
								!HOREDGE(trails[i].headEdge.row-1,trails[i].headEdge.col-1) &&
								!VERTEDGE(trails[i].headEdge.row-1,trails[i].headEdge.col-1)) {
									trails[i].headEdge.col--;
									trails[i].headOrient = DOWN;
									break;
							}
						} else {
							if (trails[i].headEdge.row < horCount - 1 &&
								!HOREDGE(trails[i].headEdge.row+1,trails[i].headEdge.col) &&
								!HOREDGE(trails[i].headEdge.row+1,trails[i].headEdge.col-1) &&
								!VERTEDGE(trails[i].headEdge.row+2,trails[i].headEdge.col-1)) {
									trails[i].headEdge.row++;
									trails[i].headEdge.col--;
									trails[i].headOrient = UP;
									break;
								}
						}
					if (j==1)
						trails[i].dead = YES;
					else
						tryingLeft = 1 - tryingLeft;
					}	
				}
			}
			if (!trails[i].dead)
				trails[i].currentLength++;
		}
		if (!trails[i].dead) {
			if (trails[i].headOrient == UP || trails[i].headOrient == DOWN) 
				VERTEDGE(trails[i].headEdge.row,trails[i].headEdge.col) = 1;
			else
				HOREDGE(trails[i].headEdge.row,trails[i].headEdge.col) = 1;
		}
		
	}
	
	firstTime = 0;
	return self;
}

- oneStep
{
	int i, level, currCol;

	/* if window level changed, reinitialize and decide whether to buffer or not */
	getWindowLevel([[self window] windowNum],&level);
	if (level != lastLevel) {
		lastLevel = level;
		if (level < NX_NORMALLEVEL) {
			[self newSize:YES];
			image = [[NXImage alloc] initSize:&(bounds.size)];
			[image lockFocus];
			PSsetgray(0);
			NXRectFill(&bounds);
			[image unlockFocus];
		} else {
			[self newSize:YES];
			if (image) {
				[image free];
				image = nil;
			}
		}
	}
	
	/* erase tail edges, as needed (calc'ed last time through */
	PSsetgray(0);
	for (i=0;i<trailCount;i++) {
		if ((!trails[i].dead && trails[i].maxLength > trails[i].currentLength) ||
			  trails[i].currentLength<=1) 
			continue;
		if (trails[i].tailOrient == UP || trails[i].tailOrient == DOWN) {
			doSeg((float)((trails[i].tailEdge.col + 1) * spacing),
					 (float)(trails[i].tailEdge.row * spacing),
					 (float)((trails[i].tailEdge.col + 1) * spacing),
					 (float)((trails[i].tailEdge.row + 1) * spacing));
		} else {
			doSeg((float)(trails[i].tailEdge.col * spacing),
					 (float)((trails[i].tailEdge.row + 1) * spacing),
					 (float)((trails[i].tailEdge.col + 1) * spacing),
					 (float)((trails[i].tailEdge.row + 1) * spacing));
		}
	}
	PSstroke();
	
	if (image) {
		[image lockFocus];
		/* erase tail edges, as needed (calc'ed last time through) */
		PSsetgray(0);
		for (i=0;i<trailCount;i++) {
			if ((!trails[i].dead && trails[i].maxLength > trails[i].currentLength) ||
				  trails[i].currentLength<=1) 
				continue;
			if (trails[i].tailOrient == UP || trails[i].tailOrient == DOWN) {
				doSeg((float)((trails[i].tailEdge.col + 1) * spacing),
						 (float)(trails[i].tailEdge.row * spacing),
						 (float)((trails[i].tailEdge.col + 1) * spacing),
						 (float)((trails[i].tailEdge.row + 1) * spacing));
			} else {
				doSeg((float)(trails[i].tailEdge.col * spacing),
						 (float)((trails[i].tailEdge.row + 1) * spacing),
						 (float)((trails[i].tailEdge.col + 1) * spacing),
						 (float)((trails[i].tailEdge.row + 1) * spacing));
			}
		}
		PSstroke();
		[image unlockFocus];
	}
		
		
	[self calcNext];
	
	/* draw head edges, as needed */
	currCol = 0; // historical accident--careful not to confuse with currColor
	PSsetrgbcolor(colors[currCol].red, colors[currCol].green, colors[currCol].blue);
	for(i = 0;i<trailCount; i++) {
		if (i==(trailCount*(currCol+1))/numColors) {
			currCol++;
			PSstroke();
			PSsetrgbcolor(colors[currCol].red, colors[currCol].green, colors[currCol].blue);
		}			
		if (trails[i].dead)
			continue;
		if (trails[i].headOrient == UP || trails[i].headOrient == DOWN) {
			PSmoveto((float)((trails[i].headEdge.col + 1) * spacing),
					 (float)(trails[i].headEdge.row * spacing));
			PSlineto((float)((trails[i].headEdge.col + 1) * spacing),
					 (float)((trails[i].headEdge.row + 1) * spacing));
		} else {
			PSmoveto((float)(trails[i].headEdge.col * spacing),
					 (float)((trails[i].headEdge.row + 1) * spacing));
			PSlineto((float)((trails[i].headEdge.col + 1) * spacing),
					 (float)((trails[i].headEdge.row + 1) * spacing));
		}
	}	
	PSstroke();
	
	if (image) {
		[image lockFocus];
		currCol = 0;
		PSsetrgbcolor(colors[currCol].red, colors[currCol].green, colors[currCol].blue);
		for(i = 0;i<trailCount; i++) {
			if (i==(trailCount*(currCol+1))/3) {
				currCol++;
				PSstroke();
				PSsetrgbcolor(colors[currCol].red, colors[currCol].green,
																 colors[currCol].blue);
			}			
			if (trails[i].dead)
				continue;
			if (trails[i].headOrient == UP || trails[i].headOrient == DOWN) {
				PSmoveto((float)((trails[i].headEdge.col + 1) * spacing),
						 (float)(trails[i].headEdge.row * spacing));
				PSlineto((float)((trails[i].headEdge.col + 1) * spacing),
						 (float)((trails[i].headEdge.row + 1) * spacing));
			} else {
				PSmoveto((float)(trails[i].headEdge.col * spacing),
						 (float)((trails[i].headEdge.row + 1) * spacing));
				PSlineto((float)((trails[i].headEdge.col + 1) * spacing),
						 (float)((trails[i].headEdge.row + 1) * spacing));
			}
		}	
		PSstroke();
		[image unlockFocus];
	}		
	
	return self;
}

- initFrame:(NXRect *)frameRect
{
	char nibPath[MAXPATHLEN];
	NXDefaultsVector defaults = {
						{ "spacing", "" },
						{ "tcRatio", "" },
						{ "tlRatio", "" },
						{ "grays", "" },
						{ "reds", "" },
						{ "greens", "" },
						{ "blues", "" },
						{ NULL } };
	char scratch[128], scratch2[64];
	int i;

	srandom(time(0));

	[super initFrame:frameRect];
	
	/* these are preserved from bezierView--I don't know if they're doing any good */
	[self allocateGState];		// For faster lock/unlockFocus
	[self setClipping:NO];		// even faster...

	/* Miscellaneous initializations */
	image = nil;
	lastLevel = 0;

	sprintf(nibPath,[[NXApp delegate] moduleDirectory:"NickSpace"]);
	strcat(nibPath,"/NickSpace.nib");
	[NXApp loadNibFile:nibPath owner:self];	
	
	/* Set target/action for each control */
	[[spaceControl setTarget:self] setAction:@selector(getSpacingFrom:)];
	[[countControl setTarget:self] setAction:@selector(getNumberFrom:)];
	[[lengthControl setTarget:self] setAction:@selector(getMaxLenFrom:)];
	[[colorWell setTarget:self] setAction:@selector(updateCurrColor:)];
	[[colorScrollers setTarget:self] setAction:@selector(scrollColor:)];
	[[[addRemoveButtons cellAt:0 :0] setTarget:self] setAction:@selector(addColor:)];
	[[[addRemoveButtons cellAt:1 :0] setTarget:self] setAction:@selector(removeColor:)];
	
	/* Check the first default; if it hasn't been written before, get all
	 * parameters from the controls; else,  read all defaults and set the controls
	 */
	NXRegisterDefaults("NickSpace",defaults);
	if (strlen(NXGetDefaultValue("NickSpace","spacing"))==0) {
	
		// Some empirically determined initial settings:
		spacing = 8;
		tcRatio = .6;
		tlRatio = 1.0;
		
		numColors = 10;
		currColor = 0;
		colors = (rgbColor *)malloc(sizeof(rgbColor)*numColors);
																	
		sprintf(scratch,"%d",spacing);
		NXWriteDefault("NickSpace","spacing",scratch);
		sprintf(scratch,"%f",tcRatio);
		NXWriteDefault("NickSpace","tcRatio",scratch);
		sprintf(scratch,"%f",tlRatio);
		NXWriteDefault("NickSpace","tlRatio",scratch);
		sprintf(scratch,"%i",numColors);
		NXWriteDefault("NickSpace","numColors",scratch);
		
		// randomize an initial set of colors:
		for (i=0;i<numColors;i++) {
			colors[i].red = (float)random()/(float)MAXLONG;
			colors[i].green = (float)random()/(float)MAXLONG;
			colors[i].blue = (float)random()/(float)MAXLONG;
			sprintf(scratch,"%f %f %f",colors[i].red, colors[i].green, colors[i].blue);
			sprintf(scratch2,"color%d",i);
			NXWriteDefault("NickSpace",scratch2,scratch);
		}
		
	} else {
		spacing = atoi(NXGetDefaultValue("NickSpace","spacing"));
		tcRatio = atof(NXGetDefaultValue("NickSpace","tcRatio"));
		tlRatio = atof(NXGetDefaultValue("NickSpace","tlRatio"));
		
		numColors = atoi(NXGetDefaultValue("NickSpace","numColors"));
		currColor = 0;
		colors = (rgbColor *)malloc(sizeof(rgbColor)*numColors);
		for (i=0;i<numColors;i++) {
			sprintf(scratch2,"color%d",i);
			sprintf(scratch,"%s",NXGetDefaultValue("NickSpace",scratch2));
			sscanf(scratch,"%f %f %f",&(colors[i].red),&(colors[i].green),&(colors[i].blue));
		}
			
	}

	[spaceControl setIntValue:spacing];
	[countControl setFloatValue:tcRatio];
	[lengthControl setFloatValue:tlRatio];
	[colorWell setColor:NXConvertRGBToColor(colors[currColor].red,colors[currColor].green,
													colors[currColor].blue)];
	sprintf(scratch,"%d/%d",currColor+1,numColors);
	[numColorsField setStringValue:scratch];
	
	[self newSize:NO];
	return self;
}

- sizeTo:(NXCoord)width :(NXCoord)height
{
	[super sizeTo:width :height];
	[self newSize:YES];
	return self;
}

- drawSelf:(const NXRect *)rects :(int)rectCount
{
	int i;
	if (!rects || !rectCount) return self;
	
	for (i=0;i<rectCount;i++)
		[image composite:NX_COPY fromRect:&(rects[i]) toPoint:&(rects[i].origin)];
	
	return self;
}

/* next two methods do initializations */
- newSize:(BOOL)freeOld;
{
	if (freeOld) {
		free(horEdges);
		free(vertEdges);
		free(trails);
	}

	horCount = (int)((bounds.size.height - 5.0) / spacing);
	vertCount = (int)((bounds.size.width - 5.0) / spacing);
	
	horEdges = (char *)malloc(HORSIZE);
	vertEdges = (char *)malloc(VERTSIZE);
	bzero(horEdges,HORSIZE);
	bzero(vertEdges,VERTSIZE);
	
	trailCount = (int)((vertCount + horCount) * tcRatio);
	trailCount = trailCount ? trailCount : 1;
	trails = (trail *)malloc(sizeof(trail)*trailCount);
	
	maxTrailLen = (vertCount + horCount) * 4 * tlRatio;
	minTrailLen = maxTrailLen/40;
	maxTrailLen = maxTrailLen < 2 ? 2 : maxTrailLen;
	minTrailLen = minTrailLen < 2 ? 2 : minTrailLen;
	
	firstTime = YES;
		
	[self startTrails];
	
	if ([self window]) {
		[self lockFocus];
		PSsetgray(0);
		NXRectFill(&bounds);
		[self unlockFocus];
	}

	if (image){
		[image lockFocus];
		PSsetgray(0);
		NXRectFill(&bounds);
		[image unlockFocus];
	}
	
	return self;
}

- startTrails
{
	int i,j;
	BOOL dup;
	int initPos[trailCount];
	

	/* This could potentially take arbitrarily long--should tighten up;
	 */	
	for (i=0;i<trailCount;) {
		dup = NO;
		if (trailCount < vertCount + horCount) {
			initPos[i] = (random() % (vertCount + horCount))*2;
			for (j=0;j<i;j++) {
				if (initPos[j] == initPos[i]) {
					dup = YES;
					break;
				}
			}
		} else
			initPos[i] = i * 2;
		if (!dup) 
			i++;
	}
		
	for (i=0;i<trailCount;i++) {
		trails[i].currentLength = 1;
		if (tlRatio == 1.0)
			trails[i].maxLength = MAXINT;
		else
			trails[i].maxLength = (random() % ((maxTrailLen - minTrailLen) + 1) + minTrailLen);
		trails[i].dead = NO;
		if (initPos[i] < vertCount) {
			trails[i].tailEdge.row = 0;
			trails[i].tailEdge.col = initPos[i];
			trails[i].headOrient = UP;
			continue;
		}
		if (vertCount <= initPos[i] && initPos[i] < vertCount + horCount) {
			trails[i].tailEdge.row = initPos[i] - vertCount;
			trails[i].tailEdge.col = vertCount;
			trails[i].headOrient = LEFT;
			continue;
		}
		if (vertCount + horCount <= initPos[i] && initPos[i] < (2*vertCount + horCount)) {
			trails[i].tailEdge.row = horCount;
			trails[i].tailEdge.col = initPos[i] - (vertCount + horCount);
			trails[i].headOrient = DOWN;
			continue;
		}
		trails[i].tailEdge.row = initPos[i] - (2*vertCount + horCount);
		trails[i].tailEdge.col = 0;
		trails[i].headOrient = RIGHT;
	}
	
	return self;
}

- inspector:sender
{
	return inspector;
}

- updateCurrColor:sender
{
	char scratch[128],scratch2[16];;
	NXConvertColorToRGB([sender color],&(colors[currColor].red),
									   &(colors[currColor].green),
									   &(colors[currColor].blue));
									   
	sprintf(scratch2,"color%d",currColor);
	sprintf(scratch,"%f %f %f",colors[currColor].red,colors[currColor].green,
							   colors[currColor].blue);
							   
	NXWriteDefault("NickSpace",scratch2,scratch);
	
	return self;
}

- scrollColor:sender
{
	char scratch[32];
	
	if ([sender selectedRow]==1)
		currColor = currColor==0 ? numColors-1 : currColor-1;
	else
		currColor = (currColor + 1)%numColors;
	
	[colorWell setColor:NXConvertRGBToColor(colors[currColor].red,
											colors[currColor].green,
											colors[currColor].blue)];
											
	sprintf(scratch,"%d/%d",currColor+1,numColors);
	[numColorsField setStringValue:scratch];
	
	return self;
}

- addColor:sender
{
	char scratch[128],scratch2[32];
	
	numColors++;
	colors = (rgbColor *)realloc(colors,sizeof(rgbColor)*numColors);
	currColor = numColors-1;
	
	colors[currColor].red = (float)random()/(float)MAXLONG;
	colors[currColor].green = (float)random()/(float)MAXLONG;
	colors[currColor].blue = (float)random()/(float)MAXLONG;
	
	sprintf(scratch2,"color%d",currColor);
	sprintf(scratch,"%f %f %f",colors[currColor].red,
							   colors[currColor].green,
							   colors[currColor].blue);
	NXWriteDefault("NickSpace",scratch2,scratch);
	
	sprintf(scratch,"%d",numColors);
	NXWriteDefault("NickSpace","numColors",scratch);
	
	[colorWell setColor:NXConvertRGBToColor(colors[currColor].red,
											colors[currColor].green,
											colors[currColor].blue)];
	
	sprintf(scratch,"%d/%d",currColor+1,numColors);
	[numColorsField setStringValue:scratch];

	return self;
}

- removeColor:sender
{
	int i;
	char scratch2[128],scratch[32];
	
	if (numColors==1)
		return self;
	
	for (i=currColor;i<numColors-1;i++) {
		colors[i] = colors[i+1];
		sprintf(scratch2,"color%d",i);
		sprintf(scratch,"%f %f %f",colors[i].red,
							   colors[i].green,
							   colors[i].blue);
		NXWriteDefault("NickSpace",scratch2,scratch);
	}
	numColors--;
	sprintf(scratch2,"color%d",numColors);
	NXRemoveDefault("NickSpace",scratch2);
	
	sprintf(scratch,"%d",numColors);
	NXWriteDefault("NickSpace","numColors",scratch);
	
	currColor %= numColors;
	
	[colorWell setColor:NXConvertRGBToColor(colors[currColor].red,
											colors[currColor].green,
											colors[currColor].blue)];
	
	sprintf(scratch,"%d/%d",currColor+1,numColors);
	[numColorsField setStringValue:scratch];

	return self;
}
		

- getSpacingFrom:sender;
{
	char scratch[128];
	spacing = [sender intValue];
	sprintf(scratch,"%d",spacing);
	NXWriteDefault("NickSpace","spacing",scratch);
	[self newSize:YES];
	return self;
}

- getNumberFrom:sender
{
	char scratch[128];
	tcRatio = [sender floatValue];
	sprintf(scratch,"%f",tcRatio);
	NXWriteDefault("NickSpace","tcRatio",scratch);
	[self newSize:YES];
	return self;
}

- getMaxLenFrom:sender
{
	char scratch[128];
	tlRatio = [sender floatValue];
	sprintf(scratch,"%f",tlRatio);
	NXWriteDefault("NickSpace","tlRatio",scratch);
	[self newSize:YES];
	return self;
}

@end

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