/**
 *
 * $Id: DragC.c,v 1.11 1997/04/03 05:01:16 miers Exp $
 *
 * Copyright (C) 1995 Free Software Foundation, Inc.
 *
 * This file is part of the GNU LessTif Library.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the Free
 * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 **/

/* This code owes a lot to Daniel Dardailler, who wrote up an
   initial implementation of the dynamic drag protocol for an X/Open
   contract while still with the X consortium.
   
   Here's his copyright notice:

   Copyright 1996 Daniel Dardailler.  
   Permission to use, copy, modify, distribute, and sell this software
   for any purpose is hereby granted without fee, provided that the above
   copyright notice appear in all copies and that both that copyright
   notice and this permission notice appear in supporting documentation,
   and that the name of Daniel Dardailler not be used in advertising or
   publicity pertaining to distribution of the software without specific,
   written prior permission.  Daniel Dardailler makes no representations
   about the suitability of this software for any purpose.  It is
   provided "as is" without express or implied warranty.
   */

static char rcsid[] = "$Id: DragC.c,v 1.11 1997/04/03 05:01:16 miers Exp $";

#include <LTconfig.h>
#include <XmI/XmI.h>

#include <Xm/XmP.h>
#include <Xm/DragCP.h>
#include <Xm/DropSMgrP.h>
#include <XmI/DragDropI.h>
#include <XmI/AtomMgrI.h>
#include <Xm/DisplayP.h>
#include <Xm/AtomMgr.h>
#include <Xm/MenuUtilP.h> /* for the _XmGrab stuff */
#include <X11/Xfuncs.h>

#include <XmI/DebugUtil.h>

/* Forward Declarations */

static void class_initialize();
static void class_part_initialize(WidgetClass w_class);
static void initialize(Widget request, Widget new_w, ArgList args, Cardinal *num_args);
static void destroy(Widget w);

static Boolean set_values(Widget current, Widget request, Widget new_w, ArgList args, Cardinal *num_args);

static void _XmDCStartDrag(XmDragContext dc,
			   Widget srcW,
			   XEvent *event);
static void _XmDCCancelDrag(XmDragContext dc);

unsigned char _XmByteOrder(void);
static void internal_notify(Widget w, XtPointer cd, XtPointer cbs);
static void external_notify(Widget w, XtPointer cd, XtPointer cbs);
Boolean _XmGetDragReceiverInfo(Display *display, Window win, XmDragReceiverInfo ri);
void _XmFreeDragReceiverInfo(XmShellDropSiteInfo di);

#define Offset(field) XtOffsetOf(XmDragContextRec, drag.field)

/* Resources for the dragContext class */

static XtResource resources[] = {
    {
	XmNsourceWidget, XmCSourceWidget, XmRWidget,
	sizeof(Widget), Offset(sourceWidget),
	XmRImmediate, (XtPointer)NULL
    },
    {
	XmNexportTargets, XmCExportTargets, XmRAtomList,
	sizeof(Atom *), Offset(exportTargets),
	XmRImmediate, NULL
    },
    {
	XmNnumExportTargets, XmCNumExportTargets, XmRInt,
	sizeof(Cardinal), Offset(numExportTargets),
	XmRImmediate, (XtPointer)0
    },
    {
	XmNconvertProc, XmCConvertProc, XmRFunction,
	sizeof(XmConvertSelectionRec), Offset(convertProc),
	XmRImmediate, (XtPointer)NULL
    },
    {
	XmNclientData, XmCClientData, XtRPointer,
	sizeof(XtPointer), Offset(clientData),
	XmRImmediate, (XtPointer)NULL
    },
    {
	XmNincremental, XmCIncremental, XmRBoolean,
	sizeof(Boolean), Offset(incremental),
	XmRImmediate, (XtPointer)False
    },
    {
	XmNdragOperations, XmCDragOperations, XmRUnsignedChar,
	sizeof(unsigned char), Offset(dragOperations),
	XmRImmediate, (XtPointer)(XmDROP_COPY | XmDROP_MOVE)
    },
    {
	XmNsourceCursorIcon, XmCSourceCursorIcon, XmRWidget,
	sizeof(Widget), Offset(sourceCursorIcon),
	XmRImmediate, (XtPointer)NULL
    },
    {
	XmNsourcePixmapIcon, XmCSourcePixmapIcon, XmRWidget,
	sizeof(Widget), Offset(sourcePixmapIcon),
	XmRImmediate, (XtPointer)NULL
    },
    {
	XmNstateCursorIcon, XmCStateCursorIcon, XmRWidget,
	sizeof(Widget), Offset(stateCursorIcon),
	XmRImmediate, (XtPointer)NULL
    },
    {
	XmNoperationCursorIcon, XmCOperationCursorIcon, XmRWidget,
	sizeof(Widget), Offset(operationCursorIcon),
	XmRImmediate, (XtPointer)NULL
    },
    {
	XmNcursorBackground, XmCCursorBackground, XmRPixel,
	sizeof(Pixel), Offset(cursorBackground),
	XmRCallProc, (XtPointer)_XmBackgroundColorDefault
    },
    {
	XmNcursorForeground, XmCCursorForeground, XmRPixel,
	sizeof(Pixel), Offset(cursorForeground),
	XmRCallProc, (XtPointer)_XmForegroundColorDefault
    },
    {
	XmNvalidCursorForeground, XmCValidCursorForeground, XmRPixel,
	sizeof(Pixel), Offset(validCursorForeground),
	XtRCallProc, (XtPointer)_XmForegroundColorDefault
    },
    {
	XmNinvalidCursorForeground, XmCInvalidCursorForeground, XmRPixel,
	sizeof(Pixel), Offset(invalidCursorForeground),
	XmRCallProc, (XtPointer)_XmForegroundColorDefault
    },
    {
	XmNnoneCursorForeground, XmCNoneCursorForeground, XmRPixel,
	sizeof(Pixel), Offset(noneCursorForeground),
	XmRCallProc, (XtPointer)_XmForegroundColorDefault
    },
    {
	XmNdropSiteEnterCallback, XmCCallback, XmRCallback,
	sizeof(XtCallbackList), Offset(siteEnterCallback),
	XmRImmediate, (XtPointer)NULL
    },
    {
	XmNdropSiteLeaveCallback, XmCCallback, XmRCallback,
	sizeof(XtCallbackList), Offset(siteLeaveCallback),
	XmRImmediate, (XtPointer)NULL
    },
    {
	XmNtopLevelEnterCallback, XmCCallback, XmRCallback,
	sizeof(XtCallbackList), Offset(topLevelEnterCallback),
	XmRImmediate, (XtPointer)NULL
    },
    {
	XmNdragMotionCallback, XmCCallback, XmRCallback,
	sizeof(XtCallbackList), Offset(dragMotionCallback),
	XmRImmediate, (XtPointer)NULL
    },
    {
	XmNtopLevelLeaveCallback, XmCCallback, XmRCallback,
	sizeof(XtCallbackList), Offset(topLevelLeaveCallback),
	XmRImmediate, (XtPointer)NULL
    },
    {
	XmNdropStartCallback, XmCCallback, XmRCallback,
	sizeof(XtCallbackList), Offset(dropStartCallback),
	XmRImmediate, (XtPointer)NULL
    },
    {
	XmNdragDropFinishCallback, XmCCallback, XmRCallback,
	sizeof(XtCallbackList), Offset(dragDropFinishCallback),
	XmRImmediate, (XtPointer)NULL
    },
    {
	XmNdropFinishCallback, XmCCallback, XmRCallback,
	sizeof(XtCallbackList), Offset(dropFinishCallback),
	XmRImmediate, (XtPointer)NULL
    },
    {
	XmNoperationChangedCallback, XmCCallback, XmRCallback,
	sizeof(XtCallbackList), Offset(operationChangedCallback),
	XmRImmediate, (XtPointer)NULL
    },
    {
	XmNblendModel, XmCBlendModel, XmRBlendModel,
	sizeof(unsigned char), Offset(blendModel),
	XtRImmediate, (XtPointer)XmBLEND_ALL
    },
    {
	XmNsourceIsExternal, XmCSourceIsExternal, XmRBoolean,
	sizeof(Boolean), Offset(sourceIsExternal),
	XtRImmediate, (XtPointer)False
    },
    {
	XmNsourceWindow, XmCSourceWindow, XmRWindow,
	sizeof(Window), Offset(srcWindow),
	XtRImmediate, (XtPointer)0
    },
    {
	XmNstartTime, XmCStartTime, XmRInt,
	sizeof(Time), Offset(dragStartTime),
	XtRImmediate, (XtPointer)0
    },
    {
	XmNiccHandle, XmCICCHandle, XmRAtom,
	sizeof(Atom), Offset(iccHandle),
	XtRImmediate, (XtPointer)0
    }
};

static void _XmDCCancel(Widget w, XEvent *event, String *params, Cardinal *num_params);
static void _XmDCMotion(Widget w, XEvent *event, String *params, Cardinal *num_params);
static void _XmDCFinish(Widget w, XEvent *event, String *params, Cardinal *num_params);
static void _XmDCHelp(Widget w, XEvent *event, String *params, Cardinal *num_params);
static void _XmDCIgnore(Widget w, XEvent *event, String *params, Cardinal *num_params);

char _XmDCTranslations[] =
   "<Btn2Up>:             FinishDrag()\n\
    <BtnUp>:              IgnoreButtons()\n\
    <BtnDown>:            IgnoreButtons()\n\
    <Key>osfCancel:       CancelDrag()\n\
    <Key>osfHelp:         HelpDrag()\n\
    Button2<Enter>:       DragMotion()\n\
    Button2<Leave>:       DragMotion()\n\
    Button2<Motion>:      DragMotion()";

static XtActionsRec actions[] = {
    {"CancelDrag",    _XmDCCancel},
    {"DragMotion",    _XmDCMotion},
    {"FinishDrag",    _XmDCFinish},
    {"HelpDrag",      _XmDCHelp},
    {"IgnoreButtons", _XmDCIgnore}
};

