ftp.nice.ch/pub/next/tools/screen/backspace/Roaches.1.1.s.tar.gz#/Roaches/RoachesView.m

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

/* RoachesView is Copyright 1994 by Brian Hill <bhill@physics.ucla.edu>. */

#import "Roach.h"
#import "RoachesView.h"
#import <dpsclient/dpswraps.h>
#import "RoachWraps.h"
#import "point.h"
#ifdef TIMING
#import "Timing.h"
#endif

#define WINDOW_TITLE "Icch!"
#define INTERVAL 50000 /* Pacemaker interval in microseconds. */

void freeRectangles(windowRectangle *aWindowRectangle) 
{
  windowRectangle *nextOne;
  while ((nextOne = aWindowRectangle->next)) {
    free(aWindowRectangle);
    aWindowRectangle = nextOne;
  } 
  free(aWindowRectangle);
  return;
}

BOOL RGBString2Color(const char *colorString, NXColor *color)
{
  float rgb[3];
  if (colorString == (char *)NULL || 
      sscanf(colorString, "%f %f %f", &rgb[0], &rgb[1], &rgb[2]) != 3) {
    *color =  NXConvertRGBToColor(1.0f, 1.0f, 1.0f);
    return NO; /* On error, color is white. */
  }
  *color =  NXConvertRGBToColor(rgb[0], rgb[1], rgb[2]);
  return YES;
}

/* colorString must hold MAX_LENGTH characters (counting null terminator). */
/* Assuming colors are non-negative, less than or equal to 1.0, and written */
/* with the default precision of 6, MAX_LENGTH = 27. */
#define MAX_LENGTH 27
void color2RGBString(const NXColor *color, char *colorString)
{
  sprintf(colorString, "%f %f %f", 
	  NXRedComponent(*color),
	  NXGreenComponent(*color),
	  NXBlueComponent(*color));
  return;
}

@implementation RoachesView

- initFrame:(const NXRect *)frameRect
{
  unsigned i, initialNumber;
  srandom((int)getpid()); /* pid_t is an int in NS. */
  [super initFrame:frameRect];
  ctxt = DPSGetCurrentContext();
  [self allocateGState];
  [self setClipping:NO];
  [self clearSharedImages];
  /* roachZone is not freeable for best performance so 
   * must explicitly be destroyed in free method. */
  roachZone = NXCreateZone(vm_page_size, vm_page_size, NO); 
  /* In some sense, windowRectangles points to the -1st window in the list.
   * The only member of windowRectangles that is ever consulted is next. */
  windowRectangles = (windowRectangle *)NXZoneMalloc(roachZone, sizeof(windowRectangle));
  windowRectangles->next = (windowRectangle *)NULL;
  lastWindowRectangle = windowRectangles;
  [self readBackgroundColor];
  [self readRoachesColor];
  [self readRoachesSpeed];
  initialNumber = [self readRoachesNumber];
  roachList = [[List allocFromZone:roachZone] initCount:initialNumber];
  for (i = 0; i < initialNumber; ++i) {
    [self addRoach];
  }
  sizeInvalid = YES;
  backgroundColor = NX_COLORBLACK;
#ifdef TIMING
  stderrStream = NXOpenFile(fileno(stderr), NX_WRITEONLY);
  timer0 = [[Timing newWithTag:0] reset];
  timer1 = [[Timing newWithTag:1] reset];
  timer2 = [[Timing newWithTag:2] reset];
  timer10 = [[Timing newWithTag:10] reset];
  timer11 = [[Timing newWithTag:11] reset];
  timer12 = [[Timing newWithTag:12] reset];
  timer3 = [[Timing newWithTag:3] reset];
#endif
  pacemaker =[[Pacemaker allocFromZone:roachZone] initInterval:(long)INTERVAL];
  return self;
}

