/* 
 * Motif
 *
 * Copyright (c) 1987-2012, The Open Group. All rights reserved.
 *
 * These libraries and programs are free software; you can
 * redistribute them and/or modify them under the terms of the GNU
 * Lesser General Public License as published by the Free Software
 * Foundation; either version 2 of the License, or (at your option)
 * any later version.
 *
 * These libraries and programs are distributed in the hope that
 * they will be useful, but WITHOUT ANY WARRANTY; without even the
 * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
 * PURPOSE. See the GNU Lesser General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with these librararies and programs; if not, write
 * to the Free Software Foundation, Inc., 51 Franklin Street, Fifth
 * Floor, Boston, MA 02110-1301 USA
*/ 
#ifdef REV_INFO
#ifndef lint
static char rcsid[] = "$XConsortium: DropTrans.c /main/15 1996/05/02 13:50:19 pascale $"
#endif
#endif

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif


#include <Xm/DropTransP.h>
#include <Xm/DragCP.h>
#include "XmI.h"
#include "DragCI.h"
#include "DragICCI.h"
#include <Xm/AtomMgr.h>
#include <stdio.h>

/* Deactivated the fix, since it causes crash.
   For details see http://bugs.motifzone.net/long_list.cgi?buglist=1361
   
   #define CR1146
*/

#ifdef CR1146
static int isValidStartDropTimerId = 0;
#endif
/********    Static Function Declarations    ********/

static void ClassPartInit( 
                        WidgetClass wc) ;
static void Initialize( 
                        Widget rw,
                        Widget nw,
                        ArgList args,
                        Cardinal *num_args) ;
static Boolean SetValues( 
                        Widget cw,
                        Widget rw,
                        Widget nw,
                        ArgList args,
                        Cardinal *num_args) ;
static void Destroy( 
                        Widget w) ;
static void SourceNotifiedCB( 
                        Widget w,
                        XtPointer client_data,
                        Atom *selection,
                        Atom *type,
                        XtPointer value,
                        unsigned long *length,
                        int *format) ;
static void TerminateTransfer( 
                        XmDropTransferObject dt,
                        Atom *selection) ;
static void ProcessTransferEntry( 
                        XmDropTransferObject dt,
                        Cardinal which) ;
static void DropTransferSelectionCB( 
                        Widget w,
                        XtPointer client_data,
                        Atom *selection,
                        Atom *type,
                        XtPointer value,
                        unsigned long *length,
                        int *format) ;
static Widget StartDropTransfer( 
                        Widget refWidget,
                        ArgList args,
                        Cardinal argCount) ;
static void AddDropTransfer( 
                        Widget widget,
                        XmDropTransferEntry transfers,
                        Cardinal num_transfers) ;
static void DragContextDestroyCB(
			Widget widget,
			XtPointer client,
			XtPointer call) ;

/********    End Static Function Declarations    ********/


static XtResource resources[] = {
	{   XmNdropTransfers, XmCDropTransfers,
		XmRDropTransfers, sizeof(XmDropTransferEntry),
		XtOffsetOf( struct _XmDropTransferRec,
			dropTransfer.drop_transfers),
		XmRImmediate, (XtPointer) NULL
	},
	{   XmNnumDropTransfers, XmCNumDropTransfers,
		XmRCardinal, sizeof(Cardinal),
		XtOffsetOf( struct _XmDropTransferRec,
			dropTransfer.num_drop_transfers),
		XmRImmediate, (XtPointer) 0
	},
	{   XmNincremental, XmCIncremental, XmRBoolean, sizeof(Boolean),
		XtOffsetOf( struct _XmDropTransferRec, dropTransfer.incremental),
		XmRImmediate, (XtPointer) FALSE
	},
	{   XmNtransferProc, XmCTransferProc,
		XmRCallbackProc, sizeof(XtSelectionCallbackProc),
		XtOffsetOf( struct _XmDropTransferRec, dropTransfer.transfer_callback),
		XmRImmediate, (XtPointer) 0
	},
	{   XmNtransferStatus, XmCTransferStatus, XmRTransferStatus,
		sizeof(unsigned char),
		XtOffsetOf( struct _XmDropTransferRec, dropTransfer.transfer_status),
		XmRImmediate, (XtPointer) XmTRANSFER_SUCCESS
	},
};


