SimpleText.c

/*
**  File:       SimpleText.c
**
**  Contains:   SimpleText - a simple document editing application for shipping
**                           with system software.
**
**  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 <ImageCompression.h>   // for CustomGetFilePreview
#include <Threads.h>
 
#define CompilingMain=1
#include "SimpleText.h"
#include "Clipboard.h"
#include "ExtendPrintRecord.h"
#include "ScriptablePrinting.h"
 
// --------------------------------------------------------------------------------------------------------------
// FORWARD DECLARES
// --------------------------------------------------------------------------------------------------------------
OSErr   DoActivate(WindowRef pWindow, Boolean activating);
OSErr   DoCommand(WindowRef pWindow, short commandID, long menuResult);
OSErr   DoKeyEvent(WindowRef pWindow, EventRecord * pEvent, Boolean processPageControls);
Boolean CommandToIDs(short commandID, short * menuID, short *itemID);
void    AdjustMenus(WindowRef pWindow, Boolean editDialogs, Boolean forceTitlesOn);
 
// --------------------------------------------------------------------------------------------------------------
// GLOBAL VARIABLES
// --------------------------------------------------------------------------------------------------------------
EventRecord         gEvent;                 // currently pending event
Boolean             gAllDone;               // true if the application is the in process of terminating
MachineInfoRec      gMachineInfo;           // info about abilities and options installed on this machine
short               gApplicationResFile;    // resource fork of application
RgnHandle           gCursorRgn;             // region to control the cursor apearence
AGRefNum            gAGRefNum = -1;         // AppleGuide database which is open
FSSpec              gAGSpec;                // where to find our database
AGCoachRefNum       gAGCoachRefNum = -1;    // coach handler refNum
FontMappingHandle   gFontMappingList = nil; // list of font mappings
ThreadID            gFontThread;            // thread that builds font menu
ThreadID            gAGThread;              // thread that looks for AppleGuide database
ThreadID            gStarterThread;         // starts our other threads for us
Boolean             gDontYield;             // whether our threads should yield
void*               gThreadResults;         // scratch space for thread results
 
// These variables are for the find/replace commands
Str255          gFindString = "\p", gReplaceString = "\p";
Boolean         gWrapAround = false, gCaseSensitive = false;
 
// Metrowerks MWCRuntime.lib defines qd for us on PPC, and their
// __runtime module does under the 68K case. OTOH, neither SC nor
// MrC give us qd for free, so we need it there. I'm still not
// certain which way to go for the ThinkC or Symantec PPC case.
#if !defined(__MWERKS__)
// QuickDraw globals
QDGlobals       qd;
#endif
 
// --------------------------------------------------------------------------------------------------------------
#pragma segment Utility
 
static pascal Boolean AlertFilter(DialogRef theDialog, EventRecord *theEvent, short *itemHit)
{
    if (theEvent->what == activateEvt && (DialogRef) theEvent->message == theDialog)
        {
        SetDialogDefaultItem(theDialog, 1);
        }
 
    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;
 
} // AlertFilter
 
 
void ConductErrorDialog(OSErr error, short commandID, short alertType)
{
    long        foundError;         // The error, converted to a number
    short       stringIndex;        // Index into the strings
    Str255      errorText;          // the error in a string format
    
    // Start with no error so far
    foundError = 0;
    
    // Start with the first string
    stringIndex = 1;
    
    // Loop until we find an error string
    errorText[0] = 0;
 
    do
        {
        // Get the string, and convert it to a number
        GetIndString(errorText, kErrorBaseID + commandID, stringIndex);
        if (errorText[0] == 0)
            break;
        StringToNum(errorText, &foundError);
        
        // If we reach the last string, or we match the error code
        if ((foundError == 0) ||
            (foundError == error))
            {
            // Get the text string for this error
            GetIndString(errorText, kErrorBaseID + commandID, stringIndex+1);
            }
        else
            {
            // Otherwise, make us continue until we get a string
            errorText[0] = 0;
            }
            
        // Advance so we get the next string number
        stringIndex += 2;
        
        } while (errorText[0] == 0);                // errorText[0] == 0
        
    if (errorText[0] != 0)
        {
        DialogRef   dPtr;
        short       hit;
        
        SetCursor(&qd.arrow);
        ParamText(errorText, "\p", "\p", "\p");
        
        #if !GENERATINGPOWERPC
            if (gMachineInfo.theEnvirons.systemVersion < 0x0700)
                {
                short ** hDialog;
                
                hDialog = (short**) GetResource('DLOG', kErrorBaseID + alertType);
                (*hDialog)[4] = dBoxProc;
                
                dPtr = GetNewDialog(kErrorBaseID + alertType, nil, (WindowRef)-1);
                
                do
                    {
                    ModalDialog(nil, &hit);
                    } while (hit != ok);
                
                DisposeDialog(dPtr);
                }
            else
                {
                dPtr = GetNewDialog(kErrorBaseID + alertType, nil, (WindowRef)-1);
                
                SetDialogDefaultItem(dPtr, ok);
                
                BeginMovableModal();
                
                do
                    {
                    MovableModalDialog(nil, &hit);
                    } while (hit != ok);
                
                DisposeDialog(dPtr);
                EndMovableModal();
                }
        #else
            dPtr = GetNewDialog(kErrorBaseID + alertType, nil, (WindowRef)-1);
            
            SetDialogDefaultItem(dPtr, ok);
            
            BeginMovableModal();
            
            do
                {
                MovableModalDialog(nil, &hit);
                } while (hit != ok);
            
            DisposeDialog(dPtr);
            EndMovableModal();
        #endif
        }
        
} // ConductErrorDialog
 
// --------------------------------------------------------------------------------------------------------------
#pragma segment Utility
 
static void MovableModalMenus(DialogRef dPtr, short *pItem, long menuResult)
{
    short   iCut, iCopy, iClear, iPaste;
    short   editMenu;
    short   menuItem = menuResult & 0xFFFF;
    
    // find out where edit menus are
    CommandToIDs(cCut, &editMenu, &iCut);
    CommandToIDs(cCopy, &editMenu, &iCopy);
    CommandToIDs(cClear, &editMenu, &iClear);
    CommandToIDs(cPaste, &editMenu, &iPaste);
    
    HiliteMenu(0);
    switch (menuResult >> 16)
        {
        case mApple:
            {
            Str255  tempString;
            
            GetMenuItemText(GetMenuHandle(menuResult>>16), menuItem, tempString);
            OpenDeskAcc(tempString);
            }
            break;
            
        case mEdit:
            {
            short   type;
            Handle  item;
            Rect    box;
            short   editField = GetDialogKeyboardFocusItem(dPtr);
            
            // return typed item, if it isn't disabled
            GetDialogItem(dPtr, editField, &type, &item, &box);
            if ((type & itemDisable) == 0)
                *pItem = editField;
                
            if (menuItem == iCut)
                {
                DialogCut(dPtr);
                ZeroScrap();
                TEToScrap();
                }
                
            if (menuItem == iCopy)
                {
                DialogCopy(dPtr);
                ZeroScrap();
                TEToScrap();
                }
                
            if (menuItem == iClear)
                DialogDelete(dPtr);
                
            if (menuItem == iPaste)
                DialogPaste(dPtr);
            }
            break;
        }
        
} // MovableModalMenus
 
// --------------------------------------------------------------------------------------------------------------
#pragma segment Utility
 
void MovableModalDialog(ModalFilterProcPtr filterProc, short *pItem)
/*
    Call this as you would ModalDialog, when the dialog is moveable
    modal.
    
    However, first call BeginMovableModal, and afterwards (after
    disposing of dialog) call EndMovableModal.
*/
{
    GrafPtr     curPort;
    DialogRef   dPtr = FrontWindow();
    
    *pItem = 0; 
    if (dPtr)
        {
        GetPort(&curPort);
        SetPort(dPtr);
        
        do
            {
            WaitNextEvent(mDownMask + mUpMask + keyDownMask + keyUpMask + autoKeyMask + updateMask + activMask + osMask,
                            &gEvent, 0, nil);
            
            // call the filter proc
            if ( (filterProc) && ((*filterProc) (dPtr, &gEvent, pItem)) )
                break;
                            
            // call the basic filtering
            if (StdFilterProc(dPtr, &gEvent, pItem))
                break;
                
            // handle keyboard
            if ((gEvent.what == keyDown) && (gEvent.modifiers & cmdKey))
                {
                MovableModalMenus(dPtr, pItem, MenuKey(gEvent.message & charCodeMask));
                break;
                }
                
            // handle clicks and drags
            if (gEvent.what == mouseDown)
                {
                WindowRef   whichWindow;
                short       part = FindWindow(gEvent.where, &whichWindow);
                
                // menu bar events
                if (part == inMenuBar)
                    {
                    MovableModalMenus(dPtr, pItem, MenuSelect(gEvent.where));
                    break;
                    }
                    
                // check for outside of our window
                if (!PtInRgn(gEvent.where, ((WindowPeek)dPtr)->strucRgn))
                    {
                    SysBeep(1);
                    gEvent.what = nullEvent;
                    }
                    
                // drag the window around
                if ( (part == inDrag) && (whichWindow == dPtr) )
                    {
                    Rect    tempRect = (**GetGrayRgn()).rgnBBox;
                    
                    DragWindow(GetDialogWindow(dPtr), gEvent.where, &tempRect);
                    gEvent.what = nullEvent;
                    }
                }
                
            // check with standard dialog stuff 
            {
            DialogRef   tempDialog;
            
            if ( IsDialogEvent(&gEvent) && DialogSelect(&gEvent, &tempDialog, pItem) )
                break;
            }
            
            // handle updates
            if (gEvent.what == updateEvt)
                {
                HandleEvent(&gEvent);
                break;
                }
            } while (true);
        
        SetPort(curPort);
        }
        
} // MovableModalDialog
 
// --------------------------------------------------------------------------------------------------------------
#pragma segment Utility
 
void BeginMovableModal(void)
{
    DialogRef   dPtr = FrontWindow();
    WindowRef   nextWindow = GetNextWindow(dPtr);
    
    if (nextWindow)
        DoActivate(nextWindow, false);
    AdjustMenus(GetDialogWindow(dPtr), (GetDialogKeyboardFocusItem(dPtr) > 0), false);
 
} // BeginMovableModal
 
// --------------------------------------------------------------------------------------------------------------
#pragma segment Utility
 
void EndMovableModal(void)
{
    WindowRef   nextWindow = FrontWindow();
    
    AdjustMenus(nextWindow, true, false);
    if (nextWindow)
        DoActivate(nextWindow, true);
    
} // EndMovableModal
 
// --------------------------------------------------------------------------------------------------------------
#pragma segment Utility
 
short ConductFindOrReplaceDialog(short dialogID)
{
    DialogRef   dPtr;
    short       hit;
    
    dPtr = GetNewDialog(dialogID, nil, (WindowRef)-1);
    if (dPtr)
        {
        short   kind;
        Rect    box;
        Handle  item;
        
        // standard default behavior
        SetDialogDefaultItem(dPtr, ok);
        SetDialogCancelItem (dPtr, cancel);
        SetDialogTracksCursor(dPtr, true);
        
        // Find string
        GetDialogItem(dPtr, iFindEdit, &kind, &item, &box);
        SetDialogItemText(item, gFindString);
 
        // check boxes
        GetDialogItem(dPtr, iCaseSensitive, &kind, &item, &box);
        SetControlValue((ControlRef)item, gCaseSensitive);
        GetDialogItem(dPtr, iWrapAround, &kind, &item, &box);
        SetControlValue((ControlRef)item, gWrapAround);
        
        if (dialogID == kReplaceWindowID)
            {
            // Replace string
            GetDialogItem(dPtr, iReplaceEdit, &kind, &item, &box);
            SetDialogItemText(item, gReplaceString);
            }
        
        // select the search text by default
        SelectDialogItemText(dPtr, iFindEdit, 0, 32767);
        
        // and away we go!
        ShowWindow(GetDialogWindow(dPtr));
        BeginMovableModal();
        
        do
            {
            MovableModalDialog(nil, &hit);
            switch (hit)
                {
                case iCaseSensitive:
                case iWrapAround:
                    GetDialogItem(dPtr, hit, &kind, &item, &box);
                    SetControlValue((ControlRef)item, 1-GetControlValue((ControlRef)item));
                    break;
                }
            } while ( (hit != ok) && (hit != cancel) && (hit != iReplaceAll) );
        
        if (hit != cancel)
            {
            // Find string
            GetDialogItem(dPtr, iFindEdit, &kind, &item, &box);
            GetDialogItemText(item, gFindString);
    
            // check boxes
            GetDialogItem(dPtr, iCaseSensitive, &kind, &item, &box);
            gCaseSensitive = GetControlValue((ControlRef)item);
            GetDialogItem(dPtr, iWrapAround, &kind, &item, &box);
            gWrapAround = GetControlValue((ControlRef)item);
            
            if (dialogID == kReplaceWindowID)
                {
                // Replace string
                GetDialogItem(dPtr, iReplaceEdit, &kind, &item, &box);
                GetDialogItemText(item, gReplaceString);
                }
            }
            
        DisposeDialog(dPtr);
        EndMovableModal();
        }
        
    return(hit);
    
} // ConductFindOrReplaceDialog
 
// --------------------------------------------------------------------------------------------------------------
#pragma segment Utility
 
void SetWatchCursor(void)
{
    CursHandle  theWatch;
        
    theWatch = GetCursor(watchCursor);
    if (theWatch)
        {
        char    oldState;
        
        oldState = HGetState((Handle) theWatch);
        HLock((Handle) theWatch);
        SetCursor(*theWatch);
        HSetState((Handle) theWatch, oldState);
        }
        
} // SetWatchCursor
 
// --------------------------------------------------------------------------------------------------------------
#pragma segment Utility
 
void LongRectToRect(LongRect* longRect, Rect *rect)
{
    rect->top       = longRect->top;
    rect->left      = longRect->left;
    rect->bottom    = longRect->bottom;
    rect->right     = longRect->right;
    
} // LongRectToRect
 
// --------------------------------------------------------------------------------------------------------------
#pragma segment Utility
 
void RectToLongRect(Rect *rect, LongRect *longRect)
{
    longRect->top       = rect->top;
    longRect->left      = rect->left;
    longRect->bottom    = rect->bottom;
    longRect->right     = rect->right;
    
} // RectToLongRect
 
// --------------------------------------------------------------------------------------------------------------
#pragma segment Utility
 
#ifndef ff
#define ff(x) (x << 16)
#endif
 
void GetPICTRectangleAt72dpi(PicHandle hPicture, Rect *pictureRect)
{
    typedef struct FixedRect {
        Fixed left;
        Fixed top;
        Fixed right;
        Fixed bottom;
    } FixedRect;
    
    typedef struct {
        Picture             pictInfo;
        unsigned short      versionOp;      // 0x1101
        Byte                opCodes[1];
    } PICTHeaderVer1;
    
    typedef struct {
        Picture         pictInfo;
        unsigned short  versionOp;      // 0x0011
        unsigned short  versionOp2;     // 0x02ff
        unsigned short  headerOp;       // 0x0c00
        unsigned short  version;        // 0xffff
        unsigned short  version2;       // 0xffff
        FixedRect       pictBounds;
        unsigned long   reserved;
        unsigned short  opCodes[1];
    } PICTHeaderVer2;
    
    typedef struct {
        Picture         pictInfo;
        unsigned short  versionOp;      // 0x0011
        unsigned short  versionOp2;     // 0x02ff
        unsigned short  headerOp;       // 0x0c00
        unsigned short  version;        // 0xfffe
        unsigned short  reserved;       // 0x0000
        Fixed           hRes;
        Fixed           vRes;
        Rect            pictBounds;
        unsigned long   reserved2;
        unsigned short  opCodes[1];
    } PICTHeaderVer2Ext;
    
    const Fixed seventyTwo = 0x480000;
 
    Fixed           hRes, vRes;
    PICTHeaderVer1* pPict = (PICTHeaderVer1*) *hPicture;
 
    hRes = vRes = seventyTwo;       // assume 72 dpi, Fixed
 
    if (pPict->versionOp == 0x0011) 
        {   
        // Version 2 PICT
    
        PICTHeaderVer2* pPict2 = (PICTHeaderVer2*) pPict;
        
        if (pPict2->version == 0xfffe) 
            {   
            // Extended Version 2
            PICTHeaderVer2Ext* pPict2ext = (PICTHeaderVer2Ext*) pPict;
            hRes = pPict2ext->hRes;
            vRes = pPict2ext->vRes;
            }
        }
 
    hRes = FixDiv(hRes, seventyTwo);
    vRes = FixDiv(vRes, seventyTwo);
    pictureRect->left   = Fix2Long(FixDiv( ff((**hPicture).picFrame.left),      hRes ));
    pictureRect->right  = Fix2Long(FixDiv( ff((**hPicture).picFrame.right),     hRes ));
    pictureRect->top    = Fix2Long(FixDiv( ff((**hPicture).picFrame.top),       vRes ));
    pictureRect->bottom = Fix2Long(FixDiv( ff((**hPicture).picFrame.bottom),    vRes ));
    
} // GetPICTRectangleAt72dpi
 
#undef ff
// --------------------------------------------------------------------------------------------------------------
#pragma segment Utility
 
static WindowDataPtr    GetWindowInfo(WindowRef pWindow)
{
    WindowDataPtr result = nil;
    
    if  (
        (pWindow) &&
        (GetWindowKind(pWindow) == userKind)
        )
        result = (WindowDataPtr) GetWRefCon(pWindow);
 
    return result;
    
} // GetWindowInfo
 
