/*
 * basic Xdnd support for Motif 2.x
 * 
 * Receive drops only.  Works fine in parallel with Motif DnD.
 *
 * Initiate drags is probably hard do do without breaking Motif DnD or
 * heavily mucking with the Motif internals.  Highlighting seems to be
 * non-trivial too.
 *
 * Usage:
 *    (1) register XdndAction as "Xdnd"
 *    (2) register Widgets using XdndDropSink (acts like XmeDropSink
 *        for Motif DnD)
 *    (3) the transfer callback functions have to call
 *        XdndDropFinished() when they are done (i.e. after calling
 *        XmTransferDone)
 *
 * Data transfer is done using the usual Motif 2.x way, using UTM.
 * Read: XmNdestinationCallback will be called, with
 * XmDestinationCallbackStruct->selection set to XdndSelection.
 *
 * It is up to the application to handle the Xdnd MIME targets
 * correctly, i.e. accept both "TEXT" and "text/plain" targets for
 * example.  Otherwise Xdnd support shouldn't need much work if Motif
 * DnD support is present already.
 *
 * Known problems:
 *   - Not working with KDE 2.x as they don't provide a TARGETS
 *     target (which IMO is illegal, see ICCCM specs).
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include <X11/Intrinsic.h>
#include <X11/StringDefs.h>
#include <Xm/Xm.h>
#include <Xm/Transfer.h>
#include <Xm/TransferP.h>

#include "xdnd.h"

/* ---------------------------------------------------------------------- */

int xdnd_debug = 0;

static Atom XdndAware;
static Atom XdndTypeList;
static Atom XdndSelection;

static Atom XdndEnter;
static Atom XdndPosition;
static Atom XdndStatus;
static Atom XdndLeave;
static Atom XdndDrop;
static Atom XdndFinished;

static Atom XdndActionCopy;
static Atom XdndActionMove;
static Atom XdndActionLink;
static Atom XdndActionAsk;
static Atom XdndActionPrivate;

/* ---------------------------------------------------------------------- */

static void XdndInit(Display *dpy)
{
    if (XdndAware)
	return;

    XdndAware         = XInternAtom(dpy, "XdndAware",         False);
    XdndTypeList      = XInternAtom(dpy, "XdndTypeList",      False);
    XdndSelection     = XInternAtom(dpy, "XdndSelection",     False);

    /* client messages */
    XdndEnter         = XInternAtom(dpy, "XdndEnter",         False);
    XdndPosition      = XInternAtom(dpy, "XdndPosition",      False);
    XdndStatus        = XInternAtom(dpy, "XdndStatus",        False);
    XdndLeave         = XInternAtom(dpy, "XdndLeave",         False);
    XdndDrop          = XInternAtom(dpy, "XdndDrop",          False);
    XdndFinished      = XInternAtom(dpy, "XdndFinished",      False);

    /* actions */
    XdndActionCopy    = XInternAtom(dpy, "XdndActionCopy",    False);
    XdndActionMove    = XInternAtom(dpy, "XdndActionMove",    False);
    XdndActionLink    = XInternAtom(dpy, "XdndActionLink",    False);
    XdndActionAsk     = XInternAtom(dpy, "XdndActionAsk",     False);
    XdndActionPrivate = XInternAtom(dpy, "XdndActionPrivate", False);
}

static void
init_window(Widget widget)
{
    int version = 4;

    /* window */
    XChangeProperty(XtDisplay(widget),XtWindow(widget),
		    XdndAware, XA_ATOM, 32, PropModeReplace,
		    (XtPointer)&version, 1);

    /* shell */
    while (!XtIsShell(widget))
	widget = XtParent(widget);

    XChangeProperty(XtDisplay(widget),XtWindow(widget),
		    XdndAware, XA_ATOM, 32, PropModeReplace,
		    (XtPointer)&version, 1);
    XtOverrideTranslations(widget, XtParseTranslationTable
                           ("<Message>XdndEnter:    Xdnd()\n"
			    "<Message>XdndPosition: Xdnd()\n"
			    "<Message>XdndLeave:    Xdnd()\n"
			    "<Message>XdndDrop:     Xdnd()"));
}

static Widget
find_window(Widget widget, int wx, int wy, int rx, int ry)
{
    WidgetList children;
    Cardinal nchildren;
    Dimension x,y,w,h;
    Widget found = NULL;
    int i;

    nchildren = 0;
    XtVaGetValues(widget,XtNchildren,&children,
                  XtNnumChildren,&nchildren,NULL);
    fprintf(stderr,"findwindow %s\n",XtName(widget));
    for (i = nchildren-1; i >= 0; i--) {
	XtVaGetValues(children[i],XtNx,&x,XtNy,&y,
		      XtNwidth,&w,XtNheight,&h,NULL);
	if (!XtIsManaged(children[i]))
	    continue;
	if (XtIsSubclass(children[i],xmGadgetClass))
	    continue;
	if (rx < wx+x || rx > wx+x+w)
	    continue;
	if (ry < wy+y || ry > wy+y+h)
	    continue;
	found = children[i];
	break;
    }
    if (found) {
	fprintf(stderr,"  more: %s\n",XtName(found));
	return find_window(found,wx+x,wy+y,rx,ry);
    }
    fprintf(stderr,"  done: %s\n",XtName(widget));
    return widget;
}

