FinderDragPro.c

/*
    File:       FinderDragPro.c
    
    Description:    Sample file illustrating drag and drop techniques for use
                with file system objects.  This file illustrates how applications
                can use drag and drop commands in a way compatible with current
                and past versions of the Finder. 
                
                This file is organized into the following sections:
                
                COPYING FILES
                    routines used for copying files.  For promised file flavors, we copy
                    files to the destination directory.  routines in this section manage
                    file copy operations performed when providing promised files.
                SENDING DRAGS
                    routines used for dragging files out of the main window.  This includes
                    code for both standard hfs flavors and promised hfs flavors. 
                RECEIVING DRAGS
                    routines for receiving drags dropped into the main window.  this implementation
                    only accepts drags containing one item that can be either a promised hfs flavor
                    or a standard hfs flavor.  Of interest here is code for handling promised hfs flavors
                    provided by Find File.
                THE MAIN FINDER DRAG PRO WINDOW
                    routines for maintaining the main window.  These routines are responsible for
                    drawing the window's contents, and for handling clicks in the main window.
                MENU HANDLING
                    routines for processing menu commands.  In this implementation, there are
                    only two valid menu commands:  one to quit the application and one to clear
                    the main window's display.
                APPLE EVENT HANDLERS
                    apple event handlers used in the application.  Of interest here is the bogus Finder
                    event handler installed for compatibility with past versions of the Finder.
                EVENT HANDLING
                    general event processing routines used in the application including calls to
                    WaitNextEvent and dispatching based on the event returned.
                MAIN
                    the main routine including initialization code, the main loop, and termination code.
 
    Author:     John Montbriand
                Some techniques borrowed from Pete Gontier's original FinderDragPro.
 
 
    Copyright:  Copyright: © 1999 by Apple Computer, Inc.
                all rights reserved.
    
    Disclaimer: 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.
    
    Change History (most recent first):
    9/9/99 by John Montbriand
*/
 
#include "FinderDragPro.h"
#include "GetIconSuiteFromFinder.h"
#include "FDPUtilities.h"
 
#include <Fonts.h>
#include <Dialogs.h>
#include <Icons.h>
#include <PLStringFuncs.h>
#include <TextUtils.h>
#include <Gestalt.h>
#include <QDOffscreen.h>
#include <Sound.h>
#include <StdIO.h>
#include <String.h>
#include <StdArg.h>
#include <Devices.h>
#include <Folders.h>
#include <Appearance.h>
#include <Threads.h>
 
 
#ifndef __MWERKS__
QDGlobals   qd; /* QuickDraw globals */
#endif
 
    /* application's globals */
Boolean gRunning = true; /* true while the application is running, set to false to quit */
Boolean gAppleEvents = false; /* true if the Apple event Manager is installed */
Boolean gHasDragManager = false; /* true if the Drag Manager is installed */
Boolean gCanTranslucentDrag = false; /* true if translucent dragging is available */
Boolean gHasIconServices = false; /* true if icon services is available */
Boolean gAppearance = false; /* true if the Appearance Manager is installed */
Boolean gColorExists = false; /* true if Color QuickDraw is installed */
Boolean gForground = true; /* true while FDP is the frontmost application */
Boolean gHasThreads = true; /* true if the thread manager is defined */
AEIdleUPP gAEIdleProc = NULL; /* idle proc called from AEInteractWithUser */
 
    /* globals specific to the main window */
DialogPtr gDialog = NULL; /* a pointer to the main dialog */
Rect gIconBox; /* area in the window where the information about the current file is drawn */ 
Rect gIconImage; /* area inside of gIconBox where the icon is drawn */
ControlHandle gPromiseControl; /* control for setting gDragMode to kUsePromiseHFS */
ControlHandle gRegularControl; /* control for setting gDragMode to kUseRegularHFS */
short gDragMode = kUseRegularHFS; /* determines the type of data we will provide for drags */
PicHandle gSplashPict; /* splash image displayed in gIconBox when there is no file selected */
UserItemUPP gFDPUserItemProc = NULL; /* UPP for the icon's user item.  This routine draws into gIconBox */
DragReceiveHandlerUPP gMainReceiveHandler = NULL; /* receive handler for the main dialog */
DragTrackingHandlerUPP gMainTrackingHandler = NULL; /* tracking handler for the main dialog */
DragSendDataUPP gSendDataProc = NULL; /* send data proc for the main dialog -- only used for promise hfs flavors. */
 
    /* file related variables */
Boolean gFileInDisplay = false; /* true when gFileAlias contains an alias handle */
AliasHandle gFileAlias; /* an alias to the last file dragged into the gIconBox */
Boolean gFileUpToDate = false; /* true if we have an icon for the file */
Handle gIconSuite; /* icon suite for displaying the file -- only used if gHasIconServices is false */
IconRef gIconRef; /* an icon services reference to an icon for the file -- only used if gHasIconServices is true */
 
    /* gTargetFile refers to the last known location of the file referred to by gFileAlias.  once
        every 2 seconds or so this location is compared to the location actually referred to by
        gFileAlias.  If they differ, both gTargetFile and the display are updated. */
FSSpec gTargetFile; /* refers to the last known location of the file.  */
OSType gType, gCreator; /* file type and creator for the file in the display */
unsigned short gFlags; /* flags for the file in the display */
 
 
 
 
/* COPYING FILES ------------------------------------------------ */
 
    /* for promise hfs flavors, FDP returns a fsspec referring to the file
    and then it completes the copy operation later after the drag and drop
    command has been completed.  Most of the routines in this section are callbacks
    called by the CopyFileCmd routine defined in FDPUtilities.h.  Normally, your
    application would create a new file containing interesting data rather than
    copying an existing one, but for the purposes of this example this is all
    we do here.  */
 
    /* copy command variables */
DialogPtr gCopyProgressWindow = NULL;
 
/* HitCopyAsWindow is called when DialogSelect indicates an item has been
    hit in the copy progress window (gCopyProgressWindow).  The only item
    we're interested in here is the 'Cancel' button.  */
static void HitCopyAsWindow(DialogPtr theDialog, EventRecord *ev, short itemNo) {
    if (itemNo == kCProCancelItem)
        AbortCopyOperation();
}
 
/* CopyProgressProc is called periodically during the file copy.  It's main function
    is to swap out our task and allow other operating system services to run; however,
    if the copy operation takes longer than kCopyProgressTicksOffset and the copy operation
    is less than 90% complete it will pull up the copy progress window showing the status
    of the copy.   */
