#include "wconfig.h"
#include "WINGsP.h"

#include <X11/Xatom.h>
#include <X11/cursorfont.h>
#ifdef SHAPE
#include <X11/extensions/shape.h>
#endif



#define XDND_DESTINATION_RESPONSE_MAX_DELAY 10000
#define MIN_X_MOVE_OFFSET 5
#define MIN_Y_MOVE_OFFSET 5
#define MAX_SLIDEBACK_ITER 15

#define VERSION_INFO(dragInfo) dragInfo->protocolVersion
#define XDND_PROPERTY_FORMAT 32
#define XDND_ACTION_DESCRIPTION_FORMAT 8

#define XDND_SOURCE_INFO(dragInfo) dragInfo->sourceInfo
#define XDND_DEST_WIN(dragInfo) dragInfo->sourceInfo->destinationWindow
#define XDND_SOURCE_ACTION(dragInfo) dragInfo->sourceAction
#define XDND_DEST_ACTION(dragInfo) dragInfo->destinationAction
#define XDND_SOURCE_VIEW(dragInfo) dragInfo->sourceInfo->sourceView
#define XDND_SOURCE_STATE(dragInfo) dragInfo->sourceInfo->state
#define XDND_SELECTION_PROCS(dragInfo) dragInfo->sourceInfo->selectionProcs
#define XDND_DRAG_ICON(dragInfo) dragInfo->sourceInfo->icon
#define XDND_MOUSE_OFFSET(dragInfo) dragInfo->sourceInfo->mouseOffset
#define XDND_DRAG_CURSOR(dragInfo) dragInfo->sourceInfo->dragCursor
#define XDND_DRAG_ICON_POS(dragInfo) dragInfo->sourceInfo->imageLocation
#define XDND_NO_POS_ZONE(dragInfo) dragInfo->sourceInfo->noPositionMessageZone
#define XDND_TIMESTAMP(dragInfo) dragInfo->timestamp
#define XDND_3_TYPES(dragInfo) dragInfo->sourceInfo->firstThreeTypes
#define XDND_SOURCE_VIEW_STORED(dragInfo) dragInfo->sourceInfo != NULL \
    && dragInfo->sourceInfo->sourceView != NULL


static WMHandlerID dndSourceTimer = NULL;


static void* idleState(WMView *srcView, XClientMessageEvent *event,
                       WMDraggingInfo *info);
static void* dropAllowedState(WMView *srcView, XClientMessageEvent *event,
                              WMDraggingInfo *info);
static void* finishDropState(WMView *srcView, XClientMessageEvent *event,
                             WMDraggingInfo *info);

#ifdef XDND_DEBUG
static const char*
stateName(W_DndState *state)
{
    if (state == NULL)
        return "no state defined";

    if (state == idleState)
        return "idleState";

    if (state == dropAllowedState)
        return "dropAllowedState";

    if (state == finishDropState)
        return "finishDropState";

    return "unknown state";
}
#endif


static WMScreen*
sourceScreen(WMDraggingInfo *info)
{
    return W_VIEW_SCREEN(XDND_SOURCE_VIEW(info));
}


static void
endDragProcess(WMDraggingInfo *info, Bool deposited)
{
    WMView *view = XDND_SOURCE_VIEW(info);
    WMScreen *scr = W_VIEW_SCREEN(XDND_SOURCE_VIEW(info));

    /* free selection handler while view exists */
    WMDeleteSelectionHandler(view,
                             scr->xdndSelectionAtom,
                             CurrentTime);
    wfree(XDND_SELECTION_PROCS(info));

    if (XDND_DRAG_CURSOR(info) != None) {
        XFreeCursor(scr->display,
                    XDND_DRAG_CURSOR(info));
        XDND_DRAG_CURSOR(info) = None;
    }

    if (view->dragSourceProcs->endedDrag != NULL) {
        /* this can destroy source view (with a "move" action for example) */
        view->dragSourceProcs->endedDrag(view, &XDND_DRAG_ICON_POS(info),
                                         deposited);
    }

    /* clear remaining draggging infos */
    wfree(XDND_SOURCE_INFO(info));
    XDND_SOURCE_INFO(info) = NULL;
}


/* ----- drag cursor ----- */
static void
initDragCursor(WMDraggingInfo *info)
{
    WMScreen *scr = sourceScreen(info);
    XColor cursorFgColor, cursorBgColor;

    /* green */
    cursorFgColor.red = 0x4500;
    cursorFgColor.green = 0xb000;
    cursorFgColor.blue = 0x4500;

    /* white */
    cursorBgColor.red = 0xffff;
    cursorBgColor.green = 0xffff;
    cursorBgColor.blue = 0xffff;

    XDND_DRAG_CURSOR(info) = XCreateFontCursor(scr->display, XC_left_ptr);
    XRecolorCursor(scr->display,
                   XDND_DRAG_CURSOR(info),
                   &cursorFgColor,
                   &cursorBgColor);

    XFlush(scr->display);
}

