ftp.nice.ch/pub/next/graphics/movie/Movie.2.51.2.s.tar.gz#/Movie/MovieView.m

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

#include <objc/NXBundle.h>
#include <dpsclient/psops.h>
#include "wraps.h"

#import "MovieView.h"

/*
 * Movie 2.51 - 5/7/92 pjf
 *
 * Differences between 2.5 and 2.51:
 * - the save: method actually has a prayer of working when the
 *   user saves on top of an existing movie and the old copy can't be renamed.
 *
 * Differences between 2.0 and 2.5:
 * - buttons to control cache depth
 *
 * - turn off multiframe .tiffs by default (define BC_VERSION_1 if you
 *   have a .tiff movie and are too lazy to use tiffutil to turn
 *   it into an .anim directory)
 *
 * - now able to save movie (currently-selected depth)
 *
 */

#define maxFrames 1024

@implementation MovieView

void error(const char *format, ...)
 {
    va_list ap;
    
    va_start(ap, format);
    vfprintf(stderr, format, ap);
    va_end(ap);
    kill(getpid(),6);
 }

//
- initFrame:(const NXRect *) frameRect
 {
	const char *x;
	[(self = [super initFrame:frameRect]) allocateGState];
	state = STOPPED;
	mode = LOOP;
	maxSize.width = maxSize.height = -1.0;
	movieFrame = NULL;
	frameCount = 0;
	anim = nil;
	pingDuringDisplay=NO;
	x=NXGetDefaultValue("Movie","DefaultDepth");
	if (!x) dmode=D_DEF; /* use default depth */
	else switch(atoi(x)) {
	    default:
	    case 0: dmode=D_DEF; break;
	    case 2: dmode=D_2; break;
	    case 8: dmode=D_8; break;
	    case 12: dmode=D_12; break;
	    case 24: dmode=D_24; break;
	};
	updateControls = NO;
	showFrameNumber = NO;
	noOriginals = NO;
	fromStream = NO;
	return self;
 }

- drawSelf:(const NXRect *) rects :(int)count
 {
    NXImage *image;
    NXPoint origin = {0.0,0.0};
    
    if (!movieFrame) return nil;
    image = movieFrame[currentFrameIndex].image;
    if (!image) return self;
    [image composite:NX_COPY toPoint:&origin];
    if (!fromStream && (state==LOADING || showFrameNumber)) {
	NXRect r;
	[self getBounds:&r];
	if (state==LOADING)
	    PSWtext(r.size.width, r.size.height, "Loading ...", currentFrameIndex+1);
	else
	    PSWframe(r.size.width, r.size.height, currentFrameIndex+1);
    }
    if (pingDuringDisplay) NXPing();
    if ((frameCount >= (int)frameRate)) {
	if (state != STOPPED && state != LOADING) {
	    double t=[anim getDoubleRealTime]+[anim getSyncTime];
	    double afps=frameCount/(t-oldt);
	    [actualFpsText setDoubleValue:afps];
	    oldt=t;
	}
	frameCount=0;
    }
    if (updateControls) {
	[fNumSlider setIntValue:currentFrameIndex];
	[fNumText setIntValue:currentFrameIndex];
    };
    return self;
 }


//
- (BOOL)open:sender
 {
#ifdef BC_VERSION_1
  const char *const types[] = { "tiff", "anim", "mpg", "mpeg", (const char *) NULL };
#else
  const char *const types[] = { "anim", "mpg", "mpeg", (const char *) NULL };
#endif
  id pan = [OpenPanel new];
  const char *const *filenames;
  char filename[FILENAME_MAX];

  if (![pan runModalForTypes:types]) return NO;
  if ((filenames = [pan filenames]) == NULL) return NO;
  sprintf(filename,"%s/%s", [pan directory], filenames[0]);
  strcpy(moviePath,filename);
  return [self openFile:filename];
 }