static void CopyProgressProc(FSSpec *theFile, short message, long percentCompleted) {
    static long gPercentDrawn = 0;
    static unsigned long gCopyStart = 0;
    switch (message) {
    
        case kCopyStart:
            gPercentDrawn = 0;
            gCopyProgressWindow = NULL;
            gCopyStart = TickCount();
            break;
            
        case kCopyRun:
                /* check the status of the copy status window */
            if (gCopyProgressWindow == NULL) {
                unsigned long now;
                    /* has the copy been active longer than kCopyProgressTicksOffset
                    and the command is less than 90 % complete. */
                now = TickCount();
                if ((now - gCopyStart > kCopyProgressTicksOffset) && (percentCompleted < 90)) {
                    Point wLocation = {25, 25};
                        /* display our progress window.  Locate it relative to the main window. */
                    ParamText(theFile->name, NULL, NULL, NULL);
                    gCopyProgressWindow = GetNewDialog((gAppearance ? kCopyProgressDialog : kPlainCopyProgressDialog), NULL, (WindowPtr) (-1));
                    SetPort(gDialog);
                    LocalToGlobal(&wLocation);
                    MoveWindow(gCopyProgressWindow, wLocation.h, wLocation.v, true);
                    ShowWindow(gCopyProgressWindow);
                    DrawDialog(gCopyProgressWindow); /* draw it */
                    gPercentDrawn = 0;
                }
            } else if (gPercentDrawn != percentCompleted) {
                short itemt;
                Handle itemh;
                Rect itemb;
                GetDialogItem(gCopyProgressWindow, kCProIndicatorItem, &itemt, &itemh, &itemb);
                if (gAppearance) {
                    SetControlValue((ControlHandle) itemh, percentCompleted);
                } else {
                    Str255 textbuf;
                    textbuf[0] = sprintf((char*) textbuf+1, "%d%% Complete", percentCompleted);
                    SetDialogItemText(itemh, textbuf);
                }
                gPercentDrawn = percentCompleted;
            }
            break;  
            
        case kCopyEnd:
            if (gCopyProgressWindow != NULL) DisposeDialog(gCopyProgressWindow);
            break;
    
    }
}
 
static void MyCopyErrorHandler(FSSpec *theFile, short errorCode) {
    if (errorCode == kCannotCopyDirError)
        ParamAlert(kCannotCopyDirAlert, theFile->name, NULL);
    else if (errorCode != userCanceledErr) {
        Str255 errNum;
        NumToString(errorCode, errNum);
        ParamAlert(kCopyFailedAlert, theFile->name, errNum);
    }
}
 
 
 
 
 
/* SENDING DRAGS ------------------------------------------------ */
 
 
 
/* MyDragSendDataProc is the send data procedure used for providing promised
    flavors.  In this routine we begin the copy operation to create the promised
    file by calling StartCopyCommand and return a FSSpec to the caller referring
    to the new file. */
static pascal OSErr MyDragSendDataProc(FlavorType flavorType, void *refcon, ItemReference itemRef, DragReference dragRef) {
    OSErr err = noErr;
    AEDesc dropLocDesc, targetDirDesc;
    FSSpec theTarget, theSource, destinationDir;
    long destinationDirID;
    Str255 targetName;
    CInfoPBRec cat;
    Boolean wasChanged;
    FInfo srcInfo, dstInfo;
    Boolean destCreated;
    
        /* set up locals */
    AECreateDesc(typeNull, NULL, 0, NULL);
    AECreateDesc(typeNull, NULL, 0, NULL);
    destCreated = false;
    
        /* get the drop location where */
    err = GetDropLocation(dragRef, &dropLocDesc);
    if (err != noErr) goto bail;
    
        /* attempt to convert the location record to a FSSpec.  By doing it this way
        instead of looking at the descriptorType field,  we don't need to know what
        type the location originally was or what coercion handlers are installed.  */
    err = AECoerceDesc(&dropLocDesc, typeFSS, &targetDirDesc);
    if (err != noErr) goto bail;
    BlockMoveData(*targetDirDesc.dataHandle, &destinationDir, sizeof(FSSpec));
        
        /* establish the target directory */
    cat.hFileInfo.ioNamePtr = destinationDir.name;
    cat.hFileInfo.ioVRefNum = destinationDir.vRefNum;
    cat.hFileInfo.ioDirID = destinationDir.parID;
    cat.hFileInfo.ioFDirIndex = 0;
    err = PBGetCatInfoSync(&cat);
    if (err != noErr) goto bail;
    destinationDirID = cat.hFileInfo.ioDirID;
    
        /* construct the target FSSpec, verify the name is free... */
    err = GetAliasInfo(gFileAlias, asiAliasName, targetName);
    if (err != noErr) goto bail;
    err = FSMakeFSSpec(destinationDir.vRefNum, destinationDirID, targetName, &theTarget);
    if (err == noErr) { err = dupFNErr; goto bail; }
    if (err != fnfErr) goto bail;
        
        /* find the source file.. */
    err = ResolveAliasQuietly(NULL, gFileAlias, &theSource, &wasChanged);
    if (err != noErr) goto bail;
 
        /* get the source file's finder info */
    err = FSpGetFInfo(&theSource, &srcInfo);
    if (err != noErr) goto bail;
        
        /* create the destination file.  Unless the promised file is created in
        the drag send proc, the Finder will not position the icon correctly.  */
    err = FSpCreate(&theTarget, srcInfo.fdCreator, srcInfo.fdType, smSystemScript);
    if (err != noErr) goto bail;
    destCreated = true;
        
        /* set the destintation's flags */
    err = FSpGetFInfo(&theTarget, &dstInfo);
    if (err != noErr) goto bail;
    dstInfo.fdFlags = (srcInfo.fdFlags & (~kHasBeenInited));
    err = FSpSetFInfo(&theTarget, &dstInfo);
    if (err != noErr) goto bail;
        
        /* begin the copy command.  Now that the file has been created, we defer the
        remainder of the copy operation until after the drag.  CopyFileCmd copies the
        resource fork and the data fork from a background thread.  */
    err = CopyFileCmd(&theSource, &theTarget, CopyProgressProc, MyCopyErrorHandler);
    if (err != noErr) goto bail;
        
        /* return a reference to the new file to the caller */
    err = SetDragItemFlavorData(dragRef, itemRef, flavorType, &theTarget, sizeof(theTarget), 0);
    if (err != noErr) goto bail;
        
        /* clean up and leave */
    AEDisposeDesc(&targetDirDesc);
    AEDisposeDesc(&dropLocDesc);
    return noErr;
    
bail:
    if (destCreated) FSpDelete(&theTarget);
    AEDisposeDesc(&targetDirDesc);
    AEDisposeDesc(&dropLocDesc);
    return err;
}
 
 
 
 
/* DragOut performs a drag operation from the main window to
    somewhere else.  if gDragMode is kUsePromiseHFS, then DragOut
    performs a drag using a promised hfs flavor that triggers a copy
    operation when successful.  If translucent dragging is supported, then
    a translucent drag image of the file's icon is added to the drag. */