static void
recolorCursor(WMDraggingInfo *info, Bool dropIsAllowed)
{
    WMScreen *scr = sourceScreen(info);

    if (dropIsAllowed) {
        XDefineCursor(scr->display,
                      scr->rootWin,
                      XDND_DRAG_CURSOR(info));
    } else {
        XDefineCursor(scr->display,
                      scr->rootWin,
                      scr->defaultCursor);
    }

    XFlush(scr->display);
}
/* ----- end of drag cursor ----- */


/* ----- selection procs ----- */
static WMData*
convertSelection(WMView *view, Atom selection, Atom target,
                 void *cdata, Atom *type)
{
    WMScreen *scr;
    WMData *data;
    char *typeName;

    scr = W_VIEW_SCREEN(view);
    typeName = XGetAtomName(scr->display, target);

    *type = target;

    if (view->dragSourceProcs->fetchDragData != NULL) {
        data = view->dragSourceProcs->fetchDragData(
                                                    view,
                                                    typeName);
    } else {
        data = NULL;
    }

    if (typeName != NULL)
        XFree(typeName);

    return data;
}


static void
selectionLost(WMView *view, Atom selection, void *cdata)
{
    wwarning("DND selection lost during drag operation...");
}


static void
selectionDone(WMView *view, Atom selection, Atom target, void *cdata)
{
#ifdef XDND_DEBUG
    printf("selection done\n");
#endif
}
/* ----- end of selection procs ----- */


/* ----- visual part ----- */

static Window
makeDragIcon(WMScreen *scr, WMPixmap *pixmap)
{
    Window window;
    WMSize size;
    unsigned long flags;
    XSetWindowAttributes attribs;
    Pixmap pix, mask;

    if (!pixmap) {
        pixmap = scr->defaultObjectIcon;
    }

    size = WMGetPixmapSize(pixmap);
    pix = pixmap->pixmap;
    mask = pixmap->mask;

    flags = CWSaveUnder|CWBackPixmap|CWOverrideRedirect|CWColormap;
    attribs.save_under = True;
    attribs.background_pixmap = pix;
    attribs.override_redirect = True;
    attribs.colormap = scr->colormap;

    window = XCreateWindow(scr->display, scr->rootWin, 0, 0, size.width,
                           size.height, 0, scr->depth, InputOutput,
                           scr->visual, flags, &attribs);

#ifdef SHAPE

    if (mask) {
        XShapeCombineMask(scr->display, window, ShapeBounding, 0, 0, mask,
                          ShapeSet);
    }
#endif

    return window;
}


static void
slideWindow(Display *dpy, Window win, int srcX, int srcY, int dstX, int dstY)
{
    double x, y, dx, dy;
    int i;
    int iterations;

    iterations = WMIN(MAX_SLIDEBACK_ITER,
                      WMAX(abs(dstX-srcX), abs(dstY-srcY)));

    x = srcX;
    y = srcY;

    dx = (double)(dstX-srcX)/iterations;
    dy = (double)(dstY-srcY)/iterations;

    for (i = 0; i <= iterations; i++) {
        XMoveWindow(dpy, win, x, y);
        XFlush(dpy);

        wusleep(800);

        x += dx;
        y += dy;
    }
}


static int
getInitialDragImageCoord(int viewCoord, int mouseCoord,
                         int viewSize, int iconSize)
{
    if (iconSize >= viewSize) {
        /* center icon coord on view */
        return viewCoord - iconSize/2;
    } else {
        /* try to center icon on mouse pos */

        if (mouseCoord - iconSize/2 <= viewCoord)
            /* if icon was centered on mouse, it would be off view
             thus, put icon left (resp. top) side
             at view (resp. top) side */
            return viewCoord;

        else if (mouseCoord + iconSize/2 >= viewCoord + viewSize)
            /* if icon was centered on mouse, it would be off view
             thus, put icon right (resp. bottom) side
             at view right (resp. bottom) side */
            return viewCoord + viewSize - iconSize;

        else
            return mouseCoord - iconSize/2;
    }

}