/*  class record definition  */

externaldef(xmgadgetclassrec) XmDropTransferClassRec
	xmDropTransferClassRec = {
   {
      (WidgetClass) &objectClassRec,    /* superclass	         */	
      "XmDropTransfer",                 /* class_name	         */	
      sizeof(XmDropTransferRec),        /* widget_size	         */	
      NULL,                             /* class_initialize      */
      ClassPartInit,                    /* class part initialize */
      False,                  		/* class_inited          */	
      Initialize,                      	/* initialize	         */	
      NULL,                             /* initialize_hook       */
      NULL,	                       	/* obj1  	         */	
      NULL,				/* obj2                  */	
      0,				/* obj3	                 */	
      resources,                        /* resources	         */	
      XtNumber(resources),              /* num_resources         */	
      NULLQUARK,                        /* xrm_class	         */	
      True,                             /* obj4                  */
      XtExposeCompressSeries,           /* obj5                  */	
      True,                             /* obj6                  */
      False,                            /* obj7                  */
      Destroy,                          /* destroy               */	
      NULL,                             /* obj8                  */	
      NULL,				/* obj9                  */	
      SetValues,                        /* set_values	         */	
      NULL,                             /* set_values_hook       */
      NULL,         			/* obj10                 */
      NULL,                             /* get_values_hook       */
      NULL,                             /* obj11    	         */	
      XtVersion,                        /* version               */
      NULL,                             /* callback private      */
      NULL,                             /* obj12                 */
      NULL,                             /* obj13                 */
      NULL,				/* obj14                 */
      NULL,                             /* extension             */
   },
   {
      StartDropTransfer,           	/* start_drop_transfer   */
      AddDropTransfer,             	/* add_drop_transfer     */
      NULL,                        	/* extension             */
   },
};

externaldef(xmdroptransferobjectclass) WidgetClass
	xmDropTransferObjectClass = (WidgetClass)
		&xmDropTransferClassRec;

/*ARGSUSED*/
static void 
ClassPartInit(
        WidgetClass wc )
{
}

/*ARGSUSED*/
static void 
Initialize(
        Widget rw,
        Widget nw,
        ArgList args,
        Cardinal *num_args )
{
	XmDropTransferObject new_w = (XmDropTransferObject) nw;
	XmDropTransferPart *dtp =
		(XmDropTransferPart *) &(new_w->dropTransfer);

	if (dtp->num_drop_transfers != 0)
	{
		dtp->num_drop_transfer_lists = 1;
		dtp->drop_transfer_lists = (XmDropTransferList)
			XtMalloc(sizeof(XmDropTransferListRec) *
				dtp->num_drop_transfer_lists);
		dtp->drop_transfer_lists[0].transfer_list =
		        (XmDropTransferEntry)_XmAllocAndCopy(
			dtp->drop_transfers, sizeof(XmDropTransferEntryRec)
				* dtp->num_drop_transfers);
		dtp->drop_transfer_lists[0].num_transfers =
			dtp->num_drop_transfers;
		
		/* strictly for hygene... */

		dtp->drop_transfers = dtp->drop_transfer_lists[0].transfer_list;
	}
	else
	{
		dtp->drop_transfer_lists = NULL;
		dtp->num_drop_transfer_lists = 0;
	}

	dtp->motif_drop_atom = XInternAtom(
		XtDisplayOfObject(nw), XmS_MOTIF_DROP, False);

	dtp->cur_drop_transfer_list = (Cardinal) -1;
	dtp->cur_xfer = (Cardinal) -1;
	dtp->cur_targets = (Atom *) NULL;
	dtp->cur_client_data = (XtPointer *) NULL;
}

/*ARGSUSED*/
static Boolean 
SetValues(
        Widget cw,
        Widget rw,
        Widget nw,
        ArgList args,
        Cardinal *num_args )
{
	/* Stub */
	/* !!! Should disallow any changes !!! */
	return True;
}