// --------------------------------------------------------------------------------------------------------------
#pragma segment Utility
 
static short ZeroStringSub(Str255 destString, Str255 subStr)
    // returns number of substitutions performed
{
    OSErr   anErr;
    Handle  destHandle = nil;
    Handle  subHandle = nil;
    short   count = 0;
 
    anErr = PtrToHand(&destString[1], &destHandle, destString[0]);
    if (anErr == noErr)
        {       
        anErr = PtrToHand(&subStr[1], &subHandle, subStr[0]);
        if (anErr == noErr)
            {
            count = ReplaceText(destHandle, subHandle, "\p^0");     // error or # of substitutions
                        
            destString[0] = GetHandleSize(destHandle);
            BlockMoveData(*destHandle, &destString[1], destString[0]);
            }
        }
 
    DisposeHandle(destHandle);
    DisposeHandle(subHandle);
 
    if (count < 0)
        count = 0;      // change error code into count = 0 substitutions
 
    return count;
 
} // ZeroStringSub
 
// --------------------------------------------------------------------------------------------------------------
// SEARCH/REPLACE UTILITY FUNCTIONS
// --------------------------------------------------------------------------------------------------------------
static Boolean IsThisTheString(
            Ptr p,                      // pointer to check
            Str255 searchString,        // string to check for
            Boolean isCaseSensitive)    // case sensitive check or not
/*
    Returns true if the supplied string is at the specified offset.
    Otherwise returns false.
*/
{
    Boolean returnValue = false;
    
    if (isCaseSensitive)
        returnValue = ( IUMagString(p, &searchString[1], searchString[0], searchString[0]) == 0 );
    else
        returnValue = ( IUMagIDString(p, &searchString[1], searchString[0], searchString[0]) == 0 );
        
    return(returnValue);
    
} // IsThisTheString
 
// --------------------------------------------------------------------------------------------------------------
 
Boolean PerformSearch(
        Handle  h,                  // handle to search
        long start,                 // offset to begin with
        Str255 searchString,        // string to search for
        Boolean isCaseSensitive,    // case sensitive search
        Boolean isBackwards,        // search backwards from starting point
        Boolean isWraparound,       // wrap search around from end->begining
        long * pNewStart,           // returned new selection start
        long * pNewEnd)             // returned new selection end
/*
    Performs a search on the supplied handle, starting at the provided
    offset.  Returns the new selection start and end values, and true
    if the search is successful.  Otherwise it returns false.
*/
{
    char    flags;
    Ptr     startPtr;
    Ptr     endPtr;
    Ptr     searchPtr;
    Boolean foundIt = false;
    
    flags = HGetState(h);
    HLock(h);
            
    // back up one when searching backwards, or we'll hit every time on the current
    // character
    if (isBackwards)
        {
        if (start != 0)
            {
            --start;
            }
        else
            {
            if (isWraparound)
                start = GetHandleSize(h);
            else
                return(false);
            }
        }
        
    // determine the bounds of the searching
    startPtr = (*h) + start;
    if ( isWraparound )
        {
        if (isBackwards)
            {
            // go backwards until just after the start, or begining of
            // document is start is the end
            if (start == GetHandleSize(h))
                endPtr = *h;
            else
                endPtr = startPtr + 1;
            }
        else
            {
            // go forwards until just before the start, or to the end
            // of the document is the start is already the begining
            if (start == 0)
                endPtr = *h + GetHandleSize(h);
            else
                endPtr = startPtr - 1;
            }
        }
    else
        {
        if (isBackwards)
            {
            // go back until hit begining of document
            endPtr = *h-1;  
            }
        else
            {
            // go forward until hit end of document
            endPtr = *h + GetHandleSize(h);
            }
        }
        
    searchPtr = startPtr;
    while (searchPtr != endPtr)
        {
        if (IsThisTheString(searchPtr, searchString, isCaseSensitive))
            {
            foundIt = true;
            *pNewStart = searchPtr - *h;
            *pNewEnd = *pNewStart + searchString[0];
            break;
            }
            
        if (isBackwards)
            --searchPtr;
        else
            ++searchPtr;
            
        if (isWraparound)
            {
            if (searchPtr < *h)
                searchPtr = *h + GetHandleSize(h);
            if (searchPtr > *h + GetHandleSize(h))
                searchPtr = *h;
            }
        }
        
    HSetState(h, flags);
    
    return(foundIt);
    
} // PerformSearch
 
// --------------------------------------------------------------------------------------------------------------
// SELECTION UTILITY ROUTINES
// --------------------------------------------------------------------------------------------------------------
void DrawSelection(WindowDataPtr pData, Rect *pSelection, short * pPhase, Boolean bumpPhase)
{
    if  (!EmptyRect(pSelection) ) 
        {
        RgnHandle   oldClip = NewRgn();
        Pattern     aPattern;
        Rect        newClip;
 
        
        if  ( 
            (bumpPhase) && 
            (MOVESELECTION(TickCount()) ) 
            )
            {
            if ((++(*pPhase)) > 7 )
                *pPhase = 1;
            }
            
        // setup for drawing in this window
        SetPort((GrafPtr) pData);
        GetClip(oldClip);
        PenMode(notPatXor);
        
        // offset the draw area (SetOrigin a must to preserve pattern appearence)
        // and the clip area to avoid stepping on the scroll bars
        SetOrigin(GetControlValue(pData->hScroll), GetControlValue(pData->vScroll));
        newClip = pData->contentRect;
        OffsetRect(&newClip, GetControlValue(pData->hScroll), GetControlValue(pData->vScroll));
        ClipRect(&newClip);
        
        // do the draw
        GetIndPattern(&aPattern, kPatternListID, (*pPhase)+1);
        PenPat(&aPattern);
        FrameRect(pSelection);
        SetOrigin(0, 0);
        
        // restore the old port settings
        SetClip(oldClip);
        DisposeRgn(oldClip);
        PenNormal();
 
        }
 
} // DrawSelection
 
// --------------------------------------------------------------------------------------------------------------
OSErr SelectContents(WindowRef pWindow, WindowDataPtr pData, EventRecord *pEvent, Rect *pSelection, Rect *pContent, short *pPhase)
{
 
    OSErr           anErr = noErr;
    Point           clickPoint = pEvent->where;
    Point           currentPoint;
    Boolean         didJustScroll;
    ControlRef      theControl;
    
    GlobalToLocal(&clickPoint);
    if (FindControl(clickPoint, pWindow, &theControl) == 0)
        {
    
        // move the click point into the proper range
        clickPoint.h += GetControlValue(pData->hScroll);
        clickPoint.v += GetControlValue(pData->vScroll);
        
        // if the shift key is held down then the selection starts from
        // a preexisting point such that we are doing an expand/contract
        // of the original selection
        if (pEvent->modifiers & shiftKey)
            {
            if (clickPoint.h < pSelection->right)
                clickPoint.h = pSelection->right;
            else
                clickPoint.h = pSelection->left;
 
            if (clickPoint.v < pSelection->bottom)
                clickPoint.v = pSelection->bottom;
            else
                clickPoint.v = pSelection->top;
            }
                        
        while (StillDown())
            {                   
            // get the current mouse 
            GetMouse(&currentPoint);
            
            didJustScroll = false;
            // scroll contents if needed
            {
            short   deltaH = 0;
            short   deltaV = 0;
            
            if (currentPoint.h < 0)
                deltaH = pData->hScrollAmount;
            if (currentPoint.h > qd.thePort->portRect.right)
                deltaH = -pData->hScrollAmount;
            if (currentPoint.v < 0)
                deltaV = pData->vScrollAmount;
            if (currentPoint.v > qd.thePort->portRect.bottom)
                deltaV = -pData->vScrollAmount;
                
            if ( (deltaH != 0) || (deltaV != 0) )
                {               
                if (deltaH)
                    SetControlAndClipAmount(pData->hScroll, &deltaH);
                if (deltaV)
                    SetControlAndClipAmount(pData->vScroll, &deltaV);
 
                DoScrollContent(pWindow, pData, deltaH, deltaV);
                
                didJustScroll = true;
                }
            }
            
            // map mouse into proper range
            currentPoint.h += GetControlValue(pData->hScroll);
            currentPoint.v += GetControlValue(pData->vScroll);
    
            // clip to the document size
            if (currentPoint.h < 0)
                currentPoint.h = 0;
            if (currentPoint.v < 0)
                currentPoint.v = 0;
            if (currentPoint.h > pContent->right)
                currentPoint.h = pContent->right;
            if (currentPoint.v > pContent->bottom)
                currentPoint.v = pContent->bottom;
                
            // draw the new selection if it is time or we are about to 
            // exit this loop
            if ((MOVESELECTION(TickCount())) || (!Button()) || (didJustScroll) )
                {
                // first, erase any old selection we might have had
                DrawSelection(pData, pSelection, pPhase, false);
 
                // make a rectangle out of the two points
                pSelection->left    = Min(currentPoint.h, clickPoint.h);
                pSelection->right   = Max(currentPoint.h, clickPoint.h);
                pSelection->top     = Min(currentPoint.v, clickPoint.v);
                pSelection->bottom  = Max(currentPoint.v, clickPoint.v);
    
                // draw the new selection
                DrawSelection(pData, pSelection, pPhase, true);
                }
            }
        
        // we handled the selection
        anErr = eActionAlreadyHandled;
        }
        
    return(anErr);
    
} // SelectContents
 
// --------------------------------------------------------------------------------------------------------------
void DragAndDropArea(WindowRef pWindow, WindowDataPtr pData, EventRecord* event, Rect *pFrameRect)
{
    RgnHandle       hilightRgn;
    Rect            r;
    DragReference   theDrag;
    OSErr           anErr = noErr;
    
    if (NewDrag(&theDrag) == noErr)
        {
        if (pData->pDragAddFlavors)
            anErr = (*(pData->pDragAddFlavors)) (pWindow, pData, theDrag);
        
        if (anErr == noErr)
            {
            Rect    globalRect = *pFrameRect;
            
            hilightRgn = NewRgn();  
            LocalToGlobal(&TopLeft(globalRect));
            LocalToGlobal(&BotRight(globalRect));
            RectRgn(hilightRgn, &globalRect);
            SetDragItemBounds(theDrag, 1, &r);
    
            // turn the region from a fill into a frame
            {   
                RgnHandle tempRgn = NewRgn();
    
                CopyRgn(hilightRgn, tempRgn);
                InsetRgn(tempRgn, 1, 1);
                DiffRgn(hilightRgn, tempRgn, hilightRgn);
                DisposeRgn(tempRgn);
            }
            
            TrackDrag(theDrag, event, hilightRgn);
            DisposeDrag(theDrag);
            DisposeRgn(hilightRgn);
            }
        }
 
} // DragAndDropArea
 
// --------------------------------------------------------------------------------------------------------------
// WINDOW UTILITY ROUTINES
// --------------------------------------------------------------------------------------------------------------
#pragma segment Main
 
static void CalculateGrowIcon(WindowDataPtr pData, Rect * location)
{
    if (pData->vScroll)
        location->top = (**pData->vScroll).contrlRect.bottom;
    else
        {
        if (pData->hScroll)
            location->top = (**pData->hScroll).contrlRect.top;
        else
            location->top = pData->theWindow.port.portRect.bottom - 15;
        }
        
    if (pData->hScroll)
        location->left = (**pData->hScroll).contrlRect.right;
    else
        {
        if (pData->vScroll)
            location->left = (**pData->vScroll).contrlRect.left;
        else
            location->left = pData->theWindow.port.portRect.right - 15;
        }
        
    location->right = location->left + 16;
    location->bottom = location->top + 16;
    
} // CalculateGrowIcon
 
// --------------------------------------------------------------------------------------------------------------
#pragma segment Main
 
OSErr   AdjustScrollBars(WindowRef pWindow,
    Boolean moveControls,               // might the controls have moved?
    Boolean didResize,                  // did we just resize the window?
    Boolean *needInvalidate)            // does the caller need to invalidate contents as a result?
{
    OSErr           anErr = noErr;
    LongRect        docRect;
    WindowDataPtr   pData = GetWindowInfo(pWindow);
    Rect            growIconRect;
    
    if (needInvalidate)
        *needInvalidate = false;
 
    if (pData)
        {
        short   oldHMax, oldVMax;
        short   oldHValue, oldVValue;
        
        // cache current values, we'll force an update if we needed to change em!
        if (pData->hScroll)
            {
            oldHMax = GetControlMaximum(pData->hScroll);
            oldHValue = GetControlValue(pData->hScroll);
            }
        if (pData->vScroll)
            {
            oldVMax = GetControlMaximum(pData->vScroll);
            oldVValue = GetControlValue(pData->vScroll);
            }
            
        // if we have a grow box but not all controls we have to invalidate the grow bar areas
        // by caclulating them
        if ( (didResize) && (pData->hasGrow) )
            {
            // if we regrow without any scroll bars, we need to update the content area
            if ( (needInvalidate) && (pData->hScroll == nil) && (pData->vScroll == nil) )
                *needInvalidate = true;
            
            // invalidate old grow bar areas
            if (pData->vScroll == nil)
                {
                growIconRect = GetWindowPort(pWindow)->portRect;
                growIconRect.left = pData->contentRect.right;
                InvalRect(&growIconRect);
                }
            if (pData->hScroll == nil)
                {
                growIconRect = GetWindowPort(pWindow)->portRect;
                growIconRect.top = pData->contentRect.bottom;
                InvalRect(&growIconRect);
                }
            
            // invalidate new grow bar areas
            if (pData->vScroll == nil)
                {
                growIconRect = GetWindowPort(pWindow)->portRect;
                growIconRect.left = growIconRect.right - kScrollBarSize;
                InvalRect(&growIconRect);
                }
            if (pData->hScroll == nil)
                {
                growIconRect = GetWindowPort(pWindow)->portRect;
                growIconRect.top = growIconRect.bottom - kScrollBarSize;
                InvalRect(&growIconRect);
                }
            }
            
        // if the controls need moving, recalculate the visible area
        if (moveControls)
            {
            pData->contentRect = GetWindowPort(pWindow)->portRect;
            if ((pData->hScroll) || (pData->hasGrow) )
                pData->contentRect.bottom -= kScrollBarSize;
            if ((pData->vScroll) || (pData->hasGrow) )
                pData->contentRect.right -= kScrollBarSize;
            }
            
        // before doing anything, make the controls invisible
        if (pData->hScroll)
            (**pData->hScroll).contrlVis = 0;   
        if (pData->vScroll)
            (**pData->vScroll).contrlVis = 0;
 
        // based on document and visiable area, adjust possible control values
        if ( (pData->pGetDocumentRect) && ((pData->hScroll) || (pData->vScroll)) )
            {
            // let the object calc the size and content if it wishes to
            anErr = (*(pData->pGetDocumentRect)) (pWindow, pData, &docRect, false);
            if (anErr == noErr)
                {
                short   amountOver;
                short   newMax;
                
                amountOver = (docRect.right - docRect.left) - (pData->contentRect.right - pData->contentRect.left);
                if  (
                    (pData->hScroll) &&
                    (amountOver > 0)
                    )
                    newMax = amountOver;
                else
                    newMax = 0;
    
                if (pData->hScroll)
                    {
                    if (GetControlValue(pData->hScroll) > newMax)
                        {
                        if (needInvalidate)
                            *needInvalidate = true;
                        }
                    SetControlMaximum(pData->hScroll, newMax);
                    }
                
                amountOver = (docRect.bottom - docRect.top) - (pData->contentRect.bottom - pData->contentRect.top);
                if  (
                    (pData->vScroll) &&
                    (amountOver > 0)
                    )
                    newMax = amountOver;
                else
                    newMax = 0;
                    
                if (pData->vScroll)
                    {
                    if (GetControlValue(pData->vScroll) > newMax)
                        {
                        if (needInvalidate)
                            *needInvalidate = true;
                        }
                    SetControlMaximum(pData->vScroll, newMax);
                    }
                }
            }
            
        // then, if the controls need moving, we move them and inval the old
        // and new locations
        if (moveControls)
            {
            // if we have grow box we invalidate the old grow location
            if ( pData->hasGrow) 
                {
                CalculateGrowIcon(pData, &growIconRect);
                InvalRect(&growIconRect);
                }
                
            if (pData->hScroll)
                {
                short   widthAdjust;
                
                if ((pData->vScroll) || (pData->hasGrow))
                    widthAdjust = -kGrowScrollAdjust;
                else
                    widthAdjust = -1;
                    
                growIconRect = (**pData->hScroll).contrlRect;
                InvalRect(&growIconRect);
                
                MoveControl(pData->hScroll, pData->hScrollOffset-1, GetWindowPort(pWindow)->portRect.bottom - kScrollBarSize);
                SizeControl(pData->hScroll, (GetWindowPort(pWindow)->portRect.right - 
                            GetWindowPort(pWindow)->portRect.left) + widthAdjust - pData->hScrollOffset,
                            16);
 
                growIconRect = (**pData->hScroll).contrlRect;
                InvalRect(&growIconRect);
                }
 
            if (pData->vScroll)
                {
                short   heightAdjust;
                
                if ((pData->hScroll) || (pData->hasGrow))
                    heightAdjust = -kGrowScrollAdjust;
                else
                    heightAdjust = -1;
                    
                growIconRect = (**pData->vScroll).contrlRect;
                InvalRect(&growIconRect);
 
                MoveControl(pData->vScroll, GetWindowPort(pWindow)->portRect.right - kScrollBarSize, pData->vScrollOffset-1);
                SizeControl(pData->vScroll, 16,
                            (GetWindowPort(pWindow)->portRect.bottom - 
                            GetWindowPort(pWindow)->portRect.top) + heightAdjust - pData->vScrollOffset);
                growIconRect = (**pData->vScroll).contrlRect;
                InvalRect(&growIconRect);
                }
                
            // if we have scroll bars, update the grow icon
            if ( pData->hasGrow )
                {
                CalculateGrowIcon(pData, &growIconRect);
                InvalRect(&growIconRect);
                }
            
            }
 
        // let the document adjust anything it needs to
        if (pData->pAdjustSize)
            anErr = (*(pData->pAdjustSize)) (pWindow, pData, &didResize);
            
        if ((didResize) && (needInvalidate))
            *needInvalidate = true;
 
 
        if ( ((WindowPeek) pWindow)->hilited )
            {
            // after doing something, make the controls visible
            if (pData->hScroll)
                {
                if ((oldHMax != GetControlMaximum(pData->hScroll)) || (oldHValue != GetControlValue(pData->hScroll)) )
                    ShowControl(pData->hScroll);
                else
                    (**pData->hScroll).contrlVis = 0xFF;    
                }
            if (pData->vScroll)
                {
                if ((oldVMax != GetControlMaximum(pData->vScroll)) || (oldVValue != GetControlValue(pData->vScroll)) )
                    ShowControl(pData->vScroll);
                else
                    (**pData->vScroll).contrlVis = 0xFF;
                }
            }
 
        }
        
    return anErr;
    
} // AdjustScrollBars
 