static void
initDragImagePos(WMView *view, WMDraggingInfo *info, XEvent *event)
{
    WMSize iconSize = WMGetPixmapSize(view->dragImage);
    WMSize viewSize = WMGetViewSize(view);
    WMPoint viewPos;
    Window foo;

    XTranslateCoordinates(W_VIEW_SCREEN(view)->display,
                          WMViewXID(view), W_VIEW_SCREEN(view)->rootWin,
                          0, 0, &(viewPos.x), &(viewPos.y),
                          &foo);

    /* set icon pos */
    XDND_DRAG_ICON_POS(info).x =
        getInitialDragImageCoord(viewPos.x, event->xmotion.x_root,
                                 viewSize.width, iconSize.width);

    XDND_DRAG_ICON_POS(info).y =
        getInitialDragImageCoord(viewPos.y, event->xmotion.y_root,
                                 viewSize.height, iconSize.height);

    /* set mouse offset relative to icon */
    XDND_MOUSE_OFFSET(info).x =
        event->xmotion.x_root - XDND_DRAG_ICON_POS(info).x;
    XDND_MOUSE_OFFSET(info).y =
        event->xmotion.y_root - XDND_DRAG_ICON_POS(info).y;
}


static void
refreshDragImage(WMView *view, WMDraggingInfo *info)
{
    WMScreen *scr = W_VIEW_SCREEN(view);

    XMoveWindow(scr->display, XDND_DRAG_ICON(info),
                XDND_DRAG_ICON_POS(info).x,
                XDND_DRAG_ICON_POS(info).y);
}


static void
startDragImage(WMView *view, WMDraggingInfo *info, XEvent* event)
{
    WMScreen *scr = W_VIEW_SCREEN(view);

    XDND_DRAG_ICON(info) = makeDragIcon(scr, view->dragImage);
    initDragImagePos(view, info, event);
    refreshDragImage(view, info);
    XMapRaised(scr->display, XDND_DRAG_ICON(info));

    initDragCursor(info);
}


static void
endDragImage(WMDraggingInfo *info, Bool slideBack)
{
    WMView *view = XDND_SOURCE_VIEW(info);
    Display *dpy = W_VIEW_SCREEN(view)->display;

    if (slideBack) {
        WMPoint toLocation;
        Window foo;

        XTranslateCoordinates(W_VIEW_SCREEN(view)->display,
                              WMViewXID(view), W_VIEW_SCREEN(view)->rootWin,
                              0, 0, &(toLocation.x), &(toLocation.y),
                              &foo);

        slideWindow(dpy, XDND_DRAG_ICON(info),
                    XDND_DRAG_ICON_POS(info).x,
                    XDND_DRAG_ICON_POS(info).y,
                    toLocation.x,
                    toLocation.y);
    }

    XDestroyWindow(dpy, XDND_DRAG_ICON(info));
}

/* ----- end of visual part ----- */


/* ----- messages ----- */

/* send a DnD message to the destination window */
static Bool
sendDnDClientMessage(WMDraggingInfo *info, Atom message,
                     unsigned long data1,
                     unsigned long data2,
                     unsigned long data3,
                     unsigned long data4)
{
    Display *dpy = sourceScreen(info)->display;
    Window srcWin = WMViewXID(XDND_SOURCE_VIEW(info));
    Window destWin = XDND_DEST_WIN(info);

    if (! W_SendDnDClientMessage(dpy,
                                 destWin,
                                 message,
                                 srcWin,
                                 data1,
                                 data2,
                                 data3,
                                 data4)) {
        /* drop failed */
        recolorCursor(info, False);
        endDragImage(info, True);
        endDragProcess(info, False);
        return False;
    }

    return True;
}


static Bool
sendEnterMessage(WMDraggingInfo *info)
{
    WMScreen *scr = sourceScreen(info);
    unsigned long data1;

    data1 = (VERSION_INFO(info) << 24)|1; /* 1: support of type list */

    return sendDnDClientMessage(info, scr->xdndEnterAtom,
                                data1,
                                XDND_3_TYPES(info)[0],
                                XDND_3_TYPES(info)[1],
                                XDND_3_TYPES(info)[2]);
}


/*
// this functon doesn't return something in all cases.
// control reaches end of non-void function. fix this -Dan */
static Bool
sendPositionMessage(WMDraggingInfo *info, WMPoint *mousePos)
{
    WMScreen *scr = sourceScreen(info);
    WMRect *noPosZone = &(XDND_NO_POS_ZONE(info));

    if (noPosZone->size.width != 0 || noPosZone->size.height != 0) {
        if (mousePos->x < noPosZone->pos.x
            || mousePos->x > (noPosZone->pos.x + noPosZone->size.width)
            || mousePos->y < noPosZone->pos.y
            || mousePos->y > (noPosZone->pos.y + noPosZone->size.width)) {
            /* send position if out of zone defined by destination */
            return sendDnDClientMessage(info, scr->xdndPositionAtom,
                                        0,
                                        mousePos->x<<16|mousePos->y,
                                        XDND_TIMESTAMP(info),
                                        XDND_SOURCE_ACTION(info));
        }
    } else {
        /* send position on each move */
        return sendDnDClientMessage(info, scr->xdndPositionAtom,
                                    0,
                                    mousePos->x<<16|mousePos->y,
                                    XDND_TIMESTAMP(info),
                                    XDND_SOURCE_ACTION(info));
    }
}


