ftp.nice.ch/pub/next/text/rtf/RTF.1.10.s.tar.gz#/rtf/lib-mac/wrapper.c

This is wrapper.c in view mode; [Download] [Up]

/*
 * wrapper.c -- main guts of RTF translator wrapper, Macintosh version (THINK C).
 *
 * The wrapper tells RTFMsg() and RTFPanic() to route output through Msg() and
 * Panic() below.  Any output sent to either of these will end up in the current
 * .err file, if there is one.  Msg() also sends the output to a TransDisplay
 * window so the user has a running log of diagnostic output.  For messages that
 * should *only* appear in the log window (and not the .err file), the wrapper
 * uses DisplayXXX() calls directly.
 *
 * errorFileName must be set empty whenever there's no error file in use.
 * This is necessary so Msg() and Panic() don't try to write to a file
 * that's not there.
 */

# include	<stdio.h>

# include	<GestaltEqu.h>

# include	"TransSkel.h"
# include	"TransDisplay.h"

# include	"rtf.h"
# include	"rtf-mac.h"


# define	maxPathLen		256

# define	tokensPerNullEvent	100		/* # tokens to process in Idle () */


typedef enum		/* Apple menu item numbers */
{
	aboutApp = 1,
	appHelp
};


typedef enum		/* File menu item numbers */
{
	openDoc = 1,
	/* --- */
	quitApp = 3
};


typedef enum		/* Edit menu item numbers */
{
	undo = 1,
	/* --- */
	cut = 3,
	copy,
	paste,
	clear,
	/* --- */
	preferences = 8	/* present only if prefsInEditMenu flag set */
};


static FSSpec	fssError = { 0, 0, "\p" };
static char		*errorFileName = (char *) NULL;


static AppState	appStateRec =
{
	0,					/* documentsProcessed */
	false,				/* processingDocument */
	false,				/* quitRequestPending */
	false,				/* quitAfterDocsProcessed */
	{ 0 },				/* fssInput */
	{ 0 },				/* fssOutput */
	0L,					/* input file size */
};


static RTFFuncPtr	readHook = (RTFFuncPtr) NULL;
static int	counter;


/* ---------------------------------------------------------------- */
/* Global variables													*/
/* ---------------------------------------------------------------- */

AppState	*appState = &appStateRec;

AppInfo		*appInfo;


/* ---------------------------------------------------------------- */
/* Spinning cursor management										*/
/* ---------------------------------------------------------------- */


/*
 * Call SpinCursor() to spin the cursor, InitCursor() to restore the
 * cursor to the arrow.  The spinning cursor resources are optional,
 * so if they're not found in the resource file, don't try to spin
 * the cursor.
 */

/*
 * 'acur' resource structure.  When read in, the cursor array contains
 * the 'CURS' resource numbers in each array element.  Those numbers
 * are used to read in the 'CURS' resources, which then replace the
 * numbers in the array so the structure actually contains handles to
 * the cursors and not their numbers.
 */

typedef struct ACur	ACur, **ACurHandle;

struct ACur						/* 'acur' resource structure */
{
	short		nCursors;		/* number of cursors in resource */
	short		curCounter;		/* current cursor counter */
	CursHandle	cursor[1];		/* array of cursors (actually variable-length) */
};


static ACurHandle aCur = (ACurHandle) nil;


/*
 * Cursor init code needs better error checking.  Only tests acur handle
 * for nil, doesn't test cursor handles.
 */

