Source/SVDrag.c

/*
    File:       SVDrag.c
 
    Contains:   
 
    Written by: Original version by Jon Lansdell and Nigel Humphreys.
                            3.1 updates by Greg Sutton. 
 
    Copyright:  Copyright ©1995-1999 by Apple Computer, Inc., All Rights Reserved.
 
                You may incorporate this Apple sample source code into your program(s) without
                restriction. This Apple sample source code has been provided "AS IS" and the
                responsibility for its operation is yours. You are not permitted to redistribute
                this Apple sample source code as "Apple sample source 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 source
                code, but that you've made changes.
 
    Change History (most recent first):
                7/19/1999   Karl Groethe    Updated for Metrowerks Codewarror Pro 2.1
                
 
*/
#include "SVDrag.h"
#include "SVEditWindow.h"
#include "SVEditGlobals.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 )
{
    OSErr           theErr;
    ItemReference   theRef;
    Boolean         bCanAccept = false;
    HFSFlavor       currHFSFlavor;
    Size            flavorDataSize;
    FlavorFlags     currFlavorFlags;
    
    
    
    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 )
{
#pragma unused(handlerRefCon)
 
    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 kDragTrackingEnterHandler:
            // Any initialization for this window handler.
            bHasAcceptableDrag = CanAcceptDragItems ( theDrag );
            
            // Let the drag manager know if we can't accept this drag
            if ( !bHasAcceptableDrag )
                theErr = dragNotAcceptedErr;
        break;
            
        case kDragTrackingEnterWindow: 
            
            caretMovedTime = theTime;
            gCaretOffset = lastOffset = -1;
            bShowCaret = true;
 
            bHasHilitedWindow = false;
            
        break;
        
        case kDragTrackingInWindow:
            // 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 & kDragHasLeftSenderWindow )
                    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 & kDragInsideSenderWindow )
                        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 kDragTrackingLeaveWindow:
            //  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 kDragTrackingLeaveHandler:
        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 & kDragInsideSenderWindow);
}
 
 
 
//
// 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 )
{
#pragma unused(handlerRefCon)
 
    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 ) && 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 & kDragInsideSenderWindow )
        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 = TEStyleNew ( &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 );
        TEStyleInsert ( 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 & kDragHasLeftSenderWindow )
            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 & kDragInsideSenderWindow) && (theWindow != FrontWindow ( )) )
        {
            SelectWindow ( theWindow );
            DoUpdate ( theWindow );
            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 ) )
            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, kZoomDecelerate );
        }
    }
    
    // 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 & kDragInsideSenderWindow) && !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);
    TEStyleInsert(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;
}