/*	XGDropperWindow.cpp
 *
 *		Implements drag and drop. The XGDropperWindow object is
 *	created and associated with an XGWindow when one of it's
 *	contained views is an XGDropper view. This is used to implement
 *	multiple drop targets per window.
 */

/**********************************************************************

  Original code Copyright 1998-1999 Sassafras Software Inc.
  Modifications Copyright 1999 William Woody

  YAAF is Copyright 1997-1999 by William Woody.

  Redistribution and use in source and binary forms, with or without 
  modification, are permitted provided that the following conditions 
  are met:

  Redistributions of source code must retain the above copyright notice, 
  this list of conditions and the following disclaimer.

  Neither the name of the copyright holder or other contributors may
  be used to promote products derived from this software without
  specific written prior permission.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 
  OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 
  BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 
  ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 
  CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 
  SOFTWARE.
  
  Contact:   woody@alumni.caltech.edu
  Web page:  http://www.pandawave.com/yaaf/

**********************************************************************/

#include <XConfig.h>			// must be first to optimize compile on MSVC++

#if OPT_MACOS
#include <XApplication.h>		// only need _GHasDragDrop symbol
#include <drag.h>
#endif

#if OPT_WINOS
#include <ole2.h>
#include <shellapi.h>
#endif

#include <XView.h>
#include <XWindow.h>
#include <XDraw.h>
#include <XError.h>
#include <XDragDrop.h>

/************************************************************************/
/*																		*/
/*	Forwards															*/
/*																		*/
/************************************************************************/

#if OPT_MACOS
pascal OSErr DraggerTracker (DragTrackingMessage msg, WindowPtr w, void *dropper, DragReference dragRef);
pascal OSErr DraggerReceiver (WindowPtr w,void *dropper, DragReference dragRef);
#endif

/************************************************************************/
/*																		*/
/*	Windows COM interfaces												*/
/*																		*/
/************************************************************************/

#if OPT_WINOS
#define FORMATETC_MAX 20

class XGDropTarget : public IDropTarget, public XGDispatch {
	public:
					XGDropTarget (XGDropperWindow *, HWND);
		virtual		~XGDropTarget (void);

		STDMETHODIMP QueryInterface (REFIID riid, LPVOID FAR* ppvObj);
		STDMETHODIMP_(ULONG) AddRef (void);
		STDMETHODIMP_(ULONG) Release (void);

		STDMETHODIMP DragEnter (LPDATAOBJECT pDataObj, DWORD keyState, POINTL pt, LPDWORD effect); 
		STDMETHODIMP DragOver  (DWORD keyState, POINTL pt, LPDWORD effect); 
		STDMETHODIMP DragLeave (void); 
		STDMETHODIMP Drop (LPDATAOBJECT pDataObj, DWORD keyState, POINTL pt, LPDWORD effect); 
		
		long		TranslateModifiers( DWORD winMods );
//		DECLAREDISPATCH
//		long		DoDropFiles (long, void *);

	private:
		XGDropperWindow *fDropper;
		HWND fWindow;
		long fRefs;
		long fScrollStart;
		long fScrollNext;
		long fScrollDir;
		long fCfText;

		bool QueryDrop (DWORD, POINTL, bool, LPDWORD);
		bool DoDragScroll (DWORD, POINTL);
};
#endif

/************************************************************************/
/*																		*/
/*	Globals																*/
/*																		*/
/************************************************************************/

#if OPT_MACOS

DragTrackingHandlerUPP		XGDropperWindow::gTracker;
DragReceiveHandlerUPP		XGDropperWindow::gReceiver;

#endif

XGDropperWindow				*XGDropperWindow::gList;

/************************************************************************/
/*																		*/
/*	Construction														*/
/*																		*/
/************************************************************************/

/*$	XGDropperWindow::XGDropperWindow
 *
 *		The default; this permits me to mix this in easily to a view.
 */

