/*	XGDragger.cpp
 *
 *	(c) 1998-99 Sassafras Software Inc.
 */

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

  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>

#if OPT_MACOS
#include <drag.h>
#endif

#include <XApplication.h>
#include <XCursorUtil.h>
#include <XWindow.h>
#include <XDraw.h>
#include <XError.h>
#include <XDragDrop.h>

/************************************************************************/
/*																		*/
/*	Support Declarations (Macintosh)									*/
/*																		*/
/************************************************************************/

#if OPT_MACOS

/*$	DragSender
 *
 *		Routine that routes drag acceptance to the 'GetObject' method
 */

static pascal OSErr DraggerSender (FlavorType flavor, 
								   void *dragger, 
								   ItemReference itemRef, 
								   DragReference)
{
	if (((XGDragger *)dragger)->GetObject(XGDragger::NativeToFlavor((long)flavor), (long)itemRef)) {
		return(noErr);
	}
	return(memFullErr);
}

#endif

/************************************************************************/
/*																		*/
/*	Support Declarations (Windows)										*/
/*																		*/
/************************************************************************/

#if OPT_WINOS
typedef struct t_objdesc {
	long itemID;
	long flavor;
	unsigned long size;
	void *data;
}  XGDataObjDesc;

class XGDropEnumFE;

/************************************************************************/
/*																		*/
/*	Support COM Objects (Windows)										*/
/*																		*/
/************************************************************************/

class XGDropSource : public IDropSource {
	public:
						XGDropSource (XGDragger *);
		virtual			~XGDropSource (void);

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

		STDMETHODIMP QueryContinueDrag (BOOL, DWORD);
		STDMETHODIMP GiveFeedback (DWORD);

	private:
		long		fRefs;

};

class XGDropObject : public IDataObject {
	public:
						XGDropObject (XGDragger *);
		virtual			~XGDropObject (void);

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

		STDMETHODIMP GetData (FORMATETC *, STGMEDIUM *);
		STDMETHODIMP GetDataHere (FORMATETC *, STGMEDIUM *);
		STDMETHODIMP QueryGetData (FORMATETC *);
		STDMETHODIMP EnumFormatEtc (DWORD, IEnumFORMATETC **);

		STDMETHODIMP GetCanonicalFormatEtc (FORMATETC *feIn, FORMATETC *feOut)
							{
								memcpy(feOut, feIn, sizeof(FORMATETC));
								feOut->ptd = NULL;
								return(DATA_S_SAMEFORMATETC);
							}
		STDMETHODIMP SetData (FORMATETC *, STGMEDIUM *, BOOL) { return(E_NOTIMPL); }
		STDMETHODIMP DAdvise (FORMATETC *, DWORD, IAdviseSink *, DWORD *) { return(E_NOTIMPL); }
		STDMETHODIMP DUnadvise (DWORD) { return(OLE_E_ADVISENOTSUPPORTED); }
		STDMETHODIMP EnumDAdvise (IEnumSTATDATA **) { return(OLE_E_ADVISENOTSUPPORTED); }

		long		Count (void) { return(fList.Length()); }
		bool		Add (XGDataObjDesc &desc) { try {fList+=desc;} catch (...) {return(false);} return(true); }
		XGDataObjDesc *	Get (long ix) { return(&fList[ix]); }

	private:
		long		fRefs;
		XGDragger *	fDragger;
		XGSDynArray<XGDataObjDesc> fList;

		XGDataObjDesc *	HaveFormat (long);

};

class XGDropEnumFE : public IEnumFORMATETC {
	public:
						XGDropEnumFE (XGDropObject *);
		virtual			~XGDropEnumFE (void);

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

		STDMETHODIMP Next (ULONG celt, FORMATETC *rgelt, ULONG *pceltFetched);
		STDMETHODIMP Skip (ULONG celt);
		STDMETHODIMP Reset (void);
		STDMETHODIMP Clone (IEnumFORMATETC **ppenum);

	private:
		long		fRefs;
		XGDropObject	*fObject;
		unsigned long fIndex;

};

#endif


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

XGView *_GDragDropInternal;

/************************************************************************/
/*																		*/
/*	Construction/Destruction											*/
/*																		*/
/************************************************************************/

/*$	XGDragger::XGDragger
 *
 *		Create a drag token which we will fill out later to represent
 *	the object or data being dragged. On the Macintosh, if drag and
 *	drop is present, this creates the drag/drop reference. Otherwise,
 *	this drops to the same code used by X Windows to handle drag/drop
 */

