Sources/MSDrag.c

// SVDrag.c
//
// Original version by Jon Lansdell and Nigel Humphreys.
// 4.0 and 3.1 updates by Greg Sutton.
// Drag Manager support by Chris White
// ©Apple Computer Inc 1996, all rights reserved.
 
#include "MSDrag.h"
#include "MSWindow.h"
#include "MSGlobals.h"
#include "Offscreen.h"
 
#include "DebugUtils.h"
 
 
#include <Drag.h>
#include <LowMem.h>
#include <Errors.h>
#include <Folders.h>
 
 
 
static short    gCaretOffset;       // Caret drawn during a drag
 
 
 
#pragma segment Drag
 
//
// InitDragHandlers creates the UPPs for the Drag Manager 
// callback routines _if_ the Drag Manager is available.
//
OSErr InitDragHandlers ( void )
{
    OSErr   theErr = noErr;
    
    if ( gHasDragManager )
    {
        gDragTrackingHandlerUPP = NewDragTrackingHandlerProc ( MyTrackingHandler );
        gDragReceiveHandlerUPP = NewDragReceiveHandlerProc ( MyReceiveHandler );
    }
    
    return theErr;
}
 
 
 
//
// InstallDragHandlers attaches the tracking and receive handlers to
// one of the application's windows.
//
OSErr InstallDragHandlers ( WindowRef theWindow )
{   
    OSErr   theErr = noErr;
    
    if ( gHasDragManager )
    {
        
        theErr = InstallTrackingHandler ( gDragTrackingHandlerUPP, theWindow, nil );
    
        if ( theErr == noErr )
        {
            theErr = InstallReceiveHandler ( gDragReceiveHandlerUPP, theWindow, nil );
            if ( theErr )
                (void) RemoveTrackingHandler ( gDragTrackingHandlerUPP, theWindow );
        }
        
    }
    
    return theErr;
}
 
 
 
//
// RemoveDragHandlers removes the tracking and receive handlers from
// one of the application's windows (usually just prior to disposal).
//
void RemoveDragHandlers ( WindowRef theWindow )
{
    if ( gHasDragManager )
    {
        
        RemoveReceiveHandler ( gDragReceiveHandlerUPP, theWindow );
        RemoveTrackingHandler ( gDragTrackingHandlerUPP, theWindow );
        
    }
    
    return;
}
 
 
 
Boolean IsDragInWindowContent ( DragReference theDrag, WindowRef theWindow )
{
    Point   localPt;
    
    GetDragMouse ( theDrag, &localPt, 0L );
    GlobalToLocal ( &localPt );         // Assumes theWindow is current port
    
    return PtInWindow ( localPt, theWindow );
}
 
 
 
Boolean PtInWindow ( Point localPt, WindowRef theWindow )
{
    DPtr theDocument;
    
    theDocument = DPtrFromWindowPtr ( theWindow );
    return PtInDocument ( localPt, theDocument );
}
 
 
 
Boolean PtInDocument ( Point localPt, DPtr theDocument )
{
    return PtInRect ( localPt, &(**(theDocument->theText)).viewRect );
}
 
 
Boolean CanAcceptDragItems ( DragReference theDrag, WindowPtr theWindow )
{
    OSErr           theErr;
    ItemReference   theRef;
    Boolean         bCanAccept = false;
    HFSFlavor       currHFSFlavor;
    Size            flavorDataSize;
    FlavorFlags     currFlavorFlags;
    DPtr            theDocument;
    
    theDocument = DPtrFromWindowPtr ( theWindow );
 
    if (theDocument->windowType == kOrdinaryWind)
    {
        theErr = GetDragItemReferenceNumber ( theDrag, 1, &theRef );
        if ( theErr == noErr )
        {
            // use GetFlavorFlags to check on flavor existence of TEXT data.
            theErr = GetFlavorFlags ( theDrag, theRef, 'TEXT', &currFlavorFlags );
            if ( theErr == noErr )
                bCanAccept = true;
            else
            {
                // check if the item is a file spec, and it contains TEXT
                flavorDataSize = sizeof ( HFSFlavor );
                theErr = GetFlavorData ( theDrag, theRef, flavorTypeHFS, &currHFSFlavor,
                                                &flavorDataSize, 0  );
                
                if ( theErr == noErr && currHFSFlavor.fileType == 'TEXT' ) 
                    bCanAccept = true;
            }
        }
    }                   
    
    return (bCanAccept);
}
 
 
 
