#include "WimpLib:DragDrop.h"
#include "WimpLib:DragSend.h"

#include <stdio.h>
#include <string.h>

#include "swis.h"

#include "WimpLib:Desktop.h"
#include "WimpLib:Display.h"
#include "WimpLib:Keyboard.h"
#include "WimpLib:File.h"
#include "WimpLib:Window.h"
#include "WimpLib:Task.h"

static const MouseShape drag_shape = {"ptr_double", 0, 0};

// For DragDrop_DragLoop
static struct
{
	Msg_Dragging       m_Dragging;
	HTask              m_Claimant;
	int                m_LastRef;
	int                m_LastFlags;
	int                m_NullCount;
	bool               m_bAborted;
	bool               m_bFinished;
	bool               m_bSendDragging;
	bool               m_bNoWimpDragBox;
	DragDrop_FShow     m_FNShow;
	DragDrop_FHide     m_FNHide;
	DragDrop_FPointer  m_FNPointer;
	void*              m_DragHandle;
	void*              m_UserHandle;
	DragDrop_FTransfer m_FNTransfer;
	void*              m_TransferHandle;
} Drag;

// For replies to Dragging Message
static struct
{
	OnDraggingCallback m_Callback;
	void* m_Handle;
	int   m_Ref;
} Dragging = {NULL, NULL, 0};

static CRect DragDrop_NullBox = {0, 0, -1, -1};

/*= Public interface =========================================================*/

/*============================================================================*/
/*= Wimp_DragBox interface ===================================================*/
/*============================================================================*/

void DragBox(CDragBox* pdrag)
{
	_swix(Wimp_DragBox, _IN(1), pdrag);
}

/*============================================================================*/
/*= DragDrop/ActiveDrag interface ============================================*/
/*============================================================================*/

CSize DragDrop_GetSpriteSize(const CSpriteHdr* pSprite)
{
	const Mode_Info* Mode = Task_GetModeInfo();
	CSize size = Desktop_GetSpriteSize(pSprite);

	size.cx = size.cx * (72000 / Mode->resx);
	size.cy = size.cy * (72000 / Mode->resy);

	return size;
}

/* ------------------------- DragDrag_GetUserHandle ----------------------------
 * Description: This function returns the user handle of the current dragging.
 *
 * Parameters:  None
 *
 * Returns:     The function returns a handle/data pointer for the data being
 *              dragged by our application or NULL for data from other tasks.
 *              Most of the time dragging over your own data means a move
 *              while dragging to another window a copy so you need to be
 *              check that to identify the data dragged around.
 *
 */

void* DragDrop_GetUserHandle(void)
{
	return Drag.m_UserHandle;
}

/* ------------------------- DragDrop_DragFile ---------------------------------
 * Description: This function drags a file icon around the desktop asking
 *              applications if they would accept such a file. The sprite
 *              dragged is in form file_xxx where xxx is the first type in the
 *              given list.
 *
 * Parameters:  id           window where the drag started
 *              UserHandle   handle/pointer to data being dragged to allow
 *                           OnDraggingCallbacks to identify the dragged data
 *              flags        drag an drop flags
 *              types        list of file types in which the data can be saved,
 *                           terminated by -1
 *
 * Returns:     The function returns the following structure:
 *              msg_ref      the claimant's last message id
 *                           or 0 if there is no claimant
 *              task         the task id of the claimant
 *                           or your task id if there is no claimant
 *                           or 0 if the drag was aborted
 *              flags        the claimant flags
 *              type         the first file type supported by
 *                           the claimant and accepted by you
 *                           or the first file type supported by
 *                           you if there is no claimant
 *
 * Other Info:  On return you should first check if the task returned is 0 in
 *              which case dragging is cancelled otherwise you should follow
 *              with a normal Message_DataSave protocol except that if there was
 *              a claimant you should use the claimant's last msg id as
 *              reference and send the message directly to the claimant.
 *              Or you can use DragDrop_Send who does all the dirty job for you.
 *
 */

typedef struct
{
	int FileType;
} DragDrop_DragFileData;

/*
 * Drag & Drop Drag a file_xxx sprite helper, shows the sprite
 */

static void DragDrop_DragFileShow(void* Handle)
{
	static char sprite[9];
	DragDrop_DragFileData* pData = (DragDrop_DragFileData*) Handle;
	const Mouse* m = Mouse_Get();
	CRect box;

	snprintf(sprite, sizeof(sprite), "file_%03x",pData->FileType);

	box.x0 = m->pt.x;
	box.x1 = m->pt.x;
	box.y0 = m->pt.y;
	box.y1 = m->pt.y;

	_swix(DragASprite_Start, _INR(0,3),  0x45, 1, sprite, &box);
}

/*
 * Drag & Drop Drag a file_xxx sprite helper, hides the sprite
 */