- readBackgroundColor
{
  const char *colorString;
  if ([self shouldDrawColor]) {
    colorString = NXReadDefault("NeXT1", "BackgroundColor"); 
    while (RGBString2Color(colorString, &backgroundColor) == NO) {
      /* Background color never set or dwrite botched. */
      colorString = "0.333338 0.333338 0.466674";
    }
  } else {
    colorString = NXReadDefault("NeXT1", "BWBackgroundColor"); 
    while (RGBString2Color(colorString, &backgroundColor) == NO) {
      /* Black and white background color never set or dwrite botched. */
      colorString = "0.333338 0.333338 0.333338"; /* Strange rounding. */
    }
  }
  return self;
}

- readRoachesSpeed
{
  const char *speedString;
  if (!(speedString = NXReadDefault("BackSpace", "RoachesSpeed"))) {
    speedString = SPEED_STRING;
  }
  sscanf(speedString, "%u", &speed);
  return self;
}

- (float)scaledSpeed
{
  return scaledSpeed;
}

- (unsigned)readRoachesNumber
{
  unsigned number;
  const char *numberString;
  if (!(numberString = NXReadDefault("BackSpace", "RoachesNumber"))) {
    numberString = NUMBER_STRING;
  }
  sscanf(numberString, "%u", &number);
  return number;
}

/* This only gets called once.  roachesColor will only change when
 * the colorWell changes it. */
- readRoachesColor
{
  const char *colorString;
  colorString = NXReadDefault("BackSpace", "RoachesColor");
  if (colorString) {
    RGBString2Color(colorString, &roachesColor);
  } else {
    /* User's preference doesn't exist.  Also, colorWell may not be 
     * instantiated and connected yet, so constants defined in 
     * RoachesView.h are used. */
    roachesColor = COLORROACHCOLOR;
  }
  return self;
}

- (NXColor)backgroundColor
{
  return backgroundColor;
}

- takeSpeedFrom:sender
{
  char speedString[10];
  speed = [sender intValue];
  [speedField setIntValue:speed];
  sprintf(speedString, "%d", speed);
  NXWriteDefault("BackSpace", "RoachesSpeed", speedString);
  return self;
}

- takeNumberFrom:sender
{
  int oldNumber, newNumber;
  char numberString[10];
  oldNumber = [roachList count];
  newNumber = [sender intValue];
  if (oldNumber < newNumber) {
    while (oldNumber++ < newNumber) {
      [self addRoach];
    }
    sizeInvalid = YES;
  } else {
    while (oldNumber-- > newNumber) {
      [self deleteRoach:[roachList lastObject]];
    }
  }
  sprintf(numberString, "%d", [roachList count]);
  NXWriteDefault("BackSpace", "RoachesNumber", numberString);
  return self;
}

- takeColorFrom:sender
{
  unsigned i, count;
  char colorString[MAX_LENGTH];
  roachesColor = [sender color];
  [self clearSharedImages];
  count = [roachList count];
  for (i = 0; i < count; ++i) {
    [[roachList objectAt:i] setColor:roachesColor];
  }
  color2RGBString(&roachesColor, colorString);
  NXWriteDefault("BackSpace", "RoachesColor", colorString);
  return self;
}  

- (BOOL *)imageClean
{
  return imageClean;
}

- (NXImage **)sharedImages
{
  return sharedImages;
}

/* It will be necessary to call clearSharedImages in two places: (1) If the
 * Roaches color changes, and (2) if the background color changes. */

- clearSharedImages
{
  unsigned i;
  for (i = 0; i < RESOLUTION; ++i) imageClean[i] = NO;
  return self;
}

- (BOOL)useBufferedWindow
{
  return NO;
  /* On my non-turbo color machine, a full-screen buffered window takes
   * annoyingly long to redraw, so I am back to unbuffered windows.
   * To reduce flicker in this case, I have interleaved the draw and 
   * erase messages in oneStep.  This necessitated a change in the
   * Roach okPosition method: the position cannot conflict with either
   * the present or the previous position. */
}