// --------------------------------------------------------------------------------------------------------------
// MENU UTILITY ROUTINES
// --------------------------------------------------------------------------------------------------------------
#pragma segment Main
 
Boolean CommandToIDs(short commandID, short * menuID, short *itemID)
{
 
    short   ** commandHandle;
    short   whichMenu;
    short   oldResFile = CurResFile();
    Boolean returnValue = false;
    
    UseResFile(gApplicationResFile);
    for (whichMenu = mApple; whichMenu <= mLastMenu; whichMenu++)
        {
        commandHandle = (short**) Get1Resource('MCMD', whichMenu);
        if (commandHandle)
            {
            short   * pCommands = *commandHandle;
            short   commandIndex;
            short   numCommands = pCommands[0];
            
            for (commandIndex = 1; commandIndex <= numCommands; ++commandIndex)
                if (pCommands[commandIndex] == commandID)
                    {
                    *menuID = whichMenu;
                    *itemID = commandIndex;
                    
                    returnValue = (commandIndex == numCommands);
                    }
            }   
        }
        
    UseResFile(oldResFile);
    
    return returnValue;
    
} // CommandToIDs
 
// --------------------------------------------------------------------------------------------------------------
#pragma segment Main
 
Boolean IsCommandEnabled(short commandID)
/*
    returns true if a given command is currently enabled
*/
{
    short       whichMenu, whichItem;
    MenuHandle  menu;
    
    CommandToIDs(commandID, &whichMenu, &whichItem);
    menu = GetMenuHandle(whichMenu);
    
    if ((**menu).enableFlags & (1 << whichItem))
        return(true);
    
    return(false);
    
} // IsCommandEnabled
 
// --------------------------------------------------------------------------------------------------------------
#pragma segment Main
 
void EnableCommand(short commandID)
/*
    Given a command ID, enables the first menu item with that command ID.
    
    If the command table for a given menu is less than the number of items in the menu,
    and the command being enabled is the last item in the command table, then all
    items from there on down are also enabled.  This is useful for menus that get
    appended to, such as the desk accessory list, font list, or speaking voices list.
*/
{
    short   whichMenu;
    short   whichItem;
    
    if (CommandToIDs(commandID, &whichMenu, &whichItem))
        {
        short       i;
        MenuHandle  menu = GetMenuHandle(whichMenu);
        
        if (menu)
            {
            short       numItems = CountMItems(menu);
            
            for (i = whichItem; i <= numItems; ++i)
                EnableItem(menu, i);
            }
        }
    else
        {
        MenuHandle  menu = GetMenuHandle(whichMenu);
 
        if (menu)
            EnableItem(menu, whichItem);
        }
        
} // EnableCommand
 
// --------------------------------------------------------------------------------------------------------------
#pragma segment Main
 
void ChangeCommandName(short commandID, short resourceID, short resourceIndex)
{
    short       whichMenu;
    short       whichItem;
    MenuHandle  menu;
    
    // figure out how this command maps into the menu bar
    CommandToIDs(commandID, &whichMenu, &whichItem);
    menu = GetMenuHandle(whichMenu);
    
    // then make this item into the requested new string
    {
    Str255      theString;
    
    GetIndString(theString, resourceID, resourceIndex);
    SetMenuItemText(menu, whichItem, theString);
    }
    
} // ChangeCommandName
 
// --------------------------------------------------------------------------------------------------------------
#pragma segment Main
 
void EnableCommandCheck(short commandID, Boolean check)
{
 
    short   whichMenu;
    short   whichItem;
    
    if (CommandToIDs(commandID, &whichMenu, &whichItem))
        {
        short       i;
        MenuHandle  menu = GetMenuHandle(whichMenu);
        short       numItems = CountMItems(menu);
        
        for (i = whichItem; i <= numItems; ++i)
            {
            EnableItem(menu, i);
            CheckItem(menu, i, check);
            }
        }
    else
        {
        MenuHandle  menu = GetMenuHandle(whichMenu);
 
        EnableItem(menu, whichItem);
        CheckItem(menu, whichItem, check);
        }
        
} // EnableCommandCheck
 
 
// --------------------------------------------------------------------------------------------------------------
#pragma segment Main
 
void EnableCommandCheckStyle(short commandID, Boolean check, short style)
{
 
    short   whichMenu;
    short   whichItem;
    
    if (CommandToIDs(commandID, &whichMenu, &whichItem))
        {
        short       i;
        MenuHandle  menu = GetMenuHandle(whichMenu);
        short       numItems = CountMItems(menu);
        
        for (i = whichItem; i <= numItems; ++i)
            {
            EnableItem(menu, i);
            CheckItem(menu, i, check);
            SetItemStyle(menu, i, style);
            }
        }
    else
        {
        MenuHandle  menu = GetMenuHandle(whichMenu);
 
        EnableItem(menu, whichItem);
        CheckItem(menu, whichItem, check);
        SetItemStyle(menu, whichItem, style);
        }
        
} // EnableCommandCheckStyle
 
// --------------------------------------------------------------------------------------------------------------
#pragma segment Main
 
void AdjustMenus(WindowRef pWindow, Boolean editDialogs, Boolean forceTitlesOn)
{
    Boolean                 wasEnabled[mNumberMenus];   // Old state of menus
    short                   whichMenu;                  // for stepping through menus
    MenuHandle              menu;                       // for reading in menu IDs
    WindowDataPtr           pData = GetWindowInfo(pWindow);
    
    // Step through all of the menus 
    for (whichMenu = mApple; whichMenu <= mLastMenu; whichMenu++)
        {
        // Save the old state of the menu title 
        menu = GetMenuHandle(whichMenu);
        if (menu)                               // because contents menu may not be around
            {
            if (forceTitlesOn)              
                wasEnabled[mLastMenu - whichMenu] = false;
            else
                wasEnabled[mLastMenu - whichMenu] = (((**menu).enableFlags && 1) == 1);
            
            // Disable the entire menu 
            (**menu).enableFlags = 0;       
            }
        }
    
    // select all, unless someone else changes it
    ChangeCommandName(cSelectAll, kMiscStrings, iSelectAllCommand);
 
    // if we have NO windows, or the current window is one we understand
    if ((pWindow == nil) || (pData))
        {
        // enable the default commands
        EnableCommand(cAbout);
        EnableCommand(cDeskAccessory);
        
        EnableCommand(cNew);
        EnableCommand(cOpen);
        EnableCommand(cQuit);
    
        EnableCommand(cShowClipboard);
        }
    else
        {
        // it's printing or a dialog, so enable cut/copy/paste
        if (editDialogs)
            {
            EnableCommand(cCut);
            EnableCommand(cCopy);
            EnableCommand(cPaste);
            EnableCommand(cClear);
            }
        
        // and desk accs too!       
        EnableCommand(cDeskAccessory);
 
        }
        
    if ( (pWindow) && (pData) )
        {
        // all windows can be closed
        if (FrontWindow())
            EnableCommand(cClose);
 
        // changed documents can be saved, but only if the file is open for write
        if (    (pData->changed) && 
                ((pData->isWritable) || (pData->dataRefNum == -1)) )
            EnableCommand(cSave);
        
        // objects with a print method can be printed and page setup-ed
        if (pData->pPrintPage)
            {
            EnableCommand(cPrint);
            EnableCommand(cPageSetup);
            EnableCommand(cPrintOneCopy);
            }
            
        // let object enable anything else that needs to be enabled
        if (pData->pAdjustMenus)
            (*(pData->pAdjustMenus)) (pWindow, pData);
        }
        
    // Now determine if any of the menus have changed state
    {
    Boolean gotToRedraw = false;
    
    for (whichMenu = mApple; whichMenu <= mLastMenu; ++whichMenu)
        {
        menu = GetMenuHandle(whichMenu);
    
        if (menu)       // because contents menu may not be around
            {
            // If any of the menu is enabled 
            if ((**menu).enableFlags != 0)
                {
                // Make sure to turn on the menu title 
                (**menu).enableFlags |= 1;
                }
                
            /*  If this new state is different than the saved state, then the menu bar
                will need to be redrawn */
            if (wasEnabled[mLastMenu - whichMenu] != ((**menu).enableFlags && 1))
                {
                gotToRedraw = true;
                }
            }
        }
        
    // And if any titles have changed state, redraw them 
    if (gotToRedraw)
        DrawMenuBar();
    }
        
} // AdjustMenus
 
// --------------------------------------------------------------------------------------------------------------
// FILE UTILITY ROUTINES
// --------------------------------------------------------------------------------------------------------------
#pragma segment Main
 
static Boolean BringToFrontIfOpen(FSSpecPtr pSpec)
{
    WindowRef       pWindow;
    
    pWindow = FrontWindow();
    while (pWindow)
        {
        WindowDataPtr pData = GetWindowInfo(pWindow);
        
        if (
            (pData) &&
            (pData->fileSpec.vRefNum == pSpec->vRefNum) &&
            (pData->fileSpec.parID == pSpec->parID) &&
            EqualString(pData->fileSpec.name, pSpec->name, false, false)
            )
            {
            SelectWindow(pWindow);
            return true;
            }
            
        pWindow = GetNextWindow(pWindow);
        }
        
    return false;
    
} // BringToFrontIfOpen
 
// --------------------------------------------------------------------------------------------------------------
#pragma segment Main
 
static Boolean BringToFrontIfExists(ResType windowKind)
{
    WindowRef       pWindow;
    
    pWindow = FrontWindow();
    while (pWindow)
        {
        WindowDataPtr pData = GetWindowInfo(pWindow);
        
        if ((pData) && (pData->windowKind == windowKind))
            {
            SelectWindow(pWindow);
            return true;
            }
            
        pWindow = GetNextWindow(pWindow);
        }
        
    return false;
    
} // BringToFrontIfExists
 
// --------------------------------------------------------------------------------------------------------------
// MAIN SIMPLETEXT ROUTINES
// --------------------------------------------------------------------------------------------------------------
#pragma segment Main
 
static OSErr MakeNewWindow(ResType windowKind, FSSpecPtr fileSpec, OSType fileType, Boolean *pWasAlreadyOpen)
{
    OSErr               anErr = fnfErr;
    PreflightRecord     thePreflight;
    PreflightWindowProc pPreflight = nil;
    WindowRef           pWindow;
    WindowDataPtr       pData;
    
    // require a certain amount of RAM free before we allow the new window to be created
    if (FreeMem() < kRAMNeededForNew)
        anErr = memFullErr;
        
    // <50> if we already have a document open from this file, bring the window to the
    // front and return with no error
    if ( (fileSpec) && (fileType != 'sEXT') && (BringToFrontIfOpen(fileSpec)) )
        {
        if (pWasAlreadyOpen)
            *pWasAlreadyOpen = true;
        anErr = noErr;
        return(anErr);
        }
    if (pWasAlreadyOpen)
        *pWasAlreadyOpen = false;
    if (anErr != fnfErr)
        {
        nrequire(anErr, SanityCheckFailed);
        }
        
    // initialize our behavior
    thePreflight.continueWithOpen   = true;
    thePreflight.resourceID         = kDefaultWindowID;
    thePreflight.wantHScroll        = false;
    thePreflight.wantVScroll        = false;
    thePreflight.storageSize        = sizeof(WindowDataRecord);
    thePreflight.makeProcPtr        = nil;
    thePreflight.openKind           = fsRdPerm;
    thePreflight.needResFork        = false;
    thePreflight.doZoom             = false;
    thePreflight.fileType           = fileType;
    
    switch (windowKind)
        {
        case kAboutWindow:
            pPreflight = AboutPreflightWindow;
            break;
 
        case kPICTWindow:
            pPreflight = PICTPreflightWindow;
            break;
 
        case kMovieWindow:
            pPreflight = MoviePreflightWindow;
            break;
 
        case kClipboardWindow:
            pPreflight = ClipboardPreflightWindow;
            break;
 
        case kTextWindow:
            pPreflight = TextPreflightWindow;
            break;
 
        case kThreeDWindow:
            pPreflight = ThreeDPreflightWindow;
            break;
        }
    
    // preflight the window 
    if (pPreflight)
        anErr = (*pPreflight) (&thePreflight);
    nrequire(anErr, PreflightFailed);
    
    if (thePreflight.continueWithOpen)
        {
        // allocate a place for the window
        pData = (WindowDataPtr)NewPtrClear(thePreflight.storageSize);
        anErr = MemError();
        nrequire(anErr, FailedToAllocateWindow);
        
        // then actually create the window
        if (gMachineInfo.theEnvirons.hasColorQD)
            pWindow = (WindowRef)GetNewCWindow(thePreflight.resourceID, pData, (WindowPtr)-1);
        else
            pWindow = (WindowRef)GetNewWindow(thePreflight.resourceID, pData, (WindowPtr)-1);
        if (!pWindow) anErr = memFullErr;
        nrequire(anErr, NewWindowFailed);
        SetWRefCon(pWindow, (long) pData);
                
        // zoom the rectangle to big size on this monitor 
        // based upon which scroll bars they want
        {
        Rect    rect = GetWindowPort(pWindow)->portRect;
        Rect    bigRect;
        
        if (gMachineInfo.theEnvirons.hasColorQD)
            bigRect = (**GetMainDevice()).gdRect;
        else
            bigRect = qd.screenBits.bounds;
        bigRect.top += GetMBarHeight() * 2;
        bigRect.left += 4;
        bigRect.bottom -= 4;
        bigRect.right -= 65;
        
        SetPort((GrafPtr) GetWindowPort(pWindow));
        LocalToGlobal(&TopLeft(rect));
        LocalToGlobal(&BotRight(rect));
        
        if ( (thePreflight.wantHScroll) || (thePreflight.doZoom) )
            {
            rect.left = bigRect.left;
            rect.right = bigRect.right;
            }
            
        if ( (thePreflight.wantVScroll) || (thePreflight.doZoom) )
            {
            rect.top = bigRect.top;
            rect.bottom = bigRect.bottom;
            }
        
        MoveWindow(pWindow, rect.left, rect.top, false);
        SizeWindow(pWindow, rect.right - rect.left, rect.bottom - rect.top, false);
        }
        
        // fill in the default contents of the window
        pData->windowKind       = windowKind;
        pData->originalFileType = fileType;
        pData->pMakeWindow      = (MakeWindowProc)thePreflight.makeProcPtr;
        pData->resRefNum        = -1;
        pData->dataRefNum       = -1;
        pData->contentRect      = GetWindowPort(pWindow)->portRect;
        
        // make the scroll bars
        {
        Rect    controlRect;
        
        if (thePreflight.wantHScroll)
            {
            pData->contentRect.bottom -= kScrollBarSize;
            controlRect = GetWindowPort(pWindow)->portRect;
            controlRect.top = controlRect.bottom - 16;
            if (thePreflight.wantVScroll)
                controlRect.right -= kGrowScrollAdjust;
            OffsetRect(&controlRect, -1, 1);
            pData->hScroll = NewControl(pWindow, &controlRect, "\p", true, 0, 0, 0, scrollBarProc, 0);
            }
        if (thePreflight.wantVScroll)
            {
            pData->contentRect.right -= kScrollBarSize;
            controlRect = GetWindowPort(pWindow)->portRect;
            controlRect.left = controlRect.right - 16;
            if (thePreflight.wantVScroll)
                controlRect.bottom -= kGrowScrollAdjust;
            OffsetRect(&controlRect, 1, -1);
            pData->vScroll = NewControl(pWindow, &controlRect, "\p", true, 0, 0, 0, scrollBarProc, 0);
            }
        }
 
        // got a name?  Open the file       
        if (fileSpec)
            {
            anErr = FSpOpenDF(fileSpec, thePreflight.openKind, &pData->dataRefNum);
            if  ( 
                    ((anErr == afpAccessDenied) || (anErr == opWrErr) || (anErr == permErr) ) && 
                    (thePreflight.openKind != fsRdPerm)
                )
                {
                thePreflight.openKind = fsRdPerm;
                pData->isWritable = false;
                anErr = FSpOpenDF(fileSpec, thePreflight.openKind, &pData->dataRefNum);
                }
            else
                pData->isWritable = true;
            nrequire(anErr, FailedToOpenFile);
 
            // okay not to find a resource fork, because some don't have them               
            pData->resRefNum = FSpOpenResFile(fileSpec, thePreflight.openKind);
            
            // save the file spec in case someone is interested
            pData->fileSpec = *fileSpec;
            }
            
        if (pData->pMakeWindow)
            {
            Rect oldContent = pData->contentRect;
            anErr = (*(pData->pMakeWindow)) (pWindow, pData);
            if (!EqualRect(&oldContent, &pData->contentRect))
                {
                SizeWindow(pWindow, 
                        pData->contentRect.right  + (pData->vScroll != 0) * kScrollBarSize,
                        pData->contentRect.bottom + (pData->hScroll != 0) * kScrollBarSize,
                        false);
                }
            }
        nrequire(anErr, FailedMakeWindow);
 
        // got a name?  Use it as the window title
        if ( (fileSpec) && (!pData->openAsNew) )
            SetWTitle(pWindow, fileSpec->name);
        else
            {
            if ((gMachineInfo.documentCount == 1) && (pData->windowKind == kTextWindow))
                {
                Str255 tempString;
        
                GetIndString(tempString, kMiscStrings, iFirstNewDocumentTitle); // get the "untitled" string (no number)
                SetWTitle(pWindow, tempString);
                }
            else
                {
                Str255  tempString;
                Str32   numString;
    
                GetWTitle(pWindow, tempString);
                NumToString(gMachineInfo.documentCount, numString);
                (void) ZeroStringSub(tempString, numString);
                SetWTitle(pWindow, tempString);
                }
 
            if (pData->bumpUntitledCount)
                gMachineInfo.documentCount++;   // bump count if appropriate for this kind of document
            }
 
        // Make sure the scroll bars are reasonable in size, and move if they must
        AdjustScrollBars(pWindow, true, true, nil);
 
        // finally, if all goes well, we can see the window itself!
        ShowWindow(pWindow);
        }
 
    return noErr;
 
// EXCEPTION HANDLING
FailedMakeWindow:
    if (pData->resRefNum != -1)
        CloseResFile(pData->resRefNum);
    if (pData->dataRefNum != -1)
        FSClose(pData->dataRefNum);
    
FailedToOpenFile:
    CloseWindow(pWindow);
    
NewWindowFailed:
    DisposePtr((Ptr)pData);
    
FailedToAllocateWindow:
PreflightFailed:
SanityCheckFailed:
    return anErr;
    
} // MakeNewWindow
 