static void DragDrop_DragFileHide(void* Handle)
{
	IGNORE(Handle);

	_swix(DragASprite_Stop, 0);
}

/*
 * Drag & Drop Drag a file_xxx sprite helper, show the pointer
 */

static void DragDrop_DragFilePointer(void* Handle)
{
	MouseShape shape = {"ptr_drag", 0, 0};
	IGNORE(Handle);

	Desktop_SetPointer(&shape);
}

/*
 * Drag & Drop Drag a file_xxx sprite
 */

DragDrop_Info DragDrop_DragFile(void* UserHandle, unsigned int flags, const file_type* types)
{
	CRect box = {0, 0, -1, -1};
	DragDrop_DragFileData Data;

	Data.FileType = types[0];

	return DragDrop_DragLoop(UserHandle
				, flags
				, &box
				, types
				, DragDrop_DragFileShow
				, DragDrop_DragFileHide
				, DragDrop_DragFilePointer
				, NULL
				, NULL
				, &Data
				);
}

/* ------------------------- Drag_DragPoint ------------------------------------
 * Description: This function drags an invisible object around the desktop.
 *
 * Parameters:  id           window where the drag started
 *              FNNullEvent  function called on Null events
 *              UserHandle   handle/pointer to data passed as parameter to the
 *                           OnDraggingCallbacks to identify the dragged data
 *              pscreenbox   bounding box of dragging
 *              mouse_shape  mouse pointer shape to use while dragging
 *
 * Returns:     The function returns false if the dragging was aborted.
 *
 * Usage:       Dragging of sliders, etc.
 *
 */

typedef struct
{
	const MouseShape* mouse_shape;
	CRect       box;
} Drag_DragPointData;

/*
 * Drag a point helper, sets the pointer
 */

static void Drag_DragPointShow(void* Handle)
{
	Drag_DragPointData* pData = Handle;
//	const Mouse* m = Mouse_Get();
//	CDragBox     drag;

	if (pData->mouse_shape)
		Desktop_SetPointer(pData->mouse_shape);
/*
	// Start dragging
	drag.w = HWind_None;
	drag.type = EDragType_Point;
	drag.box.x0 = m->pt.x;
	drag.box.y0 = m->pt.y;
	drag.box.x1 = m->pt.x;
	drag.box.y1 = m->pt.y;
	drag.bounds = pData->box;

	DragBox(&drag);
*/

	Display_SetMouseBox(&pData->box);
}

/*
 * Drag a point helper, restore the old pointer
 */

static void Drag_DragPointHide(void* Handle)
{
	const Mode_Info* Mode = Task_GetModeInfo();

	IGNORE(Handle);

	Desktop_ResetPointer();
/*
	// Cancel dragging
	_swix(Wimp_DragBox, _IN(1), -1);
*/
	Display_SetMouseBox(&Mode->box);
}

/*
 * Drag a point
 */

bool Drag_DragPoint(void* UserHandle, DragDrop_FNullEvent FNNullEvent, const CRect* pscreenbox, const MouseShape* mouse_shape)
{
	Drag_DragPointData Data;
	DragDrop_Info Info;

	Data.mouse_shape = (mouse_shape != NULL) ? mouse_shape : &drag_shape;
	Data.box = *pscreenbox;
	RoundRectForPlot(&Data.box);

	Info = DragDrop_DragLoop(UserHandle
				, DragFlags_DoNotClaim
				, &DragDrop_NullBox
				, NULL
				, Drag_DragPointShow
				, Drag_DragPointHide
				, NULL
				, FNNullEvent
				, NULL
				, &Data
				);

	return (Info.task == Task_GetId());
}

/* ------------------------- ActiveDrag_DragPoint ------------------------------
 * Description: This function drags an invisible object around the desktop.
 *
 * Parameters:  id           window where the drag started
 *              UserHandle   handle/pointer to data being dragged to allow
 *                           OnDraggingCallbacks to identify the dragged data
 *              pscreenbox   bounding box of dragging
 *              mouse_shape  mouse pointer shape to use while dragging
 *
 * Returns:     The function returns the following structure:
 *              msg_ref      the claimant's last message id
 *                           or 0 if there is no claimant
 *              task         the task id of the claimant
 *                           or your task id if there is no claimant
 *                           or 0 if the drag was aborted
 *              flags        the claimant flags
 *              type         the first file type supported by
 *                           the claimant and accepted by you
 *                           or the first file type supported by
 *                           you if there is no claimant
 *
 */

