Annotation of Examples/AppKit/Graph/GraphDoc.m, revision 1.1

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

unix.superglobalmegacorp.com

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