- addRoach;
{
  id newRoach;
  newRoach = [Roach allocFromZone:roachZone];
  [roachList addObject:newRoach];
  [[newRoach initRoachesView:self] setColor:roachesColor];
  if (numberField && numberSlider) {
    /* Only try to set if instantiated. */
    [numberField setIntValue:[roachList count]];
    [numberSlider setIntValue:[roachList count]];
  } 
  return newRoach;
}

- deleteRoach:roachGettingDeleted
{
  /* The following combination erases the last drawn image. */
  [self lockFocus];
  PSsetrgbcolor(NXRedComponent(backgroundColor),
		NXGreenComponent(backgroundColor),
		NXBlueComponent(backgroundColor));
  [roachGettingDeleted erase];
  [self unlockFocus];
  [roachList removeObject:roachGettingDeleted];
  [roachGettingDeleted free];
  if (numberField && numberSlider) {
    /* Only try to set if instantiated. */
    [numberField setIntValue:[roachList count]];
    [numberSlider setIntValue:[roachList count]];
  } 
  return self;
}

- free
{
  int i;
  freeRectangles(windowRectangles);
  /* free instances of list */
  [[roachList freeObjects] free];
  for (i = 0; i < RESOLUTION; ++i) {
    if (sharedImages[i]) [sharedImages[i] free];
  }
  [pacemaker free];
  NXDestroyZone(roachZone);
  return [super free];
}

- repositionRoaches 
{
  unsigned i;
  for (i = 0; i < [roachList count]; ++i) {
    id roachToReposition;
    roachToReposition = [roachList objectAt:i];
    if (![roachToReposition reposition]) {
      [self deleteRoach:roachToReposition];
      i--; /* Do object at i over. */
    }
  }
  return self;
}
  
/* Update one windowRectangle. */
- update 
{
  int yesOrNo; /* psops.h says PSgetboolean takes a pointer to an int. */
  float x, y, width, height;
  int windowNumber;

#ifdef TIMING
  [timer2 enter:PSTIME];
#endif

  RPSupdate(next, &yesOrNo,
	    &x, &y, &width, &height,
	    &windowNumber, &currentWindowNumber);

  if (yesOrNo == YES) {
    /* Malloc space if necessary */
    if (!lastWindowRectangle->next) {
      lastWindowRectangle->next = 
	(windowRectangle *)NXZoneMalloc(roachZone, sizeof(windowRectangle));
      lastWindowRectangle->next->next = (windowRectangle *)NULL;
    }
    lastWindowRectangle->next->rectangle.size.height = height;
    lastWindowRectangle->next->rectangle.size.width = width;
    lastWindowRectangle->next->rectangle.origin.y = y;
    lastWindowRectangle->next->rectangle.origin.x = x;
    lastWindowRectangle->next->number = windowNumber;
    lastWindowRectangle = lastWindowRectangle->next;
    next++;
  } else {
    if (lastWindowRectangle->next) {
      freeRectangles(lastWindowRectangle->next);
    }
    lastWindowRectangle->next = (windowRectangle *)NULL;
    lastWindowRectangle = windowRectangles;
    next = 0;
  }
#ifdef TIMING
  [timer2 leave];
#endif
  return self;
}

