home *** CD-ROM | disk | FTP | other *** search
Wrap
#include <stdio.h> /* File: TextFile.c Contains: Text file support for simple text application. Version: SimpleText 1.4 or later Copyright: © 1993-1997 by Apple Computer, Inc., all rights reserved. File Ownership: DRI: Tom Dowdy Other Contact: Jim Negrette Technology: Macintosh Graphics Group Writers: (DH) Dave Hersey (dmp) Dave Polaschek (ecs) Eric Schlegel (ted) Tom Dowdy (Gr) Greg Robbins (TD) Tom Dowdy (DAL) Dave Lyons Change History (most recent first): $Log: TextFile.c,v $ Revision 1.22 1999/05/04 20:03:35 jiarocci Fix for #2331923 - Expletive in SimpleText sample code. Revision 1.21 1999/05/01 01:52:25 jiarocci Comment out grafprocs so we build against latest CarbonLib interfaces on 8.x. Revision 1.20 1999/04/01 22:54:30 devans Add code to compile and execute selections as applescripts. Revision 1.19 1999/02/19 22:11:50 danp Minor changes for opaque regions. Revision 1.18 1999/02/16 00:41:41 christ Integrate document proxy icon support Revision 1.17 1999/02/10 18:25:52 danp Adding Andy Carol's Changes Revision 1.16 1999/01/08 04:42:02 christ Text dragging support is back. Revision 1.15 1998/12/18 00:42:30 wilkes Removed annoying DebugStr's. Revision 1.14 1998/11/25 21:01:22 wilkes Removed all GX references. Revision 1.13 1998/11/11 22:28:59 wilkes Fixed various problems caused by the interface changes made by Nitin earlier, mostly involving static RoutineDescriptors... Revision 1.12 1998/10/14 18:52:47 voas Eliminate all warnings. Get working with top of tree. Revision 1.11 1998/10/12 18:59:04 danp Modifications to allow cross-compiling and for CarbonLib support. Removed lots of GX stuff Revision 1.10 1998/09/30 02:19:55 jiarocci Fix timeout calculation to use GetCarretTime(). Revision 1.9 1998/09/15 18:59:50 jiarocci SimpleText now builds with -DTARGET_CARBON=1. Still needs further cleanup. Revision 1.8 1998/09/02 20:45:55 danp Changes so it works with blue mwerks Revision 1.7 1998/09/02 00:18:57 voas Fix silly bug in RecalcTE Revision 1.6 1998/05/10 08:44:56 devans Default open files to font size 18 point for the demos... Revision 1.5 1998/05/10 07:28:37 devans Hack for TextEdit font size bug. Set and change the font size when we first fill in a TextEdit handle. This causes the right calcs to occur. Also return NULL from OpenDoc instead of whatever was on the stack. Revision 1.4 1998/05/06 22:21:27 devans Replace usage of qd.arrow with GetQDarrow(). Revision 1.3 1998/03/30 22:12:32 mkellner Update to use new GetQDxxxx macros for qd.globals Revision 1.2 1998/03/20 03:20:08 mkellner change qd.thePort to FrontWindow() add SysEnvirons Revision 1.1.1.1 1998/03/18 22:56:12 ivory Initial checkin of SimpleText. 8 11/5/97 7:45 AM Tom Dowdy Saving a stationary could sometimes reset original type, dont do that any longer. 7 8/20/97 4:23 PM Tom Dowdy 1674136: adjust cursor after select all 6 8/11/97 3:35 PM Tom Dowdy Nav services now working! 5 8/11/97 3:04 PM Tom Dowdy rolling in nav services 4 8/7/97 1:34 PM Tom Dowdy picture header leak, 1671661 3 7/29/97 2:05 PM Tom Dowdy Removed all of the old and boring refs 2 7/29/97 1:51 PM Tom Dowdy Various new interface fixes 1 7/28/97 11:24 AM Duane Byram first added to Source Safe project <43> 6/18/97 ted Use Get1Resource for pictures <42> 6/17/97 ted [1662824] Fixing how style undo is handled <41> 5/29/97 ted [1659294] undo reformat bug <40> 5/19/97 ted [1655162] PICTs not printing <39> 2/12/97 DH TextReadDataFork wasn't initializing "anErr" if no data fork was open. <38> 1/13/97 ted [1616537] replace all "forever" <37> 1/2/97 ecs split TextMakeWindow into separate functions to make profiling possible; added a thread to build the Voices menu to improve boot speed <36> 12/19/96 ted [1395082] Forward delete w/ new TE <35> 12/4/96 ted [1157761] stationary does not need a special file type (thanks, Finder!) <34> 12/2/96 ted [1608152] Fixing shift arrow selection for double bytes <33> 11/26/96 ecs support GXGraphics extension; smarter cursor adjustment <32> 11/13/96 ted [1603950] Fixing undo of style <31> 10/23/96 ted [1289037] fixing 32k arrow key problem <30> 10/10/96 ted changing implementforwarddelete to TE6 version <29> 10/9/96 ted [1395082] Fixing forward delete on new double byte systems <28> 9/9/96 dmp staticfy local functions to eliminate warnings in MWC <27> 8/21/96 ecs ReadPartialResource cache; only draw pictures if not clipped <26> 8/2/96 ted fixing shift arrows for japanese <25> 8/1/96 ted fixing forward delete for japanese <24> 7/25/96 ted adding call to flushvol <23> 7/24/96 ted fixing stupid ttro draw hook bug <22> 7/24/96 ted fixing sub menu checking <21> 7/19/96 ted fixing menu nil dereference <20> 6/10/96 ted GENERATINGCFM should really be GENERATINGPOWERPC <19> 5/31/96 ted fixing clik loop for PPC <18> 5/31/96 ted adding PPC, FAT, and NuKernel builds <17> 2/19/96 ted fixing file type always on save <16> 2/8/96 ted CountMenuItems->CountMItems (??) <15> 1/4/96 ecs needed a (GrafPtr) cast <14> 12/6/95 ted adding file type open support <13> 11/1/95 ted adding partial picts <12> 10/31/95 ted increasing print margins <11> 10/17/95 ted optimizing page break code to speed up "boring" prints <10> 10/12/95 ted <9*> 10/11/95 ted adding page breaking support <9> 10/2/95 TD adding support for SC compiler <8> 9/19/95 TD fixing up undo with selection behavior <7> 9/15/95 Gr For contents menu selections, explicitly scroll before TESetSelect <6> 9/15/95 TD modifying selection and undo behavior <5> 9/15/95 Gr Add contents menu support; change BlockMove calls to BlockMoveData <4> 9/13/95 TD changing gcurrent voice check <3> 8/31/95 TD fixing a bug related to scroll bars on windows with no <cr> after last line. bug #113167 <2> 8/22/95 TD adding support for shift key w/ arrows <1> 8/21/95 TD First checked in. <46> 1/4/95 DAL Move the SetWTitle for "untitled" into SimpleText.c (to fix an Apple Menu Options problem, Radar #s 1207631 and 1207614). <45> 12/21/94 DAL Radar #1199009. Fixed bug from <39> where CalculateTextEditHeight was introduced, but it needs to return a long, like the expression it replaced in ApplySize. <44> 12/7/94 DAL Radar #110489, 110490. Removed TEToScrap in Cut and Copy operations, because it was putting a redundant copy of the 'TEXT' data into the scrap. With a styled TE record, TECopy does all the work. <39> 10/4/94 TED (#1129365) use the script manager to figure out legal text styles. For example, Japanese doesn't support underline. (#1161797) system font/size/app font are now mapped to real IDs and sizes so that folks don't get surprised by size=0 problems. <38> 9/8/94 DAL First untitled window uses FIRSTNEWDOCUMENTTITLE (#1148458). UserCanceled error now propogates from Save (fixes #1183495). We now handle update events behind Save dialog. */ #include "MacIncludes.h" #include <Threads.h> #include <OSA.h> // for AppleScript #include <AEDataModel.h> // for AppleScript #include "TextFile.h" #pragma segment Text // -------------------------------------------------------------------------------------------------------------- // INTERNAL DEFINES // -------------------------------------------------------------------------------------------------------------- #define kOnePageWidth 600 // preferred width of a window #define kMargins 4 // margins in window #define kPrintMargins 8 // margins in printing window #define kPictureBase 1000 // resource base ID for PICTs in documents #define kSoundBase 10000 // resource base ID for 'snd 's in documents // resources for controling printing #define kFormResource 'form' #define kFormFeed 0x00000001 // form feed after this line #define kContentsListID 10000 // resource ID for STR# contents menu // some memory requirements #define kPrefBufferSize 90*1024 #define kMinBufferSize 5*1024 #define kDeleteKey 8 #define kForwardDeleteKey 0x75 extern pascal unsigned char * AsmClikLoop(TEPtr pTE); #define ABS(n) (((n) < 0) ? -(n) : (n)) // -------------------------------------------------------------------------------------------------------------- // STRUCTURES USED BY THIS FILE // -------------------------------------------------------------------------------------------------------------- typedef struct { int voiceCount; MenuHandle voiceMenu; } VoiceThreadParams; // -------------------------------------------------------------------------------------------------------------- // GLOBALS USED ONLY BY THESE ROUTINES // -------------------------------------------------------------------------------------------------------------- // These variables are used for speech, notice that on purpose we only // support a single channel at a time. #if 0 static SpeechChannel gSpeechChannel = nil; static VoiceSpec gCurrentVoice; static Ptr gSpeakPtr = nil; static Boolean gAddedVoices = false; static ThreadID gVoicesThread; static void* gThreadResults; static Boolean gDontYield; #endif static Str31 gPictMarker1, gPictMarker2; // Other globals local to this file (and TextDrag.c) Boolean gTE6Version = false; // External globals for AppleScript extern ComponentInstance gOSAComponent; // -------------------------------------------------------------------------------------------------------------- // EXTERNAL FUNCTIONS // -------------------------------------------------------------------------------------------------------------- extern OSErr TextDragTracking(WindowPtr pWindow, void *pData, DragReference theDragRef, short message); extern OSErr TextDragReceive(WindowPtr pWindow, void *refCon, DragReference theDragRef); extern Boolean DragText(WindowPtr pWindow, void *pData, EventRecord *pEvent, RgnHandle hilightRgn); // -------------------------------------------------------------------------------------------------------------- // INTERNAL ROUTINES // -------------------------------------------------------------------------------------------------------------- static void TextAddContentsMenu(WindowDataPtr pData); static void TextRemoveContentsMenu(WindowDataPtr pData); static OSErr TextGetContentsListItem(WindowDataPtr pData, short itemNum, StringPtr menuStr, StringPtr searchStr, short *totalItems); static OSErr TextAdjustContentsMenu(WindowDataPtr pData); // -------------------------------------------------------------------------------------------------------------- // UNDO UTILITY FUNCTIONS // -------------------------------------------------------------------------------------------------------------- OSErr SaveCurrentUndoState(WindowDataPtr pData, short newCommandID) { OSErr anErr = noErr; TEHandle hTE = ((TextDataPtr) pData)->hTE; // this is only a new command if: // the command ID is different // OR // the selection is a range, and not equal to the old one if ( ( ((TextDataPtr) pData)->prevCommandID != newCommandID ) || ( ( (**hTE).selStart != (**hTE).selEnd ) || ( ABS((**hTE).selStart - ((TextDataPtr) pData)->prevSelStart) > 1 ) ) ) { Handle tempHandle; Size tempSize; // if we don't have save handles, make em! if (!((TextDataPtr) pData)->prevText) { ((TextDataPtr) pData)->prevText = NewHandle(0); anErr = MemError(); nrequire(anErr, MakeTextSave); } if (!((TextDataPtr) pData)->prevStyle) { ((TextDataPtr) pData)->prevStyle = NewHandle(0); anErr = MemError(); nrequire(anErr, MakeStyleSave); } // grow the save handles and block move into them tempHandle = (**hTE).hText; tempSize = GetHandleSize(tempHandle); SetHandleSize( ((TextDataPtr) pData)->prevText, tempSize); anErr = MemError(); nrequire(anErr, GrowTextSave); BlockMoveData(*tempHandle, * ((TextDataPtr) pData)->prevText, tempSize ); tempHandle = (Handle) TEGetStyleHandle(hTE); tempSize = GetHandleSize(tempHandle); SetHandleSize( ((TextDataPtr) pData)->prevStyle, tempSize); anErr = MemError(); nrequire(anErr, GrowTextSave); BlockMoveData(*tempHandle, * ((TextDataPtr) pData)->prevStyle, tempSize ); // save length ((TextDataPtr) pData)->prevLength = (**hTE).teLength; ((TextDataPtr) pData)->beforeSelStart = (**hTE).selStart; ((TextDataPtr) pData)->beforeSelEnd = (**hTE).selEnd; } // save start and end of the selection ((TextDataPtr) pData)->prevSelStart = (**hTE).selStart; // if we didn't have any problems, then we can undo this operation ((TextDataPtr) pData)->prevCommandID = newCommandID; return(noErr); // EXCEPTION HANDLING GrowTextSave: MakeStyleSave: MakeTextSave: // can't undo because of an error // (at some point might warn the user, but that's probably more annoying) ((TextDataPtr) pData)->prevCommandID = cNull; return(anErr); } // SaveCurrentUndoState // -------------------------------------------------------------------------------------------------------------- static void PerformUndo(WindowDataPtr pData) { if (((TextDataPtr) pData)->prevCommandID != cNull) { TEHandle hTE = ((TextDataPtr) pData)->hTE; long tempLong; // undo text tempLong = (long) (**hTE).hText; (**hTE).hText = ((TextDataPtr) pData)->prevText; ((TextDataPtr) pData)->prevText = (Handle)tempLong; // undo length tempLong = (long) (**hTE).teLength; (**hTE).teLength = ((TextDataPtr) pData)->prevLength; ((TextDataPtr) pData)->prevLength = tempLong; // undo style tempLong = (long) TEGetStyleHandle(hTE); HandToHand((Handle*)&tempLong); TESetStyleHandle( (TEStyleHandle) ((TextDataPtr) pData)->prevStyle, hTE); ((TextDataPtr) pData)->prevStyle = (Handle)tempLong; // undo selection { short start, end; start = ((TextDataPtr) pData)->beforeSelStart; end = ((TextDataPtr) pData)->beforeSelEnd; TESetSelect(start, end, hTE); } } } // PerformUndo // -------------------------------------------------------------------------------------------------------------- // TEXT EDIT UTILITY FUNCTIONS // -------------------------------------------------------------------------------------------------------------- static long CalculateTextEditHeight(TEHandle hTE) { long result; short length; result = TEGetHeight(32767, 0, hTE); length = (**hTE).teLength; // Text Edit doesn't return the height of the last character, if that // character is a <cr>. So if we see that, we go grab the height of // that last character and add it into the total height. if ( (length) && ( (*(**hTE).hText)[length-1] == '\n') ) { TextStyle theStyle; short theHeight; short theAscent; TEGetStyle(length, &theStyle, &theHeight, &theAscent, hTE); result += theHeight; } return result; } // CalculateTextEditHeight // -------------------------------------------------------------------------------------------------------------- void AdjustTE(WindowDataPtr pData, Boolean doScroll) { TEPtr te; short value; short prevValue; te = *((TextDataPtr) pData)->hTE; prevValue = GetControlValue(pData->vScroll); value = te->viewRect.top - te->destRect.top; SetControlValue(pData->vScroll, value); te = *((TextDataPtr) pData)->hTE; if (doScroll) { short hScroll = te->viewRect.left - te->destRect.left; short vScroll = value - prevValue; if ((hScroll != 0) || (vScroll != 0)) TEScroll(hScroll, vScroll, ((TextDataPtr) pData)->hTE); } } // AdjustTE // -------------------------------------------------------------------------------------------------------------- static void RecalcTE(WindowDataPtr pData, Boolean doInval) { Rect viewRect, destRect; short oldOffset; destRect = (**((TextDataPtr) pData)->hTE).destRect; viewRect = (**((TextDataPtr) pData)->hTE).viewRect; oldOffset = destRect.top - viewRect.top; viewRect = pData->contentRect; InsetRect(&viewRect, kMargins, kMargins); destRect = viewRect; OffsetRect(&destRect, 0, oldOffset); (**((TextDataPtr) pData)->hTE).viewRect = viewRect; (**((TextDataPtr) pData)->hTE).destRect = destRect; TECalText(((TextDataPtr) pData)->hTE); if (doInval) { WindowPtr window = FrontWindow(); if ( window ) { Rect rect; GetPortBounds( GetWindowPort( window ), &rect ); InvalWindowRect( window, &rect ); } } } // RecalcTE // -------------------------------------------------------------------------------------------------------------- pascal TEClickLoopUPP GetOldClickLoop(void) { // AEC, changed to obtain application data from the WRefCon TextDataPtr textData; textData = (TextDataPtr) GetWRefCon(GetWindowFromPort(GetQDGlobalsThePort())); return textData->docClick; } // GetOldClikLoop pascal void TextClickLoop(void) { WindowPtr window; RgnHandle region; Rect bounds; TextDataPtr textData; window = FrontWindow(); region = NewRgn(); GetClip(region); /* save clip */ ClipRect(GetWindowPortBounds(window, &bounds)); // AEC, changed to obtain application data from the WRefCon textData = (TextDataPtr) GetWRefCon(window); textData->insideClickLoop = true; AdjustScrollBars(window, false, false, nil); textData->insideClickLoop = false; SetClip(region); /* restore clip */ DisposeRgn(region); } // TextClickLoop static pascal Boolean CClikLoop(TEPtr pTE) { CallTEClickLoopProc(GetOldClickLoop(), pTE); TextClickLoop(); return(true); } // CClikLoop // -------------------------------------------------------------------------------------------------------------- pascal void MyDrawHook ( unsigned short offset, unsigned short textLen, Ptr textPtr, TEPtr tePtr, TEHandle teHdl ) { #pragma unused ( teHdl, tePtr) register short drawLen = 0; register Ptr drawPtr; drawPtr = textPtr + (long)offset; while ( textLen--) { if ( *drawPtr == (char)0xCA && ( *(drawPtr - 1) == 0x0D || *tePtr->hText == textPtr) ) { DrawText( textPtr, offset, drawLen); DrawChar( '\040'); offset += drawLen + 1; drawLen = 0; } else { ++drawLen; } ++drawPtr; } DrawText( textPtr, offset, drawLen); } // MyDrawHook // -------------------------------------------------------------------------------------------------------------- #if 0 static void DisposeOfSpeech(Boolean throwAway) { if (gSpeechChannel) { (void) StopSpeech( gSpeechChannel ); if (throwAway) { (void) DisposeSpeechChannel( gSpeechChannel ); gSpeechChannel = nil; } } if (gSpeakPtr) { DisposePtr(gSpeakPtr); gSpeakPtr = nil; } } // DisposeOfSpeech #endif // -------------------------------------------------------------------------------------------------------------- static Boolean FindNextPicture(Handle textHandle, short *offset, short *delta) { long result; // marker at begining of document means a picture there if ( (*offset == 0) && ( (*(unsigned char*)(*textHandle + (*offset))) == 0xCA) ) { *delta = 1; return true; } if (gPictMarker1[0] != 0) { result = Munger(textHandle, *offset, &gPictMarker1[1], gPictMarker1[0], nil, 0); if (result >= 0) { *offset = result; *delta = gPictMarker1[0]; return true; } } if (gPictMarker2[0] != 0) { result = Munger(textHandle, *offset, &gPictMarker2[1], gPictMarker2[0], nil, 0); if (result >= 0) { *offset = result; *delta = gPictMarker2[0]; return true; } } *delta = 1; return false; } // FindNextPicture // -------------------------------------------------------------------------------------------------------------- static Boolean LineHasPageBreak(short lineNum, TEHandle hTE) { Boolean result = false; short offset, delta, lineStartOffset; short textLength; Handle textHandle; short pictIndex = 0; textHandle = (** hTE).hText; lineStartOffset = (**hTE).lineStarts[lineNum]; textLength = (**hTE).lineStarts[lineNum+1]; offset = 0; while (offset < textLength) { if (FindNextPicture(textHandle, &offset, &delta)) { Handle formHandle; formHandle = Get1Resource(kFormResource, kFormResource + pictIndex); if (formHandle) { long options = **(long**)formHandle; ReleaseResource(formHandle); if ( (options & kFormFeed) && (offset >= lineStartOffset) && (offset < textLength) ) { result = true; break; } } ++pictIndex; } offset += delta; } return(result); } // LineHasPageBreak // -------------------------------------------------------------------------------------------------------------- #define USE_PICT_SPOOL_CACHE 1 enum {kSpoolCacheBlockSize = 1024}; // 1K is arbitrary, perhaps could be tuned static Handle gSpoolPicture; static long gSpoolOffset; static Handle gSpoolCacheBlock; static long gSpoolCacheOffset; static long gSpoolCacheSize; // -------------------------------------------------------------------------------------------------------------- /* ReadPartialResource is very painful over slow links such as ARA or ISDN. This code implements a simple cache that vastly improves the performance of displaying embedded pictures over a slow network link. The cache also improves scrolling performance in documents with many embedded pictures, even on local volumes. */ static short ReadPartialData(Ptr dataPtr, short byteCount) { #if USE_PICT_SPOOL_CACHE if (gSpoolCacheBlock == NULL) { gSpoolCacheBlock = NewHandle(kSpoolCacheBlockSize); if (gSpoolCacheBlock != NULL) { long cacheBytes = GetResourceSizeOnDisk(gSpoolPicture) - gSpoolOffset; if (cacheBytes > kSpoolCacheBlockSize) cacheBytes = kSpoolCacheBlockSize; HLock(gSpoolCacheBlock); ReadPartialResource(gSpoolPicture, gSpoolOffset, *gSpoolCacheBlock, cacheBytes); HUnlock(gSpoolCacheBlock); gSpoolCacheOffset = gSpoolOffset; gSpoolCacheSize = cacheBytes; } } // if the requested data is entirely present in the cache block, get it from there… if (gSpoolCacheBlock != NULL && gSpoolOffset >= gSpoolCacheOffset && gSpoolOffset + byteCount <= gSpoolCacheOffset + gSpoolCacheSize) { BlockMoveData(*gSpoolCacheBlock + (gSpoolOffset - gSpoolCacheOffset), dataPtr, byteCount); return byteCount; } // …else read it directly from disk, and force the cache block to be filled with new data else { ReadPartialResource(gSpoolPicture, gSpoolOffset, dataPtr, byteCount); if (gSpoolCacheBlock != NULL) { DisposeHandle(gSpoolCacheBlock); gSpoolCacheBlock = NULL; } return byteCount; } #else ReadPartialResource(gSpoolPicture, gSpoolOffset, dataPtr, byteCount); return byteCount; #endif } // ReadPartialData // -------------------------------------------------------------------------------------------------------------- static pascal void GetPartialPICTData(Ptr dataPtr,short byteCount) { while (byteCount > 0) { short readBytes = ReadPartialData(dataPtr, byteCount); byteCount -= readBytes; dataPtr += readBytes; gSpoolOffset += readBytes; } } // GetPartialPICTData // -------------------------------------------------------------------------------------------------------------- static void SpoolDrawPicture(Handle spoolPicture, PicHandle pictureHeader, Rect *pRect) { static QDGetPicUPP gGetPartialPICTData = NULL; #if USE_QD_GRAFPROCS CQDProcs spoolProcs; CQDProcs oldProcs; CGrafPtr thePort = GetQDGlobalsThePort(); #endif if (!gGetPartialPICTData) { gGetPartialPICTData = NewQDGetPicProc(GetPartialPICTData); } gSpoolPicture = spoolPicture; gSpoolOffset = sizeof(Picture); #if USE_QD_GRAFPROCS if (!GetPortGrafProcs(thePort, &spoolProcs)) SetStdCProcs(&spoolProcs); spoolProcs.getPicProc = gGetPartialPICTData; GetPortGrafProcs(thePort, &oldProcs); SetPortGrafProcs(thePort, &spoolProcs); #endif DrawPicture(pictureHeader, pRect); #if USE_QD_GRAFPROCS SetPortGrafProcs(thePort, &oldProcs); #endif if (gSpoolCacheBlock != NULL) { DisposeHandle(gSpoolCacheBlock); gSpoolCacheBlock = NULL; } } // SpoolDrawPicture // -------------------------------------------------------------------------------------------------------------- static void DrawPictures( WindowDataPtr pData, TEHandle hTE) { Handle textHandle; long textLength; short oldResFile; short pictIndex; short numPicts; if (pData->resRefNum == -1) return; oldResFile = CurResFile(); UseResFile(pData->resRefNum); numPicts = Count1Resources('PICT'); pictIndex = 0; if (numPicts != 0) { short offset, delta; RgnHandle oldClip; Rect theRect; Rect viewRect; viewRect = theRect = (** hTE).viewRect; // intersect our viewing area with the actual clip to avoid // drawing over the scroll bars { RgnHandle newClip = NewRgn(); oldClip = NewRgn(); GetClip(oldClip); RectRgn(newClip, &theRect); SectRgn(oldClip, newClip, newClip); SetClip(newClip); } textHandle = (** hTE).hText; textLength = (** hTE).teLength; offset = 0; while (offset < textLength) { if (FindNextPicture(textHandle, &offset, &delta)) { Handle pictHandle; Point picturePoint = TEGetPoint(offset, hTE); SetResLoad(false); pictHandle = Get1Resource('PICT', kPictureBase + pictIndex); SetResLoad(true); if (pictHandle) { PicHandle pictHeader = (PicHandle)NewHandle(sizeof(Picture) + sizeof(long)*8); if (pictHeader) { HLock((Handle) pictHeader); ReadPartialResource(pictHandle, 0, (Ptr)*pictHeader, GetHandleSize((Handle)pictHeader)); HUnlock((Handle) pictHeader); // calculate where to draw the picture, this location is // computed by: // 1) the frame of the original picture, normalized to 0,0 // 2) the location of the non-breaking space character // 3) centering the picture on the content frame horizontally // 4) subtracting off the line-height of the character GetPICTRectangleAt72dpi(pictHeader, &theRect); OffsetRect(&theRect, -theRect.left, -theRect.top); OffsetRect(&theRect, theRect.left + ((viewRect.right - viewRect.left) >> 1) - ((theRect.right - theRect.left) >> 1), picturePoint.v-theRect.top - pData->vScrollAmount); // only draw the picture if it will be visible (vastly improves scrolling // performance in documents with many embedded pictures) // if (RectInRgn(&theRect, GetQDthePort()->clipRgn)) SpoolDrawPicture(pictHandle, pictHeader, &theRect); DisposeHandle((Handle) pictHeader); } ReleaseResource((Handle) pictHandle); } ++pictIndex; offset += delta; } else break; } SetClip(oldClip); DisposeRgn(oldClip); } UseResFile(oldResFile); } // DrawPictures // -------------------------------------------------------------------------------------------------------------- static void UpdateFileInfo(FSSpec *pSpec, Boolean documentIsText) { FInfo theInfo; FSpGetFInfo(pSpec, &theInfo); theInfo.fdCreator = 'ttxt'; theInfo.fdType = 'TEXT'; // set the stationary bit, if we must if (!documentIsText) theInfo.fdFlags |= kIsStationery; else theInfo.fdFlags &= ~kIsStationery; FSpSetFInfo(pSpec, &theInfo); } // UpdateFileInfo // -------------------------------------------------------------------------------------------------------------- static OSErr TextSave(WindowDataPtr pData, Boolean resetStationary) { OSErr anErr; long amountToWrite; int i; Ptr data; anErr = noErr; // write out the text SetFPos(pData->dataRefNum, fsFromStart, 0); amountToWrite = (** ((TextDataPtr) pData)->hTE).teLength; data = * (** ((TextDataPtr) pData)->hTE).hText; for (i=0; i < amountToWrite; i++) { if (data[i] == '\r') data[i] = '\n'; } anErr = FSWrite(pData->dataRefNum, &amountToWrite, data); amountToWrite = (** ((TextDataPtr) pData)->hTE).teLength; data = * (** ((TextDataPtr) pData)->hTE).hText; for (i=0; i < amountToWrite; i++) { if (data[i] == '\n') data[i] = '\r'; } nrequire(anErr, FailedWrite); SetEOF(pData->dataRefNum, amountToWrite); if (pData->resRefNum == -1) { FSpCreateResFile(&pData->fileSpec, 'ttxt', pData->originalFileType, 0); pData->resRefNum = FSpOpenResFile(&pData->fileSpec, fsRdWrPerm); } else { // a save always makes it into file of type 'TEXT', for Save As… we // afterwards set it again if the user is saving as stationary. if (resetStationary) UpdateFileInfo(&pData->fileSpec, true); } if (pData->resRefNum != -1) { short oldResFile = CurResFile(); Handle resourceHandle; UseResFile(pData->resRefNum); // remove any old sounds resourceHandle = Get1Resource('snd ', kSoundBase); if (resourceHandle) { RemoveResource(resourceHandle); DisposeHandle(resourceHandle); } // save the new sound resourceHandle = ((TextDataPtr) pData)->soundHandle; if (resourceHandle) { anErr = HandToHand(&resourceHandle); if (anErr == noErr) { AddResource(resourceHandle, 'snd ', kSoundBase, "\p"); anErr = ResError(); } nrequire(anErr, AddSoundResourceFailed); } // remove any old styles resourceHandle = Get1Resource('styl', 128); if (resourceHandle) { RemoveResource(resourceHandle); DisposeHandle(resourceHandle); } // save the new style -- get the scrap handle from the TE record // To do this, we must select the text -- BUT doing so through the // TextEdit API results in lots of flashing and annoying behavior. // So we just change the offsets by hand { short oldStart, oldEnd; oldStart = (** ((TextDataPtr) pData)->hTE).selStart; oldEnd = (** ((TextDataPtr) pData)->hTE).selEnd; (** ((TextDataPtr) pData)->hTE).selStart = 0; (** ((TextDataPtr) pData)->hTE).selEnd = 32767; resourceHandle = (Handle) TEGetStyleScrapHandle( ((TextDataPtr) pData)->hTE ); (** ((TextDataPtr) pData)->hTE).selStart = oldStart; (** ((TextDataPtr) pData)->hTE).selEnd = oldEnd; } if (resourceHandle) { AddResource(resourceHandle, 'styl', 128, "\p"); anErr = ResError(); nrequire(anErr, AddStyleResourceFailed); } AddSoundResourceFailed: AddStyleResourceFailed: UpdateResFile(pData->resRefNum); UseResFile(oldResFile); } // FALL THROUGH EXCEPTION HANDLING FailedWrite: // if everything went okay, then clear the changed bit if (anErr == noErr) { (void) FlushVol("\p", pData->fileSpec.vRefNum); // we need not update the proxy here, since the file type never changes in this routine SetDocumentContentChanged( pData, false ); } return anErr; } // TextSave // -------------------------------------------------------------------------------------------------------------- #if 0 static pascal void DrawTextUserItem(DialogPtr dPtr, short theItem) /* Draw text icon in the location */ { short kind; Handle itemHandle; Rect box; GetDialogItem(dPtr, theItem, &kind, &itemHandle, &box); PlotIconID(&box, ttNone, ttNone, kTextIcon); } // DrawTextUserItem #if GENERATINGCFM static RoutineDescriptor gDrawTextUserItemRD = BUILD_ROUTINE_DESCRIPTOR(uppUserItemProcInfo, DrawTextUserItem); static UserItemUPP gDrawTextUserItem = &gDrawTextUserItemRD; #else static UserItemUPP gDrawTextUserItem = NewUserItemProc(DrawTextUserItem); #endif #endif // -------------------------------------------------------------------------------------------------------------- #if 0 static pascal void DrawStationeryUserItem(DialogPtr dPtr, short theItem) /* Draw stationery icon in the location */ { short kind; Handle itemHandle; Rect box; GetDialogItem(dPtr, theItem, &kind, &itemHandle, &box); PlotIconID(&box, ttNone, ttNone, kStationeryIcon); } // DrawStationeryUserItem #if GENERATINGCFM static RoutineDescriptor gDrawStationeryUserItemRD = BUILD_ROUTINE_DESCRIPTOR(uppUserItemProcInfo, DrawStationeryUserItem); static UserItemUPP gDrawStationeryUserItem = &gDrawStationeryUserItemRD; #else static UserItemUPP gDrawStationeryUserItem = NewUserItemProc(DrawStationeryUserItem); #endif #endif // -------------------------------------------------------------------------------------------------------------- // pre and post update procs for inline input static pascal void TSMPreUpdateProc(TEHandle textH, long refCon) { #pragma unused (refCon) ScriptCode keyboardScript; short mode; TextStyle theStyle; keyboardScript = GetScriptManagerVariable(smKeyScript); mode = doFont; if (! ( (TEContinuousStyle(&mode, &theStyle, textH) )&& (FontToScript(theStyle.tsFont) == keyboardScript) ) ) { theStyle.tsFont = GetScriptVariable(keyboardScript, smScriptAppFond); TESetStyle(doFont, &theStyle, false, textH); } } // TSMPreUpdateProc static pascal void TSMPostUpdateProc( TEHandle textH, long fixLen, long inputAreaStart, long inputAreaEnd, long pinStart, long pinEnd, long refCon) { #pragma unused (textH, fixLen, inputAreaStart, inputAreaEnd, pinStart, pinEnd) AdjustScrollBars((WindowPtr)refCon, false, false, nil); AdjustTE((WindowDataPtr)refCon, true); SetDocumentContentChanged( (WindowDataPtr)refCon, true ); } // TSMPostUpdateProc // -------------------------------------------------------------------------------------------------------------- #if 0 static pascal short SaveDialogHook(short item, DialogPtr dPtr, Boolean *isText) { short theType; Handle theHandle; Rect theRect; short returnValue = item; switch (item) { case sfHookFirstCall: if (GetWRefCon(GetDialogWindow(dPtr)) == sfMainDialogRefCon) { GetDialogItem(dPtr, iTextUserItem, &theType, &theHandle, &theRect); theHandle = (Handle) gDrawTextUserItem; SetDialogItem(dPtr, iTextUserItem, theType, theHandle, &theRect); GetDialogItem(dPtr, iStationeryUserItem, &theType, &theHandle, &theRect); theHandle = (Handle) gDrawStationeryUserItem; SetDialogItem(dPtr, iStationeryUserItem, theType, theHandle, &theRect); GetDialogItem(dPtr, iTextDocumentItem, &theType, &theHandle, &theRect); SetControlValue((ControlHandle) theHandle, 1); GetDialogItem(dPtr, iStationeryDocumentItem, &theType, &theHandle, &theRect); SetControlValue((ControlHandle) theHandle, 0); *isText = true; } break; case iTextDocumentItem: case iTextUserItem: GetDialogItem(dPtr, iTextDocumentItem, &theType, &theHandle, &theRect); SetControlValue((ControlHandle) theHandle, 1); GetDialogItem(dPtr, iStationeryDocumentItem, &theType, &theHandle, &theRect); SetControlValue((ControlHandle) theHandle, 0); *isText = true; returnValue = sfHookNullEvent; break; case iStationeryDocumentItem: case iStationeryUserItem: GetDialogItem(dPtr, iTextDocumentItem, &theType, &theHandle, &theRect); SetControlValue((ControlHandle) theHandle, 0); GetDialogItem(dPtr, iStationeryDocumentItem, &theType, &theHandle, &theRect); SetControlValue((ControlHandle) theHandle, 1); *isText = false; returnValue = sfHookNullEvent; break; } return returnValue; } // SaveDialogHook #if GENERATINGCFM static RoutineDescriptor gSaveDialogHookRD = BUILD_ROUTINE_DESCRIPTOR(uppDlgHookYDProcInfo, SaveDialogHook); static DlgHookYDUPP gSaveDialogHook = &gSaveDialogHookRD; #else static DlgHookYDUPP gSaveDialogHook = NewDlgHookYDProc(SaveDialogHook); #endif #endif // -------------------------------------------------------------------------------------------------------------- #if 0 // Handle update/activate events behind Standard File static pascal Boolean SaveDialogFilter(DialogPtr theDialog, EventRecord *theEvent, short *itemHit, void *myDataPtr) { #pragma unused(myDataPtr) if (StdFilterProc(theDialog, theEvent, itemHit)) return true; // Pass updates through (Activates are tricky...was mucking with Apple menu & thereby // drastically changing how the system handles the menu bar during our alert) if (theEvent->what == updateEvt /* || theEvent->what == activateEvt */ ) { HandleEvent(theEvent); } return false; } // SaveDialogFilter #if GENERATINGCFM static RoutineDescriptor gSaveDialogFilterRD = BUILD_ROUTINE_DESCRIPTOR(uppModalFilterYDProcInfo, SaveDialogFilter); static ModalFilterYDUPP gSaveDialogFilter = &gSaveDialogFilterRD; #else static ModalFilterYDUPP gSaveDialogFilter = NewModalFilterYDProc(SaveDialogFilter); #endif #endif // -------------------------------------------------------------------------------------------------------------- static OSErr TextSaveAs(WindowPtr pWindow, WindowDataPtr pData) { OSErr anErr = noErr; short oldRes, oldData; Boolean documentIsText = false; FSSpec fileSpec; FSSpec oldFileLocation; Boolean replacing; NavReplyRecord navReply; // save the old references -- if there is an error, we restore them oldRes = pData->resRefNum; oldData = pData->dataRefNum; oldFileLocation = pData->fileSpec; // ask where and how to save this document { Str255 defaultName; Cursor arrow; // Point where = {-1, -1}; // setup for the call GetWTitle(pWindow, defaultName); SetCursor(GetQDGlobalsArrow(&arrow)); // find out where the user wants the file if (gMachineInfo.haveNavigationServices) { Boolean stationery; anErr = SaveFileDialog(defaultName, pData->originalFileType, 'ttxt', MyEventProc, &fileSpec, &stationery, &replacing, &navReply); if ( anErr == userCanceledErr) anErr = eUserCanceled; documentIsText = stationery == false; } else { #if(0) StandardFileReply sfReply; CustomPutFile("\p", defaultName, &sfReply, kTextSaveAsDialogID, where, gSaveDialogHook, gSaveDialogFilter, nil, nil, &documentIsText); // map the cancel button into a cancelling error if (!sfReply.sfGood) anErr = eUserCanceled; replacing = sfReply.sfReplacing; fileSpec = sfReply.sfFile; #endif } } // can't replace over other types if (replacing) { FInfo theInfo; FSpGetFInfo(&fileSpec, &theInfo); if ( (theInfo.fdType != 'TEXT') && (theInfo.fdType != 'sEXT') ) anErr = eDocumentWrongKind; } nrequire(anErr, StandardPutFile); // // Same file? If so, just save. // if ( (replacing) && (oldData != -1) && (pData->fileSpec.vRefNum == fileSpec.vRefNum) && (pData->fileSpec.parID == fileSpec.parID) && EqualString(pData->fileSpec.name, fileSpec.name, false, false) ) { anErr = TextSave(pData, true); if (anErr == noErr) UpdateFileInfo(&fileSpec, documentIsText); } else { // create the data file and resource fork (void) FSpDelete(&fileSpec); anErr = FSpCreate(&fileSpec, 'ttxt', 'TEXT', 0); FSpCreateResFile(&fileSpec, 'ttxt', 'TEXT', 0); nrequire(anErr, FailedCreate); // set the stationary bit, if we must if (!documentIsText) { FInfo theInfo; FSpGetFInfo(&fileSpec, &theInfo); theInfo.fdFlags |= kIsStationery; FSpSetFInfo(&fileSpec, &theInfo); } // open both of forks anErr = FSpOpenDF(&fileSpec, fsRdWrPerm, &pData->dataRefNum); if (anErr == noErr) { pData->resRefNum = FSpOpenResFile(&fileSpec, fsRdWrPerm); anErr = ResError(); } nrequire(anErr, FailedOpen); pData->fileSpec = fileSpec; // call the standard save function to do the save anErr = TextSave(pData, false); // FALL THROUGH EXCEPTION HANDLING FailedOpen: FSpDelete(&fileSpec); FailedCreate: StandardPutFile: // finally, close the old files if everything went okay if (anErr == noErr) { if (oldRes != -1) CloseResFile(oldRes); if (oldData != -1) FSClose(oldData); pData->isWritable = true; SetWTitle(pWindow, fileSpec.name); // update the window icon if( gMachineInfo.haveProxyIcons ) SetWindowProxyFSSpec( pWindow, &fileSpec ); } else { pData->resRefNum = oldRes; pData->dataRefNum = oldData; pData->fileSpec = oldFileLocation; } } // save new location- •••why here, though? Need to do it earlier to avoid stale pData->fileSpec in the call to TextSave() above. I added a save and restore for the original spec in case of error. - christ // if (anErr == noErr) // BlockMoveData(&fileSpec, &pData->fileSpec, sizeof(FSSpec)); if( anErr != noErr ) { // restore oldfile location on error BlockMoveData( &oldFileLocation, &pData->fileSpec, sizeof(FSSpec) ); } // Return eUserCanceled so we can avoid closing/quitting if they cancel the SF dialog // // don't propagate this error // if (anErr == eUserCanceled) // anErr = noErr; if (gMachineInfo.haveNavigationServices) { // file must be closed in order to 'translate in place', so close both forks: if (pData->resRefNum != -1) CloseResFile(pData->resRefNum); if (pData->dataRefNum != -1) FSClose(pData->dataRefNum); if (anErr == noErr) { anErr = CompleteSave(&fileSpec, &navReply); // do the translation (in place) // reopen both forks: anErr = FSpOpenDF(&fileSpec, fsRdWrPerm, &pData->dataRefNum); if (anErr == noErr) { pData->resRefNum = FSpOpenResFile(&fileSpec, fsRdWrPerm); anErr = ResError(); } // •• problem: // Save As… model fails in this case because we still have the old window open, // Should SimpleText close the current text window and open a new one with the translated data? } } return anErr; } // TextSaveAs // -------------------------------------------------------------------------------------------------------------- static void ApplyFace(short requestedFace, WindowPtr pWindow, WindowDataPtr pData, short commandID) { TextStyle style; short oldNumLines = (**(((TextDataPtr) pData)->hTE)).nLines; SaveCurrentUndoState(pData, commandID); style.tsFace = requestedFace; TESetStyle(doFace + ((requestedFace != normal) ? doToggle : 0), &style, true, ((TextDataPtr) pData)->hTE); TECalText(((TextDataPtr) pData)->hTE); oldNumLines -= (**(((TextDataPtr) pData)->hTE)).nLines; AdjustTE(pData, false); AdjustScrollBars(pWindow, (oldNumLines > 0), (oldNumLines > 0), nil); SetDocumentContentChanged( (WindowDataPtr) pData, true ); } // ApplyFace // -------------------------------------------------------------------------------------------------------------- static OSErr ApplySize(short requestedSize, WindowPtr pWindow, WindowDataPtr pData, short commandID) { OSErr anErr = noErr; TextStyle style; short oldNumLines = (**(((TextDataPtr) pData)->hTE)).nLines; SaveCurrentUndoState(pData, commandID); style.tsSize = requestedSize; TESetStyle(doSize, &style, true, ((TextDataPtr) pData)->hTE); TECalText(((TextDataPtr) pData)->hTE); if (CalculateTextEditHeight(((TextDataPtr) pData)->hTE) > 32767) { style.tsSize = 0; TESetStyle(doSize, &style, true, ((TextDataPtr) pData)->hTE); anErr = eDocumentTooLarge; } oldNumLines -= (**(((TextDataPtr) pData)->hTE)).nLines; AdjustTE(pData, false); AdjustScrollBars(pWindow, (oldNumLines > 0), (oldNumLines > 0), nil); SetDocumentContentChanged( (WindowDataPtr) pData, true ); return anErr; } // ApplySize // -------------------------------------------------------------------------------------------------------------- // OOP INTERFACE ROUTINES // -------------------------------------------------------------------------------------------------------------- static OSErr TextUpdateWindow(WindowPtr pWindow, WindowDataPtr pData) { Rect updateArea = pData->contentRect; Rect bounds; // be sure to also erase the area where the horizontal scroll bar // is missing. updateArea.bottom = GetWindowPortBounds(pWindow, &bounds)->bottom; EraseRect(&updateArea); TEUpdate(&pData->contentRect, ((TextDataPtr) pData)->hTE); DrawPictures(pData, ((TextDataPtr) pData)->hTE); DrawControls(pWindow); DrawGrowIcon(pWindow); #if 0 /* The voices thread is initially created in a suspended state; once we've received an update for a text window, we enable the thread. This is done because the initial CountVoices and GetVoiceDescription in the voices thread are quite slow, and if the thread is initially enabled, they delay the first update of the text window. We're interested in optimizing for fast display of the window's contents so we delay the thread until the update has occurred. */ if (gVoicesThread != kNoThreadID) SetThreadState(gVoicesThread, kReadyThreadState, gVoicesThread); #endif return noErr; } // TextUpdateWindow // -------------------------------------------------------------------------------------------------------------- static OSErr TextCloseWindow(WindowPtr pWindow, WindowDataPtr pData) { #pragma unused (pWindow) // shut down text services if (pData->docTSMDoc) { FixTSMDocument(pData->docTSMDoc); DeactivateTSMDocument(pData->docTSMDoc); DeleteTSMDocument(pData->docTSMDoc); } #if 0 DisposeOfSpeech(true); #endif DisposeHandle(((TextDataPtr) pData)->soundHandle); TEDispose(((TextDataPtr) pData)->hTE); DisposeHandle(((TextDataPtr) pData)->prevText); DisposeHandle(((TextDataPtr) pData)->prevStyle); TextRemoveContentsMenu(pData); return noErr; } // TextCloseWindow // -------------------------------------------------------------------------------------------------------------- static OSErr TextActivateEvent(WindowPtr pWindow, WindowDataPtr pData, Boolean activating) { #pragma unused (pWindow) // only modifyable docs can be active if (pData->originalFileType == 'TEXT') { if (activating) { TEActivate(((TextDataPtr) pData)->hTE); if (pData->docTSMDoc != nil) ActivateTSMDocument(pData->docTSMDoc); } else { TEDeactivate(((TextDataPtr) pData)->hTE); if (pData->docTSMDoc != nil) DeactivateTSMDocument(pData->docTSMDoc); } } // add contents menu, if appropriate if (activating) { TextAddContentsMenu(pData); } else { TextRemoveContentsMenu(pData); } return noErr; } // TextActivateEvent // -------------------------------------------------------------------------------------------------------------- static Boolean TextFilterEvent(WindowPtr pWindow, WindowDataPtr pData, EventRecord *pEvent) { switch (pEvent->what) { case nullEvent: if (pData->originalFileType == 'TEXT') { if ( pWindow == FrontWindow() ) TEIdle(((TextDataPtr) pData)->hTE); } #if 0 // if we stop speaking, ditch the channel if (gSpeechChannel) { SpeechStatusInfo status; // Status of our speech channel. if ( (GetSpeechInfo( gSpeechChannel, soStatus, (void*) &status ) == noErr) && (!status.outputBusy ) ) DisposeOfSpeech(true); } #endif break; } return false; } // TextFilterEvent // -------------------------------------------------------------------------------------------------------------- static OSErr TextScrollContent(WindowPtr pWindow, WindowDataPtr pData, short deltaH, short deltaV) { GrafPtr port = (GrafPtr) GetWindowPort(pWindow); RgnHandle srcRgn, dstRgn, visRgn, clipRgn; Rect viewRect; // scroll the text area TEScroll(deltaH, deltaV, ((TextDataPtr) pData)->hTE); // calculate the region that is uncovered by the scroll srcRgn = NewRgn(); dstRgn = NewRgn(); visRgn = NewRgn(); clipRgn = NewRgn(); GetPortVisibleRegion(port, visRgn); GetPortClipRegion(port, clipRgn); viewRect = (**((TextDataPtr) pData)->hTE).viewRect; RectRgn( srcRgn, &viewRect ); SectRgn( srcRgn, visRgn, srcRgn ); SectRgn( srcRgn, clipRgn, srcRgn ); CopyRgn( srcRgn, dstRgn ); OffsetRgn( dstRgn, deltaH, deltaV ); SectRgn( srcRgn, dstRgn, dstRgn ); DiffRgn( srcRgn, dstRgn, srcRgn ); // clip to this new area GetClip(dstRgn); SetClip(srcRgn); DrawPictures(pData, ((TextDataPtr) pData)->hTE); SetClip(dstRgn); // all done with these calculation regions DisposeRgn( srcRgn ); DisposeRgn( dstRgn ); DisposeRgn( visRgn ); DisposeRgn( clipRgn ); return eActionAlreadyHandled; } // TextScrollContent // -------------------------------------------------------------------------------------------------------------- static OSErr TextKeyEvent(WindowPtr pWindow, WindowDataPtr pData, EventRecord *pEvent, Boolean isMotionKey) { OSErr anErr = noErr; if (!(pEvent->modifiers & cmdKey)) { char theKey = pEvent->message & charCodeMask; char theKeyCode = (pEvent->message >> 8) & charCodeMask; if ( ((** ((TextDataPtr) pData)->hTE).teLength+1 > kMaxLength) && ( (theKey != kDeleteKey) && (theKeyCode != kForwardDeleteKey) && (theKeyCode != kUpArrow) && (theKeyCode != kDownArrow) && (theKeyCode != kRightArrow) && (theKeyCode != kLeftArrow) ) ) anErr = eDocumentTooLarge; else { long oldHeight = CalculateTextEditHeight(((TextDataPtr) pData)->hTE); long end = (**(((TextDataPtr) pData)->hTE)).selEnd; long start = (**(((TextDataPtr) pData)->hTE)).selStart; ObscureCursor(); SaveCurrentUndoState(pData, cTypingCommand); if (theKeyCode != kForwardDeleteKey) { if (pEvent->modifiers & shiftKey) { switch (theKeyCode) { case kUpArrow: case kLeftArrow: TEKey(theKey, ((TextDataPtr) pData)->hTE); TESetSelect((**(((TextDataPtr) pData)->hTE)).selStart, end, ((TextDataPtr) pData)->hTE); break; case kDownArrow: case kRightArrow: TESetSelect(end, end, ((TextDataPtr) pData)->hTE); TEKey(theKey, ((TextDataPtr) pData)->hTE); TESetSelect(start, (**(((TextDataPtr) pData)->hTE)).selEnd, ((TextDataPtr) pData)->hTE); break; default: TEKey(theKey, ((TextDataPtr) pData)->hTE); } } else TEKey(theKey, ((TextDataPtr) pData)->hTE); } else { if (gTE6Version) { TEKey(theKey, ((TextDataPtr) pData)->hTE); } else { if ( end < (**(((TextDataPtr) pData)->hTE)).teLength ) { if (start == end) TEKey(kRightArrowCharCode, ((TextDataPtr) pData)->hTE); TEKey(kBackspaceCharCode, ((TextDataPtr) pData)->hTE); } } } oldHeight -= CalculateTextEditHeight(((TextDataPtr) pData)->hTE); // AEC, changed pWindow to pData for 'TextDataPtr' casts ((TextDataPtr) pData)->insideClickLoop = true; AdjustTE(pData, false); AdjustScrollBars(pWindow, (oldHeight > 0), (oldHeight > 0), nil); ((TextDataPtr) pData)->insideClickLoop = false; if (!isMotionKey) { SetDocumentContentChanged( pData, true ); } } } return anErr; } // TextKeyEvent // -------------------------------------------------------------------------------------------------------------- static OSErr TextContentClick(WindowPtr pWindow, WindowDataPtr pData, EventRecord *pEvent) { OSErr anErr = noErr; Point clickPoint = pEvent->where; ControlHandle theControl; RgnHandle hilightRgn; GlobalToLocal(&clickPoint); if (FindControl(clickPoint, pWindow, &theControl) == 0) { if (gMachineInfo.haveDragMgr) { hilightRgn = NewRgn(); TEGetHiliteRgn(hilightRgn, ((TextDataPtr) pData)->hTE); if (PtInRgn(clickPoint, hilightRgn)) { SaveCurrentUndoState(pData, cTypingCommand); if (!DragText(pWindow, pData, pEvent, hilightRgn)) anErr = eActionAlreadyHandled; } else { anErr = eActionAlreadyHandled; } DisposeRgn(hilightRgn); } else { anErr = eActionAlreadyHandled; } } if ( (anErr == eActionAlreadyHandled) && (PtInRect(clickPoint, &pData->contentRect)) ) { TEClick(clickPoint, (pEvent->modifiers & shiftKey) != 0, ((TextDataPtr) pData)->hTE); } return anErr; } // TextContentClick // -------------------------------------------------------------------------------------------------------------- static OSErr TextAdjustSize(WindowPtr pWindow, WindowDataPtr pData, Boolean *didReSize) // input: was window resized, output: t->we resized something { #pragma unused (pWindow) if (*didReSize) { RecalcTE(pData, true); AdjustTE(pData, true); } else { AdjustTE(pData, false); } return noErr; } // TextAdjustSize static OSErr TextCommand(WindowPtr pWindow, WindowDataPtr pData, short commandID, long menuResult) { OSErr anErr = noErr; AEDesc theSourceText = {typeNull, NULL}; // for AppleScript AEDesc theResultText = {typeNull, NULL}; // for AppleScript OSAID resultID; // for AppleScript Ptr resultTextPtr; // for AppleScript Size resultDataSize; // for AppleScript SetPort((GrafPtr) GetWindowPort(pWindow)); if ( (pData->docTSMDoc) && (menuResult != 0) ) FixTSMDocument(pData->docTSMDoc); switch (commandID) { case cUndo: { short oldNumLines = (**(((TextDataPtr) pData)->hTE)).nLines; PerformUndo(pData); oldNumLines -= (**(((TextDataPtr) pData)->hTE)).nLines; AdjustTE(pData, false); AdjustScrollBars(pWindow, (oldNumLines > 0), (oldNumLines > 0), nil); RecalcTE(pData, true); SetDocumentContentChanged( pData, true ); } break; case cCut: SaveCurrentUndoState(pData, cCut); { short oldNumLines = (**(((TextDataPtr) pData)->hTE)).nLines; TECut(((TextDataPtr) pData)->hTE); // no need for TEToScrap with styled TE record oldNumLines -= (**(((TextDataPtr) pData)->hTE)).nLines; AdjustTE(pData, false); AdjustScrollBars(pWindow, (oldNumLines > 0), (oldNumLines > 0), nil); SetDocumentContentChanged( (WindowDataPtr) pData, true ); } break; case cCopy: TECopy(((TextDataPtr) pData)->hTE); // no need for TEToScrap with styled TE record AdjustTE(pData, false); break; case cClear: SaveCurrentUndoState(pData, cClear); { short oldNumLines = (**(((TextDataPtr) pData)->hTE)).nLines; TEDelete(((TextDataPtr) pData)->hTE); oldNumLines -= (**(((TextDataPtr) pData)->hTE)).nLines; AdjustTE(pData, false); AdjustScrollBars(pWindow, (oldNumLines > 0), (oldNumLines > 0), nil); SetDocumentContentChanged( pData, true ); } break; case cPaste: SaveCurrentUndoState(pData, cPaste); { short oldNumLines = (**(((TextDataPtr) pData)->hTE)).nLines; anErr = TEFromScrap(); if (anErr == noErr) { // if the current length, plus the paste data, minus the data in the selection // would make the document too large, say so if ( ((** ((TextDataPtr) pData)->hTE).teLength + TEGetScrapLength() - ((** ((TextDataPtr) pData)->hTE).selEnd-(** ((TextDataPtr) pData)->hTE).selStart) ) > kMaxLength) { anErr = eDocumentTooLarge; } else { Handle aHandle = (Handle) TEGetText(((TextDataPtr) pData)->hTE); Size oldSize = GetHandleSize(aHandle); Size newSize = oldSize + TEGetScrapLength(); OSErr saveErr; SetHandleSize(aHandle, newSize); saveErr = MemError(); SetHandleSize(aHandle, oldSize); if (saveErr != noErr) anErr = eDocumentTooLarge; else TEStylePaste(((TextDataPtr) pData)->hTE); } UnloadScrap(); } oldNumLines -= (**(((TextDataPtr) pData)->hTE)).nLines; AdjustTE(pData, false); AdjustScrollBars(pWindow, (oldNumLines > 0), (oldNumLines > 0), nil); SetDocumentContentChanged( pData, true ); } break; case cReplace: SaveCurrentUndoState(pData, cReplace); { short result = ConductFindOrReplaceDialog(kReplaceWindowID); if (result == cancel) break; if (result == iReplaceAll) { long newStart, newEnd; short oldNumLines = (**(((TextDataPtr) pData)->hTE)).nLines; TESetSelect(0, 0, ((TextDataPtr) pData)->hTE); while (PerformSearch((**((TextDataPtr) pData)->hTE).hText, (**((TextDataPtr) pData)->hTE).selStart, gFindString, gCaseSensitive, false, false, &newStart, &newEnd)) { TESetSelect(newStart, newEnd, ((TextDataPtr) pData)->hTE); TEDelete(((TextDataPtr) pData)->hTE); TEInsert(&gReplaceString[1], gReplaceString[0], ((TextDataPtr) pData)->hTE); TESetSelect(newStart+gReplaceString[0], newStart+gReplaceString[0], ((TextDataPtr) pData)->hTE); SetDocumentContentChanged( (WindowDataPtr) pData, true ); } oldNumLines -= (**(((TextDataPtr) pData)->hTE)).nLines; AdjustTE(pData, false); AdjustScrollBars(pWindow, (oldNumLines > 0), (oldNumLines > 0), nil); anErr = eActionAlreadyHandled; break; } } // fall through from replace case cReplaceAgain: SaveCurrentUndoState(pData, cReplaceAgain); { long newStart, newEnd; Boolean isBackwards = ((gEvent.modifiers & shiftKey) != 0); if (PerformSearch((**((TextDataPtr) pData)->hTE).hText, isBackwards ? (**((TextDataPtr) pData)->hTE).selEnd : (**((TextDataPtr) pData)->hTE).selStart, gFindString, gCaseSensitive, isBackwards, gWrapAround, &newStart, &newEnd)) { short oldNumLines = (**(((TextDataPtr) pData)->hTE)).nLines; TESetSelect(newStart, newEnd, ((TextDataPtr) pData)->hTE); TEDelete(((TextDataPtr) pData)->hTE); TEInsert(&gReplaceString[1], gReplaceString[0], ((TextDataPtr) pData)->hTE); TESetSelect(newStart+gReplaceString[0], newStart+gReplaceString[0], ((TextDataPtr) pData)->hTE); oldNumLines -= (**(((TextDataPtr) pData)->hTE)).nLines; AdjustTE(pData, false); AdjustScrollBars(pWindow, (oldNumLines > 0), (oldNumLines > 0), nil); SetDocumentContentChanged( pData, true ); } else SysBeep(1); anErr = eActionAlreadyHandled; } break; // for AppleScript case cExecute: { fprintf(stderr, "Attempting to execute and applescript selection.\n"); SaveCurrentUndoState(pData, cExecute); AECreateDesc(typeChar, (*(**((TextDataPtr) pData)->hTE).hText) + (**((TextDataPtr) pData)->hTE).selStart, (**((TextDataPtr) pData)->hTE).selEnd - (**((TextDataPtr) pData)->hTE).selStart, &theSourceText); anErr = OSACompileExecute( gOSAComponent, &theSourceText, kOSANullScript, kAECanInteract, &resultID); if (anErr == noErr) { anErr = OSADisplay( gOSAComponent, resultID, typeChar, kOSAModeNull, &theResultText); if (anErr == noErr) { TESetSelect((**((TextDataPtr) pData)->hTE).selEnd, (**((TextDataPtr) pData)->hTE).selEnd, ((TextDataPtr) pData)->hTE); resultTextPtr = NewPtr(resultDataSize = AEGetDescDataSize(&theResultText)); AEGetDescData(&theResultText, theResultText.descriptorType, resultTextPtr, resultDataSize); TEInsert(" --> ", 5, ((TextDataPtr) pData)->hTE); TEInsert(resultTextPtr, resultDataSize, ((TextDataPtr) pData)->hTE); AdjustTE(pData, false); DisposePtr(resultTextPtr); AEDisposeDesc(&theResultText); } else fprintf(stderr, "OSADisplay returned error %i.\n", anErr); } else fprintf(stderr, "OSACompileExecute returned error %i.\n", anErr); anErr = OSADispose( gOSAComponent, resultID); AEDisposeDesc(&theSourceText); SetDocumentContentChanged( pData, true); break; } case cFind: case cFindSelection: if (commandID == cFind) { if (ConductFindOrReplaceDialog(kFindWindowID) == cancel) break; } else { gFindString[0] = (**((TextDataPtr) pData)->hTE).selEnd - (**((TextDataPtr) pData)->hTE).selStart; BlockMoveData( (*(**((TextDataPtr) pData)->hTE).hText) + (**((TextDataPtr) pData)->hTE).selStart, &gFindString[1], gFindString[0]); } // fall through from find or find selection case cFindAgain: { long newStart, newEnd; Boolean isBackwards = ((gEvent.modifiers & shiftKey) != 0); if (PerformSearch((**((TextDataPtr) pData)->hTE).hText, isBackwards ? (**((TextDataPtr) pData)->hTE).selStart : (**((TextDataPtr) pData)->hTE).selEnd, gFindString, gCaseSensitive, isBackwards, gWrapAround, &newStart, &newEnd)) { TESetSelect(newStart, newEnd, ((TextDataPtr) pData)->hTE); AdjustTE(pData, false); AdjustScrollBars(pWindow, false, false, nil); } else SysBeep(1); anErr = eActionAlreadyHandled; } break; case cSelectAll: TESetSelect(0, (**((TextDataPtr) pData)->hTE).teLength, ((TextDataPtr) pData)->hTE); AdjustTE(pData, false); AdjustScrollBars(pWindow, false, false, nil); DoAdjustCursor(pWindow, nil); anErr = eActionAlreadyHandled; break; // save turns into save as if this is a new document or if the original wasn't // available for writing case cSave: if ( (!pData->isWritable) || ( (pData->dataRefNum == -1) && (pData->resRefNum == -1) ) ) anErr = TextSaveAs(pWindow, pData); else anErr = TextSave(pData, true); break; case cSaveAs: anErr = TextSaveAs(pWindow, pData); break; // SUPPORTED FONTS case cSelectFont: SaveCurrentUndoState(pData, cSelectFont); { Str255 itemName; TextStyle theStyle; short oldNumLines = (**(((TextDataPtr) pData)->hTE)).nLines; GetMenuItemText( GetMenuHandle( menuResult>>16 ), menuResult & 0xFFFF, itemName ); GetFNum( itemName, &theStyle.tsFont ); TESetStyle( doFont, &theStyle, true, ((TextDataPtr) pData)->hTE ); TECalText(((TextDataPtr) pData)->hTE); oldNumLines -= (**(((TextDataPtr) pData)->hTE)).nLines; AdjustTE(pData, false); AdjustScrollBars(pWindow, (oldNumLines > 0), (oldNumLines > 0), nil); SetDocumentContentChanged( pData, true ); } break; // SUPPORTED STYLES case cPlain: ApplyFace(normal, pWindow, pData, cPlain); break; case cBold: ApplyFace(bold, pWindow, pData, cBold); break; case cItalic: ApplyFace(italic, pWindow, pData, cItalic); break; case cUnderline: ApplyFace(underline, pWindow, pData, cUnderline); break; case cOutline: ApplyFace(outline, pWindow, pData, cOutline); break; case cShadow: ApplyFace(shadow, pWindow, pData, cShadow); break; case cCondensed: ApplyFace(condense, pWindow, pData, cCondensed); break; case cExtended: ApplyFace(extend, pWindow, pData, cExtended); break; // SUPPORTED SIZES case cSize9: anErr = ApplySize(9, pWindow, pData, cSize9); break; case cSize10: anErr = ApplySize(10, pWindow, pData, cSize10); break; case cSize12: anErr = ApplySize(12, pWindow, pData, cSize12); break; case cSize14: anErr = ApplySize(14, pWindow, pData, cSize14); break; case cSize18: anErr = ApplySize(18, pWindow, pData, cSize18); break; case cSize24: anErr = ApplySize(24, pWindow, pData, cSize24); break; case cSize36: anErr = ApplySize(36, pWindow, pData, cSize36); break; #if 0 // SUPPORTED SOUND COMMANDS case cRecord: { Handle tempHandle; // allocate our prefered buffer if we can, but if we can't // make sure that at least a minimum amount of RAM is around // before recording. tempHandle = NewHandle(kPrefBufferSize); anErr = MemError(); if (anErr != noErr) { tempHandle = NewHandle(kMinBufferSize); anErr = MemError(); DisposeHandle(tempHandle); tempHandle = nil; } // if the preflight goes okay, do the record if (anErr == noErr) { Point where = {50, 100}; anErr = SndRecord(nil, where, siGoodQuality, (SndListHandle*) &tempHandle); if (anErr == noErr) { DisposeHandle(((TextDataPtr) pData)->soundHandle); ((TextDataPtr) pData)->soundHandle = tempHandle; SetDocumentContentChanged( (WindowDataPtr) pData, true ); } else DisposeHandle(tempHandle); if (anErr == userCanceledErr) anErr = noErr; } } break; case cPlay: if (((TextDataPtr) pData)->soundHandle) (void) SndPlay(nil, (SndListHandle) ((TextDataPtr) pData)->soundHandle, false); break; case cErase: DisposeHandle(((TextDataPtr) pData)->soundHandle); ((TextDataPtr) pData)->soundHandle = nil; SetDocumentContentChanged( (WindowDataPtr) pData, true ); break; case cSpeak: DisposeOfSpeech(false); if (gSpeechChannel == nil) anErr = NewSpeechChannel( &gCurrentVoice, &gSpeechChannel ); if ( anErr == noErr ) { short textLength, textStart; // determine which text to speak if ( (**((TextDataPtr) pData)->hTE).selEnd > (**((TextDataPtr) pData)->hTE).selStart ) // If there is a selection. { textLength = (**((TextDataPtr) pData)->hTE).selEnd - (**((TextDataPtr) pData)->hTE).selStart; textStart = (**((TextDataPtr) pData)->hTE).selStart; } else // No text selected. { textLength = (**((TextDataPtr) pData)->hTE).teLength; textStart = 0; } gSpeakPtr = NewPtr(textLength); anErr = MemError(); if (anErr == noErr) { BlockMoveData( *((**((TextDataPtr) pData)->hTE).hText) + textStart, gSpeakPtr, (Size) textLength ); anErr = SpeakText( gSpeechChannel, gSpeakPtr, textLength ); } } break; case cStopSpeaking: DisposeOfSpeech(true); break; case cSelectVoiceSubMenu: { VoiceSpec newSpec; short i, menuIndex; Str255 itemText; short theVoiceCount; MenuHandle menu = GetMenuHandle(mVoices); // in order to change voices, we need to ditch the speaking DisposeOfSpeech(true); // get the name of the selected voice menuIndex = menuResult & 0xFFFF; GetMenuItemText(menu, menuIndex, itemText); if (CountVoices( &theVoiceCount ) == noErr) { VoiceDescription description; // Info about a voice. for (i = 1; i <= theVoiceCount; ++i) { if ( (GetIndVoice( i, &newSpec ) == noErr) && (GetVoiceDescription( &newSpec, &description, sizeof(description) ) == noErr ) ) { if (IUCompString( itemText, description.name ) == 0) break; } } } gCurrentVoice = newSpec; for (i = CountMItems(menu); i >= 1; --i) CheckItem(menu, i, (menuIndex == i)); } break; #endif case cSelectContents: { Str255 searchStr; short menuIndex; long newStart, newEnd; menuIndex = menuResult & 0xFFFF; // get the search string for this menu item anErr = TextGetContentsListItem(pData, menuIndex, nil, searchStr, nil); if (anErr == noErr) { if (PerformSearch( (**((TextDataPtr) pData)->hTE).hText, 0, // start at beginning of text searchStr, false, // not case sensitive false, // forwards false, // wrap &newStart, &newEnd)) { // <7> short amount; Point newSelectionPt; // get QuickDraw offset of found text, // scroll that amount plus a line height, // and add a fifth of the window for aesthetics (and // for slop to avoid fraction-of-line problems) newSelectionPt = TEGetPoint(newEnd, ((TextDataPtr) pData)->hTE); amount = - newSelectionPt.v + pData->vScrollAmount; amount += (pData->contentRect.bottom - pData->contentRect.top) / 5; SetControlAndClipAmount(pData->vScroll, &amount); if (amount != 0) { DoScrollContent(pWindow, pData, 0, amount); } // move selection to beginning of found text // (are the Adjust calls necessary?) TESetSelect(newStart, newStart, ((TextDataPtr) pData)->hTE); AdjustTE(pData, false); AdjustScrollBars(pWindow, false, false, nil); } else { // search failed SysBeep(10); } } } break; } return anErr; } // TextCommand // -------------------------------------------------------------------------------------------------------------- static OSErr TextPreMenuAccess(WindowPtr pWindow, WindowDataPtr pData) { #pragma unused (pWindow, pData) #if 0 // run to completion before allowing access to the voices menu if (gVoicesThread != kNoThreadID) { gDontYield = true; SetThreadState(gVoicesThread, kReadyThreadState, gVoicesThread); YieldToThread(gVoicesThread); gDontYield = false; } #endif return noErr; } // -------------------------------------------------------------------------------------------------------------- static OSErr TextAdjustMenus(WindowPtr pWindow, WindowDataPtr pData) { #pragma unused (pWindow) // enable the commands that we support for editable text document if (pData->originalFileType == 'TEXT') { if (((TextDataPtr) pData)->prevCommandID != cNull) EnableCommand(cUndo); if ( (**((TextDataPtr) pData)->hTE).selEnd > (**((TextDataPtr) pData)->hTE).selStart ) // If there is a selection. { EnableCommand(cCut); EnableCommand(cCopy); EnableCommand(cClear); EnableCommand(cExecute); // for AppleScript EnableCommand(cFindSelection); } TEFromScrap(); if (TEGetScrapLength() > 0) EnableCommand(cPaste); EnableCommand(cSaveAs); EnableCommand(cSelectAll); EnableCommand(cFind); EnableCommand(cReplace); if (gFindString[0] != 0) { EnableCommand(cFindAgain); EnableCommand(cReplaceAgain); } // enable all fonts, select the font current, if that's what's best EnableCommand(cSelectFont); { short mode = doFont; Str255 fontName, itemName; Str255 styleName; TextStyle theStyle; Boolean isCont; isCont = TEContinuousStyle(&mode, &theStyle, ((TextDataPtr) pData)->hTE); if (isCont) { GetFontName(theStyle.tsFont, fontName); } { MenuHandle menu = GetMenuHandle( mFont ); short count = CountMItems(menu); short index; for (index = 1; index <= count; ++index) { short mark; GetItemMark(menu, index, &mark); if (isCont) { GetMenuItemText( menu, index, itemName ); // don't change the checkmark if it's a heirarchichal menu, because // the mark actually holds the ID of sub-menu if ((mark == noMark) || (mark == checkMark)) { CheckItem(menu, index, EqualString(itemName, fontName, true, true) ); } else { // if it is a sub menu, we check there too MenuHandle subMenu = GetMenuHandle(mark); short subCount = CountMItems(subMenu); short subIndex; if (EqualString(itemName, fontName, true, true)) { SetItemStyle(menu, index, underline); for (subIndex = 1; subIndex <= subCount; ++subIndex) { GetMenuItemText(subMenu, subIndex, itemName); CheckItem(subMenu, subIndex, EqualString(itemName, styleName, true, true) ); } } else { SetItemStyle(menu, index, normal); for (subIndex = 1; subIndex <= subCount; ++subIndex) CheckItem(subMenu, subIndex, false ); } } } else { if ((mark == noMark) || (mark == checkMark)) CheckItem(menu, index, false); else SetItemStyle(menu, index, normal); } } } } // enable the sizes, and outline what's currently valid { short mode; TextStyle theStyle; Boolean isCont; short whichToCheck; // find out the continuous run of sizes whichToCheck = 0; mode = doSize; if (TEContinuousStyle(&mode, &theStyle, ((TextDataPtr) pData)->hTE)) { whichToCheck = theStyle.tsSize; // default font size -> proper size if (whichToCheck == 0) whichToCheck = GetDefFontSize(); } // find out the font runs mode = doFont; isCont = TEContinuousStyle(&mode, &theStyle, ((TextDataPtr) pData)->hTE); EnableCommandCheckStyle(cSize9, whichToCheck == 9, (isCont & RealFont(theStyle.tsFont, 9)) ? outline : normal); EnableCommandCheckStyle(cSize10, whichToCheck == 10, (isCont & RealFont(theStyle.tsFont, 10)) ? outline : normal); EnableCommandCheckStyle(cSize12, whichToCheck == 12, (isCont & RealFont(theStyle.tsFont, 12)) ? outline : normal); EnableCommandCheckStyle(cSize14, whichToCheck == 14, (isCont & RealFont(theStyle.tsFont, 14)) ? outline : normal); EnableCommandCheckStyle(cSize18, whichToCheck == 18, (isCont & RealFont(theStyle.tsFont, 18)) ? outline : normal); EnableCommandCheckStyle(cSize24, whichToCheck == 24, (isCont & RealFont(theStyle.tsFont, 24)) ? outline : normal); EnableCommandCheckStyle(cSize36, whichToCheck == 36, (isCont & RealFont(theStyle.tsFont, 36)) ? outline : normal); } { short mode = doFace; TextStyle theStyle; Style legalStyles; if (!TEContinuousStyle(&mode, &theStyle, ((TextDataPtr) pData)->hTE)) { theStyle.tsFace = normal; EnableCommandCheck(cPlain, false); } else EnableCommandCheck(cPlain, theStyle.tsFace == normal); // <39> use the script manager to determine legal styles for this // run of text. If the legal styles are zero (trap unimplemented), // then we assume all styles. legalStyles = GetScriptVariable(GetScriptManagerVariable(smKeyScript), smScriptValidStyles); if (legalStyles == 0) legalStyles = (Style)0xFFFF; if (legalStyles & bold) EnableCommandCheck(cBold, theStyle.tsFace & bold); if (legalStyles & italic) EnableCommandCheck(cItalic, theStyle.tsFace & italic); if (legalStyles & underline) EnableCommandCheck(cUnderline, theStyle.tsFace & underline); if (legalStyles & outline) EnableCommandCheck(cOutline, theStyle.tsFace & outline); if (legalStyles & shadow) EnableCommandCheck(cShadow, theStyle.tsFace & shadow); if (legalStyles & condense) EnableCommandCheck(cCondensed, theStyle.tsFace & condense); if (legalStyles & extend) EnableCommandCheck(cExtended, theStyle.tsFace & extend); } } // enable commands related to speaking the content if we have support for that if (gMachineInfo.haveTTS) { // note that none of this code should depend on the actual contents of the voices menu, // since the voices thread might not have finished setting up the menu yet #if 0 // if we are speaking, we can stop if (gSpeechChannel) EnableCommand(cStopSpeaking); #endif // even while speaking, you can re-speak or select a new voice EnableCommand(cSpeak); EnableCommand(cSelectVoice); EnableCommand(cSelectVoiceSubMenu); if ( (**((TextDataPtr) pData)->hTE).selEnd > (**((TextDataPtr) pData)->hTE).selStart ) // If there is a selection. ChangeCommandName(cSpeak, kTextStrings, iSpeakSelection); else ChangeCommandName(cSpeak, kTextStrings, iSpeakAll); } // enable the correct controls to go with sound input/output if (((TextDataPtr) pData)->soundHandle) EnableCommand(cPlay); if (pData->originalFileType == 'TEXT') { if (((TextDataPtr) pData)->soundHandle) EnableCommand(cErase); else { if (gMachineInfo.haveRecording) EnableCommand(cRecord); } } // enable the contents menu, if any (void) TextAdjustContentsMenu(pData); // enable commands that we support at all times if (GetControlMaximum(pData->vScroll) != 0) { EnableCommand(cNextPage); EnableCommand(cPreviousPage); } return noErr; } // TextAdjustMenus // -------------------------------------------------------------------------------------------------------------- static OSErr TextGetDocumentRect(WindowPtr pWindow, WindowDataPtr pData, LongRect * documentRectangle, Boolean forGrow) { #pragma unused (pWindow) Rect theRect = pData->contentRect; Rect maxRect; GetRegionBounds( GetGrayRgn(), &maxRect ); if ( (!forGrow) && (!(((TextDataPtr) pData)->insideClickLoop) ) ) RecalcTE(pData, false); theRect.bottom = CalculateTextEditHeight(((TextDataPtr) pData)->hTE); theRect.bottom += kMargins*2; theRect.right = maxRect.right; if (theRect.bottom < pData->contentRect.bottom) theRect.bottom = pData->contentRect.bottom; if (forGrow) theRect.bottom = maxRect.bottom-kScrollBarSize; RectToLongRect(&theRect, documentRectangle); return noErr; } // TextGetDocumentRect // -------------------------------------------------------------------------------------------------------------- static OSErr TextGetBalloon(WindowPtr pWindow, WindowDataPtr pData, Point *localMouse, short * returnedBalloonIndex, Rect *returnedRectangle) { #pragma unused (pWindow, pData, localMouse, returnedRectangle) *returnedBalloonIndex = iHelpTextContent; return noErr; } // TextGetBalloon // -------------------------------------------------------------------------------------------------------------- static long TextCalculateIdleTime(WindowPtr pWindow, WindowDataPtr pData) { #pragma unused (pWindow, pData) if ( (gMachineInfo.amInBackground) || (! (**(((TextDataPtr) pData)->hTE)).active) ) return(0x7FFFFFF); else return(GetCaretTime()); } // TextCalculateIdleTime // -------------------------------------------------------------------------------------------------------------- static OSErr TextAdjustCursor(WindowPtr pWindow, WindowDataPtr pData, Point * localMouse, RgnHandle globalRgn) { #pragma unused (pWindow) OSErr anErr = noErr; CursHandle theCross; RgnHandle hilightRgn; Cursor arrow; if (gMachineInfo.haveDragMgr) { RgnHandle globalHilight; hilightRgn = NewRgn(); TEGetHiliteRgn(hilightRgn, ((TextDataPtr) pData)->hTE); globalHilight = NewRgn(); CopyRgn(hilightRgn, globalHilight); LocalToGlobalRgn(globalHilight); if (PtInRgn(*localMouse, hilightRgn)) { // we're already in the hilight rgn, so we don't need mouse-moved events as long // as we stay there CopyRgn(globalHilight, globalRgn); SetCursor(GetQDGlobalsArrow(&arrow)); DisposeRgn(hilightRgn); return eActionAlreadyHandled; } else if (!EmptyRgn(hilightRgn)) { // make sure we get mouse-moved events if the mouse moves into the hilight rgn, // so that we can change the cursor DiffRgn(globalRgn, globalHilight, globalRgn); } DisposeRgn(globalHilight); DisposeRgn(hilightRgn); } theCross = MacGetCursor(iBeamCursor); if (theCross) { char oldState; oldState = HGetState((Handle) theCross); HLock((Handle) theCross); SetCursor(*theCross); HSetState((Handle) theCross, oldState); anErr = eActionAlreadyHandled; } return anErr; } // TextAdjustCursor // -------------------------------------------------------------------------------------------------------------- short gNilCaretProc[] = { 0x584F, // ADDQ.W #$4, A7 0x4E75}; // RTS // -------------------------------------------------------------------------------------------------------------- static OSErr TextPrintPage(WindowPtr pWindow, WindowDataPtr pData, Rect * pageRect, long *pageNum) { #pragma unused (pWindow) OSErr anErr = noErr; short footerHeight; TEHandle hTE; Rect areaForThisPage; short ourPage = 1; Boolean documentHasFormControl = Count1Resources(kFormResource) != 0; // calculate area for the footer (page number) { FontInfo theInfo; TextFont(0); TextSize(0); TextFace(normal); GetFontInfo(&theInfo); footerHeight = (theInfo.ascent + theInfo.descent + theInfo.leading) << 1; } // duplicate the text edit record, disable the selection before swapping the new port in hTE = ((TextDataPtr) pData)->hTE; TEDeactivate(hTE); anErr = HandToHand((Handle*) &hTE); nrequire(anErr, DuplicateTE); // turn off outline hilighting -- because the window is disabled while // printing is going on, but we don't want that disabled hilight to draw TEFeatureFlag(teFOutlineHilite, teBitClear, ((TextDataPtr) pData)->hTE); // now HERE'S a real hack! Under certain conditions, Text Edit will draw the // cursor, even if you said the edit record is inactive! This happens when // the internal state sez that the cursor hasn't been drawn yet. Lucky // for us, the caret is drawn through a hook, which we replace with a NOP. (**hTE).caretHook = (CaretHookUPP) gNilCaretProc; // point the rectangles to be the page rect minus the footer areaForThisPage = *pageRect; areaForThisPage.bottom -= footerHeight; InsetRect(&areaForThisPage, kPrintMargins, kPrintMargins); (**hTE).viewRect = (**hTE).destRect = areaForThisPage; // recalculate the line breaks TECalText(hTE); // point it at the printing port. (**hTE).inPort = GetQDGlobalsThePort(); // now loop over all pages doing page breaking until we find our current // page, which we print, and then return. { Rect oldPageHeight = (**hTE).viewRect; short currentLine = 0; long prevPageHeight = 0; while (ourPage <= *pageNum) { long currentPageHeight = 0; // calculate the height including the current page, breaks // when one of three things happen: // 1) adding another line to this page would go beyond the length of the page // 2) a picture needs to be broken to the next page (NOT YET IMPLEMENTED) // 3) we run out of lines for the document // 4) if the line has a page break (defined as a non breaking space w/o a PICT) // POTENTIAL BUG CASES: // If a single line > the page height. Can that happen? If so, we need to // add something to handle it. do { long currentLineHeight; // zero based count -- but one based calls to TEGetHeight currentLineHeight = TEGetHeight(currentLine+1, currentLine+1, hTE); // if adding this line would just be too much, break out of here if ((currentLineHeight + currentPageHeight) > (areaForThisPage.bottom - areaForThisPage.top)) break; ++ currentLine; currentPageHeight += currentLineHeight; // if this line had a page break on it, break out of pagination if (documentHasFormControl && LineHasPageBreak(currentLine-1, hTE)) break; } while (currentLine < (**hTE).nLines); // if this the page we are trying to print if (ourPage == *pageNum) { Str255 pageString; RgnHandle oldRgn = NewRgn(); // move onto the next page via offset by the previous pages -- but // clip to the current page height because we wouldn't want to see // half of a line from the next page at the bottom of a page. OffsetRect(&oldPageHeight, 0, -(prevPageHeight)); oldPageHeight.bottom = oldPageHeight.top + currentPageHeight; (**hTE).destRect = oldPageHeight; // clip to this area as well areaForThisPage.bottom = areaForThisPage.top + currentPageHeight; GetClip(oldRgn); ClipRect(&areaForThisPage); // draw the edit record, plus our cool pictures TEUpdate(&areaForThisPage, hTE); DrawPictures(pData, hTE); // restore the clip SetClip(oldRgn); DisposeRgn(oldRgn); // Draw the page string at the bottom of the page, centered pageString[0] = 2; pageString[1] = '-'; NumToString(*pageNum, &pageString[2]); pageString[0] += pageString[2]; pageString[2] = ' '; pageString[++pageString[0]] = ' '; pageString[++pageString[0]] = '-'; MoveTo( pageRect->left + ((pageRect->right - pageRect->left) >> 1) - (StringWidth(pageString)>>1), pageRect->bottom - kPrintMargins); DrawString(pageString); // if we have completed all pages if (currentLine >= (**hTE).nLines) { // tell it to stop printing *pageNum = -1; } // get out of here! break; } // move onto the next page via count ++ourPage; // and the list of pages before now includes this page we just finished prevPageHeight += currentPageHeight; } } // restore text for visible page if done if (*pageNum == -1) { TEFeatureFlag(teFOutlineHilite, teBitSet, ((TextDataPtr) pData)->hTE); TECalText(((TextDataPtr) pData)->hTE); if (pData->originalFileType != 'ttro') TEActivate(((TextDataPtr) pData)->hTE); } // FALL THROUGH EXCEPTION HANDLING // Dispose this way to avoid disposing of any owned objects DisposeHandle((Handle) hTE); DuplicateTE: return anErr; } // TextPrintPage // -------------------------------------------------------------------------------------------------------------- static OSErr TextInitData(WindowDataPtr pData) { static TEClickLoopUPP gMyClickLoop = NULL; OSErr anErr = noErr; if (!gMyClickLoop) { gMyClickLoop = NewTEClickLoopProc(CClikLoop); } pData->bumpUntitledCount = true; pData->pScrollContent = (ScrollContentProc) TextScrollContent; pData->pAdjustSize = (AdjustSizeProc) TextAdjustSize; pData->pGetDocumentRect = (GetDocumentRectProc) TextGetDocumentRect; pData->pPreMenuAccess = (PreMenuAccessProc) TextPreMenuAccess; pData->pAdjustMenus = (AdjustMenusProc) TextAdjustMenus; pData->pCommand = (CommandProc) TextCommand; pData->pCloseWindow = (CloseWindowProc) TextCloseWindow; pData->pFilterEvent = (FilterEventProc) TextFilterEvent; pData->pActivateEvent = (ActivateEventProc) TextActivateEvent; pData->pUpdateWindow = (UpdateWindowProc) TextUpdateWindow; pData->pPrintPage = (PrintPageProc) TextPrintPage; // we only support keydowns and editing for modifable docs if (pData->originalFileType != 'ttro') { pData->pKeyEvent = (KeyEventProc) TextKeyEvent; pData->pContentClick = (ContentClickProc) TextContentClick; pData->pAdjustCursor = (AdjustCursorProc) TextAdjustCursor; pData->pGetBalloon = (GetBalloonProc) TextGetBalloon; pData->pCalculateIdleTime = (CalculateIdleTimeProc) TextCalculateIdleTime; // We can always reference our Drag handlers, because they will not be called if we // don't have the Drag Manager available. We needn't check here (it would be redundant). pData->pDragTracking = (DragTrackingProc) TextDragTracking; pData->pDragReceive = (DragReceiveProc) TextDragReceive; pData->documentAcceptsText = true; } // leave room for the grow area at bottom pData->hasGrow = true; pData->contentRect.bottom -= kScrollBarSize; if ((pData->contentRect.right - pData->contentRect.left) > kOnePageWidth) pData->contentRect.right = pData->contentRect.left + kOnePageWidth; ((TextDataPtr) pData)->hTE = TEStyleNew(&pData->contentRect, &pData->contentRect); anErr = MemError(); nrequire(anErr, TENewFailed); pData->hScrollAmount = 0; pData->vScrollAmount = TEGetHeight(0, 0, ((TextDataPtr) pData)->hTE); TEAutoView(true, ((TextDataPtr) pData)->hTE); // Setup our click loop to handle autoscrolling ((TextDataPtr) pData)->docClick = (**(((TextDataPtr) pData)->hTE)).clickLoop; TESetClickLoop(gMyClickLoop, ((TextDataPtr) pData)->hTE); // initalize undo information ((TextDataPtr) pData)->prevCommandID = cNull; return noErr; TENewFailed: DebugStr("\pTextFile: TEStyleNew returned an error." ); return anErr; } // TextInitData // -------------------------------------------------------------------------------------------------------------- static OSErr TextReadDataFork(WindowDataPtr pData) { OSErr anErr = noErr; // if we have a data fork, read the contents into the record if (pData->dataRefNum != -1) { long dataSize; GetEOF(pData->dataRefNum, &dataSize); if (dataSize > kMaxLength) anErr = eDocumentTooLarge; else { Handle tempHandle = NewHandle(dataSize); anErr = MemError(); if (anErr == noErr) { int i; // read the text in SetFPos(pData->dataRefNum, fsFromStart, 0); anErr = FSRead(pData->dataRefNum, &dataSize, * tempHandle); for (i=0; i < dataSize; i++) { if ((*tempHandle)[i] == '\n') (*tempHandle)[i] = '\r'; } // then insert it. if (anErr == noErr) { HLock(tempHandle); TEStyleInsert(*tempHandle, dataSize, nil, ((TextDataPtr) pData)->hTE); anErr = MemError(); } DisposeHandle(tempHandle); } } } nrequire(anErr, ReadData); return noErr; ReadData: return anErr; } // TextReadDataFork // -------------------------------------------------------------------------------------------------------------- static void TextReadResourceFork(WindowDataPtr pData) { // if we have a resource fork, read the contents if (pData->resRefNum != -1) { short oldResFile = CurResFile(); Handle theStyle; // read the style information UseResFile(pData->resRefNum); theStyle = Get1Resource('styl', 128); if (theStyle) { HNoPurge(theStyle); TEUseStyleScrap(0, 32767, (StScrpHandle) theStyle, true, ((TextDataPtr) pData)->hTE); ReleaseResource(theStyle); } // if we have sound, load it in and detach it { Handle soundHandle = Get1Resource('snd ', kSoundBase); if (soundHandle) { HNoPurge(soundHandle); DetachResource(soundHandle); ((TextDataPtr) pData)->soundHandle = soundHandle; } } UseResFile(oldResFile); } } // TextReadResourceFork // -------------------------------------------------------------------------------------------------------------- static void TextFinishTESetup(WindowDataPtr pData) { // hook out drawing of the non-breaking space for read only documents, // for modifiable documents, enable outline hiliting (ie, when TE window // isn't in front, show the gray outline) if (pData->originalFileType == 'ttro') { static DrawHookUPP gMyDrawHook = NULL; UniversalProcPtr hookRoutine; if (!gMyDrawHook) { gMyDrawHook = NewDrawHookProc((DrawHookProcPtr)MyDrawHook); } hookRoutine = (UniversalProcPtr)gMyDrawHook; TECustomHook(intDrawHook, &hookRoutine, ((TextDataPtr) pData)->hTE); } else { TEFeatureFlag(teFOutlineHilite, teBitSet, ((TextDataPtr) pData)->hTE); } // make a TSM document if this is editable if ( (pData->originalFileType != 'ttro') && (gMachineInfo.haveTSMTE) ) { OSType supportedInterfaces[1]; supportedInterfaces[0] = kTSMTEInterfaceType; if (NewTSMDocument(1, supportedInterfaces, &pData->docTSMDoc, (long)&pData->docTSMRecHandle) == noErr) { static TSMTEPreUpdateUPP gTSMPreUpdateProc = NULL; static TSMTEPostUpdateUPP gTSMPostUpdateProc = NULL; long response; if (!gTSMPreUpdateProc) { gTSMPreUpdateProc = NewTSMTEPreUpdateProc(TSMPreUpdateProc); } if (!gTSMPostUpdateProc) { gTSMPostUpdateProc = NewTSMTEPostUpdateProc(TSMPostUpdateProc); } (**(pData->docTSMRecHandle)).textH = ((TextDataPtr) pData)->hTE; if ((Gestalt(gestaltTSMTEVersion, &response) == noErr) && (response == gestaltTSMTE1)) (**(pData->docTSMRecHandle)).preUpdateProc = gTSMPreUpdateProc; (**(pData->docTSMRecHandle)).postUpdateProc = gTSMPostUpdateProc; (**(pData->docTSMRecHandle)).updateFlag = kTSMTEAutoScroll; (**(pData->docTSMRecHandle)).refCon = (long)pData; } } // now we have added text, so adjust views and such as needed TESetSelect(0, 0, ((TextDataPtr) pData)->hTE); RecalcTE(pData, true); AdjustTE(pData, true); // ???? Hack to get around a 7.0 TextEdit bug. If you are pasting a multiple // line clipboard into TE, *and* the TextEdit record is new, *and* the selection // is at the begining of the doc (0,0 as above), *and* you haven't moved the // cursor around at all, then TE pastes thinking it's at the end of the line, // when it really should be at the begining. Then if you <cr> with the cursor // visible, it'll leave a copy behind. // I'm not happy with this, but I don't know another way around the problem. if (pData->originalFileType != 'ttro') { TEKey(0x1F, ((TextDataPtr) pData)->hTE); TEKey(0x1E, ((TextDataPtr) pData)->hTE); } // <39> if this is a new document, convert the "system size", "system font", and // "application font" into real font IDs and sizes. This is so that // if someone saves this document and opens it with another script // system, they don't get all huffy that the font changed on them. // It also solves problems with cut and paste to applications too stupid // to know that "zero" means system size. if (pData->dataRefNum == -1) { TEHandle hTE = ((TextDataPtr) pData)->hTE; short mode = doAll; TextStyle theStyle; TEContinuousStyle(&mode, &theStyle, hTE); if (theStyle.tsSize == 0) theStyle.tsSize = GetDefFontSize(); if (theStyle.tsFont == systemFont) theStyle.tsFont = GetSysFont(); if (theStyle.tsFont == applFont) theStyle.tsFont = GetAppFont(); mode = doAll; TESetStyle(mode, &theStyle, false, hTE); } // Carbon TextEdit really sucks right now. // to work around some of the font sizing // problems let's try to puppet string the // right result. sorry... //DebugStr("\pSetting size to 18 point." ); TESetSelect(0,32000,((TextDataPtr) pData)->hTE); { TEHandle hTE = ((TextDataPtr) pData)->hTE; short mode = doAll; short saveSize; TextStyle theStyle; TEContinuousStyle(&mode, &theStyle, hTE); if (theStyle.tsSize == 0) theStyle.tsSize = GetDefFontSize(); if (theStyle.tsFont == systemFont) theStyle.tsFont = GetSysFont(); if (theStyle.tsFont == applFont) theStyle.tsFont = GetAppFont(); saveSize = theStyle.tsSize; theStyle.tsSize = 18; TESetStyle(doSize, &theStyle, true, hTE); TECalText(((TextDataPtr) pData)->hTE); AdjustTE(pData, false); //theStyle.tsSize = saveSize; //TESetStyle(doSize, &theStyle, true, hTE); //TECalText(((TextDataPtr) pData)->hTE); //AdjustTE(pData, false); } TESetSelect(0,0,((TextDataPtr) pData)->hTE); //DebugStr( "\pDone setting size.\n" ); } // TextFinishTESetup // -------------------------------------------------------------------------------------------------------------- static void TextCloseStationery(WindowDataPtr pData) { // if stationery, use untitled and close down the files if (pData->originalFileType == 'sEXT') { pData->originalFileType = 'TEXT'; pData->openAsNew = true; if (pData->resRefNum != -1) CloseResFile(pData->resRefNum); if (pData->dataRefNum != -1) FSClose(pData->dataRefNum); pData->resRefNum = pData->dataRefNum = -1; } } // TextCloseStationery // -------------------------------------------------------------------------------------------------------------- #if 0 static int FindVoicePosition(MenuHandle voicesMenu, short menuCount, VoiceDescription* pdesc) { short item; for ( item = 1; item <= menuCount; ++item ) { Str255 itemText; GetMenuItemText( voicesMenu, item, itemText ); /*1st > 2nd*/ if ( IUCompString( itemText, pdesc->name ) == 1 ) break; // Found where name goes in list. } return item; } // FindVoicePosition // -------------------------------------------------------------------------------------------------------------- static void TextAdd1Voice(short i, MenuHandle voicesMenu) { VoiceSpec spec; // A voice to add to the menu. VoiceDescription description; // Info about a voice. short item; if ( (GetIndVoice( i, &spec ) == noErr) && (GetVoiceDescription( &spec, &description, sizeof(description) ) == noErr ) ) { short menuCount = CountMItems( voicesMenu ); // first one we are adding == get rid of item already there if ( (i == 1) && (menuCount > 0) ) { DeleteMenuItem( voicesMenu, 1 ); --menuCount; } item = FindVoicePosition(voicesMenu, menuCount, &description); InsertMenuItem( voicesMenu, "\p ", item - 1 ); SetMenuItemText( voicesMenu, item, description.name ); CheckItem(voicesMenu, item, ((gCurrentVoice.creator == spec.creator) && (gCurrentVoice.id == spec.id)) ); } } // TextAdd1Voice // -------------------------------------------------------------------------------------------------------------- static pascal void* VoicesThread(void *threadParam) { #pragma unused(threadParam) short theVoiceCount; if (CountVoices( &theVoiceCount ) == noErr) { OSErr anErr; VoiceDescription description; // Info about a voice. MenuHandle voicesMenu = GetMenuHandle(mVoices); if (!gDontYield) YieldToAnyThread(); anErr = GetVoiceDescription( nil, &description, sizeof(description) ); if (anErr == noErr) { int i; if (!gDontYield) YieldToAnyThread(); gCurrentVoice = description.voice; for (i = 1; i <= theVoiceCount; ++i) { TextAdd1Voice(i, voicesMenu); if (!gDontYield) YieldToAnyThread(); } } } gVoicesThread = kNoThreadID; return 0; } // VoicesThread // -------------------------------------------------------------------------------------------------------------- static void TextAddVoices(void) { // AEC, added correct callback proc creation ThreadEntryUPP voicesThreadUPP = NewThreadEntryProc(VoicesThread); // if we have voices, add them to the menu if ( (gMachineInfo.haveTTS) && (!gAddedVoices) ) { OSErr anErr = paramErr; if (gMachineInfo.haveThreads) { // AEC, added cast anErr = NewThread(kCooperativeThread, voicesThreadUPP, NULL, 0, kNewSuspend, &gThreadResults, &gVoicesThread); } if (anErr != noErr) VoicesThread(NULL); gAddedVoices = true; } // end of adding voices } // TextAddVoices #endif // -------------------------------------------------------------------------------------------------------------- static OSErr TextMakeWindow(WindowPtr pWindow, WindowDataPtr pData) { //#pragma unused(pWindow) OSErr anErr; anErr = TextInitData(pData); nrequire(anErr, InitData); anErr = TextReadDataFork(pData); nrequire(anErr, ReadData); TextReadResourceFork(pData); TextFinishTESetup(pData); TextCloseStationery(pData); if(gMachineInfo.haveProxyIcons) { // Set the view size of the scrollbar to the textedit view size (for proportional scrolling) SetControlViewSize( pData->vScroll, pData->contentRect.bottom - pData->contentRect.top ); // make the document happy SetWindowProxyCreatorAndType( pWindow, 'ttxt', 'TEXT', kOnSystemDisk ); } #if 0 TextAddVoices(); #endif return noErr; // EXCEPTION HANDLING ReadData: TEDispose(((TextDataPtr) pData)->hTE); InitData: return anErr; } // TextMakeWindow // -------------------------------------------------------------------------------------------------------------- OSErr TextPreflightWindow(PreflightPtr pPreflightData) { pPreflightData->continueWithOpen = true; pPreflightData->wantVScroll = true; pPreflightData->doZoom = true; pPreflightData->makeProcPtr = TextMakeWindow; if (pPreflightData->fileType != 'ttro') pPreflightData->openKind = fsRdWrPerm; pPreflightData->storageSize = sizeof(TextDataRecord); // get strings that mark the picture GetIndString(gPictMarker1, kTextStrings, iPictureMarker1); GetIndString(gPictMarker2, kTextStrings, iPictureMarker2); // do we need to account for bugs in older TEs? { long version; if ( (Gestalt(gestaltTextEditVersion, &version) == noErr) && (version > gestaltTE5) ) gTE6Version = true; } return noErr; } // TextPreflightWindow // -------------------------------------------------------------------------------------------------------------- void TextGetFileTypes(OSType * pFileTypes, OSType * pDocumentTypes, short * numTypes) { pFileTypes[*numTypes] = 'TEXT'; pDocumentTypes[*numTypes] = kTextWindow; (*numTypes)++; pFileTypes[*numTypes] = 'ttro'; pDocumentTypes[*numTypes] = kTextWindow; (*numTypes)++; pFileTypes[*numTypes] = 'sEXT'; pDocumentTypes[*numTypes] = kTextWindow; (*numTypes)++; } // TextGetFileTypes // -------------------------------------------------------------------------------------------------------------- // TextAddContentsMenu checks if there is a contents list and, if there // is, creates a new menu handle for the contents list and fills it with // the appropriate visible items void TextAddContentsMenu(WindowDataPtr pData) { MenuHandle contentsMenu; Str255 menuStr; short totalItems; short index; OSErr err; contentsMenu = GetMenuHandle(mContents); require(contentsMenu == nil, ContentsMenuAlreadyInstalled); // Is there a contents list? If so, get the menu name // and the number of items in the list if (TextGetContentsListItem(pData, 0, menuStr, nil, &totalItems) == noErr) { // create the menu and fill it with all the items // listed in the string list resource contentsMenu = NewMenu(mContents, menuStr); require(contentsMenu != nil, CantCreateContentsMenu); for (index = 1; index < totalItems; index++) { err = TextGetContentsListItem(pData, index, menuStr, nil, nil); require(err == noErr, CantGetItem); AppendMenu(contentsMenu, menuStr); } // add the menu to the menu bar, and redraw the menu bar InsertMenu(contentsMenu, 0); DrawMenuBar(); } else { // no contents, do nothing } return; // error handling CantGetItem: CantCreateContentsMenu: if (contentsMenu) DisposeMenu(contentsMenu); ContentsMenuAlreadyInstalled: return; } // TextAddContentsMenu // TextRemoveContentsMenu removes the contents menu, if any, // and redraws the menu bar static void TextRemoveContentsMenu(WindowDataPtr pData) { #pragma unused (pData) MenuHandle contentsMenu; contentsMenu = GetMenuHandle(mContents); if (contentsMenu) { DeleteMenu(mContents); DisposeMenu(contentsMenu); DrawMenuBar(); } } // TextRemoveContentsMenu // TextGetContentsListItem is a general utility routine for examining the // contents menu list, returning the menu and search strings, and returning // the total number of entries in the contents list. // // Pass 0 as itemNum to retrieve the strings for the contents menu title. // // Pass nil for menuStr, searchStr, or totalItems if you don't want that // info returned. // // Returns eDocumentHasNoContentsEntries if there is no contents string list // resource for the specified window static OSErr TextGetContentsListItem(WindowDataPtr pData, short itemNum, StringPtr menuStr, StringPtr searchStr, short *totalItems) { OSErr err; short oldResFile; short menuItemNum; short searchItemNum; Handle contentsStrListHandle; // if no original resource file, don't bother if (pData->resRefNum == -1) { return eDocumentHasNoContentsEntries; } err = noErr; oldResFile = CurResFile(); UseResFile(pData->resRefNum); // two entries per item // // first (itemNum zero) is content menu title // (second -- itemNum one, search string for menu title -- is unused) menuItemNum = itemNum * 2 + 1; searchItemNum = menuItemNum + 1; contentsStrListHandle = Get1Resource('STR#', kContentsListID); if (contentsStrListHandle) { if (totalItems) *totalItems = (*(short *)*contentsStrListHandle) / 2; if (menuStr) GetIndString(menuStr, kContentsListID, menuItemNum); if (searchStr) { GetIndString(searchStr, kContentsListID, searchItemNum); if (searchStr[0] == 0) { // search string was empty, so use the // menu string as the search string GetIndString(searchStr, kContentsListID, menuItemNum); } } } else { err = eDocumentHasNoContentsEntries; if (totalItems) *totalItems = 0; } UseResFile(oldResFile); return err; } // TextGetContentsListItem // TextAdjustContentsMenu enables the items in the contents menu // // This routine is essentially a placeholder in case the contents // menu really were to be dynamically enabled. static OSErr TextAdjustContentsMenu(WindowDataPtr pData) { #pragma unused (pData) EnableCommand(cSelectContents); return(noErr); } // TextAdjustContentsMenu