/*
 * source2.c - Example of how to source a drag and drop operation.
 *
 */

#include <stdio.h>
#include <sys/types.h>
#include <xview/xview.h>
#include <xview/frame.h>
#include <xview/canvas.h>
#include <xview/cursor.h>
#include <xview/dragdrop.h>
#include <xview/sel_pkg.h>
#include <xview/xv_xrect.h>
#include <xview/svrimage.h>
#include <X11/Xlib.h>
#include <X11/Xatom.h>

unsigned short drop_icon[] =
{
#include "./drop.icon"
};

#if defined(__linux__) && !defined(NBBY)
#define NBBY 8
#endif

#define  POINT_IN_RECT(px, py, rx, ry, rw, rh) \
    ((px) >= rx && (py) >= ry && \
     (px) < rx+rw && (py) < ry+rh)

#define  STRING_MSG "fe-line: adj. Of the cat family; catlike; sly."

#define  HOST   0
#define  STRING 1
#define  LENGTH 2

Frame       frame;
Canvas      canvas;
Dnd     dnd;
Cursor      arrow_cursor;
Server_image    arrow_image;
Server_image    arrow_image_mask;
Server_image    box_image;
Server_image    drop_here_image;
Cursor      drop_here_cursor;
Selection_owner sel;
Selection_item  selItem[5];
Atom        selAtom[5];

int  SelectionConvert();
extern int sel_convert_proc();

static XColor   fg = {0L, 65535, 65535, 0};
static XColor   bg = {0L, 65535, 0, 0};

typedef struct _DragObject
{
    Server_image  image;
    int       x, y;
    unsigned int  w, h;
    int       inverted;
} DragObject;

DragObject  dO;

main(argc, argv)
int    argc;
char **argv;
{
    void    EventProc(),
            SelectionLose(),
            PaintCanvas();
    Xv_Server   server;
    Cursor  arrow_cursor;

    server = xv_init(XV_INIT_ARGC_PTR_ARGV, &argc, argv, 0);

    frame = xv_create((Window)NULL, FRAME,
                      XV_LABEL,       "Drag & Drop Source",
                      XV_X,           10,
                      XV_Y,           10,
                      FRAME_SHOW_FOOTER,  True,
                      0);

    canvas = xv_create(frame, CANVAS,
                       XV_HEIGHT,      100,
                       XV_WIDTH,       300,
                       CANVAS_REPAINT_PROC,    PaintCanvas,
                       CANVAS_X_PAINT_WINDOW,  TRUE,
                       0);

    xv_set(canvas_paint_window(canvas),
           WIN_BIT_GRAVITY,    ForgetGravity,
           WIN_CONSUME_EVENTS,
           WIN_MOUSE_BUTTONS,
           LOC_DRAG,
           WIN_RESIZE,
           0,
           WIN_EVENT_PROC,     EventProc,
           0);

    /* Create the drag cursor images */

    arrow_image = xv_create(NULL, SERVER_IMAGE,
                            SERVER_IMAGE_BITMAP_FILE, "arrow2.bm", NULL);

    arrow_image_mask = xv_create(NULL, SERVER_IMAGE,
                                 SERVER_IMAGE_BITMAP_FILE, "arrow2_mask.bm", NULL);

    /* Create a cursor to use in dnd ops. */

    arrow_cursor = XCreatePixmapCursor(XV_DISPLAY_FROM_WINDOW(canvas),
                                       (XID)xv_get(arrow_image, XV_XID),
                                       (XID)xv_get(arrow_image_mask, XV_XID),
                                       &fg, &bg, 4, 4);

    drop_here_image = xv_create(NULL, SERVER_IMAGE,
                                XV_WIDTH,       64,
                                XV_HEIGHT,      64,
                                SERVER_IMAGE_BITS,  drop_icon,
                                0);

    drop_here_cursor = XCreatePixmapCursor(XV_DISPLAY_FROM_WINDOW(canvas),
                                           (XID)xv_get(drop_here_image, XV_XID),
                                           (XID)xv_get(drop_here_image, XV_XID),
                                           &fg, &bg, 32, 32);

    dO.image = xv_create(NULL, SERVER_IMAGE,
                         SERVER_IMAGE_BITMAP_FILE, "arrowb2.bm", NULL);
    dO.w = xv_get(dO.image, XV_WIDTH);
    dO.h = xv_get(dO.image, XV_HEIGHT);
    dO.inverted = False;

    CreateSelection(server, canvas_paint_window(canvas));

    window_fit(frame);

    xv_main_loop(frame);
    exit(0);

}