static void
SpinCursor (void)
{
static short inited = 0;
short	i;
short	nCursors, curCounter, curFrame;
Cursor	curCursor;

	if (inited == 0)			/* first call; get cursor resources */
	{
	short cursId;
	CursHandle	*hCursPtr;

		++inited;
		aCur = (ACurHandle) Get1Resource ('acur', acurRes);
		if(aCur == (ACurHandle) nil)
			return;
		DetachResource ((Handle) aCur);
		MoveHHi ((Handle) aCur);
		HLock ((Handle) aCur);
		nCursors = (**aCur).nCursors;
		hCursPtr = (**aCur).cursor;
		/* march through array replacing cursor numbers with cursor handles */
		for(i = 0; i < nCursors; i++)
		{
			*hCursPtr = GetCursor (* (short *) hCursPtr);
			MoveHHi((Handle) *hCursPtr);
			HLock((Handle) *hCursPtr);
			++hCursPtr;
		}
		(**aCur).curCounter = 0;
	}

	if (aCur == (ACurHandle) nil)	/* couldn't get resources, don't spin */
		return;

	nCursors = (**aCur).nCursors;
	curCounter = (((**aCur).curCounter + 16) % (nCursors * 32));
	(**aCur).curCounter = curCounter;
	curFrame = curCounter / 32;
	curCursor = **(**aCur).cursor[curFrame];
	SetCursor(&curCursor);
}


/* ---------------------------------------------------------------- */
/* Translation initiation/termination								*/
/* ---------------------------------------------------------------- */


static void
ReadEchoHook ()
{
	RTFMsg ("%d  %d  %d  %d  \"%s\"\n",
			rtfClass, rtfMajor, rtfMinor, rtfParam, rtfTextBuf);
}


static void
CounterReadHook ()
{
	if (++counter >= tokensPerNullEvent)
	{
		SpinCursor ();
		StatusWindUpdate ();
		counter = 0;
	}
	if (readHook != (RTFFuncPtr) NULL)
		(*readHook) ();
}


/*
 * Compare two file names to see if they're the same.  If so, generate a log
 * message and return true.  The desired return value is false!
 */

static Boolean
FileNamesEqual (char *name1, char *type1, char *name2, char *type2)
{
	if (strcmp (name1, name2) != 0)
		return (false);
	DisplayCString ("The ");
	DisplayCString (type1);
	DisplayCString (" and ");
	DisplayCString (type2);
	DisplayCString (" files have the same name!\r");
	return (true);
}