XGDropperWindow::XGDropperWindow(void)
{
	fLast = NULL;
	fDrop = NULL;
	fAccepting = false;
	fTracking = false;
	fNext = NULL;
	
#if OPT_MACOS
	fWindow = NULL;
#endif

#if OPT_WINOS
	fTarget = NULL;
	fWindow = NULL;
#endif

#if OPT_XWIN
	fWindow = 0;
#endif
}

/*$	XGDropperWindow::~XGDropperWindow
 *
 *		Free memory associated with this thing
 */

XGDropperWindow::~XGDropperWindow (void)
{
	XGDropperWindow *ptr;
	
#if OPT_MACOS
	if (fWindow && _GHasDragDrop) {
		RemoveTrackingHandler(gTracker, fWindow);
		RemoveReceiveHandler(gReceiver, fWindow);
	}
#endif

#if OPT_WINOS
//	DragAcceptFiles(fView->GetRootWindow(), FALSE);
	XGDropTarget *tdo;
	if (NULL != (tdo = (XGDropTarget *)fTarget)) {
		RevokeDragDrop(fWindow);
		CoLockObjectExternal(tdo, FALSE, TRUE);
		delete tdo;
	}
#endif

#if OPT_XWIN
	// Nothing more needed here
#endif

	if (gList == this) gList = fNext;
	for (ptr = gList; ptr != NULL; ptr = ptr->fNext) {
		if (ptr->fNext == this) {
			ptr->fNext = fNext;
			break;
		}
	}
}

/************************************************************************/
/*																		*/
/*	Construction Support												*/
/*																		*/
/************************************************************************/

/*	XGDropperWindow::IInitDropper
 *
 *		Internal initialization; this builds the appropriate internal
 *	structures, depending on if this is Windows or the Mac.
 */

void XGDropperWindow::IInitDropper (void)
{
#if OPT_MACOS
	Assert(fWindow != NULL);
	
	if (_GHasDragDrop) {
		if (gTracker == NULL) gTracker = NewDragTrackingHandlerUPP(DraggerTracker);
		if (gReceiver == NULL) gReceiver = NewDragReceiveHandlerUPP(DraggerReceiver);
		
		InstallTrackingHandler(gTracker, fWindow, this);
		InstallReceiveHandler(gReceiver, fWindow, this);
	}
#endif

#if OPT_WINOS
//	DragAcceptFiles(fView->GetRootWindow(), TRUE);
	XGDropTarget *tdo = new XGDropTarget(this, fWindow);
	if (NULL != (fTarget = (void *)tdo)) {
		CoLockObjectExternal(tdo, TRUE, FALSE);
		RegisterDragDrop(fWindow, tdo);
	}
#endif

#if OPT_XWIN
	// Nothing more needed here
#endif

	/*
	 *	Add to linked list
	 */
	
	fNext = gList;
	gList = this;
}

/************************************************************************/
/*																		*/
/*	Global installation/shutdown										*/
/*																		*/
/************************************************************************/

/*	XGDropperWindow::InstallDropper
 *
 *		Install the dropper; this creates a XGDropperWindow if one
 *	is needed, and installs it.
 */

XGDropperWindow *XGDropperWindow::InstallDropper(XGDropper *drop)
{
#if OPT_MACOS
	WindowPtr w;
#endif

#if OPT_WINOS
	HWND w;
#endif

#if OPT_XWIN
	Window w;
#endif

	XGDropperWindow *dw;

#if OPT_MACOS	
	w = drop->GetView()->GetWindow()->GetGrafPtr();
	Assert(w != NULL);
#endif

#if OPT_WINOS
	w = drop->GetView()->GetWindow()->GetHWND();
	Assert(w != NULL);
#endif

#if OPT_XWIN
	w = drop->GetView()->GetWindow()->GetWindow();
	Assert(w != 0);
#endif

	for (dw = gList; dw != NULL; dw = dw->fNext) {
		if (dw->fWindow == w) break;
	}
	if (dw == NULL) {
		/*
		 *	An XGDropperWindow doesn't exist for this thing. Create it.
		 */
		
		dw = new XGDropperWindow;
		dw->fWindow = w;
		dw->IInitDropper();
	}
	
	/*
	 *	Now add the dropper to the list.
	 */
	
	drop->fNext = dw->fDrop;
	dw->fDrop = drop;
	return dw;
}

