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