/* CEClassEditor.m				 
 *
 * This object controls the data of a beaker (molecules, cameras, groups etc.)
 * It is the main document of BeakerBoy and controls everything from loading to
 * setting up the browser which does most of the other work.
 *
 * For interface-info see the header file. The comments in this file mostly
 * cover only the real implementation details.
 *
 * Written by: 		Thomas Engel
 * Created:    		23.10.1993 (Copyleft)
 * Last modified: 	12.11.1994
 */

#define CURRENT_VERSION 1

#import "CEClassEditor.h"
#import "CEMethod.h"
#import "CEBrowserCell.h"
#import "Text_MiscExtensions.h"
#import "MiscStringArray_List.h"

#import <misckit/MiscString.h>
#import <misckit/MiscStringArray.h>
#import <misckit/MiscSortedList.h>

@implementation CEClassEditor


+ initialize
{
	if ( self == [CEClassEditor class] )
		[CEClassEditor setVersion:CURRENT_VERSION];

	return self;
}

- init
{
	return [self initFromFile:""];
}

- initFromFile:(const char *)fileName
{	
	id	basename;
	id	dummyFile;
	
	// this should really become a global part of info !!
	
	id	headerSearchPathes;
	id	sourceSearchPathes;
	id	docuSearchPathes;
	
	headerSearchPathes = [MiscStringArray new];
	[headerSearchPathes addString:"/NextDeveloper/Headers/appkit"];
	[headerSearchPathes addString:"/NextDeveloper/Headers/foundation"];
	[headerSearchPathes addString:"/NextDeveloper/Headers/soundkit"];
	[headerSearchPathes addString:"/NextDeveloper/Headers/3Dkit"];
	[headerSearchPathes addString:"/NextDeveloper/Headers/indexing"];
	[headerSearchPathes addString:"/NextDeveloper/Headers/eointerface"];
	[headerSearchPathes addString:"/NextDeveloper/Headers/eoaccess"];
	[headerSearchPathes addString:"/NextDeveloper/Headers/nikit"];
	[headerSearchPathes addString:"/NextDeveloper/Headers/remote"];
	[headerSearchPathes addString:"/NextDeveloper/Headers/dbkit"];
	[headerSearchPathes addString:"/LocalDeveloper/Headers/misckit"];
	[headerSearchPathes addString:"/LocalDeveloper/Headers/iconkit"];
	[headerSearchPathes addString:"/LocalDeveloper/Headers/WavesWorld"];
	
	// NOTE: We might use the source pathes for header search too. Should
	// take a  look at it some day..

	sourceSearchPathes = [MiscStringArray new];
	[sourceSearchPathes addString:"~/Developer/ClassEditor"];
	[sourceSearchPathes addString:"~/Developer/BeakerBoy"];
	[sourceSearchPathes addString:"~/Developer/BeakerBoy"];
	[sourceSearchPathes addString:"~/Developer/ClipNotes"];
	[sourceSearchPathes addString:"~/Developer/UNIX-HatersTool"];

	docuSearchPathes = [MiscStringArray new];
	[docuSearchPathes addString:"/NextLibrary/Documentation/NextDev/GeneralRef/02_ApplicationKit/Classes"];
	[docuSearchPathes addString:"/LocalDeveloper/Documentation/MiscKit/Classes"];
	[docuSearchPathes addString:"/LocalDeveloper/Documentation/MiscKit/Categories"];
	[docuSearchPathes addString:"/LocalDeveloper/Documentation/MiscKit/Protocols"];
	[docuSearchPathes addString:"/LocalDeveloper/Documentation/IconKit/Classes"];
	[docuSearchPathes addString:"/LocalDeveloper/Documentation/IconKit/Protocols"];

	// On we go...

	self = [super init];
	if( !self ) return self;

	// OK. We really are an object...here we go with our init.
	// Lets load the NIB. It has set our beaker to be its window-delegate.
	// Without a NIB there will be no beaker object !

	if( [NXApp loadNibSection:"ClassWindow.nib" owner:self] == nil )
	{
		NXRunAlertPanel( NULL, "Couldn't load ClassWindow.nib", 
							   "OK", NULL, NULL );
		return nil;
	}
	// Lets setup the names.
	// We need to track all those areas where we update the text to find the
	// places where the user makes changes to the text!
	
	dummyFile = [[MiscString new] setPath:fileName];
	
	basename = [dummyFile fileBasename];
	filename = [dummyFile pathName];
	[filename addChar:[MiscString pathSeparator]];
	[filename concatenate:basename];
	[basename free];

	// Get the paseboard and ensure that nobody will see the texthandling we
	// do now.

	_tempPb = [Pasteboard newName:"CETempPb"];	
	[self _setIsUpdatingTextViews:YES];

	// Now we will always refer to the file WITHOUT any extention.
	// If we are lucky we will be able to read it all..m, .h and .rtf
	// If we were able to read the file...make the view editable.
	
	[dummyFile takeStringValue:filename];
	[dummyFile addExtensionIfNeeded:"h"];
	[self _try:dummyFile withAlternatives:headerSearchPathes
			forText:headerFile andGetStyle:&headerStyle];
	
	// Well we really need a header file... it is crucial !

	if( headerStyle == CE_FILETYPE_NONE )
	{
		basename = [dummyFile fileBasename];
		NXRunAlertPanel( NULL, "Couldn't load %s.h. Not in any searchpath.", 
							   "Ooops", NULL, NULL, 
						[basename stringValue] );
		[basename free];
		return nil;
	}

	// The next part is loading the source code for that class.
	// This can be either ASCII or RTF !
	
	[dummyFile takeStringValue:filename];
	[dummyFile addExtensionIfNeeded:"m"];
	[self _try:dummyFile withAlternatives:sourceSearchPathes
			forText:sourceFile andGetStyle:&sourceStyle];
	
	// Now the documentation. if there is no RTF docu we should ty to find a
	// RTFD docu too !
	
	[dummyFile takeStringValue:filename];
	[dummyFile addExtensionIfNeeded:"rtf"];
	[self _try:dummyFile withAlternatives:docuSearchPathes
			forText:docuFile andGetStyle:&docuStyle];
	
	[dummyFile free];
	
	// Its time to check all the methods we can find.
	
	methodList = [List new];
	[self reparseMethods:self];
	
	// If desired we should check to documentation first...maybe we have to add
	// something before we start. But the methods have to be know !!
	
	// if( [preferences should AutoCheckDocu] )
	
	[self checkDocumentation:self];
	
	// Now there are some window settings to be done.
	
	[browser setCellPrototype:[CEBrowserCell new]];
	[browser setTarget:self];
	[browser setAction:@selector(selectNewMethod:)];
	[browser setDoubleAction:@selector(selectNewMethod:)];
	[browser loadColumnZero];
	
	[window setTitleAsFilename:[self filename]];
	[window setMiniwindowImage:[NXImage findImageNamed:"CESmallObjectPink"]];
	
	// Now lets connect the popUp
	
    [[popUp target] setTarget:self];
	[[popUp target] setAction:@selector(switchToNewDisplayMode:)];
	
	// Well this is ugly.
	// We should update all the view bevor it gets visible....but then we 
	// have no selected cell and bang..crash..not method..and bang..not string
	// for the Text object. And the find methdo crashes...
	// [browser selectCell:[browser getLoadedCellAtRow:0 inColumn:0]];
	// somehow this doesn't work...but I don't want to waste my time.
	// Ok I fixed the selectMethod to not crash...still its not clean.
	
	[self selectNewMethod:self];

	// ok now we are a new document so lets become active.

	[self _setIsUpdatingTextViews:NO];
	_changedTexts = NO;
	[window makeKeyAndOrderFront:self];
	
	return self;
}

