home *** CD-ROM | disk | FTP | other *** search
/ Nebula 1995 August / NEBULA.mdf / Apps / DevTools / ClassEditor.0.3 / Source / CEClassEditor.m < prev    next >
Encoding:
Text File  |  1995-02-14  |  34.7 KB  |  1,386 lines

  1. /* CEClassEditor.m                 
  2.  *
  3.  * This object controls the data of a beaker (molecules, cameras, groups etc.)
  4.  * It is the main document of BeakerBoy and controls everything from loading to
  5.  * setting up the browser which does most of the other work.
  6.  *
  7.  * For interface-info see the header file. The comments in this file mostly
  8.  * cover only the real implementation details.
  9.  *
  10.  * Written by:         Thomas Engel
  11.  * Created:            23.10.1993 (Copyleft)
  12.  * Last modified:     12.11.1994
  13.  */
  14.  
  15. #define CURRENT_VERSION 1
  16.  
  17. #import "CEClassEditor.h"
  18. #import "CEMethod.h"
  19. #import "CEBrowserCell.h"
  20. #import "Text_MiscExtensions.h"
  21. #import "MiscStringArray_List.h"
  22.  
  23. #import <misckit/MiscString.h>
  24. #import <misckit/MiscStringArray.h>
  25. #import <misckit/MiscSortedList.h>
  26.  
  27. @implementation CEClassEditor
  28.  
  29.  
  30. + initialize
  31. {
  32.     if ( self == [CEClassEditor class] )
  33.         [CEClassEditor setVersion:CURRENT_VERSION];
  34.  
  35.     return self;
  36. }
  37.  
  38. - init
  39. {
  40.     return [self initFromFile:""];
  41. }
  42.  
  43. - initFromFile:(const char *)fileName
  44. {    
  45.     id    basename;
  46.     id    dummyFile;
  47.     
  48.     // this should really become a global part of info !!
  49.     
  50.     id    headerSearchPathes;
  51.     id    sourceSearchPathes;
  52.     id    docuSearchPathes;
  53.     
  54.     headerSearchPathes = [MiscStringArray new];
  55.     [headerSearchPathes addString:"/NextDeveloper/Headers/appkit"];
  56.     [headerSearchPathes addString:"/NextDeveloper/Headers/foundation"];
  57.     [headerSearchPathes addString:"/NextDeveloper/Headers/soundkit"];
  58.     [headerSearchPathes addString:"/NextDeveloper/Headers/3Dkit"];
  59.     [headerSearchPathes addString:"/NextDeveloper/Headers/indexing"];
  60.     [headerSearchPathes addString:"/NextDeveloper/Headers/eointerface"];
  61.     [headerSearchPathes addString:"/NextDeveloper/Headers/eoaccess"];
  62.     [headerSearchPathes addString:"/NextDeveloper/Headers/nikit"];
  63.     [headerSearchPathes addString:"/NextDeveloper/Headers/remote"];
  64.     [headerSearchPathes addString:"/NextDeveloper/Headers/dbkit"];
  65.     [headerSearchPathes addString:"/LocalDeveloper/Headers/misckit"];
  66.     [headerSearchPathes addString:"/LocalDeveloper/Headers/iconkit"];
  67.     [headerSearchPathes addString:"/LocalDeveloper/Headers/WavesWorld"];
  68.     
  69.     // NOTE: We might use the source pathes for header search too. Should
  70.     // take a  look at it some day..
  71.  
  72.     sourceSearchPathes = [MiscStringArray new];
  73.     [sourceSearchPathes addString:"~/Developer/ClassEditor"];
  74.     [sourceSearchPathes addString:"~/Developer/BeakerBoy"];
  75.     [sourceSearchPathes addString:"~/Developer/BeakerBoy"];
  76.     [sourceSearchPathes addString:"~/Developer/ClipNotes"];
  77.     [sourceSearchPathes addString:"~/Developer/UNIX-HatersTool"];
  78.  
  79.     docuSearchPathes = [MiscStringArray new];
  80.     [docuSearchPathes addString:"/NextLibrary/Documentation/NextDev/GeneralRef/02_ApplicationKit/Classes"];
  81.     [docuSearchPathes addString:"/LocalDeveloper/Documentation/MiscKit/Classes"];
  82.     [docuSearchPathes addString:"/LocalDeveloper/Documentation/MiscKit/Categories"];
  83.     [docuSearchPathes addString:"/LocalDeveloper/Documentation/MiscKit/Protocols"];
  84.     [docuSearchPathes addString:"/LocalDeveloper/Documentation/IconKit/Classes"];
  85.     [docuSearchPathes addString:"/LocalDeveloper/Documentation/IconKit/Protocols"];
  86.  
  87.     // On we go...
  88.  
  89.     self = [super init];
  90.     if( !self ) return self;
  91.  
  92.     // OK. We really are an object...here we go with our init.
  93.     // Lets load the NIB. It has set our beaker to be its window-delegate.
  94.     // Without a NIB there will be no beaker object !
  95.  
  96.     if( [NXApp loadNibSection:"ClassWindow.nib" owner:self] == nil )
  97.     {
  98.         NXRunAlertPanel( NULL, "Couldn't load ClassWindow.nib", 
  99.                                "OK", NULL, NULL );
  100.         return nil;
  101.     }
  102.     // Lets setup the names.
  103.     // We need to track all those areas where we update the text to find the
  104.     // places where the user makes changes to the text!
  105.     
  106.     dummyFile = [[MiscString new] setPath:fileName];
  107.     
  108.     basename = [dummyFile fileBasename];
  109.     filename = [dummyFile pathName];
  110.     [filename addChar:[MiscString pathSeparator]];
  111.     [filename concatenate:basename];
  112.     [basename free];
  113.  
  114.     // Get the paseboard and ensure that nobody will see the texthandling we
  115.     // do now.
  116.  
  117.     _tempPb = [Pasteboard newName:"CETempPb"];    
  118.     [self _setIsUpdatingTextViews:YES];
  119.  
  120.     // Now we will always refer to the file WITHOUT any extention.
  121.     // If we are lucky we will be able to read it all..m, .h and .rtf
  122.     // If we were able to read the file...make the view editable.
  123.     
  124.     [dummyFile takeStringValue:filename];
  125.     [dummyFile addExtensionIfNeeded:"h"];
  126.     [self _try:dummyFile withAlternatives:headerSearchPathes
  127.             forText:headerFile andGetStyle:&headerStyle];
  128.     
  129.     // Well we really need a header file... it is crucial !
  130.  
  131.     if( headerStyle == CE_FILETYPE_NONE )
  132.     {
  133.         basename = [dummyFile fileBasename];
  134.         NXRunAlertPanel( NULL, "Couldn't load %s.h. Not in any searchpath.", 
  135.                                "Ooops", NULL, NULL, 
  136.                         [basename stringValue] );
  137.         [basename free];
  138.         return nil;
  139.     }
  140.  
  141.     // The next part is loading the source code for that class.
  142.     // This can be either ASCII or RTF !
  143.     
  144.     [dummyFile takeStringValue:filename];
  145.     [dummyFile addExtensionIfNeeded:"m"];
  146.     [self _try:dummyFile withAlternatives:sourceSearchPathes
  147.             forText:sourceFile andGetStyle:&sourceStyle];
  148.     
  149.     // Now the documentation. if there is no RTF docu we should ty to find a
  150.     // RTFD docu too !
  151.     
  152.     [dummyFile takeStringValue:filename];
  153.     [dummyFile addExtensionIfNeeded:"rtf"];
  154.     [self _try:dummyFile withAlternatives:docuSearchPathes
  155.             forText:docuFile andGetStyle:&docuStyle];
  156.     
  157.     [dummyFile free];
  158.     
  159.     // Its time to check all the methods we can find.
  160.     
  161.     methodList = [List new];
  162.     [self reparseMethods:self];
  163.     
  164.     // If desired we should check to documentation first...maybe we have to add
  165.     // something before we start. But the methods have to be know !!
  166.     
  167.     // if( [preferences should AutoCheckDocu] )
  168.     
  169.     [self checkDocumentation:self];
  170.     
  171.     // Now there are some window settings to be done.
  172.     
  173.     [browser setCellPrototype:[CEBrowserCell new]];
  174.     [browser setTarget:self];
  175.     [browser setAction:@selector(selectNewMethod:)];
  176.     [browser setDoubleAction:@selector(selectNewMethod:)];
  177.     [browser loadColumnZero];
  178.     
  179.     [window setTitleAsFilename:[self filename]];
  180.     [window setMiniwindowImage:[NXImage findImageNamed:"CESmallObjectPink"]];
  181.     
  182.     // Now lets connect the popUp
  183.     
  184.     [[popUp target] setTarget:self];
  185.     [[popUp target] setAction:@selector(switchToNewDisplayMode:)];
  186.     
  187.     // Well this is ugly.
  188.     // We should update all the view bevor it gets visible....but then we 
  189.     // have no selected cell and bang..crash..not method..and bang..not string
  190.     // for the Text object. And the find methdo crashes...
  191.     // [browser selectCell:[browser getLoadedCellAtRow:0 inColumn:0]];
  192.     // somehow this doesn't work...but I don't want to waste my time.
  193.     // Ok I fixed the selectMethod to not crash...still its not clean.
  194.     
  195.     [self selectNewMethod:self];
  196.  
  197.     // ok now we are a new document so lets become active.
  198.  
  199.     [self _setIsUpdatingTextViews:NO];
  200.     _changedTexts = NO;
  201.     [window makeKeyAndOrderFront:self];
  202.     
  203.     return self;
  204. }
  205.  
  206. - _try:path withAlternatives:anArray forText:aText andGetStyle:(int *)aStyle
  207. {
  208.     id    ourPath;
  209.     id    theFilename;
  210.     id    aSearchPath;
  211.     int    trys;
  212.  
  213.     trys = 0;
  214.     *aStyle = CE_FILETYPE_NONE;
  215.     ourPath = [path copy];
  216.     theFilename = [ourPath fileName];
  217.  
  218.     while( *aStyle == CE_FILETYPE_NONE &&
  219.            trys <= [anArray count] )
  220.     {
  221.         [self _readFile:ourPath forText:aText andGetStyle:(int *)aStyle];
  222.  
  223.         aSearchPath = [anArray objectAt:trys];
  224.         [ourPath free];
  225.         ourPath = nil;
  226.         if( aSearchPath != nil )
  227.         {
  228.             
  229.             ourPath = [aSearchPath copy];
  230.             [ourPath addChar:[MiscString pathSeparator]];
  231.             [ourPath concatenate: theFilename];
  232.         }        
  233.         trys++;
  234.     }
  235.     
  236.     if( ourPath != nil ) [ourPath free];
  237.     [theFilename free];
  238.     return self;
  239. }
  240.  
  241. - _readFile:path forText:aText andGetStyle:(int *)aStyle
  242. {
  243.     // We will try to load the requested file...and try to set the right
  244.     // text type info.
  245.     
  246.     NXStream     * aStream;
  247.     
  248.     [path replaceTildeWithHome];
  249.     if( [path doesExistInFileSystem] == NO )
  250.         return self;
  251.     
  252.     // Looks like we have a file...so lets get it into our text object.
  253.     // We have to ensure that all the setting are in the proper fashion.
  254.     // Setting the tabs for ASCII Files in one of them
  255.         
  256.     if( [self _isFileRTF:path] )
  257.     {
  258.         *aStyle = CE_FILETYPE_RTF;
  259.         [aText setMonoFont:NO];
  260.     }
  261.     else    
  262.     {
  263.         *aStyle = CE_FILETYPE_ASCII;
  264.         [aText setMonoFont:YES];
  265.     }
  266.     
  267.     [aText setEditable:YES];
  268.      
  269.     aStream = NXMapFile( [path stringValue], NX_READONLY );
  270.     if( *aStyle == CE_FILETYPE_ASCII )
  271.             [aText readText:aStream];
  272.     else     [aText readRichText:aStream];
  273.     NXCloseMemory( aStream, NX_FREEBUFFER );
  274.         
  275.     return self; 
  276. }
  277. - (BOOL)_isFileRTF:path
  278. {
  279.     // Tells us if the requested file is a RTF text.
  280.     // Lets get the first characters and test for "{\rtf0"
  281.     // If it is there...we should load the same text as RTF again.
  282.     // Well quite stupid code..
  283.  
  284.     FILE *     aStream;
  285.     char    magic[10];
  286.     id        aString;
  287.     BOOL    answer;
  288.     
  289.     magic[6] = 0;
  290.     answer = NO;
  291.     
  292.     if( [path doesExistInFileSystem] )
  293.     {
  294.         aStream = fopen( [path stringValue], "r" );
  295.         fgets( magic, 7, aStream );
  296.         fclose( aStream );
  297.         aString = [MiscString newWithString:magic];
  298.     
  299.         if( [aString cmp:"{\\rtf0"] == 0 ) 
  300.             answer = YES;
  301.         
  302.         [aString free];
  303.     }    
  304.     return answer;
  305. }
  306.  
  307. - free
  308.  {
  309.      // We do not free all the NIB objects...hmm should check that out.
  310.     
  311.       [window free];
  312.       [cheatWindow free];
  313.       [filename free];
  314.     [[methodList freeObjects] free];
  315.     
  316.       return [super free];
  317.  }
  318.  
  319. - (const char *)filename
  320. {
  321.     return [filename stringValue];
  322. }
  323.  
  324. - window
  325. {
  326.     // We have to return the main window to ensure that the right window gets
  327.     // topped when the app decided that we should become visible.
  328.     
  329.     return window;
  330. }
  331.  
  332. /*
  333.  * Here come the methods we will implement to satisfy our window.
  334.  * They are useful to change the inspector and handle oher tasks.
  335.  */
  336.  
  337. - reparseMethods:sender
  338. {
  339.     // This is used to keep the two view up to date. Making changes to the
  340.     // We will reset those origin pointers because copying our current text-
  341.     // sections back would most propably destroy something!
  342.  
  343.     _docuOriginStart = 0;
  344.     _docuOriginEnd = 0;
  345.     _sourceOriginStart = 0;
  346.     _sourceOriginEnd = 0;
  347.     _changedTexts = NO;
  348.  
  349.     [self _setIsUpdatingTextViews:YES];
  350.  
  351.     [methodList freeObjects];
  352.     [self _parseMethodFile];
  353.     [self checkDocumentation:self];
  354.  
  355.     [browser loadColumnZero];
  356.     [self _setIsUpdatingTextViews:NO];
  357.     
  358.     return self;
  359. }
  360.  
  361. - _parseMethodFile
  362. {
  363.     id    newLine;
  364.     id    newMethod;
  365.     int    lines;
  366.     int    i;
  367.  
  368.     lines = [headerFile lineFromPosition:[headerFile textLength]];
  369.  
  370.     for( i=1; i<lines; i++ )
  371.     {
  372.         newLine = [headerFile substringFromLine:i];
  373.         if ( [newLine charAt:0] == '-' ||
  374.              [newLine charAt:0] == '+' )
  375.         {
  376.             newMethod = [[CEMethod alloc] initFromText:newLine];
  377.             [methodList addObject:newMethod];
  378.         }
  379.         [newLine free];
  380.     }
  381.         
  382.     return self;
  383. }
  384.  
  385. - save:sender
  386. {
  387.     // Here we will save all the files in a stupid fashion...anyway.
  388.     // It works for the moment.
  389.     // We will only save file we were able to load !!
  390.     
  391.     id    dummyFile;
  392.     id    topWindow;
  393.     
  394.     if( ![window isDocEdited] ) return self;
  395.     
  396.     // Now lets be sure that ALL changes are in the main files...and
  397.     // Nobody sees the changes going on.
  398.     // But this should ONLY happen when the key window is or class browser.
  399.     // We might corrupt data otherwise !!! The selection might not refer to the
  400.     // right positions anymore !
  401.     // We have to take care of which window was the source of the last
  402.     // cahnges. Depening on that we will hvae to make some consitency 
  403.     // adjustment work !
  404.     
  405.     if( _changedTexts )
  406.             topWindow = _changedWindow;
  407.     else    topWindow = nil;
  408.  
  409.     if( topWindow == window )
  410.         [self _silentlySyncWindows];
  411.         
  412.     // Now if the class has been edited we will save the ASCII Header,
  413.     // RTF source and RTF Documentaion. Quite stupid...but it works for me.
  414.     
  415.     dummyFile = [filename copy];
  416.     [dummyFile addExtensionIfNeeded:"h"];
  417.     [self _writeText:headerFile withStyle:headerStyle to:dummyFile];
  418.     
  419.     [dummyFile takeStringValue:filename];
  420.     [dummyFile addExtensionIfNeeded:"m"];
  421.     [self _writeText:sourceFile withStyle:sourceStyle to:dummyFile];
  422.  
  423.     [dummyFile takeStringValue:filename];
  424.     if( docuStyle == CE_FILETYPE_RTFD )
  425.             [dummyFile addExtensionIfNeeded:"rtfd"];
  426.     else    [dummyFile addExtensionIfNeeded:"rtf"];
  427.     [self _writeText:docuFile withStyle:docuStyle to:dummyFile];
  428.  
  429.     [dummyFile free];
  430.     
  431.     // Ok ... well now there is nothing new here.
  432.     
  433.     [cheatWindow setDocEdited:NO];
  434.     [window setDocEdited:NO];
  435.  
  436.     // Now tell the workspace that we did some changes..
  437.  
  438.     [[Application workspace] fileSystemChanged];
  439.  
  440.     // If we have saved from inside the cheat window we have to update the
  441.     // main browser. Otherwise this one will no recognize the changes made.
  442.     
  443.     if( topWindow == cheatWindow ) 
  444.     {
  445.         [self reparseMethods:self];
  446.         [self selectNewMethod:self];
  447.     }
  448.  
  449.     return self;
  450. }
  451.  
  452. - _writeText:aText withStyle:(int)aStyle to:path
  453. {
  454.     // If the file does no exist...we will create it.
  455.     // But it there is no real text for that file...we won't save anything !
  456.     // RTFD requires a differnt style of saving !
  457.     
  458.     FILE        * dummyStream;
  459.     NXStream     * aStream;
  460.     
  461.     if( aStyle == CE_FILETYPE_NONE ) return self;
  462.     
  463.     else if( aStyle == CE_FILETYPE_RTFD )
  464.         return self;
  465.     
  466.     else
  467.     {
  468.         // Well the streams won't truncate a file so we have to do it on
  469.         // our own.
  470.         // We should do a backup of existing files here if requested.
  471.         
  472.         dummyStream = fopen( [path stringValue], "w" );
  473.         if( dummyStream ) fclose( dummyStream );
  474.         
  475.         aStream = NXMapFile( [path stringValue], NX_WRITEONLY );
  476.         switch( aStyle )
  477.         {
  478.             case CE_FILETYPE_RTF:
  479.                 [aText writeRichText:aStream];
  480.                 break;
  481.             default:
  482.                 [aText writeText:aStream];
  483.         }    
  484.         NXSaveToFile( aStream, [path stringValue] );
  485.         NXCloseMemory( aStream, NX_FREEBUFFER );
  486.     }
  487.     return self;
  488. }
  489.  
  490. - close:sender
  491. {
  492.     // Tell both windows to go away...
  493.     // To give feedback on which windows will dissappear we will make the 
  494.     // key first.
  495.     // The browser window is the main window...the only one to focus at...
  496.     // so we can close the other window if the main window is gone...
  497.     
  498.     [window makeKeyAndOrderFront:self];
  499.     [window performClose:self];
  500.     if( [window isVisible] ) return nil;
  501.     
  502.     // Oh...we really did close the window...the cheat window has
  503.     // passed away during the main-close too...so we are done.
  504.  
  505.     return self;
  506. }
  507.  
  508. - undo:sender
  509. {
  510.     // Well sorry this is a stupid undo...it only undos alle the changes done
  511.     // since switched to a certain method.
  512.     // This only works if the key window is our main browser.
  513.     // The replacement of the selection will be visiible to give visual
  514.     // feedback that something is happening.
  515.     
  516.     id    ourMethod;
  517.     id    focusView;
  518.     
  519.     if( [NXApp keyWindow] == window )
  520.     {
  521.         [self _setIsUpdatingTextViews:YES];
  522.         ourMethod = [[browser selectedCell] source];
  523.         if( ourMethod != nil )
  524.         {
  525.             [self _showDocuForMethod:ourMethod];
  526.             [self _showSourceForMethod:ourMethod];
  527.         }
  528.         if( [sourceTextView isEditable] )
  529.                 focusView = sourceTextView;
  530.         else    focusView = docuTextView;
  531.     
  532.         [focusView setSel:0 :0];
  533.         [focusView scrollSelToVisible];
  534.         _changedTexts = NO;
  535.         [self _setIsUpdatingTextViews:NO];
  536.  
  537.     }
  538.     return self;    
  539. }
  540.  
  541. - switchToNewDisplayMode:sender
  542. {
  543.     // Called by the PopUp when something changes.
  544.     
  545.     int    tag;
  546.     id    popUpMatrix;
  547.     
  548.     popUpMatrix = [[popUp target] itemList];
  549.     tag = [[popUpMatrix selectedCell] tag];
  550.     
  551.     if( tag == 1 || tag > 6 ) [self showCheatWindow:self];
  552.  
  553.     return self;
  554. }
  555.  
  556. - selectNewMethod:sender
  557. {
  558.     id    ourMethod;
  559.     id    focusView;
  560.     
  561.     // Now lets stop redraws until we have finished...looks a little better.
  562.     // This part is a little trick because somehow I have to find the right
  563.     // selections inside the original texts. Now the Text object is not very
  564.     // helpful here. The find method is more then stupid.
  565.     
  566.     [self _setIsUpdatingTextViews:YES];
  567.     [self _syncWindows];
  568.  
  569.     // During the init phase ther is no selected method inside the browser..
  570.     // To solve that we simple take the first method we have..
  571.     
  572.     ourMethod = [[browser selectedCell] source];
  573.     if( ourMethod == nil ) ourMethod = [methodList objectAt:0];
  574.     
  575.     [selectorNameField setStringValue:[ourMethod selectorName]];
  576.     [methodNameField setStringValue:[ourMethod name]];
  577.     
  578.     if( [ourMethod isInstanceMethod] )
  579.             [methodTypeSwitches selectCellWithTag:2];
  580.     else    [methodTypeSwitches selectCellWithTag:1];
  581.         
  582.     // Lets update the method docz/source view with the new text.
  583.     
  584.     [self _showDocuForMethod:ourMethod];
  585.     [self _showSourceForMethod:ourMethod];
  586.     
  587.     // It is time to redraw all the fun stuff again. We will make a startup
  588.     // selection first.
  589.  
  590.     if( [sourceTextView isEditable] )
  591.             focusView = sourceTextView;
  592.     else    focusView = docuTextView;
  593.     
  594.     [focusView setSel:0 :0];
  595.     [focusView scrollSelToVisible];
  596.  
  597.     // Now be sure that the changes really get displayed.
  598.  
  599.     [self _setIsUpdatingTextViews:NO];
  600.     [window display];
  601.     [cheatWindow display];
  602.  
  603.     // Ok here comes the test section
  604.     return self;
  605. }
  606.  
  607. - _setIsUpdatingTextViews:(BOOL)flag
  608. {
  609.     // If we have a switch of the docu update stuff...we should take
  610.     // care that the pasteboard won't get corrupted !
  611.     // Which means..wehn finshed with the updates...we will put back the 
  612.     // pervious pastebord contents.
  613.     
  614.     if( _updatingTextViews == NO &&
  615.         flag == YES )
  616.     {
  617.         [window disableDisplay];
  618.         [cheatWindow disableDisplay];
  619.     }
  620.  
  621.     else if( _updatingTextViews == YES &&
  622.             flag == NO )
  623.     {
  624.         [window reenableDisplay];
  625.         [cheatWindow reenableDisplay];
  626.     }
  627.  
  628.     _updatingTextViews = flag;
  629.  
  630.     return self;
  631. }
  632.  
  633. - _showSourceForMethod:aMethod
  634. {
  635.     // Now lets update the source view. 
  636.     // We assume that no changes get lost...so be sure to backup changes first.
  637.     // Everything that is inside the sourceTextView will get lost.
  638.     
  639.     // Now if we really find the method we can show it.
  640.     
  641.     [self _selectSourceForMethod:aMethod];
  642.     
  643.     // Well...now we have a possible selection inside the original text.
  644.     // If it is no useful selection we don't have any text...and we just mark 
  645.     // the area as useless.
  646.     
  647.     if( _sourceOriginStart > 0 && _sourceOriginEnd > 0 )
  648.     {
  649.         [sourceFile setSel:_sourceOriginStart :_sourceOriginEnd];
  650.         [sourceFile copyTo:_tempPb];
  651.         [sourceTextView setEditable:YES];
  652.         [sourceTextView selectAll:self];
  653.         [sourceTextView pasteFrom:_tempPb];
  654.     }
  655.     else
  656.     {
  657.         [sourceTextView setEditable:YES];
  658.         [sourceTextView selectAll:self];
  659.         [sourceTextView delete:self];
  660.         [sourceTextView setEditable:NO];
  661.     }
  662.     // We shouldn't corrupt the fonts if we can only handle ASCII !
  663.  
  664.     if( sourceStyle == CE_FILETYPE_ASCII )
  665.             [sourceTextView setMonoFont:YES];
  666.     else    [sourceTextView setMonoFont:NO];
  667.         
  668.     return self;
  669. }
  670.  
  671. - (BOOL)_selectSourceForMethod:aMethod
  672. {
  673.     // This private method searches for the method inside the SourceFile
  674.     // and _selects_ the code-portion of this method.
  675.     // This really is only for finding the part. 
  676.     // Returns success and sets the internal range information.
  677.     
  678.     // Now the text[] part is more then just ugly..sorry.
  679.     
  680.     char    text[10000]; 
  681.     NXSelPt    from;
  682.     NXSelPt    to;
  683.     int     i;
  684.     int        first;
  685.     int        last;
  686.     BOOL    gotIt;
  687.  
  688.     // Find the definition of the method.
  689.  
  690.     _sourceOriginStart = 0;
  691.     _sourceOriginEnd = 0;    
  692.     [sourceFile setSel:0 :0];
  693.     gotIt = [sourceFile findText:[aMethod name] 
  694.                     ignoreCase:NO backwards:NO wrap:NO];
  695.  
  696.     if( ! gotIt ) return NO;
  697.  
  698.     // Now we assume that people stick to the rules and have the
  699.     // '{' and '}' as the first characters of each line.
  700.     
  701.     [sourceFile getSel:&from :&to];
  702.     [sourceFile getSubstring:text start:from.cp length:9990];
  703.     
  704.     // The first '{' is our start. A '0' terminates the string and the
  705.     // search !
  706.  
  707.     gotIt = NO;
  708.     first = -1;
  709.     for( i=0; i<9990; i++ )
  710.     {
  711.         if( text[i] == 0 ) 
  712.             break;
  713.         else if( text[i] == '{' )
  714.         {
  715.             gotIt = YES;
  716.             first = i;
  717.             break;
  718.         }
  719.     }
  720.     
  721.     // Now we need a new-line followed by a '}'. A '0' terminates the string
  722.     // and the search ! Same as before.
  723.     
  724.     gotIt = NO;
  725.     last = -1;
  726.     for( i=0; i<9990; i++ )
  727.     {
  728.         if( text[i] == 0 ) 
  729.             break;
  730.         else if( text[i] == '\n' &&
  731.                  text[i+1] == '}' )
  732.         {
  733.             gotIt = YES;
  734.             last = i+2;
  735.             break;
  736.         }
  737.     }
  738.     
  739.     // Now select the right area.
  740.     
  741.     if( gotIt )
  742.     {
  743.         _sourceOriginStart = from.cp+first;
  744.         _sourceOriginEnd = from.cp+last;
  745.         [sourceFile setSel:_sourceOriginStart :_sourceOriginEnd];
  746.     }
  747.  
  748.     return gotIt;    
  749. }
  750.  
  751. - _silentlySyncWindows
  752. {
  753.     // this method silently syncs both window. You won't see any selections
  754.     // cluttering your screen.
  755.     // We have to reselect the source and documenation here because otherwise
  756.     // our references won't be corret anymore !!!
  757.     
  758.     [window disableDisplay];
  759.     [cheatWindow disableDisplay];
  760.     [self _setIsUpdatingTextViews:YES];
  761.     [self _syncWindows];
  762.     [self _selectDocuForMethod:[[browser selectedCell] source]];
  763.     [self _selectSourceForMethod:[[browser selectedCell] source]];
  764.     [self _setIsUpdatingTextViews:NO];
  765.     [cheatWindow reenableDisplay];
  766.     [window reenableDisplay];
  767.         
  768.     return self;
  769. }
  770.  
  771. - _syncWindows
  772. {
  773.     // This method does sync the cheat window with the main window.
  774.     // If there is a currently used documentation we have to copy it back
  775.     // first !
  776.     // After that it remarks that there are no more differneces between
  777.     // the two views.
  778.     // After all we DON'T trace the changes here. This is just a common
  779.     // source part of _sliently Syncing and selecting a new method !!
  780.     
  781.     if( _docuOriginStart > 0 && _docuOriginEnd > 0 && _changedTexts )
  782.     {
  783.         [docuTextView selectAll:self];
  784.         [docuTextView copyTo:_tempPb];
  785.         [docuFile setSel:_docuOriginStart :_docuOriginEnd];
  786.         [docuFile pasteFrom:_tempPb];
  787.         [docuTextView setSel:0 :0];
  788.     }
  789.     
  790.     // Same for the source code.
  791.     
  792.     if( _sourceOriginStart > 0 && _sourceOriginEnd > 0 && _changedTexts )
  793.     {
  794.         [sourceTextView selectAll:self];
  795.         [sourceTextView copyTo:_tempPb];
  796.         [sourceFile setSel:_sourceOriginStart :_sourceOriginEnd];
  797.         [sourceFile pasteFrom:_tempPb];
  798.         [sourceTextView setSel:0 :0];
  799.     }
  800.     
  801.     _changedTexts = NO;
  802.  
  803.     return self;
  804. }
  805.  
  806. - checkDocumentation:sender
  807. {
  808.     // Now this method tries to get the documentation up to date.
  809.     // Missing parts will be inserted...etc.pp.
  810.     
  811.     id    aString;
  812.     id    aMethod;
  813.     int    i;
  814.     id    aList;
  815.     
  816.     // Be care full. we have the remember all the Docu and Source settings
  817.     // becaus ehte method s we use will change them !!!
  818.     
  819.     
  820.     // If there is no docu we ask the app to provide the default template.
  821.     
  822.     if( docuStyle == CE_FILETYPE_NONE )
  823.     {
  824.         [[NXApp delegate] copyClassDocuTemplate];
  825.         [docuFile setMonoFont:NO];
  826.         [docuFile setEditable:YES];
  827.         [docuFile selectAll:self];
  828.         [docuFile pasteFrom:_tempPb];
  829.  
  830.         docuStyle = CE_FILETYPE_RTF;
  831.         
  832.         // The main template does need some customization to be useful
  833.         // First the version
  834.  
  835.         [docuFile setSel:0 :0];
  836.         
  837.         if( [docuFile findText:"MyVersion" 
  838.                       ignoreCase:NO backwards:NO wrap:NO] )
  839.             [docuFile replaceSel:"0.1"];
  840.  
  841.         // Then the Date
  842.  
  843.         if( [docuFile findText:"1994" 
  844.                       ignoreCase:NO backwards:NO wrap:NO] )
  845.             [docuFile replaceSel:"1995"];
  846.  
  847.         // Now the Copyright part
  848.  
  849.         if( [docuFile findText:"MyCompany" 
  850.                       ignoreCase:NO backwards:NO wrap:NO] )
  851.             [docuFile replaceSel:"ClassEditor"];
  852.  
  853.         // Its time for the class name
  854.     
  855.         if( [docuFile findText:"MyClass" 
  856.                       ignoreCase:NO backwards:NO wrap:NO] )
  857.         {
  858.             aString = [filename fileName];
  859.             [docuFile replaceSel:[aString stringValue]];
  860.             [aString free];
  861.         }
  862.         
  863.         // Some header infos please
  864.         
  865.         if( [docuFile findText:"MyClass.h" 
  866.                       ignoreCase:NO backwards:NO wrap:NO] )
  867.         {
  868.             aString = [filename fileName];
  869.             [aString cat:".h"];
  870.             [docuFile replaceSel:[aString stringValue]];
  871.             [aString free];
  872.         }
  873.     }
  874.     
  875.     // Now we should have a documentation...but lets see if there is one
  876.     // for every method. If not we should try to find the right place
  877.     // to place a template.
  878.     
  879.     // We add the in a sorted order. 
  880.     // Because the _addDocu method does not take care of ordering...we
  881.     // have to sort things the other way around.
  882.     
  883.     aList = [MiscSortedList new];
  884.     [aList setSortOrder:Misc_DESCENDING];
  885.     [aList setSortEnabled:YES];
  886.  
  887.     for( i=0; i<[methodList count];i++ )
  888.         [aList addObject:[methodList objectAt:i]];
  889.     
  890.     for( i=0; i<[aList count];i++ )
  891.     {
  892.         aMethod = [aList objectAt:i];
  893.         if( [self _selectDocuForMethod:aMethod] == 0 )
  894.             [self _addDocuForMethod:aMethod];
  895.     }
  896.     [aList free];
  897.     
  898.     // That's it for today..
  899.  
  900.     return self;
  901. }
  902.  
  903. - _showDocuForMethod:aMethod
  904. {
  905.     // Now lets update the documentation view. 
  906.     // We assume that no changes get lost...so be sure to backup changes first.
  907.     // Everything that is inside the docuTextView will get lost.
  908.     
  909.     // Now if we really find the method we have to remove the blakn lines and
  910.     // set the right selection part.
  911.     
  912.     [self _selectDocuForMethod:aMethod];
  913.     
  914.     // Well...now we have a possible selection inside the original text.
  915.     // If it is no useful selection we don't have any text...and we just mark 
  916.     // the area as useless.
  917.     
  918.     if( _docuOriginStart > 0 && _docuOriginEnd > 0 )
  919.     {
  920.         [docuFile setSel:_docuOriginStart :_docuOriginEnd];
  921.         [docuFile copyTo:_tempPb];
  922.         [docuTextView setEditable:YES];
  923.         [docuTextView selectAll:self];
  924.         [docuTextView pasteFrom:_tempPb];
  925.     }
  926.     else
  927.     {
  928.         [docuTextView setEditable:YES];
  929.         [docuTextView selectAll:self];
  930.         [docuTextView delete:self];
  931.         [docuTextView setEditable:NO];
  932.     }
  933.     return self;    
  934. }
  935.  
  936. - (BOOL)_selectDocuForMethod:aMethod
  937. {
  938.     // This private method searches for the method inside the DocuFile
  939.     // and _selects_ the docu-portion of this method.
  940.     // This really is only for finding the part. 
  941.     // Returns the offset to the description text. An offset of '0' meens that
  942.     // no method descript was found.
  943.     // "See also:" is included..AND the blank lines below.
  944.     
  945.     // Now the text[] part is more then just ugly..sorry.
  946.     
  947.     char    text[10000]; 
  948.     NXSelPt    from;
  949.     NXSelPt    to;
  950.     int        first;
  951.     int        last;
  952.     int         i;
  953.     BOOL        gotIt;
  954.  
  955.     // We have to find the right start-selection inside the documentation
  956.     // first. This depends on the type of method.
  957.     
  958.     _docuOriginStart = 0;
  959.     _docuOriginEnd = 0;
  960.     [docuFile setSel:0 :0];
  961.     
  962.     if( [aMethod isInstanceMethod] )
  963.             gotIt = [docuFile findText:"Instance Methods" 
  964.                               ignoreCase:NO backwards:NO wrap:NO];
  965.     else    gotIt = [docuFile findText:"Class Methods" 
  966.                               ignoreCase:NO backwards:NO wrap:NO];
  967.     
  968.     if( !gotIt ) return NO;
  969.     
  970.     // Well finding the right section is not enough.. we have to find the
  971.     // right selector too. And sure...the font has to be correct. We
  972.     // Don't want the find every ref to that method.
  973.     
  974.     gotIt = [docuFile findText:[aMethod selectorName] 
  975.                     ignoreCase:NO 
  976.                     backwards:NO 
  977.                     wrap:NO
  978.                     font:[Font newFont:"Helvetica-Bold" size:14]];
  979.     if( !gotIt ) return NO;
  980.  
  981.     // Well now we should have the first selector for that method.
  982.     // Now if the docu was created with CM it should work if we grab for
  983.     // those right numbers of linefeeds.
  984.     // Really nasty. Don't look at it.
  985.     
  986.     [docuFile getSel:&from :&to];
  987.     [docuFile getSubstring:text start:from.cp length:9990];
  988.     
  989.     // The first new-lines is our start. A '0' terminates the string and the
  990.     // search !
  991.  
  992.     gotIt = NO;
  993.     first = -1;
  994.     for( i=0; i<9990; i++ )
  995.     {
  996.         if( text[i] == 0 ) 
  997.             break;
  998.         else if( text[i] == '\n' &&
  999.                  text[i+1] == '\n' )
  1000.         {
  1001.             gotIt = YES;
  1002.             first = i+2;
  1003.             break;
  1004.         }
  1005.     }
  1006.     
  1007.     // Now we need the 5 last new-lines. A '0' terminates the string and the
  1008.     // search !
  1009.     
  1010.     gotIt = NO;
  1011.     last = -1;
  1012.     for( i=0; i<9990; i++ )
  1013.     {
  1014.         if( text[i] == 0 ) 
  1015.             break;
  1016.         else if( text[i] == '\n' &&
  1017.                  text[i+1] == '\n' &&
  1018.                  text[i+2] == '\n' &&
  1019.                  text[i+3] == '\n' &&
  1020.                  text[i+4] == '\n' )
  1021.         {
  1022.             gotIt = YES;
  1023.             last = i+5;
  1024.             break;
  1025.         }
  1026.     }
  1027.     // Now select the right area.
  1028.     
  1029.     if( gotIt )
  1030.     {    
  1031.         _docuOriginStart = from.cp+first;
  1032.         _docuOriginEnd = from.cp+last-5;
  1033.          [docuFile setSel:_docuOriginStart :_docuOriginEnd];
  1034.     }
  1035.     
  1036.     return gotIt;
  1037. }
  1038.  
  1039. - _addDocuForMethod:aMethod
  1040. {
  1041.     // This method adds a template for the documentation of a certain method.
  1042.     // It DOES corrupt the pasteboard !
  1043.     // BUG: We also don't care about sorting ! This is bad !
  1044.     
  1045.     NXSelPt    from;
  1046.     NXSelPt    to;
  1047.     int     i;
  1048.     int        paramCount;
  1049.     BOOL        gotIt;
  1050.     id        tokens;
  1051.     id        aString;
  1052.  
  1053.     // We have to find the right start-selection inside the documentation
  1054.     // first. This depends on the type of method.
  1055.     
  1056.     [docuFile setSel:0 :0];
  1057.     
  1058.     if( [aMethod isInstanceMethod] )
  1059.             gotIt = [docuFile findText:"Instance Methods" 
  1060.                               ignoreCase:NO backwards:NO wrap:NO];
  1061.     else    gotIt = [docuFile findText:"Class Methods" 
  1062.                               ignoreCase:NO backwards:NO wrap:NO];
  1063.     
  1064.     if( !gotIt ) return self;
  1065.  
  1066.     // Now this is ugly...we should sort the methods alpahbetically !!
  1067.     // Buggy because we can only HOPE that nobody adds something behind the
  1068.     // "* Methods" section.
  1069.     
  1070.     [docuFile getSel:&from :&to];
  1071.     [docuFile setSel:to.cp+2 :to.cp+2];
  1072.  
  1073.     [[NXApp delegate] copyMethodDocuTemplate];
  1074.     [docuFile pasteFrom:_tempPb];
  1075.     [docuFile setSel:to.cp+2 :to.cp+2];
  1076.  
  1077.     [docuFile findText:"_CE_myMethod" 
  1078.               ignoreCase:NO backwards:NO wrap:NO];
  1079.     [docuFile replaceSel:[aMethod selectorName]];
  1080.  
  1081.     [docuFile findText:"_CE_myMethod" 
  1082.               ignoreCase:NO backwards:NO wrap:NO];
  1083.     [docuFile getSel:&from :&to];
  1084.     [docuFile replaceSel:[aMethod name]];
  1085.     
  1086.     // First lets fix the method prefix of the class methods and
  1087.     // remove the dulicate prefix"
  1088.     
  1089.     [docuFile setSel:from.cp-2 :from.cp-1];
  1090.     
  1091.     if( [aMethod isInstanceMethod] )
  1092.     {
  1093.         [docuFile findText:"- " ignoreCase:NO backwards:NO wrap:NO];
  1094.         [docuFile delete:self];
  1095.     }
  1096.     else
  1097.     {
  1098.         [docuFile replaceSel:"+"];
  1099.         [docuFile findText:"+ " ignoreCase:NO backwards:NO wrap:NO];
  1100.         [docuFile delete:self];
  1101.     }
  1102.     
  1103.     // Now its time to fix all the fonts.
  1104.     // Lets tokenize the methods name. This will create a list of
  1105.     // all the parts that have a different font.
  1106.     // We need to know how many parms the method takes to get a simpler
  1107.     // algorithm for coosing the fonts.
  1108.     
  1109.     tokens = [aMethod methodTokens];
  1110.     paramCount = [aMethod numberOfArguments];
  1111.     
  1112.     for( i=0; i<[tokens count]; i++ )
  1113.     {
  1114.         // Now select the new token...if we fail to find it..lets quit here.
  1115.         
  1116.         aString = [tokens objectAt:i];
  1117.         if( [docuFile findText:[aString stringValue]
  1118.                         ignoreCase:NO backwards:NO wrap:NO] == NO ) break;
  1119.         
  1120.         
  1121.         // Depending on the type of token we will choose a different font.
  1122.         // First check for casts and then for paramerters.
  1123.         
  1124.         if( [aString grep:")"] )
  1125.             [docuFile setSelFont:[Font newFont:"Times-Roman" size:14]];
  1126.         
  1127.         // If it is not a cast the situation is a little bit trickier.
  1128.         // Imagine a - (BOOL)delegate or a simple -free method.
  1129.         // The token will be a part of the selector and still have no
  1130.         // ':' attached because there ist no argument                        
  1131.         
  1132.         else if( [aString grep:":"] == NO && paramCount > 0 )
  1133.         {
  1134.             [docuFile setSelFont:[Font newFont:"Times-Italic" size:14]];
  1135.         }
  1136.     }
  1137.     
  1138.     // The tokens are bound to die now.
  1139.     
  1140.     [[tokens freeObjects] free];
  1141.     
  1142.     return self;
  1143. }
  1144.  
  1145. - showCheatWindow:sender
  1146. {
  1147.     id    aString;
  1148.     // Lets choose a defaults selection to clean up the window.
  1149.     [docuFile setSel:0 :0];
  1150.     aString = [filename fileName];
  1151.     [aString cat:"...Cheat Window"];
  1152.     [cheatWindow setMiniwindowImage:
  1153.                     [NXImage findImageNamed:"ObjectPink.rtf.m.h"]];
  1154.     [cheatWindow setTitle:[aString stringValue]];
  1155.     [aString free];
  1156.     
  1157.     [cheatWindow makeKeyAndOrderFront:self];
  1158.     return self;
  1159. }
  1160.  
  1161. - showDocumentationOnly:sender
  1162. {
  1163.     return self;
  1164. }
  1165.  
  1166. - showInterfaceOnly:sender
  1167. {
  1168.     return self;
  1169. }
  1170.  
  1171. - showImplementationOnly:sender
  1172. {
  1173.     return self;
  1174. }
  1175.  
  1176. - (int)browser:sender fillMatrix:matrix inColumn:(int)column
  1177. {
  1178.     // We just fill the matrix with the needed number of cells.
  1179.     // According the methods inside the list.
  1180.     
  1181.     int    i, n;
  1182.     
  1183.     n = [methodList count];
  1184.     for( i=0; i<n; i++ ) [matrix addRow];
  1185.  
  1186.     return n;
  1187. }
  1188.  
  1189. - browser:sender loadCell:cell atRow:(int)row inColumn:(int)column
  1190. {
  1191.     // Just set the atoms name as the String value.
  1192.     id    theMethod;
  1193.     
  1194.     theMethod = [methodList objectAt:row];
  1195.     [cell setStringValue:[theMethod selectorName]];
  1196.     [cell setSource:theMethod];
  1197.     [cell setLoaded:YES];
  1198.     [cell setLeaf:YES];
  1199.  
  1200.     return self;
  1201. }
  1202.  
  1203. - textDidChange:sender
  1204. {
  1205.     // Here we will make both windows as edited. This is for checking 
  1206.     // before saving.
  1207.     // But if we are updating the views from the program we won't say that 
  1208.     // this are real changes. Only user changes count !!
  1209.     // For internal use we are tracking the "real"changes since the last
  1210.     // sync between the two windows.
  1211.     
  1212.     if( _updatingTextViews == NO &&
  1213.         _changedTexts == NO )
  1214.     {
  1215.         [cheatWindow setDocEdited:YES];
  1216.         [window setDocEdited:YES];
  1217.         _changedTexts = YES;
  1218.         _changedWindow = [sender window];
  1219.     }
  1220.     
  1221.     return self;
  1222. }
  1223.  
  1224. - textDidGetKeys:sender isEmpty:(BOOL)flag
  1225. {
  1226.     // This is for tracing down changes inside the window and highlighing the
  1227.     // character pairs.
  1228.     
  1229.     [self textDidChange:sender];
  1230.  
  1231.     // Now if someone typed a character that should have a corresponding pair
  1232.     // we will highlight temp here.
  1233.     
  1234.     return self;
  1235. }
  1236.  
  1237. - _highlightPrevious:(char *)aString inView:aText
  1238. {
  1239.     // this is for highlighing the character pairs.
  1240.     // The current char and his counter part get highlighted if is is found
  1241.     // If not we will Ping loud.
  1242.      
  1243.     NXSelPt    currentFrom;
  1244.     NXSelPt    currentTo;
  1245.     NXSelPt    from;
  1246.     NXSelPt    to;
  1247.     BOOL    gotIt;
  1248.     
  1249.     [aText getSel:¤tFrom :¤tTo];
  1250.     gotIt = [aText findText:aString 
  1251.                    ignoreCase:NO backwards:YES wrap:NO];
  1252.  
  1253.     if( gotIt )
  1254.     {
  1255.         [aText getSel:&from :&to];
  1256.         [aText setSel:currentFrom.cp-1 :currentFrom.cp];
  1257.     //    wait( 1 );
  1258.         [aText setSel:from.cp :to.cp];
  1259.     }
  1260.     else //    NXBeep();
  1261.     
  1262.     [aText setSel:currentFrom.cp :currentTo.cp];
  1263.     return self;
  1264. }
  1265.  
  1266. - textShouldPerformCompletion:sender
  1267. {
  1268.     id    nameArray;
  1269.     id    aMethod;
  1270.     id    aString;
  1271.     id    stringToFind;
  1272.     int    i;
  1273.     int    foundItems;
  1274.     int    firstMatch;
  1275.  
  1276.     // Ok..only inside the methodField we will do completion
  1277.  
  1278.     if( [sender delegate] != methodNameField ) return nil;
  1279.  
  1280.     // Now get the string and find what we need.
  1281.  
  1282.     stringToFind = [MiscString newWithString:[methodNameField stringValue]];
  1283.     nameArray = [MiscStringArray new];
  1284.  
  1285.     for( i=0; i<[methodList count]; i++ )
  1286.     {
  1287.         aMethod = [methodList objectAt:i];
  1288.         [nameArray addString:[aMethod selectorName]];
  1289.         [nameArray addString:[aMethod name]];
  1290.     }
  1291.     // Now cont the hits...
  1292.  
  1293.     foundItems = 0;
  1294.     firstMatch = -1;
  1295.     
  1296.     for( i=0; i<[nameArray count]; i++ )
  1297.     {
  1298.         aString = [nameArray objectAt:i];
  1299.         if( [aString spotOfString:stringToFind caseSensitive:YES] == 0 )
  1300.         {
  1301.             foundItems++;
  1302.             if( firstMatch == -1 ) firstMatch = i;
  1303.         }    
  1304.     }
  1305.     // Now what have we found.. if there are more then one don't do anything.
  1306.  
  1307.     if( foundItems == 1 )
  1308.         [methodNameField setStringValue:[nameArray stringAt:firstMatch]];
  1309.  
  1310.     // Free it all! We hope that the Array frees the 
  1311.  
  1312.     [stringToFind free];
  1313.     [nameArray free];
  1314.     return self;
  1315. }
  1316.  
  1317. - windowDidBecomeKey:sender
  1318. {
  1319.     // Lets look only at the window. Depending on which window has become key
  1320.     // We have to make some updates between the two views.
  1321.  
  1322.     if( _changedTexts == NO ) return self;
  1323.  
  1324.     // If the sender is the window where we did make the last changes there
  1325.     // is no need to do some updates at all. Just leave it as it is.
  1326.     
  1327.     if( sender == _changedWindow ) return self;
  1328.     
  1329.     if( sender == window ) 
  1330.     {
  1331.         [self reparseMethods:self];
  1332.         [self selectNewMethod:self];
  1333.     }
  1334.  
  1335.     if( sender == cheatWindow )
  1336.         [self _silentlySyncWindows];
  1337.     
  1338.     return self;
  1339. }
  1340.  
  1341. - windowWillClose:sender
  1342. {
  1343.     // Lets save the contents if the user wants it and free ourself.
  1344.  
  1345.     int    result;
  1346.  
  1347.     if( sender != window ) return self;
  1348.  
  1349.     // Oh...looks like somebody wants to close the main window...
  1350.     // Lets check if we have something to save.
  1351.  
  1352.     if( [window isDocEdited] ) 
  1353.     {
  1354.         result = NXRunAlertPanel( "Close", "Save changes to %s?", 
  1355.                                 "Save", "Don't Save", "Cancel", 
  1356.                                 [filename stringValue] );
  1357.  
  1358.         if( result == NX_ALERTOTHER )
  1359.             return nil;
  1360.  
  1361.         else if( result == NX_ALERTDEFAULT )
  1362.             [self save:self];
  1363.     }
  1364.     // We will tell the window that they have to interesting information
  1365.     // anymore. This is because the app checks for these infos before quitting.
  1366.     // And if we decide "Don't save"...this should be done correct.
  1367.  
  1368.     [window setDocEdited:NO];
  1369.     [cheatWindow setDocEdited:NO];
  1370.     [cheatWindow deminiaturize:self];
  1371.     [cheatWindow performClose:self];
  1372.  
  1373.     // Its time to go....
  1374.     
  1375.     [[NXApp delegate] addToReleasePool:self];
  1376.     return self; 
  1377. }
  1378.  
  1379. @end
  1380.  
  1381. /*
  1382.  * History: 13.01.95 Buh
  1383.  *            
  1384.  *
  1385.  * Bugs: - ...
  1386.  */