/**
 *
 * $Id: DragBS.c,v 1.4 1997/03/25 02:34:25 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.
 *
 **/

static char rcsid[] = "$Id: DragBS.c,v 1.4 1997/03/25 02:34:25 miers Exp $";

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

#include <Xm/XmP.h>
#include <Xm/DisplayP.h>
#include <Xm/AtomMgr.h>
#include <X11/Xfuncs.h>
#include <XmI/AtomMgrI.h>
#include <XmI/DragDropI.h>
#include <XmI/CutPasteI.h>

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

#include <XmI/DebugUtil.h>

/*
 * here's a little tidbit to look at.  If you've run mwm, or a Motif
 * application, do an 'xprop' on the root window.  You'll see a property
 * _MOTIF_DRAG_WINDOW; now, do an xprop on _it_.  Interesting things running
 * around there, no?  This stuff is crucial to DnD...
 *
 * Daniel Dardailler's documentation is invaluable to actually understanding
 * this mess.
 *
 * convention: read/write refers to properties.  get/set refers to
 * XContexts.
 *
 * Don't panic when you find all the XGrabServer/XUngrabServer pairs.
 * Basically, they delineate critical sections where only the grabber should
 * have access to the X server.
 */

/*
 * byte ordering
 */
extern unsigned char	_XmByteOrder();

/*
 * these are used for protected sections
 */
static Boolean error_flag, bad_window;
static int (*oldErrorHandler)(Display *, XErrorEvent *);
static int firstProtectRequest;
static Window errorWindow = None;

/*
 * these targets should always be present on the drag window
 */
static Atom noTarget = 0, stringTarget = XA_STRING;

/*
 * some contexts for the drag window, the atoms table, and the targets table.
 */
static XContext displayToDW = None;
static XContext displayToAtoms = None;
static XContext displayToTargets = None;

/*
 * basic atoms that must be agreed on
 */
static char *motifAtoms[] = {
    _XA_MOTIF_DRAG_WINDOW,
    _XA_MOTIF_DRAG_ATOM_PAIRS,
    _XA_MOTIF_DRAG_ATOMS,
    _XA_MOTIF_ATOM_0,
    _XA_MOTIF_DRAG_TARGETS,
    _XA_MOTIF_WM_OFFSET,
    _XA_MOTIF_DRAG_PROXY_WINDOW,
    _XA_MOTIF_WM_MESSAGES,
    _XA_MOTIF_WM_HINTS,
    _XA_MOTIF_WM_MENU,
    _XA_MOTIF_WM_INFO,
    _XA_MOTIF_BINDINGS,
    _XA_ATOM_PAIR,
    _XA_AVERAGE_WIDTH,
    _XA_CLIPBOARD,
    _XA_CLIP_TEMPORARY,
    _XA_COMPOUND_TEXT,
    _XA_DELETE,
    _XA_INCR,
    _XA_INSERT_SELECTION,
    _XA_LENGTH,
    _XA_MOTIF_DESTINATION,
    _XA_MULTIPLE,
    _XA_PIXEL_SIZE,
    _XA_RESOLUTION_Y,
    _XA_TARGETS,
    _XA_TEXT,
    _XA_TIMESTAMP,
    _XA_WM_DELETE_WINDOW,
    _XA_WM_PROTOCOLS,
    _XA_WM_STATE,
    _XA_XmTRANSFER_SUCCESS,
    _XA_XmTRANSFER_FAILURE,
    _XA_MOTIF_CLIP_DATA_DELETE,
    _XA_MOTIF_CLIP_DATA_REQUEST,
    _XA_MOTIF_CLIP_HEADER,
    _XA_MOTIF_CLIP_LOCK_ACCESS_VALID,
    _XA_MOTIF_CLIP_MESSAGE,
    _XA_MOTIF_CLIP_NEXT_ID,
    _XA_MOTIF_CLIP_TIME,
    _XA_MOTIF_DROP,
    _XA_MOTIF_DRAG_INITIATOR_INFO,
    _XA_MOTIF_DRAG_RECEIVER_INFO,
    _XA_MOTIF_MESSAGE,
    _XA_XM_TEXT_I_S_PROP
};

static int
DWError(Display *display, XErrorEvent *event)
{
    error_flag = True;

    return 0;
}

/*
 * create the drag window
 */
static Window
create_drag_window(Display *display)
{
    Window win;
    XSetWindowAttributes attr;


    attr.override_redirect = True;
    attr.event_mask = PropertyChangeMask;

    win = XCreateWindow(display, DefaultRootWindow(display),
			-100, -100, 10, 10, 0, 0,
			InputOnly, CopyFromParent,
			(CWOverrideRedirect|CWEventMask),
			&attr);

    XMapWindow(display, win);

    return win;
}

/*
 * see if the drag window property can be found on the root window
 */
