Logo Search packages:      
Sourcecode: saods9 version File versions

bltUnixDnd.c

/*
 *  bltUnixDnd.c --
 *
 *    This module implements a drag-and-drop manager for the BLT
 *    Toolkit.  Allows widgets to be registered as drag&drop sources
 *    and targets for handling "drag-and-drop" operations between
 *    Tcl/Tk applications.
 *
 * Copyright 1993-1998 Lucent Technologies, Inc.
 *
 * Permission to use, copy, modify, and distribute this software and
 * its documentation for any purpose and without fee is hereby
 * granted, provided that the above copyright notice appear in all
 * copies and that both that the copyright notice and warranty
 * disclaimer appear in supporting documentation, and that the names
 * of Lucent Technologies any of their entities not be used in
 * advertising or publicity pertaining to distribution of the software
 * without specific, written prior permission.
 *
 * Lucent Technologies disclaims all warranties with regard to this
 * software, including all implied warranties of merchantability and
 * fitness.  In no event shall Lucent Technologies be liable for any
 * special, indirect or consequential damages or any damages
 * whatsoever resulting from loss of use, data or profits, whether in
 * an action of contract, negligence or other tortuous action, arising
 * out of or in connection with the use or performance of this
 * software.
 *
 *    The "drag&drop" command was created by Michael J. McLennan.
 */

#include "bltInt.h"

#ifndef NO_DRAGDROP

#include <bltHash.h>
#include <bltChain.h>


#include <X11/Xatom.h>
#include <X11/Xproto.h>

#define DND_THREAD_KEY  "BLT Dnd Data"

#define DND_SELECTED    (1<<0)
#define DND_INITIATED   (1<<1)
#define DND_ACTIVE      (DND_SELECTED | DND_INITIATED)
#define DND_IN_PACKAGE  (1<<2)      /* Indicates if a token package command is
                         * currently active. The user may invoke
                         * "update" or "tkwait" commands from within
                         * the package command script. This allows the
                         * "drag" operation to preempt itself. */
#define DND_VOIDED      (1<<3)
#define DND_DELETED     (1<<4)

#define PACK(lo,hi)     (((hi) << 16) | ((lo) & 0x0000FFFF))
#define UNPACK(x,lo,hi) ((lo) = (x & 0x0000FFFF), (hi) = (x >> 16))

#define WATCH_ENTER     (1<<0)
#define WATCH_LEAVE     (1<<1)
#define WATCH_MOTION    (1<<2)
#define WATCH_MASK      (WATCH_ENTER | WATCH_LEAVE | WATCH_MOTION)

/* Source-to-Target Message Types */

#define ST_DRAG_ENTER   0x1001
#define ST_DRAG_LEAVE   0x1002
#define ST_DRAG_MOTION  0x1003
#define ST_DROP         0x1004

/* Target-to-Source Message Types */

#define TS_DRAG_STATUS  0x1005
#define TS_START_DROP   0x1006
#define TS_DROP_RESULT  0x1007

/* Indices of information fields in ClientMessage array. */

#define MESG_TYPE 0     /* Message type. */
#define MESG_WINDOW     1     /* Window id of remote. */
#define MESG_TIMESTAMP  2     /* Transaction timestamp. */
#define MESG_POINT      3     /* Root X-Y coordinate. */
#define MESG_STATE      4     /* Button and key state. */
#define MESG_RESPONSE   3     /* Response to drag/drop message. */
#define MESG_FORMAT     3     /* Format atom. */
#define MESG_PROPERTY   4     /* Index of button #/key state. */

/* Drop Status Values (actions included) */

#define DROP_CONTINUE   -2
#define DROP_FAIL -1
#define DROP_CANCEL     0
#define DROP_OK         1
#define DROP_COPY 1
#define DROP_LINK 2
#define DROP_MOVE 3

#define PROP_WATCH_FLAGS      0
#define PROP_DATA_FORMATS     1
#define PROP_MAX_SIZE         1000  /* Maximum size of property. */

#define PROTO_BLT 0
#define PROTO_XDND      1

#define TOKEN_OFFSET    0
#define TOKEN_REDRAW    (1<<0)

#define TOKEN_NORMAL    0
#define TOKEN_REJECT    -1
#define TOKEN_ACTIVE    1
#define TOKEN_TIMEOUT   5000  /* 5 second timeout for drop requests. */

/*
 *   Each widget representing a drag & drop target is tagged with 
 *   a "BltDndTarget" property in XA_STRING format.  This property 
 *   identifies the window as a target.  It's formated as a Tcl list 
 *   and contains the following information:
 *
 *    "flags DATA_TYPE DATA_TYPE ..."
 *
 *    "INTERP_NAME TARGET_NAME WINDOW_ID DATA_TYPE DATA_TYPE ..."
 *
 *    INTERP_NAME Name of the target application's interpreter.
 *    TARGET_NAME Path name of widget registered as the drop target.  
 *    WINDOW_ID   Window Id of the target's communication window. 
 *                Used to forward Enter/Leave/Motion event information 
 *                to the target.
 *    DATA_TYPE   One or more "types" handled by the target.
 *
 *   When the user invokes the "drag" operation, the window hierarchy
 *   is progressively examined.  Window information is cached during
 *   the operation, to minimize X server traffic. Windows carrying a
 *   "BltDndTarget" property are identified.  When the token is dropped 
 *   over a valid site, the drop information is sent to the application 
 *   via the usual "send" command.  If communication fails, the drag&drop 
 *   facility automatically posts a rejection symbol on the token window.  
 */

/* 
 * Drop Protocol:
 *
 *          Source                        Target
 *              ------                          ------
 *   ButtonRelease-? event.
 *   Invokes blt::dnd drop
 *             +
 *   Send "drop" message to target (via 
 *   ClientMessage). Contains X-Y, key/ --> Gets "drop" message. 
 *   button state, source window XID.       Invokes LeaveCmd proc.
 *                                Gets property from source of 
 *                                ordered matching formats.  
 *                                        +
 *                                Invokes DropCmd proc. Arguments
 *                                are X-Y coordinate, key/button 
 *                                state, transaction timestamp, 
 *                                list of matching formats.  
 *                                      +
 *                                Target selects format and invokes
 *                                blt::dnd pull to transfer the data
 *                                in the selected format.
 *                                      +
 *                                Sends "drop start" message to 
 *                                source.  Contains selected format
 *   Gets "drop start" message.           <-- (as atom), ?action?, target window
 *   Invokes data handler for the             ID, transaction timestamp. 
 *   selected format.                         +
 *                +                     Waits for property to change on
 *   Places first packet of data in         its window.  Time out set for
 *   property on target window.         --> no response.
 *                +                                 +
 *   Waits for response property            After each packet, sets zero-length
 *   change. Time out set for no resp.  <-- property on source window.
 *   If non-zero length packet, error               +
 *   occurred, packet is error message.     Sends "drop finished" message.
 *                                Contains transaction timestamp, 
 *   Gets "drop finished" message.      <-- status, ?action?.
 *   Invokes FinishCmd proc. 
 */

/* Configuration Parameters */

#define DEF_DND_BUTTON_BACKGROUND         RGB_YELLOW
#define DEF_DND_BUTTON_BG_MONO            STD_NORMAL_BG_MONO
#define DEF_DND_BUTTON_NUMBER       "3"
#define DEF_DND_ENTER_COMMAND       (char *)NULL
#define DEF_DND_LEAVE_COMMAND       (char *)NULL
#define DEF_DND_MOTION_COMMAND            (char *)NULL
#define DEF_DND_DROP_COMMAND        (char *)NULL
#define DEF_DND_RESULT_COMMAND            (char *)NULL
#define DEF_DND_PACKAGE_COMMAND           (char *)NULL
#define DEF_DND_SELF_TARGET         "no"
#define DEF_DND_SEND                (char *)NULL
#define DEF_DND_IS_TARGET           "no"
#define DEF_DND_IS_SOURCE           "no"
#define DEF_DND_SITE_COMMAND        (char *)NULL

#define DEF_DND_DRAG_THRESHOLD            "0"
#define DEF_TOKEN_ACTIVE_BACKGROUND STD_ACTIVE_BACKGROUND
#define DEF_TOKEN_ACTIVE_BG_MONO    STD_ACTIVE_BG_MONO
#define DEF_TOKEN_ACTIVE_BORDERWIDTH      "3"
#define DEF_TOKEN_ACTIVE_RELIEF           "sunken"
#define DEF_TOKEN_ANCHOR            "se"
#define DEF_TOKEN_BACKGROUND        STD_NORMAL_BACKGROUND
#define DEF_TOKEN_BG_MONO           STD_NORMAL_BG_MONO
#define DEF_TOKEN_BORDERWIDTH       "3"
#define DEF_TOKEN_CURSOR            "top_left_arrow"
#define DEF_TOKEN_REJECT_BACKGROUND STD_NORMAL_BACKGROUND
#define DEF_TOKEN_REJECT_BG_MONO    RGB_WHITE
#define DEF_TOKEN_REJECT_FOREGROUND RGB_RED
#define DEF_TOKEN_REJECT_FG_MONO    RGB_BLACK
#define DEF_TOKEN_REJECT_STIPPLE_COLOR    (char *)NULL
#define DEF_TOKEN_REJECT_STIPPLE_MONO     RGB_GREY50
#define DEF_TOKEN_RELIEF            "raised"

static int StringToCursors _ANSI_ARGS_((ClientData clientData, 
      Tcl_Interp *interp, Tk_Window tkwin, char *string, char *widgRec, 
      int offset));
static char *CursorsToString _ANSI_ARGS_((ClientData clientData, Tk_Window tkwin,
      char *widgRec, int offset, Tcl_FreeProc **freeProcPtr));

Tk_CustomOption cursorsOption =
{
    StringToCursors, CursorsToString, (ClientData)0
};

typedef struct {
    Blt_HashTable dndTable;   /* Hash table of dnd structures keyed by 
                         * the address of the reference Tk window */
    Tk_Window mainWindow;
    Display *display;
    Atom mesgAtom;            /* Atom signifying a drag-and-drop message. */
    Atom formatsAtom;         /* Source formats property atom.  */
    Atom targetAtom;          /* Target property atom. */
    Atom commAtom;            /* Communication property atom. */

#ifdef HAVE_XDND
    Blt_HashTable handlerTable; /* Table of toplevel windows with XdndAware 
                         * properties attached to them. */
    Atom XdndActionListAtom;
    Atom XdndAwareAtom;
    Atom XdndEnterAtom;
    Atom XdndFinishedAtom;
    Atom XdndLeaveAtom;
    Atom XdndPositionAtom;
    Atom XdndSelectionAtom;
    Atom XdndStatusAtom;
    Atom XdndTypeListAtom;

    Atom XdndActionCopyAtom;
    Atom XdndActionMoveAtom;
    Atom XdndActionLinkAtom;
    Atom XdndActionAskAtom;
    Atom XdndActionPrivateAtom;
    Atom XdndActionDescriptionAtom;
#endif
} DndInterpData;


typedef struct {
    Tcl_DString dString;
    Window window;            /* Source/Target window */
    Display *display;
    Atom commAtom;            /* Data communication property atom. */
    int packetSize;
    Tcl_TimerToken timerToken;
    int status;               /* Status of transaction:  CONTINUE, OK, FAIL,
                         * or TIMEOUT. */
    int timestamp;            /* Timestamp of the transaction. */
    int offset;
    int protocol;       /* Drag-and-drop protocol used by the source:
                         * either PROTO_BLT or PROTO_XDND. */
} DropPending;

/* 
 * SubstDescriptors --
 *
 *    Structure to hold letter-value pairs for percent substitutions.
 */
typedef struct {
    char letter;        /* character like 'x' in "%x" */
    char *value;        /* value to be substituted in place of "%x" */
} SubstDescriptors;

/*
 *  Drag&Drop Registration Data
 */
typedef struct {
    Tk_Window tkwin;          /* Window that embodies the token.  NULL
                         * means that the window has been destroyed
                         * but the data structures haven't yet been
                         * cleaned up. */

    Display *display;         /* Display containing widget.  Used, among
                         * other things, so that resources can be
                         * freed even after tkwin has gone away. */
    Tcl_Interp *interp;       /* Interpreter associated with widget.  Used
                         * to delete widget command. */
    Tk_3DBorder border;       /* Structure used to draw 3-D border and
                         * background.  NULL means no background
                         * or border. */
    int borderWidth;          /* Width of 3-D border (if any). */
    int relief;               /* 3-d effect: TK_RELIEF_RAISED etc. */

    int flags;                /* Various flags;  see below for
                         * definitions. */

    /* Token specific fields */
    int x, y;                 /* Last position of token window */
    int startX, startY;

    int status;               /* Indicates the current status of the token:
                         * 0 is normal, 1 is active. */
    int lastStatus;           /* Indicates the last status of the token. */
    Tcl_TimerToken timerToken;      /* Token for routine to hide tokenwin */
    GC fillGC;                /* GC used to draw rejection fg: (\) */
    GC outlineGC;       /* GC used to draw rejection bg: (\) */
    int width, height;

    /* User-configurable fields */

    Tk_Anchor anchor;         /* Position of token win relative to mouse */
    Tk_3DBorder normalBorder; /* Border/background for token window */
    Tk_3DBorder activeBorder; /* Border/background for token window */
    int activeRelief;
    int activeBorderWidth;    /* Border width in pixels */
    XColor *fillColor;        /* Color used to draw rejection fg: (\) */
    XColor *outlineColor;     /* Color used to draw rejection bg: (\) */
    Pixmap rejectStipple;     /* Stipple used to draw rejection: (\) */
    int reqWidth, reqHeight;

    int nSteps;

} Token;

/*
 *  Winfo --
 *
 *    This structure represents a window hierarchy examined during a single
 *    "drag" operation.  It's used to cache information to reduce the round
 *    trip calls to the server needed to query window geometry information
 *    and grab the target property.  
 */
typedef struct WinfoStruct {
    Window window;            /* Window in hierarchy. */
    
    int initialized;          /* If zero, the rest of this structure's
                         * information hasn't been set. */
    
    int x1, y1, x2, y2;       /* Extents of the window (upper-left and
                         * lower-right corners). */
    
    struct WinfoStruct *parentPtr;  /* Parent node. NULL if root. Used to
                         * compute offset for X11 windows. */
    
    Blt_Chain *chainPtr;      /* List of this window's children. If NULL,
                         * there are no children. */
    
    int isTarget;       /* Indicates if this window is a drag&drop
                         * target. */
    int lookedForProperty;    /* Indicates if this window  */
    
    int eventFlags;           /* Retrieved from the target's drag&drop 
                         * property, indicates what kinds of pointer
                         * events should be relayed to the target via
                         * ClientMessages. Possible values are OR-ed 
                         * combinations of the following bits: 
                         *    001 Enter events.  
                         *    010 Motion events.
                         *    100 Leave events.  
                         */
    char *matches;
    
} Winfo;

/*
 *  Dnd --
 *
 *    This structure represents the drag&drop manager.  It is associated
 *    with a widget as a drag&drop source, target, or both.  It contains
 *    both the source and target components, since a widget can be both 
 *    a drag source and a drop target.  
 */
typedef struct {
    Tcl_Interp *interp;       /* Interpreter associated with the drag&drop
                         * manager. */
    
    Tk_Window tkwin;          /* Tk window representing the drag&drop 
                         * manager (can be source and/or target). */

    Display *display;         /* Display for drag&drop widget. Saved to free
                         * resources after window has been destroyed. */

    int isSource;       /* Indicates if this drag&drop manager can act
                         * as a drag source. */
    int isTarget;       /* Indicates if this drag&drop manager can act
                         * as a drop target. */

    int targetPropertyExists; /* Indicates is the drop target property has 
                         * been set. */

    unsigned int flags;       /* Various flags;  see below for
                         * definitions. */
    int timestamp;            /* Id of the current drag&drop transaction. */

    int x, y;                 /* Last known location of the mouse pointer. */

    Blt_HashEntry *hashPtr;

    DndInterpData *dataPtr;   

    /* Source component. */
    
    Blt_HashTable getDataTable;     /* Table of data handlers (converters)
                         * registered for this source. */

    int reqButton;            /* Button used to invoke drag operation. */

    int button;               /* Last button press detected. */
    int keyState;       /* Last key state.  */

    Tk_Cursor cursor;         /* Cursor restored after dragging */

    int selfTarget;           /* Indicated if the source should drop onto 
                         * itself. */

    char **reqFormats;        /* List of requested data formats. The
                         * list should be ordered with the more 
                         * desireable formats first. You can also
                         * temporarily turn off a source by setting 
                         * the value to the empty string. */

    Winfo *rootPtr;           /* Cached window information: Gathered
                         * and used during the "drag" operation 
                         * to see if the mouse pointer is over a 
                         * valid target. */

    Winfo *windowPtr;         /* Points to information about the last 
                         * target the pointer was over. If NULL, 
                         * the pointer was not over a valid target. */

    char **packageCmd;        /* Tcl command executed at start of the drag
                         * operation to initialize token. */

    char **resultCmd;         /* Tcl command executed at the end of the
                         * "drop" operation to indicate its status. */

    char **siteCmd;           /* Tcl command executed to update token 
                         * window. */

    Token *tokenPtr;          /* Token used to provide special cursor. */
    

    Tcl_TimerToken timerToken;

    Tk_Cursor *cursors;       /* Array of drag-and-drop cursors. */
    int cursorPos;

    int dragStart;            /* Minimum number of pixels movement
                         * before B1-Motion is considered to
                         * start dragging. */

    /* Target component. */

    Blt_HashTable setDataTable;     /* Table of data handlers (converters)
                         * registered for this target. */
    char **enterCmd;          /* Tcl proc called when the mouse enters the
                         * target. */
    char **leaveCmd;          /* Tcl proc called when the mouse leaves the
                         * target. */
    char **motionCmd;         /* Tcl proc called when the mouse is moved
                         * over the target. */
    char **dropCmd;           /* Tcl proc called when the mouse button
                         * is released over the target. */

    char *matchingFormats;
    int lastId;               /* The last transaction id used. This is used
                         * to cache the above formats string. */

    DropPending *pendingPtr;  /* Points to structure containing information
                         * about a current drop in progress. If NULL,
                         * no drop is in progress. */

    short int dropX, dropY;   /* Location of the current drop. */
    short int dragX, dragY;   /* Starting position of token window */
} Dnd;


typedef struct {
    Tk_Window tkwin;          /* Toplevel window of the drop target. */
    int refCount;       /* # of targets referencing this structure. */
    Dnd *dndPtr;        /* Last drop target selected.  Used the 
                         * implement Enter/Leave events for targets. 
                         * If NULL, indicates that no drop target was 
                         * previously selected. */
    int lastRepsonse;         /* Indicates what the last response was. */
    Window window;            /* Window id of the top-level window (ie.
                         * the wrapper). */
    char **formatArr;         /* List of formats available from source. 
                         * Must be pruned down to matching list. */
    DndInterpData *dataPtr;
    int x, y;
    
} XDndHandler;

extern Tk_CustomOption bltListOption;
extern Tk_CustomOption bltDistanceOption;

static Tk_ConfigSpec configSpecs[] =
{
    {TK_CONFIG_CUSTOM, "-allowformats", "allowFormats", "AllowFormats", 
      DEF_DND_SEND, Tk_Offset(Dnd, reqFormats), 
        TK_CONFIG_NULL_OK, &bltListOption},
    {TK_CONFIG_INT, "-button", "buttonNumber", "ButtonNumber",
      DEF_DND_BUTTON_NUMBER, Tk_Offset(Dnd, reqButton), 0},
    {TK_CONFIG_CUSTOM, "-dragthreshold", "dragThreshold", "DragThreshold",
      DEF_DND_DRAG_THRESHOLD, Tk_Offset(Dnd, dragStart), 0, 
        &bltDistanceOption},
    {TK_CONFIG_CUSTOM, "-cursors", "cursors", "cursors",
      DEF_TOKEN_CURSOR, Tk_Offset(Dnd, cursors), 
      TK_CONFIG_NULL_OK, &cursorsOption },
    {TK_CONFIG_CUSTOM, "-onenter", "onEnter", "OnEnter",
      DEF_DND_ENTER_COMMAND, Tk_Offset(Dnd, enterCmd), 
      TK_CONFIG_NULL_OK, &bltListOption},
    {TK_CONFIG_CUSTOM, "-onmotion", "onMotion", "OnMotion",
      DEF_DND_MOTION_COMMAND, Tk_Offset(Dnd, motionCmd), 
      TK_CONFIG_NULL_OK, &bltListOption},
    {TK_CONFIG_CUSTOM, "-onleave", "onLeave", "OnLeave",
      DEF_DND_LEAVE_COMMAND, Tk_Offset(Dnd, leaveCmd), 
      TK_CONFIG_NULL_OK, &bltListOption},
    {TK_CONFIG_CUSTOM, "-ondrop", "onDrop", "OnDrop",
      DEF_DND_DROP_COMMAND, Tk_Offset(Dnd, dropCmd), 
      TK_CONFIG_NULL_OK, &bltListOption},
    {TK_CONFIG_CUSTOM, "-package", "packageCommand", "PackageCommand",
      DEF_DND_PACKAGE_COMMAND, Tk_Offset(Dnd, packageCmd), 
        TK_CONFIG_NULL_OK, &bltListOption },
    {TK_CONFIG_CUSTOM, "-result", "result", "Result",
      DEF_DND_RESULT_COMMAND, Tk_Offset(Dnd, resultCmd), 
      TK_CONFIG_NULL_OK, &bltListOption},
    {TK_CONFIG_BOOLEAN, "-selftarget", "selfTarget", "SelfTarget",
      DEF_DND_SELF_TARGET, Tk_Offset(Dnd, selfTarget), 0},
    {TK_CONFIG_CUSTOM, "-site", "siteCommand", "Command",
      DEF_DND_SITE_COMMAND, Tk_Offset(Dnd, siteCmd), 
        TK_CONFIG_NULL_OK, &bltListOption},
    {TK_CONFIG_BOOLEAN, "-source", "source", "Source",
      DEF_DND_IS_SOURCE, Tk_Offset(Dnd, isSource), 0},
    {TK_CONFIG_BOOLEAN, "-target", "target", "Target",
      DEF_DND_IS_TARGET, Tk_Offset(Dnd, isTarget), 0},
    {TK_CONFIG_END, (char *)NULL, (char *)NULL, (char *)NULL, (char *)NULL, 
      0, 0},
};

