/***********************************************************************\
Controller for Convert PICT which converts graphics from PICT to eps formats.
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
\***********************************************************************/

/*
====================================================================
	This is $Revision: 1.10 $ of this file
	It was last modified by $Author: death $ on $Date: 93/04/04 23:30:20 $
Note that this file was created while using the New Century Schoolbook Roman typeface.  You may find that some things line up strangely if you don't use that family.

	NOTE: This has a preference called something like USECLUTS.  This is a feature that
	was dropped before the 1.0 version.  I'm leaving the hooks in just in case I later wish to
	implement it.  The gist of the feature was: given a bitmap or pattern with a color lookup
	table, substitute the looked up color values into the bitmap for the original indices.  This
	would have the effect of making the resulting file ENORMOUSLY larger in size than
	the source.  But, it would also be printable or displayable on a PS level 1 device (that had
	the common color extensions, of course)

$Log:	PictController.m,v $
Revision 1.10  93/04/04  23:30:20  death
Sun Apr  4 23:30:20 PDT 1993

Revision 1.9  93/01/09  21:07:17  death
Sat Jan  9 21:07:17 PST 1993

Revision 1.8  93/01/01  11:51:24  death
Fri Jan  1 11:51:24 PST 1993

Revision 1.7  92/12/31  15:33:52  death
Thu Dec 31 15:33:52 PST 1992

Revision 1.6  92/12/05  23:06:22  death
Sat Dec  5 23:06:21 PST 1992

 ====================================================================
 */
 
 #import "PictController.h"
#import "PSFile.h"
#import "PICTFile.h"
#import "PictConverter.h"
#import <appkit/Matrix.h>	// For matrix stuff for preferences setting
#import "string.h"
#import <appkit/Application.h>	// For NXApp
#import <appkit/Listener.h>	// For NXApp


@implementation	PictController

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//	Routine:		init
//	Parameters:	none
//	Returns:		self
//	Stores:		none
//	Description:
//		This overrides the defalut init method.  and sets up some strings used for
//		interacting with the user. We also create the pict converter instance
//		needed later.
//	Bugs:
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- init
{
	static NXDefaultsVector theDefaults =
	{
		{PACKIMAGE, "YES"},
		{USECLUTS, "NO"},
		{VERBOSEPS, "NO"},
		{CONVERTCHARS, "NO"},
		{CONVERTPICC, DISCARDALLPICC},
		{NULL, NULL}
	};
	CString	tempConvertString;

	[super init];
	//
	//	Provide silly default strings.
	//
	ConversionString = "Converting Mac PICT to NeXT eps";
	SourcePrompt = "PICT file:";
	DestPrompt = "EPS file:";
	DestExtension = ".eps";
	DefaultsOwner = "PICTConverter";

	NXRegisterDefaults(DefaultsOwner, theDefaults);
	
	PackImage = [self   GetBoolPref: PACKIMAGE];
	FoldInCLUTs = [self   GetBoolPref: USECLUTS];
	UseVerbosePS = [self   GetBoolPref: VERBOSEPS];
	ConvertAllChars = [self  GetBoolPref: CONVERTCHARS];
	tempConvertString =  [self  GetPref: CONVERTPICC];

	if (strcmp(tempConvertString, DISCARDALLPICC) == 0)
		CommentOperation = DiscardPicComments;
	else if (strcmp(tempConvertString, CONVERTALLPICC) == 0)
		CommentOperation = ConvertPicComments;
	else	// An error occurred.  But, let's just be silently lazy for the moment:
		CommentOperation = ConvertPicComments;
	FreeCString(tempConvertString);

	converterInst = [[PictConverter   alloc] init];
	return self;
}