pascal OSErr MyTrackingHandler ( DragTrackingMessage theMessage, WindowPtr theWindow,
                                    void* handlerRefCon, DragReference theDrag )
{
#ifdef __MWERKS__
    #pragma unused(handlerRefCon)
#endif
 
    static Boolean  bHasAcceptableDrag;
    static Boolean  bHasHilitedWindow;
    static Boolean  bShowCaret;
    
    static long     caretMovedTime;
    static short    lastOffset, insertPosition;
    
    short           theOffset;
    OSErr           theErr = noErr;
    long            theTime = TickCount ( );
    unsigned long   theAttributes;
    RgnHandle       tempRgn;
    DPtr            theDocument, hitDocument;
    
    
    GetDragAttributes ( theDrag, &theAttributes );
    theDocument = DPtrFromWindowPtr ( theWindow );
    
    switch ( theMessage )
    {
        case dragTrackingEnterHandler:
            // Any initialization for this window handler.
            
            bHasAcceptableDrag = CanAcceptDragItems ( theDrag, theWindow );
            
            // Let the drag manager know if we can't accept this drag
            if ( !bHasAcceptableDrag )
                theErr = dragNotAcceptedErr;
        break;
            
        case dragTrackingEnterWindow: 
            
            caretMovedTime = theTime;
            gCaretOffset = lastOffset = -1;
            bShowCaret = true;
 
            bHasHilitedWindow = false;
            
        break;
        
        case dragTrackingInWindow:
            // Hiliting of the window during a drag is done
            // here.  Do it only if we can accept these items
            // and we're not in the source window.
            
            if ( bHasAcceptableDrag )
            {
                // Check if the mouse is in a window's content region. Make an
                // exception if the window has yet to leave the source window,
                // since we don't want to hilite it in that stuation.
                Boolean     bMouseInContent;
                Point       theMouse;
                
                bMouseInContent = false;
 
                if ( theAttributes & dragHasLeftSenderWindow )
                {
                    bMouseInContent = IsDragInWindowContent ( theDrag, theWindow );
                }
                
                // If the mouse is in a window and it isn't hilited...
                if ( bMouseInContent && !bHasHilitedWindow )
                {
                    tempRgn = NewRgn ( );
                    RectRgn ( tempRgn, &(**(theDocument->theText)).viewRect );
                    
                    // ...draw the hilight...
                    if ( ShowDragHilite ( theDrag, tempRgn, true ) == noErr )
                        // ... and remember it's now hilited
                        bHasHilitedWindow = true;
                    
                    DisposeRgn ( tempRgn );
                }
                
                
                GetDragMouse ( theDrag, &theMouse, 0L );
                theOffset = HitTest ( theMouse, &hitDocument );
                
                if ( theDocument == hitDocument )
                {
                
                    // Do not allow tracking through the selection in the
                    // window that sourced the drag.
                    if ( theAttributes & dragInsideSenderWindow )
                        if ( IsOffsetInSelection ( theOffset, theDocument->theText ) )
                            theOffset = -1;
                        
                    
                    
                    insertPosition = theOffset;
                    
                    //  Reset flashing counter if the offset has moved. This makes the
                    //  caret blink only after the caret has stopped moving long enough.
                    if ( theOffset != lastOffset )
                    {
                        caretMovedTime = theTime;
                        bShowCaret = true;
                    }
                    lastOffset = theOffset;
                    
                    
                    //  Flash caret.
                    if ( theTime - caretMovedTime > LMGetCaretTime ( ) )
                    {
                        bShowCaret = !bShowCaret;
                        caretMovedTime = theTime;
                    }
                    
                    if ( !bShowCaret )
                        theOffset = -1;
                        
                    //  If caret offset has changed, move caret on screen.
                    if (theOffset != gCaretOffset)
                    {
                        if (gCaretOffset != -1)
                            DrawCaret(gCaretOffset, theDocument->theText);
                        
                        if (theOffset != -1)
                            DrawCaret(theOffset, theDocument->theText);
                    }
                    
                    gCaretOffset = theOffset;
                }
                else
                {
                    lastOffset = theOffset;
                    insertPosition = -1;
                }
 
            }
        break;
        
        case dragTrackingLeaveWindow:
            //  If the caret is on the screen, remove it.
            if (gCaretOffset != -1)
            {
                DrawCaret(gCaretOffset, theDocument->theText);
                gCaretOffset = -1;
            }
            
            // Remove any window hiliting here. If the mouse
            // is not in the window and it's hilited...
            if ( bHasHilitedWindow )
                // ...erase the hilight...
                if ( HideDragHilite ( theDrag ) == noErr )
                    // ...remember that nothing is hilited
                    bHasHilitedWindow = false;
        break;
        
        case dragTrackingLeaveHandler:
        break;
        
        default:
            theErr = paramErr;
        break;
    }
    
    return theErr;
}
 
 
 
