TextFile.c

/*
**  File:       TextFile.c
**
**  Contains:   Text file support for simple text application.
**
**  Version:    SimpleText 1.4 or later
**
** Copyright 1993-1999 Apple Computer. All rights reserved.
**
**  You may incorporate this sample code into your applications without
**  restriction, though the sample code has been provided "AS IS" and the
**  responsibility for its operation is 100% yours.  However, what you are
**  not permitted to do is to redistribute the source as "DSC Sample Code"
**  after having made changes. If you're going to re-distribute the source,
**  we require that you make it clear in the source that the code was
**  descended from Apple Sample Code, but that you've made changes.
 
*/
 
#include "MacIncludes.h"
 
#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 kGXPrintMargins     10          // margins in printing a GX 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 void AsmClikLoop();
 
#define ABS(n)              (((n) < 0) ? -(n) : (n))
 
// --------------------------------------------------------------------------------------------------------------
// 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.
static SpeechChannel    gSpeechChannel = nil;       
static VoiceSpec        gCurrentVoice;
static Ptr              gSpeakPtr = nil;
static Boolean          gAddedVoices = false;
static Str31            gPictMarker1, gPictMarker2;
 
// --------------------------------------------------------------------------------------------------------------
// EXTERNAL FUNCTIONS
// --------------------------------------------------------------------------------------------------------------
 
extern OSErr    TextDragTracking(WindowRef pWindow, void *pData, DragReference theDragRef, short message);
extern OSErr    TextDragReceive(WindowRef pWindow, void *refCon, DragReference theDragRef);
extern Boolean  DragText(WindowRef 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
GrowStyleSave:
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);
        if (HandToHand((Handle*) &tempLong) == noErr)
            {
            TESetStyleHandle( (TEStyleHandle) ((TextDataPtr) pData)->prevStyle, hTE );
            ((TextDataPtr) pData)->prevStyle = (Handle)tempLong;
            }
            
        // undo selection
            {
            short   start, end;
            
            start = ((TextDataPtr) pData)->beforeSelStart;
            end = ((TextDataPtr) pData)->beforeSelEnd;
            ((TextDataPtr) pData)->prevSelStart = (**hTE).selStart;
            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)
        InvalRect(&qd.thePort->portRect);
    
} // RecalcTE
 
// --------------------------------------------------------------------------------------------------------------
 
pascal TEClickLoopUPP GetOldClickLoop(void)
{
    return ((TextDataPtr) FrontWindow())->docClick;
    
} // GetOldClikLoop
 
pascal void TextClickLoop(void)
{   
    WindowRef   window;
    RgnHandle   region;
    
    window = FrontWindow();
    region = NewRgn();
    GetClip(region);                    /* save clip */
    ClipRect(&GetWindowPort(window)->portRect);
    ((TextDataPtr) window)->insideClickLoop = true;
        AdjustScrollBars(window, false, false, nil);
    ((TextDataPtr) window)->insideClickLoop = false;
    SetClip(region);                    /* restore clip */
    DisposeRgn(region);
 
} // TextClickLoop
 
#if GENERATINGPOWERPC
static pascal Boolean CClikLoop(TEPtr pTE)
{
    CallTEClickLoopProc(GetOldClickLoop(), pTE);
    
    TextClickLoop();
    
    return(true);
    
} // CClikLoop
 
    static RoutineDescriptor gMyClickLoopRD = BUILD_ROUTINE_DESCRIPTOR(uppTEClickLoopProcInfo, CClikLoop);
    static TEClickLoopUPP gMyClickLoop = &gMyClickLoopRD;
#else
    static TEClickLoopUPP gMyClickLoop = NewDrawHookProc(AsmClikLoop);
#endif
 
// --------------------------------------------------------------------------------------------------------------
#if GENERATINGPOWERPC
pascal void MyDrawHook ( unsigned short offset, unsigned short textLen,
     Ptr textPtr, TEPtr tePtr, TEHandle teHdl )
#else
pascal void MyDrawGlue();
 