- _try:path withAlternatives:anArray forText:aText andGetStyle:(int *)aStyle
{
	id	ourPath;
	id	theFilename;
	id	aSearchPath;
	int	trys;

	trys = 0;
	*aStyle = CE_FILETYPE_NONE;
	ourPath = [path copy];
	theFilename = [ourPath fileName];

	while( *aStyle == CE_FILETYPE_NONE &&
		   trys <= [anArray count] )
	{
		[self _readFile:ourPath forText:aText andGetStyle:(int *)aStyle];

		aSearchPath = [anArray objectAt:trys];
		[ourPath free];
		ourPath = nil;
		if( aSearchPath != nil )
		{
			
			ourPath = [aSearchPath copy];
			[ourPath addChar:[MiscString pathSeparator]];
			[ourPath concatenate: theFilename];
		}		
		trys++;
	}
	
	if( ourPath != nil ) [ourPath free];
	[theFilename free];
	return self;
}

- _readFile:path forText:aText andGetStyle:(int *)aStyle
{
	// We will try to load the requested file...and try to set the right
	// text type info.
	
	NXStream 	* aStream;
	
	[path replaceTildeWithHome];
	if( [path doesExistInFileSystem] == NO )
		return self;
	
	// Looks like we have a file...so lets get it into our text object.
	// We have to ensure that all the setting are in the proper fashion.
	// Setting the tabs for ASCII Files in one of them
		
	if( [self _isFileRTF:path] )
	{
		*aStyle = CE_FILETYPE_RTF;
		[aText setMonoFont:NO];
	}
	else	
	{
		*aStyle = CE_FILETYPE_ASCII;
		[aText setMonoFont:YES];
	}
	
	[aText setEditable:YES];
 	
	aStream = NXMapFile( [path stringValue], NX_READONLY );
	if( *aStyle == CE_FILETYPE_ASCII )
			[aText readText:aStream];
	else 	[aText readRichText:aStream];
	NXCloseMemory( aStream, NX_FREEBUFFER );
		
	return self; 
}
- (BOOL)_isFileRTF:path
{
	// Tells us if the requested file is a RTF text.
	// Lets get the first characters and test for "{\rtf0"
	// If it is there...we should load the same text as RTF again.
	// Well quite stupid code..

	FILE * 	aStream;
	char	magic[10];
	id		aString;
	BOOL	answer;
	
	magic[6] = 0;
	answer = NO;
	
	if( [path doesExistInFileSystem] )
	{
		aStream = fopen( [path stringValue], "r" );
		fgets( magic, 7, aStream );
		fclose( aStream );
		aString = [MiscString newWithString:magic];
	
		if( [aString cmp:"{\\rtf0"] == 0 ) 
			answer = YES;
		
		[aString free];
	}	
	return answer;
}