static Bool
sendLeaveMessage(WMDraggingInfo *info)
{
    WMScreen *scr = sourceScreen(info);

    return sendDnDClientMessage(info, scr->xdndLeaveAtom,
                                0, 0, 0, 0);
}


static Bool
sendDropMessage(WMDraggingInfo *info)
{
    WMScreen *scr = sourceScreen(info);

    return sendDnDClientMessage(info,
                                scr->xdndDropAtom,
                                0,
                                XDND_TIMESTAMP(info),
                                0, 0);
}

/* ----- end of messages ----- */


static Atom*
getTypeAtomList(WMScreen *scr, WMView *view, int* count)
{
    WMArray* types;
    Atom* typeAtoms;
    int i;

    types = view->dragSourceProcs->dropDataTypes(view);

    if (types != NULL) {
        *count = WMGetArrayItemCount(types);
        if (*count > 0) {
            typeAtoms = wmalloc((*count)*sizeof(Atom));
            for (i=0; i < *count; i++) {
                typeAtoms[i] = XInternAtom(scr->display,
                                           WMGetFromArray(types, i),
                                           False);
            }

            /* WMFreeArray(types); */
            return typeAtoms;
        }

        /* WMFreeArray(types); */
    }

    *count = 1;
    typeAtoms = wmalloc(sizeof(Atom));
    *typeAtoms = None;

    return typeAtoms;
}


static void
registerDropTypes(WMScreen *scr, WMView *view, WMDraggingInfo *info)
{
    Atom* typeList;
    int i, count;

    typeList = getTypeAtomList(scr, view, &count);

    /* store the first 3 types */
    for(i=0; i < 3 && i < count; i++)
        XDND_3_TYPES(info)[i] = typeList[i];

    for(; i < 3; i++)
        XDND_3_TYPES(info)[i] = None;


    /* store the entire type list */
    XChangeProperty(scr->display,
                    WMViewXID(view),
                    scr->xdndTypeListAtom,
                    XA_ATOM,
                    XDND_PROPERTY_FORMAT,
                    PropModeReplace,
                    (unsigned char*) typeList,
                    count);
}


static void
registerOperationList(WMScreen *scr, WMView *view, WMArray* operationArray)
{
    Atom* actionList;
    WMDragOperationType operation;
    int count = WMGetArrayItemCount(operationArray);
    int i;

    actionList = wmalloc(sizeof(Atom)*count);

    for(i=0; i < count; i++) {
        operation = WMGetDragOperationItemType(
                                               WMGetFromArray(operationArray, i));
        actionList[i] = W_OperationToAction(scr, operation);
    }

    XChangeProperty(scr->display,
                    WMViewXID(view),
                    scr->xdndActionListAtom,
                    XA_ATOM,
                    XDND_PROPERTY_FORMAT,
                    PropModeReplace,
                    (unsigned char*) actionList,
                    count);
}

static void
registerDescriptionList(WMScreen *scr, WMView *view, WMArray* operationArray)
{
    char *text, *textListItem, *textList;
    int count = WMGetArrayItemCount(operationArray);
    int i;
    int size = 0;

    /* size of XA_STRING info */
    for(i=0; i < count; i++) {
        size += strlen(WMGetDragOperationItemText(
                                                  WMGetFromArray(operationArray, i))) + 1; /* +1 = +NULL */
    }

    /* create text list */
    textList = wmalloc(size);
    textListItem = textList;

    for(i=0; i < count; i++) {
        text = WMGetDragOperationItemText(WMGetFromArray(operationArray, i));
        strcpy(textListItem, text);

        /* to next text offset */
        textListItem  = &(textListItem[strlen(textListItem) + 1]);
    }

    XChangeProperty(scr->display,
                    WMViewXID(view),
                    scr->xdndActionDescriptionAtom,
                    XA_STRING,
                    XDND_ACTION_DESCRIPTION_FORMAT,
                    PropModeReplace,
                    (unsigned char*) textList,
                    size);
}

/* called if wanted operation is WDOperationAsk */
static void
registerSupportedOperations(WMView *view)
{
    WMScreen *scr = W_VIEW_SCREEN(view);
    WMArray* operationArray;

    operationArray = view->dragSourceProcs->askedOperations(view);

    registerOperationList(scr, view, operationArray);
    registerDescriptionList(scr, view, operationArray);

    /* WMFreeArray(operationArray); */
}


