Annotation of Examples/AppKit/Graph/Graph3DDoc.m, revision 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.