static XmBaseClassExtRec _XmDragCCoreClassExtRec = {
    /* next_extension            */ NULL,
    /* record_type               */ NULLQUARK,                             
    /* version                   */ XmBaseClassExtVersion,
    /* size                      */ sizeof(XmBaseClassExtRec),
    /* initialize_prehook        */ NULL, /* FIXME */
    /* set_values_prehook        */ NULL, /* FIXME */
    /* initialize_posthook       */ NULL, /* FIXME */
    /* set_values_posthook       */ NULL, /* FIXME */
    /* secondary_object_class    */ NULL, /* FIXME */
    /* secondary_object_create   */ NULL, /* FIXME */
    /* get_secondary_resources   */ NULL, /* FIXME */
    /* fast_subclass             */ { 0 }, /* FIXME */
    /* get_values_prehook        */ NULL, /* FIXME */
    /* get_values_posthook       */ NULL, /* FIXME */
    /* class_part_init_prehook   */ NULL, /* FIXME */
    /* class_part_init_posthook  */ NULL, /* FIXME */
    /* ext_resources             */ NULL, /* FIXME */
    /* compiled_ext_resources    */ NULL, /* FIXME */
    /* num_ext_resources         */ 0, /* FIXME */
    /* use_sub_resources         */ False, /* FIXME */
    /* widget_navigable          */ NULL,
    /* focus_change              */ NULL,
    /* wrapper_data              */ NULL
};

XmDragContextClassRec xmDragContextClassRec = {
    /* Core class part */
    {
	/* superclass            */ (WidgetClass) &coreClassRec,
        /* class_name            */ "XmDragContext",
	/* widget_size           */ sizeof(XmDragContextRec),
	/* class_initialize      */ class_initialize,
	/* class_part_initialize */ class_part_initialize,
	/* class_inited          */ False,
	/* initialize            */ initialize,
	/* initialize_hook       */ NULL,
	/* realize               */ XtInheritRealize,
	/* actions               */ actions,
	/* num_actions           */ XtNumber(actions),
	/* resources             */ resources,
	/* num_resources         */ XtNumber(resources),
	/* xrm_class             */ NULLQUARK,
	/* compress_motion       */ True,
	/* compress_exposure     */ XtExposeCompressMaximal,
	/* compress_enterleave   */ True,
	/* visible_interest      */ False,
	/* destroy               */ destroy,
	/* resize                */ NULL,
	/* expose                */ NULL,
	/* set_values            */ set_values,
	/* set_values_hook       */ NULL,
	/* set_values_almost     */ XtInheritSetValuesAlmost,
	/* get_values_hook       */ NULL,
	/* accept_focus          */ NULL,
	/* version               */ XtVersion,
	/* callback offsets      */ NULL,
	/* tm_table              */ _XmDCTranslations,
	/* query_geometry        */ NULL,
	/* display_accelerator   */ XtInheritDisplayAccelerator,
	/* extension             */ (XtPointer)&_XmDragCCoreClassExtRec
    },
    /* DragContext Class part */
    {
        /* start     */ _XmDCStartDrag,
        /* cancel    */ _XmDCCancelDrag,
        /* extension */ NULL,
    }
};

WidgetClass xmDragContextClass = (WidgetClass)&xmDragContextClassRec;

static void
class_initialize()
{
    _XmDragCCoreClassExtRec.record_type = XmQmotif;
}

static void
class_part_initialize(WidgetClass widget_class)
{
    WidgetClass sc;

    _XmFastSubclassInit(widget_class, XmDRAG_CONTEXT_BIT);

    sc = widget_class->core_class.superclass;

    if (DCC_DragStartProc(widget_class) == XmInheritDragStartProc)
	DCC_DragStartProc(widget_class) = DCC_DragStartProc(sc);

    if (DCC_DragCancelProc(widget_class) == XmInheritDragCancelProc)
	DCC_DragCancelProc(widget_class) = DCC_DragCancelProc(sc);
}

static void
initialize(Widget request,
	   Widget new_w,
	   ArgList args,
	   Cardinal *num_args)
{
    Atom *exports;
    XmDropSiteManagerObject dsm;
    Arg argl[2];

    XdbDebug(__FILE__, new_w, "Initialize\n");

    DC_InDropSite(new_w) = False;
    DC_DragTimerId(new_w) = None;

    DC_RoundOffTime(new_w) = 32;
    DC_DragFinishTime(new_w) = CurrentTime;	/* read 0 */
    DC_DropFinishTime(new_w) = CurrentTime;

    /* copy from resource */
    DC_ActiveBlendModel(new_w) = DC_BlendModel(new_w);

    DC_StartX(new_w) = DC_StartY(new_w) = 0;

    /* no cursor yet */
    DC_CurDragOver(new_w) = NULL;
    DC_OrigDragOver(new_w) = NULL;

    /* setup the notify cb */
    dsm = _XmGetDropSiteManagerObject(_XmDCtoDD(new_w));
    XtSetArg(argl[0], XmNclientData, (XtPointer)new_w);
    if (DC_SourceIsExternal(new_w))
	XtSetArg(argl[1], XmNnotifyProc, external_notify);
    else
	XtSetArg(argl[1], XmNnotifyProc, internal_notify);
    XtSetValues((Widget)dsm, args, 2);

    /* copy the export targets */
    if (DC_ExportTargets(new_w)) {
	exports = (Atom *)XtMalloc(DC_NumExportTargets(new_w) * sizeof(Atom));

	bcopy(DC_ExportTargets(new_w), exports,
	      DC_NumExportTargets(new_w) * sizeof(Atom));

	DC_ExportTargets(new_w) = exports;
    }

    XtWidth(new_w) = XtHeight(new_w) = 32;
    XtX(new_w) = XtY(new_w) = 0;

    /* Setup the receiver info to known state */
    DC_CurrReceiverInfo(new_w) = NULL;
    DC_ReceiverInfos(new_w) = NULL;
    DC_NumReceiverInfos(new_w) = 0;
    DC_MaxReceiverInfos(new_w) = 0;

    XtRealizeWidget(new_w);

    XdbDebug(__FILE__, new_w, "Initialize: Parent %s window 0x%X us 0x%X\n", 
	XtName(XtParent(new_w)), XtWindow(XtParent(new_w)), XtWindow(new_w));
}

static void
destroy(Widget w)
{
    XdbDebug(__FILE__, w, "Destroy\n");

    if (DC_ExportTargets(w))
	XtFree((char *)DC_ExportTargets(w));

    if (DC_ReceiverInfos(w) != NULL)
	XtFree((char *)DC_ReceiverInfos(w));

    /* if you want to find really weird SIGSEGV's, leave this out */
    if (DC_DragTimerId(w)) {
	XtRemoveTimeOut(DC_DragTimerId(w));
	DC_DragTimerId(w) = None;
    }
}

static Boolean
set_values(Widget old,
	   Widget request,
	   Widget new_w,
	   ArgList args,
	   Cardinal *num_args)
{
    Atom *exports;

    XdbDebug(__FILE__, new_w, "SetValues\n");

    if (DC_ExportTargets(old) != DC_ExportTargets(new_w)) {

	if (DC_ExportTargets(old))
	    XtFree((char *)DC_ExportTargets(old));

	exports = (Atom *)XtMalloc(DC_NumExportTargets(new_w) * sizeof(Atom));

	bcopy(DC_ExportTargets(new_w), exports,
	      DC_NumExportTargets(new_w) * sizeof(Atom));

	DC_ExportTargets(new_w) = exports;
    }

    return False;
}

static void
send_drag_message(Widget dc, Window win, XmDragDropCallbackStruct *cbs,
		  int type)
{
    XClientMessageEvent cmsg;
    XmDndMessage *dnd_message = (XmDndMessage *)&cmsg.data.b[0];
    Atom msg;

    msg = XmInternAtom(XtDisplay(dc), _XA_MOTIF_DRAG_AND_DROP_MESSAGE, False);

    cmsg.type = ClientMessage;
    cmsg.serial = LastKnownRequestProcessed(XtDisplay(dc));
    cmsg.send_event = True;
    cmsg.display = XtDisplay(dc);
    cmsg.window = win;
    cmsg.format = 8;
    cmsg.message_type = msg;

    dnd_message->reason = cbs->any.reason;

    dnd_message->byte_order = _XmByteOrder();

    /* we're filling in flags with more stuff that necessary,
       depending on the reason, but it doesn't matter */
    /* MLM: what he's not telling you is that normally the data that gets
     * sent is identical in content to the callback structs... */
    dnd_message->flags = 0;
    dnd_message->flags |= DND_SET_STATUS(0);
    dnd_message->flags |= DND_SET_OPERATION(DC_Operation(dc));
    dnd_message->flags |= DND_SET_OPERATIONS(DC_Operations(dc));
    dnd_message->flags |= DND_SET_COMPLETION(DC_DragCompletionStatus(dc));
    dnd_message->flags |= DND_SET_EVENT_TYPE(type);

    dnd_message->time = DC_LastChangeTime(dc);

    switch(cbs->any.reason) {
    case XmDROP_SITE_LEAVE:
	break;

    case XmTOP_LEVEL_ENTER:
    case XmTOP_LEVEL_LEAVE:
        dnd_message->data.top.src_window = DC_SrcWindow(dc);
        dnd_message->data.top.property = DC_ICCHandle(dc);
        break; /* cannot fall thru since the byte layout is different in
                  both set of messages, see top and pot union stuff */

    case XmDRAG_MOTION:
    case XmOPERATION_CHANGED:
    case XmDROP_SITE_ENTER:
    case XmDROP_START:
        dnd_message->data.pot.x = XtX(dc); /* mouse position */
        dnd_message->data.pot.y = XtY(dc);
        dnd_message->data.pot.src_window = DC_SrcWindow(dc);
        dnd_message->data.pot.property = DC_ICCHandle(dc);
        break;

    default:
        break;
    }

    /* need to send the event */
    XSendEvent(XtDisplay(dc), win, False, NoEventMask, (XEvent *)&cmsg);
}