static Tk_ConfigSpec tokenConfigSpecs[] =
{
    {TK_CONFIG_BORDER, "-activebackground", "activeBackground",
      "ActiveBackground", DEF_TOKEN_ACTIVE_BACKGROUND,
      Tk_Offset(Token, activeBorder), TK_CONFIG_COLOR_ONLY},
    {TK_CONFIG_BORDER, "-activebackground", "activeBackground",
      "ActiveBackground", DEF_TOKEN_ACTIVE_BG_MONO, 
      Tk_Offset(Token, activeBorder), TK_CONFIG_MONO_ONLY},
    {TK_CONFIG_RELIEF, "-activerelief", "activeRelief", "activeRelief",
      DEF_TOKEN_ACTIVE_RELIEF, Tk_Offset(Token, activeRelief), 0},
    {TK_CONFIG_ANCHOR, "-anchor", "anchor", "Anchor",
      DEF_TOKEN_ANCHOR, Tk_Offset(Token, anchor), 0},
    {TK_CONFIG_PIXELS, "-activeborderwidth", "activeBorderWidth",
      "ActiveBorderWidth", DEF_TOKEN_ACTIVE_BORDERWIDTH, 
      Tk_Offset(Token, activeBorderWidth), 0},
    {TK_CONFIG_BORDER, "-background", "background", "Background",
      DEF_TOKEN_BACKGROUND, Tk_Offset(Token, normalBorder),
      TK_CONFIG_COLOR_ONLY},
    {TK_CONFIG_BORDER, "-background", "background", "Background",
      DEF_TOKEN_BG_MONO, Tk_Offset(Token, normalBorder),
      TK_CONFIG_MONO_ONLY},
    {TK_CONFIG_PIXELS, "-borderwidth", "borderWidth", "BorderWidth",
      DEF_TOKEN_BORDERWIDTH, Tk_Offset(Token, borderWidth), 0},
    {TK_CONFIG_COLOR, "-outline", "outline", "Outline",
      DEF_TOKEN_REJECT_BACKGROUND, Tk_Offset(Token, outlineColor), 
      TK_CONFIG_COLOR_ONLY},
    {TK_CONFIG_COLOR, "-outline", "outline", "Outline",
      DEF_TOKEN_REJECT_BG_MONO, Tk_Offset(Token, outlineColor), 
      TK_CONFIG_MONO_ONLY},
    {TK_CONFIG_COLOR, "-fill", "fill", "Fill",
      DEF_TOKEN_REJECT_FOREGROUND, Tk_Offset(Token, fillColor), 
      TK_CONFIG_COLOR_ONLY},
    {TK_CONFIG_COLOR, "-fill", "fill", "Fill",
      DEF_TOKEN_REJECT_BACKGROUND, Tk_Offset(Token, fillColor), 
      TK_CONFIG_MONO_ONLY},
    {TK_CONFIG_BITMAP, "-rejectstipple", "rejectStipple", "Stipple",
      DEF_TOKEN_REJECT_STIPPLE_COLOR, Tk_Offset(Token, rejectStipple),
      TK_CONFIG_COLOR_ONLY | TK_CONFIG_NULL_OK},
    {TK_CONFIG_BITMAP, "-rejectstipple", "rejectStipple", "Stipple",
      DEF_TOKEN_REJECT_STIPPLE_MONO, Tk_Offset(Token, rejectStipple),
      TK_CONFIG_MONO_ONLY},
    {TK_CONFIG_RELIEF, "-relief", "relief", "Relief",
      DEF_TOKEN_RELIEF, Tk_Offset(Token, relief), 0},
    {TK_CONFIG_INT, "-width", "width", "Width",
      (char *)NULL, Tk_Offset(Token, reqWidth), 0},
    {TK_CONFIG_INT, "-height", "height", "Height",
      (char *)NULL, Tk_Offset(Token, reqHeight), 0},
    {TK_CONFIG_END, (char *)NULL, (char *)NULL, (char *)NULL, (char *)NULL, 
      0, 0},
};


/*
 *  Forward Declarations
 */
static int DndCmd _ANSI_ARGS_((ClientData clientData, Tcl_Interp *interp,
      int argc, char **argv));
static void TokenEventProc _ANSI_ARGS_((ClientData clientData, 
      XEvent *eventPtr));
static void MoveToken _ANSI_ARGS_((Dnd *dndPtr));
static void DisplayToken _ANSI_ARGS_((ClientData clientData));
static void HideToken _ANSI_ARGS_((Dnd *dndPtr));
static void DrawRejectSymbol _ANSI_ARGS_((Dnd *dndPtr));

static int GetDnd _ANSI_ARGS_((ClientData clientData, Tcl_Interp *interp, 
      char *name, Dnd **dndPtrPtr));
static Dnd *CreateDnd _ANSI_ARGS_((Tcl_Interp *interp, Tk_Window tkwin));
static void DestroyDnd _ANSI_ARGS_((DestroyData data));
static int DndEventProc _ANSI_ARGS_((ClientData clientData, XEvent *eventPtr));
static int ConfigureToken _ANSI_ARGS_((Tcl_Interp *interp, Dnd *dndPtr,
      int argc, char **argv, int flags));

static Winfo *OverTarget _ANSI_ARGS_((Dnd *dndPtr));
static void AddTargetProperty _ANSI_ARGS_((Dnd *dndPtr));

static Winfo *InitRoot _ANSI_ARGS_((Dnd *dndPtr));
static void FreeWinfo _ANSI_ARGS_((Winfo *wr));
static void GetWinfo _ANSI_ARGS_((Display *display, Winfo * windowPtr));

static void CancelDrag _ANSI_ARGS_((Dnd *dndPtr));

/*
 * ----------------------------------------------------------------------------
 *
 * StringToCursors --
 *
 *    Converts the resize mode into its numeric representation.  Valid
 *    mode strings are "none", "expand", "shrink", or "both".
 *
 * ----------------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
StringToCursors(clientData, interp, tkwin, string, widgRec, offset)
    ClientData clientData;    /* Not used. */
    Tcl_Interp *interp;       /* Interpreter to send results back to */
    Tk_Window tkwin;          /* Not used. */
    char *string;       /* String representing cursors. */
    char *widgRec;            /* Structure record */
    int offset;               /* Offset of field in record. */
{
    Tk_Cursor **cursorPtrPtr = (Tk_Cursor **)(widgRec + offset);
    int result = TCL_OK;
    int nElems;
    char **elemArr;

    if (*cursorPtrPtr != NULL) {
      Blt_Free(*cursorPtrPtr);
      *cursorPtrPtr = NULL;
    }
    if (string == NULL) {
      return TCL_OK;
    }
    if (Tcl_SplitList(interp, string, &nElems, &elemArr) != TCL_OK) {
      return TCL_ERROR;
    }
    if (nElems > 0) {
      Tk_Cursor *cursorArr;
      register int i;

      cursorArr = Blt_Calloc(nElems + 1, sizeof(Tk_Cursor));
      for (i = 0; i < nElems; i++) {
          cursorArr[i] = Tk_GetCursor(interp, tkwin, Tk_GetUid(elemArr[i]));
          if (cursorArr[i] == None) {
            *cursorPtrPtr = cursorArr;
            result = TCL_ERROR;
            break;
          }
      }    
      Blt_Free(elemArr);
      *cursorPtrPtr = cursorArr;
    }
    return result;
}

/*
 * ----------------------------------------------------------------------------
 *
 * CursorsToString --
 *
 *    Returns resize mode string based upon the resize flags.
 *
 * Results:
 *    The resize mode string is returned.
 *
 * ----------------------------------------------------------------------------
 */
/*ARGSUSED*/
static char *
CursorsToString(clientData, tkwin, widgRec, offset, freeProcPtr)
    ClientData clientData;    /* Not used. */
    Tk_Window tkwin;          /* Not used. */
    char *widgRec;            /* Cursor record */
    int offset;               /* Offset of record. */
    Tcl_FreeProc **freeProcPtr;     /* Memory deallocation scheme to use */
{
    Tk_Cursor *cursorArr = *(Tk_Cursor **)(widgRec + offset);
    Tk_Cursor *cursorPtr;
    Tcl_DString dString;
    char *result;

    if (cursorArr == NULL) {
      return "";
    }
    Tcl_DStringInit(&dString);
    for (cursorPtr = cursorArr; *cursorPtr != NULL; cursorPtr++) {
      Tcl_DStringAppendElement(&dString, 
             Tk_NameOfCursor(Tk_Display(tkwin), *cursorPtr));
    }
    result = Blt_Strdup(Tcl_DStringValue(&dString));
    Tcl_DStringFree(&dString);
    *freeProcPtr = (Tcl_FreeProc *)Blt_Free;
    return result;
}


static char *
PrintList(list)
    char **list;
{
    int count;
    char **p;

    count = 0;
    for(p = list; *p != NULL; p++) {
      count++;
    }
    return Tcl_Merge(count, list);
}


/* ARGSUSED */
static int
XSendEventErrorProc(clientData, errEventPtr)
    ClientData clientData;
    XErrorEvent *errEventPtr;
{
    int *errorPtr = clientData;

    *errorPtr = TCL_ERROR;
    return 0;
}

static void
SendClientMsg(display, window, mesgAtom, data0, data1, data2, data3, data4)
    Display *display;
    Window window;
    Atom mesgAtom;
    int data0, data1, data2, data3, data4;
{
    XEvent event;
    Tk_ErrorHandler handler;
    int result;
    int any = -1;

    event.xclient.type = ClientMessage;
    event.xclient.serial = 0;
    event.xclient.send_event = True;
    event.xclient.display = display;
    event.xclient.window = window;
    event.xclient.message_type = mesgAtom;
    event.xclient.format = 32;
    event.xclient.data.l[0] = data0;
    event.xclient.data.l[1] = data1;
    event.xclient.data.l[2] = data2;
    event.xclient.data.l[3] = data3;
    event.xclient.data.l[4] = data4;

    result = TCL_OK;
    handler = Tk_CreateErrorHandler(display, any, X_SendEvent, any,
      XSendEventErrorProc, &result);
    if (!XSendEvent(display, window, False, ClientMessage, &event)) {
      result = TCL_ERROR;
    }
    Tk_DeleteErrorHandler(handler);
    XSync(display, False);
    if (result != TCL_OK) {
      fprintf(stderr, "XSendEvent response to drop: Protocol failed\n");
    }
}

/*
 * ------------------------------------------------------------------------
 *
 *  GetWindowZOrder --
 *
 *    Returns a chain of the child windows according to their stacking
 *    order. The window ids are ordered from top to bottom.
 *
 * ------------------------------------------------------------------------ 
 */
static Blt_Chain *
GetWindowZOrder(display, window)
    Display *display;
    Window window;
{
    Blt_Chain *chainPtr;
    Window *childArr;
    unsigned int nChildren;
    Window dummy;

    chainPtr = NULL;
    if ((XQueryTree(display, window, &dummy, &dummy, &childArr, &nChildren)) &&
      (nChildren > 0)) {
      register int i;

      chainPtr = Blt_ChainCreate();
      for (i = 0; i < nChildren; i++) {
          /* 
           * XQuery returns windows in bottom to top order.  We only care
           * about the top window.  
           */
          Blt_ChainPrepend(chainPtr, (ClientData)childArr[i]);
      }
      if (childArr != NULL) {
          XFree((char *)childArr);  /* done with list of kids */
      }
    }
    return chainPtr;
}

static int
GetMaxPropertySize(display)
    Display *display;
{
    int size;

    size = Blt_MaxRequestSize(display, sizeof(char));
    size -= 32;
    return size;
}

/*
 * ------------------------------------------------------------------------
 *
 *  GetProperty --
 *
 *    Returns the data associated with the named property on the
 *    given window.  All data is assumed to be 8-bit string data.
 *
 * ------------------------------------------------------------------------ 
 */
static char *
GetProperty(display, window, atom)
    Display *display;
    Window window;
    Atom atom;
{
    char *data;
    int result, format;
    Atom typeAtom;
    unsigned long nItems, bytesAfter;

    if (window == None) {
      return NULL;
    }
    data = NULL;
    result = XGetWindowProperty(
        display,        /* Display of window. */
      window,                 /* Window holding the property. */
        atom,                 /* Name of property. */
        0,              /* Offset of data (for multiple reads). */
      GetMaxPropertySize(display), /* Maximum number of items to read. */
      False,                  /* If true, delete the property. */
        XA_STRING,            /* Desired type of property. */
        &typeAtom,            /* (out) Actual type of the property. */
        &format,        /* (out) Actual format of the property. */
        &nItems,        /* (out) # of items in specified format. */
        &bytesAfter,          /* (out) # of bytes remaining to be read. */
      (unsigned char **)&data);
    if ((result != Success) || (format != 8) || (typeAtom != XA_STRING)) {
      if (data != NULL) {
          XFree((char *)data);
          data = NULL;
      }
    }
    return data;
}

/*
 * ------------------------------------------------------------------------
 *
 *  SetProperty --
 *
 *    Associates the given data with the a property on a given window.
 *    All data is assumed to be 8-bit string data.
 *
 * ------------------------------------------------------------------------ 
 */
static void
SetProperty(tkwin, atom, data)
    Tk_Window tkwin;
    Atom atom;
    char *data;
{
    XChangeProperty(Tk_Display(tkwin), Tk_WindowId(tkwin), atom, XA_STRING,
      8, PropModeReplace, (unsigned char *)data, strlen(data) + 1);
}

/*
 * ------------------------------------------------------------------------
 *
 *  GetWindowRegion --
 *
 *    Queries for the upper-left and lower-right corners of the 
 *    given window.  
 *
 *  Results:
 *    Returns if the window is currently viewable.  The coordinates
 *    of the window are returned via parameters.
 *
 * ------------------------------------------------------------------------ 
 */
static int
GetWindowRegion(display, windowPtr)
    Display *display;
    Winfo *windowPtr;
{
    XWindowAttributes winAttrs;

    if (XGetWindowAttributes(display, windowPtr->window, &winAttrs)) {
      windowPtr->x1 = winAttrs.x;
      windowPtr->y1 = winAttrs.y;
      windowPtr->x2 = winAttrs.x + winAttrs.width - 1;
      windowPtr->y2 = winAttrs.y + winAttrs.height - 1;
    }
    return (winAttrs.map_state == IsViewable);
}

/*
 * ------------------------------------------------------------------------
 *
 *  FindTopWindow --
 *
 *    Searches for the topmost window at a given pair of X-Y coordinates.
 *
 *  Results:
 *    Returns a pointer to the node representing the window containing
 *    the point.  If one can't be found, NULL is returned.
 *
 * ------------------------------------------------------------------------
 */
static Winfo *
FindTopWindow(dndPtr, x, y)
    Dnd *dndPtr;
    int x, y;
{
    Winfo *rootPtr;
    register Blt_ChainLink *linkPtr;
    register Winfo *windowPtr;

    rootPtr = dndPtr->rootPtr;
    if (!rootPtr->initialized) {
      GetWinfo(dndPtr->display, rootPtr);
    }
    if ((x < rootPtr->x1) || (x > rootPtr->x2) ||
      (y < rootPtr->y1) || (y > rootPtr->y2)) {
      return NULL;            /* Point is not over window  */
    }
    windowPtr = rootPtr;

    /*  
     * The window list is ordered top to bottom, so stop when we find the
     * first child that contains the X-Y coordinate. It will be the topmost
     * window in that hierarchy.  If none exists, then we already have the
     * topmost window.  
     */
  descend:
    for (linkPtr = Blt_ChainFirstLink(rootPtr->chainPtr); linkPtr != NULL;
      linkPtr = Blt_ChainNextLink(linkPtr)) {
      rootPtr = Blt_ChainGetValue(linkPtr);
      if (!rootPtr->initialized) {
          GetWinfo(dndPtr->display, rootPtr);
      }
      if (rootPtr->window == Blt_GetRealWindowId(dndPtr->tokenPtr->tkwin)) {
          continue;           /* Don't examine the token window. */
      }
      if ((x >= rootPtr->x1) && (x <= rootPtr->x2) &&
          (y >= rootPtr->y1) && (y <= rootPtr->y2)) {
          /*   
           * Remember the last window containing the pointer and descend
           * into its window hierarchy. We'll look for a child that also
           * contains the pointer.  
           */
          windowPtr = rootPtr;
          goto descend;
      }
    }
    return windowPtr;
}

/*
 * ------------------------------------------------------------------------
 *
 *  GetWidgetCursor --
 *
 *    Queries a widget for its current cursor.   The given window
 *    may or may not be a Tk widget that has a -cursor option. 
 *
 *  Results:
 *    Returns the current cursor of the widget.
 *
 * ------------------------------------------------------------------------ 
 */
static Tk_Cursor
GetWidgetCursor(interp, tkwin)
    Tcl_Interp *interp;       /* Interpreter to evaluate widget command. */
    Tk_Window tkwin;          /* Window of drag&drop source. */
{
    Tk_Cursor cursor;
    Tcl_DString dString, savedResult;

    cursor = None;
    Tcl_DStringInit(&dString);
    Blt_DStringAppendElements(&dString, Tk_PathName(tkwin), "cget", "-cursor",
            (char *)NULL);
    Tcl_DStringInit(&savedResult);
    Tcl_DStringGetResult(interp, &savedResult);
    if (Tcl_GlobalEval(interp, Tcl_DStringValue(&dString)) == TCL_OK) {
      char *name;

      name = Tcl_GetStringResult(interp);
      if ((name != NULL) && (name[0] != '\0')) {
          cursor = Tk_GetCursor(interp, tkwin, Tk_GetUid(name));
      }
    }
    Tcl_DStringResult(interp, &savedResult);
    Tcl_DStringFree(&dString);
    return cursor;
}

/*
 * ------------------------------------------------------------------------
 *
 *  NameOfStatus --
 *
 *    Converts a numeric drop result into its string representation.
 *
 *  Results:
 *    Returns a static string representing the drop result.
 *
 * ------------------------------------------------------------------------
 */
static char *
NameOfStatus(status)
    int status;
{
    switch (status) {
    case DROP_OK:
      return "active";
    case DROP_CONTINUE:
      return "normal";
    case DROP_FAIL:
      return "reject";
    case DROP_CANCEL:
      return "cancel";
    default:
      return "unknown status value";
    }
}

/*
 * ------------------------------------------------------------------------
 *
 *  NameOfAction --
 *
 *    Converts a numeric drop result into its string representation.
 *
 *  Results:
 *    Returns a static string representing the drop result.
 *
 * ------------------------------------------------------------------------
 */
static char *
NameOfAction(action)
    int action;
{
    switch (action) {
    case DROP_COPY:
      return "copy";
    case DROP_CANCEL:
      return "cancel";
    case DROP_MOVE:
      return "move";
      break;
    case DROP_LINK:
      return "link";
    case DROP_FAIL:
      return "fail";
    default:
      return "unknown action";
    }
}

/*
 * ------------------------------------------------------------------------
 *
 *  GetAction --
 *
 *    Converts a string to its numeric drop result value.
 *
 *  Results:
 *    Returns the drop result.
 *
 * ------------------------------------------------------------------------
 */
static int
GetAction(string)
    char *string;
{
    char c;

    c = string[0];
    if ((c == 'c') && (strcmp(string, "cancel") == 0)) {
      return DROP_CANCEL;
    } else if ((c == 'f') && (strcmp(string, "fail") == 0)) {
      return DROP_FAIL;
    } else if ((c == 'm') && (strcmp(string, "move") == 0)) {
      return DROP_MOVE;
    } else if ((c == 'l') && (strcmp(string, "link") == 0)) {
      return DROP_LINK;
    } else if ((c == 'c') && (strcmp(string, "copy") == 0)) {
      return DROP_COPY;
    } else {
      return DROP_COPY;
    }
}

/*
 * ------------------------------------------------------------------------
 *
 *  GetDragResult --
 *
 *    Converts a string to its numeric drag result value.
 *
 *  Results:
 *    Returns the drag result.
 *
 * ------------------------------------------------------------------------
 */
static int
GetDragResult(interp, string)
    Tcl_Interp *interp;
    char *string;
{
    char c;
    int bool;

    c = string[0];
    if ((c == 'c') && (strcmp(string, "cancel") == 0)) {
      return DROP_CANCEL;
    } else if (Tcl_GetBoolean(interp, string, &bool) != TCL_OK) {
      Tcl_BackgroundError(interp);
      return DROP_CANCEL;
    }
    return bool;
}

static void
AnimateActiveCursor(clientData)
    ClientData clientData;
{
    Dnd *dndPtr = clientData;    
    Tk_Cursor cursor;

    dndPtr->cursorPos++;
    cursor = dndPtr->cursors[dndPtr->cursorPos];
    if (cursor == None) {
      cursor = dndPtr->cursors[1];
      dndPtr->cursorPos = 1;
    }
    Tk_DefineCursor(dndPtr->tkwin, cursor);
    dndPtr->timerToken = Tcl_CreateTimerHandler(100, AnimateActiveCursor,
          dndPtr);
}

static void
StartActiveCursor(dndPtr)
    Dnd *dndPtr;    
{
    if (dndPtr->timerToken != NULL) {
      Tcl_DeleteTimerHandler(dndPtr->timerToken);
    }
    if (dndPtr->cursors != NULL) {
      Tk_Cursor cursor;

      dndPtr->cursorPos = 1;
      cursor = dndPtr->cursors[1];
      if (cursor != None) {
          Tk_DefineCursor(dndPtr->tkwin, cursor);
          dndPtr->timerToken = Tcl_CreateTimerHandler(125, 
            AnimateActiveCursor, dndPtr);
      }
    }
}

static void
StopActiveCursor(dndPtr)
    Dnd *dndPtr;    
{
    if (dndPtr->cursorPos > 0) {
      dndPtr->cursorPos = 0;
    }
    if (dndPtr->cursors != NULL) {
      Tk_DefineCursor(dndPtr->tkwin, dndPtr->cursors[0]);
    }
    if (dndPtr->timerToken != NULL) {
      Tcl_DeleteTimerHandler(dndPtr->timerToken);
      dndPtr->timerToken = NULL;
    }
}

/*
 *----------------------------------------------------------------------
 *
 * EventuallyRedrawToken --
 *
 *    Queues a request to redraw the widget at the next idle point.
 *
 * Results:
 *    None.
 *
 * Side effects:
 *    Information gets redisplayed.  Right now we don't do selective
 *    redisplays:  the whole window will be redrawn.
 *
 *----------------------------------------------------------------------
 */
static void
EventuallyRedrawToken(dndPtr)
    Dnd *dndPtr;
{
    Token *tokenPtr;

    if (dndPtr->tokenPtr == NULL) {
      return;
    }
    tokenPtr = dndPtr->tokenPtr;
    if ((tokenPtr->tkwin != NULL) && (tokenPtr->tkwin != NULL) && 
      !(tokenPtr->flags & TOKEN_REDRAW)) {
      tokenPtr->flags |= TOKEN_REDRAW;
      Tcl_DoWhenIdle(DisplayToken, dndPtr);
    }
}

/*
 * ------------------------------------------------------------------------
 *
 *  RaiseToken --
 *
 * ------------------------------------------------------------------------
 */
static void
RaiseToken(dndPtr)
    Dnd *dndPtr;
{
    Token *tokenPtr = dndPtr->tokenPtr;

    if (dndPtr->flags & DND_INITIATED) {
      if ((Tk_Width(tokenPtr->tkwin) != Tk_ReqWidth(tokenPtr->tkwin)) ||
          (Tk_Height(tokenPtr->tkwin) != Tk_ReqHeight(tokenPtr->tkwin))) {
          Blt_ResizeToplevel(tokenPtr->tkwin, 
            Tk_ReqWidth(tokenPtr->tkwin),
            Tk_ReqHeight(tokenPtr->tkwin));
      }
      Blt_MapToplevel(tokenPtr->tkwin);
      Blt_RaiseToplevel(tokenPtr->tkwin);
    }
}



