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.