Boolean IsOffsetInSelection ( short theOffset, TEHandle theTE )
{
    return (theOffset >= (*theTE)->selStart && theOffset <= (*theTE)->selEnd);
}
 
 
 
//
// DragIsNotInSourceWindow returns true if the drag in progress
// is not in the same window it originated in. This is called by
// the tracking and receive handlers.
//
static Boolean DragIsNotInSourceWindow ( DragReference theDrag )
{
    DragAttributes currDragFlags;
    
    GetDragAttributes ( theDrag, &currDragFlags );
    return !(currDragFlags & dragInsideSenderWindow);
}
 
 
 
//
// DragReceiver is called by the drag manager whenever an
// item is dropped on one of the application's windows.
//
pascal OSErr MyReceiveHandler ( WindowPtr theWindow, void* handlerRefCon, 
                                    DragReference theDrag )
{
#ifdef __MWERKS__
    #pragma unused(handlerRefCon)
#endif
 
    Boolean             bHaveData = false;
    Boolean             bMove = false;
    OSErr               theErr = noErr;
    
    
    unsigned short      numItems;
    int                 i;
    unsigned long       theAttributes;
    ItemReference       theItem;
    Size                textSize, styleSize;
    DPtr                theDocument;
    
    short               theOffset;
    Point               thePoint = { 0, 0 };
    
    Ptr                 textData = nil;
    StScrpHandle        styleHandle = nil;
 
    TEHandle            tempTE = nil;
    Rect                tempRect;
 
    
    
    RgnHandle           tempRgn;
    Rect                sourceRect, targetRect;
 
    tWindowOffscreen*   theOffscreen = nil;
    
    if ( !(CanAcceptDragItems ( theDrag, theWindow ) && IsDragInWindowContent ( theDrag, theWindow )) )
        return dragNotAcceptedErr;
    
    theDocument = DPtrFromWindowPtr ( theWindow );
    GetDragAttributes ( theDrag, &theAttributes );
    
    // Get the location of the drop
    theErr = GetDragMouse ( theDrag, &thePoint, 0L );
    if ( theErr ) goto CleanupAndBail;
    
    GlobalToLocal ( &thePoint );            // Assumes theWindow is current port
    // Map the drop location to the text
    theOffset = TEGetOffset ( thePoint, theDocument->theText );
 
    // Don't allow a drop within the original selection
    if ( theAttributes & dragInsideSenderWindow )
        if ( IsOffsetInSelection ( theOffset, theDocument->theText ) )
            return dragNotAcceptedErr;
            
    
    // Is this drag copying or a moving the text?
    bMove = IsDragMoving ( theDrag );
    
    // First, gather all the text into a temp TE record, and then insert that into
    // the doucment. This approach makes it easy to handle things like text selection.
    SetRect ( &tempRect, 0, 0, 0, 0 );
    tempTE = TEStylNew ( &tempRect, &tempRect );
    theErr = MemError ( );
    if ( tempTE == nil || theErr ) goto CleanupAndBail;
    
    theErr = CountDragItems ( theDrag, &numItems );
    if ( theErr ) goto CleanupAndBail;
    
    for ( i = 1; i <= numItems; i++ )
    {
        
        theErr = GetDragItemReferenceNumber ( theDrag, i, &theItem );
        if ( theErr ) goto CleanupAndBail;
        
        theErr = GetFlavorDataSize ( theDrag, theItem, 'TEXT', &textSize );
        if ( theErr ) goto CleanupAndBail;
        
        textData = NewPtr ( textSize );
        theErr = MemError ( );
        if ( textData == nil || theErr ) goto CleanupAndBail;
        
        theErr = GetFlavorData ( theDrag, theItem, 'TEXT', textData, &textSize, 0L );
        if ( theErr ) goto CleanupAndBail;
        
        
        // Check for optional styl data for the TEXT.
        styleHandle = 0L;
        theErr = GetFlavorDataSize ( theDrag, theItem, 'styl', &styleSize );
        if ( !theErr )
        {
            styleHandle = (StScrpHandle) NewHandle ( styleSize );
            theErr = MemError ( );
            if ( styleHandle == nil || theErr ) goto CleanupAndBail;
            
            HLock ( (Handle) styleHandle );
            theErr = GetFlavorData ( theDrag, theItem, 'styl', *styleHandle, &styleSize, 0L );
            if ( theErr ) goto CleanupAndBail;
            HUnlock ( (Handle) styleHandle );
        }
        
        // Insert this drag item's text into the tempTE.
        TESetSelect ( 32767, 32767, tempTE );
        TEStylInsert ( textData, textSize, styleHandle, tempTE );
        
        DisposePtr ( textData );
        textData = nil;
        if ( styleHandle )
        {
            DisposeHandle ( (Handle) styleHandle );
            styleHandle = nil;
        }
        
    }
    
    
    // Pull the TEXT and styl data out of the tempTE handle.
    textData = NewPtr (textSize = (**tempTE).teLength );
    theErr = MemError ( );
    if ( textData == nil || theErr ) goto CleanupAndBail;
    
    BlockMoveData ( *(*tempTE)->hText, textData, textSize );
    TESetSelect ( 0, 32767, tempTE );
    styleHandle = TEGetStyleScrapHandle (tempTE );
    TEDispose ( tempTE );
    tempTE = nil;
    
    
    // Insert any text into the destination.
    if ( textSize != 0 )
    {
        
        // Get rid of the hilite/caret before inserting
        if ( theAttributes & dragHasLeftSenderWindow )
            HideDragHilite ( theDrag );
        
        if ( gCaretOffset != -1 )
        {
            DrawCaret ( gCaretOffset, theDocument->theText );
            gCaretOffset = -1;
        }
        
        //  If the drag occurred completely within the same window and the window is not
        //  frontmost, bring the window forward and update its contents before completing
        //  the drag.
        if ( (theAttributes & dragInsideSenderWindow) && (theWindow != FrontWindow ( )) )
        {
            SelectWindow ( theWindow );
            DoUpdate ( theWindow );
            if (theDocument->windowType == kOrdinaryWind)
                TEActivate ( theDocument->theText );
        }
        
        //  If the window is not active, must activate TE before inserting
        //  text or the background hilite will not update correctly.
        if ( !IsWindowHilited ( theWindow ) )
            if (theDocument->windowType == kOrdinaryWind)
                TEActivate ( theDocument->theText );
        
        //  Draw everything into offscreen pixmap.
        theOffscreen = DrawOffscreen ( theWindow );
        if ( theOffscreen  )
            (*theDocument->theText)->inPort = (GrafPtr) theOffscreen->offscreenWorld;
        
        if ( bMove )
        {
            // Get the current hilite rgn for zooming (source)
            tempRgn = NewRgn ( );
            theErr = MemError ( );
            if ( theErr )   goto CleanupAndBail;
            GetSelectedTextRgn ( theDocument, tempRgn );
            sourceRect = (*tempRgn)->rgnBBox;
            LocalRectToGlobalRect ( &sourceRect, theWindow );
            
            // If this is a move operation, delete the old text.
            DeleteTextSelection ( theDocument->theText, &theOffset );
        }
        
        InsertTextAtOffset ( theOffset, textData, textSize, styleHandle, theDocument->theText );
        
        // If the text is moving (not copying) within the same window, provide a ZoomRects
        // from the source to the destination before revealing the reflowed text.
        
        if ( bMove )
        {
            // Get the current hilite rgn for zooming (target)
            GetSelectedTextRgn ( theDocument, tempRgn );
            targetRect = (*tempRgn)->rgnBBox;
            DisposeRgn ( tempRgn );
            tempRgn = nil;
            LocalRectToGlobalRect ( &targetRect, theWindow );
            
            ZoomRects ( &sourceRect, &targetRect, 12, zoomDecelerate );
        }
    }
    
    // Undo the TEActivate, if needed.
    if ( !IsWindowHilited ( theWindow ) )
        TEDeactivate ( theDocument->theText );
    
    //  Show the offscreen bitmap.
    if ( theOffscreen )
    {
        theOffscreen = DrawOnscreen ( theOffscreen );
        (*theDocument->theText)->inPort = theWindow;
    }
    
    // Make the document dirty
    theDocument->dirty = true;
    
    
CleanupAndBail:
    
    
    // All of these will be nil if no error occurred
    if ( textData )
        DisposePtr ( textData );
    if ( styleHandle )
        DisposeHandle ( (Handle) styleHandle );
    if ( tempTE )
        TEDispose ( tempTE );
    if ( tempRgn )
        DisposeRgn ( tempRgn );
    
    // Normally  nil since DrawOnscreen calls DisposeOffscreen
    if ( theOffscreen )
    {
        DisposeOffscreen ( theOffscreen );
        (*theDocument->theText)->inPort = theWindow;
    }
    
    
    return theErr;
}
 
 
 