static pascal OSErr DragOut(const EventRecord *event) {
    DragReference dragRef;
    Boolean dragRefExists;
    Boolean hasDragImage;
    GWorldPtr imageGWorld;
    RgnHandle maskRgn, dragRgn, insetRgn;
    Point offsetPt, globalOrigin;
    OSErr err;
    Rect imageRect;
    HFSFlavor theFlavor;
    
        /* set up locals */
    dragRefExists = false;
    hasDragImage = false;
    imageGWorld = NULL;
    maskRgn = NULL;
    dragRgn = NULL;
    insetRgn = NULL;
 
        /* create a new dragreference */
    err = NewDrag(&dragRef);
    if (err != noErr) goto bail;
    dragRefExists = true;
    
        /* convert the file alias to a HFSFlavor structure */
    err = MakeHFSFlavorFromAlias(gFileAlias, &theFlavor);
    if (err != noErr) goto bail;
    
        /* add our flavors to the drag */
    if (gDragMode == kUsePromiseHFS) { /* if the option key is down work in promises */ 
        PromiseHFSFlavor phfs;
        
            /* add a send data proc */
        err = SetDragSendProc(dragRef, gSendDataProc, NULL);
        if (err != noErr) goto bail;
 
            /* add the promise flavor first */
        phfs.fileType = theFlavor.fileType;
        phfs.fileCreator = theFlavor.fileCreator;
        phfs.fdFlags = theFlavor.fdFlags;
        phfs.promisedFlavor = kPromisedFlavor;
        err = AddDragItemFlavor(dragRef, kFDPDragItemID, flavorTypePromiseHFS, &phfs, sizeof(phfs), flavorNotSaved);
        if (err != noErr) goto bail;
            /* add the HFS flavor immediately after the promise */
        err = AddDragItemFlavor(dragRef, kFDPDragItemID, kPromisedFlavor, NULL, 0, flavorNotSaved);
        if (err != noErr) goto bail;
 
    } else if (gDragMode == kUseRegularHFS) {
            /* add a hfs flavor to the drag  */
        err = AddDragItemFlavor(dragRef, kFDPDragItemID, flavorTypeHFS, &theFlavor, sizeof(HFSFlavor), flavorNotSaved);
        if (err != noErr) goto bail;
    } else {
        err = paramErr;
        goto bail;
    }
 
        /* add a translucent image, if appropriate */
    if (gCanTranslucentDrag) {
        err = IconsToMaskedPixMap(&gIconImage, gIconSuite, gIconRef, &imageGWorld, &maskRgn);
        if (err == noErr) {
            imageRect = gIconImage;
            OffsetRect(&imageRect, -imageRect.left, -imageRect.top);
            SetPt(&offsetPt, gIconImage.left, gIconImage.top);
            LocalToGlobal(&offsetPt);
            err = SetDragImage(dragRef, GetGWorldPixMap(imageGWorld), maskRgn, offsetPt, kDragStandardTranslucency);
            if (err != noErr) goto bail;
        }
    }
    
        /* calculate an outline-style drag region */
    if ((dragRgn = NewRgn()) == NULL) { err = memFullErr; goto bail; }
    if (gHasIconServices)
        err = IconRefToRgn(dragRgn, &gIconImage, kAlignNone, kIconServicesNormalUsageFlag, gIconRef);
    else err = IconSuiteToRgn(dragRgn, &gIconImage, kAlignNone, gIconSuite);
    if (err != noErr) goto bail;
    if ((insetRgn = NewRgn()) == NULL) { err = memFullErr; goto bail; }
    CopyRgn(dragRgn, insetRgn);
    InsetRgn(insetRgn, 1, 1);
    DiffRgn(dragRgn, insetRgn, dragRgn);
    SetPt(&globalOrigin, qd.thePort->portRect.left, qd.thePort->portRect.top);
    LocalToGlobal(&globalOrigin);
    OffsetRgn(dragRgn, globalOrigin.h, globalOrigin.v);
 
        /* perform the drag operation */
    err = TrackDrag(dragRef, event, dragRgn);
    if (err == userCanceledErr) err = noErr;
    if (err != noErr) goto bail;
 
        /* clean up and leave */
    if (imageGWorld != NULL) DisposeGWorld(imageGWorld);
    if (maskRgn != NULL) DisposeRgn(maskRgn);
    DisposeRgn(dragRgn);
    DisposeRgn(insetRgn);
    DisposeDrag(dragRef);
    return noErr;
 
bail:   /* error recovery */
    if (imageGWorld != NULL) DisposeGWorld(imageGWorld);
    if (maskRgn != NULL) DisposeRgn(maskRgn);
    if (dragRgn != NULL) DisposeRgn(dragRgn);
    if (insetRgn != NULL) DisposeRgn(insetRgn);
    if (dragRefExists) DisposeDrag(dragRef);
    return err;
}
 
 
 
 
/* RECEIVING DRAGS ------------------------------------------------ */
 
 
/* ApproveDragReference is called by the drag tracking handler to determine
    if the contents of the drag can be handled by our receive handler.
    
    This routine checks to see if there is only one item in the drag
    and that item contains either one of two flavors: 'flavorTypeHFS'
    or 'flavorTypePromiseHFS'.  If the option key was held down at
    the beginning of the drag, then this routine only checks for
    the 'flavorTypePromiseHFS' in the first drag item.
    We accept one item and one item only.
 
    Note that if a flavor can't be found, it's not really an
    error; it only means the flavor wasn't there and we should
    not accept the drag. Therefore, we translate 'badDragFlavorErr'
    into a 'false' value for '*approved'. */
static pascal OSErr ApproveDragReference(DragReference theDragRef, Boolean *approved) {
 
    OSErr err;
    UInt16 itemCount;
    DragAttributes dragAttrs;
    FlavorFlags flavorFlags;
    ItemReference theItem;
        
        /* we cannot drag to our own window */
    if ((err = GetDragAttributes(theDragRef, &dragAttrs)) != noErr) goto bail;
    if ((dragAttrs & kDragInsideSenderWindow) != 0) { err = userCanceledErr; goto bail; }
    
        /* we only accept drags containing one item */
    if ((err = CountDragItems(theDragRef, &itemCount)) != noErr) goto bail;
    if (itemCount != 1) { err = paramErr; goto bail; }
        
        /* gather information about the drag & a reference to item one. */
    if ((err = GetDragItemReferenceNumber(theDragRef, 1, &theItem)) != noErr) goto bail;
        
        /* check for flavorTypeHFS */
    err = GetFlavorFlags(theDragRef, theItem, flavorTypeHFS, &flavorFlags);
    if (err == noErr) {
        *approved = true;
        return noErr;
    } else if (err != badDragFlavorErr)
        goto bail;
        
        /* check for flavorTypePromiseHFS */
    err = GetFlavorFlags(theDragRef, theItem, flavorTypePromiseHFS, &flavorFlags);
    if (err == noErr) {
        *approved = true;
        return noErr;
    } else if (err != badDragFlavorErr)
        goto bail;
        
        /* none of our flavors were found */
    *approved = false;
    return noErr;
bail:
        /* an error occured, clean up.  set result to false. */
    *approved = false;
    return err;
}
 
 
    /* these routines are used both in the receive handler and inside of the
        tracking handler.  The following variables are shared between MyDragTrackingHandler
        and MyDragReceiveHandler.  */