static void 
Destroy(
        Widget w )
{
    XmDropTransferObject new_w = (XmDropTransferObject) w;
    Cardinal 		 i;
    XmDragContext	 dc;

    /*
     * clean up the hanging dragContext 
     */
    dc = (XmDragContext)XmGetDragContext((Widget)new_w, 
					 new_w->dropTransfer.timestamp);
    if (dc && dc->drag.sourceIsExternal)
      XtDestroyWidget((Widget)dc);

    for (i = 0; i < new_w->dropTransfer.num_drop_transfer_lists; i++)
      {
	  XmDropTransferList	currEntry =
	    &(new_w->dropTransfer.drop_transfer_lists[i]);
	  
	  XtFree((char *)currEntry->transfer_list);
      }
    XtFree((char *)new_w->dropTransfer.drop_transfer_lists);
}

/*ARGSUSED*/
static void 
SourceNotifiedCB(
        Widget w,
        XtPointer client_data,
        Atom *selection,
        Atom *type,
        XtPointer value,
        unsigned long *length,
        int *format )
{
    XmDropTransferObject	dt = (XmDropTransferObject)client_data;

    if (value != NULL)
      XtFree((char *) value);
    /* self-immolution aaaaaaiii */
    XtDestroyWidget((Widget)dt);
}

static void 
TerminateTransfer(
        XmDropTransferObject dt,
        Atom *selection )
{
	Atom status;
	XmDropTransferPart *dtp =
		(XmDropTransferPart *) &(dt->dropTransfer);
	XmDragContext	dc = (XmDragContext) dtp->dragContext;

	if (dtp->transfer_status == XmTRANSFER_SUCCESS)
		status = XInternAtom(XtDisplayOfObject((Widget)dt),
			XmSTRANSFER_SUCCESS, False);
	else /* XmTRANSFER_FAILURE */
		status = XInternAtom(XtDisplayOfObject((Widget)dt),
			XmSTRANSFER_FAILURE, False);
	
	/*
	 * we need to pass in the shell since that is the only widget
	 * visible to the initiator.
	 */

	XtGetSelectionValue(dc->drag.currReceiverInfo->shell,
				*selection, status,
				SourceNotifiedCB, (XtPointer)dt,
				dtp->timestamp);
}

static void 
ProcessTransferEntry(
        XmDropTransferObject dt,
        Cardinal which )
{
	XmDropTransferPart *dtp =
		(XmDropTransferPart *) &(dt->dropTransfer);
	XmDropTransferList	tl = &(dtp->drop_transfer_lists[which]);
	XmDragContext dc = (XmDragContext)dtp->dragContext;
	Cardinal i;
	Arg args[1];
	Atom real_selection_atom;

	dtp->cur_drop_transfer_list = which;
	dtp->cur_targets = (Atom *)
		XtMalloc((tl->num_transfers * sizeof(Atom)));
	dtp->cur_client_data = (XtPointer *)
		XtMalloc((tl->num_transfers * sizeof(XtPointer)));

	i = 0;
	XtSetArg(args[i], XmNiccHandle, &real_selection_atom); i++;
	XtGetValues(dtp->dragContext, args, i);

	for (i=0; i < tl->num_transfers; i++)
	{
		dtp->cur_targets[i] = tl->transfer_list[i].target;
		/* 
		 * all of the client data have to point to us so that we can
		 * bootstrap 
		 */
		dtp->cur_client_data[i] = (XtPointer)dt;
	}

	dtp->cur_xfer = 0;

	/*
	 * we need to pass in the destShell since that is the only widget
	 * visible to the initiator.
	 */

	/*
	 * As both an optimization and workaround for an Xt bug,
	 * if the number of transfers is just one then call the
	 * singular version of XtGetSelectionValue{,Incremental}.
	 * If there is only one item there is no need to call
	 * the plural version; XtGetSelectionValues
	 * Also there is a bug in some Xt's which fail to handle
	 * both MULTIPLE and INCR together correctly and can hang
	 * the drop transfer forever.  By adding this special case
	 * we allow knowledgeable clients to request any target that
	 * might be large enough to trigger an INCR as a single item
	 * that would avoid the MULTIPLE case.
	 */

	if (dtp->incremental)
	{
           if (tl->num_transfers == 1) {
               XtGetSelectionValueIncremental(
                       dc->drag.currReceiverInfo->shell,
                       real_selection_atom, dtp->cur_targets[0],
                       DropTransferSelectionCB,
                       (XtPointer)dtp->cur_client_data[0],
                       dtp->timestamp);
           } else {

		XtGetSelectionValuesIncremental(
			dc->drag.currReceiverInfo->shell,
			real_selection_atom, dtp->cur_targets,
			tl->num_transfers, DropTransferSelectionCB,
			(XtPointer *)dtp->cur_client_data, 
			dtp->timestamp);
            }
	}
	else
	{
           if (tl->num_transfers == 1) {
               XtGetSelectionValue(dc->drag.currReceiverInfo->shell,
                       real_selection_atom, dtp->cur_targets[0],
                       DropTransferSelectionCB,
                       (XtPointer)dtp->cur_client_data[0],
                       dtp->timestamp);
           } else {
		XtGetSelectionValues(dc->drag.currReceiverInfo->shell,
			real_selection_atom, dtp->cur_targets,
			tl->num_transfers, DropTransferSelectionCB,
			(XtPointer *)dtp->cur_client_data, 
			dtp->timestamp);
           }
	}
}