pascal void MyDrawHook ( TEHandle teHdl, TEPtr tePtr, Ptr textPtr, short textLen, short offset )
#endif
{
#pragma unused  ( teHdl, tePtr)
 
    register short          drawLen = 0;
    register Ptr            drawPtr;
    
    drawPtr = textPtr + (long)offset;
    
    while ( textLen--)
        {
        if ( *drawPtr == 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 GENERATINGCFM
    static RoutineDescriptor gMyDrawGlueRD = BUILD_ROUTINE_DESCRIPTOR(uppDrawHookProcInfo, MyDrawHook);
    static DrawHookUPP gMyDrawGlue = &gMyDrawGlueRD;
#else
    static DrawHookUPP gMyDrawGlue = NewDrawHookProc(MyDrawGlue);
#endif
 
// --------------------------------------------------------------------------------------------------------------
static void DisposeOfSpeech(Boolean throwAway)
{
    if (gSpeechChannel)
        {
        (void) StopSpeech( gSpeechChannel );
        if (throwAway)
            {
            (void) DisposeSpeechChannel( gSpeechChannel );
            gSpeechChannel = nil;
            }
        }
    if (gSpeakPtr)
        {
        DisposePtr(gSpeakPtr);
        gSpeakPtr = nil;
        }
        
} // DisposeOfSpeech
 
// --------------------------------------------------------------------------------------------------------------
 
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
 
#if GENERATINGCFM
    static RoutineDescriptor gGetPartialPICTDataRD = BUILD_ROUTINE_DESCRIPTOR(uppQDGetPicProcInfo, GetPartialPICTData);
    static QDGetPicUPP gGetPartialPICTData = &gGetPartialPICTDataRD;
#else
    static QDGetPicUPP gGetPartialPICTData = NewQDGetPicProc(GetPartialPICTData);
#endif
 
// --------------------------------------------------------------------------------------------------------------
static void SpoolDrawPicture(Handle spoolPicture, PicHandle pictureHeader, Rect *pRect)
{
    CQDProcs    spoolProcs;
    QDProcs     *oldProcs;
 
    gSpoolPicture = spoolPicture;
    gSpoolOffset = sizeof(Picture);
    
    if (gMachineInfo.theEnvirons.hasColorQD)
        SetStdCProcs(&spoolProcs);
    else
        SetStdProcs((QDProcs*) &spoolProcs);
        
    spoolProcs.getPicProc = gGetPartialPICTData;
    oldProcs = qd.thePort->grafProcs;
    qd.thePort->grafProcs = (QDProcs*) &spoolProcs;
        DrawPicture(pictureHeader, pRect);
    qd.thePort->grafProcs = oldProcs;
    
    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;
    
    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, qd.thePort->clipRgn))
                            SpoolDrawPicture(pictHandle, pictHeader, &theRect);
                        }
                    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';
 
    // set the stationary bit, if we must
    if (!documentIsText)
        {
        theInfo.fdFlags |= kIsStationery;
        theInfo.fdType = 'sEXT';
        }
    else
        {
        theInfo.fdFlags &= ~kIsStationery;
        theInfo.fdType = 'TEXT';
        }
    FSpSetFInfo(pSpec, &theInfo);
 
} // UpdateFileInfo
 
// --------------------------------------------------------------------------------------------------------------
static OSErr    TextSave(WindowDataPtr pData)
{
    OSErr   anErr = noErr;
    long    amountToWrite;
    
    // write out the text
    SetFPos(pData->dataRefNum, fsFromStart, 0);
    amountToWrite = (** ((TextDataPtr) pData)->hTE).teLength;
    anErr = FSWrite(pData->dataRefNum, &amountToWrite, * (** ((TextDataPtr) pData)->hTE).hText);
    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.
        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)
        {
        pData->changed = false;
        (void) FlushVol("\p", pData->fileSpec.vRefNum);
        }
        
    return anErr;
    
} // TextSave
 
// --------------------------------------------------------------------------------------------------------------
 
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
 
// --------------------------------------------------------------------------------------------------------------
 
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
 
 
// --------------------------------------------------------------------------------------------------------------
// 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
 
#if GENERATINGCFM
    static RoutineDescriptor gTSMPreUpdateProcRD = BUILD_ROUTINE_DESCRIPTOR(uppTSMTEPreUpdateProcInfo, TSMPreUpdateProc);
    static TSMTEPreUpdateUPP gTSMPreUpdateProc = &gTSMPreUpdateProcRD;
#else
    static TSMTEPreUpdateUPP gTSMPreUpdateProc = NewTSMTEPreUpdateProc(TSMPreUpdateProc);
