/*===============================
Copyright © 1998-2002 Brian Hall

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*===============================*/
#include "BlugsPrivate.h"

static OSErr	local_BLMakeDragImage( DragRef ioDragRef, BLCell inCell, GWorldPtr *outDragGWorld,
										RgnHandle *outDragRgn, RgnHandle* outTempRgn, BlugsRef inList );
static void		local_BLMakeTranslucentImage( BLCell inCell, const Rect* inCellRect, RgnHandle inCellRgn,
												RgnHandle* outImageRgn, GWorldPtr* outGWorld,
												DragRef inDragRef, BlugsRef inList );
static pascal OSErr
				local_DragWithinList_InputProc( Point *ioMouse, SInt16 *ioModifiers,
												BlugsRef inList, DragRef inDragRef );
static SInt16	local_DetermineDragType( DragRef inDragRef, BlugsRef inList );
static OSErr	local_TrackHorzTitle( DragRef inDragRef, Point inCurrentMouseLocal,
									Point inOriginMouseGlobal, BlugsRef inList );
static OSErr	local_TrackVertTitle( DragRef inDragRef, Point inCurrentMouseLocal,
									Point inOriginMouseGlobal, BlugsRef inList );
static void		local_BLTrackContent( DragRef inDragRef, Point inCurrentMouseLocal, BlugsRef inList );
static void		local_BLCanDropHere( DragRef inDragRef, Point inCurrentMouseLocal, BlugsRef inList );
static Boolean	local_BLSameListDragConstraints( DragRef inDragRef, UInt16 inDropUnderThisRow,
												UInt16 inDisclosureLevel, BlugsRef inList );
static UInt16	local_BLMouseToDisclosureLevel( Point inMouseLocal, UInt16* ioDropUnderThisRow,
												BlugsRef inList );
static void		local_BLEraseInsertionCaret( DragRef inDragRef, BlugsRef inList );



//===============================
BL_PROC OSErr BLTrackDrag( DragTrackingMessage inMessage, DragRef inDragRef, BlugsRef inList )
{
	OSErr		err;

	_CheckHandlerFlag(inList)
	err = _BLValidateList( inList );
	if (err || inDragRef == nil) err = dragNotAcceptedErr;
	else
	{
		SInt16		dragType;
		Point		originMouse;
		GWorldPtr	saveWorld;
		Boolean		restore;
		
		restore = _BLGotoWindow( &saveWorld, inList );
		switch (inMessage)
		{
			case kDragTrackingEnterWindow:
			// determine whether we can accept this drag or not.
			bl_Clear((*inList)->privateFlags,
					blDraggingHorzTitle | blDraggingVertTitle | blDraggingContent | blHaveDrawnInsertionCaret );
			dragType = local_DetermineDragType( inDragRef, inList );
			switch (dragType)
			{
				case blDragType_HorzTitle:
				bl_Set((*inList)->privateFlags, blDraggingHorzTitle );
				break;
				
				case blDragType_VertTitle:
				bl_Set((*inList)->privateFlags, blDraggingVertTitle );
				break;
				
				case blDragType_Content:
				bl_Set((*inList)->privateFlags, blDraggingContent );
				// Reset clickTime, so a quick click after an aborted drag isn't a double click.
				(*inList)->lastClickTime = 0;
				break;
				
				default:
				err = badDragFlavorErr;
				break;
			}
			break;

		case kDragTrackingInWindow:
			// Get current mouse location.
			// Moses was here. For some reason, the drag mouse was being pinned so mouse.v
			//	was always 0 in local coords. So we simply use GetMouse().
			//err = GetDragMouse( inDragRef, &mouse, nil );

			// Get the original mouse location (will come in handy later).
			//if ( err == noErr )
				err = GetDragOrigin( inDragRef, &originMouse );

			if ( err == noErr )
			{
				Point	mouse;
				
				// Make the current mouse have local coordinates.
				//GlobalToLocal( &mouse );
				GetMouse( &mouse );
				if ( bl_Test( (*inList)->privateFlags, blDraggingHorzTitle ) )
					err = local_TrackHorzTitle( inDragRef, mouse, originMouse, inList );
				else if ( bl_Test( (*inList)->privateFlags, blDraggingVertTitle ) )
					err = local_TrackVertTitle( inDragRef, mouse, originMouse, inList );
				else if ( bl_Test( (*inList)->privateFlags, blDraggingContent ) )
					local_BLTrackContent( inDragRef, mouse, inList );
				else err = dragNotAcceptedErr;
			}
		break; // case kDragTrackingInWindow

		case kDragTrackingLeaveWindow:
		//case kDragTrackingLeaveHandler:
			// Drag has left this window
			// dehilite the list area if necessary
			// Clear insertion caret.
			_BLUpdate( blUpdateNoOptions, nil, inList );
			//local_BLEraseInsertionCaret( inDragRef, inList );
			bl_Clear((*inList)->privateFlags,
					blDraggingHorzTitle | blDraggingVertTitle | blDraggingContent | blHaveDrawnDragHilite);
			//HideDragHilite( inDragRef );
		break;

		default:	err = handlerNotFoundErr;
		break;
		} // case inMessage
		if (restore) SetGWorld( saveWorld, nil );
	}
	// return result code
	return err;
}