- free
 {
 	// We do not free all the NIB objects...hmm should check that out.
	
  	[window free];
  	[cheatWindow free];
  	[filename free];
	[[methodList freeObjects] free];
	
  	return [super free];
 }
 
- (const char *)filename
{
	return [filename stringValue];
}

- window
{
	// We have to return the main window to ensure that the right window gets
	// topped when the app decided that we should become visible.
	
	return window;
}

/*
 * Here come the methods we will implement to satisfy our window.
 * They are useful to change the inspector and handle oher tasks.
 */

- reparseMethods:sender
{
	// This is used to keep the two view up to date. Making changes to the
	// We will reset those origin pointers because copying our current text-
	// sections back would most propably destroy something!

	_docuOriginStart = 0;
	_docuOriginEnd = 0;
	_sourceOriginStart = 0;
	_sourceOriginEnd = 0;
	_changedTexts = NO;

	[self _setIsUpdatingTextViews:YES];

	[methodList freeObjects];
	[self _parseMethodFile];
	[self checkDocumentation:self];

	[browser loadColumnZero];
	[self _setIsUpdatingTextViews:NO];
	
	return self;
}

- _parseMethodFile
{
	id	newLine;
	id	newMethod;
	int	lines;
	int	i;

	lines = [headerFile lineFromPosition:[headerFile textLength]];

	for( i=1; i<lines; i++ )
	{
		newLine = [headerFile substringFromLine:i];
		if ( [newLine charAt:0] == '-' ||
			 [newLine charAt:0] == '+' )
		{
			newMethod = [[CEMethod alloc] initFromText:newLine];
			[methodList addObject:newMethod];
		}
		[newLine free];
	}
		
	return self;
}

- save:sender
{
	// Here we will save all the files in a stupid fashion...anyway.
	// It works for the moment.
	// We will only save file we were able to load !!
	
	id	dummyFile;
	id	topWindow;
	
	if( ![window isDocEdited] ) return self;
	
	// Now lets be sure that ALL changes are in the main files...and
	// Nobody sees the changes going on.
	// But this should ONLY happen when the key window is or class browser.
	// We might corrupt data otherwise !!! The selection might not refer to the
	// right positions anymore !
	// We have to take care of which window was the source of the last
	// cahnges. Depening on that we will hvae to make some consitency 
	// adjustment work !
	
	if( _changedTexts )
			topWindow = _changedWindow;
	else	topWindow = nil;

	if( topWindow == window )
		[self _silentlySyncWindows];
		
	// Now if the class has been edited we will save the ASCII Header,
	// RTF source and RTF Documentaion. Quite stupid...but it works for me.
	
	dummyFile = [filename copy];
	[dummyFile addExtensionIfNeeded:"h"];
	[self _writeText:headerFile withStyle:headerStyle to:dummyFile];
	
	[dummyFile takeStringValue:filename];
	[dummyFile addExtensionIfNeeded:"m"];
	[self _writeText:sourceFile withStyle:sourceStyle to:dummyFile];

	[dummyFile takeStringValue:filename];
	if( docuStyle == CE_FILETYPE_RTFD )
			[dummyFile addExtensionIfNeeded:"rtfd"];
	else	[dummyFile addExtensionIfNeeded:"rtf"];
	[self _writeText:docuFile withStyle:docuStyle to:dummyFile];

	[dummyFile free];
	
	// Ok ... well now there is nothing new here.
	
	[cheatWindow setDocEdited:NO];
	[window setDocEdited:NO];

	// Now tell the workspace that we did some changes..

	[[Application workspace] fileSystemChanged];

	// If we have saved from inside the cheat window we have to update the
	// main browser. Otherwise this one will no recognize the changes made.
	
	if( topWindow == cheatWindow ) 
	{
		[self reparseMethods:self];
		[self selectNewMethod:self];
	}

	return self;
}

- _writeText:aText withStyle:(int)aStyle to:path
{
	// If the file does no exist...we will create it.
	// But it there is no real text for that file...we won't save anything !
	// RTFD requires a differnt style of saving !
	
	FILE		* dummyStream;
	NXStream 	* aStream;
	
	if( aStyle == CE_FILETYPE_NONE ) return self;
	
	else if( aStyle == CE_FILETYPE_RTFD )
		return self;
	
	else
	{
		// Well the streams won't truncate a file so we have to do it on
		// our own.
		// We should do a backup of existing files here if requested.
		
		dummyStream = fopen( [path stringValue], "w" );
		if( dummyStream ) fclose( dummyStream );
		
		aStream = NXMapFile( [path stringValue], NX_WRITEONLY );
		switch( aStyle )
		{
			case CE_FILETYPE_RTF:
				[aText writeRichText:aStream];
				break;
			default:
				[aText writeText:aStream];
		}	
		NXSaveToFile( aStream, [path stringValue] );
		NXCloseMemory( aStream, NX_FREEBUFFER );
	}
	return self;
}

