ftp.nice.ch/pub/next/graphics/convertors/Convert_PICT.NIHS.bs.tar.gz#/Convert_PICT/Source/shared.subproj/ConvertController.m

This is ConvertController.m in view mode; [Download] [Up]

/***********************************************************************\
Common class for subclass Controller in all Convert programs
Copyright (C) 1993 David John Burrowes

This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 1, or (at your option) any later version.

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

The author, David John Burrowes, can be reached at:
	davidjohn@kira.net.netcom.com
	David John Burrowes
	1926 Ivy #10
	San Mateo, CA 94403-1367
\***********************************************************************/

#import "ConvertController.h"
#import "File.h"
#import  "AbstractConverter.h"		// mainly to stop annoying 'I don't know about this runtime method call'

#import <stdio.h>
#import <stdlib.h>
#import <string.h>
#import <appkit/Panel.h> 
#import <appkit/Window.h>		// for manipulations of ProgressWindow
#import <appkit/Matrix.h>
#import <appkit/Cell.h>
#import <appkit/SavePanel.h>
#import <appkit/OpenPanel.h>
#import <libc.h>				//for getwd
#import <sys/param.h>
//
//	Include the following 2 for drag and drop facility, plus for IAC communications.
//
#import "ConvertListener.h"
#import "ConvertSpeaker.h"
#import <appkit/Application.h>		// To get NXApp (I thought that is what defaults was...)
#import <appkit/publicWraps.h>	// for NXConvertWinNumToGlobal
#import <appkit/graphics.h>		// For NXPing
#import <appkit/publicWraps.h> 	//	For NXBeep

@implementation ConvertController

/*============================================================*\

	The following routines are all for the application delegate responsibilities of this
	class.  Basically dealing with starting up, shutting down, and with allowing the
	user to double click on a file in the workspace and have it open up, and setting up
	for providnig communications between applications

\*============================================================*/

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//	Routine:		appWillInit:
//	Parameters:	our caller
//	Returns:		self
//	Stores:		none
//	Description:
//		This is used to store a specialized speaker and listener in the application.
//		these specialized objects are subclasses of the normal speaker and listener
//		classes, and together implement a method to allow the caller to instruct this
//		class to convert a file.
//	Bugs:
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- appWillInit: sender
{
	Instance		mySpeaker;
	Instance		myListener;
	//
	//	Set up the conversion-aware speaker
	//
	mySpeaker = [[ConvertSpeaker alloc] init];
	[mySpeaker setDelegate:NXApp];
	[NXApp setAppSpeaker:mySpeaker];
	//
	//	Set up the conversion-aware listener
	//
	myListener = [[ConvertListener alloc] init];
	[myListener setDelegate:NXApp];
	[NXApp setAppListener:myListener];
    
	return self;
}


//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//	Routine:		appDidInit:
//	Parameters:	our caller
//	Returns:		self
//	Stores:		none
//	Description:
//		This is for when this class acts as a delegate to the application.  It allows it to
//		do some final initialization stuff.  The big thins is to allow us to register out
//		window so the user will be able to drag icons over it, and thus serve as the
//		drag-and-drop facilities that we support.  This later causes iconEntered: and
//		iconReleasedAt::ok: messages to be sent later.
//		The 2.x code for the drag-and-drop was mainly copied from DrawDocument.
//		The 3.x code is copied straight from NeXT's Window.rtf help doc.   The window
//		registration for 3.0 is much more straight forward.  
//	Bugs:
//		Perhaps I should be using appDidBecomeActive here some?
//	History
//		93.01.24	djb	Modified so will make use of NS 3.0 drag and drop
//		93.07.16	djb	Added services delegate call for filtering (and boy did it take me
//					an embarassingly long time to figure out that I needed this.  duhhh.. =)
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- appDidInit: sender
{
#if ((NSmajor == 2) || (DoNS3DragNDrop == 0))
	//
	//	Allocate a listener instance, and store as one of our instance variables.
	//	Set it up as using our private port, and have the app's speaker register
	//	the window and port.
	//
	listener = [[Listener allocFromZone:[self zone]] init]; // from MY zone?
	[listener setDelegate:self];	// We'll now get the messages from the workspace
	[listener usePrivatePort];
	[listener addPort];
#endif
	[self   allowDragAndDrop];	// do nothing special for NS 3.0 style
	//
	//	Force the window the front, and give it's miniwindow an icon.
	//
	[ProgressWindow setMiniwindowIcon: "Application"];
	[ProgressWindow makeKeyAndOrderFront:self];
	//
	//	93.01.08	djb	Whoops.  Wasn't disabling parts of edit menu right off as needs to be
	//
	[cutCommand		setEnabled: NO];
	[pasteCommand		setEnabled: NO];
	[spellingCommand	setEnabled: NO];
	[checkSpellingCommand	setEnabled: NO];
	//
	//	93.07.18	djb	Added this so any subclasses doing filtering stuff (or serives stuff)
	//				will be happy
	//
	[[NXApp appListener] setServicesDelegate:self];
	
	return self;
}


//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//	Routine:		appWillTerminate:
//	Parameters:	our caller
//	Returns:		self
//	Stores:		none
//	Description:
//		This serves tobalance the init above!
//		Mainly, it unregisters our window information that was used to allow the
//			workspace to have the user drag folders over our window!
//		This code is basically copied directly from DrawDocument.m  =)
//	Bugs:
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- appWillTerminate: sender
{
	[self   refuseDragAndDrop];
	if (listener != NullInstance)
		[listener   free];
	return self;
}


//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//	Routine:		appAcceptsAnotherFile:
//	Parameters:	our caller
//	Returns:		self
//	Stores:		none
//	Description:
//		If we can accept another file to be opened, we return YES.  If we can't we respond
//		NO.  For now, we always return that we'll accept another file.  This just ends
//		up queueing files waiting to be converted.
//	Bugs:
//		To be honest, this makes me a bit uncomfortable.  I feel as though I should be
//		keeping some kinda lock and returning NO if I'm presently converting
//		something.  yet, if I'm converting something, nothing else should be able to
//		be doing any execution here, so I guess it doesn't matter.  
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