//===============================
BL_PROC OSErr BLReceiveDrag( DragRef inDragRef, BlugsRef inList )
{
	OSErr			err = noErr;
	UInt16			currentTitleNum;
	BLTitleBarHdl	titleBar;
	
	_CheckHandlerFlag(inList)
	// If we were dragging a title, go ahead and accept it.  Everything is in  place with live feedback.
	if ( bl_Test( (*inList)->privateFlags, blDraggingHorzTitle ) ||
		bl_Test( (*inList)->privateFlags, blDraggingVertTitle ) )
	{
		Size		dataSize = sizeof(currentTitleNum);
		
		// Need to remove the blTitleBeingDragged flag.
		err = GetFlavorData( inDragRef, blPrivateDragItemRef, blDragFlavor_CurrentTitlePosition, &currentTitleNum, &dataSize, 0 );
		if ( err == noErr && dataSize == sizeof(currentTitleNum) )
		{
			titleBar = bl_Test( (*inList)->privateFlags, blDraggingHorzTitle ) ? bl_HTitle(inList) : bl_VTitle(inList);
			_BLTempRemoveFlagTitle( false, blTitleBeingDragged | blTitleTempSelected, currentTitleNum, titleBar, inList );
			_BLUpdateBarOnly( titleBar, inList );
			_BLUpdateView( blUpdateBlit, inList );
		}
	}

	// Should we accept content?
	else if ( bl_Test( (*inList)->privateFlags, blDraggingContent ) )
	{
		err = dragNotAcceptedErr; // assume failure to drop
		if (bl_Test((*inList)->privateFlags,blHaveDrawnInsertionCaret))
		{
			Rect		viewRect;
			Point		mouse;
			
			// We should probably call GetDragMouse here.
			GetMouse( &mouse );
			viewRect = _BLViewRect( inList );
			if (PtInRect(mouse,&viewRect))
			{
				BLDropProcPtr	dropProc;
				
				dropProc = (*inList)->callbacks.dropProc;
				if (dropProc)
				{
					(*dropProc)( inDragRef, (*inList)->lastInsertionCaretRow,
											(*inList)->lastInsertionCaretDisclosure, inList );
					err = noErr;
				}
			}
		}
	}
	return err;
}

//===============================
// The inList has already been validated within BLClick.
OSErr _BLStartDrag( BLCell inCell, UInt32 inWhen, Point inWhereLocal,
					EventModifiers inModifiers, BlugsRef inList )
{
	OSErr		err = noErr;
	
	//if (bl_Rep(inList)) inCell.col = bl_Rep(inList);
	// This should look just like the user didn't click and move the mouse.
	// If the user de-selected the cell with this click, don't start a drag.
	if (!bl_DragManager || !bl_Test( (**inList).dragFlags, blCanStartDrags ) ||
		!bl_Selected( bl_CellIndexAddr( inCell, inList ))) err = errAENoUserInteraction;
	else
	{
		EventRecord		theEvent;
		GWorldPtr		saveWorld;
		Boolean			restore;
		
		// Set up the port
		restore = _BLGotoWindow( &saveWorld, inList );
		// Fabricate an EventRecord for TrackDrag
		theEvent.what = mouseDown;
		theEvent.message = 0;
		theEvent.when = inWhen;
		theEvent.where = inWhereLocal;
		LocalToGlobal( &theEvent.where );
		theEvent.modifiers = inModifiers;
		// Before starting the drag, the user must move the mouse a certain
		//	distance away from the initial mouse location; this allows a short click in
		//	the selection area to select the cell instead of starting a drag-and-drop sequence
		if ( !WaitMouseMoved( theEvent.where ) ) err = errAENoUserInteraction;
		else
		{
			DragRef	theDrag = nil;
			
			err = NewDrag( &theDrag );
			if (!err && theDrag)
			{
				GWorldPtr		dragGWorld = nil;
				RgnHandle		dragRgn = nil, tempRgn = nil;
				
				// Create drag image & region, add all the items and flavors also.
				err = local_BLMakeDragImage( theDrag, inCell, &dragGWorld, &dragRgn, &tempRgn, inList );
				if (!err)
				{
					// Add a little internal info in the ItemReference == 1.
					err = AddDragItemFlavor( theDrag, blPrivateDragItemRef, blDragFlavor_OriginList, &inList, sizeof(inList), flavorSenderOnly );
					if (!err)
					{
						// Add the original mouse offset so we can force the drag into the list rect better.
						Rect			box;
						Point			offset;
						
						GetRegionBounds( dragRgn, &box );
						offset.h = theEvent.where.h - box.left;
						offset.v = theEvent.where.v - box.top;
						err = AddDragItemFlavor( theDrag, blPrivateDragItemRef, blDragFlavor_OriginalMouseOffset, &offset, sizeof(offset), flavorSenderOnly );
						if (!err)
						{
							DragInputUPP	inputUPP = nil;
							
							if (bl_Test( (*inList)->dragFlags, blAllowDragsOnlyToSelf ))
							{
								// Need to install a drag input routine to keep the drag within the list rect.
								inputUPP = NewDragInputUPP( (DragInputProcPtr)local_DragWithinList_InputProc );
					 			err = SetDragInputProc( theDrag, inputUPP, (void*)inList );
					 		}
					 		if (!err)
					 		{
					 			BLPreDragProcPtr	preDragProc;
					 			
					 			preDragProc = (*inList)->callbacks.preDragProc;
					 			// Call preDragProc here and possibly abort before we call TrackDrag.
					 			if (preDragProc) err = (*preDragProc)( theDrag, dragGWorld, dragRgn, &theEvent, inList );
					 		}
					 		if (!err)
					 		{
					 			BLPostDragProcPtr	postDragProc;
					 			
					 			// Track the drag.
					 			err = TrackDrag( theDrag, &theEvent, dragRgn );
					 			postDragProc = (*inList)->callbacks.postDragProc;
					 			if (postDragProc) (*postDragProc)( theDrag, inList );
					 		}
					 		// We're mostly done.  Do some clean up now.
					 		// Dispose of the input UPP if there was one.
					 		if (inputUPP) DisposeDragInputUPP( inputUPP );
						} // No error adding blDragFlavor_OriginalMouseOffset
					} // No error adding blDragFlavor_OriginList
				} // No error making drag image.
				if (dragGWorld) DisposeGWorld( dragGWorld );
				if (dragRgn) DisposeRgn( dragRgn );
				if (tempRgn) DisposeRgn( tempRgn );
				DisposeDrag( theDrag );
			} // No error from NewDrag, and theDrag is non-nil.
		} // WaitMouseMoved
		if (restore) SetGWorld( saveWorld, nil );
	} // blCanStartDrags set and inCell is selected.
	return err;
}