// --------------------------------------------------------------------------------------------------------------
#pragma segment Main
 
#define dontSaveChanges 3
 
#define kVisualDelay 8
 
static pascal Boolean SaveChangesFilter(DialogRef theDialog, EventRecord *theEvent, short *itemHit)
{
    if (StdFilterProc(theDialog, theEvent, itemHit))
        return true;
 
    if (theEvent->what == updateEvt)
        {
        HandleEvent(theEvent);
        }
 
    if (theEvent->what == keyDown)
        {
        StringPtr keyEquivs = *GetString(kSaveChangesWindowID);
        unsigned char theKey = theEvent->message & charCodeMask;
 
        if (keyEquivs && (theKey == keyEquivs[1] || theKey == keyEquivs[2]))
            {
            short itemType;
            Rect theRect;
            ControlRef theControl;
            unsigned long finalTicks;
 
            GetDialogItem(theDialog, dontSaveChanges, &itemType, (Handle *) &theControl, &theRect);
            HiliteControl(theControl, kControlButtonPart);
            Delay(kVisualDelay, &finalTicks);
            HiliteControl(theControl, 0);
 
            *itemHit = dontSaveChanges;
            return true;
            }
        }
 
    return false;
}
 
 
static OSErr DoCloseWindow(WindowRef pWindow)
{
    OSErr           anErr = noErr;
    WindowDataPtr   pData = GetWindowInfo(pWindow);
    
    if ( (pData) && (pData->changed) )
        {
        short       hit;
        Str255      wTitle;
        DialogRef   dPtr;
        
        GetWTitle(pWindow, wTitle);
        SetCursor(&qd.arrow);
        ParamText(wTitle, "\p", "\p", "\p");
        
        hit = cancel;
        dPtr = GetNewDialog(kSaveChangesWindowID, nil, (WindowRef)-1);
        if (dPtr)
            {
            SetDialogDefaultItem(dPtr, ok);
            SetDialogCancelItem (dPtr, cancel);
            BeginMovableModal();
            do
                {
                MovableModalDialog(SaveChangesFilter, &hit);
                } while ((hit != dontSaveChanges) && (hit != ok) && (hit != cancel));
                
            DisposeDialog(dPtr);
            EndMovableModal();
            }
        switch (hit)
            {
            case ok:
                anErr = DoCommand(pWindow, cSave, 0);
                if (anErr == eUserCanceled)     // redundant?
                    gAllDone = false;
                break;
                
            case cancel:
                anErr = eUserCanceled;
                gAllDone = false;
                break;
                
            case dontSaveChanges:
                // don't save, so just close it
                break;
            }
        }
 
    if (anErr == noErr)
        {
        if ( (pData) && (pData->pCloseWindow) )
            {
            // let the object close the window if it wishes to
            anErr = (*(pData->pCloseWindow)) (pWindow, pData);
            }
 
        // otherwise we close it the default way
        if (anErr == noErr)
            {
            if (pData)
                {
                CloseWindow(pWindow);
    
                if (pData->hPrint)
                    {
                    DisposeHandle((Handle) pData->hPrint);
                    }
                    
                if (pData->resRefNum != -1)
                    CloseResFile(pData->resRefNum);
                if (pData->dataRefNum != -1)
                    FSClose(pData->dataRefNum);
                DisposePtr((Ptr) pData);
                }
            }
        }
 
    // If we closed the last window, clean up
    if (FrontWindow() == nil)
        {
        AdjustMenus(nil, true, false);
        gMachineInfo.documentCount = 1;     // back to "untitled"
        }
    
    return anErr;
    
} // DoCloseWindow
 
#undef dontSaveChanges
 
// --------------------------------------------------------------------------------------------------------------
#pragma segment Main
 
static OSErr    DetermineWindowTypeOrOpen(
    FSSpecPtr theSpec, OSType theType,              // optional input params -- file to open
    OSType *returnedTypeList, short * pNumTypes,    // optional input params -- returns list of files
    Boolean *pWasAlreadyOpen)                       // optional input params -- was file already open
{
    OSErr       anErr = noErr;
    OSType      typeList[20];
    OSType      docList[20];
    short       numTypes;
 
    // use local copies if the input params are nil 
    if (returnedTypeList == nil)
        returnedTypeList = &typeList[0];
    if (pNumTypes == nil)
        pNumTypes = &numTypes;
    *pNumTypes = 0;
    
    // Load up all of the file types we know how to handle
    AboutGetFileTypes(returnedTypeList, docList, pNumTypes);
    PICTGetFileTypes(returnedTypeList, docList, pNumTypes);
    MovieGetFileTypes(returnedTypeList, docList, pNumTypes);
    ClipboardGetFileTypes(returnedTypeList, docList, pNumTypes);
    TextGetFileTypes(returnedTypeList, docList, pNumTypes);
    ThreeDGetFileTypes(returnedTypeList, docList, pNumTypes);
 
    if (theSpec != nil)
        {
        short       index;
        OSType      windowType = '????';
 
        for (index = 0; index < (*pNumTypes); ++index)
            if (theType == returnedTypeList[index])
                windowType = docList[index];
                
        if (windowType != '????')
            {
            
            if ( (theType == 'TEXT') || (theType == 'sEXT') )
                {
                FInfo   theInfo;
                
                FSpGetFInfo(theSpec, &theInfo);
                if ((theInfo.fdFlags & kIsStationery) != 0)
                    theType = 'sEXT';
                else
                    theType = 'TEXT';
                }
                
            anErr = MakeNewWindow(windowType, theSpec, theType, pWasAlreadyOpen);
            }
        else
            anErr = eDocumentWrongKind;
        }
        
        
    return anErr;
    
} // DetermineWindowTypeOrOpen
 
// --------------------------------------------------------------------------------------------------------------
#pragma segment Main
 
// Handle update/activate events behind Standard File
static pascal Boolean OpenDialogFilter(DialogRef theDialog, EventRecord *theEvent,
                                      short *itemHit, void *myDataPtr)
{
    #pragma unused(myDataPtr)
 
    // 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->message != (long)theDialog) )
        HandleEvent(theEvent);
 
    if (StdFilterProc(theDialog, theEvent, itemHit))
        return(true);
 
    return(false);
 
} // OpenDialogFilter
 
#if GENERATINGCFM
    static RoutineDescriptor gOpenDialogFilterRD = BUILD_ROUTINE_DESCRIPTOR(uppModalFilterYDProcInfo, OpenDialogFilter);
    static ModalFilterYDUPP gOpenDialogFilter = &gOpenDialogFilterRD;
#else
    static ModalFilterYDUPP gOpenDialogFilter = NewModalFilterYDProc(OpenDialogFilter);
#endif
 
 
static OSErr DoOpenWindow(void)
{
    OSErr               anErr = noErr;
    short               numTypes;
    OSType              typeList[20];
    StandardFileReply   sfReply;
    Point thePoint = { -1, -1 };
 
    DetermineWindowTypeOrOpen(nil, '????', &typeList[0], &numTypes, nil);
    
    if (gMachineInfo.haveQuickTime)
        {
        CustomGetFilePreview(nil, numTypes, typeList, &sfReply, 0, thePoint, nil, gOpenDialogFilter, nil, nil, nil);
        }
    else
        {
        CustomGetFile(nil, numTypes, typeList, &sfReply, 0, thePoint, nil, gOpenDialogFilter, nil, nil, nil);
        }
    
    if (sfReply.sfGood)
        {
        SetWatchCursor();
        
        anErr = DetermineWindowTypeOrOpen(&sfReply.sfFile, sfReply.sfType, &typeList[0], &numTypes, nil);
 
        SetCursor(&qd.arrow);
        }
        
    return anErr;
    
} // DoOpenWindow
 
// --------------------------------------------------------------------------------------------------------------
#pragma segment Main
 
static OSErr DoUpdateWindow(WindowRef pWindow)
{
    OSErr           anErr = noErr;
    WindowDataPtr   pData = GetWindowInfo(pWindow);
    GrafPtr         curPort;
    
    // only handle updates for windows we know about
    if (pData)
        {
        GetPort(&curPort);
        SetPort((GrafPtr)GetWindowPort(pWindow));
        BeginUpdate(pWindow);
                    
        if (pData->pUpdateWindow)
            anErr = (*(pData->pUpdateWindow)) (pWindow, pData);
    
        EndUpdate(pWindow);
        SetPort(curPort);
        }
    
    return anErr;
    
} // DoUpdateWindow
 
// --------------------------------------------------------------------------------------------------------------
#pragma segment Main
 
OSErr DoScrollContent(WindowRef pWindow, WindowDataPtr pData, short deltaH, short deltaV)
{
    OSErr   anErr = noErr;
    
    if ((deltaH) || (deltaV))
        {
        // if we have a balloon displayed, update before scrolling anything
        if (HMIsBalloon())
            DoUpdateWindow(pWindow);
        
        if ((pData) && (pData->pScrollContent))
            anErr = (*(pData->pScrollContent)) (pWindow, pData, deltaH, deltaV);
            
        if (anErr == noErr)
            {
            RgnHandle   invalidRgn = NewRgn();
            
            ScrollRect(&pData->contentRect, deltaH, deltaV, invalidRgn);
            InvalRgn(invalidRgn);
            DisposeRgn(invalidRgn);
    
            DoUpdateWindow(pWindow);
            }
        }
    
    return anErr;
    
} // DoScrollContent
 
// --------------------------------------------------------------------------------------------------------------
// BEGIN SCROLL ACTION PROCS
// --------------------------------------------------------------------------------------------------------------
#pragma segment Main
 
void SetControlAndClipAmount(ControlRef control, short * amount)
{
    short       value, max;
    
    value = GetControlValue(control);   /* get current value */
    max = GetControlMaximum(control);       /* and maximum value */
    *amount = value - *amount;
    if ( *amount < 0 )
        *amount = 0;
    else
        {
        if ( *amount > max )
            *amount = max;
        }
    SetControlValue(control, *amount);
    *amount = value - *amount;      /* calculate the real change */
    
} // SetControlAndClipAmount
 
// --------------------------------------------------------------------------------------------------------------
static pascal void VActionProc(ControlRef control, short part)
{
    if (part != 0)
        {
        WindowRef       pWindow = (**control).contrlOwner;
        WindowDataPtr   pData = GetWindowInfo(pWindow);
        short           amount = 0;
        
        switch (part)
            {
            case kControlUpButtonPart:
                amount = pData->vScrollAmount;
                break;
                
            case kControlDownButtonPart:
                amount = -pData->vScrollAmount;
                break;
                
            // vertical page scrolling should be a multiple of the incremental scrolling -- so that
            // we avoid half-lines of text at the bottom of pages.
            
            // More generically, if there was a method for dealing with text scrolling by a non-constant
            // amount, this would be better -- but SimpleText currently doesn't have a framework to allow
            // the document object to override the scroll amount dynamically.  Maybe something to add in
            // the future.
            case kControlPageUpPart:
                amount = (((pData->contentRect.bottom - pData->contentRect.top) / pData->vScrollAmount)-1) * pData->vScrollAmount;
                if (amount == 0)
                    amount = pData->contentRect.bottom - pData->contentRect.top;
                break;
 
            case kControlPageDownPart:
                amount = (((pData->contentRect.top - pData->contentRect.bottom) / pData->vScrollAmount)+1) * pData->vScrollAmount;
                if (amount == 0)
                    amount = pData->contentRect.top - pData->contentRect.bottom;
                break;
            }
        
        SetControlAndClipAmount(control, &amount);
        if (amount != 0)
            DoScrollContent(pWindow, pData, 0, amount);
        }
        
} // VActionProc
 
#if GENERATINGCFM
    static RoutineDescriptor gVActionProcRD = BUILD_ROUTINE_DESCRIPTOR(uppControlActionProcInfo, VActionProc);
    static ControlActionUPP gVActionProc = &gVActionProcRD;
#else
    static ControlActionUPP gVActionProc = VActionProc;
#endif
 
// --------------------------------------------------------------------------------------------------------------
static pascal void HActionProc(ControlRef control, short part)
{
    if (part != 0)
        {
        WindowRef       pWindow = (**control).contrlOwner;
        WindowDataPtr   pData = GetWindowInfo(pWindow);
        short           amount = 0;
        
        switch (part)
            {
            case kControlUpButtonPart:
                amount = pData->hScrollAmount;
                break;
                
            case kControlDownButtonPart:
                amount = -pData->hScrollAmount;
                break;
                
            case kControlPageUpPart:
                amount = pData->contentRect.right - pData->contentRect.left;
                break;
 
            case kControlPageDownPart:
                amount = pData->contentRect.left - pData->contentRect.right;
                break;
            }
        
        SetControlAndClipAmount(control, &amount);
        if (amount != 0)
            DoScrollContent(pWindow, pData, amount, 0);
        }
        
} // HActionProc
 
#if GENERATINGCFM
    static RoutineDescriptor gHActionProcRD = BUILD_ROUTINE_DESCRIPTOR(uppControlActionProcInfo, HActionProc);
    static ControlActionUPP gHActionProc = &gHActionProcRD;
#else
    static ControlActionUPP gHActionProc = HActionProc;
#endif
 
// --------------------------------------------------------------------------------------------------------------
// END SCROLL ACTION PROCS
// --------------------------------------------------------------------------------------------------------------
 
#pragma segment Main
 
static OSErr DoContentClick(WindowRef pWindow)
{
    OSErr           anErr = noErr;
    WindowDataPtr   pData = GetWindowInfo(pWindow);
    
    
    if ( pData )
        {
        SetPort((GrafPtr) GetWindowPort(pWindow));
        
        if (pData->pContentClick)
            {
            // let the object handle the click if it wishes to
            anErr = (*(pData->pContentClick)) (pWindow, pData, &gEvent);
            }
        
        if (anErr == noErr) 
            {
            ControlRef      theControl;
            short           part;
            
            GlobalToLocal(&gEvent.where);
            part = FindControl(gEvent.where, pWindow, &theControl);
            switch (part)
                {
                // do nothing for viewRect case
                case 0:
                    break;
 
                // track the thumb, and then update all at once
                case kControlIndicatorPart:
                    {
                    short   value = GetControlValue(theControl);
                    
                    part = TrackControl(theControl, gEvent.where, nil);
                    if (part != 0)
                        {
                        // turn the value into a delta
                        value -= GetControlValue(theControl);
                        
                        // if we actually moved
                        if (value != 0)
                            {
                            if (theControl == pData->hScroll)
                                DoScrollContent(pWindow, pData, value, 0);
                            if (theControl == pData->vScroll)
                                DoScrollContent(pWindow, pData, 0, value);
                                
                            }
                        }
                    }
                    break;
                    
                // track the control, and scroll as we go
                default:
                    if (theControl)
                        {
                        if (theControl == pData->hScroll)
                            part = TrackControl(theControl, gEvent.where, gHActionProc);
                        if (theControl == pData->vScroll)
                            part = TrackControl(theControl, gEvent.where, gVActionProc);
                        }
                    break;
                }
            }
 
        }
 
        
    return anErr;
    
} // DoContentClick
 
// --------------------------------------------------------------------------------------------------------------
#pragma segment Main
 