static Boolean
receive_drag_message(Widget dc, XEvent *event, XmDragDropCallbackStruct *cbs)
{
    XClientMessageEvent *cm = (XClientMessageEvent *)event;
    XmDndMessage *dnd_message = (XmDndMessage *)&cm->data.b[0];
    Atom msg;

    msg = XmInternAtom(XtDisplay(dc), _XA_MOTIF_DRAG_AND_DROP_MESSAGE, False);

    if (cm->message_type != msg) {
	_XmWarning(dc, "Invalid Drag/Drop message.\n");
	return False;
    }

    if (dnd_message->byte_order != _XmByteOrder()) {
	SWAP2BYTES(dnd_message->flags);
	SWAP4BYTES(dnd_message->time);
    }

    cbs->any.reason = dnd_message->reason;

    cbs->any.reason &= DND_CLEAR_EVENT_TYPE;

    cbs->any.timeStamp = dnd_message->time;

    /* FIXME: we should check the INITIATOR/RECEIVER flag in here */
    switch(cbs->any.reason) {
    case XmTOP_LEVEL_ENTER:
	if (dnd_message->byte_order != _XmByteOrder()) {
	    SWAP4BYTES(dnd_message->data.top.src_window);
	    SWAP4BYTES(dnd_message->data.top.property);
	}
	cbs->tle.window = dnd_message->data.top.src_window;
	cbs->tle.iccHandle = dnd_message->data.top.property;
	break;

    case XmTOP_LEVEL_LEAVE:
	if (dnd_message->byte_order != _XmByteOrder())
	    SWAP4BYTES(dnd_message->data.top.src_window);
	cbs->tle.window = dnd_message->data.top.src_window;
	break;

    case XmDRAG_MOTION:
	cbs->dm.operation = DND_GET_OPERATION(dnd_message->flags);
	cbs->dm.operations = DND_GET_OPERATIONS(dnd_message->flags);
	cbs->dm.dropSiteStatus = DND_GET_STATUS(dnd_message->flags);
	if (dnd_message->byte_order != _XmByteOrder()) {
	    SWAP2BYTES(dnd_message->data.pot.x);
	    SWAP2BYTES(dnd_message->data.pot.y);
	}
	cbs->dm.x = dnd_message->data.pot.x;
	cbs->dm.y = dnd_message->data.pot.y;
	break;

    case XmOPERATION_CHANGED:
	cbs->oc.operation = DND_GET_OPERATION(dnd_message->flags);
	cbs->oc.operations = DND_GET_OPERATIONS(dnd_message->flags);
	cbs->oc.dropSiteStatus = DND_GET_STATUS(dnd_message->flags);
	break;

    case XmDROP_SITE_ENTER:
	cbs->dse.operation = DND_GET_OPERATION(dnd_message->flags);
	cbs->dse.operations = DND_GET_OPERATIONS(dnd_message->flags);
	cbs->dse.dropSiteStatus = DND_GET_STATUS(dnd_message->flags);
	if (dnd_message->byte_order != _XmByteOrder()) {
	    SWAP2BYTES(dnd_message->data.pot.x);
	    SWAP2BYTES(dnd_message->data.pot.y);
	}
	cbs->dse.x = dnd_message->data.pot.x;
	cbs->dse.y = dnd_message->data.pot.y;
	break;

    case XmDROP_SITE_LEAVE:
        break;

    case XmDROP_START:
	cbs->ds.operation = DND_GET_OPERATION(dnd_message->flags);
	cbs->ds.operations = DND_GET_OPERATIONS(dnd_message->flags);
	cbs->ds.dropSiteStatus = DND_GET_STATUS(dnd_message->flags);
	cbs->ds.dropAction = DND_GET_COMPLETION(dnd_message->flags);
    
	if (dnd_message->byte_order != _XmByteOrder()) {
	    SWAP2BYTES(dnd_message->data.pot.x);
	    SWAP2BYTES(dnd_message->data.pot.y);
	    SWAP4BYTES(dnd_message->data.pot.property);
	    SWAP4BYTES(dnd_message->data.pot.src_window);
	}
	cbs->ds.x = dnd_message->data.pot.x;
	cbs->ds.y = dnd_message->data.pot.y;
	cbs->ds.iccHandle = dnd_message->data.pot.property;
	cbs->ds.window = dnd_message->data.pot.src_window;
	break;

    default:
	_XmWarning(dc,
		   "We got something, but it wasn't a good drag message.\n");
	break;
    }

    return True;
}

static void
maybe_send_drag_message(Widget dc, Window win, int reason)
{
    XmDragDropCallbackStruct cbs;
    Widget disp = XmGetXmDisplay(XtDisplay(dc));
    XmDragTopLevelClientDataStruct tld;
    XmDragMotionClientDataStruct md;

    if (DC_ActiveProtocolStyle(dc) == XmDRAG_DROP_ONLY) {
	if (reason == XmTOP_LEVEL_ENTER) {
	    _XmDragOverChange((Widget)DC_CurDragOver(dc), XmVALID_DROP_SITE);
	    return;
	}
	else if (reason == XmTOP_LEVEL_LEAVE) {
	    _XmDragOverChange((Widget)DC_CurDragOver(dc), XmINVALID_DROP_SITE);
	    return;
	}
    }

    if (DC_ActiveProtocolStyle(dc) == XmDRAG_NONE)
	return;

    if (DC_ActiveProtocolStyle(dc) == XmDRAG_DROP_ONLY &&
	reason != XmDROP_START)
	return;

    cbs.any.reason = reason;
    cbs.any.event = NULL;
    switch (reason) {
    case XmTOP_LEVEL_ENTER:
	cbs.tle.timeStamp = DC_LastChangeTime(dc);
	cbs.tle.screen = DC_CurrScreen(dc);
	cbs.tle.window = DC_SrcWindow(dc);
	cbs.tle.x = DC_CurrReceiverInfo(dc)->xOrigin;
	cbs.tle.x = DC_CurrReceiverInfo(dc)->yOrigin;
	cbs.tle.dragProtocolStyle = DC_ActiveProtocolStyle(dc);
	cbs.tle.iccHandle= DC_ICCHandle(dc);
	break;

    case XmTOP_LEVEL_LEAVE:
	cbs.tll.timeStamp = DC_LastChangeTime(dc);
	cbs.tll.screen = DC_CurrScreen(dc);
	cbs.tll.window = DC_SrcWindow(dc);
	break;

    case XmDRAG_MOTION:
	cbs.dm.timeStamp = DC_LastChangeTime(dc);
	cbs.dm.operation = DC_Operation(dc);
	cbs.dm.operations = DC_Operations(dc);
	cbs.dm.dropSiteStatus = XmNO_DROP_SITE;
	cbs.dm.x = XtX(dc);
	cbs.dm.y = XtY(dc);
	break;

    case XmDROP_START:
	cbs.ds.timeStamp = DC_DragFinishTime(dc);
	cbs.ds.operation = DC_Operation(dc);
	cbs.ds.operations = DC_Operations(dc);
	cbs.ds.dropAction = DC_DragCompletionStatus(dc);
	cbs.ds.x = XtX(dc);
	cbs.ds.y = XtY(dc);
	cbs.ds.window = XtWindow(DC_SrcShell(dc));
	cbs.ds.iccHandle = DC_ICCHandle(dc);
	break;

    case XmOPERATION_CHANGED:
	break;

    case XmDROP_SITE_ENTER:
    case XmDROP_SITE_LEAVE:
    case XmDROP_FINISH:
    case XmDRAG_DROP_FINISH:
	break;

    default:
	break;
    }

    /* if we're not in the same client */
    if (DC_CurrReceiverInfo(dc)->shell == NULL &&
	!DC_SourceIsExternal(dc) &&
	(DC_ActiveProtocolStyle(dc) == XmDRAG_DYNAMIC ||
	 reason == XmDROP_START)) {

	send_drag_message(dc, win, &cbs, XmINITIATOR);
	return;
    }
    else if (reason == XmTOP_LEVEL_ENTER ||
	     reason == XmTOP_LEVEL_LEAVE ||
	     reason == XmDROP_START) {

	tld.sourceIsExternal = DC_SourceIsExternal(dc);
	tld.dos = DC_CurDragOver(dc);
	tld.window = DC_CurrReceiverInfo(dc)->window;
	tld.shell = DC_CurrReceiverInfo(dc)->shell;
	tld.xOrigin = DC_CurrReceiverInfo(dc)->xOrigin;
	tld.yOrigin = DC_CurrReceiverInfo(dc)->yOrigin;
	tld.width = DC_CurrReceiverInfo(dc)->width;
	tld.height = DC_CurrReceiverInfo(dc)->height;
	tld.iccInfo = DC_CurrReceiverInfo(dc)->iccInfo;

	_XmDSMUpdate(_XmGetDropSiteManagerObject((XmDisplay)disp),
		     (XtPointer)&tld,
		     (XtPointer)&cbs);
    }
    else if (reason == XmDRAG_MOTION ||
	     reason == XmOPERATION_CHANGED) {

	md.dos = DC_CurDragOver(dc);
	md.window = DC_CurrReceiverInfo(dc)->window;

	_XmDSMUpdate(_XmGetDropSiteManagerObject((XmDisplay)disp),
		     (XtPointer)&md,
		     (XtPointer)&cbs);
    }
    else {
	_XmDSMUpdate(_XmGetDropSiteManagerObject((XmDisplay)disp),
		     (XtPointer)NULL,
		     (XtPointer)&cbs);
    }
}

