DragStuff.c

/*
    File:       DragStuff.c
 
    Contains:   Drag Manager handlers and associated routines
 
    Written by: Chris White 
 
    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):
                8/5/1999    Karl Groethe    Updated for Metrowerks Codewarror Pro 2.1
                
 
*/
 
// System includes
#ifndef __DRAG__
    #include <Drag.h>
#endif
 
#ifndef __ERRORS__
    #include <Errors.h>
#endif
 
#ifndef __GESTALT__
    #include <Gestalt.h>
#endif
 
#ifndef __CODEFRAGMENTS__
    #include <CodeFragments.h>
#endif
 
#ifndef __SCRIPT__
    #include <Script.h>
#endif
 
#ifndef __RESOURCES__
    #include <Resources.h>
#endif
 
#ifndef __STDDEF__
    #include <stddef.h>
#endif
 
 
 
// Application includes
#ifndef __FRAGMENTTOOL__
    #include "FragmentTool.h"
#endif
 
#ifndef __PROTOTYPES__
    #include "Prototypes.h"
#endif
 
 
#include "Utilities.h"
 
 
 
static Boolean gHasAcceptableDrag = false;
static Boolean gHasHilitedList = false;
 
 
 
 
static Boolean DragItemsAreAcceptable ( DragReference theDrag );
static Boolean DragIsNotInSourceWindow ( DragReference theDrag );
 
 
static pascal OSErr DragTracker ( DragTrackingMessage theMessage, WindowRef theWindow,
                                    void *handlerRefCon, DragReference theDrag );
static pascal OSErr DragTracker ( DragTrackingMessage theMessage, WindowRef theWindow,
                                    void *handlerRefCon, DragReference theDrag );
static pascal OSErr DragReceiver ( WindowRef theWindow, void *handlerRefCon, 
                                    DragReference theDrag );
static pascal OSErr SendDataProc ( FlavorType theType,  void *dragSendRefCon,
                                    ItemReference theItemRef, DragReference theDragRef );
 
 
 
//
// 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 ( DragTracker );
        gDragReceiveHandlerUPP = NewDragReceiveHandlerProc ( DragReceiver );
        gDragSendDataProcUPP = NewDragSendDataProc ( SendDataProc );
    }
    
    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;
}
 
 
 
//
// DragItemsAreAcceptable returns true if the contents (data) of
// the drag are acceptable. This is called by the tracking and 
// receive handlers.
//
static Boolean DragItemsAreAcceptable ( DragReference theDrag )
{
    OSErr           theErr;
    unsigned short  totalItems;
    ItemReference   itemRef;
    Boolean         bAcceptIt;
    OSType          currOSType;
    Size            flavorDataSize;
    
    
    bAcceptIt = false;
    
    
    // this app can only accept the drag of a single item
    theErr = CountDragItems ( theDrag, &totalItems );
    
    if ( theErr == noErr && totalItems == 1 )
    {
        // get the reference number of the dragged item
        theErr = GetDragItemReferenceNumber ( theDrag, 1, &itemRef );
 
        if ( theErr == noErr )
        {
            // check if the item is one of ours
            flavorDataSize = sizeof ( OSType );
            theErr = GetFlavorData ( theDrag, itemRef, kCreatorCode, &currOSType,
                                        &flavorDataSize, 0 );
            
            //#if DEBUGGING
            //if ( theErr ) DebugStr ( "\p GetFlavorData returned an error" );
            //#endif
            
            if ( theErr == noErr ) 
                bAcceptIt = true;
        }
    }
    return bAcceptIt;
}
 
 
 
//
// 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);
}
 
 
 