static OSErr DoGrowWindow(WindowRef pWindow, EventRecord *pEvent)
{
    OSErr           anErr = noErr;
    WindowDataPtr   pData = GetWindowInfo(pWindow);
    Rect            tempRect;
    LongRect        docRect;
    long            growResult;
    
    if (pData)
        {
        GrafPtr pPort = (GrafPtr)GetWindowPort(pWindow);
        
        SetPort(pPort);
        
        RectToLongRect(&pData->contentRect, &docRect);
        if (pData->pGetDocumentRect)
            (*(pData->pGetDocumentRect)) (pWindow, pData, &docRect, true);
        if (pData->vScroll)
            docRect.right += 16;
        if (pData->hScroll)
            docRect.bottom += 16;
        
        if ( (pData->hasGrow) && (pData->hScroll == nil) && (pData->vScroll == nil) )
            {
            docRect.right += 16;
            docRect.bottom += 16;
            }
            
        // set up resize constraints
        tempRect.left = pData->minHSize;
        if (tempRect.left == 0)
            tempRect.left = kMinDocSize;
        tempRect.right = docRect.right - docRect.left;
        if (tempRect.right < tempRect.left)
            tempRect.right = tempRect.left;
        tempRect.top = pData->minVSize;
        if (tempRect.top == 0)
            tempRect.top = kMinDocSize;
        tempRect.bottom = docRect.bottom - docRect.top;
        if (tempRect.bottom < tempRect.top)
            tempRect.bottom = tempRect.top;
            
        growResult = GrowWindow(pWindow, pEvent->where, &tempRect);
        if ( growResult != 0 ) 
            {
            Rect        oldRect;
            RgnHandle   currentInval = NewRgn();
            Boolean     needInvalidate;
            
            // save old content area
            oldRect = pData->contentRect;
            
            // save old pending update
            GetWindowUpdateRgn(pWindow, currentInval);
            OffsetRgn(currentInval, pPort->portBits.bounds.left, pPort->portBits.bounds.top);
            
            // grow window and recalc what is needed
            SizeWindow(pWindow, growResult & 0xFFFF, growResult >> 16, true);
            AdjustScrollBars(pWindow, true, true, &needInvalidate);
            
            if (needInvalidate)
                {
                InvalRect(&pData->contentRect);
                }
            else
                {
                // don't bother to redraw things that haven't changed
                SectRect(&oldRect, &pData->contentRect, &oldRect);
                ValidRect(&oldRect);
                
                // but, if a pending update was there, be sure to deal with that!
                InvalRgn(currentInval);
                }
 
            // if we have offset scrollbars, then force a redraw of them
            if (pData->hScrollOffset)
                {
                oldRect = GetWindowPort(pWindow)->portRect;
                oldRect.right = oldRect.left + pData->hScrollOffset;
                oldRect.top = oldRect.bottom - kScrollBarSize;
                InvalRect(&oldRect);
                }
            if (pData->vScrollOffset)
                {
                oldRect = GetWindowPort(pWindow)->portRect;
                oldRect.bottom = oldRect.top + pData->vScrollOffset;
                oldRect.left = oldRect.right - kScrollBarSize;
                InvalRect(&oldRect);
                }
 
            DisposeRgn(currentInval);
            }
            
        }
        
    
    return anErr;
    
} // DoGrowWindow
 
// --------------------------------------------------------------------------------------------------------------
#pragma segment Main
 
static OSErr DoZoomWindow(WindowRef pWindow, short zoomDir)
{
    Rect                *windRect, *zoomRect;
    Rect                globalPortRect, theSect, dGDRect;
    GDHandle            nthDevice, dominantGDevice;
    long                sectArea, greatestArea;
    short               hMax, vMax;
    
    // determine the max size of the window
    {
    WindowDataPtr       pData = GetWindowInfo(pWindow);
    LongRect            docRect;
    
    RectToLongRect(&pData->contentRect, &docRect);
    if (pData->pGetDocumentRect)
        (*(pData->pGetDocumentRect)) (pWindow, pData, &docRect, true);
    if (pData->vScroll)
        docRect.right += kScrollBarSize;
    if (pData->hScroll)
        docRect.bottom += kScrollBarSize;
    
    if ( (pData->hasGrow) && (pData->hScroll == nil) && (pData->vScroll == nil) )
        {
        docRect.right += kScrollBarSize;
        docRect.bottom += kScrollBarSize;
        }
 
    hMax = docRect.right - docRect.left;
    vMax = docRect.bottom - docRect.top;
    }
    
    SetPort((GrafPtr) GetWindowPort(pWindow));
    EraseRect(&GetWindowPort(pWindow)->portRect);   // recommended for cosmetic reasons
 
    if (zoomDir == inZoomOut) 
        {
 
        /*
         *  ZoomWindow() is a good basic tool, but it doesn't do everything necessary to
         *  implement a good human interface when zooming. In fact it's not even close for
         *  more high-end hardware configurations. We must help it along by calculating an
         *  appropriate window size and location any time a window zooms out.
         */
        {
        RgnHandle   structRgn = NewRgn();
        
        GetWindowStructureRgn(pWindow, structRgn);
        windRect = &(**structRgn).rgnBBox;
        DisposeRgn(structRgn);
        }
        dominantGDevice = nil;
        if (gMachineInfo.theEnvirons.hasColorQD) 
            {
 
            /*
             *  Color QuickDraw implies the possibility of multiple monitors. This is where
             *  zooming becomes more interesting. One should zoom onto the monitor containing
             *  the greatest portion of the window. This requires walking the gDevice list.
             */
 
            nthDevice = GetDeviceList();
            greatestArea = 0;
            while (nthDevice != nil) 
                {
                if (TestDeviceAttribute(nthDevice, screenDevice)) 
                    {
                    if (TestDeviceAttribute(nthDevice, screenActive)) 
                        {
                        SectRect(windRect, &(**nthDevice).gdRect, &theSect);
                        sectArea = (long) RectWidth(theSect) * (long) RectHeight(theSect);
                        if (sectArea > greatestArea) 
                            {
                            greatestArea = sectArea;        // save the greatest intersection
                            dominantGDevice = nthDevice;    // and which device it belongs to
                            }
                        }
                    }
                nthDevice = GetNextDevice(nthDevice);
                }
            }
 
        /*
         *  At this point, we know the dimensions of the window we're zooming, and we know
         *  what screen we're going to put it on. To be more specific, however, we need a
         *  rectangle which defines the maximum dimensions of the resized window's contents.
         *  This rectangle accounts for the thickness of the window frame, the menu bar, and
         *  one or two pixels around the edges for cosmetic compatibility with ZoomWindow().
         */
 
        if (dominantGDevice != nil) 
            {
            dGDRect = (**dominantGDevice).gdRect;
            if (dominantGDevice == GetMainDevice())     // account for menu bar on main device
                dGDRect.top += GetMBarHeight();
            }
        else 
            {
            dGDRect = qd.screenBits.bounds;             // if no gDevice, use default monitor
            dGDRect.top += GetMBarHeight();
            }
 
        globalPortRect = GetWindowPort(pWindow)->portRect;
        LocalToGlobal(&TopLeft(globalPortRect));        // calculate the window's portRect
        LocalToGlobal(&BotRight(globalPortRect));       // in global coordinates
 
        // account for the window frame and inset it a few pixels
        dGDRect.left    += 2 + globalPortRect.left - windRect->left;
        dGDRect.top     += 2 + globalPortRect.top - windRect->top;
        dGDRect.right   -= 1 + windRect->right - globalPortRect.right;
        dGDRect.bottom  -= 1 + windRect->bottom - globalPortRect.bottom;
 
        /*
         *  Now we know exactly what our limits are, and since there are input parameters
         *  specifying the dimensions we'd like to see, we can move and resize the zoom
         *  state rectangle for the best possible results. We have three goals in this:
         *  1. Display the window entirely visible on a single device.
         *  2. Resize the window to best represent the dimensions of the document itself.
         *  3. Move the window as short a distance as possible to achieve #1 and #2.
         */
 
        zoomRect = &(**(WStateDataHandle) ((WindowPeek) pWindow)->dataHandle).stdState;
 
        /*
         *  Initially set the zoom rectangle to the size requested by the input parameters,
         *  although not smaller than a minimum size. We do this without moving the origin.
         */
 
        zoomRect->right = (zoomRect->left = globalPortRect.left) +
                                Max(hMax, kMinDocSize);
        zoomRect->bottom = (zoomRect->top = globalPortRect.top) +
                                Max(vMax, kMinDocSize);
 
        // Shift the entire rectangle if necessary to bring its origin inside dGDRect.
        OffsetRect(zoomRect,
                    Max(dGDRect.left - zoomRect->left, 0),
                    Max(dGDRect.top - zoomRect->top, 0));
 
        /*
         *  Shift the rectangle up and/or to the left if necessary to accomodate the view,
         *  and if it is possible to do so. The rectangle may not be moved such that its
         *  origin would fall outside of dGDRect.
         */
 
        OffsetRect(zoomRect,
                    -Pin(zoomRect->right - dGDRect.right, 0, zoomRect->left - dGDRect.left),
                    -Pin(zoomRect->bottom - dGDRect.bottom, 0, zoomRect->top - dGDRect.top));
 
        // Clip expansion to dGDRect, in case view is larger than dGDRect.
        zoomRect->right = Min(zoomRect->right, dGDRect.right);
        zoomRect->bottom = Min(zoomRect->bottom, dGDRect.bottom);
        }
 
    ZoomWindow(pWindow, zoomDir, pWindow == FrontWindow());
    
    AdjustScrollBars(pWindow, true, true, nil);
 
    InvalRect(&GetWindowPort(pWindow)->portRect);
    
    return noErr;
    
} // DoZoomWindow
 
// --------------------------------------------------------------------------------------------------------------
#pragma segment Main
 
OSErr DoActivate(WindowRef pWindow, Boolean activating)
{
 
    OSErr           anErr = noErr;
    WindowDataPtr   pData = GetWindowInfo(pWindow);
    
    SetPort((GrafPtr) GetWindowPort(pWindow));
    
    if ( pData )
        {
        if (pData->pActivateEvent)
            anErr = (*(pData->pActivateEvent)) (pWindow, pData, activating);
            
        if (anErr == noErr)
            {
            if (activating)
                {
                // Reshow all controls on activation
                if (pData->hScroll)
                    ShowControl(pData->hScroll);
                if (pData->vScroll)
                    ShowControl(pData->vScroll);
                }
            else
                {
                // Hide all controls on deactivation
                if (pData->hScroll)
                    HideControl(pData->hScroll);
                if (pData->vScroll)
                    HideControl(pData->vScroll);
                }
                
            if (pData->hasGrow) 
                {
                Rect    growIconRect;
                
                CalculateGrowIcon(pData, &growIconRect);
                InvalRect(&growIconRect);
                }
            }
        }
 
    AdjustMenus(pWindow, true, false);
        
    return anErr;
    
} // DoActivate
 
// --------------------------------------------------------------------------------------------------------------
#pragma segment Main
 
OSErr   DoDefault(WindowDataPtr     pData)
{
    OSErr   anErr = noErr;
    
    PrOpen();
    anErr = PrError();
    if (anErr == noErr)
        {
        if (pData->hPrint == nil)
            {
            pData->hPrint = NewHandleClear(sizeof(TPrint));
            anErr = MemError();
            if (anErr == noErr)
                extendPrDefault(pData->hPrint);
            }
            
        }
    PrClose();
        
    return anErr;
    
} // DoDefault
 
// --------------------------------------------------------------------------------------------------------------
#pragma segment Main
 
OSErr   DoPageSetup(WindowRef pWindow)
{
    OSErr           anErr = noErr;
    WindowDataPtr   pData = GetWindowInfo(pWindow);
        
    anErr = DoDefault(pData);
    nrequire(anErr, DoDefault);
    
    PrOpen();
    anErr = PrError();
    if (anErr == noErr)
        {
        SetCursor(&qd.arrow);
        PrStlDialog(pData->hPrint);
        }
    PrClose();
 
// FALL THROUGH EXCEPTION HANDLING
DoDefault:      
    return anErr;
    
} // DoPageSetup
 
// --------------------------------------------------------------------------------------------------------------
#pragma segment Main
 
static OSErr    DoPrintSetup(WindowRef pWindow, StringPtr pPrinterName)
{
    OSErr           anErr = noErr;
    WindowDataPtr   pData = GetWindowInfo(pWindow);
        
    anErr = DoDefault(pData);
    nrequire(anErr, DoDefault);
    
    PrOpen();
    anErr = PrError();
    if (anErr == noErr)
        {
        SetCursor(&qd.arrow);
        if (PrJobDialog(pData->hPrint) == false)
            anErr = eUserCanceled;
        }
        
    PrClose();
        
// FALL THROUGH EXCEPTION HANDLING
DoDefault:      
    return anErr;
    
} // DoPrintSetup
 
// --------------------------------------------------------------------------------------------------------------
#pragma segment Main
 
// This function is a duplicate of DoPrintSetup, but without the DoDefault call
// that DoPrintSetup normally does. The reason for this is that with the addition
// of the scriptable printing, I may be passing a print record into this function
// which I want to feed into the print dialog. If that's the case, the DoDefault
// would wipe out that print record, which is not what I would want to do.
//
// Also, rather than modifying the DoPrintSetup function, I duplicate the function
// here to illustrate the change more clearly. This probably isn't how you would
// handle this sort of conflict (a function needing to change) in the real world,
// but I think this better shows what you need to change.
static OSErr    DoPrintSetupNoDefault(WindowRef pWindow, StringPtr pPrinterName)
{
    OSErr           anErr = noErr;
    WindowDataPtr   pData = GetWindowInfo(pWindow);
 
    PrOpen();
    anErr = PrError();
    if (anErr == noErr)
        {
        SetCursor(&qd.arrow);
        if (PrJobDialog(pData->hPrint) == false)
            anErr = eUserCanceled;
        }
        
    PrClose();
        
    return anErr;
    
} // DoPrintSetupNoDefault
 
// --------------------------------------------------------------------------------------------------------------
#pragma segment Main
 
static OSErr    DoPrint(WindowRef pWindow, void * hPrint, Boolean oneCopy)
{
    OSErr               anErr = noErr;
    WindowDataPtr       pData = GetWindowInfo(pWindow);
    Boolean             didAllocate = false;
    TPPrPort            printingPort;
    
    // use a watch cursor while printing
    SetWatchCursor();
    
    PrOpen();
    anErr = PrError();
    if (anErr == noErr)
        {
        if (hPrint == nil)
            {
            hPrint = NewHandleClear(sizeof(TPrint));
            anErr = MemError();
            if (anErr == noErr)
                {
                extendPrDefault(hPrint);
                didAllocate = true;
                }
            }
        
        if (anErr == noErr)
            {
            short   firstPage, lastPage;
            
            // be sure to get the page range BEFORE calling PrValidate(), 
            // which blows it away for many drivers.
            firstPage = (**(THPrint)hPrint).prJob.iFstPage;
            lastPage = (**(THPrint)hPrint).prJob.iLstPage;
            
            // make sure the print record is cool to use
            extendPrValidate(hPrint);
 
            // then clear out the job itself -- some drivers don't
            // do this from within PrValidate().  We want the job
            // clear in case the driver bases background behavior
            // from this range (and many do).
            (**(THPrint)hPrint).prJob.iFstPage = 1;
            (**(THPrint)hPrint).prJob.iLstPage = 9999;
            
            if (oneCopy)
                (** ((THPrint)hPrint)).prJob.iCopies = 1;
                
            // start printing, and then loop over the pages
            printingPort = PrOpenDoc(hPrint, nil, nil);
            anErr = PrError();
            if (anErr == noErr)
                {
                long    pageIndex;
                Rect    pageRect;
                
                SetPort((GrafPtr) printingPort);
                
                pageRect = (**(THPrint)hPrint).prInfo.rPage;
                if (firstPage < 1)
                    firstPage = 1;
                if (lastPage < firstPage)
                    lastPage = firstPage;
                for (pageIndex = firstPage; pageIndex <= lastPage; ++pageIndex)
                    {
                    PrOpenPage(printingPort, nil);
                    anErr = PrError();
                    if (anErr == noErr)
                        anErr = (*(pData->pPrintPage)) (pWindow, pData, &pageRect, &pageIndex);
                        
                    PrClosePage(printingPort);
                    if (anErr == noErr)
                        anErr = PrError();
                    if ( (anErr != noErr) || (pageIndex == -1) )
                        break;
                    }
                }
                
            // Finish up printing of the document
            PrCloseDoc(printingPort);
            if (anErr == noErr)
                anErr = PrError();
            if ( (anErr == noErr) && ((**(THPrint)hPrint).prJob.bJDocLoop == bSpoolLoop) )
                {
                TPrStatus   theStatus;
                
                PrPicFile(hPrint, nil, nil, nil, &theStatus);
                anErr = PrError();
                }
            }
            
        if (didAllocate)
            DisposeHandle((Handle) hPrint);
            
        }
    PrClose();
    
        
    // restore cursor
    SetCursor(&qd.arrow);
 
    return anErr;
    
} // DoPrint
 
// --------------------------------------------------------------------------------------------------------------
#pragma segment Main
 