static void
invoke_initiator_callback(Widget dc, int cr_reason)
{
    XmDragDropCallbackStruct cbs;
    XtCallbackList cb = NULL;

    switch (cr_reason) {
    case XmCR_TOP_LEVEL_ENTER:
	if (DC_TopLevelEnterCallback(dc) == NULL)
	    return;
	cb = DC_TopLevelEnterCallback(dc);

	cbs.tle.reason = XmCR_TOP_LEVEL_ENTER;
	cbs.tle.event = NULL;
	cbs.tle.timeStamp = DC_LastChangeTime(dc);
	cbs.tle.screen = DC_CurrScreen(dc);
	/*
	 * From the Motif includes:
	 * the window field is different if this is an outbound or inbound
	 * callback. Outbound == reciever, Inbound == initiator.
	 * This stuff has gotta come from the receiverInfo
	 */
	cbs.tle.dragProtocolStyle = DC_ActiveProtocolStyle(dc);
	cbs.tle.window = DC_CurrReceiverInfo(dc)->window;
	cbs.tle.x = DC_CurrReceiverInfo(dc)->xOrigin;
	cbs.tle.y = DC_CurrReceiverInfo(dc)->yOrigin;
	cbs.tle.iccHandle = DC_ICCHandle(dc);
	break;

    case XmCR_TOP_LEVEL_LEAVE:
	if (DC_TopLevelLeaveCallback(dc) == NULL)
	    return;
	cb = DC_TopLevelLeaveCallback(dc);

	cbs.tll.reason = XmCR_TOP_LEVEL_LEAVE;
	cbs.tll.event = NULL;
	cbs.tll.timeStamp = DC_LastChangeTime(dc);
	cbs.tll.screen = DC_CurrScreen(dc);
	cbs.tll.window = DC_CurrReceiverInfo(dc)->window;
	break;

    case XmCR_DRAG_MOTION:
	if (DC_DragMotionCallback(dc) == NULL)
	    return;
	cb = DC_DragMotionCallback(dc);

	cbs.dm.reason = XmCR_DRAG_MOTION;
	cbs.dm.event = NULL;
	cbs.dm.timeStamp = DC_LastChangeTime(dc);
	cbs.dm.operation = DC_Operation(dc);
	cbs.dm.operations = DC_Operations(dc);
	if (DC_ActiveProtocolStyle(dc) == XmDRAG_DROP_ONLY)
	    cbs.dm.dropSiteStatus = XmVALID_DROP_SITE;
	else
	    cbs.dm.dropSiteStatus = XmNO_DROP_SITE;
	cbs.dm.x = XtX(dc);
	cbs.dm.y = XtY(dc);
	break;

    case XmCR_DROP_SITE_LEAVE:
	if (DC_SiteLeaveCallback(dc) == NULL)
	    return;
	cb = DC_SiteLeaveCallback(dc);

	cbs.dsl.reason = XmCR_DROP_SITE_LEAVE;
	cbs.dsl.event = NULL;
	cbs.dsl.timeStamp = DC_LastChangeTime(dc);
	break;

    case XmCR_DROP_START:
	if (DC_DropStartCallback(dc) == NULL)
	    return;
	cb = DC_DropStartCallback(dc);

	cbs.ds.reason = XmCR_DROP_START;
	cbs.ds.event = NULL;
	cbs.ds.timeStamp = DC_DragFinishTime(dc);
	cbs.ds.operation = DC_Operation(dc);
	cbs.ds.operations = DC_Operations(dc);
	cbs.ds.x = XtX(dc);
	cbs.ds.y = XtY(dc);
	cbs.ds.iccHandle = DC_ICCHandle(dc);
	cbs.ds.window = XtWindow(DC_SrcShell(dc));
	break;

    case XmCR_DROP_FINISH:
	if (DC_DropFinishCallback(dc) == NULL)
	    return;
	cb = DC_DropFinishCallback(dc);

	cbs.df.reason = XmCR_DROP_FINISH;
	cbs.df.event = NULL;
	cbs.df.timeStamp = DC_DropFinishTime(dc);
	cbs.df.completionStatus = DC_DragDropCompletionStatus(dc);
	cbs.df.operation = DC_Operation(dc);
	break;

    case XmCR_DRAG_DROP_FINISH:
	if (DC_DragDropFinishCallback(dc) == NULL)
	    return;
	cb = DC_DragDropFinishCallback(dc);

	cbs.ddf.reason = XmCR_DRAG_DROP_FINISH;
	cbs.ddf.event = NULL;
	cbs.ddf.timeStamp = DC_DropFinishTime(dc);
	break;

    case XmCR_OPERATION_CHANGED:
	if (DC_OperationChangedCallback(dc) == NULL)
	    return;
	cb = DC_OperationChangedCallback(dc);

	cbs.oc.reason = XmCR_OPERATION_CHANGED;
	cbs.oc.event = NULL;
	cbs.oc.timeStamp = DC_LastChangeTime(dc);
	cbs.oc.operation = DC_Operation(dc);
	cbs.oc.operations = DC_Operations(dc);
	break;

    default:
	break;
    }

    if (!cb)
	return;

    XtCallCallbackList(dc, cb, (XtPointer)&cbs);

    switch (cr_reason) {
    case XmCR_DROP_FINISH:
	DC_DragCompletionStatus(dc) = cbs.df.completionStatus;
	break;

    default:
	break;
    }
}

static void
invoke_receiver_callback(Widget dc, XmDragDropCallbackStruct  *cbs)
{
    XtCallbackList cb = NULL;
    int state;

    switch (cbs->any.reason) {
    case XmCR_DROP_SITE_ENTER:
	cb = DC_SiteEnterCallback(dc);
	break;

    case XmCR_DROP_SITE_LEAVE:
	DC_InDropSite(dc) = False;
	cb = DC_SiteLeaveCallback(dc);
	break;

    case XmCR_DRAG_MOTION:
	cb = DC_DragMotionCallback(dc);
	break;

    case XmCR_OPERATION_CHANGED:
	cb = DC_OperationChangedCallback(dc);
	break;

    case XmCR_DROP_START:
	if (DC_DragTimerId(dc)) {
	    XtRemoveTimeOut(DC_DragTimerId(dc));
	    DC_DragTimerId(dc) = None;
	}
	cb = DC_DropStartCallback(dc);
	break;

    default:
	break;
    }

    if (cb)
	XtCallCallbackList(dc, cb, cbs);

    switch (cbs->any.reason) {
    case XmCR_DROP_SITE_ENTER:
	DC_Operation(dc) = cbs->ds.operation;
	DC_Operations(dc) = cbs->ds.operations;
	DC_InDropSite(dc) = True;
	_XmDragOverChange((Widget)DC_CurDragOver(dc), cbs->dse.dropSiteStatus);
	break;

    case XmCR_DROP_SITE_LEAVE:

	/* reset drag ops */
	DC_Operations(dc) = DC_DragOperations(dc);

	state = DC_LastEventState(dc);

	if (state & ShiftMask && state & ControlMask) {
	    DC_Operation(dc) = DC_DragOperations(dc) & XmDROP_LINK;
	    DC_Operations(dc) = DC_DragOperations(dc) & XmDROP_LINK;
	}
	else if (state & ShiftMask) {
	    DC_Operation(dc) = DC_DragOperations(dc) & XmDROP_MOVE;
	    DC_Operations(dc) = DC_DragOperations(dc) & XmDROP_MOVE;
	}
	else if (state & ControlMask) {
	    DC_Operation(dc) = DC_DragOperations(dc) & XmDROP_COPY;
	    DC_Operations(dc) = DC_DragOperations(dc) & XmDROP_COPY;
	}
	else if (DC_DragOperations(dc) & XmDROP_LINK)
	    DC_Operation(dc) = XmDROP_LINK;
	else if (DC_DragOperations(dc) && XmDROP_COPY)
	    DC_Operation(dc) = XmDROP_COPY;
	else if (DC_DragOperations(dc) & XmDROP_MOVE)
	    DC_Operation(dc) = XmDROP_MOVE;
	else {
	    DC_Operation(dc) = XmDROP_NOOP;
	    DC_Operations(dc) = XmDROP_NOOP;
	}

	if (DC_DragFinishTime(dc) == CurrentTime)
	    _XmDragOverChange((Widget)DC_CurDragOver(dc), XmNO_DROP_SITE);

	break;

    case XmCR_DRAG_MOTION:
	DC_Operation(dc) = cbs->dm.operation;
	DC_Operations(dc) = cbs->dm.operations;
	_XmDragOverChange((Widget)DC_CurDragOver(dc), cbs->dm.dropSiteStatus);
	break;

    case XmCR_OPERATION_CHANGED:
	DC_Operation(dc) = cbs->oc.operation;
	DC_Operations(dc) = cbs->oc.operations;
	_XmDragOverChange((Widget)DC_CurDragOver(dc), cbs->oc.dropSiteStatus);
	break;

    case XmCR_DROP_START:
	DC_DragCompletionStatus(dc) = cbs->ds.dropAction;
	break;

    default:
	break;
    }
}

static void
internal_notify(Widget w, XtPointer cd, XtPointer cbs)
{
    XmDragDropCallbackStruct *cb = (XmDragDropCallbackStruct *)cbs;
    Widget dc = (Widget)cd;

    invoke_receiver_callback(dc, cb);
}

static void
external_notify(Widget w, XtPointer cd, XtPointer cbs)
{
    XmDragDropCallbackStruct *cb = (XmDragDropCallbackStruct *)cbs;
    Widget dc = (Widget)cd;

    switch (cb->any.reason) {
    case XmCR_DROP_SITE_ENTER:
    case XmCR_DROP_SITE_LEAVE:
    case XmCR_DRAG_MOTION:
    case XmCR_OPERATION_CHANGED:
    case XmCR_DROP_START:
	send_drag_message(dc, DC_SrcWindow(dc), cbs, XmRECEIVER);
	break;

    default:
	break;
    }
}

/*
 */
static void
external_msg_handler(Widget widget, XtPointer client_data, XEvent *event,
		     Boolean *dontSwallow)
{
    Widget dc = (Widget)client_data;
    XmDragDropCallbackStruct cbs;

    XdbDebug(__FILE__, widget, "Drag shell focus change\n");

    if (dc != NULL && event->type != ClientMessage)
	return;

    if (!receive_drag_message(dc, event, &cbs))
	return;

    if (DC_DragStartTime(dc) > cbs.any.timeStamp)
	return;

    if (DC_CrossingTime(dc) > cbs.any.timeStamp)
	return;

    invoke_receiver_callback(dc, &cbs);
}

static Boolean
drop_convert_incr_callback(Widget w, Atom *selection, Atom *target,
			   Atom *type, XtPointer *val,
			   unsigned long *length, int *format,
			   unsigned long *maxlen, XtPointer cd,
			   XtRequestId *receiver)
{
    XSelectionRequestEvent *sev;
    Atom ok, fail, drop;
    Widget dc;

    sev = XtGetSelectionRequest(w, *selection, NULL);

    if ((dc = _XmGetDragContextFromHandle(w, *selection)) == NULL) {
	_XmWarning(w, "Invalid selection.\n");
	return False;
    }

    ok = XmInternAtom(XtDisplay(dc), _XA_XmTRANSFER_SUCCESS, False);
    fail = XmInternAtom(XtDisplay(dc), _XA_XmTRANSFER_FAILURE, False);

    if (*target == ok || *target == fail) {
	if (*target == ok)
	    DC_DragDropCompletionStatus(dc) = XmDROP_SUCCESS;
	else
	    DC_DragDropCompletionStatus(dc) = XmDROP_FAILURE;

	*type = *target;
	*val = NULL;
	*length = 0;
	*format = 32;
	*maxlen = 0;

	DC_DropFinishTime(dc) = sev->time;

	invoke_initiator_callback(dc, XmCR_DROP_FINISH);

	if (DC_BlendModel(dc) != XmBLEND_NONE)
	    _XmDragOverFinish((Widget)DC_CurDragOver(dc),
			      DC_DragDropCompletionStatus(dc));

	invoke_initiator_callback(dc, XmCR_DRAG_DROP_FINISH);

	XtDisownSelection(DC_SrcShell(dc), DC_ICCHandle(dc),
			  DC_DragFinishTime(dc));

	_XmFreeMotifAtom(dc, DC_ICCHandle(dc));

	XtRemoveEventHandler(DC_SrcShell(dc), FocusChangeMask, True,
			     external_msg_handler, (XtPointer)dc);

	XtDestroyWidget(dc);

	return True;
    }
    else {
	drop = XmInternAtom(XtDisplay(dc), _XA_MOTIF_DROP, False);

	return (Boolean)(DC_ConvertProc(dc).sel_incr)(dc,
						      &drop,
						      target, type,
						      val, length,
						      format, maxlen,
						      cd, receiver);
    }
}