static Boolean gApprovedDrag = false; /* set to true if the drag is approved */
static Boolean gInIconBox = false; /* set true if the drag is inside our drop box */
 
 
/* MyDragTrackingHandler is called for tracking the mouse while a drag is passing over our
    window.  if the drag is approved, then the drop box will be hilitied appropriately
    as the mouse passes over it.  */
static pascal OSErr MyDragTrackingHandler(DragTrackingMessage message, WindowPtr theWindow, void *refCon, DragReference theDragRef) {
        /* we're drawing into the image well if we hilite... */
    switch (message) {
    
        case kDragTrackingEnterWindow:
            {   Point mouse;
                gApprovedDrag = false;
                if (theWindow == FrontWindow()) {
                    if (ApproveDragReference(theDragRef, &gApprovedDrag) != noErr) break;
                    if ( ! gApprovedDrag ) break;
                    SetPort(theWindow);
                    GetMouse(&mouse);
                    if (PtInRect(mouse, &gIconBox)) {  /* if we're in the box, hilite... */
                        SetPort(theWindow);
                        gInIconBox = (ShowDragHiliteBox(theDragRef, &gIconBox) == noErr);
                    }
                }
            }
            break;
 
        case kDragTrackingInWindow:
            if (gApprovedDrag) {
                Point mouse;
                SetPort(theWindow);
                GetMouse(&mouse);
                if (PtInRect(mouse, &gIconBox)) {
                    if ( ! gInIconBox) {  /* if we're entering the box, hilite... */
                        SetPort(theWindow);
                        gInIconBox = (ShowDragHiliteBox(theDragRef, &gIconBox) == noErr);
                    }
                } else if (gInIconBox) {  /* if we're exiting the box, unhilite... */
                    HideDragHilite(theDragRef);
                    gInIconBox = false;
                }
            }
            break;
 
        case kDragTrackingLeaveWindow:
            if (gApprovedDrag && gInIconBox) {
                HideDragHilite(theDragRef);
            }
            gApprovedDrag = gInIconBox = false;
            break;
    }
    return noErr; // there's no point in confusing Drag Manager or its caller
}
 
 
 
 
/* SetDragAndDropDirectory sets the drop location for the drag reference
    to the indicated directory.  This routine will be called from inside of the
    drag receive handler when a promised hfs flavor about to be received.  The purpose
    of the call to SetDropLocation is to tell the sending application where to put the
    promised file or folder.  This routine sets up parameters referring to the
    (vRefNum, dirID) directory and calls SetDropLocation. */
static OSErr SetDragAndDropDirectory(DragReference theDragRef, short vRefNum, long dirID) {
    Str255 name;
    CInfoPBRec cat;
    FSSpec spec;
    AliasHandle theAlias;
    AEDesc theDropLocation;
    OSErr err;
        /* set up locals */
    theAlias = NULL;
    AECreateDesc(typeNull, NULL, 0, NULL);
        /* find out the folder's parent ID */
    cat.hFileInfo.ioNamePtr = name;
    cat.hFileInfo.ioVRefNum = vRefNum;
    cat.hFileInfo.ioDirID = dirID;
    cat.hFileInfo.ioFDirIndex = -1;
    err = PBGetCatInfoSync(&cat);
    if (err != noErr) goto bail;
        /* now convert it to a FSSpec record */
    err = FSMakeFSSpec(vRefNum, cat.dirInfo.ioDrParID, name, &spec);
    if (err != noErr) goto bail;
        /* convert the FSSpec to an alias */
    err = NewAlias(NULL, &spec, &theAlias);
    if (err != noErr) goto bail;
        /* and make it into a descriptor record */
    HLock((Handle) theAlias);
    err = AECreateDesc(typeAlias,*theAlias, GetHandleSize((Handle) theAlias), &theDropLocation);
    if (err != noErr) goto bail;
        /* set it as the drag's drop location */
    err = SetDropLocation(theDragRef, &theDropLocation);
    if (err != noErr) goto bail;
        /* we're done, clean up */
    AEDisposeDesc(&theDropLocation);
    DisposeHandle((Handle) theAlias);
    return noErr;
bail:
    AEDisposeDesc(&theDropLocation);
    if (theAlias != NULL) DisposeHandle((Handle) theAlias);
    return err;
}
 
 
 
 
/* MyDragReceiveHandler is the receive handler for the main window.  It is called
    when a file or folder (or a promised file or folder) is dropped into the drop
    box in the main window.  Here, if the drag reference has been approved in the
    track drag call, we handle three different cases:
    
    1. standard hfs flavors,
    
    2. promised flavors provided by find file,
    
    3. promised flavors provided by other applications.
    
    After receiving a file, the display is updated.  Any promised files are
    placed on the desktop. */
static pascal OSErr MyDragReceiveHandler(WindowPtr theWindow, void *refcon, DragReference theDragRef) {
 
    ItemReference theItem;
    HFSFlavor targetFile;
    PromiseHFSFlavor targetPromise;
    FSSpec targetSpec;
    Size theSize;
    OSErr err;
    
        /* validate the drag.  Recall the receive handler will only be called after
        the tracking handler has received a kDragTrackingInWindow event.  As a result,
        the gApprovedDrag and gInIconBox will be defined when we arrive here.  Hence,
        there is no need to spend extra time validating the drag at this point. */
    if ( ! (gApprovedDrag && gInIconBox) ) { err = userCanceledErr; goto bail; }
    
        /* get the first item reference */
    if ((err = GetDragItemReferenceNumber(theDragRef, 1, &theItem)) != noErr) goto bail;
        
        /* try to get a  HFSFlavor*/
    theSize = sizeof(HFSFlavor);
    err = GetFlavorData(theDragRef, theItem, flavorTypeHFS, &targetFile, &theSize, 0);
    if (err == noErr) {
        SetNewDisplay(&targetFile);
        return noErr;
    } else if (err != badDragFlavorErr) goto bail;
    
        /* try to get a  promised HFSFlavor*/
    theSize = sizeof(PromiseHFSFlavor);
    err = GetFlavorData(theDragRef, theItem, flavorTypePromiseHFS, &targetPromise, &theSize, 0);
    if (err != noErr) goto bail;
    
        /* check for a drop from find file */
    if (targetPromise.promisedFlavor == kPromisedFlavorFindFile) {
    
            /* from find file, no need to set the file location... */
        theSize = sizeof(FSSpec);
        err = GetFlavorData(theDragRef, theItem, targetPromise.promisedFlavor, &targetSpec, &theSize, 0);
        if (err != noErr) goto bail;
    
    } else {
        short deskVol;
        long deskDir;
            /* we'll put promised files on the desktop */
        err = FindFolder(kOnSystemDisk, kDesktopFolderType, kCreateFolder, &deskVol, &deskDir);
        if (err != noErr) goto bail;
            /* set the drag destination to the desktop */
        err = SetDragAndDropDirectory(theDragRef, deskVol, deskDir);
        if (err != noErr) goto bail;
        
            /* get the promised file */
        theSize = sizeof(FSSpec);
        err = GetFlavorData(theDragRef, theItem, targetPromise.promisedFlavor, &targetSpec, &theSize, 0);
        if (err != noErr) goto bail;
        
            /* verify the promise structure, make sure it's valid.  */
        err = ValidFSSpec(&targetSpec);
        if (err != noErr) goto bail;
    }
        /* display the located file*/
    SetNewDisplay(&targetFile);
    return noErr;
bail:
    return err;
}
 
 
 
 
/* THE MAIN FINDER DRAG PRO WINDOW ------------------------------------------------ */
 
 
/* SetNewDisplay is called to set the file or folder being displayed in the main window.
    Here, structures are deallocated and an alias is saved referring to the file. 
    SetNewDisplay is called from the drag receive handler and since it is not
    safe to call "GetIconSuiteFromFinder()" from inside of the drag receive handler
    (it uses apple events), the flag gFileUpToDate is used to defer that operation
    until the next time ValidateFDPWindowDisplay.  ValidateFDPWindowDisplay is
    called from the main loop.  If targetFile is NULL, then the display is cleared. */