/*	XGDropperWindow::RemoveDropper
 *
 *		Remove this dropper. This is done when the dropper is being
 *	disposed of, and deletes me when all of the droppers have been
 *	completely disposed of.
 */

void XGDropperWindow::RemoveDropper(XGDropper *drop)
{
	XGDropper *ptr;
	
	if (fDrop == drop) {
		fDrop = drop->fNext;
		if (fDrop == NULL) delete this;
	} else {
		for (ptr = fDrop; ptr != NULL; ptr = ptr->fNext) {
			if (ptr->fNext == drop) {
				ptr->fNext = drop->fNext;
			}
		}
	}
}

/************************************************************************/
/*																		*/
/*	Drag/Drop Support													*/
/*																		*/
/************************************************************************/

/*$	XGDropperWindow::FindDropper
 *
 *		Given a global location inside of a window, this scans all of
 *	the droppers inside and finds the drag and drop target which the
 *	point is inside of.
 */

XGDropper *XGDropperWindow::FindDropper(XPoint where)
{
	XRect wrect;
	XGDropper *drop;
	XGView *v;
	
	if (fLast) {
		v = fLast->GetView();
		wrect = v->GetContentRect();
		v->ViewToGlobal(&wrect);
		if (PtInRect(where,&wrect)) return fLast;
	}
	
	for (drop = fDrop; drop != NULL; drop = drop->fNext) {
		if (fLast == drop) continue;	/* Already checked this one */
		v = drop->GetView();
		wrect = v->GetContentRect();
		v->ViewToGlobal(&wrect);
		if (PtInRect(where,&wrect)) return drop;
	}
	return NULL;
}

/*$	XGDropperWindow::ClearTrackDropper
 *
 *		Clear the dropper associated with this window.
 */

void XGDropperWindow::ClearTrackDropper()
{
	if (fLast) {
		fLast->Leave();
		fLast = NULL;
	}
}

/*$	XGDropperWindow::SetTrackDropper
 *
 *		This sets this dropper target as the target of this drag and
 *	drop. This determines if this item is accepting drag and drop (set
 *	by the DragAccept() method), and calls the appropriate stuff.
 *
 *		This also converts from window coordinates to local coordinates
 *	so that the view being called can just do the right thing.
 */

void XGDropperWindow::SetTrackDropper(XGDropper *drop, XPoint where, long mods, bool inside)
{
	XGView *v;
	
	if (drop == fLast) {
		if (drop && (drop->IsAccepting())) {
			v = drop->GetView();
			v->GlobalToView(&where);
			drop->Track(where,mods);
		}
		return;
	}
	
	if (fLast) {
		if (fLast->IsAccepting()) fLast->Leave();
	}
	fLast = drop;
	if (fLast) {
		if (fLast->IsAccepting()) {
			v = fLast->GetView();
			v->GlobalToView(&where);
			fLast->Enter(where,mods,inside);
		}
	}
}

/*$	XGDropperWindow::TrackDropper
 *
 *		This single entry point does the right thing; this figures out
 *	which dropper is the one accepting this, and calls the appropriate
 *	routines
 */

void XGDropperWindow::TrackDropper(XPoint where, long mods, bool inside)
{
	SetTrackDropper(FindDropper(where),where,mods,inside);
}

/************************************************************************/
/*																		*/
/*	Tracker Routines													*/
/*																		*/
/************************************************************************/