XGDragger::XGDragger (XGView *v, bool internal)
{
	fView = v;
	fInternal = internal;
	if (fInternal) _GDragDropInternal = v;
	else _GDragDropInternal = NULL;
	
#if OPT_MACOS
	fRgn = NewRgn();
	if (_GHasDragDrop) {
		NewDrag(&fDragRef);
		fSender = NULL;
	}
#endif

#if OPT_WINOS
	fSource = new XGDropSource(this);
	fObject = new XGDropObject(this);
#endif

#if OPT_XWIN
	// No additional stuff required here
#endif
}

/*$	XGDragger::~XGDragger
 *
 *		Dispose of me, including various memory objects...
 */

XGDragger::~XGDragger (void)
{
#if OPT_MACOS
	if (fRgn) DisposeRgn(fRgn);
	if (_GHasDragDrop) {
		if (fDragRef) DisposeDrag(fDragRef);
		if (fSender) DisposeDragSendDataUPP(fSender);
	}
#endif

#if OPT_WINOS
	delete (XGDropSource *)fSource;
	delete (XGDropObject *)fObject;
#endif

#if OPT_XWIN
	// No additional stuff required here
#endif

	if (_GDragDropInternal) _GDragDropInternal = NULL;
}

/************************************************************************/
/*																		*/
/*	Event Processing													*/
/*																		*/
/************************************************************************/

/*$	XGDragger::Wait
 *
 *		When you first get a mouse down event (DoMouseDown call to a
 *	view), call this routine. This returns 'true' if we should start
 *	the drag effect, or false if we should process this as a click.
 *	This allows us a fast way to determine if we are dragging or only
 *	selecting
 */

bool XGDragger::Wait (XGView *v, XPoint pt)
{
#if OPT_MACOS
	XRect r;
	
	if (_GHasDragDrop) {
		if (v) v->ViewToScreen(&pt);
		return WaitMouseMoved(pt.MacPoint());
	}
	
	XGDraw draw(v);			// set focus to me so coordinate system for
	r.left = pt.h - 3;		// getmouse matches
	r.right = pt.h + 3;
	r.top = pt.v - 3;
	r.bottom = pt.v + 3;
	while (StillDown()) {
		Point p;
		GetMouse(&p);
		if (!PtInRect(p,&r)) return true;
	}
	return false;
#endif

#if OPT_WINOS
	/* GetSystemMetrics(SM_CYDRAG), GetSystemMetrics(SM_CXDRAG)
	 * give the width, height of the slop rect around which the
	 * mouse is centered.  If GetSystemMetrics returns zero, that
	 * indicates failure of the call, so use something reasonable
	 * like 5,5 (i.e., +/- 3)
	 */

	MSG msg;
	XRect r;
	int slop;

	if (v) v->ViewToGlobal(&pt);
	if (!(slop = GetSystemMetrics(SM_CYDRAG)))
		slop = 3;
	r.top = pt.v - slop;
	r.bottom = pt.v + slop;
	if (!(slop = GetSystemMetrics(SM_CXDRAG)))
		slop = 3;
	r.left = pt.h - slop;
	r.right = pt.h + slop;
	do {
		if (!PeekMessage(&msg, v->GetRootWindow(), WM_MOUSEFIRST, WM_MOUSELAST, PM_NOREMOVE)) {
			msg.message = WM_MOUSEMOVE;
		} else if ((msg.message == WM_MOUSEMOVE)
			&& (TRUE)
			&& PeekMessage(&msg, v->GetRootWindow(), WM_MOUSEMOVE, WM_MOUSEMOVE, PM_REMOVE))
		{
			XPoint npt;
			npt.h = msg.pt.x;
			npt.v = msg.pt.y;
			if (!PtInRect(npt, &r))
				return(true);
		}
	} while (msg.message == WM_MOUSEMOVE);
	return(false);
#endif

#if OPT_XWIN
	XRect r;
	Window rw,cw;
	int rx,ry,cx,cy;
	unsigned int mask;
	
	v->ViewToGlobal(&pt);
	r.left = pt.h - 3;		// getmouse matches
	r.right = pt.h + 3;
	r.top = pt.v - 3;
	r.bottom = pt.v + 3;
	while (XQueryPointer(_GDisplay,
						 v->GetRootWindow(),
						 &rw,&cw,
						 &rx,&ry,
						 &cx,&cy,
						 &mask)) {
		
		pt.h = cx;
		pt.v = cy;
		if (!PtInRect(pt,&r)) break;
		if (0 == (mask & (Button1Mask | Button2Mask | Button3Mask | Button4Mask | Button5Mask))) {
			// mouse released while still inside rectangle.
			return false;
		}
	}
	
	// if we get here, mouse either moved to new screen, or the
	// mouse wandered outside my rectangle.
	return true;
#endif
}