static Boolean
drop_convert_callback(Widget w, Atom *selection, Atom *target,
		      Atom *type, XtPointer *val,
		      unsigned long *length, int *format)
{
    XSelectionRequestEvent *sev;
    Atom ok, fail, drop;
    Widget dc;

    XdbDebug(__FILE__, w, "DropConvertCallback\n");

    sev = XtGetSelectionRequest(w, *selection, NULL);

    if ((dc = _XmGetDragContextFromHandle(w, *selection)) == NULL) {
	_XmWarning(w, "Invalid selection.\n");
	return False;
    }

    ok = XmInternAtom(XtDisplay(dc), _XA_XmTRANSFER_SUCCESS, False);
    fail = XmInternAtom(XtDisplay(dc), _XA_XmTRANSFER_FAILURE, False);

    if (*target == ok || *target == fail) {

	if (*target == ok)
	    DC_DragDropCompletionStatus(dc) = XmDROP_SUCCESS;
	else
	    DC_DragDropCompletionStatus(dc) = XmDROP_FAILURE;

	*type = *target;
	*val = NULL;
	*length = 0;
	*format = 32;

	DC_DropFinishTime(dc) = sev->time;

	invoke_initiator_callback(dc, XmCR_DROP_FINISH);

	if (DC_BlendModel(dc) != XmBLEND_NONE)
	    _XmDragOverFinish((Widget)DC_CurDragOver(dc),
			      DC_DragDropCompletionStatus(dc));

	invoke_initiator_callback(dc, XmCR_DRAG_DROP_FINISH);

	XtDisownSelection(DC_SrcShell(dc), DC_ICCHandle(dc),
			  DC_DragFinishTime(dc));

	_XmFreeMotifAtom(dc, DC_ICCHandle(dc));

	XtRemoveEventHandler(DC_SrcShell(dc), FocusChangeMask, True,
			     external_msg_handler, (XtPointer)dc);

	XtDestroyWidget(dc);

	return True;
    }
    else {

	drop = XmInternAtom(XtDisplay(dc), _XA_MOTIF_DROP, False);

	return (Boolean)(DC_ConvertProc(dc).sel)(dc,
					         &drop,
					         target, type,
					         val, length,
					         format);
    }
}

static void
drop_lose_incr_callback(Widget w, Atom *selection, XtPointer cd)
{
    Widget dc;

    if ((dc = _XmGetDragContextFromHandle(w, *selection)) == NULL)
	_XmWarning(w, "bad selection\n");

    if (dc && DC_DropFinishTime(dc) == CurrentTime)
	_XmWarning(w, "lost the drop selection.\n");
}

static void
drop_lose_callback(Widget w, Atom *selection)
{
    Widget dc;

    if ((dc = _XmGetDragContextFromHandle(w, *selection)) == NULL)
	_XmWarning(w, "bad selection\n");

    if (dc && DC_DropFinishTime(dc) == CurrentTime)
	_XmWarning(w, "lost the drop selection.\n");
}

static void
drop_timeout(XtPointer data, XtIntervalId *id)
{
    Widget dc = (Widget)data;

    DC_DragCompletionStatus(dc) = XmDROP_CANCEL;
    invoke_initiator_callback(dc, XmCR_DROP_START);

    DC_DragDropCompletionStatus(dc) = XmDROP_FAILURE;
    DC_DropFinishTime(dc) = DC_DragFinishTime(dc);

    invoke_initiator_callback(dc, XmCR_DROP_FINISH);

    if (DC_BlendModel(dc) != XmBLEND_NONE)
	_XmDragOverFinish((Widget)DC_CurDragOver(dc),
			  DC_DragDropCompletionStatus(dc));

    invoke_initiator_callback(dc, XmCR_DRAG_DROP_FINISH);

    XtDisownSelection(DC_SrcShell(dc), DC_ICCHandle(dc),
		      DC_DragFinishTime(dc));

    _XmFreeMotifAtom(dc, DC_ICCHandle(dc));

    XtRemoveEventHandler(DC_SrcShell(dc), FocusChangeMask, True,
			 external_msg_handler, (XtPointer)dc);

    XtDestroyWidget(dc);
}

/*
 * create receiver info for a root window
 */
static void
create_root_info(Widget w)
{
    Screen *scr = DC_CurrScreen(w);
    XmDragReceiverInfo ri;
    Display *disp = XtDisplay(w);

    if (DC_NumReceiverInfos(w) == 0)
	ri = _XmAllocReceiverInfo((XmDragContext)w);
    else
	ri = &DC_ReceiverInfos(w)[0];

    DC_RootReceiverInfo(w) = ri;

    ri->frame = None;
    ri->window = RootWindowOfScreen(scr);
    ri->shell = XtWindowToWidget(disp, XtWindow(DC_CurDragOver(w)));
    ri->xOrigin = 0;
    ri->yOrigin = 0;
    ri->width = WidthOfScreen(scr);
    ri->height = HeightOfScreen(scr);
    ri->depth = DefaultDepthOfScreen(scr);

    if (!_XmGetDragReceiverInfo(disp, ri->window, ri))
	return;

    if (ri->dragProtocolStyle == XmDRAG_NONE ||
	ri->dragProtocolStyle == XmDRAG_DROP_ONLY ||
	ri->dragProtocolStyle == XmDRAG_DYNAMIC) {

	_XmFreeDragReceiverInfo(ri->iccInfo);

    }
}

/*
 * create a receiverInfo and a DragOverShell for a new
 * root window
 */
static void
new_root(Widget dc, Window root)
{
    int i, mask, argc;
    XmDragOverShellWidget dos;
    Cursor cursor;
    Arg args[10];

    dos = DC_CurDragOver(dc);

    for (i = 0; i < ScreenCount(XtDisplay(dc)); i++) {
	if (RootWindowOfScreen(ScreenOfDisplay(XtDisplay(dc), i)) == root)
	    break;
    }

    DC_CurrScreen(dc) = ScreenOfDisplay(XtDisplay(dc), i);
    DC_CurrWmRoot(dc) = root;

    argc = 0;
    if (DC_ActiveProtocolStyle(dc) == XmDRAG_DYNAMIC) {
	XtSetArg(args[argc], XmNdragOverMode, XmCURSOR); argc++;
    }
    else {
	XtSetArg(args[argc], XmNdragOverMode, XmPIXMAP); argc++;
    }
    XtSetArg(args[argc], XmNhotX, 1); argc++;
    XtSetArg(args[argc], XmNhotY, 1); argc++;
    XtSetArg(args[argc], XmNbackgroundPixmap, None); argc++;
    XtSetArg(args[argc], XmNscreen, DC_CurrScreen(dc)); argc++;
    XtSetArg(args[argc], XmNdepth,
	     DefaultDepthOfScreen(DC_CurrScreen(dc))); argc++;
    XtSetArg(args[argc], XmNcolormap,
	     DefaultColormapOfScreen(DC_CurrScreen(dc))); argc++;
    XtSetArg(args[argc], XmNvisual,
	     DefaultVisualOfScreen(DC_CurrScreen(dc))); argc++;
    DC_CurDragOver(dc) =
	(XmDragOverShellWidget)XtCreatePopupShell("dragOver",
						  xmDragOverShellWidgetClass,
						  dc, args, argc);

    if (XtScreen(DC_CurDragOver(dc)) == XtScreen(DC_SrcShell(dc))) {
	_XmDragOverSetInitialPosition((Widget)DC_CurDragOver(dc),
				      DC_StartX(dc), DC_StartY(dc));
    }

    if (dos) {
	if (DC_OrigDragOver(dc) != dos)
	    XtDestroyWidget((Widget)DC_OrigDragOver(dc));
	else
	    _XmDragOverHide((Widget)DC_OrigDragOver(dc), 0, 0, NULL);
    }

    create_root_info(dc);

    if (DC_OrigDragOver(dc) == NULL)
	DC_OrigDragOver(dc) = DC_CurDragOver(dc);

    mask = ButtonMotionMask|ButtonPressMask|ButtonReleaseMask;

    cursor = _XmDragOverGetActiveCursor((Widget)DC_CurDragOver(dc));

    XGrabPointer(XtDisplay(dc), RootWindowOfScreen(XtScreen(dc)), False,
		 mask, GrabModeSync, GrabModeAsync,
		 None, cursor, DC_LastChangeTime(dc));

    XAllowEvents(XtDisplay(dc), SyncPointer, DC_LastChangeTime(dc));
}

/*
 * write the initiator info on the source window
 */
void
write_initiator(Widget dc)
{
    XmDndSourceProp src_prop;
    Atom init;

    src_prop.byte_order = _XmByteOrder();
    src_prop.protocol_version = DND_PROTOCOL_VERSION;
    src_prop.target_index = _XmTargetsToIndex(dc,
					      DC_ExportTargets(dc),
					      DC_NumExportTargets(dc));
    src_prop.selection = DC_ICCHandle(dc); 

    init = XmInternAtom(XtDisplay(dc), _XA_MOTIF_DRAG_INITIATOR_INFO, False);

    /* write the buffer to the property */
    XChangeProperty (XtDisplay(dc), DC_SrcWindow(dc), DC_ICCHandle(dc),
		     init, 8, PropModeReplace, (unsigned char *)&src_prop,
                     sizeof(XmDndSourceProp));
}

/*
 * find the shell child of a frame.  This will be the
 * child that has the WM_STATE property set -- we need to skip over the
 * frame and the matte.
 */
Window
find_shell_child(Display *display, Window win)
{
    Atom type = 0;
    int format;
    unsigned int nchildren;
    unsigned long bafter, length;
    unsigned char *prop;
    Window root, parent, *children;
    Atom state;

    state = XmInternAtom(display, _XA_WM_STATE, True);

    XGetWindowProperty(display, win, state, 0L, 0L, False,
		       0, &type, &format, &length, &bafter,
		       &prop);

    if (type != 0) {
	XdbDebug(__FILE__, NULL, "find_shell_child: %08x\n", win);
	return win;
    }

    if (XQueryTree(display, win, &root, &parent, &children, &nchildren)
	== 0)
	return None;

    if (nchildren == 0)
	return None;

    while (--nchildren >= 0) {

	win = find_shell_child(display, children[nchildren]);

	if (win != None) {
	    XFree((char *)children);

	    XdbDebug(__FILE__, NULL, "find_shell_child 2: %08x\n", win);

	    return win;
	}
    }

    if (children)
	XFree((char *)children);

    return None;
}

/*
 * from a top-level window, find the receiver info
 */
static XmDragReceiverInfo
find_receiver_info(Widget dc, Window win)
{
    int i;
    XmDragReceiverInfo ri;

    /* maybe there's a match */
    for (i = 0; i < DC_NumReceiverInfos(dc); i++) {

	if (DC_ReceiverInfos(dc)[i].frame == win ||
	    DC_ReceiverInfos(dc)[i].window == win)
	    break;
    }

    if (i == DC_NumReceiverInfos(dc))
	ri = NULL;
    else
	ri = &DC_ReceiverInfos(dc)[i];

    return ri;
}