- oneStep
{
  /* For this particular screen saver, the behavior depends on the position
   * of other application's windows.  Hence it must be done while in contact
   * with the window server.  drawSelf does a fallback amount of drawing if
   * I'm not on screen.  Otherwise, it just calls this method.  this method
   * is called directly by BackSpace, but only when I'm on screen.  BackSpace
   * does lockFocus before calling this method. */
  unsigned i, count;
  NXRect windowFrame;
  /* Make sure roachesViewOffset is sensibly set here.  Probably should put a
   * hook into moveTo:: or something so that roachesViewOffset gets 
   * recalculated less often.  RoachesViewOffset is the RoachesView origin
   * in Window coordinates plus the Window origin in screen coordinates. */
#ifdef TIMING
  [timer1 enter:PSTIME];
  [timer10 enter:PSTIME];
#endif
  zero(&roachesViewOffset);
  [self convertPoint:&roachesViewOffset toView:nil];
  [window getFrame:&windowFrame];
  translate(&roachesViewOffset, &windowFrame.origin);
  if (sizeInvalid == YES) {
    [self repositionRoaches];
    sizeInvalid = NO;
  }
#ifdef TIMING
  [timer10 leave];
  [timer11 enter:PSTIME];
#endif
  [roachList makeObjectsPerform:@selector(oneStep)];
  PSsetrgbcolor(NXRedComponent(backgroundColor),
		NXGreenComponent(backgroundColor),
		NXBlueComponent(backgroundColor));
  PSsetexposurecolor();
  [self update];
  count = [roachList count];

#ifdef TIMING
  [timer11 leave];
  [timer12 enter:PSTIME];
#endif
  {
    long actualInterval;
    float ratio;
    actualInterval = [pacemaker doze];
    if (actualInterval > 2 * INTERVAL) {
      ratio = 2.0f;
    } else {
      ratio = (float)actualInterval / (float)INTERVAL;
    }
    scaledSpeed = ratio * speed;
  }
  for (i = 0; i < count; ++i) {
    [[roachList objectAt:i] prepareToDraw];
    [[roachList objectAt:i] erase];
    [[roachList objectAt:i] compositeDraw];
  }
#ifdef TIMING
  [timer12 leave];
  [timer1 leave];
#endif

  /* Timing results (20 Roaches--non-turbo color):

(gdb) print [timer0 summary:stderrStream]
Timer 0 : 247 trials App: 2.576158  Server: 0.976000  
                    Percent Server: 0.274763 Total: 3.552158
$8 = (struct Timing *) 0x18bee0
(gdb) print [timer1 summary:stderrStream]
Timer 1 : 247 trials App: 1.515907  Server: 1.584000  
                    Percent Server: 0.510983 Total: 3.099907
$9 = (struct Timing *) 0x17d9b4
(gdb) print [timer20 summary:stderrStream]
Timer 10 : 4940 trials App: 1.312533  Server: 7.856000  
                    Percent Server: 0.856844 Total: 9.168533                
$10 = (struct Timing *) 0x17da64
(gdb) print [timer21 summary:stderrStream] 
Timer 11 : 4940 trials App: 0.875321  Server: 9.040000  
                    Percent Server: 0.911720 Total: 9.915321
$11 = (struct Timing *) 0x17db14
(gdb) print [timer22 summary:stderrStream] 
Timer 12 : 4940 trials App: 1.297191  Server: 5.440000  
                    Percent Server: 0.807458 Total: 6.737191
$12 = (struct Timing *) 0x17dbc4
     
Timer 10's contribution will become a negligible part of the total after
a large number of oneStep's.

  */

  return self;
}

- (BOOL)okPosition:(const NXPoint *)position :(NXCoord)size
{
  /* Many Roaches will execute okPosition:: and positionExposed:: with 
   * each invocation of oneStep.  The relevant RoachesView instance 
   * variables are therefore updated in oneStep. */
  unsigned i, count, flag;
  flag = 0;
  count = [roachList count];
  for (i = 0; i < count; ++i) {
    if ([[roachList objectAt:i] okPosition:position :size] == NO && ++flag > 1)
      return NO;
  }
  /* Flag ought to be precisely one now, but why check. */
  return insideBounds(position, &bounds, size);
}

- (int)positionExposed:(const NXPoint *)newPosition :(NXCoord)size
{
  windowRectangle *wRect;
  NXPoint newPositionInScreenCoordinates;
  set(&newPositionInScreenCoordinates, newPosition);
  translate(&newPositionInScreenCoordinates, &roachesViewOffset);
  wRect = windowRectangles;
  while ((wRect = wRect->next) && wRect->number != currentWindowNumber) {
    if (insideBounds(&newPositionInScreenCoordinates,&wRect->rectangle,size)) {
      return HIDDEN;
    }
  }
  return EXPOSED;
  /* Note that a Roach may actually be hidden by two windows, yet not be
   * hidden by either of them separately.  In this case, positionExposed::
   * would return EXPOSED, and the Roach would move along, until a single 
   * window was by itself hiding it. */
}  