/*$	XGDragger::Track
 *
 *		Once we know we're tracking an object, call this routine or the
 *	one above. This returns 'true' if the object was accepted for dragging,
 *	or 'false' if the object wasn't dragged, or there was an error.
 */

bool XGDragger::Track()
{
#if OPT_MACOS
	EventRecord event;
	RgnHandle rgn;
	
	if (NULL != (rgn = NewRgn())) {
		CopyRgn(fRgn, rgn);
		InsetRgn(rgn, 1, 1);
		DiffRgn(fRgn, rgn, fRgn);
		DisposeRgn(rgn);
	}
	if (_GHasDragDrop) {
		event = *(EventRecord *)XGAppCore::GetLastEvent();
		return(TrackDrag(fDragRef, &event, fRgn) == noErr);
	} else {
		if (fDragData.GetSize() > 0) return TrackDragEvent();
		return false;
	}
#endif

#if OPT_WINOS
	DWORD effect;
	return(DoDragDrop((IDataObject *)fObject, (IDropSource *)fSource, DROPEFFECT_COPY | DROPEFFECT_MOVE, &effect) == DRAGDROP_S_DROP);
#endif

#if OPT_XWIN
	if (fDragData.GetSize() > 0) return TrackDragEvent();
	return false;
#endif
}

/************************************************************************/
/*																		*/
/*	Drag Construction Support											*/
/*																		*/
/*		There are two ways to associate data with a drag object. The	*/
/*	first way is to call 'AddObject' with the data; when accepted, the	*/
/*	data is copied over to the receiver.								*/
/*																		*/
/*		The second mechanism is to defer sending the data until the		*/
/*	drag is complete. This permits us to handle dragging objects which	*/
/*	take a long time to "render", which may not necessarly be accepted.	*/
/*	(No point in creating the data if it takes a long time, and if the	*/
/*	drag won't be accepted.) To do this, you must override the			*/
/*	'GetObject()' method, and when asked to render the data, call the	*/
/*	'SetObject()' method with the requested data. Then, when dragging	*/
/*	data on demand, call 'AddObject()', passing NULL to the data 		*/
/*	pointer.															*/
/*																		*/
/************************************************************************/

/*$	XGDragger::AddObject
 *
 *		On the Macintosh this adds this rectangular region to the
 *	outline that is dragged around on the screen when this object is
 *	moved. On Windows (which doesn't drag a graphic outline), this
 *	does nothing.
 *
 *		Normally this is called by 'AddObject' (below), but may be
 *	called by itself to alter the shape of the rectangular region.
 */

bool XGDragger::AddObject (XRect *r)
{
#if OPT_MACOS
	if (r) {
		XRect s;
		
		RgnHandle rgn = NewRgn();
		if (rgn) {
			s = *r;
			if (fView) fView->ViewToScreen(&s);
			Rect mr = s.MacRect();
			RectRgn(rgn, &mr);
			UnionRgn(rgn, fRgn, fRgn);
			DisposeRgn(rgn);
		}
	}
#endif
#if OPT_WINOS
#if __MWERKS__
#pragma unused(r)
#endif
#endif
	return(true);
}

/*$	XGDragger::AddObject
 *
 *		This adds the data to the object we're moving. 'data' and 'len' can
 *	be NULL if we're defering this.
 */

