This is LifeView.m in view mode; [Download] [Up]
// LifeView by David Bau // Copyright 1994 by David Bau. All rights reserved. // // I feel silly even saying this, but please don't charge for this // module or any product containing a portion or modification of it, // and when distributing, please give credit where credit is due. // // If you add anything to this module or derive anything interesting // from it, please let me know! // // I'll probably be bau@cs.cornell.edu for a while. // // David Bau 01/13/94 // 777 South Avenue, Weston, MA 02193 // // This code is shamelessly ripped off of Sam Streeper's Life module. // Here is Sam's original description. // // **** // Life is the classical demonstration of cellular automata. // It was originally created as a simplisting simulation of the dynamics // of living communities. I've always thought these things are pretty // cool; though the algorithm behind Life is exceedingly simple, // getting good performance seems to require different hacks for // the display architecture of every machine. // ... // Living cell with < 2 neighbors -> dies of isolation // Living cell with 2 or 3 neighbors -> lives // Living cell with > 3 neighbors -> dies of overcrowding // empty cell with 3 neighbors -> life is created (reproduction) // ... // **** // // I've changed several things. First, the method for computing and // drawing cells was changed to a sparse algorithm that traverses a // list of live cells. This way empty cells are looked at as little // as possible, which is a big win when the field is mostly empty. // As a nice side effect, the squares get drawn in a (more or less) // uniformly random order, which gives a smoother visual effect // without any left-to-right flickering. // // The drawing-buffering code was cleaned up. Smaller lists of rects // are used, but one for each color. No noticable performance hit. // // A hack in drawSelf was added to work around the double-refresh // problem when the screensaver first kicks in. (countDown!=ITERATIONS) // // The initLife seeding algorithm was changed. Now it puts inital // cells in a small circular patch, leaving the rest of the field // empty, which is much better for the sparse algorithm. For big // windows, the circular patch appears in a random starting place. // // Stasis checking was changed. Now it automatically deduces any // stasis period (up to the size of the stasis buffer), but it takes // more generations for stasis to be detected. // // The color table was generalized to dish out NXColors, instead of // just hues. Cell size was generalized. // // The control panel was souped up. Now the color table can be // changed by the user. The cell sizes can be changed. The panel // animation appears (slowly) partially obscured by the panel // controls. The panel animation can be stepped manually by the // user. Defaults are saved in the user's defaults database. // Programming the panel was the hardest part, but the result // looks pretty nice. I'm beginning to appreciate IB. // // 10/20/93 fixes and improvements: used perform:with:afterDelay to // credits button remove itself correctly. Also used the same method // to do the panel animation in a more polite, lazy way. Changed // "step" button to a continuous button. Fixed the timed panel // animation so it doesn't draw if the view is gone from the window. // // 10/23/93 Changed sizing buttons to a matrix of buttons. Stop // timed panel animation if already doing an animation somewhere else // (window or background) by making sure countDown isn't changing. // // 10/27/93 Added LIVEEDGE boundary convention. (Later removed.) // Made it so drawing can go right up to the edge of the window, // using a random offset when it can't completely fill it. Removed // unneeded calls to flushColor to avoid NXSetColor calls. // // 10/29/93 Added DEEPEDGE, so field can extend beyond the visible screen. // turned off LIVEEDGE feature. Fine tuned seeding parameters. // // 11/05/93 Changed MINCELLSIZE to 2, which can be obtained by a dwrite // to SparseLifeCellSize only. Played with matrix of radio buttons for // so none are highlighed when cellSize matches none of them (e.g.=2), // but so that once the user starts selecting them, they go into // setEmptySelectionEnabled:NO mode. // // 11/12/93 Changed seeding to make more patches when field is very large. // // 11/13/93 Modified by Richard Hess (rhess@consilium.com) // Added a "transparent" button in the upper right corner of the // control panel which sets the cell size to MINCELLSIZE if the // mouse is clicked in it... // // 01/09/94 Fixed allocation of Grid so only as much memory is allocated // as is needed. // // 01/12/94 Put a limit on the maximum animation speed to fight flickering. // Changed name from SparseLife to just Life. // // 01/13/94 Fixed a memory leak pointed out by Sam Streeper. // 01/13/94 Modified by Richard Hess (rhess@consilium.com) // Added a LifeCluster default mechanism for setting the number of patches // to be generated when your running very small cell sizes on a big screen... // // 01/14/94 Eliminated lockfocusing when [self canDraw]==NO. Centered // static life view correctly. // // 01/15/94 Put lower bound on ncols, nrows, as in MazeView. Added // LifeFrameDelay default. #import <appkit/appkit.h> #import <defaults/defaults.h> #import <libc.h> #import <time.h> #import <dpsclient/wraps.h> #import "LifeView.h" #import "Thinker.h" /* the kind of Life view that appears in the control panel */ @implementation StaticLifeView - initLife { int x,y,count,t; /* frame the field 2 cells bigger than the view */ ncols=MIN((bounds.size.width/cellSize+2+2*DEEPEDGE+2),MAXCOLS); nrows=MIN((bounds.size.height/cellSize+2+2*DEEPEDGE+2),MAXROWS); if (ncols<4) ncols=4; if (nrows<4) nrows=4; t=(int)bounds.size.width-(ncols-2-2*DEEPEDGE)*cellSize; xoffset=(t<0 ? t/2-(1+DEEPEDGE)*cellSize : random()%(t+1)-(1+DEEPEDGE)*cellSize); t=(int)bounds.size.height-(nrows-2-2*DEEPEDGE)*cellSize; yoffset=(t<0 ? t/2-(1+DEEPEDGE)*cellSize : random()%(t+1)-(1+DEEPEDGE)*cellSize); /* allocate grid */ if (Grid) NX_ZONEREALLOC([self zone],Grid,lifecell,nrows*ncols); NX_ZONEMALLOC([self zone],Grid,lifecell,nrows*ncols); /* clear the field */ [self clearLife]; /* use uniform random seeding */ for (count=ncols*nrows/5; count; count--) { x=random()%(ncols-2)+1; y=random()%(nrows-2)+1; if (Grid[x+y*ncols].color==(-1)) { Grid[x+y*ncols].color=0; Grid[x+y*ncols].next=ifirst; ifirst=x+y*ncols; } if (Grid[x+y*ncols].color<COLORS-1) Grid[x+y*ncols].color++; } /* don't go for too many generations. Seems it would be boring. */ countDown = 1000; lasttime = currentTimeInMs(); return self; } /* main view can tell panel view what colors to use */ - setYoungColor:(NXColor)yc MediumColor:(NXColor)mc OldColor:(NXColor)oc { youngColor=yc; mediumColor=mc; oldColor=oc; [self computeColors]; return self; } /* main view can tell panel what cell size to use */ - setLifeCellSize:(int)cs; { cellSize=cs; [self setupSquareBuffer]; return self; } @end @implementation LifeView /* here's where the sparse computation and drawing is done */ - oneStep { BStimeval curtime; int icur, *picur; int checksum = 0; running=YES; curtime=currentTimeInMs(); if (curtime-lasttime<delay) { if (delay-(curtime-lasttime)>30) { usleep(15000); return self; } usleep((delay-(curtime-lasttime))*1000); curtime=currentTimeInMs(); } lasttime=curtime; /* finished */ if (--countDown < 0) { [self initLife]; [self display]; } /* pass one: count up neighbors. Hope gcc does cse well... */ #define CONTRIBUTE_TO_GRID(iadj) \ if (Grid[iadj].color==(-1)) { \ Grid[iadj].color=0; \ Grid[iadj].next=ifirst; \ ifirst=iadj; \ } \ Grid[iadj].neighbors++; for (icur=ifirst; icur>=0; icur=Grid[icur].next) { /* contribute to the west */ CONTRIBUTE_TO_GRID(icur-1); CONTRIBUTE_TO_GRID(icur-1-ncols); CONTRIBUTE_TO_GRID(icur-ncols); CONTRIBUTE_TO_GRID(icur+1-ncols); CONTRIBUTE_TO_GRID(icur+1); CONTRIBUTE_TO_GRID(icur+1+ncols); CONTRIBUTE_TO_GRID(icur+ncols); CONTRIBUTE_TO_GRID(icur-1+ncols); } #undef CONTRIBUTE_TO_GRID /* pass two: redraw and prune */ picur=&ifirst; while (*picur>=0) { /* a cell only if 2 neighbors and was a cell, or 3 neighbors */ if (((Grid[*picur].neighbors==2 && Grid[*picur].color) || Grid[*picur].neighbors==3)) { if (Grid[*picur].color<COLORS-1) { Grid[*picur].color++; [self putSquare:(*picur)%ncols :(*picur)/ncols Color: Grid[*picur].color]; } Grid[*picur].neighbors=0; picur=&(Grid[*picur].next); /* advance */ } else { if (Grid[*picur].color) { [self putSquare:(*picur)%ncols :(*picur)/ncols Color: 0]; checksum += (*picur * *picur); } Grid[*picur].color=(-1); Grid[*picur].neighbors=0; *picur=Grid[*picur].next; /* delete from list and advance */ } } /* empty anything left in the drawing buffers */ [self flushSquares]; /* check for termination if things are cycling */ [self checkStasis:checksum]; return self; } - drawSquares { int icur; for (icur=ifirst; icur>=0; icur=Grid[icur].next) { [self putSquare:(icur%ncols):(icur/ncols) Color:Grid[icur].color]; } [self flushSquares]; return self; } - putSquare:(int)x :(int)y Color:(int)color { NXRect *newsquare; #if DEEPEDGE if (y<(1+DEEPEDGE) || y>=nrows-(1+DEEPEDGE) || x<(1+DEEPEDGE) || x>=ncols-(1+DEEPEDGE)) return self; #endif if (squareCount[color]>=SQUAREBLOCK) [self flushColor:color]; newsquare = squareBuffer[color]+squareCount[color]; newsquare->origin.x=x*cellSize+xoffset; newsquare->origin.y=y*cellSize+yoffset; squareCount[color]++; return self; } - flushColor:(int)color { if (squareCount[color]) { NXSetColor(colorTable[color]); NXRectFillList(squareBuffer[color],squareCount[color]); squareCount[color]=0; } return self; } - flushSquares { int color; for (color = 0; color<COLORS; color++) { [self flushColor: color]; } return self; } - drawSelf:(const NXRect *)rects :(int)rectCount { PSsetgray(NX_BLACK); if (rectCount>1) { NXRectFillList(rects+1,rectCount-1); } else { NXRectFill(rects); } if (countDown!=ITERATIONS) [self drawSquares]; return self; } - (const char *) windowTitle {return "Life";} - setupSquareBuffer { int i,b; for (i=0; i<COLORS; i++) { squareCount[i]=0; for (b=0; b<SQUAREBLOCK; b++) { squareBuffer[i][b].origin.x=0; squareBuffer[i][b].origin.y=0; squareBuffer[i][b].size.width=cellSize; squareBuffer[i][b].size.height=cellSize; } } return self; } - initFrame:(const NXRect *)frameRect { [super initFrame:frameRect]; Grid=NULL; [self getLifeDefaults]; [self computeColors]; srandom(time(NULL)); [self setupSquareBuffer]; [self initLife]; return self; } - free { if (Grid) NXZoneFree([self zone], Grid); Grid=NULL; return [super free]; } static void ColorToString(NXColor color, char *str) { sprintf(str,"%f %f %f",NXRedComponent(color), NXGreenComponent(color),NXBlueComponent(color)); } static NXColor ColorFromString(const char *str) { NXColor color; float r,g,b; sscanf(str,"%f %f %f",&r,&g,&b); r=(r<0.0 ? 0.0 : (r>1.0 ? 1.0 : r)); g=(g<0.0 ? 0.0 : (g>1.0 ? 1.0 : g)); b=(b<0.0 ? 0.0 : (b>1.0 ? 1.0 : b)); color=NXConvertRGBToColor(r,g,b); return color; } - getLifeDefaults { static NXDefaultsVector LifeDefaults = { {"LifeYoungColor", "0.083331 0.000000 1.000000"}, {"LifeMediumColor","0.916669 1.000000 0.000000"}, {"LifeOldColor", "0.400005 0.000000 0.066668"}, {"LifeCellSize", "8"}, {"LifeClusters", "2"}, {"LifeFrameDelay", "16"}, {NULL} }; NXRegisterDefaults([NXApp appName],LifeDefaults); youngColor= ColorFromString(NXGetDefaultValue([NXApp appName],"LifeYoungColor")); mediumColor= ColorFromString(NXGetDefaultValue([NXApp appName],"LifeMediumColor")); oldColor= ColorFromString(NXGetDefaultValue([NXApp appName],"LifeOldColor")); sscanf(NXGetDefaultValue([NXApp appName],"LifeCellSize"),"%d",&cellSize); sscanf(NXGetDefaultValue([NXApp appName],"LifeClusters"),"%d",&clusters); sscanf(NXGetDefaultValue([NXApp appName],"LifeFrameDelay"),"%d",&delay); if (cellSize<MINCELLSIZE) cellSize=MINCELLSIZE; if (cellSize>MAXCELLSIZE) cellSize=MAXCELLSIZE; if (clusters<MINCLUSTERS) clusters=MINCLUSTERS; if (clusters>MAXCLUSTERS) clusters=MAXCLUSTERS; if (delay<MINDELAY) delay=MINDELAY; if (delay>MAXDELAY) delay=MAXDELAY; return self; } - sizeTo:(NXCoord)width :(NXCoord)height { [super sizeTo:width :height]; [self initLife]; return self; } /* clear the field and do some random seeding */ - initLife { int x,y,xr,yr,xo,yo,xa,ya,i,count,repeat,size,t; /* frame the field */ ncols=MIN((bounds.size.width/cellSize+2+2*DEEPEDGE),MAXCOLS); nrows=MIN((bounds.size.height/cellSize+2+2*DEEPEDGE),MAXROWS); if (ncols<4) ncols=4; if (nrows<4) nrows=4; t=(int)bounds.size.width-(ncols-2-2*DEEPEDGE)*cellSize; xoffset=(t<0 ? t/2-(1+DEEPEDGE)*cellSize : random()%(t+1)-(1+DEEPEDGE)*cellSize); t=(int)bounds.size.height-(nrows-2-2*DEEPEDGE)*cellSize; yoffset=(t<0 ? t/2-(1+DEEPEDGE)*cellSize : random()%(t+1)-(1+DEEPEDGE)*cellSize); /* allocate grid */ if (Grid) NX_ZONEREALLOC([self zone],Grid,lifecell,nrows*ncols); else NX_ZONEMALLOC([self zone],Grid,lifecell,nrows*ncols); /* clear the field */ [self clearLife]; /* do some seeding: multiple patches if big field, one patch if smaller */ if (ncols>256 && nrows>256) { size=96; repeat=clusters; } else { size=128; repeat=1; } while (repeat) { if ((ncols-3)>size){xr=size/8+1;xo=1+random()%((ncols-3)-size+1);xa=1;} else {xr=((ncols-3)/8)+1; xo=1; xa=((ncols-3)%8)+1;} if ((nrows-3)>size){yr=size/8+1;yo=1+random()%((nrows-3)-size+1);ya=1;} else {yr=((nrows-3)/8)+1; yo=1; ya=((nrows-3)%8)+1;} for (count=MAX(xr*yr*4,100); count; count--) { /* add up 8 rolls of dice for each coordinate */ for (i=0, x=xo+random()%xa; i<8; i++) x+=random()%xr; for (i=0, y=yo+random()%ya; i<8; i++) y+=random()%yr; if (Grid[x+y*ncols].color==(-1)) { Grid[x+y*ncols].color=0; Grid[x+y*ncols].next = ifirst; ifirst=x+y*ncols; } if (Grid[x+y*ncols].color<COLORS-1) Grid[x+y*ncols].color++; } repeat--; } countDown = ITERATIONS; lasttime = currentTimeInMs(); return self; } - clearLife { int x,y,i; /* empty linked list of interesting cells */ ifirst = -1; /* clear the field */ for (x=0; x<ncols; x++) { for (y=0; y<nrows; y++) { if (x==0 || y==0 || x==ncols-1 || y==nrows-1) { Grid[x+y*ncols].color=(-2); } else { Grid[x+y*ncols].neighbors=0; Grid[x+y*ncols].color=(-1); } } } /* init stasis array */ for (i=0; i<STATSIZE; i++) stasis[i] = i; strack = 0; sindex = 0; spass = 0; return self; } /* check for stasis (any period of repetition up to STATSIZE) */ - checkStasis:(int)checksum { int i; if (!strack || stasis[(sindex+STATSIZE-strack)%STATSIZE]!=checksum) { spass=0; strack=0; for (i=0; i<STATSIZE; i+=STATIVAL) { if (stasis[i]==checksum) { if (i==sindex) strack=STATSIZE; else strack=(sindex+STATSIZE-i)%STATSIZE; break; } } } else { spass++; if (spass>=STATIVAL) countDown=0; /* must match STATIVAL generations */ /* STATIVAL should be more than 2 */ } stasis[sindex++] = checksum; if (sindex>=STATSIZE) sindex = 0; return self; } /* given old-cell color, young-cell color, and medium-cell color, */ /* linearly interpolate the whole color table in HSB space */ - computeColors { float yhue, ysat, ybri; float mhue, msat, mbri; float ohue, osat, obri; float chue; int i; NXConvertColorToHSB(youngColor, &yhue, &ysat, &ybri); NXConvertColorToHSB(mediumColor, &mhue, &msat, &mbri); NXConvertColorToHSB(oldColor, &ohue, &osat, &obri); /* hue space is circular, so decide which direction to go */ /* (take the shortest path out of the two possible) */ if (yhue>mhue && yhue-mhue>0.5) yhue -= 1.0; else if (mhue>yhue && mhue-yhue>0.5) yhue += 1.0; if (ohue>mhue && ohue-mhue>0.5) ohue -= 1.0; else if (mhue>ohue && mhue-ohue>0.5) ohue += 1.0; colorTable[0]=NXConvertGrayToColor(NX_BLACK); /* interpolate from young to medium */ for (i=1; i<COLORS/3; i++) { chue=(float)(yhue*(COLORS/3-i)+mhue*(i-1))/(COLORS/3-1); if (chue<0.0) chue += 1.0; else if (chue>1.0) chue -= 1.0; colorTable[i]=NXConvertHSBToColor(chue, (float)(ysat*(COLORS/3-i)+msat*(i-1))/(COLORS/3-1), (float)(ybri*(COLORS/3-i)+mbri*(i-1))/(COLORS/3-1)); } /* from medium to old */ for (i=COLORS/3; i<COLORS; i++) { chue=(float)(mhue*(COLORS-1-i)+ohue*(i-COLORS/3))/(COLORS-1-COLORS/3); if (chue<0.0) chue += 1.0; else if (chue>1.0) chue -= 1.0; colorTable[i]=NXConvertHSBToColor(chue, (float)(msat*(COLORS-1-i)+osat*(i-COLORS/3))/(COLORS-1-COLORS/3), (float)(mbri*(COLORS-1-i)+obri*(i-COLORS/3))/(COLORS-1-COLORS/3)); } /* pass colors on to panel also, if needed */ if (panelLifeView && sharedInspectorPanel) { [panelLifeView setYoungColor:youngColor MediumColor:mediumColor OldColor:oldColor]; } return self; } /* quick update called when color has been changed */ - updateViews { /* update the panel view, if needed */ if (sharedInspectorPanel) [sharedInspectorPanel display]; /* update myself */ if ([self canDraw]) { [self lockFocus]; [self drawSquares]; [self unlockFocus]; } return self; } - inspector:sender { char buf[MAXPATHLEN]; if (!sharedInspectorPanel) { sprintf(buf,"%s/LifeInspector.nib",[sender moduleDirectory:"Life"]); [NXApp loadNibFile:buf owner:self withNames:NO]; /* initialize some of the panel objects... */ if (panelYoungColorWell) [panelYoungColorWell setColor:youngColor]; if (panelMediumColorWell) [panelMediumColorWell setColor:mediumColor]; if (panelOldColorWell) [panelOldColorWell setColor:oldColor]; if (panelSizeMatrix) { [panelSizeMatrix setEmptySelectionEnabled:YES]; if (![panelSizeMatrix selectCellWithTag:cellSize]) { [panelSizeMatrix selectCellAt:-1:-1]; } else { [panelSizeMatrix setEmptySelectionEnabled:NO]; } } if (panelStepButton) { [panelStepButton sendActionOn:NX_MOUSEDOWNMASK]; [panelStepButton setContinuous:YES]; [panelStepButton setPeriodicDelay:PANELTIME/1000.0 andInterval:PANELTIME/1000.0]; } [self computeColors]; /* updates color table inside panelLifeView */ } return sharedInspectorPanel; } - inspectorInstalled { [self hideRHSizeButton:self]; [self hideCredits:self]; if (sharedInspectorPanel) [sharedInspectorPanel display]; if (panelLifeView && sharedInspectorPanel) { running=NO; [self perform:@selector(animateSingleStep:) with: (id)5 afterDelay:PANELTIME cancelPrevious:YES]; } return self; } - takeYoungColorFrom:sender { char str[80]; youngColor=[(NXColorWell *)sender color]; [self computeColors]; [self updateViews]; ColorToString(youngColor,str); NXWriteDefault([NXApp appName],"LifeYoungColor",str); return self; } - takeMediumColorFrom:sender { char str[80]; mediumColor=[(NXColorWell *)sender color]; [self computeColors]; [self updateViews]; ColorToString(mediumColor,str); NXWriteDefault([NXApp appName],"LifeMediumColor",str); return self; } - takeOldColorFrom:sender { char str[80]; oldColor=[(NXColorWell *)sender color]; [self computeColors]; [self updateViews]; ColorToString(oldColor,str); NXWriteDefault([NXApp appName],"LifeOldColor",str); return self; } - doSizeMatrix:sender { char str[80]; int newSize; id selected; selected=[sender selectedCell]; if (selected) newSize=[selected tag]; if (!selected || cellSize==newSize || newSize<MINCELLSIZE || newSize>MAXCELLSIZE) { if (![panelSizeMatrix selectCellWithTag:cellSize]) { [panelSizeMatrix selectCellAt:-1:-1]; } return self; } if ([sender isEmptySelectionEnabled]) [sender setEmptySelectionEnabled:NO]; cellSize=newSize; sprintf(str,"%d",cellSize); NXWriteDefault([NXApp appName],"LifeCellSize",str); [self setupSquareBuffer]; [self initLife]; [self display]; if (panelLifeView) { [panelLifeView setLifeCellSize:cellSize]; [panelLifeView initLife]; } if (sharedInspectorPanel) [sharedInspectorPanel display]; return self; } - animateSingleStep:count { if (running) return self; [self doSingleStep:self]; if ((int)count) [self perform:@selector(animateSingleStep:) with:((id)((int)count-1)) afterDelay:PANELTIME cancelPrevious:YES]; return self; } - doSingleStep:sender { /* should not do the panel animation if the view is not loaded or */ /* if it has been removed from the inspector window */ if (panelLifeView && sharedInspectorPanel && [panelLifeView canDraw] && [sharedInspectorPanel canDraw]) { [panelLifeView lockFocus]; [panelLifeView oneStep]; [panelLifeView unlockFocus]; [sharedInspectorPanel display]; } return self; } - doRestart:sender { if (panelLifeView) [panelLifeView initLife]; if (sharedInspectorPanel) [sharedInspectorPanel display]; return self; } - doRHSizeButton:sender { if ([sender isTransparent] && cellSize!=MINCELLSIZE) { [self perform:@selector(showRHSizeButton:) with:self afterDelay:0 cancelPrevious:YES]; } else { [self perform:@selector(hideRHSizeButton:) with:self afterDelay:0 cancelPrevious:YES]; if (cellSize!=MINCELLSIZE) { char str[80]; [panelSizeMatrix setEmptySelectionEnabled:YES]; [panelSizeMatrix selectCellAt:-1:-1]; cellSize=MINCELLSIZE; sprintf(str,"%d",cellSize); NXWriteDefault([NXApp appName],"LifeCellSize",str); [self setupSquareBuffer]; [self initLife]; [self display]; if (sharedInspectorPanel) { if (panelLifeView) { [panelLifeView setLifeCellSize:cellSize]; [panelLifeView initLife]; } [sharedInspectorPanel display]; } } } return self; } - showRHSizeButton:sender { if (panelRHSizeButton && sharedInspectorPanel && [panelRHSizeButton isTransparent]) { [panelRHSizeButton setTransparent:NO]; } return self; } - hideRHSizeButton:sender { if (panelRHSizeButton && sharedInspectorPanel && ![panelRHSizeButton isTransparent]) { [panelRHSizeButton setTransparent:YES]; } return self; } - showCredits:sender { if (panelCreditsView && sharedInspectorPanel) { if ([panelCreditsView isDescendantOf:sharedInspectorPanel]) { [panelCreditsView removeFromSuperview]; } [sharedInspectorPanel addSubview:panelCreditsView]; [sharedInspectorPanel display]; } return self; } - hideCredits:sender { if (panelCreditsView && sharedInspectorPanel) { if ([panelCreditsView isDescendantOf:sharedInspectorPanel]) { [panelCreditsView removeFromSuperview]; } [sharedInspectorPanel display]; } return self; } /* these methods are needed because we should not rearrange the view */ /* hierarchy while a button is active; we queue the rearranging to be */ /* done later as an event, when nothing is locked on the view */ - doShowCredits:sender { [self perform:@selector(hideRHSizeButton:) with:self afterDelay:0 cancelPrevious:YES]; [self perform:@selector(showCredits:) with:self afterDelay:0 cancelPrevious:YES]; return self; } - doHideCredits:sender { [self perform:@selector(hideCredits:) with:self afterDelay:0 cancelPrevious:YES]; return self; } @end
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.