void SetNewDisplay(HFSFlavor *targetFile) {
        /* remove the old file */
    if (gFileInDisplay) {
        DisposeHandle((Handle) gFileAlias);
        if (gFileUpToDate) {
            if (gHasIconServices)
                ReleaseIconRef(gIconRef);
            else DisposeIconSuite(gIconSuite, true);
            gFileUpToDate = false;
        }
        gFileInDisplay = false;
    }
        /* set the new file, if there is one */
    if (targetFile != NULL) {
        gType = targetFile->fileType;
        gCreator = targetFile->fileCreator;
        gFlags = targetFile->fdFlags;
        gTargetFile = targetFile->fileSpec;
        gFileInDisplay = (NewAliasMinimal(&gTargetFile, &gFileAlias) == noErr);
    }
        /* post an update for the window */
    SetPort(gDialog);
    InvalRect(&gIconBox);
}
 
 
/* ValidateFDPWindowDisplay is called from the main loop, before update events,
    and when the application is switching in.  It works together with SetNewDisplay.
    SetNewDisplay is called from the drag receive handler where sometimes it is
    unsafe to retrieve a file's icons.  Instead of gathering the icon information inside
    of SetNewDisplay, SetNewDisplay sets the flag gFileUpToDate.  ValidateFDPWindowDisplay
    watches gFileUpToDate.  When this flag is false and there is a new file to be displayed,
    ValidateFDPWindowDisplay retrieves the icon and posts an update event for the window.
    To minimize resolve alias calls, if the icons have been fetched, then the alias is only
    verified at most once per second. */
static OSErr ValidateFDPWindowDisplay(void) {
    Boolean wasChanged;
    OSErr err;
    static unsigned long gLastValidCall = 0;
    unsigned long now;
    
    wasChanged = false;
 
    if (gFileInDisplay) {
        now = TickCount();
        if ( ( ! gFileUpToDate) || (gLastValidCall == 0) || (now - gLastValidCall >= 60)) {
            gLastValidCall = now;
            err = ResolveAliasQuietly(NULL, gFileAlias, &gTargetFile, &wasChanged);
            if (err != noErr) goto bail;
            if ( ! gFileUpToDate) {
                if (gHasIconServices) {
                    SInt16 theLabel;
                    err = GetIconRefFromFile(&gTargetFile, &gIconRef, &theLabel);
                } else err = GetIconSuiteFromFinder(&gTargetFile, &gIconSuite);
                if (err != noErr) goto bail;
                gFileUpToDate = true;
                wasChanged = true;
            }
        }
    }
    if (wasChanged) {
        SetPort(gDialog);
        InvalRect(&gIconBox);
    }
    return noErr;
bail:
    SetNewDisplay(NULL);
    return err;
}
 
 
 
/* MyFDPUserItem draws the image in the drop box in the window.  If the window
    is not the active window, then the image is drawn grayed out.  If appearance
    is in use, then the drop box is drawn as a generic well. */
static pascal void MyFDPUserItem(WindowPtr theWindow, DialogItemIndex itemNo) {
    Rect rdraw;
    RGBColor sForground, sBackground;
    RGBColor rgbWhite = {0xFFFF, 0xFFFF, 0xFFFF}, rgbBlack = {0, 0, 0}, rgbGray = {0x7FFF, 0x7FFF, 0x7FFF};
        /* set up */
    SetPort(theWindow);
        /* set the colors we're using for drawing */
    if (gColorExists) {
        GetForeColor(&sForground);
        GetBackColor(&sBackground);
        RGBForeColor(&rgbBlack);
        RGBBackColor(&rgbWhite);
    }
        /* draw the frame */
    if (gAppearance) {
        ThemeDrawState themeDrawState;
        themeDrawState = (gForground && (theWindow == FrontWindow())) ? kThemeStateActive : kThemeStateInactive;
        DrawThemeGenericWell(&gIconBox, themeDrawState,  true);
 
    } else {
        rdraw = gIconBox;
        EraseRect(&gIconBox);
        InsetRect(&rdraw, -1, -1);
        FrameRect(&rdraw);
    }
        /* draw the file information */
    if (gFileInDisplay && gFileUpToDate) {
        OSErr err;
        short baseLine;
        long textLength;
        char textbuf[256];
        FontInfo fin;
        Str255 name;
            /* begin drawing */
        TextFont(kFontIDGeneva); /* geneva */
        TextSize(9);
        GetFontInfo(&fin);
            /* draw the icon image */
        if (gHasIconServices)
            err = PlotIconRef(&gIconImage, kAlignNone, kTransformNone, kIconServicesNormalUsageFlag, gIconRef);
        else err = PlotIconSuite(&gIconImage, kAlignNone, kTransformNone, gIconSuite);
            /* draw the file name */
        baseLine = gIconImage.bottom + fin.ascent;
        memcpy(name, gTargetFile.name, gTargetFile.name[0] + 1);
        TruncString(gIconBox.right - gIconBox.left - 4, name,  truncEnd);
        MoveTo((gIconBox.left + gIconBox.right - StringWidth(name))/2, baseLine);
        DrawString(name);
            /* draw the location */
        textLength = sprintf(textbuf, "vRefNum %d, parID %d", gTargetFile.vRefNum, gTargetFile.parID);
        baseLine += fin.ascent + fin.descent;
        MoveTo((gIconBox.left + gIconBox.right - TextWidth(textbuf, 0, textLength))/2, baseLine);
        DrawText(textbuf, 0, textLength);
            /* draw the type and flags */
        textLength = sprintf(textbuf, "%.4s %.4s  0x%.4X", &gType, &gCreator, gFlags);
        baseLine += fin.ascent + fin.descent;
        MoveTo((gIconBox.left + gIconBox.right - TextWidth(textbuf, 0, textLength))/2, baseLine);
        DrawText(textbuf, 0, textLength);
            /* end drawing */
        TextFont(systemFont); /* back to the system font */
        TextSize(12);
    } else {
        Rect rPic;
        rPic = (**gSplashPict).picFrame;
        OffsetRect(&rPic, -rPic.left, -rPic.top);
        OffsetRect(&rPic, (gIconBox.left + gIconBox.right - rPic.right)/2, (gIconBox.top + gIconBox.bottom - rPic.bottom)/2);
        DrawPicture(gSplashPict, &rPic);
    }
        /* gray the image if we're in the background */
    if ( ! (gForground && (theWindow == FrontWindow())) ) {
        GrayOutBox(&gIconBox);
    }
        /* restore previous colors */
    if (gColorExists) {
        RGBForeColor(&sForground);
        RGBBackColor(&sBackground);
    }
}
 
 
 