bool XGDragger::AddObject (long itemRef, long flavor, void *data, long dlen, XRect *r)
{
	AddObject(r);

#if OPT_MACOS
	if (_GHasDragDrop) {
		FlavorFlags f;
		if (!data && !fSender) {
			if (!(fSender = NewDragSendDataProc(DraggerSender)))
				return(false);
			if (SetDragSendProc(fDragRef, fSender, this))
				return(false);
		}

		f = 0;
		if (((flavor >= 0) && (flavor <= KDragFlavorLast)) || fInternal) f |= flavorSenderOnly;
		return (noErr == AddDragItemFlavor(fDragRef, 
										   (ItemReference)itemRef, 
										   (FlavorType)XGDragger::FlavorToNative(flavor),
					 					   data, 
					 					   dlen, 
					 					   f));
	}
#endif

#if OPT_WINOS
	XGDataObjDesc desc;
	XGDropObject *obj = (XGDropObject *)fObject;

	desc.itemID = itemRef;
	desc.flavor = flavor;
	desc.data = data;
	desc.size = dlen;
	try {
		obj->Add(desc);
	} catch (...) {
		return(false);
	}
	return(true);
#endif

#if OPT_MACOS || OPT_XWIN
	/*
	 *	If we get here, either we are on a Mac w/o drag/drop, or on
	 *	X Windows
	 */
	
	long offset;
	
	if (!data) dlen = 0;

	offset = fDragData.GetSize();
	fDragData.Append(sizeof(long) * 3 + dlen);
	fDragData.SetLong(offset,itemRef);
	fDragData.SetLong(4+offset,flavor);
	fDragData.SetLong(8+offset,dlen);
	if (data) fDragData.SetMemory(12+offset,dlen,data);
	return true;
#endif
}

/*$	XGDragger::GetObject
 *
 *		(Overridable) This is called when a drag is accepted, and the
 *	data needs to be rendered. By default this does nothing.
 *
 *		Return 'false' if there was a problem rendering the data.
 *
 *		Normally you would only create this if the data rendering
 *	process took a long time. In that case, you would also pass
 *	NULL to the data field in the AddObject() method when the dragger
 *	was first created.
 */

bool XGDragger::GetObject(long /* itemRef */, long /* flavor */)
{
	return false;
}

/*$	XGDragger::SetObject
 *
 *		Call this in response to a call to 'GetObject'; this sends the
 *	requested data. This is only called when no data is provided with
 *	the AddObject() method, and oly when the drop was successful.
 */

bool XGDragger::SetObject (long itemRef, long flavor, void *data, long dlen)
{
#if OPT_MACOS
	if (_GHasDragDrop) {
		return(SetDragItemFlavorData(fDragRef, (ItemReference)itemRef, (FlavorType)XGDragger::FlavorToNative(flavor),
									 data, dlen, 0) == noErr);
	}
#endif

#if OPT_WINOS
	int i;
	XGDataObjDesc *desc;
	XGDropObject *obj = (XGDropObject *)fObject;

	for (i = 0; i < obj->Count(); i++) {
		desc = obj->Get(i);
		if (desc->itemID == itemRef && desc->flavor == flavor) {
			desc->size = dlen;
			desc->data = data;
			return(true);
		}
	}
	return(false);
#endif

#if OPT_XWIN || OPT_MACOS
	// store for transfer
	fDragTemp.SetSize(dlen);
	fDragTemp.SetMemory(0,dlen,data);
	return true;
#endif
}

/*	XGDragger::FlavorToNative
 *
 *		Convert from YAAF flavor type to Macintosh native flavor type
 *	or Microsoft Windows.
 */

#if OPT_MACOS
long XGDragger::FlavorToNative (long flavor)
{
	long flavs[] = { 'TEXT', flavorTypeHFS, 'DATA' };
	if (flavor < 0 || flavor > KDragFlavorLast)
		return(flavor);
	return(flavs[flavor]);
}
#endif

#if OPT_WINOS
long XGDragger::FlavorToNative (long flavor)
{
	long flavs[] = { CF_TEXT, CF_HDROP, CF_PRIVATEFIRST };
	if (flavor >= 0 && flavor <= KDragFlavorLast)
		return(flavs[flavor]);
	char fmtName[64];
	wsprintf(fmtName, "CF_YAAF#%ld", flavor);
	return(RegisterClipboardFormat(fmtName));
}
#endif

#if OPT_XWIN
long XGDragger::FlavorToNative(long flavor)
{
	return flavor;
}
#endif


/*	XGDragger::NativeToFlavor
 *
 *		Convert from native Mac or Windows flavor to YAAF flavor types
 */

#if OPT_MACOS
long XGDragger::NativeToFlavor (long flavor)
{
	switch (flavor) {
	case 'TEXT':
		return(KDragFlavorText);
	case flavorTypeHFS:
		return(KDragFlavorFile);
	case 'DATA':
		return(KDragFlavorData);
	default:
		return(flavor);
	}
}

#endif