/* 
 * This routine is called with the shell as the widget and us as the
 * client data. We can't pass ourselves since we're not windowed
 */
/*ARGSUSED*/
static void 
DropTransferSelectionCB(
        Widget w,
        XtPointer client_data,
        Atom *selection,
        Atom *type,
        XtPointer value,
        unsigned long *length,
        int *format )
{
	XmDropTransferObject dt = (XmDropTransferObject) client_data;
	XmDropTransferPart *dtp =
		(XmDropTransferPart *) &(dt->dropTransfer);
	XmDropTransferList	tl =
		&(dtp->drop_transfer_lists[dtp->cur_drop_transfer_list]);


	(*(dtp->transfer_callback)) 
		((Widget)dt, tl->transfer_list[dtp->cur_xfer].client_data,
			selection, type, value, length, format);

       /* The transfer list needs to be reassigned at this point in case
	* an XmDropTransferAdd() was called in the callback.
        */
	tl = &(dtp->drop_transfer_lists[dtp->cur_drop_transfer_list]);

	if ( !(dtp->incremental)
		||
		((dtp->incremental) && (value != NULL) && (*length == 0)))
	{
		dtp->cur_xfer++;

		if (dtp->cur_xfer == tl->num_transfers)
		{
			XtFree((char *)dtp->cur_targets);
			XtFree((char *)dtp->cur_client_data);

			if (++(dtp->cur_drop_transfer_list) <
				dtp->num_drop_transfer_lists)
			{
			/* Get the next transfer entry in the list and process it */
				ProcessTransferEntry(dt, dtp->cur_drop_transfer_list);
			}
			else /* notify the source that we're done */
				TerminateTransfer(dt, selection);
		}
	}
}


/*ARGSUSED*/
static void 
StartDropTimer(
        XtPointer clientData,
        XtIntervalId *id )
{
	XmDropTransferObject dt = (XmDropTransferObject)clientData;
	XmDropTransferPart *dtp;
	Arg my_args[1];
	int i;
	Atom selection;

#ifdef CR1146
    isValidStartDropTimerId = 0;
#endif

	
	dtp = (XmDropTransferPart *) &(dt->dropTransfer);

	if (dtp->num_drop_transfer_lists)
	{
		/* Get the first transfer entry in the list and process it */
		ProcessTransferEntry(dt, 0);
	}
	else /* notify the source that we've changed our mind */
	{
		i = 0;
		XtSetArg(my_args[i], XmNiccHandle, &selection); i++;
		XtGetValues(dtp->dragContext, my_args, i);

		TerminateTransfer(dt, &selection);
	}
}

static void
DragContextDestroyCB(
        Widget widget,
        XtPointer client,
        XtPointer call)
{
        XtIntervalId timer = (XtIntervalId) client;
#ifdef CR1146
        if (!isValidStartDropTimerId) return;
#endif
        XtRemoveTimeOut(timer);
}