/*$	XGDropperWindow::DragAccept
 *
 *		Accept drag and drop; this determines if I can handle this
 *	item for drag and drop, and also updates all of the drop targets
 *	inside of me for later use.
 */

#if OPT_MACOS

bool XGDropperWindow::DragAccept(DragReference dragRef)
{
	short mods;
	unsigned short items, flavors;
	int i, j;
	ItemReference itemRef;
	FlavorType itemFlavor;
	bool accept;
	XGDropper *drop;
	
	/*
	 *	Get the modifiers ###
	 */
	
	GetDragModifiers(dragRef,&mods,NULL,NULL);

	/*
	 *	Now iterate through all of my droppers to determine which can
	 *	handle this target
	 */
	
	for (drop = fDrop; drop != NULL; drop = drop->fNext) {
		drop->SetDragReference(dragRef);
		drop->Accepting(false);

		if (_GDragDropInternal) {
			// check to see if this is an internal (inside same view) drag/drop
			if (_GDragDropInternal != drop->GetView()) continue;
		}

		CountDragItems(dragRef, &items);
		for (i = 1; i <= items; i++) {
			if (GetDragItemReferenceNumber(dragRef, i, &itemRef))
				continue;
			if (CountDragItemFlavors(dragRef, itemRef, &flavors))
				continue;
			for (j = 1; j <= flavors; j++) {
				if (!GetFlavorType(dragRef, itemRef, j, &itemFlavor)
					&& drop->Accept(mods, itemRef, XGDragger::NativeToFlavor((long)itemFlavor)))
				{
					drop->Accepting(true);
					accept = true;
					break;
				}
			}
			if (drop->IsAccepting()) break;
		}
	}
	
	fAccepting = accept;
	return accept;
}

#endif

#if OPT_WINOS

bool XGDropperWindow::DragAccept(LPDATAOBJECT pDataObj, DWORD keyState, POINTL)
{
	XGDropper *drop;
	LPENUMFORMATETC lpEnumFmtEtc = NULL;
	FORMATETC rgfmtetc[FORMATETC_MAX];
	unsigned long i, cFetched;
	HRESULT err;
	bool accept = false;

	err = pDataObj->EnumFormatEtc(DATADIR_GET,
							(LPENUMFORMATETC FAR*)&lpEnumFmtEtc);
	if (err != NOERROR) {
		fAccepting = false;
		return(false);
	}

	// Enumerate the formats offered by the source
	cFetched = 0;
	memset(rgfmtetc, 0, sizeof(rgfmtetc));
	if (lpEnumFmtEtc->Next(FORMATETC_MAX, rgfmtetc, &cFetched) == NOERROR
		|| (cFetched > 0 && cFetched <= FORMATETC_MAX) )
	{
		for (drop = fDrop; drop != NULL; drop = drop->fNext) {
			drop->Accepting(false);

			if (_GDragDropInternal) {
				// check to see if this is an internal (inside same view) drag/drop
				if (_GDragDropInternal != drop->GetView()) continue;
			}
			for (i = 0; i < cFetched; i++) {
				if (drop->Accept(keyState, 0, XGDragger::NativeToFlavor(rgfmtetc[i].cfFormat))) {
					drop->Accepting(true);
					accept = true;
					break;
				}
			}
		}
	}

	// Clean up
	if (lpEnumFmtEtc)
		lpEnumFmtEtc->Release();
		
	fAccepting = accept;
	return accept;
}

#endif

#if OPT_MACOS || OPT_XWIN

/*	XGDropperWindow::DragAccept
 *
 *		The portable version used by the MacOS without drag/drop (Pre Sys7.1)
 *	and on X
 */