//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//	Routine:		displayPreferences: 
//	Parameters:	the object that called us
//	Returns:		self
//	Stores:		none
//		This is called whenever we need to bring the preferences panel onto the screen.
//		Using the value in our several preferences variables, we set the buttons in
//		the preferences panel to reflect the current values, and then shows the panel.
//	History:
//		93.07.18	djb	Modified for Convert pict comment stuff.
//	Bugs:
//		As pointed out elsewhere, we don't really need to do this after the first time,
//		since the buttons keep their own state.  So this paradigm needs revising!!
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-displayPreferences: target
{
	//
	//	Get the default preference values, and compare judge what buttons to
	//	highlight for the user.
	//
	if (PackImage == YES)
		[CompressButton  selectCellWithTag: 1];
	else
		[CompressButton  selectCellWithTag: 0];

	if (FoldInCLUTs == YES)
		[ColorTableButton  selectCellWithTag: 1];
	else
		[ColorTableButton  selectCellWithTag: 0];

	if (UseVerbosePS == YES)
		[PSCodeSetButton  selectCellWithTag: 1];
	else
		[PSCodeSetButton  selectCellWithTag: 0];

	if (ConvertAllChars == YES)
		[ConvertAllCharsButton  selectCellWithTag: 1];
	else
		[ConvertAllCharsButton  selectCellWithTag: 0];

	switch  (CommentOperation)
	{
		case ConvertPicComments:
			[PicCommentItem  selectCellWithTag: PICCCONVERTTAG];
			break;
		case DiscardPicComments:
			[PicCommentItem  selectCellWithTag: PICCDELETETAG];
			break;
		default:
			//	Something is wrong.  But rather than let the user know, since this
			//	isn't a commercial quality app, we just silently select the safest option.
			[PicCommentItem  selectCellWithTag: PICCCONVERTTAG];
			break;
	}
	//
	//	Display the panel for the user.
	//
	[prefPanel	makeKeyAndOrderFront:self];
	return self;
}


//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//	Routine:		ChangePacking: 
//	Parameters:	the object that called us
//	Returns:		self;
//	Description:
//		This method gets called whenever the user clicks on a button to change
//		the setting of whether the output should be packed or not.  If the button they
//		clicked on had a key of '1', then we will pack the image, otherwise we don't.
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-ChangePacking: sender
{
	if ( [[sender selectedCell] tag] == 1)
		PackImage = YES;
	else
		PackImage = NO;

	[self   SetBoolPref: PACKIMAGE To: PackImage];

	return self;
}


//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//	Routine:		ChangeCLUTuse: 
//	Parameters:	the object that called us
//	Returns:		self
//	Description:
//		This method gets called whenever the user clicks on a button to change
//		the setting of whether we should include wrap the clut information into a
//		color bitmap being written out, or if we should write the clut separately.
//		See the bitmap processing routines for more info.
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-ChangeCLUTuse: sender
{
	if ( [[sender selectedCell] tag] == 1)
		FoldInCLUTs = YES;
	else
		FoldInCLUTs = NO;

	[self   SetBoolPref: USECLUTS To: FoldInCLUTs];

	return self;
}


//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//	Routine:		ChangePSCodeSet: 
//	Parameters:	the object that called us
//	Returns:		self
//	Description:
//		When the user asks to change which PS code set to use, this method is
//		invoked.  If the button had a tag of one, we assume they want to use the
//		'verbose' (commented) set of PS routines when writing out.  Otherwise, then
//		want the dense but uncommented set.
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-ChangePSCodeSet: sender
{
	if ( [[sender selectedCell] tag] == 1)
		UseVerbosePS = YES;
	else
		UseVerbosePS = NO;

	[self   SetBoolPref: VERBOSEPS To: UseVerbosePS];

	return self;
}



//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//	Routine:		ChangeCharConversion: 
//	Parameters:	the object that called us
//	Returns:		self
//	Description:
//		Sets a flag that will govern whether we conver all, or only known type families.
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-ChangeCharConversion: sender
{
	if ( [[sender selectedCell] tag] == 1)
		ConvertAllChars = YES;
	else
		ConvertAllChars = NO;

	[self   SetBoolPref: CONVERTCHARS To: ConvertAllChars];

	return self;
}


//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//	Routine:		ChangeCommentProcessing: 
//	Parameters:	the object that called us
//	Returns:		self
//	Description:
//		This is called, presumably, by the user's clicking on a button in the interface.
//		That button click indicates a desire to change what the default conversion
//		for PicComments should be.  So, figure out which button they pressed
//		(e.g. convert the comments, or discard them) and set the default
//		accordingly.
//	Notes:
//		At present this supports 2 forms of pic comment conversion.  I've tried to leave
//		all the constants and stuff about PicComments general enough to allow one to
//		add more options later (I can forsee, for instance, one that only strips out the
//		bulky and unneeded PS code but leaves the rotated text instructions)
//	History
//		93.07.18	djb	created.
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- ChangeCommentProcessing: sender
{
	if ( [[sender selectedCell] tag] == PICCCONVERTTAG)
	{
		CommentOperation = ConvertPicComments;
		[self   SetPref: CONVERTPICC To: CONVERTALLPICC];
	}
	else
	{
		CommentOperation = DiscardPicComments;
		[self   SetPref: CONVERTPICC To: DISCARDALLPICC];
	}

	return self;
}