/*
 * ------------------------------------------------------------------------
 *
 *  DisplayToken --
 *
 * ------------------------------------------------------------------------
 */
static void
DisplayToken(clientData)
    ClientData clientData;
{
    Dnd *dndPtr = clientData;
    Token *tokenPtr = dndPtr->tokenPtr;
    int relief;
    Tk_3DBorder border;
    int borderWidth;

    tokenPtr->flags &= ~TOKEN_REDRAW;
    if (tokenPtr->status == DROP_OK) {
      relief = tokenPtr->activeRelief;
      border = tokenPtr->activeBorder;
      borderWidth = tokenPtr->activeBorderWidth;
      if ((dndPtr->cursors != NULL) && (dndPtr->cursorPos == 0)) {
          StartActiveCursor(dndPtr);
      }
    } else {
      relief = tokenPtr->relief;
      border = tokenPtr->normalBorder;
      borderWidth = tokenPtr->borderWidth;
      StopActiveCursor(dndPtr);
    } 
    Blt_Fill3DRectangle(tokenPtr->tkwin, Tk_WindowId(tokenPtr->tkwin), border, 
      0, 0, Tk_Width(tokenPtr->tkwin), Tk_Height(tokenPtr->tkwin), 
      borderWidth, relief);
    tokenPtr->lastStatus = tokenPtr->status;
    if (tokenPtr->status == DROP_FAIL) {
      DrawRejectSymbol(dndPtr);
    }
}

/*
 * ------------------------------------------------------------------------
 *
 *  FadeToken --
 *
 *    Fades the token into the target.
 *
 * ------------------------------------------------------------------------
 */
static void
FadeToken(dndPtr)
    Dnd *dndPtr;        /* Drag-and-drop manager (source). */
{ 
    Token *tokenPtr = dndPtr->tokenPtr;
    int w, h;
    int dx, dy;
    Window window;

    if (tokenPtr->status == DROP_FAIL) {
      tokenPtr->nSteps = 1;
      return;
    }
    if (tokenPtr->nSteps == 1) {
      HideToken(dndPtr);
      dndPtr->flags &= ~(DND_ACTIVE | DND_VOIDED);
      return;
    }
    if (tokenPtr->timerToken != NULL) {
      Tcl_DeleteTimerHandler(tokenPtr->timerToken);
    }
    tokenPtr->timerToken = Tcl_CreateTimerHandler(10, 
              (Tcl_TimerProc *)FadeToken, dndPtr);
    tokenPtr->nSteps--;

    w = Tk_ReqWidth(tokenPtr->tkwin) * tokenPtr->nSteps / 10;
    h = Tk_ReqHeight(tokenPtr->tkwin) * tokenPtr->nSteps / 10;
    if (w < 1) {
      w = 1;
    } 
    if (h < 1) {
      h = 1;
    }
    dx = (Tk_ReqWidth(tokenPtr->tkwin) - w) / 2;
    dy = (Tk_ReqHeight(tokenPtr->tkwin) - h) / 2;
    window = Blt_GetRealWindowId(tokenPtr->tkwin);
    XMoveResizeWindow(dndPtr->display, window, tokenPtr->x + dx, 
           tokenPtr->y + dy, (unsigned int)w, (unsigned int)h);
    tokenPtr->width = w, tokenPtr->height = h;
}

/*
 * ------------------------------------------------------------------------
 *
 *  SnapToken --
 *
 *    Snaps the token back to the source.
 *
 * ------------------------------------------------------------------------
 */
static void
SnapToken(dndPtr)
    Dnd *dndPtr;        /* drag&drop source window data */
{ 
    Token *tokenPtr = dndPtr->tokenPtr;

    if (tokenPtr->nSteps == 1) {
      HideToken(dndPtr);
      return;
    }
    if (tokenPtr->timerToken != NULL) {
      Tcl_DeleteTimerHandler(tokenPtr->timerToken);
    }
    tokenPtr->timerToken = Tcl_CreateTimerHandler(10, 
      (Tcl_TimerProc *)SnapToken, dndPtr);
    tokenPtr->nSteps--;
    tokenPtr->x -= (tokenPtr->x - tokenPtr->startX) / tokenPtr->nSteps;
    tokenPtr->y -= (tokenPtr->y - tokenPtr->startY) / tokenPtr->nSteps;
    if ((tokenPtr->x != Tk_X(tokenPtr->tkwin)) || 
      (tokenPtr->y != Tk_Y(tokenPtr->tkwin))) {
      Tk_MoveToplevelWindow(tokenPtr->tkwin, tokenPtr->x, tokenPtr->y);
    }
    RaiseToken(dndPtr);
}

/*
 * ------------------------------------------------------------------------
 *
 *  HideToken --
 *
 *    Unmaps the drag&drop token.  Invoked directly at the end of a
 *    successful communication, or after a delay if the communication
 *    fails (allowing the user to see a graphical picture of failure).
 *
 * ------------------------------------------------------------------------
 */
static void
HideToken(dndPtr)
    Dnd *dndPtr;
{
    Token *tokenPtr = dndPtr->tokenPtr;

    if (tokenPtr->timerToken != NULL) {
      Tcl_DeleteTimerHandler(tokenPtr->timerToken);
      tokenPtr->timerToken = NULL;
    }
    if (dndPtr->flags & DND_INITIATED) {
      /* Reset the cursor back to its normal state.  */
      StopActiveCursor(dndPtr);
      if (dndPtr->cursor == None) {
          Tk_UndefineCursor(dndPtr->tkwin);
      } else {
          Tk_DefineCursor(dndPtr->tkwin, dndPtr->cursor);
      }
      if (tokenPtr->tkwin != NULL) {
          Tk_UnmapWindow(tokenPtr->tkwin); 
          Blt_ResizeToplevel(tokenPtr->tkwin, 
                  Tk_ReqWidth(tokenPtr->tkwin), 
                  Tk_ReqHeight(tokenPtr->tkwin));
      }
    }
    if (dndPtr->rootPtr != NULL) {
      FreeWinfo(dndPtr->rootPtr);
      dndPtr->rootPtr = NULL;
    }
    dndPtr->flags &= ~(DND_ACTIVE | DND_VOIDED);
    tokenPtr->status = DROP_CONTINUE;
}

/*
 * ------------------------------------------------------------------------
 *
 *  MorphToken --
 *
 *    Fades the token into the target.
 *
 * ------------------------------------------------------------------------
 */
static void
MorphToken(dndPtr)
    Dnd *dndPtr;        /* Drag-and-drop manager (source). */
{ 
    Token *tokenPtr = dndPtr->tokenPtr;

    if (tokenPtr->status == DROP_FAIL) {
      tokenPtr->nSteps = 1;
      return;
    }
    if (tokenPtr->nSteps == 1) {
      HideToken(dndPtr);
      dndPtr->flags &= ~(DND_ACTIVE | DND_VOIDED);
      return;
    }
    if (tokenPtr->timerToken != NULL) {
      Tcl_DeleteTimerHandler(tokenPtr->timerToken);
    }
    tokenPtr->timerToken = Tcl_CreateTimerHandler(10, 
      (Tcl_TimerProc *)MorphToken, dndPtr);
    tokenPtr->nSteps--;

    if (dndPtr->flags & DROP_CANCEL) {
      tokenPtr->nSteps--;
      tokenPtr->x -= (tokenPtr->x - tokenPtr->startX) / tokenPtr->nSteps;
      tokenPtr->y -= (tokenPtr->y - tokenPtr->startY) / tokenPtr->nSteps;
      if ((tokenPtr->x != Tk_X(tokenPtr->tkwin)) || 
          (tokenPtr->y != Tk_Y(tokenPtr->tkwin))) {
          Tk_MoveToplevelWindow(tokenPtr->tkwin, tokenPtr->x, tokenPtr->y);
      }
    } else {
      int w, h;
      int dx, dy;
      Window window;

      w = Tk_ReqWidth(tokenPtr->tkwin) * tokenPtr->nSteps / 10;
      h = Tk_ReqHeight(tokenPtr->tkwin) * tokenPtr->nSteps / 10;
      if (w < 1) {
          w = 1;
      } 
      if (h < 1) {
          h = 1;
      }
      dx = (Tk_ReqWidth(tokenPtr->tkwin) - w) / 2;
      dy = (Tk_ReqHeight(tokenPtr->tkwin) - h) / 2;
      window = Blt_GetRealWindowId(tokenPtr->tkwin);
      XMoveResizeWindow(dndPtr->display, window, tokenPtr->x + dx, 
                    tokenPtr->y + dy, (unsigned int)w, (unsigned int)h);
      tokenPtr->width = w, tokenPtr->height = h;
    }
    RaiseToken(dndPtr);
}

static void
GetTokenPosition(dndPtr, x, y)
    Dnd *dndPtr;
    int x, y;
{ 
    Token *tokenPtr = dndPtr->tokenPtr;
    int maxX, maxY;
    int vx, vy, dummy;
    Screen *screenPtr;

    /* Adjust current location for virtual root windows.  */
    Tk_GetVRootGeometry(dndPtr->tkwin, &vx, &vy, &dummy, &dummy);
    x += vx - TOKEN_OFFSET;
    y += vy - TOKEN_OFFSET;

    screenPtr = Tk_Screen(tokenPtr->tkwin);
    maxX = WidthOfScreen(screenPtr) - Tk_Width(tokenPtr->tkwin);
    maxY = HeightOfScreen(screenPtr) - Tk_Height(tokenPtr->tkwin);
    Blt_TranslateAnchor(x, y, Tk_Width(tokenPtr->tkwin),
      Tk_Height(tokenPtr->tkwin), tokenPtr->anchor, &x, &y);
    if (x > maxX) {
      x = maxX;
    } else if (x < 0) {
      x = 0;
    }
    if (y > maxY) {
      y = maxY;
    } else if (y < 0) {
      y = 0;
    }
    tokenPtr->x = x, tokenPtr->y  = y;
}

/*
 * ------------------------------------------------------------------------
 *
 *  MoveToken --
 *
 *    Invoked during "drag" operations to move a token window to its
 *    current "drag" coordinate.
 *
 * ------------------------------------------------------------------------
 */
static void
MoveToken(dndPtr)
    Dnd *dndPtr;        /* drag&drop source window data */
{ 
    Token *tokenPtr = dndPtr->tokenPtr;

    GetTokenPosition(dndPtr, dndPtr->x, dndPtr->y);
    if ((tokenPtr->x != Tk_X(tokenPtr->tkwin)) || 
      (tokenPtr->y != Tk_Y(tokenPtr->tkwin))) {
      Tk_MoveToplevelWindow(tokenPtr->tkwin, tokenPtr->x, tokenPtr->y);
    }
}


/*
 * ------------------------------------------------------------------------
 *
 *  ChangeToken --
 *
 *    Invoked when the event loop is idle to determine whether or not
 *    the current drag&drop token position is over another drag&drop
 *    target.
 *
 * ------------------------------------------------------------------------
 */
static void
ChangeToken(dndPtr, status)
    Dnd *dndPtr;
    int status;
{
    Token *tokenPtr = dndPtr->tokenPtr;

    tokenPtr->status = status;
    EventuallyRedrawToken(dndPtr);

    /*
     *  If the source has a site command, then invoke it to
     *  modify the appearance of the token window.  Pass any
     *  errors onto the drag&drop error handler.
     */
    if (dndPtr->siteCmd) {
      Tcl_Interp *interp = dndPtr->interp;
      Tcl_DString dString, savedResult;
      char **p;
      
      Tcl_DStringInit(&dString);
      for (p = dndPtr->siteCmd; *p != NULL; p++) {
          Tcl_DStringAppendElement(&dString, *p);
      }
      Tcl_DStringAppendElement(&dString, Tk_PathName(dndPtr->tkwin));
      Tcl_DStringAppendElement(&dString, "timestamp");
      Tcl_DStringAppendElement(&dString, Blt_Utoa(dndPtr->timestamp));
      Tcl_DStringAppendElement(&dString, "status");
      Tcl_DStringAppendElement(&dString, NameOfStatus(status));
      Tcl_DStringInit(&savedResult);
      Tcl_DStringGetResult(interp, &savedResult);
      if (Tcl_GlobalEval(interp, Tcl_DStringValue(&dString)) != TCL_OK) {
          Tcl_BackgroundError(interp);
      }
      Tcl_DStringFree(&dString);
      Tcl_DStringResult(interp, &savedResult);
    }
}

/*
 * ------------------------------------------------------------------------
 *
 *  DrawRejectSymbol --
 *
 *    Draws a rejection mark on the current drag&drop token, and arranges
 *    for the token to be unmapped after a small delay.
 *
 * ------------------------------------------------------------------------
 */
static void
DrawRejectSymbol(dndPtr)
    Dnd *dndPtr;
{
    Token *tokenPtr = dndPtr->tokenPtr;
    int divisor = 6;          /* controls size of rejection symbol */
    int w, h, lineWidth, x, y, margin;

    margin = 2 * tokenPtr->borderWidth;
    w = Tk_Width(tokenPtr->tkwin) - 2 * margin;
    h = Tk_Height(tokenPtr->tkwin) - 2 * margin;
    lineWidth = (w < h) ? w / divisor : h / divisor;
    lineWidth = (lineWidth < 1) ? 1 : lineWidth;

    w = h = lineWidth * (divisor - 1);
    x = (Tk_Width(tokenPtr->tkwin) - w) / 2;
    y = (Tk_Height(tokenPtr->tkwin) - h) / 2;

    /*
     *  Draw the rejection symbol background (\) on the token window...
     */
    XSetLineAttributes(Tk_Display(tokenPtr->tkwin), tokenPtr->outlineGC,
      lineWidth + 2, LineSolid, CapButt, JoinBevel);

    XDrawArc(Tk_Display(tokenPtr->tkwin), Tk_WindowId(tokenPtr->tkwin),
      tokenPtr->outlineGC, x, y, w, h, 0, 23040);

    XDrawLine(Tk_Display(tokenPtr->tkwin), Tk_WindowId(tokenPtr->tkwin),
      tokenPtr->outlineGC, x + lineWidth, y + lineWidth, x + w - lineWidth,
      y + h - lineWidth);

    /*
     *  Draw the rejection symbol foreground (\) on the token window...
     */
    XSetLineAttributes(Tk_Display(tokenPtr->tkwin), tokenPtr->fillGC,
      lineWidth, LineSolid, CapButt, JoinBevel);

    XDrawArc(Tk_Display(tokenPtr->tkwin), Tk_WindowId(tokenPtr->tkwin),
      tokenPtr->fillGC, x, y, w, h, 0, 23040);

    XDrawLine(Tk_Display(tokenPtr->tkwin), Tk_WindowId(tokenPtr->tkwin),
      tokenPtr->fillGC, x + lineWidth, y + lineWidth, x + w - lineWidth,
      y + h - lineWidth);

    tokenPtr->status = DROP_FAIL;
    /*
     *  Arrange for token window to disappear eventually.
     */
    if (tokenPtr->timerToken != NULL) {
      Tcl_DeleteTimerHandler(tokenPtr->timerToken);
    }
    tokenPtr->timerToken = Tcl_CreateTimerHandler(1000, 
      (Tcl_TimerProc *)HideToken, dndPtr);
    RaiseToken(dndPtr);
    dndPtr->flags &= ~(DND_ACTIVE | DND_VOIDED);
}

/*
 * ------------------------------------------------------------------------
 *
 *  CreateToken --
 *
 *    Looks for a Source record in the hash table for drag&drop source
 *    widgets.  Creates a new record if the widget name is not already
 *    registered.  Returns a pointer to the desired record.
 *
 * ------------------------------------------------------------------------
 */
static void
DestroyToken(data)
    DestroyData data;
{
    Dnd *dndPtr = (Dnd *)data;
    Token *tokenPtr = dndPtr->tokenPtr;

    dndPtr->tokenPtr = NULL;
    if (tokenPtr == NULL) {
      return;
    }
    if (tokenPtr->flags & TOKEN_REDRAW) {
      Tcl_CancelIdleCall(DisplayToken, dndPtr);
    }
    Tk_FreeOptions(tokenConfigSpecs, (char *)tokenPtr, dndPtr->display, 0);
    if (tokenPtr->timerToken) {
      Tcl_DeleteTimerHandler(tokenPtr->timerToken);
    }
    if (tokenPtr->fillGC != NULL) {
      Tk_FreeGC(dndPtr->display, tokenPtr->fillGC);
    }
    if (tokenPtr->outlineGC != NULL) {
      Tk_FreeGC(dndPtr->display, tokenPtr->outlineGC);
    }
    if (tokenPtr->tkwin != NULL) {
      Tk_DeleteEventHandler(tokenPtr->tkwin, 
            ExposureMask | StructureNotifyMask, TokenEventProc, dndPtr);
      Tk_DestroyWindow(tokenPtr->tkwin);
    }
    Blt_Free(tokenPtr);
}

/*
 * ------------------------------------------------------------------------
 *
 *  TokenEventProc --
 *
 *    Invoked by the Tk dispatcher to handle widget events.
 *    Manages redraws for the drag&drop token window.
 *
 * ------------------------------------------------------------------------
 */
static void
TokenEventProc(clientData, eventPtr)
    ClientData clientData;    /* data associated with widget */
    XEvent *eventPtr;         /* information about event */
{
    Dnd *dndPtr = clientData;
    Token *tokenPtr = dndPtr->tokenPtr;

    if ((eventPtr->type == Expose) && (eventPtr->xexpose.count == 0)) {
      if (tokenPtr->tkwin != NULL) {
          EventuallyRedrawToken(dndPtr);
      }
    } else if (eventPtr->type == DestroyNotify) {
      tokenPtr->tkwin = NULL;
      if (tokenPtr->flags & TOKEN_REDRAW) {
          tokenPtr->flags &= ~TOKEN_REDRAW;
          Tcl_CancelIdleCall(DisplayToken, dndPtr);
      }
      Tcl_EventuallyFree(dndPtr, DestroyToken);
    }
}

/*
 * ------------------------------------------------------------------------
 *
 *  CreateToken --
 *
 *    Looks for a Source record in the hash table for drag&drop source
 *    widgets.  Creates a new record if the widget name is not already
 *    registered.  Returns a pointer to the desired record.
 *
 * ------------------------------------------------------------------------
 */
static int
CreateToken(interp, dndPtr)
    Tcl_Interp *interp;
    Dnd *dndPtr;
{
    XSetWindowAttributes attrs;
    Tk_Window tkwin;
    unsigned int mask;
    Token *tokenPtr;

    tokenPtr = Blt_Calloc(1, sizeof(Token));
    assert(tokenPtr);
    tokenPtr->anchor = TK_ANCHOR_SE;
    tokenPtr->relief = TK_RELIEF_RAISED;
    tokenPtr->activeRelief = TK_RELIEF_SUNKEN;
    tokenPtr->borderWidth = tokenPtr->activeBorderWidth = 3;

    /* Create toplevel on parent's screen. */
    tkwin = Tk_CreateWindow(interp, dndPtr->tkwin, "dndtoken", "");
    if (tkwin == NULL) {
      Blt_Free(tokenPtr);
      return TCL_ERROR;
    }
    tokenPtr->tkwin = tkwin;
    Tk_SetClass(tkwin, "DndToken"); 
    Tk_CreateEventHandler(tkwin, ExposureMask | StructureNotifyMask,
      TokenEventProc, dndPtr);
    attrs.override_redirect = True;
    attrs.backing_store = WhenMapped;
    attrs.save_under = True;
    mask = CWOverrideRedirect | CWSaveUnder | CWBackingStore;
    Tk_ChangeWindowAttributes(tkwin, mask, &attrs);
    Tk_SetInternalBorder(tkwin, tokenPtr->borderWidth + 2);
    Tk_MakeWindowExist(tkwin);
    dndPtr->tokenPtr = tokenPtr;
    return TCL_OK;
}

/*
 * ------------------------------------------------------------------------
 *
 *  ConfigureToken --
 *
 *    Called to process an (argc,argv) list to configure (or
 *    reconfigure) a drag&drop source widget.
 *
 * ------------------------------------------------------------------------
 */
static int
ConfigureToken(interp, dndPtr, argc, argv, flags)
    Tcl_Interp *interp;       /* current interpreter */
    Dnd *dndPtr;        /* Drag&drop source widget record */
    int argc;                 /* number of arguments */
    char **argv;        /* argument strings */
    int flags;                /* flags controlling interpretation */
{
    GC newGC;
    Token *tokenPtr = dndPtr->tokenPtr;
    XGCValues gcValues;
    unsigned long gcMask;

    Tk_MakeWindowExist(tokenPtr->tkwin);
    if (Tk_ConfigureWidget(interp, tokenPtr->tkwin, tokenConfigSpecs, argc, 
            argv, (char *)tokenPtr, flags) != TCL_OK) {
      return TCL_ERROR;
    }
    /*
     *  Set up the rejection outline GC for the token window...
     */
    gcValues.foreground = tokenPtr->outlineColor->pixel;
    gcValues.subwindow_mode = IncludeInferiors;
    gcValues.graphics_exposures = False;
    gcValues.line_style = LineSolid;
    gcValues.cap_style = CapButt;
    gcValues.join_style = JoinBevel;

    gcMask = GCForeground | GCSubwindowMode | GCLineStyle |
      GCCapStyle | GCJoinStyle | GCGraphicsExposures;
    newGC = Tk_GetGC(dndPtr->tkwin, gcMask, &gcValues);

    if (tokenPtr->outlineGC != NULL) {
      Tk_FreeGC(dndPtr->display, tokenPtr->outlineGC);
    }
    tokenPtr->outlineGC = newGC;

    /*
     *  Set up the rejection fill GC for the token window...
     */
    gcValues.foreground = tokenPtr->fillColor->pixel;
    if (tokenPtr->rejectStipple != None) {
      gcValues.stipple = tokenPtr->rejectStipple;
      gcValues.fill_style = FillStippled;
      gcMask |= GCStipple | GCFillStyle;
    }
    newGC = Tk_GetGC(dndPtr->tkwin, gcMask, &gcValues);

    if (tokenPtr->fillGC != NULL) {
      Tk_FreeGC(dndPtr->display, tokenPtr->fillGC);
    }
    tokenPtr->fillGC = newGC;

    if ((tokenPtr->reqWidth > 0) && (tokenPtr->reqHeight > 0)) {
      Tk_GeometryRequest(tokenPtr->tkwin, tokenPtr->reqWidth, 
            tokenPtr->reqHeight);
    }
    /*
     *  Reset the border width in case it has changed...
     */
    Tk_SetInternalBorder(tokenPtr->tkwin, tokenPtr->borderWidth + 2);
    return TCL_OK;
}