/*
 * setup the current receiver information.
 */
static void
get_current_receiver(Widget dc, Window root, Window win)
{
    XmDragReceiverInfo ri;
    Window tmp;
    Display *display = XtDisplay(dc);
    unsigned char last_style;
    Position deltax, deltay;
    int rootx, rooty;

    DC_CrossingTime(dc) = DC_LastChangeTime(dc);

    last_style = DC_ActiveProtocolStyle(dc);

    ri = find_receiver_info(dc, win);

    DC_CurrReceiverInfo(dc) = ri;

    if (XtWindow(DC_SrcShell(dc)) == win) {

	if (ri == NULL || ri->frame == ri->window) {
	    deltax = DC_StartX(dc) - XtX(DC_SrcShell(dc));
	    deltay = DC_StartY(dc) - XtY(DC_SrcShell(dc));

	    if (deltax < 0)
		deltax = 0;
	    if (deltay < 0)
		deltay = 0;

	    XTranslateCoordinates(XtDisplay(dc), win, DC_CurrWmRoot(dc),
				  deltax, deltay, &rootx, &rooty,
				  &win);

	    if (ri != NULL)
		ri->frame = win;
	}
    }

    if (ri == NULL) {

	tmp = find_shell_child(display, win);
	if (tmp == None)
	    tmp = win;

	ri = _XmAllocReceiverInfo((XmDragContext)dc);

	DC_CurrReceiverInfo(dc) = ri;

	ri->frame = win;
	ri->window = tmp;
	ri->shell = XtWindowToWidget(display, tmp);
    }

    /* if it's not the root */
    if (DC_CurrReceiverInfo(dc) != DC_RootReceiverInfo(dc)) {

	/* if it's not local */
	if (ri->shell == NULL) {

	    if (_XmGetDragReceiverInfo(display, ri->window, ri)) {

		if (ri->dragProtocolStyle == XmDRAG_NONE ||
		    ri->dragProtocolStyle == XmDRAG_DROP_ONLY ||
		    ri->dragProtocolStyle == XmDRAG_DYNAMIC)
		    _XmFreeDragReceiverInfo(ri->iccInfo);
	    }
	}
	else {
	    if (!_XmDropSiteShell(ri->shell))
		ri->dragProtocolStyle = XmDRAG_NONE;
	    else
		ri->dragProtocolStyle = DC_ActiveProtocolStyle(dc);

	    ri->xOrigin = XtX(ri->shell);
	    ri->yOrigin = XtY(ri->shell);
	    ri->width = XtWidth(ri->shell);
	    ri->height = XtHeight(ri->shell);
	    ri->depth = CoreDepth(ri->shell);
	    ri->iccInfo = NULL;
	}
    }

    DC_ActiveProtocolStyle(dc) = _XmGetActiveProtocolStyle(dc);
}

/************************* DRAG START *************************/
/*
 * set the initiator, get the first receiver, and handle motion messages
 */
static void
drag_main_loop(XtPointer data, XtIntervalId *id)
{
    Widget dc = *(Widget *)data, fw;
    XtAppContext app;
    XEvent event;

    if (dc == NULL)
	return;

    app = XtWidgetToApplicationContext(dc);

    fw = XmGetFocusWidget(DC_SrcShell(dc));

    if (_XmGetFocusPolicy(DC_SrcShell(dc)) != XmEXPLICIT) {
	WidgetClass wc = XtClass(fw);

	if (XmIsPrimitive(fw) && PrimC_BorderUnhighlight(wc) != NULL)
	    PrimC_BorderUnhighlight(wc)(fw);
	else if (XmIsGadget(fw) && GC_BorderUnhighlight(wc) != NULL)
	    GC_BorderUnhighlight(wc)(fw);
    }
    else
	XtSetKeyboardFocus(DC_SrcShell(dc), NULL);

    write_initiator(dc);

    get_current_receiver(dc,
			 RootWindowOfScreen(XtScreen(dc)),
			 XtWindow(DC_SrcShell(dc)));

    /* need to invoke dragStart callbacks */
    invoke_initiator_callback(dc, XmCR_TOP_LEVEL_ENTER);

    maybe_send_drag_message(dc, DC_CurrReceiverInfo(dc)->window,
			    XmTOP_LEVEL_ENTER);

    maybe_send_drag_message(dc, DC_CurrReceiverInfo(dc)->window,
			    XmDRAG_MOTION);

    if (dc) {
	while (*(Widget *)data) {

	    XtAppNextEvent(app, &event);

	    switch(event.xany.type) {
	    case KeyPress:
	    case KeyRelease:
	    case ButtonPress:
	    case ButtonRelease:
	    case MotionNotify:
	    case EnterNotify:
	    case LeaveNotify:
		XdbDebug(__FILE__, dc,
			 "Type: %d Window: %08x swindow: %08x root: %08x\n",
			 event.type, event.xbutton.subwindow,
			 event.xbutton.window, event.xbutton.root);

		/* so that the event gets delivered to the right widget (us) */
		event.xany.window = XtWindow(dc);
		break;

	    case FocusIn:
	    case FocusOut:
	    default:
		break;
	    }

	    XdbDebug(__FILE__, dc,
		     "Dispatch event: state: %d last %d send: %d\n",
		     event.xmotion.state, DC_LastEventState(dc),
		     event.xany.send_event);

	    XtDispatchEvent(&event);
	}
    }

    if (_XmGetFocusPolicy(DC_SrcShell(dc)) == XmEXPLICIT)
	XtSetKeyboardFocus(DC_SrcShell(dc), fw);
}

/************************* DRAG MOTION *************************/
static void
add_motion(Widget w, XmMotionBuffer *mb, XEvent *event)
{
    XmDragReceiverInfo ri;

    ri = DC_CurrReceiverInfo(w);

    if (!ri)
	return;

    DC_LastChangeTime(w) = event->xmotion.time;

    switch (event->type) {
    case MotionNotify:
	if (mb->num_motions == mb->max_motions) {
	    if (mb->max_motions == 0) {
		mb->max_motions = 100;

		mb->motions = (XmMotionRec *)XtMalloc(mb->max_motions *
							sizeof(XmMotionRec));
	    }
	    else {
		mb->max_motions += 100;

		mb->motions = (XmMotionRec *)XtRealloc((char *)mb->motions,
							mb->max_motions *
							sizeof(XmMotionRec));
	    }
	}

	mb->motions[mb->num_motions].time = event->xmotion.time;
	mb->motions[mb->num_motions].window = event->xmotion.root;
	mb->motions[mb->num_motions].subwindow = event->xmotion.subwindow;
	mb->motions[mb->num_motions].xroot = event->xmotion.x_root;
	mb->motions[mb->num_motions].yroot = event->xmotion.y_root;
	mb->motions[mb->num_motions].state = event->xmotion.state;

	mb->num_motions++;

	break;

    default:
	return;
    }
}

static void
motion_message(Widget w, Window root, Window win)
{
    Boolean left_drop = False;

    if (root != DC_CurrWmRoot(w) ||
	(DC_CurrReceiverInfo(w)->frame != win)) {

	if (DC_CurrReceiverInfo(w)->window != None) {

	    if (DC_ActiveProtocolStyle(w) != XmDRAG_NONE &&
		DC_ActiveProtocolStyle(w) != XmDRAG_DROP_ONLY) {

		if (DC_ActiveProtocolStyle(w) == XmDRAG_DYNAMIC &&
		    DC_CurrReceiverInfo(w)->shell == NULL &&
		    DC_InDropSite(w)) {

		    invoke_initiator_callback(w, XmCR_DROP_SITE_LEAVE);

		    DC_InDropSite(w) = False;

		    left_drop = True;
		}

		maybe_send_drag_message(w, DC_CurrReceiverInfo(w)->window,
					XmDRAG_MOTION);

		maybe_send_drag_message(w, DC_CurrReceiverInfo(w)->window,
					XmTOP_LEVEL_LEAVE);
	    }

	    invoke_initiator_callback(w, XmCR_TOP_LEVEL_LEAVE);
	}

	/* did we move over to a new screen? */
	if (DC_CurrWmRoot(w) != root)
	    new_root(w, root);

	get_current_receiver(w, root, win);

	if (DC_CurrReceiverInfo(w)->window != None) {

	    if (DC_ActiveProtocolStyle(w) != XmDRAG_NONE) {
		maybe_send_drag_message(w, DC_CurrReceiverInfo(w)->window,
					XmTOP_LEVEL_ENTER);
	    }

	    invoke_initiator_callback(w, XmCR_TOP_LEVEL_ENTER);

	    DC_CurrReceiverInfo(w)->iccInfo = NULL;
	}
    }

    if (DC_CurrReceiverInfo(w)->window != None &&
	DC_ActiveProtocolStyle(w) != XmDRAG_NONE &&
	DC_ActiveProtocolStyle(w) != XmDRAG_DROP_ONLY) {

	maybe_send_drag_message(w, DC_CurrReceiverInfo(w)->window,
				XmDRAG_MOTION);
    }

    invoke_initiator_callback(w, XmCR_DRAG_MOTION);

    if (left_drop)
	DC_LastChangeTime(w)++;
}

static void
maybe_operation_changed(Widget w, unsigned int state)
{
    unsigned char ops, op;

    DC_LastEventState(w) = state;

    ops = DC_Operations(w);
    op = DC_Operation(w);

    DC_Operations(w) = DC_DragOperations(w);

    if (state & ShiftMask && state & ControlMask) {
	DC_Operation(w) = DC_DragOperations(w) & XmDROP_LINK;
	DC_Operations(w) = DC_DragOperations(w) & XmDROP_LINK;
    }
    else if (state & ShiftMask) {
	DC_Operation(w) = DC_DragOperations(w) & XmDROP_MOVE;
	DC_Operations(w) = DC_DragOperations(w) & XmDROP_MOVE;
    }
    else if (state & ControlMask) {
	DC_Operation(w) = DC_DragOperations(w) & XmDROP_COPY;
	DC_Operations(w) = DC_DragOperations(w) & XmDROP_COPY;
    }
    else if (DC_DragOperations(w) & XmDROP_LINK)
	DC_Operation(w) = XmDROP_LINK;
    else if (DC_DragOperations(w) && XmDROP_COPY)
	DC_Operation(w) = XmDROP_COPY;
    else if (DC_DragOperations(w) & XmDROP_MOVE)
	DC_Operation(w) = XmDROP_MOVE;
    else {
	DC_Operation(w) = XmDROP_NOOP;
	DC_Operations(w) = XmDROP_NOOP;
    }

    if (DC_Operations(w) == ops && DC_Operation(w) == op)
	return;

    if (DC_CurrReceiverInfo(w)->window != None &&
	DC_ActiveProtocolStyle(w) != XmDRAG_NONE &&
	DC_ActiveProtocolStyle(w) != XmDRAG_DROP_ONLY) {
	maybe_send_drag_message(w, DC_CurrReceiverInfo(w)->window,
				XmOPERATION_CHANGED);
    }
    else
	invoke_initiator_callback(w, XmCR_OPERATION_CHANGED);
}