- close:sender
{
	// Tell both windows to go away...
	// To give feedback on which windows will dissappear we will make the 
	// key first.
	// The browser window is the main window...the only one to focus at...
	// so we can close the other window if the main window is gone...
	
	[window makeKeyAndOrderFront:self];
	[window performClose:self];
	if( [window isVisible] ) return nil;
	
	// Oh...we really did close the window...the cheat window has
	// passed away during the main-close too...so we are done.

	return self;
}

- undo:sender
{
	// Well sorry this is a stupid undo...it only undos alle the changes done
	// since switched to a certain method.
	// This only works if the key window is our main browser.
	// The replacement of the selection will be visiible to give visual
	// feedback that something is happening.
	
	id	ourMethod;
	id	focusView;
	
	if( [NXApp keyWindow] == window )
	{
		[self _setIsUpdatingTextViews:YES];
		ourMethod = [[browser selectedCell] source];
		if( ourMethod != nil )
		{
			[self _showDocuForMethod:ourMethod];
			[self _showSourceForMethod:ourMethod];
		}
		if( [sourceTextView isEditable] )
				focusView = sourceTextView;
		else	focusView = docuTextView;
	
		[focusView setSel:0 :0];
		[focusView scrollSelToVisible];
		_changedTexts = NO;
		[self _setIsUpdatingTextViews:NO];

	}
	return self;	
}

- switchToNewDisplayMode:sender
{
	// Called by the PopUp when something changes.
	
	int	tag;
	id	popUpMatrix;
	
	popUpMatrix = [[popUp target] itemList];
	tag = [[popUpMatrix selectedCell] tag];
	
	if( tag == 1 || tag > 6 ) [self showCheatWindow:self];

	return self;
}

- selectNewMethod:sender
{
	id	ourMethod;
	id	focusView;
	
	// Now lets stop redraws until we have finished...looks a little better.
	// This part is a little trick because somehow I have to find the right
	// selections inside the original texts. Now the Text object is not very
	// helpful here. The find method is more then stupid.
	
	[self _setIsUpdatingTextViews:YES];
	[self _syncWindows];

	// During the init phase ther is no selected method inside the browser..
	// To solve that we simple take the first method we have..
	
	ourMethod = [[browser selectedCell] source];
	if( ourMethod == nil ) ourMethod = [methodList objectAt:0];
	
	[selectorNameField setStringValue:[ourMethod selectorName]];
	[methodNameField setStringValue:[ourMethod name]];
	
	if( [ourMethod isInstanceMethod] )
			[methodTypeSwitches selectCellWithTag:2];
	else	[methodTypeSwitches selectCellWithTag:1];
		
	// Lets update the method docz/source view with the new text.
	
	[self _showDocuForMethod:ourMethod];
	[self _showSourceForMethod:ourMethod];
	
	// It is time to redraw all the fun stuff again. We will make a startup
	// selection first.

	if( [sourceTextView isEditable] )
			focusView = sourceTextView;
	else	focusView = docuTextView;
	
	[focusView setSel:0 :0];
	[focusView scrollSelToVisible];

	// Now be sure that the changes really get displayed.

	[self _setIsUpdatingTextViews:NO];
	[window display];
	[cheatWindow display];

	// Ok here comes the test section
	return self;
}

- _setIsUpdatingTextViews:(BOOL)flag
{
	// If we have a switch of the docu update stuff...we should take
	// care that the pasteboard won't get corrupted !
	// Which means..wehn finshed with the updates...we will put back the 
	// pervious pastebord contents.
	
	if( _updatingTextViews == NO &&
		flag == YES )
	{
		[window disableDisplay];
		[cheatWindow disableDisplay];
	}

	else if( _updatingTextViews == YES &&
			flag == NO )
	{
		[window reenableDisplay];
		[cheatWindow reenableDisplay];
	}

	_updatingTextViews = flag;

	return self;
}

- _showSourceForMethod:aMethod
{
	// Now lets update the source view. 
	// We assume that no changes get lost...so be sure to backup changes first.
	// Everything that is inside the sourceTextView will get lost.
	
	// Now if we really find the method we can show it.
	
	[self _selectSourceForMethod:aMethod];
	
	// Well...now we have a possible selection inside the original text.
	// If it is no useful selection we don't have any text...and we just mark 
	// the area as useless.
	
	if( _sourceOriginStart > 0 && _sourceOriginEnd > 0 )
	{
		[sourceFile setSel:_sourceOriginStart :_sourceOriginEnd];
		[sourceFile copyTo:_tempPb];
		[sourceTextView setEditable:YES];
		[sourceTextView selectAll:self];
		[sourceTextView pasteFrom:_tempPb];
	}
	else
	{
		[sourceTextView setEditable:YES];
		[sourceTextView selectAll:self];
		[sourceTextView delete:self];
		[sourceTextView setEditable:NO];
	}
	// We shouldn't corrupt the fonts if we can only handle ASCII !

	if( sourceStyle == CE_FILETYPE_ASCII )
			[sourceTextView setMonoFont:YES];
	else	[sourceTextView setMonoFont:NO];
		
	return self;
}