OSErr   DoCommand(WindowRef pWindow, short commandID, long menuResult)
{
    OSErr           anErr = noErr;
    WindowDataPtr   pData = nil;
    
    if (pWindow)
        {
        pData = (WindowDataPtr) GetWindowInfo(pWindow);
        
        if ( (pData) && (pData->pCommand) )
            anErr = (*(pData->pCommand)) (pWindow, pData, commandID, menuResult);
        }
    
    if (anErr == noErr)
        {
        // default command handling
        switch (commandID)
            {
            // About box command
            case cAbout:
                if (!BringToFrontIfExists(kAboutWindow))
                    anErr = MakeNewWindow(kAboutWindow, nil, '????', nil);
                break;
                
            case cDeskAccessory:
                {
                Str255  tempString;
                
                GetMenuItemText(GetMenuHandle(menuResult>>16), menuResult & 0xFFFF, tempString);
                OpenDeskAcc(tempString);
                }
                break;
                
            // New window command
            case cNew:
                anErr = MakeNewWindow(kTextWindow, nil, 'TEXT', nil);
                break;
                
            // Open window command
            case cOpen:
                anErr = DoOpenWindow();
                break;
                
            // Close window command
            case cClose:
                anErr = DoCloseWindow(pWindow);
                break;
                
            case cPageSetup:
                anErr = DoPageSetup(pWindow);
                break;
                
            case cPrint:
                anErr = DoPrintSetup(pWindow, nil);
                if (anErr == noErr) {
                    anErr = DoPrint(pWindow, pData->hPrint, false);
                }
                break;
                
            case cPrintOneCopy:
                anErr = DoPrint(pWindow, pData->hPrint, true);
                break;
                
            // get out of here command!
            case cQuit:
                gAllDone = true;
                break;
    
            // show/hide clipboard
            case cShowClipboard:
                if (!BringToFrontIfExists(kClipboardWindow))
                    {
                    anErr = MakeNewWindow(kClipboardWindow, nil, '????', nil);
                    }
                else
                    {
                    pWindow = FrontWindow();
                    anErr = DoCloseWindow(pWindow);
                    }
                break;
                
            case cNextPage:
                gEvent.what = keyDown;
                gEvent.message = kPageDown << 8;
                gEvent.modifiers = 0;
                DoKeyEvent(pWindow, &gEvent, false);
                break;
                
            case cPreviousPage:
                gEvent.what = keyDown;
                gEvent.message = kPageUp << 8;
                gEvent.modifiers = 0;
                DoKeyEvent(pWindow, &gEvent, false);
                break;
                
            // Do nothing command
            case cNull:
                break;
                            
            default:
                break;
            }
        }
        
    // don't report cancels
    if (anErr == iPrAbort)
        anErr = noErr;
        
    if ( (anErr != noErr) && (anErr != eActionAlreadyHandled) && (anErr != eUserCanceled) )
        {
        // some commands are so similar to other commands that we map their IDs
        // for the purposes of the error strings
        if (commandID == cSaveAs)
            commandID = cSave;
        if (commandID == cPrintOneCopy)
            commandID = cPrint;
            
        ConductErrorDialog(anErr, commandID, cancel);
        }
        
    // in any case, unhilite the menu selected after command processing is done 
    HiliteMenu(0);
    
    return anErr;
    
} // DoCommand
 
// --------------------------------------------------------------------------------------------------------------
#pragma segment Main
 
static OSErr    DoMenuCommand(WindowRef pWindow, long menuResult)
{
    OSErr   anErr = noErr;
    short   commandID = cNull;
    short   ** commandHandle;
    short   menuID = menuResult >> 16;
    
    if (menuID >= mFontSubMenusStart)
        {
        commandID = cSelectFontStyle;
        }
    else
        {
        // read in the resource that controls this menu
            {
            short   oldResFile = CurResFile();
            
            UseResFile(gApplicationResFile);
            commandHandle = (short**) Get1Resource('MCMD', menuID);
            UseResFile(oldResFile);
            anErr = ResError();
            nrequire(anErr, FailedToLoadCommandTable);
            }
        
        if (commandHandle)
            {
            short   item = menuResult & 0xFFFF;
            short   * pCommands = *commandHandle;
            
            if (item <= pCommands[0])
                commandID = pCommands[item];
            else
                commandID = pCommands[pCommands[0]];
            }
        }
    
    anErr = DoCommand(pWindow, commandID, menuResult);
    
// FALL THROUGH EXCEPTION HANDLING
FailedToLoadCommandTable:
 
    return anErr;
    
} // DoMenuCommand
 
 
// --------------------------------------------------------------------------------------------------------------
#pragma segment Main
 
static void DoKeyPageDown(WindowRef pWindow, WindowDataPtr pData, Boolean processPageControls)
{
 
    if (GetControlValue(pData->vScroll) < GetControlMaximum(pData->vScroll))
        VActionProc(pData->vScroll, kControlPageDownPart);
    else
        {
        if ( (processPageControls) && (IsCommandEnabled(cNextPage)) )
            {
            short amount;
 
            if (DoCommand(pWindow, cNextPage, 0) == eActionAlreadyHandled)
                {
                amount = GetControlValue(pData->vScroll);
                SetControlAndClipAmount(pData->vScroll, &amount);
                if (amount != 0)
                    DoScrollContent(pWindow, pData, 0, amount);
                }
            
            AdjustMenus(pWindow, true, false);
            }
        }
    
} // DoKeyPageDown
 
// --------------------------------------------------------------------------------------------------------------
#pragma segment Main
 
static void DoKeyPageUp(WindowRef pWindow, WindowDataPtr pData, Boolean processPageControls)
{
    if (GetControlValue(pData->vScroll) > GetControlMinimum(pData->vScroll))
        VActionProc(pData->vScroll, kControlPageUpPart);
    else
        {
        if ( (processPageControls) && (IsCommandEnabled(cPreviousPage)) )
            {
            short amount;
            
            if (DoCommand(pWindow, cPreviousPage, 0) == eActionAlreadyHandled)
                {
                amount = -(GetControlMaximum(pData->vScroll)-GetControlValue(pData->vScroll));
                SetControlAndClipAmount(pData->vScroll, &amount);
                if (amount != 0)
                    DoScrollContent(pWindow, pData, 0, amount);
                }
            
            AdjustMenus(pWindow, true, false);
            }
        }
        
} // DoKeyPageUp
 
// --------------------------------------------------------------------------------------------------------------
#pragma segment Main
 
OSErr   DoKeyEvent(WindowRef pWindow, EventRecord * pEvent, Boolean processPageControls)
{
    OSErr           anErr = noErr;
    WindowDataPtr   pData = nil;
    Boolean         passToObject = false;
    Boolean         isMotionKey = false;
    long            menuResult = 0;
    
    char keyCode = (pEvent->message >> 8) & charCodeMask;
 
    if ( pEvent->modifiers & cmdKey )           /* Command key down */
        {
        AdjustMenus(pWindow, true, false);
        menuResult = MenuKey(pEvent->message & charCodeMask);
        DoMenuCommand(pWindow, menuResult);
        pWindow = FrontWindow();
        }
 
    if (menuResult == 0)
        {
        if (pWindow)
            {
            pData = (WindowDataPtr)GetWindowInfo(pWindow);
            if ( (pData) && (pData->pKeyEvent) )
                passToObject = true;
            SetPort((GrafPtr) GetWindowPort(pWindow));
            }
            
        if (pData)
            {
            switch (keyCode)
                {
                case kHome: // top of file
                    isMotionKey = true;
                    if (pData->vScroll)
                        {
                        short amount;
                        
                        if ( (processPageControls) && (IsCommandEnabled(cGotoPage)) )
                            DoCommand(pWindow, cGotoPage, cGotoFirst);
 
                        amount = GetControlValue(pData->vScroll);
                        SetControlAndClipAmount(pData->vScroll, &amount);
                        if (amount != 0)
                            DoScrollContent(pWindow, pData, 0, amount);
                        passToObject = false;
                        }
                    break;
                    
                case kEnd: // end of file
                    isMotionKey = true;
                    if (pData->vScroll)
                        {
                        short amount;
 
                        if ( (processPageControls) && (IsCommandEnabled(cGotoPage)) )
                            DoCommand(pWindow, cGotoPage, cGotoLast);
                            
                        amount = -(GetControlMaximum(pData->vScroll)-GetControlValue(pData->vScroll));
                        SetControlAndClipAmount(pData->vScroll, &amount);
                        if (amount != 0)
                            DoScrollContent(pWindow, pData, 0, amount);
                        passToObject = false;
                        }
                    break;
                    
                case kPageUp: // scroll bar page up
                    isMotionKey = true;
                    if (pData->vScroll)
                        {
                        DoKeyPageUp(pWindow, pData, processPageControls);
                        passToObject = false;
                        }
                    break;
                    
                case kPageDown: // scroll bar page down
                    isMotionKey = true;
                    if (pData->vScroll)
                        {
                        DoKeyPageDown(pWindow, pData, processPageControls);
                        passToObject = false;
                        }
                    break;
                            
                case kUpArrow:      // scroll bar up arrow
                    isMotionKey = true;
                    if ( (pData->vScroll) && (!pData->pKeyEvent) )
                        {
                        if ( pEvent->modifiers & cmdKey )           /* Command key down */
                            DoKeyPageUp(pWindow, pData, processPageControls);
                        else
                            VActionProc(pData->vScroll, kControlUpButtonPart);
                        passToObject = false;
                        }
                    break;
                    
                case kDownArrow:    // scroll bar down arrow
                    isMotionKey = true;
                    if ( (pData->vScroll) && (!pData->pKeyEvent) )
                        {
                        if ( pEvent->modifiers & cmdKey )           /* Command key down */
                            DoKeyPageDown(pWindow, pData, processPageControls);
                        else
                            VActionProc(pData->vScroll, kControlDownButtonPart);
                        passToObject = false;
                        }
                    break;
        
                case kLeftArrow:    // scroll bar left arrow
                    isMotionKey = true;
                    if ( (pData->hScroll) && (!pData->pKeyEvent) )
                        {
                        if ( pEvent->modifiers & cmdKey )           /* Command key down */
                            HActionProc(pData->hScroll, kControlPageUpPart);
                        else
                            HActionProc(pData->hScroll, kControlUpButtonPart);
                        passToObject = false;
                        }
                    break;
                    
                case kRightArrow:   // scroll bar right arrow
                    isMotionKey = true;
                    if ( (pData->hScroll) && (!pData->pKeyEvent) )
                        {
                        if ( pEvent->modifiers & cmdKey )           /* Command key down */
                            HActionProc(pData->hScroll, kControlPageDownPart);
                        else
                            HActionProc(pData->hScroll, kControlDownButtonPart);
                        passToObject = false;
                        }
                    break;
                    }
        
            if (passToObject)
                anErr = (*(pData->pKeyEvent)) (pWindow, pData, pEvent, isMotionKey);
            else
                {
                if ( (pData->documentAcceptsText == false) && !( pEvent->modifiers & cmdKey ) && !(isMotionKey) )
                    anErr = eDocumentNotModifiable;
                }
            }
 
        if ( (anErr != noErr) && (anErr != eActionAlreadyHandled) )
            ConductErrorDialog(anErr, cTypingCommand, ok);
            
        } // (menuResult == 0)
    
        
    return anErr;
    
} // DoKeyEvent
 
// --------------------------------------------------------------------------------------------------------------
#pragma segment Main
 
static OSErr DoAdjustCursor(WindowRef pWindow)
{
    OSErr       anErr = noErr;
    Point       mouse;
    Boolean     didAdjust = false;
    
    if (pWindow)
        {
        // not one of our windows?  don't do anything
        if (GetWindowKind(pWindow) != userKind)
            didAdjust = true;
            
        SetPort((GrafPtr) GetWindowPort(pWindow));
        
        if ( (!didAdjust) && (gMachineInfo.haveTSM) )
            {
            GetMouse(&mouse);
            LocalToGlobal(&mouse);
            if (SetTSMCursor(mouse))
                didAdjust = true;
            }
            
        if (!didAdjust)
            {
            WindowDataPtr   pData = GetWindowInfo(pWindow);
            RgnHandle       content = NewRgn();
            Point           globalMouse;
            
            GetMouse(&mouse);
            globalMouse = mouse;
            LocalToGlobal(&globalMouse);
            
            GetWindowContentRgn(pWindow, content);
            if ((pData) && (PtInRgn(globalMouse, content)) && (PtInRect(mouse, &pData->contentRect)))
                {
                Rect            tempRect;
                
                tempRect = pData->contentRect;
                LocalToGlobal(&TopLeft(tempRect));
                LocalToGlobal(&BotRight(tempRect));
                
                if (pData->pAdjustCursor)
                    anErr = (*(pData->pAdjustCursor)) (pWindow, pData, &mouse, &tempRect);
    
                RectRgn(gCursorRgn, &tempRect);
                }
            DisposeRgn(content);
            }
        else
            anErr = eActionAlreadyHandled;
        }
    
    // nobody set the cursor, we do it ourselves
    if (anErr != eActionAlreadyHandled)
        SetCursor(&qd.arrow);
        
    return anErr;
    
} // DoAdjustCursor
 
// --------------------------------------------------------------------------------------------------------------
#pragma segment Main
 
static long DetermineWaitTime(WindowRef pWindow)
{
    long    waitTime = kMaxWaitTime;
    
    while (pWindow)
        {
        long            newWaitTime;
        WindowDataPtr   pData = GetWindowInfo(pWindow);
        
        if ((pData) && (pData->pCalculateIdleTime))
            newWaitTime = (*(pData->pCalculateIdleTime)) (pWindow, pData);
        else
            newWaitTime = kMaxWaitTime;
        
        if (newWaitTime < waitTime)
            waitTime = newWaitTime;
            
        pWindow = GetNextWindow(pWindow);
        }
    
    return(waitTime);
    
} // DetermineWaitTime
 
// --------------------------------------------------------------------------------------------------------------
#pragma segment Main
 
void HandleEvent(EventRecord * pEvent)
{
    WindowRef pWindow = FrontWindow();
    
    switch (pEvent->what)
        {
        case kHighLevelEvent:
            AEProcessAppleEvent(pEvent);
            break;
            
        case osEvt:
            switch ((pEvent->message >> 24) & 0xFF) /* high byte of message */
                {       
                case mouseMovedMessage:
                    DoAdjustCursor(pWindow);
                    break;
                    
                case suspendResumeMessage:      /* suspend/resume is also an activate/deactivate */
                    gMachineInfo.amInBackground = (pEvent->message & 1) == 0;
                    if (pWindow)
                        DoActivate(pWindow, !gMachineInfo.amInBackground);
                        
                    break;
                }
            break;
            
        case activateEvt:
            pWindow = (WindowRef) pEvent->message;
            DoActivate(pWindow, (pEvent->modifiers & activeFlag) != 0);
            break;
                            
        // disk inserted events must be handled, or uninitialized floppies 
        // won't be recognized.
        case diskEvt:
            if ( HiWord(pEvent->message) != noErr ) 
                {
                Point   where;
            
                SetPt(&where, 70, 50);
                ShowCursor();
                (void) DIBadMount(where, pEvent->message);
                }       
            break;
                
        case mouseUp:
            break;
            
        case mouseDown:
            {
            short part = FindWindow(pEvent->where, &pWindow);                   
            
            switch ( part ) 
                {
                case inContent:
                    if (pWindow != FrontWindow())
                        SelectWindow(pWindow);
                    else
                        DoContentClick(pWindow);
                    break;
                    
                case inGoAway:
                    if (TrackGoAway(pWindow, pEvent->where) )
                        DoCommand(pWindow, cClose, 0);
                    break;
                    
                case inGrow:
                    DoGrowWindow(pWindow, pEvent);
                    break;
                    
                case inZoomIn:
                case inZoomOut:
                    if ( TrackBox(pWindow, pEvent->where, part) )
                        DoZoomWindow(pWindow, part);
                    break;
                    
                case inDrag:
                    {
                    WindowDataPtr   pData = GetWindowInfo(pWindow);
                    
                    if ( (pData) && (pData->dragWindowAligned) )
                        DragAlignedWindow((WindowPtr) pWindow, pEvent->where, &qd.screenBits.bounds, nil, nil);
                    else
                        DragWindow(pWindow, pEvent->where, &qd.screenBits.bounds);
                    }
                    break;
                    
                case inMenuBar:             /* process a mouse menu command (if any) */
                    {
                    long    menuResult;
                    
                    // force these threads to run to completion so the
                    // contents of the menus are fully initialized
                    
                    if (gFontThread != kNoThreadID)
                        {
                        gDontYield = true;
                        SetThreadState(gFontThread, kReadyThreadState, gFontThread);
                        YieldToThread(gFontThread);
                        gDontYield = false;
                        }
                    if (gAGThread != kNoThreadID)
                        {
                        gDontYield = true;
                        SetThreadState(gAGThread, kReadyThreadState, gAGThread);
                        YieldToThread(gAGThread);
                        gDontYield = false;
                        }
                    
                    pWindow = FrontWindow();
                    AdjustMenus(pWindow, true, false);
                    menuResult = MenuSelect(pEvent->where);
                    if ( (gMachineInfo.haveTSM) && (TSMMenuSelect(menuResult)) )
                        HiliteMenu(0);
                    else
                        DoMenuCommand(pWindow, menuResult);
                    }
                    break;
                    
                case inSysWindow:           /* let the system handle the mouseDown */
                    SystemClick(pEvent, pWindow);
                    break;
                    
                } // switch(part)
            }
            break;
            
        case keyDown:
        case autoKey:                       /* check for menukey equivalents */
            DoKeyEvent(pWindow, pEvent, true);
            break;
            
        case updateEvt:
            pWindow = (WindowRef) pEvent->message;
            DoUpdateWindow(pWindow);
            break;
 
        } // switch (pEvent->what)
    
} // HandleEvent
 
// --------------------------------------------------------------------------------------------------------------
#pragma segment Main
 