void DeleteTextSelection ( TEHandle theTE, short* theInsertPosition )
{
    short selStart, selEnd;
    
    selStart = (*theTE)->selStart;
    selEnd = (*theTE)->selEnd;
    if ( WhiteSpaceAtOffset ( selStart - 1, theTE ) &&
            !WhiteSpaceAtOffset ( selStart, theTE ) &&
            !WhiteSpaceAtOffset ( selEnd - 1, theTE ) &&
            WhiteSpaceAtOffset ( selEnd, theTE ) )
    {
        
        if ( GetCharAtOffset ( selEnd, theTE ) == ' ' )
            (*theTE)->selEnd++;
    }
    
    if ( *theInsertPosition > selStart )
        *theInsertPosition -= ((*theTE)->selEnd - (*theTE)->selStart);
    
    TEDelete ( theTE );
    
    return;
}
 
 
 
static Boolean IsDragMoving ( DragReference theDrag )
{
    DragAttributes  theAttributes;
    
    GetDragAttributes ( theDrag, &theAttributes );
    return (theAttributes & dragInsideSenderWindow) && !IsDragWithOptionKey ( theDrag );
}
 
 
 
static Boolean IsDragWithOptionKey ( DragReference theDrag )
{
    short   mouseDownModifiers, mouseUpModifiers;
    
    GetDragModifiers ( theDrag, 0L, &mouseDownModifiers, &mouseUpModifiers );
    return (mouseDownModifiers & optionKey) | (mouseUpModifiers & optionKey);
}
 
 
 