#pragma mark -
//===============================
// Returns errAENoUserSelection is no cells are selected or if the drag data proc
//	rejects all cells, or if there is no drag data proc.
// Starts with port set to list window.
// The outImageRgn passed out of the call is the region for SetDragImage. It needs to stick around
//	*unmodified* for the duration of the drag. This realization has terminated literally months of fruitless
//	search for a bug fix.
static OSErr local_BLMakeDragImage( DragRef ioDragRef, BLCell inCell, GWorldPtr *outDragGWorld,
									RgnHandle *outDragRgn, RgnHandle* outImageRgn, BlugsRef inList )
{
	BLDragDataProcPtr	dragDataProc;
	OSErr				err = noErr;
	
	// We'll assume our parameters are groovy, because we only call this from _BLStartDrag.
	dragDataProc = (*inList)->callbacks.dragDataProc;
	if (!dragDataProc) err = handlerNotFoundErr;
	else
	{
		RgnHandle			tempRgn = nil, itemBoundsRgn = nil, contentRgn = nil;
		BLIndexPtr			index;
		DragItemRef			tempItemRef = blPrivateDragItemRef;
		Point				offset = { 0, 0 };
		BLCell				tempCell;
		Handle				indexHdl;
		UInt8				state;
		Boolean				haveAtLeastOneCellToDrag = false;
		UInt16				nRows, nCols;
		
		// Get an offset from local to global coordinates.
		LocalToGlobal( &offset );
		// Allocate some regions.
		*outDragRgn = NewRgn();
		err = MemError();
		if (!err)
		{
			tempRgn = NewRgn();
			err = MemError();
		}
		if (!err)
		{
			itemBoundsRgn = NewRgn();
			err = MemError();
		}
		if (!err)
		{
			contentRgn = NewRgn();
			err = MemError();
		}
		if (!err)
		{
		#if __BL_USE_CARBON_API__
			GetWindowRegion( bl_Window(inList), kWindowContentRgn, contentRgn );
  		#else
			GetWindowContentRgn( bl_Window(inList), contentRgn );
		#endif
			// We need to drag all selected cells.
			indexHdl = bl_Index( inList );
			state = HGetState( (Handle)indexHdl );
			HLock( (Handle)indexHdl );
			index = (BLIndexPtr)*indexHdl;
			nRows = bl_NRows(inList);
			nCols = bl_NCols(inList);
			for (tempCell.row = 1; tempCell.row <= nRows; tempCell.row++)
			{
				for (tempCell.col = 1; tempCell.col <= nCols; tempCell.col++, index++)
				{
					if ( bl_Selected( index ) )
					{
						OSErr			dragDataErr;
						
						dragDataErr = (*dragDataProc)( ioDragRef, tempItemRef, tempCell, inList );
						// Add this cell to the drag if the callback said OK.
						if (!dragDataErr)
						{
							Rect	localBounds;
							
							// If the cell is selected, add it to the drag.
							err = AddDragItemFlavor( ioDragRef, tempItemRef, blDragFlavor_Cell, &tempCell, sizeof(BLCell), 0 );
							if (!err)
							{
								haveAtLeastOneCellToDrag = true;
								bl_Set( index->flags, blBeingDragged );
								if (_BLCellIsVisible( tempCell, inList ))
								{
									Rect		cellRect;
									
									// Now that the data is added, need to set the image.
									// Cell rect in local coords.
									(void)_BLCellRect( tempCell, &cellRect, inList );
									OffsetRect( &cellRect, offset.h, offset.v );
									_BLCellRegion( tempRgn, tempCell, &cellRect, inList );
									if (bl_TranslucentDrags && tempCell.row == inCell.row && tempCell.col == inCell.col)
										local_BLMakeTranslucentImage( tempCell, &cellRect, tempRgn,
																		outImageRgn, outDragGWorld,
																		ioDragRef, inList );
									//_BLCellRegion( tempRgn, tempCell, &cellRect, inList );
									// Make a hollowed out region of the cellRect.
									SectRgn( tempRgn, contentRgn, tempRgn );
									CopyRgn( tempRgn, itemBoundsRgn );
									InsetRgn( tempRgn, 1, 1 );
									DiffRgn( itemBoundsRgn, tempRgn, itemBoundsRgn );
									// Add this cellRgn to the overall drag region.
									UnionRgn( itemBoundsRgn, *outDragRgn, *outDragRgn );
									GetRegionBounds( itemBoundsRgn, &localBounds );
									err = SetDragItemBounds( ioDragRef, tempItemRef, &localBounds );
								}
								// The cell is not visible, so this item knows no bounds.
								//else SetRect( &localBounds, 0, 0, 0, 0 );
								// Note that this resets any errors that may have occured while adding flavors.
								// This is generally OK, since other cells may still have valid flavors to add.
								//err = SetDragItemBounds( ioDragRef, tempItemRef, &localBounds );
								tempItemRef++;
							}
						}
					}
				}
			}
			if (!haveAtLeastOneCellToDrag) err = errAENoUserSelection;
			HSetState( (Handle)indexHdl, state );
			if (tempRgn) DisposeRgn( tempRgn );
			if (itemBoundsRgn) DisposeRgn( itemBoundsRgn );
			if (contentRgn) DisposeRgn( contentRgn );
		}
	} // drag data proc exists.
	return err;
}

//===============================
static void local_BLMakeTranslucentImage( BLCell inCell, const Rect* inCellRect, RgnHandle inCellRgn,
											RgnHandle* outImageRgn, GWorldPtr* outGWorld,
											DragRef inDragRef, BlugsRef inList )
{
	OSErr		err;
#if __BL_USE_CARBON_API__
	Rect		bounds;
	
	GetRegionBounds( inCellRgn, &bounds );
	err = NewGWorld( outGWorld, (bl_Aqua) ? 16 : 8, (bl_Aqua) ? &bounds : inCellRect, nil, nil, useTempMem );
#else
	err = NewGWorld( outGWorld, 8, inCellRect, nil, nil, useTempMem );
#endif
	if (!err)
	{
		RgnHandle		imageRgn = NewRgn();
		
		if (imageRgn)
		{
			GWorldPtr		saveWorld;
			GDHandle		saveDevice;
			PixMapHandle	pm;
			Point			offset = {0,0};
			
			pm = GetGWorldPixMap( *outGWorld );
			GetGWorld( &saveWorld, &saveDevice );
			// Draw cell in the offscreen.
			SetGWorld( *outGWorld, nil );
			LockPixels( pm );
			// Aqua does not draw selected items as selected.
		//#if __BL_USE_CARBON_API__
			if (bl_Aqua) bl_Clear(bl_CellIndexAddr(inCell,inList)->flags,blSelected);
		//#endif
			_BLDrawCell( blCellDrawSuppressTriangle | blCellDrawSuppressBorders, inCell, inCellRect, inList );
			// We know it was selected before -- that's why it was dragged.
		#if __BL_USE_CARBON_API__
			if (bl_Aqua) bl_Set(bl_CellIndexAddr(inCell,inList)->flags,blSelected);
		#endif
			CopyRgn( inCellRgn, imageRgn );
			*outImageRgn = imageRgn;
			SetGWorld( saveWorld, saveDevice );
			UnlockPixels( pm );
		#if __BL_USE_CARBON_API__
			err = SetDragImage( inDragRef, pm, (bl_Aqua) ? (bl_Jaguar ? imageRgn : inCellRgn) : imageRgn, offset, kDragRegionAndImage | kDragStandardTranslucency );
		#else
			err = SetDragImage( inDragRef, pm, imageRgn, offset, kDragRegionAndImage | kDragStandardTranslucency );
		#endif
		}
	}
}