static Window
read_drag_window(Display *display)
{
    int (*old_handler)(Display *, XErrorEvent *);
    Atom mw, atype;
    int aformat;
    unsigned long nitems, bafter;
    unsigned char *prop = NULL;
    Window win = None;


    old_handler = XSetErrorHandler(DWError);

    error_flag = False;

    mw = XmInternAtom(display, _XA_MOTIF_DRAG_WINDOW, False);

    if (XGetWindowProperty(display, DefaultRootWindow(display),
			   mw, 0L, 100000L,
			   False, AnyPropertyType,
			   &atype, &aformat, &nitems, &bafter, &prop)
	== Success && atype == XA_WINDOW && aformat == 32 && nitems == 1)
    {
	win = *((Window *)prop);
    }

    if (prop)
	XFree((char *)prop);

    XSetErrorHandler(old_handler);

    if (error_flag)
	return None;

    return win;
}

/*
 * set the drag window property on the root window
 */
static void
write_drag_window(Display *display, Window *win)
{
    Atom mw;

    mw = XmInternAtom(display, _XA_MOTIF_DRAG_WINDOW, False);

    XChangeProperty(display, DefaultRootWindow(display), mw,
		    XA_WINDOW, 32, PropModeReplace, (unsigned char *)win, 1);
}

/*
 * lookup (program local) the value of the drag window
 */
static Window
get_drag_window(Display *display)
{
    Window win;

    if (displayToDW == None)
	displayToDW = XUniqueContext();

    if (XFindContext(display, DefaultRootWindow(display),
		     displayToDW, (XPointer *)&win)
         == XCSUCCESS) {
	return win;
    }

    return None;
}

/*
 * cache (program local) the value of the drag window
 */
static void
set_drag_window(Display *display, Window win)
{
    if (displayToDW == None)
	displayToDW = XUniqueContext();

    if (XFindContext(display, DefaultRootWindow(display),
		     displayToDW, (XPointer *)&win)
         == XCSUCCESS) {
	XDeleteContext(display, DefaultRootWindow(display),
		       displayToDW);
    }

    XSaveContext(display, DefaultRootWindow(display),
		 displayToDW, (XPointer)win);
}

/*
 * catch window errors when fetching or setting the DRAG_WINDOW properties
 */
static int
protect_handler(Display *display, XErrorEvent *event)
{
    if (event->error_code == BadWindow &&
	event->resourceid == errorWindow &&
	event->serial > firstProtectRequest) {
	bad_window = True;
	return 0;
    }

    if (oldErrorHandler == NULL)
	return 0;

    return (*oldErrorHandler)(display, event);
}

/*
 * begin catching window errors
 */
static void
begin_protection(Display *display, Window win)
{
    bad_window = False;

    oldErrorHandler = XSetErrorHandler(protect_handler);

    firstProtectRequest = NextRequest(display);

    errorWindow = win;
}

/*
 * end catching window errors
 */
static void
end_protection(Display *display)
{
    XSync(display, False);

    XSetErrorHandler(oldErrorHandler);

    oldErrorHandler = NULL;
}

/*
 * read from a drag buffer (sort of)
 */
short
_XmReadDragBuffer(XmDndBufMgr bmgr, Boolean read_string,
		  char *retbuf, short size)
{
    int rsize, eaten;

    if (read_string) {

	rsize = bmgr->name_size;

	eaten = bmgr->name_start - bmgr->names;

	if (rsize <= eaten)
	    size = rsize - eaten;

	bcopy(bmgr->name_start, retbuf, size);

	bmgr->name_start += size;

	return size;
    }
    else {
	rsize = bmgr->atom_size;

	eaten = bmgr->atom_start - bmgr->atoms;

	if (rsize <= eaten)
	    size = rsize - eaten;

	bcopy(bmgr->atom_start, retbuf, size);

	bmgr->atom_start += size;

	return size;
    }
}

/*
 * write to a drag buffer (sort of)
 */
short
_XmWriteDragBuffer(XmDndBufMgr bmgr, Boolean write_string,
		   char *data, short size)
{
    if (write_string) {

	if ((bmgr->name_size + size) > bmgr->name_avail) {

	    bmgr->name_avail += 1000;

	    if (bmgr->names == bmgr->name_ptr) {
		bmgr->names = XtMalloc(bmgr->name_avail);

		bcopy(bmgr->name_ptr, bmgr->names, bmgr->name_size);
	    }
	    else
		bmgr->names = XtRealloc((char *)bmgr->names, bmgr->name_avail);
	}

	bcopy(data, bmgr->names + bmgr->name_size, size);

	bmgr->name_size += size;

	return size;
    }
    else {
	if ((bmgr->atom_size + size) > bmgr->atom_avail) {

	    bmgr->atom_avail += 1000;

	    if (bmgr->atoms == bmgr->atom_ptr) {
		bmgr->atoms = XtMalloc(bmgr->atom_avail);

		bcopy(bmgr->atom_ptr, bmgr->atoms, bmgr->atom_size);
	    }
	    else {
		bmgr->atoms = XtRealloc(bmgr->atoms, bmgr->atom_avail);
	    }
	}

	bcopy(data, bmgr->atoms + bmgr->atom_size, size);

	bmgr->atom_size += size;

	return size;
    }
}

/*
 * get the ATOM_PAIRS.  They may or may not already exist
 */