//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//	Routine:		openSourceFile: 
//	Parameters:	the name of the file we are to open
//	Returns:		the new file object
//	Stores:		error
//	Description:
//		This simply attempts to open the source file.  If we are successfull, we
//		return it to the caller, otherwise we return an error.
//	Bugs:
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- openSourceFile: (roCString) theFile
{
	Instance	fileInstance;
	[self   ResetResults];
	
	fileInstance = [[PICTFile alloc] initAndUse: theFile];
	if ([fileInstance   GetErrorCode] == ERR_OK)
		[fileInstance   OpenExistingFor: FILE_READ];
	if ([fileInstance   GetErrorCode] != ERR_OK)
	{
		if ([fileInstance   GetErrorCode] == ERR_UNKNOWNVERSION)
			[self   StoreErrorCode: ERR_CANTOPEN
				AndText: "Aborting because that doesn't seem to be a PICT file."];
		else
			[self   StoreErrorCode: ERR_CANTOPEN
				AndText: "Unable to open that file for some reason."];
		[fileInstance   free];
		fileInstance = NullInstance;
	}
	return fileInstance;
}


//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//	Routine:		openDestFile: 
//	Parameters:	the name of the file we are to open
//	Returns:		the new file object
//	Stores:		error
//	Description:
//		This simply attempts to open the destination file.  If we are successfull, we
//		return it to the caller, otherwise we return an error.
//	Bugs:
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- openDestFile: (roCString) theFile
{
	Instance	fileInstance;
	[self   ResetResults];
	
	fileInstance = [[PSFile alloc] initAndUse: theFile];
	if ([fileInstance   GetErrorCode] == ERR_OK)
		[fileInstance   ClearAndOpenFor: FILE_WRITE];
	if ([fileInstance   GetErrorCode] != ERR_OK)
	{
		[self   StoreErrorCode: ERR_CREATEFAILED
			AndText: "We were not able to create/open that file"];
		[fileInstance   free];
		fileInstance = NullInstance;
	}
	return fileInstance;
}


//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//	Routine:		FilterPICT:userData:error:
//	Parameters:
//		The data to be filtered
//		A pointer to an error string for us to return.
//	Returns:		self
//	Stores:		none
//	Description:
//		Yet Another Hack (YAH(tm)).
//		This method can be summarized quickly: This provides the code to implement a
//		filter service Given pict data or the name of a pict file on the pasteboard, this will
//		turn it into eps data on the pasteboard and return it to the caller.  By and large, the
//		operation is pretty straightforward.  The one weird thing in all this is that in the case
//		of PICT data on the pasteboard, we create a temporary file and write the pasteboard
//		out to it.  And, then, we create another temporary file to hold the results of the
//		conversion which, when done, we read in and return to the caller on the pasteboard.
//		Why not just pass pointers to chunks of RAM?  Easy answer: the PICT converter only
//		understands the File object, and subclasses, and I've not yet written one which
//		uses ram as it's 'file'.  So, the quick and dirty soltuion was to massage the source
//		data into a form that class could handle
//	Bugs
//		93.07.18	This doesn't deal well if the 'PICT' data doesn't look like PICT data at all.
//		93.07.18	At the moment, we hide the app at the start of this method, and don't
//				re-display it.  The theory here is that a filter should be out of sight,
//				mostly, since it's mainly just behind-the-scenes anyway.  However,
//				this whole routine brings up three issues.  First, this whole 'architecture'
//				for getting set up to call the PICT Converter is stressed a bit...  It feels like
//				there's too much repeated material scattered around, because the original
//				structuring (user chooses both source and destination files by way of
//				menus) didn't flex well with the addition of drag and drop, speaker/listener
//				stuff with Convert RTF, and now this.
//				Second, we might want to show the conversion window at some point,
//				because long filterings can take, well, a long time, and watching the
//				percent indicator would make things reassurring.  For reasons I do'nt
//				understand, though (nor have I devoted lots of time to investigating)
//				that beastie isn't appearing anyway right now.
//				Thirdly, maybe the best solution to some of the 'hide show' issues is to not
//				display a window at the outset, and have it only appear when menu coices
//				are made (unless they need drag and drop?)
//				Oh, and of course, we write everything out to files so we can convert it.
//				I suspect we need some kinda subclass of PICT file to deal with in-ram data
//	History
//		93.07.18	djb	Created
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