#if OPT_WINOS
long XGDragger::NativeToFlavor (long flavor)
{
	switch (flavor) {
	case CF_TEXT:
		return(KDragFlavorText);
	case CF_HDROP:
		return(KDragFlavorFile);
	case CF_PRIVATEFIRST:
		return(KDragFlavorData);
	default:
		char fmtName[64];
		GetClipboardFormatName(flavor, fmtName, sizeof(fmtName));
		if (memcmp(fmtName, "CF_YAAF#", 8) != 0)
			return(flavor);
		return(atoi(&fmtName[8]));
	}
}
#endif

#if OPT_XWIN
long XGDragger::NativeToFlavor(long flavor)
{
	return flavor;
}
#endif

/************************************************************************/
/*																		*/
/*	Mac/X drag and drop													*/
/*																		*/
/************************************************************************/

/*	XGDragger::TrackDragEvent
 *
 *		This is used to track a drag/drop event.
 */

#if OPT_MACOS

bool XGDragger::TrackDragEvent()
{
	Point p;
	bool dropflag = false;
	Point cur;
	GrafPtr port;
	XGDropperWindow *dest = NULL;
	Point start,last;
	EventRecord *event;
	PenState state;
	RgnHandle r;
	RgnHandle oclip;
	WindowPtr where;
	WindowPtr oldw = NULL;
	WindowPtr wstart;
	
	/*
	 *	Save drawing context using an XGDraw kludge, and then set up
	 *	drawing for me inside my global screen area. This is great because
	 *	if this fails for some reason, the screen drawing context is
	 *	put back.
	 */
	
	XGDraw draw(fView);
	GetWMgrPort(&port);
	SetPort(port);
	GetPenState(&state);
	oclip = NewRgn();
	GetClip(oclip);
	ClipRect(&(port->portRect));
	PenPat(&qd.gray);
	PenMode(patXor);
	
	/*
	 *	Get my global mouse location
	 */
	
	event = (EventRecord *)XGAppCore::GetLastEvent();
	start = event->where;
	last = start;
	if (fRgn) {
		r = NewRgn();
		CopyRgn(fRgn,r);
		PaintRgn(r);
	}
	FindWindow(start,&wstart);
	
	/*
	 *	Run my drag/drop event loop
	 */
	
	while (StillDown()) {
		GetMouse(&cur);
		if ((cur.h != last.h) || (cur.v != last.v)) {
			if (fRgn) PaintRgn(r);
			
			// Find which window this is in
			FindWindow(cur,&where);
			if (where != oldw) {
				// deselect dest
				if (dest) {
					if (dest->IsAccepting()) {
						dest->ClearTrackDropper();
					}
				}
				
				// find new dest
				if (where) {
					for (dest = XGDropperWindow::gList; dest; dest = dest->fNext) {
						if (dest->fWindow == where) break;
					}
					if (dest == NULL) where = NULL;
				} else {
					dest = NULL;
				}
				
				// will it accept?
				if (dest) {
					dest->DragAccept(fDragData,event->modifiers);
				}
				
				oldw = where;
			} else if (where) {
				if (where != wstart) wstart = NULL;
				 
				// drag this inside the drop area
				if (dest && dest->IsAccepting()) {
					SetPort(where);			// convert to window coords
					SetOrigin(0,0);
					p = cur;
					GlobalToLocal(&p);
					SetPort(port);
					
					dest->TrackDropper(p,event->modifiers,where != wstart);
				}
			}
									
			if (fRgn) {
				CopyRgn(fRgn,r);
				OffsetRgn(r,cur.h-start.h,cur.v-start.v);
				PaintRgn(r);
			}
			last = cur;
		}
	}
	
	if (fRgn) {
		PaintRgn(r);
		DisposeRgn(r);
	}
	
	/*
	 *	Restore
	 */
	
	SetPort(port);
	SetClip(oclip);
	SetPenState(&state);

	if (dest && dest->IsAccepting()) {
		SetPort(where);			// convert to window coords
		SetOrigin(0,0);
		p = last;
		GlobalToLocal(&p);
		SetPort(port);

		DoDropEvent(p,dest,event->modifiers);
		dest->ClearTrackDropper();
	}
	return dropflag;
}

#endif

/*	XGDragger::TrackDragEvent
 *
 *		The X windows version of the above routine. This runs an
 *	X event loop internally, grabbing mouse events and processing them
 *	as mine.
 */

#if OPT_XWIN