static int
GetFormattedData(dndPtr, format, timestamp, resultPtr)
    Dnd *dndPtr;
    char *format;
    int timestamp;
    Tcl_DString *resultPtr;
{
    Tcl_Interp *interp = dndPtr->interp;
    Blt_HashEntry *hPtr;
    char **formatCmd;
    Tcl_DString savedResult;
    Tcl_DString dString;
    char **p;
    int x, y;

    /* Find the data converter for the prescribed format. */
    hPtr = Blt_FindHashEntry(&(dndPtr->getDataTable), format);
    if (hPtr == NULL) {
      Tcl_AppendResult(interp, "can't find format \"", format, 
       "\" in source \"", Tk_PathName(dndPtr->tkwin), "\"", (char *)NULL);
      return TCL_ERROR;
    }
    formatCmd = (char **)Blt_GetHashValue(hPtr);
    Tcl_DStringInit(&dString);
    for (p = formatCmd; *p != NULL; p++) {
      Tcl_DStringAppendElement(&dString, *p);
    }
    x = dndPtr->dragX - Blt_RootX(dndPtr->tkwin);
    y = dndPtr->dragY - Blt_RootY(dndPtr->tkwin);
    Tcl_DStringAppendElement(&dString, Tk_PathName(dndPtr->tkwin));
    Tcl_DStringAppendElement(&dString, "x");
    Tcl_DStringAppendElement(&dString, Blt_Itoa(x));
    Tcl_DStringAppendElement(&dString, "y");
    Tcl_DStringAppendElement(&dString, Blt_Itoa(y));
    Tcl_DStringAppendElement(&dString, "timestamp");
    Tcl_DStringAppendElement(&dString, Blt_Utoa(timestamp));
    Tcl_DStringAppendElement(&dString, "format");
    Tcl_DStringAppendElement(&dString, format);
    Tcl_DStringInit(&savedResult);
    Tcl_DStringGetResult(interp, &savedResult);
    if (Tcl_GlobalEval(interp, Tcl_DStringValue(&dString)) != TCL_OK) {
      Tcl_BackgroundError(interp);
    }
    Tcl_DStringFree(&dString);
    Tcl_DStringInit(resultPtr);
    Tcl_DStringGetResult(interp, resultPtr);

    /* Restore the interpreter result. */
    Tcl_DStringResult(interp, &savedResult);
    return TCL_OK;
}


/*
 * ------------------------------------------------------------------------
 *
 *  DestroyDnd --
 *
 *    Free resources allocated for the drag&drop window.
 *
 * ------------------------------------------------------------------------
 */
static void
DestroyDnd(data)
    DestroyData data;
{
    Dnd *dndPtr = (Dnd *)data;
    Blt_HashEntry *hPtr;
    Blt_HashSearch cursor;
    char **cmd;

    Tk_FreeOptions(configSpecs, (char *)dndPtr, dndPtr->display, 0);
    Tk_DeleteGenericHandler(DndEventProc, dndPtr);
    for (hPtr = Blt_FirstHashEntry(&(dndPtr->getDataTable), &cursor);
      hPtr != NULL; hPtr = Blt_NextHashEntry(&cursor)) {
      cmd = (char **)Blt_GetHashValue(hPtr);
      if (cmd != NULL) {
          Blt_Free(cmd);
      }
    }
    Blt_DeleteHashTable(&(dndPtr->getDataTable));

    for (hPtr = Blt_FirstHashEntry(&(dndPtr->setDataTable), &cursor);
      hPtr != NULL; hPtr = Blt_NextHashEntry(&cursor)) {
      cmd = (char **)Blt_GetHashValue(hPtr);
      if (cmd != NULL) {
          Blt_Free(cmd);
      }
    }
    Blt_DeleteHashTable(&(dndPtr->setDataTable));
    if (dndPtr->rootPtr != NULL) {
      FreeWinfo(dndPtr->rootPtr);
    }
    if (dndPtr->cursor != None) {
      Tk_FreeCursor(dndPtr->display, dndPtr->cursor);
    }
    if (dndPtr->reqFormats != NULL) {
      Blt_Free(dndPtr->reqFormats);
    }
    if (dndPtr->matchingFormats != NULL) {
      Blt_Free(dndPtr->matchingFormats);
    }

    /* Now that the various commands are custom list options, we need
     * to explicitly free them. */
    if (dndPtr->motionCmd != NULL) {
      Blt_Free(dndPtr->motionCmd);
    }
    if (dndPtr->leaveCmd != NULL) {
      Blt_Free(dndPtr->leaveCmd);
    }
    if (dndPtr->enterCmd != NULL) {
      Blt_Free(dndPtr->enterCmd);
    }
    if (dndPtr->dropCmd != NULL) {
      Blt_Free(dndPtr->dropCmd);
    }
    if (dndPtr->resultCmd != NULL) {
      Blt_Free(dndPtr->resultCmd);
    }
    if (dndPtr->packageCmd != NULL) {
      Blt_Free(dndPtr->packageCmd);
    }
    if (dndPtr->siteCmd != NULL) {
      Blt_Free(dndPtr->siteCmd);
    }

    if (dndPtr->hashPtr != NULL) {
      Blt_DeleteHashEntry(&(dndPtr->dataPtr->dndTable), dndPtr->hashPtr);
    }
    if (dndPtr->tokenPtr != NULL) {
      DestroyToken((DestroyData)dndPtr);
    }
    if (dndPtr->tkwin != NULL) {
      XDeleteProperty(dndPtr->display, Tk_WindowId(dndPtr->tkwin),
                  dndPtr->dataPtr->targetAtom);
      XDeleteProperty(dndPtr->display, Tk_WindowId(dndPtr->tkwin),
                  dndPtr->dataPtr->commAtom);
    }
    Blt_Free(dndPtr);
}


/*
 * ------------------------------------------------------------------------
 *
 *  GetDnd --
 *
 *    Looks for a Source record in the hash table for drag&drop source
 *    widgets.  Returns a pointer to the desired record.
 *
 * ------------------------------------------------------------------------
 */
static int
GetDnd(clientData, interp, pathName, dndPtrPtr)
    ClientData clientData;
    Tcl_Interp *interp;
    char *pathName;           /* widget pathname for desired record */
    Dnd **dndPtrPtr;
{
    DndInterpData *dataPtr = clientData;
    Blt_HashEntry *hPtr;
    Tk_Window tkwin;

    tkwin = Tk_NameToWindow(interp, pathName, dataPtr->mainWindow);
    if (tkwin == NULL) {
      return TCL_ERROR;
    }
    hPtr = Blt_FindHashEntry(&(dataPtr->dndTable), (char *)tkwin);
    if (hPtr == NULL) {
      Tcl_AppendResult(interp, "window \"", pathName,
           "\" is not a drag&drop source/target", (char *)NULL);
      return TCL_ERROR;
    }
    *dndPtrPtr = (Dnd *)Blt_GetHashValue(hPtr);
    return TCL_OK;
}

/*
 * ------------------------------------------------------------------------
 *
 *  CreateDnd --
 *
 *    Looks for a Source record in the hash table for drag&drop source
 *    widgets.  Creates a new record if the widget name is not already
 *    registered.  Returns a pointer to the desired record.
 *
 * ------------------------------------------------------------------------
 */
static Dnd *
CreateDnd(interp, tkwin)
    Tcl_Interp *interp;
    Tk_Window tkwin;          /* widget for desired record */
{
    Dnd *dndPtr;

    dndPtr = Blt_Calloc(1, sizeof(Dnd));
    assert(dndPtr);
    dndPtr->interp = interp;
    dndPtr->display = Tk_Display(tkwin);
    dndPtr->tkwin = tkwin;
    Tk_MakeWindowExist(tkwin);
    Blt_InitHashTable(&(dndPtr->setDataTable), BLT_STRING_KEYS);
    Blt_InitHashTable(&(dndPtr->getDataTable), BLT_STRING_KEYS);
    Tk_CreateGenericHandler(DndEventProc, dndPtr);
    return dndPtr;
}

static int
ConfigureDnd(interp, dndPtr)
    Tcl_Interp *interp;
    Dnd *dndPtr;
{
    Tcl_CmdInfo cmdInfo;
    Tcl_DString dString;
    int button, result;

    if (!Tcl_GetCommandInfo(interp, "blt::DndInit", &cmdInfo)) {
      static char cmd[] = "source [file join $blt_library dnd.tcl]";
      /* 
       * If the "DndInit" routine hasn't been sourced, do it now.
       */
      if (Tcl_GlobalEval(interp, cmd) != TCL_OK) {
          Tcl_AddErrorInfo(interp,
                 "\n    (while loading bindings for blt::drag&drop)");
          return TCL_ERROR;
      }
    }
    /*  
     * Reset the target property if it's changed state or
     * added/subtracted one of its callback procedures.  
     */
    if (Blt_ConfigModified(configSpecs, "-target", "-onenter",  "-onmotion",
         "-onleave", (char *)NULL)) {
      if (dndPtr->targetPropertyExists) {
          XDeleteProperty(dndPtr->display, Tk_WindowId(dndPtr->tkwin),
                      dndPtr->dataPtr->targetAtom);
          dndPtr->targetPropertyExists = FALSE;
      }
      if (dndPtr->isTarget) {
          AddTargetProperty(dndPtr);
          dndPtr->targetPropertyExists = TRUE;
      }
    }
    if (dndPtr->isSource) {
      /* Check the button binding for valid range (0 or 1-5) */
      if ((dndPtr->reqButton < 0) || (dndPtr->reqButton > 5)) {
          Tcl_AppendResult(interp, 
                       "button must be 1-5, or 0 for no bindings",
                       (char *)NULL);
          return TCL_ERROR;
      }
      button = dndPtr->reqButton;
    } else {
      button = 0;
    }
    Tcl_DStringInit(&dString);
    Blt_DStringAppendElements(&dString, "blt::DndInit", 
      Tk_PathName(dndPtr->tkwin), Blt_Itoa(button), (char *)NULL);
    result = Tcl_GlobalEval(interp, Tcl_DStringValue(&dString));
    Tcl_DStringFree(&dString);
    if (result != TCL_OK) {
      return TCL_ERROR;
    }
    return TCL_OK;
}


/*
 *----------------------------------------------------------------------
 *
 * SendRestrictProc --
 *
 *    This procedure filters incoming events when a "send" command
 *    is outstanding.  It defers all events except those containing
 *    send commands and results.
 *
 * Results:
 *    False is returned except for property-change events on a
 *    commWindow.
 *
 * Side effects:
 *    None.
 *
 *----------------------------------------------------------------------
 */

/* ARGSUSED */
static Tk_RestrictAction
SendRestrictProc(clientData, eventPtr)
    ClientData clientData;          /* Drag-and-drop manager. */
    register XEvent *eventPtr;            /* Event that just arrived. */
{
    Dnd *dndPtr = clientData;

    if (eventPtr->xproperty.window != Tk_WindowId(dndPtr->tkwin)) {
      return TK_PROCESS_EVENT; /* Event not in our window. */
    }
    if ((eventPtr->type == PropertyNotify) &&
      (eventPtr->xproperty.state == PropertyNewValue)) {
      return TK_PROCESS_EVENT; /* This is the one we want to process. */
    }
    if (eventPtr->type == Expose) {
      return TK_PROCESS_EVENT; /* Let expose events also get
                          * handled. */
    }
    return TK_DEFER_EVENT;    /* Defer everything else. */
}

/*
 *----------------------------------------------------------------------
 *
 * SendTimerProc --
 *
 *    Procedure called when the timer event elapses.  Used to wait
 *    between attempts checking for the designated window.
 *
 * Results:
 *    None.
 *
 * Side Effects:
 *    Sets a flag, indicating the timeout occurred.
 *
 *----------------------------------------------------------------------
 */
static void
SendTimerProc(clientData)
    ClientData clientData;
{
    int *statusPtr = clientData;

    /* 
     * An unusually long amount of time has elapsed since the drag
     * start message was sent.  Assume that the other party has died
     * and abort the operation.  
     */
    *statusPtr = DROP_FAIL;
}

#define WAIT_INTERVAL   2000  /* Twenty seconds. */

/*
 * ------------------------------------------------------------------------
 *
 *  TargetPropertyEventProc --
 *
 *    Invoked by the Tk dispatcher to handle widget events.
 *    Manages redraws for the drag&drop token window.
 *
 * ------------------------------------------------------------------------
 */
static void
TargetPropertyEventProc(clientData, eventPtr)
    ClientData clientData;    /* Data associated with transaction. */
    XEvent *eventPtr;         /* information about event */
{
    DropPending *pendingPtr = clientData;
    char *data;
    int result, format;
    Atom typeAtom;
    unsigned long nItems, bytesAfter;

#ifdef notdef
    fprintf(stderr, "TargetPropertyEventProc\n");
#endif
    if ((eventPtr->type != PropertyNotify) ||
      (eventPtr->xproperty.atom != pendingPtr->commAtom) || 
      (eventPtr->xproperty.state != PropertyNewValue)) {
      return;
    }
    Tcl_DeleteTimerHandler(pendingPtr->timerToken);
    data = NULL;
    result = XGetWindowProperty(
        eventPtr->xproperty.display,      /* Display of window. */
      eventPtr->xproperty.window,     /* Window holding the property. */
        eventPtr->xproperty.atom,   /* Name of property. */
        0,              /* Offset of data (for multiple reads). */
      pendingPtr->packetSize, /* Maximum number of items to read. */
      False,                  /* If true, delete the property. */
        XA_STRING,            /* Desired type of property. */
        &typeAtom,            /* (out) Actual type of the property. */
        &format,        /* (out) Actual format of the property. */
        &nItems,        /* (out) # of items in specified format. */
        &bytesAfter,          /* (out) # of bytes remaining to be read. */
      (unsigned char **)&data);
#ifdef notdef
    fprintf(stderr, 
      "TargetPropertyEventProc: result=%d, typeAtom=%d, format=%d, nItems=%d\n",
          result, typeAtom, format, nItems);
#endif
    pendingPtr->status = DROP_FAIL;
    if ((result == Success) && (typeAtom == XA_STRING) && (format == 8)) {
      pendingPtr->status = DROP_OK;
#ifdef notdef
      fprintf(stderr, "data found is (%s)\n", data);
#endif
      Tcl_DStringAppend(&(pendingPtr->dString), data, -1);
      XFree(data);
      if (nItems == pendingPtr->packetSize) {
          /* Normally, we'll receive the data in one chunk. But if
           * more are required, reset the timer and go back into the 
           * wait loop again. */
          pendingPtr->timerToken = Tcl_CreateTimerHandler(WAIT_INTERVAL, 
            SendTimerProc, &pendingPtr->status);
          pendingPtr->status = DROP_CONTINUE;
      }
    } 
    /* Set an empty, zero-length value on the source's property. This
     * acts as a handshake, indicating that the target received the
     * latest chunk. */
#ifdef notdef
    fprintf(stderr, "TargetPropertyEventProc: set response property\n");
#endif
    XChangeProperty(pendingPtr->display, pendingPtr->window, 
      pendingPtr->commAtom, XA_STRING, 8, PropModeReplace, 
      (unsigned char *)"", 0);
}

#ifdef HAVE_XDND

static int 
XDndSelectionProc(clientData, interp, portion)
    ClientData clientData;
    Tcl_Interp *interp;
    char *portion;
{
    DropPending *pendingPtr = clientData;

    Tcl_DStringAppend(&(pendingPtr->dString), portion, -1);
#ifdef notdef
    fprintf(stderr, "-> XDndGetSelectionProc\n");
#endif
    return TCL_OK;
}

#endif /* HAVE_XDND */

static void
CompleteDataTransaction(dndPtr, format, pendingPtr)
    Dnd *dndPtr;
    char *format;
    DropPending *pendingPtr;
{
    DndInterpData *dataPtr = dndPtr->dataPtr;
    Tk_Window tkwin;
    Atom formatAtom;

#ifdef notdef
    fprintf(stderr, "-> CompleteDataTransaction\n");
#endif
    /* Check if the source is local to the application. */
    tkwin = Tk_IdToWindow(dndPtr->display, pendingPtr->window);
    if (tkwin != NULL) {
      Blt_HashEntry *hPtr;

      hPtr = Blt_FindHashEntry(&(dndPtr->dataPtr->dndTable), (char *)tkwin);
      if (hPtr != NULL) {
          Dnd *srcPtr;

          srcPtr = (Dnd *)Blt_GetHashValue(hPtr);
          GetFormattedData(srcPtr, format, pendingPtr->timestamp, 
            &(pendingPtr->dString));
      }
      return;
    }

    formatAtom = XInternAtom(pendingPtr->display, format, False);

    if (pendingPtr->protocol == PROTO_XDND) { 
#ifdef HAVE_XDND
      if (Tk_GetSelection(dndPtr->interp, dndPtr->tkwin, 
            dataPtr->XdndSelectionAtom, formatAtom, XDndSelectionProc, 
            pendingPtr) != TCL_OK) {
          pendingPtr->status = DROP_FAIL;
      }
#endif
      pendingPtr->status = DROP_OK;
    } else {
      Tk_RestrictProc *proc;
      ClientData arg;

      SendClientMsg(pendingPtr->display, pendingPtr->window, 
            dataPtr->mesgAtom, 
            TS_START_DROP, 
            (int)Tk_WindowId(dndPtr->tkwin),
            pendingPtr->timestamp, 
            (int)formatAtom, 
            (int)pendingPtr->commAtom);

      pendingPtr->commAtom = dndPtr->dataPtr->commAtom;
      pendingPtr->status = DROP_CONTINUE;
      pendingPtr->display = dndPtr->display;
      proc = Tk_RestrictEvents(SendRestrictProc, dndPtr, &arg);
      Tk_CreateEventHandler(dndPtr->tkwin, PropertyChangeMask, 
            TargetPropertyEventProc, pendingPtr);
      pendingPtr->timerToken = Tcl_CreateTimerHandler(WAIT_INTERVAL, 
             SendTimerProc, &pendingPtr->status);
      /*  
       * ----------------------------------------------------------
       *
       * Enter a loop processing X events until the all the data is
       * received or the source is declared to be dead (i.e. we
       * timeout).  While waiting for a result, restrict handling to
       * just property-related events so that the transfer is
       * synchronous with respect to other events in the widget.
       *
       * ---------------------------------------------------------- 
       */
      while (pendingPtr->status == DROP_CONTINUE) { 
          /* Wait for property event. */
          Tcl_DoOneEvent(TCL_ALL_EVENTS);
      }
      Tk_RestrictEvents(proc, arg, &arg);
      Tcl_DeleteTimerHandler(pendingPtr->timerToken);
      Tk_DeleteEventHandler(dndPtr->tkwin, PropertyChangeMask, 
            TargetPropertyEventProc, pendingPtr);
    }
#ifdef notdef
    fprintf(stderr, "<- CompleteDataTransaction\n");
#endif
}

/*
 * ------------------------------------------------------------------------
 *
 *  SourcePropertyEventProc --
 *
 *    Invoked by the Tk dispatcher when a PropertyNotify event occurs
 *    on the source window.  The event acts as a handshake between the
 *    target and the source.  The source acknowledges the target has 
 *    received the last packet of data and sends the next packet.  
 *
 *    Note a special case.  If the data is divisible by the packetsize,
 *    then an extra zero-length packet is sent to mark the end of the
 *    data.  A packetsize length packet indicates more is to follow.
 *
 *    Normally the property is empty (zero-length).  But if an
 *    errored occurred on the target, it will contain the error
 *    message.
 *
 * ------------------------------------------------------------------------ 
 */
static void
SourcePropertyEventProc(clientData, eventPtr)
    ClientData clientData;    /* data associated with widget */
    XEvent *eventPtr;         /* information about event */
{
    DropPending *pendingPtr = clientData;
    char *data;
    int result, format;
    Atom typeAtom;
    unsigned long nItems, bytesAfter;
    int size, bytesLeft;
    unsigned char *p;

#ifdef notdef
    fprintf(stderr, "-> SourcePropertyEventProc\n");
#endif
    if ((eventPtr->xproperty.atom != pendingPtr->commAtom)
      || (eventPtr->xproperty.state != PropertyNewValue)) {
      return;
    }
    Tcl_DeleteTimerHandler(pendingPtr->timerToken);
    data = NULL;
    result = XGetWindowProperty(
        eventPtr->xproperty.display,      /* Display of window. */
      eventPtr->xproperty.window,     /* Window holding the property. */
        eventPtr->xproperty.atom,   /* Name of property. */
        0,              /* Offset of data (for multiple reads). */
      pendingPtr->packetSize, /* Maximum number of items to read. */
      True,             /* If true, delete the property. */
        XA_STRING,            /* Desired type of property. */
        &typeAtom,            /* (out) Actual type of the property. */
        &format,        /* (out) Actual format of the property. */
        &nItems,        /* (out) # of items in specified format. */
        &bytesAfter,          /* (out) # of bytes remaining to be read. */
      (unsigned char **)&data);

    if ((result != Success) || (typeAtom != XA_STRING) || (format != 8)) {
      pendingPtr->status = DROP_FAIL;
#ifdef notdef
    fprintf(stderr, "<- SourcePropertyEventProc: wrong format\n");
#endif
      return;                 /* Wrong data format. */
    }
    if (nItems > 0) {
      pendingPtr->status = DROP_FAIL;
      Tcl_DStringFree(&(pendingPtr->dString));
      Tcl_DStringAppend(&(pendingPtr->dString), data, -1);
      XFree(data);
#ifdef notdef
    fprintf(stderr, "<- SourcePropertyEventProc: error\n");
#endif
      return;                 /* Error occurred on target. */
    }    
    bytesLeft = Tcl_DStringLength(&(pendingPtr->dString)) - pendingPtr->offset;
    if (bytesLeft <= 0) {
#ifdef notdef
      fprintf(stderr, "<- SourcePropertyEventProc: done\n");
#endif
      pendingPtr->status = DROP_OK;
      size = 0;
    } else {
      size = MIN(bytesLeft, pendingPtr->packetSize);
      pendingPtr->status = DROP_CONTINUE;
    }
    p = (unsigned char *)Tcl_DStringValue(&(pendingPtr->dString)) + 
      pendingPtr->offset;
    XChangeProperty(pendingPtr->display, pendingPtr->window, 
      pendingPtr->commAtom, XA_STRING, 8, PropModeReplace, p, size);
    pendingPtr->offset += size;
    pendingPtr->timerToken = Tcl_CreateTimerHandler(WAIT_INTERVAL, 
         SendTimerProc, &pendingPtr->status);
#ifdef notdef
    fprintf(stderr, "<- SourcePropertyEventProc\n");
#endif
}