/* CreateFDPWindow creates the main finder drag pro window. It installs
    tracking handlers and sets up the controls and user items in the window. */
static pascal OSErr CreateFDPWindow(DialogPtr *theDialog) {
    OSErr err;
    Boolean installedTracker, installedReceiver;
    short itemt;
    Rect itemb;
    Handle itemh;
    DialogPtr dialog;
    
        /* set up locals for recovery */
    dialog = NULL;
    installedTracker = installedReceiver = false;
    
        /* create the dialog */
    dialog = GetNewDialog(kFDPDialogResource, NULL, (WindowPtr) (-1));  
    if (dialog == NULL) { err = memFullErr; goto bail; }
    
        /* grab and set up our dialog items */
    GetDialogItem(dialog, kFDPUserItem, &itemt, (Handle*) &itemh, &gIconBox);
    SetDialogItem(dialog, kFDPUserItem, userItem, (Handle) gFDPUserItemProc, &gIconBox);
    GetDialogItem(dialog, kFDPpromiseItem, &itemt, (Handle*) &gPromiseControl, &itemb);
    GetDialogItem(dialog, kFDPhfsItem, &itemt, (Handle*) &gRegularControl, &itemb);
        
        /* set initial control values */
    SetControlValue(gPromiseControl, (gDragMode == kUsePromiseHFS ? 1 : 0));
    SetControlValue(gRegularControl, (gDragMode == kUseRegularHFS ? 1 : 0));
 
        /* calculate the drawn icon's boundary */
    SetRect(&gIconImage, 0, 0, 32, 32);
    OffsetRect(&gIconImage, (gIconBox.right + gIconBox.left - 32) / 2, gIconBox.top + 16);
 
        /* install the drag handlers */
    err = InstallTrackingHandler(gMainTrackingHandler, dialog, NULL);
    if (err != noErr) { err = memFullErr; goto bail; }
    installedTracker = true;
    err = InstallReceiveHandler(gMainReceiveHandler, dialog, NULL);
    if (err != noErr) { err = memFullErr; goto bail; }
    installedReceiver = true;
 
        /* done, window complete */
    *theDialog = dialog;
    return noErr;
 
bail:
    if (installedReceiver)
        RemoveReceiveHandler(gMainReceiveHandler, dialog);
    if (installedTracker)
        RemoveTrackingHandler(gMainTrackingHandler, dialog);
    if (dialog != NULL) DisposeDialog(dialog);
    return err;
}
 
/* DisposeFDPWindow disposes of any structures allocated for the    
    main finder drag pro window.  the call to SetNewDisplay with a
    NULL parameter forces it to deallocate any icons and the
    alias allocated for the item being displayed. */
static void DisposeFDPWindow(DialogPtr theDialog) {
    SetNewDisplay(NULL);
    RemoveTrackingHandler(gMainTrackingHandler, theDialog);
    RemoveReceiveHandler(gMainReceiveHandler, theDialog);
    DisposeDialog(theDialog);
}
 
 
 
 
/* HitFDPWindow is called when the dialog manager's DialogSelect indicates
    that an item in the main finder drag pro window has been hit.  Here,
    either we begin a drag, or we adjust the promise/regular hfs controls */
static void HitFDPWindow(DialogPtr theDialog, EventRecord *event, short itemNo) {
    switch (itemNo) {
        case kFDPUserItem:
            if (gFileInDisplay) {
                Point where;
                Boolean isInSuite;
                OSErr err, err2;
                    /* set up */
                SetPort(theDialog);
                where = event->where;
                GlobalToLocal(&where);
                    /* if the click is in the icon.... */
                if (gHasIconServices)
                    isInSuite = PtInIconRef(&where, &gIconImage, kAlignNone, kIconServicesNormalUsageFlag,  gIconRef);
                else isInSuite = PtInIconSuite(where, &gIconImage, kAlignNone, gIconSuite);
                if (isInSuite) {
                        /* draw the icon as 'selected' */
                    if (gHasIconServices)
                        err = PlotIconRef(&gIconImage, kAlignNone, kTransformSelected, kIconServicesNormalUsageFlag, gIconRef);
                    else err = PlotIconSuite(&gIconImage, kAlignNone, kTransformSelected, gIconSuite);
                    if (err == noErr) {
                            /* is it a drag command? */
                        if (WaitMouseMoved (event->where)) {
                                /* if it is, drag it */
                            err = DragOut(event);
                        }
                            /* restore the icon's original view */
                        if (gHasIconServices)
                            err2 = PlotIconRef(&gIconImage, kAlignNone, kTransformNone, kIconServicesNormalUsageFlag, gIconRef);
                        else err2 = PlotIconSuite(&gIconImage, kAlignNone, kTransformNone, gIconSuite);
                        if (err == noErr) err = err2;
                    }
                    if (err != noErr) {
                        Str255 errStr;
                        NumToString(err, errStr);
                        ParamAlert(kDragFailedAlert, errStr, NULL);
                    }
                }
            }
            break;
            
        case kFDPhfsItem: /* use regular hfs drags */
            gDragMode = kUseRegularHFS;
            SetControlValue(gPromiseControl, 0);
            SetControlValue(gRegularControl, 1);
            break;
            
        case kFDPpromiseItem: /* use promised hfs drags */
            gDragMode = kUsePromiseHFS;
            SetControlValue(gPromiseControl, 1);
            SetControlValue(gRegularControl, 0);
            break;
    }
}
 
 
 
 
/* MENU HANDLING ------------------------------------------------ */
 
 
 
/* ResetMenus is called to reset the menus immediately before
    either MenuSelect or MenuKey is called.  Here, we disable the
    quit command during file copies. */