static Boolean
read_atom_pairs(Display *display)
{
    Atom pa, atype;
    Window win;
    Boolean gotit = False;
    XmDndAtomPairs *pairs;
    XmDndBufMgrRec bmgr;
    int i;
    XmDndAtomPair pair;
    char buf[32];
    int aformat;
    unsigned long nitems, bafter;
    unsigned char *prop = NULL;

    pa = XmInternAtom(display, _XA_MOTIF_DRAG_ATOM_PAIRS, False);

    win = get_drag_window(display);

    begin_protection(display, win);

    if (XGetWindowProperty(display, win, pa, 0L, 100000L,
			   False, pa,
			   &atype, &aformat, &nitems, &bafter,
			   (unsigned char **)&pairs)
	== Success && nitems >= 8 && pairs != NULL)
	gotit = True;

    end_protection(display);

    if (bad_window)
	_XmWarning((Widget)XmGetXmDisplay(display),
		   "Bad window ATOM_PAIRS property on DRAG_WINDOW.");

    if (!gotit) {
#if 0
	_XmWarning((Widget)XmGetXmDisplay(display),
		   "No ATOM_PAIRS property on DRAG_WINDOW.");
#endif

	if (prop)
	    XFree((char *)prop);

	return False;
    }

    if (pairs->protocol_version != DND_PROTOCOL_VERSION)
	_XmWarning((Widget)XmGetXmDisplay(display),
		   "DND Protocol version mismatch.");

    if (pairs->byte_order != _XmByteOrder()) {
	SWAP2BYTES(pairs->num_pairs);
	SWAP4BYTES(pairs->data_size);
    }

    if (pairs->num_pairs > 0) {

	bmgr.atoms = (char *)pairs;
	bmgr.atom_start = (char *)(pairs+1);
	bmgr.atom_size = pairs->data_size;
	bmgr.names = (char *)pairs + pairs->data_size;
	bmgr.name_start = (char *)pairs + pairs->data_size;
	bmgr.name_size = nitems - pairs->data_size;

	for (i = 0; i < pairs->num_pairs; i++) {

	    _XmReadDragBuffer(&bmgr, False, (char *)&pair,
			      sizeof(XmDndAtomPair));

	    if (pairs->byte_order != _XmByteOrder()) {
		SWAP4BYTES(pair.atom);
		SWAP2BYTES(pair.namelen);
	    }

	    _XmReadDragBuffer(&bmgr, True, buf, pair.namelen);

	    _XmInternAtomAndName(display, pair.atom, buf);
	}
    }

    if (prop)
	XFree((char *)prop);

    return gotit;
}

/*
 * if the ATOM_PAIRS didn't exist, put them on the drag window
 */
static void
write_atom_pairs(Display *display)
{
    char pairs[1000];
    char names[1000];
    XmDndAtomPairs pair_rec;
    XmDndBufMgrRec bmgr;
    XmDndAtomPair pair;
    Atom pa;
    int i;
    Window win;

    pair_rec.byte_order = _XmByteOrder();
    pair_rec.protocol_version = DND_PROTOCOL_VERSION;
    pair_rec.num_pairs = XtNumber(motifAtoms);

    bmgr.atoms = pairs;
    bmgr.atom_ptr = pairs;
    bmgr.atom_start = NULL;
    bmgr.atom_size = 0;
    bmgr.atom_avail = 1000;
    bmgr.names = names;
    bmgr.name_ptr = names;
    bmgr.name_start = NULL;
    bmgr.name_size = 0;
    bmgr.name_avail = 1000;

    _XmWriteDragBuffer(&bmgr, False, (char *)&pair_rec, sizeof(XmDndAtomPairs));

    for (i = 0; i < XtNumber(motifAtoms); i++) {

	pair.atom = XmInternAtom(display, motifAtoms[i], False);

	pair.namelen = strlen(motifAtoms[i]) + 1;

	_XmWriteDragBuffer(&bmgr, True, motifAtoms[i], pair.namelen);

	_XmWriteDragBuffer(&bmgr, False, (char *)&pair, sizeof(XmDndAtomPair));

    }

    ((XmDndAtomPairs *)(bmgr.atoms))->data_size = bmgr.atom_size;

    pa = XmInternAtom(display, _XA_MOTIF_DRAG_ATOM_PAIRS, False);

    win = get_drag_window(display);

    begin_protection(display, win);

    XChangeProperty(display, win, pa, pa, 8, PropModeReplace,
		    (unsigned char *)bmgr.atoms, bmgr.atom_size);

    if (bmgr.atoms != bmgr.atom_ptr)
	XtFree(bmgr.atoms);

    if (bmgr.name_size) {
	XChangeProperty(display, win, pa, pa, 8, PropModeAppend,
			(unsigned char *)bmgr.names, bmgr.name_size);

	if (bmgr.names != bmgr.name_ptr)
	    XtFree(bmgr.names);
    }

    end_protection(display);

    if (bad_window) {
	_XmWarning((Widget)XmGetXmDisplay(display),
		   "Bad window writing ATOM_PAIRS property on DRAG_WINDOW.");
    }
}

/*
 * fetch the cached atoms table
 */
static XmDndAtomsTable
get_atoms_table(Display *display)
{
    XmDndAtomsTable tbl = NULL;

    if (displayToAtoms == (XContext)0)
	displayToAtoms = XUniqueContext();

    if (XFindContext(display, DefaultRootWindow(display),
		     displayToAtoms, (XPointer *)&tbl) != XCSUCCESS)
	return NULL;

    return tbl;
}

