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.