static void ResetMenus(void) {
    MenuHandle fileMenu;
        /* get the file menu from the current menu list */
    fileMenu = GetMenuHandle(mFile);
        /* disable quit if we're in the middle of copying a file */
    if (CopyFileInProgress())
        DisableItem(fileMenu, iQuit);
    else EnableItem(fileMenu, iQuit);
}
 
 
/* DoMenuCommand is called after either MenuSelect of MenuKey.  The
    parameter rawMenuSelectResult is the result from one of these two routines. 
    DoMenuCommand parses this result into its two parts, and dispatches
    the menu command as appropriate. */
static void DoMenuCommand(long rawMenuSelectResult) {
    short menu, item;
        /* decode the MenuSelect result */
    menu = (rawMenuSelectResult >> 16);
    if (menu == 0) return;
    item = (rawMenuSelectResult & 0x0000FFFF);
        /* dispatch on result */
    switch (menu) {
        case mApple:
            if (item == iAbout) {
                    /* show the about box. */
                ParamAlert(kAboutBoxAlert, NULL, NULL);
            } else if (item >= iFirstAppleItem) {
                Str255 deskAccName;
                    /* open an apple menu item. */
                GetMenuItemText(GetMenuHandle(mApple), item, deskAccName);
                OpenDeskAcc(deskAccName);
            }
            break;
        case mFile:
            if (item == iQuit) gRunning = false; /* file.quit == quit */
            break;
        case mEdit:
            if (item == iClear) SetNewDisplay(NULL); /* edit.clear == clear the display */
            break;
    }
        /* unhilite the menu once we're done the command */
    HiliteMenu(0);
}
 
 
 
/* APPLE EVENT HANDLERS ------------------------------------------------ */
 
 
/* BogusFinderEventHandler is an event handler installed for compatibility reasons
    as described below.  Not all finders behave in this way, but or universal compatibility,
    applications wishing to drag and drop hfs flavors to the finder should add one of
    these handlers to their code.  In cases where the finder does have this problem,
    this routine will lie dormant and will not be used.
 
    When a HFSFlavor item is provided to the Finder and the drop location is on a
    different volume, the finder will attempt to copy the file to the new volume.  As
    a part of the copy operation, the finder sends some apple events to the frontmost
    application (assuming it's the finder) that are used to run the finder's copy progress
    windows.  If our application happens to be frontmost, those events will be sent to
    us which will cause the Finder to cancel.  By installing the handler, we avoid this problem
    by ignoring the bogus events ourselves.,.... */
static pascal OSErr BogusFinderEventHandler(const AppleEvent *theEvent, AppleEvent *theReply, long refcon) {
    return noErr;
}
 
 
 
static OSErr GotReqParam(const AppleEvent *apple_event) {
    DescType retType;
    Size actSize;
    OSErr err;
    err = AEGetAttributePtr(apple_event, keyMissedKeywordAttr, typeWildCard, &retType, NULL, 0, &actSize);
    if (err == errAEDescNotFound)
        return noErr;
    else if (err == noErr)
        return errAEEventNotHandled;
    else return err;
}
 
/* OpenApplication is an apple event handler called for 'open application' apple events. */
static pascal OSErr OpenApplication(const AppleEvent *appleEvt, AppleEvent* reply, long refcon) {
    OSErr err;
    if ((err = GotReqParam(appleEvt)) != noErr) return err;
    err = CreateFDPWindow(&gDialog);
    if (err != noErr) {
        Str255 errStr;
        NumToString(err, errStr);
        ParamAlert(kOpenAppFailedAlert, errStr, NULL);
    }
    return noErr;
}
 
/* CloseApplication is an apple event handler called for 'close application' apple events. */
static pascal OSErr CloseApplication(const AppleEvent *appleEvt, AppleEvent* reply, long refcon) {
    OSErr err;
    if ((err = GotReqParam(appleEvt)) != noErr) return err;
    gRunning = false;
    return noErr;
}
 
 
 
/* EVENT HANDLING ------------------------------------------------ */
 
 
/* HandleNextEvent handles the event in the event record *ev dispatching
    the event to appropriate routines.   */
static void HandleNextEvent(EventRecord *ev) {
    DialogPtr theDialog;
    WindowPtr theWindow;
    short itemNo;
    
        /* dialog pre-processing */
    if (((ev->what == keyDown) || (ev->what == autoKey)) && ((ev->modifiers & cmdKey) != 0)) {
        ResetMenus();
        DoMenuCommand(MenuKey((char) (ev->message & charCodeMask)));
    } else if (ev->what == osEvt) {
        if ( (((ev->message >> 24) & 0x0FF) == suspendResumeMessage) && ((ev->message & resumeFlag) != 0)) {
                /* switching in */
            ValidateFDPWindowDisplay();
            gForground = true;
        } else gForground = false;
        if (gDialog != NULL) {
            HiliteControl(gPromiseControl, (gForground ? 0 : 255));
            HiliteControl(gRegularControl, (gForground ? 0 : 255));
            DrawDialog(gDialog);
        }
    } else if (ev->what == updateEvt) {
        if (gDialog == ((DialogPtr) ev->message))
            ValidateFDPWindowDisplay();
    } else if (ev->what == activateEvt) {
        if (gDialog == ((DialogPtr) ev->message)) {
            HiliteControl(gPromiseControl, ((gDialog == FrontWindow()) ? 0 : 255));
            HiliteControl(gRegularControl, ((gDialog == FrontWindow()) ? 0 : 255));
            DrawDialog(gDialog);
        }
    }
 
        /* handle clicks in the dialog window */
    if (IsDialogEvent(ev))
        if (DialogSelect(ev, &theDialog, &itemNo)) {
            if (theDialog == gDialog)
                HitFDPWindow(theDialog, ev, itemNo);
            else if (theDialog == gCopyProgressWindow)
                HitCopyAsWindow(theDialog, ev, itemNo);
        }
 
        /* clicks and apple events... */
    if (ev->what == kHighLevelEvent && gAppleEvents) {
        AEProcessAppleEvent(ev);
    } else if (ev->what == nullEvent) {
        ValidateFDPWindowDisplay(); /* revalidate once every two seconds */
    } else if (ev->what == mouseDown)
        switch (FindWindow(ev->where, &theWindow)) {
            
                /* menu bar clicks */
            case inMenuBar:
                ResetMenus();
                DoMenuCommand(MenuSelect(ev->where));
                break;
                
                /* clicks in the close box, close the app */
            case inGoAway:
                if (TrackGoAway(theWindow, ev->where)) {
                    gRunning = false;
                }
                break;
                
                /* allow window drags */
            case inDrag:
                if (theWindow == FrontWindow()) {
                    Rect boundsRect = { -32000, -32000, 32000, 32000};
                    DragWindow(theWindow, ev->where, &boundsRect);
                }
                break;
                
                /* desktop clicks, etc... */
            case inSysWindow:
                SystemClick(ev, theWindow);
                break;
        }
}
 
 
/* ProcessNextEvent calls WaitNextEvent to get the next event and then it passes
    the event along to the HandleNextEvent routine.  sleepTime is passed to the
    WaitNextEvent routine in the sleep parameter. */