bool XGDragger::TrackDragEvent()
{
	bool dropflag = false;
	XEvent event;
	XGWindow *wptr;
	int i;
	Window parent,child;
	int xtmp,ytmp;
	bool done = false;
	XGDropperWindow *dest = NULL;
	XPoint point;
	Window ochild,cstart;
	unsigned int btnmask;
	
	i = ::XGrabPointer(_GDisplay,
						fView->GetWindow()->GetWindow(),
						False,
				   		ButtonReleaseMask |
				   			ButtonPressMask |
				   			PointerMotionMask,
				  		GrabModeAsync,
				  		GrabModeAsync,
			 			None,
						None,
						CurrentTime);

	cstart = fView->GetWindow()->GetWindow();
	ochild = 0;
	while (!done) {
		/*
		 *	Get the next event. If we're idling (because the user hasn't
		 *	moved the cursor), handle updates
		 */
		
		for (;;) {
			if (::XPending(_GDisplay)) {
				::XNextEvent(_GDisplay,&event);
				break;
			} else {
				XGAppCore::UpdateWindows();
				sched_yield();
			}
		}
		
		switch (event.type) {
			case Expose:
				/*
				 *	Exposure message
				 */
				
				wptr = XGWindow::GetXGWindow(event.xexpose.window);
				if (wptr) {
					XRectangle r;
					
					r.x = event.xexpose.x;
					r.width = event.xexpose.width;
					r.y = event.xexpose.y;
					r.height = event.xexpose.height;
					
					/*
					 *	Accumulate update region
					 */
					
					if (wptr->fUpdateRgn == 0) {
						wptr->fUpdateRgn = ::XCreateRegion();
					}
					::XUnionRectWithRegion(&r,wptr->fUpdateRgn,wptr->fUpdateRgn);
				}
				break;
				
			case ButtonRelease:
				/*
				 *	Button release--I'm being dropped
				 */
				
				done = true;
				break;
			
			case MotionNotify:
				/*
				 *	Get the next pointer location, and find which one
				 *	of my windows the pointer currently is in. If the
				 *	pointer is not in one of my windows, child = NULL
				 */
				
				while (XCheckMaskEvent(_GDisplay,ButtonMotionMask,&event)) ;
				if (!XQueryPointer(_GDisplay,
								   event.xmotion.window,
								   &parent,
								   &child,
								   &xtmp,
								   &ytmp,
								   &event.xmotion.x,
								   &event.xmotion.y,
								   &btnmask)) child = NULL;
				else if (!XTranslateCoordinates(_GDisplay,
												event.xmotion.window,
												DefaultRootWindow(_GDisplay),
												event.xmotion.x,
												event.xmotion.y,
												&xtmp,
												&ytmp,
												&child)) child = NULL;
				else {
					wptr = XGWindow::GetXGWindowByParent(child);
					if (wptr == NULL) child = NULL;
					else {
						child = wptr->GetWindow();
						if (!XTranslateCoordinates(_GDisplay,
												   event.xmotion.window,
												   child,
												   event.xmotion.x,
												   event.xmotion.y,
												   &xtmp,
												   &ytmp,
												   &parent)) break;
					}
				}
				point.h = xtmp;
				point.v = ytmp;
				
				/*
				 *	Now process drag/drop
				 */
				
				if (child != ochild) {			// Moved into different window?
					// deselect dest
					if (dest) {
						if (dest->IsAccepting()) {
							dest->ClearTrackDropper();
						}
					}
					
					// find new dest
					if (child) {
						for (dest = XGDropperWindow::gList; dest; dest = dest->fNext) {
							if (dest->fWindow == child) break;
						}
						if (dest == NULL) child = NULL;
					} else {
						dest = NULL;
					}
					
					// will it accept?
					if (dest) {
						dest->DragAccept(fDragData,btnmask);
					}
					
					// update cursor
					if (dest && dest->IsAccepting()) {
						SetCursorID(XC_hand2);
					} else {
						SetCursorID(XC_X_cursor);
					}
					ochild = child;
				} else if (child) {				// Moving inside window?
					if (child != cstart) cstart = NULL;
					
					if (dest && dest->IsAccepting()) {
						dest->TrackDropper(point,btnmask,(child != cstart));
					}
				} else {
					SetCursorID(XC_X_cursor);
				}
				
				break;
		}
	}
	
	::XUngrabPointer(_GDisplay,CurrentTime);
	NormalCursor();
	
	/*
	 *	Drop this thing
	 */
	
	if (dest && dest->IsAccepting()) {
		DoDropEvent(point,dest,btnmask);
		dest->ClearTrackDropper();
	}
	return dropflag;
}

#endif