static void
SendDataToTarget(dndPtr, pendingPtr)
    Dnd *dndPtr;
    DropPending *pendingPtr;
{
    int size;
    Tk_RestrictProc *proc;
    ClientData arg;

#ifdef notdef
    fprintf(stderr, "-> SendDataToTarget\n");
#endif
    Tk_CreateEventHandler(dndPtr->tkwin, PropertyChangeMask, 
      SourcePropertyEventProc, pendingPtr);
    pendingPtr->timerToken = Tcl_CreateTimerHandler(WAIT_INTERVAL, 
      SendTimerProc, &pendingPtr->status);
    size = MIN(Tcl_DStringLength(&pendingPtr->dString), pendingPtr->packetSize);

    proc = Tk_RestrictEvents(SendRestrictProc, dndPtr, &arg);

    /* 
     * Setting the property starts the process.  The target will
     * see the PropertyChange event and respond accordingly.  
     */
    XChangeProperty(dndPtr->display, pendingPtr->window, 
      pendingPtr->commAtom, XA_STRING, 8, PropModeReplace, 
      (unsigned char *)Tcl_DStringValue(&(pendingPtr->dString)), size);
    pendingPtr->offset += size;

    /*
     * Enter a loop processing X events until the result comes
     * in or the target is declared to be dead.  While waiting
     * for a result, look only at property-related events so that
     * the handshake is synchronous with respect to other events in
     * the application.
     */
    pendingPtr->status = DROP_CONTINUE;
    while (pendingPtr->status == DROP_CONTINUE) {
      /* Wait for the property change event. */
      Tcl_DoOneEvent(TCL_ALL_EVENTS);
    }
    Tk_RestrictEvents(proc, arg, &arg);
    Tcl_DeleteTimerHandler(pendingPtr->timerToken);
    Tk_DeleteEventHandler(dndPtr->tkwin, PropertyChangeMask, 
        SourcePropertyEventProc, pendingPtr);
#ifdef notdef
    fprintf(stderr, "<- SendDataToTarget\n");
#endif
}

static void
DoDrop(dndPtr, eventPtr)
    Dnd *dndPtr;
    XEvent *eventPtr;
{
    DndInterpData *dataPtr = dndPtr->dataPtr;
    Token *tokenPtr = dndPtr->tokenPtr;
    Tcl_Interp *interp = dndPtr->interp;
    struct DropRequest {
      int mesg;         /* TS_DROP_RESULT message. */
      Window window;          /* Target window. */
      int timestamp;          /* Transaction timestamp. */
      Atom formatAtom;  /* Format requested. */
    } *dropPtr;
    char *format;
    DropPending pending;

    if (tokenPtr->timerToken != NULL) {
      Tcl_DeleteTimerHandler(tokenPtr->timerToken);
    }
    dropPtr = (struct DropRequest *)eventPtr->xclient.data.l;
    format = XGetAtomName(dndPtr->display, dropPtr->formatAtom);
#ifdef notdef
    fprintf(stderr, "DoDrop %s 0x%x\n", Tk_PathName(dndPtr->tkwin), 
          dropPtr->window);
#endif
    if (GetFormattedData(dndPtr, format, dropPtr->timestamp, &(pending.dString))
      != TCL_OK) {
      Tcl_BackgroundError(interp);
      /* Send empty string to break target's wait loop. */
      XChangeProperty(dndPtr->display, dropPtr->window, dataPtr->commAtom, 
            XA_STRING, 8, PropModeReplace, (unsigned char *)"", 0);
      return;
    } 
    pending.window = dropPtr->window;
    pending.display = dndPtr->display;
    pending.commAtom = dataPtr->commAtom;
    pending.offset = 0;
    pending.packetSize = GetMaxPropertySize(dndPtr->display);
    SendDataToTarget(dndPtr, &pending);
    Tcl_DStringFree(&(pending.dString));
#ifdef notdef
    fprintf(stderr, "<- DoDrop\n");
#endif
}

static void
DropFinished(dndPtr, eventPtr)
    Dnd *dndPtr;
    XEvent *eventPtr;
{
    Tcl_Interp *interp = dndPtr->interp;
    Tcl_DString dString, savedResult;
    int result;
    char **p;
    struct DropResult {
      int mesg;         /* TS_DROP_RESULT message. */
      Window window;          /* Target window. */
      int timestamp;          /* Transaction timestamp. */
      int result;       /* Result of transaction. */
    } *dropPtr;

#ifdef notdef
    fprintf(stderr, "DropFinished\n");
#endif
    dropPtr = (struct DropResult *)eventPtr->xclient.data.l;

    Tcl_DStringInit(&dString);
    for (p = dndPtr->resultCmd; *p != NULL; p++) {
      Tcl_DStringAppendElement(&dString, *p);
    }
    Tcl_DStringAppendElement(&dString, Tk_PathName(dndPtr->tkwin));
    Tcl_DStringAppendElement(&dString, "action");
    Tcl_DStringAppendElement(&dString, NameOfAction(dropPtr->result));
    Tcl_DStringAppendElement(&dString, "timestamp");
    Tcl_DStringAppendElement(&dString, Blt_Utoa(dropPtr->timestamp));

    Tcl_DStringInit(&savedResult);
    Tcl_DStringGetResult(interp, &savedResult);
    result = Tcl_GlobalEval(interp, Tcl_DStringValue(&dString));
    Tcl_DStringFree(&dString);
    if (result != TCL_OK) {
      Tcl_BackgroundError(interp);
    }
    Tcl_DStringResult(interp, &savedResult);
}


static void
FreeFormats(dndPtr) 
    Dnd *dndPtr;
{
    if (dndPtr->matchingFormats != NULL) {
      Blt_Free(dndPtr->matchingFormats);
      dndPtr->matchingFormats = NULL;
    }
    dndPtr->lastId = None;
}

static char *
GetSourceFormats(dndPtr, window, timestamp)
    Dnd *dndPtr;
    Window window;
    int timestamp;
{
    if (dndPtr->lastId != timestamp) {
      char *data;
      
      FreeFormats(dndPtr);
      data = GetProperty(dndPtr->display, window, 
               dndPtr->dataPtr->formatsAtom);
      if (data != NULL) {
          dndPtr->matchingFormats = Blt_Strdup(data);
          XFree(data);
      }
      dndPtr->lastId = timestamp;
    }
    if (dndPtr->matchingFormats == NULL) {
      return "";
    }
    return dndPtr->matchingFormats;
}
 

static int
InvokeCallback(dndPtr, cmd, x, y, formats, button, keyState, timestamp)
    Dnd *dndPtr;
    char **cmd;
    int x, y;
    char *formats;
    int button, keyState, timestamp;
{
    Tcl_DString dString, savedResult;
    Tcl_Interp *interp = dndPtr->interp;
    int result;
    char **p;

    Tcl_DStringInit(&dString);
    for (p = cmd; *p != NULL; p++) {
      Tcl_DStringAppendElement(&dString, *p);
    }
    Tcl_DStringAppendElement(&dString, Tk_PathName(dndPtr->tkwin));
    x -= Blt_RootX(dndPtr->tkwin); /* Send coordinates relative to target. */
    y -= Blt_RootY(dndPtr->tkwin);
    Tcl_DStringAppendElement(&dString, "x");
    Tcl_DStringAppendElement(&dString, Blt_Itoa(x));
    Tcl_DStringAppendElement(&dString, "y");
    Tcl_DStringAppendElement(&dString, Blt_Itoa(y));
    Tcl_DStringAppendElement(&dString, "formats");
    if (formats == NULL) {
      formats = "";
    }
    Tcl_DStringAppendElement(&dString, formats);
    Tcl_DStringAppendElement(&dString, "button");
    Tcl_DStringAppendElement(&dString, Blt_Itoa(button));
    Tcl_DStringAppendElement(&dString, "state");
    Tcl_DStringAppendElement(&dString, Blt_Itoa(keyState));
    Tcl_DStringAppendElement(&dString, "timestamp");
    Tcl_DStringAppendElement(&dString, Blt_Utoa(timestamp));
    Tcl_Preserve(interp);
    Tcl_DStringInit(&savedResult);
    Tcl_DStringGetResult(interp, &savedResult);
    result = Tcl_GlobalEval(interp, Tcl_DStringValue(&dString));
    Tcl_DStringFree(&dString);
    if (result == TCL_OK) {
      result = GetDragResult(interp, Tcl_GetStringResult(interp));
    } else {
      result = DROP_CANCEL;
      Tcl_BackgroundError(interp);
    }
    Tcl_DStringResult(interp, &savedResult);
    Tcl_Release(interp);
    return result;
}

/* 
 * ------------------------------------------------------------------------
 *
 *  AcceptDrop --
 *    
 *    Invokes a Tcl procedure to handle the target's side of the
 *    drop.  A Tcl procedure is invoked, either one designated for 
 *    this target by the user (-ondrop) or a default Tcl procedure. 
 *    It is passed the following arguments:
 *
 *          widget            The path name of the target. 
 *          x           X-coordinate of the mouse relative to the 
 *                      widget.
 *          y           Y-coordinate of the mouse relative to the 
 *                      widget.
 *          formats           A list of data formats acceptable to both
 *                      the source and target.
 *          button            Button pressed.
 *          state       Key state.
 *          timestamp   Timestamp of transaction.
 *          action            Requested action from source.
 *
 *    If the Tcl procedure returns "cancel", this indicates that the drop was
 *    not accepted by the target and the reject symbol should be displayed.
 *    Otherwise one of the following strings may be recognized:
 *
 *          "cancel"    Drop was canceled.
 *          "copy"            Source data has been successfully copied.
 *          "link"            Target has made a link to the data. It's 
 *                      Ok for the source to remove it's association
 *                      with the data, but not to delete the data
 *                      itself.
 *          "move"            Source data has been successfully copied,
 *                      it's Ok for the source to delete its
 *                      association with the data and the data itself.
 *
 *    The result is relayed back to the source via another client message.
 *    The source may or may not be waiting for the result.
 *
 * Results:
 *    None.
 *
 * Side Effects:
 *    A Tcl procedure is invoked in the target to handle the drop event.
 *    The result of the drop is sent (via another ClientMessage) to the
 *    source.
 *
 * ------------------------------------------------------------------------
 */
static int
AcceptDrop(dndPtr, x, y, formats, button, keyState, timestamp) 
    Dnd *dndPtr;        /* Target where the drop event occurred. */
    int x, y;
    char *formats;
    int button, keyState, timestamp;
{
    Tcl_Interp *interp = dndPtr->interp;
    char **cmd;
    Tcl_DString dString, savedResult;
    int result;
    
    if (dndPtr->motionCmd != NULL) {
      result = InvokeCallback(dndPtr, dndPtr->motionCmd, x, y, formats, 
            button, keyState, timestamp);
      if (result != DROP_OK) {
          return result;
      }
    }
    if (dndPtr->leaveCmd != NULL) {
      InvokeCallback(dndPtr, dndPtr->leaveCmd, x, y, formats, button, 
             keyState, timestamp);
    }
    Tcl_DStringInit(&dString);
    cmd = dndPtr->dropCmd;
    if (cmd != NULL) {
      char **p;

      for (p = cmd; *p != NULL; p++) {
          Tcl_DStringAppendElement(&dString, *p);
      }
    } else {
      Tcl_DStringAppendElement(&dString, "blt::DndStdDrop");
    }
    Tcl_DStringAppendElement(&dString, Tk_PathName(dndPtr->tkwin));
    dndPtr->dropX = x - Blt_RootX(dndPtr->tkwin);
    dndPtr->dropY = y - Blt_RootY(dndPtr->tkwin);
    Tcl_DStringAppendElement(&dString, "x");
    Tcl_DStringAppendElement(&dString, Blt_Itoa(dndPtr->dropX));
    Tcl_DStringAppendElement(&dString, "y");
    Tcl_DStringAppendElement(&dString, Blt_Itoa(dndPtr->dropY));
    Tcl_DStringAppendElement(&dString, "formats");
    Tcl_DStringAppendElement(&dString, formats);
    Tcl_DStringAppendElement(&dString, "button");
    Tcl_DStringAppendElement(&dString, Blt_Itoa(button));
    Tcl_DStringAppendElement(&dString, "state");
    Tcl_DStringAppendElement(&dString, Blt_Itoa(keyState));
    Tcl_DStringAppendElement(&dString, "timestamp");
    Tcl_DStringAppendElement(&dString, Blt_Utoa(timestamp));
    Tcl_Preserve(interp);
    Tcl_DStringInit(&savedResult);
    Tcl_DStringGetResult(interp, &savedResult);
    result = Tcl_GlobalEval(interp, Tcl_DStringValue(&dString));
    Tcl_DStringFree(&dString);
    if (result == TCL_OK) {
      result = GetAction(Tcl_GetStringResult(interp));
    } else {
      result = DROP_CANCEL;
      Tcl_BackgroundError(interp);
    }
    Tcl_DStringResult(interp, &savedResult);
    Tcl_Release(interp);
    return result;
}

/* 
 * ------------------------------------------------------------------------
 *
 *  HandleDropEvent --
 *    
 *    Invokes a Tcl procedure to handle the target's side of the
 *    drop.  This routine is triggered via a client message from the
 *    drag source indicating that the token was dropped over this
 *    target. The fields of the incoming message are:
 *
 *          data.l[0]   Message type.
 *          data.l[1]   Window Id of the source.
 *          data.l[2]   Screen X-coordinate of the pointer.
 *          data.l[3]   Screen Y-coordinate of the pointer.
 *          data.l[4]   Id of the drag&drop transaction.
 *
 *    A Tcl procedure is invoked, either one designated for this 
 *    target by the user (-ondrop) or a default Tcl procedure. It
 *    is passed the following arguments:
 *
 *          widget            The path name of the target. 
 *          x           X-coordinate of the mouse relative to the 
 *                      widget.
 *          y           Y-coordinate of the mouse relative to the 
 *                      widget.
 *          formats           A list of data formats acceptable to both
 *                      the source and target.
 *
 *    If the Tcl procedure returns "cancel", this indicates that the drop was
 *    not accepted by the target and the reject symbol should be displayed.
 *    Otherwise one of the following strings may be recognized:
 *
 *          "cancel"    Drop was canceled.
 *          "copy"            Source data has been successfully copied.
 *          "link"            Target has made a link to the data. It's 
 *                      Ok for the source to remove it's association
 *                      with the data, but not to delete the data
 *                      itself.
 *          "move"            Source data has been successfully copied,
 *                      it's Ok for the source to delete its
 *                      association with the data and the data itself.
 *
 *    The result is relayed back to the source via another client message.
 *    The source may or may not be waiting for the result.
 *
 * Results:
 *    None.
 *
 * Side Effects:
 *    A Tcl procedure is invoked in the target to handle the drop event.
 *    The result of the drop is sent (via another ClientMessage) to the
 *    source.
 *
 * ------------------------------------------------------------------------
 */
static void
HandleDropEvent(dndPtr, eventPtr) 
    Dnd *dndPtr;        /* Target where the drop event occurred. */
    XEvent *eventPtr;         /* Message sent from the drag source. */
{
    int button, keyState;
    int x, y;
    char *formats;
    int result;
    struct DropInfo {
      int mesg;         /* TS_DROP message. */
      Window window;          /* Source window. */
      int timestamp;          /* Transaction timestamp. */
      int point;        /* Root X-Y coordinate of pointer. */
      int flags;        /* Button/keystate information. */
    } *dropPtr;
    DropPending pending;

    dropPtr = (struct DropInfo *)eventPtr->xclient.data.l;
    UNPACK(dropPtr->point, x, y);
    UNPACK(dropPtr->flags, button, keyState);

    /* Set up temporary bookkeeping for the drop transaction */
    memset (&pending, 0, sizeof(pending));
    pending.window = dropPtr->window;
    pending.display = eventPtr->xclient.display;
    pending.timestamp = dropPtr->timestamp;
    pending.protocol = PROTO_BLT;
    pending.packetSize = GetMaxPropertySize(pending.display);
    Tcl_DStringInit(&(pending.dString));

    formats = GetSourceFormats(dndPtr, dropPtr->window, dropPtr->timestamp);

    dndPtr->pendingPtr = &pending;
    result = AcceptDrop(dndPtr, x, y, formats, button, keyState, 
      dropPtr->timestamp);
    dndPtr->pendingPtr = NULL;

    /* Target-to-Source: Drop result message. */
    SendClientMsg(dndPtr->display, dropPtr->window, dndPtr->dataPtr->mesgAtom,
      TS_DROP_RESULT, (int)Tk_WindowId(dndPtr->tkwin), dropPtr->timestamp, 
      result, 0);
    FreeFormats(dndPtr);
}

/* 
 * ------------------------------------------------------------------------
 *
 *  HandleDragEvent --
 *    
 *    Invokes one of 3 Tcl procedures to handle the target's side of 
 *    the drag operation.  This routine is triggered via a ClientMessage 
 *    from the drag source indicating that the token as either entered,
 *    moved, or left this target.  The source sends messages only if
 *    Tcl procedures on the target have been defined to watch the 
 *    events. The message_type field can be either 
 *
 *      ST_DRAG_ENTER         The mouse has entered the target.
 *      ST_DRAG_MOTION  The mouse has moved within the target.
 *      ST_DRAG_LEAVE         The mouse has left the target.
 *
 *    The data fields are as follows:
 *      data.l[0]       Message type.
 *      data.l[1]       Window Id of the source.
 *      data.l[2]       Timestamp of the drag&drop transaction.
 *      data.l[3]       Root X-Y coordinate of the pointer.
 *      data.l[4]       Button and key state information.
 *
 *    For any of the 3 Tcl procedures, the following arguments
 *    are passed:
 *
 *      widget          The path name of the target. 
 *      x               X-coordinate of the mouse in the widget.
 *      y               Y-coordinate of the mouse in the widget.
 *      formats         A list of data formats acceptable to both
 *                      the source and target.
 *
 *    If the Tcl procedure returns "cancel", this indicates that the drag 
 *    operation has been canceled and the reject symbol should be displayed.
 *    Otherwise it should return a boolean:
 *
 *      true                  Target will accept drop.
 *      false                 Target will not accept the drop.
 *
 *    The purpose of the Enter and Leave procedure is to allow the
 *    target to provide visual feedback that the drop can occur or not.
 *    The Motion procedure is for cases where the drop area is a smaller
 *    area within the target, such as a canvas item on a canvas. The 
 *    procedure can determine (based upon the X-Y coordinates) whether 
 *    the pointer is over the canvas item and return a value accordingly.
 *
 *    The result of the Tcl procedure is then relayed back to the 
 *    source by a ClientMessage.  
 *
 * Results:
 *    None.
 *
 * Side Effects:
 *    A Tcl procedure is invoked in the target to handle the drag event.
 *    The result of the drag is sent (via another ClientMessage) to the
 *    source.
 *
 * ------------------------------------------------------------------------
 */
static void
HandleDragEvent(dndPtr, eventPtr) 
    Dnd *dndPtr;        /* Target where the drag event occurred. */
    XEvent *eventPtr;         /* Message sent from the drag source. */
{
    char **cmd;
    int resp;
    int x, y;
    int button, keyState;
    char *formats;
    struct DragInfo {
      int mesg;         /* Drag-and-drop message type. */
      Window window;          /* Source window. */
      int timestamp;          /* Transaction timestamp. */
      int point;        /* Root X-Y coordinate of pointer. */
      int flags;        /* Button/keystate information. */
    } *dragPtr;

    dragPtr = (struct DragInfo *)eventPtr->xclient.data.l;

    cmd = NULL;
    switch (dragPtr->mesg) {
    case ST_DRAG_ENTER:
      cmd = dndPtr->enterCmd;
      break;
    case ST_DRAG_MOTION:
      cmd = dndPtr->motionCmd;
      break;
    case ST_DRAG_LEAVE:
      cmd = dndPtr->leaveCmd;
      break;
    } 
    if (cmd == NULL) {
      return;                 /* Nothing to do. */
    }
    UNPACK(dragPtr->point, x, y);
    UNPACK(dragPtr->flags, button, keyState);
    formats = GetSourceFormats(dndPtr, dragPtr->window, dragPtr->timestamp);
    resp = InvokeCallback(dndPtr, cmd, x, y, formats, button, keyState,
              dragPtr->timestamp);

    /* Target-to-Source: Drag result message. */
    SendClientMsg(dndPtr->display, dragPtr->window, dndPtr->dataPtr->mesgAtom,
      TS_DRAG_STATUS, (int)Tk_WindowId(dndPtr->tkwin), dragPtr->timestamp, 
      resp, 0);
}

/*
 * ------------------------------------------------------------------------
 *
 *  DndEventProc --
 *
 *    Invoked by Tk_HandleEvent whenever a DestroyNotify event is received
 *    on a registered drag&drop source widget.
 *
 * ------------------------------------------------------------------------
 */
static int
DndEventProc(clientData, eventPtr)
    ClientData clientData;    /* Drag&drop record. */
    XEvent *eventPtr;         /* Event description. */
{
    Dnd *dndPtr = clientData;

    if (eventPtr->xany.window != Tk_WindowId(dndPtr->tkwin)) {
      return 0;
    }
    if (eventPtr->type == DestroyNotify) {
      dndPtr->tkwin = NULL;
      dndPtr->flags |= DND_DELETED;
      Tcl_EventuallyFree(dndPtr, DestroyDnd);
      return 0;         /* Other handlers have to see this event too.*/
    } else if (eventPtr->type == ButtonPress) {
      dndPtr->keyState = eventPtr->xbutton.state;
      dndPtr->button =  eventPtr->xbutton.button;
      return 0;
    } else if (eventPtr->type == ButtonRelease) {
      dndPtr->keyState = eventPtr->xbutton.state;
      dndPtr->button =  eventPtr->xbutton.button;
      return 0;
    } else if (eventPtr->type == MotionNotify) {
      dndPtr->keyState = eventPtr->xmotion.state;
      return 0;
    } else if ((eventPtr->type == ClientMessage) &&
      (eventPtr->xclient.message_type == dndPtr->dataPtr->mesgAtom)) {
      int result;

      switch((unsigned int)eventPtr->xclient.data.l[0]) {
      case TS_START_DROP:
          DoDrop(dndPtr, eventPtr);
          return 1;
          
      case TS_DROP_RESULT:
          result = eventPtr->xclient.data.l[MESG_RESPONSE];
          dndPtr->tokenPtr->status = result;
          if (result == DROP_CANCEL) {
            CancelDrag(dndPtr);
          } else if (result == DROP_FAIL) {
            EventuallyRedrawToken(dndPtr);
          } else {
            dndPtr->tokenPtr->nSteps = 10;
            FadeToken(dndPtr);
          }
          if (dndPtr->resultCmd != NULL) {
            DropFinished(dndPtr, eventPtr);
          }
          return 1;

      case TS_DRAG_STATUS:
          result = eventPtr->xclient.data.l[MESG_RESPONSE];
          ChangeToken(dndPtr, result);
          return 1;

      case ST_DROP:
          HandleDropEvent(dndPtr, eventPtr);
          return 1;

      case ST_DRAG_ENTER:
      case ST_DRAG_MOTION:
      case ST_DRAG_LEAVE:
          HandleDragEvent(dndPtr, eventPtr);
          return 1;
      }
    }
    return 0;
}

