// ============================================================================ // CDragAndDrop.cp ©1995 J. Rodden, DD/MF & Associates. All rights reserved // ============================================================================ // CDragAndDrop picks up drag manager handling where LDragAndDrop leaves off. // Everything necessary for a drag and droppable pane is encapsulated in this // class. // ============================================================================ #include "CDragTask.h" #include "CDragAndDrop.h" #include #include // ============================================================================ // € CDragAndDrop // ============================================================================ CDragAndDrop::CDragAndDrop(WindowPtr inMacWindow, LPane *inPane) : LDragAndDrop(inMacWindow, inPane) { mFlavorAccepted = 0L; mUseDefaultRect = true; mHighlightDrag = true; } // ============================================================================ // € ClickIsDragEvent // ============================================================================ Boolean CDragAndDrop::ClickIsDragEvent(const SMouseDownEvent &inMouseDown, Rect* inRect) { // Track item long enough to distinguish between a click to // select, and the beginning of a drag if ( ::WaitMouseMoved(inMouseDown.macEvent.where) ) { // If we leave the window, the drag manager will be changing thePort, // so we'll make sure thePort remains properly set. mPane->GetSuperView()->FocusDraw(); CreateDragEvent(inMouseDown,inRect); mPane->GetSuperView()->OutOfFocus(nil); return true; } return false; } // ============================================================================ // € CreateDragEvent // ============================================================================ void CDragAndDrop::CreateDragEvent(const SMouseDownEvent &inMouseDown, Rect* inRect) { if ( DragAndDropIsPresent() ) { CDragTask theDragTask( inMouseDown.macEvent, (mUseDefaultRect ? inRect : nil), kItemRef, this); mHighlightDrag = false; // we start a drag here, don't highlight until // we've gone out and returned theDragTask.DoDrag(); // If the drag is to the trash, it should be a move, not a copy if ( DroppedInTrash(&theDragTask) ) RemoveDragItem(inMouseDown); } } // ============================================================================ // € SetLocalFrame // ============================================================================ // Setup local frame (in global coords) for reference by drawing routines void CDragAndDrop::SetLocalFrame (void) { mPane->CalcPortFrameRect(mLocalFrame); mPane->PortToGlobalPoint(topLeft (mLocalFrame)); mPane->PortToGlobalPoint(botRight (mLocalFrame)); } // ============================================================================ // € ItemIsAcceptable // ============================================================================ Boolean CDragAndDrop::ItemIsAcceptable(DragReference inDragRef, ItemReference inItemRef) { // ItemIsAcceptable will be called whenever the Drag Manager wants to know if // the item the user is currently dragging contains any information that we // can accept. // // In our case, the only thing we'll accept are mFlavorAccepted items. FlavorFlags theFlags; if (::GetFlavorFlags(inDragRef, inItemRef, mFlavorAccepted, &theFlags) == noErr) { if ( mFlavorAccepted != flavorTypeHFS ) return true; else { Boolean targetIsFolder; HFSFlavor hfsFlavorData; FilterHFSDragItem(inDragRef, inItemRef, hfsFlavorData, targetIsFolder); return HFSItemIsAcceptable(hfsFlavorData, targetIsFolder); } } return false; } // ============================================================================ // € HFSItemIsAcceptable // ============================================================================ Boolean CDragAndDrop::HFSItemIsAcceptable(HFSFlavor& inHFSFlavorData, Boolean& inTargetIsFolder) { return true; } // ============================================================================ // € LeaveDropArea // ============================================================================ void CDragAndDrop::LeaveDropArea (DragReference inDragRef) { LDragAndDrop::LeaveDropArea(inDragRef); mHighlightDrag = true; // next time we enter, highlight } // ============================================================================ // € InsideDropArea // ============================================================================ void CDragAndDrop::InsideDropArea (DragReference inDragRef) { // Let LDragAndDrop do its thing - this is not really necessary, since // the inherited version doesn't do anything. But it's safer this // way because someday it might. LDragAndDrop::InsideDropArea(inDragRef); // And we'll do ours - we'll just read the mouse coordinates. Point theMouseLocation; Point thePinnedLocation; ::GetDragMouse(inDragRef, &theMouseLocation, &thePinnedLocation); mPane->GlobalToPortPoint(theMouseLocation); mPane->PortToLocalPoint(theMouseLocation); mPane->GlobalToPortPoint(thePinnedLocation); mPane->PortToLocalPoint(thePinnedLocation); mPane->FocusDraw(); InsideDropArea( inDragRef, theMouseLocation, thePinnedLocation); } // ============================================================================ // € InsideDropArea // ============================================================================ // The mouse location is where the mouse actually is on the screen. The // alternative is the pinned location, which is _usually_ the same location, // but can be different if the cursor is being constrained by a tracking handler. // This is useful when you want an area within a view to be 'off-limits' to // the ongoing drag. // // If we did want to do something based on where the cursor currently is in // our area (such as indicating an insertion point or something), it would // usually be best to use the pinned location for that work. // // Both mouse locations are in global screen coordinates // ============================================================================ void CDragAndDrop::InsideDropArea( DragReference inDragRef, Point& theMouseLocation, Point& thePinnedLocation) { } // ============================================================================ // € ReceiveDragItem // ============================================================================ void CDragAndDrop::ReceiveDragItem( DragReference inDragRef, DragAttributes inDragAttrs, ItemReference inItemRef, Rect &inItemBounds) // In Local coordinates { mHighlightDrag = true; // First, we'll figure out whether we want to copy the item(s), or only move the item(s). // // The rules for copying vs moving are spelled out in MD+DDK pp 19 through 21, and in // a simplified form are: copy the item unless where it's coming from and // where it's going to are the same window; then move it. If the option key // was held down when the drag began, always copy it. // // Optional behaviour is for an application to also check the option // key at the end of the drag. Copying then takes place if the option key was // held down at the beginning _OR_ at the end. Boolean optionKeyWasDown = CheckForOptionKey(inDragRef); // Check to see if this View (the destination) is the // same view (the source) that the object is coming from. Boolean dragIsFromThisView = CheckIfViewIsAlsoSender(inDragRef); Boolean copyData = !(dragIsFromThisView && !optionKeyWasDown); // Information about the drag contents we'll be needing. FlavorFlags theFlags; // We actually only use the flags to see if a flavor exists // Check to make sure the drag contains an accepted item. if ( ItemIsAcceptable( inDragRef, inItemRef) ) { mPane->FocusDraw(); Boolean fromFinder = ( ::GetFlavorFlags(inDragRef, inItemRef, flavorTypeHFS, &theFlags) == noErr ); ReceiveDragItem( inDragRef, inItemRef, copyData, fromFinder, inItemBounds); } } // ============================================================================ // € ReceiveDragItem // ============================================================================ // You MUST override this routine to pull out your own dragged data. // (flavorTypeHFS drags are automatically taken care of) void CDragAndDrop::ReceiveDragItem( DragReference inDragRef, ItemReference inItemRef, Boolean inCopyData, // Should we copy the data? Boolean inFromFinder, // Data came from the Finder Rect& inItemBounds) // In Local coordinates { /* * Sample ReceiveDragItem routine: * * FlavorFlags theFlags; * Size theDataSize; * * if (GetFlavorFlags(inDragRef, inItemRef, mFlavorAccepted, &theFlags) == noErr) { * ::GetFlavorDataSize(inDragRef, inItemRef, mFlavorAccepted, &theDataSize); * * if ( theDataSize <= sizeof(MyDragDataType) ) { * ThrowIf_(theDataSize > sizeof(MyDragDataType)); // sanity check * * // Prepare space to store new data * MyDragDataType theFlavorData; * * // Get the data about the item we are receiving. * ::GetFlavorData(inDragRef, inItemRef, mFlavorAccepted, * &theFlavorData, &theDataSize, 0L); * * CopyOrMoveDragData( theFlavorData, theDataSize, inCopyData, * inFromFinder, inItemBounds); * } *} */ FlavorFlags theFlags; if ( ::GetFlavorFlags(inDragRef, inItemRef, mFlavorAccepted, &theFlags) == noErr ) { switch ( mFlavorAccepted ) { case flavorTypeHFS: ReceiveHFSDragItem( inDragRef, inItemRef, inCopyData, inFromFinder, inItemBounds, (kAcceptFolder | kOpenFolder | kRecurseDown)); break; default: break; } } } // ============================================================================ // € CopyOrMoveDragData // ============================================================================ void CDragAndDrop::CopyOrMoveDragData( void* inDragData, Size inDataSize, Boolean inCopyData, Boolean inFromFinder, Rect& inItemBounds) { } // ============================================================================ // € RemoveDragItem // ============================================================================ // The drag item orriginated by inMouseDown was dropped in the trash. void CDragAndDrop::RemoveDragItem(const SMouseDownEvent &inMouseDown) { } // ============================================================================ // € DroppedInTrash // ============================================================================ Boolean CDragAndDrop::DroppedInTrash(LDragTask* inDragTask) { // An exception to the copy vs move option is if the user drags // the item to the trash. Then it should be _moved_, not copied. // // If we run into any error messages along the way, we'll abort // the test. We can't lose any data this way; only _not_ making // a deletion that really should be made. OSErr theErr; AEDesc theDropDestination; DragReference theDragRef = inDragTask->GetDragReference(); theErr = ::GetDropLocation(theDragRef, &theDropDestination); if ((theErr) || (theDropDestination.descriptorType == typeNull)) return false; if (theDropDestination.descriptorType == typeAlias) { // The drag was to the finder. The question now is whether it // was to the trash. Boolean aliasWasChanged; FSSpec theDestinationFSSpec; FSSpec theTrashFSSpec; short theTrashVRefNum; long theTrashDirID; // First, build the FSSpec of the destination to which the user dragged // the object HLock(theDropDestination.dataHandle); theErr = ::ResolveAlias( nil, (AliasHandle) theDropDestination.dataHandle, &theDestinationFSSpec, &aliasWasChanged); HUnlock(theDropDestination.dataHandle); if (theErr) return false; // Next, find the FSSpec of the system's trash theErr = ::FindFolder( kOnSystemDisk, kTrashFolderType, kDontCreateFolder, &theTrashVRefNum, &theTrashDirID); if (theErr) return false; theErr = ::FSMakeFSSpec( theTrashVRefNum, theTrashDirID, nil, &theTrashFSSpec); if (theErr) return false; // Compare the two FSSpecs. if (( theDestinationFSSpec.vRefNum == theTrashFSSpec.vRefNum) && ( theDestinationFSSpec.parID == theTrashFSSpec.parID) && (EqualString(theDestinationFSSpec.name, theTrashFSSpec.name, false, true))) { // Since the FSSpec of the destination of the drag is the same as the FSSpec of // the trash, the drag was to the trash. // // We get to this point _after_ the clipping file has been placed in the trash // so to complete the 'move', we simply delete this item. return true; } } return false; } // ============================================================================ // € CheckForOptionKey // ============================================================================ Boolean CDragAndDrop::CheckForOptionKey(DragReference inDragRef) { // We'll check whether the option key was down at either the beginning _or_ the // end of the drag, since (a) it's the preferred behaviour and (b) its so easy to do. Int16 theModifiersNow; // The state of the modifier keys right now Int16 theModifiersAtMouseDown; // The state of the modifier keys when the drag began Int16 theModifiersAtMouseUp; // The state of the modifier keys when the drag ended ::GetDragModifiers(inDragRef, &theModifiersNow, &theModifiersAtMouseDown, &theModifiersAtMouseUp); return ((theModifiersAtMouseDown & optionKey) || (theModifiersAtMouseUp & optionKey)); } // ============================================================================ // € CheckIfViewIsAlsoSender // ============================================================================ Boolean CDragAndDrop::CheckIfViewIsAlsoSender(DragReference inDragRef) { // Just a note: While we are using the drag attributes only at the end of the // drag, they are also available to you during the drag. // Drag Attributes are described in MD+DDK, page 2-31. DragAttributes theDragAttributes; ::GetDragAttributes(inDragRef, &theDragAttributes); return (theDragAttributes & dragInsideSenderWindow); } // ============================================================================ // € FilterHFSDragItem // ============================================================================ // Utility function to pull the FSSpec out of a flavorTypeHFS drag void CDragAndDrop::FilterHFSDragItem(DragReference inDragRef, ItemReference inItemRef, HFSFlavor& outHFSFlavorData, Boolean& outTargetIsFolder) { Size theDataSize; Boolean wasAliased; ::GetFlavorDataSize(inDragRef, inItemRef, flavorTypeHFS, &theDataSize); ::GetFlavorData(inDragRef, inItemRef, flavorTypeHFS, &outHFSFlavorData, &theDataSize, 0L); ::ResolveAliasFile( &outHFSFlavorData.fileSpec, true, &outTargetIsFolder, &wasAliased); } // ============================================================================ // € ReceiveHFSDragItem // ============================================================================ Boolean CDragAndDrop::ReceiveHFSDragItem( DragReference inDragRef, ItemReference inItemRef, Boolean inCopyData, // Should we copy the data? Boolean inFromFinder, // Data came from the Finder Rect& inItemBounds, // In Local coordinates short inFolderAction) // What should we do with a folder? { FlavorFlags theFlags; if ( ::GetFlavorFlags(inDragRef, inItemRef, flavorTypeHFS, &theFlags) == noErr ) { // Prepare space to store new data Boolean targetIsFolder; HFSFlavor hfsFlavorData; Size theDataSize; // Get the data about the item we are receiving. ::GetFlavorDataSize(inDragRef, inItemRef, flavorTypeHFS, &theDataSize); FilterHFSDragItem(inDragRef, inItemRef, hfsFlavorData, targetIsFolder); targetIsFolder = targetIsFolder || (hfsFlavorData.fileType == 'fold'); Boolean acceptFolder = IsBitOn( inFolderAction, kAcceptFolder); Boolean openFolder = IsBitOn( inFolderAction, kOpenFolder); Boolean recurseDown = IsBitOn( inFolderAction, kRecurseDown); if ( !targetIsFolder || ( acceptFolder && !openFolder ) ) { CopyOrMoveDragData( &hfsFlavorData.fileSpec, theDataSize, inCopyData, inFromFinder, inItemBounds); } else if ( acceptFolder && openFolder ) { FSSpec theSpec; Boolean isFolder; StFolderIter theFolder(hfsFlavorData.fileSpec); while ( theFolder.NextFile( theSpec, isFolder, recurseDown) ) { CopyOrMoveDragData( &theSpec, theDataSize, inCopyData, inFromFinder, inItemBounds); } } return true; } return false; }