bool XGDropperWindow::DragAccept(XGHandle &data, short mods)
{
	bool accept;
	XGDropper *drop;
	long off,len;
	long iref,flav;
	
	/*
	 *	Now iterate through all of my droppers to determine which can
	 *	handle this data object
	 */
	
	accept = false;
	XGHandleLock lock(data);
	for (drop = fDrop; drop != NULL; drop = drop->fNext) {
		drop->Accepting(false);

		if (_GDragDropInternal) {
			// check to see if this is an internal (inside same view) drag/drop
			if (_GDragDropInternal != drop->GetView()) continue;
		}

		len = data.GetSize();
		off = 0;
		while (off < len) {
			iref = data.GetLong(off);
			flav = data.GetLong(off+4);
			off += data.GetLong(off+8) + 12;
		
			if (drop->Accept(mods,iref,flav)) {
				drop->Accepting(true);
				accept = true;
				break;
			}
		}
	}

	fAccepting = accept;
	return accept;
}

#endif

/************************************************************************/
/*																		*/
/*	Internal Routines													*/
/*																		*/
/************************************************************************/

#if OPT_MACOS

/*	DraggerTracker
 *
 *		This is called by the Mac Drag and Drop manager to determine
 *	how I should handle this drag and drop event
 */

pascal OSErr DraggerTracker (DragTrackingMessage msg, WindowPtr w,void *dropperx, DragReference dragRef)
{
	short mods;
	DragAttributes attributes;
	Point where;
	bool inside;
	Point oldorigin;
	GrafPtr oldport;
	XGDropperWindow *dropper;
	
	dropper = (XGDropperWindow *)dropperx;
	GetDragAttributes(dragRef, &attributes);
	GetDragMouse(dragRef,&where,NULL);
	
	/*
	 *	Convert to local coordinates (there has got to be a better way...)
	 */
	
	GetPort(&oldport);
	oldorigin.h = oldport->portRect.left;
	oldorigin.v = oldport->portRect.top;
	SetPort(w);
	SetOrigin(0,0);
	GlobalToLocal(&where);
	SetPort(oldport);
	SetOrigin(oldorigin.h,oldorigin.v);
	
	/*
	 *	Drawing
	 */
	
	switch(msg) {
		case kDragTrackingEnterHandler:
			break;

		case kDragTrackingEnterWindow:
			/*
			 *	The dragger has entered this window. Figure out where
			 *	it should go. The call to DragAccept sets the accept
			 *	flag for all of the droppers, for efficiency...
			 */
			
			dropper->DragAccept(dragRef);
			if (!dropper->IsAccepting()) return(dragNotAcceptedErr);
			
			/*
			 *	Now determine if the dragger is still inside the
			 *	sender window. If it is, then we assume that this
			 *	event was sent in response to the start of the
			 *	drag operation. In order to avoid the initial 'enter'
			 *	call from being made, we preinitialize the fDrop
			 *	value.
			 */
			
			dropper->InitTrackDropper(NULL);
			break;

		case kDragTrackingInWindow:
			/*
			 *	Tracking the drop point inside of this window.
			 *	This simply gets the modifiers and mouse location,
			 *	and defers to the TrackDropper routine.
			 */
			
			if (dropper->IsAccepting()) {
				GetDragModifiers(dragRef,&mods,NULL,NULL);
				inside = (attributes & kDragHasLeftSenderWindow) ? true : false;
				dropper->TrackDropper(where,mods,inside);
			}
			break;

		case kDragTrackingLeaveWindow:
			if (dropper->IsAccepting()) {
				dropper->ClearTrackDropper();
			}
			break;

		case kDragTrackingLeaveHandler:
			break;
	}
	return(noErr);
}

/*	DraggerReceiver
 *
 *		This is the callback which is called when it's time to
 *	receive
 */