DragDrop_Info ActiveDrag_DragPoint(void* UserHandle, const CRect* pscreenbox, const MouseShape* mouse_shape)
{
	Drag_DragPointData Data;

	Data.mouse_shape = (mouse_shape != NULL) ? mouse_shape : &drag_shape;
	Data.box = *pscreenbox;
	RoundRectForPlot(&Data.box);

	return DragDrop_DragLoop(UserHandle, 0, &DragDrop_NullBox, NULL
				, Drag_DragPointShow
				, Drag_DragPointHide
				, NULL, NULL, NULL, &Data);
}

/* ------------------------- ActiveDrag_DragSprite -----------------------------
 * Description: This function drags a sprite around the desktop.
 *
 * Parameters:  id           window where the drag started
 *              UserHandle   handle/pointer to data being dragged to allow
 *                           OnDraggingCallbacks to identify the dragged data
 *              pscreenbox   bounding box of dragging
 *              sprite       sprite to use while dragging
 *
 * Returns:     The function returns the following structure:
 *              msg_ref      the claimant's last message id
 *                           or 0 if there is no claimant
 *              task         the task id of the claimant
 *                           or your task id if there is no claimant
 *                           or 0 if the drag was aborted
 *              flags        the claimant flags
 *              type         the first file type supported by
 *                           the claimant and accepted by you
 *                           or the first file type supported by
 *                           you if there is no claimant
 *
 */

typedef struct
{
	const char* sprite;
	CRect       box;
} Drag_DragSpriteData;

/*
 * Drag a sprite helper, sets the sprite
 */

static void Drag_DragSpriteShow(void* Handle)
{
	Drag_DragSpriteData* pData = Handle;
	const Mouse* m = Mouse_Get();
	CRect box;

	box.x0 = m->pt.x;
	box.x1 = m->pt.x;
	box.y0 = m->pt.y;
	box.y1 = m->pt.y;

	_swix(DragASprite_Start, _INR(0,3), 0x45, 1, pData->sprite, &box);
}

/*
 * Drag a sprite helper, hides the sprite
 */

static void Drag_DragSpriteHide(void* Handle)
{
	IGNORE(Handle);

	_swix(DragASprite_Stop, 0);
}

/*
 * Drag a sprite
 */

DragDrop_Info ActiveDrag_DragSprite(void* UserHandle, const CRect* pscreenbox, const char* sprite)
{
	Drag_DragSpriteData Data;

	Data.sprite = sprite;
	Data.box = *pscreenbox;
	RoundRectForPlot(&Data.box);

	return DragDrop_DragLoop(UserHandle, 0, &DragDrop_NullBox, NULL
				, Drag_DragSpriteShow
				, Drag_DragSpriteHide
				, NULL, NULL, NULL, &Data);
}

/* ------------------------- DragDrop_GetFileType ------------------------------
 * Description: This function returns the first proposed type supported.
 *
 * Parameters:  proposed     list of file types he would like to receive,
 *                           classified by order of preference,
 *                           terminated by -1
 *              supported    list of file types we support terminated by -1
 *
 * Returns:     The function returns the first proposed type supported.
 *              It returns -1 if no proposed type is supported.
 *
 */

file_type DragDrop_GetFileType(const file_type* proposed, const file_type* supported)
{
	const file_type* ptype;
	const file_type* pcheck;

	for(ptype = proposed; *ptype != -1; ptype++)
	{
		for(pcheck = supported; *pcheck != -1; pcheck++)
		{
			if (*ptype == * pcheck)
				return *ptype;
		}
	}

	// None is supported
	return -1;
}

/* ------------------------- DragDrop_SetFileType ------------------------------
 * Description: This function returns the first proposed type supported.
 *
 * Parameters:  proposed     list of file types he would like to receive,
 *                           classified by order of preference,
 *                           terminated by -1
 *              supported    list of file types we support terminated by -1
 *
 * Returns:     The function returns the first proposed type supported.
 *              It returns first supported type if no proposed type is supported.
 *
 */

file_type DragDrop_SetFileType(const file_type* proposed, const file_type* supported)
{
	const file_type* ptype;
	const file_type* pcheck;

	for(ptype = proposed; *ptype != -1; ptype++)
	{
		for(pcheck = supported; *pcheck != -1; pcheck++)
		{
			if (*ptype == * pcheck)
				return *ptype;
		}
	}

	// None is supported
	return *supported;
}

bool DragDrop_IsValidFileType(file_type proposed, const file_type* supported)
{
	const file_type* pcheck;

	for(pcheck = supported; *pcheck != -1; pcheck++)
	{
		if (proposed == * pcheck)
			return true;
	}

	return false;
}

/* ------------------------- DragDrop_ReplyToDragging --------------------------
 * Description: You should use this function to reply to a Dragging message.
 *              It will send a DragClaim message.
 *
 * Parameters:  msg          the Dragging message received
 *              flags        Claim_PointerChange if you changed the pointer
 *                           Claim_RemoveDragBox to ask the caller to hide
 *                           its drag box/sprite/object
 *                           Claim_DeleteSource if source data is to be deleted
 *                           (e.g. you check the Shift key to distinguish
 *                           a move from a copy)
 *              types        list of file types you support terminated by -1
 */