static void
initSourceDragInfo(WMView *sourceView, WMDraggingInfo *info)
{
    WMRect emptyZone;

    XDND_SOURCE_INFO(info) = (W_DragSourceInfo*) wmalloc(sizeof(W_DragSourceInfo));

    XDND_SOURCE_VIEW(info) = sourceView;
    XDND_DEST_WIN(info) = None;
    XDND_DRAG_ICON(info) = None;

    XDND_SOURCE_ACTION(info) = W_OperationToAction(
                                                   W_VIEW_SCREEN(sourceView),
                                                   sourceView->dragSourceProcs->wantedDropOperation(sourceView));

    XDND_DEST_ACTION(info) = None;

    XDND_SOURCE_STATE(info) = idleState;

    emptyZone.pos = wmkpoint(0, 0);
    emptyZone.size = wmksize(0, 0);
    XDND_NO_POS_ZONE(info) = emptyZone;
}


/*
 Returned array is destroyed after dropDataTypes call
 */
static WMArray*
defDropDataTypes(WMView *self)
{
    return NULL;
}


static WMDragOperationType
defWantedDropOperation(WMView *self)
{
    return WDOperationNone;
}


/*
 Must be defined if wantedDropOperation return WDOperationAsk
 (useless otherwise).
 Return a WMDragOperationItem array (destroyed after call).
 A WMDragOperationItem links a label to an operation.
 static WMArray*
 defAskedOperations(WMView *self); */


static Bool
defAcceptDropOperation(WMView *self, WMDragOperationType allowedOperation)
{
    return False;
}


static void
defBeganDrag(WMView *self, WMPoint *point)
{
}


static void
defEndedDrag(WMView *self, WMPoint *point, Bool deposited)
{
}


/*
 Returned data is not destroyed
 */
static WMData*
defFetchDragData(WMView *self, char *type)
{
    return NULL;
}


void
WMSetViewDragSourceProcs(WMView *view, WMDragSourceProcs *procs)
{
    if (view->dragSourceProcs)
        wfree(view->dragSourceProcs);
    view->dragSourceProcs = wmalloc(sizeof(WMDragSourceProcs));

    *view->dragSourceProcs = *procs;

    if (procs->dropDataTypes == NULL)
        view->dragSourceProcs->dropDataTypes = defDropDataTypes;

    if (procs->wantedDropOperation == NULL)
        view->dragSourceProcs->wantedDropOperation = defWantedDropOperation;

    /*
     Note: askedOperations can be NULL, if wantedDropOperation never returns
     WDOperationAsk.
     */

    if (procs->acceptDropOperation == NULL)
        view->dragSourceProcs->acceptDropOperation = defAcceptDropOperation;

    if (procs->beganDrag == NULL)
        view->dragSourceProcs->beganDrag = defBeganDrag;

    if (procs->endedDrag == NULL)
        view->dragSourceProcs->endedDrag = defEndedDrag;

    if (procs->fetchDragData == NULL)
        view->dragSourceProcs->fetchDragData = defFetchDragData;
}


static Bool
isXdndAware(WMScreen *scr, Window win)
{
    Atom type;
    int format;
    unsigned long count, remain;
    unsigned char *winXdndVersion;

    if (win == None)
        return False;

    XGetWindowProperty(scr->display, win, scr->xdndAwareAtom,
                       0, 1, False, XA_ATOM, &type, &format,
                       &count, &remain, &winXdndVersion);

    if (type != XA_ATOM
        || format != XDND_PROPERTY_FORMAT
        || count == 0 || !winXdndVersion) {
        if (winXdndVersion)
            XFree(winXdndVersion);
        return False;
    }

    XFree(winXdndVersion);
    return (count == 1); /* xdnd version is set */
}


static Window*
windowChildren(Display *dpy, Window win, unsigned *nchildren)
{
    Window *children;
    Window foo, bar;

    if (!XQueryTree(dpy, win, &foo, &bar, &children, nchildren)) {
        *nchildren = 0;
        return NULL;
    } else
        return children;
}

static Window
lookForAwareWindow(WMScreen *scr, WMPoint *mousePos, Window win)
{
    int tmpx, tmpy;
    Window child;

    /* Since xdnd v3, only the toplevel window should be aware */
    if (isXdndAware(scr, win))
        return win;

    /* inspect child under pointer */
    if (XTranslateCoordinates(scr->display, scr->rootWin, win,
                              mousePos->x, mousePos->y, &tmpx, &tmpy,
                              &child)) {
        if (child == None)
            return None;
        else
            return lookForAwareWindow(scr, mousePos, child);
    }

    return None;
}


static Window
findDestination(WMDraggingInfo *info, WMPoint *mousePos)
{
    WMScreen *scr = sourceScreen(info);
    unsigned nchildren;
    Window *children = windowChildren(scr->display, scr->rootWin, &nchildren);
    int i;
    XWindowAttributes attr;

    if (isXdndAware(scr, scr->rootWin))
        return scr->rootWin;

    /* exclude drag icon (and upper) from search */
    for (i = nchildren-1; i >= 0; i--) {
        if (children[i] == XDND_DRAG_ICON(info)) {
            i--;
            break;
        }
    }

    if (i < 0) {
        /* root window has no child under drag icon, and is not xdnd aware. */
        return None;
    }

    /* inspecting children, from upper to lower */
    for (; i >= 0; i--) {
        if (XGetWindowAttributes(scr->display, children[i], &attr)
            && attr.map_state == IsViewable
            && mousePos->x >= attr.x
            && mousePos->y >= attr.y
            && mousePos->x < attr.x + attr.width
            && mousePos->y < attr.y + attr.height) {
            return lookForAwareWindow(scr, mousePos, children[i]);
        }
    }

    /* No child window under drag pointer */
    return None;
}