//===============================
static pascal OSErr local_DragWithinList_InputProc( Point *ioMouse, SInt16 *ioModifiers,
													BlugsRef inList, DragRef inDragRef )
{
	#pragma unused( ioModifiers )
	OSErr		err;
	Rect		dragBounds;
	
	err = GetDragItemBounds( inDragRef, blPrivateDragItemRef, &dragBounds );
	if (!err)
	{
		Size		dataSize;
		Point		originalOffset;
		
		dataSize = sizeof( originalOffset );
		err = GetFlavorData( inDragRef, blPrivateDragItemRef,
							blDragFlavor_OriginalMouseOffset, &originalOffset, &dataSize, 0 );
		if (!err)
		{
			GWorldPtr	saveWorld;
			Boolean		restore;
			Rect		box;
			Point		offset;
			
			restore = _BLGotoWindow( &saveWorld, inList );
			// Find the area that we can drag within.
			box = _BLViewRect( inList );
			OffsetRect( &box, originalOffset.h, originalOffset.v );
			box.bottom = box.bottom - dragBounds.bottom + dragBounds.top;
			box.right = box.right - dragBounds.right + dragBounds.left;
			// Need to offset mouse to local coordinates to compare with box.
			offset = *ioMouse;
			GlobalToLocal( &offset );
			if ( !PtInRect( offset, &box ) )
			{
				// If we're outside the list, reset to nearest list border.
				if ( offset.v < box.top ) offset.v = box.top;
				else if ( offset.v > box.bottom ) offset.v = box.bottom;
				if ( offset.h < box.left ) offset.h = box.left;
				else if ( offset.h > box.right ) offset.h = box.right;
				*ioMouse = offset;
				LocalToGlobal( ioMouse );
			}
			if (restore) SetGWorld( saveWorld, nil );
		}
	}
	return err;
}

//===============================
static SInt16 local_DetermineDragType( DragRef inDragRef, BlugsRef inList )
{
	SInt16		dragType = blDragType_Content;
	OSErr		err;
	Size		dataSize;
	Boolean		isHorz;
	
	dataSize = sizeof(isHorz);
	err = GetFlavorData( inDragRef, blPrivateDragItemRef, blDragFlavor_DraggingTitle, &isHorz, &dataSize, 0 );
	if (!err && dataSize == sizeof(isHorz))
	{
		BlugsRef			originList;
		
		// Need to make sure that this is the originating inList.
		err = BLGetListFromDrag( inDragRef, &originList );
		if (err == noErr && originList == inList)
		{
			// Determine if we are dragging a vertical or horizontal title.
			dragType = isHorz ? blDragType_HorzTitle : blDragType_VertTitle;
		}
		// We don't want to drag titles to other lists, so just say there was an error.
		else dragType = blDragType_None;
	}
	return dragType;
}