void OutlineRegion ( RgnHandle theRgn )
{
    RgnHandle tempRgn;
    
    tempRgn = NewRgn ( );
    CopyRgn ( theRgn, tempRgn );
    InsetRgn ( tempRgn, 1, 1 );
    DiffRgn ( theRgn, tempRgn, theRgn );
    DisposeRgn ( tempRgn );
    
    return;
}
 
 
 
OSErr DoWindowContentDrag ( WindowPtr theWindow, EventRecord* theEvent )
{
    OSErr           theErr = noErr;
    DragReference   theDrag = (unsigned long) nil;
    RgnHandle       dragRgn = nil;
    Ptr             dataPtr = nil;
    StScrpHandle    styleHandle = nil;
    short           dataSize;
    
    DPtr            theDocument;
    Rect            dragBounds;
    ItemReference   theItem;
    
    
    // create a new drag
    theErr = NewDrag ( &theDrag );
    if ( theErr ) goto CleanupAndBail;
    
    // use the window ptr as item reference for the heck of it
    theItem = (ItemReference) theWindow;
    
    // add the data to the drag
    theDocument = DPtrFromWindowPtr ( theWindow );
    dataPtr = NewPtr ( 0L );
    theErr = GetSelectedText ( theDocument, dataPtr, &dataSize );
    if ( theErr || dataSize == 0 )  goto CleanupAndBail;
    
    theErr = AddDragItemFlavor ( theDrag, theItem, 'TEXT', dataPtr, dataSize, 0 );
    if ( theErr )   goto CleanupAndBail;
    DisposePtr ( dataPtr );
    dataPtr = nil;
    
    
    // Add style data
    styleHandle = TEGetStyleScrapHandle ( theDocument->theText );
    HLock ( (Handle) styleHandle );
    AddDragItemFlavor ( theDrag, theItem, 'styl', (Ptr) *styleHandle, GetHandleSize ( (Handle) styleHandle ), 0 );
    HUnlock ( (Handle) styleHandle );
    DisposeHandle ( (Handle) styleHandle );
    styleHandle = nil;
    
    
    // generate the bounds and region for the drag using the window's
    // content rectangle
    dragBounds = (**((WindowPeek) theWindow)->contRgn).rgnBBox;
    theErr = SetDragItemBounds(theDrag, theItem, &dragBounds);
    if ( theErr ) goto CleanupAndBail;
    
    dragRgn = NewRgn ( );
    GetSelectedTextRgn ( theDocument, dragRgn );
    LocalRgnToGlobalRgn ( dragRgn, nil );
    OutlineRegion ( dragRgn );
    
    // do the drag and clean up
    TrackDrag ( theDrag, theEvent, dragRgn );
    
    
    if ( DragIsNotInSourceWindow ( theDrag ) )
    {
        AEDesc  dropLocation;
        
        // Get the drop location
        GetDropLocation ( theDrag, &dropLocation );
        if ( !IsDragWithOptionKey ( theDrag ) && DropLocationIsFinderTrash ( &dropLocation) )
        {
            // Delete the exact text. Don't call DeleteTextSelection
            TEDelete ( theDocument->theText );
            theDocument->dirty = true;
        }
        
        AEDisposeDesc ( &dropLocation );
    }
    
    
CleanupAndBail:
    
    if ( theDrag )
        DisposeDrag(theDrag);
    
    if ( dragRgn )
        DisposeRgn ( dragRgn );
    
    // These should be nil
    if ( dataPtr )
        DisposePtr ( dataPtr );
    if ( styleHandle )
        DisposeHandle ( (Handle) styleHandle );
    
    
    return theErr;
}
 
 
 