pascal OSErr DraggerReceiver (WindowPtr w,void *dropwinx, DragReference dragRef)
{
	XPoint where;
	Point mwhere;
	short mods;
	unsigned short items, flavors;
	int i, j;
	ItemReference itemRef;
	FlavorType itemFlavor;
	Size dlen;
	void *data;
	bool accepted;
	GrafPtr oldport;
	Point oldorigin;
	XGDropper *dropper;
	XGDropperWindow *dropwin;
	
	dropwin = (XGDropperWindow *)dropwinx;
	dropper = dropwin->GetTrackDropper();
	if (dropper == NULL) return dragNotAcceptedErr;
	if (!dropper->IsAccepting()) return dragNotAcceptedErr;

	/*
	 *	Drop accepted; now try to drop the data
	 */

	accepted = false;
	GetDragMouse(dragRef, &mwhere, NULL);
	GetDragModifiers(dragRef,&mods,NULL,NULL);

	/*
	 *	Convert to local coordinates (there has got to be a better way...)
	 */
	
	GetPort(&oldport);
	oldorigin.h = oldport->portRect.left;
	oldorigin.v = oldport->portRect.top;
	SetPort(w);
	SetOrigin(0,0);
	GlobalToLocal(&mwhere);
	SetPort(oldport);
	SetOrigin(oldorigin.h,oldorigin.v);
	where = mwhere;
	dropper->GetView()->GlobalToView(&where);

	/*
	 *	Drop the items
	 */
	
	CountDragItems(dragRef, &items);
	dropper->BeginDrop( where, mods );
	for (i = 1; i <= items; i++) {
		if (GetDragItemReferenceNumber(dragRef, i, &itemRef))
			continue;
		if (CountDragItemFlavors(dragRef, itemRef, &flavors))
			continue;
		for (j = 1; j <= flavors; j++) {
			if (!GetFlavorType(dragRef, itemRef, j, &itemFlavor)
				&& dropper->Accept(mods, itemRef, XGDragger::NativeToFlavor((long)itemFlavor)))
			{
				if (GetFlavorDataSize(dragRef, itemRef, itemFlavor, &dlen))
					continue;
				if (NULL != (data = NewPtr(dlen))) {
					GetFlavorData(dragRef, itemRef, itemFlavor, data, &dlen, NULL);
					dropper->Drop(where, mods, itemRef, XGDragger::NativeToFlavor((long)itemFlavor), data, dlen);
					DisposePtr((Ptr)data);
					accepted = true;
				}
			}
		}
	}
	dropper->EndDrop( where, mods );
	if (!accepted)
		return(dragNotAcceptedErr);
	dropwin->ClearTrackDropper();
	return(noErr);
}
#endif

/************************************************************************/
/*																		*/
/*	Windows COM interfaces												*/
/*																		*/
/************************************************************************/

#if OPT_WINOS


long XGDropTarget::TranslateModifiers( DWORD winMods )
{
	long yaafMods = 0;
	
	if (winMods & MK_CONTROL) yaafMods |= KKeyControl;
	if (winMods & MK_SHIFT) yaafMods |= KKeyShift;
	if (winMods & MK_ALT) yaafMods |= KKeyMenu;
	if (winMods & MK_LBUTTON) yaafMods |= KKeyButton;
	if (winMods & MK_RBUTTON) yaafMods |= KKeyRButton;
	if (winMods & MK_MBUTTON) yaafMods |= KKeyMButton;
	
	return yaafMods;
}


XGDropTarget::XGDropTarget (XGDropperWindow *dropper, HWND window)
{
	fDropper = dropper;
	fWindow = window;
	fRefs = 0;
	fScrollStart = 0;
	fScrollNext = 0;
	fScrollDir = 0;
//	XGBroadcast::AddBroadcast(this);
}

XGDropTarget::~XGDropTarget (void)
{
	Assert(fRefs == 0);
//	XGBroadcast::RemoveBroadcast(this);
}

STDMETHODIMP
XGDropTarget::QueryInterface (REFIID riid, LPVOID FAR* ppvObj)
{
	*ppvObj = NULL;	// must set out pointer parameters to NULL

	// looking for IUnknown
	if (riid == IID_IUnknown) {
		AddRef();
		*ppvObj = this;
		return(S_OK);
	}

	// looking for IDropTarget
	if (riid == IID_IDropTarget) {
		AddRef();
		*ppvObj = this;
		return(S_OK);
	} 

	// Not a supported interface
	return(E_NOINTERFACE);
}

