/*
 *	(c) 1998-99 Sassafras Software Inc.
 *
 *	Implements drag and drop.  The XGDropper class is a mix-in class,
 *	usually for a view, but it doesn't have to be.  It currently
 *	supports only one drop target per window.
 */

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

  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/

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

#ifndef SS_DRAGDROP
#define SS_DRAGDROP

#include <XConfig.h>
#include <XHandle.h>
#include <XView.h>

/**@name Drag and Drop Support
 *
 *		These classes implement drag and drop support for YAAF. There
 *	are a few limitations: currently only one drop target is supported
 *	per window, and the code currently does not support X Windows.
 *
 *		The XGDropper class is a mix-in class which can be mixed
 *	into a view to provide drop support. The XGDragger class, on
 *	the other hand, is a stack-based object which provides dragging
 *	support. When you wish to start a drag, you create an XGDragger
 *	object dynamically and initialize it; it then handles all of the
 *	event handling required to drag and drop the object.
 *
 *		For future reference, it is my intention to expand this to
 *	provide multiple drop targets per window. But only one drop target
 *	per view.
 */
 
//@{

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

class XGDropper;
class XGDragger;
class XGDropperWindow;
class XGFileSpecifier;

/************************************************************************/
/*																		*/
/*	Constants															*/
/*																		*/
/************************************************************************/

/*	Flavor enumerations
 *
 *		The types of data that we can drag around.
 */

enum {
	/// The data we are dragging is plain text
	KDragFlavorText = 0,
	/// The data we are dragging is a file
	KDragFlavorFile = 1,
	/// The data we are dragging is raw data
	KDragFlavorData = 2,
	KDragFlavorLast = 2
};

/************************************************************************/
/*																		*/
/*	Class Declarations													*/
/*																		*/
/************************************************************************/

/*$	XGDragger
 *
 *		This is an abstraction of the object that is being dragged.
 *	You would create one of these when the user clicked on an object
 *	in order to handle dragging it to a destination.
 *
 *		In practice, we could create one of these on the stack in
 *	order to handle drag and dropping.
 *
 *		When you've received a click on a draggable object, do the
 *	following:
\begin{verbose}

		if (XGDragger::Wait(this,where)) {
			// start dragging			
			XGDragger drag;
			
			// This assumes all of the data is already initialized.
			drag.AddObject(itemRef,KDragFlavorXXXX,data,dataLen);
			
			// Now drag the data. 'flags' comes from the flags param
			// that was passed to DoMouseDown()
			drag.Track(where,flags);
		}

\end{verbose}
 *
 *		If the data that is to be dragged is complex, and takes time
 *	to render, there is no point in redering it right away because the
 *	drag operation may not succeed. If this is the case, you need to
 *	override the GetObject() method. Then, when adding objects to the
 *	drag object, you would lset the data and data length fields of AddObject
 *	default to NULL and 0, respectively.
 */

class XGDragger {
	public:
								XGDragger (XGView *v = NULL, bool internal = false);
		virtual					~XGDragger (void);

		static bool				Wait (XGView *, XPoint);
		bool					Track ();

		bool					AddObject (XRect *);
		bool					AddObject (long, long, void *data = NULL, long dlen = 0, XRect *r = NULL);
		bool					SetObject (long, long, void *, long);
		virtual bool			GetObject (long, long);

		static long				FlavorToNative (long flavor);
		static long				NativeToFlavor (long flavor);

	private:
		XGView					*fView;
		bool					fInternal;
		
#if OPT_MACOS
		DragReference			fDragRef;
		RgnHandle				fRgn;
		DragSendDataUPP			fSender;
#endif

#if OPT_WINOS
		void *					fSource;
		void *					fObject;
#endif

#if OPT_MACOS || OPT_XWIN
		XGHandle				fDragData;
		XGHandle				fDragTemp;
		
		bool					TrackDragEvent();
		bool					DoDropEvent(XPoint, XGDropperWindow *, short mods);
#endif
};

/*$	XGDropperWindow
 *
 *		This is a temporary object which is created when any one of the
 *	contained views are drop targets. This gets created when one or more
 *	views wish to accept drag and drop.
 *
 *		The idea is rather simple: each window which accepts drag and
 *	drop has one of these associated with it which determines which
 *	one of the contained views accept drag and drop inside.
 */

class XGDropperWindow {
	public:
								XGDropperWindow();
								~XGDropperWindow();
		
		static XGDropperWindow	*InstallDropper(XGDropper *);
		void					RemoveDropper(XGDropper *);

		bool					IsAccepting()
									{
										return fAccepting;
									}
									
