|
|
1.1 ! root 1: ! 2: /* ! 3: Graph3DDoc.m ! 4: ! 5: The Graph3DDoc represents an open 3D Graph document. Graph3DDoc receives ! 6: messages from the user interface objects and uses three Expression objects ! 7: to do calculations, and a RotatorCamera and a PointMesh to display the ! 8: results. ! 9: ! 10: The Graph3DDoc class has one slightly odd external dependency. It requires ! 11: that the delegate of NXApp be able to provide it with a NXStringTable ! 12: (via the stringTable method) that it can use to look up strings that ! 13: are presented to the user. In this application, the delegate of NXApp is ! 14: always an instance of the GraphApp class. ! 15: ! 16: If this were a larger program, the NXStringTable needed by this document ! 17: class would probably be in its own nib section, which would be loaded once ! 18: the first time a Graph3DDoc is created, and then shared. ! 19: ! 20: You may freely copy, distribute, and reuse the code in this example. ! 21: NeXT disclaims any warranty of any kind, expressed or implied, as to its ! 22: fitness for any particular use. ! 23: */ ! 24: ! 25: #import "Graph.h" ! 26: ! 27: /* declare methods static to this class */ ! 28: @interface Graph3DDoc(Private) ! 29: - _updateShape; ! 30: - _updateGraph:(BOOL)finalChange; ! 31: - (void)_equationChanged; ! 32: - (BOOL)_parseEquationFrom:(const char *)str using:(Expression *)expr enableVariables:(BOOL *)enableVars; ! 33: - _uOrVRangeSliderChanged:sender varName:(int)var; ! 34: - _uOrVRangeTextChanged:sender varName:(int)var; ! 35: - _write:(const char *)filename; ! 36: - _read:(const char *)filename; ! 37: - (void)_initVariable:(int)index enable:(BOOL)flag value:(float)val; ! 38: - _doSaveWithNewName:(BOOL)doNewName retainName:(BOOL)doRetain; ! 39: - _writeGraphToPasteboard:(Pasteboard *)pb types:(const char *const *)types num:(int)numTypes; ! 40: - (void)_setUVRangeOf:(int)var; ! 41: - (void)_setVariable:(int)index toValue:(float)val; ! 42: - (void)_setUpLighting; ! 43: - (void)_setSliderCell:(SliderCell *)slider value:(float)val minLabel:(Cell *)minLabel maxLabel:(Cell *)maxLabel; ! 44: ! 45: @end ! 46: ! 47: @implementation Graph3DDoc ! 48: ! 49: /* variable names must be between A and H */ ! 50: #define MIN_CONST 'A' ! 51: #define MAX_CONST 'H' ! 52: ! 53: /* indices into instance variable arrays */ ! 54: #define X 0 ! 55: #define Y 1 ! 56: #define Z 2 ! 57: #define U 0 ! 58: #define V 1 ! 59: ! 60: /* default resolution of a new graph */ ! 61: #define DEFAULT_RES 12 ! 62: ! 63: /* name used for the real document inside a doc wrapper */ ! 64: #define DOC_NAME "/Graph3DDoc.xyzgraph" ! 65: ! 66: static void writeCellFloat(Cell *obj, NXTypedStream *ts); ! 67: static NXTypedStream *openDocStream(const char *filename, int mode); ! 68: static int removeFile(const char *file); ! 69: ! 70: - init { ! 71: /* the default initialization is to just open a new document */ ! 72: return [self initFromFile:NULL]; ! 73: } ! 74: ! 75: /* ! 76: * Opens a document. If file is NULL, we open an untitiled document. We also ! 77: * create a NXDataLinkManager and hook ourselves up as its delegate for doing ! 78: * Object Links. ! 79: */ ! 80: - initFromFile:(const char *)file { ! 81: int rows, cols; ! 82: int i; ! 83: TextField *aCell; ! 84: int xyz; ! 85: RtPoint axis; ! 86: char realPath[MAXPATHLEN+1]; ! 87: ! 88: [super init]; ! 89: ! 90: /* load the UI from the nib section and set attributes not available in IB */ ! 91: [NXApp loadNibSection:"Graph3DDoc.nib" owner:self withNames:NO fromZone:[self zone]]; ! 92: [variableTexts getNumRows:&rows numCols:&cols]; ! 93: for (i = rows; i--; ) { ! 94: aCell = [variableTexts cellAt:i :0]; ! 95: [aCell setFloatingPointFormat:YES left:3 right:2]; ! 96: [aCell setEnabled:NO]; ! 97: } ! 98: [minUVText[U] setFloatingPointFormat:YES left:3 right:2]; ! 99: [maxUVText[U] setFloatingPointFormat:YES left:3 right:2]; ! 100: [minUVText[V] setFloatingPointFormat:YES left:3 right:2]; ! 101: [maxUVText[V] setFloatingPointFormat:YES left:3 right:2]; ! 102: [variableSliders setAutosizeCells:YES]; ! 103: [resolutionText setEntryType:NX_POSINTTYPE]; ! 104: [camera setFieldOfViewByAngle:90.0]; ! 105: [self _setUpLighting]; ! 106: ! 107: /* create Expression objects we will use to evaluate expressions */ ! 108: for (xyz = X; xyz <= Z; xyz++) { ! 109: expr[xyz] = [[Expression allocFromZone:[self zone]] init]; ! 110: [expr[xyz] setDimensions:2]; /* we eval u * v points */ ! 111: } ! 112: ! 113: /* create and position the shape that we will use to show our data */ ! 114: [[camera setWorldShape:[[PointMesh alloc] init]] free]; ! 115: axis[1] = axis[2] = 0; ! 116: axis[0] = 1.0; ! 117: [[camera worldShape] rotateAngle:-90.0 axis:axis]; ! 118: ! 119: if (file) { ! 120: /* read existing document */ ! 121: if ([self _read:file]) { ! 122: name = NXCopyStringBufferFromZone(file, [self zone]); ! 123: if (realpath(name, realPath)) ! 124: realName = NXCopyStringBufferFromZone(realPath, [self zone]); ! 125: [window setTitleAsFilename:name]; ! 126: [self _updateShape]; ! 127: linkMgr = [[NXDataLinkManager allocFromZone:[self zone]] initWithDelegate:self fromFile:file]; ! 128: } else { ! 129: [self free]; /* couldn't load file */ ! 130: return nil; ! 131: } ! 132: } else { ! 133: /* ! 134: * Create a new document. We initialize it with a trivial expression ! 135: * because that was easier than allowing the state where there is no ! 136: * current expression. ! 137: */ ! 138: RtPoint from = {12.0, 8.0, 10.0}, to = {0.0, 0.0, 0.0}; ! 139: ! 140: [camera setEyeAt:from toward:to roll:0.0]; ! 141: /* set initial shape color to non-white on a color display */ ! 142: if ([camera shouldDrawColor]) ! 143: [[camera worldShape] setColor:NXConvertRGBToColor(51.0/255.0, 153.0/255.0, 116.0/255.0)]; ! 144: ! 145: [window setTitleAsFilename:[[[NXApp delegate] stringTable] valueForStringKey:"untitled doc"]]; ! 146: [resolutionSlider setIntValue:DEFAULT_RES]; ! 147: [resolutionText setIntValue:DEFAULT_RES]; ! 148: [equation[X] setStringValue:"u"]; ! 149: [equation[Y] setStringValue:"v"]; ! 150: [equation[Z] setStringValue:"A * (u^2 + v^2) + C"]; ! 151: for (xyz = X; xyz <= Z; xyz++) { ! 152: [expr[xyz] setResolution:DEFAULT_RES]; ! 153: } ! 154: [self _equationChanged]; ! 155: [self _setUVRangeOf:U]; ! 156: [self _setUVRangeOf:V]; ! 157: [self _updateShape]; ! 158: linkMgr = [[NXDataLinkManager allocFromZone:[self zone]] initWithDelegate:self]; ! 159: } ! 160: ! 161: [[NXApp delegate] showThreeDPanel:self]; ! 162: [window makeKeyAndOrderFront:self]; ! 163: return self; ! 164: } ! 165: ! 166: - free { ! 167: int xyz; ! 168: ! 169: for (xyz = X; xyz <= Z; xyz++) ! 170: [expr[xyz] free]; ! 171: NXZoneFree([self zone], name); ! 172: NXZoneFree([self zone], realName); ! 173: [linkMgr free]; ! 174: return [super free]; ! 175: } ! 176: ! 177: - (const char *)filename { ! 178: return name; ! 179: } ! 180: ! 181: - (const char *)realFilename { ! 182: return realName; ! 183: } ! 184: ! 185: /* ! 186: * This method is called whenever our window becomes the app's main window. ! 187: * When this happens we get the 3D Panel and set its camera to our camera, so ! 188: * the panel reflects the attributes of our window. ! 189: */ ! 190: - windowDidBecomeMain:sender { ! 191: [[[NXApp delegate] threeDPanel] setCamera:camera]; ! 192: return self; ! 193: } ! 194: ! 195: #define BUF_MAX 256 ! 196: ! 197: /* ! 198: * This method is called when the user has just finished typing in an ! 199: * expression. This is where we validate that it is a legal expression. ! 200: * If we dont like the expression, we return YES, which tells the text object ! 201: * not to accept the user's entry. ! 202: */ ! 203: - (BOOL)textWillEnd:textObject { ! 204: BOOL parseError; ! 205: BOOL enableVars[MAX_CONST - MIN_CONST + 1]; ! 206: NXStringTable *stringTable; ! 207: Expression *tempExpr = [[Expression allocFromZone:[self zone]] init]; ! 208: char buffer[BUF_MAX]; ! 209: char *newText; ! 210: int length; ! 211: ! 212: /* get the text out of the text object where the expression was typed */ ! 213: length = [textObject byteLength] + 1; ! 214: if (length > BUF_MAX) ! 215: newText = NXZoneMalloc(NXDefaultMallocZone(), sizeof(char) * length); ! 216: else ! 217: newText = buffer; ! 218: [textObject getSubstring:newText start:0 length:length]; ! 219: ! 220: if (*newText) { ! 221: parseError = [self _parseEquationFrom:newText using:tempExpr enableVariables:enableVars]; ! 222: if (!parseError) { ! 223: /* parse the new equation into the real expressions */ ! 224: [self _equationChanged]; ! 225: ! 226: /* update the shape object with current results and display it */ ! 227: [self _updateGraph:YES]; ! 228: } ! 229: } else ! 230: parseError = YES; ! 231: ! 232: if (parseError) { ! 233: stringTable = [[NXApp delegate] stringTable]; ! 234: NXRunAlertPanel([stringTable valueForStringKey:"parse alert title"], ! 235: [stringTable valueForStringKey:"3D parse alert message"], ! 236: [stringTable valueForStringKey:"ok button"], ! 237: NULL, NULL); ! 238: } ! 239: [tempExpr free]; ! 240: if (length > BUF_MAX) ! 241: NXZoneFree(NXDefaultMallocZone(), newText); ! 242: return parseError; ! 243: } ! 244: ! 245: /* This method does most of the work of parsing a new expression. */ ! 246: - (void)_equationChanged { ! 247: BOOL enableVars[MAX_CONST - MIN_CONST + 1]; ! 248: int i; ! 249: int xyz; ! 250: float newVarVal; ! 251: ! 252: for (i = 0; i <= MAX_CONST - MIN_CONST; i++) ! 253: enableVars[i] = NO; ! 254: for (xyz = X; xyz <= Z; xyz++) { ! 255: (void)[self _parseEquationFrom:[equation[xyz] stringValue] using:expr[xyz] enableVariables:enableVars]; ! 256: } ! 257: ! 258: /* turn on the UI for constants used in these expressions */ ! 259: for (i = 0; i <= MAX_CONST - MIN_CONST; i++) { ! 260: if ([[variableSliders cellAt:i :0] isEnabled]) ! 261: newVarVal = [[variableSliders cellAt:i :0] floatValue]; ! 262: else ! 263: newVarVal = 1.0; ! 264: [self _initVariable:i enable:enableVars[i] value:newVarVal]; ! 265: } ! 266: ! 267: /* update the range of the independent variables */ ! 268: [self _setUVRangeOf:U]; ! 269: [self _setUVRangeOf:V]; ! 270: } ! 271: ! 272: /* ! 273: * This method is called after the user typed in a new expression. The ! 274: * expression has already been verified in our textWillEnd: method. ! 275: */ ! 276: - equationChanged:sender { ! 277: /* reselect the equation field so the user can type another */ ! 278: [sender selectCell:[sender selectedCell]]; ! 279: return self; ! 280: } ! 281: ! 282: /* called when either the minu, maxu, minv or maxv slider changes */ ! 283: - _uOrVRangeSliderChanged:sender varName:(int)var { ! 284: TextFieldCell *text; ! 285: SliderCell *slider; ! 286: NXEvent *event; ! 287: ! 288: /* update the associated text field */ ! 289: slider = [sender selectedCell]; ! 290: if (slider == maxUVSlider[var]) ! 291: text = maxUVText[var]; ! 292: else if (slider == minUVSlider[var]) ! 293: text = minUVText[var]; ! 294: else ! 295: text = nil; ! 296: NX_ASSERT(text, "Funny sender of _uOrVRangeSliderChanged: message"); ! 297: [text setFloatValue:[slider floatValue]]; ! 298: ! 299: /* update the u or v range in the Expression and display the new graph */ ! 300: [self _setUVRangeOf:var]; ! 301: event = [NXApp currentEvent]; ! 302: [self _updateGraph:event->type == NX_LMOUSEUP]; ! 303: return self; ! 304: } ! 305: ! 306: /* called when either the minu or maxu slider changes */ ! 307: - uRangeSliderChanged:sender { ! 308: return [self _uOrVRangeSliderChanged:sender varName:U]; ! 309: } ! 310: ! 311: /* called when either the minv or maxv slider changes */ ! 312: - vRangeSliderChanged:sender { ! 313: return [self _uOrVRangeSliderChanged:sender varName:V]; ! 314: } ! 315: ! 316: /* called when either the minu, maxu, minv or maxv text changes */ ! 317: - _uOrVRangeTextChanged:sender varName:(int)var { ! 318: TextFieldCell *text; ! 319: SliderCell *slider; ! 320: float val; ! 321: ! 322: /* ! 323: * update the associated slider. If the value typed is outside the current ! 324: * range of the slider, we extend its range. ! 325: */ ! 326: text = [sender selectedCell]; ! 327: if (text == maxUVText[var]) ! 328: slider = maxUVSlider[var]; ! 329: else if (text == minUVText[var]) ! 330: slider = minUVSlider[var]; ! 331: else ! 332: slider = nil; ! 333: NX_ASSERT(slider, "Funny sender of _uOrVRangeTextChanged: message"); ! 334: val = [text floatValue]; ! 335: [self _setSliderCell:slider value:val minLabel:nil maxLabel:nil]; ! 336: ! 337: /* update the u or v range in the Expression and display the new graph */ ! 338: [self _setUVRangeOf:var]; ! 339: [self _updateGraph:YES]; ! 340: [sender selectText:self]; ! 341: return self; ! 342: } ! 343: ! 344: /* called when either the minu or maxu text changes */ ! 345: - uRangeTextChanged:sender { ! 346: return [self _uOrVRangeTextChanged:sender varName:U]; ! 347: } ! 348: ! 349: /* called when either the minv or maxv text changes */ ! 350: - vRangeTextChanged:sender { ! 351: return [self _uOrVRangeTextChanged:sender varName:V]; ! 352: } ! 353: ! 354: /* called when one of the variables' sliders changes */ ! 355: - variableSliderChanged:sender { ! 356: int index; ! 357: float val; ! 358: NXEvent *event; ! 359: ! 360: /* update the associated text field */ ! 361: index = [variableSliders selectedRow]; ! 362: val = [[variableSliders selectedCell] floatValue]; ! 363: [[variableTexts cellAt:index :0] setFloatValue:val]; ! 364: [self _setVariable:index toValue:val]; ! 365: event = [NXApp currentEvent]; ! 366: [self _updateGraph:event->type == NX_LMOUSEUP]; ! 367: return self; ! 368: } ! 369: ! 370: /* called when one of the variables' text fields changes */ ! 371: - variableTextChanged:sender { ! 372: int index; ! 373: float val; ! 374: TextFieldCell *text; ! 375: SliderCell *slider; ! 376: ! 377: /* update the associated slider */ ! 378: text = [variableTexts selectedCell]; ! 379: index = [variableTexts selectedRow]; ! 380: slider = [variableSliders cellAt:index :0]; ! 381: val = [text floatValue]; ! 382: [self _setSliderCell:slider value:val ! 383: minLabel:[variableMinLabels cellAt:index :0] ! 384: maxLabel:[variableMaxLabels cellAt:index :0]]; ! 385: ! 386: [self _setVariable:index toValue:val]; ! 387: [self _updateGraph:YES]; ! 388: [sender selectCell:text]; ! 389: return self; ! 390: } ! 391: ! 392: /* The maximum allowable resolution. ~10K polygons */ ! 393: #define MAX_RES 100 ! 394: ! 395: /* called when either the resolution slider or text fields changes */ ! 396: - resolutionChanged:sender { ! 397: Cell *senderCell, *otherCell; ! 398: int iVal; ! 399: float fVal; ! 400: int xyz; ! 401: NXEvent *event; ! 402: ! 403: if ([[sender cellAt:0 :0] isKindOfClassNamed:"SliderCell"]) { ! 404: senderCell = resolutionSlider; ! 405: otherCell = resolutionText; ! 406: } else { ! 407: senderCell = resolutionText; ! 408: otherCell = resolutionSlider; ! 409: } ! 410: ! 411: fVal = [senderCell floatValue]; ! 412: iVal = rint(fVal); ! 413: if (iVal > MAX_RES) ! 414: iVal = MAX_RES; ! 415: [otherCell setIntValue:iVal]; ! 416: ! 417: /* update the Expression's resolution and display the new graph */ ! 418: for (xyz = X; xyz <= Z; xyz++) ! 419: [expr[xyz] setResolution:iVal]; ! 420: event = [NXApp currentEvent]; ! 421: [self _updateGraph:event->type != NX_LMOUSEDRAGGED]; ! 422: if (senderCell == resolutionText) ! 423: [sender selectText:self]; ! 424: return self; ! 425: } ! 426: ! 427: /* ! 428: * Copies the current view of the graph into the Pasteboard as Renderman code. ! 429: * This is sent from the Copy Graph menu item. ! 430: */ ! 431: - copyGraph:sender { ! 432: Pasteboard *pb; ! 433: const char *types[2]; ! 434: NXDataLink *link; ! 435: ! 436: pb = [Pasteboard new]; ! 437: types[0] = N3DRIBPboardType; ! 438: types[1] = NXDataLinkPboardType; ! 439: [self _writeGraphToPasteboard:pb types:types num:2]; ! 440: link = [[NXDataLink alloc] initLinkedToSourceSelection:[NXSelection allSelection] managedBy:linkMgr supportingTypes:&N3DRIBPboardType count:1]; ! 441: [link writeToPasteboard:pb]; ! 442: [link free]; ! 443: return self; ! 444: } ! 445: ! 446: /* ! 447: * Copies the current view of the graph into the Pasteboard as RIB. ! 448: * This is used by the Copy Graph menu item and to support Object Links. ! 449: * ! 450: * Types must contain N3DRIBPboardType. ! 451: */ ! 452: - _writeGraphToPasteboard:(Pasteboard *)pb types:(const char *const *)types num:(int)numTypes { ! 453: NXStream *st; ! 454: char *data; ! 455: int dataLen, maxDataLen; ! 456: ! 457: /* Open a stream on memory where we will collect the PostScript */ ! 458: st = NXOpenMemory(NULL, 0, NX_WRITEONLY); ! 459: ! 460: /* Tell the Pasteboard we're going to copy RIB */ ! 461: [pb declareTypes:types num:numTypes owner:nil]; ! 462: ! 463: /* writes the RIB for the whole graph into the stream */ ! 464: [camera copyRIBCode:st]; ! 465: ! 466: /* get the buffered up PostScript out of the stream */ ! 467: NXGetMemoryBuffer(st, &data, &dataLen, &maxDataLen); ! 468: ! 469: /* put the buffer in the Pasteboard, free the stream (and the buffer) */ ! 470: [pb writeType:N3DRIBPboardType data:data length:dataLen]; ! 471: NXCloseMemory(st, NX_FREEBUFFER); ! 472: return self; ! 473: } ! 474: ! 475: /*** Object Links support - methods called by the DataLinkManager ***/ ! 476: ! 477: /* called by the DataLinkManager when new data is needed for a link */ ! 478: - copyToPasteboard:(Pasteboard *)pboard at:(NXSelection *)selection cheapCopyAllowed:(BOOL)flag { ! 479: NX_ASSERT([selection isEqual:[NXSelection allSelection]] || [selection isEqual:[NXSelection currentSelection]], "Funny selection passed to copyToPasteboard:at:"); ! 480: [self _writeGraphToPasteboard:pboard types:&N3DRIBPboardType num:1]; ! 481: return self; ! 482: } ! 483: ! 484: /* returns the window for the given selection */ ! 485: - windowForSelection:(NXSelection *)selection { ! 486: return window; /* all our sels are always in our one window */ ! 487: } ! 488: ! 489: /* ! 490: * We support continuously updating links by tracking changes to links to ! 491: * us from open documents. This is easy for Graph because all links can ! 492: * only be to the whole graph, so any change in the graph is a change relevant ! 493: * to any link. ! 494: */ ! 495: - (BOOL)dataLinkManagerTracksLinksIndividually:(NXDataLinkManager *)sender { ! 496: return YES; ! 497: } ! 498: ! 499: /* ! 500: * Sent when we should start tracking a link. We just keep all links we're ! 501: * tracking in a list, and send them a message whenever the graph is changed. ! 502: */ ! 503: - dataLinkManager:(NXDataLinkManager *)sender startTrackingLink:(NXDataLink *)link { ! 504: if (!linksTracked) ! 505: linksTracked = [[List allocFromZone:[self zone]] init]; ! 506: [linksTracked addObject:link]; ! 507: return self; ! 508: } ! 509: ! 510: /* Sent when we can forget a links we're tracking */ ! 511: - dataLinkManager:(NXDataLinkManager *)sender stopTrackingLink:(NXDataLink *)link { ! 512: [linksTracked removeObject:link]; ! 513: if ([linksTracked count] == 0) { ! 514: [linksTracked free]; ! 515: linksTracked = nil; ! 516: } ! 517: return self; ! 518: } ! 519: ! 520: /* ! 521: * Called by other methods of Graph3DDoc whenever we make a change to the ! 522: * document. We tell the DataLinkManager about the change, and also notify ! 523: * any links we are tracking. ! 524: */ ! 525: - docChanged { ! 526: [linkMgr documentEdited]; ! 527: [linksTracked makeObjectsPerform:@selector(sourceEdited)]; ! 528: [window setDocEdited:YES]; ! 529: return self; ! 530: } ! 531: ! 532: /* called by ThreeDPanel when it changes something about the document */ ! 533: - threeDPanelDidChangeDoc:(ThreeDPanel *)sender { ! 534: [self docChanged]; ! 535: return self; ! 536: } ! 537: ! 538: ! 539: /* called when Save, Save As, or Save To is picked from the menu */ ! 540: - save:sender { return [self _doSaveWithNewName:NO retainName:YES]; } ! 541: - saveAs:sender { return [self _doSaveWithNewName:YES retainName:YES]; } ! 542: - saveTo:sender { return [self _doSaveWithNewName:YES retainName:NO]; } ! 543: ! 544: /* ! 545: * All varieties of save go through this routine. It covers all the cases ! 546: * of running the Save Panel and retaining the name chosen. ! 547: */ ! 548: - _doSaveWithNewName:(BOOL)doNewName retainName:(BOOL)doRetain { ! 549: SavePanel *savePanel; ! 550: const char *saveName; /* filename to save into */ ! 551: NXZone *zone; ! 552: BOOL previouslySaved = (name != NULL); ! 553: char realPath[MAXPATHLEN+1]; ! 554: ! 555: /* ! 556: * If the file is untitled or we are saving under a different name, ! 557: * run the save panel to get a new name. ! 558: */ ! 559: zone = [self zone]; ! 560: if (!name || doNewName) { ! 561: savePanel = [SavePanel new]; ! 562: [savePanel setRequiredFileType:"xyzgraph"]; ! 563: if ([savePanel runModalForDirectory:NULL file:name]) { ! 564: /* if we want to keep this name, replace any old name */ ! 565: if (doRetain) { ! 566: NXZoneFree(zone, name); ! 567: NXZoneFree(zone, realName); ! 568: realName = NULL; ! 569: name = NXCopyStringBufferFromZone([savePanel filename], zone); ! 570: } ! 571: saveName = [savePanel filename]; ! 572: } else ! 573: return nil; /* user canceled */ ! 574: } else ! 575: /* if we didn't run the Save Panel, save using the existing name */ ! 576: saveName = name; ! 577: [self _write:saveName]; ! 578: if (!doRetain) ! 579: [linkMgr documentSavedTo:saveName]; ! 580: else if (!previouslySaved || doNewName) ! 581: [linkMgr documentSavedAs:saveName]; ! 582: else ! 583: [linkMgr documentSaved]; ! 584: if (doRetain) { ! 585: [window setDocEdited:NO]; ! 586: [window setTitleAsFilename:name]; ! 587: if (!realName && realpath(name, realPath)) { ! 588: realName = NXCopyStringBufferFromZone(realPath, [self zone]); ! 589: } ! 590: } ! 591: return self; ! 592: } ! 593: ! 594: - revertToSaved:sender { ! 595: int response; ! 596: NXStringTable *stringTable; ! 597: const char *shortName; ! 598: Graph3DDoc *newDoc; ! 599: ! 600: if ([window isDocEdited] && name) { ! 601: stringTable = [[NXApp delegate] stringTable]; ! 602: shortName = strrchr(name, '/') + 1; ! 603: response = NXRunAlertPanel([stringTable valueForStringKey:"revert alert title"], ! 604: [stringTable valueForStringKey:"revert alert message"], ! 605: [stringTable valueForStringKey:"revert button"], ! 606: [stringTable valueForStringKey:"cancel button"], ! 607: NULL, shortName); ! 608: if (response == NX_ALERTDEFAULT) { ! 609: [window setDelegate:nil]; ! 610: [window close]; ! 611: [NXApp delayedFree:self]; ! 612: [linkMgr documentClosed]; ! 613: [linkMgr free]; ! 614: linkMgr = nil; ! 615: newDoc = [[Graph3DDoc allocFromZone:[self zone]] initFromFile:name]; ! 616: if (!newDoc) { ! 617: NXRunAlertPanel( ! 618: [stringTable valueForStringKey:"open alert title"], ! 619: [stringTable valueForStringKey:"open alert message"], ! 620: [stringTable valueForStringKey:"ok button"], ! 621: NULL, NULL, name); ! 622: } ! 623: } ! 624: } ! 625: return self; ! 626: } ! 627: ! 628: /* Called when the window is closing. We free the Graph3DDoc. */ ! 629: - windowWillClose:sender { ! 630: int response; ! 631: NXStringTable *stringTable; ! 632: const char *shortName; ! 633: ! 634: if ([window isDocEdited]) { ! 635: stringTable = [[NXApp delegate] stringTable]; ! 636: if (name) { ! 637: shortName = strrchr(name, '/') + 1; ! 638: } else { ! 639: shortName = [stringTable valueForStringKey:"untitled doc"]; ! 640: } ! 641: response = NXRunAlertPanel([stringTable valueForStringKey:"close alert title"], ! 642: [stringTable valueForStringKey:"close alert message"], ! 643: [stringTable valueForStringKey:"save button"], ! 644: [stringTable valueForStringKey:"dont save button"], ! 645: [stringTable valueForStringKey:"cancel button"], ! 646: shortName); ! 647: if (response != NX_ALERTDEFAULT && response != NX_ALERTALTERNATE) { ! 648: return nil; ! 649: } else { ! 650: if (response == NX_ALERTDEFAULT && ![self save:sender]) ! 651: return nil; ! 652: } ! 653: } ! 654: [NXApp delayedFree:self]; ! 655: [linkMgr documentClosed]; ! 656: return self; /* says its OK to close */ ! 657: } ! 658: ! 659: /* Called whenever something changes to update the view of the graph. */ ! 660: - _updateGraph:(BOOL)finalChange { ! 661: [self _updateShape]; /* update the values that we graph */ ! 662: [camera display]; /* draw the new graph */ ! 663: if (finalChange) ! 664: [self docChanged]; ! 665: return self; ! 666: } ! 667: ! 668: /* Called whenever something changes to update the values that we graph. */ ! 669: - _updateShape { ! 670: float *vals[3]; ! 671: int numVals[3]; ! 672: float min[3], max[3]; ! 673: int xyz; ! 674: ! 675: /* extract the values from the Expressions. This may cause a recalc. */ ! 676: for (xyz = X; xyz <= Z; xyz++) { ! 677: [expr[xyz] resultsVector:&vals[xyz] numVals:&numVals[xyz]]; ! 678: [expr[xyz] resultsMin:&min[xyz] max:&max[xyz]]; ! 679: } ! 680: ! 681: /* set the points of the graph */ ! 682: [[camera worldShape] setPointsX:vals[X] y:vals[Y] z:vals[Z] ! 683: minX:min[X] minY:min[Y] minZ:min[Z] ! 684: maxX:max[X] maxY:max[Y] maxZ:max[Z] ! 685: numU:[expr[X] resolution] numV:[expr[X] resolution]]; ! 686: return self; ! 687: } ! 688: ! 689: /* parses and validates the text for one equation field */ ! 690: - (BOOL)_parseEquationFrom:(const char *)str using:(Expression *)anExpr enableVariables:(BOOL *)enableVars { ! 691: BOOL parseError; ! 692: EXPEnumState state; /* used to run through the vars of the expression */ ! 693: const char *var; ! 694: ! 695: parseError = ![anExpr parse:str]; ! 696: if (!parseError) { ! 697: /* ! 698: * If it parsed successfully, make sure all the variables are single ! 699: * letters, and are either "u", "v" or between "A" and "H". As we ! 700: * find suitable variables, we remember then in the enableVars ! 701: * array, so we can enable their controls later. ! 702: */ ! 703: state = [anExpr beginVariableEnumeration]; ! 704: while (var = [anExpr nextVariable:state]) ! 705: if (*var && !var[1] && *var >= MIN_CONST && *var <= MAX_CONST) ! 706: enableVars[*var - MIN_CONST] = YES; ! 707: else if ((*var != 'u' && *var != 'v') || var[1]) ! 708: parseError = YES; ! 709: [anExpr endVariableEnumeration:state]; ! 710: } ! 711: return parseError; ! 712: } ! 713: ! 714: /* ! 715: * Writes a document to the given file using typedstreams. We're very careful ! 716: * about catching exceptions here so we can tell the user if his file didn't ! 717: * get written out successfully. ! 718: */ ! 719: - _write:(const char *)filename { ! 720: NXTypedStream *ts; ! 721: const char *stringVal; ! 722: int intVal; ! 723: char charVal; ! 724: RtPoint fromPoint; ! 725: RtPoint toPoint; ! 726: float aRollAngle; ! 727: RtMatrix shapeXform; ! 728: int rows, cols; ! 729: int numVars; ! 730: int i; ! 731: NXStringTable *stringTable; ! 732: volatile BOOL hadAnError = NO; ! 733: char buffer[MAXPATHLEN+1]; ! 734: int xyz; ! 735: NXRect winFrame; ! 736: ! 737: /* cons up the name of the backup file and remove it */ ! 738: strcpy(buffer, filename); ! 739: strcat(buffer, "~"); ! 740: removeFile(buffer); ! 741: ! 742: /* move the existing file to the backup */ ! 743: rename(filename, buffer); ! 744: ! 745: /* create the new document as a file package (doc wrapper) */ ! 746: strcpy(buffer, filename); ! 747: strcat(buffer, DOC_NAME); ! 748: mkdir(filename, 0777); ! 749: ts = NXOpenTypedStreamForFile(buffer, NX_WRITEONLY); ! 750: if (ts) { ! 751: NX_DURING ! 752: intVal = 2; /* version of this write's data */ ! 753: NXWriteType(ts, "i", &intVal); ! 754: for (xyz = X; xyz <= Z; xyz++) { ! 755: stringVal = [expr[xyz] text]; ! 756: NXWriteType(ts, "*", &stringVal); ! 757: } ! 758: [variableSliders getNumRows:&rows numCols:&cols]; ! 759: numVars = 0; ! 760: for (i = 0; i < rows; i++) { ! 761: if ([[variableSliders cellAt:i :0] isEnabled]) ! 762: numVars++; ! 763: } ! 764: NXWriteType(ts, "i", &numVars); ! 765: for (i = 0; i < rows; i++) ! 766: if ([[variableSliders cellAt:i :0] isEnabled]) { ! 767: charVal = MIN_CONST + i; ! 768: NXWriteType(ts, "c", &charVal); ! 769: writeCellFloat([variableSliders cellAt:i :0], ts); ! 770: } ! 771: intVal = [expr[X] resolution]; ! 772: NXWriteType(ts, "i", &intVal); ! 773: writeCellFloat(minUVSlider[U], ts); ! 774: writeCellFloat(maxUVSlider[U], ts); ! 775: writeCellFloat(minUVSlider[V], ts); ! 776: writeCellFloat(maxUVSlider[V], ts); ! 777: NXWriteColor(ts, [[camera worldShape] color]); ! 778: NXWriteColor(ts, [camera backgroundColor]); ! 779: intVal = [[camera worldShape] surfaceType]; ! 780: NXWriteType(ts, "i", &intVal); ! 781: [camera getEyeAt:&fromPoint toward:&toPoint roll:&aRollAngle]; ! 782: NXWriteArray(ts, "f", 3, fromPoint); ! 783: NXWriteArray(ts, "f", 3, toPoint); ! 784: NXWriteType(ts, "f", &aRollAngle); ! 785: [[camera worldShape] getTransformMatrix:shapeXform]; ! 786: NXWriteArray(ts, "f", 4, shapeXform[0]); ! 787: NXWriteArray(ts, "f", 4, shapeXform[1]); ! 788: NXWriteArray(ts, "f", 4, shapeXform[2]); ! 789: NXWriteArray(ts, "f", 4, shapeXform[3]); ! 790: [window getFrame:&winFrame]; ! 791: NXWriteRect(ts, &winFrame); ! 792: NXCloseTypedStream(ts); ! 793: NX_HANDLER ! 794: hadAnError = YES; ! 795: NX_DURING ! 796: NXCloseTypedStream(ts); ! 797: NX_HANDLER ! 798: /* ignore any error at this point */ ! 799: NX_ENDHANDLER ! 800: NX_ENDHANDLER ! 801: } else ! 802: hadAnError = YES; ! 803: if (hadAnError) { ! 804: stringTable = [[NXApp delegate] stringTable]; ! 805: NXRunAlertPanel([stringTable valueForStringKey:"save alert title"], ! 806: [stringTable valueForStringKey:"save alert message"], ! 807: [stringTable valueForStringKey:"ok button"], ! 808: NULL, NULL, filename); ! 809: return nil; ! 810: } else ! 811: return self; ! 812: } ! 813: ! 814: /* reads a document from the given file using typedstreams */ ! 815: - _read:(const char *)filename { ! 816: NXTypedStream *ts; ! 817: char *stringVal; ! 818: int i; ! 819: char varName; ! 820: float floatVal1, floatVal2; ! 821: int intVal; ! 822: RtPoint fromPoint; ! 823: RtPoint toPoint; ! 824: float aRollAngle; ! 825: RtMatrix shapeXform; ! 826: int version; ! 827: int numVars; ! 828: BOOL parseError; ! 829: int xyz, uv; ! 830: NXRect winFrame; ! 831: ! 832: ts = openDocStream(filename, NX_READONLY); ! 833: if (ts) { ! 834: NX_DURING ! 835: NXReadType(ts, "i", &version); /* read file version */ ! 836: NX_ASSERT(version <= 2, "Unknown file version in -read"); ! 837: for (xyz = X; xyz <= Z; xyz++) { ! 838: NXReadType(ts, "*", &stringVal); ! 839: [equation[xyz] setStringValue:stringVal]; ! 840: parseError = ![expr[xyz] parse:stringVal]; ! 841: free(stringVal); ! 842: NX_ASSERT(!parseError, "Bad expression stored in data file"); ! 843: if (parseError) ! 844: NX_VALRETURN(nil); ! 845: } ! 846: NXReadType(ts, "i", &numVars); ! 847: for (i = 0; i < numVars; i++) { ! 848: NXReadType(ts, "c", &varName); ! 849: NXReadType(ts, "f", &floatVal1); ! 850: [self _initVariable:varName - MIN_CONST enable:YES ! 851: value:floatVal1]; ! 852: } ! 853: NXReadType(ts, "i", &intVal); /* read resolution */ ! 854: [resolutionText setIntValue:intVal]; ! 855: [resolutionSlider setIntValue:intVal]; ! 856: for (xyz = X; xyz <= Z; xyz++) ! 857: [expr[xyz] setResolution:intVal]; ! 858: for (uv = U; uv <= V; uv++) { ! 859: NXReadType(ts, "f", &floatVal1); /* read min */ ! 860: NXReadType(ts, "f", &floatVal2); /* read max */ ! 861: [minUVText[uv] setFloatValue:floatVal1]; ! 862: [self _setSliderCell:minUVSlider[uv] value:floatVal1 ! 863: minLabel:nil maxLabel:nil]; ! 864: [maxUVText[uv] setFloatValue:floatVal2]; ! 865: [self _setSliderCell:maxUVSlider[uv] value:floatVal2 ! 866: minLabel:nil maxLabel:nil]; ! 867: [self _setUVRangeOf:uv]; ! 868: } ! 869: [[camera worldShape] setColor:NXReadColor(ts)]; ! 870: [camera setBackgroundColor:NXReadColor(ts)]; ! 871: NXReadType(ts, "i", &intVal); /* read surface type */ ! 872: [camera setSurfaceTypeForAll:intVal chooseHider:YES]; ! 873: NXReadArray(ts, "f", 3, fromPoint); ! 874: NXReadArray(ts, "f", 3, toPoint); ! 875: NXReadType(ts, "f", &aRollAngle); ! 876: [camera setEyeAt:fromPoint toward:toPoint roll:aRollAngle]; ! 877: NXReadArray(ts, "f", 4, shapeXform[0]); ! 878: NXReadArray(ts, "f", 4, shapeXform[1]); ! 879: NXReadArray(ts, "f", 4, shapeXform[2]); ! 880: NXReadArray(ts, "f", 4, shapeXform[3]); ! 881: [[camera worldShape] setTransformMatrix:shapeXform]; ! 882: NXReadRect(ts, &winFrame); ! 883: [window placeWindow:&winFrame]; ! 884: NXCloseTypedStream(ts); ! 885: /* must use NX_VALRETURN to return from inside a NX_DURING */ ! 886: NX_VALRETURN(self); ! 887: NX_HANDLER ! 888: NX_DURING ! 889: NXCloseTypedStream(ts); ! 890: NX_HANDLER ! 891: /* ignore any error at this point */ ! 892: NX_ENDHANDLER ! 893: return nil; ! 894: NX_ENDHANDLER ! 895: } else ! 896: return nil; ! 897: } ! 898: ! 899: /* ! 900: * Inits a variable to either be on or off. Used when we discover a new ! 901: * set of variables after a parse, or to set up the variables from a document ! 902: * that we read from a file. ! 903: */ ! 904: - (void)_initVariable:(int)index enable:(BOOL)flag value:(float)val { ! 905: TextFieldCell *text; ! 906: SliderCell *slider; ! 907: ! 908: /* set the initial value in the Expression */ ! 909: if (flag) ! 910: [self _setVariable:index toValue:val]; ! 911: ! 912: /* if the enabled status is changing... */ ! 913: if (flag != [[variableSliders cellAt:index :0] isEnabled]) ! 914: if (flag) { ! 915: ! 916: /* enable all controls associated with the variable */ ! 917: [[variableLabels cellAt:index :0] setTextGray:NX_BLACK]; ! 918: slider = [variableSliders cellAt:index :0]; ! 919: [slider setEnabled:YES]; ! 920: [self _setSliderCell:slider value:val ! 921: minLabel:[variableMinLabels cellAt:index :0] ! 922: maxLabel:[variableMaxLabels cellAt:index :0]]; ! 923: text = [variableTexts cellAt:index :0]; ! 924: [text setEnabled:YES]; ! 925: [text setFloatValue:val]; ! 926: } else if (!flag) { ! 927: ! 928: /* disable all controls associated with the variable */ ! 929: [[variableLabels cellAt:index :0] setTextGray:NX_DKGRAY]; ! 930: slider = [variableSliders cellAt:index :0]; ! 931: [slider setFloatValue:[slider minValue]]; ! 932: [slider setEnabled:NO]; ! 933: text = [variableTexts cellAt:index :0]; ! 934: [text setStringValue:NULL]; ! 935: [text setEnabled:NO]; ! 936: } ! 937: } ! 938: ! 939: /* ! 940: * sets the range of the u or v variable in the Expression. ! 941: * It ensures that the min value isn't greater than the max value. ! 942: */ ! 943: - (void)_setUVRangeOf:(int)var { ! 944: int xyz; ! 945: float min = [minUVSlider[var] floatValue]; ! 946: float max = [maxUVSlider[var] floatValue]; ! 947: char *varName = (var == U) ? "u" : "v"; ! 948: ! 949: for (xyz = X; xyz <= Z; xyz++) { ! 950: [expr[xyz] setVar:varName min:MIN(min, max) max:MAX(min, max)]; ! 951: /* set up two dimensions of evaluation */ ! 952: [expr[xyz] setVar:varName dimension:var]; ! 953: } ! 954: } ! 955: ! 956: /* sets one of the expressions' variables to a value */ ! 957: - (void)_setVariable:(int)index toValue:(float)val { ! 958: char varName[2]; ! 959: int xyz; ! 960: ! 961: varName[0] = MIN_CONST + index; ! 962: varName[1] = '\0'; ! 963: for (xyz = X; xyz <= Z; xyz++) ! 964: [expr[xyz] setVar:varName value:val]; ! 965: } ! 966: ! 967: /* sets a slider to a value and adjust min/max */ ! 968: - (void)_setSliderCell:(SliderCell *)slider value:(float)val minLabel:(Cell *)minLabel maxLabel:(Cell *)maxLabel { ! 969: if (val < [slider minValue]) { ! 970: [window disableDisplay]; /* so slider won't flash to new location */ ! 971: [slider setMinValue:val]; ! 972: [window reenableDisplay]; ! 973: [minLabel setIntValue:floor(val)]; ! 974: } else if (val > [slider maxValue]) { ! 975: [window disableDisplay]; /* so slider won't flash to new location */ ! 976: [slider setMaxValue:val]; ! 977: [window reenableDisplay]; ! 978: [maxLabel setIntValue:ceil(val)]; ! 979: } ! 980: [slider setFloatValue:val]; ! 981: } ! 982: ! 983: /* sets up some default lights for the camera */ ! 984: - (void)_setUpLighting { ! 985: RtPoint distantlight_from, distantlight2_from, pointlight_from; ! 986: RtPoint distantlight_to; ! 987: RtFloat intensity; ! 988: N3DLight *pointLight, *distantLight, *distantLight2, *ambientLight; ! 989: ! 990: if ([[camera lightList] count] == 4) ! 991: return; /* we've already added lights to this camera */ ! 992: ! 993: pointlight_from[0] = 12.0; ! 994: pointlight_from[1] = -7.0; ! 995: pointlight_from[2] = 8.5; ! 996: distantlight_from[0] = 12.0; ! 997: distantlight_from[1] = -7.0; ! 998: distantlight_from[2] = 8.5; ! 999: distantlight2_from[0] = 3.0; ! 1000: distantlight2_from[1] = 2.0; ! 1001: distantlight2_from[2] = 8.5; ! 1002: ! 1003: distantlight_to[0] = 0.0; ! 1004: distantlight_to[1] = 0.0; ! 1005: distantlight_to[2] = 0.0; ! 1006: ! 1007: intensity = 60.0; ! 1008: pointLight = [[N3DLight allocFromZone:[camera zone]] init]; ! 1009: [pointLight makePointFrom:pointlight_from intensity:intensity]; ! 1010: [camera addLight:pointLight]; ! 1011: ! 1012: intensity = 0.6; ! 1013: distantLight = [[N3DLight allocFromZone:[camera zone]] init]; ! 1014: [distantLight makeDistantFrom:distantlight_from to:distantlight_to intensity:intensity]; ! 1015: [camera addLight:distantLight]; ! 1016: ! 1017: intensity = 0.9; ! 1018: distantLight2 = [[N3DLight allocFromZone:[camera zone]] init]; ! 1019: [distantLight2 makeDistantFrom:distantlight2_from to:distantlight_to intensity:intensity]; ! 1020: [camera addLight:distantLight2]; ! 1021: ! 1022: intensity = 0.15; ! 1023: ambientLight = [[N3DLight allocFromZone:[camera zone]] init]; ! 1024: [ambientLight makeAmbientWithIntensity:intensity]; ! 1025: [camera addLight:ambientLight]; ! 1026: } ! 1027: ! 1028: /* ! 1029: * These methods are called as the IB file is unarchived. We override them to ! 1030: * store these outlets in arrays, since IB doesnt know how to declare arrays ! 1031: * of outlets. ! 1032: */ ! 1033: - setEquationX:obj { equation[X] = obj; return self; } ! 1034: - setEquationY:obj { equation[Y] = obj; return self; } ! 1035: - setEquationZ:obj { equation[Z] = obj; return self; } ! 1036: - setMinUSlider:obj { minUVSlider[U] = obj; return self; } ! 1037: - setMinVSlider:obj { minUVSlider[V] = obj; return self; } ! 1038: - setMaxUSlider:obj { maxUVSlider[U] = obj; return self; } ! 1039: - setMaxVSlider:obj { maxUVSlider[V] = obj; return self; } ! 1040: - setMinUText:obj { minUVText[U] = obj; return self; } ! 1041: - setMinVText:obj { minUVText[V] = obj; return self; } ! 1042: - setMaxUText:obj { maxUVText[U] = obj; return self; } ! 1043: - setMaxVText:obj { maxUVText[V] = obj; return self; } ! 1044: ! 1045: @end ! 1046: ! 1047: /* little utility proc to write out the float value of a control */ ! 1048: static void writeCellFloat(Cell *obj, NXTypedStream *ts) { ! 1049: float val; ! 1050: ! 1051: val = [obj floatValue]; ! 1052: NXWriteType(ts, "f", &val); ! 1053: } ! 1054: ! 1055: /* Opens a stream on the document regardless of whether its a doc wrapper. */ ! 1056: static NXTypedStream *openDocStream(const char *filename, int mode) { ! 1057: NXTypedStream *ts = NULL; ! 1058: struct stat statInfo; ! 1059: char buffer[MAXPATHLEN+1]; ! 1060: ! 1061: if (stat(filename, &statInfo) == 0) { ! 1062: if (statInfo.st_mode & S_IFDIR) { ! 1063: strcpy(buffer, filename); ! 1064: strcat(buffer, DOC_NAME); ! 1065: ts = NXOpenTypedStreamForFile(buffer, mode); ! 1066: } else { ! 1067: ts = NXOpenTypedStreamForFile(filename, mode); ! 1068: } ! 1069: } ! 1070: return ts; ! 1071: } ! 1072: ! 1073: /* removes a directory, removing anything inside it. Does not recurse */ ! 1074: static int removeFile(const char *file) { ! 1075: DIR *dirp; ! 1076: struct stat st; ! 1077: struct direct *dp; ! 1078: char *leaf = NULL; ! 1079: char path[MAXPATHLEN+1]; ! 1080: ! 1081: if (!stat(file, &st)) { ! 1082: if ((st.st_mode & S_IFMT) == S_IFDIR) { ! 1083: dirp = opendir(file); ! 1084: for (dp = readdir(dirp); dp != NULL; dp = readdir(dirp)) { ! 1085: if (strcmp(dp->d_name, ".") && strcmp(dp->d_name, "..")) { ! 1086: if (!leaf) { ! 1087: strcpy(path, file); ! 1088: strcat(path, "/"); ! 1089: leaf = path + strlen(path); ! 1090: } ! 1091: strcpy(leaf, dp->d_name); ! 1092: if (unlink(path)) { ! 1093: closedir(dirp); ! 1094: return -1; ! 1095: } ! 1096: } ! 1097: } ! 1098: return rmdir(file); ! 1099: } else { ! 1100: return unlink(file); ! 1101: } ! 1102: } ! 1103: ! 1104: return -1; ! 1105: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.