- drawSelf:(const NXRect *)rects :(int)rectCount
{
  NXColor oldBColor;
  oldBColor = backgroundColor;
  /* Make background color user's background color, but still
   * black when window is on top.  drawSelf:: gets used a lot.
   * I should figure out a time that is used less often to do
   * the following checks.  On the other hand, the frequent 
   * checks give immediate feedback if the user changes the 
   * background preference. */
#ifdef TIMING
  [timer0 enter:PSTIME];
#endif
  {
    int saveNext;
    windowRectangle *saveLastWindowRectangle;
    saveNext = next;
    next = 0; 
    saveLastWindowRectangle = lastWindowRectangle;
    lastWindowRectangle = windowRectangles;
    [self update];
    next = saveNext;
    lastWindowRectangle = saveLastWindowRectangle;
  }
  if (currentWindowNumber == windowRectangles->next->number) {
    backgroundColor = NX_COLORBLACK;
  } else {
    /* Running as background (or as normal window).  Background color is
     * user's background color.  Reread user's default. */
    [self readBackgroundColor];
  }

  /* The following has to be done in every drawSelf::.  If I just do it
   * in one, the grestore which follows the drawSelf:: loses it.  This is
   * also why this sequence is duplicated in oneStep. */
  PSsetrgbcolor(NXRedComponent(backgroundColor),
		NXGreenComponent(backgroundColor),
		NXBlueComponent(backgroundColor));
  PSsetexposurecolor();

  if (NXRedComponent(backgroundColor) != NXRedComponent(oldBColor) ||
      NXGreenComponent(backgroundColor) != NXGreenComponent(oldBColor) ||
      NXBlueComponent(backgroundColor) != NXBlueComponent(oldBColor)) {
/*    [self clearSharedImages]; */ /* No longer necessary now that Roach
                                      background color is clear. */
    NXRectFill(&bounds);
    return self;
  }

  if (rectCount == 1) {
    NXRectFill(rects);
  } else {
    ++rects;
    rectCount = 2;
    NXRectFillList(rects, rectCount);
  } 
#ifdef TIMING
  [timer0 leave];
#endif
  return self;
}

- removeFromSuperview
/* - windowChanged:newWindow */ /* This didn't work. */
{
  const NXColor black = NX_COLORBLACK;
  [self lockFocus];
  PSsetrgbcolor(NXRedComponent(black),
		NXGreenComponent(black),
		NXBlueComponent(black));
  PSsetexposurecolor();
  [self unlockFocus];
  return [super removeFromSuperview /* windowChanged:newWindow */ ];
}

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

- inspector:sender
{
  char buf[MAXPATHLEN];
  if (!inspector)
    {
      [NXBundle getPath:buf 
                forResource:"RoachesView" 
                ofType:"nib" 
                inDirectory:[sender moduleDirectory:"Roaches"] 
                withVersion:0];
      [NXApp loadNibFile:buf owner:self withNames:NO];
    }
  [colorWell setColor:roachesColor];
  [numberSlider setIntValue:[roachList count]];
  [numberField setIntValue:[roachList count]];
  [speedSlider setIntValue:speed];
  [speedField setIntValue:speed];
  return inspector;
}

- (const char *)windowTitle
{
  return WINDOW_TITLE;
}

- fillBoundsWithBlack
{
  /*  if ([self canDraw])
    {
      [self lockFocus];
      PSsetrgbcolor(NXRedComponent(backgroundColor),
		    NXGreenComponent(backgroundColor),
		    NXBlueComponent(backgroundColor));
      NXRectFill(&bounds);
      [self unlockFocus];
    } */
  return self;
}

@end

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