/*
 * cache the atoms table
 */
static void
set_atoms_table(Display *display, XmDndAtomsTable table)
{
    XmDndAtomsTable atoms;

    if (displayToAtoms == (XContext)0)
	displayToAtoms = XUniqueContext();

    if (XFindContext(display, DefaultRootWindow(display),
		     displayToAtoms, (XPointer *)&atoms) == XCSUCCESS) {

	if (atoms == table)
	    return;

	XDeleteContext(display, DefaultRootWindow(display),
		       displayToAtoms);
    }

    XSaveContext(display, DefaultRootWindow(display),
		 displayToAtoms, (XPointer)table);
}

static XmDndAtomsTable
create_default_atoms_table(Display *display)
{
    XmDndAtomsTable tbl;

    tbl = (XmDndAtomsTable)XtMalloc(sizeof(XmDndAtomsTableRec));
    tbl->num_entries = 1;

    tbl->entries =
	(XmDndAtomsTableEntry)XtMalloc(sizeof(XmDndAtomsTableEntryRec));

    tbl->entries->atom = XmInternAtom(display, _XA_MOTIF_ATOM_0, False);
    tbl->entries->time = CurrentTime;

    set_atoms_table(display, tbl);

    return tbl;
}

static Boolean
read_atoms_table(Display *display, XmDndAtomsTable tbl)
{
    Atom da, atype;
    Window win;
    int aformat, i;
    unsigned long nitems, bafter;
    XmDndAtoms *atoms = NULL;
    Boolean got_it = False;
    XmDndAtomsTableEntryRec atom_ent;
    XmDndBufMgrRec bmgr;

    da = XmInternAtom(display, _XA_MOTIF_DRAG_ATOMS, False);

    win = get_drag_window(display);

    begin_protection(display, win);

    if (XGetWindowProperty(display, win, da, 0L, 100000L,
			   False, da,
			   &atype, &aformat, &nitems, &bafter,
			   (unsigned char **)&atoms)
	== Success && nitems >= 8)
	got_it = True;

    end_protection(display);

    if (bad_window) {
	_XmWarning((Widget)XmGetXmDisplay(display),
		   "Invalid DRAG WINDOW fetching DRAG_ATOMS.");
    }

    if (!got_it) {

	if (atoms)
	    XFree((XPointer)atoms);

	return False;
    }

    if (atoms->protocol_version != DND_PROTOCOL_VERSION) {
	_XmWarning((Widget)XmGetXmDisplay(display),
		   "DND Protocol version mismatch.");
    }

    if (atoms->byte_order != _XmByteOrder()) {
	SWAP2BYTES(atoms->num_atoms);
	SWAP4BYTES(atoms->data_size);
    }

    if (!tbl) {
	tbl = (XmDndAtomsTable)XtMalloc(sizeof(XmDndAtomsTableRec));

	tbl->num_entries = 0;
	tbl->entries = NULL;

	set_atoms_table(display, tbl);
    }

    if (tbl->num_entries < atoms->num_atoms) {
	tbl->entries = (XmDndAtomsTableEntry)XtRealloc((char *)tbl->entries,
				atoms->num_atoms *
					sizeof(XmDndAtomsTableEntryRec));
    }

    if (atoms->num_atoms > 0) {

	bmgr.atoms = (char *)atoms;
	bmgr.atom_start = (char *)(atoms+1);
	bmgr.atom_size = atoms->data_size;

	for (i = 0; i < atoms->num_atoms; i++) {

	    _XmReadDragBuffer(&bmgr, False, (char *)&atom_ent,
			      sizeof(XmDndAtomsTableEntryRec));

	    if (atoms->byte_order != _XmByteOrder()) {
		SWAP4BYTES(atom_ent.atom);
		SWAP4BYTES(atom_ent.time);
	    }

	    tbl->entries[i].atom = atom_ent.atom;
	    tbl->entries[i].time = atom_ent.time;
	}
    }

    return True;
}

/*
 * write the atoms table out to the drag window
 */
static void
write_atoms_table(Display *display, XmDndAtomsTable tbl)
{
    char atoms[1000];
    XmDndAtoms atoms_rec;
    XmDndBufMgrRec bmgr;
    Atom pa;
    int i;
    Window win;

    if (tbl == NULL) {
	_XmWarning((Widget)XmGetXmDisplay(display),
		   "No DRAG_ATOMS to write to DRAG_WINDOW.");

	return;
    }

    atoms_rec.byte_order = _XmByteOrder();
    atoms_rec.protocol_version = DND_PROTOCOL_VERSION;
    atoms_rec.num_atoms = tbl->num_entries;

    bmgr.atoms = atoms;
    bmgr.atom_ptr = atoms;
    bmgr.atom_start = NULL;
    bmgr.atom_size = 0;
    bmgr.atom_avail = 1000;

    _XmWriteDragBuffer(&bmgr, False, (char *)&atoms_rec, sizeof(XmDndAtoms));

    for (i = 0; i < tbl->num_entries; i++) {
	_XmWriteDragBuffer(&bmgr, False, (char *)&tbl->entries[i],
			   sizeof(XmDndAtomsTableEntryRec));

    }

    ((XmDndAtoms *)(bmgr.atoms))->data_size = bmgr.atom_size;

    pa = XmInternAtom(display, _XA_MOTIF_DRAG_ATOMS, False);

    win = get_drag_window(display);

    begin_protection(display, win);

    XChangeProperty(display, win, pa, pa, 8, PropModeReplace,
		    (unsigned char *)bmgr.atoms, bmgr.atom_size);

    if (bmgr.atoms != bmgr.atom_ptr)
	XtFree(bmgr.atoms);

    end_protection(display);

    if (bad_window) {
	_XmWarning((Widget)XmGetXmDisplay(display),
		   "Bad window writing DRAG_ATOMS on DRAG_WINDOW.");
    }
}