void DragDrop_ReplyToDragging(const Msg_Dragging* rcv
                              , unsigned int flags
                              , const file_type* types
                              , DragDrop_FTransfer proc
                              , void* handle)
{
	Msg_DragClaim msg;
	int i;

	msg.hdr.size = sizeof(Msg_Hdr) + 8;
	msg.hdr.ref = rcv->hdr.id;
	msg.hdr.action = EMsg_DragClaim;
	msg.flags = flags;

	if (types)
	{
		for (i = 0; types[i] != -1; i++)
		{
			msg.types[i] = types[i];
			msg.hdr.size += 4;
		}
		msg.types[i] = -1;
	}
	else
		msg.types[0] = -1;

	Task_SendMsg((Msg*) &msg, rcv->hdr.task);

	Drag.m_FNTransfer = proc;
	Drag.m_TransferHandle = handle;
}

/* ------------------------ DragDrop_SetOnDraggingCallback ---------------------
 * Description: You should use this function when you receive an Dragging
 *              message. First, you have to setup a callback function of type
 *              OnDraggingCallback that will analyse the message:
 *              - if the drag is supported your callback
 *                + may set a ghost caret
 *                + may change the pointer
 *                + may force windows to scroll
 *                + must call DragDrop_ReplyToDragging
 *                + must return true
 *              - if the drag is not supported or if the message contains
 *                the Drag_DoNotClaim flag your callback must
 *                + remove a possibly set ghost caret
 *                + not reset the pointer (the sender of the Dragging
 *                  message will do it).
 *                + return false.
 *              This function will call your callback and if you accept the
 *              dragging, it will setup a prehandler so that you every
 *              received OnDragging messages will be passed to your callback
 *              until you reject the dragging or a data transfer message
 *              is sent.
 *
 *              If no Dragging message is specified, a dummy Dragging
 *              message is generated with the Drag_DoNotClaim flag
 *              and sent to any call pending callback. This is used
 *              to cleanup everything when dragging terminates.
 *
 * Parameters:  msg                     the Dragging message received
 *              ondragging              the callback that will reply to
 *                                      Dragging messages
 *              Handle                  handle for data required by
 *                                      the callback (a pointer to a
 *                                      view of the document, ...)
 *
 * Returns:     The function true if the drag is supported
 */

bool DragDrop_SetOnDraggingCallback(const Msg_Dragging* pmsg, OnDraggingCallback ondragging, void* Handle)
{
	if (pmsg)
	{
		// Reply to message
		if (ondragging(Handle, pmsg))
		{
			// the callback claimed the drag
			Dragging.m_Callback = ondragging;
			Dragging.m_Handle   = Handle;
			return true;
		}
		Dragging.m_Callback = NULL;
		Dragging.m_Handle   = NULL;
	}
	else
	{
		// Force pending callback to cleanup everything
		if (Dragging.m_Callback)
		{
			Msg_Dragging msg;

			msg.pos.w = HWind_None;
			msg.pos.i = HIcon_None;
			msg.pos.pt.x = 0;
			msg.pos.pt.y = 0;
			msg.flags = DragFlags_DoNotClaim;
			msg.types[0] = -1;
			Dragging.m_Callback(Dragging.m_Handle, &msg);
		}
		Dragging.m_Callback = NULL;
		Dragging.m_Handle   = NULL;
	}

	return false;
}

/*= Protected interface used by dragging functions ===========================*/

/* ------------------------- DragDrop_DragLoop ---------------------------------
 * Description: This function starts the drag, polls the wimp and returns when
 *              either the user release the drags or the drag is aborted.
 *
 * Parameters:  id           window where the drag started
 *              flags        drag an drop flags
 *              pscreenbox   bounding box of dragging
 *              types        list of file types in which the data can be saved,
 *                           terminated by -1
 *                           or NULL for non DragDrop drags
 *              FNShow       function to show the drag box/sprite/object
 *              FNHide       function to hide the drag box/sprite/object
 *              FNPointer    function to set the dragging pointer
 *              FNNullEvent  function to call on null events
 *                           or NULL to broadcast Dragging messages
 *              DragHandle   handle/pointer for data used by the FNShow, FNHide
 *                           and FNPointer functions
 *              UserHandle   handle/pointer to data being dragged to allow
 *                           OnDraggingCallbacks to identify the dragged data
 *
 * Returns:     The function returns the following structure:
 *              msg_ref      the claimant's last message id
 *                           or 0 if there is no claimant
 *              task         the task id of the claimant
 *                           or your task id if there is no claimant
 *                           or 0 if the drag was aborted
 *              flags        the claimant flags
 *              type         the first file type supported by
 *                           the claimant and accepted by you
 *                           or the first file type supported by
 *                           you if there is no claimant
 *
 */