- (BOOL)appAcceptsAnotherFile:sender
{
		return YES;
}


//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//	Routine:		app:openFile:type:
//	Parameters:	The name of the file to be converted
//				It's extension
//	Returns:		a flag indicating whether we can deal with the file
//	Stores:		none
//	Description:
//		The user double clicked on a file in the workspace, and now we are asked to open it.
//		All this does, though, is pass the file information on to the first level conversion
//		method and let it try to deal with it.
//	Bugs:
//		Might it be nicer to play a game, like below, where we do a 'perform' to do the
//		conversion after we return YES to the caller?  That begins to seem a bit
//		dangerous to me.  still.....
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

- (int)app:sender openFile:(const char *)filename type:(const char *)aType
{
	CString	tempString;
	//
	//	Ask the convert controlling object to convert this particular file
	//
	[self   ConvertThisFile: filename];
	//
	//	Check if the file was converted alright...
	//
	if ([self   GetErrorCode] == ERR_OK)
	{
		[SourceTitle	setStringValue: "Converted:"];
		[Status	setStringValue: "The conversion is done."];
		[self StoreErrorCode:  ERR_OK AndText: "Conversion process done"];
	}
	else
	{
		[SourceTitle	setStringValue: "Failed:"];
		tempString = [self   GetErrorText];
		[Status	setStringValue: tempString];
		FreeCString(tempString);
	}

	return YES;	// Always report that it was converted ok
}


/*============================================================*\

	The following routines are all for the drag-and-drop facilities which allow the
	user to drag files from the workspace, and drop them over the main window of
	the application.  Fun with InterProcessCommuniction. =)

\*============================================================*/


//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//	Routine:		iconEntered:at:iconWindow:iconX:iconY:iconWidth:iconHeight:pathList:
//	Parameters:	various, mostly not important at the moment
//	Returns:		0 if successfull, something else if not
//	Stores:		none
//	Description:
//		When the user drags a clump of one of more files over our window, we get
//		this message.  We store away the list of files, because we are not given it
//		later on.  =(  Note that we first free any pending set of file paths in the
//		extremely unlikely condition that there is one already there, and then
//		copy the list of paths for use in iconReleasedAt: below
//	Bugs:
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (int)iconEntered:(int)windowNum at:(double)x :(double)y
    iconWindow:(int)iconWindowNum iconX:(double)iconX iconY:(double)iconY
    iconWidth:(double)iconWidth iconHeight:(double)iconHeight
    pathList:(char *)pathList
{
	if (filePaths != NullCString)
		FreeCString(filePaths);
	filePaths = NewCString(strlen(pathList));
	strcpy(filePaths, pathList);
	return 0;
}


//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//	Routine:		iconMovedTo::
//	Parameters:	n/a
//	Returns:		n/a
//	Stores:		n/a
//	Description:
//		I'm NOT implimenting this, though it might still be nice if we wanted some
//		kinda graphical effect, or if we could enforce dropping in only a particular
//		area (I like this idea, rather than the generic droppoing we have now...
//		(heh.  drop on the NeXT icon to convert it TO NeXt, drop it on the Mac
//		to convert TO mac.  =)  However, the below code demonstrates that one can
//		not change the little-copy-cursor-icon-thingie based on a return value
//	Bugs:
//		None, of course, since it is commented out, unless you count that I've grotesquely put it
//		all on one line so one doesn't mistake that it is in fact commented out for now.
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/*- (int)iconMovedTo:(double)x :(double)y { if ((x > 100) || (y > 100)) {NXBeep(); return 0;} else  return 1; } */



//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//	Routine:		iconReleasedAt::ok:
//	Parameters:	x and y coords of the dropping point
//	Returns:		0 if successfull, something else if not
//				a ptr to a boolean flag returned by reference
//	Stores:		none
//	Description:
//		When the user droppes the files, we inform the workspace manager that we
//		can deal with them (always).  We then ask ourselves to do something in
//		some fraction of a second.
//		The reason for all this is simple: processing the files is apt to take some time,
//		and we don't want that icon hanging on the window while we do so.  So,
//		we gotta return soon.  Fact is, we also want to be liberal about what kind
//		of files to accept, since in general many of these conversion programs may not
//		have files with 'proper' extensions (e.g. what isthe extension for a Mac font?).
//		So, we tell the workspace all is well, so the icon goes away.  Soon, we get
//		reminded to ProcessDroppedFiles, which will go through and actually start
//		the processing.
//	Bugs:
//		As with some of the routines above, a bit of me is concerned that if this code is
//		executed while this instance is also doing some conversion, trouble will result.
//		but, then, of course, this isn't a multi-threaded thing, so I shouldn't need to
//		worry about that.  Why does my mind keep trying to think in terms of a
//		processing architecture that doesn't exist yet?
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (int)iconReleasedAt:(double)x :(double)y ok:(int *)flag
{

	[self   perform: @selector(ProcessDroppedFiles)
		with: self
		afterDelay: 100
		cancelPrevious: NO];
	*flag = YES;
	return 0;
}


//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//	Routine:		iconExitedAt::
//	Parameters:	The x and y coordinates at which the icon left the window
//	Returns:		0 if successfull, something else if not
//	Stores:		none
//	Description:
//		If the user drags the icon off the window, we just free the string of
//		paths and reutrn.
//	Bugs:
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (int)iconExitedAt:(double)x :(double)y
{
	if (filePaths != NullCString)
	{
		FreeCString(filePaths);
		filePaths = NullCString;
	}
	return 0;
}


#if (NSmajor == 3)