static void
BeginTranslation (FSSpec *fss)
{
extern long	_fcreator;		/* undocumented THINK C global (in fopen.c) */
Str255	dir;
char	inputFileName[maxPathLen];
char	outputFileName[maxPathLen];
char	*op = (char *) NULL;
int		okay;
long	oldCreator;


	appState->fssInput = *fss;

	/* convert FSSpec name to C string pathname */

	MakeDirPath (appState->fssInput.parID, appState->fssInput.vRefNum, dir);
	MakeCFilePath (dir, appState->fssInput.name, inputFileName);

	/*
	 * Set default directory used by putfile dialog to the input file's dir.
	 * It becomes the default directory for selecting the output file if the
	 * user is prompted for that (and also the error file if user isn't asked
	 * for output file).  If user is asked for output file and changes the
	 * directory, that becomes the default dir for the error file.  This tends
	 * to keep the output and error files in the same directory.
	 */

	CurDirStore = appState->fssInput.parID;
	SFSaveDisk = -appState->fssInput.vRefNum;

	/*
	 * Generate the output file name if an output file should be created.  Allow
	 * user to override the default name if autoname flag isn't set.
	 *
	 * Do the same for the error file.
	 */

	if (TstAppFlag (appInfo, usesOutputFile))
	{
		appState->fssOutput = *fss;
		MakeOutputFileName (appState->fssInput.name, appInfo->inputSuffix,
						appState->fssOutput.name, appInfo->outputSuffix);
		if (!TstAppFlag (appInfo, autoNameOutputFile))
		{
			/* set PutFile dialog directory */
			CurDirStore = appState->fssOutput.parID;
			SFSaveDisk = -appState->fssOutput.vRefNum;
	
			if (!SelectOutputFile (&appState->fssOutput, "\poutput"))
			{
				DisplayCString ("Cancelled: ");
				DisplayString (appState->fssInput.name);
				DisplayLn ();
				StatusWindUpdate ();
				return;
			}
		}
		MakeDirPath (appState->fssOutput.parID, appState->fssOutput.vRefNum, dir);
		MakeCFilePath (dir, appState->fssOutput.name, outputFileName);
		op = outputFileName;
		if (FileNamesEqual (inputFileName, "input", outputFileName, "output"))
		{
			StatusWindUpdate ();
			return;
		}
	}

	if (TstAppFlag (appInfo, usesErrorFile))
	{
		/*
		 * Error file appears in same directory as output file if there is one,
		 * else in the same directory as the input file.
		 */
	
		if (TstAppFlag (appInfo, usesOutputFile))
		{
			fssError = appState->fssInput;
			MakeOutputFileName (appState->fssInput.name, appInfo->inputSuffix,
								fssError.name, appInfo->errorSuffix);
		}
		else
		{
			fssError = appState->fssOutput;
			MakeOutputFileName (appState->fssOutput.name, appInfo->outputSuffix,
								fssError.name, appInfo->errorSuffix);
		}
		if (!TstAppFlag (appInfo, autoNameErrorFile))
		{
			if (!SelectOutputFile (&fssError, "\perror"))
			{
				DisplayCString ("Cancelled: ");
				DisplayString (appState->fssInput.name);
				DisplayLn ();
				StatusWindUpdate ();
				return;
			}
		}
		MakeDirPath (fssError.parID, fssError.vRefNum, dir);
		MakeCFilePath (dir, fssError.name, errorFileName);
		if (FileNamesEqual (inputFileName, "input", errorFileName, "error"))
		{
			errorFileName[0] = '\0';
			StatusWindUpdate ();
			return;
		}
		if (TstAppFlag (appInfo, usesOutputFile)
			&& FileNamesEqual (outputFileName, "output", errorFileName, "error"))
		{
			errorFileName[0] = '\0';
			StatusWindUpdate ();
			return;
		}
	}

	/*
	 * Show in log window the files involved in the translation, opening
	 * each that should be opened by the wrapper.  Make the creator
	 * of any created files those specified in the appInfo structure, or
	 * the defaults, if no creator is specified (zero).  (Type is set
	 * to 'TEXT' by fopen()/freopen().)
	 */

	okay = 1;

	oldCreator = _fcreator;

	DisplayCString ("Input file: ");
	DisplayCString (inputFileName);
	DisplayLn ();
	if (freopen (inputFileName, "r", stdin) == (FILE *) NULL)
	{
		DisplayCString ("Cannot open input file\r");
		okay = 0;
	}

	if (okay && TstAppFlag (appInfo, usesOutputFile))
	{
		DisplayCString ("Output file: ");
		DisplayCString (outputFileName);
		DisplayLn ();
		if (TstAppFlag (appInfo, autoOpenOutputFile))
		{
			_fcreator = appInfo->outputCreator;
			if (_fcreator == 0L)
				_fcreator = docCreator;
			if (freopen (outputFileName, "w", stdout) == (FILE *) NULL)
			{
				DisplayCString ("Cannot open output file\r");
				okay = 0;
			}
		}
	}

	if (okay && TstAppFlag (appInfo, usesErrorFile))
	{
		DisplayCString ("Error file: ");
		DisplayCString (errorFileName);
		DisplayLn ();
		_fcreator = appInfo->errorCreator;
		if (_fcreator == 0L)
			_fcreator = docCreator;
		if (freopen (errorFileName, "w", stderr) == (FILE *) NULL)
		{
			errorFileName[0] = '\0';
			DisplayCString ("Cannot open error file\n");
			okay = 0;
		}
	}

	/* restore default creator */

	_fcreator = oldCreator;

	if (!okay)
	{
		errorFileName[0] = '\0';
		StatusWindUpdate ();
		return;
	}

	/* determine size of input file for status window */

	(void) fseek (stdin, 0L, SEEK_END);
	appState->inputSize = ftell (stdin);
	(void) fseek (stdin, 0L, SEEK_SET);

	/* initialize reader, writer, and i/o streams */

	RTFInit ();
	RTFSetStream (stdin);
	RTFSetInputName (inputFileName);
	if (TstAppFlag (appInfo, usesOutputFile))
		RTFSetOutputName (outputFileName);

	if (!appInfo->pWriterBeginFile)	/* writer needs no special notification */
		appState->processingDocument = true;
	else							/* tell writer to begin file */
	{
		/*
		 * Tell the writer that a new file is beginning to be processed.
		 * If the writer returns zero, an error occurred and processing
		 * shouldn't proceed.
		 */
		if ((*appInfo->pWriterBeginFile) ())
			appState->processingDocument = true;
	}

	if (appState->processingDocument)
	{
		/*
		 * Ready to begin processing new file in idle loop.
		 *
		 * Get any read hook the begin-file proc might have installed and
		 * piggyback another one on top of it.  CounterReadHook is used
		 * to help avoid long waits between status wind updates when RTFGetToken()
		 * actually reads multiple tokens (e.g., when the font table or style
		 * sheet destination is read).
		 */
		readHook = RTFGetReadHook ();
		RTFSetReadHook (CounterReadHook);
		StatusWindUpdate ();
		SpinCursor ();
	}
	else
	{
		errorFileName[0] = '\0';
		(void) fclose (stdin);
		(void) fclose (stdout);
		(void) fclose (stderr);
	}
}