/*
 * fetch the cached atoms table
 */
static XmDndTargetsTable
get_targets_table(Display *display)
{
    XmDndTargetsTable targets = NULL;

    if (displayToTargets == (XContext)0)
	displayToTargets = XUniqueContext();

    if (XFindContext(display, DefaultRootWindow(display),
		     displayToTargets, (XPointer *)&targets) != XCSUCCESS)
	return NULL;

    return targets;
}

/*
 * cache the atoms table
 */
static void
set_targets_table(Display *display, XmDndTargetsTable table)
{
    XmDndTargetsTable targets;

    if (displayToTargets == (XContext)0)
	displayToTargets = XUniqueContext();

    if (XFindContext(display, DefaultRootWindow(display),
		     displayToTargets, (XPointer *)&targets) == XCSUCCESS) {

	if (targets == table)
	    return;

	XDeleteContext(display, DefaultRootWindow(display),
		       displayToTargets);
    }

    XSaveContext(display, DefaultRootWindow(display),
		 displayToTargets, (XPointer)table);
}

/*
 * create the default target table
 */
static XmDndTargetsTable
create_default_targets_table(Display *display)
{
    XmDndTargetsTable tbl;

    tbl = (XmDndTargetsTable)XtMalloc(sizeof(XmDndTargetsTableRec));

    tbl->num_entries = 2;
    tbl->entries =
	(XmDndTargetsTableEntry)XtMalloc(sizeof(XmDndTargetsTableEntryRec) *
					 tbl->num_entries);

    tbl->entries[0].num_targets = 1;
    tbl->entries[0].targets = &noTarget;

    tbl->entries[1].num_targets = 1;
    tbl->entries[1].targets = &stringTarget;

    set_targets_table(display, tbl);

    return tbl;
}

static Boolean
read_targets_table(Display *display, XmDndTargetsTable tbl)
{
    Atom ta, atype;
    Window win;
    int aformat, i, j;
    unsigned long nitems, bafter;
    XmDndTargets *targets = NULL;
    Boolean got_it = False;
    XmDndBufMgrRec bmgr;
    CARD16 nents;

    ta = XmInternAtom(display, _XA_MOTIF_DRAG_TARGETS, False);

    win = get_drag_window(display);

    begin_protection(display, win);

    if (XGetWindowProperty(display, win, ta, 0L, 100000L,
			   False, ta,
			   &atype, &aformat, &nitems, &bafter,
			   (unsigned char **)&targets)
	== Success && nitems >= 8)
	got_it = True;

    end_protection(display);

    if (bad_window) {
	_XmWarning((Widget)XmGetXmDisplay(display),
		   "Invalid DRAG WINDOW fetching DRAG_ATOMS.");
    }

    if (!got_it) {

	if (targets)
	    XFree((XPointer)targets);

	return False;
    }

    if (targets->protocol_version != DND_PROTOCOL_VERSION) {
	_XmWarning((Widget)XmGetXmDisplay(display),
		   "DND Protocol version mismatch.");
    }

    if (targets->byte_order != _XmByteOrder()) {
	SWAP2BYTES(targets->num_target_lists);
	SWAP4BYTES(targets->data_size);
    }

    if (!tbl) {
	tbl = (XmDndTargetsTable)XtMalloc(sizeof(XmDndTargetsTableRec));

	tbl->num_entries = 0;
	tbl->entries = NULL;

	set_targets_table(display, tbl);
    }

    if (tbl->num_entries < targets->num_target_lists) {
	tbl->entries =
		(XmDndTargetsTableEntry)XtRealloc((char *)tbl->entries,
				  targets->num_target_lists *
					sizeof(XmDndTargetsTableEntryRec));
    }

    if (targets->num_target_lists > 0) {

	bmgr.atoms = (char *)targets;
	bmgr.atom_start = (char *)(targets+1);
	bmgr.atom_size = targets->data_size;

	for (i = 0; i < targets->num_target_lists; i++) {

	    _XmReadDragBuffer(&bmgr, False, (char *)&nents,
			      sizeof(CARD16));

	    if (targets->byte_order != _XmByteOrder())
		SWAP2BYTES(nents);

	    tbl->entries[i].num_targets = nents;
	    tbl->entries[i].targets = (Atom *)XtMalloc(nents * sizeof(Atom));

	    _XmReadDragBuffer(&bmgr, False,
			      (char *)&tbl->entries[i].targets[0],
			      sizeof(Atom) * nents);

	    if (targets->byte_order != _XmByteOrder()) {

		for (j = 0; j < tbl->entries[i].num_targets; j++)
		    SWAP4BYTES(tbl->entries[i].targets[j]);
	    }
	}
    }

    return True;
}