/*
 * if events pile up on us, don't look at all events.  This
 * could cause the protocol to thrash.  Instead, look at
 * a progressively smaller subset, based on the number of
 * events queued.
 */
#define OPTIMAL_GRANULARITY	16
static void
motion_hysteresis(Widget w, XmMotionBuffer *mb)
{
    int i, grain_size, box_count, box_offset;
    Window sw = None;

    grain_size = mb->num_motions / OPTIMAL_GRANULARITY;

    if (grain_size == 0)
	grain_size = 1;

    box_count = mb->num_motions / grain_size;

    box_offset = (mb->num_motions + grain_size - 1) % grain_size;

    for (i = 0; i < box_count; i++) {

	XtX(w) = mb->motions[i * grain_size + box_offset].xroot;
	XtY(w) = mb->motions[i * grain_size + box_offset].yroot;

	if (DC_LastEventState(w) != mb->motions[i * grain_size + box_offset].state)
	    maybe_operation_changed(w, mb->motions[i * grain_size + box_offset].state);

	if (mb->motions[i * grain_size + box_offset].window != DC_CurrWmRoot(w)) {
	    motion_message(w, mb->motions[i * grain_size + box_offset].window, None);
	    sw = None;
	}
	else
	    sw = mb->motions[i * grain_size + box_offset].subwindow;
    }

    _XmDragOverMove((Widget)DC_CurDragOver(w), XtX(w), XtY(w));

    if (sw != None)
	motion_message(w, DC_CurrWmRoot(w), sw);

    if (mb->num_motions != 0)
	XtFree((char *)mb->motions);
}

/************************* DRAG FINISH *************************/
static void
drag_finish(Widget w, XEvent *event) {
    int state = 0;
    XmDisplay disp = _XmDCtoDD(w);
    int ac;
    Arg al[5];
    XtAppContext app;

    /* NOTE: This is how we stop the main loop */
    Display_ActiveDC(disp) = NULL;

    if (event != NULL) {
	/* Term on button release */
	if (event->type == ButtonRelease) {
	    state = event->xbutton.state;
	    XtX(w) = event->xbutton.x_root;
	    XtY(w) = event->xbutton.y_root;
	    DC_LastChangeTime(w) = event->xbutton.time;
	}
	/* Term on KeyCancel */
	else if (event->type == KeyPress) {
	    state = event->xkey.state;
	    XtX(w) = event->xkey.x_root;
	    XtY(w) = event->xkey.y_root;
	    DC_LastChangeTime(w) = event->xkey.time;
	}

	DC_Operations(w) = DC_DragOperations(w);
	DC_LastEventState(w) = state;

	if (state & ShiftMask && state & ControlMask) {
	    DC_Operation(w) = DC_DragOperations(w) & XmDROP_LINK;
	    DC_Operations(w) = DC_DragOperations(w) & XmDROP_LINK;
	}
	else if (state & ShiftMask) {
	    DC_Operation(w) = DC_DragOperations(w) & XmDROP_MOVE;
	    DC_Operations(w) = DC_DragOperations(w) & XmDROP_MOVE;
	}
	else if (state & ControlMask) {
	    DC_Operation(w) = DC_DragOperations(w) & XmDROP_COPY;
	    DC_Operations(w) = DC_DragOperations(w) & XmDROP_COPY;
	}
	else if (DC_DragOperations(w) & XmDROP_LINK)
	    DC_Operation(w) = XmDROP_LINK;
	else if (DC_DragOperations(w) && XmDROP_COPY)
	    DC_Operation(w) = XmDROP_COPY;
	else if (DC_DragOperations(w) & XmDROP_MOVE)
	    DC_Operation(w) = XmDROP_MOVE;
	else {
	    DC_Operation(w) = XmDROP_NOOP;
	    DC_Operations(w) = XmDROP_NOOP;
	}
    }

    ac = 0;
    XtSetArg(al[ac], XmNhotX, XtX(w)); ac++;
    XtSetArg(al[ac], XmNhotY, XtY(w)); ac++;
    XtSetArg(al[ac], XmNdragOverMode, XmWINDOW); ac++;
    XtSetValues((Widget)DC_CurDragOver(w), al, ac);

    XUngrabPointer(XtDisplay(w), DC_LastChangeTime(w));

    XtUngrabPointer(w, DC_DragFinishTime(w));

    XUngrabKeyboard(XtDisplay(w), DC_LastChangeTime(w));

    _XmRemoveGrab(w);

    DC_DragFinishTime(w) = DC_LastChangeTime(w);

    if (DC_InDropSite(w)) {
	invoke_initiator_callback(w, XmCR_DROP_SITE_LEAVE);
	DC_InDropSite(w) = False;
    }

    if (DC_CurrReceiverInfo(w)->window != None) {

	maybe_send_drag_message(w,
				DC_CurrReceiverInfo(w)->window, 
				XmTOP_LEVEL_LEAVE);

	invoke_initiator_callback(w, XmCR_TOP_LEVEL_LEAVE);

	if (DC_ActiveProtocolStyle(w) != XmDRAG_NONE &&
	    (DC_DragCompletionStatus(w) == XmDROP ||
	     DC_DragCompletionStatus(w) == XmDROP_HELP)) {

	    app = XtWidgetToApplicationContext(w);

	    DC_DragTimerId(w) = XtAppAddTimeOut(app,
						XtAppGetSelectionTimeout(app),
						drop_timeout, (XtPointer)w);

	    maybe_send_drag_message(w,
				    DC_CurrReceiverInfo(w)->window,
				    XmDROP_START);
	}
	else {
	    DC_DragDropCompletionStatus(w) = XmDROP_FAILURE;
	    DC_DropFinishTime(w) = DC_DragFinishTime(w);
	    drop_timeout((XtPointer)w, NULL);
	}
    }

    DC_CurrReceiverInfo(w)->frame = None;
}

/******************** ACTION PROCS ********************/
static void 
_XmDCStartDrag(XmDragContext dc,
	       Widget srcW,
	       XEvent *event)
{
    unsigned int state;
    Time time;
    XmDisplay disp;
    Cursor cursor;
    int mask;
    XWindowAttributes attr;
    Window win;

    XdbDebug(__FILE__, srcW, "_XmDCStartDrag\n");

    disp = _XmDCtoDD((Widget)dc);

    state = event->xbutton.state;
    time = event->xbutton.time;

    Display_ActiveDC(disp) = dc;

    DC_DragStartTime(dc) = time;
    DC_LastChangeTime(dc) = time;
    DC_CrossingTime(dc) = time;

    XtX(dc) = DC_StartX(dc) = event->xbutton.x_root;
    XtY(dc) = DC_StartY(dc) = event->xbutton.y_root;

    DC_CurDragOver(dc) = NULL;
    DC_OrigDragOver(dc) = NULL;

    /* find the shell this guy lives in */
    DC_SrcShell(dc) = _XmFindTopMostShell(srcW);
    DC_SrcWindow(dc) = XtWindow(DC_SrcShell(dc));

    DC_ICCHandle(dc) = _XmAllocMotifAtom(DC_SrcShell(dc), DC_DragStartTime(dc));

    if (DC_Incremental(dc))
	XtOwnSelectionIncremental(DC_SrcShell(dc), DC_ICCHandle(dc),
				  DC_DragStartTime(dc), 
				  drop_convert_incr_callback,
				  drop_lose_incr_callback,
				  NULL,	NULL, DC_ClientData(dc));
    else
	XtOwnSelection(DC_SrcShell(dc), DC_ICCHandle(dc),
		       DC_DragStartTime(dc),
		       drop_convert_callback,
		       drop_lose_callback,
		       NULL);

    DC_SourceIsExternal(dc) = False;

    DC_ActiveProtocolStyle(dc) = _XmGetActiveProtocolStyle((Widget)dc);

    /* the protocol gotten from _XmGetActiveProtocolStyle() needs to be
     * filtered for reasonable values for the initiator. */
    switch (DC_ActiveProtocolStyle(dc)) {
	case XmDRAG_DROP_ONLY:
	    DC_ActiveProtocolStyle(dc) = XmDRAG_NONE;
	    break;

	case XmDRAG_PREREGISTER:
	    DC_ActiveProtocolStyle(dc) = XmDRAG_DYNAMIC;
	    break;

	default:
	    break;
    }

    DC_Operations(dc) = DC_DragOperations(dc);
    DC_LastEventState(dc) = state;

    if (state & ShiftMask && state & ControlMask) {
	DC_Operation(dc) = DC_DragOperations(dc) & XmDROP_LINK;
	DC_Operations(dc) = DC_DragOperations(dc) & XmDROP_LINK;
    }
    else if (state & ShiftMask) {
	DC_Operation(dc) = DC_DragOperations(dc) & XmDROP_MOVE;
	DC_Operations(dc) = DC_DragOperations(dc) & XmDROP_MOVE;
    }
    else if (state & ControlMask) {
	DC_Operation(dc) = DC_DragOperations(dc) & XmDROP_COPY;
	DC_Operations(dc) = DC_DragOperations(dc) & XmDROP_COPY;
    }
    else if (DC_DragOperations(dc) & XmDROP_LINK)
	DC_Operation(dc) = XmDROP_LINK;
    else if (DC_DragOperations(dc) && XmDROP_COPY)
	DC_Operation(dc) = XmDROP_COPY;
    else if (DC_DragOperations(dc) & XmDROP_MOVE)
	DC_Operation(dc) = XmDROP_MOVE;
    else {
	DC_Operation(dc) = XmDROP_NOOP;
	DC_Operations(dc) = XmDROP_NOOP;
    }

    new_root((Widget)dc, RootWindowOfScreen(XtScreen(dc)));

    XtInsertEventHandler(DC_SrcShell(dc), FocusChangeMask, True,
			 external_msg_handler, (XtPointer)dc, XtListHead);

    mask = ButtonMotionMask|ButtonPressMask|ButtonReleaseMask;

    cursor = _XmDragOverGetActiveCursor((Widget)DC_CurDragOver(dc));

    win = XtWindow(dc);
    CoreWindow(dc) = RootWindowOfScreen(XtScreen(dc));
    if (XtGrabPointer((Widget)dc, False, mask, GrabModeSync, GrabModeAsync,
		      None, cursor, DC_DragStartTime(dc)) != GrabSuccess ||
	XGrabPointer(XtDisplay(dc), RootWindowOfScreen(XtScreen(dc)),
		     False, mask, GrabModeSync, GrabModeAsync,
		     None, cursor, DC_DragStartTime(dc)) != GrabSuccess ||
	XGrabKeyboard(XtDisplay(dc), RootWindowOfScreen(XtScreen(dc)),
		      False, GrabModeSync, GrabModeAsync,
		      DC_DragStartTime(dc)) != GrabSuccess) {

	_XmWarning((Widget)dc, "No grab on _XmDCStartDrag!\n");
    }

    _XmAddGrab((Widget)dc, True, False);

    CoreWindow(dc) = win;

    XGetWindowAttributes(XtDisplay(dc), DC_CurrWmRoot(dc), &attr);
    attr.your_event_mask |= ButtonMotionMask;
    XSelectInput(XtDisplay(dc), DC_CurrWmRoot(dc), attr.your_event_mask);

    if (DC_DragFinishTime(dc) == CurrentTime)
	XAllowEvents(XtDisplay(dc), SyncPointer, DC_LastChangeTime(dc));

    XSync(XtDisplay(dc), False);

    XtAppAddTimeOut(XtWidgetToApplicationContext((Widget)dc), 0, drag_main_loop,
		    (XtPointer)&Display_ActiveDC(disp));
}

