/* * 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 #include #include #include #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], ×tamp) != 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 */