static void
write_targets_table(Display *display, XmDndTargetsTable tbl)
{
    char targets[1000];
    XmDndTargets target_rec;
    XmDndBufMgrRec bmgr;
    Atom ta;
    int i;
    Window win;
    CARD16 nents;

    if (tbl == NULL) {
	_XmWarning((Widget)XmGetXmDisplay(display),
		   "No DRAG_TARGETS to write to DRAG_WINDOW.");

	return;
    }

    target_rec.byte_order = _XmByteOrder();
    target_rec.protocol_version = DND_PROTOCOL_VERSION;
    target_rec.num_target_lists = tbl->num_entries;

    bmgr.atoms = targets;
    bmgr.atom_ptr = targets;
    bmgr.atom_start = NULL;
    bmgr.atom_size = 0;
    bmgr.atom_avail = 1000;

    _XmWriteDragBuffer(&bmgr, False, (char *)&target_rec, sizeof(XmDndTargets));

    for (i = 0; i < tbl->num_entries; i++) {

	nents = tbl->entries[i].num_targets;
	_XmWriteDragBuffer(&bmgr, False, (char *)&nents, sizeof(CARD16));

	_XmWriteDragBuffer(&bmgr, False, (char *)&tbl->entries[i].targets[0],
			   sizeof(Atom) * nents);
    }

    ((XmDndTargets *)(bmgr.atoms))->data_size = bmgr.atom_size;

    ta = XmInternAtom(display, _XA_MOTIF_DRAG_TARGETS, False);

    win = get_drag_window(display);

    begin_protection(display, win);

    XChangeProperty(display, win, ta, ta, 8, PropModeReplace,
		    (unsigned char *)bmgr.atoms, bmgr.atom_size);

    if (bmgr.atoms != bmgr.atom_ptr)
	XtFree(bmgr.atoms);

    end_protection(display);

    if (bad_window) {
	_XmWarning((Widget)XmGetXmDisplay(display),
		   "Bad window writing DRAG_ATOMS on DRAG_WINDOW.");
    }
}

/*
 * fetch and/or set the ATOM_PAIRS.  This is triggered at least once
 * by the first call to XmInternAtom().
 */
void
_XmInitAtomPairs(Display *display)
{
    Window win;
    char *dstr;
    Display *d;

    if ((win = read_drag_window(display)) == None) {

	dstr = XDisplayString(display);

	d = XOpenDisplay(dstr);

	if (d == NULL) {
	    _XmWarning((Widget)XmGetXmDisplay(display),
		       "Where's your display?");

	    return;
	}

	XGrabServer(d);

	if ((win = read_drag_window(d)) == None) {

	    XSetCloseDownMode(d, RetainPermanent);

	    win = create_drag_window(d);

	    write_drag_window(d, &win);
	}

	XCloseDisplay(d);
    }

    set_drag_window(display, win);

    if (!read_atom_pairs(display)) {

	XGrabServer(display);

	if (!read_atom_pairs(display))
	    write_atom_pairs(display);

	XUngrabServer(display);

	XFlush(display);
    }
}

/*
 * intialize the DRAG_TARGETS table, and the DRAG_ATOMS table
 */
void
_XmInitTargetsTable(Display *display)
{
    Window win;
    char *dstr;
    Display *d;
    XmDndAtomsTable atoms;
    XmDndTargetsTable targets;

    if ((win = read_drag_window(display)) == None) {

	dstr = XDisplayString(display);

	d = XOpenDisplay(dstr);

	if (d == NULL) {
	    _XmWarning((Widget)XmGetXmDisplay(display),
		       "Where's your display?");

	    return;
	}

	XGrabServer(d);

	if ((win = read_drag_window(d)) == None) {

	    XSetCloseDownMode(d, RetainPermanent);

	    win = create_drag_window(d);

	    write_drag_window(d, &win);
	}

	XCloseDisplay(d);
    }

    set_drag_window(display, win);

    if (!read_atom_pairs(display)) {

	XGrabServer(display);

	if (!read_atom_pairs(display))
	    write_atom_pairs(display);

	XUngrabServer(display);

	XFlush(display);
    }

    if (!read_atoms_table(display, get_atoms_table(display))) {

	atoms = create_default_atoms_table(display);

	write_atoms_table(display, atoms);
    }

    if (!read_targets_table(display, get_targets_table(display))) {

	XGrabServer(display);

	if (!read_targets_table(display, get_targets_table(display))) {

	    targets = create_default_targets_table(display);

	    write_targets_table(display, targets);
	}

	XUngrabServer(display);

	XFlush(display);
    }
}

/*
 * called from DropSMgr's CreateInfo method.
 */