static OSErr    DoEventLoop(void)
{
    OSErr       anErr = noErr;
    Boolean     gotEvent;
    Boolean     trueGotEvent;
    WindowRef   pWindow;
    
    do  {
        pWindow = LMGetFirstWindow();       // walk all of our windows, even invisible ones
        
        DoAdjustCursor(pWindow);
        gotEvent = WaitNextEvent(everyEvent, &gEvent, DetermineWaitTime(pWindow), gCursorRgn);
        trueGotEvent = gotEvent;
 
        // WNE may close the window if it's owned by some silly extension.
        pWindow = LMGetFirstWindow();       
        
        // let text services handle the event first if it wishes to do so
        if ( gMachineInfo.haveTSM )
            {
            ScriptCode  keyboardScript;
            WindowRef   theFront = FrontWindow();
            
            if (theFront)
                {
                SetPort((GrafPtr) GetWindowPort(theFront));
                
                keyboardScript = GetScriptManagerVariable(smKeyScript);
                if (FontToScript(qd.thePort->txFont) != keyboardScript)
                    TextFont(GetScriptVariable(keyboardScript, smScriptAppFond));
                }
            
            if (TSMEvent(&gEvent))
                gotEvent = false;
            }
            
        // let all windows filter this event, and get time if they wish to
        while (pWindow)
            {
            WindowDataPtr   pData = GetWindowInfo(pWindow);
            Boolean         finishedEvent = false;
            
            // help manager for the front window
            if ( (pWindow == FrontWindow()) && (pData) && (!gMachineInfo.amInBackground) && (HMGetBalloons()) )
                {
                Point   theMouse, tipLocation;
                short   newBalloon = iNoBalloon;
                Rect    tempRect;
                
                // find out where the mouse is              
                SetPort((GrafPtr) GetWindowPort(pWindow));
                GetMouse(&theMouse);
                
                // and only do something if we are within the window itself
                if (PtInRect(theMouse, &GetWindowPort(pWindow)->portRect))
                    {
                    // is it in the vertical scroll bar?
                    if (pData->vScroll)
                        {
                        tempRect = (**(pData->vScroll)).contrlRect;
                        if (PtInRect(theMouse, &tempRect))
                            {
                            newBalloon = iHelpActiveScroll;
                            if (GetControlMinimum(pData->vScroll) == GetControlMaximum(pData->vScroll))
                                newBalloon = iHelpDimVertScroll;
                            tipLocation.h = tempRect.right - kFromBottomTipOffset;
                            tipLocation.v = tempRect.bottom - kFromBottomTipOffset;
                            }
                        }
                        
                    // is it in the horizontal scroll bar?
                    if (pData->hScroll)
                        {
                        tempRect = (**(pData->hScroll)).contrlRect;
                        if (PtInRect(theMouse, &tempRect))
                            {
                            newBalloon = iHelpActiveScroll;
                            if (GetControlMinimum(pData->hScroll) == GetControlMaximum(pData->hScroll))
                                newBalloon = iHelpDimHorizScroll;
                            tipLocation.h = tempRect.right - kFromBottomTipOffset;
                            tipLocation.v = tempRect.bottom - kFromBottomTipOffset;
                            }
                        }
                    
                    // is it in the grow box?
                    if (pData->hasGrow)
                        {
                        CalculateGrowIcon(pData, &tempRect);
                        if (PtInRect(theMouse, &tempRect))
                            {
                            newBalloon = iHelpGrowBox;
                            tipLocation.h = tempRect.right - kFromBottomTipOffset;
                            tipLocation.v = tempRect.bottom - kFromBottomTipOffset;
                            }
                        }
                    
                    // none of the above, must be the content
                    if (newBalloon == iNoBalloon)
                        {
                        newBalloon = iHelpGenericContent;
                        tempRect = pData->contentRect;
                        if (pData->pGetBalloon)
                            (*(pData->pGetBalloon)) (pWindow, pData, &theMouse, &newBalloon, &tempRect);
                            
                        tipLocation.h = tempRect.left + kFromTopTipOffset;
                        tipLocation.v = tempRect.top + kFromTopTipOffset;
                        }
                        
                    // show our new balloon, or remove the old one
                    if (newBalloon != iNoBalloon)
                        {
                        if ( (gMachineInfo.lastBalloonIndex != newBalloon) || (!HMIsBalloon()) )
                            {
                            HMMessageRecord message;
                            
                            if (newBalloon != iDidTheBalloon)
                                {
                                message.hmmHelpType = khmmString;
                                GetIndString(message.u.hmmString, kWindowHelpID, newBalloon);
                                LocalToGlobal(&tipLocation);
                                (void) HMShowBalloon(&message, tipLocation, nil, nil, 0, kDefaultBalloonVariant, 0);
                                }
                            gMachineInfo.lastBalloonIndex = newBalloon;
                            }
                        }
                    else
                        HMRemoveBalloon();
                    }
                    
                    
                }
                
            // if we hit a window we know about, then do filtering
            if (pData)
                {
                if (pData->pFilterEvent)
                    finishedEvent = (*(pData->pFilterEvent)) (pWindow, pData, &gEvent);
                }
 
            // if filtering indicates complete handling of event, then stop, and
            // do no regular processing.
            if (finishedEvent)
                {
                gotEvent = false;
                pWindow = nil;
                }
            else
                pWindow = GetNextWindow(pWindow);
            }
            
        if (gotEvent)
            HandleEvent(&gEvent);
            
        // close request?
        if (gAllDone)
            {
            pWindow = FrontWindow();
            while ((gAllDone) && (pWindow) )
                {
                WindowRef   nextWindow = GetNextWindow(pWindow);
                OSErr       closeError = DoCloseWindow(pWindow);
                
                // window didn't close?  then don't quit
                if (pWindow == FrontWindow())
                    gAllDone = false;
                    
                // something bad happened, then don't quit
                if ( (closeError != noErr) /* && (closeError != eUserCanceled) */ )
                    gAllDone = false;
                    
                pWindow = nextWindow;
                }
            }
        
        // our threads are low-priority, so we only give time to them on idle
        if (gMachineInfo.haveThreads && !trueGotEvent && !gAllDone)
            YieldToAnyThread();
        
        } while (!gAllDone);
        
    return anErr;
    
} // DoEventLoop
 
 
// --------------------------------------------------------------------------------------------------------------
// DRAG MANAGEMENT GLOBAL SUPPORT ROUTINES
// --------------------------------------------------------------------------------------------------------------
 
// Globals for our drag handlers
 
Boolean             gCanAccept;             // if we can receive the item(s) being dragged
 
// --------------------------------------------------------------------------------------------------------------
#pragma segment Drag
 
static pascal OSErr GlobalTrackingHandler(short message, WindowRef pWindow, void *handlerRefCon, DragReference theDragRef)
{
    #pragma unused(handlerRefCon)
 
    WindowDataPtr pData = GetWindowInfo(pWindow);
 
    // Call the tracking handler associated with this type of window. Only allow messages referencing
    // a specific window to be passed to the handler.
 
    if (pData)
        {   
        if (pData->pDragTracking)
            return ((*(pData->pDragTracking)) (pWindow, pData, theDragRef, message));
        }
    
    return noErr;
 
} // GlobalTrackingHandler
 
DragTrackingHandlerUPP gGlobalTrackingHandler;
 
// --------------------------------------------------------------------------------------------------------------
#pragma segment Drag
 
static pascal OSErr GlobalReceiveHandler(WindowRef pWindow, void *handlerRefCon, DragReference theDragRef)
{
    #pragma unused(handlerRefCon)
 
    WindowDataPtr pData = GetWindowInfo(pWindow);
    
    if (pData)
        {
        if (pData->pDragTracking)
            return ((*(pData->pDragReceive)) (pWindow, pData, theDragRef));
        }
 
    return noErr;
 
} // GlobalReceiveHandler
 
DragReceiveHandlerUPP gGlobalReceiveHandler;
 
// --------------------------------------------------------------------------------------------------------------
//
// IsOnlyThisFlavor - Given a DragReference and a FlavorType, we iterate through the drag items to determine if
//                    all are of flavor theType. If this is so, we return true. If any of the items are not
//                    theType, we return false, indicating that we should not accept the drag.
//
#pragma segment Drag
 
Boolean IsOnlyThisFlavor(DragReference theDragRef, FlavorType theType)
{
    unsigned short  items, index;
    FlavorFlags     theFlags;
    ItemReference   itemID;
    OSErr           anErr = noErr;
 
    CountDragItems(theDragRef, &items);
    
    for(index = 1; index <= items; index++)
        {
        GetDragItemReferenceNumber(theDragRef, index, &itemID);
 
        anErr = GetFlavorFlags(theDragRef, itemID, theType, &theFlags);
        if(anErr == noErr)
            continue;   // it's okay, this flavor is cool
 
        return false;   // this item has at least one flavor we don't like
        }
 
    return true;        // all flavors in this item were cool
 
} // IsOnlyThisFlavor
 
// --------------------------------------------------------------------------------------------------------------
//
// IsDropInFinderTrash - Returns true if the given dropLocation AEDesc is a descriptor of the Finder's Trash.
//
#pragma segment Drag
 
Boolean IsDropInFinderTrash(AEDesc *dropLocation)
{
    OSErr           result;
    AEDesc          dropSpec;
    FSSpec          *theSpec;
    CInfoPBRec      thePB;
    short           trashVRefNum;
    long            trashDirID;
 
    //  Coerce the dropLocation descriptor into an FSSpec. If there's no dropLocation or
    //  it can't be coerced into an FSSpec, then it couldn't have been the Trash.
 
    if ((dropLocation->descriptorType != typeNull) &&
        (AECoerceDesc(dropLocation, typeFSS, &dropSpec) == noErr)) 
        {
        unsigned char flags = HGetState(dropSpec.dataHandle);
        
        HLock(dropSpec.dataHandle);
        theSpec = (FSSpec *) *dropSpec.dataHandle;
 
        //  Get the directory ID of the given dropLocation object.
 
        thePB.dirInfo.ioCompletion = 0L;
        thePB.dirInfo.ioNamePtr = (StringPtr) &theSpec->name;
        thePB.dirInfo.ioVRefNum = theSpec->vRefNum;
        thePB.dirInfo.ioFDirIndex = 0;
        thePB.dirInfo.ioDrDirID = theSpec->parID;
 
        result = PBGetCatInfoSync(&thePB);
 
        HSetState(dropSpec.dataHandle, flags);
        AEDisposeDesc(&dropSpec);
 
        if (result != noErr)
            return false;
 
        //  If the result is not a directory, it must not be the Trash.
 
        if (!(thePB.dirInfo.ioFlAttrib & (1 << 4)))
            return false;
 
        //  Get information about the Trash folder.
 
        FindFolder(theSpec->vRefNum, kTrashFolderType, kCreateFolder, &trashVRefNum, &trashDirID);
 
        //  If the directory ID of the dropLocation object is the same as the directory ID
        //  returned by FindFolder, then the drop must have occurred into the Trash.
 
        if (thePB.dirInfo.ioDrDirID == trashDirID)
            return true;
        }
 
    return false;
 
} // IsDropInFinderTrash
 
// --------------------------------------------------------------------------------------------------------------
// APPLE EVENT SUPPORT ROUTINES
// --------------------------------------------------------------------------------------------------------------
#pragma segment Main
 
static OSErr    MissingParameterCheck(
    AppleEvent  *inputEvent)
/*
    This routine checks an input AppleEvent for the missing keyword.
    If the missing keyword is found, that means that some required
    parameters were missing (ie, an error). 
    
    However, if the missing keyword isn't found, that means that we aren't missing 
    any required parameters (that is to say, all REQUIRED parameters were supplied
    by the person who created the event).
    
    SOME DAY, THE ABOVE COMMENT WILL MAKE SENSE TO YOU.  IT STILL DOESN'T
    TO ME AND I WAS THE ONE WHO WROTE IT.
*/
{
    OSErr       anErr;
    AEKeyword   missingKeyword;
    DescType    ignoredActualType;
    Size        ignoredActualSize;
    
    anErr = AEGetAttributePtr(
        inputEvent, 
        keyMissedKeywordAttr,
        typeWildCard,
        &ignoredActualType,
        (Ptr) &missingKeyword,
        sizeof(AEKeyword),
        &ignoredActualSize);
            
    if (anErr == noErr)
        anErr = errAEParamMissed;
    else
        if (anErr == errAEDescNotFound)
            anErr = noErr;
        
    return anErr;
    
} // MissingParameterCheck
 
// --------------------------------------------------------------------------------------------------------------
// Globals for our handlers
Boolean gQuitAfterPrint = true;
 
// --------------------------------------------------------------------------------------------------------------
#pragma segment Main
 
static pascal OSErr DoOpenApp(
    AppleEvent  *inputEvent,
    AppleEvent  *outputEvent,
    long        handlerRefCon)
{
#pragma unused (outputEvent, handlerRefCon)
 
    DoCommand(nil, cNew, 0);
    gQuitAfterPrint = false;
    
    // so that the initial document opens more quickly, we don't start
    // the threads until we get an OpenApp or OpenDocument AppleEvent
    if (gStarterThread != kNoThreadID)
        SetThreadState(gStarterThread, kReadyThreadState, gStarterThread);
    
    return(MissingParameterCheck(inputEvent));
    
} // DoAppOpen
 
// --------------------------------------------------------------------------------------------------------------
#pragma segment Main
 
static pascal OSErr DoQuitApp(
    AppleEvent  *inputEvent,
    AppleEvent  *outputEvent,
    long        handlerRefCon)
{
#pragma unused (outputEvent, handlerRefCon)
 
    DoCommand(nil, cQuit, 0);
 
    return(MissingParameterCheck(inputEvent));
    
} // DoQuitApp
 
// --------------------------------------------------------------------------------------------------------------
#pragma segment Main
 
static pascal OSErr DoOpenOrPrint(
    AppleEvent  *inputEvent,
    StringPtr   pPrinterName)   // nil => do not print, zero length => print to default, other => printer name
{
 
    OSErr       anErr, anErr2;
    AEDescList  docList;                // list of docs passed in
    long        index, itemsInList;
    void*       hPrint;
    Boolean     wasAlreadyOpen;
    
    anErr = AEGetParamDesc( inputEvent, keyDirectObject, typeAEList, &docList);
    nrequire(anErr, GetFileList);
 
    anErr = AECountItems( &docList, &itemsInList);          // how many files passed in
    nrequire(anErr, CountDocs);
    for (index = 1; index <= itemsInList; index++)          // handle each file passed in
        {   
        AEKeyword   keywd;
        DescType    returnedType;
        Size        actualSize;
        FSSpec      theFSS; 
 
        anErr = AEGetNthPtr( &docList, index, typeFSS, &keywd, &returnedType,   // get file's info
                            (Ptr)(&theFSS), sizeof(theFSS), &actualSize);
        nrequire(anErr, AEGetNthPtr);
        
        {
        FInfo   theFileInfo;
        
        anErr = FSpGetFInfo(&theFSS, &theFileInfo);
        if (anErr == noErr)
            anErr = DetermineWindowTypeOrOpen(&theFSS, theFileInfo.fdType, nil, nil, &wasAlreadyOpen);
            
        if (anErr == eDocumentWrongKind)
            {
            if (pPrinterName)
                ConductErrorDialog(anErr, cPrint, cancel);
            else
                ConductErrorDialog(anErr, cOpen, cancel);
 
            anErr = noErr;
            break;
            }
            
        nrequire(anErr, DetermineWindowTypeOrOpen);
        }
        
        if (pPrinterName)
            {
            WindowRef       pWindow = FrontWindow();
            WindowDataPtr   pData = GetWindowInfo(pWindow);
            
            if (pData->pPrintPage)
                {
                if (index == 1)
                    {
                    THPrint aePrintRecord = NULL;
                    Boolean showDialog = true;  // default to true
 
                    // Get the default print record into pData->hPrint
                    anErr = DoDefault(pData);
                    if (anErr == noErr) {
 
                        // look for the [optional] print settings parameter
                        anErr = getPrintRecordFromEvent(inputEvent, &aePrintRecord);
                        if ((anErr == noErr) && (aePrintRecord != NULL))
                            {
                            THPrint jobPrintRecord = NULL;
                            // There was a print settings param. It's been saved in
                            // aePrintRecord.
 
                            // Merge the record we retrieved with a default print record.
                            // This is necessary so that some of the hints, specifically
                            // first and last page, get merged into the print record.
                            
                            // Since SimpleText hasn't opened the driver at this point,
                            // PrOpen and PrClose must wrap the call to PrJobMerge().
                            PrOpen();
                            anErr = getPrintJobPrintRec(pData->hPrint, aePrintRecord, &jobPrintRecord);
                            if (anErr == noErr) {
                                pData->hPrint = jobPrintRecord;
                            }
                            PrClose();
                            
                            // pData->hPrint now has a valid print record which can either
                            // be used directly or can be used to "seed" the print dialog
                            }
 
                        if (anErr == noErr)
                            {
                            // look for the [optional] parameter saying whether we show the print dialog
                            anErr = getPrintJobShowDialog(inputEvent, &showDialog);
                            if (anErr == noErr)
                                {
                                if (!showDialog)
                                    {
                                    // no need to show the print dialog, since we were told not to.
                                    // We do need to copy the print record from the window into
                                    // hPrint so it can be passed into doPrint().
                                    hPrint = pData->hPrint;
                                    }
                                else
                                    {
                                    // we were told to show the print dialog. pWindow contains
                                    // (in pData->hPrint) the print record that should set up
                                    // the dialog. I want to call my new version of DoPrintSetup
                                    // which does not default the print record that has been
                                    // passed in.
                                    
                                    // NOTE: First Page and Last Page will not be entered into
                                    // the dialog correctly and the dialog will show "All Pages".
                                    // This is a bug in the driver (LaserWriter 8, version 8.7)
                                    // and will be fixed in a future version of the driver.
                                    anErr = DoPrintSetupNoDefault(pWindow, pPrinterName);
                                    if (anErr == noErr)
                                        hPrint = pData->hPrint;
                                    }
                                }
                            }
                        }
                    }
                    
                if (anErr == noErr)
                    anErr = DoPrint(pWindow, hPrint, false);
                    
                if (index != itemsInList)
                    pData->hPrint = nil;
                }
            
            if (!wasAlreadyOpen)
                DoCloseWindow(pWindow);
 
            if (anErr != noErr)
                break;
            }
        }
 
    // finally, make sure we didn't miss any parameters
    anErr2 = MissingParameterCheck(inputEvent);
    if (anErr == noErr)
        anErr = anErr2;
        
// FALL THROUGH EXCEPTION HANDLING
DetermineWindowTypeOrOpen:
AEGetNthPtr:
CountDocs:
    // done with doc list
    (void) AEDisposeDesc( &docList);                        
    
GetFileList:
 
    // don't report cancels from prints
    if (pPrinterName)
        {
        if (anErr == iPrAbort)
            anErr = noErr;
        }
    
    if ( (anErr != noErr) && (anErr != eActionAlreadyHandled) && (anErr != eUserCanceled) )
        {
        if (pPrinterName)
            ConductErrorDialog(anErr, cPrint, cancel);
        else
            ConductErrorDialog(anErr, cOpen, cancel);
        }
        
    return anErr;
    
} // DoOpenOrPrint
 