/*
 * Close the I/O streams, remove the error file if it's empty.
 */

static void
EndTranslation (void)
{
long	size;

	if (!appState->processingDocument)
		return;


	if (appInfo->pWriterEndFile)	/* tell writer to finish up output file */
		(*appInfo->pWriterEndFile) ();
	DisplayCString ("Done: ");
	DisplayString (appState->fssInput.name);
	DisplayLn ();

	/*
	 * Close files.  stdin is always closed.  stdout and stderr are closed
	 * if output and error files were opened.  If error file was opened but
	 * is empty (i.e., there's no error output), remove it.
	 */

	(void) fclose (stdin);

	if (TstAppFlag (appInfo, usesOutputFile))
		(void) fclose (stdout);

	if (TstAppFlag (appInfo, usesErrorFile))
	{
		size = ftell (stderr);
		(void) fclose (stderr);
		if (size == 0)				/* no diagnostic output */
			remove (errorFileName);
		else
		{
			DisplayCString ("(There are error messages in ");
			DisplayCString (errorFileName);
			DisplayCString (")\r");
		}
	}

	appState->processingDocument = false;
	++appState->documentsProcessed;
	errorFileName[0] = '\0';

	StatusWindUpdate ();
	InitCursor ();
}


/*
 * Quit was selected from the file menu or a Quit Application Apple Event
 * was received.  Tell the application to quit.  If any documents are in
 * progress or pending, allow the user to cancel the quit.
 */

typedef enum
{
	quitItem = 1,
	cancelItem
};


void
RequestQuit (void)
{
FSSpec	fss;
short	result;

	if (appState->processingDocument || PendingDocuments ())
	{
		result = SkelAlert (quitAlrtRes, SkelDlogFilter (nil, true),
											skelPositionOnParentDevice);
		SkelRmveDlogFilter ();
		SkelDoUpdates ();
		if (result == cancelItem)
				return;
	}
	EndTranslation ();					/* terminate current document, if any */
	while (RemoveDocument (&fss))		/* drain document queue */
	{ /* loop */ }
	appState->quitRequestPending = true;
}


/*
 * Idle loop.
 *
 * Priorities:
 * - If a quit has been successfully requested, terminate the main event loop.
 * - Otherwise, if processing document, keep processing it.
 * - Otherwise, if there is a document in the queue, start processing it.
 * - If there's no quit pending, but no documents are waiting or in process,
 * and the application's supposed to quit when all documents have been
 * processed, terminate the event loop.  (This is what happens when the
 * application is launched by dragging documents onto it.)
 *
 * Process a whole bunch of tokens at a shot, because doing a single token
 * each time a null event occurs is too slow.
 */