static void
initMotionProcess(WMView *view, WMDraggingInfo *info,
                  XEvent *event, WMPoint *startLocation)
{
    WMScreen *scr = W_VIEW_SCREEN(view);

    /* take ownership of XdndSelection */
    XDND_SELECTION_PROCS(info) =
        (WMSelectionProcs*) wmalloc(sizeof(WMSelectionProcs));
    XDND_SELECTION_PROCS(info)->convertSelection = convertSelection;
    XDND_SELECTION_PROCS(info)->selectionLost = selectionLost;
    XDND_SELECTION_PROCS(info)->selectionDone = selectionDone;
    XDND_TIMESTAMP(info) = event->xmotion.time;

    if (!WMCreateSelectionHandler(view, scr->xdndSelectionAtom,
                                  CurrentTime,
                                  XDND_SELECTION_PROCS(info), NULL)) {
        wwarning("could not get ownership or DND selection");
        return;
    }

    registerDropTypes(scr, view, info);

    if (XDND_SOURCE_ACTION(info) == W_VIEW_SCREEN(view)->xdndActionAsk)
        registerSupportedOperations(view);

    if (view->dragSourceProcs->beganDrag != NULL) {
        view->dragSourceProcs->beganDrag(view, startLocation);
    }
}


static void
processMotion(WMDraggingInfo *info, Window windowUnderDrag, WMPoint *mousePos)
{
    /* // WMScreen *scr = sourceScreen(info); */
    Window newDestination = findDestination(info, mousePos);

    W_DragSourceStopTimer();

    if (newDestination != XDND_DEST_WIN(info)) {
        recolorCursor(info, False);

        if (XDND_DEST_WIN(info) != None) {
            /* leaving a xdnd window */
            sendLeaveMessage(info);
        }

        XDND_DEST_WIN(info) = newDestination;
        XDND_SOURCE_STATE(info) = idleState;
        XDND_DEST_ACTION(info) = None;
        XDND_NO_POS_ZONE(info).size.width = 0;
        XDND_NO_POS_ZONE(info).size.height = 0;

        if (newDestination != None) {
            /* entering a xdnd window */
            if (! sendEnterMessage(info)) {
                XDND_DEST_WIN(info) = None;
                return;
            }

            W_DragSourceStartTimer(info);
        }
    } else {
        if (XDND_DEST_WIN(info) != None) {
            if (! sendPositionMessage(info, mousePos)) {
                XDND_DEST_WIN(info) = None;
                return;
            }

            W_DragSourceStartTimer(info);
        }
    }
}


static Bool
processButtonRelease(WMDraggingInfo *info)
{
    if (XDND_SOURCE_STATE(info) == dropAllowedState) {
        /* begin drop */
        W_DragSourceStopTimer();

        if (! sendDropMessage(info))
            return False;

        W_DragSourceStartTimer(info);
        return True;
    } else {
        if (XDND_DEST_WIN(info) != None)
            sendLeaveMessage(info);

        W_DragSourceStopTimer();
        return False;
    }
}


Bool
WMIsDraggingFromView(WMView *view)
{
    WMDraggingInfo *info = &W_VIEW_SCREEN(view)->dragInfo;

    return ( XDND_SOURCE_INFO(info) != NULL
            &&  XDND_SOURCE_STATE(info) != finishDropState);
    /* return W_VIEW_SCREEN(view)->dragInfo.sourceInfo != NULL; */
}