/*
 * Helper for Drag Loop, Drag must already be initialised
 */

static void DragDrop_SetFlags(int new_flags)
{
	int changes = Drag.m_LastFlags ^ new_flags;

	// Reset pointer ?
	if (Drag.m_bFinished)
		Desktop_ResetPointer();
	else if (changes & ClaimFlags_PointerChange)
	{
		if ((!(new_flags & ClaimFlags_PointerChange)) && (Drag.m_FNPointer != NULL))
			Drag.m_FNPointer(Drag.m_DragHandle);
	}

	// Show or hide drag box ?
	if (changes & ClaimFlags_RemoveDragBox)
	{
		if (new_flags & ClaimFlags_RemoveDragBox)
		{
			Drag.m_FNHide(Drag.m_DragHandle);
			if (!Drag.m_bFinished)
			{
				const Mode_Info* Info = Task_GetModeInfo();
				const Mouse* m = Mouse_Get();
				CDragBox drag;

				// Drag invisible point
				drag.w = HWind_None;
				drag.type = EDragType_Point;
				drag.box.x0 = m->pt.x;
				drag.box.y0 = m->pt.y;
				drag.box.x1 = m->pt.x;
				drag.box.y1 = m->pt.y;
				drag.bounds = Info->box;

				DragBox(&drag);
			}
		}
		else
			Drag.m_FNShow(Drag.m_DragHandle);
	}
	else
	{
		if (Drag.m_bFinished & (new_flags & ClaimFlags_RemoveDragBox))
		{
				// Cancel invisible drag
				_swix(Wimp_DragBox, _IN(1), -1);
		}
	}

	Drag.m_LastFlags = new_flags;
}

/*
 * Helper for Drag Loop, m_Dragging must already be initialised
 */

static const _kernel_oserror* DragDrop_SendDragging(void)
{
	const Mouse* m = Mouse_Get();
	const _kernel_oserror* err = NULL;

	// If not in send dragging mode, only send message when finished to exit DragDrop_Loop
	if (!Drag.m_bSendDragging && !Drag.m_bFinished) return NULL;

	// Send a Message_Dragging
	Drag.m_Dragging.pos.w = m->w;
	Drag.m_Dragging.pos.i = m->i;
	Drag.m_Dragging.pos.pt = m->pt;
	if (Drag.m_bAborted)
		Drag.m_Dragging.flags |= DragFlags_DoNotClaim;

	if (Drag.m_Claimant)
	{
		Drag.m_Dragging.hdr.ref = Drag.m_LastRef;
		err = Task_PostMsg((Msg*) &Drag.m_Dragging, Drag.m_Claimant);
	}
	else
	{
		Drag.m_Dragging.hdr.ref = 0;
		if (Drag.m_Dragging.types[0] == -1)
		{
			// Internal dragging: stay within our task
			if (Drag.m_bFinished)
				err = Task_PostMsg((Msg*) &Drag.m_Dragging, Task_GetId());
			else
				err = Task_SendMsg((Msg*) &Drag.m_Dragging, Task_GetId());
		}
		else
		{
			// Normal Dragging protocol
			if (Drag.m_bFinished)
				err = Task_WPostMsg((Msg*) &Drag.m_Dragging, m->w, m->i);
			else
				err = Task_WSendMsg((Msg*) &Drag.m_Dragging, m->w, m->i);
		}
	}

	return err;
}

/*
 * Helper for Drag Loop, fills DragDrop_Info structure on exit of draggging
 */

static DragDrop_Info DragDrop_ExitWithInfo(const Msg_DragClaim* msg)
{
	DragDrop_Info Info;

	// Restore ptr & hide drag box
	DragDrop_SetFlags(ClaimFlags_RemoveDragBox);

	if (msg)
	{
		// Drag was claimed
		Info.task = Drag.m_Claimant;
		Info.msg_ref = Drag.m_LastRef;
		Info.flags = msg->flags & ~(ClaimFlags_PointerChange | ClaimFlags_RemoveDragBox);
		Info.type = DragDrop_SetFileType(&msg->types[0]
						, &Drag.m_Dragging.types[0]);
		if (Info.task == Task_GetId())
		{
			Info.FTransfer = Drag.m_FNTransfer;
			Info.TransferHandle = Drag.m_TransferHandle;
		}
		else
		{
		  	Info.FTransfer = NULL;
		  	Info.TransferHandle = NULL;
		}
	}
	else
	{
		// Drag was not claimed, set task != 0 but msg_ref = 0
		Info.task = Task_GetId();
		Info.msg_ref = 0;
		Info.flags = 0;
		Info.type = Drag.m_Dragging.types[0];
	  	Info.FTransfer = NULL;
	  	Info.TransferHandle = NULL;
	}

	/* Ignore drags on system icons of our windows,
	   don't do it for other tasks they are maybe expecting
	   a data transfer message for removing a ghost caret.
	   Ignore also drags that where aborted. */
	if (((Info.task == Task_GetId()) &&  (Mouse_Get()->i < -1))
	||  (Drag.m_bAborted))
		Info.task = (HTask) 0;

	Drag.m_UserHandle = NULL;

	// Remove possibly pending intra-task callback
	DragDrop_SetOnDraggingCallback(NULL, NULL, NULL);

	return Info;
}