// --------------------------------------------------------------------------------------------------------------
#pragma segment Main
 
static pascal OSErr DoOpenDocument(
    AppleEvent  *inputEvent,
    AppleEvent  *outputEvent,
    long        handlerRefCon)
{
#pragma unused (outputEvent, handlerRefCon)
 
    OSErr       anErr;
    
    if (IsCommandEnabled(cOpen))
        {
        gQuitAfterPrint = false;
        anErr = DoOpenOrPrint(inputEvent, nil);
        }
    else
        {
        anErr = errAEEventNotHandled;
        ConductErrorDialog(anErr, cOpen, cancel);
        }
        
    // so that the initial document opens more quickly, we don't start
    // the threads until we get an OpenApp or OpenDocument AppleEvent
    if (gStarterThread != kNoThreadID)
        SetThreadState(gStarterThread, kReadyThreadState, gStarterThread);
    
    return anErr;
    
} // DoOpenDocument
 
// --------------------------------------------------------------------------------------------------------------
#pragma segment Main
 
static pascal OSErr DoPrintDocument(
    AppleEvent  *inputEvent,
    AppleEvent  *outputEvent,
    long        handlerRefCon)
{
#pragma unused (outputEvent, handlerRefCon)
    OSErr       anErr;
    FSSpec      printerFSS;
    AEDescList  dtpList;                // list of docs passed in
    
    if (IsCommandEnabled(cOpen))
        {
        // try to find out if this doc was dropped onto a printer
        anErr = AEGetAttributeDesc( inputEvent, keyOptionalKeywordAttr, typeAEList, &dtpList);
    
        if (anErr == noErr)                                         // doc dragged to dtp?
            {
            AEKeyword   keywd;
            DescType    returnedType;
            Size        actualSize;
    
            anErr = AEGetNthPtr( &dtpList, 1, typeFSS, &keywd, &returnedType,   // get dtp info
                            (Ptr)(&printerFSS), sizeof(printerFSS), &actualSize);
            }
            
        // if it wasn't, that's not an error, just print normally
        if (anErr != noErr)
            {
            printerFSS.name[0] = 0;
            anErr = noErr;
            }
            
        anErr = DoOpenOrPrint(inputEvent, &printerFSS.name[0]);
        
        // if we are opened just for printing -- quit afterwards
        if (gQuitAfterPrint)
            DoCommand(nil, cQuit, 0);
        }
    else
        {
        anErr = errAEEventNotHandled;
        ConductErrorDialog(anErr, cPrint, cancel);
        }
        
    return anErr;
    
} // DoPrintDocument
 
#if GENERATINGCFM
    static RoutineDescriptor    gDoOpenAppRD = BUILD_ROUTINE_DESCRIPTOR(uppAEEventHandlerProcInfo, DoOpenApp);
    static AEEventHandlerUPP    gDoOpenApp = &gDoOpenAppRD;
 
    static RoutineDescriptor    gDoQuitAppRD = BUILD_ROUTINE_DESCRIPTOR(uppAEEventHandlerProcInfo, DoQuitApp);
    static AEEventHandlerUPP    gDoQuitApp = &gDoQuitAppRD;
 
    static RoutineDescriptor    gDoOpenDocumentRD = BUILD_ROUTINE_DESCRIPTOR(uppAEEventHandlerProcInfo, DoOpenDocument);
    static AEEventHandlerUPP    gDoOpenDocument = &gDoOpenDocumentRD;
 
    static RoutineDescriptor    gDoPrintDocumentRD = BUILD_ROUTINE_DESCRIPTOR(uppAEEventHandlerProcInfo, DoPrintDocument);
    static AEEventHandlerUPP    gDoPrintDocument = &gDoPrintDocumentRD;
#else
    static AEEventHandlerUPP    gDoOpenApp = (AEEventHandlerUPP) DoOpenApp;
    static AEEventHandlerUPP    gDoQuitApp = (AEEventHandlerUPP) DoQuitApp;
    static AEEventHandlerUPP    gDoOpenDocument = (AEEventHandlerUPP) DoOpenDocument;
    static AEEventHandlerUPP    gDoPrintDocument = (AEEventHandlerUPP) DoPrintDocument;
#endif
// --------------------------------------------------------------------------------------------------------------
#pragma segment Main
 
static pascal OSErr SimpleTextCoachHandler(Rect *pRect, Ptr name, long refCon)
{
#pragma unused (refCon)
 
    OSErr           anErr = noErr;
    WindowRef       pWindow = FrontWindow();
    WindowDataPtr   pData = GetWindowInfo(pWindow);
    
    if ((pData) && (pData->pGetCoachRectangle))
        anErr = (*(pData->pGetCoachRectangle)) (pWindow, pData, pRect, name);
        
    return(anErr);
    
} // SimpleTextCoachHandler
 
// --------------------------------------------------------------------------------------------------------------
// MAIN INITIALIZE/SHUTDOWN/LOOP ROUTINES
// --------------------------------------------------------------------------------------------------------------
#pragma segment Main
 
static pascal void* StarterThread(void* threadParam)
{
    #pragma unused(threadParam)
    
    /*
        All threads, including the starter thread, are initially created
        in a suspended state. The starter thread is made ready to run when
        we get an open application or open document event. It runs until
        there are no activate or update events pending, and then starts the
        font menu and Apple Guide threads. This gives much better performance
        for the initial creation and update of a document, because the threads
        (especially the font thread) chew up a lot of time at first.
        
        The starter thread isn't really necessary - we could get the same
        effect by just checking in the event loop for activate/update events -
        but hey, it's a lot easier to do it this way, doesn't cost much, and
        isn't that what threads are for?
    */
    
    for (;;)
        {
        EventRecord     er;
    
        YieldToAnyThread();
        
        if (!EventAvail(activMask | updateMask, &er))
            {
            if (gFontThread != kNoThreadID)
                SetThreadState(gFontThread, kReadyThreadState, gFontThread);
            if (gAGThread != kNoThreadID)
                SetThreadState(gAGThread, kReadyThreadState, gAGThread);
            
            break;
            }
        }
    
    gStarterThread = kNoThreadID;
    return NULL;
    
} // StarterThread
 
// --------------------------------------------------------------------------------------------------------------
#pragma segment Initialize
 
static OSErr CreateThread(ThreadEntryProcPtr pThread, void* threadParam, ThreadID* ptid)
{
    OSErr   anErr;
    
    anErr = NewThread(kCooperativeThread, pThread, threadParam, 0, kNewSuspend,
        &gThreadResults, ptid);
        
    if (anErr == noErr && gStarterThread == kNoThreadID)
        {
        anErr = NewThread(kCooperativeThread, StarterThread, NULL, 0, kNewSuspend,
            &gThreadResults, &gStarterThread);
        if (anErr != noErr)
            DisposeThread(*ptid, &gThreadResults, false);
            // anErr remains != noErr
        }
        
    return anErr;
}
        
// --------------------------------------------------------------------------------------------------------------
#pragma segment Initialize
 
static OSErr BuildFontMenu(MenuHandle menu)
{
    OSErr   anErr = noErr;
    
    AppendResMenu(menu, 'FONT');
    
    return(anErr);
    
} // BuildFontMenu
 
// --------------------------------------------------------------------------------------------------------------
#pragma segment Initialize
 
static OSErr    DoInitialize(void)
{
    short               count;          // loop counter
    Handle              menuBar;        // for loading our menus in
    OSErr               anErr = noErr;  // any errors we get, none so far
    long                version;        // version for Gestalt calls
    
    InitGraf((Ptr) &qd.thePort);
    InitFonts();
    InitWindows();
    InitMenus();
    TEInit();
    InitDialogs(nil);
    InitCursor();
    
    gAllDone = false;
    
    // check that the system is correct to handle things
    SysEnvirons(1, &gMachineInfo.theEnvirons);
    if (gMachineInfo.theEnvirons.systemVersion < 0x0700)
        {
        // Wait for app to come to front
        for (count = 1; count <= 3; ++count)
            EventAvail(everyEvent, &gEvent);
            
        anErr = eMachineToOld;
        nrequire(anErr, SysEnvirons);
        }
 
    gMachineInfo.lastBalloonIndex = iNoBalloon;
    gMachineInfo.amInBackground = false;
    gMachineInfo.documentCount  = 1;
    gMachineInfo.haveQuickTime  = (Gestalt(gestaltQuickTime, &version) == noErr);
    gMachineInfo.haveRecording  = (Gestalt(gestaltSoundAttr, &version) == noErr) && ((version & (1<<gestaltHasSoundInputDevice)) != 0);
    gMachineInfo.haveTTS        = (Gestalt(gestaltSpeechAttr, &version) == noErr) && ((version & (1<<gestaltSpeechMgrPresent)) != 0);
    gMachineInfo.haveTSM        = (Gestalt(gestaltTSMgrVersion, &version) == noErr) && (version >= 1);
    gMachineInfo.haveTSMTE      = (Gestalt(gestaltTSMTEAttr, &version) == noErr) && ((version & (1<<gestaltTSMTE)) != 0);
    gMachineInfo.haveDragMgr    = (Gestalt(gestaltDragMgrAttr, &version) == noErr) && ((version & (1<<gestaltDragMgrPresent)) != 0) &&
                                    (Gestalt(gestaltTEAttr, &version) == noErr) && ((version & (1<<gestaltTEHasGetHiliteRgn)) != 0);
    gMachineInfo.haveThreeD     = false;
    gMachineInfo.haveThreads    = (Gestalt(gestaltThreadMgrAttr, &version) == noErr) && ((version & (1<<gestaltThreadMgrPresent)) != 0);
    
    #if GENERATINGPOWERPC
        {
        CFragConnectionID   connID;
        Ptr                 mainAddr;
        Str255              errName;
        
        if ( (gMachineInfo.haveQuickTime)   && (GetSharedLibrary("\pQuickTimeLib", kPowerPCCFragArch, kFindCFrag, &connID, &mainAddr, errName) != noErr) )
            gMachineInfo.haveQuickTime = false;
        if ( (gMachineInfo.haveTTS)         && (GetSharedLibrary("\pSpeechLib", kPowerPCCFragArch, kFindCFrag, &connID, &mainAddr, errName) != noErr) )
            gMachineInfo.haveTTS = false;
        if ( (gMachineInfo.haveDragMgr)     && (GetSharedLibrary("\pDragLib", kPowerPCCFragArch, kFindCFrag, &connID, &mainAddr, errName) != noErr) )
            gMachineInfo.haveDragMgr = false;
        if ( (gMachineInfo.haveThreads)     && (GetSharedLibrary("\pThreadsLib", kPowerPCCFragArch, kFindCFrag, &connID, &mainAddr, errName) != noErr) )
            gMachineInfo.haveThreads = false;
        }
    #endif
    
 
    // initialize text services if they exist
    if (gMachineInfo.haveTSMTE)
        {
        if (InitTSMAwareApplication() != noErr)
            {
            gMachineInfo.haveTSM = false;
            gMachineInfo.haveTSMTE = false;
            }
        }
        
    // save away info we need from the get-go   
    gApplicationResFile = CurResFile();
    gCursorRgn = NewRgn();
 
    // load up the menus
    menuBar = (Handle) GetNewMBar(rMenuBar);            /* read menus into menu bar */
    anErr = ResError();
    if ( (anErr == noErr) && (menuBar == nil) )
        anErr = resNotFound;
    nrequire(anErr, GetNewMBar);
    
    // install menus
    SetMenuBar(menuBar);    
    DisposeHandle(menuBar);
 
    // build the Apple menu
    AppendResMenu(GetMenuHandle(mApple), 'DRVR');   /* add DA names to Apple menu */
    
    // Build the font menu
    anErr = BuildFontMenu(GetMenuHandle(mFont));
    nrequire(anErr, BuildFontMenu);
    
    // insert our heirarchical menus
    {
    MenuHandle  menu = GetMenu( mVoices );
    short       menuID, itemID;
    
    InsertMenu( menu, hierMenu );
    
    CommandToIDs(cSelectVoice, &menuID, &itemID);
    menu = GetMenuHandle(menuID);
 
    SetItemCmd( menu, itemID, hMenuCmd );
    SetItemMark( menu, itemID, mVoices );
    }
 
    AdjustMenus(nil, true, false);
    DrawMenuBar();
    
    // start up QuickTime, but problems result in us pretending not to have it
    if (gMachineInfo.haveQuickTime)
        if (EnterMovies() != noErr)
            gMachineInfo.haveQuickTime = false;
        
    // Install AppleEvent handlers for the base classes
 
    #define INSTALL(event, handler) \
            AEInstallEventHandler(kCoreEventClass, event, handler, 0, false)
 
    INSTALL (kAEOpenApplication, gDoOpenApp);
    INSTALL (kAEQuitApplication, gDoQuitApp);
    INSTALL (kAEOpenDocuments,   gDoOpenDocument);
    INSTALL (kAEPrintDocuments,  gDoPrintDocument);
 
    #undef INSTALL
 
    // Install our global dragging procs, but only if we have Drag and Drop. An error results
    // in us pretending that we don't have drag support. Notice that in the test above, we also
    // require TextEdit to have TEGetHiliteRgn avalilable, which is always the case with the
    // present Drag Manager.
 
    if (gMachineInfo.haveDragMgr)
        {
        gGlobalTrackingHandler = NewDragTrackingHandlerProc(GlobalTrackingHandler);
        gGlobalReceiveHandler = NewDragReceiveHandlerProc(GlobalReceiveHandler);
        
        anErr = InstallTrackingHandler(gGlobalTrackingHandler, nil, nil);
 
        if (anErr == noErr)
            {
            anErr = InstallReceiveHandler(gGlobalReceiveHandler, nil, nil);
 
            if (anErr != noErr)
                {
                RemoveTrackingHandler(gGlobalTrackingHandler, nil);
                gMachineInfo.haveDragMgr = false;
                }
            }
        else
            gMachineInfo.haveDragMgr = false;
        }
 
    return noErr;
    
    
// EXCEPTION HANDLING
BuildFontMenu:
GetNewMBar:
SysEnvirons:
    ConductErrorDialog(anErr, cNull, cancel);
    
    return anErr;
 
} // DoInitialize
 
// --------------------------------------------------------------------------------------------------------------
#pragma segment Terminate
 
static OSErr    DoTerminate(void)
{
    OSErr   anErr = noErr;
    
    if (gFontThread != kNoThreadID)
        DisposeThread(gFontThread, &gThreadResults, false);
    if (gAGThread != kNoThreadID)
        DisposeThread(gAGThread, &gThreadResults, false);
    if (gStarterThread != kNoThreadID)
        DisposeThread(gStarterThread, &gThreadResults, false);
 
    if (gMachineInfo.haveQuickTime)
        ExitMovies();
        
    if (gMachineInfo.haveTSMTE)
        CloseTSMAwareApplication();
 
    if (gMachineInfo.haveDragMgr)
        {
        RemoveReceiveHandler(gGlobalReceiveHandler, nil);
        RemoveTrackingHandler(gGlobalTrackingHandler, nil);
        }
 
    return anErr;
    
} // DoTerminate
 
// --------------------------------------------------------------------------------------------------------------
#pragma segment Main
 
main(void)
{
    OSErr   anErr;
    
#ifndef __MWERKS__
    UnloadSeg((Ptr) _DataInit);                     /* note that _DataInit must not be in Main! */
#endif
    MaxApplZone();                                  /* expand the heap so code segments load at the top */
    MoreMasters(); MoreMasters(); MoreMasters();    /* we love handles */
    anErr = DoInitialize();
    UnloadSeg((Ptr) DoInitialize);                  
    if (anErr == noErr)
        {
        DoEventLoop();
 
// REVIEW: don't want to unload the segment we're in!!
//      UnloadSeg((Ptr) DoEventLoop);
        DoTerminate();                  
        }
    return 0;
} // main