//
// DragTracker is called by the drag manager whenever a drag is
// over one of the application's windows. Upon entry, the Drag
// Manager has already set the current port current to ÔtheWindowÕ.
pascal OSErr DragTracker ( DragTrackingMessage theMessage, WindowRef theWindow,
                            void* handlerRefCon, DragReference theDrag )
{
    #pragma unused(handlerRefCon)
    RgnHandle   tempRgn;
    Boolean     mouseInList;
    OSErr       err;
    
    err = noErr;
 
    switch ( theMessage )
    {
        case kDragTrackingEnterHandler:
            
            // Any initialization for this window handler.
            gHasAcceptableDrag = DragItemsAreAcceptable ( theDrag );
            gHasHilitedList = false;
            
            // Let the drag manager know if we can't accept this drag
            if ( !gHasAcceptableDrag )
                err = dragNotAcceptedErr;
            break;
            
        case kDragTrackingEnterWindow: 
        case kDragTrackingInWindow:
        case kDragTrackingLeaveWindow:
            
            // Highlighting 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 ( gHasAcceptableDrag )
            {
                // Unless the mouse is leaving the visible area of the
                // window, check if it's in the window's content region
                
                mouseInList = false;
                if ( theMessage != kDragTrackingLeaveWindow )
                {
                    if ( DragIsNotInSourceWindow ( theDrag ) )
                    {
                        Point localPt;
                    
                        (void) GetDragMouse ( theDrag, &localPt, 0L );
                        GlobalToLocal ( &localPt );
                        mouseInList = PtInList ( localPt, GetWListRef ( theWindow ) );
                    }
                }
                
                // If the mouse is in the list and it isn't hilited...
                if ( mouseInList && !gHasHilitedList )
                {
                    Rect    nuSpaceRect;
 
                    GetListRect ( &nuSpaceRect, GetWListRef ( theWindow ) );
 
                    tempRgn = NewRgn ( );
                    RectRgn ( tempRgn, &nuSpaceRect );
 
                    // ...draw the hilight...
                    if ( ShowDragHilite ( theDrag, tempRgn, true ) == noErr )
                        // ... and remember it's now hilited
                        gHasHilitedList = mouseInList;
                    
                    DisposeRgn ( tempRgn );
                }
                
                // else if the mouse is not in the list and the window is hilited...
                else if ( !mouseInList && gHasHilitedList )
                    // ...erase the hilight...
                    if ( HideDragHilite ( theDrag ) == noErr )
                        // ...remember that nothing is hilited
                        gHasHilitedList = false;
            }
            break;
 
        // do nothing for the leaveHandler message
        case kDragTrackingLeaveHandler:
            break;
        
        // let the drag manager know if we didn't recognize the message
        default:
            err = paramErr;
    }
    
    return err;
}
 
 
 
//
// DragReceiver is called by the drag manager whenever an
// item is dropped on one of the application's windows.
//
pascal OSErr DragReceiver ( WindowRef theWindow, void *handlerRefCon, DragReference theDrag )
{
#pragma unused (theWindow, handlerRefCon)
 
    Boolean         bMove;
    ItemReference   itemRef;
    Size            dataSize;
    OSErr           err = noErr;
    unsigned short  numItems, counter;
    tDragData       theData;
    int16           mouseDownModifiers, mouseUpModifiers;
    
    
    
    if (!DragItemsAreAcceptable(theDrag) || (gHasHilitedList == false))
        return dragNotAcceptedErr;
    
                        
    err = GetDragModifiers ( theDrag, nil, &mouseDownModifiers, &mouseUpModifiers );
    if ( err )  goto CleanupAndBail;
    bMove = !((mouseDownModifiers & optionKey) | (mouseUpModifiers & optionKey));
 
    CountDragItems(theDrag, &numItems);
    for (counter = 1; counter <= numItems; counter++)
    {
        err = GetDragItemReferenceNumber(theDrag, counter, &itemRef);
        if (err != noErr)  goto CleanupAndBail;
        
        dataSize = sizeof ( tDragData );
        err = GetFlavorDataSize ( theDrag, itemRef, kCreatorCode, &dataSize );
        if ( dataSize == sizeof ( tDragData ) )
            err = GetFlavorData ( theDrag, itemRef, kCreatorCode, &theData, &dataSize, 0 );
        
        
        // We're going to first remove the drag hilite from here, because
        // not doing so would result in a cleared out list rect, and when
        // we go through the trackingLeaveWindow message up above, the
        // HideDragHilite() call would draw the border again (since it's drawn
        // in XOr mode).
        
        if ( gHasHilitedList )
        {
            if ( HideDragHilite ( theDrag ) == noErr)
                // remember that nothing is hilited
                gHasHilitedList = false;
        }
        
        
        if ( bMove )
            err = MoveWindowFragment ( theData.theWindow, theData.theIndex, theWindow );
        else
            err = CopyWindowFragment ( theData.theWindow, theData.theIndex, theWindow );
    }
    
CleanupAndBail:
    return err;
}
 
 
 
//
// This is the Drag Manager's SendDataProc. It's called after a successful drag, when
// the target application wants some data that was promised by the source application.
//
pascal OSErr SendDataProc ( FlavorType theType,  void* dragSendRefCon,
                          ItemReference theItemRef, DragReference theDragRef )
 