#if OPT_MACOS || OPT_XWIN

/*	XGDragger::DoDropEvent
 *
 *		Handle the drop event. The provided point is in window
 *	coordinates
 */

bool XGDragger::DoDropEvent(XPoint where, XGDropperWindow *dw, short mods)
{
	bool accept;
	XGDropper *drop;
	long off,len;
	long iref,flav;
	long dlen;
	void *ddata;
	bool lockflag = false;
	
	if (!dw->IsAccepting()) return false;
	drop = dw->GetTrackDropper();
	if (drop == NULL) return false;
	if (!drop->IsAccepting()) return false;
	
	/*
	 *	Convert to local coordinate system
	 */
	
	drop->GetView()->GlobalToView(&where);
	
	/*
	 *	We're dropping data into this thinng
	 */
	
	XGHandleLock lock(fDragData);
	accept = false;
	len = fDragData.GetSize();
	off = 0;
	drop->BeginDrop( where, mods );
	while (off < len) {
		iref = fDragData.GetLong(off);
		flav = fDragData.GetLong(off+4);
		dlen = fDragData.GetLong(off+8);
		ddata = (void *)(12 + off + (char *)*fDragData);
		off += 12 + dlen;
		if (dlen == 0) {
			/*
			 *	Ask where the data is
			 */
			
			if (!GetObject(iref,flav)) continue;		// couldn't render
			
			lockflag = true;
			fDragTemp.Lock();
			dlen = fDragTemp.GetSize();
			ddata = *fDragTemp;
		}
		
		if (drop->Drop(where,mods,iref,flav,ddata,dlen)) accept = true;
		
		if (lockflag) {
			fDragTemp.Unlock();
			fDragTemp.SetSize(0);						// clear buffer
		}
	}
	drop->EndDrop( where, mods );
	return accept;
}

#endif

/************************************************************************/
/*																		*/
/*	Windows Drag/Drop COM interfaces									*/
/*																		*/
/************************************************************************/

#if OPT_WINOS

XGDropSource::XGDropSource (XGDragger *)
{
	fRefs = 0;
}

XGDropSource::~XGDropSource (void)
{
	Assert(fRefs == 0);
}

STDMETHODIMP
XGDropSource::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 IDropSource
	if (riid == IID_IDropSource) {
		AddRef();
		*ppvObj = this;
		return(S_OK);
	}

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

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

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

STDMETHODIMP
XGDropSource::QueryContinueDrag (BOOL escape, DWORD keyState)
{
	if (escape)
		return(DRAGDROP_S_CANCEL);
	if (!(keyState & MK_LBUTTON))
		return(DRAGDROP_S_DROP);
	return(S_OK);
}

STDMETHODIMP
XGDropSource::GiveFeedback (DWORD)
{
	return(DRAGDROP_S_USEDEFAULTCURSORS);
}

XGDropObject::XGDropObject (XGDragger *dragger)
{
	fRefs = 0;
	fDragger = dragger;
}

XGDropObject::~XGDropObject (void)
{
	Assert(fRefs == 0);
}

STDMETHODIMP
XGDropObject::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 IDataObject
	if (riid == IID_IDataObject) {
		AddRef();
		*ppvObj = this;
		return(S_OK);
	}

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

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

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

STDMETHODIMP
XGDropObject::GetData (FORMATETC *fmt, STGMEDIUM *stg)
{
	HRESULT err;
	XGDataObjDesc *desc;
	HANDLE h;
	void *p;

	stg->tymed = TYMED_NULL;
	err = QueryGetData(fmt);
	if (err != S_OK)
		return(err);

	desc = HaveFormat(fmt->cfFormat);
	if (!desc->data) {
		if (!fDragger->GetObject(desc->itemID, desc->flavor))
			return(E_OUTOFMEMORY);
		desc = HaveFormat(fmt->cfFormat);
		if (!desc->data)
			return(E_OUTOFMEMORY);
	}

	if (!(h = GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT | GMEM_DDESHARE, desc->size)))
		return(E_OUTOFMEMORY);
	if (!(p = GlobalLock(h))) {
		GlobalFree(h);
		return(E_OUTOFMEMORY);
	}
	memcpy(p, desc->data, desc->size);
	GlobalUnlock(h);
	stg->tymed = TYMED_HGLOBAL;
	stg->hGlobal = h;
	return(S_OK);
}