- (BOOL)_selectSourceForMethod:aMethod
{
	// This private method searches for the method inside the SourceFile
	// and _selects_ the code-portion of this method.
	// This really is only for finding the part. 
	// Returns success and sets the internal range information.
	
	// Now the text[] part is more then just ugly..sorry.
	
	char	text[10000]; 
	NXSelPt	from;
	NXSelPt	to;
	int 	i;
	int		first;
	int		last;
	BOOL	gotIt;

	// Find the definition of the method.

	_sourceOriginStart = 0;
	_sourceOriginEnd = 0;	
	[sourceFile setSel:0 :0];
	gotIt = [sourceFile findText:[aMethod name] 
					ignoreCase:NO backwards:NO wrap:NO];

	if( ! gotIt ) return NO;

	// Now we assume that people stick to the rules and have the
	// '{' and '}' as the first characters of each line.
	
	[sourceFile getSel:&from :&to];
	[sourceFile getSubstring:text start:from.cp length:9990];
	
	// The first '{' is our start. A '0' terminates the string and the
	// search !

	gotIt = NO;
	first = -1;
	for( i=0; i<9990; i++ )
	{
		if( text[i] == 0 ) 
			break;
		else if( text[i] == '{' )
		{
			gotIt = YES;
			first = i;
			break;
		}
	}
	
	// Now we need a new-line followed by a '}'. A '0' terminates the string
	// and the search ! Same as before.
	
	gotIt = NO;
	last = -1;
	for( i=0; i<9990; i++ )
	{
		if( text[i] == 0 ) 
			break;
		else if( text[i] == '\n' &&
				 text[i+1] == '}' )
		{
			gotIt = YES;
			last = i+2;
			break;
		}
	}
	
	// Now select the right area.
	
	if( gotIt )
	{
		_sourceOriginStart = from.cp+first;
		_sourceOriginEnd = from.cp+last;
		[sourceFile setSel:_sourceOriginStart :_sourceOriginEnd];
	}

	return gotIt;	
}

- _silentlySyncWindows
{
	// this method silently syncs both window. You won't see any selections
	// cluttering your screen.
	// We have to reselect the source and documenation here because otherwise
	// our references won't be corret anymore !!!
	
	[window disableDisplay];
	[cheatWindow disableDisplay];
	[self _setIsUpdatingTextViews:YES];
	[self _syncWindows];
	[self _selectDocuForMethod:[[browser selectedCell] source]];
	[self _selectSourceForMethod:[[browser selectedCell] source]];
	[self _setIsUpdatingTextViews:NO];
	[cheatWindow reenableDisplay];
	[window reenableDisplay];
		
	return self;
}

- _syncWindows
{
	// This method does sync the cheat window with the main window.
	// If there is a currently used documentation we have to copy it back
	// first !
	// After that it remarks that there are no more differneces between
	// the two views.
	// After all we DON'T trace the changes here. This is just a common
	// source part of _sliently Syncing and selecting a new method !!
	
	if( _docuOriginStart > 0 && _docuOriginEnd > 0 && _changedTexts )
	{
		[docuTextView selectAll:self];
		[docuTextView copyTo:_tempPb];
		[docuFile setSel:_docuOriginStart :_docuOriginEnd];
		[docuFile pasteFrom:_tempPb];
		[docuTextView setSel:0 :0];
	}
	
	// Same for the source code.
	
	if( _sourceOriginStart > 0 && _sourceOriginEnd > 0 && _changedTexts )
	{
		[sourceTextView selectAll:self];
		[sourceTextView copyTo:_tempPb];
		[sourceFile setSel:_sourceOriginStart :_sourceOriginEnd];
		[sourceFile pasteFrom:_tempPb];
		[sourceTextView setSel:0 :0];
	}
	
	_changedTexts = NO;

	return self;
}