- (BOOL)play:sender
 {
     fromStream = YES;
     noOriginals = YES;
     return [self open:sender];
 }
 
- makeWindow;
 {
    Window *w=[self window];
    
    [w sizeWindow:maxSize.width:maxSize.height]; /* will recache images */
    [w setMiniwindowIcon:"movieDoc.tiff"];
    [w makeKeyAndOrderFront:self];
    [w display];
    return self;
 }
 
- makePanel:(char *)filename;
 {
    char ptitle[FILENAME_MAX];
    char *ptr=rindex(filename,'/')+1;
    
    if (ptr == (char *)1) ptr=filename;
    sprintf(ptitle,"Controls for %s",ptr);
    [panel setTitle:ptitle];
    [panel setNextResponder:[self window]];
    [[fNumSlider setMaxValue:numFrames-1] setEnabled:YES];
    [nFramesText setIntValue:numFrames-1];
    [depthButtons selectCellAt:(int)dmode:0];
    [panel orderFront:self];
    return self;
 }
 
- setFps:(float)fps;
 {
     frameRate = fps;
    [fpsSlider setFloatValue:fps];
    [fpsText setFloatValue:fps];
    return self;
 }

- setNoOriginals;
 {
    int i;
    Window *w=[self window];

    if (noOriginals) return self;
    if (movieFrame) for (i=0; i<numFrames; i++) if (movieFrame[i].original) {
	[movieFrame[i].original free];
	movieFrame[i].original = nil;
    }
    noOriginals = TRUE;
    [w setMinSize:&maxSize];
    [w setMaxSize:&maxSize];
    return self;
 }
 
// openFile: returns YES if the frames were successfully read, NO if not.
- (BOOL)openFile:(char *)filename
 {
    char *ptr=rindex(filename,'.');

    if (!ptr) {
	NXRunAlertPanel(NULL,"Impossible filename %s", NULL, NULL, NULL, filename);
	return NO;
    };

    state = LOADING;
    [[self window] setTitleAsFilename:filename];

  /* get the bitmaps */
    if (!strcmp(ptr,".anim")) {  /* the file is an Icon-style .anim directory */
	char buf[FILENAME_MAX];
	char *ptr2;
	*ptr='\0'; /* clobber extension */
	ptr2=1+rindex(filename,'/'); /* danger danger */
	if (ptr2 == (char *)1) ptr2=filename; /* if not /full/path/name */
	sprintf(buf,"%s.anim/%s",filename,ptr2);
	[self openAnimDirectory:buf];
    }
    else if (!strcmp(ptr,".mpg") || !strcmp(ptr,".mpeg")) { /* an MPEG file */
	[self openMPEGfile:filename];	/* 30-Dec-94 -- added .mpeg case */
    }					/* Mike Carlton carlton@isi.edu */
#ifdef BC_VERSION_1
    else if (!strcmp(ptr,".tiff")) { /* a slew o' TIFFs in one file */
	bitmaps = [NXBitmapImageRep newListFromFile:filename];
	if (!bitmaps) {
	    NXRunAlertPanel(NULL,"Couldn't get bitmaps from %s",
		NULL,NULL,NULL, filename);
	    return NO;
	};
	[self allocateFrames:bitmaps];
	[bitmaps free]; /* does not free elements */
	[self setFps:15.0];
    }
#endif
    else { /* this shouldn't happen */
	NXRunAlertPanel(NULL,"Impossible filename %s",NULL,NULL,NULL,filename);
	return NO;
    };
    state = STOPPED;
    if (fromStream) [window close];
    else [self makePanel:filename];
    return YES;
 }