//===============================
static OSErr local_TrackHorzTitle( DragRef inDragRef, Point inCurrentMouseLocal,
									Point inOriginMouseGlobal, BlugsRef inList )
{
	OSErr			err;
	Point			originalOffset;
	Rect			listRect, dragBounds;
	Size			dataSize;
	UInt16			currentTitleNum;
	
	err = GetDragItemBounds( inDragRef, blPrivateDragItemRef, &dragBounds );
	if ( err == noErr )
	{
		dataSize = sizeof(originalOffset);
		err = GetFlavorData( inDragRef, blPrivateDragItemRef, blDragFlavor_OriginalMouseOffset, &originalOffset, &dataSize, 0 );
	}
	if ( err == noErr && dataSize == sizeof(originalOffset) )
	{
		dataSize = sizeof(currentTitleNum);
		err = GetFlavorData( inDragRef, blPrivateDragItemRef, blDragFlavor_CurrentTitlePosition, &currentTitleNum, &dataSize, 0 );
	}
	if ( err == noErr && dataSize == sizeof(currentTitleNum) )
	{
		// Only want to allow dragging within cell area + horz title bar area.
		listRect = _BLViewRect( inList );
		listRect.top -= bl_HTitleHt( inList );
		
		// Check to see if we're outside of the inList rectangle.
		if ( !PtInRect( inCurrentMouseLocal, &listRect ) )
		{
			BLPixelOffset	originalScrollPos;
			
			// Return scroll bars to original position.
			dataSize = sizeof(originalScrollPos);
			err = GetFlavorData( inDragRef, blPrivateDragItemRef, blDragFlavor_Offset, &originalScrollPos, &dataSize, 0 );
			if ( err == noErr && dataSize == sizeof(originalScrollPos) )
				// Moses was here. Pass nil if you don't need actual scroll amount.
				_BLScroll( blScrollAdjustThumb, originalScrollPos - bl_HScrollLoc(inList), 0, nil, nil, inList );

			// Pin mouse to original position.
			inCurrentMouseLocal = inOriginMouseGlobal;
		}
		else
		{
			//SInt32		autoscrollDist;
			
			// We're inside the inList.  
			// Can only drag the title over the width of the actual items in the list
			//	(in case the window is very large).
			if (bl_Width(listRect) > bl_TotalWd(inList))
				listRect.right = listRect.left + bl_TotalWd( inList );

			// Want the dragBounds to stay within the list cell area.
			OffsetRect( &listRect, originalOffset.h, originalOffset.v );
			// This could make for a bad rectangle if the title is higher than the list area.
			listRect.bottom = listRect.bottom - dragBounds.bottom + dragBounds.top;
			if ( listRect.bottom < listRect.top ) listRect.bottom = listRect.top;
			// This could make for a bad rectangle if the title is wider than the list area.
			listRect.right = listRect.right - dragBounds.right + dragBounds.left;
			if ( listRect.right < listRect.left ) listRect.right = listRect.left;

			// If the dragBounds goes outside the listArea, stick it to the nearest edge.
			if ( inCurrentMouseLocal.h < listRect.left ) inCurrentMouseLocal.h = listRect.left;
			else if ( inCurrentMouseLocal.h > listRect.right ) inCurrentMouseLocal.h = listRect.right;

			// Check if we want to do any autoscrolling.
			/*if ( inCurrentMouseLocal.h < ( listRect.left + blDefaultResizeASDist ) )
				// Try to scroll left.
				autoscrollDist = inCurrentMouseLocal.h - ( listRect.left + blDefaultResizeASDist );
			else if ( inCurrentMouseLocal.h > ( listRect.right - blDefaultResizeASDist ) )
				autoscrollDist = inCurrentMouseLocal.h - ( listRect.right - blDefaultResizeASDist );
			else autoscrollDist = 0;

			if ( autoscrollDist != 0 )
				_BLScroll( blScrollAdjustThumb, autoscrollDist, 0, nil, nil, inList );*/
			
			listRect.right -= blDefaultResizeASDist;
			listRect.left += blDefaultResizeASDist;
			_BLAutoscroll( inCurrentMouseLocal, listRect, blAutoscrollNoV, nil, nil, inList );
			
			// Whether we scrolled or not, pin the inCurrentMouseLocal to horizontal movement only.
			LocalToGlobal( &inCurrentMouseLocal );
			inCurrentMouseLocal.v = inOriginMouseGlobal.v;
		}
		// Note: inCurrentMouseLocal is now in global coordinates.
		err = SetDragMouse( inDragRef, inCurrentMouseLocal );
	}
	if ( err == noErr )
	{
		UInt16	dropTitleNum;
		
		// Now determine where the drop will occur.
		dropTitleNum = _BLPointToDropHTitle( dragBounds.left, currentTitleNum,
											bl_HTitle(inList), inList );
		if ( dropTitleNum && currentTitleNum != dropTitleNum )
		{
			UInt16	dropAfter;
			
			// Translate the absolute drop position into relative position.
			dropAfter = dropTitleNum;
			if (dropTitleNum < currentTitleNum) dropAfter--;
			err = _BLMoveColumns( currentTitleNum, dropAfter, 1, inList );
			currentTitleNum = dropTitleNum;
		}
	}

	// If everything went smoothly, set the data for the current title location.
	// Note: It's OK to call SetDragItemFlavorData from within a drag handler.
	if (err == noErr) err = SetDragItemFlavorData( inDragRef, blPrivateDragItemRef,
													blDragFlavor_CurrentTitlePosition,
													&currentTitleNum,
													sizeof(currentTitleNum), 0 );
	return err;
}

//===============================
//	inCurrentMouseLocal is in local coordinates
//	inOriginMouseGlobal is in global coordinates.
static OSErr local_TrackVertTitle( DragRef inDragRef, Point inCurrentMouseLocal,
									Point inOriginMouseGlobal, BlugsRef inList )
{
	OSErr			err;
	Rect			listRect, dragBounds;
	Point			originalOffset;
	Size			dataSize;
	BLPixelOffset	originalScrollPos;
	//SInt32			autoscrollDist;
	UInt16			dropTitleNum, currentTitleNum;

	err = GetDragItemBounds( inDragRef, blPrivateDragItemRef, &dragBounds );

	if ( err == noErr ) {
		dataSize = sizeof(originalOffset);
		err = GetFlavorData( inDragRef, blPrivateDragItemRef, blDragFlavor_OriginalMouseOffset, &originalOffset, &dataSize, 0 );
	}

	if ( err == noErr && dataSize == sizeof(originalOffset) ) {
		dataSize = sizeof(currentTitleNum);
		err = GetFlavorData( inDragRef, blPrivateDragItemRef, blDragFlavor_CurrentTitlePosition, &currentTitleNum, &dataSize, 0 );
	}

	if ( err == noErr && dataSize == sizeof(currentTitleNum) ) {
		// Only want to allow dragging within cell area + vert title bar area.
		listRect = _BLViewRect( inList );
		listRect.left -= bl_VTitleWd( inList );

		// Check to see if we're outside of the inList rectangle.
		if ( !PtInRect( inCurrentMouseLocal, &listRect ) ) {
			// Return scroll bars to original position.
			dataSize = sizeof(originalScrollPos);
			err = GetFlavorData( inDragRef, blPrivateDragItemRef, blDragFlavor_Offset, &originalScrollPos, &dataSize, 0 );
			if ( err == noErr && dataSize == sizeof(originalScrollPos) )
				_BLScroll( blScrollAdjustThumb, 0, originalScrollPos - bl_VScrollLoc(inList), nil, nil, inList );
			// Pin mouse to original position.
			inCurrentMouseLocal = inOriginMouseGlobal;
		}
		else
		{
			// We're inside the inList.  Check if we should autoscroll.
			// Can only drag the title over the height of the actual items in the list (in case the window is very large).
			if (bl_Height(listRect) > bl_TotalHt( inList ))
				listRect.bottom = listRect.top + bl_TotalHt( inList );
			// Want the dragBounds to stay within the list cell area.
			OffsetRect( &listRect, originalOffset.h, originalOffset.v );
			// This could make for a bad rectangle if the title is higher than the list area.
			listRect.bottom = listRect.bottom - dragBounds.bottom + dragBounds.top;
			if ( listRect.bottom < listRect.top ) listRect.bottom = listRect.top;
			// This could make for a bad rectangle if the title is wider than the list area.
			listRect.right = listRect.right - dragBounds.right + dragBounds.left;
			if ( listRect.right < listRect.left ) listRect.right = listRect.left;

			// If the dragBounds goes outside the listArea, stick it to the nearest edge.
			if ( inCurrentMouseLocal.v < listRect.top ) inCurrentMouseLocal.v = listRect.top;
			else if ( inCurrentMouseLocal.v > listRect.bottom ) inCurrentMouseLocal.v = listRect.bottom;

			/*if ( inCurrentMouseLocal.v < ( listRect.top + blDefaultResizeASDist ) )
				autoscrollDist = inCurrentMouseLocal.v - ( listRect.top + blDefaultResizeASDist );
			else if ( inCurrentMouseLocal.v > ( listRect.bottom - blDefaultResizeASDist ) )
				autoscrollDist = inCurrentMouseLocal.v - ( listRect.bottom - blDefaultResizeASDist );
			else autoscrollDist = 0;
			if (autoscrollDist) _BLScroll( blScrollAdjustThumb, 0, autoscrollDist, nil, nil, inList );*/
			
			listRect.bottom -= blDefaultResizeASDist;
			listRect.top += blDefaultResizeASDist;
			_BLAutoscroll( inCurrentMouseLocal, listRect, blAutoscrollNoH, nil, nil, /*nil,*/ inList );
			
			// Whether we scrolled or not, pin the mouse to horizontal movement only.
			LocalToGlobal( &inCurrentMouseLocal );
			inCurrentMouseLocal.h = inOriginMouseGlobal.h;
		}

		// Note: inCurrentMouseLocal is now in global coordinates.
		err = SetDragMouse( inDragRef, inCurrentMouseLocal );
	}

	if ( err == noErr )
		err = GetDragItemBounds( inDragRef, blPrivateDragItemRef, &dragBounds );

	if ( err == noErr )
	{
		// Now determine where the drop will occur.
		dropTitleNum = _BLPointToDropVTitle( dragBounds.top, currentTitleNum, bl_VTitle(inList), inList );
		if ( dropTitleNum && currentTitleNum != dropTitleNum )
		{
			UInt16	dropAfter;
			
			// Translate the absolute drop position into relative position.
			dropAfter = dropTitleNum;
			if (dropTitleNum < currentTitleNum) dropAfter--;
			err = _BLMoveRows( currentTitleNum, dropAfter, 1, 0, inList );
			currentTitleNum = dropTitleNum;
		}
	}

	// If everything went smoothly, set the data for the current title location.
	// Note: It's OK to call SetDragItemFlavorData from within a drag handler.
	if (err == noErr)
		err = SetDragItemFlavorData( inDragRef, blPrivateDragItemRef,
									blDragFlavor_CurrentTitlePosition, &currentTitleNum,
									sizeof(currentTitleNum), 0 );
	return err;
}