CreateSelection(server, window)
Xv_Server   server;
Xv_object   window;
{
    char   name[15];
    int    len;
    void   SelectionLose();

    /* Primary selection, acquired whenever the arrow
     * bitmap is selected by the user.
     * This is not a requirement for dnd to work.
     */
    sel = xv_create(window, SELECTION_OWNER,
                    SEL_RANK,       XA_PRIMARY,
                    SEL_LOSE_PROC,      SelectionLose,
                    0);

    /* Create the drag and drop object.  */
    dnd = xv_create(window, DRAGDROP,
                    DND_TYPE,       DND_COPY,
                    DND_X_CURSOR,       arrow_cursor,
                    DND_ACCEPT_X_CURSOR,    drop_here_cursor,
                    SEL_CONVERT_PROC,   SelectionConvert,
                    0);

    /* Associate some selection items with the dnd object.*/
    (void) gethostname(name, 15);
    selAtom[HOST] = (Atom)xv_get(server, SERVER_ATOM, "HOST_NAME");
    selItem[HOST] = xv_create(dnd, SELECTION_ITEM,
                              SEL_TYPE,       selAtom[HOST],
                              SEL_DATA, (Xv_opaque)name,
                              0);

    selAtom[STRING] = (Atom)XA_STRING;
    selItem[STRING] = xv_create(dnd, SELECTION_ITEM,
                                SEL_TYPE,       selAtom[STRING],
                                SEL_DATA, (Xv_opaque)STRING_MSG,
                                0);

    len = strlen(STRING_MSG);
    selAtom[LENGTH] = (Atom)xv_get(server, SERVER_ATOM, "LENGTH");
    selItem[LENGTH] = xv_create(dnd, SELECTION_ITEM,
                                SEL_TYPE,       selAtom[LENGTH],
                                SEL_FORMAT,     sizeof(int) * NBBY,
                                SEL_LENGTH,     1,
                                SEL_DATA, (Xv_opaque)&len,
                                0);
}

void
EventProc(window, event)
Xv_Window       window;
Event           *event;
{
    static int drag_pixels = 0;
    static int dragging = False;

    switch(event_action(event))
    {
    case ACTION_SELECT:
        if(event_is_down(event))
        {
            dragging = False;

            /* If the user selected our dnd object, highlight
             * the box and acquire the primary selection.
             */
            if(POINT_IN_RECT(event_x(event), event_y(event),
                             dO.x, dO.y, dO.w, dO.h))
            {
                xv_set(sel, SEL_OWN, True, 0);
                dO.inverted = True;
                PaintObject(dO, xv_get(window, XV_XID),
                            XV_DISPLAY_FROM_WINDOW(window));
            }
            else
                /* If the user selected outside of the dnd object,
                 * de-highlight the object. And release the primary
                 * selection.
                 */
                xv_set(sel, SEL_OWN, False, 0);
        }
        else
            drag_pixels = 0;

        break;

    case LOC_DRAG:

        /* If the user dragged at least five pixel over our
         * dnd object, begin the dnd operation.
         */
        if(event_left_is_down(event))
        {
            if(POINT_IN_RECT(event_x(event),
                             event_y(event), dO.x, dO.y, dO.w, dO.h))
                dragging = True;

            if(dragging && drag_pixels++ == 5)
            {
                xv_set(frame, FRAME_LEFT_FOOTER, "Drag and Drop:", 0);

                switch(dnd_send_drop(dnd))
                {
                case XV_OK:
                    xv_set(frame, FRAME_LEFT_FOOTER,
                           "Drag and Drop: Began", 0);
                    break;

                case DND_TIMEOUT:
                    xv_set(frame, FRAME_LEFT_FOOTER,
                           "Drag and Drop: Timed Out", 0);
                    break;

                case DND_ILLEGAL_TARGET:
                    xv_set(frame, FRAME_LEFT_FOOTER,
                           "Drag and Drop: Illegal Target", 0);
                    break;

                case DND_SELECTION:
                    xv_set(frame, FRAME_LEFT_FOOTER,
                           "Drag and Drop: Bad Selection", 0);
                    break;

                case DND_ROOT:
                    xv_set(frame, FRAME_LEFT_FOOTER,
                           "Drag and Drop: Root Window", 0);
                    break;

                case XV_ERROR:
                    xv_set(frame, FRAME_LEFT_FOOTER,
                           "Drag and Drop: Failed", 0);
                    break;
                }

                drag_pixels = 0;
            }
        }

        break;
    }
}