static void
SendPointerMessage(dndPtr, eventType, windowPtr, x, y)
    Dnd *dndPtr;        /* Source drag&drop manager. */
    int eventType;            /* Type of event to relay. */
    Winfo *windowPtr;         /* Generic window information. */
    int x, y;                 /* Root coordinates of mouse. */
{
    /* Source-to-Target: Pointer event messages. */
    SendClientMsg(
      dndPtr->display,  /* Display of recipient window. */
      windowPtr->window,      /* Recipient window. */
      dndPtr->dataPtr->mesgAtom, /* Message type. */
      eventType,        /* Data 1 */
      (int)Tk_WindowId(dndPtr->tkwin), /* Data 2 */
      dndPtr->timestamp,      /* Data 3  */
      PACK(x, y),       /* Data 4 */
      PACK(dndPtr->button, dndPtr->keyState)); /* Data 5 */
    /* Don't wait the response. */
}

static void
RelayEnterEvent(dndPtr, windowPtr, x, y)
    Dnd *dndPtr;
    Winfo *windowPtr;
    int x, y;
{
    if ((windowPtr != NULL) && (windowPtr->eventFlags & WATCH_ENTER)) {
      SendPointerMessage(dndPtr, ST_DRAG_ENTER, windowPtr, x, y);
    }
}

static void
RelayLeaveEvent(dndPtr, windowPtr, x, y)
    Dnd *dndPtr;
    Winfo *windowPtr;
    int x, y;
{ 
    if ((windowPtr != NULL) && (windowPtr->eventFlags & WATCH_LEAVE)) {
      SendPointerMessage(dndPtr, ST_DRAG_LEAVE, windowPtr, x, y);
    }
}

static void
RelayMotionEvent(dndPtr, windowPtr, x, y)
    Dnd *dndPtr;
    Winfo *windowPtr;
    int x, y;
{
    if ((windowPtr != NULL) && (windowPtr->eventFlags & WATCH_MOTION)) {
      SendPointerMessage(dndPtr, ST_DRAG_MOTION, windowPtr, x, y);
    }
}

static void
RelayDropEvent(dndPtr, windowPtr, x, y)
    Dnd *dndPtr;
    Winfo *windowPtr;
    int x, y;
{
    SendPointerMessage(dndPtr, ST_DROP, windowPtr, x, y);
}

/*
 * ------------------------------------------------------------------------
 *
 *  FreeWinfo --
 *
 * ------------------------------------------------------------------------
 */
static void
FreeWinfo(windowPtr)
    Winfo *windowPtr;         /* window rep to be freed */
{
    Winfo *childPtr;
    Blt_ChainLink *linkPtr;

    for (linkPtr = Blt_ChainFirstLink(windowPtr->chainPtr); linkPtr != NULL;
      linkPtr = Blt_ChainNextLink(linkPtr)) {
      childPtr = Blt_ChainGetValue(linkPtr);
      FreeWinfo(childPtr);    /* Recursively free children. */
    }
    if (windowPtr->matches != NULL) {
      Blt_Free(windowPtr->matches);
    }
    Blt_ChainDestroy(windowPtr->chainPtr);
    Blt_Free(windowPtr);
}

/*
 * ------------------------------------------------------------------------
 *
 *  GetWinfo --
 *
 *    Invoked during "drag" operations. Digs into the root window
 *    hierarchy and caches the window-related information.
 *    If the current point lies over an uninitialized window (i.e.
 *    one that already has an allocated Winfo structure, but has
 *    not been filled in yet), this routine is called to query 
 *    window coordinates.  If the window has any children, more 
 *    uninitialized Winfo structures are allocated.  Further queries 
 *    will cause these structures to be initialized in turn.
 *
 * ------------------------------------------------------------------------
 */
static void
GetWinfo(display, windowPtr)
    Display *display;
    Winfo *windowPtr;         /* window rep to be initialized */
{
    int visible;

    if (windowPtr->initialized) {
      return;
    }
    /* Query for the window coordinates.  */
    visible = GetWindowRegion(display, windowPtr);
    if (visible) {
      Blt_ChainLink *linkPtr;
      Blt_Chain *chainPtr;
      Winfo *childPtr;

      /* Add offset from parent's origin to coordinates */
      if (windowPtr->parentPtr != NULL) {
          windowPtr->x1 += windowPtr->parentPtr->x1;
          windowPtr->y1 += windowPtr->parentPtr->y1;
          windowPtr->x2 += windowPtr->parentPtr->x1;
          windowPtr->y2 += windowPtr->parentPtr->y1;
      }
      /*
       * Collect a list of child windows, sorted in z-order.  The
       * topmost window will be first in the list.
       */
      chainPtr = GetWindowZOrder(display, windowPtr->window);

      /* Add and initialize extra slots if needed. */
      for (linkPtr = Blt_ChainFirstLink(chainPtr); linkPtr != NULL;
          linkPtr = Blt_ChainNextLink(linkPtr)) {
          childPtr = Blt_Calloc(1, sizeof(Winfo));
          assert(childPtr);
          childPtr->initialized = FALSE;
          childPtr->window = (Window)Blt_ChainGetValue(linkPtr);
          childPtr->parentPtr = windowPtr;
          Blt_ChainSetValue(linkPtr, childPtr);
      }
      windowPtr->chainPtr = chainPtr;
    } else {
      /* If it's not viewable don't bother doing anything else. */
      windowPtr->x1 = windowPtr->y1 = windowPtr->x2 = windowPtr->y2 = -1;
      windowPtr->chainPtr = NULL;
    }
    windowPtr->initialized = TRUE;
}

/*
 * ------------------------------------------------------------------------
 *
 *  InitRoot --
 *
 *    Invoked at the start of a "drag" operation to capture the
 *    positions of all windows on the current root.  Queries the
 *    entire window hierarchy and determines the placement of each
 *    window.  Queries the "BltDndTarget" property info where
 *    appropriate.  This information is used during the drag
 *    operation to determine when the drag&drop token is over a
 *    valid drag&drop target.
 *
 *  Results:
 *    Returns the record for the root window, which contains records
 *    for all other windows as children.
 *
 * ------------------------------------------------------------------------
 */
static Winfo *
InitRoot(dndPtr)
    Dnd *dndPtr;
{
    Winfo *rootPtr;

    rootPtr = Blt_Calloc(1, sizeof(Winfo));
    assert(rootPtr);
    rootPtr->window = DefaultRootWindow(dndPtr->display);
    dndPtr->windowPtr = NULL;
    GetWinfo(dndPtr->display, rootPtr);
    return rootPtr;
}


static int
ParseProperty(interp, dndPtr, windowPtr, data)
    Tcl_Interp *interp;
    Dnd *dndPtr;
    Winfo *windowPtr;
    char *data;
{
    int nElems;
    char **elemArr;
    int eventFlags;
    Tcl_DString dString;
    int count;
    register int i;
    
    if (Tcl_SplitList(interp, data, &nElems, &elemArr) != TCL_OK) {
      return TCL_ERROR; /* Malformed property list. */
    }
    if (nElems < 1) {
      Tcl_AppendResult(interp, "Malformed property \"", data, "\"", 
                   (char *)NULL);
      goto error;
    }
    if (Tcl_GetInt(interp, elemArr[PROP_WATCH_FLAGS], &eventFlags) != TCL_OK) {
      goto error;
    }

    /* target flags, type1, type2, ... */
    /*
     * The target property contains a list of possible formats.
     * Compare this with what formats the source is willing to
     * convert and compress the list down to just the matching
     * formats.  It's up to the target to request the specific 
     * type (or types) that it wants.
     */
    count = 0;
    Tcl_DStringInit(&dString);
    if (dndPtr->reqFormats == NULL) {
      Blt_HashEntry *hPtr;
      Blt_HashSearch cursor;
      char *fmt;

      for (i = 1; i < nElems; i++) {
          for(hPtr = Blt_FirstHashEntry(&(dndPtr->getDataTable), &cursor);
            hPtr != NULL; hPtr = Blt_NextHashEntry(&cursor)) {
            fmt = Blt_GetHashKey(&(dndPtr->getDataTable), hPtr);
            if ((*fmt == elemArr[i][0]) && (strcmp(fmt, elemArr[i]) == 0)) {
                Tcl_DStringAppendElement(&dString, elemArr[i]);
                count++;
                break;
            }
          }
      }
    } else {
      register char **s;

      for (i = 1; i < nElems; i++) {
          for (s = dndPtr->reqFormats; *s != NULL; s++) {
            if ((**s == elemArr[i][0]) && (strcmp(*s, elemArr[i]) == 0)) {
                Tcl_DStringAppendElement(&dString, elemArr[i]);
                count++;
            }
          }
      }
    }
    if (count == 0) {
#ifdef notdef
      fprintf(stderr, "source/target mismatch: No matching types\n");
#endif
      return TCL_BREAK;
    } 
    if (eventFlags != 0) {
      SetProperty(dndPtr->tkwin, dndPtr->dataPtr->formatsAtom, 
            Tcl_DStringValue(&dString));
      windowPtr->matches = NULL;
    } else {
      windowPtr->matches = Blt_Strdup(Tcl_DStringValue(&dString));
    }
    Tcl_DStringFree(&dString);      
    windowPtr->eventFlags = eventFlags;
    return TCL_OK;
 error:
    Blt_Free(elemArr);
    return TCL_ERROR;
}

/*
 * ------------------------------------------------------------------------
 *
 *  OverTarget --
 *
 *      Checks to see if a compatible drag&drop target exists at the
 *      given position.  A target is "compatible" if it is a drag&drop
 *      window, and if it has a handler that is compatible with the
 *      current source window.
 *
 *  Results:
 *    Returns a pointer to a structure describing the target, or NULL
 *    if no compatible target is found.
 *
 * ------------------------------------------------------------------------
 */
static Winfo *
OverTarget(dndPtr)
    Dnd *dndPtr;        /* drag&drop source window */
{
    Tcl_Interp *interp = dndPtr->interp;
    int x, y;
    int vx, vy;
    int dummy;
    Winfo *windowPtr;

    /* 
     * If no window info has been been gathered yet for this target,
     * then abort this call.  This probably means that the token is
     * moved before it has been properly built.  
     */
    if (dndPtr->rootPtr == NULL) {
      fprintf(stderr, "rootPtr not initialized\n");
      return NULL;
    }
    /* Adjust current location for virtual root windows.  */
    Tk_GetVRootGeometry(dndPtr->tkwin, &vx, &vy, &dummy, &dummy);
    x = dndPtr->x + vx;
    y = dndPtr->y + vy;

    windowPtr = FindTopWindow(dndPtr, x, y);
    if (windowPtr == NULL) {
      return NULL;            /* Not over a window. */
    }
    if ((!dndPtr->selfTarget) && 
      (Tk_WindowId(dndPtr->tkwin) == windowPtr->window)) {
      return NULL;            /* If the self-target flag isn't set,
                         *  don't allow the source window to
                         *  drop onto itself.  */
    }
    if (!windowPtr->lookedForProperty) {
      char *data;
      int result;

      windowPtr->lookedForProperty = TRUE;
      /* See if this window has a "BltDndTarget" property. */
      data = GetProperty(dndPtr->display, windowPtr->window, 
            dndPtr->dataPtr->targetAtom);
      if (data == NULL) {
#ifdef notdef
          fprintf(stderr, "No property on 0x%x\n", windowPtr->window);
#endif
          return NULL;        /* No such property on window. */
      }
      result = ParseProperty(interp, dndPtr, windowPtr, data);
      XFree(data);
      if (result == TCL_BREAK) {
#ifdef notdef
          fprintf(stderr, "No matching formats\n");
#endif
          return NULL;
      }
      if (result != TCL_OK) {
          Tcl_BackgroundError(interp);
          return NULL;        /* Malformed property list. */
      } 
      windowPtr->isTarget = TRUE;
    }
    if (!windowPtr->isTarget) {
      return NULL;
    }
    return windowPtr;

}

/*
 * ------------------------------------------------------------------------
 *
 *  AddTargetProperty --
 *
 *    Attaches a drag&drop property to the given target window.
 *    This property allows us to recognize the window later as a
 *    valid target. It also stores important information including
 *    the interpreter managing the target and the pathname of the
 *    target window.  Usually this routine is called when the target
 *    is first registered or first exposed (so that the X-window
 *    really exists).
 *
 * ------------------------------------------------------------------------
 */
static void
AddTargetProperty(dndPtr)
    Dnd *dndPtr;        /* drag&drop target window data */
{
    Tcl_DString dString;
    Blt_HashEntry *hPtr;
    unsigned int eventFlags;
    Blt_HashSearch cursor;
    char *fmt;
    char string[200];

    Tcl_DStringInit(&dString);
    /*
     * Each target window's dnd property contains
     *
     *      1. Mouse event flags.
     *  2. List of all the data types that can be handled. If none
     *     are listed, then all can be handled.
     */
    eventFlags = 0;
    if (dndPtr->enterCmd != NULL) {
      eventFlags |= WATCH_ENTER;
    }
    if (dndPtr->leaveCmd != NULL) {
      eventFlags |= WATCH_LEAVE;
    }
    if (dndPtr->motionCmd != NULL) {
      eventFlags |= WATCH_MOTION;
    }
    sprintf(string, "0x%x", eventFlags);
    Tcl_DStringAppendElement(&dString, string);
    for (hPtr = Blt_FirstHashEntry(&(dndPtr->setDataTable), &cursor);
      hPtr != NULL; hPtr = Blt_NextHashEntry(&cursor)) {
      fmt = Blt_GetHashKey(&(dndPtr->setDataTable), hPtr);
      Tcl_DStringAppendElement(&dString, fmt);
    }
    SetProperty(dndPtr->tkwin, dndPtr->dataPtr->targetAtom, 
      Tcl_DStringValue(&dString));
    dndPtr->targetPropertyExists = TRUE;
    Tcl_DStringFree(&dString);
}

static void
CancelDrag(dndPtr)
    Dnd *dndPtr;
{
    if (dndPtr->flags & DND_INITIATED) {
      dndPtr->tokenPtr->nSteps = 10;
      SnapToken(dndPtr);
      StopActiveCursor(dndPtr);
      if (dndPtr->cursor == None) {
          Tk_UndefineCursor(dndPtr->tkwin);
      } else {
          Tk_DefineCursor(dndPtr->tkwin, dndPtr->cursor);
      }
    }
    if (dndPtr->rootPtr != NULL) {
      FreeWinfo(dndPtr->rootPtr);
      dndPtr->rootPtr = NULL;
    }
}

static int
DragInit(dndPtr, x, y)
    Dnd *dndPtr;
    int x, y;
{
    Token *tokenPtr = dndPtr->tokenPtr;
    int result;
    Winfo *newPtr;

    assert((dndPtr->flags & DND_ACTIVE) == DND_SELECTED);
    
    if (dndPtr->rootPtr != NULL) {
      FreeWinfo(dndPtr->rootPtr);
    }
    dndPtr->rootPtr = InitRoot(dndPtr); /* Reset information cache. */ 
    dndPtr->flags &= ~DND_VOIDED;

    dndPtr->x = x;      /* Save current location. */
    dndPtr->y = y;
    result = TRUE;
    Tcl_Preserve(dndPtr);
    if (dndPtr->packageCmd != NULL) {
      Tcl_DString dString, savedResult;
      Tcl_Interp *interp = dndPtr->interp;
      int status;
      char **p;
      int rx, ry;

      Tcl_DStringInit(&dString);
      for (p = dndPtr->packageCmd; *p != NULL; p++) {
          Tcl_DStringAppendElement(&dString, *p);
      }
      Tcl_DStringAppendElement(&dString, Tk_PathName(dndPtr->tkwin));
      rx = dndPtr->dragX - Blt_RootX(dndPtr->tkwin);
      ry = dndPtr->dragY - Blt_RootY(dndPtr->tkwin);
      Tcl_DStringAppendElement(&dString, "x");
      Tcl_DStringAppendElement(&dString, Blt_Itoa(rx));
      Tcl_DStringAppendElement(&dString, "y");
      Tcl_DStringAppendElement(&dString, Blt_Itoa(ry));
      Tcl_DStringAppendElement(&dString, "button");
      Tcl_DStringAppendElement(&dString, Blt_Itoa(dndPtr->button));
      Tcl_DStringAppendElement(&dString, "state");
      Tcl_DStringAppendElement(&dString, Blt_Itoa(dndPtr->keyState));
      Tcl_DStringAppendElement(&dString, "timestamp");
      Tcl_DStringAppendElement(&dString, Blt_Utoa(dndPtr->timestamp));
      Tcl_DStringAppendElement(&dString, "token");
      Tcl_DStringAppendElement(&dString, Tk_PathName(tokenPtr->tkwin));

      Tcl_DStringInit(&savedResult);
      Tcl_DStringGetResult(interp, &savedResult);
      dndPtr->flags |= DND_IN_PACKAGE;
      status = Tcl_GlobalEval(interp, Tcl_DStringValue(&dString));
      dndPtr->flags &= ~DND_IN_PACKAGE;
      if (status == TCL_OK) {
          result = GetDragResult(interp, Tcl_GetStringResult(interp));
      } else {
          Tcl_BackgroundError(interp);
      }
      Tcl_DStringFree(&dString);
      Tcl_DStringResult(interp, &savedResult);
      Tcl_DStringFree(&dString);
      if (status != TCL_OK) {
          HideToken(dndPtr);
          Tcl_Release(dndPtr);
          return TCL_ERROR;
      }
    }
    if (dndPtr->flags & DND_VOIDED) {
      HideToken(dndPtr);
      Tcl_Release(dndPtr);
      return TCL_RETURN;
    }
    if ((!result) || (dndPtr->flags & DND_DELETED)) {
      HideToken(dndPtr);
      Tcl_Release(dndPtr);
      return TCL_RETURN;
    }
    Tcl_Release(dndPtr);

    if (dndPtr->cursor != None) {
      Tk_Cursor cursor;
      
      /* Save the old cursor */
      cursor = GetWidgetCursor(dndPtr->interp, dndPtr->tkwin);
      if (dndPtr->cursor != None) {
          Tk_FreeCursor(dndPtr->display, dndPtr->cursor);
      }
      dndPtr->cursor = cursor;
      if (dndPtr->cursors != NULL) {
          /* Temporarily install the drag-and-drop cursor. */
          Tk_DefineCursor(dndPtr->tkwin, dndPtr->cursors[0]);
      }
    }
    if (Tk_WindowId(tokenPtr->tkwin) == None) {
      Tk_MakeWindowExist(tokenPtr->tkwin);
    }
    if (!Tk_IsMapped(tokenPtr->tkwin)) {
      Tk_MapWindow(tokenPtr->tkwin);
    }
    dndPtr->flags |= DND_INITIATED;
    newPtr = OverTarget(dndPtr);
    RelayEnterEvent(dndPtr, newPtr, x, y);
    dndPtr->windowPtr = newPtr;
    tokenPtr->status = (newPtr != NULL) ? DROP_OK : DROP_CONTINUE;
    if (tokenPtr->lastStatus != tokenPtr->status) {
      EventuallyRedrawToken(dndPtr);
    }
    MoveToken(dndPtr);        /* Move token to current drag point. */ 
    RaiseToken(dndPtr);
    return TCL_OK;
}

/*
 * ------------------------------------------------------------------------
 *
 *  CancelOp --
 *
 *    Cancels the current drag&drop operation for the source.  Calling
 *    this operation does not affect the transfer of data from the 
 *    source to the target, once the drop has been made.  From the 
 *    source's point of view, the drag&drop operation is already over. 
 *
 *    Example: dnd cancel .widget
 *
 * Results:
 *    A standard Tcl result.
 *
 * Side Effects:
 *    Hides the token and sets a flag indicating that further "drag"
 *    and "drop" operations should be ignored.
 *
 * ------------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
CancelOp(clientData, interp, argc, argv)
    ClientData clientData;    /* Thread-specific data. */
    Tcl_Interp *interp;
    int argc;                 /* Not used. */
    char **argv;
{
    Dnd *dndPtr;

    if (GetDnd(clientData, interp, argv[2], &dndPtr) != TCL_OK) {
      return TCL_ERROR;
    }
    if (!dndPtr->isSource) {
      Tcl_AppendResult(interp, "widget \"", Tk_PathName(dndPtr->tkwin), 
       "\" is not a registered drag&drop source.", (char *)NULL);
      return TCL_ERROR;
    }
    /* Send the target a Leave message so it can change back. */
    RelayLeaveEvent(dndPtr, dndPtr->windowPtr, 0, 0);
    CancelDrag(dndPtr);
    return TCL_OK;
}

/*
 * ------------------------------------------------------------------------
 *
 *  CgetOp --
 *
 *    Called to process an (argc,argv) list to configure (or
 *    reconfigure) a drag&drop widget.
 *
 * ------------------------------------------------------------------------
 */
/* ARGSUSED*/
static int
CgetOp(clientData, interp, argc, argv)
    ClientData clientData;    /* Thread-specific data. */
    Tcl_Interp *interp;
    int argc;                 /* Not used. */
    char **argv;
{
    Dnd *dndPtr;

    if (GetDnd(clientData, interp, argv[2], &dndPtr) != TCL_OK) {
      return TCL_ERROR;
    }
    return Tk_ConfigureValue(interp, dndPtr->tkwin, configSpecs, (char *)dndPtr,
      argv[3], 0);
}

/*
 * ------------------------------------------------------------------------
 *
 *  ConfigureOp --
 *
 *    Called to process an (argc,argv) list to configure (or
 *    reconfigure) a drag&drop widget.
 *
 * ------------------------------------------------------------------------
 */
static int
ConfigureOp(clientData, interp, argc, argv)
    ClientData clientData;    /* Thread-specific data. */
    Tcl_Interp *interp;       /* current interpreter */
    int argc;                 /* number of arguments */
    char **argv;        /* argument strings */
{
    Dnd *dndPtr;
    int flags;

    if (GetDnd(clientData, interp, argv[2], &dndPtr) != TCL_OK) {
      return TCL_ERROR;
    }
    flags = TK_CONFIG_ARGV_ONLY;
    if (argc == 3) {
      return Tk_ConfigureInfo(interp, dndPtr->tkwin, configSpecs, 
            (char *)dndPtr, (char *)NULL, flags);
    } else if (argc == 4) {
      return Tk_ConfigureInfo(interp, dndPtr->tkwin, configSpecs,
          (char *)dndPtr, argv[3], flags);
    } 
    if (Tk_ConfigureWidget(interp, dndPtr->tkwin, configSpecs, argc - 3,
               argv + 3, (char *)dndPtr, flags) != TCL_OK) {
      return TCL_ERROR;
    }
    if (ConfigureDnd(interp, dndPtr) != TCL_OK) {
      return TCL_ERROR;
    }
    return TCL_OK;
}

/*
 * ------------------------------------------------------------------------
 *
 *  DeleteOp --
 *
 *    Deletes the drag&drop manager from the widget.  If a "-source"
 *    or "-target" switch is present, only that component of the
 *    drag&drop manager is shutdown.  The manager is not deleted
 *    unless both the target and source components are shutdown.
 *
 *    Example: dnd delete .widget
 *
 * Results:
 *    A standard Tcl result.
 *
 * Side Effects:
 *    Deletes the drag&drop manager.  Also the source and target window 
 *    properties are removed from the widget.
 *
 * ------------------------------------------------------------------------ 
 */