#endif
 
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((WindowRef)refCon, false, false, nil);
    AdjustTE((WindowDataPtr)refCon, true);
    
    ((WindowDataPtr)refCon)->changed = true;
    
} // TSMPostUpdateProc
 
#if GENERATINGCFM
    static RoutineDescriptor gTSMPostUpdateProcRD = BUILD_ROUTINE_DESCRIPTOR(uppTSMTEPostUpdateProcInfo, TSMPostUpdateProc);
    static TSMTEPostUpdateUPP gTSMPostUpdateProc = &gTSMPostUpdateProcRD;
#else
    static TSMTEPostUpdateUPP gTSMPostUpdateProc = NewTSMTEPostUpdateProc(TSMPostUpdateProc);
#endif
 
// --------------------------------------------------------------------------------------------------------------
 
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
 
// --------------------------------------------------------------------------------------------------------------
 
// 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
 
// --------------------------------------------------------------------------------------------------------------
 
static OSErr    TextSaveAs(WindowRef pWindow, WindowDataPtr pData)
{
    OSErr               anErr = noErr;
    short               oldRes, oldData;
    StandardFileReply   sfReply;
    Boolean             documentIsText;
    
    // save the old references -- if there is an error, we restore them
    oldRes = pData->resRefNum;
    oldData = pData->dataRefNum;
    
    // ask where and how to save this document
    {
    Str255  defaultName;
    Point   where = {-1, -1};
    
    // setup for the call
    GetWTitle(pWindow, defaultName);
    SetCursor(&qd.arrow);
    
    // find out where the user wants the file
    CustomPutFile("\p", defaultName, &sfReply, 
                kTextSaveAsDialogID, where,
                gSaveDialogHook, gSaveDialogFilter, nil, nil, &documentIsText);
    
    // map the cancel button into a cancelling error
    if (!sfReply.sfGood)
        anErr = eUserCanceled;
    }
        
    // can't replace over other types   
    if (sfReply.sfReplacing)
        {
        FInfo   theInfo;
        
        FSpGetFInfo(&sfReply.sfFile, &theInfo);
        
        if ( (theInfo.fdType != 'TEXT') && (theInfo.fdType != 'sEXT') )
            anErr = eDocumentWrongKind;
        }
    nrequire(anErr, StandardPutFile);
        
    if  (
        (sfReply.sfReplacing) && 
        (oldData != -1) && 
        (pData->fileSpec.vRefNum == sfReply.sfFile.vRefNum) &&
        (pData->fileSpec.parID == sfReply.sfFile.parID) &&
        EqualString(pData->fileSpec.name, sfReply.sfFile.name, false, false)
        )
        {
 
        anErr = TextSave(pData);
        
        if (anErr == noErr)
            UpdateFileInfo(&sfReply.sfFile, documentIsText);
        }
    else
        {
        // create the data file and resource fork
        (void) FSpDelete(&sfReply.sfFile);
        anErr = FSpCreate(&sfReply.sfFile, 'ttxt', documentIsText ? 'TEXT' : 'sEXT', 0);
        FSpCreateResFile(&sfReply.sfFile, 'ttxt', documentIsText ? 'TEXT' : 'sEXT', 0);
        nrequire(anErr, FailedCreate);
 
        // set the stationary bit, if we must
        if (!documentIsText)
            {
            FInfo   theInfo;
            
            FSpGetFInfo(&sfReply.sfFile, &theInfo);
            theInfo.fdFlags |= kIsStationery;
            FSpSetFInfo(&sfReply.sfFile, &theInfo);
            }
 
        // open both of forks
        anErr = FSpOpenDF(&sfReply.sfFile, fsRdWrPerm, &pData->dataRefNum);
        if (anErr == noErr)
            {
            pData->resRefNum = FSpOpenResFile(&sfReply.sfFile, fsRdWrPerm);
            anErr = ResError();
            }
        nrequire(anErr, FailedOpen);
 
        // call the standard save function to do the save
        anErr = TextSave(pData);
    
    // FALL THROUGH EXCEPTION HANDLING
    FailedOpen:
        FSpDelete(&sfReply.sfFile); 
    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, sfReply.sfFile.name);
            }
        else
            {
            pData->resRefNum = oldRes;
            pData->dataRefNum = oldData;
            }
        }
    
    // save new location
    if (anErr == noErr)
        BlockMoveData(&sfReply.sfFile, &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;
 
    return anErr;
    
} // TextSaveAs
 