void LocalRgnToGlobalRgn ( RgnHandle theRgn, WindowRef theWindow )
{
    GrafPtr     savePort;
    Point       localPt, globalPt;
    
    
    if ( theWindow )
    {
        GetPort ( &savePort );
        SetPortWindowPort ( theWindow );
    }
    
    localPt = globalPt = *(Point*) &(*theRgn)->rgnBBox;     // top left
    LocalToGlobal ( &globalPt );
    SubPt ( localPt, &globalPt );
    OffsetRgn ( theRgn, globalPt.h, globalPt.v );
    
    if ( theWindow )
        SetPort ( savePort );
        
    return;
}
 
 
 
void LocalRectToGlobalRect ( Rect* theRect, WindowRef theWindow )
{
    GrafPtr     savePort;
    Point       localPt, globalPt;
    
    
    if ( theWindow )
    {
        GetPort ( &savePort );
        SetPortWindowPort ( theWindow );
    }
    
    localPt = globalPt = *(Point*) theRect;     // top left
    LocalToGlobal ( &globalPt );
    SubPt ( localPt, &globalPt );
    OffsetRect ( theRect, globalPt.h, globalPt.v );
    
    if ( theWindow )
        SetPort ( savePort );
        
    return;
}
 
 
 
 
OSErr GetSelectedText ( DPtr theDocument, Ptr dataPtr, short* dataSize )
{
    OSErr theErr;
    TEHandle teHandle = theDocument->theText;
    
    *dataSize = (**(teHandle)).selEnd - (**(teHandle)).selStart;
    if ( *dataSize )
    {
        SetPtrSize ( dataPtr, *dataSize );
        theErr = MemError ( );
        if ( theErr )
            return theErr;
        BlockMoveData ( *(**(teHandle)).hText + (**(teHandle)).selStart, dataPtr, *dataSize );
    }
    
    return noErr;
}
 
 
 