void ProcessNextEvent(long sleepTime) {
    EventRecord ev;
        /* get the next event */
    if ( ! WaitNextEvent(everyEvent, &ev,  sleepTime, NULL) )
        ev.what = nullEvent;
    HandleNextEvent(&ev);
}
 
/* FDPIdleProcedure is the idle procedure called by AEInteractWithUser while we are waiting
    for the application to be pulled into the forground.  It simply passes the event along
    to HandleNextEvent */
static pascal Boolean FDPIdleProcedure(EventRecord *theEvent, long *sleepTime, RgnHandle *mouseRgn) {
    HandleNextEvent(theEvent);
    return false;
}
 
 
/* ParamAlert is a general alert handling routine.  If Apple events exist, then it
    calls AEInteractWithUser to ensure the application is in the forground, and then
    it displays an alert after passing the s1 and s2 parameters to ParamText. */
short ParamAlert(short alertID, StringPtr s1, StringPtr s2) {
    if (gAppleEvents)
        AEInteractWithUser(kNoTimeOut, NULL, gAEIdleProc);
    ParamText(s1, s2, NULL, NULL);
    return Alert(alertID, NULL);
}
 
 
 
 
/* MAIN ------------------------------------------------ */
 
 
int main(void) {
    OSErr err;
    long response;
    
    
        /* ***** set up the os ***** */
        /* set up our app */
    SetApplLimit(GetApplLimit());
    MaxApplZone();
    InitGraf(&qd.thePort);
    InitFonts();
    InitWindows();
    TEInit();
    InitMenus();
    InitDialogs(0);
    FlushEvents(everyEvent, 0);
    InitCursor();
            
            
        /* ***** look at machine features ***** */
        /* apple events??? */
    if (Gestalt(gestaltAppleEventsAttr, &response) != noErr) response = 0;
    gAppleEvents = ((response & (1<<gestaltAppleEventsPresent)) != 0);
        /* check for the drag manager & translucent feature??? */
    if (Gestalt(gestaltDragMgrAttr, &response) != noErr) response = 0;
    gHasDragManager = ((response & (1 << gestaltDragMgrPresent)) != 0);
    gCanTranslucentDrag = ((response & (1 << gestaltDragMgrHasImageSupport)) != 0);
        /* icon services??? */
    if (Gestalt(gestaltIconUtilitiesAttr, &response) != noErr) response = 0;
    gHasIconServices = ((response & (1 << gestaltIconUtilitiesHasIconServices)) != 0);
        /* colour quickdraw */
    if (Gestalt(gestaltQuickdrawVersion, &response) != noErr) response = 0;
    gColorExists = (response >= gestalt8BitQD);
        /* colour quickdraw */
    if (Gestalt(gestaltThreadMgrAttr, &response) != noErr) response = 0;
    gHasThreads = ((response & (1<<gestaltThreadMgrPresent)) != 0);
        /* appearance */
    if (Gestalt(gestaltAppearanceAttr, &response) != noErr) response = 0;
    if ((response & (1<<gestaltAppearanceExists)) != 0) {
        err = RegisterAppearanceClient();
        if (err != noErr) goto bail;
        gAppearance = true;
    }
        
        
        /* ***** abort if features we need aren't present ***** */
    if ( ! gHasDragManager) { err = kNoDragMgrError; goto bail; }
    if ( ! gHasThreads) { err = kNoThreadMgrError; goto bail; }
        
        
        /* ***** install apple event handlers ***** */
        /* install our Apple event handlers */
    if (gAppleEvents) {
        AEEventHandlerUPP aehandler;
            /* the bogus finder event handler */
        aehandler = NewAEEventHandlerProc(BogusFinderEventHandler);
        if (aehandler == NULL) { err = memFullErr; goto bail; }
        err = AEInstallEventHandler ('cwin','****', aehandler, 0, false);
        if (err != noErr) goto bail;
            /* standard apple events */
        aehandler = NewAEEventHandlerProc(OpenApplication);
        if (aehandler == NULL) { err = memFullErr; goto bail; }
        err = AEInstallEventHandler(kCoreEventClass, kAEOpenApplication, aehandler, 0, false);
        if (err != noErr) goto bail;
        aehandler = NewAEEventHandlerProc(CloseApplication);
        if (aehandler == NULL) { err = memFullErr; goto bail; }
        err = AEInstallEventHandler(kCoreEventClass, kAEQuitApplication, aehandler, 0, false);
        if (err != noErr) goto bail;
    }
 
 
        /* ***** initialize the application's globals ***** */
        /* set up our routine descriptors */
    gFDPUserItemProc = NewUserItemProc(MyFDPUserItem);
    if (gFDPUserItemProc == NULL) { err = memFullErr; goto bail; }
    gMainTrackingHandler = NewDragTrackingHandlerProc(MyDragTrackingHandler);
    if (gMainTrackingHandler == NULL) { err = memFullErr; goto bail; }
    gMainReceiveHandler = NewDragReceiveHandlerProc(MyDragReceiveHandler);
    if (gMainReceiveHandler == NULL) { err = memFullErr; goto bail; }
    gSendDataProc = NewDragSendDataProc(MyDragSendDataProc);
    if (gSendDataProc == NULL) { err = memFullErr; goto bail; }
    gAEIdleProc = NewAEIdleProc(FDPIdleProcedure);
    if (gAEIdleProc == NULL) { err = memFullErr; goto bail; }
        
        /* get other resources */
    gSplashPict = GetPicture(kFDPSpashPictResource);
    if (gSplashPict == NULL) { err = resNotFound; goto bail; }
    
    
        /* ***** set up the menu bar ***** */
    SetMenuBar(GetNewMBar(kFDPMenuBarResource));
    DrawMenuBar();
    AppendResMenu(GetMenuHandle(mApple), 'DRVR');
 
        /* ***** run the app ***** */
    while (gRunning) {
        ProcessNextEvent(CopyFileInProgress() ? kCopySleepTime : kNormalSleepTime);
        YieldToAnyThread();
    }
    
        /* ***** tear down ***** */
bail:
    switch (err) {
        case userCanceledErr:
        case noErr:
            /* no alert here */
            break;
        case kNoDragMgrError:
            ParamAlert(kNoDragManagerAlert, NULL, NULL);
            break;
        default:
            {   Str255 errStr;
                NumToString(err, errStr);
                ParamAlert(kMainFailedAlert, errStr, NULL);
            }
            break;
    }
    if (gDialog != NULL) DisposeFDPWindow(gDialog);
    if (gAppearance)
        UnregisterAppearanceClient();
    ExitToShell();
    return 0;
}