- FilterPICT: myPasteboard
	userData: (ConstCString) userData
	error: (CString*) msg;
{
	char*			sourceData;
	int				sourceLength;
	Integer			destLength;
	ByteString		pictHeader;
	CString			finalString;
	CString			tempString;
	Instance			result;
	Instance			destFile;
	Instance			sourceFile;
	NXAtom			filterableTypes[5];
	NXAtom			sourceType;
	NXAtom			returnTypes[2];
	Integer			errorValue;
	//
	//	Hide the application.  We're a Filter, darnit! not a gaudy user interface thingie!
	//
	[NXApp	hide: self];
	//
	//	Set up array of tye pasteboard types we can deal with.
	//
	filterableTypes[0] = NXCreateFilenamePboardType("PICT");
	filterableTypes[1] = NXCreateFilenamePboardType("pict");
	filterableTypes[2] = NXCreateFilenamePboardType("macpict");
	filterableTypes[3] = "PICT";
	filterableTypes[4] = NULL;
	//
	//	Find a type that we can understand from the pasteboard.
	//	LAZY ALERT:  Under some unusual circumstances, this could probably
	//	return a null pointer.  Or something.  I'm not checking for this, but should be!
	//
	sourceType =
		[myPasteboard
			findAvailableTypeFrom: filterableTypes
			num:4
		];
	//
	//	Now that we, of course, found a data type we understand, read it in it in.
	//
	result =
		[myPasteboard
			readType: sourceType
			data: &sourceData
			length: &sourceLength
		];
	//
	//	If we failed to read the data in, then return an error message, a la one of
	//	NeXT's examples.  Otherwise, move on to setting up for the filtering
	//
	if (result == nil)
		*msg = "Error: Could not get the data so as to filter it.";
	else
	{
		//
		//	Set up the PICT file to read the data from.  If the stuff was on the
		//	pasteboard, then copy it to a temporary file.  If, however, the
		//	pasteboard was a filename, just open the specified file.
		//	Because the PICTFile object is presently read-only (sigh), then we must
		//	open a temporary generic file, dump the data in, close and then reopen it.
		//	What a wonder of efficiency!
		//
		if (strcmp(sourceType, "PICT") == 0)
		{
			sourceFile =  [[File  alloc] initAndUseTemporary];
			[sourceFile   CreateAndOpenFor: FILE_WRITE];
			//
			//	Write out a bogus 512 byte header, since PICT files have to have
			//	this.  Seems like a silly way to get 512 junk bytes, byt also seems
			//	faster than writing out 512 individual bytes or 128 longwords
			//
			pictHeader = NewByteString(512);
			[sourceFile   Write: 512 BytesFrom: pictHeader];
			FreeByteString(pictHeader);
			[sourceFile   Write: sourceLength BytesFrom: sourceData];
			tempString = [sourceFile   GetPathname];
			[sourceFile   CloseAndSave];
			[sourceFile   free];

			sourceFile =  [[PICTFile  alloc] initAndUse: tempString];
			[sourceFile   OpenExistingFor: FILE_READ];
			FreeCString(tempString);
		}
		else	// One of the various PICT file types (.macpict, .pict, or .PICT)
		{
			sourceFile = [[PICTFile  alloc] initAndUse: sourceData];
			[sourceFile   OpenExistingFor: FILE_READ];
		}
		//
		//	LAZY ALERT: Naturally, there were no errors
		//
		//
		//	Now, we can just open a temporary file for the converted data,
		//	set up the user interface (strings in the window, and disabling
		//	of the menu items), and call the conversion routine.
		//
		destFile = [[PSFile  alloc] initAndUseTemporary];
		[destFile   CreateAndOpenFor: FILE_READWRITE];

		[SourceFileName	setStringValue: "PICT data"];
		[SourceTitle		setStringValue: "Filtering:"];
		[SourcePathTitle	setStringValue: ""];
		[SourcePath		setStringValue: ""];
		[DestFileName	setStringValue: "eps data"];
		[DestTitle		setStringValue: "To:"];
		[DestPathTitle	setStringValue: ""];
		[DestPath		setStringValue: ""];
		[StatusTitle		setStringValue: "Status:"];
		[Status			setStringValue:  "Filtering"];

		[quitCommand	setEnabled: NO];
		[hideCommand	setEnabled: NO];
		[infoCommands	setEnabled: NO];
		[editCommands	setEnabled: NO];
		[servicesCommands setEnabled: NO];
		[windowsCommands setEnabled: NO];

		[self  ConvertFrom: sourceFile To: destFile];
		errorValue =  [self   GetErrorCode] ;
		//
		//	Restore the menu items.(isn't there a nicer way?)
		//
		[quitCommand	setEnabled: YES];
		[hideCommand	setEnabled: YES];
		[infoCommands	setEnabled: YES];
		[editCommands	setEnabled: YES];
		[servicesCommands setEnabled: YES];
		[windowsCommands setEnabled: YES];
		//
		//	Do different actions, depending on whether we succeeded or not
		//
		if (errorValue == ERR_OK)
		{
			//
			//	Well, amazing as it may seem, we seem to have succeeded in filtering the
			//	data.  Get the results back onto the pasteboard and update the gui
			//	(just in case someone turned off the 'hide' of the app above)
			//
			destLength = [destFile  FileSize];
			[destFile   MoveTo: 0];
			finalString = NewByteString(destLength);
			[destFile   Read: destLength BytesInto: finalString];
	
			returnTypes[0] = NXPostScriptPboardType;
			[myPasteboard declareTypes: returnTypes num: 1 owner: nil]; 
			[myPasteboard writeType: NXPostScriptPboardType
					data: finalString length: destLength];
			FreeByteString(finalString);

			[SourceTitle	setStringValue: "Filtered:"];
			[Status	setStringValue: "Filtered successfully."];
			[self StoreErrorCode:  ERR_OK AndText: "Filtering process done"];
		}
		else
		{
			//
			//	Return a pretty trivial eps file, so nothing barfs if it looks. Set an
			//	error return string, modify the user interface.
			//
			finalString = "%!PS-Adobe-3.0 EPSF-3.0\n%%BoundingBox: 0 0 0 0\n%%EndComments";
			returnTypes[0] = NXPostScriptPboardType;
			returnTypes[1] = NULL;
			[myPasteboard declareTypes: returnTypes num: 1 owner: nil]; 
			[myPasteboard writeType: NXPostScriptPboardType
					data: finalString length: strlen(finalString)];
			*msg = "Error: Could not filter that (was it really PICT data?).";

			[SourceTitle	setStringValue: "Failed:"];
			tempString = [self   GetErrorText];
			[Status	setStringValue: tempString];
			FreeCString(tempString);
		}
		//
		//	Close the destination file, and deal with the source (if we created it,
		//	delete it.  It we did not, then just close it)
		//
		[destFile   CloseAndDelete];

		if (strcmp(sourceType, "PICT") == 0)
			[sourceFile   CloseAndDelete];
		else
			[sourceFile   Close];
		//
		//	Unlike the NeXT example, we're going to actually release the
		//	data when done.
		//
		[myPasteboard  deallocatePasteboardData: sourceData  length: sourceLength ];
	}

	return self;

}