/*============================================================*\

	The following routines are NeXTSTEP 3.0 routines that will allow for drag and drop
	of filenames.  NOTE that these are equivalent to the routines above, and that both
	are being maintained so I can recompile easily under 2.1 if needed.
	NOTE!  For this to work, the instance that this is must be designated the delagate
	of the progresswindow!
	It Might be more efficient, by the way, instead of registering and undregistering the
	accepted types all the time, to instead set a flag and return NX_DragOperationNone.

\*============================================================*/

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//	Routine:		draggingEntered:
//	Parameters:	the caller
//	Returns:		NX_DragOperationCopy
//	Stores:		none
//	Description:
//		Aside from telling the caller that we'll happily convert whatever files we're
//		being asked about, this also gets and stores the tab delimited list of file names
//		for potential later processing.
//	Bugs:
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (NXDragOperation)draggingEntered:(id)sender
{
	Instance	filenamePasteboard;
	CString	names;
	int		thelength;
	filenamePasteboard = [sender  draggingPasteboard];

	if (filePaths != NullCString)
		FreeCString(filePaths);
	[filenamePasteboard   readType: NXFilenamePboardType data: &names
			length: &thelength];
	filePaths = NewCString(thelength);
	strcpy(filePaths, names);
	[filenamePasteboard   deallocatePasteboardData: names length: thelength];

	return NX_DragOperationCopy;
}