void GetSelectedTextRgn ( DPtr theDocument, RgnHandle dataRgn )
{
    TEGetHiliteRgn ( dataRgn, theDocument->theText );
    
    return;
}
 
 
 
//
// Does the user want to drag something? First, checks the click could
// be a drag, then waits to see if the user starts to drag the text.
// 
Boolean UserWantsToDrag ( WindowRef theWindow, Point globalPt )
{
    Point   localPt;
    
    localPt = globalPt;
    GlobalToLocal ( &localPt );         // Assumes theWindow is current port
    if ( PointInWindowSelection ( localPt, theWindow ) )
        return WaitMouseMoved ( globalPt );
        
    return false;
}
 
 
 
//
// Returns true if the local point is in the window's current selection
//
Boolean PointInWindowSelection ( Point localPt, WindowRef theWindow )
{
    Boolean     bHit;
    RgnHandle   tempRgn;
    DPtr        theDocument;
    
    theDocument = DPtrFromWindowPtr ( theWindow );
    if ( !(PtInDocument ( localPt, theDocument )) )
        return false;
    
    tempRgn = NewRgn ( );
    GetSelectedTextRgn ( theDocument, tempRgn );
    bHit = PtInRgn ( localPt, tempRgn );
    DisposeRgn ( tempRgn );
    
    return bHit;
}
 
 
 
//
//  Given a point in global coordinates, HitTest returns a pointer to a
//  document structure if the point is inside a document window on the screen.
//  If the point is not inside a document window, HitTest return NULL in
//  theDoc. If the point is in a doument window and also in the viewRect of
//  the document's TextEdit field, HitTest also returns the offset into
//  the text that corresponds to that point. If the point is not in the text,
//  HitTest returns -1.
//
short HitTest(Point theLoc, DPtr* theDoc)
{
    GrafPtr     savePort;
    WindowPtr   theWindow;
    short       offset;
    
    *theDoc = 0L;
    offset = -1;
    
    if (FindWindow(theLoc, &theWindow) == inContent)
    {
        if ( Ours ( theWindow ) )
        {
            *theDoc = DPtrFromWindowPtr ( theWindow );
            GetPort ( &savePort );
            SetPort(theWindow);
            GlobalToLocal(&theLoc);
            SetPort ( savePort );
            
            if ((PtInRect(theLoc, &(**((**theDoc).theText)).viewRect)) && 
                (PtInRect(theLoc, &(**((**theDoc).theText)).destRect))) {
                
                offset = TEGetOffset(theLoc, (**theDoc).theText);
 
                if ((TEIsFrontOfLine(offset, (**theDoc).theText)) && (offset) &&            
                        ((*((**((**theDoc).theText)).hText))[offset - 1] != 0x0D) &&
                        (TEGetPoint(offset - 1, (**theDoc).theText).h < theLoc.h)) {
                    offset--;
                }
            }
        }
    }
 
    return(offset);
}
 
 
 
// TEIsFrontOfLine, given an offset and a TextEdit handle, returns true if
// the given offset is at the beginning of a line start.
short TEIsFrontOfLine ( short offset, TEHandle theTE )
 
{   short       line = 0;
 
    if ((**theTE).teLength == 0)
        return(true);
 
    if (offset >= (**theTE).teLength)
        return( (*((**theTE).hText))[(**theTE).teLength - 1] == 0x0d );
 
    while ((**theTE).lineStarts[line] < offset)
        line++;
 
    return( (**theTE).lineStarts[line] == offset );
}
 
 
 
// TEGetLine, given an offset and a TextEdit handle, returns the line number
// of the line that contains the offset.
short TEGetLine ( short offset, TEHandle theTE )
 
