home *** CD-ROM | disk | FTP | other *** search
/ NeXTSTEP 3.0 / NeXTSTEP3.0.iso / NextDeveloper / Examples / AppKit / Graph / Graph3DDoc.m < prev    next >
Encoding:
Text File  |  1992-06-24  |  32.6 KB  |  1,048 lines

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