// --------------------------------------------------------------------------------------------------------------
static void ApplyFace(short requestedFace, WindowRef 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);
 
    pData->changed = true;
    
} // ApplyFace
 
// --------------------------------------------------------------------------------------------------------------
static OSErr ApplySize(short requestedSize, WindowRef 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);
 
    pData->changed = true;
    
    return anErr;
 
} // ApplySize
 
// --------------------------------------------------------------------------------------------------------------
// OOP INTERFACE ROUTINES
// --------------------------------------------------------------------------------------------------------------
 
static OSErr    TextUpdateWindow(WindowRef pWindow, WindowDataPtr pData)
{
    Rect    updateArea = pData->contentRect;
    
    // be sure to also erase the area where the horizontal scroll bar
    // is missing.
    updateArea.bottom = GetWindowPort(pWindow)->portRect.bottom;
    EraseRect(&updateArea);
    
    TEUpdate(&pData->contentRect, ((TextDataPtr) pData)->hTE);
    
    DrawPictures(pData, ((TextDataPtr) pData)->hTE);
    
    DrawControls(pWindow);
    DrawGrowIcon(pWindow);
    
    return noErr;
    
} // TextUpdateWindow
 
// --------------------------------------------------------------------------------------------------------------
 
static OSErr    TextCloseWindow(WindowRef pWindow, WindowDataPtr pData)
{
#pragma unused (pWindow)
 
    // shut down text services
    if (pData->docTSMDoc)
        {
        FixTSMDocument(pData->docTSMDoc);
        DeactivateTSMDocument(pData->docTSMDoc);
        DeleteTSMDocument(pData->docTSMDoc);
        }
    
    DisposeOfSpeech(true);
    DisposeHandle(((TextDataPtr) pData)->soundHandle);
    TEDispose(((TextDataPtr) pData)->hTE);
 
    DisposeHandle(((TextDataPtr) pData)->prevText);
    DisposeHandle(((TextDataPtr) pData)->prevStyle);
    
    TextRemoveContentsMenu(pData);
 
    return noErr;
 
} // TextCloseWindow
 
// --------------------------------------------------------------------------------------------------------------
 
static OSErr    TextActivateEvent(WindowRef 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(WindowRef pWindow, WindowDataPtr pData, EventRecord *pEvent)
{   
    switch (pEvent->what)
        {
        case nullEvent:
            if (pData->originalFileType == 'TEXT')
                {
                if ( pWindow == FrontWindow() )
                    TEIdle(((TextDataPtr) pData)->hTE);
                }
                
            // 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);
                }
            break;
        }
        
    return false;
    
} // TextFilterEvent
 
// --------------------------------------------------------------------------------------------------------------
 
static OSErr    TextScrollContent(WindowRef pWindow, WindowDataPtr pData, short deltaH, short deltaV)
{
    GrafPtr     port = (GrafPtr) GetWindowPort(pWindow);
    RgnHandle   srcRgn, dstRgn;
    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();
    viewRect = (**((TextDataPtr) pData)->hTE).viewRect;
    RectRgn( srcRgn, &viewRect );
    SectRgn( srcRgn, port->visRgn,  srcRgn );
    SectRgn( srcRgn, port->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 );
    
    return eActionAlreadyHandled;
    
} // TextScrollContent
 
// --------------------------------------------------------------------------------------------------------------
 