Cardinal
_XmIndexToTargets(Widget shell, Cardinal t_index, Atom **targetsRtn)
{
    Display *dpy = XtDisplay(shell);
    XmDndTargetsTable targets;

    if ((targets = get_targets_table(dpy)) == NULL) {

	_XmInitTargetsTable(dpy);

	targets = get_targets_table(dpy);
    }

    if (t_index >= targets->num_entries) {

	if (!read_targets_table(dpy, targets))
	    _XmInitTargetsTable(dpy);

	targets = get_targets_table(dpy);
    }

    if (t_index >= targets->num_entries) {

	_XmWarning(shell, "Index out of range to _XmIndexToTargets.");

	*targetsRtn = NULL;
	return 0;
    }

    *targetsRtn = targets->entries[t_index].targets;

    return targets->entries[t_index].num_targets;
}

/*
 * minimize duplicates in the targets table by sorting compound entries
 */
static int
acompare(XmConst void *a, XmConst void *b)
{
    Atom *pa = (Atom *)a, *pb = (Atom *)b;

    return *pa - *pb;
}

/*
 * also called from DropSMgr.  Creates target list if not already
 * there.
 */
Cardinal
_XmTargetsToIndex(Widget shell, Atom *tlist, Cardinal numTargets)
{
    Display *dpy = XtDisplay(shell);
    XmDndTargetsTable targets;
    int i;
    Atom *atoms;

    if ((targets = get_targets_table(dpy)) == NULL) {

	_XmInitTargetsTable(dpy);

	targets = get_targets_table(dpy);
    }

    atoms = (Atom *)XtMalloc(numTargets * sizeof(Atom));

    qsort(atoms, numTargets, sizeof(Atom), acompare);

    /* maybe it's already there ? */
    for (i = 0; i < targets->num_entries; i++) {

	if (numTargets == targets->entries[i].num_targets &&
	    bcmp(atoms, targets->entries[i].targets,
		 sizeof(Atom) * numTargets) == 0)
	{
	    XtFree((char *)atoms);

	    return i;
	}
    }

    XGrabServer(dpy);

    if (!read_targets_table(dpy, targets)) {

	XUngrabServer(dpy);

	_XmInitTargetsTable(dpy);

	XGrabServer(dpy);

	targets = get_targets_table(dpy);
    }

    /* maybe somebody added while we've been fooling around, but before
     * we said nuhuh with the grab */
    for (i = 0; i < targets->num_entries; i++) {

	if (numTargets == targets->entries[i].num_targets &&
	    bcmp(atoms, targets->entries[i].targets,
		 sizeof(Atom) * numTargets) == 0)
	{
	    XtFree((char *)atoms);

	    return i;
	}
    }

    /* nope.  add them */
    i = targets->num_entries;

    targets->num_entries++;
    targets->entries =
	(XmDndTargetsTableEntry)XtRealloc((char *)targets->entries,
					  sizeof(XmDndTargetsTableEntryRec) *
						targets->num_entries);

    targets->entries[i].num_targets = numTargets;
    targets->entries[i].targets = atoms;

    write_targets_table(dpy, targets);

    XUngrabServer(dpy);

    XFlush(dpy);

    return i;
}

/*
 * called when a drag starts, to allocate an Atom for the DragContext
 */
Atom
_XmAllocMotifAtom(Widget shell, Time time)
{
    XmDndAtomsTable atoms;
    Display *dpy = XtDisplay(shell);
    Atom alloc = None;
    int i;
    char buf[32];

    if ((atoms = get_atoms_table(dpy)) == NULL) {

	_XmInitTargetsTable(dpy);

	atoms = get_atoms_table(dpy);
    }

    XGrabServer(dpy);

    if (!read_atoms_table(dpy, atoms)) {

	XUngrabServer(dpy);

	_XmInitTargetsTable(dpy);

	XGrabServer(dpy);

	atoms = get_atoms_table(dpy);
    }

    if (atoms->num_entries != 0) {

	for (i = 0; i < atoms->num_entries; i++) {

	    if (atoms->entries[i].time == CurrentTime) {
		alloc = atoms->entries[i].atom;
		atoms->entries[i].time = time;
		break;
	    }
	}
    }

    if (alloc == None) {

	i = atoms->num_entries;

	atoms->num_entries++;

	atoms->entries =
		(XmDndAtomsTableEntry)XtRealloc((char *)atoms->entries,
			atoms->num_entries * sizeof(XmDndAtomsTableEntryRec));

	sprintf(buf, _XA_MOTIF_ATOM_FORMAT, atoms->num_entries);
	alloc = XmInternAtom(dpy, buf, False);

	atoms->entries[i].atom = alloc;
	atoms->entries[i].time = time;
    }

    write_atoms_table(dpy, atoms);

    XUngrabServer(dpy);

    XFlush(dpy);

    return alloc;
}

/*
 * Free the atom.
 */