/*
 * this gets called directly by XmDragCancel()
 */
static void 
_XmDCCancelDrag(XmDragContext dc)
{
    XdbDebug(__FILE__, NULL, "_XmDCCancelDrag\n");

    DC_DragCompletionStatus(dc) = XmDROP_CANCEL;

    drag_finish((Widget)dc, NULL);
}

/*
 * but this gets called by translation
 */
static void
_XmDCCancel(Widget w,
	    XEvent *event,
	    String *params,
	    Cardinal *num_params)
{
    XdbDebug(__FILE__, w, "_XmDCCancel\n");

    /* make sure we don't cancel if already finished dragging */
    if (DC_DragFinishTime(w) == CurrentTime) {
	DC_DragCompletionStatus(w) = XmDROP_CANCEL;
	drag_finish(w, event);
    }
}

/*
 * handle motion events
 */
static void 
_XmDCMotion(Widget w,
	    XEvent *event,
	    String *params,
	    Cardinal *num_params)
{
    Window child, root;
    unsigned mask, dummy;
    int child_y, child_x;
    int root_y, root_x;
    Boolean done = False;
    XmMotionBuffer mb;

    if (event->type == MotionNotify)
	XdbDebug(__FILE__, w, "_XmDCMotion: Motion\n");
    else if (event->type == EnterNotify)
	XdbDebug(__FILE__, w, "_XmDCMotion: Enter\n");
    else if (event->type == LeaveNotify)
	XdbDebug(__FILE__, w, "_XmDCMotion: Leave\n");

    XdbDebug(__FILE__, w, "DCMotion: w: %08x sw: %08x\n",
	     event->xbutton.window, event->xbutton.subwindow);

    root = DC_CurrWmRoot(w);

    mb.curReceiver = DC_CurrReceiverInfo(w);
    mb.num_motions = 0;
    mb.max_motions = 0;

    add_motion(w, &mb, event);

    mask = ButtonMotionMask|ButtonPressMask|ButtonReleaseMask;

    while (!done && XCheckMaskEvent(XtDisplay(w), mask, event)) {

	XdbDebug(__FILE__, w, "DCMotionLoop 1\n");

	switch (event->type) {
	case ButtonPress:
	case ButtonRelease:
	case KeyPress:
	case KeyRelease:
	    done = True;
	default:
	    done = False;
	}

	if (!done)
	    add_motion(w, &mb, event);
	else {
	    XdbDebug(__FILE__, w, "Put back button or key event: 1.\n");
	    XPutBackEvent(XtDisplay(w), event);
	}

    }

    XQueryPointer(XtDisplay(w), root, &root,
		  &child, &root_x, &root_y, &child_x, &child_y,
		  &dummy);

    while (!done && XCheckMaskEvent(XtDisplay(w), mask, event)) {

	XdbDebug(__FILE__, w, "DCMotionLoop 2\n");

	switch (event->type) {
	case ButtonPress:
	case ButtonRelease:
	case KeyPress:
	case KeyRelease:
	    done = True;
	default:
	    done = False;
	}

	if (!done)
	    add_motion(w, &mb, event);
	else {
	    XdbDebug(__FILE__, w, "Put back button or key event: 1.\n");
	    XPutBackEvent(XtDisplay(w), event);
	}

    }

    motion_hysteresis(w, &mb);

    XFlush(XtDisplay(w));
}

/*
 * terminate with drop
 */
static void 
_XmDCFinish(Widget w,
	    XEvent *event,
	    String *params,
	    Cardinal *num_params)
{
    XdbDebug(__FILE__, w, "_XmDCFinish\n");

    DC_DragCompletionStatus(w) = XmDROP;
    drag_finish(w, event);
}

/*
 * terminate with help
 */
static void 
_XmDCHelp(Widget w,
	  XEvent *event,
	  String *params,
	  Cardinal *num_params)
{
    XdbDebug(__FILE__, w, "_XmDCHelp\n");

    DC_DragCompletionStatus(w) = XmDROP_HELP;

    drag_finish(w, event);
}

/* 
 * We have to allowevents here or else if someone is dragging with
 * button two down and hits button one, we freeze.
 */
static void 
_XmDCIgnore(Widget w,
	    XEvent *event,
	    String *params,
	    Cardinal *num_params)
{
    XdbDebug(__FILE__, w, "_XmDCIgnore\n");

    XAllowEvents(XtDisplay(w), SyncPointer, event->xbutton.time);
}

/*
 * array is dragInitiator (7) by dragReceiver (6).  Matrix comes from
 * 1-371 in OSF/Motif Programmer's Reference
 * Order is:
 *  XmDRAG_NONE,
 *  XmDRAG_DROP_ONLY,
 *  XmDRAG_PREFER_PREREGISTER,
 *  XmDRAG_PREREGISTER,
 *  XmDRAG_PREFER_DYNAMIC,
 *  XmDRAG_DYNAMIC,
 *  XmDRAG_PREFER_RECEIVER
 */
static unsigned char
protocolStyle[XmDRAG_PREFER_RECEIVER+1][XmDRAG_DYNAMIC+1] = {
    /* dragInitiator is XmDRAG_NONE */
    { XmDRAG_NONE,               XmDRAG_NONE,              XmDRAG_NONE,
      XmDRAG_NONE,               XmDRAG_NONE,              XmDRAG_NONE },

    /* dragInitiator is XmDRAG_DROP_ONLY */
    { XmDRAG_NONE,               XmDRAG_DROP_ONLY,         XmDRAG_DROP_ONLY,
      XmDRAG_DROP_ONLY,          XmDRAG_DROP_ONLY,         XmDRAG_DROP_ONLY },

    /* dragInitiator is XmDRAG_PREFER_PREREGISTER */
    { XmDRAG_NONE,               XmDRAG_DROP_ONLY,         XmDRAG_PREREGISTER,
      XmDRAG_PREREGISTER,        XmDRAG_PREREGISTER,       XmDRAG_DYNAMIC },

    /* dragInitiator is XmDRAG_PREREGISTER */
    { XmDRAG_NONE,               XmDRAG_DROP_ONLY,         XmDRAG_PREREGISTER,
      XmDRAG_PREREGISTER,        XmDRAG_PREREGISTER,       XmDRAG_DROP_ONLY },

    /* dragInitiator is XmDRAG_PREFER_DYNAMIC */
    { XmDRAG_NONE,               XmDRAG_DROP_ONLY,         XmDRAG_DYNAMIC,
      XmDRAG_PREREGISTER,        XmDRAG_DYNAMIC,           XmDRAG_DYNAMIC },

    /* dragInitiator is XmDRAG_DYNAMIC */
    { XmDRAG_NONE,               XmDRAG_DROP_ONLY,         XmDRAG_DYNAMIC,
      XmDRAG_DROP_ONLY,          XmDRAG_DYNAMIC,           XmDRAG_DYNAMIC },

    /* dragInitiator is XmDRAG_PREFER_RECEIVER */
    { XmDRAG_NONE,               XmDRAG_DROP_ONLY,         XmDRAG_PREREGISTER,
      XmDRAG_PREREGISTER,        XmDRAG_DYNAMIC,           XmDRAG_DYNAMIC },
};

unsigned char
_XmGetActiveProtocolStyle(Widget w)
{
    unsigned char dis, drs;

    XdbDebug(__FILE__, w, "_XmGetActiveProtocolStyle\n");

    dis = Display_DragInitiatorProtocolStyle(_XmDCtoDD(w));
    drs = Display_DragReceiverProtocolStyle(_XmDCtoDD(w));

    if (!DC_SourceIsExternal(w)) {

	if (DC_CurrReceiverInfo(w))
	    drs = DC_CurrReceiverInfo(w)->dragProtocolStyle;

	return protocolStyle[dis][drs];
    }
    else {
	/* unless and until I can figure out preregister, this is going
	 * to stay this way. */
	switch (drs) {
	case XmDRAG_DROP_ONLY:
	case XmDRAG_PREFER_PREREGISTER:
	case XmDRAG_PREREGISTER:
	case XmDRAG_PREFER_DYNAMIC:
	case XmDRAG_DYNAMIC:
	    return XmDRAG_DYNAMIC;

	case XmDRAG_NONE:
	default:
	    return XmDRAG_NONE;
	}
    }
}

XmDragReceiverInfo
_XmAllocReceiverInfo(XmDragContext dc)
{
    XmDragReceiverInfo ri;

    XdbDebug(__FILE__, NULL, "_XmAllocReceiverInfo\n");

    if (DC_CurrReceiverInfo(dc) == NULL) {
	DC_ReceiverInfos(dc) =
		(XmDragReceiverInfo)XtMalloc(sizeof(XmDragReceiverInfoStruct)
						* 8);
	DC_MaxReceiverInfos(dc) = 8;
    }

    if (DC_NumReceiverInfos(dc) == DC_MaxReceiverInfos(dc)) {
	DC_MaxReceiverInfos(dc) <<= 1;
	DC_ReceiverInfos(dc) =
		(XmDragReceiverInfo)XtRealloc((XtPointer)DC_ReceiverInfos(dc),
					      sizeof(XmDragReceiverInfoStruct)
						* DC_MaxReceiverInfos(dc));
    }

    ri = &DC_ReceiverInfos(dc)[DC_NumReceiverInfos(dc)];
    DC_NumReceiverInfos(dc)++;

    return ri;
}
