//	Zinc Application Framework - M_UTILS.CPP
//	COPYRIGHT (C) 1990-1999.  All Rights Reserved.
//	Zinc Software Incorporated.  Pleasant Grove, Utah  USA

#include <z_scrdsp.hpp>
#include <z_stdlib.hpp>
#include <z_mouse2.hpp>
#include "m_utils.hpp"

extern "C"
{
#include _ManagerP_
#include _PrimitiveP_
}

// --- Miscellaneous --------------------------------------------------------

KeySym ZafKeySym(ZafIChar value)
{
	// For values 0x020 - 0x7e KeySym values are the ascii equivalent.
	// See file /usr/lib/x11/XKeySymDB for specific information on these
	// values.
	KeySym keySym = NoSymbol;
	if (value >= 0x0020 && value <= 0x007E)
	{
#		if defined(ZAF_UNICODE)
		wchar_t osChar[4];
		zafCodeSet->ConvertToOSWChar(value, osChar);
#		else
		char osChar[4];
		zafCodeSet->ConvertToOSChar(value, osChar);
#		endif
		keySym = (KeySym)osChar[0];
	}
	return (keySym);
}

void ZafProcessXtEvents(void)
{
	// This function is used to synchronize ZAF objects with the Motif 
	// system.  Frequently, a delete or other type of motif action causes
	// a series of events to be processed.  This function allows these 
	// processes to complete before ZAF continues with its normal operation.
	while (XSync(((ZafScreenDisplay *)zafDisplay)->xDisplay, false),
	  XtAppPending(((ZafScreenDisplay *)zafDisplay)->xAppContext))
	{
		XEvent message;
		XtAppNextEvent(((ZafScreenDisplay *)zafDisplay)->xAppContext, &message);
		XtDispatchEvent(&message);
	}
}

void mouse_callback(Widget , ZafWindowObject *, XEvent *, Boolean *)
{
	// This provides a hook to ensure the messages are processed.
}

void ZafMouseCallback(Widget widget, XEvent *xEvent, String *, Cardinal *)
{
	// Check for a valid ZAF object.
	ZafWindowObject *zafObject = ZAF_NULLP(ZafWindowObject);
	XtVaGetValues(widget, XmNuserData, &zafObject, 0);
	if (!zafObject || !zafObject->screenID)
		return;

	// Construct the event.
	ZafEventStruct event(E_MOTIF, xEvent);
	Widget frame = zafObject->OSScreenID(ZAF_FRAMEID);
	if (xEvent->type == MotionNotify)
	{
		if (xEvent->xmotion.state & Button1Mask)
			event.type = L_CONTINUE_SELECT;
		else if (xEvent->xmotion.state & Button3Mask)
			event.type = L_CONTINUE_ESCAPE;
		else
			event.type = L_VIEW;
		event.position.Assign(xEvent->xmotion.x, xEvent->xmotion.y, ZAF_PIXEL);
		widget = XtWindowToWidget(xEvent->xmotion.display, xEvent->xmotion.window);
	}
	else
	{
		if (xEvent->xbutton.button == Button1)
			event.type = (xEvent->type == ButtonPress) ? L_BEGIN_SELECT : L_END_SELECT;
		else
			event.type = (xEvent->type == ButtonPress) ? L_BEGIN_ESCAPE : L_END_ESCAPE;
		event.position.Assign(xEvent->xbutton.x, xEvent->xbutton.y, ZAF_PIXEL);
		widget = XtWindowToWidget(xEvent->xbutton.display, xEvent->xbutton.window);
	}

	// Determine the actual coordinates.
	while (widget && widget != frame)
	{
		event.position.column += widget->core.x;
		event.position.line += widget->core.y;
		widget = XtParent(widget);
	}

	// Now, call the event function.
	zafObject->Event(event);
}

// ----- Edit mode ----------------------------------------------------------

static void edit_callback(Widget , ZafWindowObject *, XEvent *, Boolean *)
{
	// This provides a hook to ensure the messages are processed.
}