static int
check_window(Widget widget)
{
    Atom type;
    int format,rc;
    unsigned long nitems,rest;
    unsigned long *ldata;

    rc = XGetWindowProperty(XtDisplay(widget),XtWindow(widget),
			    XdndAware,0,64,False,AnyPropertyType,
			    &type,&format,&nitems,&rest,
			    (XtPointer)&ldata);
    XFree(ldata);
    return rc == Success && nitems > 0;
}

static XtEnum get_operation(Atom action)
{
    if (XdndActionCopy == action)
	return XmCOPY;
    if (XdndActionLink == action)
	return XmLINK;
    if (XdndActionMove == action)
	return XmMOVE;
    return 0;
}

static Atom get_action(XtEnum operation)
{
    if (XmCOPY == operation)
	return XdndActionCopy;
    if (XmLINK == operation)
	return XdndActionLink;
    if (XmMOVE == operation)
	return XdndActionMove;
    return None;
}

static void
XdndEvent(Widget widget, XtPointer clientdata, XEvent *event, Boolean *cont)
{
    switch(event->type) {
    case MapNotify:
	init_window(widget);
	break;
    }
}

/* ---------------------------------------------------------------------- */

/*
 * not very nice this way, but as you can hardly have two drags at the
 * same time with one pointer only it should be fine ...
 */
static Widget target;
static int target_ok,drop_ok;
static Window source;
static XtEnum operation;

void
XdndAction(Widget widget, XEvent *event,
	   String *params, Cardinal *num_params)
{
    char *name;
    XEvent reply;
        
    if (NULL == event)
	return;
    if (ClientMessage != event->type)
	return;

    if (XdndEnter == event->xclient.message_type) {
	if (xdnd_debug)
	    fprintf(stderr,"Xdnd: Enter: win=0x%lx ver=%ld more=%s\n",
		    event->xclient.data.l[0],
		    event->xclient.data.l[1] >> 24,
		    (event->xclient.data.l[1] & 1) ? "yes" : "no");
    }

    if (XdndPosition == event->xclient.message_type) {
	source = event->xclient.data.l[0];
	target = find_window(widget,0,0,
			     event->xclient.data.l[2] >> 16,
			     event->xclient.data.l[2] & 0xffff);
	target_ok = check_window(target);
	if (target_ok) {
	    operation = get_operation(event->xclient.data.l[4]);
	    operation = XmCOPY;  /* FIXME */
	    drop_ok   = 1;
	} else {
	    operation = 0;
	    drop_ok   = 0;
	}
	if (xdnd_debug) {
	    name = NULL;
	    if (event->xclient.data.l[4])
		name=XGetAtomName(XtDisplay(widget),event->xclient.data.l[4]);
	    fprintf(stderr,"Xdnd: Position: win=0x%lx pos=+%ld+%ld ts=%ld "
		    "ac=%s op=%d widget=%s drop=%s\n",
		    event->xclient.data.l[0],
		    event->xclient.data.l[2] >> 16,
		    event->xclient.data.l[2] & 0xffff,
		    event->xclient.data.l[3],
		    name,operation,
		    XtName(target),target_ok ? "yes" : "no");
	    if (name)
		XFree(name);
	}
	memset(&reply,0,sizeof(reply));
	reply.xany.type = ClientMessage;
	reply.xany.display = XtDisplay(widget);
	reply.xclient.window = event->xclient.data.l[0];
	reply.xclient.message_type = XdndStatus;
	reply.xclient.format = 32;
	reply.xclient.data.l[0] = XtWindow(widget);
	reply.xclient.data.l[1] = drop_ok ? 1 : 0;
	reply.xclient.data.l[4] = get_action(operation);
	XSendEvent(XtDisplay(widget),reply.xclient.window,0,0,&reply);
    }

    if (XdndDrop == event->xclient.message_type) {
	source = event->xclient.data.l[0];
	if (xdnd_debug)
	    fprintf(stderr,"Xdnd: Drop: win=0x%lx ts=%ld\n",
		    event->xclient.data.l[0],
		    event->xclient.data.l[2]);
	XmeNamedSink(target,XdndSelection,XmCOPY,NULL,
		     XtLastTimestampProcessed(XtDisplay(widget)));
    }

    if (XdndLeave == event->xclient.message_type) {
	source = 0;
	if (xdnd_debug)
	    fprintf(stderr,"Xdnd: Leave: win=0x%lx\n",
		    event->xclient.data.l[0]);
    }
}

void XdndDropFinished(Widget widget, XmSelectionCallbackStruct *scs)
{
    XEvent reply;

    if (XdndSelection != scs->selection)
	return;
    if (0 == source)
	return;

    if (xdnd_debug)
	fprintf(stderr,"Xdnd: sending Finished (0x%lx)\n",source);
    memset(&reply,0,sizeof(reply));
    reply.xany.type = ClientMessage;
    reply.xany.display = XtDisplay(widget);
    reply.xclient.window = source;
    reply.xclient.message_type = XdndFinished;
    reply.xclient.format = 32;
    while (!XtIsShell(widget))
	widget = XtParent(widget);
    reply.xclient.data.l[0] = XtWindow(widget);
    XSendEvent(XtDisplay(widget),reply.xclient.window,0,0,&reply);
    source = 0;
}

void XdndDropSink(Widget widget)
{
    XdndInit(XtDisplay(widget));

    if (XtWindow(widget))
	init_window(widget);
    XtAddEventHandler(widget,StructureNotifyMask,True,XdndEvent,NULL);
}