- checkDocumentation:sender
{
	// Now this method tries to get the documentation up to date.
	// Missing parts will be inserted...etc.pp.
	
	id	aString;
	id	aMethod;
	int	i;
	id	aList;
	
	// Be care full. we have the remember all the Docu and Source settings
	// becaus ehte method s we use will change them !!!
	
	
	// If there is no docu we ask the app to provide the default template.
	
	if( docuStyle == CE_FILETYPE_NONE )
	{
		[[NXApp delegate] copyClassDocuTemplate];
		[docuFile setMonoFont:NO];
		[docuFile setEditable:YES];
		[docuFile selectAll:self];
		[docuFile pasteFrom:_tempPb];

		docuStyle = CE_FILETYPE_RTF;
		
		// The main template does need some customization to be useful
		// First the version

		[docuFile setSel:0 :0];
		
		if( [docuFile findText:"MyVersion" 
					  ignoreCase:NO backwards:NO wrap:NO] )
			[docuFile replaceSel:"0.1"];

		// Then the Date

		if( [docuFile findText:"1994" 
					  ignoreCase:NO backwards:NO wrap:NO] )
			[docuFile replaceSel:"1995"];

		// Now the Copyright part

		if( [docuFile findText:"MyCompany" 
					  ignoreCase:NO backwards:NO wrap:NO] )
			[docuFile replaceSel:"ClassEditor"];

		// Its time for the class name
	
		if( [docuFile findText:"MyClass" 
					  ignoreCase:NO backwards:NO wrap:NO] )
		{
			aString = [filename fileName];
			[docuFile replaceSel:[aString stringValue]];
			[aString free];
		}
		
		// Some header infos please
		
		if( [docuFile findText:"MyClass.h" 
					  ignoreCase:NO backwards:NO wrap:NO] )
		{
			aString = [filename fileName];
			[aString cat:".h"];
			[docuFile replaceSel:[aString stringValue]];
			[aString free];
		}
	}
	
	// Now we should have a documentation...but lets see if there is one
	// for every method. If not we should try to find the right place
	// to place a template.
	
	// We add the in a sorted order. 
	// Because the _addDocu method does not take care of ordering...we
	// have to sort things the other way around.
	
	aList = [MiscSortedList new];
	[aList setSortOrder:Misc_DESCENDING];
	[aList setSortEnabled:YES];

	for( i=0; i<[methodList count];i++ )
		[aList addObject:[methodList objectAt:i]];
	
	for( i=0; i<[aList count];i++ )
	{
		aMethod = [aList objectAt:i];
		if( [self _selectDocuForMethod:aMethod] == 0 )
			[self _addDocuForMethod:aMethod];
	}
	[aList free];
	
	// That's it for today..

	return self;
}

- _showDocuForMethod:aMethod
{
	// Now lets update the documentation view. 
	// We assume that no changes get lost...so be sure to backup changes first.
	// Everything that is inside the docuTextView will get lost.
	
	// Now if we really find the method we have to remove the blakn lines and
	// set the right selection part.
	
	[self _selectDocuForMethod:aMethod];
	
	// Well...now we have a possible selection inside the original text.
	// If it is no useful selection we don't have any text...and we just mark 
	// the area as useless.
	
	if( _docuOriginStart > 0 && _docuOriginEnd > 0 )
	{
		[docuFile setSel:_docuOriginStart :_docuOriginEnd];
		[docuFile copyTo:_tempPb];
		[docuTextView setEditable:YES];
		[docuTextView selectAll:self];
		[docuTextView pasteFrom:_tempPb];
	}
	else
	{
		[docuTextView setEditable:YES];
		[docuTextView selectAll:self];
		[docuTextView delete:self];
		[docuTextView setEditable:NO];
	}
	return self;	
}

- (BOOL)_selectDocuForMethod:aMethod
{
	// This private method searches for the method inside the DocuFile
	// and _selects_ the docu-portion of this method.
	// This really is only for finding the part. 
	// Returns the offset to the description text. An offset of '0' meens that
	// no method descript was found.
	// "See also:" is included..AND the blank lines below.
	
	// Now the text[] part is more then just ugly..sorry.
	
	char	text[10000]; 
	NXSelPt	from;
	NXSelPt	to;
	int		first;
	int		last;
	int 		i;
	BOOL		gotIt;

	// We have to find the right start-selection inside the documentation
	// first. This depends on the type of method.
	
	_docuOriginStart = 0;
	_docuOriginEnd = 0;
	[docuFile setSel:0 :0];
	
	if( [aMethod isInstanceMethod] )
			gotIt = [docuFile findText:"Instance Methods" 
							  ignoreCase:NO backwards:NO wrap:NO];
	else	gotIt = [docuFile findText:"Class Methods" 
							  ignoreCase:NO backwards:NO wrap:NO];
	
	if( !gotIt ) return NO;
	
	// Well finding the right section is not enough.. we have to find the
	// right selector too. And sure...the font has to be correct. We
	// Don't want the find every ref to that method.
	
	gotIt = [docuFile findText:[aMethod selectorName] 
					ignoreCase:NO 
					backwards:NO 
					wrap:NO
					font:[Font newFont:"Helvetica-Bold" size:14]];
	if( !gotIt ) return NO;

	// Well now we should have the first selector for that method.
	// Now if the docu was created with CM it should work if we grab for
	// those right numbers of linefeeds.
	// Really nasty. Don't look at it.
	
	[docuFile getSel:&from :&to];
	[docuFile getSubstring:text start:from.cp length:9990];
	
	// The first new-lines is our start. A '0' terminates the string and the
	// search !

	gotIt = NO;
	first = -1;
	for( i=0; i<9990; i++ )
	{
		if( text[i] == 0 ) 
			break;
		else if( text[i] == '\n' &&
				 text[i+1] == '\n' )
		{
			gotIt = YES;
			first = i+2;
			break;
		}
	}
	
	// Now we need the 5 last new-lines. A '0' terminates the string and the
	// search !
	
	gotIt = NO;
	last = -1;
	for( i=0; i<9990; i++ )
	{
		if( text[i] == 0 ) 
			break;
		else if( text[i] == '\n' &&
				 text[i+1] == '\n' &&
				 text[i+2] == '\n' &&
				 text[i+3] == '\n' &&
				 text[i+4] == '\n' )
		{
			gotIt = YES;
			last = i+5;
			break;
		}
	}
	// Now select the right area.
	
	if( gotIt )
	{	
		_docuOriginStart = from.cp+first;
		_docuOriginEnd = from.cp+last-5;
 		[docuFile setSel:_docuOriginStart :_docuOriginEnd];
	}
	
	return gotIt;
}