//===============================
static void local_BLTrackContent( DragRef inDragRef, Point inCurrentMouseLocal, BlugsRef inList )
{
	// First see if we can track in this list.
	// Make sure the list can receive drags.
	if (bl_Test((*inList)->privateFlags,blReceiveDrags))
	{
		BlugsRef		originList;
		OSErr	err;
		
		err = BLGetListFromDrag( inDragRef, &originList );
		if (err) originList = nil;
		// If not blReceiveDragsFromSelf and origin drag is same as this one, disallow.
		// In other words, this constraint is satisfied if blReceiveDragsFromSelf or
		//	the origin and target are different.
		if (bl_Test((*inList)->dragFlags,blReceiveDragsFromSelf) || originList != inList)
		{
			// Now, we also reject if blReceiveDragOnlyFromSelf and origin is not target.
			// In other words, this constraint is satisfied if not blReceiveDragOnlyFromSelf,
			//	or the origin and target are the same.
			if (!bl_Test((*inList)->dragFlags,blReceiveDragsOnlyFromSelf) || originList == inList)
			{
				Rect		viewRect;
				
				viewRect = _BLViewRect( inList );
				// Check to see if we're outside of the inList rectangle.
				if (PtInRect( inCurrentMouseLocal, &viewRect ))
				{
					SInt32			actualVAutoscroll, actualHAutoscroll;
					Rect			autoscrollRect = viewRect;
					DragAttributes	attributes;
					
					(void)GetDragAttributes( inDragRef, &attributes );
					if (bl_Test(attributes,kDragHasLeftSenderWindow)) _BLPaintDragHilite( viewRect, inList );
					InsetRect( &autoscrollRect, 6, 6 );
					_BLAutoscroll( inCurrentMouseLocal, autoscrollRect, blAutoscrollConstSpeed,
									&actualHAutoscroll, &actualVAutoscroll, /*nil,*/ inList );
					// We must redraw insertion caret if we have autoscrolled.
					if (actualHAutoscroll || actualVAutoscroll)
					{
						bl_Clear((*inList)->privateFlags,blHaveDrawnInsertionCaret | blHaveDrawnDragHilite);
						//bl_Clear((*inList)->privateFlags,blHaveDrawnDragHilite);
					}
					local_BLCanDropHere( inDragRef, inCurrentMouseLocal, inList );
				}
				// Since insertion caret may have been drawn, update to erase it.
				else
				{
					if (bl_Test((*inList)->privateFlags,blHaveDrawnInsertionCaret) ||
						bl_Test((*inList)->privateFlags,blHaveDrawnDragHilite))
					{
						_BLUpdate( blUpdateNoOptions, nil, inList );
						bl_Clear((*inList)->privateFlags,blHaveDrawnInsertionCaret | blHaveDrawnDragHilite);
						//bl_Clear((*inList)->privateFlags,blHaveDrawnDragHilite);
					}
				}
			}
		}
	}
}