- (List *)listAnimDirectory:(char *)filenameRoot
 {
  List *bitmaps = [[List alloc] init];
  int i=1;
  while (1) {
    char buf[FILENAME_MAX];
    NXBitmapImageRep *newbitmap;
    sprintf(buf,"%s.%d.tiff",filenameRoot,i++);
    if ((access(buf,R_OK)) == -1) break;
    newbitmap = [[NXBitmapImageRep alloc] initFromFile:buf];
    if (!newbitmap) {
      NXRunAlertPanel(NULL,"Couldn't get bitmap from %s",NULL,NULL,NULL,
                    buf);
      [[bitmaps freeObjects] free];
      return nil;
      }
    else
      [bitmaps addObject:newbitmap];
    };
  return bitmaps;
 }

- openAnimDirectory:(char *)filenameRoot;
 {
    int i;
    NXBitmapImageRep *newbitmap;
    char buf[FILENAME_MAX];

    for (i=1; TRUE; i++) {
	sprintf(buf,"%s.%d.tiff", filenameRoot, i);
	if ((access(buf, R_OK)) == -1) break;
	newbitmap = [[NXBitmapImageRep alloc] initFromFile:buf];
	if (!newbitmap) {
	    NXRunAlertPanel(NULL,"Couldn't get bitmap from %s",NULL,NULL,NULL, buf);
	    continue;
	}
	[self addBitmap:newbitmap];
    }
    [self setFps:15.0];
    return self;
 }
 
- openMPEGfile:(char *)filename
 {
    int i, n, flen;
    char command[256];
    FILE *fd;
    mpegInfo *pInfo;
    long data;
    BOOL swab;
    NXStream *pStream;
    NXBitmapImageRep *newbitmap;
    
    if (!(fd = fopen(filename, "r"))) error("Could not open %s", filename);
    fread(&data, 4, 1, fd);
    if (data!=0x000001b3 && data!=0xb3010000)
	error("%s does not contain an mpeg stream: %x", filename, data);
    if (data==0xb3010000) swab=YES; else swab=NO;
    if (!(pInfo = calloc(1, sizeof(mpegInfo)))) error("Could not allocate pInfo.");
	// Get horizontal and vertical size of image space
	// as two 12 bit words, respectively
	// then aspect ratio and picture rate
	// as two 4 bit words.
    fread(&data, 4, 1, fd); if (swab) data = NXSwapLong(data);
    pInfo->picture_rate = 0x0F & data;
    data >>= 4;
    pInfo->aspect_ratio = 0x0F & data;
    data >>= 4;
	// In Motorola format, least significant bits come last
	// v_size is actually the second value in the file
	// i.e. h:12,v:12,a:4,p:4
    pInfo->v_size = 0x0FFF & data;
    pInfo->h_size = 0x0FFF & data >> 12;
    maxSize.width = ((pInfo->h_size + 15) / 16) * 16.0;
    maxSize.height = ((pInfo->v_size + 15) / 16) * 16.0;
	// Get bit rate, vbv buffer size, and constrained parameter flag
    fread(&data, 4, 1, fd); if (swab) data = NXSwapLong(data);
	// throw away (non) intra quant matrix flags
    data >>= 2;
    pInfo->const_param_flag = 1 & data;
    data >>= 1;
    pInfo->vbv_buffer_size = 0x03FF & data;
    data >>= 10 + 1;	// 1 marker bit
    pInfo->bit_rate = 0x03FFFF & data;
    fclose(fd);
    pInfo->fps = pInfo->picture_rate;
    flen = 3 * (int)maxSize.width * (int)maxSize.height;
    
 //    printf("Dimensions: %d %d, buffer size:%d\n", (int)maxSize.width, (int)maxSize.height, flen);
    sprintf(command, "exec %s/mpegDecode %s",
	[[NXBundle mainBundle] directory], filename);
    if (!(fd = popen(command, "r")))
	error("Could not create MPEG process:\n   %s", command);
    pStream = NXOpenFile(fileno(fd), O_RDONLY);

 //    printf("Reading frames:\n");
    for (i=0; TRUE; i++) {
	newbitmap = [[NXBitmapImageRep alloc] initData:NULL
	    pixelsWide:(int)maxSize.width
	    pixelsHigh:(int)maxSize.height
	    bitsPerSample:8
	    samplesPerPixel:3	// (cSpace == RGB_COLOR) ? 3 : 1
	    hasAlpha:NO
	    isPlanar:NO
	    colorSpace:NX_RGBColorSpace
	    bytesPerRow:0
	    bitsPerPixel:0];
	if (4!=NXRead(pStream, &data, 4)) {
 //	    printf("Finished.\n");
	    [newbitmap free];
	    break;
	}
 //	printf("Frame # %d.\n", data);
	n = NXRead(pStream, [newbitmap data], flen);
	if (n!=flen) error("Error reading image data (%d/%d bytes read).", n, flen);
	[self addBitmap:newbitmap];
    }
    [self setFps:pInfo->fps];
    return self;
 }