- _addDocuForMethod:aMethod
{
	// This method adds a template for the documentation of a certain method.
	// It DOES corrupt the pasteboard !
	// BUG: We also don't care about sorting ! This is bad !
	
	NXSelPt	from;
	NXSelPt	to;
	int 	i;
	int		paramCount;
	BOOL		gotIt;
	id		tokens;
	id		aString;

	// We have to find the right start-selection inside the documentation
	// first. This depends on the type of method.
	
	[docuFile setSel:0 :0];
	
	if( [aMethod isInstanceMethod] )
			gotIt = [docuFile findText:"Instance Methods" 
							  ignoreCase:NO backwards:NO wrap:NO];
	else	gotIt = [docuFile findText:"Class Methods" 
							  ignoreCase:NO backwards:NO wrap:NO];
	
	if( !gotIt ) return self;

	// Now this is ugly...we should sort the methods alpahbetically !!
	// Buggy because we can only HOPE that nobody adds something behind the
	// "* Methods" section.
	
	[docuFile getSel:&from :&to];
	[docuFile setSel:to.cp+2 :to.cp+2];

	[[NXApp delegate] copyMethodDocuTemplate];
	[docuFile pasteFrom:_tempPb];
	[docuFile setSel:to.cp+2 :to.cp+2];

	[docuFile findText:"_CE_myMethod" 
			  ignoreCase:NO backwards:NO wrap:NO];
	[docuFile replaceSel:[aMethod selectorName]];

	[docuFile findText:"_CE_myMethod" 
			  ignoreCase:NO backwards:NO wrap:NO];
	[docuFile getSel:&from :&to];
	[docuFile replaceSel:[aMethod name]];
	
	// First lets fix the method prefix of the class methods and
	// remove the dulicate prefix"
	
	[docuFile setSel:from.cp-2 :from.cp-1];
	
	if( [aMethod isInstanceMethod] )
	{
		[docuFile findText:"- " ignoreCase:NO backwards:NO wrap:NO];
		[docuFile delete:self];
	}
	else
	{
		[docuFile replaceSel:"+"];
		[docuFile findText:"+ " ignoreCase:NO backwards:NO wrap:NO];
		[docuFile delete:self];
	}
	
	// Now its time to fix all the fonts.
	// Lets tokenize the methods name. This will create a list of
	// all the parts that have a different font.
	// We need to know how many parms the method takes to get a simpler
	// algorithm for coosing the fonts.
	
	tokens = [aMethod methodTokens];
	paramCount = [aMethod numberOfArguments];
	
	for( i=0; i<[tokens count]; i++ )
	{
		// Now select the new token...if we fail to find it..lets quit here.
		
		aString = [tokens objectAt:i];
		if( [docuFile findText:[aString stringValue]
			  		  ignoreCase:NO backwards:NO wrap:NO] == NO ) break;
		
		
		// Depending on the type of token we will choose a different font.
		// First check for casts and then for paramerters.
		
		if( [aString grep:")"] )
			[docuFile setSelFont:[Font newFont:"Times-Roman" size:14]];
		
		// If it is not a cast the situation is a little bit trickier.
		// Imagine a - (BOOL)delegate or a simple -free method.
		// The token will be a part of the selector and still have no
		// ':' attached because there ist no argument						
		
		else if( [aString grep:":"] == NO && paramCount > 0 )
		{
			[docuFile setSelFont:[Font newFont:"Times-Italic" size:14]];
		}
	}
	
	// The tokens are bound to die now.
	
	[[tokens freeObjects] free];
	
	return self;
}

- showCheatWindow:sender
{
	id	aString;
	// Lets choose a defaults selection to clean up the window.
	[docuFile setSel:0 :0];
	aString = [filename fileName];
	[aString cat:"...Cheat Window"];
	[cheatWindow setMiniwindowImage:
					[NXImage findImageNamed:"ObjectPink.rtf.m.h"]];
	[cheatWindow setTitle:[aString stringValue]];
	[aString free];
	
	[cheatWindow makeKeyAndOrderFront:self];
	return self;
}

- showDocumentationOnly:sender
{
	return self;
}

- showInterfaceOnly:sender
{
	return self;
}

- showImplementationOnly:sender
{
	return self;
}

- (int)browser:sender fillMatrix:matrix inColumn:(int)column
{
	// We just fill the matrix with the needed number of cells.
	// According the methods inside the list.
	
	int	i, n;
	
	n = [methodList count];
	for( i=0; i<n; i++ ) [matrix addRow];

	return n;
}