//===============================
static void local_BLCanDropHere( DragRef inDragRef, Point inCurrentMouseLocal, BlugsRef inList )
{
	BLCell	dropUnderThisCell;
	Boolean	canDrop = false, disclosure;
	Rect	cellRect, viewRect;
	Point	caretLoc;
	UInt16	nRows;
	
	disclosure = bl_Disclosure(inList);
	nRows = bl_NRows(inList);
	dropUnderThisCell = _BLPointToCell( true, inCurrentMouseLocal, inList );
	viewRect = _BLViewRect( inList );
	// Draw an insertion caret if the mouse is within blReorderMovementPixelThreshold
	//	of bottom of cell. First check below mouse, then as plan B check above.
	// This strategy enforces the idea of putting the drag material "into", hence under,
	//	the target.
	// Should we only try this when inside a valid column? I think not. We force the column to 1.
	dropUnderThisCell.col = 1;
	if (dropUnderThisCell.row)
	{
		(void)_BLCellRect( dropUnderThisCell, &cellRect, inList );
		// We are in a valid cell, so see if we are within blReorderMovementPixelThreshold
		//	of the bottom.
		if (inCurrentMouseLocal.v >= cellRect.bottom - blReorderMovementPixelThreshold)
		{
			canDrop = true;
			caretLoc.v = cellRect.bottom;
		}
		// Otherwise if we are within blReorderMovementPixelThreshold of the top then
		//	we will be trying to drop under the previous disclosed row.
		else if (inCurrentMouseLocal.v <= cellRect.top + blReorderMovementPixelThreshold)
		{
			/*if (disclosure)
				dropUnderThisCell.row = _BLPreviousDisclosedRow( dropUnderThisCell.row, inList );
			else*/ dropUnderThisCell.row--;
			canDrop = true;
			caretLoc.v = cellRect.top;
		}
	}
	else
	{
		// We are below the last row.
		// Our disclosure level calculations are a little screwy here if the last n rows
		//	are not disclosed. If moving into last disclosed row, disclosure level max
		//	is that disclosed row + 1 and we move just after that row. If moving after
		//	it, at a lesser or equal disclosure level, the row we move after is the
		//	last one in the list.
		dropUnderThisCell.row = nRows;
		canDrop = true;
		if (nRows) (void)_BLCellRect( dropUnderThisCell, &cellRect, inList );
		else cellRect.bottom = viewRect.top;
		caretLoc.v = cellRect.bottom;
	}
	if (canDrop)
	{
		UInt16					disclosureLevel = 0;
		BLDropValidationProcPtr	dropValidationProc = (*inList)->callbacks.dropValidationProc;
		
		if (disclosure)
			disclosureLevel = local_BLMouseToDisclosureLevel( inCurrentMouseLocal, &dropUnderThisCell.row, inList );
		canDrop = local_BLSameListDragConstraints( inDragRef, dropUnderThisCell.row, disclosureLevel, inList );
		if (canDrop && dropValidationProc)
		{
			OSErr	err;
			
			err = (*dropValidationProc)( inDragRef, dropUnderThisCell.row, disclosureLevel, inList );
			if (err) canDrop = false;
		}
		if (canDrop)
		{
			Boolean		drawIt = false;
			
			// See if we have drawn an insertion caret before, for this drag.
			// We will draw if rows are different, or if same and levels differ.
			// Or if the list says we haven't drawn it.
			if (!bl_Test((*inList)->privateFlags,blHaveDrawnInsertionCaret) ||
				(*inList)->lastInsertionCaretRow != dropUnderThisCell.row)
				drawIt = true;
			if (disclosure)
			{
				// If in same row as before, check if new level.
				if ((*inList)->lastInsertionCaretDisclosure != disclosureLevel) drawIt = true;
			}
			if (drawIt)
			{
				RgnHandle	saveClip = NewRgn();
				
				caretLoc.h = cellRect.left + (bl_Indent(inList) * (disclosureLevel + 1));
				// Update to clear away old caret.
				local_BLEraseInsertionCaret( inDragRef, inList );
				GetClip( saveClip );
				ClipRect( &viewRect );
				// Move up one pixel so we're pointing across the cell border.
				if (dropUnderThisCell.row) caretLoc.v--;
				_BLDrawDragInsertionCaret( caretLoc.h, caretLoc.v, viewRect.right - 3 );
				SetClip( saveClip );
				DisposeRgn( saveClip );
				(*inList)->lastInsertionCaretPos = caretLoc;
				(*inList)->lastInsertionCaretRow = dropUnderThisCell.row;
				(*inList)->lastInsertionCaretDisclosure = disclosureLevel;
				bl_Set((*inList)->privateFlags,blHaveDrawnInsertionCaret);
			}
		} // can drop
		else local_BLEraseInsertionCaret( inDragRef, inList );
	}
}

//===============================
// Returns true if it is possible to drop under this row.
// We've done the simple calculations, now we do the harder (and potentially slower)
//	ones: disclosure constraints.
static Boolean local_BLSameListDragConstraints( DragRef inDragRef,
												UInt16 inDropUnderThisRow,
												UInt16 inDisclosureLevel,
												BlugsRef inList )
{
	OSErr		err;
	BlugsRef			originList;
	Boolean		canDrop = true;
	
	// We only have to worry about this if we are dragging within a list: if
	//	the origin list stored in the DragRef and the list we're tracking are the same.
	err = BLGetListFromDrag( inDragRef, &originList );
	if (!err && originList == inList)
	{
		UInt16		nDragItems;
		
		err = CountDragItems( inDragRef, &nDragItems );
		if (!err)
		{
			UInt16				i, storedRow = 0;
			DragItemRef			itemRef;
			Boolean				haveMultipleRows = false, disclosure;
			//BLRowDataArrayHdl	rowData;
			
			//rowData = bl_RowData(inList);
			disclosure = bl_Disclosure(inList);
			// Check each cell in the drag.
			// We can't drag a cell into itself or one of its descendants.
			// If one cell is invalid, the target is invalid.
			for (i = 1; i <= nDragItems; i++)
			{
				err = GetDragItemReferenceNumber( inDragRef, i, &itemRef );
				if (!err)
				{
					BLCell		dragCell;
					Size		dataSize;
					
					dataSize = sizeof(BLCell);
					err = GetFlavorData( inDragRef, i, blDragFlavor_Cell, &dragCell, &dataSize, 0 );
					if (!err && dataSize == sizeof(BLCell))
					{
						UInt16	draggedRowDisclosureLevel;
						
						draggedRowDisclosureLevel = _BLGetRowDisclosureLevel( dragCell.row, inList );
						// If the same row, no can do.
						// But we do allow it if we're dragging to a shallower disclosure level.
						if (dragCell.row == inDropUnderThisRow)
						{
							if (draggedRowDisclosureLevel <= inDisclosureLevel)
							{
								canDrop = false;
								break;
							}
						}
						// We can drop under the immediately previous row,
						//	but only allow it if the disclosure level changes,
						//	or if we're dragging multiple rows.
						// We keep track of whether we have seen multiple rows
						//	by comparing each row number with the one stored previously;
						//	when there is a difference and the stored row number is nonzero
						//	we record that we've seen multiple rows.
						if (storedRow && dragCell.row != storedRow) haveMultipleRows = true;
						storedRow = dragCell.row;
						if (!haveMultipleRows)
						{
							// We only check this the last time thru the loop so we've had
							//	a chance to check for multiple rows.
							if (i == nDragItems)
							{
								if (dragCell.row - 1 == inDropUnderThisRow)
								{
									if (inDisclosureLevel == draggedRowDisclosureLevel)
									{
										canDrop = false;
										break;
									}
								}
							}
						}
						// If one of the cells being dragged is an ancestor of this
						//	so-called 'inDropUnderThisCell', no can do neither.
						// We call _BLCountDescendants for the dragged cell. If the 'drop
						//	under' row number is less than the dragged cell row number
						//	plus descendants, then we cannot drop.
						// This means if we are dragging row 2 into row 10, if 2 has
						//	8 descendants we can't drop. If 7 descendants we can:
						//	2 + 7 < 10.
						// This only needs to be done when dragging from above to below.
						if (dragCell.row < inDropUnderThisRow)
						{
							if (disclosure)
							{
								UInt16	nDescendants;
								
								nDescendants = _BLCountDescendants( dragCell.row, inList );
								if (dragCell.row + nDescendants >= inDropUnderThisRow)
								{
									canDrop = false;
									break;
								}
							}
						}
					}
				}
			}
		}
	}
	return canDrop;
}

