1#import "NickSpaceView.h"
2#import <AppKit/AppKit.h>
3#if !defined(__FreeBSD__) && !defined(__DragonFly__)
4#import <values.h>
5#endif
6#import <time.h>
7#import <limits.h>
8
9/*
10 * the file originally used MAXINT and MAXLONG
11 * ANSI c89 defines LONG_MAX and INT_MAX
12 */
13#ifndef INT_MAX
14#define INT_MAX MAXINT
15#endif
16#ifndef LONG_MAX
17#define LONG_MAX MAXLONG
18#endif
19
20
21#define COLORWIDTH 2
22#define ERASEWIDTH 5
23
24/** Draw a line segment */
25void doSeg(float x1, float y1, float x2, float y2)
26{
27  PSmoveto(x1,y1);
28  PSlineto(x2,y2);
29}
30
31@interface NSColor (GetColorsFromString)
32+ (NSColor *)colorFromStringRepresentation:(NSString *)colorString;
33- (NSString *)stringRepresentation;
34@end
35
36@implementation NSColor (GetColorsFromString)
37+ (NSColor *)colorFromStringRepresentation:(NSString *)colorString
38{
39    float r, g, b, a;
40    NSArray *array = [colorString componentsSeparatedByString:@" "];
41    if(!array) return nil;
42    if([array count] < 3) {
43        NSLog(@"%@: + colorFromStringRepresentation", [[self class] description]);
44        NSLog(@"%@: String must contain red, green, and blue components", [[self class] description]);
45        return nil;
46    }
47    r = [[array objectAtIndex:0] floatValue];
48    g = [[array objectAtIndex:1] floatValue];
49    b = [[array objectAtIndex:2] floatValue];
50    a = [array count] > 3 ? [[array objectAtIndex:3] floatValue] : 1.0;
51    return [NSColor colorWithCalibratedRed:r green:g blue:b alpha:a];
52}
53
54- (NSString *)stringRepresentation
55{
56    float r, g, b, a;
57    [[self colorUsingColorSpaceName:NSCalibratedRGBColorSpace] getRed:&r green:&g blue:&b alpha:&a];
58    return [NSString stringWithFormat:@"%f %f %f %f",r,g,b,a];
59}
60@end
61
62@interface NSString (ColorValue)
63- (NSColor *) colorValue;
64@end
65
66@implementation NSString (ColorValue)
67- (NSColor *) colorValue
68{
69  return [NSColor colorFromStringRepresentation: self];
70}
71@end
72
73@implementation NickSpaceView
74
75- (void) calcNext
76{
77  int i, j;
78  BOOL tryingLeft, tryingRight;  // with respect to the CURRENT ORIENTATION!!
79
80
81  for (i=0;i<trailCount;i++) {
82    if ((!trails[i].dead && trails[i].maxLength > trails[i].currentLength) ||
83	trails[i].currentLength<=1)
84      continue;
85
86    if (trails[i].tailOrient == UP || trails[i].tailOrient == DOWN)
87      VERTEDGE(trails[i].tailEdge.row,trails[i].tailEdge.col) = 0;
88    else
89      HOREDGE(trails[i].tailEdge.row,trails[i].tailEdge.col) = 0;
90
91    trails[i].currentLength--;
92
93    /* update tail edges */
94    switch (trails[i].tailOrient) {
95
96    case UP:
97      if (VERTEDGE(trails[i].tailEdge.row+1,trails[i].tailEdge.col))
98	trails[i].tailEdge.row++;
99      else if (HOREDGE(trails[i].tailEdge.row,trails[i].tailEdge.col))
100	trails[i].tailOrient = LEFT;
101      else if (HOREDGE(trails[i].tailEdge.row,trails[i].tailEdge.col+1)) {
102	trails[i].tailEdge.col++;
103	trails[i].tailOrient = RIGHT;
104      }
105      break;
106
107    case DOWN:
108      if (VERTEDGE(trails[i].tailEdge.row-1,trails[i].tailEdge.col))
109	trails[i].tailEdge.row--;
110      else if (HOREDGE(trails[i].tailEdge.row-1,trails[i].tailEdge.col)) {
111	trails[i].tailEdge.row--;
112	trails[i].tailOrient = LEFT;
113      }
114      else if (HOREDGE(trails[i].tailEdge.row-1,trails[i].tailEdge.col+1)) {
115	trails[i].tailEdge.row--;
116	trails[i].tailEdge.col++;
117	trails[i].tailOrient = RIGHT;
118      }
119      break;
120
121    case RIGHT:
122      if (HOREDGE(trails[i].tailEdge.row,trails[i].tailEdge.col+1))
123	trails[i].tailEdge.col++;
124      else if (VERTEDGE(trails[i].tailEdge.row,trails[i].tailEdge.col))
125	trails[i].tailOrient = DOWN;
126      else if (VERTEDGE(trails[i].tailEdge.row+1,trails[i].tailEdge.col)) {
127	trails[i].tailEdge.row++;
128	trails[i].tailOrient = UP;
129      }
130      break;
131
132    case LEFT:
133      if (HOREDGE(trails[i].tailEdge.row,trails[i].tailEdge.col-1))
134	trails[i].tailEdge.col--;
135      else if (VERTEDGE(trails[i].tailEdge.row,trails[i].tailEdge.col-1)) {
136	trails[i].tailEdge.col--;
137	trails[i].tailOrient = DOWN;
138      }
139      else if (VERTEDGE(trails[i].tailEdge.row+1,trails[i].tailEdge.col-1)) {
140	trails[i].tailEdge.row++;
141	trails[i].tailEdge.col--;
142	trails[i].tailOrient = UP;
143      }
144      break;
145    }
146  }
147
148  /* update head edges */
149  for (i=0;i<trailCount;i++) {
150    if (firstTime) {
151      trails[i].headEdge = trails[i].tailEdge;
152      trails[i].tailOrient = trails[i].headOrient;
153    } else {
154      trails[i].dead = NO;
155      switch (trails[i].headOrient) {
156
157      case UP:
158	if (trails[i].headEdge.row < horCount - 1 &&
159	    !VERTEDGE(trails[i].headEdge.row + 2,trails[i].headEdge.col) &&
160	    !HOREDGE(trails[i].headEdge.row + 1,trails[i].headEdge.col) &&
161	    !HOREDGE(trails[i].headEdge.row + 1,trails[i].headEdge.col + 1))
162	  trails[i].headEdge.row++; /* continue UP */
163	else {
164	  tryingLeft = (BOOL)random()%2;
165	  for (j=0;j<2;j++) {
166	    if (tryingLeft) {
167	      if (trails[i].headEdge.col > 0 &&
168		  !VERTEDGE(trails[i].headEdge.row,trails[i].headEdge.col-1) &&
169		  !VERTEDGE(trails[i].headEdge.row+1,trails[i].headEdge.col-1) &&
170		  !HOREDGE(trails[i].headEdge.row,trails[i].headEdge.col-1)) {
171		trails[i].headOrient = LEFT;
172		break;
173	      }
174	    } else {
175	      if (trails[i].headEdge.col < vertCount - 1 &&
176		  !VERTEDGE(trails[i].headEdge.row,trails[i].headEdge.col+1) &&
177		  !VERTEDGE(trails[i].headEdge.row+1,trails[i].headEdge.col+1) &&
178		  !HOREDGE(trails[i].headEdge.row,trails[i].headEdge.col+2)) {
179		trails[i].headEdge.col++;
180		trails[i].headOrient = RIGHT;
181		break;
182	      }
183	    }
184	    if (j==1)
185	      trails[i].dead = YES;
186	    else
187	      tryingLeft = 1 - tryingLeft;
188	  }
189	}
190	break;
191
192      case DOWN:
193	if (trails[i].headEdge.row > 1 &&
194	    !VERTEDGE(trails[i].headEdge.row - 2,trails[i].headEdge.col) &&
195	    !HOREDGE(trails[i].headEdge.row - 2,trails[i].headEdge.col) &&
196	    !HOREDGE(trails[i].headEdge.row - 2,trails[i].headEdge.col + 1))
197	  trails[i].headEdge.row--; /* continue DOWN */
198	else {
199	  tryingRight = (BOOL)random()%2;
200	  for (j=0;j<2;j++) {
201	    if (tryingRight) {
202	      if (trails[i].headEdge.col > 0 &&
203		  !VERTEDGE(trails[i].headEdge.row,trails[i].headEdge.col-1) &&
204		  !VERTEDGE(trails[i].headEdge.row-1,trails[i].headEdge.col-1) &&
205		  !HOREDGE(trails[i].headEdge.row-1,trails[i].headEdge.col-1)) {
206		trails[i].headEdge.row--;
207		trails[i].headOrient = LEFT;
208		break;
209	      }
210	    } else {
211	      if (trails[i].headEdge.col < vertCount - 1 &&
212		  !VERTEDGE(trails[i].headEdge.row,trails[i].headEdge.col+1) &&
213		  !VERTEDGE(trails[i].headEdge.row-1,trails[i].headEdge.col+1) &&
214		  !HOREDGE(trails[i].headEdge.row-1,trails[i].headEdge.col+2)) {
215		trails[i].headEdge.col++;
216		trails[i].headEdge.row--;
217		trails[i].headOrient = RIGHT;
218		break;
219	      }
220	    }
221	    if (j==1)
222	      trails[i].dead = YES;
223	    else
224	      tryingRight = 1 - tryingRight;
225	  }
226	}
227	break;
228
229      case RIGHT:
230	if (trails[i].headEdge.col < vertCount - 1 &&
231	    !HOREDGE(trails[i].headEdge.row,trails[i].headEdge.col+2) &&
232	    !VERTEDGE(trails[i].headEdge.row,trails[i].headEdge.col + 1) &&
233	    !VERTEDGE(trails[i].headEdge.row + 1,trails[i].headEdge.col + 1))
234	  trails[i].headEdge.col++; // continue RIGHT
235	else {
236	  tryingRight = (BOOL)random()%2;
237	  for (j=0;j<2;j++) {
238	    if (tryingRight) {
239	      if (trails[i].headEdge.row > 0 &&
240		  !HOREDGE(trails[i].headEdge.row-1,trails[i].headEdge.col) &&
241		  !HOREDGE(trails[i].headEdge.row-1,trails[i].headEdge.col+1) &&
242		  !VERTEDGE(trails[i].headEdge.row-1,trails[i].headEdge.col)) {
243		trails[i].headOrient = DOWN;
244		break;
245	      }
246	    } else {
247	      if (trails[i].headEdge.row < horCount - 1 &&
248		  !HOREDGE(trails[i].headEdge.row+1,trails[i].headEdge.col) &&
249		  !HOREDGE(trails[i].headEdge.row+1,trails[i].headEdge.col+1) &&
250		  !VERTEDGE(trails[i].headEdge.row+2,trails[i].headEdge.col)) {
251		trails[i].headEdge.row++;
252		trails[i].headOrient = UP;
253		break;
254	      }
255	    }
256	    if (j==1)
257	      trails[i].dead = YES;
258	    else
259	      tryingRight = 1 - tryingRight;
260	  }
261	}
262	break;
263
264      case LEFT:
265	if (trails[i].headEdge.col > 1 &&
266	    !HOREDGE(trails[i].headEdge.row,trails[i].headEdge.col-2) &&
267	    !VERTEDGE(trails[i].headEdge.row+1,trails[i].headEdge.col-2) &&
268	    !VERTEDGE(trails[i].headEdge.row,trails[i].headEdge.col-2))
269	  trails[i].headEdge.col--; // continue LEFT
270	else {
271	  tryingLeft = (BOOL)random()%2;
272	  for (j=0;j<2;j++) {
273	    if (tryingLeft) {
274	      if (trails[i].headEdge.row > 0 &&
275		  !HOREDGE(trails[i].headEdge.row-1,trails[i].headEdge.col) &&
276		  !HOREDGE(trails[i].headEdge.row-1,trails[i].headEdge.col-1) &&
277		  !VERTEDGE(trails[i].headEdge.row-1,trails[i].headEdge.col-1)) {
278		trails[i].headEdge.col--;
279		trails[i].headOrient = DOWN;
280		break;
281	      }
282	    } else {
283	      if (trails[i].headEdge.row < horCount - 1 &&
284		  !HOREDGE(trails[i].headEdge.row+1,trails[i].headEdge.col) &&
285		  !HOREDGE(trails[i].headEdge.row+1,trails[i].headEdge.col-1) &&
286		  !VERTEDGE(trails[i].headEdge.row+2,trails[i].headEdge.col-1)) {
287		trails[i].headEdge.row++;
288		trails[i].headEdge.col--;
289		trails[i].headOrient = UP;
290		break;
291	      }
292	    }
293	    if (j==1)
294	      trails[i].dead = YES;
295	    else
296	      tryingLeft = 1 - tryingLeft;
297	  }
298	}
299      }
300      if (!trails[i].dead)
301	trails[i].currentLength++;
302    }
303    if (!trails[i].dead) {
304      if (trails[i].headOrient == UP || trails[i].headOrient == DOWN)
305	VERTEDGE(trails[i].headEdge.row,trails[i].headEdge.col) = 1;
306      else
307	HOREDGE(trails[i].headEdge.row,trails[i].headEdge.col) = 1;
308    }
309
310  }
311
312  firstTime = 0;
313}
314
315-(void)oneStep
316{
317  int i, level, currCol;
318  NSRect bounds = [self bounds];
319
320
321  /* if window level changed, reinitialize and decide whether to buffer or not */
322  level = [[self window] level];
323  if (level != lastLevel)
324    {
325      lastLevel = level;
326      if (level < NSNormalWindowLevel)
327	{
328	  [self newSize:YES];
329	  image = [[NSImage alloc] initWithSize: bounds.size];
330	  [image lockFocus];
331	  PSsetgray(0);
332	  NSRectFill(bounds);
333	  [image unlockFocus];
334      }
335      else
336	{
337	  [self newSize:YES];
338	  if (image)
339	    {
340	      [image free];
341	      image = nil;
342	    }
343	}
344    }
345
346  /* erase tail edges, as needed (calc'ed last time through */
347  PSsetgray(0);
348  PSsetlinewidth(ERASEWIDTH);
349  for (i=0;i<trailCount;i++)
350    {
351      if ((!trails[i].dead && trails[i].maxLength > trails[i].currentLength) ||
352	  trails[i].currentLength<=1)
353	continue;
354
355      if (trails[i].tailOrient == UP || trails[i].tailOrient == DOWN)
356	{
357	  doSeg((float)((trails[i].tailEdge.col + 1) * spacing),
358		(float)(trails[i].tailEdge.row * spacing),
359		(float)((trails[i].tailEdge.col + 1) * spacing),
360		(float)((trails[i].tailEdge.row + 1) * spacing));
361	}
362      else
363	{
364	  doSeg((float)(trails[i].tailEdge.col * spacing),
365		(float)((trails[i].tailEdge.row + 1) * spacing),
366		(float)((trails[i].tailEdge.col + 1) * spacing),
367		(float)((trails[i].tailEdge.row + 1) * spacing));
368	}
369    }
370  PSstroke();
371
372  if (image)
373    {
374      [image lockFocus];
375      /* erase tail edges, as needed (calc'ed last time through) */
376      PSsetgray(0);
377      PSsetlinewidth(ERASEWIDTH);
378      for (i=0;i<trailCount;i++)
379	{
380	  if ((!trails[i].dead && trails[i].maxLength > trails[i].currentLength) ||
381	      trails[i].currentLength<=1)
382	    continue;
383
384	  if (trails[i].tailOrient == UP || trails[i].tailOrient == DOWN)
385	    {
386	      doSeg((float)((trails[i].tailEdge.col + 1) * spacing),
387		    (float)(trails[i].tailEdge.row * spacing),
388		    (float)((trails[i].tailEdge.col + 1) * spacing),
389		    (float)((trails[i].tailEdge.row + 1) * spacing));
390	    }
391	  else
392	    {
393	      doSeg((float)(trails[i].tailEdge.col * spacing),
394		    (float)((trails[i].tailEdge.row + 1) * spacing),
395		    (float)((trails[i].tailEdge.col + 1) * spacing),
396		    (float)((trails[i].tailEdge.row + 1) * spacing));
397	    }
398	}
399    PSstroke();
400    [image unlockFocus];
401  }
402
403
404  [self calcNext];
405
406  /* draw head edges, as needed */
407  currCol = 0; // historical accident--careful not to confuse with currColor
408  [[[colors objectAtIndex: currCol] colorValue] set];
409  for(i = 0;i<trailCount; i++)
410    {
411      PSsetlinewidth(COLORWIDTH);
412      if (i==(trailCount*(currCol+1))/numColors)
413	{
414	  currCol++;
415	  PSstroke();
416	  [[[colors objectAtIndex: currCol] colorValue] set];
417	}
418      if (trails[i].dead)
419	continue;
420      if (trails[i].headOrient == UP || trails[i].headOrient == DOWN)
421	{
422	  PSmoveto((float)((trails[i].headEdge.col + 1) * spacing),
423		   (float)(trails[i].headEdge.row * spacing));
424	  PSlineto((float)((trails[i].headEdge.col + 1) * spacing),
425		   (float)((trails[i].headEdge.row + 1) * spacing));
426	}
427      else
428	{
429	  PSmoveto((float)(trails[i].headEdge.col * spacing),
430		   (float)((trails[i].headEdge.row + 1) * spacing));
431	  PSlineto((float)((trails[i].headEdge.col + 1) * spacing),
432		   (float)((trails[i].headEdge.row + 1) * spacing));
433	}
434    }
435  PSstroke();
436
437  if (image)
438    {
439      [image lockFocus];
440      currCol = 0;
441      [[[colors objectAtIndex: currCol] colorValue] set];
442      PSsetlinewidth(COLORWIDTH);
443      for(i = 0;i<trailCount; i++)
444	{
445	  if (i==(trailCount*(currCol+1))/3)
446	    {
447	      currCol++;
448	      PSstroke();
449	      [[[colors objectAtIndex: currCol] colorValue] set];
450	    }
451
452	  if (trails[i].dead)
453	    continue;
454
455	  if (trails[i].headOrient == UP || trails[i].headOrient == DOWN)
456	    {
457	      PSmoveto((float)((trails[i].headEdge.col + 1) * spacing),
458		       (float)(trails[i].headEdge.row * spacing));
459	      PSlineto((float)((trails[i].headEdge.col + 1) * spacing),
460		       (float)((trails[i].headEdge.row + 1) * spacing));
461	    }
462	  else
463	    {
464	      PSmoveto((float)(trails[i].headEdge.col * spacing),
465		       (float)((trails[i].headEdge.row + 1) * spacing));
466	      PSlineto((float)((trails[i].headEdge.col + 1) * spacing),
467		       (float)((trails[i].headEdge.row + 1) * spacing));
468	    }
469	}
470      PSstroke();
471      [image unlockFocus];
472    }
473}
474
475-(id)initWithFrame:(NSRect)frameRect
476{
477  NSString *defaults = [NSString stringWithString: @"{\"spacing\" = \"\"; \"tcRatio\" = \"\"; \"tlRatio\" = \"\";}"];
478  NSDictionary *defDict = [defaults propertyList];
479  NSUserDefaults *userDef = [NSUserDefaults standardUserDefaults];
480  int i;
481
482  srandom(time(0));
483
484  if((self = [super initWithFrame: frameRect]) != nil)
485    {
486
487      /* these are preserved from bezierView--I don't know if they're doing any good */
488      [self allocateGState];		// For faster lock/unlockFocus
489      // [self setClipping:NO];		// even faster...
490
491      /* Miscellaneous initializations */
492      image = nil;
493      lastLevel = 0;
494
495      if(![NSBundle loadNibNamed: @"NickSpace" owner:self])
496	{
497	  NSLog(@"Failed to load inspector");
498	}
499
500      /* Set target/action for buttons in the matrix */
501      [[addRemoveButtons cellAtRow:0 column:0] setTarget:self];
502      [[addRemoveButtons cellAtRow:0 column:0] setAction:@selector(addColor:)];
503      [[addRemoveButtons cellAtRow:1 column:0] setTarget:self];
504      [[addRemoveButtons cellAtRow:1 column:0] setAction:@selector(removeColor:)];
505
506      /* Check the first default; if it hasn't been written before, get all
507       * parameters from the controls; else,  read all defaults and set the controls
508       */
509      [userDef registerDefaults: defDict];
510      if ([[userDef stringForKey: @"spacing"] length] == 0)
511	{
512	  // Some empirically determined initial settings:
513	  spacing = 8;
514	  tcRatio = .6;
515	  tlRatio = 1.0;
516
517	  numColors = 10;
518	  currColor = 0;
519	  colors = [[NSMutableArray alloc] init];
520
521	  [userDef setFloat: spacing forKey: @"spacing"];
522	  [userDef setFloat: tcRatio forKey: @"tcRatio"];
523	  [userDef setFloat: tlRatio forKey: @"tlRatio"];
524	  [userDef setInteger: numColors forKey: @"numColors"];
525
526	  // randomize an initial set of colors:
527	  for (i=0;i<numColors;i++)
528	    {
529	      float red = (float)random()/(float)LONG_MAX;
530	      float green = (float)random()/(float)LONG_MAX;
531	      float blue = (float)random()/(float)LONG_MAX;
532	      NSColor *color = [NSColor colorWithCalibratedRed: red
533					green: green
534					blue: blue
535					alpha: 1.0];
536	      [colors addObject: [color stringRepresentation]];
537	    }
538
539	  [userDef setObject: colors forKey: @"colors"];
540	}
541      else
542	{
543	  spacing = [userDef floatForKey: @"spacing"];
544	  tcRatio = [userDef floatForKey: @"tcRatio"];
545	  tlRatio = [userDef floatForKey: @"tlRatio"];
546	  numColors = [userDef integerForKey: @"numColors"];
547	  colors = [[NSMutableArray alloc] initWithArray: AUTORELEASE([userDef arrayForKey: @"colors"])];
548	  currColor = 0;
549	}
550
551      [spaceControl setIntValue:spacing];
552      [countControl setFloatValue:tcRatio];
553      [lengthControl setFloatValue:tlRatio];
554      [colorWell setColor: [[colors objectAtIndex: currColor] colorValue]];
555      [numColorsField setStringValue: [NSString stringWithFormat: @"%d/%d", currColor+1, numColors]];
556
557      [self newSize:NO];
558    }
559  return self;
560}
561
562- (void) setFrame: (NSRect)frame
563{
564  [super setFrame: frame];
565  [self newSize:YES];
566}
567
568/*
569- drawSelf:(const NXRect *)rects :(int)rectCount
570{
571  int i;
572  if (!rects || !rectCount) return self;
573
574  for (i=0;i<rectCount;i++)
575    [image composite:NX_COPY fromRect:&(rects[i]) toPoint:&(rects[i].origin)];
576
577  return self;
578}
579*/
580
581- (void) drawRect:(NSRect)rects
582{
583  PSsetlinewidth(0);
584  PSsetgray(0);
585  NSRectFill(rects);
586}
587
588/* next two methods do initializations */
589- newSize:(BOOL)freeOld;
590{
591  NSRect bounds = [self bounds];
592
593  if (freeOld) {
594    free(horEdges);
595    free(vertEdges);
596    free(trails);
597  }
598
599  horCount = (int)((bounds.size.height - 5.0) / spacing);
600  vertCount = (int)((bounds.size.width - 5.0) / spacing);
601
602  horEdges = (char *)malloc(HORSIZE);
603  vertEdges = (char *)malloc(VERTSIZE);
604  bzero(horEdges,HORSIZE);
605  bzero(vertEdges,VERTSIZE);
606
607  trailCount = (int)((vertCount + horCount) * tcRatio);
608  trailCount = trailCount ? trailCount : 1;
609  trails = (trail *)malloc(sizeof(trail)*trailCount);
610
611  maxTrailLen = (vertCount + horCount) * 4 * tlRatio;
612  minTrailLen = maxTrailLen/40;
613  maxTrailLen = maxTrailLen < 2 ? 2 : maxTrailLen;
614  minTrailLen = minTrailLen < 2 ? 2 : minTrailLen;
615
616  firstTime = YES;
617
618  [self startTrails];
619
620  if ([self window]) {
621    [self lockFocus];
622    PSsetgray(0);
623    NSRectFill(bounds);
624    [self unlockFocus];
625  }
626
627  if (image){
628    [image lockFocus];
629    PSsetgray(0);
630    NSRectFill(bounds);
631    [image unlockFocus];
632  }
633
634  return self;
635}
636
637- startTrails
638{
639  int i,j;
640  BOOL dup;
641  int initPos[trailCount];
642
643
644  /* This could potentially take arbitrarily long--should tighten up;
645   */
646  for (i=0;i<trailCount;) {
647    dup = NO;
648    if (trailCount < vertCount + horCount) {
649      initPos[i] = (random() % (vertCount + horCount))*2;
650      for (j=0;j<i;j++) {
651	if (initPos[j] == initPos[i]) {
652	  dup = YES;
653	  break;
654	}
655      }
656    } else
657      initPos[i] = i * 2;
658    if (!dup)
659      i++;
660  }
661
662  for (i=0;i<trailCount;i++) {
663    trails[i].currentLength = 1;
664    if (tlRatio == 1.0)
665      trails[i].maxLength = INT_MAX;
666    else
667      trails[i].maxLength = (random() % ((maxTrailLen - minTrailLen) + 1) + minTrailLen);
668    trails[i].dead = NO;
669    if (initPos[i] < vertCount) {
670      trails[i].tailEdge.row = 0;
671      trails[i].tailEdge.col = initPos[i];
672      trails[i].headOrient = UP;
673      continue;
674    }
675    if (vertCount <= initPos[i] && initPos[i] < vertCount + horCount) {
676      trails[i].tailEdge.row = initPos[i] - vertCount;
677      trails[i].tailEdge.col = vertCount;
678      trails[i].headOrient = LEFT;
679      continue;
680    }
681    if (vertCount + horCount <= initPos[i] && initPos[i] < (2*vertCount + horCount)) {
682      trails[i].tailEdge.row = horCount;
683      trails[i].tailEdge.col = initPos[i] - (vertCount + horCount);
684      trails[i].headOrient = DOWN;
685      continue;
686    }
687    trails[i].tailEdge.row = initPos[i] - (2*vertCount + horCount);
688    trails[i].tailEdge.col = 0;
689    trails[i].headOrient = RIGHT;
690  }
691
692  return self;
693}
694
695- (id)inspector:(id)sender
696{
697  return inspector;
698}
699
700- (id)updateCurrColor:(id)sender
701{
702  NSColor *color = [sender color];
703  [colors replaceObjectAtIndex: currColor withObject: [color stringRepresentation]];
704  [[NSUserDefaults standardUserDefaults] setObject: colors forKey: @"colors"];
705  return self;
706}
707
708- (id)scrollColor:(id)sender
709{
710  if ([sender selectedRow]==1)
711    currColor = currColor==0 ? numColors-1 : currColor-1;
712  else
713    currColor = (currColor + 1)%numColors;
714
715  [colorWell setColor: [[colors objectAtIndex: currColor] colorValue]];
716  [numColorsField setStringValue: [NSString stringWithFormat: @"%d/%d", currColor+1, numColors]];
717  return self;
718}
719
720- (id)addColor:(id)sender
721{
722  float
723    red = (float)random()/(float)LONG_MAX,
724    green = (float)random()/(float)LONG_MAX,
725    blue = (float)random()/(float)LONG_MAX;
726  NSUserDefaults *userDefs = [NSUserDefaults standardUserDefaults];
727  NSColor  *color = [NSColor colorWithCalibratedRed: red
728			     green: green
729			     blue: blue
730			     alpha: 1.0];
731
732  numColors++;
733  currColor = numColors-1;
734
735  [colors addObject: [color stringRepresentation]];
736
737  [userDefs setObject: colors forKey: @"colors"];
738  [userDefs setInteger: numColors forKey: @"numColors"];
739  [colorWell setColor: color];
740
741  [numColorsField setStringValue: [NSString stringWithFormat: @"%d/%d", currColor+1, numColors]];
742
743  return self;
744}
745
746- (id)removeColor: (id)sender
747{
748  NSUserDefaults *userDefs = [NSUserDefaults standardUserDefaults];
749  NSColor *cColor = nil;
750
751  if (numColors==1)
752    return self;
753
754  [colors removeLastObject];
755  [userDefs setObject: colors forKey: @"colors"];
756
757  numColors--;
758  [userDefs setInteger: numColors forKey: @"numColors"];
759
760  currColor %= numColors;
761  cColor = [[colors objectAtIndex: currColor] colorValue];
762  [colorWell setColor: cColor];
763  [numColorsField setStringValue: [NSString stringWithFormat: @"%d/%d", currColor+1, numColors]];
764
765  return self;
766}
767
768
769- getSpacingFrom:sender;
770{
771  NSUserDefaults *userDefs = [NSUserDefaults standardUserDefaults];
772  spacing = [sender intValue];
773  [userDefs setInteger: spacing forKey: @"spacing"];
774  [self newSize:YES];
775  return self;
776}
777
778- getNumberFrom:sender
779{
780  NSUserDefaults *userDefs = [NSUserDefaults standardUserDefaults];
781  tcRatio = [sender floatValue];
782  [userDefs setFloat: tcRatio forKey: @"tcRatio"];
783  [self newSize:YES];
784  return self;
785}
786
787- getMaxLenFrom:sender
788{
789  NSUserDefaults *userDefs = [NSUserDefaults standardUserDefaults];
790  tlRatio = [sender floatValue];
791  [userDefs setFloat: tlRatio forKey: @"tlRatio"];
792  [self newSize:YES];
793  return self;
794}
795
796@end
797