- browser:sender loadCell:cell atRow:(int)row inColumn:(int)column
{
	// Just set the atoms name as the String value.
	id	theMethod;
	
	theMethod = [methodList objectAt:row];
	[cell setStringValue:[theMethod selectorName]];
	[cell setSource:theMethod];
	[cell setLoaded:YES];
	[cell setLeaf:YES];

	return self;
}

- textDidChange:sender
{
	// Here we will make both windows as edited. This is for checking 
	// before saving.
	// But if we are updating the views from the program we won't say that 
	// this are real changes. Only user changes count !!
	// For internal use we are tracking the "real"changes since the last
	// sync between the two windows.
	
	if( _updatingTextViews == NO &&
		_changedTexts == NO )
	{
		[cheatWindow setDocEdited:YES];
		[window setDocEdited:YES];
		_changedTexts = YES;
		_changedWindow = [sender window];
	}
	
	return self;
}

- textDidGetKeys:sender isEmpty:(BOOL)flag
{
	// This is for tracing down changes inside the window and highlighing the
	// character pairs.
	
	[self textDidChange:sender];

	// Now if someone typed a character that should have a corresponding pair
	// we will highlight temp here.
	
	return self;
}

- _highlightPrevious:(char *)aString inView:aText
{
	// this is for highlighing the character pairs.
	// The current char and his counter part get highlighted if is is found
	// If not we will Ping loud.
	 
	NXSelPt	currentFrom;
	NXSelPt	currentTo;
	NXSelPt	from;
	NXSelPt	to;
	BOOL	gotIt;
	
	[aText getSel:&currentFrom :&currentTo];
	gotIt = [aText findText:aString 
				   ignoreCase:NO backwards:YES wrap:NO];

	if( gotIt )
	{
		[aText getSel:&from :&to];
		[aText setSel:currentFrom.cp-1 :currentFrom.cp];
	//	wait( 1 );
		[aText setSel:from.cp :to.cp];
	}
	else //	NXBeep();
	
	[aText setSel:currentFrom.cp :currentTo.cp];
	return self;
}

- textShouldPerformCompletion:sender
{
	id	nameArray;
	id	aMethod;
	id	aString;
	id	stringToFind;
	int	i;
	int	foundItems;
	int	firstMatch;

	// Ok..only inside the methodField we will do completion

	if( [sender delegate] != methodNameField ) return nil;

	// Now get the string and find what we need.

	stringToFind = [MiscString newWithString:[methodNameField stringValue]];
	nameArray = [MiscStringArray new];

	for( i=0; i<[methodList count]; i++ )
	{
		aMethod = [methodList objectAt:i];
		[nameArray addString:[aMethod selectorName]];
		[nameArray addString:[aMethod name]];
	}
	// Now cont the hits...

	foundItems = 0;
	firstMatch = -1;
	
	for( i=0; i<[nameArray count]; i++ )
	{
		aString = [nameArray objectAt:i];
		if( [aString spotOfString:stringToFind caseSensitive:YES] == 0 )
		{
			foundItems++;
			if( firstMatch == -1 ) firstMatch = i;
		}	
	}
	// Now what have we found.. if there are more then one don't do anything.

	if( foundItems == 1 )
		[methodNameField setStringValue:[nameArray stringAt:firstMatch]];

	// Free it all! We hope that the Array frees the 

	[stringToFind free];
	[nameArray free];
	return self;
}

- windowDidBecomeKey:sender
{
	// Lets look only at the window. Depending on which window has become key
	// We have to make some updates between the two views.

	if( _changedTexts == NO ) return self;

	// If the sender is the window where we did make the last changes there
	// is no need to do some updates at all. Just leave it as it is.
	
	if( sender == _changedWindow ) return self;
	
	if( sender == window ) 
	{
		[self reparseMethods:self];
		[self selectNewMethod:self];
	}

	if( sender == cheatWindow )
		[self _silentlySyncWindows];
	
	return self;
}

- windowWillClose:sender
{
	// Lets save the contents if the user wants it and free ourself.

	int	result;

	if( sender != window ) return self;

	// Oh...looks like somebody wants to close the main window...
	// Lets check if we have something to save.

	if( [window isDocEdited] ) 
	{
		result = NXRunAlertPanel( "Close", "Save changes to %s?", 
								"Save", "Don't Save", "Cancel", 
								[filename stringValue] );

		if( result == NX_ALERTOTHER )
			return nil;

		else if( result == NX_ALERTDEFAULT )
			[self save:self];
	}
	// We will tell the window that they have to interesting information
	// anymore. This is because the app checks for these infos before quitting.
	// And if we decide "Don't save"...this should be done correct.

	[window setDocEdited:NO];
	[cheatWindow setDocEdited:NO];
	[cheatWindow deminiaturize:self];
	[cheatWindow performClose:self];

	// Its time to go....
	
	[[NXApp delegate] addToReleasePool:self];
	return self; 
}

@end

/*
 * History: 13.01.95 Buh
 *			
 *
 * Bugs: - ...
 */