//===============================
// ASSUMES DISCLOSURE
// Calculates disclosure level mouse corresponds to.
// If no previous row, level is 0.
// If previous row,
//	if not a parent row then clamp values from level of next row (if exists) or 0 (if not),
//	to level of previous. (0 || next ... previous)
//	but if previous is a parent, clamp values from parent level + 1 to next row (if exists)
//	or 0 (if not). (0 || next ... previous + 1)
//
// We calculate the disclosure level by dividing the distance between viewRect left and the
//	mouse, by the indent.
static UInt16 local_BLMouseToDisclosureLevel( Point inMouseLocal, UInt16* ioDropUnderThisRow,
												BlugsRef inList )
{
	UInt16	level = 0, newRow;
	
	newRow = *ioDropUnderThisRow;
	if (newRow)
	{
		Rect			viewRect;
		UInt16			min, max;
		SInt16			firstIndent; // Location of first indent level.
		
		viewRect = _BLViewRect( inList );
		firstIndent = viewRect.left + bl_Indent(inList);
		if (inMouseLocal.h < firstIndent) level = 0;
		else level = (inMouseLocal.h - firstIndent) / bl_Indent(inList);
		// We always use inDropUnderThisRow except when inDropUnderThisRow is not disclosed.
		if (bl_Test(_BLGetRowFlags(newRow,inList),blUndisclosed))
		{
			UInt16			prevDisclosedRow, prevDisclosedRowLevel;
			
			// Anything greater than the previous disclosed row's level uses that parent
			//	as the new drop-under, so we can drop only one level in, and always
			//	at the top of that parent's hierarchy.
			prevDisclosedRow = _BLPreviousDisclosedRow( newRow, inList );
			prevDisclosedRowLevel = _BLGetRowDisclosureLevel( prevDisclosedRow, inList );
			if (level > prevDisclosedRowLevel) newRow = prevDisclosedRow;
		}
		if (!_BLValidDisclosureTarget( true, newRow, level, &min, &max, inList ))
		{
			if (level < min) level = min;
			else if (level > max) level = max;
		}
		// If dropping under the last row, we allow levels of 0 thru level of last disclosed row.
		if (newRow == bl_NRows(inList))
		{
			if (!_BLRowIsDisclosed( newRow, inList ))
			{
				UInt16	prevDisclosedRow;
				
				prevDisclosedRow = _BLPreviousDisclosedRow( newRow, inList );
				if (prevDisclosedRow)
				{
					max = _BLGetRowDisclosureLevel( prevDisclosedRow, inList );
					if (level > max) level = max;
				}
			}
		}
	}
	*ioDropUnderThisRow = newRow;
	return level;
}

//===============================
BL_PROC OSErr BLGetListFromDrag( DragRef inDragRef, BlugsRef* outList )
{
	BlugsRef	list = nil;
	SInt32		dataSize = sizeof(BlugsRef);
	OSErr		err;
	
	err = GetFlavorData( inDragRef, blPrivateDragItemRef, blDragFlavor_OriginList, &list, &dataSize, 0 );
	if (err || !list || dataSize != sizeof(BlugsRef)) err = cantGetFlavorErr;
	*outList = (err) ? nil : list;
	return err;
}

//===============================
BL_PROC OSErr BLGetCellFromDragItemRef( DragRef inDragRef, DragItemRef inDragItem, BLCell* outCell )
{
	SInt32		dataSize = sizeof(BLCell);
	OSErr		err;
	BLCell		cell;
	
	err = GetFlavorData( inDragRef, inDragItem, blDragFlavor_Cell, &cell, &dataSize, 0 );
	if (err || dataSize != sizeof(BLCell)) err = cantGetFlavorErr;
	else *outCell = cell;
	return err;
}

//===============================
static void local_BLEraseInsertionCaret( DragRef inDragRef, BlugsRef inList )
{
	if (bl_Test((*inList)->privateFlags,blHaveDrawnInsertionCaret))
	{
		Point			lastCaret = (*inList)->lastInsertionCaretPos;
		Rect			viewRect, r;
		RgnHandle		updateRgn = NewRgn();
		DragAttributes	attributes;
		
		r = viewRect = _BLViewRect( inList );
		r.top = lastCaret.v - 5;
		r.bottom = lastCaret.v + 5;
		// Don't blit over the drag hilite on the left or right side
		//	unless the caret was drawn to the left of the hilite edge.
		if (lastCaret.h > viewRect.left) r.left += 3;
		r.right -= 3;
		RectRgn( updateRgn, &r );
		_BLUpdate( blUpdateBlit, updateRgn, inList );
		GetClip( updateRgn );
		ClipRect( &r );
		bl_Clear((*inList)->privateFlags,blHaveDrawnDragHilite);	
		(void)GetDragAttributes( inDragRef, &attributes );
		if (bl_Test(attributes,kDragHasLeftSenderWindow)) _BLPaintDragHilite( viewRect, inList );
		SetClip( updateRgn );
		DisposeRgn( updateRgn );
		bl_Clear((*inList)->privateFlags,blHaveDrawnInsertionCaret);
	}
}