STDMETHODIMP
XGDropObject::GetDataHere (FORMATETC *fmt, STGMEDIUM *stg)
{
	HRESULT err;
	XGDataObjDesc *desc;
	void *p;

	err = QueryGetData(fmt);
	if (err != S_OK)
		return(err);

	if (stg->tymed != TYMED_HGLOBAL)
		return(E_INVALIDARG);

	desc = HaveFormat(fmt->cfFormat);
	if (!desc->data) {
		if (!fDragger->GetObject(desc->itemID, desc->flavor))
			return(E_OUTOFMEMORY);
		desc = HaveFormat(fmt->cfFormat);
		if (!desc->data)
			return(E_OUTOFMEMORY);
	}

	if (GlobalSize(stg->hGlobal) < desc->size)
		return(STG_E_MEDIUMFULL);
	if (!(p = GlobalLock(stg->hGlobal)))
		return(E_OUTOFMEMORY);
	memcpy(p, desc->data, desc->size);
	GlobalUnlock(stg->hGlobal);
	return(S_OK);
}

STDMETHODIMP
XGDropObject::QueryGetData (FORMATETC *fmt)
{
	if (fmt->lindex != -1)
		return(DV_E_LINDEX);
	if (fmt->tymed != TYMED_HGLOBAL)
		return(DV_E_TYMED);
	if (fmt->dwAspect != DVASPECT_CONTENT)
		return(DV_E_DVASPECT);
	if (!HaveFormat(fmt->cfFormat))
		return(DV_E_FORMATETC);
	return(S_OK);
}

STDMETHODIMP
XGDropObject::EnumFormatEtc (DWORD dir, IEnumFORMATETC **ppenum)
{
	if (dir != DATADIR_GET)
		return(E_NOTIMPL);

	XGDropEnumFE *defe = new XGDropEnumFE(this);
	if (!defe) {
		*ppenum = NULL;
		return(E_OUTOFMEMORY);
	}
	defe->AddRef();
	*ppenum = (IEnumFORMATETC *)defe;
	return(S_OK);
}

XGDataObjDesc *
XGDropObject::HaveFormat (long format)
{
	long i, flavor;

	flavor = XGDragger::NativeToFlavor(format);
	for (i = 0; i < fList.Length(); i++) {
		if (flavor == fList[i].flavor)
			return(&fList[i]);
	}
	return(NULL);
}

XGDropEnumFE::XGDropEnumFE (XGDropObject *obj)
{
	fRefs = 0;
	fObject = obj;
	fIndex = 0;
}

XGDropEnumFE::~XGDropEnumFE (void)
{
	Assert(fRefs == 0);
}

STDMETHODIMP
XGDropEnumFE::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 IID_IEnumFORMATETC
	if (riid == IID_IEnumFORMATETC) {
		AddRef();
		*ppvObj = this;
		return(S_OK);
	}

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

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

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

STDMETHODIMP
XGDropEnumFE::Next (ULONG celt, FORMATETC *rgelt, ULONG *pceltFetched)
{
	/* get the stuff.  pceltFetched can be NULL if celt is 1 */
	unsigned long i;
	XGDataObjDesc *desc;

	if (pceltFetched == NULL && celt != 1)
		return(S_FALSE);

	for (i = 0; i < celt && (i + fIndex) < (unsigned long)fObject->Count(); i++) {
		desc = fObject->Get(i + fIndex);
		rgelt[i].cfFormat = XGDragger::FlavorToNative(desc->flavor);
		rgelt[i].ptd = NULL;
		rgelt[i].dwAspect = DVASPECT_CONTENT;
		rgelt[i].lindex = -1;
		rgelt[i].tymed = TYMED_HGLOBAL;
		fIndex++;
	}

	if (pceltFetched)
		*pceltFetched = i;
	if (i < celt)
		return(S_FALSE);
	return(S_OK);
}

STDMETHODIMP
XGDropEnumFE::Skip (ULONG celt)
{
	fIndex += celt;
	return(S_OK);
}

STDMETHODIMP
XGDropEnumFE::Reset (void)
{
	fIndex = 0;
	return(S_OK);
}

STDMETHODIMP
XGDropEnumFE::Clone (IEnumFORMATETC **ppenum)
{
	XGDropEnumFE *defe = new XGDropEnumFE(fObject);
	defe->fIndex = fIndex;
	if (!defe) {
		*ppenum = NULL;
		return(E_OUTOFMEMORY);
	}
	defe->AddRef();
	*ppenum = (IEnumFORMATETC *)defe;
	return(S_OK);
}
#endif