{   short       line = 0;
 
    if (offset > (**theTE).teLength)
        return((**theTE).nLines);
 
    while ((**theTE).lineStarts[line] < offset)
        line++;
    
    return(line);
}
 
 
 
//
//  DrawCaret draws a caret in a TextEdit field at the given offset. DrawCaret
//  expects the port to be set to the port that the TextEdit field is in.
//  DrawCaret inverts the image of the caret onto the screen.
//
void DrawCaret(short offset, TEHandle theTE)
 
{   Point       theLoc;
    short       theLine, lineHeight;
 
 
    // Get the coordinates and the line of the offset to draw the caret.
 
    theLoc  = TEGetPoint(offset, theTE);
    theLine = TEGetLine(offset, theTE);
    
    
    // For some reason, TextEdit dosen't return the proper coordinates
    // of the last offset in the field if the last character in the record
    // is a carriage return. TEGetPoint returns a point that is one line
    // higher than expected. The following code fixes this problem.
    if ((offset == (**theTE).teLength) &&
            (*((**theTE).hText))[(**theTE).teLength - 1] == 0x0D) {
        theLoc.v += TEGetHeight(theLine, theLine, theTE);
    }
 
    // Always invert the caret when drawing.
    PenMode(patXor);
 
    //Get the height of the line that the offset points to.
    lineHeight = TEGetHeight(theLine, theLine, theTE);
 
    // Draw the appropriate caret image.
    MoveTo(theLoc.h - 1, theLoc.v - 1);
    Line(0, 1 - lineHeight);
 
    PenNormal();
}
 
 
 
char GetCharAtOffset(short offset, TEHandle theTE)
 
{
    if (offset < 0)
        return(0x0D);
 
    return(((char *) *((**theTE).hText))[offset]);
}
 
 
 
Boolean WhiteSpace(char theChar)
 
{
    return((theChar == ' ') || (theChar == 0x0D));
}
 
 
 
Boolean WhiteSpaceAtOffset(short offset, TEHandle theTE)
 
{   char        theChar;
 
    if ((offset < 0) || (offset > (**theTE).teLength - 1))
        return(true);
 
    theChar = ((char *) *((**theTE).hText))[offset];
    return((theChar == ' ') || (theChar == 0x0D));
}
 
 
 
void InsertTextAtOffset(short offset, char *theBuf, long size, StScrpHandle theStyl, TEHandle theTE)
 
{
    if (size == 0)
        return;
 
    // If inserting at the end of a word and the selection does not begin with
    // a space, insert a space before the insertion.
    if (!WhiteSpaceAtOffset(offset - 1, theTE) &&
         WhiteSpaceAtOffset(offset, theTE) &&
        !WhiteSpace(theBuf[0])) {
 
        TESetSelect(offset, offset, theTE);
        TEKey(' ', theTE);
        offset++;
    }
 
    //  If inserting at the beginning of a word and the selection does not end
    //  with a space, insert a space after the insertion.
    if ( WhiteSpaceAtOffset(offset - 1, theTE) &&
        !WhiteSpaceAtOffset(offset, theTE) &&
        !WhiteSpace(theBuf[size - 1])) {
 
        TESetSelect(offset, offset, theTE);
        TEKey(' ', theTE);
    }
    
    TESetSelect(offset, offset, theTE);
    TEStylInsert(theBuf, size, theStyl, theTE);
    TESetSelect(offset, offset + size, theTE);
    
    return;
}
 
 
 
//
// DropLocationIsFinderTrash returns true if the given dropLocation
// AEDesc is a descriptor of the Finder's Trash.
//
Boolean DropLocationIsFinderTrash ( AEDesc* dropLocation )
 
{   OSErr           result;
    AEDesc          dropSpec;
    FSSpecPtr       theSpec;
    CInfoPBRec      thePB;
    short           trashVRefNum;
    long            trashDirID;
    
    
    
    //  Coerce the dropLocation descriptor to 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))
    {
 
        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 = PBGetCatInfo(&thePB, false);
 
        HUnlock(dropSpec.dataHandle);
        AEDisposeDesc(&dropSpec);
        
        if ( result )
            return(false);
 
        //  If the result is not a directory, it cannot 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;
}