void
WMDragImageFromView(WMView *view, XEvent *event)
{
    WMDraggingInfo *info = &W_VIEW_SCREEN(view)->dragInfo;
    WMPoint mouseLocation;

    switch(event->type) {
    case ButtonPress:
        if (event->xbutton.button == Button1) {
            XEvent nextEvent;

            XPeekEvent(event->xbutton.display, &nextEvent);

            /* Initialize only if a drag really begins (avoid clicks) */
            if (nextEvent.type == MotionNotify) {
                initSourceDragInfo(view, info);
            }
        }

        break;

    case ButtonRelease:
        if (WMIsDraggingFromView(view)) {
            Bool dropBegan = processButtonRelease(info);

            recolorCursor(info, False);
            if (dropBegan) {
                endDragImage(info, False);
                XDND_SOURCE_STATE(info) = finishDropState;
            } else {
                /* drop failed */
                endDragImage(info, True);
                endDragProcess(info,False);
            }
        }
        break;

    case MotionNotify:
        if (WMIsDraggingFromView(view)) {
            mouseLocation = wmkpoint(event->xmotion.x_root,
                                     event->xmotion.y_root);

            if (abs(XDND_DRAG_ICON_POS(info).x - mouseLocation.x) >=
                MIN_X_MOVE_OFFSET
                || abs(XDND_DRAG_ICON_POS(info).y - mouseLocation.y) >=
                MIN_Y_MOVE_OFFSET) {
                if (XDND_DRAG_ICON(info) == None) {
                    initMotionProcess(view, info, event, &mouseLocation);
                    startDragImage(view, info, event);
                } else {
                    XDND_DRAG_ICON_POS(info).x =
                        mouseLocation.x - XDND_MOUSE_OFFSET(info).x;
                    XDND_DRAG_ICON_POS(info).y =
                        mouseLocation.y - XDND_MOUSE_OFFSET(info).y;

                    refreshDragImage(view, info);
                    processMotion(info,
                                  event->xmotion.window,
                                  &mouseLocation);
                }
            }
        }
        break;
    }
}


/* Minimal mouse events handler: no right or double-click detection,
 only drag is supported */
static void
dragImageHandler(XEvent *event, void *cdata)
{
    WMView *view = (WMView*)cdata;

    WMDragImageFromView(view, event);
}


/* ----- source states ----- */

#ifdef XDND_DEBUG
static void
traceStatusMsg(Display *dpy, XClientMessageEvent *statusEvent)
{
    printf("Xdnd status message:\n");

    if (statusEvent->data.l[1] & 0x2UL)
        printf("send position on every move\n");
    else {
        int x, y, w, h;
        x = statusEvent->data.l[2] >> 16;
        y = statusEvent->data.l[2] & 0xFFFFL;
        w = statusEvent->data.l[3] >> 16;
        h = statusEvent->data.l[3] & 0xFFFFL;

        printf("send position out of ((%d,%d) , (%d,%d))\n",
               x, y, x+w, y+h);
    }

    if (statusEvent->data.l[1] & 0x1L)
        printf("allowed action: %s\n",
               XGetAtomName(dpy, statusEvent->data.l[4]));
    else
        printf("no action allowed\n");
}
#endif


static void
storeDropAction(WMDraggingInfo *info, Atom destAction)
{
    WMView* sourceView = XDND_SOURCE_VIEW(info);
    WMScreen *scr = W_VIEW_SCREEN(sourceView);

    if (sourceView->dragSourceProcs->acceptDropOperation != NULL) {
        if (sourceView->dragSourceProcs->acceptDropOperation(
                                                             sourceView,
                                                             W_ActionToOperation(scr, destAction)))
            XDND_DEST_ACTION(info) = destAction;
        else
            XDND_DEST_ACTION(info) = None;
    } else {
        XDND_DEST_ACTION(info) = destAction;
    }
}


static void
storeStatusMessageInfos(WMDraggingInfo *info, XClientMessageEvent *statusEvent)
{
    WMRect* noPosZone = &(XDND_NO_POS_ZONE(info));

#ifdef XDND_DEBUG

    traceStatusMsg(sourceScreen(info)->display, statusEvent);
#endif

    if (statusEvent->data.l[1] & 0x2UL) {
        /* bit 1 set: destination wants position messages on every move */
        noPosZone->size.width = 0;
        noPosZone->size.height = 0;
    } else {
        /* don't send another position message while in given rectangle */
        noPosZone->pos.x = statusEvent->data.l[2] >> 16;
        noPosZone->pos.y = statusEvent->data.l[2] & 0xFFFFL;
        noPosZone->size.width = statusEvent->data.l[3] >> 16;
        noPosZone->size.height = statusEvent->data.l[3] & 0xFFFFL;
    }

    if ((statusEvent->data.l[1] & 0x1L) || statusEvent->data.l[4] != None) {
        /* destination accept drop */
        storeDropAction(info, statusEvent->data.l[4]);
    } else {
        XDND_DEST_ACTION(info) = None;
    }
}


static void*
idleState(WMView *view, XClientMessageEvent *event, WMDraggingInfo *info)
{
    WMScreen *scr;
    Atom destMsg = event->message_type;

    scr = W_VIEW_SCREEN(view);

    if (destMsg == scr->xdndStatusAtom) {
        storeStatusMessageInfos(info, event);

        if (XDND_DEST_ACTION(info) != None) {
            recolorCursor(info, True);
            W_DragSourceStartTimer(info);
            return dropAllowedState;
        } else {
            /* drop denied */
            recolorCursor(info, False);
            return idleState;
        }
    }

    if (destMsg == scr->xdndFinishedAtom) {
        wwarning("received xdndFinishedAtom before drop began");
    }

    W_DragSourceStartTimer(info);
    return idleState;
}