- addBitmap:bm;
 {
    NXSize sz;
    NXRect r;
    int flen, flag=0;

    [bm getSize:&sz];
    if (sz.width > maxSize.width) maxSize.width=sz.width, flag=1;
    if (sz.height > maxSize.height) maxSize.height=sz.height, flag=1;
    if (flag || !movieFrame) [[self window] sizeWindow:maxSize.width:maxSize.height];
    flen = ([bm pixelsWide]*[bm pixelsHigh]*[bm bitsPerPixel])>>3;
    if (numFrames*flen>20000000) [self setNoOriginals];
    if (!movieFrame) {
	numFrames=1;
	NX_MALLOC(movieFrame, movieFrameStruct, numFrames);
    } else {
	numFrames++;
	NX_REALLOC(movieFrame, movieFrameStruct, numFrames);
    }
    currentFrameIndex=numFrames-1;
    if (!noOriginals) movieFrame[currentFrameIndex].original=bm;
    else movieFrame[currentFrameIndex].original=nil;
 //    printf("Frame # %d, Size: %8.3f %8.3f\n", currentFrameIndex, sz.width, sz.height);
    if (!fromStream || !currentFrameIndex) {
	movieFrame[currentFrameIndex].image=[[NXImage alloc] initSize:&sz];
	[movieFrame[currentFrameIndex].image setUnique:YES]; /* make caches disjoint */
	[movieFrame[currentFrameIndex].image setBackgroundColor:NX_COLORBLACK];
    } else {
	movieFrame[currentFrameIndex].image=movieFrame[currentFrameIndex-1].image;
    }
    [self getBounds:&r];
    if ([movieFrame[currentFrameIndex].image lockFocus]) {
	[bm drawIn:&r];
	[movieFrame[currentFrameIndex].image unlockFocus];
    } else error("Could not lock focus on image");
    if (noOriginals) [bm free];
    if (numFrames==1) [self makeWindow];
    else [self display];
    NXPing();
    return self;
 }

NXWindowDepth deps[] = {
  NX_DefaultDepth, NX_TwoBitGrayDepth,
  NX_EightBitGrayDepth, NX_TwelveBitRGBDepth,
  NX_TwentyFourBitRGBDepth /*,NX_PurinaCatChow__ChowChowChowDepth*/
  };

// set up Frame data structures and find max frame size
- allocateFrames:(List *)frames
 {
  int i;
  numFrames=[frames count];
  NX_MALLOC(movieFrame,movieFrameStruct,numFrames);
  for(i=0;i<numFrames;i++) {
    NXImage *nxi;
    NXBitmapImageRep *bm=[frames objectAt:i];
    NXSize sz;
    [bm getSize:&sz];
    movieFrame[i].original=bm;
    nxi=movieFrame[i].image=[[NXImage alloc] initSize:&sz];
    [nxi setUnique:YES]; /* make caches disjoint */
    [nxi setBackgroundColor:NX_COLORBLACK];
    /* keep track of largest frame */
    if (sz.width > maxSize.width) maxSize.width=sz.width;
    if (sz.height > maxSize.height) maxSize.height=sz.height;
    };
  return self;
 }