STDMETHODIMP_(ULONG)
XGDropTarget::AddRef (void)
{
	return(++fRefs);
}

STDMETHODIMP_(ULONG)
XGDropTarget::Release (void)
{
	Assert(fRefs > 0);
	if (!--fRefs) {
		// delete this ??
	}
	return(fRefs);
}

bool XGDropTarget::QueryDrop (DWORD, POINTL, bool scroll, LPDWORD effect)
{
	if (fDropper->IsAccepting()) {
	    if (*effect & DROPEFFECT_COPY) {
			*effect = DROPEFFECT_COPY;
			if (scroll)
				*effect |= DROPEFFECT_SCROLL;
			return TRUE;
		}
	}

	*effect = DROPEFFECT_NONE;
	return FALSE;
}

bool XGDropTarget::DoDragScroll (DWORD, POINTL pt)
{
	long direction = XGDropper::kScrollNone;
	long now = GetCurrentTime();
	XPoint vpt;
	XRect r;
	XGDropper *drop;
	XGView *v;
	
	drop = fDropper->GetTrackDropper();
	if (drop == NULL) return false;
	v = drop->GetView();

	vpt.h = pt.x;
	vpt.v = pt.y;
	v->ScreenToView(&vpt);
	r = v->GetContentRect();

	if (vpt.v <= r.top + 4)
		direction = XGDropper::kScrollUp;
	else if (r.bottom - 4 <= vpt.v)
		direction = XGDropper::kScrollDown;
	else if (vpt.h <= r.left + 5)
		direction = XGDropper::kScrollLeft;
	else if (r.right - 5 <= vpt.h)
		direction = XGDropper::kScrollRight;

	if (!fScrollStart) {
		if (direction != XGDropper::kScrollNone) {
			/* cursor moved INTO a scroll area.
			 *	reset "EnterScrollArea" time to start a new 50ms delay.
			 */
			fScrollStart = now;
			fScrollNext = now + 50;
			fScrollDir = direction;
		}
	} else {
		/* cursor was already in Scroll Area */
		if (direction == XGDropper::kScrollNone) {
			/* cursor moved OUT of scroll area.
			 *	clear "EnterScrollArea" time.
			 */
			fScrollStart = 0;
			fScrollNext = 0;
			fScrollDir = 0;

		} else if (direction != fScrollDir) {
			/* cursor moved into a different direction scroll area.
			 *	reset "EnterScrollArea" time to start a new 50ms delay.
			 */
			fScrollStart = now;
			fScrollNext = now + 50;
			fScrollDir = direction;

		} else if (now >= fScrollNext) {
			drop->Scroll(direction);
			fScrollNext = now + 20;
		}
	}

	return(direction ? true : false);
}

STDMETHODIMP
XGDropTarget::DragEnter (LPDATAOBJECT pDataObj, DWORD keyState, POINTL pt, LPDWORD effect)
{
	fDropper->DragAccept(pDataObj,TranslateModifiers(keyState),pt);
	if (!fDropper->IsAccepting()) {
		*effect = DROPEFFECT_NONE;
	} else {
		DragOver(keyState, pt, effect);
	}
	return(NOERROR);
}

STDMETHODIMP
XGDropTarget::DragOver  (DWORD keyState, POINTL pt, LPDWORD effect)
{
	POINT delta;
	XPoint localMouse;
	
	if (!fDropper->IsAccepting()) {
		*effect = DROPEFFECT_NONE;
	} else {
		QueryDrop(keyState, pt, DoDragScroll(keyState, pt), effect);

		/*
		 *	TrackDropper expects my location in HWND coordinates.
		 *	And dispite Microsoft's documentation, we get the
		 *	coordinates in this routine in screen (not window local)
		 *	coordinates. So we convert.
		 */
		
		delta.x = 0, delta.y = 0;
		::ScreenToClient(fWindow,&delta);
		localMouse.h = pt.x + delta.x;
		localMouse.v = pt.y + delta.y;
		
		fDropper->TrackDropper(localMouse,TranslateModifiers(keyState),true);
	}
	return(NOERROR);
}