static int
DeleteOp(clientData, interp, argc, argv)
    ClientData clientData;    /* Thread-specific data. */
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    Dnd *dndPtr;
    register int i;

    for(i = 3; i < argc; i++) {
      if (GetDnd(clientData, interp, argv[i], &dndPtr) != TCL_OK) {
          return TCL_ERROR;
      }
      dndPtr->flags |= DND_DELETED;
      Tcl_EventuallyFree(dndPtr, DestroyDnd);
    }
    return TCL_OK;
}

/*
 * ------------------------------------------------------------------------
 *
 *  SelectOp --
 *
 *    Initializes a drag&drop transaction.  Typically this operation
 *    is called from a ButtonPress event on a source widget.  The
 *    window information cache is initialized, and the token is 
 *    initialized and displayed.  
 *
 *    Example: dnd pickup .widget x y 
 *
 * Results:
 *    A standard Tcl result.
 *
 * Side Effects:
 *    The token is initialized and displayed.  This may require invoking
 *    a user-defined package command.  The window information cache is
 *    also initialized.
 *
 * ------------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
SelectOp(clientData, interp, argc, argv)
    ClientData clientData;    /* Thread-specific data. */
    Tcl_Interp *interp;
    int argc;                 /* Not used. */
    char **argv;
{
    Dnd *dndPtr;
    int x, y, timestamp;
    Token *tokenPtr;

    if (GetDnd(clientData, interp, argv[2], &dndPtr) != TCL_OK) {
      return TCL_ERROR;
    }
    if (!dndPtr->isSource) {
      Tcl_AppendResult(interp, "widget \"", Tk_PathName(dndPtr->tkwin), 
       "\" is not a registered drag&drop source.", (char *)NULL);
      return TCL_ERROR;
    }
    tokenPtr = dndPtr->tokenPtr;
    if (tokenPtr == NULL) {
      Tcl_AppendResult(interp, "no drag&drop token created for \"", 
             argv[2], "\"", (char *)NULL);
      return TCL_ERROR;
    }
    if ((Tcl_GetInt(interp, argv[3], &x) != TCL_OK) ||
      (Tcl_GetInt(interp, argv[4], &y) != TCL_OK)) {
      return TCL_ERROR;
    }
    if (Tcl_GetInt(interp, argv[5], &timestamp) != TCL_OK) {
      return TCL_ERROR;
    }
    if (dndPtr->flags & (DND_IN_PACKAGE | DND_ACTIVE | DND_VOIDED)) {
      return TCL_OK;
    }

    if (tokenPtr->timerToken != NULL) {
      HideToken(dndPtr);      /* If the user selected again before the
                         * token snap/melt has completed, first 
                         * disable the token timer callback. */
    }
    /* At this point, simply save the starting pointer location. */
    dndPtr->dragX = x, dndPtr->dragY = y;
    GetTokenPosition(dndPtr, x, y);
    tokenPtr->startX = tokenPtr->x;
    tokenPtr->startY = tokenPtr->y;
    dndPtr->timestamp = timestamp;
    dndPtr->flags |= DND_SELECTED;
    
    if (dndPtr->dragStart == 0) {
      if (DragInit(dndPtr, x, y) == TCL_ERROR) {
          return TCL_ERROR;
      }
    }
    return TCL_OK;
}

/*
 * ------------------------------------------------------------------------
 *
 *  DragOp --
 *
 *    Continues the drag&drop transaction.  Typically this operation
 *    is called from a button Motion event on a source widget.  Pointer
 *    event messages are possibly sent to the target, indicating Enter,
 *    Leave, and Motion events.
 *
 *    Example: dnd drag .widget x y 
 *
 * Results:
 *    A standard Tcl result.
 *
 * Side Effects:
 *    Pointer events are relayed to the target (if the mouse is over
 *    one).
 *
 * ------------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
DragOp(clientData, interp, argc, argv)
    ClientData clientData;    /* Thread-specific data. */
    Tcl_Interp *interp;
    int argc;                 /* Not used. */
    char **argv;
{
    Winfo *newPtr, *oldPtr;
    Dnd *dndPtr;
    int x, y;

    if (GetDnd(clientData, interp, argv[2], &dndPtr) != TCL_OK) {
      return TCL_ERROR;
    }
    if (!dndPtr->isSource) {
      Tcl_AppendResult(interp, "widget \"", Tk_PathName(dndPtr->tkwin), 
       "\" is not a registered drag&drop source.", (char *)NULL);
      return TCL_ERROR;
    }
    if (dndPtr->tokenPtr == NULL) {
      Tcl_AppendResult(interp, "no drag&drop token created for \"", 
             argv[2], "\"", (char *)NULL);
      return TCL_ERROR;
    }
    if ((Tcl_GetInt(interp, argv[3], &x) != TCL_OK) ||
      (Tcl_GetInt(interp, argv[4], &y) != TCL_OK)) {
      return TCL_ERROR;
    }

    if ((dndPtr->flags & DND_SELECTED) == 0) {
      return TCL_OK;          /* Re-entered this routine. */
    } 
    /* 
     * The following code gets tricky because the package command may 
     * call "update" or "tkwait".  A motion event may then trigger 
     * this routine, before the token has been initialized. Until the 
     * package command finishes, no target messages are sent and drops 
     * are silently ignored.  Note that we do still track mouse 
     * movements, so that when the package command completes, it will 
     * have the latest pointer position.  
     */
    dndPtr->x = x;      /* Save current location. */
    dndPtr->y = y;

    if (dndPtr->flags & DND_IN_PACKAGE) {
      return TCL_OK;          /* Re-entered this routine. */
    }
    if ((dndPtr->flags & DND_INITIATED) == 0) {
      int dx, dy;
      int result;

      dx = dndPtr->dragX - x;
      dy = dndPtr->dragY - y;
      if ((ABS(dx) < dndPtr->dragStart) && (ABS(dy) < dndPtr->dragStart)) {
          return TCL_OK;
      }
      result = DragInit(dndPtr, x, y);
      if (result == TCL_ERROR) {
          return TCL_ERROR;
      }
      if (result == TCL_RETURN) {
          return TCL_OK;
      }
    }
    if (dndPtr->flags & DND_VOIDED) {
      return TCL_OK;
    }
    oldPtr = dndPtr->windowPtr;
    newPtr = OverTarget(dndPtr);
    if (newPtr == oldPtr) {
      RelayMotionEvent(dndPtr, oldPtr, x, y); 
      dndPtr->windowPtr = oldPtr;
    } else {
      RelayLeaveEvent(dndPtr, oldPtr, x, y);
      RelayEnterEvent(dndPtr, newPtr, x, y);
      dndPtr->windowPtr = newPtr;
    } 
    dndPtr->tokenPtr->status = (newPtr != NULL) ? DROP_OK : DROP_CONTINUE;
    if (dndPtr->tokenPtr->lastStatus != dndPtr->tokenPtr->status) {
      EventuallyRedrawToken(dndPtr);
    }
    MoveToken(dndPtr);        /* Move token to current drag point. */ 
    RaiseToken(dndPtr);
    return TCL_OK;
}


/*
 * ------------------------------------------------------------------------
 *
 *  DropOp --
 *
 *    Finishes the drag&drop transaction by dropping the data on the
 *    selected target.  Typically this operation is called from a 
 *    ButtonRelease event on a source widget.  Note that a Leave message
 *    is always sent to the target so that is can un-highlight itself.
 *    The token is hidden and a drop message is sent to the target.
 *
 *    Example: dnd drop .widget x y 
 *
 * Results:
 *    A standard Tcl result.
 *
 * Side Effects:
 *    The token is hidden and a drop message is sent to the target.
 *
 * ------------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
DropOp(clientData, interp, argc, argv)
    ClientData clientData;    /* Thread-specific data. */
    Tcl_Interp *interp;
    int argc;                 /* Not used. */
    char **argv;
{
    Winfo *windowPtr;
    Dnd *dndPtr;
    int x, y;

    if (GetDnd(clientData, interp, argv[2], &dndPtr) != TCL_OK) {
      return TCL_ERROR;
    }
    if (!dndPtr->isSource) {
      Tcl_AppendResult(interp, "widget \"", Tk_PathName(dndPtr->tkwin), 
       "\" is not a registered drag&drop source.", (char *)NULL);
      return TCL_ERROR;
    }
    if ((Tcl_GetInt(interp, argv[3], &x) != TCL_OK) ||
      (Tcl_GetInt(interp, argv[4], &y) != TCL_OK)) {
      return TCL_ERROR;
    }
    dndPtr->x = x;      /* Save drag&drop location */
    dndPtr->y = y;
    if ((dndPtr->flags & DND_INITIATED) == 0) {
      return TCL_OK;          /* Never initiated any drag operation. */
    }
    if (dndPtr->flags & DND_VOIDED) {
      HideToken(dndPtr);
      return TCL_OK;
    }
    windowPtr = OverTarget(dndPtr);
    if (windowPtr != NULL) {
      if (windowPtr->matches != NULL) {
          SetProperty(dndPtr->tkwin, dndPtr->dataPtr->formatsAtom, 
                  windowPtr->matches);
      }
      MoveToken(dndPtr);      /* Move token to current drag point. */ 
      RaiseToken(dndPtr);
      RelayDropEvent(dndPtr, windowPtr, x, y);
#ifdef notdef
      tokenPtr->nSteps = 10;
      FadeToken(dndPtr);
#endif
    } else {
      CancelDrag(dndPtr);
    }
    return TCL_OK;
}

/*
 * ------------------------------------------------------------------------
 *
 *  GetdataOp --
 *
 *    Registers one or more data formats with a drag&drop source.  
 *    Each format has a Tcl command associated with it.  This command
 *    is automatically invoked whenever data is pulled from the source
 *    to a target requesting the data in that particular format.  The 
 *    purpose of the Tcl command is to get the data from in the 
 *    application. When the Tcl command is invoked, special percent 
 *    substitutions are made:
 *
 *          %#          Drag&drop transaction timestamp.
 *          %W          Source widget.
 *
 *    If a converter (command) already exists for a format, it 
 *    overwrites the existing command.
 *
 *    Example: dnd getdata .widget "color" { %W cget -bg }
 *
 * Results:
 *    A standard Tcl result.  
 *
 * ------------------------------------------------------------------------
 */
static int
GetdataOp(clientData, interp, argc, argv)
    ClientData clientData;    /* Thread-specific data. */
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    Dnd *dndPtr;
    Blt_HashEntry *hPtr;
    Blt_HashSearch cursor;
    int isNew, nElem;
    char **cmd;
    register int i;

    if (GetDnd(clientData, interp, argv[2], &dndPtr) != TCL_OK) {
      return TCL_ERROR;
    }
    if (argc == 3) {
      /* Return list of source data formats */
      for (hPtr = Blt_FirstHashEntry(&(dndPtr->getDataTable), &cursor);
          hPtr != NULL; hPtr = Blt_NextHashEntry(&cursor)) {
          Tcl_AppendElement(interp,
            Blt_GetHashKey(&(dndPtr->getDataTable), hPtr));
      }
      return TCL_OK;
    }

    if (argc == 4) {
      hPtr = Blt_FindHashEntry(&(dndPtr->getDataTable), argv[3]);
      if (hPtr == NULL) {
          Tcl_AppendResult(interp, "can't find handler for format \"", 
           argv[3], "\" for source \"", Tk_PathName(dndPtr->tkwin), "\"",
           (char *)NULL);
          return TCL_ERROR;
      }
      cmd = (char **)Blt_GetHashValue(hPtr);
      if (cmd == NULL) {
          Tcl_SetResult(interp, "", TCL_STATIC);
      } else {
          Tcl_SetResult(interp, PrintList(cmd), TCL_DYNAMIC);
      }
      return TCL_OK;
    }

    for (i = 3; i < argc; i += 2) {
      hPtr = Blt_CreateHashEntry(&(dndPtr->getDataTable), argv[i], &isNew);
      if (!isNew) {
          cmd = (char **)Blt_GetHashValue(hPtr);
          Blt_Free(cmd);
      }
      if (Tcl_SplitList(interp, argv[i + 1], &nElem, &cmd) != TCL_OK) {
          Blt_DeleteHashEntry(&(dndPtr->getDataTable), hPtr);
          return TCL_ERROR;
      }
      Blt_SetHashValue(hPtr, cmd);
    }
    return TCL_OK;
}

/*
 * ------------------------------------------------------------------------
 *
 *  NamesOp --
 *
 *    Returns the names of all the drag&drop managers.  If either
 *    a "-source" or "-target" switch is present, only the names of
 *    managers acting as sources or targets respectively are returned.
 *    A pattern argument may also be given.  Only those managers
 *    matching the pattern are returned.
 *
 *    Examples: dnd names
 *            dnd names -source
 *            dnd names -target
 *            dnd names .*label
 * Results:
 *    A standard Tcl result.  A Tcl list of drag&drop manager
 *    names is returned.
 *
 * ------------------------------------------------------------------------
 */
static int
NamesOp(clientData, interp, argc, argv)
    ClientData clientData;    /* Thread-specific data. */
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    DndInterpData *dataPtr = clientData;
    Blt_HashEntry *hPtr;
    Blt_HashSearch cursor;
    Dnd *dndPtr;
    int findSources, findTargets;

    findSources = findTargets = TRUE;
    if (argc > 2) {
      if ((argv[2][0] == '-') && (strcmp(argv[2], "-source") == 0)) {
          findTargets = FALSE;
          argc--, argv++;
      } else if ((argv[2][0] == '-') && (strcmp(argv[2], "-target") == 0)) {
          findSources = FALSE;
          argc--, argv++;
      }
    }
    for (hPtr = Blt_FirstHashEntry(&(dataPtr->dndTable), &cursor); 
       hPtr != NULL; hPtr = Blt_NextHashEntry(&cursor)) {
      dndPtr = (Dnd *)Blt_GetHashValue(hPtr);
      if ((argc > 3) && 
          (!Tcl_StringMatch(Tk_PathName(dndPtr->tkwin), argv[3]))) {
          continue;
      }
      if (((findSources) && (dndPtr->isSource)) ||
          ((findTargets) && (dndPtr->isTarget))) {
          Tcl_AppendElement(interp, Tk_PathName(dndPtr->tkwin));
      }
    }
    return TCL_OK;
}

/*
 * ------------------------------------------------------------------------
 *
 *  PullOp --
 *
 *    Pulls the current data from the source in the given format.
 *    application.
 *
 *    dnd pull .widget format data
 *
 * Results:
 *    A standard Tcl result.
 *
 * Side Effects:
 *    Invokes the target's data converter to store the data. 
 *
 * ------------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
PullOp(clientData, interp, argc, argv)
    ClientData clientData;    /* Thread-specific data. */
    Tcl_Interp *interp;
    int argc;                 /* Not used. */
    char **argv;
{
    Dnd *dndPtr;        /* drag&drop source record */
    int result;
    char **formatCmd;
    Blt_HashEntry *hPtr;

    if (GetDnd(clientData, interp, argv[2], &dndPtr) != TCL_OK) {
      return TCL_ERROR;
    }
    if (!dndPtr->isTarget) {
      Tcl_AppendResult(interp, "widget \"", Tk_PathName(dndPtr->tkwin), 
       "\" is not a registered drag&drop target.", (char *)NULL);
      return TCL_ERROR;
    }
    hPtr = Blt_FindHashEntry(&(dndPtr->setDataTable), argv[3]);
    if (hPtr == NULL) {
      Tcl_AppendResult(interp, "can't find format \"", argv[3], 
       "\" in target \"", Tk_PathName(dndPtr->tkwin), "\"", (char *)NULL);
      return TCL_ERROR;
    }
    formatCmd = (char **)Blt_GetHashValue(hPtr);
    if (dndPtr->pendingPtr == NULL) {
      Tcl_AppendResult(interp, "no drop in progress", (char *)NULL);
      return TCL_ERROR;
    }

    CompleteDataTransaction(dndPtr, argv[3], dndPtr->pendingPtr);
    result = TCL_OK;
    if (Tcl_DStringLength(&(dndPtr->pendingPtr->dString)) > 0) {
      Tcl_DString dString, savedResult;
      char **p;

      Tcl_DStringInit(&dString);
      for (p = formatCmd; *p != NULL; p++) {
          Tcl_DStringAppendElement(&dString, *p);
      }
      Tcl_DStringAppendElement(&dString, Tk_PathName(dndPtr->tkwin));
      Tcl_DStringAppendElement(&dString, "x");
      Tcl_DStringAppendElement(&dString, Blt_Itoa(dndPtr->dropX));
      Tcl_DStringAppendElement(&dString, "y");
      Tcl_DStringAppendElement(&dString, Blt_Itoa(dndPtr->dropY));
      Tcl_DStringAppendElement(&dString, "timestamp");
      Tcl_DStringAppendElement(&dString, 
            Blt_Utoa(dndPtr->pendingPtr->timestamp));
      Tcl_DStringAppendElement(&dString, "format");
      Tcl_DStringAppendElement(&dString, argv[3]);
      Tcl_DStringAppendElement(&dString, "value");
      Tcl_DStringAppendElement(&dString, 
            Tcl_DStringValue(&(dndPtr->pendingPtr->dString)));
      Tcl_DStringInit(&savedResult);
      Tcl_DStringGetResult(interp, &savedResult);
      if (Tcl_GlobalEval(interp, Tcl_DStringValue(&dString)) != TCL_OK) {
          Tcl_BackgroundError(interp);
      }
      Tcl_DStringResult(interp, &savedResult);
      Tcl_DStringFree(&dString);
    }
    return result;
}

/*
 * ------------------------------------------------------------------------
 *
 *  SetdataOp --
 *
 *    Registers one or more data formats with a drag&drop target.  
 *    Each format has a Tcl command associated with it.  This command
 *    is automatically invoked whenever data arrives from a source
 *    to be converted to that particular format.  The purpose of the 
 *    command is to set the data somewhere in the application (either 
 *    using a Tcl command or variable).   When the Tcl command is invoked, 
 *    special percent substitutions are made:
 *
 *          %#          Drag&drop transaction timestamp.
 *          %W          Target widget.
 *          %v          Data value transfered from the source to
 *                      be converted into the correct format.
 *
 *    If a converter (command) already exists for a format, it 
 *    overwrites the existing command.
 *
 *    Example: dnd setdata .widget color { . configure -bg %v }
 *
 * Results:
 *    A standard Tcl result.
 *
 * ------------------------------------------------------------------------
 */
static int
SetdataOp(clientData, interp, argc, argv)
    ClientData clientData;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    Dnd *dndPtr;
    Blt_HashEntry *hPtr;
    Blt_HashSearch cursor;
    int isNew, nElem;
    char **cmd;
    int i;

    if (GetDnd(clientData, interp, argv[2], &dndPtr) != TCL_OK) {
      return TCL_ERROR;
    }
    if (argc == 3) {
      /* Show target handler data formats */
      for (hPtr = Blt_FirstHashEntry(&(dndPtr->setDataTable), &cursor);
          hPtr != NULL; hPtr = Blt_NextHashEntry(&cursor)) {
          Tcl_AppendElement(interp,
            Blt_GetHashKey(&(dndPtr->setDataTable), hPtr));
      }
      return TCL_OK;
    }
    if (argc == 4) {
      hPtr = Blt_FindHashEntry(&(dndPtr->setDataTable), argv[3]);
      if (hPtr == NULL) {
          Tcl_AppendResult(interp, "can't find handler for format \"", 
           argv[3], "\" for target \"", Tk_PathName(dndPtr->tkwin), "\"",
           (char *)NULL);
          return TCL_ERROR;
      }
      cmd = (char **)Blt_GetHashValue(hPtr);
      if (cmd == NULL) {
          Tcl_SetResult(interp, "", TCL_STATIC);
      } else {
          Tcl_SetResult(interp, PrintList(cmd), TCL_DYNAMIC);
      }
      return TCL_OK;
    }
    for (i = 3; i < argc; i += 2) {
      hPtr = Blt_CreateHashEntry(&(dndPtr->setDataTable), argv[i], &isNew);
      if (!isNew) {
          cmd = (char **)Blt_GetHashValue(hPtr);
          Blt_Free(cmd);
      }
      if (Tcl_SplitList(interp, argv[i + 1], &nElem, &cmd) != TCL_OK) {
          Blt_DeleteHashEntry(&(dndPtr->setDataTable), hPtr);
          return TCL_ERROR;
      }
      Blt_SetHashValue(hPtr, cmd);
    }
    AddTargetProperty(dndPtr);
    return TCL_OK;
}

/*
 * ------------------------------------------------------------------------
 *
 *  RegisterOp --
 *
 *  dnd register .window 
 * ------------------------------------------------------------------------
 */
static int
RegisterOp(clientData, interp, argc, argv)
    ClientData clientData;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    DndInterpData *dataPtr = clientData;
    Tk_Window tkwin;
    Blt_HashEntry *hPtr;
    Dnd *dndPtr;
    int isNew;

    tkwin = Tk_NameToWindow(interp, argv[2], dataPtr->mainWindow);
    if (tkwin == NULL) {
      return TCL_ERROR;
    }
    hPtr = Blt_CreateHashEntry(&(dataPtr->dndTable), (char *)tkwin, &isNew);
    if (!isNew) {
      Tcl_AppendResult(interp, "\"", Tk_PathName(tkwin), 
           "\" is already registered as a drag&drop manager", (char *)NULL);
      return TCL_ERROR;
    }
    dndPtr = CreateDnd(interp, tkwin);
    dndPtr->hashPtr = hPtr;
    dndPtr->dataPtr = dataPtr;
    Blt_SetHashValue(hPtr, dndPtr);
    if (Tk_ConfigureWidget(interp, dndPtr->tkwin, configSpecs, argc - 3,
         argv + 3, (char *)dndPtr, 0) != TCL_OK) {
      return TCL_ERROR;
    }
    if (ConfigureDnd(interp, dndPtr) != TCL_OK) {
      return TCL_ERROR;
    }
    return TCL_OK;
}

/*
 * ------------------------------------------------------------------------
 *
 *  TokenWindowOp --
 *
 * ------------------------------------------------------------------------
 */
static int
TokenWindowOp(clientData, interp, argc, argv)
    ClientData clientData;    /* Thread-specific data. */
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    Dnd *dndPtr;
    int flags;

    if (GetDnd(clientData, interp, argv[3], &dndPtr) != TCL_OK) {
      return TCL_ERROR;
    }
    flags = 0;
    if (dndPtr->tokenPtr == NULL) {
      if (CreateToken(interp, dndPtr) != TCL_OK) {
          return TCL_ERROR;
      }
    } else {
      flags = TK_CONFIG_ARGV_ONLY;
    }
    if (ConfigureToken(interp, dndPtr, argc - 4, argv + 4, flags) != TCL_OK) {
      return TCL_ERROR;
    }
    Tcl_SetResult(interp, Tk_PathName(dndPtr->tokenPtr->tkwin), TCL_VOLATILE);
    return TCL_OK;
}

/*
 * ------------------------------------------------------------------------
 *
 *  TokenCgetOp --
 *
 *    Called to process an (argc,argv) list to configure (or
 *    reconfigure) a drag&drop widget.
 *
 * ------------------------------------------------------------------------
 */