static Widget 
StartDropTransfer(
        Widget refWidget,
        ArgList args,
        Cardinal argCount )
{
	static int which = 0;
	XmDropTransferObject dt;
	char buf[30];
	XtIntervalId timer;

	_XmProcessLock();
	sprintf(buf, "Transfer%d", which++);
	_XmProcessUnlock();
	dt = (XmDropTransferObject) XtCreateWidget(buf,
		xmDropTransferObjectClass,
		(Widget) XmGetXmDisplay(XtDisplayOfObject(refWidget)),
		args, argCount);

	dt->dropTransfer.dragContext = refWidget;
	dt->dropTransfer.timestamp =
	  ((XmDragContext)refWidget)->drag.dragFinishTime;

	/*
	 * The processing of the dropTransfer should happen after the
	 * dropStart message is echoed to the initiator. Since we're
	 * being called out of the dropProc of the receiver we can't
	 * just proceed or else the entire transfer could happen
	 * inline. We therefore add ourselves as a zero length timer
	 * which will allow us to get called after the dropProc has
	 * returned. 
 	 * To prevent against this code's being called twice by disparate
 	 * parts of the drag/drop code (especially on failure) self-guard by
 	 * watching for destruction of the widget and avoid the rest of the
 	 * operations.
 	 */

	timer = XtAppAddTimeOut(XtWidgetToApplicationContext( (Widget)dt),
				0, StartDropTimer, (XtPointer)dt);
#ifdef CR1146
    if (isValidStartDropTimerId) {
        printf("Motif Error in file %s at line %d: A DropTimer is already active,\n", __FILE__, __LINE__);
    }
    isValidStartDropTimerId = 1;
#endif
	XtAddCallback(refWidget, XmNdestroyCallback, DragContextDestroyCB, 
		      (XtPointer)timer);

	return((Widget) dt);
}



Widget 
XmDropTransferStart(
        Widget refWidget,
        ArgList args,
        Cardinal argCount )
{
	Widget ddo = (Widget) XmGetXmDisplay(XtDisplayOfObject(refWidget));
	XmDropTransferObjectClass wc;
	Arg lclArgs[1];
	int n;
	Widget ret_val;
	_XmWidgetToAppContext(refWidget);

	_XmAppLock(app);
	n = 0;
	XtSetArg(lclArgs[n], XmNdropTransferClass, &wc); n++;
	XtGetValues(ddo, lclArgs, n);

	ret_val = (*(wc->dropTransfer_class.start_drop_transfer))
		(refWidget, args, argCount);
	_XmAppUnlock(app);
	return ret_val;
}



static void 
AddDropTransfer(
        Widget widget,
        XmDropTransferEntry transfers,
        Cardinal num_transfers )
{
        XmDropTransferObject dto = (XmDropTransferObject) widget;
	XmDropTransferPart *dtp =
		(XmDropTransferPart *) &(dto->dropTransfer);
	Cardinal index = dtp->num_drop_transfer_lists++;

	dtp->drop_transfer_lists = (XmDropTransferList)
		XtRealloc((char *)dtp->drop_transfer_lists,
			sizeof(XmDropTransferListRec) *
			dtp->num_drop_transfer_lists);
	dtp->drop_transfer_lists[index].transfer_list =
	        (XmDropTransferEntry)_XmAllocAndCopy(
		transfers, sizeof(XmDropTransferEntryRec) * num_transfers);
	dtp->drop_transfer_lists[index].num_transfers = num_transfers;
}


void 
XmDropTransferAdd(
        Widget widget,
        XmDropTransferEntry transfers,
        Cardinal num_transfers )
{
	XmDropTransferObject dt = (XmDropTransferObject) widget;
	XmDropTransferObjectClass wc;
	_XmWidgetToAppContext(widget);

	_XmAppLock(app);
	wc = (XmDropTransferObjectClass) XtClass(dt);
	((*(wc->dropTransfer_class.add_drop_transfer))
		(widget, transfers, num_transfers));
	_XmAppUnlock(app);
}


