Annotation of Examples/AppKit/Graph/Graph3DDoc.m, revision 1.1.1.1

1.1       root        1: 
                      2: /*
                      3:     Graph3DDoc.m
                      4: 
                      5:     The Graph3DDoc represents an open 3D Graph document.  Graph3DDoc receives
                      6:     messages from the user interface objects and uses three Expression objects
                      7:     to do calculations, and a RotatorCamera and a PointMesh to display the
                      8:     results.
                      9: 
                     10:     The Graph3DDoc class has one slightly odd external dependency.  It requires
                     11:     that the delegate of NXApp be able to provide it with a NXStringTable
                     12:     (via the stringTable method) that it can use to look up strings that
                     13:     are presented to the user.  In this application, the delegate of NXApp is
                     14:     always an instance of the GraphApp class.
                     15: 
                     16:     If this were a larger program, the NXStringTable needed by this document
                     17:     class would probably be in its own nib section, which would be loaded once
                     18:     the first time a Graph3DDoc is created, and then shared.
                     19: 
                     20:     You may freely copy, distribute, and reuse the code in this example.
                     21:     NeXT disclaims any warranty of any kind, expressed or implied, as to its
                     22:     fitness for any particular use.
                     23: */
                     24: 
                     25: #import "Graph.h"
                     26: 
                     27: /* declare methods static to this class */
                     28: @interface Graph3DDoc(Private)
                     29: - _updateShape;
                     30: - _updateGraph:(BOOL)finalChange;
                     31: - (void)_equationChanged;
                     32: - (BOOL)_parseEquationFrom:(const char *)str using:(Expression *)expr enableVariables:(BOOL *)enableVars;
                     33: - _uOrVRangeSliderChanged:sender varName:(int)var;
                     34: - _uOrVRangeTextChanged:sender varName:(int)var;
                     35: - _write:(const char *)filename;
                     36: - _read:(const char *)filename;
                     37: - (void)_initVariable:(int)index enable:(BOOL)flag value:(float)val;
                     38: - _doSaveWithNewName:(BOOL)doNewName retainName:(BOOL)doRetain;
                     39: - _writeGraphToPasteboard:(Pasteboard *)pb types:(const char *const *)types num:(int)numTypes;
                     40: - (void)_setUVRangeOf:(int)var;
                     41: - (void)_setVariable:(int)index toValue:(float)val;
                     42: - (void)_setUpLighting;
                     43: - (void)_setSliderCell:(SliderCell *)slider value:(float)val minLabel:(Cell *)minLabel maxLabel:(Cell *)maxLabel;
                     44: 
                     45: @end
                     46: 
                     47: @implementation Graph3DDoc
                     48: 
                     49: /* variable names must be between A and H */
                     50: #define  MIN_CONST     'A'
                     51: #define  MAX_CONST     'H'
                     52: 
                     53: /* indices into instance variable arrays */
                     54: #define  X     0
                     55: #define  Y     1
                     56: #define  Z     2
                     57: #define  U     0
                     58: #define  V     1
                     59: 
                     60: /* default resolution of a new graph */
                     61: #define DEFAULT_RES    12
                     62: 
                     63: /* name used for the real document inside a doc wrapper */
                     64: #define DOC_NAME "/Graph3DDoc.xyzgraph"
                     65: 
                     66: static void writeCellFloat(Cell *obj, NXTypedStream *ts);
                     67: static NXTypedStream *openDocStream(const char *filename, int mode);
                     68: static int removeFile(const char *file);
                     69: 
                     70: - init {
                     71:   /* the default initialization is to just open a new document */
                     72:     return [self initFromFile:NULL];
                     73: }
                     74: 
                     75: /*
                     76:  * Opens a document.  If file is NULL, we open an untitiled document.  We also
                     77:  * create a NXDataLinkManager and hook ourselves up as its delegate for doing
                     78:  * Object Links.
                     79:  */
                     80: - initFromFile:(const char *)file {
                     81:     int rows, cols;
                     82:     int i;
                     83:     TextField *aCell;
                     84:     int xyz;
                     85:     RtPoint axis;
                     86:     char realPath[MAXPATHLEN+1];
                     87: 
                     88:     [super init];
                     89: 
                     90:   /* load the UI from the nib section and set attributes not available in IB */
                     91:     [NXApp loadNibSection:"Graph3DDoc.nib" owner:self withNames:NO fromZone:[self zone]];
                     92:     [variableTexts getNumRows:&rows numCols:&cols];
                     93:     for (i = rows; i--; ) {
                     94:        aCell = [variableTexts cellAt:i :0];
                     95:        [aCell setFloatingPointFormat:YES left:3 right:2];
                     96:        [aCell setEnabled:NO];
                     97:     }
                     98:     [minUVText[U] setFloatingPointFormat:YES left:3 right:2];
                     99:     [maxUVText[U] setFloatingPointFormat:YES left:3 right:2];
                    100:     [minUVText[V] setFloatingPointFormat:YES left:3 right:2];
                    101:     [maxUVText[V] setFloatingPointFormat:YES left:3 right:2];
                    102:     [variableSliders setAutosizeCells:YES];
                    103:     [resolutionText setEntryType:NX_POSINTTYPE];
                    104:     [camera setFieldOfViewByAngle:90.0];
                    105:     [self _setUpLighting];
                    106: 
                    107:   /* create Expression objects we will use to evaluate expressions */
                    108:     for (xyz = X; xyz <= Z; xyz++) {
                    109:        expr[xyz] = [[Expression allocFromZone:[self zone]] init];
                    110:        [expr[xyz] setDimensions:2];    /* we eval u * v points */
                    111:     }
                    112: 
                    113:   /* create and position the shape that we will use to show our data */
                    114:     [[camera setWorldShape:[[PointMesh alloc] init]] free];
                    115:     axis[1] = axis[2] = 0;
                    116:     axis[0] = 1.0;
                    117:     [[camera worldShape] rotateAngle:-90.0 axis:axis];
                    118: 
                    119:     if (file) {
                    120:       /* read existing document */
                    121:        if ([self _read:file]) {
                    122:            name = NXCopyStringBufferFromZone(file, [self zone]);
                    123:            if (realpath(name, realPath))
                    124:                realName = NXCopyStringBufferFromZone(realPath, [self zone]);
                    125:            [window setTitleAsFilename:name];
                    126:            [self _updateShape];
                    127:            linkMgr = [[NXDataLinkManager allocFromZone:[self zone]] initWithDelegate:self fromFile:file];
                    128:        } else {
                    129:            [self free];        /* couldn't load file */
                    130:            return nil;
                    131:        }
                    132:     } else {
                    133:       /*
                    134:        * Create a new document.  We initialize it with a trivial expression
                    135:        * because that was easier than allowing the state where there is no
                    136:        * current expression.
                    137:        */
                    138:        RtPoint from = {12.0, 8.0, 10.0}, to = {0.0, 0.0, 0.0};
                    139: 
                    140:        [camera setEyeAt:from toward:to roll:0.0];
                    141:       /* set initial shape color to non-white on a color display */
                    142:        if ([camera shouldDrawColor])
                    143:            [[camera worldShape] setColor:NXConvertRGBToColor(51.0/255.0, 153.0/255.0, 116.0/255.0)];
                    144: 
                    145:        [window setTitleAsFilename:[[[NXApp delegate] stringTable] valueForStringKey:"untitled doc"]];
                    146:        [resolutionSlider setIntValue:DEFAULT_RES];
                    147:        [resolutionText setIntValue:DEFAULT_RES];
                    148:        [equation[X] setStringValue:"u"];
                    149:        [equation[Y] setStringValue:"v"];
                    150:        [equation[Z] setStringValue:"A * (u^2 + v^2) + C"];
                    151:        for (xyz = X; xyz <= Z; xyz++) {
                    152:            [expr[xyz] setResolution:DEFAULT_RES];
                    153:        }
                    154:        [self _equationChanged];
                    155:        [self _setUVRangeOf:U];
                    156:        [self _setUVRangeOf:V];
                    157:        [self _updateShape];
                    158:        linkMgr = [[NXDataLinkManager allocFromZone:[self zone]] initWithDelegate:self];
                    159:     }
                    160: 
                    161:     [[NXApp delegate] showThreeDPanel:self];
                    162:     [window makeKeyAndOrderFront:self];
                    163:     return self;
                    164: }
                    165: 
                    166: - free {
                    167:     int xyz;
                    168: 
                    169:     for (xyz = X; xyz <= Z; xyz++)
                    170:        [expr[xyz] free];
                    171:     NXZoneFree([self zone], name);
                    172:     NXZoneFree([self zone], realName);
                    173:     [linkMgr free];
                    174:     return [super free];
                    175: }
                    176: 
                    177: - (const char *)filename {
                    178:     return name;
                    179: }
                    180: 
                    181: - (const char *)realFilename {
                    182:     return realName;
                    183: }
                    184: 
                    185: /*
                    186:  * This method is called whenever our window becomes the app's main window.
                    187:  * When this happens we get the 3D Panel and set its camera to our camera, so
                    188:  * the panel reflects the attributes of our window.
                    189:  */
                    190: - windowDidBecomeMain:sender {
                    191:     [[[NXApp delegate] threeDPanel] setCamera:camera];
                    192:     return self;
                    193: }
                    194: 
                    195: #define BUF_MAX                256
                    196: 
                    197: /*
                    198:  * This method is called when the user has just finished typing in an
                    199:  * expression.  This is where we validate that it is a legal expression.
                    200:  * If we dont like the expression, we return YES, which tells the text object
                    201:  * not to accept the user's entry.
                    202:  */
                    203: - (BOOL)textWillEnd:textObject {
                    204:     BOOL parseError;
                    205:     BOOL enableVars[MAX_CONST - MIN_CONST + 1];
                    206:     NXStringTable *stringTable;
                    207:     Expression *tempExpr = [[Expression allocFromZone:[self zone]] init];
                    208:     char buffer[BUF_MAX];
                    209:     char *newText;
                    210:     int length;
                    211: 
                    212:   /* get the text out of the text object where the expression was typed */
                    213:     length = [textObject byteLength] + 1;
                    214:     if (length > BUF_MAX)
                    215:        newText = NXZoneMalloc(NXDefaultMallocZone(), sizeof(char) * length);
                    216:     else
                    217:        newText = buffer;
                    218:     [textObject getSubstring:newText start:0 length:length];
                    219: 
                    220:     if (*newText) {
                    221:        parseError = [self _parseEquationFrom:newText using:tempExpr enableVariables:enableVars];
                    222:        if (!parseError) {
                    223:          /* parse the new equation into the real expressions */
                    224:            [self _equationChanged];
                    225:     
                    226:          /* update the shape object with current results and display it */
                    227:            [self _updateGraph:YES];
                    228:        }
                    229:     } else
                    230:        parseError = YES;
                    231: 
                    232:     if (parseError) {
                    233:        stringTable = [[NXApp delegate] stringTable];
                    234:        NXRunAlertPanel([stringTable valueForStringKey:"parse alert title"],
                    235:                        [stringTable valueForStringKey:"3D parse alert message"],
                    236:                        [stringTable valueForStringKey:"ok button"],
                    237:                        NULL, NULL);
                    238:     }
                    239:     [tempExpr free];
                    240:     if (length > BUF_MAX)
                    241:        NXZoneFree(NXDefaultMallocZone(), newText);
                    242:     return parseError;
                    243: }
                    244: 
                    245: /* This method does most of the work of parsing a new expression. */
                    246: - (void)_equationChanged {
                    247:     BOOL enableVars[MAX_CONST - MIN_CONST + 1];
                    248:     int i;
                    249:     int xyz;
                    250:     float newVarVal;
                    251: 
                    252:     for (i = 0; i <= MAX_CONST - MIN_CONST; i++)
                    253:        enableVars[i] = NO;
                    254:     for (xyz = X; xyz <= Z; xyz++) {
                    255:        (void)[self _parseEquationFrom:[equation[xyz] stringValue] using:expr[xyz] enableVariables:enableVars];
                    256:     }
                    257: 
                    258:   /* turn on the UI for constants used in these expressions */
                    259:     for (i = 0; i <= MAX_CONST - MIN_CONST; i++) {
                    260:        if ([[variableSliders cellAt:i :0] isEnabled])
                    261:            newVarVal = [[variableSliders cellAt:i :0] floatValue];
                    262:        else
                    263:            newVarVal = 1.0;
                    264:        [self _initVariable:i enable:enableVars[i] value:newVarVal];
                    265:     }
                    266: 
                    267:   /* update the range of the independent variables */
                    268:     [self _setUVRangeOf:U];
                    269:     [self _setUVRangeOf:V];
                    270: }
                    271: 
                    272: /*
                    273:  * This method is called after the user typed in a new expression.  The
                    274:  * expression has already been verified in our textWillEnd: method.
                    275:  */
                    276: - equationChanged:sender {
                    277:   /* reselect the equation field so the user can type another */
                    278:     [sender selectCell:[sender selectedCell]];
                    279:     return self;
                    280: }
                    281: 
                    282: /* called when either the minu, maxu, minv or maxv slider changes */
                    283: - _uOrVRangeSliderChanged:sender varName:(int)var {
                    284:     TextFieldCell *text;
                    285:     SliderCell *slider;
                    286:     NXEvent *event;
                    287: 
                    288:   /* update the associated text field */
                    289:     slider = [sender selectedCell];
                    290:     if (slider == maxUVSlider[var])
                    291:        text = maxUVText[var];
                    292:     else if (slider == minUVSlider[var])
                    293:        text = minUVText[var];
                    294:     else
                    295:        text = nil;
                    296:     NX_ASSERT(text, "Funny sender of _uOrVRangeSliderChanged: message");
                    297:     [text setFloatValue:[slider floatValue]];
                    298: 
                    299:   /* update the u or v range in the Expression and display the new graph */
                    300:     [self _setUVRangeOf:var];
                    301:     event = [NXApp currentEvent];
                    302:     [self _updateGraph:event->type == NX_LMOUSEUP];
                    303:     return self;
                    304: }
                    305: 
                    306: /* called when either the minu or maxu slider changes */
                    307: - uRangeSliderChanged:sender {
                    308:     return [self _uOrVRangeSliderChanged:sender varName:U];
                    309: }
                    310: 
                    311: /* called when either the minv or maxv slider changes */
                    312: - vRangeSliderChanged:sender {
                    313:     return [self _uOrVRangeSliderChanged:sender varName:V];
                    314: }
                    315: 
                    316: /* called when either the minu, maxu, minv or maxv text changes */
                    317: - _uOrVRangeTextChanged:sender varName:(int)var {
                    318:     TextFieldCell *text;
                    319:     SliderCell *slider;
                    320:     float val;
                    321: 
                    322:   /*
                    323:    * update the associated slider.  If the value typed is outside the current
                    324:    * range of the slider, we extend its range.
                    325:    */
                    326:     text = [sender selectedCell];
                    327:     if (text == maxUVText[var])
                    328:        slider = maxUVSlider[var];
                    329:     else if (text == minUVText[var])
                    330:        slider = minUVSlider[var];
                    331:     else
                    332:        slider = nil;
                    333:     NX_ASSERT(slider, "Funny sender of _uOrVRangeTextChanged: message");
                    334:     val = [text floatValue];
                    335:     [self _setSliderCell:slider value:val minLabel:nil maxLabel:nil];
                    336: 
                    337:   /* update the u or v range in the Expression and display the new graph */
                    338:     [self _setUVRangeOf:var];
                    339:     [self _updateGraph:YES];
                    340:     [sender selectText:self];
                    341:     return self;
                    342: }
                    343: 
                    344: /* called when either the minu or maxu text changes */
                    345: - uRangeTextChanged:sender {
                    346:     return [self _uOrVRangeTextChanged:sender varName:U];
                    347: }
                    348: 
                    349: /* called when either the minv or maxv text changes */
                    350: - vRangeTextChanged:sender {
                    351:     return [self _uOrVRangeTextChanged:sender varName:V];
                    352: }
                    353: 
                    354: /* called when one of the variables' sliders changes */
                    355: - variableSliderChanged:sender {
                    356:     int index;
                    357:     float val;
                    358:     NXEvent *event;
                    359: 
                    360:   /* update the associated text field */
                    361:     index = [variableSliders selectedRow];
                    362:     val = [[variableSliders selectedCell] floatValue];
                    363:     [[variableTexts cellAt:index :0] setFloatValue:val];
                    364:     [self _setVariable:index toValue:val];
                    365:     event = [NXApp currentEvent];
                    366:     [self _updateGraph:event->type == NX_LMOUSEUP];
                    367:     return self;
                    368: }
                    369: 
                    370: /* called when one of the variables' text fields changes */
                    371: - variableTextChanged:sender {
                    372:     int index;
                    373:     float val;
                    374:     TextFieldCell *text;
                    375:     SliderCell *slider;
                    376: 
                    377:   /* update the associated slider */
                    378:     text = [variableTexts selectedCell];
                    379:     index = [variableTexts selectedRow];
                    380:     slider = [variableSliders cellAt:index :0];
                    381:     val = [text floatValue];
                    382:     [self _setSliderCell:slider value:val
                    383:                minLabel:[variableMinLabels cellAt:index :0]
                    384:                maxLabel:[variableMaxLabels cellAt:index :0]];
                    385: 
                    386:     [self _setVariable:index toValue:val];
                    387:     [self _updateGraph:YES];
                    388:     [sender selectCell:text];
                    389:     return self;
                    390: }
                    391: 
                    392: /* The maximum allowable resolution.  ~10K polygons */
                    393: #define MAX_RES        100
                    394: 
                    395: /* called when either the resolution slider or text fields changes */
                    396: - resolutionChanged:sender {
                    397:     Cell *senderCell, *otherCell;
                    398:     int iVal;
                    399:     float fVal;
                    400:     int xyz;
                    401:     NXEvent *event;
                    402: 
                    403:     if ([[sender cellAt:0 :0] isKindOfClassNamed:"SliderCell"]) {
                    404:        senderCell = resolutionSlider;
                    405:        otherCell = resolutionText;
                    406:     } else {
                    407:        senderCell = resolutionText;
                    408:        otherCell = resolutionSlider;
                    409:     }
                    410: 
                    411:     fVal = [senderCell floatValue];
                    412:     iVal = rint(fVal);
                    413:     if (iVal > MAX_RES)
                    414:        iVal = MAX_RES;
                    415:     [otherCell setIntValue:iVal];
                    416: 
                    417:   /* update the Expression's resolution and display the new graph */
                    418:     for (xyz = X; xyz <= Z; xyz++)
                    419:        [expr[xyz] setResolution:iVal];
                    420:     event = [NXApp currentEvent];
                    421:     [self _updateGraph:event->type != NX_LMOUSEDRAGGED];
                    422:     if (senderCell == resolutionText)
                    423:        [sender selectText:self];
                    424:     return self;
                    425: }
                    426: 
                    427: /*
                    428:  * Copies the current view of the graph into the Pasteboard as Renderman code.
                    429:  * This is sent from the Copy Graph menu item.
                    430:  */
                    431: - copyGraph:sender {
                    432:     Pasteboard *pb;
                    433:     const char *types[2];
                    434:     NXDataLink *link;
                    435: 
                    436:     pb = [Pasteboard new];
                    437:     types[0] = N3DRIBPboardType;
                    438:     types[1] = NXDataLinkPboardType;
                    439:     [self _writeGraphToPasteboard:pb types:types num:2];
                    440:     link = [[NXDataLink alloc] initLinkedToSourceSelection:[NXSelection allSelection] managedBy:linkMgr supportingTypes:&N3DRIBPboardType count:1];
                    441:     [link writeToPasteboard:pb];
                    442:     [link free];
                    443:     return self;
                    444: }
                    445: 
                    446: /*
                    447:  * Copies the current view of the graph into the Pasteboard as RIB.
                    448:  * This is used by the Copy Graph menu item and to support Object Links.
                    449:  *
                    450:  * Types must contain N3DRIBPboardType.
                    451:  */
                    452: - _writeGraphToPasteboard:(Pasteboard *)pb types:(const char *const *)types num:(int)numTypes {
                    453:     NXStream *st;
                    454:     char *data;
                    455:     int dataLen, maxDataLen;
                    456: 
                    457:   /* Open a stream on memory where we will collect the PostScript */
                    458:     st = NXOpenMemory(NULL, 0, NX_WRITEONLY);
                    459: 
                    460:   /* Tell the Pasteboard we're going to copy RIB */
                    461:     [pb declareTypes:types num:numTypes owner:nil];
                    462: 
                    463:   /* writes the RIB for the whole graph into the stream */
                    464:     [camera copyRIBCode:st];
                    465: 
                    466:   /* get the buffered up PostScript out of the stream */
                    467:     NXGetMemoryBuffer(st, &data, &dataLen, &maxDataLen);
                    468: 
                    469:   /* put the buffer in the Pasteboard, free the stream (and the buffer) */
                    470:     [pb writeType:N3DRIBPboardType data:data length:dataLen];
                    471:     NXCloseMemory(st, NX_FREEBUFFER);
                    472:     return self;
                    473: }
                    474: 
                    475: /*** Object Links support - methods called by the DataLinkManager ***/
                    476: 
                    477: /* called by the DataLinkManager when new data is needed for a link */
                    478: - copyToPasteboard:(Pasteboard *)pboard at:(NXSelection *)selection cheapCopyAllowed:(BOOL)flag {
                    479:     NX_ASSERT([selection isEqual:[NXSelection allSelection]] || [selection isEqual:[NXSelection currentSelection]], "Funny selection passed to copyToPasteboard:at:");
                    480:     [self _writeGraphToPasteboard:pboard types:&N3DRIBPboardType num:1];
                    481:     return self;
                    482: }
                    483: 
                    484: /* returns the window for the given selection */
                    485: - windowForSelection:(NXSelection *)selection {
                    486:     return window;             /* all our sels are always in our one window */
                    487: }
                    488: 
                    489: /*
                    490:  * We support continuously updating links by tracking changes to links to
                    491:  * us from open documents.  This is easy for Graph because all links can
                    492:  * only be to the whole graph, so any change in the graph is a change relevant
                    493:  * to any link.
                    494:  */
                    495: - (BOOL)dataLinkManagerTracksLinksIndividually:(NXDataLinkManager *)sender {
                    496:     return YES;
                    497: }
                    498: 
                    499: /*
                    500:  * Sent when we should start tracking a link.  We just keep all links we're
                    501:  * tracking in a list, and send them a message whenever the graph is changed.
                    502:  */
                    503: - dataLinkManager:(NXDataLinkManager *)sender startTrackingLink:(NXDataLink *)link {
                    504:     if (!linksTracked)
                    505:        linksTracked = [[List allocFromZone:[self zone]] init];
                    506:     [linksTracked addObject:link];
                    507:     return self;
                    508: }
                    509: 
                    510: /* Sent when we can forget a links we're tracking */
                    511: - dataLinkManager:(NXDataLinkManager *)sender stopTrackingLink:(NXDataLink *)link {
                    512:     [linksTracked removeObject:link];
                    513:     if ([linksTracked count] == 0) {
                    514:        [linksTracked free];
                    515:        linksTracked = nil;
                    516:     }
                    517:     return self;
                    518: }
                    519: 
                    520: /*
                    521:  * Called by other methods of Graph3DDoc whenever we make a change to the
                    522:  * document.  We tell the DataLinkManager about the change, and also notify
                    523:  * any links we are tracking.
                    524:  */
                    525: - docChanged {
                    526:     [linkMgr documentEdited];
                    527:     [linksTracked makeObjectsPerform:@selector(sourceEdited)];
                    528:     [window setDocEdited:YES];
                    529:     return self;
                    530: }
                    531: 
                    532: /* called by ThreeDPanel when it changes something about the document */
                    533: - threeDPanelDidChangeDoc:(ThreeDPanel *)sender {
                    534:     [self docChanged];
                    535:     return self;
                    536: }
                    537: 
                    538: 
                    539: /* called when Save, Save As, or Save To is picked from the menu */
                    540: - save:sender   {  return [self _doSaveWithNewName:NO retainName:YES];  }
                    541: - saveAs:sender {  return [self _doSaveWithNewName:YES retainName:YES]; }
                    542: - saveTo:sender {  return [self _doSaveWithNewName:YES retainName:NO];  }
                    543: 
                    544: /*
                    545:  * All varieties of save go through this routine.  It covers all the cases
                    546:  * of running the Save Panel and retaining the name chosen.
                    547:  */
                    548: - _doSaveWithNewName:(BOOL)doNewName retainName:(BOOL)doRetain {
                    549:     SavePanel *savePanel;
                    550:     const char *saveName;      /* filename to save into */
                    551:     NXZone *zone;
                    552:     BOOL previouslySaved = (name != NULL);
                    553:     char realPath[MAXPATHLEN+1];
                    554: 
                    555:   /*
                    556:    * If the file is untitled or we are saving under a different name,
                    557:    * run the save panel to get a new name.
                    558:    */
                    559:     zone = [self zone];
                    560:     if (!name || doNewName) {
                    561:        savePanel = [SavePanel new];
                    562:        [savePanel setRequiredFileType:"xyzgraph"];
                    563:        if ([savePanel runModalForDirectory:NULL file:name]) {
                    564:          /* if we want to keep this name, replace any old name */      
                    565:            if (doRetain) {
                    566:                NXZoneFree(zone, name);
                    567:                NXZoneFree(zone, realName);
                    568:                realName = NULL;
                    569:                name = NXCopyStringBufferFromZone([savePanel filename], zone);
                    570:            }
                    571:            saveName = [savePanel filename];
                    572:        } else
                    573:            return nil;         /* user canceled */
                    574:     } else
                    575:       /* if we didn't run the Save Panel, save using the existing name */
                    576:        saveName = name;
                    577:     [self _write:saveName];
                    578:     if (!doRetain)
                    579:        [linkMgr documentSavedTo:saveName];
                    580:     else if (!previouslySaved || doNewName)
                    581:        [linkMgr documentSavedAs:saveName];
                    582:     else
                    583:        [linkMgr documentSaved];
                    584:     if (doRetain) {
                    585:        [window setDocEdited:NO];
                    586:        [window setTitleAsFilename:name];
                    587:        if (!realName && realpath(name, realPath)) {
                    588:            realName = NXCopyStringBufferFromZone(realPath, [self zone]);
                    589:        }
                    590:     }
                    591:     return self;
                    592: }
                    593: 
                    594: - revertToSaved:sender {
                    595:     int response;
                    596:     NXStringTable *stringTable;
                    597:     const char *shortName;
                    598:     Graph3DDoc *newDoc;
                    599: 
                    600:     if ([window isDocEdited] && name) {
                    601:        stringTable = [[NXApp delegate] stringTable];
                    602:        shortName = strrchr(name, '/') + 1;
                    603:        response =  NXRunAlertPanel([stringTable valueForStringKey:"revert alert title"],
                    604:                        [stringTable valueForStringKey:"revert alert message"],
                    605:                        [stringTable valueForStringKey:"revert button"],
                    606:                        [stringTable valueForStringKey:"cancel button"],
                    607:                        NULL, shortName);
                    608:        if (response == NX_ALERTDEFAULT) {
                    609:            [window setDelegate:nil];
                    610:            [window close];
                    611:            [NXApp delayedFree:self];
                    612:            [linkMgr documentClosed];
                    613:            [linkMgr free];
                    614:            linkMgr = nil;
                    615:            newDoc = [[Graph3DDoc allocFromZone:[self zone]] initFromFile:name];
                    616:            if (!newDoc) {
                    617:                NXRunAlertPanel(
                    618:                    [stringTable valueForStringKey:"open alert title"],
                    619:                    [stringTable valueForStringKey:"open alert message"],
                    620:                    [stringTable valueForStringKey:"ok button"],
                    621:                    NULL, NULL, name);
                    622:            }
                    623:        }
                    624:     }
                    625:     return self;
                    626: }
                    627: 
                    628: /* Called when the window is closing.  We free the Graph3DDoc. */
                    629: - windowWillClose:sender {
                    630:     int response;
                    631:     NXStringTable *stringTable;
                    632:     const char *shortName;
                    633: 
                    634:     if ([window isDocEdited]) {
                    635:        stringTable = [[NXApp delegate] stringTable];
                    636:        if (name) {
                    637:            shortName = strrchr(name, '/') + 1;
                    638:        } else {
                    639:            shortName = [stringTable valueForStringKey:"untitled doc"];
                    640:        }
                    641:        response =  NXRunAlertPanel([stringTable valueForStringKey:"close alert title"],
                    642:                        [stringTable valueForStringKey:"close alert message"],
                    643:                        [stringTable valueForStringKey:"save button"],
                    644:                        [stringTable valueForStringKey:"dont save button"],
                    645:                        [stringTable valueForStringKey:"cancel button"],
                    646:                        shortName);
                    647:        if (response != NX_ALERTDEFAULT && response != NX_ALERTALTERNATE) {
                    648:            return nil;
                    649:        } else {
                    650:            if (response == NX_ALERTDEFAULT && ![self save:sender])
                    651:                return nil;
                    652:        }
                    653:     }
                    654:     [NXApp delayedFree:self];
                    655:     [linkMgr documentClosed];
                    656:     return self;                       /* says its OK to close */
                    657: }
                    658: 
                    659: /* Called whenever something changes to update the view of the graph. */
                    660: - _updateGraph:(BOOL)finalChange {
                    661:     [self _updateShape];       /* update the values that we graph */
                    662:     [camera display];          /* draw the new graph */
                    663:     if (finalChange)
                    664:        [self docChanged];
                    665:     return self;
                    666: }
                    667: 
                    668: /* Called whenever something changes to update the values that we graph. */
                    669: - _updateShape {
                    670:     float *vals[3];
                    671:     int numVals[3];
                    672:     float min[3], max[3];
                    673:     int xyz;
                    674: 
                    675:   /* extract the values from the Expressions.  This may cause a recalc. */
                    676:     for (xyz = X; xyz <= Z; xyz++) {
                    677:        [expr[xyz] resultsVector:&vals[xyz] numVals:&numVals[xyz]];
                    678:        [expr[xyz] resultsMin:&min[xyz] max:&max[xyz]];
                    679:     }
                    680: 
                    681:   /* set the points of the graph */
                    682:     [[camera worldShape] setPointsX:vals[X] y:vals[Y] z:vals[Z]
                    683:                        minX:min[X] minY:min[Y] minZ:min[Z]
                    684:                        maxX:max[X] maxY:max[Y] maxZ:max[Z]
                    685:                        numU:[expr[X] resolution] numV:[expr[X] resolution]];
                    686:     return self;
                    687: }
                    688: 
                    689: /* parses and validates the text for one equation field */
                    690: - (BOOL)_parseEquationFrom:(const char *)str using:(Expression *)anExpr enableVariables:(BOOL *)enableVars {
                    691:     BOOL parseError;
                    692:     EXPEnumState state;        /* used to run through the vars of the expression */
                    693:     const char *var;
                    694: 
                    695:     parseError = ![anExpr parse:str];
                    696:     if (!parseError) {
                    697:        /*
                    698:        * If it parsed successfully, make sure all the variables are single
                    699:        * letters, and are either "u", "v" or between "A" and "H".  As we
                    700:        * find suitable variables, we remember then in the enableVars
                    701:        * array, so we can enable their controls later.
                    702:        */
                    703:        state = [anExpr beginVariableEnumeration];
                    704:        while (var = [anExpr nextVariable:state])
                    705:            if (*var && !var[1] && *var >= MIN_CONST && *var <= MAX_CONST)
                    706:                enableVars[*var - MIN_CONST] = YES;
                    707:            else if ((*var != 'u' && *var != 'v') || var[1])
                    708:                parseError = YES;
                    709:        [anExpr endVariableEnumeration:state];
                    710:     }
                    711:     return parseError;
                    712: }
                    713: 
                    714: /*
                    715:  * Writes a document to the given file using typedstreams.  We're very careful
                    716:  * about catching exceptions here so we can tell the user if his file didn't
                    717:  * get written out successfully.
                    718:  */
                    719: - _write:(const char *)filename {
                    720:     NXTypedStream *ts;
                    721:     const char *stringVal;
                    722:     int intVal;
                    723:     char charVal;
                    724:     RtPoint fromPoint;
                    725:     RtPoint toPoint;
                    726:     float aRollAngle;
                    727:     RtMatrix shapeXform;
                    728:     int rows, cols;
                    729:     int numVars;
                    730:     int i;
                    731:     NXStringTable *stringTable;
                    732:     volatile BOOL hadAnError = NO;
                    733:     char buffer[MAXPATHLEN+1];
                    734:     int xyz;
                    735:     NXRect winFrame;
                    736: 
                    737:   /* cons up the name of the backup file and remove it */
                    738:     strcpy(buffer, filename);
                    739:     strcat(buffer, "~");
                    740:     removeFile(buffer);
                    741: 
                    742:   /* move the existing file to the backup */
                    743:     rename(filename, buffer);
                    744: 
                    745:   /* create the new document as a file package (doc wrapper) */
                    746:     strcpy(buffer, filename);
                    747:     strcat(buffer, DOC_NAME);
                    748:     mkdir(filename, 0777);
                    749:     ts = NXOpenTypedStreamForFile(buffer, NX_WRITEONLY);
                    750:     if (ts) {
                    751:        NX_DURING
                    752:            intVal = 2;                 /* version of this write's data */
                    753:            NXWriteType(ts, "i", &intVal);
                    754:            for (xyz = X; xyz <= Z; xyz++) {
                    755:                stringVal = [expr[xyz] text];
                    756:                NXWriteType(ts, "*", &stringVal);
                    757:            }
                    758:            [variableSliders getNumRows:&rows numCols:&cols];
                    759:            numVars = 0;
                    760:            for (i = 0; i < rows; i++) {
                    761:                if ([[variableSliders cellAt:i :0] isEnabled])
                    762:                    numVars++;
                    763:            }
                    764:            NXWriteType(ts, "i", &numVars);
                    765:            for (i = 0; i < rows; i++)
                    766:                if ([[variableSliders cellAt:i :0] isEnabled]) {
                    767:                    charVal = MIN_CONST + i;
                    768:                    NXWriteType(ts, "c", &charVal);
                    769:                    writeCellFloat([variableSliders cellAt:i :0], ts);
                    770:                }
                    771:            intVal = [expr[X] resolution];
                    772:            NXWriteType(ts, "i", &intVal);
                    773:            writeCellFloat(minUVSlider[U], ts);
                    774:            writeCellFloat(maxUVSlider[U], ts);
                    775:            writeCellFloat(minUVSlider[V], ts);
                    776:            writeCellFloat(maxUVSlider[V], ts);
                    777:            NXWriteColor(ts, [[camera worldShape] color]);
                    778:            NXWriteColor(ts, [camera backgroundColor]);
                    779:            intVal = [[camera worldShape] surfaceType];
                    780:            NXWriteType(ts, "i", &intVal);
                    781:            [camera getEyeAt:&fromPoint toward:&toPoint roll:&aRollAngle];
                    782:            NXWriteArray(ts, "f", 3, fromPoint);
                    783:            NXWriteArray(ts, "f", 3, toPoint);
                    784:            NXWriteType(ts, "f", &aRollAngle);
                    785:            [[camera worldShape] getTransformMatrix:shapeXform];
                    786:            NXWriteArray(ts, "f", 4, shapeXform[0]);
                    787:            NXWriteArray(ts, "f", 4, shapeXform[1]);
                    788:            NXWriteArray(ts, "f", 4, shapeXform[2]);
                    789:            NXWriteArray(ts, "f", 4, shapeXform[3]);
                    790:            [window getFrame:&winFrame];
                    791:            NXWriteRect(ts, &winFrame);
                    792:            NXCloseTypedStream(ts);
                    793:        NX_HANDLER
                    794:            hadAnError = YES;
                    795:            NX_DURING
                    796:                NXCloseTypedStream(ts);
                    797:            NX_HANDLER
                    798:                /* ignore any error at this point */
                    799:            NX_ENDHANDLER
                    800:        NX_ENDHANDLER
                    801:     } else 
                    802:        hadAnError = YES;
                    803:     if (hadAnError) {
                    804:        stringTable = [[NXApp delegate] stringTable];
                    805:        NXRunAlertPanel([stringTable valueForStringKey:"save alert title"],
                    806:                        [stringTable valueForStringKey:"save alert message"],
                    807:                        [stringTable valueForStringKey:"ok button"],
                    808:                        NULL, NULL, filename);
                    809:        return nil;
                    810:     } else
                    811:        return self;
                    812: }
                    813: 
                    814: /* reads a document from the given file using typedstreams */
                    815: - _read:(const char *)filename {
                    816:     NXTypedStream *ts;
                    817:     char *stringVal;
                    818:     int i;
                    819:     char varName;
                    820:     float floatVal1, floatVal2;
                    821:     int intVal;
                    822:     RtPoint fromPoint;
                    823:     RtPoint toPoint;
                    824:     float aRollAngle;
                    825:     RtMatrix shapeXform;
                    826:     int version;
                    827:     int numVars;
                    828:     BOOL parseError;
                    829:     int xyz, uv;
                    830:     NXRect winFrame;
                    831: 
                    832:     ts = openDocStream(filename, NX_READONLY);
                    833:     if (ts) {
                    834:        NX_DURING
                    835:            NXReadType(ts, "i", &version);      /* read file version */
                    836:            NX_ASSERT(version <= 2, "Unknown file version in -read");
                    837:            for (xyz = X; xyz <= Z; xyz++) {
                    838:                NXReadType(ts, "*", &stringVal);
                    839:                [equation[xyz] setStringValue:stringVal];
                    840:                parseError = ![expr[xyz] parse:stringVal];
                    841:                free(stringVal);
                    842:                NX_ASSERT(!parseError, "Bad expression stored in data file");
                    843:                if (parseError)
                    844:                    NX_VALRETURN(nil);
                    845:            }
                    846:            NXReadType(ts, "i", &numVars);
                    847:            for (i = 0; i < numVars; i++) {
                    848:                NXReadType(ts, "c", &varName);
                    849:                NXReadType(ts, "f", &floatVal1);
                    850:                [self _initVariable:varName - MIN_CONST enable:YES
                    851:                                                        value:floatVal1];
                    852:            }
                    853:            NXReadType(ts, "i", &intVal);               /* read resolution */
                    854:            [resolutionText setIntValue:intVal];
                    855:            [resolutionSlider setIntValue:intVal];
                    856:            for (xyz = X; xyz <= Z; xyz++)
                    857:                [expr[xyz] setResolution:intVal];
                    858:            for (uv = U; uv <= V; uv++) {
                    859:                NXReadType(ts, "f", &floatVal1);        /* read min */
                    860:                NXReadType(ts, "f", &floatVal2);        /* read max */
                    861:                [minUVText[uv] setFloatValue:floatVal1];
                    862:                [self _setSliderCell:minUVSlider[uv] value:floatVal1
                    863:                                                minLabel:nil maxLabel:nil];
                    864:                [maxUVText[uv] setFloatValue:floatVal2];
                    865:                [self _setSliderCell:maxUVSlider[uv] value:floatVal2
                    866:                                                minLabel:nil maxLabel:nil];
                    867:                [self _setUVRangeOf:uv];
                    868:            }
                    869:            [[camera worldShape] setColor:NXReadColor(ts)];
                    870:            [camera setBackgroundColor:NXReadColor(ts)];
                    871:            NXReadType(ts, "i", &intVal);               /* read surface type */
                    872:            [camera setSurfaceTypeForAll:intVal chooseHider:YES];
                    873:            NXReadArray(ts, "f", 3, fromPoint);
                    874:            NXReadArray(ts, "f", 3, toPoint);
                    875:            NXReadType(ts, "f", &aRollAngle);
                    876:            [camera setEyeAt:fromPoint toward:toPoint roll:aRollAngle];
                    877:            NXReadArray(ts, "f", 4, shapeXform[0]);
                    878:            NXReadArray(ts, "f", 4, shapeXform[1]);
                    879:            NXReadArray(ts, "f", 4, shapeXform[2]);
                    880:            NXReadArray(ts, "f", 4, shapeXform[3]);
                    881:            [[camera worldShape] setTransformMatrix:shapeXform];
                    882:            NXReadRect(ts, &winFrame);
                    883:            [window placeWindow:&winFrame];
                    884:            NXCloseTypedStream(ts);
                    885:          /* must use NX_VALRETURN to return from inside a NX_DURING */
                    886:            NX_VALRETURN(self);
                    887:        NX_HANDLER
                    888:            NX_DURING
                    889:                NXCloseTypedStream(ts);
                    890:            NX_HANDLER
                    891:                /* ignore any error at this point */
                    892:            NX_ENDHANDLER
                    893:            return nil;
                    894:        NX_ENDHANDLER
                    895:     } else
                    896:        return nil;
                    897: }
                    898: 
                    899: /*
                    900:  * Inits a variable to either be on or off.  Used when we discover a new
                    901:  * set of variables after a parse, or to set up the variables from a document
                    902:  * that we read from a file.
                    903:  */
                    904: - (void)_initVariable:(int)index enable:(BOOL)flag value:(float)val {
                    905:     TextFieldCell *text;
                    906:     SliderCell *slider;
                    907: 
                    908:   /* set the initial value in the Expression */
                    909:     if (flag)
                    910:        [self _setVariable:index toValue:val];
                    911: 
                    912:   /* if the enabled status is changing... */
                    913:     if (flag != [[variableSliders cellAt:index :0] isEnabled])
                    914:        if (flag) {
                    915: 
                    916:          /* enable all controls associated with the variable */
                    917:            [[variableLabels cellAt:index :0] setTextGray:NX_BLACK];
                    918:            slider = [variableSliders cellAt:index :0];
                    919:            [slider setEnabled:YES];
                    920:            [self _setSliderCell:slider value:val
                    921:                        minLabel:[variableMinLabels cellAt:index :0]
                    922:                        maxLabel:[variableMaxLabels cellAt:index :0]];
                    923:            text = [variableTexts cellAt:index :0];
                    924:            [text setEnabled:YES];
                    925:            [text setFloatValue:val];
                    926:        } else if (!flag) {
                    927: 
                    928:          /* disable all controls associated with the variable */
                    929:            [[variableLabels cellAt:index :0] setTextGray:NX_DKGRAY];
                    930:            slider = [variableSliders cellAt:index :0];
                    931:            [slider setFloatValue:[slider minValue]];
                    932:            [slider setEnabled:NO];
                    933:            text = [variableTexts cellAt:index :0];
                    934:            [text setStringValue:NULL];
                    935:            [text setEnabled:NO];
                    936:        }
                    937: }
                    938: 
                    939: /*
                    940:  * sets the range of the u or v variable in the Expression.
                    941:  * It ensures that the min value isn't greater than the max value.
                    942:  */
                    943: - (void)_setUVRangeOf:(int)var {
                    944:     int xyz;
                    945:     float min = [minUVSlider[var] floatValue];
                    946:     float max = [maxUVSlider[var] floatValue];
                    947:     char *varName = (var == U) ? "u" : "v";
                    948: 
                    949:     for (xyz = X; xyz <= Z; xyz++) {
                    950:        [expr[xyz] setVar:varName min:MIN(min, max) max:MAX(min, max)];
                    951:       /* set up two dimensions of evaluation */
                    952:        [expr[xyz] setVar:varName dimension:var];
                    953:     }
                    954: }
                    955: 
                    956: /* sets one of the expressions' variables to a value */
                    957: - (void)_setVariable:(int)index toValue:(float)val {
                    958:     char varName[2];
                    959:     int xyz;
                    960: 
                    961:     varName[0] = MIN_CONST + index;
                    962:     varName[1] = '\0';
                    963:     for (xyz = X; xyz <= Z; xyz++)
                    964:        [expr[xyz] setVar:varName value:val];
                    965: }
                    966: 
                    967: /* sets a slider to a value and adjust min/max */
                    968: - (void)_setSliderCell:(SliderCell *)slider value:(float)val minLabel:(Cell *)minLabel maxLabel:(Cell *)maxLabel {
                    969:     if (val < [slider minValue]) {
                    970:        [window disableDisplay];   /* so slider won't flash to new location */
                    971:        [slider setMinValue:val];
                    972:        [window reenableDisplay];
                    973:        [minLabel setIntValue:floor(val)];
                    974:     } else if (val > [slider maxValue]) {
                    975:        [window disableDisplay];   /* so slider won't flash to new location */
                    976:        [slider setMaxValue:val];
                    977:        [window reenableDisplay];
                    978:        [maxLabel setIntValue:ceil(val)];
                    979:     }
                    980:     [slider setFloatValue:val];
                    981: }
                    982: 
                    983: /* sets up some default lights for the camera */
                    984: - (void)_setUpLighting {
                    985:     RtPoint distantlight_from, distantlight2_from, pointlight_from;
                    986:     RtPoint distantlight_to;
                    987:     RtFloat intensity;
                    988:     N3DLight *pointLight, *distantLight, *distantLight2, *ambientLight;
                    989: 
                    990:     if ([[camera lightList] count] == 4)
                    991:        return;         /* we've already added lights to this camera */
                    992: 
                    993:     pointlight_from[0] = 12.0;
                    994:     pointlight_from[1] = -7.0;
                    995:     pointlight_from[2] = 8.5;
                    996:     distantlight_from[0] = 12.0;
                    997:     distantlight_from[1] = -7.0;
                    998:     distantlight_from[2] = 8.5;
                    999:     distantlight2_from[0] = 3.0;
                   1000:     distantlight2_from[1] = 2.0;
                   1001:     distantlight2_from[2] = 8.5;
                   1002: 
                   1003:     distantlight_to[0] = 0.0;
                   1004:     distantlight_to[1] = 0.0;
                   1005:     distantlight_to[2] = 0.0;
                   1006: 
                   1007:     intensity = 60.0;
                   1008:     pointLight = [[N3DLight allocFromZone:[camera zone]] init];
                   1009:     [pointLight makePointFrom:pointlight_from intensity:intensity];
                   1010:     [camera addLight:pointLight];
                   1011: 
                   1012:     intensity = 0.6;
                   1013:     distantLight = [[N3DLight allocFromZone:[camera zone]] init];
                   1014:     [distantLight makeDistantFrom:distantlight_from to:distantlight_to intensity:intensity];
                   1015:     [camera addLight:distantLight];
                   1016: 
                   1017:     intensity = 0.9;
                   1018:     distantLight2 = [[N3DLight allocFromZone:[camera zone]] init];
                   1019:     [distantLight2 makeDistantFrom:distantlight2_from to:distantlight_to intensity:intensity];
                   1020:     [camera addLight:distantLight2];
                   1021: 
                   1022:     intensity = 0.15;
                   1023:     ambientLight = [[N3DLight allocFromZone:[camera zone]] init];
                   1024:     [ambientLight makeAmbientWithIntensity:intensity];
                   1025:     [camera addLight:ambientLight];
                   1026: }
                   1027: 
                   1028: /*
                   1029:  * These methods are called as the IB file is unarchived.  We override them to
                   1030:  * store these outlets in arrays, since IB doesnt know how to declare arrays
                   1031:  * of outlets.
                   1032:  */
                   1033: - setEquationX:obj     { equation[X] = obj; return self;       }
                   1034: - setEquationY:obj     { equation[Y] = obj; return self;       }
                   1035: - setEquationZ:obj     { equation[Z] = obj; return self;       }
                   1036: - setMinUSlider:obj    { minUVSlider[U] = obj; return self;    }
                   1037: - setMinVSlider:obj    { minUVSlider[V] = obj; return self;    }
                   1038: - setMaxUSlider:obj    { maxUVSlider[U] = obj; return self;    }
                   1039: - setMaxVSlider:obj    { maxUVSlider[V] = obj; return self;    }
                   1040: - setMinUText:obj      { minUVText[U] = obj; return self;      }
                   1041: - setMinVText:obj      { minUVText[V] = obj; return self;      }
                   1042: - setMaxUText:obj      { maxUVText[U] = obj; return self;      }
                   1043: - setMaxVText:obj      { maxUVText[V] = obj; return self;      }
                   1044: 
                   1045: @end
                   1046: 
                   1047: /* little utility proc to write out the float value of a control */
                   1048: static void writeCellFloat(Cell *obj, NXTypedStream *ts) {
                   1049:     float val;
                   1050: 
                   1051:     val = [obj floatValue];
                   1052:     NXWriteType(ts, "f", &val);
                   1053: }
                   1054: 
                   1055: /*  Opens a stream on the document regardless of whether its a doc wrapper. */
                   1056: static NXTypedStream *openDocStream(const char *filename, int mode) {
                   1057:     NXTypedStream *ts = NULL;
                   1058:     struct stat statInfo;
                   1059:     char buffer[MAXPATHLEN+1];
                   1060: 
                   1061:     if (stat(filename, &statInfo) == 0) {
                   1062:        if (statInfo.st_mode & S_IFDIR) {
                   1063:            strcpy(buffer, filename);
                   1064:            strcat(buffer, DOC_NAME);
                   1065:            ts = NXOpenTypedStreamForFile(buffer, mode);
                   1066:        } else {
                   1067:            ts = NXOpenTypedStreamForFile(filename, mode);
                   1068:        }
                   1069:     }
                   1070:     return ts;
                   1071: }
                   1072: 
                   1073: /* removes a directory, removing anything inside it.  Does not recurse */
                   1074: static int removeFile(const char *file) {
                   1075:     DIR *dirp;
                   1076:     struct stat st;
                   1077:     struct direct *dp;
                   1078:     char *leaf = NULL;
                   1079:     char path[MAXPATHLEN+1];
                   1080: 
                   1081:     if (!stat(file, &st)) {
                   1082:        if ((st.st_mode & S_IFMT) == S_IFDIR) {
                   1083:            dirp = opendir(file);
                   1084:            for (dp = readdir(dirp); dp != NULL; dp = readdir(dirp)) {
                   1085:                if (strcmp(dp->d_name, ".") && strcmp(dp->d_name, "..")) {
                   1086:                    if (!leaf) {
                   1087:                        strcpy(path, file);
                   1088:                        strcat(path, "/");
                   1089:                        leaf = path + strlen(path);
                   1090:                    }
                   1091:                    strcpy(leaf, dp->d_name);
                   1092:                    if (unlink(path)) {
                   1093:                        closedir(dirp);
                   1094:                        return -1;
                   1095:                    }
                   1096:                }
                   1097:            }
                   1098:            return rmdir(file);
                   1099:        } else {
                   1100:            return unlink(file);
                   1101:        }
                   1102:     }
                   1103: 
                   1104:     return -1;
                   1105: }

unix.superglobalmegacorp.com

This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.