static void ZafEditCallback(Widget widget, XEvent *xEvent, String *, Cardinal *)
{
	// Check for a valid ZAF object.
	ZafWindowObject *zafObject;
	XtVaGetValues(widget, XmNuserData, &zafObject, 0);
	if (!zafObject || !zafObject->screenID)
		return;
	else if (zafObject->SupportObject() && zafObject->IsA(ID_ZAF_SCROLL_BAR))
		zafObject = zafObject->parent;

	// Construct the event.
	ZafEventStruct event(E_MOTIF, xEvent);
	Widget frame = zafObject->OSScreenID(ZAF_FRAMEID);
	if (xEvent->type == MotionNotify)
	{
		if (xEvent->xmotion.state & Button1Mask)
			event.type = L_CONTINUE_SELECT;
		else if (xEvent->xmotion.state & Button3Mask)
			event.type = L_CONTINUE_ESCAPE;
		else
			event.type = L_VIEW;
		event.position.Assign(xEvent->xmotion.x, xEvent->xmotion.y, ZAF_PIXEL);
		widget = XtWindowToWidget(xEvent->xmotion.display, xEvent->xmotion.window);
	}
	else
	{
		if (xEvent->xbutton.button == Button1)
			event.type = (xEvent->type == ButtonPress) ? L_BEGIN_SELECT : L_END_SELECT;
		else
			event.type = (xEvent->type == ButtonPress) ? L_BEGIN_ESCAPE : L_END_ESCAPE;
		event.position.Assign(xEvent->xbutton.x, xEvent->xbutton.y, ZAF_PIXEL);
		widget = XtWindowToWidget(xEvent->xbutton.display, xEvent->xbutton.window);
	}

	// Determine the actual coordinates.
	while (widget && widget != frame)
	{
		event.position.column += widget->core.x;
		event.position.line += widget->core.y;
		widget = XtParent(widget);
	}

	// If the object is actually in edit mode then the message can be
	// routed directly to ZafWindowObject.  Otherwise, it needs to go through
	// the virtual Event() so the designer can pick up the message.
	if (zafObject->EditMode())
	{
		// Check for pre-cursor updates.
		if (event.type == L_CONTINUE_SELECT || event.type == L_CONTINUE_ESCAPE)
		{
			ZafEventStruct mouseEvent(DM_VIEW);
			mouseEvent.screenID = frame;
			zafEventManager->Event(mouseEvent, E_MOUSE);
		}
		// Next, call the event function.
		// Notebook is a special case. It handles edit mode at the top of
		// its event function.
		if (zafObject->IsA(ID_ZAF_NOTEBOOK))
			zafObject->Event(event);
		else
			zafObject->ZafWindowObject::Event(event);
	}
	else
	{
		// First, check for pre-cursor updates.
		if (event.type != L_END_SELECT && event.type != L_END_ESCAPE)
		{
			ZafEventStruct mouseEvent(DM_VIEW);
			mouseEvent.screenID = frame;
			zafEventManager->Event(mouseEvent, E_MOUSE);
		}

		// Next, call the event function.
		zafObject->Event(event);
	}
}

void ZafEditModeRegister(ZafWindowObject *object)
{
	// Check for a edit mode.
	if (object->EditMode())
	{
		// Add edit mode operations.
		static bool initialized = false;
		static XtActionsRec edit_actions[] =
		{
			{ "ZafEditCallback", 	ZafEditCallback }
		};
		if (!initialized)
		{
			initialized = true;
			XtAppAddActions(((ZafScreenDisplay *)zafDisplay)->xAppContext, edit_actions, XtNumber(edit_actions));
		}

		// Define the object translations.
		XtTranslations translationTable = XtParseTranslationTable(
			"<Btn1Down>:			ZafEditCallback()\n\
			 <Btn1Up>:				ZafEditCallback()\n\
			 <Btn3Down>:			ZafEditCallback()\n\
			 <Btn3Up>:				ZafEditCallback()\n\
	 	 	 <Motion>:				ZafEditCallback()");

		// Determine the appropriate override levels.
		Widget frame = object->OSScreenID(ZAF_FRAMEID);
		for (Widget widget = object->screenID; widget; widget = XtParent(widget))
		{
			XtAddEventHandler(widget, ButtonPress | MotionNotify, false,
				(XtEventHandler)edit_callback, (XtPointer)object);
			XtOverrideTranslations(widget, translationTable);
			if (widget == frame)
				break;
		}
	}
}

// ----- Drag/drop ----------------------------------------------------------

static Atom zafDragDropAtom = 0;
ZafEventStruct dropEvent;

static bool DragConvert(Widget widget, Atom *selection, Atom *target,
	Atom *typeRtn, XtPointer *valueRtn, unsigned long *lengthRtn,
	int *formatRtn, unsigned long *, XtPointer, XtRequestId *)
{
	// Check for a valid drag/drop operation.
	if (*target != zafDragDropAtom)
		return (false);

	// Check for a valid drop type.
	ZafWindowObject *zafObject; XtVaGetValues(widget, XmNclientData, &zafObject, 0);
	Atom mtfDrop = XmInternAtom(((ZafScreenDisplay *)zafObject->display)->xDisplay, "_MOTIF_DROP", false);
	if (*selection != mtfDrop)
		return (false);

	// Set the return information.
	*typeRtn = zafDragDropAtom;
	*valueRtn = (XtPointer)zafObject;
	*lengthRtn = sizeof(zafObject);
	*formatRtn = 8;
	return(true);
}

static void TransferProc(Widget, ZafWindowObject *target, Atom *,
	Atom *type, ZafWindowObject *source, unsigned long *, int *)
{
	// Transfer the data.
	if (dropEvent.type != S_UNKNOWN)
	{
		dropEvent.windowObject = source;
		if (*type == zafDragDropAtom && source && source != target)
			target->Event(dropEvent);
		source->Event(S_END_DRAG);
	}
}