STDMETHODIMP
XGDropTarget::DragLeave (void)
{
	if (fDropper->IsAccepting()) {
		fDropper->ClearTrackDropper();
	}
	return(NOERROR);
}

STDMETHODIMP
XGDropTarget::Drop (LPDATAOBJECT pDataObj, DWORD keyState, POINTL pt, LPDWORD effect)
{
	LPENUMFORMATETC lpEnumFmtEtc = NULL;
	FORMATETC rgfmtetc[FORMATETC_MAX];
	unsigned long i, cFetched;
	STGMEDIUM stg;
	HRESULT err;
 	XPoint where;
 	XGDropper *dropper;

	if (!fDropper->IsAccepting()) {
		*effect = DROPEFFECT_NONE;
	} else if (NULL == (dropper = fDropper->GetTrackDropper())) {
		*effect = DROPEFFECT_NONE;
	} else if (!dropper->IsAccepting()) {
		*effect = DROPEFFECT_NONE;
	} else {
		*effect = DROPEFFECT_NONE;
 
		err = pDataObj->EnumFormatEtc(DATADIR_GET,
								(LPENUMFORMATETC FAR*)&lpEnumFmtEtc);
		if (err != NOERROR)
			return(err);
 
		where.h = pt.x;
		where.v = pt.y;
		dropper->GetView()->ScreenToView(&where);
		
		dropper->BeginDrop( where, TranslateModifiers(keyState) );
		
		// Enumerate the formats offered by the source
		cFetched = 0;
		memset(rgfmtetc, 0, sizeof(rgfmtetc));
		if (lpEnumFmtEtc->Next(FORMATETC_MAX, rgfmtetc, &cFetched) == NOERROR
			|| (cFetched > 0 && cFetched <= FORMATETC_MAX) )
		{
			for (i = 0; i < cFetched; i++) {
				if (!dropper->Accept(keyState, 0, XGDragger::NativeToFlavor(rgfmtetc[i].cfFormat)))
					continue;
				rgfmtetc[i].tymed = TYMED_HGLOBAL;
				if (pDataObj->GetData(&rgfmtetc[i], &stg) != NOERROR)
					continue;
				if (stg.tymed == TYMED_HGLOBAL) {
					if (rgfmtetc[i].cfFormat != CF_HDROP) {
						char *p = (char *)GlobalLock(stg.hGlobal);
						dropper->Drop(where, TranslateModifiers(keyState), 0, XGDragger::NativeToFlavor(rgfmtetc[i].cfFormat), p, GlobalSize(stg.hGlobal));
						GlobalUnlock(stg.hGlobal);
					} else {
						int j, count;
						char name[260];
						count = DragQueryFile((HDROP)stg.hGlobal, (UINT)-1, NULL, 0);
						for (j = 0; j < count; j++) {
							DragQueryFile((HDROP)stg.hGlobal, j, name, sizeof(name));
							dropper->Drop(where, 0, 0, KDragFlavorFile, name, strlen(name) + 1);
						}
					}
					*effect = DROPEFFECT_COPY;
				}
			#if 0 // this will leak memory, but it stops a crash
				ReleaseStgMedium(&stg);
			#endif
			}
		}
		
		dropper->EndDrop( where, TranslateModifiers(keyState) );

		// Clean up
		if (lpEnumFmtEtc)
			lpEnumFmtEtc->Release();
		fDropper->ClearTrackDropper();
	}
	return(NOERROR);
}

#endif