static OSErr    TextKeyEvent(WindowRef 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 ( ((theKey != kDeleteKey) || (theKeyCode != kForwardDeleteKey)) && 
            ((** ((TextDataPtr) pData)->hTE).teLength+1 > kMaxLength) )
            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:
                            TEKey(theKey, ((TextDataPtr) pData)->hTE);
                            TESetSelect((**(((TextDataPtr) pData)->hTE)).selStart, end, ((TextDataPtr) pData)->hTE);
                            break;
                            
                        case kDownArrow:
                            TESetSelect(end, end, ((TextDataPtr) pData)->hTE);
                            TEKey(theKey, ((TextDataPtr) pData)->hTE);
                            TESetSelect(start, (**(((TextDataPtr) pData)->hTE)).selEnd, ((TextDataPtr) pData)->hTE);
                            break;
                            
                        case kRightArrow:
                            {
                            Handle textHandle = (**(((TextDataPtr) pData)->hTE)).hText;
                            Ptr textBuf;
                            char state;
                            
                            state = HGetState(textHandle);
                            HLock(textHandle);
                            textBuf = *(**(((TextDataPtr) pData)->hTE)).hText;
                            if (CharacterByteType(textBuf, start, smCurrentScript) != smSingleByte)
                                ++end;
                            HSetState(textHandle, state);
                            TESetSelect(start, ++end, ((TextDataPtr) pData)->hTE);
                            }
                            break;
                            
                        case kLeftArrow:
                            if (start > 0)
                                {
                                if (start > 1)
                                    {
                                    Handle textHandle = (**(((TextDataPtr) pData)->hTE)).hText;
                                    Ptr textBuf;
                                    char state;
                                    
                                    state = HGetState(textHandle);
                                    HLock(textHandle);
                                    textBuf = *(**(((TextDataPtr) pData)->hTE)).hText;
                                    if (CharacterByteType(textBuf, start-1, smCurrentScript) != smSingleByte)
                                        --start;
                                    HSetState(textHandle, state);
                                    }
                                TESetSelect(--start, end, ((TextDataPtr) pData)->hTE);
                                }
                            break;
                            
                        default:
                            TEKey(theKey, ((TextDataPtr) pData)->hTE);
                        }
                    }
                else
                    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);
                            
            ((TextDataPtr) pWindow)->insideClickLoop = true;
                AdjustTE(pData, false);
                AdjustScrollBars(pWindow, (oldHeight > 0), (oldHeight > 0), nil);
            ((TextDataPtr) pWindow)->insideClickLoop = false;
 
            if (!isMotionKey)
                pData->changed = true;
            }
        }
 
    return anErr;
 
} // TextKeyEvent
 
// --------------------------------------------------------------------------------------------------------------
 
static OSErr    TextContentClick(WindowRef 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(WindowRef 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(WindowRef pWindow, WindowDataPtr pData, short commandID, long menuResult)
{
 
    OSErr   anErr = noErr;
    
    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);
            pData->changed = 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);
            pData->changed = 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);
            pData->changed = 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);
            pData->changed = 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, newStart+gReplaceString[0], ((TextDataPtr) pData)->hTE);              
                    pData->changed = 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, newStart+gReplaceString[0], ((TextDataPtr) pData)->hTE);              
                oldNumLines -= (**(((TextDataPtr) pData)->hTE)).nLines;
                
                AdjustTE(pData, false);
                AdjustScrollBars(pWindow, (oldNumLines > 0), (oldNumLines > 0), nil);
                pData->changed = true;
                }
            else
                SysBeep(1);
 
            anErr = eActionAlreadyHandled;
            }
            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);
            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);
            break;
            
        case cSaveAs:
            anErr = TextSaveAs(pWindow, pData);
            break;
            
        // SUPPORTED FONTS