/*****************************************************************
 *****************************************************************/
- superviewSizeChanged:(NXSize *)old
 {
    [anim stopEntry];
    [super superviewSizeChanged:old];
    if (noOriginals) {
	if (!fromStream)
	    NXRunAlertPanel("Resize","Can't resize, no originals.",NULL,NULL,NULL);
    }
    else if (state!=LOADING) {
	[self recache];
	[self renderFrames];
    }
    if (movieFrame) [[self window] display];
    NXPing();
    [anim resetRealTime];
    [anim startEntry];
    return self;
 }

- renderFrames
 {
    int cfi;
    NXRect r;

    [self getBounds:&r];
    cfi = currentFrameIndex;
    state = LOADING;
    for(currentFrameIndex=0; currentFrameIndex<numFrames; currentFrameIndex++) {
	if ([movieFrame[currentFrameIndex].image lockFocus]) {
	    [movieFrame[currentFrameIndex].original drawIn:&r];
	    [movieFrame[currentFrameIndex].image unlockFocus];
	} else {
	    fprintf(stderr,"Barf: couldn't lockFocus on image %d\n",
		    (int)movieFrame[currentFrameIndex].image);
	    abort();
	}
	[self display];
	NXPing();
    }
    state = STOPPED;
    currentFrameIndex = cfi;
    return self;
 }

- recache
 // assume depth & size both changed 
 //
 // Appkit bug?  Can one render down from 24 bit color to 2 bit gray?
 //
 {
    NXRect r;
    int i;
    [self getBounds:&r];
    [self freeCaches];
    for(i=0; i<numFrames; i++) {
	movieFrame[i].image=[[NXImage alloc] initSize:&r.size];
	[movieFrame[i].image useCacheWithDepth:deps[(int)dmode]];
    };
    return self;
 }

- save:sender
 {
  const char *type = "anim"; // will only save in .anim format.
  SavePanel *sp = [SavePanel new];
  [sp setDelegate:self];
  [sp setRequiredFileType:type];
  if ([sp runModal]) { // OK was hit
    int i;
    char cwd[MAXPATHLEN];
    /* if directory exists, rename it with a wiggle in back. */
    if (access([sp filename],F_OK) == 0) {
      /* I could do this with a couple of calls to system(), but noooo,
       * I had to do it the had way. yeccch. */
      char *buf=malloc(strlen([sp filename]+2));
      sprintf(buf,"%s~",[sp filename]);
      if (!getwd(cwd)) {
        NXRunAlertPanel("FATAL","Couldn't get current directory.",NULL,NULL,NULL);
        abort();
        };
      if (rename([sp filename],buf) == -1) {
        // sledgehammer time.
        struct direct *de;
        DIR *dp;
        chdir([sp filename]);
        dp=opendir(".");
        while(de=readdir(dp)) unlink(de->d_name);
        closedir(dp);
        chdir(cwd);
        unlink([sp filename]);
        };
      };
    mkdir([sp filename],0755);
    chdir([sp filename]);
    for(i=0;i<numFrames;i++) {
      char buf3[MAXPATHLEN];
      char buf2[MAXPATHLEN];
      char *ptr;
      int fd;
      NXStream *s;
      strcpy(buf3,[sp filename]);
      ptr=rindex(buf3,'/')+1;
      *(rindex(ptr,'.'))='\0';
      sprintf(buf2,"./%s.%d.tiff",ptr,i+1);
      fd=open(buf2,O_WRONLY|O_CREAT,0644);
      s=NXOpenFile(fd,NX_WRITEONLY);
      [movieFrame[i].image writeTIFF:s];
      NXClose(s);
      close(fd);
      };
    chdir(cwd);
    };
  return self;
 }

