home *** CD-ROM | disk | FTP | other *** search
-
- /*
- GraphDoc.m
-
- The GraphDoc represents an open Graph document. GraphDoc receives
- messages from the user interface objects and uses an Expression object
- to do calculations and a LineGraph to display the results.
-
- The GraphDoc class has one slightly odd external dependency. It requires
- that the delegate of NXApp be able to provide it with a NXStringTable
- (via the stringTable method) that it can use to look up strings that
- are presented to the user. In this application, the delegate of NXApp is
- always an instance of the GraphApp class.
-
- If this were a larger program, the NXStringTable needed by this document
- class would probably be in its own nib section, which would be loaded once
- the first time a GraphDoc is created, and then shared among all GraphDocs.
-
- You may freely copy, distribute, and reuse the code in this example.
- NeXT disclaims any warranty of any kind, expressed or implied, as to its
- fitness for any particular use.
- */
-
- #import "Graph.h"
-
- /* declare methods static to this class */
- @interface GraphDoc(GraphDocPrivate)
- - _updateGraphVals;
- - _updateGraph:(BOOL)finalChange;
- - _updateLinks;
- - _write:(const char *)filename;
- - _read:(const char *)filename;
- - (void)_initVariable:(int)index enable:(BOOL)flag value:(float)val;
- - _doSaveWithNewName:(BOOL)doNewName retainName:(BOOL)doRetain;
- - _writeGraphToPasteboard:(Pasteboard *)pb types:(const char *const *)types num:(int)numTypes;
- - (void)_setSliderCell:(SliderCell *)slider value:(float)val;
- @end
-
- @implementation GraphDoc
-
- /* variable names must be between A and H */
- #define MIN_CONST 'A'
- #define MAX_CONST 'H'
-
- /* default resolution of a new graph */
- #define DEFAULT_RES 30
-
- /* name used for the real document inside a doc wrapper */
- #define DOC_NAME "/GraphDoc.xygraph"
-
- static void writeCellFloat(Cell *obj, NXTypedStream *ts);
- static void setXRange(Expression *expr, float min, float max);
- static NXTypedStream *openDocStream(const char *filename, int mode);
- static int removeFile(const char *file);
-
- - init {
- /* the default initialization is to just open a new document */
- return [self initFromFile:NULL];
- }
-
- /*
- * Opens a document. If file is NULL, we open an untitiled document. We also
- * create a NXDataLinkManager and hook ourselves up as its delegate for doing
- * Object Links.
- */
- - initFromFile:(const char *)file {
- int rows, cols;
- int i;
- TextField *aCell;
- char realPath[MAXPATHLEN+1];
-
- [super init];
-
- /* load the UI from the nib section and set attributes not available in IB */
- [NXApp loadNibSection:"GraphDoc.nib" owner:self withNames:NO fromZone:[self zone]];
- [variableTexts getNumRows:&rows numCols:&cols];
- for (i = rows; i--; ) {
- aCell = [variableTexts cellAt:i :0];
- [aCell setFloatingPointFormat:YES left:3 right:2];
- [aCell setEnabled:NO];
- }
- [minXText setFloatingPointFormat:YES left:3 right:2];
- [maxXText setFloatingPointFormat:YES left:3 right:2];
- [variableSliders setAutosizeCells:YES];
- [resolutionText setEntryType:NX_POSINTTYPE];
-
- /* fake a resize so we get the resolution max up to date */
- [self windowDidResize:window];
-
- /* create an Expression object we will use to evaluate expressions */
- expr = [[Expression allocFromZone:[self zone]] init];
-
- if (file) {
- /* read existing document */
- if ([self _read:file]) {
- name = NXCopyStringBufferFromZone(file, [self zone]);
- if (realpath(name, realPath))
- realName = NXCopyStringBufferFromZone(realPath, [self zone]);
- [window setTitleAsFilename:name];
- [self _updateGraphVals];
- linkMgr = [[NXDataLinkManager allocFromZone:[self zone]] initWithDelegate:self fromFile:file];
- [graph display];
- } else {
- [self free]; /* couldn't load file */
- return nil;
- }
- } else {
- /*
- * Create a new document. We initialize it with a trivial expression
- * because that was easier than allowing the state where there is no
- * current expression.
- */
- [graph scaleToFit];
- [window setTitleAsFilename:[[[NXApp delegate] stringTable] valueForStringKey:"untitled doc"]];
- [resolutionSlider setIntValue:DEFAULT_RES];
- [resolutionText setIntValue:DEFAULT_RES];
- [expr setResolution:DEFAULT_RES];
- [equation setStringValue:"x"];
- [expr parse:"x"];
- setXRange(expr, [minXSlider floatValue], [maxXSlider floatValue]);
- [self _updateGraphVals];
- linkMgr = [[NXDataLinkManager allocFromZone:[self zone]] initWithDelegate:self];
- [graph scaleToFit];
- [graph display];
- }
- [window makeKeyAndOrderFront:self];
- return self;
- }
-
- - free {
- [expr free];
- NXZoneFree([self zone], name);
- NXZoneFree([self zone], realName);
- [linkMgr free];
- return [super free];
- }
-
- - windowDidResize:sender {
- NXRect frame;
-
- /*
- * Whenever the window changes size, we update the maximum resolution value
- * to be the width of the graph view.
- */
- [graph getFrame:&frame];
- [resolutionSlider setMaxValue:frame.size.width];
- return self;
- }
-
- - (const char *)filename {
- return name;
- }
-
- - (const char *)realFilename {
- return realName;
- }
-
- /*
- * This method is called whenever our window becomes the app's main window.
- * When this happens we get the 3D Panel and set its camera nil, so
- * the panel reflects the attributes of our window.
- */
- - windowDidBecomeMain:sender {
- [[[NXApp delegate] threeDPanel] setCamera:nil];
- return self;
- }
-
- #define BUF_MAX 256
-
- /*
- * This method is called when the user has just finished typing in an
- * expression. This is where we validate that it is a legal expression.
- * If we dont like the expression, we return YES, which tells the text object
- * not to accept the user's entry.
- */
- - (BOOL)textWillEnd:textObject {
- const char *var;
- BOOL parseError;
- char buffer[BUF_MAX];
- char *newText;
- int length;
- BOOL enableVariables[MAX_CONST - MIN_CONST + 1];
- int i;
- EXPEnumState state; /* used to run through the vars of the expression */
- NXStringTable *stringTable;
-
- /* get the text out of the text object where the expression was typed */
- length = [textObject byteLength] + 1;
- if (length > BUF_MAX)
- newText = NXZoneMalloc(NXDefaultMallocZone(), sizeof(char) * length);
- else
- newText = buffer;
- [textObject getSubstring:newText start:0 length:length];
-
- if (*newText && (![expr text] || strcmp([expr text], newText))) {
-
- /* try parsing the text of the expression */
- parseError = ![expr parse:newText];
- if (!parseError) {
- for (i = 0; i <= MAX_CONST - MIN_CONST; i++)
- enableVariables[i] = NO;
-
- /*
- * If it parsed successfully, make sure all the variables are single
- * letters, and are either "x" or between "A" and "H". As we
- * find suitable variables, we remember then in the enableVariables
- * array, so we can enable their controls later.
- */
- state = [expr beginVariableEnumeration];
- while (var = [expr nextVariable:state])
- if (*var && !var[1] && *var >= MIN_CONST && *var <= MAX_CONST)
- enableVariables[*var - MIN_CONST] = YES;
- else if (*var != 'x' || var[1])
- parseError = YES;
- [expr endVariableEnumeration:state];
- if (!parseError) {
- for (i = 0; i <= MAX_CONST - MIN_CONST; i++)
- [self _initVariable:i enable:enableVariables[i] value:1.0];
- /* update the range of the x variable */
- setXRange(expr, [minXSlider floatValue], [maxXSlider floatValue]);
-
- /* update the graph object with current results and display it */
- [self _updateGraphVals];
- [graph scaleToFit];
- [graph display];
- [self _updateLinks];
- }
- }
- } else if (!*newText) {
- parseError = YES;
- } else
- parseError = NO;
- if (length > BUF_MAX)
- NXZoneFree(NXDefaultMallocZone(), newText);
-
- if (parseError) {
- stringTable = [[NXApp delegate] stringTable];
- NXRunAlertPanel([stringTable valueForStringKey:"parse alert title"],
- [stringTable valueForStringKey:"parse alert message"],
- [stringTable valueForStringKey:"ok button"],
- NULL, NULL);
- }
- return parseError;
- }
-
- /*
- * This method is called after the user typed in a new expression. The
- * expression has already been parsed in our textWillEnd: method.
- */
- - equationChanged:sender {
-
- /* reselect the equation field so the user can type another */
- [equation selectText:self];
- return self;
- }
-
- /* called when either the minx or maxx slider changes */
- - xRangeSliderChanged:sender {
- TextFieldCell *text;
- SliderCell *slider;
- NXEvent *event;
-
- /* update the associated text field */
- slider = [sender selectedCell];
- if (slider == maxXSlider)
- text = maxXText;
- else if (slider == minXSlider)
- text = minXText;
- else
- text = nil;
- NX_ASSERT(text, "Funny sender of xRangeSliderChanged: message");
- [text setFloatValue:[slider floatValue]];
-
- /* update the x range in the Expression and display the new graph */
- setXRange(expr, [minXText floatValue], [maxXText floatValue]);
- event = [NXApp currentEvent];
- [self _updateGraph:event->type == NX_LMOUSEUP];
- return self;
- }
-
- /* called when either the minx or maxx text changes */
- - xRangeTextChanged:sender {
- TextFieldCell *text;
- SliderCell *slider;
- float val;
-
- /*
- * update the associated slider. If the value typed is outside the current
- * range of the slider, we extend its range.
- */
- text = [sender selectedCell];
- if (text == maxXText)
- slider = maxXSlider;
- else if (text == minXText)
- slider = minXSlider;
- else
- slider = nil;
- NX_ASSERT(slider, "Funny sender of xRangeTextChanged: message");
- val = [text floatValue];
- [self _setSliderCell:slider value:val];
-
- /* update the x range in the Expression and display the new graph */
- setXRange(expr, [minXText floatValue], [maxXText floatValue]);
- [self _updateGraph:YES];
- [sender selectCell:text];
- return self;
- }
-
- /* called when one of the variables' sliders changes */
- - variableSliderChanged:sender {
- int index;
- float val;
- char varName[2];
- NXEvent *event;
-
- /* update the associated text field */
- index = [variableSliders selectedRow];
- val = [[variableSliders selectedCell] floatValue];
- [[variableTexts cellAt:index :0] setFloatValue:val];
- varName[0] = MIN_CONST + index;
- varName[1] = '\0';
-
- /* update the variable's value in the Expression and display the new graph */
- [expr setVar:varName value:val];
- event = [NXApp currentEvent];
- [self _updateGraph:event->type == NX_LMOUSEUP];
- return self;
- }
-
- /* called when one of the variables' text fields changes */
- - variableTextChanged:sender {
- int index;
- float val;
- char varName[2];
- TextFieldCell *text;
- SliderCell *slider;
-
- /* update the associated slider */
- text = [variableTexts selectedCell];
- index = [variableTexts selectedRow];
- slider = [variableSliders cellAt:index :0];
- val = [text floatValue];
- [self _setSliderCell:slider value:val];
-
- /* update the variable's value in the Expression and display the new graph */
- varName[0] = MIN_CONST + index;
- varName[1] = '\0';
- [expr setVar:varName value:val];
- [self _updateGraph:YES];
- [sender selectText:self];
- [sender selectCell:text];
- return self;
- }
-
- /*
- * The maximum allowable resolution. The coordinates string of the userpath
- * that the LineGraph class uses to draw can only have 64K of data (like any
- * PostScript string). 64K of data is 16K of floats, or 8K coordinate pairs.
- * But every userpath has to have 4 numbers devoted to the bounding box, so
- * this reduces the number of allowable points to 8190.
- *
- * (In spite of all that nice math, 8190 doesn't seem to work, but 8189 does.
- * We need to look into this, but for now we don't push the limit.)
- */
- #define MAX_RES 8189
-
- /* called when either the resolution slider or text fields changes */
- - resolutionChanged:sender {
- Cell *senderCell, *otherCell;
- int iVal;
- float fVal;
- NXEvent *event;
-
- if ([[sender cellAt:0 :0] isKindOfClassNamed:"SliderCell"]) {
- senderCell = resolutionSlider;
- otherCell = resolutionText;
- } else {
- senderCell = resolutionText;
- otherCell = resolutionSlider;
- }
-
- fVal = [senderCell floatValue];
- iVal = rint(fVal);
- if (iVal > MAX_RES)
- iVal = MAX_RES;
- [otherCell setIntValue:iVal];
-
- /* update the Expression's resolution and display the new graph */
- [expr setResolution:iVal];
- event = [NXApp currentEvent];
- [self _updateGraph:event->type != NX_LMOUSEDRAGGED];
- if (senderCell == resolutionText)
- [sender selectText:self];
- return self;
- }
-
- /* called when the zoom in button is pressed */
- - zoomIn:sender {
- [autoScale setIntValue:0];
- [graph zoom:2.0];
- [graph display];
- [self _updateLinks];
- return self;
- }
-
- /* called when the zoom out button is pressed */
- - zoomOut:sender {
- [autoScale setIntValue:0];
- [graph zoom:0.5];
- [graph display];
- [self _updateLinks];
- return self;
- }
-
- /* called when the auto scale switch changes */
- - autoScale:sender {
- if ([autoScale intValue]) {
- [graph scaleToFit]; /* it got turned on, get scaled to fit */
- [graph display];
- [self _updateLinks];
- }
- return self;
- }
-
- /*
- * Copies the current view of the graph into the Pasteboard as PostScript. It
- * also writes a DataLink to the Pasteboard which can be used to create an
- * Object Link to the graph. Since there is no notion of user selection
- * within a graph, its very easy for us to generate NXSelection objects - we
- * just use the standard Selection meaning "Select All". This is sent from the
- * Copy Graph menu item.
- */
- - copyGraph:sender {
- Pasteboard *pb;
- const char *types[2];
- NXDataLink *link;
-
- pb = [Pasteboard new];
- types[0] = NXPostScriptPboardType;
- types[1] = NXDataLinkPboardType;
- [self _writeGraphToPasteboard:pb types:types num:2];
- link = [[NXDataLink alloc] initLinkedToSourceSelection:[NXSelection allSelection] managedBy:linkMgr supportingTypes:&NXPostScriptPboardType count:1];
- [link writeToPasteboard:pb];
- [link free];
- return self;
- }
-
- /*
- * Does the real work of copying the graph as PostScript. This code is used
- * for copy/paste and Object Links support.
- *
- * Types must contain NXPostScriptPboardType.
- */
- - _writeGraphToPasteboard:(Pasteboard *)pb types:(const char *const *)types num:(int)numTypes {
- NXStream *st;
- char *data;
- int dataLen, maxDataLen;
-
- /* Open a stream on memory where we will collect the PostScript */
- st = NXOpenMemory(NULL, 0, NX_WRITEONLY);
-
- /* Tell the Pasteboard we're going to copy PostScript */
- [pb declareTypes:types num:numTypes owner:nil];
-
- /* writes the PostScript for the whole graph as EPS into the stream */
- [graph copyPSCodeInside:NULL to:st];
-
- /* get the buffered up PostScript out of the stream */
- NXGetMemoryBuffer(st, &data, &dataLen, &maxDataLen);
-
- /* put the buffer in the Pasteboard, free the stream (and the buffer) */
- [pb writeType:NXPostScriptPboardType data:data length:dataLen];
- NXCloseMemory(st, NX_FREEBUFFER);
- return self;
- }
-
- /*** Object Links support - methods called by the DataLinkManager ***/
-
- /* called by the DataLinkManager when new data is needed for a link */
- - copyToPasteboard:(Pasteboard *)pboard at:(NXSelection *)selection cheapCopyAllowed:(BOOL)flag {
- NX_ASSERT([selection isEqual:[NXSelection allSelection]] || [selection isEqual:[NXSelection currentSelection]], "Funny selection passed to copyToPasteboard:at:");
- [self _writeGraphToPasteboard:pboard types:&NXPostScriptPboardType num:1];
- return self;
- }
-
- /* returns the window for the given selection */
- - windowForSelection:(NXSelection *)selection {
- return window; /* all our sels are always in our one window */
- }
-
- /*
- * We support continuously updating links by tracking changes to links to
- * us from open documents. This is easy for Graph because all links can
- * only be to the whole graph, so any change in the graph is a change relevant
- * to any link.
- */
- - (BOOL)dataLinkManagerTracksLinksIndividually:(NXDataLinkManager *)sender {
- return YES;
- }
-
- /*
- * Sent when we should start tracking a link. We just keep all links we're
- * tracking in a list, and send them a message whenever the graph is changed.
- */
- - dataLinkManager:(NXDataLinkManager *)sender startTrackingLink:(NXDataLink *)link {
- if (!linksTracked)
- linksTracked = [[List allocFromZone:[self zone]] init];
- [linksTracked addObject:link];
- return self;
- }
-
- /* Sent when we can forget a links we're tracking */
- - dataLinkManager:(NXDataLinkManager *)sender stopTrackingLink:(NXDataLink *)link {
- [linksTracked removeObject:link];
- if ([linksTracked count] == 0) {
- [linksTracked free];
- linksTracked = nil;
- }
- return self;
- }
-
- /*
- * Called by other methods of GraphDoc whenever we make a change to the
- * document. We tell the DataLinkManager about the change, and also notify
- * any links we are tracking.
- */
- - _updateLinks {
- [linkMgr documentEdited];
- [linksTracked makeObjectsPerform:@selector(sourceEdited)];
- [window setDocEdited:YES];
- return self;
- }
-
- /* called when Save, Save As, or Save To is picked from the menu */
- - save:sender { return [self _doSaveWithNewName:NO retainName:YES]; }
- - saveAs:sender { return [self _doSaveWithNewName:YES retainName:YES]; }
- - saveTo:sender { return [self _doSaveWithNewName:YES retainName:NO]; }
-
- /*
- * All varieties of save go through this routine. It covers all the cases
- * of running the Save Panel and retaining the name chosen.
- */
- - _doSaveWithNewName:(BOOL)doNewName retainName:(BOOL)doRetain {
- SavePanel *savePanel;
- const char *saveName; /* filename to save into */
- NXZone *zone;
- BOOL previouslySaved = (name != NULL);
- char realPath[MAXPATHLEN+1];
-
- /*
- * If the file is untitled or we are saving under a different name,
- * run the save panel to get a new name.
- */
- zone = [self zone];
- if (!name || doNewName) {
- savePanel = [SavePanel new];
- [savePanel setRequiredFileType:"xygraph"];
- if ([savePanel runModalForDirectory:NULL file:name]) {
- /* if we want to keep this name, replace any old name */
- if (doRetain) {
- NXZoneFree(zone, name);
- NXZoneFree(zone, realName);
- realName = NULL;
- name = NXCopyStringBufferFromZone([savePanel filename], zone);
- }
- saveName = [savePanel filename];
- } else
- return nil; /* user canceled */
- } else
- /* if we didn't run the Save Panel, save using the existing name */
- saveName = name;
- [self _write:saveName];
- if (!doRetain)
- [linkMgr documentSavedTo:saveName];
- else if (!previouslySaved || doNewName)
- [linkMgr documentSavedAs:saveName];
- else
- [linkMgr documentSaved];
- if (doRetain) {
- [window setDocEdited:NO];
- [window setTitleAsFilename:name];
- if (!realName && realpath(name, realPath)) {
- realName = NXCopyStringBufferFromZone(realPath, [self zone]);
- }
- }
- return self;
- }
-
- - revertToSaved:sender {
- int response;
- NXStringTable *stringTable;
- const char *shortName;
- GraphDoc *newDoc;
-
- if ([window isDocEdited] && name) {
- stringTable = [[NXApp delegate] stringTable];
- shortName = strrchr(name, '/') + 1;
- response = NXRunAlertPanel([stringTable valueForStringKey:"revert alert title"],
- [stringTable valueForStringKey:"revert alert message"],
- [stringTable valueForStringKey:"revert button"],
- [stringTable valueForStringKey:"cancel button"],
- NULL, shortName);
- if (response == NX_ALERTDEFAULT) {
- [window setDelegate:nil];
- [window close];
- [NXApp delayedFree:self];
- [linkMgr documentClosed];
- [linkMgr free];
- linkMgr = nil;
- newDoc = [[GraphDoc allocFromZone:[self zone]] initFromFile:name];
- if (!newDoc) {
- NXRunAlertPanel(
- [stringTable valueForStringKey:"open alert title"],
- [stringTable valueForStringKey:"open alert message"],
- [stringTable valueForStringKey:"ok button"],
- NULL, NULL, name);
- }
- }
- }
- return self;
- }
-
- /* switches the colors of the graph and its background. */
- - invertColors:sender {
- float bgGray;
-
- bgGray = [graph backgroundGray];
- [graph setBackgroundGray:[graph lineGray]];
- [graph setLineGray:bgGray];
- [graph display]; /* draw the new graph */
- return self;
- }
-
- /* Called when the window is closing. We free the GraphDoc. */
- - windowWillClose:sender {
- int response;
- NXStringTable *stringTable;
- const char *shortName;
-
- if ([window isDocEdited]) {
- stringTable = [[NXApp delegate] stringTable];
- if (name) {
- shortName = strrchr(name, '/') + 1;
- } else {
- shortName = [stringTable valueForStringKey:"untitled doc"];
- }
- response = NXRunAlertPanel([stringTable valueForStringKey:"close alert title"],
- [stringTable valueForStringKey:"close alert message"],
- [stringTable valueForStringKey:"save button"],
- [stringTable valueForStringKey:"dont save button"],
- [stringTable valueForStringKey:"cancel button"],
- shortName);
- if (response != NX_ALERTDEFAULT && response != NX_ALERTALTERNATE) {
- return nil;
- } else {
- if (response == NX_ALERTDEFAULT && ![self save:sender])
- return nil;
- }
- }
- [NXApp delayedFree:self];
- [linkMgr documentClosed];
- return self; /* says its OK to close */
- }
-
- /* Called whenever something changes to update the view of the graph. */
- - _updateGraph:(BOOL)finalChange {
- [self _updateGraphVals]; /* update the values that we graph */
- if ([autoScale intValue])
- [graph scaleToFit];
- [graph display]; /* draw the new graph */
- if (finalChange)
- [self _updateLinks];
- return self;
- }
-
- /* Called whenever something changes to update the values that we graph. */
- - _updateGraphVals {
- float *xVals, *yVals;
- int numXVals, numYVals;
- float minX, minY, maxX, maxY;
-
- /* extract the x values from the Expression */
- [expr varVector:"x" vector:&xVals numVals:&numXVals];
- [expr var:"x" min:&minX max:&maxX];
-
- /* extract the y values from the Expression. This may cause a recalc. */
- [expr resultsVector:&yVals numVals:&numYVals];
- [expr resultsMin:&minY max:&maxY];
-
- /* set the points of the graph */
- [graph setPoints:[expr resolution] x:xVals y:yVals
- minX:minX minY:minY maxX:maxX maxY:maxY];
- return self;
- }
-
- /*
- * Writes a document to the given file using typedstreams. We're very careful
- * about catching exceptions here so we can tell the user if his file didn't
- * get written out successfully.
- */
- - _write:(const char *)filename {
- NXTypedStream *ts;
- NXRect graphViewRect;
- const char *stringVal;
- int intVal;
- float floatVal;
- char charVal;
- int rows, cols;
- int numVars;
- int i;
- NXStringTable *stringTable;
- volatile BOOL hadAnError = NO;
- char buffer[MAXPATHLEN+1];
-
- /* cons up the name of the backup file and remove it */
- strcpy(buffer, filename);
- strcat(buffer, "~");
- removeFile(buffer);
-
- /* move the existing file to the backup */
- rename(filename, buffer);
-
- /* create the new document as a file package (doc wrapper) */
- strcpy(buffer, filename);
- strcat(buffer, DOC_NAME);
- mkdir(filename, 0777);
- ts = NXOpenTypedStreamForFile(buffer, NX_WRITEONLY);
- if (ts) {
- NX_DURING
- intVal = 1; /* version of this write's data */
- NXWriteType(ts, "i", &intVal);
- stringVal = [expr text];
- NXWriteType(ts, "*", &stringVal);
- [graph getBounds:&graphViewRect];
- NXWriteRect(ts, &graphViewRect);
- [variableSliders getNumRows:&rows numCols:&cols];
- numVars = 0;
- for (i = 0; i < rows; i++) {
- if ([[variableSliders cellAt:i :0] isEnabled])
- numVars++;
- }
- NXWriteType(ts, "i", &numVars);
- for (i = 0; i < rows; i++)
- if ([[variableSliders cellAt:i :0] isEnabled]) {
- charVal = MIN_CONST + i;
- NXWriteType(ts, "c", &charVal);
- writeCellFloat([variableSliders cellAt:i :0], ts);
- }
- intVal = [expr resolution];
- NXWriteType(ts, "i", &intVal);
- writeCellFloat(minXSlider, ts);
- writeCellFloat(maxXSlider, ts);
- floatVal = [graph backgroundGray];
- NXWriteType(ts, "f", &floatVal);
- floatVal = [graph lineGray];
- NXWriteType(ts, "f", &floatVal);
- intVal = [autoScale intValue];
- NXWriteType(ts, "i", &intVal);
- NXCloseTypedStream(ts);
- NX_HANDLER
- hadAnError = YES;
- NX_DURING
- NXCloseTypedStream(ts);
- NX_HANDLER
- /* ignore any error at this point */
- NX_ENDHANDLER
- NX_ENDHANDLER
- } else
- hadAnError = YES;
- if (hadAnError) {
- stringTable = [[NXApp delegate] stringTable];
- NXRunAlertPanel([stringTable valueForStringKey:"save alert title"],
- [stringTable valueForStringKey:"save alert message"],
- [stringTable valueForStringKey:"ok button"],
- NULL, NULL, filename);
- return nil;
- } else
- return self;
- }
-
- /* reads a document from the given file using typedstreams */
- - _read:(const char *)filename {
- NXTypedStream *ts;
- NXRect graphViewRect;
- char *stringVal;
- int i;
- char varName;
- float floatVal1, floatVal2;
- int intVal;
- int numVars;
- BOOL parseError;
-
- ts = openDocStream(filename, NX_READONLY);
- if (ts) {
- NX_DURING
- NXReadType(ts, "i", &intVal); /* read file version */
- NX_ASSERT(intVal == 1, "Unknown file version in -read");
- NXReadType(ts, "*", &stringVal);
- [equation setStringValue:stringVal];
- parseError = ![expr parse:stringVal];
- free(stringVal);
- NX_ASSERT(!parseError, "Bad expression stored in data file");
- if (parseError)
- NX_VALRETURN(nil);
- NXReadRect(ts, &graphViewRect);
- [graph setDrawSize:graphViewRect.size.width
- :graphViewRect.size.height];
- [graph setDrawOrigin:graphViewRect.origin.x
- :graphViewRect.origin.y];
- NXReadType(ts, "i", &numVars);
- for (i = 0; i < numVars; i++) {
- NXReadType(ts, "c", &varName);
- NXReadType(ts, "f", &floatVal1);
- [self _initVariable:varName - MIN_CONST enable:YES
- value:floatVal1];
- }
- NXReadType(ts, "i", &intVal); /* read resolution */
- [resolutionText setIntValue:intVal];
- [self _setSliderCell:resolutionSlider value:intVal];
- [expr setResolution:intVal];
- NXReadType(ts, "f", &floatVal1); /* read minX */
- NXReadType(ts, "f", &floatVal2); /* read maxX */
- [minXText setFloatValue:floatVal1];
- [self _setSliderCell:minXSlider value:floatVal1];
- [maxXText setFloatValue:floatVal2];
- [self _setSliderCell:maxXSlider value:floatVal2];
- setXRange(expr, floatVal1, floatVal2);
- NXReadType(ts, "f", &floatVal1); /* read background gray */
- [graph setBackgroundGray:floatVal1];
- NXReadType(ts, "f", &floatVal1); /* read line gray */
- [graph setLineGray:floatVal1];
- NXReadType(ts, "i", &intVal); /* read auto scale */
- [autoScale setIntValue:intVal];
- NXCloseTypedStream(ts);
- /* must use NX_VALRETURN to return from inside a NX_DURING */
- NX_VALRETURN(self);
- NX_HANDLER
- NX_DURING
- NXCloseTypedStream(ts);
- NX_HANDLER
- /* ignore any error at this point */
- NX_ENDHANDLER
- return nil;
- NX_ENDHANDLER
- } else
- return nil;
- }
-
- /*
- * Inits a variable to either be on or off. Used when we discover a new
- * set of variables after a parse, or to set up the variables from a document
- * that we read from a file.
- */
- - (void)_initVariable:(int)index enable:(BOOL)flag value:(float)val {
- TextFieldCell *text;
- SliderCell *slider;
- char varName[2];
-
- /* set the initial value in the Expression */
- if (flag) {
- varName[0] = MIN_CONST + index;
- varName[1] = '\0';
- [expr setVar:varName value:val];
- }
-
- /* if the enabled status is changing... */
- if (flag != [[variableSliders cellAt:index :0] isEnabled])
- if (flag) {
-
- /* enable all controls associated with the variable */
- [[variableLabels cellAt:index :0] setTextGray:NX_BLACK];
- slider = [variableSliders cellAt:index :0];
- [slider setEnabled:YES];
- [self _setSliderCell:slider value:val];
- text = [variableTexts cellAt:index :0];
- [text setEnabled:YES];
- [text setFloatValue:val];
- } else if (!flag) {
-
- /* disable all controls associated with the variable */
- [[variableLabels cellAt:index :0] setTextGray:NX_DKGRAY];
- slider = [variableSliders cellAt:index :0];
- [slider setFloatValue:[slider minValue]];
- [slider setEnabled:NO];
- text = [variableTexts cellAt:index :0];
- [text setStringValue:NULL];
- [text setEnabled:NO];
- }
- }
-
- /* sets a slider to a value and adjust min/max */
- - (void)_setSliderCell:(SliderCell *)slider value:(float)val {
- if (val < [slider minValue]) {
- [window disableDisplay]; /* so slider won't flash to new location */
- [slider setMinValue:val];
- [window reenableDisplay];
- } else if (val > [slider maxValue]) {
- [window disableDisplay]; /* so slider won't flash to new location */
- [slider setMaxValue:val];
- [window reenableDisplay];
- }
- [slider setFloatValue:val];
- }
-
- @end
-
- /* little utility proc to write out the float value of a control */
- static void writeCellFloat(Cell *obj, NXTypedStream *ts) {
- float val;
-
- val = [obj floatValue];
- NXWriteType(ts, "f", &val);
- }
-
-
- /*
- * A little utility proc to set the range of the x variable in the Expression.
- * It ensures that the min value isn't greater than the max value.
- */
- static void setXRange(Expression *expr, float min, float max) {
- [expr setVar:"x" min:MIN(min, max) max:MAX(min, max)];
- }
-
- /* Opens a stream on the document regardless of whether its a doc wrapper. */
- static NXTypedStream *openDocStream(const char *filename, int mode) {
- NXTypedStream *ts = NULL;
- struct stat statInfo;
- char buffer[MAXPATHLEN+1];
-
- if (stat(filename, &statInfo) == 0) {
- if (statInfo.st_mode & S_IFDIR) {
- strcpy(buffer, filename);
- strcat(buffer, DOC_NAME);
- ts = NXOpenTypedStreamForFile(buffer, mode);
- } else {
- ts = NXOpenTypedStreamForFile(filename, mode);
- }
- }
- return ts;
- }
-
- /* removes a directory, removing anything inside it. Does not recurse */
- static int removeFile(const char *file) {
- DIR *dirp;
- struct stat st;
- struct direct *dp;
- char *leaf = NULL;
- char path[MAXPATHLEN+1];
-
- if (!stat(file, &st)) {
- if ((st.st_mode & S_IFMT) == S_IFDIR) {
- dirp = opendir(file);
- for (dp = readdir(dirp); dp != NULL; dp = readdir(dirp)) {
- if (strcmp(dp->d_name, ".") && strcmp(dp->d_name, "..")) {
- if (!leaf) {
- strcpy(path, file);
- strcat(path, "/");
- leaf = path + strlen(path);
- }
- strcpy(leaf, dp->d_name);
- if (unlink(path)) {
- closedir(dirp);
- return -1;
- }
- }
- }
- return rmdir(file);
- } else {
- return unlink(file);
- }
- }
-
- return -1;
- }
-