static pascal void
Idle (void)
{
FSSpec	fss;
int		i;

	if (appState->quitRequestPending)
		SkelStopEventLoop ();
	else if (appState->processingDocument)
	{
		SpinCursor ();
		counter = 0;
		for (i = 0; i + counter < tokensPerNullEvent; i++)
		{
			if (RTFGetToken () == rtfEOF)
			{
				StatusWindUpdate ();
				SkelPause (30L);
				EndTranslation ();
				break;
			}
			RTFRouteToken ();
		}
		if (appState->processingDocument)	/* true if EndTranslation() not just called */
			StatusWindUpdate ();
	}
	else if (RemoveDocument (&fss))	/* start translating next waiting document */
		BeginTranslation (&fss);
	else if (appState->quitAfterDocsProcessed)
		appState->quitRequestPending = true;
}


/* ---------------------------------------------------------------- */
/* Menu handling routines											*/
/* ---------------------------------------------------------------- */


/*
 * Handle selection of About... item from Apple menu.
 * The appHelp item is created only if the help 'TEXT' resource exists.
 */

static pascal void
DoAppleMenu (short item)
{
StringHandle	what;

	switch (item)
	{
	case aboutApp:
		what = GetString (whatStringRes);
		HLock ((Handle) what);
		ParamText (ApplicationName (), *what, "\p", "\p");
		HUnlock ((Handle) what);
		(void) SkelAlert (aboutAlrtRes, SkelDlogFilter (nil, true),
							skelPositionOnParentDevice);
		SkelRmveDlogFilter ();
		break;
	case appHelp:
		HelpWindow ();
		break;
	}
}


static pascal void
DoFileMenu (short item)
{
FSSpec	fss;

	switch (item)
	{
	case openDoc:
		if (SelectInputFile (&fss, docType))
		{
			AddDocument (&fss);
			StatusWindUpdate ();
		}
		break;
	case quitApp:
		RequestQuit ();
		break;
	}
}


static pascal void
DoEditMenu (short item)
{
	if (item == preferences && appInfo->pPrefs != nil)
		(*appInfo->pPrefs) ();
}


static pascal void
AdjustMenus (void)
{
	/*
	 * Do adjustment of standard menus (nothing at present)
	 */

	/*
	 * Do any app-specific menu adjustment
	 */

	if (appInfo->pMenuAdjust)
		(*appInfo->pMenuAdjust) ();
}


static void
MenuInit (void)
{
MenuHandle	m;
Str255		s;
Handle		h;

	PStrCpy (s,"\pAbout ");
	PStrCat (s, ApplicationName ());
	PStrCat (s, "\p\311");	/* \311 = ellipsis */

	/*
	 * Add help item to apple menu if help 'TEXT' resource exists
	 */

	if ((h = GetResource ('TEXT', helpTextRes)) != (Handle) nil)
	{
		ReleaseResource (h);
		PStrCat (s, "\p;Help\311");
	}
	
	/*
	 * Initialize standard menus
	 */

	SkelApple (s, DoAppleMenu);
	m = GetMenu (fileMenuRes);
	(void) SkelMenu (m, DoFileMenu, nil, false, false);
	m = GetMenu (editMenuRes);
	(void) SkelMenu (m, DoEditMenu, nil, false, false);
	if (TstAppFlag (appInfo, prefsInEditMenu))
		AppendMenu (m, "\p(-;Preferences\311");

	/*
	 * Perform any app-specific menu initialization
	 */

	if (appInfo->pMenuInit)
		(*appInfo->pMenuInit) ();

	DrawMenuBar ();

	SkelSetMenuHook (AdjustMenus);
}


/* ---------------------------------------------------------------- */
/* Message and Panic routines										*/
/* ---------------------------------------------------------------- */


/*
 * Display an error message.
 *
 * \n needs to be mapped into \r.  It's easiest to modify the string in-place
 * and then call DisplayCString(), but that modifies the original string.
 * Next easiest is to print each character individually, printing \r whenever
 * \n is seen, but that's unnecessarily slow.  So what's done instead is to
 * find and print sequences of non-newline characters and print them all
 * at once, and map \n to DisplayLn().
 *
 * This isn't prototyped because it's passed as a callback to the RTF library.
 */