void
_XmFreeMotifAtom(Widget shell, Atom atom)
{
    XmDndAtomsTable atoms;
    Display *dpy = XtDisplay(shell);
    int i;

    if (atom == None)
	return;

    if ((atoms = get_atoms_table(dpy)) == NULL) {

	_XmInitTargetsTable(dpy);

	atoms = get_atoms_table(dpy);
    }

    XGrabServer(dpy);

    if (!read_atoms_table(dpy, atoms)) {

	XUngrabServer(dpy);

	_XmInitTargetsTable(dpy);

	XGrabServer(dpy);

	atoms = get_atoms_table(dpy);
    }

    if (atoms->num_entries != 0) {

	for (i = 0; i < atoms->num_entries; i++) {

	    if (atoms->entries[i].atom == atom) {
		atoms->entries[i].time = CurrentTime;
		break;
	    }
	}
    }

    write_atoms_table(dpy, atoms);

    XUngrabServer(dpy);

    XFlush(dpy);
}

/*
 * I understand what it does, by why would you want to do it?
 */
void
_XmDestroyMotifWindow(Display *dpy)
{
    Atom dw;
    Window win;

    win = read_drag_window(dpy);
    if (win == None)
	return;

    dw = XmInternAtom(dpy, _XA_MOTIF_DRAG_WINDOW, False);

    XDeleteProperty(dpy, DefaultRootWindow(dpy), dw);

    XDestroyWindow(dpy, win);
}

/*
 * get the drag proxy window.  XmDisplay uses this to initialize
 * the proxy instance variable.
 */
Window
_XmGetDragProxyWindow(Display *display)
{
    Window win, ret = None;
    Atom pw, atype;
    int aformat;
    unsigned long nitems, bafter;
    unsigned char *prop = NULL;

    if ((win = read_drag_window(display)) == None)
	return None;

    pw = XmInternAtom(display, _XA_MOTIF_DRAG_PROXY_WINDOW, False);

    if (XGetWindowProperty(display, win, pw, 0L, 100000L,
			   False, AnyPropertyType,
			   &atype, &aformat, &nitems, &bafter, &prop)
	== Success && atype == XA_WINDOW && aformat == 32 && nitems == 1)
    {
	ret = *((Window *)prop);
    }

    if (prop)
	XFree((XPointer)prop);

    return win;
}

/*
 * get the drag receiver info property from the target
 *
 * check out the _XA_MOTIF_DRAG_RECEIVER_INFO property on a Motif
 * application's window.  There's a lot more there than what Daniel
 * talks about.  Must be due to PREREGISTER, yes?
 */
Boolean
_XmGetDragReceiverInfo(Display *display, Window win, XmDragReceiverInfo ri)
{
    Atom dri;
    XmDndReceiverProp *receiver;
    Atom type;
    int format;
    unsigned int border;
    unsigned long bafter, length;
    Window root;

    dri = XmInternAtom(display, _XA_MOTIF_DRAG_RECEIVER_INFO, False);

    if (XGetWindowProperty(display, win, dri, 0L, 100000L, False,
			   dri, &type, &format, &length, &bafter,
			   (unsigned char **)&receiver) != Success)
	return False;

    if (length < sizeof(XmDndReceiverProp)) {
	ri->dragProtocolStyle = XmDRAG_NONE;
	XFree((char *)receiver);
	return False;
    }

    if (receiver->protocol_version != DND_PROTOCOL_VERSION) {
	_XmWarning(NULL, "Drag protocol version mismatch: %d vs %d\n",
		   DND_PROTOCOL_VERSION, receiver->protocol_version);
    }

    if (receiver->byte_order != _XmByteOrder()) {
	SWAP4BYTES(receiver->proxy_window);
	SWAP2BYTES(receiver->num_drop_sites);
	SWAP4BYTES(receiver->total_size);
    }

    Display_ProxyWindow(XmGetXmDisplay(display)) = receiver->proxy_window;

    ri->dragProtocolStyle = receiver->protocol_style;
    ri->iccInfo = (XtPointer)XtMalloc(sizeof(XmShellDropSiteInfoRec));

    DSI_ByteOrder(ri->iccInfo) = receiver->byte_order;
    DSI_NumDropSites(ri->iccInfo) = receiver->num_drop_sites;
    DSI_Info(ri->iccInfo).atoms = (char *)receiver;
    DSI_Info(ri->iccInfo).atom_size = receiver->total_size;
    DSI_Info(ri->iccInfo).atom_start = (char *)(receiver+1);
    DSI_Info(ri->iccInfo).names = (char *)receiver + receiver->total_size;
    DSI_Info(ri->iccInfo).name_size = length - receiver->total_size;

    XGetGeometry(display, win, &root,
		 &ri->xOrigin, &ri->yOrigin, &ri->width, &ri->height,
		 &border, &ri->depth);

    XTranslateCoordinates(display, win, root, -border, -border,
			  &ri->xOrigin, &ri->yOrigin, &root);

    return True;
}

void
_XmSetDragReceiverInfo(Widget w, Widget widget)
{
}

void
_XmClearDragReceiverInfo(Widget shell)
{
}

/*
 * free up the dropsite info
 */
void
_XmFreeDragReceiverInfo(XmShellDropSiteInfo di)
{
    if (di) {
	if (di->info.atoms != NULL)
	    XFree(di->info.atoms);

	XtFree((char *)di);
    }
}

unsigned char 
_XmByteOrder(void)
{
  static unsigned char byte_order = 0;
  
  if (!byte_order) {
    unsigned int endian = 1;

    byte_order = (*((char *)&endian)) ? 'l' : 'B';
  }

  return byte_order;
}