/*
 * Drag & Drop Drag Poll Loop
 */

DragDrop_Info DragDrop_DragLoop(void* UserHandle
				, unsigned int flags
				, const CRect* pbox
				, const file_type* types
				, DragDrop_FShow FNShow
				, DragDrop_FHide FNHide
				, DragDrop_FPointer FNPointer
				, DragDrop_FNullEvent FNNullEvent
				, DragDrop_FProcessKey FNProcessKey
				, void* DragHandle
				)
{
	Event* e;
	unsigned int mask = Task_GetPollMask();
	CCaret old_caret;
	int i;
	int step;

	// Store current caret position
	Caret_Get(&old_caret);

	// grab key presses
	KeyGrabber_Activate();

	// Prepare Message_Dragging info
	Drag.m_Dragging.hdr.size = 60;
	Drag.m_Dragging.hdr.ref = 0;
	Drag.m_Dragging.hdr.action = EMsg_Dragging;
	Drag.m_Dragging.flags = flags;
	Drag.m_Dragging.box = *pbox;

	if (types)
	{
		for (i = 0; types[i] != -1; i++)
		{
			Drag.m_Dragging.hdr.size += 4;
			Drag.m_Dragging.types[i] = types[i];
		};
		Drag.m_Dragging.types[i] = -1;
	}
	else
		Drag.m_Dragging.types[0] = -1;

	// Init variables
	Drag.m_Claimant = (HTask) 0;
	Drag.m_bSendDragging = ((flags & DragFlags_DoNotClaim) == 0);
	Drag.m_bAborted = false;
	Drag.m_bFinished = false;
	Drag.m_LastRef = 0;
	Drag.m_FNShow = FNShow;
	Drag.m_FNHide = FNHide;
	Drag.m_FNPointer = FNPointer;
	Drag.m_DragHandle = DragHandle;
	Drag.m_UserHandle = UserHandle;
	Drag.m_LastFlags = ClaimFlags_PointerChange | ClaimFlags_RemoveDragBox;
	Drag.m_FNTransfer = NULL;
	Drag.m_TransferHandle = NULL;
	Drag.m_NullCount = 0;

	// Show drag box
	DragDrop_SetFlags(0);

	// Set poll step to cursor repeat rate, cf scrolling of windows
	step = 2;

	while (true)
	{
		// allow null events and key presses
		e = Task_PollIdle(mask & ~(EPollMask_Null | EPollMask_Key), step);

		switch (e->Type)
		{
			case EEvent_Null:
			{
				if (!Mouse_Get()->but)
				{
					if ((Drag.m_FNShow != Drag_DragPointShow) || !Drag.m_NullCount)
					{
						// Drag was aborted, probably because of an error window
						Drag.m_bAborted = true;
					}
					// Here again ? Protect against response/ack to the SendDragging being lost
					if (Drag.m_bFinished == true)
						break;
					Drag.m_bFinished = true;
					// Inform claimant to stop claiming the drag
					DragDrop_SendDragging();
				}
				else
				{
					Drag.m_NullCount++;

					if (FNNullEvent)
						FNNullEvent(UserHandle, Mouse_Get());
					else
						DragDrop_SendDragging();

					// Process event as normally
					if (!(mask & EPollMask_Null))
						Task_ProcessEvent(e);
				}
				continue;
			}
			break;
			case EEvent_Key:
			{
				const Event_Key* key = e->pData;
				const Mouse* m = Mouse_Get();

				// Test ESC key
				if (key->code != 27)
				{
					if (!FNProcessKey
					||  !FNProcessKey(UserHandle, m, key->code))
					{
						switch(key->code)
						{
							case 0x18c: // left arrow
							{
								Mouse_Set(m->pt.x - Task_GetModeInfo()->dx, m->pt.y);
							}
							break;
							case 0x18d: // right arrow
							{
								Mouse_Set(m->pt.x + Task_GetModeInfo()->dx, m->pt.y);
							}
							break;
							case 0x18e: // down arrow
							{
								Mouse_Set(m->pt.x, m->pt.y - Task_GetModeInfo()->dy);
							}
							break;
							case 0x18f: // up arrow
							{
								Mouse_Set(m->pt.x, m->pt.y + Task_GetModeInfo()->dy);
							}
							break;
							default:
								// ignore other key presses while dragging
								Task_WimpProcessKey(key->code);
						}
					}
					continue;
				}
				Drag.m_bAborted = true;
				Drag.m_bFinished = true;
				// Inform claimant to stop claiming the drag
				DragDrop_SendDragging();
				continue;
			}
			break;
			case EEvent_Drag:
			{
				// User dropped the file
				Drag.m_bFinished = true;
				/* Inform claimant with latest x, y pos
				   but do not tell it to stop claiming the drag,
				   he will be warned of this by the DataSave
				   message following the Drop */
				DragDrop_SendDragging();
				continue;
			}
			break;
			case EEvent_Message:
			case EEvent_MessageWantAck:
			{
				const Msg_DragClaim* msg = e->pData;

				// Test if someone replyed a Message_DragClaim
				if (msg->hdr.action == EMsg_DragClaim)
				{
					DragDrop_SetFlags(msg->flags);
					Drag.m_Claimant = msg->hdr.task;
					Drag.m_LastRef  = msg->hdr.id;
					if (Drag.m_bFinished)
					{
						// Restore caret pos if still relevant
						KeyGrabber_Deactivate();
						if ((old_caret.pos.w != HWind_None) && Window_IsOpen(old_caret.pos.w))
							Caret_Set(&old_caret);

						return DragDrop_ExitWithInfo(msg);
					}

					continue;
				}
				if (msg->hdr.action == EMsg_MenusDeleted)
				{
					// Drag was aborted, cf Menus closed by Esc which we do not receive
					Drag.m_bAborted = true;
					Drag.m_bFinished = true;
					// Inform claimant to stop claiming the drag
					DragDrop_SendDragging();
					// Let the message be processed
				}
			}
			break;
			case EEvent_MessageAck:
			{
				// Test if our Message_Dragging bounced back
				if (((Msg*) e->pData)->hdr.action == EMsg_Dragging)
				{
					// Message bounced back to us
					if (Drag.m_Claimant)
					{
						// Claimant released the drag
						DragDrop_SetFlags(0);
						Drag.m_Claimant = 0;
						Drag.m_FNTransfer = NULL;
						Drag.m_TransferHandle = NULL;
						DragDrop_SendDragging();
					}
					else if (Drag.m_bFinished)
					{
						// Restore caret pos if still relevant
						KeyGrabber_Deactivate();
						if ((old_caret.pos.w != HWind_None) && Window_IsOpen(old_caret.pos.w))
							Caret_Set(&old_caret);

						return DragDrop_ExitWithInfo(NULL);
					}

					continue;
				}
			}
			break;
		}

		// Normal processing
		Task_ProcessEvent(e);
	}
}