PaintObject(object, win, dpy)
DragObject   object;
Window   win;
Display     *dpy;
{
    static GC   gc;
    static int  gcCreated = False;

    if(!gcCreated)
    {
        XGCValues gcv;
        gcv.stipple = (Pixmap) xv_get(object.image, XV_XID);
        gcv.fill_style = FillStippled;
        gc = XCreateGC(dpy, win, GCStipple | GCForeground | GCBackground |
                       GCFillStyle, &gcv);
        XSetForeground(dpy, gc, BlackPixel(dpy, XDefaultScreen(dpy)));
        XSetBackground(dpy, gc, WhitePixel(dpy, XDefaultScreen(dpy)));
    }

    if(object.inverted)
    {
        XSetFillStyle(dpy, gc, FillSolid);
        XDrawRectangle(dpy, win, gc, object.x - 1, object.y - 1, 66, 66);
        XSetFillStyle(dpy, gc, FillStippled);
    }
    else
        XClearWindow(dpy, win);

    XSetTSOrigin(dpy, gc, object.x, object.y);
    XFillRectangle(dpy, win, gc, object.x, object.y, 65, 65);
}

void
PaintCanvas(canvas, paint_window, dpy, xwin, xrects)
Canvas        canvas;         /* unused */
Xv_Window     paint_window;   /* unused */
Display      *dpy;
Window        xwin;
Xv_xrectlist *xrects;         /* unused */
{
    unsigned    width, height;
    int         x, y;

    width = xv_get(paint_window, XV_WIDTH);
    height = xv_get(paint_window, XV_HEIGHT);

    x = (width / 2) - (dO.w / 2);
    y = (height / 2) - (dO.h / 2);

    dO.x = x;
    dO.y = y;

    PaintObject(dO, xwin, dpy);
}

/* The convert proc is called whenever someone makes a request to the dnd
 * selection.  Two cases we handle within the convert proc: DELETE and
 * _SUN_DRAGDROP_DONE.  Everything else we pass on to the default convert
 * proc which knows about our selection items.
 */
int
SelectionConvert(seln, type, data, length, format)
Selection_owner  seln;
Atom        *type;
Xv_opaque       *data;
unsigned long   *length;
int         *format;
{
    Xv_Server        server = XV_SERVER_FROM_WINDOW(xv_get(seln, XV_OWNER));

    if(*type == (Atom)xv_get(server, SERVER_ATOM, "_SUN_SELECTION_END"))
    {
        /* Destination has told us it has completed the drag
         * and drop transaction.  We should respond with a
         * zero-length NULL reply.
         */
        xv_set(dnd, SEL_OWN, False, 0);
        xv_set(frame, FRAME_LEFT_FOOTER, "Drag and Drop: Completed", 0);
        *format = 32;
        *length = 0;
        *data = NULL;
        *type = (Atom)xv_get(server, SERVER_ATOM, "NULL");
        return(True);
    }
    else if(*type == (Atom)xv_get(server, SERVER_ATOM, "DELETE"))
    {
        /* Destination asked us to delete the selection.
         * If it is appropriate to do so, we should.
         */
        *format = 32;
        *length = 0;
        *data = NULL;
        *type = (Atom)xv_get(server, SERVER_ATOM, "NULL");
        return(True);
    }
    else
        /* Let the default convert procedure deal with the
         * request.
         */
        return(sel_convert_proc(seln, type, data, length, format));
}

/* When we lose the primary selection, this procedure is called.  We dehigh-
 * light our selection.
 */
void
SelectionLose(seln)
Selection_owner seln;
{
    Xv_Window   owner = xv_get(seln, XV_OWNER);

    if(xv_get(seln, SEL_RANK) == XA_PRIMARY)
    {
        dO.inverted = False;
        PaintObject(dO, xv_get(owner, XV_XID), XV_DISPLAY_FROM_WINDOW(owner));
    }
}