static void
Msg (s)
char	*s;
{
char	*p, *pbeg;

	/*
	 * Write the message to the error file, if there is one.
	 */
	if (errorFileName != (char *) NULL && errorFileName[0] != '\0')
		fprintf (stderr, "%s", s);

	p = pbeg = s;
	while (*p != '\0')
	{
		if (*p == '\n')		/* found newline, print preceding non-nl text */
		{
			DisplayText ((Ptr) pbeg, (long) (p - pbeg));
			pbeg = p + 1;
			DisplayLn ();
		}
		++p;
	}
	DisplayText ((Ptr) pbeg, (long) (p - pbeg));
}


/*
 * Panic processing.
 *
 * This isn't prototyped because it's passed as a callback to the RTF library.
 */

static void
Panic (s)
char	*s;
{
char	*p = s;

	Msg (s);				/* echo to log window and error file */
	
	while (*p != '\0')		/* can modify string because we're going to exit */
	{
		if (*p == '\n')
			*p = '\r';
		++p;
	}
	if (errorFileName != (char *) NULL && errorFileName[0] != '\0')
	{	/* an error file exists */
		ParamText (CtoPstr (s),
					"\p (error output may be found in ",
					fssError.name,
					"\p)");
	}
	else
		ParamText (CtoPstr (s), "\p", "\p", "\p");

	(void) SkelAlert (msgeAlrtRes, SkelDlogFilter (nil, true),
											skelPositionOnParentDevice);
	SkelRmveDlogFilter ();
	SkelCleanup ();
	ExitToShell ();
}


/* ---------------------------------------------------------------- */
/* Main wrapper														*/
/* ---------------------------------------------------------------- */


static void
Require (long selector, short bit, char *message)
{
long	result;

	if (!SkelQuery (skelQHasGestalt)
		|| Gestalt (selector, &result) != noErr
		|| ((1 << bit) & result) == 0)
	{
		RTFPanic ("%s requires System 7 %s support.",
					PtoCstr (ApplicationName ()), message);
	}
}


/*
 * Initiate the Macintosh interface wrapper.  ap is a pointer to your
 * application information structure, and it MUST be non-nil.  The wrapper
 * just keeps a copy of the pointer, so any changes to it made by the host
 * application will be seen in the wrapper immediately.
 */

void
MacintoshWrapper (AppInfo *ap)
{
	appInfo = ap;			/* copy pointer to host application information */

	/*
	 * Install RTF library OS-specific callbacks
	 */

	RTFSetMsgProc (Msg);		/* route non-fatal error output */
	RTFSetPanicProc (Panic);		/* route fatal error output */
	RTFSetOpenLibFileProc (MacOpenLibFile);

	SkelInit ((SkelInitParamsPtr) nil);

	/*
	 * Check for required system capabilities
	 */

	Require (gestaltAppleEventsAttr, gestaltAppleEventsPresent, "Apple Event");
	Require (gestaltFindFolderAttr, gestaltFindFolderPresent, "Find Folder");
	Require (gestaltStandardFileAttr, gestaltStandardFile58, "file dialog");
	Require (gestaltFSAttr, gestaltHasFSSpecCalls, "FSSpec call");

	if ((errorFileName = RTFAlloc (maxPathLen)) == (char *) NULL)
		RTFPanic ("cannot allocate error file name");
	errorFileName[0] = '\0';

	MenuInit ();
	LogWindInit ();
	StatusWindInit ();
	AppleEventsInit ();

	/*
	 * Run once-only writer initialization
	 */

	if (appInfo->pWriterInit)
		(*appInfo->pWriterInit) ();

	/*
	 * Pick up any pending apple events immediately, then check the document
	 * queue.  If it's non-empty, then set a flag so the application quits
	 * after the documents have been processed.  (Otherwise there are no
	 * documents dropped on the application, so we just wait for user actions
	 * in the event loop.)
	 */

	SkelDoEvents (highLevelEventMask);
	appState->quitAfterDocsProcessed = PendingDocuments ();

	SkelSetIdle (Idle);

	/*
	 * Main document-processing loop
	 */

	SkelEventLoop ();

	/*
	 * Run once-only writer cleanup
	 */

	if (appInfo->pWriterEnd)
		(*appInfo->pWriterEnd) ();

	SkelCleanup ();
}

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