//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//	Routine:		draggingUpdated:
//	Parameters:	the caller
//	Returns:		NX_DragOperationCopy
//	Stores:		none
//	Description:
//		This merely indicates that we'll be happy to try to convert the specified
//		filenames.  We aren't particular aboout what kinds of files they are. =)
//	Bugs:
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (NXDragOperation)draggingUpdated:(id)sender
{
	return NX_DragOperationCopy;
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//	Routine:		prepareForDragOperation:
//	Parameters:	the caller
//	Returns:		YES
//	Stores:		none
//	Description:
//		We will always do the drag operation.
//	Bugs:
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (BOOL)prepareForDragOperation:(id)sender
{
	return YES;
}


//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//	Routine:		performDragOperation:
//	Parameters:	the caller
//	Returns:		YES
//	Stores:		none
//	Description:
//		Convert the file(s).  Indicate that we could.
//	Bugs:
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
 - (BOOL)performDragOperation:(id)sender
{
	[self   perform: @selector(ProcessDroppedFiles)
		with: self
		afterDelay: 100
		cancelPrevious: NO];
	return YES;
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//	Routine:		concludeDragOperation:
//	Parameters:	the caller
//	Returns:		self
//	Stores:		none
//	Description:
//		Do nothing, as there's nothing to clean up, really (we could free the copy of
//		the pathnames, but there's no real urgent need).
//	Bugs:
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- concludeDragOperation:(id)sender
{
	return self;
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//	Routine:		draggingExited:
//	Parameters:	the caller
//	Returns:		returns self
//	Stores:		none
//	Description:
//		If the user drags the icon off the window, we just free the string of
//		paths and reuturn
//	Bugs:
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- draggingExited:(id)sender
{
	if (filePaths != NullCString)
	{
		FreeCString(filePaths);
		filePaths = NullCString;
	}
	return self;
}

#endif


/*============================================================*\

	The following routines are various basic utility routines used by  the above
	methods.  In short:  toggle drag-and-drop on and off,  and process the file names
	when/if they are actually dropped.

\*============================================================*/


//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//	Method:		refuseDragAndDrop
//	Parameters:	none
//	Returns:		self
//	Stores:		none
//	Description:
//		This handy method is used to turn off our request to allow files to be dropped
//		over the application's window.  Clearly, it has conditionally compiled sections
//		for NS 3.0 and 2.x style drag and drop
//	Bugs:
//	History:
//		93.01.24	djb	Added support for 3.0 style drag and drop
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

- refuseDragAndDrop
{
#if ((NSmajor == 2) || (DoNS3DragNDrop == 0))
	unsigned int		windowNum;
	Instance			speaker = [NXApp appSpeaker];

	if (listener != NullInstance)
	{
		[speaker   setSendPort: NXPortFromName(NX_WORKSPACEREQUEST, NULL)];
		NXConvertWinNumToGlobal([ProgressWindow   windowNum], &windowNum);
		[speaker   unregisterWindow: windowNum];
	}
#else
	[ProgressWindow   unregisterDraggedTypes];
#endif

	return self;
}


//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//	Method:		allowDragAndDrop
//	Parameters:	none
//	Returns:		self
//	Stores:		none
//	Description:
//		This turns on the ability to have files droped over our window so we can
//		process them.   It has code fro both NS 2.x and 3.x style implementations
//	Bugs:
//	History:
//		93.01.24	djb	Added support for 3.0 style drag and drop
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

- allowDragAndDrop
{
#if ((NSmajor == 2) || (DoNS3DragNDrop == 0))
	unsigned int		windowNum;
	Instance			speaker = [NXApp appSpeaker];

	NXConvertWinNumToGlobal([ProgressWindow   windowNum], &windowNum);
	[speaker   setSendPort: NXPortFromName(NX_WORKSPACEREQUEST, NULL)];
	[speaker   registerWindow:windowNum toPort:[listener   listenPort]];
#else
	//
	//	We must tell the window to be ready for some dragged file names
	//
	const char *dataType[] = {NXFilenamePboardType};
	[ProgressWindow   registerForDraggedTypes: dataType   count:1];
#endif

	return self;
}



//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//	Routine:		ProcessDroppedFiles
//	Parameters:	none
//	Returns:		self
//	Stores:		none
//	Description:
//		There is, presumably,  string of file names waiting to be processed.  We
//		should process them one at a time, then.  We make a copy of the file name
//		list, and then repeatedly  locate the and start and end of each file path name
//		in the list (they're tab delimited).  For each that we find, we process it.
//	Bugs:
//		We could almost certainly munge the file path string directly, but I feel
//		safer treating it 'right'.
//		If any of the files have tabs in their names, I have no idea what will or will
//		have happened.
//		I chose not to nuke the filePaths at when done here.  This might not be good?
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- ProcessDroppedFiles
{
	CString	allpaths,
			nextpath,
			location;
	CString	tempString;
	
	allpaths = NewCString(strlen(filePaths));
	strcpy(allpaths, filePaths);
	nextpath = allpaths;
	do
	{
		//
		//	Find the next tab.  If we found it, put a null there.  If we did not,
		//	then this is the end of allpaths.  We still have the last file name to
		//	process, so we keep going.  In all cases, convert the file, set up for
		//	the next conversion (if any).  Check whether we reached the end, and
		//	if so quit.
		//
		location = strchr(nextpath, '\t');
		if (location != EndOfCString)
			location[0] = EndOfCString;
		[self   ConvertThisFile: nextpath];
		//
		//	Check if the file was converted alright...
		//
		if ([self   GetErrorCode] == ERR_OK)
		{
			[SourceTitle	setStringValue: "Converted:"];
			[Status	setStringValue: "The conversion is done."];
			[self StoreErrorCode:  ERR_OK AndText: "Conversion process done"];
		}
		else
		{
			[SourceTitle	setStringValue: "Failed:"];
			tempString = [self   GetErrorText];
			[Status	setStringValue: tempString];
			FreeCString(tempString);
		}
		nextpath = location+1;
	}
	while (location != EndOfCString);
	FreeCString(allpaths);
	return self;
}



/*============================================================*\

	The following methods are intended to be used to allow a limited dialog as the
	'slave' of another app that wants us to convert a file.

\*============================================================*/


//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//	Routine:		msgQuit
//	Parameters:	An integer that we fill in YES if we work (we always work =)
//	Returns:		self
//	Stores:		none
//	Description:
//		This will set up a delayed message to the app to have it quit.  We allow PLENTY
//		of time for us to quit and retun to the caller before that method gets invoked.  If
//		we didn't provide enough time (as, for instance the Draw demo app does not), then
//		the caller will hang until time out because we would never return from the call.
//	Bugs:
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (int)msgQuit:(int *)flag
{
	[NXApp   perform:@selector(terminate)  with: NXApp  afterDelay: 300
		cancelPrevious: NO];
	*flag = YES;
	return 0;
}


//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//	Routine:		msgConvert:To:
//	Parameters:	The name of the file to convert from, and the name of the file to convert to.
//	Returns:		self
//	Stores:		none
//	Description:
//		This provides a way for an other application to call us, and get into our
//		main conversion routines below.  This takes the two files, opens them, and jumps
//		to the main conversio method.  If either fails to open, we abort the process.
//		However, the caller never learns of this.
//		These file names, basically, should be complete pathnames
//	Bugs:
//		I still don't understand how we we can be sure that these strings haven't been
//		freed by the time we get to them...
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- msgConvert: (char *) sourceFile  To: (char*) destFile
{
	CString	sourceName = NewCString(strlen(sourceFile));
	CString	destName = NewCString(strlen(destFile));
	CString	dirname;
	CString	filename;
	CString	tempString;
	Instance	source;
	Instance	destination;

	strcpy(sourceName, sourceFile);
	strcpy(destName, destFile);
	//
	//	Get ourselves out of the way of the caller.
	//	Bug: We still display our window briefly.
	//
	[NXApp	hide: self];
	
	source = [self openSourceFile: sourceName];
	if ([self   GetErrorCode] == ERR_OK)
	{
		[StatusTitle	setStringValue: "Status:"];
		[Status	setStringValue: "Converting"];
		[SourceTitle	setStringValue: "Converting:"];
		[SourcePathTitle	setStringValue: "In:"];
		filename = [source GetFilename];
		dirname = [source GetDirectory];
		[SourceFileName	setStringValue: filename];
		[SourcePath		setStringValue: dirname];
		FreeCString (filename);
		FreeCString (dirname);

		destination = [self openDestFile: destName];
		if ([self   GetErrorCode] == ERR_OK)
		{
			[DestTitle	setStringValue: "To:"];
			[DestPathTitle	setStringValue: "In:"];

			filename = [destination GetFilename];
			dirname = [destination GetDirectory];
			[DestFileName	setStringValue: filename];
			[DestPath		setStringValue: dirname];
			FreeCString (filename);
			FreeCString (dirname);
			[self   DoConversionFrom: source  To: destination];
			if ([self   GetErrorCode] == ERR_OK)
			{
				[SourceTitle	setStringValue: "Converted:"];
				[Status	setStringValue: "The conversion is done."];
			}
			else
			{
				[SourceTitle	setStringValue: "Failed:"];
				tempString = [self   GetErrorText];
				[Status	setStringValue: tempString];
				FreeCString(tempString);
			}
		}
		else
		{
			[SourceTitle	setStringValue: "Failed:"];
			[Status	setStringValue: "Could not open the destination file."];
			[source   free];
		}
	}
	else
	{
		[SourceTitle	setStringValue: "Failed:"];
		[Status	setStringValue: "Could not open the source file."];
	}
	FreeCString(sourceName);
	FreeCString(destName);

	return 0;
}



/*============================================================*\

	The rest of the methods here do the stuff that is the focus of the application.
	Specifically, we init, deal with user preference settings, set the indicator on the
	screen to indicate how far done the conversion is, open and close files and check
	that the file to be converted is OK.

\*============================================================*/


//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//	Routine:		init
//	Parameters:	none
//	Returns:		self
//	Stores:		none
//	Description:
//		This overrides the defalut init method.  But it does little else.
//		Other initializations done in AppDidInit, above...
//		Note that subclasses should OVERRIDE this 
//	Bugs:
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- init
{
	CString	temppath,
			fullpath,
			tempPos;
			
	[super init];
	destIsDead = NO;
	//
	//	Clear, if necessary, the string of file names
	//
	filePaths = NullCString;
	//
	//	Provide silly defaul strings.
	//
	ConversionString = "Converting a file to another";
	SourcePrompt = "a file:";
	DestPrompt = "another file:";
	DestExtension = ".a";
	DefaultsOwner = "I have no defaults";
	//
	//	Locate the path where the application exists (if the app is a foo.app type appwrapper,
	//	this includes the foo.app directory).
	//	(if NXArgv doesn't have a full path, build one using getwd)
	//
	AppHome = NewCString(MAXPATHLEN);
	strcpy(AppHome, NXArgv[0]);
	if (AppHome[0] != '/')
	{
		//
		//	We musta been started up from the command line, so use our working
		//	directory to constructe the rest of the path.
		//
		temppath = NewCString(MAXPATHLEN);
		getwd(temppath);
		fullpath = NewCString(strlen(temppath) + 1 + strlen(AppHome));
		sprintf(fullpath, "%s/%s", temppath, AppHome);
		FreeCString(AppHome);
		AppHome = fullpath;
		FreeCString(temppath);
	}
	//
	//	Now, remove the application's name from the end of the path.
	//
	tempPos = strrchr(AppHome, '/');
	tempPos[0] = EndOfCString;
	return self;
}


//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//	Routine:		free
//	Parameters:	none
//	Returns:		self
//	Stores:		none
//	Description:
//		This merely cleans up after ourselves. 
//	Bugs:
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- free
{
	FreeCString(AppHome);
	return self;
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//	Routine:		GetBoolPref
//	Parameters:	The preference that we are to retrieve the value for
//	Returns:		YES or NO, depending on the value of the preference
//	Stores:		None
//	Description:
//		This is used to lookup the value for a preference in the defaults database,
//		and return a Boolean value to the caller.  It's really just provided as a
//		shortcut to the several lines of code needed to retrieve the value, compare
//		it to a string value of YES or NO, and then return the appropriate result.
//	Bugs:
//		If anything goes wrong, the user will get a NO value, and not an error code.
//		Should I be disposing of the string that I get back?
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (Boolean) GetBoolPref: (ConstCString) thePreference
{
	ConstCString	theValue;
	Boolean	result;
	
	theValue = NXGetDefaultValue(DefaultsOwner, thePreference);
	if (strcmp(theValue, "YES") == 0)
		result = YES;
	else
		result = NO;
	
	return result;
}


//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//	Routine:		SetBoolPref:To:
//	Parameters:	The preference that we are to retrieve the value for
//				The Boolean value to assign it
//	Returns:		self
//	Stores:		None
//	Description:
//		This is used to store a boolean value for a preference in the defaults database.
//	Bugs:
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- SetBoolPref: (ConstCString) thePreference To: (Boolean) value
{
	if (value == YES)
		NXWriteDefault(DefaultsOwner, thePreference, "YES");
	else
		NXWriteDefault(DefaultsOwner, thePreference, "NO");
	
	return self;
}


//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//	Routine:		SetPref:To:
//	Parameters:	The preference that we are to retrieve the value for
//				The string value to assign it
//	Returns:		self
//	Stores:		None
//	Description:
//		This is used to store a string value for a preference in the defaults database.
//	Bugs:
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- SetPref: (ConstCString) thePreference To: (CString) value
{
	NXWriteDefault(DefaultsOwner, thePreference, value);
	return self;
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//	Routine:		GetPref:
//	Parameters:	The preference that we are to retrieve the value for
//	Returns:		A copy of the preference string.
//	Stores:		None
//	Description:
//		This retrieves a string from the defaults database associated with the name
//		thePreference, makes a copy of it, and returns it.
//	Bugs:
//	History:
//		93.02.15	djb	Added for the rtf converter's new three option preference.
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (CString) GetPref: (ConstCString) thePreference
{
	ConstCString	theValue;
	CString			result;
	
	theValue = NXGetDefaultValue(DefaultsOwner, thePreference);

	result = NewCString(strlen(theValue));
	strcpy(result, theValue);
	
	return result;
}



//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//	Routine:		displayPreferences: 
//	Parameters:	the object that called us
//	Returns:		self
//	Stores:		none
//	Description:
//		This method is indended to be overridden.  The subclass would just do whatever
//		it needs to do to display preferences for the user..
//	Bugs:
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-displayPreferences: target
{
	return self;
}



//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//	Routine:		SetPercentageDone: 
//	Parameters:	A new percentage to display
//	Returns:		self
//	Stores:		none
//	Description:
//		This allows a calling object to set the percentage of conversion done so far.
//		Note that we only actually ask the percentage  meter to display itself in
//		in increments of 5%, simply because re-drawing after every percent, for
//		example, is EXTREMELY slow.
//	Bugs:
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- SetPercentageDone: (Real) percentage
{
	if (percentage > lastPercent+5)
	{
		[ProgressMeter  SetTo: percentage];
		lastPercent = percentage;
		NXPing();
	}
	return self;
}



//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//	Routine:		openSource and Dest files 
//	Parameters:	the full path of the file to be converted.
//	Returns:		the file object
//	Stores:		errors
//	Description:
//		These tiny methods are provided so a subclass can override what kind of
//		file gets opened for the source or the dest, without overriding the whole
//		of the conversion methods.  Note that they should always open a subclass
//		of File, though.
//	Bugs:
//	Modifications
//		92.11.27	djb	Changed CreateAndOpenFor: to ClearAnd....  Any time this method
//					is called within the framework of this class, the user has already
//					specified they definitely want to use this file, even if it already
//					exists, so we want to just clear the file if it exists already.
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- openDestFile: (roCString) theFile
{
	Instance	fileInstance;
	[self   ResetResults];
	
	fileInstance = [[File alloc] initAndUse: theFile];
	if ([fileInstance   GetErrorCode] == ERR_OK)
		[fileInstance   ClearAndOpenFor: FILE_WRITE];
	if ([fileInstance   GetErrorCode] != ERR_OK)
	{
		[self   StoreErrorCode: ERR_CREATEFAILED
			AndText: "We failed to create the file"];
		[fileInstance   free];
		fileInstance = NullInstance;
	}
	return fileInstance;
}

- openSourceFile: (roCString) theFile
{
	Instance	fileInstance;
	[self   ResetResults];
	
	fileInstance = [[File alloc] initAndUse: theFile];
	if ([fileInstance   GetErrorCode] == ERR_OK)
		[fileInstance   OpenExistingFor: FILE_READ];
	if ([fileInstance   GetErrorCode] != ERR_OK)
	{
		[self   StoreErrorCode: ERR_OPENFAILED
			AndText: "We failed to open the file"];
		[fileInstance   free];
		fileInstance = NullInstance;
	}
	return fileInstance;
}



//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//	Routine:		closeSource and Dest files 
//	Parameters:	the file object to be closed
//	Returns:		self
//	Stores:		errors
//	Description:
//		These Little methods are used to undo the work of the above open methods.
//		they close the file objects, and free them.  Like the above, they are pretty
//		simple and generic, and will probably be overridden by many converters.
//		Note that the dest file closing method has a boolean flag.  If true, we also delete
//		the file (presumably something went wrong.
//	Bugs:
//	History
//		93.01.01	djb	Added tempString and freeing of it, so as not to leak memory.
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

- closeSourceFile: fileInstance
{
	CString	tempString	=  [fileInstance  GetErrorText];
	[self   ResetResults];

	[self   StoreErrorCode: [fileInstance  GetErrorCode]
		AndText: tempString];
	FreeCString(tempString);
	[fileInstance   free];

	return self;
}


- closeDestFile: fileInstance  andDelete: (Boolean) deleteit
{
	CString	tempString	=  [fileInstance  GetErrorText];
	[self   ResetResults];

	if (deleteit == NO)
		[fileInstance   CloseAndSave];
	else
		[fileInstance   CloseAndDelete];
	[self   StoreErrorCode: [fileInstance  GetErrorCode]
		AndText:  tempString];
	FreeCString(tempString);
	[fileInstance   free];

	return self;
}



//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//	Method:		preConversion
//	Parameters:	none
//	Returns:		self
//	Stores:		none
//	Description:
//		This method is called just before calling the conversion routine (i.e. after
//		files have been located and opened alright).  It does any work needed
//		to finish up preparing for the conversion.  At the moment, this means unregistering
//		the window, and setting the progress meter up.  One is welcome to subclass
//		this method, but should probably [super preConversion] before proceeding there.
//	Bugs:
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

- preConversion
{
	[self   refuseDragAndDrop];
	lastPercent = 0;
	[ProgressMeter   ActivateWithGoal: 100];
	return self;
}


//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//	Method:		postConversion
//	Parameters:	none
//	Returns:		self
//	Stores:		none
//	Description:
//		This method is called just after calling the conversion routine has finished.
//		It does some work needed to clean up aspects of this instance after the conversion.
//		At the moment, this means re-registering the window for file-icon-dropping,
//		and making sure the progress meter shows 100% done.  One is welcome to subclass
//		this method, but should probably [super postConversion] before proceeding there.
//	Bugs:
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

- postConversion
{
	[self   allowDragAndDrop];
	[ProgressMeter  SetTo: 100];
	[ProgressMeter   Deactivate];
	return self;
}


//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//	Method:		CheckFileOK
//	Parameters:	none
//	Returns:		self
//	Stores:		none
//	Description:
//		This asks the converter if this is an ok file to be converted.  If no, then it
//		gets the reason why, displays it for the user, and goes into a modal loop to
//		force the user to decide whether to continue or not.  If the user says continue,
//		we continue.
//	Bugs:
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (Boolean) CheckFileOK: fileInst
{
	cancelResult = NO;
	if ( [converterInst   respondsTo:@selector(isThisAGoodFile:)] ) 
	{
		cancelResult = [converterInst   isThisAGoodFile: fileInst];
		if (cancelResult != YES)
		{
			[decisionText	setStringValue: [converterInst   GetCStringFrom: SECOND_RESULT]];
			[decisionWindow   center];
			[decisionWindow   makeKeyAndOrderFront: self];
			[NXApp   runModalFor: decisionWindow];
		}
	}
	else
		cancelResult = YES;
	return cancelResult;
}


//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//	Method:		UserDecided
//	Parameters:	the thing that called us
//	Returns:		self
//	Stores:		none
//	Description:
//		This method gets called when the user clicks on a button in a window during
//		the modal loop set up in CheckFileOK, above.  It gets the result and stores it in
//		cancelResult, closes the window and shuts down the modal loop.
//	Bugs:
//		I dislike modality in general.
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- UserDecided: sender
{
	if ( [sender   tag] == 0)
		cancelResult = NO;
	else
		cancelResult = YES;
	[decisionWindow   close];
	[NXApp   stopModal];
	return self;
}



/*============================================================*\

	At last, here are the true guts of the application.   The following three methods
	deal with processing files.  Briefly, PrepareForConversion: starts from scratch,
	and prompts the user for a file to convert from.  ConvertThisFile: takes a file name
	and determines what file to put the conversion results into.  ConvertFrom:To:
	then just converts between the two files (this last and init are things that
	subclasses MUST override.

\*============================================================*/


//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//	Method:		PrepareForConversion:
//	Parameters:	the object that send this message
//	Returns:		self
//	Stores:		error code
//	Description:
//		This merely creates the converstion necessary object, and sets its
//		preference for using curly quotes according to the value stored in this
//		object.  Then, call the method to start getting information from the user to
//		do the conversion.
//	Bugs:
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- PrepareForConversion: sender
{
	Instance			theOpenPanel = [OpenPanel new];
	CString const *	openedFiles;
	roCString		theDirectory;
	CString			fullPath, theFile;
	CString			tempString;
	Integer			result, pathLength, index;

	[self  ResetResults];
	//
	//	Set up the  display fields
	//
	[SourceFileName	setStringValue: ""];
	[SourcePath	setStringValue: ""];
	[SourceTitle	setStringValue: "Converting:"];
	[SourcePathTitle	setStringValue: "In:"];

	[DestFileName	setStringValue: ""];
	[DestPath	setStringValue: ""];
	[DestTitle	setStringValue: "To:"];
	[DestPathTitle	setStringValue: "In:"];
	
	[StatusTitle	setStringValue: "Status:"];
	[Status	setStringValue: ConversionString];
	//
	//	Prepare the open dialog, and get info from the user.
	//	@@ Bug: it doesn't let the user know what kind of conversion
	//	@@ process is now underway in this dialog
	//
	[cutCommand	setEnabled: YES];
	[pasteCommand	setEnabled: YES];
	[spellingCommand	setEnabled: YES];
	[checkSpellingCommand	setEnabled: YES];
	
	[theOpenPanel allowMultipleFiles: YES];
	[theOpenPanel setPrompt: SourcePrompt]; // use instance variable for prompt
	[theOpenPanel setTitle: "Convert"];
	result = [theOpenPanel runModal];

	[cutCommand	setEnabled: NO];
	[pasteCommand	setEnabled: NO];
	[spellingCommand	setEnabled: NO];
	[checkSpellingCommand	setEnabled: NO];

	if (result != 1)
	{
		[self StoreErrorCode:  ERR_USERABORTED
			AndText: "User canceled when opening a source file!"];
		[Status	setStringValue: "Conversion process canceled at your request!"];
	}
	else
	{
		//
		//	get the set of files, as well as the path to them (or it)
		//
		openedFiles = [theOpenPanel filenames];
		theDirectory = [theOpenPanel directory];
		pathLength = strlen(theDirectory);
		//
		//	For each of the one or more files, allocate a space for the full path.
		//	construct the path using the directory name and file name.
		//	then, call the method to finish up, and dispose of the path we built.
		//	Note: re: the if-then-else to fuse the file name and path
		//	If the directory path is not empty, and it doesn't end in a slash, then
		//	we build the full path from dir/file.  Otherwise, do the same without a
		//	slash.  This last is usefull if someone has chosen a file at the root and we
		//	don't want a double slash (i.e. //myfile). (the check for zero length is really
		//	just for completeness...)
		//
		for (index=0;  openedFiles[index] != NULL; index++)
		{
			theFile = openedFiles[index];
			fullPath = NewCString(strlen(theFile) + 1 + pathLength);
			if ((pathLength > 0) && (theDirectory[pathLength-1] != '/'))
				sprintf(fullPath, "%s/%s", theDirectory, theFile);
			else
				sprintf(fullPath, "%s%s", theDirectory, theFile);
			//
			//	With a path in hand, do the next stage in the converstion.
			//
			[self ConvertThisFile: fullPath];
			FreeCString(fullPath);
		}
		//
		//	Check if the file was converted alright...
		//
		if ([self   GetErrorCode] == ERR_OK)
		{
			[SourceTitle	setStringValue: "Converted:"];
			[Status	setStringValue: "The conversion is done."];
			[self StoreErrorCode:  ERR_OK AndText: "Conversion process done"];
		}
		else
		{
			[SourceTitle	setStringValue: "Failed:"];
			tempString = [self   GetErrorText];
			[Status	setStringValue: tempString];
			FreeCString(tempString);
		}
	}
	return self;
}





//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//	Routine: ConvertThisFile 
//	Parameters: the full path of the file to be converted.
//	Returns:		self
//	Stores:		error code
//	Description:
//		Given a file, you want to convert it!  First, you make sure you can open the file OK,
//		then, you ask the user for what the destination file should be called.  Having done
//		this, you then assure that the destination file can be created without problems,
//		and then you ask the converting routine to convert between the two..
//	Bugs:
//		I find this routine a bit too confusing for my tastes...  (long-winded)
//		We retrieve some strings below, but can't free them.
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- ConvertThisFile: (roCString) theFile
{
	roCString	destname;
	Instance		sourceFile, destFile;
	Integer		outcome;
	PositiveInteger	extenLen, destLen;
	CString		filename, directory, tempstring, basename;
	Instance		theSavePanel;
	Boolean		goodfile;
	CString		tempString;
	//
	[self ResetResults];
	//
	//	Set up the  display fields (we don't always go through prepare for conversion, above.
	//
	[SourceFileName	setStringValue: ""];
	[SourcePath	setStringValue: ""];
	[SourceTitle	setStringValue: "Converting:"];
	[SourcePathTitle	setStringValue: "In:"];

	[DestFileName	setStringValue: ""];
	[DestPath	setStringValue: ""];
	[DestTitle	setStringValue: "To:"];
	[DestPathTitle	setStringValue: "In:"];
	
	[StatusTitle	setStringValue: "Status:"];
	[Status	setStringValue: ConversionString];
	//
	//	If possible,, open the source file, and update the display. 
	//	
	sourceFile = [self openSourceFile: theFile];
	if ([self   GetErrorCode] != ERR_OK)
	{
		[SourceFileName	setStringValue: theFile];
		[SourcePath	setStringValue: ""];
		tempString = [self   GetErrorText];
		[Status	setStringValue: tempString];
		FreeCString(tempString);
	}
	else
	{
		goodfile = [self  CheckFileOK: sourceFile];
		if (goodfile == NO)
		{
			filename = [sourceFile GetFilename];
			directory = [sourceFile GetDirectory];
			[SourceFileName	setStringValue: filename];
			[SourcePath		setStringValue: directory];
			FreeCString (filename);
			FreeCString (directory);
			[Status	setStringValue: "The conversion was canceled at your request"];
			[self   StoreErrorCode: ERR_USERABORTED  AndText: "The conversion was canceled at your request"];
		}
		else
		{
			////
			//	Get descriptions of the file's name, and use these to build a message
			//	on the screen for the user, and a prompt in the save panel
			////
			filename = [sourceFile GetFilename];
			directory = [sourceFile GetDirectory];
			basename = [sourceFile GetBasename];
			//
			//	Update the user about current events.
			//
			[SourceFileName	setStringValue: filename];
			[SourcePath	setStringValue: directory];
			[Status	setStringValue:  "OK"];
			//
			//	Then, build a name for the file we want to convert to
			//
			[cutCommand	setEnabled: YES];
			[pasteCommand	setEnabled: YES];
			[spellingCommand	setEnabled: YES];
			[checkSpellingCommand	setEnabled: YES];

			theSavePanel = [SavePanel   new];
			[theSavePanel setPrompt: DestPrompt];
			[theSavePanel setTitle: "Convert to"];
			tempstring = NewCString(strlen(basename) + strlen(DestExtension));
			sprintf(tempstring, "%s%s", basename, DestExtension);
			//	93.01.01	djb	Added this to free the leak with basename
			FreeCString(basename);
			outcome = [theSavePanel runModalForDirectory: directory file: tempstring];
			
			FreeCString (tempstring);
			FreeCString (filename);
			FreeCString (directory);

			[cutCommand	setEnabled: NO];
			[pasteCommand	setEnabled: NO];
			[spellingCommand	setEnabled: NO];
			[checkSpellingCommand	setEnabled: NO];

			////
			//	Now, check the output of the call to runModalForDirectory:file.  If
			//	the user canceled, tell the user this.  Otherwise, continue processing.
			////
			if (outcome != 1)
			{
				[self StoreErrorCode:  ERR_USERABORTED
					AndText: "Conversion process canceled at your request"];
				[SourceTitle	setStringValue: "Canceled:"];
				[Status	setStringValue:  "Conversion process canceled at your request!"];
			}
			else
			{
				////
				//	Open the destination file.  If we can't, display why, otherwise show the
				//	user what the destination file name and path is.   Finally, CONVERT
				//	the bloody file!
				////
				//
				//	See if the file ends with the proper extension.  If so, copy it into a
				//	temporary string.  Otherwise, tack the extension onto it...
				//
				destname = [theSavePanel filename];
				extenLen = strlen(DestExtension);
				destLen = strlen(destname);
				if (strcmp(&destname[destLen - extenLen], DestExtension) == 0)
				{
					tempstring = NewCString(destLen);
					strcpy(tempstring, destname);
				}
				else
				{
					tempstring = NewCString(destLen+extenLen);
					sprintf(tempstring, "%s%s", destname, DestExtension);
				}
				//
				//	Make sure that this ne name doesn't refer to the source file
				//
				if ([sourceFile	  SameFileAs:  tempstring] == YES) 
				{
					[SourceTitle	setStringValue: "Error:"];
					[Status	setStringValue:  "These refer to the same file.  This is not allowed!" ];
	
					[self StoreErrorCode:  ERR_TRIEDTOOVERWRITE
						AndText: "Attempt to overwrite source file with destination file! Conversion NOT done."];
					[sourceFile	Close];
					FreeCString(tempstring);
				}
				else
				{
					destFile = [self openDestFile: tempstring];
					FreeCString(tempstring);
					if ([self    GetErrorCode] != ERR_OK)
					{
						[SourceTitle	setStringValue: "Failed:"];
						tempString = [self   GetErrorText];
						[Status	setStringValue: tempString];
						FreeCString(tempString);
						[sourceFile	Close];
					}
					else
					{
						//
						//	we opened the dest file.  So, tell the user, and continue.
						//
						filename = [destFile GetFilename];
						directory = [destFile GetDirectory];
						[DestFileName	setStringValue: filename];
						[DestPath		setStringValue: directory];
						FreeCString (filename);
						FreeCString (directory);
						[self  DoConversionFrom: sourceFile  To: destFile];
					}
				}
			}
		}
	}

	return self;
}


//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//	Routine:		DoConversionFrom:To: 
//	Parameters:	The file to be converted from, and the file to be converted to
//	Returns:		self
//	Stores:		none
//	Description:
//		This is called once we have a source and destination file object opened.
//		It does any preparation needed by the application, then calls the convertFrom:To:
//		method, and cleans up afterwards.
//		Note: tempInst is a kludge that was added for things like rtfConverter which
//		destroys the destination file and creates a new one.  This allows us to deal with
//		this situation.
//	Bugs
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- DoConversionFrom: sourceFile  To: destinationFile
{
	Instance		tempInst;
	Integer		tempError;
	CString		errorText;
	//
	//	Do some preparing of the conversion window...
	//
	[self   preConversion];
	//
	//	Dim the menus during any conversion process
	//
	[quitCommand	setEnabled: NO];
	[hideCommand	setEnabled: NO];
	[infoCommands	setEnabled: NO];
	[editCommands	setEnabled: NO];
	[servicesCommands setEnabled: NO];
	[windowsCommands setEnabled: NO];

	[Status	setStringValue:  "Converting"];
	tempInst = [self ConvertFrom: sourceFile To: destinationFile];
	if (destIsDead == YES)
		destinationFile = tempInst;
	destIsDead = NO;
	[quitCommand	setEnabled: YES];
	[hideCommand	setEnabled: YES];
	[infoCommands	setEnabled: YES];
	[editCommands	setEnabled: YES];
	[servicesCommands setEnabled: YES];
	[windowsCommands setEnabled: YES];
	//
	//	Close the files, now that we are done converting.
	//
	tempError = [self GetErrorCode];
	errorText = [self GetErrorText];
	if (tempError < ERR_OK)
		[self   closeDestFile: destinationFile  andDelete: YES];
	else
		[self   closeDestFile: destinationFile  andDelete: NO];
	[self   closeSourceFile: sourceFile];

	[self   postConversion];

	[self   StoreErrorCode: tempError AndText: errorText];
	FreeCString(errorText);
	return self;
}


//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//	Routine:		ConvertFrom:To: 
//	Parameters:	The file to be converted from, and the file to be converted to
//	Returns:		self
//	Stores:		none
//	Description:
//		This is a method indended to be subclassed.  It would do the actual conversion
//		of the source to destination file.
//	Bugs
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- ConvertFrom: sourceFile To: destinationFile
{
	//
	//	If our converter instance has the ReportTo: method, then we tell it to report to it
	//
	if ( [converterInst respondsTo:@selector(ReportTo:)] ) 
		[converterInst   ReportTo: self];
	[Status	setStringValue:  "Converting"];
	return self;
}

@end

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.