static void*
dropAllowedState(WMView *view, XClientMessageEvent *event, WMDraggingInfo *info)
{
    WMScreen *scr = W_VIEW_SCREEN(view);
    Atom destMsg = event->message_type;

    if (destMsg == scr->xdndStatusAtom) {
        storeStatusMessageInfos(info, event);

        if (XDND_DEST_ACTION(info) == None) {
            /* drop denied */
            recolorCursor(info, False);
            return idleState;
        }
    }

    W_DragSourceStartTimer(info);
    return dropAllowedState;
}


static void*
finishDropState(WMView *view, XClientMessageEvent *event, WMDraggingInfo *info)
{
    WMScreen *scr = W_VIEW_SCREEN(view);
    Atom destMsg = event->message_type;

    if (destMsg == scr->xdndFinishedAtom) {
        endDragProcess(info, True);
        return NULL;
    }

    W_DragSourceStartTimer(info);
    return finishDropState;
}
/* ----- end of source states ----- */


/* ----- source timer ----- */
static void
dragSourceResponseTimeOut(void *source)
{
    WMView *view = (WMView*)source;
    WMDraggingInfo *info = &(W_VIEW_SCREEN(view)->dragInfo);

    wwarning("delay for drag destination response expired");
    sendLeaveMessage(info);

    recolorCursor(info, False);
    if (XDND_SOURCE_STATE(info) == finishDropState) {
        /* drop failed */
        endDragImage(info, True);
        endDragProcess(info, False);
    } else {
        XDND_SOURCE_STATE(info) = idleState;
    }
}

void
W_DragSourceStopTimer()
{
    if (dndSourceTimer != NULL) {
        WMDeleteTimerHandler(dndSourceTimer);
        dndSourceTimer = NULL;
    }
}

void
W_DragSourceStartTimer(WMDraggingInfo *info)
{
    W_DragSourceStopTimer();

    dndSourceTimer = WMAddTimerHandler(
                                       XDND_DESTINATION_RESPONSE_MAX_DELAY,
                                       dragSourceResponseTimeOut,
                                       XDND_SOURCE_VIEW(info));
}

/* ----- End of Destination timer ----- */


void
W_DragSourceStateHandler(WMDraggingInfo *info, XClientMessageEvent *event)
{
    WMView *view;
    W_DndState* newState;

    if (XDND_SOURCE_VIEW_STORED(info)) {
        view = XDND_SOURCE_VIEW(info);
#ifdef XDND_DEBUG

        printf("current source state: %s\n",
               stateName(XDND_SOURCE_STATE(info)));
#endif

        newState = (W_DndState*) XDND_SOURCE_STATE(info)(view, event, info);

#ifdef XDND_DEBUG

        printf("new source state: %s\n", stateName(newState));
#endif

        if (newState != NULL)
            XDND_SOURCE_STATE(info) = newState;
        /* else drop finished, and info has been flushed */
    }
}


void WMSetViewDragImage(WMView* view, WMPixmap *dragImage)
{
    if (view->dragImage != NULL)
        WMReleasePixmap(view->dragImage);

    view->dragImage = WMRetainPixmap(dragImage);
}


void WMReleaseViewDragImage(WMView* view)
{
    if (view->dragImage != NULL)
        WMReleasePixmap(view->dragImage);
}


/* Create a drag handler, associating drag event masks with dragEventProc */
void
WMCreateDragHandler(WMView *view, WMEventProc *dragEventProc, void *clientData)
{
    WMCreateEventHandler(view,
                         ButtonPressMask|ButtonReleaseMask|Button1MotionMask,
                         dragEventProc, clientData);
}


void
WMDeleteDragHandler(WMView *view, WMEventProc *dragEventProc, void *clientData)
{
    WMDeleteEventHandler(view,
                         ButtonPressMask|ButtonReleaseMask|Button1MotionMask,
                         dragEventProc, clientData);
}


/* set default drag handler for view */
void
WMSetViewDraggable(WMView *view, WMDragSourceProcs *dragSourceProcs,
                   WMPixmap *dragImage)
{
    wassertr(dragImage != NULL);
    view->dragImage = WMRetainPixmap(dragImage);

    WMSetViewDragSourceProcs(view, dragSourceProcs);

    WMCreateDragHandler(view, dragImageHandler, view);
}


void
WMUnsetViewDraggable(WMView *view)
{
    if (view->dragSourceProcs) {
        wfree(view->dragSourceProcs);
        view->dragSourceProcs = NULL;
    }

    WMReleaseViewDragImage(view);

    WMDeleteDragHandler(view, dragImageHandler, view);
}




syntax highlighted by Code2HTML, v. 0.9.1