//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//	Routine:		ConvertFrom:To: 
//	Parameters:
//		The file to be converted from
//		the file to be converted to
//	Returns:		self
//	Stores:		error
//	Description:
//		This asks the super class to do any initalizing it needs to, and then goes
//		ahead and asks the converter object to go at it.
//		Before we do the conversion, though, we turn off the conversion menu item,
//		set up a string describing where to find the PS source code, and
//		set a couple settings in the converter instance.  Once we are finished, we
//		re-enable the menu item, and store any error information.
//	Bugs
//	History
//		93.07.18	djb	Added nxping() cal.  This should help to ensure that all the
//					gui updates of menus, etc, have been flushed to the screen.. 
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- ConvertFrom: sourceFile To: destinationFile
{
	CString		CodeSet = NewCString(strlen(AppHome)+20);
	CString		tempString;
	[self   ResetResults];
	[ConvertCommand	setEnabled: NO];
	[super   ConvertFrom: sourceFile To: destinationFile];

	if (UseVerbosePS == YES)
		sprintf(CodeSet, "%s/%s", AppHome, "CommentedPSCode/");
	else
		sprintf(CodeSet, "%s/%s", AppHome, "UncommentedPSCode/");
	
	[converterInst	SetImagePacking: PackImage UsingPSIn: CodeSet];
	[converterInst	SetConvertAllChars: ConvertAllChars];
	[converterInst	SetPicCommentConversion: CommentOperation];
	NXPing();
	[converterInst	ConvertPICTfile: sourceFile ToEPSfile: destinationFile];
	[ConvertCommand	setEnabled: YES];
	FreeCString(CodeSet);
	
	if ( [converterInst   GetErrorCode] < ERR_OK)
	{
		tempString =   [converterInst   GetErrorText];
		[self   StoreErrorCode:  [converterInst   GetErrorCode]
			AndText: tempString];
		FreeCString(tempString);
	}
	else
		[self   StoreErrorCode: ERR_OK
			AndText: "Converted Successfully" ];
	return self;
}




@end