//      case cSelectFontStyle:          
        case cSelectFont:
            SaveCurrentUndoState(pData, cSelectFont);
            {
            Str255      itemName;
            Str255      menuName;
            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);
            pData->changed = 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;
            
            
        // 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;
                    pData->changed = 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;
            pData->changed = 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;
        
        
        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    TextAdjustMenus(WindowRef 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(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 = 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)
        {
        // if we are speaking, we can stop
        if (gSpeechChannel) 
            EnableCommand(cStopSpeaking);
 
        // 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(WindowRef pWindow, WindowDataPtr pData, 
            LongRect * documentRectangle, Boolean forGrow)
{
#pragma unused (pWindow)
    
    Rect    theRect = pData->contentRect;
    Rect    maxRect = (**GetGrayRgn()).rgnBBox;
    
    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(WindowRef pWindow, WindowDataPtr pData, 
        Point *localMouse, short * returnedBalloonIndex, Rect *returnedRectangle)
{
#pragma unused (pWindow, pData, localMouse, returnedRectangle)
 
    *returnedBalloonIndex = iHelpTextContent;
    
    return noErr;
    
} // TextGetBalloon
 
// --------------------------------------------------------------------------------------------------------------
 
static long TextCalculateIdleTime(WindowRef pWindow, WindowDataPtr pData)
{
#pragma unused (pWindow, pData)
 
    if ( (gMachineInfo.amInBackground) || (! (**(((TextDataPtr) pData)->hTE)).active) )
        return(0x7FFFFFF);
    else
        return(1);
        
} // TextCalculateIdleTime
 
// --------------------------------------------------------------------------------------------------------------
 
static OSErr    TextAdjustCursor(WindowRef pWindow, WindowDataPtr pData, 
                        Point * localMouse, Rect * globalRect)
{
#pragma unused (pWindow, globalRect)
 
    OSErr           anErr = noErr;
    CursHandle      theCross;
    RgnHandle       hilightRgn;
    
    if (gMachineInfo.haveDragMgr)
        {
        hilightRgn = NewRgn();
        TEGetHiliteRgn(hilightRgn, ((TextDataPtr) pData)->hTE);
        if (PtInRgn(*localMouse, hilightRgn))
            {
            SetCursor(&qd.arrow);
            DisposeRgn(hilightRgn);
            return eActionAlreadyHandled;   
            }
    
        DisposeRgn(hilightRgn);
        }
        
    theCross = GetCursor(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(WindowRef 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 = qd.thePort;
 
    // 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    TextMakeWindow(WindowRef pWindow, WindowDataPtr pData)
{
    #pragma unused(pWindow)
 
    OSErr               anErr = noErr;
 
    pData->bumpUntitledCount    = true;
    
    pData->pScrollContent       = (ScrollContentProc)   TextScrollContent;
    pData->pAdjustSize          = (AdjustSizeProc)      TextAdjustSize;
    pData->pGetDocumentRect     = (GetDocumentRectProc) TextGetDocumentRect;
    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);
    
    // 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)
                {
                // read the text in
                SetFPos(pData->dataRefNum, fsFromStart, 0);
                anErr = FSRead(pData->dataRefNum, &dataSize, * tempHandle);             
 
                // then insert it.
                if (anErr == noErr)
                    {
                    HLock(tempHandle);
                    TEStyleInsert(*tempHandle, dataSize, nil, ((TextDataPtr) pData)->hTE);
                    anErr = MemError();
                    }
                DisposeHandle(tempHandle);
                }
            
            }
        
        }
    nrequire(anErr, ReadData);
    
    // 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);
        }
            
    // 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')
        {
        UniversalProcPtr    hookRoutine = (UniversalProcPtr)gMyDrawGlue;
        
        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)
            {
            long response;
 
            (**(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);
        }
 
    // if stationary, 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;
        }
        
    // initalize undo information
    ((TextDataPtr) pData)->prevCommandID = cNull;
    
    // if we have voices, add them to the menu
    if ( (gMachineInfo.haveTTS) && (!gAddedVoices) )
        {
        short   theVoiceCount;
        short   i, item;
        
        if (CountVoices( &theVoiceCount ) == noErr)
            {
            VoiceSpec           spec;               // A voice to add to the menu.
            VoiceDescription    description;        // Info about a voice.
            MenuHandle          voicesMenu = GetMenuHandle(mVoices);
 
            anErr = GetVoiceDescription( nil, &description, sizeof(description) );
            if (anErr == noErr)
                {
                gCurrentVoice = description.voice;
                for (i = 1; i <= theVoiceCount; ++i)
                    {
                    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;
                            }
                            
                        for ( item = 1; item <= menuCount; ++item )
                            {
                            Str255  itemText;
                            
                            GetMenuItemText( voicesMenu, item, itemText );
                            /*1st > 2nd*/
                            if ( IUCompString( itemText, description.name ) == 1 )
                                break;                      // Found where name goes in list.
                            }
        
                        InsertMenuItem( voicesMenu, "\p ", item - 1 );
                        SetMenuItemText( voicesMenu, item, description.name );
                        
                        CheckItem(voicesMenu, item, 
                            ((gCurrentVoice.creator == spec.creator) && (gCurrentVoice.id == spec.id)) );
                        }
                    }
    
                }
            
            gAddedVoices = true;
            }
            
        } // end of adding voices
        
    return noErr;
    
// EXCEPTION HANDLING
ReadData:
    TEDispose(((TextDataPtr) pData)->hTE);
    
TENewFailed:
 
    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);
    
    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