home *** CD-ROM | disk | FTP | other *** search
/ Nebula 1995 August / NEBULA.mdf / SourceCode / Database / SimpleTableView-1 / DataEditor.m < prev    next >
Encoding:
Text File  |  1993-06-01  |  17.1 KB  |  690 lines

  1. // -------------------------------------------------------------------------------------
  2. //  Editor panel
  3. //  This software is without warranty of any kind.  Use at your own risk.
  4. // -------------------------------------------------------------------------------------
  5.  
  6. #import <appkit/appkit.h>
  7. #import <dbkit/dbkit.h>
  8. #import <objc/objc.h>
  9. #import <libc.h>
  10. #import <stdlib.h>
  11. #import <string.h>
  12. #import <c.h>
  13. #import <ctype.h>
  14. #import <sys/param.h>
  15. #import <sys/types.h>
  16. #import <sys/time.h>
  17. #import <sys/dir.h>
  18. #import "FileTable.h"
  19. #import "BoolFormatter.h"
  20. #import "EditFormatter.h"
  21. #import "DataTableView.h"
  22. #import "SimpleTableView.h"
  23. #import "DataEditor.h"
  24.  
  25. // -------------------------------------------------------------------------------------
  26. // DataEditor global vars
  27. static id                    instanceList = (id)nil;        // list of instantiated editors
  28. static BOOL                    shutDown = NO;                // becomes true when terminating
  29.  
  30. // -------------------------------------------------------------------------------------
  31. // global preferences
  32. static BOOL                    prefShowTableGrid = YES;
  33.  
  34. // -------------------------------------------------------------------------------------
  35. // NXRect abbreviations
  36. #define    X                    origin.x
  37. #define    Y                    origin.y
  38. #define    W                    size.width
  39. #define    H                    size.height
  40.  
  41. // -------------------------------------------------------------------------------------
  42. // misc constants/macros
  43. #define LOCALIZE(X)            NXLocalizedString((X), cpNIL, cpNIL)
  44. #define    cpNIL                (char*)nil
  45. #define    KEY_COLUMN            0
  46.  
  47. // -------------------------------------------------------------------------------------
  48. // textWidth macro
  49. #define textWidth(T)        ({ \
  50.                                 NXSize s; \
  51.                                 [[[[Cell alloc] initTextCell:T] calcCellSize:&s] free]; \
  52.                                 rint(s.width * 1.40); \
  53.                             })
  54.  
  55. // -------------------------------------------------------------------------------------
  56. @implementation DataEditor
  57. // -------------------------------------------------------------------------------------
  58.  
  59. /* return custom column formatter */
  60. - _columnFormatterForType:(int)type
  61. {
  62.     id    fmtId = (id)nil;
  63.     switch (type) {
  64.         case CDT_BOOLEAN:
  65.             fmtId = [[BoolFormatter alloc] init];
  66.             [fmtId setIcon:""];
  67.             [fmtId setAltIcon:"checkH"];
  68.             break;
  69.         case CDT_UNKNOWN:
  70.         case CDT_STRING:
  71.         case CDT_INTEGER:
  72.         default:
  73.             fmtId = [[EditFormatter alloc] init];
  74.             break;
  75.     }
  76.     return fmtId;
  77. }
  78.  
  79. /* return vector column for identifier */
  80. - (int)_vectorColumn:(int)col
  81. {
  82.     int    c;
  83.     for (c = 0; c < [dataTableView columnCount]; c++) {
  84.         id <DBTableVectors> v = [dataTableView columnAt:c];
  85.         if ([v identifier] == (id)col) return c;
  86.     }
  87.     return -1;
  88. }
  89.  
  90. /* add column at view order */
  91. - _addColumnAt:(int)i
  92. {
  93.     id                    fmtId;
  94.     id <DBTableVectors>    vectId;
  95.     dataColumn_t        *ci = [dataTable orderedColumnInfoAt:i];
  96.     
  97.     /* column not found, or is hidden */
  98.     if (!ci || ci->isHidden) return self;
  99.     
  100.     /* add column to TableView */
  101.     fmtId = [self _columnFormatterForType:ci->type];
  102.     [dataTableView addColumn:(id)ci->index withFormatter:fmtId andTitle:ci->title at:i];
  103.     
  104.     /* set column attributes */
  105.     vectId = [dataTableView columnAt:i];
  106.     [vectId setTitle:ci->title];
  107.     [vectId setTitleAlignment:NX_CENTERED];
  108.     [vectId sizeTo:((ci->size > 0.0)? ci->size : textWidth(ci->title))];
  109.     [vectId setMinSize:ci->minSize];
  110.     [vectId setContentAlignment:ci->alignment];
  111.     [vectId setEditable:ci->isEditable];
  112.     [vectId setResizable:YES];
  113.     [vectId setAutosizable:NO];
  114.     
  115.     return self;
  116. }
  117.  
  118. /* reset column info */
  119. - _resetColumnInfo
  120. {
  121.     int    c;
  122.  
  123.     /* update dataTable column header info */
  124.     for (c = 0; c < [dataTable actualColumnCount]; c++) {
  125.         int n = [self _vectorColumn:c];
  126.         float size = n>=0? [[dataTableView columnAt:n] size] : 0.0;
  127.         [dataTable setDisplayOrder:n andWidth:size forColumnIndex:c];
  128.     }
  129.     
  130.     return self;
  131. }
  132.  
  133. /* reload tableView data */
  134. - _reloadData
  135. {
  136.     [rcdCount setIntValue:[dataTable rowCount]];
  137.     [dataTableView reloadData:self];
  138.     return self;
  139. }
  140.  
  141. /* hide selected column */
  142. - hideColumn:sender
  143. {
  144.     int        col = [dataTableView selectedColumn];
  145.     if (col >= 0) {
  146.         [dataTableView removeColumnAt:col];
  147.         [self _resetColumnInfo];
  148.         [self _reloadData];
  149.     } else NXBeep();
  150.     return self;
  151. }
  152.  
  153. /* reset column info */
  154. - unhideAllColumns:sender
  155. {
  156.     int    c, vcnt = [dataTable columnCount], acnt = [dataTable actualColumnCount];
  157.  
  158.     /* display display */
  159.     [editorWindow disableFlushWindow];
  160.     [editorWindow disableDisplay];
  161.     
  162.     /* unhide hidden columns */
  163.     for (c = 0; (c < acnt) && (vcnt < acnt); c++) {
  164.         dataColumn_t *ci = [dataTable columnInfoAt:c];
  165.         if (ci && ci->isHidden) {
  166.             [dataTable setDisplayOrder:vcnt andWidth:-1.0 forColumnIndex:c];
  167.             [self _addColumnAt:vcnt++];
  168.         }
  169.     }
  170.     
  171.     /* reload data */
  172.     [self _resetColumnInfo];
  173.     [editorWindow reenableDisplay];
  174.     [editorWindow reenableFlushWindow];
  175.     [self _reloadData];
  176.     
  177.     return self;
  178. }
  179.  
  180. // -------------------------------------------------------------------------------------
  181. // DataEditor initialization
  182.  
  183. /* initialize window */
  184. - _initWindowViews
  185. {
  186.     int        i, c;
  187.     NXRect    rect;
  188.     char    *docTitle;
  189.     id        tblViewSav;
  190.  
  191.     /* disable window flushing */
  192.     [editorWindow disableFlushWindow];
  193.     [editorWindow disableDisplay];
  194.     
  195.     /* set document title */
  196.     docTitle = (char*)[dataTable tableTitle];
  197.     if (*docTitle == '/') [editorWindow setTitleAsFilename:docTitle];
  198.     else [editorWindow setTitle:docTitle];
  199.     
  200.     /* init window header box */
  201.     [headerBox setBorderType:NX_NOBORDER];
  202.     [rcdCount setIntValue:[dataTable rowCount]];
  203.  
  204.     /* initialize dataTableBox & contents */
  205.     [dataTableBox setBorderType:NX_NOBORDER];
  206.     tblViewSav = dataTableView;
  207.     [tblViewSav getFrame:&rect];
  208.     dataTableView = [[TableView alloc] initFrame:&rect];
  209.     [[tblViewSav superview] addSubview:dataTableView];
  210.     [tblViewSav free];
  211.     
  212.     /* set tableview attributes */
  213.     [dataTableView setMode:DB_LISTMODE];
  214.     [dataTableView setTarget:self];
  215.     [dataTableView setAction:@selector(selectTableCell1:)];
  216.     [dataTableView setDoubleAction:@selector(selectTableCell2:)];
  217.     [dataTableView setDelegate:self];
  218.     [dataTableView allowVectorReordering:YES];
  219.     [dataTableView allowVectorResizing:YES];
  220.     [dataTableView setEditable:YES];
  221.     [dataTableView setGridVisible:prefShowTableGrid];
  222.     [dataTableView setHorizScrollerRequired:YES];
  223.     
  224.     /* add columns */
  225.     for (c = [dataTable columnCount], i = 0; i < c; i++) [self _addColumnAt:i];
  226.     [dataTableView setDataSource:dataTable];
  227.  
  228.     /* re-enable window flushing */
  229.     [editorWindow reenableDisplay];
  230.     [editorWindow reenableFlushWindow];
  231.     [editorWindow display];
  232.  
  233.     return self;
  234. }
  235.  
  236. /* place window */
  237. - _placeWindow:(BOOL)moveWindow
  238. {
  239.     NXSize    scrSize;
  240.     NXRect    rect;
  241.     [NXApp getScreenSize:&scrSize];
  242.     [editorWindow getFrame:&rect];
  243.     if (moveWindow) {
  244.         static int winCount = 0;
  245.         rect.X = floor(scrSize.width  * 0.20) + ((float)winCount *  64.0);
  246.         rect.Y = floor(scrSize.height * 0.85) + ((float)winCount * -24.0);
  247.         if (rect.X > (scrSize.width  - 64.0)) rect.X = scrSize.width  - 64.0;
  248.         if (rect.Y > (scrSize.height - 24.0)) rect.Y = scrSize.height - 24.0;
  249.         rect.Y -= rect.H;
  250.         winCount = (++winCount) % 5;
  251.     }
  252.     if ([dataTable viewSize]) rect.size = *[dataTable viewSize];
  253.     [editorWindow placeWindow:&rect];
  254.     [self windowDidResize:editorWindow];
  255.     return self;
  256. }
  257.  
  258. /* initialization */
  259. - initFromFile:(const char*)fileName
  260. {
  261.     
  262.     /* init super and add to instance list */
  263.     [super init];
  264.     if (!instanceList) instanceList = [[[List alloc] initCount:1] empty];
  265.     [instanceList addObject:self];
  266.  
  267.     /* load nib */
  268.     if (![NXApp loadNibSection:"DataEditor.nib" owner:self]) {
  269.         NXLogError("Could not load nib file 'DataEditor.nib'");
  270.         [NXApp delayedFree:self];
  271.         return (id)nil;
  272.     }
  273.     
  274.     /* open/fill table */
  275.     dataTable = [[FileTable alloc] initFromFile:fileName];
  276.     [dataTable setDelegate:self];
  277.     [self sortByDisplayOrder];
  278.     
  279.     /* init/show window */
  280.     [editorWindow setDelegate:self];
  281.     [self _initWindowViews];
  282.     [self _placeWindow:YES];
  283.     [[editorWindow display] makeKeyAndOrderFront:(id)nil];
  284.     
  285.     /* return */
  286.     return self;
  287. }
  288.  
  289. /* normal free */
  290. - free
  291. {
  292.     [instanceList removeObject:self];
  293.     [dataTable free];
  294.     return [super free];
  295. }
  296.  
  297. /* close window (free) */
  298. - closeWindow
  299. {
  300.     [editorWindow performClose:self];
  301.     return self;
  302. }
  303.  
  304. /* free all instances */
  305. + terminateAllEditors
  306. {
  307.     shutDown = YES;
  308.     [[[instanceList copy] makeObjectsPerform:@selector(closeWindow)] free];
  309.     return self;
  310. }
  311.  
  312. // -------------------------------------------------------------------------------------
  313. // key name
  314.  
  315. /* get a new key name */
  316. - (const char*)getKeyName:(const char*)keyName
  317. {
  318.     char    title[256], *newName, *tableName = (char*)[dataTable tableTitle];
  319.     
  320.     /* init panel fields and show */
  321.     sprintf(title, "%s: Key Name", (*tableName=='/'?"<file>":tableName));
  322.     [dataNamePanel setTitle:title];
  323.     [dataNameField setStringValue:(keyName?keyName:"")];
  324.     [dataNameError setStringValue:""];
  325.     [[[dataNamePanel center] display] makeKeyAndOrderFront:(id)nil];
  326.  
  327.     /* loop until successful */
  328.     for (;;) {
  329.     
  330.         /* show panel */
  331.         [NXApp runModalFor:dataNamePanel];
  332.         newName = (char*)[dataNameField stringValue];
  333.         if (dataNameFlag || !*newName || !strcmp(newName, keyName)) {
  334.             newName = (char*)nil;
  335.             break;
  336.         }
  337.     
  338.         /* check for existance */
  339.         if ([dataTable indexForRowName:newName exactMatch:YES] < 0) break;
  340.         [dataNameError setStringValue:"Key name already exists. Please try again."];
  341.         NXLogError("Key %s already exists", newName);
  342.         NXBeep();
  343.     
  344.     }
  345.     
  346.     [dataNamePanel orderOut:(id)nil];
  347.     return newName;
  348.     
  349. }
  350.  
  351. /* add key button selection */
  352. - keyResponse:sender
  353. {
  354.     dataNameFlag = [sender tag];
  355.     [NXApp abortModal];
  356.     return self;
  357. }
  358.  
  359. // -------------------------------------------------------------------------------------
  360. // adding/duplicating rows
  361.  
  362. /* add a new row */
  363. - addRow:sender
  364. {
  365.     char    *keyName;
  366.     if (keyName = (char*)[self getKeyName:""]) {
  367.         [dataTable newRowName:keyName copyFromRow:-1];
  368.         [self resort:(id)nil];
  369.     }
  370.     return self;
  371. }
  372.  
  373. /* duplicate selected row */
  374. - duplicateRow:sender
  375. {
  376.     int        row = [dataTableView selectedRow];
  377.     char    *keyName;
  378.     if ((row >= 0) && (keyName = (char*)[self getKeyName:""])) {
  379.         [dataTable newRowName:keyName copyFromRow:row];
  380.         [self resort:(id)nil];
  381.     }
  382.     return self;
  383. }
  384.  
  385. /* delete selected row */
  386. - deleteRow:sender
  387. {
  388.     int        row = [dataTableView selectedRow];
  389.     if (row >= 0) {
  390.         char *value = (char*)[dataTable valueFor:row:KEY_COLUMN];
  391.           const char *ttl = LOCALIZE("Delete");
  392.         const char *msg = LOCALIZE("%s: Delete Record Entry '%s'?");
  393.           const char *op1 = LOCALIZE("Delete");
  394.         const char *op2 = LOCALIZE("Don't Delete");
  395.         const char *op3 = cpNIL;
  396.         int rtn = NXRunAlertPanel(ttl,msg,op1,op2,op3, [dataTable tableName], value);
  397.         if (rtn == NX_ALERTDEFAULT) {
  398.             [dataTable deleteRowAt:row];
  399.             [self _reloadData];
  400.         }
  401.     } else NXBeep();
  402.     return self;
  403. }
  404.  
  405. // -------------------------------------------------------------------------------------
  406. // table cell selection
  407.  
  408. /* single-click table cell selection */
  409. - selectTableCell1:sender
  410. {
  411.     return self;
  412. }
  413.  
  414. /* double-click table cell selection: edit record name field */
  415. - selectTableCell2:sender
  416. {
  417.     char            *keyName;
  418.     dataColumn_t    *ci;
  419.     int                row = [dataTableView selectedCellRow];
  420.     int                col = [dataTableView selectedCellColumn];
  421.     
  422.     /* check for proper row */
  423.     if (row < 0) return self;
  424.     
  425.     /* check for proper column */
  426.     ci = [dataTable orderedColumnInfoAt:col];
  427.     if (!ci || (ci->index != KEY_COLUMN)) return self;
  428.  
  429.     /* get new key name */
  430.     keyName = (char*)[self getKeyName:[dataTable valueFor:row:KEY_COLUMN]];
  431.     if (!keyName) return self;
  432.     
  433.     /* change key name */
  434.     [dataTable setValue:keyName atIndex:row:KEY_COLUMN];
  435.     [dataTableView rowsChangedFrom:row to:row];
  436.  
  437.     return self;
  438. }
  439.  
  440. // -------------------------------------------------------------------------------------
  441. // sorting functions
  442.  
  443. /* sort by display order */
  444. - sortByDisplayOrder
  445. {
  446.     dataColumn_t    *pci = [dataTable orderedColumnInfoAt:0];
  447.     dataColumn_t    *sci = pci? [dataTable orderedColumnInfoAt:1] : (dataColumn_t*)nil;
  448.     if (!pci || ![dataTable sortTableByColumn:pci->index :(sci?sci->index:-1)]) {
  449.         NXLogError("Cannot sort by display order");
  450.         return self;
  451.     }
  452.     return self;
  453. }
  454.  
  455. /* sort */
  456. - resort:sender
  457. {
  458.     [self sortByDisplayOrder];
  459.     [self _reloadData];
  460.     return self;
  461. }
  462.  
  463. // -------------------------------------------------------------------------------------
  464. // pasteboard functions
  465.  
  466. /* delete column/row */
  467. - delete:sender
  468. {
  469.     int    row = [dataTableView selectedRow], col = [dataTableView selectedColumn];
  470.     if ((row < 0) && (col >= 0)) [self hideColumn:(id)nil];
  471.     else [self deleteRow:(id)nil];
  472.     return self;
  473. }
  474.  
  475. /* cut column/row */
  476. - cut:sender
  477. {
  478.     // not supported
  479.     NXBeep();
  480.     return self;
  481. }
  482.  
  483. /* copy column/row */
  484. - copy:sender
  485. {
  486.     // not supported
  487.     NXBeep();
  488.     return self;
  489. }
  490.  
  491. /* paste column/row */
  492. - paste:sender
  493. {
  494.     // not supported
  495.     NXBeep();
  496.     return self;
  497. }
  498.  
  499. // -------------------------------------------------------------------------------------
  500. // save
  501.  
  502. /* set doc modified flag */
  503. - setDocEdited:(BOOL)flag
  504. {
  505.     return [editorWindow setDocEdited:flag];
  506. }
  507.  
  508. /* save contents of document */
  509. - save:sender
  510. {
  511.     NXRect    rect;
  512.     
  513.     /* reset column info */
  514.     [self _resetColumnInfo];
  515.     
  516.     /* check for existing errors */
  517.     if ([dataTable hasVerificationErrors]) {
  518.            const char *ttl = LOCALIZE("Save");
  519.         const char *msg = LOCALIZE("%s has errors");
  520.           const char *op1 = LOCALIZE("Save Anyway");
  521.         const char *op2 = LOCALIZE("Don't Save");
  522.           const char *op3 = cpNIL;
  523.         int rtn = NXRunAlertPanel(ttl,msg,op1,op2,op3, [dataTable tableName]);
  524.         if (rtn != NX_ALERTDEFAULT) return (id)nil;  // Don't Save
  525.     }
  526.  
  527.     /* check for table changes */
  528.     if ([dataTable tableHasChanged]) {
  529.            const char *ttl = LOCALIZE("Save");
  530.         const char *msg = LOCALIZE("%s has been edited by someone else");
  531.           const char *op1 = LOCALIZE("Overwrite");
  532.         const char *op2 = LOCALIZE("Cancel");
  533.           const char *op3 = cpNIL;
  534.         int rtn = NXRunAlertPanel(ttl,msg,op1,op2,op3, [dataTable tableName]);
  535.         if (rtn != NX_ALERTDEFAULT) return (id)nil;  // Cancel
  536.     }
  537.     
  538.     /* commit(save) table */
  539.     [editorWindow getFrame:&rect];
  540.     [dataTable setViewSize:&rect.size];
  541.     [dataTable commitTable];
  542.     
  543.     return self;
  544.     
  545. }
  546.  
  547. /* save contents of document */
  548. - saveAs:sender
  549. {
  550.     NXLogError("SaveAs not implemented...");
  551.     NXBeep();
  552.     return self;
  553. }
  554.  
  555. /* revert to saved */
  556. - revertToSaved:sender
  557. {
  558.     id    oldData;
  559.     if ([editorWindow isDocEdited]) {
  560.            const char *ttl = LOCALIZE("Revert");
  561.         const char *msg = LOCALIZE("Revert changes to %s?");
  562.          const char *op1 = LOCALIZE("Revert");
  563.         const char *op2 = LOCALIZE("Cancel");
  564.         const char *op3 = cpNIL;
  565.         int rtn = NXRunAlertPanel(ttl,msg,op1,op2,op3, [dataTable tableName]);
  566.         if (rtn != NX_ALERTDEFAULT) return (id)nil;    // Cancel
  567.     }
  568.     oldData = dataTable;
  569.     dataTable = [[FileTable alloc] initFromFile:[oldData tableAccess]];
  570.     [dataTable setDelegate:self];
  571.     [self sortByDisplayOrder];
  572.     [self _initWindowViews];
  573.     [self _placeWindow:NO];
  574.     [self setDocEdited:NO];
  575.     [oldData free];
  576.     return self;
  577. }
  578.  
  579. /* close window */
  580. - close:sender
  581. {
  582.     return [self closeWindow];
  583. }
  584.  
  585. // -------------------------------------------------------------------------------------
  586. // TableView delegate methods
  587.  
  588. /* user move column */
  589. - tableView:aTableView movedColumnFrom:(u_int)oldpos to:(u_int)newpos
  590. {
  591.     [self _resetColumnInfo];
  592.     if ((oldpos <= 1) || (newpos <= 1)) [self resort:(id)nil];
  593.     [self setDocEdited:YES];
  594.     return self;
  595. }
  596.  
  597. /* selection will change */
  598. - (BOOL)tableViewWillChangeSelection:aTableView
  599. {
  600.     return YES;    // allow selection change
  601. }
  602.  
  603. /* user selection changed */
  604. - tableViewDidChangeSelection:aTableView
  605. {
  606.     return self;
  607. }
  608.  
  609. // -------------------------------------------------------------------------------------
  610. // window delegate methods
  611.  
  612. /* window is resizing */
  613. - windowWillResize:windowId toSize:(NXSize*)newSize
  614. {
  615.     NXRect    fRect, cRect;
  616.  
  617.     /* set max width: account for ScrollView(TableView) */
  618.     [[dataTableView docView] getFrame:&cRect];
  619.     [[dataTableView class] getFrameSize:&fRect.size forContentSize:&cRect.size
  620.         horizScroller:YES vertScroller:YES borderType:[dataTableView borderType]];
  621.     
  622.     /* set max width: account for Box (nil since Box has no border) */
  623.     
  624.     /* set max width: account for Window */
  625.     cRect.size = fRect.size;
  626.     cRect.X = cRect.Y = 0.0;
  627.     [[windowId class] getFrameRect:&fRect forContentRect:&cRect style:[windowId style]];
  628.  
  629.     /* reset width */
  630.     if (newSize->width > fRect.W) newSize->width = fRect.W;
  631.     
  632.     return self;
  633. }
  634.  
  635. /* window is resizing */
  636. - windowDidResize:windowId
  637. {
  638.     NXRect    winFrame, rect;
  639.     
  640.     /* get window size */
  641.     [[windowId contentView] getFrame:&winFrame];
  642.  
  643.     /* reset header width */
  644.     [headerBox getFrame:&rect];
  645.     winFrame.H -= rect.H;
  646.     rect.X = 0.0;
  647.     rect.Y = winFrame.H;
  648.     rect.W = winFrame.W;
  649.     [headerBox setFrame:&rect];
  650.  
  651.     /* reset table box frame */
  652.     [dataTableBox getFrame:&rect];
  653.     rect.X = 0.0;
  654.     rect.Y = 0.0;
  655.     rect.size = winFrame.size;
  656.     [dataTableBox setFrame:&rect];
  657.     
  658.     /* resize contents of table box */
  659.     [[dataTableBox contentView] getFrame:&rect];
  660.     [dataTableView setFrame:&rect];
  661.     
  662.     return self;
  663. }
  664.  
  665. /* window will close */
  666. - windowWillClose:windowId
  667. {
  668.  
  669.     /* check for edited document */
  670.     if ([editorWindow isDocEdited]) {
  671.            const char *ttl = LOCALIZE("Save");
  672.         const char *msg = LOCALIZE("Save changes to %s?");
  673.          const char *op1 = LOCALIZE("Save");
  674.         const char *op2 = LOCALIZE("Don't Save");
  675.           const char *op3 = shutDown? cpNIL : LOCALIZE("Cancel");
  676.         int rtn = NXRunAlertPanel(ttl,msg,op1,op2,op3, [dataTable tableName]);
  677.         if (!shutDown && (rtn == NX_ALERTOTHER)) return (id)nil;    // Cancel
  678.         if (rtn == NX_ALERTDEFAULT) [self save:(id)nil];            // Save (else No)
  679.     }
  680.     
  681.     /* free/close window */
  682.     [instanceList removeObject:self];
  683.     [NXApp delayedFree:self];
  684.     return self;
  685.     
  686. }
  687.  
  688. // -------------------------------------------------------------------------------------
  689. @end
  690.