{
    // Gotcha: If we had just dragged out to the Finder, and we had a windowKind of
    // 20, the system would have crashed by now. Why? The Finder uses a windowKind
    // of 20, and thinks this is a drag from one of its windows. It then starts
    // interpreting the window's refCon and inevitably doesn't like what it finds.
    // A windowKind of 20 is now reserved for use by the system.
    
    
    OSErr       result = noErr;
    FSSpec      locationSpec;
    tHeaderHan  theHeader = nil;
    hdrHand     theResource = nil;
 
    
    
    // We use the file type for the HFSPromise flavor type
    // and we don't handle any other pomised flavour types.
    if ( theType != kCFragLibraryFileType )
        cantGetFlavorErr;
    
    
    result = CreateTemporaryFile ( &locationSpec );
    if ( result == noErr )
    {
        OSErr           theErr;
        int16           theIndex = 0;
        int16           theRef, saveFile;
        WindowRef       theWindow;
        ListRef         theList;
        tWindowInfoPtr  theInfo;
        
        theWindow = (WindowRef) dragSendRefCon;
        theInfo = (tWindowInfoPtr) GetWRefCon ( theWindow );
        theList = GetWListRef ( theWindow );
        
        theHeader = (tHeaderHan) NewHandleClear ( sizeof ( tHeader ) );
        theErr = MemError ( );
        if ( theErr ) goto CleanupAndBail;
        
        (*theHeader)->version = 1;      // Current version number
        
        // First, we'll add the content to the file
        while ( GetSelection ( theList, &theIndex ) )
        {
            int16   itemIndex;
            
            itemIndex = GetIndexFromNthWindowItem ( theWindow, theIndex );
            theErr = CopyFragment ( (tHeaderHan) theInfo->dataHandle, &theInfo->fileSpec,
                                        itemIndex, theHeader, &locationSpec );
            if ( theErr )   goto CleanupAndBail;
            theIndex++;
        }
        
        theResource = (hdrHand) NewHandleClear ( offsetof ( cfrgHeader, arrayStart ) );
        (*theResource)->version = 1;        // Current version number
        
        theErr = BuildResource ( (tHeaderHan) theHeader, (Handle) theResource );
        if ( theErr )   goto CleanupAndBail;
        
        saveFile = CurResFile ( );
        theRef = FSpOpenResFile ( &locationSpec, fsRdWrPerm );
        // If the file is already open, it may not be the current resource file
        UseResFile ( theRef );
        AddResource ( (Handle) theResource, kCFragResourceType, kCFragResourceID, "\p" );
        UpdateResFile ( theRef );
        ReleaseResource ( (Handle) theResource );
        CloseResFile ( theRef );
        UseResFile ( saveFile );
        
        
        // Now, set the flavor data of our kCFragLibraryFileType flavor 
        // with an FSSpec to the new file.
        result = SetDragItemFlavorData ( theDragRef, theItemRef, theType,
                                            &locationSpec, sizeof ( FSSpec ), 0L );
        
    }
    
    
    // Any errors? Return a cantGetFlavorErr
    if ( result )
        result = cantGetFlavorErr;
        
    return result;
    
CleanupAndBail:
    
    // We'll leave the temp file for the system to handle. It
    // could still be useful, if only for debugging purposes.
     if ( theHeader )
        DisposeHandle ( (Handle) theHeader );
     
     
     if ( theResource )
     {
        if ( IsAResource ( (Handle) theResource ) )
            ReleaseResource ( (Handle) theResource );
        else
            DisposeHandle ( (Handle) theResource );
    }
    
    return cantGetFlavorErr;
}
 
 
 
//
// This routine just promises the Drag Manager that we'll create a file
// if the user drags out to the Finder
//
OSErr AddHFSPromise ( DragReference theDrag, ItemReference theItem )
{
    OSErr               theErr;
    PromiseHFSFlavor    thePromise;
    
    
    // Here's what we're going to promise to create in our send proc
    thePromise.fileType = kCFragLibraryFileType;
    thePromise.fileCreator = kFourQuestionMarks;
    thePromise.fdFlags = 0;
    thePromise.promisedFlavor = kCFragLibraryFileType;
    
    
    // The promised flavor can be anything, just as long as we add a drag item
    // that has the same type, and set it in our send proc. Failure to do this
    // will cause the zoomback.
    
    theErr = AddDragItemFlavor ( theDrag, theItem, flavorTypePromiseHFS, &thePromise,
                                        sizeof ( PromiseHFSFlavor ), 0L );
    if ( theErr == noErr )
        // Here's the promised flavor we're going to deliver
        theErr = AddDragItemFlavor ( theDrag, theItem, kCFragLibraryFileType, nil, 0L, 0L );
 
    
    return theErr;
}