		void					TrackDropper(XPoint where, long mods, bool flag);
		void					ClearTrackDropper(void);
#if OPT_MACOS
		bool					DragAccept(DragReference dragRef);
#endif

#if OPT_WINOS
		bool					DragAccept(LPDATAOBJECT, DWORD, POINTL);
#endif

#if OPT_XWIN || OPT_MACOS
		bool					DragAccept(XGHandle &data, short mods);
#endif

		XGDropper				*FindDropper(XPoint where);
		void					SetTrackDropper(XGDropper *d, XPoint, long, bool);
		void					InitTrackDropper(XGDropper *d)
									{
										fLast = d;
									}
		XGDropper				*GetTrackDropper()
									{
										return fLast;
									}
		
	private:
		void					IInitDropper();

		bool					fAccepting;
		bool					fTracking;
		
		XGDropper				*fLast;		/* Last target that this was in */
		XGDropper				*fDrop;		/* Linked list of targets */
		XGDropperWindow			*fNext;		/* Linked list of dropper windows */
		static XGDropperWindow	*gList;

#if OPT_MACOS
		WindowPtr				fWindow;
		static DragTrackingHandlerUPP	gTracker;
		static DragReceiveHandlerUPP	gReceiver;
#endif

#if OPT_WINOS
		HWND					fWindow;
		void					*fTarget;
#endif

#if OPT_XWIN
		Window					fWindow;
#endif

	friend class XGDragger;
};

/*$	XGDropper
 *
 *		This is a mix-in class which is used to accept dragging.
 *	Normally this would be mixed into an XGView, though it can
 *	be mixed in elsewhere. If you do mix this in elsewhere, you must
 *	provide the view that is the target destination of this drag.
 */

class XGDropper {
	public:
#if 0
								XGDropper (void);
#endif
								XGDropper (XGView *, XRect *r = NULL);
		virtual					~XGDropper (void);

		void					Constrain (XRect *r = NULL);

		virtual bool			Accept (long, long, long);
		virtual void			BeginDrop( XPoint where, long mods );
		virtual bool			Drop (XPoint where, long mods, long itemRef, long flavor, void * dataptr, long datalen);
		virtual void			EndDrop( XPoint where, long mods );
		virtual void			Enter ( XPoint where, long mods, bool hilite );
		virtual void			Leave (void);
		virtual void			Track (XPoint, long);

		enum {
			kScrollNone = 0,
			kScrollUp = 1,
			kScrollDown = 2,
			kScrollLeft = 3,
			kScrollRight = 4
		};

		virtual void			Scroll (long);

		/*
		 * "private" accessor functions, needed so the
		 * tracking and dragging handlers can access
		 * the private data members.
		 */
		
		/*$	XGDropper::IsAccepting
		 *
		 *		Is this accepting a drop target? This returns true if this
		 *	view is currently accepting a drop target during the drag and
		 *	drop process.
		 */
		
		bool					IsAccepting (void) { return(fAccepting); }

		/*$	XGDropper::Accepting
		 *
		 *		This sets the 'accepting' flag. This allows you to set
		 *	the 'is accepting drop target' flag. Normally this is called
		 *	internally by YAAF.
		 */
		
		void					Accepting (bool value = true) { fAccepting = value; }

		/*$	XGDropper::GetBounds
		 *
		 *		This gets the boundary of the rectangle within this view
		 *	which is accepting drag and drop.
		 */
		
		XRect					GetBounds(void);
		
		/*$	XGDropper::GetView
		 *
		 *		This gets the view associated with this drag and drop target
		 */
		
		XGView *				GetView(void) { return(fView); }
		
#if OPT_MACOS
		/*$ XGDropper::SetDragReference
		 *
		 *		[Macintosh] Sets the drag reference for internal use.
		 */
		
		void					SetDragReference(DragReference dr)
									{
										fDragReference = dr;
									}
#endif
		
		static void				MakeFileSpecFromDropData( XGFileSpecifier &fs, const void *data, int len );
									

	protected:
		bool					fHiliteFlag;
		
		void					ShowDragFeedback(XGDraw &, XRect *);
		void					HideDragFeedback(XGDraw &, XRect *);
		
#if OPT_MACOS
		DragReference			fDragReference;
#endif

#if OPT_WINOS
		void					DrawDragFeedback(XGDraw &, XRect *);
#endif

	private:
		XGDropper				*fNext;
		XGDropperWindow			*fDrop;
		
		XGView *				fView;
		bool					fConstrained;
		XRect 					fBounds;

		bool					fAccepting;

		void					IInitDropper (void);
		
	friend class XGDropperWindow;

};

//@}

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

extern XGView *_GDragDropInternal;

#endif	// SS_DRAGDROP