- (BOOL) panelValidateFilenames:sender
 {
  if (!strcmp([sender filename],moviePath)) {
    NXRunAlertPanel("Save","Cannot overwrite original movie",NULL,NULL,NULL);
    return NO;
    };
  return YES;
 }

- freeCaches
 {
  int i;
  for(i=0;i<numFrames;i++) [movieFrame[i].image free];
  return self;
 }

- freeOriginals
 {
    int i;
    for(i=0;i<numFrames;i++) {
	[movieFrame[i].original free];
	movieFrame[i].original = nil;
    }
    return self;
 }

- free
 {
  [self freeCaches];
  [self freeOriginals];
  [self freeGState];
  [anim free];
  anim=nil;
  return [super free];
 }

- copy:sender
 {
    char *buffer;
    NXStream *stream;
    int length, maxLength;
    Pasteboard *pasteboard = [Pasteboard new];
    runState s=state;

    [anim stopEntry];
    if (s!=STOPPED) [self stop:self];
    [pasteboard declareTypes:&NXPostScriptPboardType num:1 owner:self];
    stream = NXOpenMemory(NULL, 0, NX_WRITEONLY);
    [self copyPSCodeInside:&bounds to:stream];
    NXFlush(stream);
    NXGetMemoryBuffer(stream, &buffer, &length, &maxLength);
    [pasteboard writeType:NXPostScriptPboard data:buffer length:length];
    NXCloseMemory(stream, NX_FREEBUFFER);
    switch(s) {
	case STOPPED:
	case LOADING: break;
	case FORWARD: 
	case REVERSE: [anim startEntry]; break;
    };
    return self;
 }

- tick:sender
 {
    int next, end=(state == FORWARD) ? numFrames-1 : 0;

    switch(mode) {
	case ONCE:
	    if (currentFrameIndex == end) {
		[self stop:self];
		return self;
	    } else currentFrameIndex += (int)state;
	    break;
	case LOOP:
	    next = currentFrameIndex + (int)state;
	    if (((state == FORWARD)&&(next>end)) ||
		((state == REVERSE)&&(next<end))) {
		currentFrameIndex = (state < 0) ? numFrames-1 : 0;
	    } else currentFrameIndex = next;
	    break;
	case BOUNCE:
	    next = currentFrameIndex + (int)state;
	    if (((state == FORWARD)&&(next>end)) ||
		((state == REVERSE)&&(next<end))) {
		if (state == FORWARD) [self selectStateButton:REV];
		if (state == REVERSE) [self selectStateButton:FWD];
		state *= -1;
		currentFrameIndex += (int)state;
	    } else currentFrameIndex = next;
	    break;
    };
    frameCount++;
    [self display];
    return self;
 }

/*****************************************************************
 *****************************************************************/

- fwd:sender
 {

    if (numFrames < 2) return self;
    if (state != STOPPED) [self stop:self];
    state = FORWARD;
    [self move:sender];
    return self;
 }

- rev:sender
 {
  if (numFrames < 2) return self;
  if (state != STOPPED) [self stop:self];
  state = REVERSE;
  [self move:sender];
  return self;
 }

- move:sender
 {
    double period = 1.0/frameRate;
    if (numFrames < 2) { /* duh */
	[self selectStateButton:STOP];
	return self;
    };
    anim = [[Animator alloc] initChronon:period adaptation:0.05 /*?*/
			    target:self action:@selector(tick:)
			    autoStart:YES eventMask:0];
    if (state == FORWARD) [self selectStateButton:FWD];
    if (state == REVERSE) [self selectStateButton:REV];
    [fNumText setStringValue:""];
    [fNumSlider setEnabled:NO];
    oldt=[anim getSyncTime];
    frameCount = 0;
    return self;
 }