static void HandleDrop(Widget widget, XtPointer , XmDropProcCallbackStruct *callback)
{
	int nargs = 0;
	Arg args[10];
	dropEvent.type = S_UNKNOWN;

	if (callback->dropAction == XmDROP)
	{
		XmDropTransferEntryRec transferEntry;

		transferEntry.target = zafDragDropAtom;
		XtVaGetValues(widget, XmNuserData, &transferEntry.client_data, 0);

		XtSetArg(args[nargs], XmNdropTransfers, &transferEntry); nargs++;
		XtSetArg(args[nargs], XmNnumDropTransfers, 1); nargs++;
		XtSetArg(args[nargs], XmNtransferProc, TransferProc); nargs++;
		
		// Set the dropEvent to be the same as drop being preformed.
		dropEvent.type = S_DROP_DEFAULT;
		if (callback->operation == XmDROP_COPY)
			dropEvent.type = S_DROP_COPY;
		else if (callback->operation == XmDROP_MOVE)
			dropEvent.type = S_DROP_MOVE;
		else if (callback->operation == XmDROP_LINK)
			dropEvent.type = S_DROP_LINK;

	}
	else
	{
		XtSetArg(args[nargs], XmNtransferStatus, XmTRANSFER_FAILURE); nargs++;
	}

	XmDropTransferStart(callback->dragContext, args, nargs);
}

static void SendDragMessages(Widget widget, XtPointer , XmDragProcCallbackStruct *callback)
{
	// If leaving the object don't send the message.
	if (callback->reason == XmCR_DROP_SITE_LEAVE_MESSAGE)
		return;

 	ZafWindowObject *zafObject;
 	XtVaGetValues(widget, XmNuserData, &zafObject, 0);
 	ZafEventStruct dragEvent;
 	
	// Set the event type for Drag operation being preformed.
	dragEvent.type = S_DRAG_DEFAULT;
	if (callback->operation == XmDROP_COPY)
 		dragEvent.type = S_DRAG_COPY;
 	else if (callback->operation == XmDROP_MOVE)
 		dragEvent.type = S_DRAG_MOVE;
 	else if (callback->operation == XmDROP_LINK)
 		dragEvent.type = S_DRAG_LINK;
 	zafObject->Event(dragEvent);
}

ZafEventType ZafDragEnd(ZafWindowObject *object, const ZafEventStruct &event)
{
	// Restore zinc.
	object->windowManager->dragObject = ZAF_NULLP(ZafWindowObject);
	return (event.type);
}

ZafEventType ZafDragBegin(ZafWindowObject *object, const ZafEventStruct &event)
{
	// Make sure the object has drag/drop capabilities.
	if (!object->CopyDraggable() && !object->MoveDraggable() && !object->LinkDraggable())
		return (S_ERROR);

	// Initiate zinc.
	object->windowManager->dragObject = object;

	// Initiate motif.
	// set the arguments.
	int nargs = 0;
	Arg args[10];

	// atom
	if (!zafDragDropAtom)
		zafDragDropAtom = XmInternAtom(((ZafScreenDisplay *)object->display)->xDisplay, "ZafWindowObject", false);
	XtSetArg(args[nargs], XmNexportTargets, &zafDragDropAtom); nargs++;
	XtSetArg(args[nargs], XmNnumExportTargets, 1); nargs++;

	// operation
	unsigned long dragOperations = 0;
	if (object->CopyDraggable())
		dragOperations |= XmDROP_COPY;
	if (object->MoveDraggable())
		dragOperations |= XmDROP_MOVE;
	if (object->LinkDraggable())
		dragOperations |= XmDROP_LINK;
	XtSetArg(args[nargs], XmNdragOperations, dragOperations); nargs++;

	// convert procedure
	XtSetArg(args[nargs], XmNconvertProc, DragConvert); nargs++;

	// source
	XtSetArg(args[nargs], XmNclientData, object); nargs++;

	// start the operation.
	XmDragStart(object->screenID, (XEvent *)&event.osEvent, args, nargs);
	return (event.type);
}

void ZafDropSiteRegister(ZafWindowObject *object)
{
	// Check the drop capability of the object.
	if (!object->screenID)
		return;
	XmDropSiteUnregister(object->screenID);
	if (!object->AcceptDrop())
		return;

	// set the arguments.
	int nargs = 0;
	Arg args[10];

	// atom
	if (!zafDragDropAtom)
		zafDragDropAtom = XmInternAtom(((ZafScreenDisplay *)object->display)->xDisplay, "ZAF_DRAG_DROP", false);
	XtSetArg(args[nargs], XmNimportTargets, &zafDragDropAtom); nargs++;
	XtSetArg(args[nargs], XmNnumImportTargets, 1); nargs++;

	// Enable the drop site for all types of drop operations
	unsigned long dropOperations = 0;
		dropOperations = XmDROP_COPY | XmDROP_MOVE | XmDROP_LINK;
	XtSetArg(args[nargs], XmNdropSiteOperations, dropOperations); nargs++;
	XtSetArg(args[nargs], XmNdropProc, HandleDrop); nargs++;
	XtSetArg(args[nargs], XmNdragProc, SendDragMessages); nargs++;

	if (object->IsA(ID_ZAF_WINDOW))
	{
		XtSetArg(args[nargs], XmNdropSiteType, XmDROP_SITE_COMPOSITE);
		nargs++;
	}

	// register the object.
	XmDropSiteRegister(object->screenID, args, nargs);
}