/* ARGSUSED*/
static int
TokenCgetOp(clientData, interp, argc, argv)
    ClientData clientData;    /* Thread-specific data. */
    Tcl_Interp *interp;
    int argc;                 /* Not used. */
    char **argv;
{
    Dnd *dndPtr;

    if (GetDnd(clientData, interp, argv[3], &dndPtr) != TCL_OK) {
      return TCL_ERROR;
    }
    if (dndPtr->tokenPtr == NULL) {
      Tcl_AppendResult(interp, "no token created for \"", argv[3], "\"",
             (char *)NULL);
      return TCL_ERROR;
    }
    return Tk_ConfigureValue(interp, dndPtr->tokenPtr->tkwin, tokenConfigSpecs, 
           (char *)dndPtr->tokenPtr, argv[4], TK_CONFIG_ARGV_ONLY);
}

/*
 * ------------------------------------------------------------------------
 *
 *  TokenConfigureOp --
 *
 * ------------------------------------------------------------------------
 */
static int
TokenConfigureOp(clientData, interp, argc, argv)
    ClientData clientData;    /* Thread-specific data. */
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    Token *tokenPtr;
    Dnd *dndPtr;
    int flags;

    if (GetDnd(clientData, interp, argv[3], &dndPtr) != TCL_OK) {
      return TCL_ERROR;
    }
    flags = TK_CONFIG_ARGV_ONLY;
    if (dndPtr->tokenPtr == NULL) {
      Tcl_AppendResult(interp, "no token created for \"", argv[3], "\"",
             (char *)NULL);
      return TCL_ERROR;
    }
    tokenPtr = dndPtr->tokenPtr;
    if (argc == 3) {
      return Tk_ConfigureInfo(interp, tokenPtr->tkwin, tokenConfigSpecs,
          (char *)tokenPtr, (char *)NULL, flags);
    } else if (argc == 4) {
      return Tk_ConfigureInfo(interp, tokenPtr->tkwin, tokenConfigSpecs,
          (char *)tokenPtr, argv[3], flags);
    } 
    return ConfigureToken(interp, dndPtr, argc - 4, argv + 4, flags);
}

static Blt_OpSpec tokenOps[] =
{
    {"cget", 2, (Blt_Op)TokenCgetOp, 5, 5, "widget option",},
    {"configure", 2, (Blt_Op)TokenConfigureOp, 4, 0, 
      "widget ?option value?...",},
    {"window", 5, (Blt_Op)TokenWindowOp, 4, 0, 
      "widget ?option value?...",},
};

static int nTokenOps = sizeof(tokenOps) / sizeof(Blt_OpSpec);

/*
 * ------------------------------------------------------------------------
 *
 *  TokenOp --
 *
 * ------------------------------------------------------------------------
 */
static int
TokenOp(clientData, interp, argc, argv)
    ClientData clientData;    /* Thread-specific data. */
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    Blt_Op proc;
    int result;

    proc = Blt_GetOp(interp, nTokenOps, tokenOps, BLT_OP_ARG2, argc, argv, 0);
    if (proc == NULL) {
      return TCL_ERROR;
    }
    result = (*proc) (clientData, interp, argc, argv);
    return result;
}

static Blt_OpSpec dndOps[] =
{
    {"cancel", 2, (Blt_Op)CancelOp, 3, 3, "widget",},
    {"cget", 2, (Blt_Op)CgetOp, 4, 4, "widget option",},
    {"configure", 4, (Blt_Op)ConfigureOp, 3, 0, 
      "widget ?option value?...",},
#ifdef notdef
    {"convert", 4, (Blt_Op)ConvertOp, 5, 5, "widget data format",},
#endif
    {"delete", 2, (Blt_Op)DeleteOp, 3, 0,"?-source|-target? widget...",},
    {"drag", 3, (Blt_Op)DragOp, 3, 0, "widget x y ?token?",},
    {"drop", 3, (Blt_Op)DropOp, 3, 0, "widget x y ?token?",},
    {"getdata", 1, (Blt_Op)GetdataOp, 3, 0, "widget ?format command?",},
    {"names", 1, (Blt_Op)NamesOp, 2, 4, "?-source|-target? ?pattern?",},
    {"pull", 1, (Blt_Op)PullOp, 4, 4, "widget format",},
    {"register", 1, (Blt_Op)RegisterOp, 3, 0, "widget ?option value?...",},
    {"select", 3, (Blt_Op)SelectOp, 6, 6, "widget x y timestamp",},
    {"setdata", 3, (Blt_Op)SetdataOp, 3, 0, "widget ?format command?",},
    {"token", 5, (Blt_Op)TokenOp, 3, 0, "args...",},
};

static int nDndOps = sizeof(dndOps) / sizeof(Blt_OpSpec);

/*
 * ------------------------------------------------------------------------
 *
 *  DndCmd --
 *
 *    Invoked by TCL whenever the user issues a drag&drop command.
 *
 * ------------------------------------------------------------------------
 */
static int
DndCmd(clientData, interp, argc, argv)
    ClientData clientData;    /* Thread-specific data. */
    Tcl_Interp *interp;       /* current interpreter */
    int argc;                 /* number of arguments */
    char **argv;        /* argument strings */
{
    Blt_Op proc;
    int result;

    proc = Blt_GetOp(interp, nDndOps, dndOps, BLT_OP_ARG1, argc, argv, 0);
    if (proc == NULL) {
      return TCL_ERROR;
    }
    result = (*proc) (clientData, interp, argc, argv);
    return result;
}

/*
 * -----------------------------------------------------------------------
 *
 * DndInterpDeleteProc --
 *
 *    This is called when the interpreter hosting the "dnd" command is 
 *    destroyed.
 *
 * Results:
 *    None.
 *
 * Side effects:
 *    Destroys the hash table containing the drag&drop managers.
 *
 * ------------------------------------------------------------------------
 */
/* ARGSUSED */
static void
DndInterpDeleteProc(clientData, interp)
    ClientData clientData;    /* Thread-specific data. */
    Tcl_Interp *interp;
{
    DndInterpData *dataPtr = clientData;
    Dnd *dndPtr;
    Blt_HashEntry *hPtr;
    Blt_HashSearch cursor;

    for (hPtr = Blt_FirstHashEntry(&(dataPtr->dndTable), &cursor);
       hPtr != NULL; hPtr = Blt_NextHashEntry(&cursor)) {
      dndPtr = (Dnd *)Blt_GetHashValue(hPtr);
      dndPtr->hashPtr = NULL;
      DestroyDnd((DestroyData)dndPtr);
    }
    Blt_DeleteHashTable(&(dataPtr->dndTable));
    Tcl_DeleteAssocData(interp, DND_THREAD_KEY);
    Blt_Free(dataPtr);
}

static DndInterpData *
GetDndInterpData(interp)
     Tcl_Interp *interp;
{
    DndInterpData *dataPtr;
    Tcl_InterpDeleteProc *proc;

    dataPtr = (DndInterpData *)Tcl_GetAssocData(interp, DND_THREAD_KEY, &proc);
    if (dataPtr == NULL) {
      Display *display;
      Tk_Window tkwin;

      dataPtr = Blt_Malloc(sizeof(DndInterpData));
      assert(dataPtr);
      tkwin = Tk_MainWindow(interp);
      display = Tk_Display(tkwin);
      dataPtr->mainWindow = tkwin;
      dataPtr->display = display;
      Tcl_SetAssocData(interp, DND_THREAD_KEY, DndInterpDeleteProc,
            dataPtr);
      Blt_InitHashTable(&(dataPtr->dndTable), BLT_ONE_WORD_KEYS);
      dataPtr->mesgAtom = XInternAtom(display, "BLT Dnd Message", False);
      dataPtr->targetAtom = XInternAtom(display, "BLT Dnd Target", False);
      dataPtr->formatsAtom = XInternAtom(display, "BLT Dnd Formats",False);
      dataPtr->commAtom = XInternAtom(display, "BLT Dnd CommData", False);

#ifdef HAVE_XDND
      dataPtr->XdndActionListAtom = XInternAtom(display, "XdndActionList", 
            False);
      dataPtr->XdndAwareAtom = XInternAtom(display, "XdndAware", False);
      dataPtr->XdndEnterAtom = XInternAtom(display, "XdndEnter", False);
      dataPtr->XdndFinishedAtom = XInternAtom(display, "XdndFinished", False);
      dataPtr->XdndLeaveAtom = XInternAtom(display, "XdndLeave", False);
      dataPtr->XdndPositionAtom = XInternAtom(display, "XdndPosition", False);
      dataPtr->XdndSelectionAtom = XInternAtom(display, "XdndSelection", 
                                     False);
      dataPtr->XdndStatusAtom = XInternAtom(display, "XdndStatus", False);
      dataPtr->XdndTypeListAtom = XInternAtom(display, "XdndTypeList", False);
#endif /* HAVE_XDND */
    }
    return dataPtr;
}

/*
 * ------------------------------------------------------------------------
 *
 *  Blt_DndInit --
 *
 *    Adds the drag&drop command to the given interpreter.  Should
 *    be invoked to properly install the command whenever a new
 *    interpreter is created.
 *
 * ------------------------------------------------------------------------
 */
int
Blt_DndInit(interp)
    Tcl_Interp *interp;       /* interpreter to be updated */
{
    static Blt_CmdSpec cmdSpec =
    {
      "dnd", DndCmd
    };
    DndInterpData *dataPtr;

    dataPtr = GetDndInterpData(interp);
    cmdSpec.clientData = dataPtr;
    if (Blt_InitCmd(interp, "blt", &cmdSpec) == NULL) {
      return TCL_ERROR;
    }
    return TCL_OK;
}

#ifdef notdef
/*
 * Registers bitmap outline of dragged data, used to indicate
 * what is being dragged by source.  Bitmap is XOR-ed as cursor/token
 * is moved around the screen.
 */
static void
Blt_DndSetOutlineBitmap(tkwin, bitmap, x, y)
    Tk_Window tkwin;
    Pixmap bitmap;
    int x, y;
{
    
}
#endif

#ifdef HAVE_XDND

static void
XDndFreeFormats(handlerPtr) 
    XDndHandler *handlerPtr;
{
    if (handlerPtr->formatArr != NULL) {
      char **p;

      for (p = handlerPtr->formatArr; *p != NULL; p++) {
          XFree(*p);
      }
      Blt_Free(handlerPtr->formatArr);
      handlerPtr->formatArr = NULL;
    }
}

static char **
XDndGetFormats(handlerPtr, eventPtr)
    XDndHandler *handlerPtr;
    XEvent *eventPtr;
{
    int flags;
    Window window;
    unsigned char *data;
    Atom typeAtom;
    Atom format;
    int nItems, bytesAfter;
    Atom *atomArr;
    char *nameArr[XDND_MAX_TYPES + 1];
    Display *display;

    XDndFreeFormats(handlerPtr);
    display = eventPtr->xclient.display;
    window = eventPtr->xclient.data.l[0];
    flags = eventPtr->xclient.data.l[1];
    data = NULL;
    if (flags & 0x01) {
      result = XGetWindowProperty(
          display,            /* Display of window. */
          window,       /* Window holding the property. */
          handlerPtr->dataPtr->XdndTypeListAtom, /* Name of property. */
          0,                  /* Offset of data (for multiple reads). */
          XDND_MAX_TYPES,     /* Maximum number of items to read. */
          False,        /* If true, delete the property. */
          XA_ATOM,            /* Desired type of property. */
          &typeAtom,          /* (out) Actual type of the property. */
          &format,            /* (out) Actual format of the property. */
          &nItems,            /* (out) # of items in specified format. */
          &bytesAfter,  /* (out) # of bytes remaining to be read. */
          (unsigned char **)&data);
      if ((result != Success) || (format != 32) || (typeAtom != XA_ATOM)) {
          if (data != NULL) {
            XFree((char *)data);
            return (char **)NULL;
          }
      }
      atomArr = (Atom *)data;
      nAtoms = nItems;
    } else {
      atomArr = &(eventPtr->xclient.data.l[2]);
      nAtoms = 3;
    }
    formatArr = Blt_Calloc(nAtoms + 1, sizeof(char *));
    for (i = 0; (i < nAtoms) && (atomArr[i] != None); i++) {
      formatArr[i] = XGetAtomName(display, atomArr[i]);
    }
    if (data != NULL) {
      XFree((char *)data);
    }
    handlerPtr->formatArr = formatArr;
}

static char *
GetMatchingFormats(dndPtr, formatArr)
    Dnd *dndPtr;
    char **formatArr;
{
    int nMatches;

    nMatches = 0;
    Tcl_DStringInit(&dString);
    for (p = formatArr; *p != NULL; p++) {
      for(hPtr = Blt_FirstHashEntry(&(dndPtr->setDataTable), &cursor);
          hPtr != NULL; hPtr = Blt_NextHashEntry(&cursor)) {
          fmt = Blt_GetHashKey(&(dndPtr->setDataTable), hPtr);
          if ((*fmt == **p) && (strcmp(fmt, *p) == 0)) {
            Tcl_DStringAppendElement(&dString, *p);
            nMatches++;
            break;
          }
      }
    }
    if (nMatches > 0) {
      char *string;

      string = Blt_Strdup(Tcl_DStringValue(&dString));
      Tcl_DStringFree(&dString);
      return string;
    }
    return NULL;
}

static void
XDndPointerEvent(handlerPtr, eventPtr)
    XDndHandler *handlerPtr;
    XEvent *eventPtr;
{
    Tk_Window tkwin;
    int flags;
    Atom action;
    Window window;
    
    flags = 0;
    action = None;
    window = eventPtr->xclient.data.l[MESG_XDND_WINDOW];
    /* 
     * If the XDND source has no formats specified, don't process any further.
     * Simply send a "no accept" action with the message.
     */
    if (handlerPtr->formatArr != NULL) { 
      Dnd *newPtr, *oldPtr;
      int point;
      int button, keyState;
      int x, y;
      char *formats;

      point = (int)eventPtr->xclient.data.l[MESG_XDND_POINT];
      UNPACK(point, x, y);

      /*  
       * See if the mouse pointer currently over a drop target. We first
       * determine what Tk window is under the mouse, and then check if 
       * that window is registered as a drop target.  
       */
      newPtr = NULL;
      tkwin = Tk_CoordsToWindow(x, y, handlerPtr->tkwin);
      if (tkwin != NULL) {
          Blt_HashEntry *hPtr;
          
          hPtr = Blt_FindHashEntry(&(handlerPtr->dataPtr->dndTable), 
            (char *)tkwin);
          if (hPtr != NULL) {
            newPtr = (Dnd *)Blt_GetHashValue(hPtr);
            if (!newPtr->isTarget) {
                newPtr = NULL; /* Not a DND target. */
            }
            formats = GetMatchingFormats(newPtr, handlerPtr->formatArr);
            if (formats == NULL) {
                newPtr = NULL; /* Source has no matching formats. */
            } 
          }
      }
      button = keyState = 0;
      oldPtr = handlerPtr->dndPtr;
      resp = DROP_CANCEL;
      if (newPtr == oldPtr) {
          if ((oldPtr != NULL) && (oldPtr->motionCmd != NULL)) {
            resp = InvokeCallback(oldPtr, oldPtr->motionCmd, x, y, formats,
                   button, keyState, dndPtr->timestamp);
          }
      } else {
          if ((oldPtr != NULL) && (oldPtr->leaveCmd != NULL)) {
            InvokeCallback(oldPtr, oldPtr->leaveCmd, x, y, formats, button,
                   keyState, dndPtr->timestamp);
          }
          if ((newPtr != NULL) && (newPtr->enterCmd != NULL)) {
            resp = InvokeCallback(newPtr, newPtr->enterCmd, x, y, formats, 
                  button, keyState, dndPtr->timestamp);
          }
          handlerPtr->dndPtr = newPtr;
          /* 
           * Save the current mouse position, since we get them from the
           * drop message. 
           */
          newPtr->x = x;      
          newPtr->y = y;
      } 
      if (formats != NULL) {
          Blt_Free(formats);
      }
      flags = XDND_FLAGS_WANT_POSITION_MSGS;
      if (resp) {
          flags |= XDND_FLAGS_ACCEPT_DROP;
          action = handlerPtr->dataPtr->XdndActionCopyAtom;
      }
    }
    /* Target-to-Source: Drag result message. */
    SendClientMsg(handlerPtr->display, window, 
      handlerPtr->dataPtr->XdndStatusAtom, handlerPtr->window, 
      flags, 0, 0, action);
}

static void
XDndDropEvent(handlerPtr, eventPtr)
    XDndHandler *handlerPtr;
    XEvent *eventPtr;
{
    Tk_Window tkwin;
    int flags;
    Atom action;
    Window window;
    int timestamp;

    flags = 0;
    action = None;
    window = eventPtr->xclient.data.l[MESG_XDND_WINDOW];
    timestamp = eventPtr->xclient.data.l[MESG_XDND_TIMESTAMP];

    /* 
     * If no formats were specified for the XDND source or if the last 
     * motion event did not place the mouse over a valid drop target, 
     * don't process any further. Simply send a "no accept" action with 
     * the message.
     */
    if ((handlerPtr->formatArr != NULL) && (handlerPtr->dndPtr != NULL)) { 
      int button, keyState;
      Dnd *dndPtr = handlerPtr->dndPtr;
      DropPending pending;
      int resp;

      button = keyState = 0;  /* Protocol doesn't supply this information. */

      /* Set up temporary bookkeeping for the drop transaction */
      memset (&pending, 0, sizeof(pending));
      pending.window = window;
      pending.display = eventPtr->xclient.display;
      pending.timestamp = timestamp;
      pending.protocol = PROTO_XDND;
      pending.packetSize = GetMaxPropertySize(pending.display);
      Tcl_DStringInit(&(pending.dString));
      
      formats = GetMatchingFormats(handlerPtr->dndPtr, handlerPtr->formatArr);
      if (formats == NULL) {
      }
      dndPtr->pendingPtr = &pending;
      resp = AcceptDrop(dndPtr, dndPtr->x, dndPtr->y, formats, button,
           keyState, action, timestamp);
      dndPtr->pendingPtr = NULL;
      if (resp) {
          flags |= XDND_FLAGS_ACCEPT_DROP;
          action = handlerPtr->dataPtr->XdndActionCopyAtom;
      }
    }
    /* Target-to-Source: Drag result message. */
    SendClientMsg(handlerPtr->display, window, 
      handlerPtr->dataPtr->XdndStatusAtom, handlerPtr->window, 
      flags, 0, 0, action);
}

/*
 * ------------------------------------------------------------------------
 *
 *  XDndProtoEventProc --
 *
 *    Invoked by Tk_HandleEvent whenever a DestroyNotify event is received
 *    on a registered drag&drop source widget.
 *
 * ------------------------------------------------------------------------
 */
static int
XDndProtoEventProc(clientData, eventPtr)
    ClientData clientData;    /* Drag&drop record. */
    XEvent *eventPtr;         /* Event description. */
{
    DndInterpData *dataPtr = clientData;
    Tk_Window tkwin;
    Blt_HashEntry *hPtr;
    XDndHandler *handlerPtr;
    int point;
    int x, y;
    Atom mesg;

    if (eventPtr->type != ClientMessage) {
      return 0;         /* Not a ClientMessage event. */
    }
    /* Was the recipient a registered toplevel window? */
    hPtr = Blt_FindHashEntry(&(dataPtr->handlerTable), 
           (char *)eventPtr->xclient.window);
    if (hPtr == NULL) {
      return 0;         /* No handler registered with window. */
    }
    handlerPtr = (XDndHandler *)Blt_GetHashValue(hPtr);
    mesg = eventPtr->xclient.message_type;
    if (mesg == dataPtr->XdndEnterAtom) {
      XDndGetFormats(handlerPtr, eventPtr);
      handlerPtr->dndPtr = NULL;
    } else if (mesg == dataPtr->XdndPositionAtom) {
      XDndPointerEvent(handlerPtr, eventPtr);
    } else if (mesg == dataPtr->XdndLeaveAtom) {
      XDndFreeFormats(handlerPtr);  /* Free up any collected formats. */
      if (handlerPtr->dndPtr != NULL) {
          InvokeCallback(handlerPtr->dndPtr, handlerPtr->dndPtr->leaveCmd, 
            -1, -1, NULL, 0, 0);
          /* Send leave event to drop target. */
      }
    } else if (mesg == dataPtr->XdndDropAtom) {
      XDndDropEvent(handlerPtr, eventPtr);
    } else {
      fprintf(stderr, "Unknown client message type = 0x%x\n", mesg);
      return 0;         /* Unknown message type.  */
    }
    return 1;
}

static XDndHandler *
XDndCreateHandler(dndPtr) 
    Dnd *dndPtr;
{
    Tk_Window tkwin;
    Window window;
    Blt_HashEntry *hPtr;
    int isNew;
    XDndHandler *handlerPtr;

    /* 
     * Find the containing toplevel of this window. See if an XDND 
     * handler is already registered for it.  
     */
    tkwin = Blt_GetToplevel(dndPtr->tkwin);
    window = Blt_GetRealWindowId(tkwin); /* Use the wrapper window as
                                * the real toplevel window. */
    hPtr = Blt_CreateHashEntry(&(dataPtr->XDndHandlerTable), (char *)window, 
      &isNew);
    if (!isNew) {
      handlerPtr = (XDndHandler *)Blt_GetHashEntry(hPtr);
      handlerPtr->refCount++;
    } else {
      handlerPtr = Blt_Malloc(sizeof(XDndHandler));
      handlerPtr->tkwin = tkwin;
      handlerPtr->dndPtr = NULL;
      handlerPtr->refCount = 1;
      handlerPtr->dataPtr = dataPtr;
      /* FIXME */
      SetProperty(window, dataPtr->XdndAwareAtom, "3");
      Blt_SetHashValue(hPtr, handlerPtr);
    }
    return handlerPtr;
}

static void
XDndDeleteHandler(dndPtr) 
    Dnd *dndPtr;
{
    Tk_Window tkwin;
    Window window;
    Blt_HashEntry *hPtr;

    tkwin = Blt_GetToplevel(dndPtr->tkwin);
    window = Blt_GetRealWindowId(tkwin); /* Use the wrapper window as the real 
                                * toplevel window. */
    hPtr = Blt_FindHashEntry(&(dataPtr->XDndHandlerTable), (char *)window);
    if (hPtr != NULL) {
      XDndHandler *handlerPtr;

      handlerPtr = (XDndHandler *)Blt_GetHashEntry(hPtr);
      handlerPtr->refCount--;
      if (handlerPtr->refCount == 0) {
          XDndFreeFormats(handlerPtr); 
          XDeleteProperty(dndPtr->display, window, 
            dndPtr->dataPtr->XdndAwareAtom);
          Blt_DeleteHashEntry(&(dataPtr->XDndHandlerTable), hPtr);
          Blt_Free(handlerPtr);
      }
    }
}

#endif /* HAVE_XDND */

#endif /* NO_DRAGDROP */

Generated by  Doxygen 1.6.0   Back to index