- stop:sender
 {
    if (numFrames < 2) return self;
    switch(state) {
	case FORWARD:
	case REVERSE: [anim free]; anim=nil;
	case STOPPED:
	case LOADING: break;
    }
    state = STOPPED;
    [self selectStateButton:STOP];
    [fNumText setIntValue:currentFrameIndex];
    [fNumSlider setEnabled:YES];
    [fNumSlider setIntValue:currentFrameIndex];
    return self;
 }

- fwdStep:sender
 {
  [self step:(int) FORWARD];
  return self;
 }

- revStep:sender
 {
  [self step:(int) REVERSE];
  return self;
 }

- step:(int) direction
 {
  if (numFrames < 2) return self;
  if (state != STOPPED) [self stop:self];
  if ((currentFrameIndex = (currentFrameIndex + direction) % numFrames) < 0)
    currentFrameIndex = numFrames + currentFrameIndex;
  [self selectStateButton:STOP];
  [fNumText setIntValue:currentFrameIndex];
  [fNumSlider setEnabled:YES];
  [fNumSlider setIntValue:currentFrameIndex];
  return [self display];
 }

- reSize:(NXSize *)s
{
    if (noOriginals)
	NXRunAlertPanel("Resize","Resize disabled for lack of memory.",NULL,NULL,NULL);
    else [[self window] sizeWindow:s->width :s->height];
    return self;
}

- expand2x:sender
 {
  NXRect r;
  [self getBounds:&r];
  r.size.width *= 2.0;
  r.size.height *= 2.0;
  [self reSize:&r.size];
  return self;
 }

- reduce50pct:sender
 {
  NXRect r;
  [self getBounds:&r];
  r.size.width *= 0.5;
  r.size.height *= 0.5;
  [self reSize:&r.size];
  return self;
 }

- restore:sender
 {
  [self reSize:&maxSize];
  return self;
 }


- modeButtonsChanged:sender
 {
  mode = (runMode)[sender selectedRow];
  return self;
 }

- fNumSliderChanged:sender
 {
  if (currentFrameIndex == [sender intValue]) return self;
  currentFrameIndex=[sender intValue];
  [self stop:self];
  [self display];
  return self;
 }

- fpsSliderChanged:sender
 {
    frameRate = [sender floatValue];
    [fpsText setFloatValue:frameRate];
    switch(state) {
	case FORWARD: 
	case REVERSE: {
	    double period = 1.0/frameRate;
	    [anim free];
	    anim = [[Animator alloc] initChronon:period adaptation:0.05
				    target:self action:@selector(tick:)
				    autoStart:YES eventMask:0];
	    break;
	    };
	case STOPPED:
	case LOADING: break;
    };
    return self;
 }

- pingButtonChanged:sender
 {
  switch([sender selectedRow]) {
    case 0: pingDuringDisplay=NO; break;
    case 1: pingDuringDisplay=YES; break;
    };
  return self;
 }

- selectStateButton:(runState)b
 {
  [stateButtons selectCellAt:0:((int)b)];
  return self;
 }

- depthButtonsChanged:sender
 {
    if (noOriginals) {
	NXRunAlertPanel("Depth","Can't change depth, no originals.",NULL,NULL,NULL);
	return self;
    }
    dmode=(depthMode)[sender selectedRow];
    [anim stopEntry];
    [self recache];
    [self renderFrames];
    [self display];
    [anim resetRealTime];
    [anim startEntry];
    return self;
 }

- updateCheckBoxChanged:sender
 {
  updateControls = !updateControls;
  return self;
 }

- frameCheckBoxChanged:sender
 {
    showFrameNumber = !showFrameNumber;
    return self;
 }


// Window's delegate methods

- windowWillClose:sender
 {
  [panel close];
  [self free];
  return self;
 }

-windowDidMiniaturize:sender
 {
  [panel orderOut:sender];
  [anim stopEntry];
  return self;
 }

-windowDidDeminiaturize:sender
 {
  [panel orderFront:sender];
  [anim resetRealTime];
  [anim startEntry];
  return self;
 }
@end

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