/*= Interface used by the task framework =====================================*/

/* ------------------------- DragDrop_CallDraggingCallback ---------------------
 * Description: This function is called by the task framework when it receives
 *              a Message_Dragging to let any registred dragging callback
 *              be the first to process that message, so that for example the
 *              callback unregister itself if the pointer moves outside the
 *              window.
 *
 * Parameters:  msg          the Dragging message received
 *
 * Returns:     The function returns true if the callback handled the message
 *
 */

bool DragDrop_CallDraggingCallback(const Msg_Dragging* rcv)
{
	// If a call-back is installed call it first
	if (Dragging.m_Callback)
	{
		if (Dragging.m_Callback( Dragging.m_Handle, rcv))
			return true;
		else
			Dragging.m_Callback = NULL;
	}

	return false;
}

/* ------------------------- DragDrop_ReplyNoDrop ------------------------------
 * Description: This function is called by the task framework when no handler
 *              responded to a received Message_Dragging. It will claim the
 *              dragging message if the pointer is over one of the task's own
 *              windows and turn the pointer into a 'forbidden' symbol by using
 *              the sprite ptr_nodrop.
 *
 * Parameters:  msg          the Dragging message received
 *
 * Returns:     Nothing
 *
 */

void DragDrop_ReplyNoDrop(const Msg_Dragging* rcv)
{
	Msg_DragClaim msg;
	MouseShape shape = {"ptr_nodrop", 0, 0};

	if (rcv->flags & DragFlags_DoNotClaim)
		return;

	if (Window_GetTask(rcv->pos.w, rcv->pos.i) == Task_GetId())
	{
		Desktop_SetPointer(&shape);
		msg.hdr.size = 24;
		msg.hdr.ref = rcv->hdr.id;
		msg.hdr.action = EMsg_DragClaim;
		msg.flags = ClaimFlags_PointerChange;
		msg.types[0] = -1;

		Task_SendMsg((Msg*) &msg, rcv->hdr.task);
		Dragging.m_Ref = msg.hdr.id;

        Drag.m_FNTransfer = NULL;
		Drag.m_TransferHandle = NULL;
	}
}

