|
|
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: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.