/* ------------------------- DragDrop_IsNoDropRef ------------------------------
 * Description: This function is called by the task framework to check if the
 *              reference is the one of the message sent by the last
 *              DragDrop_ReplyNoDrop call so that the task can ignore any
 *              DataSave message that use this reference as we don't want the
 *              data but nothing in the protocol prevents the other task to send
 *              us the message if user drops the data while ptr_nodrop is shown.
 *
 * Parameters:  ref          a message reference
 *
 * Returns:     true if is the reference our last sent Dragging message
 *
 */

bool DragDrop_IsNoDropRef(int ref)
{
	return (ref && (ref == Dragging.m_Ref));
}

/*= Public Interface for data transfer =======================================*/

/* ---------------------------- DragDrop_Send ----------------------------------
 * Description: Allows the user to export application data on return on the
 *              DragDrop protocol.
 *
 * Parameters:  DragDrop_Info   Info returned by the DragDrop protocol
 *              char* filename  suggested file name
 *              int estsize     estimated size of the file
 *              FNSendProc      caller-supplied function for providing
 *                              application data chunks during the
 *                              data transfer
 *              FNRemoveProc    caller-supplied function to delete source
 *                              data when transfer is completed
 *                              or 0 if source data must remain intact
 *              FNOnSafeProc    caller-supplied function for updating
 *                              application data modif status and
 *                              filename if it was a safe transfer to a file
 *                              or 0 (i.e. when saving a selection)
 *              FNPrintProc     caller-supplied function for printing
 *                              application data, if "icon" is
 *                              dragged onto printer application
 *                              ! not supported yet !
 *              FNOnErrorProc   caller-supplied function for reporting
 *                              errors occurring during the transfer
 *              void* handle    handle to be passed to handler functions.
 *
 * Returns:     true if transfer was completed successfully.
 *
 * Other Info:  You should typically call this function on exit from
 *              DragDrop_DragFile and it will call XFer_Send with the appropriate
 *              parameters. See XFerSend for more details.
 *
 */

bool DragDrop_Send(const DragDrop_Info* pInfo
		, const char* filename
		, int estsize
		, XFer_SendProc FNSend
		, XFer_RemoveProc FNRemove
		, XFer_OnSafeProc FNOnSafe
		, XFer_PrintProc FNPrint
		, XFer_OnErrorProc FNOnError
		, bool asfileonly
		, void* handle)
{
	const Mouse* m = Mouse_Get();

	// If drag aborted do nothing
	/* I think that data dropped on a window's tool (scroll bar, close icon, ...)
	   should be ignored (where do you insert the data in the document) unless
	   the drag was claimed by another task */
	if ((pInfo->task == 0)
	||  ((pInfo->task == Task_GetId()) && ((m->w == HWind_None) || (m->i < -1))))
		return false;

	// If DeleteSource is not requested, remove the corresponding callback
	if (!(pInfo->flags & ClaimFlags_DeleteSource))
		FNRemove = NULL;

	return XFer_Send( pInfo->msg_ref
		, pInfo->task
		, m->w
		, m->i
		, m->pt.x
		, m->pt.y
		, estsize
		, pInfo->type
		, filename
		, FNSend
		, FNRemove
		, FNOnSafe
		, FNPrint
		, FNOnError
		, asfileonly
		, handle
		);
}

/* ---------------------------- DragDrop_Transfer ----------------------------------
 * Description: Allows the user to internally transfer data on return on the
 *              DragDrop protocol.
 *
 * Parameters:  DragDrop_Info   Info returned by the DragDrop protocol
 *              FNRemoveProc    caller-supplied function to delete source
 *                              data when transfer is completed
 *                              or 0 if source data must remain intact
 *              void* data      handle of the data to be passed to handler functions.
 *              void* handle    handle to be passed to handler functions.
 *
 * Returns:     true if transfer was completed successfully.
 *
 * Other Info:  You should typically call this function on exit from
 *              DragDrop_DragFile when pInfo->FTransfer is not NULL.
 *
 */

bool DragDrop_Transfer(const DragDrop_Info* pInfo
		, XFer_RemoveProc FNRemove
		, void* data
		, void* handle)
{
	bool bDone = false;
	const Mouse* m = Mouse_Get();
	ScreenPos pos;

	if (pInfo->FTransfer)
	{
		pos.w = m->w;
		pos.i = m->i;
		pos.pt = m->pt;
		bDone = pInfo->FTransfer(pInfo->TransferHandle, pInfo->type, data, &pos);
	}

	if (bDone && (pInfo->flags & ClaimFlags_DeleteSource))
		FNRemove(handle);

	return bDone;
}
