diff options
author | joye <joye> | 2013-08-27 18:44:24 (GMT) |
---|---|---|
committer | joye <joye> | 2013-08-27 18:44:24 (GMT) |
commit | 48974a09f23839821ca95f228fc0f3f53bb1cefa (patch) | |
tree | 9d5cfa1305ef57b69c9b1dd09dc3ffe536c0777d /src/bltGrBind.C | |
parent | 05d8c009040abfcb5f62644fbb99c8ff453d4519 (diff) | |
download | blt-48974a09f23839821ca95f228fc0f3f53bb1cefa.zip blt-48974a09f23839821ca95f228fc0f3f53bb1cefa.tar.gz blt-48974a09f23839821ca95f228fc0f3f53bb1cefa.tar.bz2 |
*** empty log message ***
Diffstat (limited to 'src/bltGrBind.C')
-rw-r--r-- | src/bltGrBind.C | 1825 |
1 files changed, 1825 insertions, 0 deletions
diff --git a/src/bltGrBind.C b/src/bltGrBind.C new file mode 100644 index 0000000..7b288f7 --- /dev/null +++ b/src/bltGrBind.C @@ -0,0 +1,1825 @@ + +/* + * bltBind.c -- + * + * This module implements object binding procedures for the BLT toolkit. + * + * Copyright 1998 George A Howlett. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include "bltInt.h" +#include "bltBind.h" +#include <bltList.h> + +static Tk_EventProc BindProc; + +typedef struct _Blt_BindTable BindTable; + +/* Make button presses on objects have implicit grab. */ +#define FULLY_SIMULATE_GRAB 1 + +/* + * Binding table procedures. + */ +#define REPICK_IN_PROGRESS (1<<0) +#define LEFT_GRABBED_ITEM (1<<1) + +#define ALL_BUTTONS_MASK \ + (Button1Mask | Button2Mask | Button3Mask | Button4Mask | Button5Mask) + +#ifndef VirtualEventMask +#define VirtualEventMask (1L << 30) +#endif + +#define ALL_VALID_EVENTS_MASK \ + (ButtonMotionMask | Button1MotionMask | Button2MotionMask | \ + Button3MotionMask | Button4MotionMask | Button5MotionMask | \ + ButtonPressMask | ButtonReleaseMask | EnterWindowMask | \ + LeaveWindowMask | KeyPressMask | KeyReleaseMask | \ + PointerMotionMask | VirtualEventMask) + +static int buttonMasks[] = +{ + 0, /* No buttons pressed */ + Button1Mask, Button2Mask, Button3Mask, Button4Mask, Button5Mask, +}; + + +/* + * How to make drag&drop work? + * + * Right now we generate pseudo <Enter> <Leave> events within button grab + * on an object. They're marked NotifyVirtual instead of NotifyAncestor. + * A better solution: generate new-style virtual <<DragEnter>> + * <<DragMotion>> <<DragLeave>> events. These virtual events don't have + * to exist as "real" event sequences, like virtual events do now. + */ + +/* + *--------------------------------------------------------------------------- + * + * DoEvent -- + * + * This procedure is called to invoke binding processing for a new event + * that is associated with the current item for a legend. + * + * Results: + * None. + * + * Side effects: + * Depends on the bindings for the legend. A binding script could delete + * an entry, so callers should protect themselves with Tcl_Preserve and + * Tcl_Release. + * + *--------------------------------------------------------------------------- + */ +static void +DoEvent( + BindTable *bindPtr, /* Binding information for widget in which + * event occurred. */ + XEvent *eventPtr, /* Real or simulated X event that is to be + * processed. */ + ClientData item, /* Item picked. */ + ClientData context) /* Context of item. */ +{ + Blt_List tagList; + + if ((bindPtr->tkwin == NULL) || (bindPtr->bindingTable == NULL)) { + return; + } + if ((eventPtr->type == KeyPress) || (eventPtr->type == KeyRelease)) { + item = bindPtr->focusItem; + context = bindPtr->focusContext; + } + if (item == NULL) { + return; + } + /* + * Invoke the binding system. + */ + tagList = Blt_List_Create(BLT_ONE_WORD_KEYS); + if (bindPtr->tagProc == NULL) { + Blt_List_Append(tagList, Tk_GetUid("all"), 0); + Blt_List_Append(tagList, (char *)item, 0); + } else { + (*bindPtr->tagProc) (bindPtr, item, context, tagList); + } + if (Blt_List_GetLength(tagList) > 0) { + int nTags; + ClientData *tagArray; +#define MAX_STATIC_TAGS 64 + ClientData staticTags[MAX_STATIC_TAGS]; + Blt_ListNode node; + + tagArray = staticTags; + nTags = Blt_List_GetLength(tagList); + if (nTags >= MAX_STATIC_TAGS) { + tagArray = Blt_AssertMalloc(sizeof(ClientData) * nTags); + + } + nTags = 0; + for (node = Blt_List_FirstNode(tagList); node != NULL; + node = Blt_List_NextNode(node)) { + tagArray[nTags++] = (ClientData)Blt_List_GetKey(node); + } + Tk_BindEvent(bindPtr->bindingTable, eventPtr, bindPtr->tkwin, nTags, + tagArray); + if (tagArray != staticTags) { + Blt_Free(tagArray); + } + } + Blt_List_Destroy(tagList); +} + +/* + *--------------------------------------------------------------------------- + * + * PickCurrentItem -- + * + * Find the topmost item in a legend that contains a given location and + * mark the the current item. If the current item has changed, generate + * a fake exit event on the old current item and a fake enter event on + * the new current item. + * + * Results: + * None. + * + * Side effects: + * The current item may change. If it does, then the commands associated + * with item entry and exit could do just about anything. A binding + * script could delete the legend, so callers should protect themselves + * with Tcl_Preserve and Tcl_Release. + * + *--------------------------------------------------------------------------- + */ +static void +PickCurrentItem( + BindTable *bindPtr, /* Binding table information. */ + XEvent *eventPtr) /* Event describing location of mouse cursor. + * Must be EnterWindow, LeaveWindow, + * ButtonRelease, or MotionNotify. */ +{ + int buttonDown; + ClientData newItem, oldItem; + ClientData newContext; + + /* + * Check whether or not a button is down. If so, we'll log entry and exit + * into and out of the current item, but not entry into any other item. + * This implements a form of grabbing equivalent to what the X server does + * for windows. + */ + buttonDown = (bindPtr->state & ALL_BUTTONS_MASK); + if (!buttonDown) { + bindPtr->flags &= ~LEFT_GRABBED_ITEM; + } + + /* + * Save information about this event in the widget. The event in the + * widget is used for two purposes: + * + * 1. Event bindings: if the current item changes, fake events are + * generated to allow item-enter and item-leave bindings to trigger. + * 2. Reselection: if the current item gets deleted, can use the + * saved event to find a new current item. + * Translate MotionNotify events into EnterNotify events, since that's + * what gets reported to item handlers. + */ + + if (eventPtr != &bindPtr->pickEvent) { + if ((eventPtr->type == MotionNotify) || + (eventPtr->type == ButtonRelease)) { + bindPtr->pickEvent.xcrossing.type = EnterNotify; + bindPtr->pickEvent.xcrossing.serial = eventPtr->xmotion.serial; + bindPtr->pickEvent.xcrossing.send_event = + eventPtr->xmotion.send_event; + bindPtr->pickEvent.xcrossing.display = eventPtr->xmotion.display; + bindPtr->pickEvent.xcrossing.window = eventPtr->xmotion.window; + bindPtr->pickEvent.xcrossing.root = eventPtr->xmotion.root; + bindPtr->pickEvent.xcrossing.subwindow = None; + bindPtr->pickEvent.xcrossing.time = eventPtr->xmotion.time; + bindPtr->pickEvent.xcrossing.x = eventPtr->xmotion.x; + bindPtr->pickEvent.xcrossing.y = eventPtr->xmotion.y; + bindPtr->pickEvent.xcrossing.x_root = eventPtr->xmotion.x_root; + bindPtr->pickEvent.xcrossing.y_root = eventPtr->xmotion.y_root; + bindPtr->pickEvent.xcrossing.mode = NotifyNormal; + bindPtr->pickEvent.xcrossing.detail = NotifyNonlinear; + bindPtr->pickEvent.xcrossing.same_screen + = eventPtr->xmotion.same_screen; + bindPtr->pickEvent.xcrossing.focus = False; + bindPtr->pickEvent.xcrossing.state = eventPtr->xmotion.state; + } else { + bindPtr->pickEvent = *eventPtr; + } + } + bindPtr->activePick = TRUE; + + /* + * If this is a recursive call (there's already a partially completed call + * pending on the stack; it's in the middle of processing a Leave event + * handler for the old current item) then just return; the pending call + * will do everything that's needed. + */ + if (bindPtr->flags & REPICK_IN_PROGRESS) { + return; + } + /* + * A LeaveNotify event automatically means that there's no current item, + * so the check for closest item can be skipped. + */ + newContext = NULL; + if (bindPtr->pickEvent.type != LeaveNotify) { + int x, y; + + x = bindPtr->pickEvent.xcrossing.x; + y = bindPtr->pickEvent.xcrossing.y; + newItem = (*bindPtr->pickProc) (bindPtr->clientData, x, y, &newContext); + } else { + newItem = NULL; + } + if (((newItem == bindPtr->currentItem) && + (newContext == bindPtr->currentContext)) && + ((bindPtr->flags & LEFT_GRABBED_ITEM) == 0)) { + /* + * Nothing to do: the current item hasn't changed. + */ + return; + } +#if FULLY_SIMULATE_GRAB + if (((newItem != bindPtr->currentItem) || + (newContext != bindPtr->currentContext)) && + (buttonDown)) { + bindPtr->flags |= LEFT_GRABBED_ITEM; +#ifdef notdef + fprintf(stderr, "pickcurrentitem: simulate grab.\n"); +#endif + return; + } +#endif + /* + * Simulate a LeaveNotify event on the previous current item and an + * EnterNotify event on the new current item. Remove the "current" tag + * from the previous current item and place it on the new current item. + */ + oldItem = bindPtr->currentItem; + Tcl_Preserve(oldItem); + Tcl_Preserve(newItem); + + if ((bindPtr->currentItem != NULL) && + ((newItem != bindPtr->currentItem) || + (newContext != bindPtr->currentContext)) && + ((bindPtr->flags & LEFT_GRABBED_ITEM) == 0)) { + XEvent event; + + event = bindPtr->pickEvent; + event.type = LeaveNotify; + /* + * If the event's detail happens to be NotifyInferior the binding + * mechanism will discard the event. To be consistent, always use + * NotifyAncestor. + */ + event.xcrossing.detail = NotifyAncestor; + + bindPtr->flags |= REPICK_IN_PROGRESS; + DoEvent(bindPtr, &event, bindPtr->currentItem, bindPtr->currentContext); + bindPtr->flags &= ~REPICK_IN_PROGRESS; + + /* + * Note: during DoEvent above, it's possible that bindPtr->newItem got + * reset to NULL because the item was deleted. + */ + } + if (((newItem != bindPtr->currentItem) || + (newContext != bindPtr->currentContext)) && + (buttonDown)) { + XEvent event; + + bindPtr->flags |= LEFT_GRABBED_ITEM; + event = bindPtr->pickEvent; + if ((newItem != bindPtr->newItem) || + (newContext != bindPtr->newContext)) { + ClientData savedItem; + ClientData savedContext; + + /* + * Generate <Enter> and <Leave> events for objects during button + * grabs. This isn't standard. But for example, it allows one to + * provide balloon help on the individual entries of the Hierbox + * widget. + */ + savedItem = bindPtr->currentItem; + savedContext = bindPtr->currentContext; + if (bindPtr->newItem != NULL) { + event.type = LeaveNotify; + event.xcrossing.detail = NotifyVirtual /* Ancestor */ ; + bindPtr->currentItem = bindPtr->newItem; + DoEvent(bindPtr, &event, bindPtr->newItem, bindPtr->newContext); + } + bindPtr->newItem = newItem; + bindPtr->newContext = newContext; + if (newItem != NULL) { + event.type = EnterNotify; + event.xcrossing.detail = NotifyVirtual /* Ancestor */ ; + bindPtr->currentItem = newItem; + DoEvent(bindPtr, &event, newItem, newContext); + } + bindPtr->currentItem = savedItem; + bindPtr->currentContext = savedContext; + } + goto done; + } + /* + * Special note: it's possible that + * bindPtr->newItem == bindPtr->currentItem + * here. This can happen, for example, if LEFT_GRABBED_ITEM was set. + */ + + bindPtr->flags &= ~LEFT_GRABBED_ITEM; + bindPtr->currentItem = bindPtr->newItem = newItem; + bindPtr->currentContext = bindPtr->newContext = newContext; + if (bindPtr->currentItem != NULL) { + XEvent event; + + event = bindPtr->pickEvent; + event.type = EnterNotify; + event.xcrossing.detail = NotifyAncestor; + DoEvent(bindPtr, &event, newItem, newContext); +#ifdef notdef + if ((eventPtr->type == MotionNotify) || + (eventPtr->type == ButtonRelease)) { + fprintf(stderr, "pickcurrentitem: DoEvent Button buttondown=%d.\n", + buttonDown); + event.type = eventPtr->type; + event.xbutton.button = eventPtr->xbutton.button; + DoEvent(bindPtr, &event, newItem, newContext); + fprintf(stderr, "pickcurrentitem: done.\n"); + } +#endif + } + done: + Tcl_Release(newItem); + Tcl_Release(oldItem); +} + +/* + *--------------------------------------------------------------------------- + * + * BindProc -- + * + * This procedure is invoked by the Tk dispatcher to handle events + * associated with bindings on items. + * + * Results: + * None. + * + * Side effects: + * Depends on the command invoked as part of the binding + * (if there was any). + * + *--------------------------------------------------------------------------- + */ +static void +BindProc( + ClientData clientData, /* Pointer to widget structure. */ + XEvent *eventPtr) /* Pointer to X event that just happened. */ +{ + BindTable *bindPtr = clientData; + int mask; + + Tcl_Preserve(bindPtr->clientData); + /* + * This code below keeps track of the current modifier state in + * bindPtr->state. This information is used to defer repicks of the + * current item while buttons are down. + */ + switch (eventPtr->type) { + case ButtonPress: + case ButtonRelease: + mask = 0; + if ((eventPtr->xbutton.button >= Button1) && + (eventPtr->xbutton.button <= Button5)) { + mask = buttonMasks[eventPtr->xbutton.button]; + } + /* + * For button press events, repick the current item using the button + * state before the event, then process the event. For button release + * events, first process the event, then repick the current item using + * the button state *after* the event (the button has logically gone + * up before we change the current item). + */ + if (eventPtr->type == ButtonPress) { + + /* + * On a button press, first repick the current item using the + * button state before the event, the process the event. + */ + + bindPtr->state = eventPtr->xbutton.state; + PickCurrentItem(bindPtr, eventPtr); + bindPtr->state ^= mask; + DoEvent(bindPtr, eventPtr, bindPtr->currentItem, + bindPtr->currentContext); + + } else { + + /* + * Button release: first process the event, with the button still + * considered to be down. Then repick the current item under the + * assumption that the button is no longer down. + */ + bindPtr->state = eventPtr->xbutton.state; + DoEvent(bindPtr, eventPtr, bindPtr->currentItem, + bindPtr->currentContext); + eventPtr->xbutton.state ^= mask; + bindPtr->state = eventPtr->xbutton.state; + PickCurrentItem(bindPtr, eventPtr); + eventPtr->xbutton.state ^= mask; + } + break; + + case EnterNotify: + case LeaveNotify: + bindPtr->state = eventPtr->xcrossing.state; + PickCurrentItem(bindPtr, eventPtr); + break; + + case MotionNotify: + bindPtr->state = eventPtr->xmotion.state; + PickCurrentItem(bindPtr, eventPtr); + DoEvent(bindPtr, eventPtr, bindPtr->currentItem, + bindPtr->currentContext); + break; + + case KeyPress: + case KeyRelease: + bindPtr->state = eventPtr->xkey.state; + PickCurrentItem(bindPtr, eventPtr); + DoEvent(bindPtr, eventPtr, bindPtr->currentItem, + bindPtr->currentContext); + break; + } + Tcl_Release(bindPtr->clientData); +} + +int +Blt_ConfigureBindings( + Tcl_Interp *interp, + BindTable *bindPtr, + ClientData item, + int argc, + const char **argv) +{ + const char *command; + unsigned long mask; + const char *seq; + + if (argc == 0) { + Tk_GetAllBindings(interp, bindPtr->bindingTable, item); + return TCL_OK; + } + if (argc == 1) { + command = Tk_GetBinding(interp, bindPtr->bindingTable, item, argv[0]); + if (command == NULL) { + Tcl_AppendResult(interp, "can't find event \"", argv[0], "\"", + (char *)NULL); + return TCL_ERROR; + } + Tcl_SetStringObj(Tcl_GetObjResult(interp), command, -1); + return TCL_OK; + } + + seq = argv[0]; + command = argv[1]; + + if (command[0] == '\0') { + return Tk_DeleteBinding(interp, bindPtr->bindingTable, item, seq); + } + + if (command[0] == '+') { + mask = Tk_CreateBinding(interp, bindPtr->bindingTable, item, seq, + command + 1, TRUE); + } else { + mask = Tk_CreateBinding(interp, bindPtr->bindingTable, item, seq, + command, FALSE); + } + if (mask == 0) { + Tcl_AppendResult(interp, "event mask can't be zero for \"", item, "\"", + (char *)NULL); + return TCL_ERROR; + } + if (mask & (unsigned)~ALL_VALID_EVENTS_MASK) { + Tk_DeleteBinding(interp, bindPtr->bindingTable, item, seq); + Tcl_ResetResult(interp); + Tcl_AppendResult(interp, "requested illegal events; ", + "only key, button, motion, enter, leave, and virtual ", + "events may be used", (char *)NULL); + return TCL_ERROR; + } + return TCL_OK; +} + + +int +Blt_ConfigureBindingsFromObj( + Tcl_Interp *interp, + BindTable *bindPtr, + ClientData item, + int objc, + Tcl_Obj *const *objv) +{ + const char *command; + unsigned long mask; + const char *seq; + const char *string; + + if (objc == 0) { + Tk_GetAllBindings(interp, bindPtr->bindingTable, item); + return TCL_OK; + } + string = Tcl_GetString(objv[0]); + if (objc == 1) { + command = Tk_GetBinding(interp, bindPtr->bindingTable, item, string); + if (command == NULL) { + Tcl_ResetResult(interp); + Tcl_AppendResult(interp, "invalid binding event \"", string, "\"", + (char *)NULL); + return TCL_ERROR; + } + Tcl_SetStringObj(Tcl_GetObjResult(interp), command, -1); + return TCL_OK; + } + + seq = string; + command = Tcl_GetString(objv[1]); + + if (command[0] == '\0') { + return Tk_DeleteBinding(interp, bindPtr->bindingTable, item, seq); + } + + if (command[0] == '+') { + mask = Tk_CreateBinding(interp, bindPtr->bindingTable, item, seq, + command + 1, TRUE); + } else { + mask = Tk_CreateBinding(interp, bindPtr->bindingTable, item, seq, + command, FALSE); + } + if (mask == 0) { + return TCL_ERROR; + } + if (mask & (unsigned)~ALL_VALID_EVENTS_MASK) { + Tk_DeleteBinding(interp, bindPtr->bindingTable, item, seq); + Tcl_ResetResult(interp); + Tcl_AppendResult(interp, "requested illegal events; ", + "only key, button, motion, enter, leave, and virtual ", + "events may be used", (char *)NULL); + return TCL_ERROR; + } + return TCL_OK; +} + +Blt_BindTable +Blt_CreateBindingTable( + Tcl_Interp *interp, + Tk_Window tkwin, + ClientData clientData, + Blt_BindPickProc *pickProc, + Blt_BindTagProc *tagProc) +{ + unsigned int mask; + BindTable *bindPtr; + + bindPtr = Blt_AssertCalloc(1, sizeof(BindTable)); + bindPtr->bindingTable = Tk_CreateBindingTable(interp); + bindPtr->clientData = clientData; + bindPtr->tkwin = tkwin; + bindPtr->pickProc = pickProc; + bindPtr->tagProc = tagProc; + mask = (KeyPressMask | KeyReleaseMask | ButtonPressMask | + ButtonReleaseMask | EnterWindowMask | LeaveWindowMask | + PointerMotionMask); + Tk_CreateEventHandler(tkwin, mask, BindProc, bindPtr); + return bindPtr; +} + +void +Blt_DestroyBindingTable(BindTable *bindPtr) +{ + unsigned int mask; + + Tk_DeleteBindingTable(bindPtr->bindingTable); + mask = (KeyPressMask | KeyReleaseMask | ButtonPressMask | + ButtonReleaseMask | EnterWindowMask | LeaveWindowMask | + PointerMotionMask); + Tk_DeleteEventHandler(bindPtr->tkwin, mask, BindProc, bindPtr); + Blt_Free(bindPtr); +} + +void +Blt_PickCurrentItem(BindTable *bindPtr) +{ + if (bindPtr->activePick) { + PickCurrentItem(bindPtr, &bindPtr->pickEvent); + } +} + +void +Blt_DeleteBindings( + BindTable *bindPtr, + ClientData object) +{ + Tk_DeleteAllBindings(bindPtr->bindingTable, object); + + /* + * If this is the object currently picked, we need to repick one. + */ + if (bindPtr->currentItem == object) { + bindPtr->currentItem = NULL; + bindPtr->currentContext = NULL; + } + if (bindPtr->newItem == object) { + bindPtr->newItem = NULL; + bindPtr->newContext = NULL; + } + if (bindPtr->focusItem == object) { + bindPtr->focusItem = NULL; + bindPtr->focusContext = NULL; + } +} + +void +Blt_MoveBindingTable( + BindTable *bindPtr, + Tk_Window tkwin) +{ + unsigned int mask; + + mask = (KeyPressMask | KeyReleaseMask | ButtonPressMask | + ButtonReleaseMask | EnterWindowMask | LeaveWindowMask | + PointerMotionMask); + if (bindPtr->tkwin != NULL) { + Tk_DeleteEventHandler(bindPtr->tkwin, mask, BindProc, bindPtr); + } + Tk_CreateEventHandler(tkwin, mask, BindProc, bindPtr); + bindPtr->tkwin = tkwin; +} + +/* + * The following union is used to hold the detail information from an + * XEvent (including Tk's XVirtualEvent extension). + */ +typedef union { + KeySym keySym; /* KeySym that corresponds to xkey.keycode. */ + int button; /* Button that was pressed (xbutton.button). */ + Tk_Uid name; /* Tk_Uid of virtual event. */ + ClientData clientData; /* Used when type of Detail is unknown, and to + * ensure that all bytes of Detail are initialized + * when this structure is used in a hash key. */ +} Detail; + + +/* + * The following structure defines a pattern, which is matched against X + * events as part of the process of converting X events into TCL commands. + */ +typedef struct { + int eventType; /* Type of X event, e.g. ButtonPress. */ + int needMods; /* Mask of modifiers that must be + * present (0 means no modifiers are + * required). */ + Detail detail; /* Additional information that must + * match event. Normally this is 0, + * meaning no additional information + * must match. For KeyPress and + * KeyRelease events, a keySym may + * be specified to select a + * particular keystroke (0 means any + * keystrokes). For button events, + * specifies a particular button (0 + * means any buttons are OK). For virtual + * events, specifies the Tk_Uid of the + * virtual event name (never 0). */ +} Pattern; + +typedef struct { + const char *name; /* Name of modifier. */ + int mask; /* Button/modifier mask value, such as + * Button1Mask. */ + int flags; /* Various flags; see below for + * definitions. */ +} EventModifier; + +/* + * Flags for EventModifier structures: + * + * DOUBLE - Non-zero means duplicate this event, + * e.g. for double-clicks. + * TRIPLE - Non-zero means triplicate this event, + * e.g. for triple-clicks. + * QUADRUPLE - Non-zero means quadruple this event, + * e.g. for 4-fold-clicks. + * MULT_CLICKS - Combination of all of above. + */ + +#define DOUBLE (1<<0) +#define TRIPLE (1<<1) +#define QUADRUPLE (1<<2) +#define MULT_CLICKS (DOUBLE|TRIPLE|QUADRUPLE) + +#define META_MASK (AnyModifier<<1) +#define ALT_MASK (AnyModifier<<2) + +static EventModifier eventModifiers[] = { + {"Alt", ALT_MASK, 0}, + {"Any", 0, 0}, /* Ignored: historical relic. */ + {"B1", Button1Mask, 0}, + {"B2", Button2Mask, 0}, + {"B3", Button3Mask, 0}, + {"B4", Button4Mask, 0}, + {"B5", Button5Mask, 0}, + {"Button1", Button1Mask, 0}, + {"Button2", Button2Mask, 0}, + {"Button3", Button3Mask, 0}, + {"Button4", Button4Mask, 0}, + {"Button5", Button5Mask, 0}, + {"Command", Mod1Mask, 0}, + {"Control", ControlMask, 0}, + {"Double", 0, DOUBLE}, + {"Lock", LockMask, 0}, + {"M", META_MASK, 0}, + {"M1", Mod1Mask, 0}, + {"M2", Mod2Mask, 0}, + {"M3", Mod3Mask, 0}, + {"M4", Mod4Mask, 0}, + {"M5", Mod5Mask, 0}, + {"Meta", META_MASK, 0}, + {"Mod1", Mod1Mask, 0}, + {"Mod2", Mod2Mask, 0}, + {"Mod3", Mod3Mask, 0}, + {"Mod4", Mod4Mask, 0}, + {"Mod5", Mod5Mask, 0}, + {"Option", Mod2Mask, 0}, + {"Quadruple", 0, QUADRUPLE}, + {"Shift", ShiftMask, 0}, + {"Triple", 0, TRIPLE}, +}; + +typedef struct { + const char *name; /* Name of event. */ + int type; /* Event type for X, such as + * ButtonPress. */ + int eventMask; /* Mask bits (for XSelectInput) + * for this event type. */ +} EventInfo; + +/* + * Note: some of the masks below are an OR-ed combination of + * several masks. This is necessary because X doesn't report + * up events unless you also ask for down events. Also, X + * doesn't report button state in motion events unless you've + * asked about button events. + */ + +static EventInfo events[] = { + {"Activate", ActivateNotify, ActivateMask}, + {"Button", ButtonPress, ButtonPressMask}, + {"ButtonPress", ButtonPress, ButtonPressMask}, + {"ButtonRelease", ButtonRelease, ButtonPressMask|ButtonReleaseMask}, + {"Circulate", CirculateNotify, StructureNotifyMask}, + {"CirculateRequest", CirculateRequest, SubstructureRedirectMask}, + {"Colormap", ColormapNotify, ColormapChangeMask}, + {"Configure", ConfigureNotify, StructureNotifyMask}, + {"ConfigureRequest", ConfigureRequest, SubstructureRedirectMask}, + {"Create", CreateNotify, SubstructureNotifyMask}, + {"Deactivate", DeactivateNotify, ActivateMask}, + {"Destroy", DestroyNotify, StructureNotifyMask}, + {"Enter", EnterNotify, EnterWindowMask}, + {"Expose", Expose, ExposureMask}, + {"FocusIn", FocusIn, FocusChangeMask}, + {"FocusOut", FocusOut, FocusChangeMask}, + {"Gravity", GravityNotify, StructureNotifyMask}, + {"Key", KeyPress, KeyPressMask}, + {"KeyPress", KeyPress, KeyPressMask}, + {"KeyRelease", KeyRelease, KeyPressMask|KeyReleaseMask}, + {"Leave", LeaveNotify, LeaveWindowMask}, + {"Map", MapNotify, StructureNotifyMask}, + {"MapRequest", MapRequest, SubstructureRedirectMask}, + {"Motion", MotionNotify, ButtonPressMask|PointerMotionMask}, + {"MouseWheel", MouseWheelEvent, MouseWheelMask}, + {"Property", PropertyNotify, PropertyChangeMask}, + {"Reparent", ReparentNotify, StructureNotifyMask}, + {"ResizeRequest", ResizeRequest, ResizeRedirectMask}, + {"Unmap", UnmapNotify, StructureNotifyMask}, + {"Visibility", VisibilityNotify, VisibilityChangeMask}, +}; + +/* + * The defines and table below are used to classify events into + * various groups. The reason for this is that logically identical + * fields (e.g. "state") appear at different places in different + * types of events. The classification masks can be used to figure + * out quickly where to extract information from events. + */ + +#define KEY 0x1 +#define BUTTON 0x2 +#define MOTION 0x4 +#define CROSSING 0x8 +#define FOCUS 0x10 +#define EXPOSE 0x20 +#define VISIBILITY 0x40 +#define CREATE 0x80 +#define DESTROY 0x100 +#define UNMAP 0x200 +#define MAP 0x400 +#define REPARENT 0x800 +#define CONFIG 0x1000 +#define GRAVITY 0x2000 +#define CIRC 0x4000 +#define PROP 0x8000 +#define COLORMAP 0x10000 +#define VIRTUAL 0x20000 +#define ACTIVATE 0x40000 +#define MAPREQ 0x80000 +#define CONFIGREQ 0x100000 +#define RESIZEREQ 0x200000 +#define CIRCREQ 0x400000 + +#define KEY_BUTTON_MOTION_VIRTUAL (KEY|BUTTON|MOTION|VIRTUAL) +#define KEY_BUTTON_MOTION_CROSSING (KEY|BUTTON|MOTION|CROSSING|VIRTUAL) + +static int flagArray[TK_LASTEVENT+1] = { + /* Not used */ 0, + /* Not used */ 0, + /* KeyPress */ KEY, + /* KeyRelease */ KEY, + /* ButtonPress */ BUTTON, + /* ButtonRelease */ BUTTON, + /* MotionNotify */ MOTION, + /* EnterNotify */ CROSSING, + /* LeaveNotify */ CROSSING, + /* FocusIn */ FOCUS, + /* FocusOut */ FOCUS, + /* KeymapNotify */ 0, + /* Expose */ EXPOSE, + /* GraphicsExpose */ EXPOSE, + /* NoExpose */ 0, + /* VisibilityNotify */ VISIBILITY, + /* CreateNotify */ CREATE, + /* DestroyNotify */ DESTROY, + /* UnmapNotify */ UNMAP, + /* MapNotify */ MAP, + /* MapRequest */ MAPREQ, + /* ReparentNotify */ REPARENT, + /* ConfigureNotify */ CONFIG, + /* ConfigureRequest */ CONFIGREQ, + /* GravityNotify */ GRAVITY, + /* ResizeRequest */ RESIZEREQ, + /* CirculateNotify */ CIRC, + /* CirculateRequest */ 0, + /* PropertyNotify */ PROP, + /* SelectionClear */ 0, + /* SelectionRequest */ 0, + /* SelectionNotify */ 0, + /* ColormapNotify */ COLORMAP, + /* ClientMessage */ 0, + /* MappingNotify */ 0, +#ifdef GenericEvent + /* GenericEvent */ 0, +#endif + /* VirtualEvent */ VIRTUAL, + /* Activate */ ACTIVATE, + /* Deactivate */ ACTIVATE, + /* MouseWheel */ KEY +}; + + +static EventModifier * +FindModifier(const char *string) +{ + int high, low; + char c; + + low = 0; + high = (sizeof(eventModifiers) / sizeof(EventModifier)) - 1; + c = string[0]; + while (low <= high) { + EventModifier *modPtr; + int compare; + int median; + + median = (low + high) >> 1; + modPtr = eventModifiers + median; + + /* Test the first character */ + compare = c - modPtr->name[0]; + if (compare == 0) { + compare = strcmp(string, modPtr->name); + } + if (compare < 0) { + high = median - 1; + } else if (compare > 0) { + low = median + 1; + } else { + return modPtr; /* Modifier found. */ + } + } + return NULL; /* Can't find modifier */ +} + +static EventInfo * +FindEvent(const char *string) +{ + int high, low; + char c; + + low = 0; + high = (sizeof(events) / sizeof(EventInfo)) - 1; + c = string[0]; + while (low <= high) { + EventInfo *infoPtr; + int compare; + int median; + + median = (low + high) >> 1; + infoPtr = events + median; + + /* Test the first character */ + compare = c - infoPtr->name[0]; + if (compare == 0) { + compare = strcmp(string, infoPtr->name); + } + if (compare < 0) { + high = median - 1; + } else if (compare > 0) { + low = median + 1; + } else { + return infoPtr; /* Event found. */ + } + } + return NULL; /* Can't find event. */ +} + + +/* + *---------------------------------------------------------------------- + * + * GetField -- + * + * Used to parse pattern descriptions. Copies up to + * size characters from p to copy, stopping at end of + * string, space, "-", ">", or whenever size is + * exceeded. + * + * Results: + * The return value is a pointer to the character just + * after the last one copied (usually "-" or space or + * ">", but could be anything if size was exceeded). + * Also places NULL-terminated string (up to size + * character, including NULL), at copy. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static char * +GetField(p, copy, size) + char *p; /* Pointer to part of pattern. */ + char *copy; /* Place to copy field. */ + int size; /* Maximum number of characters to + * copy. */ +{ + while ((*p != '\0') && !isspace(UCHAR(*p)) && (*p != '>') + && (*p != '-') && (size > 1)) { + *copy = *p; + p++; + copy++; + size--; + } + *copy = '\0'; + return p; +} + +static int +ParseEventDescription(Tcl_Interp *interp, const char **eventStringPtr, + Pattern *patPtr, unsigned long *eventMaskPtr) +{ + char *p; + unsigned long eventMask; + int count, eventFlags; +#define FIELD_SIZE 48 + char field[FIELD_SIZE]; + EventInfo *infoPtr; + + Tcl_DString copy; + Tcl_DStringInit(©); + p = Tcl_DStringAppend(©, *eventStringPtr, -1); + + patPtr->eventType = -1; + patPtr->needMods = 0; + patPtr->detail.clientData = 0; + + eventMask = 0; + count = 1; + + /* + * Handle simple ASCII characters. + */ + + if (*p != '<') { + char string[2]; + + patPtr->eventType = KeyPress; + eventMask = KeyPressMask; + string[0] = *p; + string[1] = 0; + patPtr->detail.keySym = XStringToKeysym(string); + if (patPtr->detail.keySym == NoSymbol) { + if (isprint(UCHAR(*p))) { + patPtr->detail.keySym = *p; + } else { + char buf[64]; + + sprintf(buf, "bad ASCII character 0x%x", (unsigned char) *p); + Tcl_SetResult(interp, buf, TCL_VOLATILE); + count = 0; + goto done; + } + } + p++; + goto end; + } + + /* + * A physical event description consists of: + * + * 1. open angle bracket. + * 2. any number of modifiers, each followed by spaces + * or dashes. + * 3. an optional event name. + * 4. an option button or keysym name. Either this or + * item 3 *must* be present; if both are present + * then they are separated by spaces or dashes. + * 5. a close angle bracket. + */ + + p++; + + while (1) { + EventModifier *modPtr; + p = GetField(p, field, FIELD_SIZE); + if (*p == '>') { + /* + * This solves the problem of, e.g., <Control-M> being + * misinterpreted as Control + Meta + missing keysym + * instead of Control + KeyPress + M. + */ + break; + } + modPtr = FindModifier(field); + if (modPtr == NULL) { + break; + } + patPtr->needMods |= modPtr->mask; + if (modPtr->flags & (MULT_CLICKS)) { + int i = modPtr->flags & MULT_CLICKS; + count = 2; + while (i >>= 1) count++; + } + while ((*p == '-') || isspace(UCHAR(*p))) { + p++; + } + } + + eventFlags = 0; + infoPtr = FindEvent(field); + if (infoPtr != NULL) { + patPtr->eventType = infoPtr->type; + eventFlags = flagArray[infoPtr->type]; + eventMask = infoPtr->eventMask; + while ((*p == '-') || isspace(UCHAR(*p))) { + p++; + } + p = GetField(p, field, FIELD_SIZE); + } + if (*field != '\0') { + if ((*field >= '1') && (*field <= '5') && (field[1] == '\0')) { + if (eventFlags == 0) { + patPtr->eventType = ButtonPress; + eventMask = ButtonPressMask; + } else if (eventFlags & KEY) { + goto getKeysym; + } else if ((eventFlags & BUTTON) == 0) { + Tcl_AppendResult(interp, "specified button \"", field, + "\" for non-button event", (char *) NULL); + count = 0; + goto done; + } + patPtr->detail.button = (*field - '0'); + } else { + getKeysym: + patPtr->detail.keySym = XStringToKeysym(field); + if (patPtr->detail.keySym == NoSymbol) { + Tcl_AppendResult(interp, "bad event type or keysym \"", + field, "\"", (char *)NULL); + count = 0; + goto done; + } + if (eventFlags == 0) { + patPtr->eventType = KeyPress; + eventMask = KeyPressMask; + } else if ((eventFlags & KEY) == 0) { + Tcl_AppendResult(interp, "specified keysym \"", field, + "\" for non-key event", (char *)NULL); + count = 0; + goto done; + } + } + } else if (eventFlags == 0) { + Tcl_AppendResult(interp, "no event type or button # or keysym", + (char *)NULL); + count = 0; + goto done; + } + + while ((*p == '-') || isspace(UCHAR(*p))) { + p++; + } + if (*p != '>') { + while (*p != '\0') { + p++; + if (*p == '>') { + Tcl_AppendResult(interp, + "extra characters after detail in binding", + (char *)NULL); + count = 0; + goto done; + } + } + Tcl_AppendResult(interp, "missing \">\" in binding", (char *)NULL); + count = 0; + goto done; + } + p++; + +end: + *eventStringPtr += (p - Tcl_DStringValue(©)); + *eventMaskPtr |= eventMask; +done: + Tcl_DStringFree(©); + return count; +} + +typedef struct { + int numKey; /* Integer representation of a value. */ + const char *strKey; /* String representation of a value. */ +} TkStateMap; + +static TkStateMap notifyMode[] = { + {NotifyNormal, "NotifyNormal"}, + {NotifyGrab, "NotifyGrab"}, + {NotifyUngrab, "NotifyUngrab"}, + {NotifyWhileGrabbed, "NotifyWhileGrabbed"}, + {-1, NULL} +}; + +static TkStateMap notifyDetail[] = { + {NotifyAncestor, "NotifyAncestor"}, + {NotifyVirtual, "NotifyVirtual"}, + {NotifyInferior, "NotifyInferior"}, + {NotifyNonlinear, "NotifyNonlinear"}, + {NotifyNonlinearVirtual, "NotifyNonlinearVirtual"}, + {NotifyPointer, "NotifyPointer"}, + {NotifyPointerRoot, "NotifyPointerRoot"}, + {NotifyDetailNone, "NotifyDetailNone"}, + {-1, NULL} +}; + +static TkStateMap circPlace[] = { + {PlaceOnTop, "PlaceOnTop"}, + {PlaceOnBottom, "PlaceOnBottom"}, + {-1, NULL} +}; + +static TkStateMap visNotify[] = { + {VisibilityUnobscured, "VisibilityUnobscured"}, + {VisibilityPartiallyObscured, "VisibilityPartiallyObscured"}, + {VisibilityFullyObscured, "VisibilityFullyObscured"}, + {-1, NULL} +}; + +static TkStateMap configureRequestDetail[] = { + {None, "None"}, + {Above, "Above"}, + {Below, "Below"}, + {BottomIf, "BottomIf"}, + {TopIf, "TopIf"}, + {Opposite, "Opposite"}, + {-1, NULL} +}; + +static TkStateMap propNotify[] = { + {PropertyNewValue, "NewValue"}, + {PropertyDelete, "Delete"}, + {-1, NULL} +}; + +/* + *--------------------------------------------------------------------------- + * + * HandleEventGenerate -- + * + * Helper function for the "event generate" command. Generate and + * process an XEvent, constructed from information parsed from the + * event description string and its optional arguments. + * + * argv[0] contains name of the target window. + * argv[1] contains pattern string for one event (e.g, <Control-v>). + * argv[2..argc-1] contains -field/option pairs for specifying + * additional detail in the generated event. + * + * Either virtual or physical events can be generated this way. + * The event description string must contain the specification + * for only one event. + * + * Results: + * None. + * + * Side effects: + * When constructing the event, + * event.xany.serial is filled with the current X serial number. + * event.xany.window is filled with the target window. + * event.xany.display is filled with the target window's display. + * Any other fields in eventPtr which are not specified by the pattern + * string or the optional arguments, are set to 0. + * + * The event may be handled sychronously or asynchronously, depending + * on the value specified by the optional "-when" option. The + * default setting is synchronous. + * + *--------------------------------------------------------------------------- + */ +static int +SendEventCmd(ClientData clientData, Tcl_Interp *interp, int objc, + Tcl_Obj *const *objv) +{ + XEvent event; + CONST char *p; + char *name; + Window window; + Display *display; + Tk_Window tkwin; + int count, flags, synch, i, number, warp; + Tcl_QueuePosition pos; + Pattern pat; + unsigned long eventMask; + static CONST char *fieldStrings[] = { + "-when", "-above", "-borderwidth", "-button", + "-count", "-delta", "-detail", "-focus", + "-height", + "-keycode", "-keysym", "-mode", "-override", + "-place", "-root", "-rootx", "-rooty", + "-sendevent", "-serial", "-state", "-subwindow", + "-time", "-warp", "-width", "-window", + "-x", "-y", NULL + }; + enum field { + EVENT_WHEN, EVENT_ABOVE, EVENT_BORDER, EVENT_BUTTON, + EVENT_COUNT, EVENT_DELTA, EVENT_DETAIL, EVENT_FOCUS, + EVENT_HEIGHT, + EVENT_KEYCODE, EVENT_KEYSYM, EVENT_MODE, EVENT_OVERRIDE, + EVENT_PLACE, EVENT_ROOT, EVENT_ROOTX, EVENT_ROOTY, + EVENT_SEND, EVENT_SERIAL, EVENT_STATE, EVENT_SUBWINDOW, + EVENT_TIME, EVENT_WARP, EVENT_WIDTH, EVENT_WINDOW, + EVENT_X, EVENT_Y + }; + tkwin = Tk_MainWindow(interp); + if (Blt_GetWindowFromObj(interp, objv[1], &window) != TCL_OK) { + return TCL_ERROR; + } + name = Tcl_GetStringFromObj(objv[2], NULL); + + display = Tk_Display(tkwin); + p = name; + eventMask = 0; + count = ParseEventDescription(interp, &p, &pat, &eventMask); + if (count == 0) { + return TCL_ERROR; + } + if (count != 1) { + Tcl_AppendResult(interp, "Double or Triple modifier not allowed", + (char *)NULL); + return TCL_ERROR; + } + if (*p != '\0') { + Tcl_AppendResult(interp, "only one event specification allowed", + (char *)NULL); + return TCL_ERROR; + } + + memset((VOID *) &event, 0, sizeof(event)); + event.xany.type = pat.eventType; + event.xany.serial = NextRequest(display); + event.xany.send_event = False; + event.xany.window = window; + event.xany.display = display; + + flags = flagArray[event.xany.type]; + if (flags & DESTROY) { + /* + * Event DestroyNotify should be generated by destroying + * the window. + */ + XDestroyWindow(display, window); + return TCL_OK; + } + if (flags & (KEY_BUTTON_MOTION_VIRTUAL)) { + event.xkey.state = pat.needMods; + if ((flags & KEY) && (event.xany.type != MouseWheelEvent)) { + TkpSetKeycodeAndState(tkwin, pat.detail.keySym, &event); + } else if (flags & BUTTON) { + event.xbutton.button = pat.detail.button; + } else if (flags & VIRTUAL) { + ((XVirtualEvent *) &event)->name = pat.detail.name; + } + } + if (flags & (CREATE|UNMAP|MAP|REPARENT|CONFIG|GRAVITY|CIRC)) { + event.xcreatewindow.window = event.xany.window; + } + + if (flags & (KEY_BUTTON_MOTION_VIRTUAL|CROSSING)) { + event.xkey.x_root = -1; + event.xkey.y_root = -1; + } + + /* + * Process the remaining arguments to fill in additional fields + * of the event. + */ + + synch = 1; + warp = 0; + pos = TCL_QUEUE_TAIL; + for (i = 3; i < objc; i += 2) { + Tcl_Obj *optionPtr, *valuePtr; + int index; + + optionPtr = objv[i]; + valuePtr = objv[i + 1]; + + if (Tcl_GetIndexFromObj(interp, optionPtr, fieldStrings, "option", + TCL_EXACT, &index) != TCL_OK) { + return TCL_ERROR; + } + if ((objc & 1) == 0) { + /* + * This test occurs after Tcl_GetIndexFromObj() so that + * "event generate <Button> -xyz" will return the error message + * that "-xyz" is a bad option, rather than that the value + * for "-xyz" is missing. + */ + + Tcl_AppendResult(interp, "value for \"", + Tcl_GetStringFromObj(optionPtr, NULL), "\" missing", + (char *)NULL); + return TCL_ERROR; + } + + switch ((enum field) index) { + case EVENT_WARP: { + if (Tcl_GetBooleanFromObj(interp, valuePtr, &warp) != TCL_OK) { + return TCL_ERROR; + } + if (!(flags & (KEY_BUTTON_MOTION_VIRTUAL))) { + goto badopt; + } + break; + } + case EVENT_WHEN: { +#ifdef notdef + pos = (Tcl_QueuePosition) TkFindStateNumObj(interp, optionPtr, + queuePosition, valuePtr); + if ((int) pos < -1) { + return TCL_ERROR; + } + synch = 0; + if ((int) pos == -1) { + synch = 1; + } +#endif + break; + } + case EVENT_ABOVE: { + Window window2; + + if (Blt_GetWindowFromObj(interp, valuePtr, &window2) != TCL_OK) { + return TCL_ERROR; + } + if (flags & CONFIG) { + event.xconfigure.above = window2; + } else { + goto badopt; + } + break; + } + case EVENT_BORDER: { + if (Tk_GetPixelsFromObj(interp, tkwin, valuePtr, &number)!=TCL_OK) { + return TCL_ERROR; + } + if (flags & (CREATE|CONFIG)) { + event.xcreatewindow.border_width = number; + } else { + goto badopt; + } + break; + } + case EVENT_BUTTON: { + if (Tcl_GetIntFromObj(interp, valuePtr, &number) != TCL_OK) { + return TCL_ERROR; + } + if (flags & BUTTON) { + event.xbutton.button = number; + } else { + goto badopt; + } + break; + } + case EVENT_COUNT: { + if (Tcl_GetIntFromObj(interp, valuePtr, &number) != TCL_OK) { + return TCL_ERROR; + } + if (flags & EXPOSE) { + event.xexpose.count = number; + } else { + goto badopt; + } + break; + } + case EVENT_DELTA: { + if (Tcl_GetIntFromObj(interp, valuePtr, &number) != TCL_OK) { + return TCL_ERROR; + } + if ((flags & KEY) && (event.xkey.type == MouseWheelEvent)) { + event.xkey.keycode = number; + } else { + goto badopt; + } + break; + } + case EVENT_DETAIL: { + number = TkFindStateNumObj(interp, optionPtr, notifyDetail, + valuePtr); + if (number < 0) { + return TCL_ERROR; + } + if (flags & FOCUS) { + event.xfocus.detail = number; + } else if (flags & CROSSING) { + event.xcrossing.detail = number; + } else { + goto badopt; + } + break; + } + case EVENT_FOCUS: { + if (Tcl_GetBooleanFromObj(interp, valuePtr, &number) != TCL_OK) { + return TCL_ERROR; + } + if (flags & CROSSING) { + event.xcrossing.focus = number; + } else { + goto badopt; + } + break; + } + case EVENT_HEIGHT: { + if (Tk_GetPixelsFromObj(interp, tkwin, valuePtr, &number) != TCL_OK) { + return TCL_ERROR; + } + if (flags & EXPOSE) { + event.xexpose.height = number; + } else if (flags & CONFIG) { + event.xconfigure.height = number; + } else { + goto badopt; + } + break; + } + case EVENT_KEYCODE: { + if (Tcl_GetIntFromObj(interp, valuePtr, &number) != TCL_OK) { + return TCL_ERROR; + } + if ((flags & KEY) && (event.xkey.type != MouseWheelEvent)) { + event.xkey.keycode = number; + } else { + goto badopt; + } + break; + } + case EVENT_KEYSYM: { + KeySym keysym; + char *value; + + value = Tcl_GetStringFromObj(valuePtr, NULL); + keysym = TkStringToKeysym(value); + if (keysym == NoSymbol) { + Tcl_AppendResult(interp, "unknown keysym \"", value, "\"", + (char *)NULL); + return TCL_ERROR; + } + + TkpSetKeycodeAndState(tkwin, keysym, &event); + if (event.xkey.keycode == 0) { + Tcl_AppendResult(interp, "no keycode for keysym \"", value, + "\"", (char *)NULL); + return TCL_ERROR; + } + if (!(flags & KEY) || (event.xkey.type == MouseWheelEvent)) { + goto badopt; + } + break; + } + case EVENT_MODE: { + number = TkFindStateNumObj(interp, optionPtr, notifyMode, + valuePtr); + if (number < 0) { + return TCL_ERROR; + } + if (flags & CROSSING) { + event.xcrossing.mode = number; + } else if (flags & FOCUS) { + event.xfocus.mode = number; + } else { + goto badopt; + } + break; + } + case EVENT_OVERRIDE: { + if (Tcl_GetBooleanFromObj(interp, valuePtr, &number) != TCL_OK) { + return TCL_ERROR; + } + if (flags & CREATE) { + event.xcreatewindow.override_redirect = number; + } else if (flags & MAP) { + event.xmap.override_redirect = number; + } else if (flags & REPARENT) { + event.xreparent.override_redirect = number; + } else if (flags & CONFIG) { + event.xconfigure.override_redirect = number; + } else { + goto badopt; + } + break; + } + case EVENT_PLACE: { + number = TkFindStateNumObj(interp, optionPtr, circPlace, + valuePtr); + if (number < 0) { + return TCL_ERROR; + } + if (flags & CIRC) { + event.xcirculate.place = number; + } else { + goto badopt; + } + break; + } + case EVENT_ROOT: { + Window window2; + + if (Blt_GetWindowFromObj(interp, valuePtr, &window2) != TCL_OK) { + return TCL_ERROR; + } + if (flags & (KEY_BUTTON_MOTION_VIRTUAL|CROSSING)) { + event.xkey.root = window2; + } else { + goto badopt; + } + break; + } + case EVENT_ROOTX: { + if (Tk_GetPixelsFromObj(interp, tkwin, valuePtr, &number) != TCL_OK) { + return TCL_ERROR; + } + if (flags & (KEY_BUTTON_MOTION_VIRTUAL|CROSSING)) { + event.xkey.x_root = number; + } else { + goto badopt; + } + break; + } + case EVENT_ROOTY: { + if (Tk_GetPixelsFromObj(interp, tkwin, valuePtr, &number) != TCL_OK) { + return TCL_ERROR; + } + if (flags & (KEY_BUTTON_MOTION_VIRTUAL|CROSSING)) { + event.xkey.y_root = number; + } else { + goto badopt; + } + break; + } + case EVENT_SEND: { + CONST char *value; + + value = Tcl_GetStringFromObj(valuePtr, NULL); + if (isdigit(UCHAR(value[0]))) { + /* + * Allow arbitrary integer values for the field; they + * are needed by a few of the tests in the Tk test suite. + */ + + if (Tcl_GetIntFromObj(interp, valuePtr, &number) + != TCL_OK) { + return TCL_ERROR; + } + } else { + if (Tcl_GetBooleanFromObj(interp, valuePtr, &number) + != TCL_OK) { + return TCL_ERROR; + } + } + event.xany.send_event = number; + break; + } + case EVENT_SERIAL: { + if (Tcl_GetIntFromObj(interp, valuePtr, &number) != TCL_OK) { + return TCL_ERROR; + } + event.xany.serial = number; + break; + } + case EVENT_STATE: { + if (flags & (KEY_BUTTON_MOTION_VIRTUAL|CROSSING)) { + if (Tcl_GetIntFromObj(interp, valuePtr, &number) + != TCL_OK) { + return TCL_ERROR; + } + if (flags & (KEY_BUTTON_MOTION_VIRTUAL)) { + event.xkey.state = number; + } else { + event.xcrossing.state = number; + } + } else if (flags & VISIBILITY) { + number = TkFindStateNumObj(interp, optionPtr, visNotify, + valuePtr); + if (number < 0) { + return TCL_ERROR; + } + event.xvisibility.state = number; + } else { + goto badopt; + } + break; + } + case EVENT_SUBWINDOW: { + Window window2; + + if (Blt_GetWindowFromObj(interp, valuePtr, &window2) != TCL_OK) { + return TCL_ERROR; + } + if (flags & (KEY_BUTTON_MOTION_VIRTUAL|CROSSING)) { + event.xkey.subwindow = window2; + } else { + goto badopt; + } + break; + } + case EVENT_TIME: { + if (Tcl_GetIntFromObj(interp, valuePtr, &number) != TCL_OK) { + return TCL_ERROR; + } + if (flags & (KEY_BUTTON_MOTION_VIRTUAL|CROSSING)) { + event.xkey.time = (Time) number; + } else if (flags & PROP) { + event.xproperty.time = (Time) number; + } else { + goto badopt; + } + break; + } + case EVENT_WIDTH: { + if (Tk_GetPixelsFromObj(interp, tkwin, valuePtr, &number) + != TCL_OK) { + return TCL_ERROR; + } + if (flags & EXPOSE) { + event.xexpose.width = number; + } else if (flags & (CREATE|CONFIG)) { + event.xcreatewindow.width = number; + } else { + goto badopt; + } + break; + } + case EVENT_WINDOW: { + Window window2; + + if (Blt_GetWindowFromObj(interp, valuePtr, &window2) != TCL_OK) { + return TCL_ERROR; + } + if (flags & (CREATE|UNMAP|MAP|REPARENT|CONFIG + |GRAVITY|CIRC)) { + event.xcreatewindow.window = window2; + } else { + goto badopt; + } + break; + } + case EVENT_X: { + if (Tk_GetPixelsFromObj(interp, tkwin, valuePtr, &number) + != TCL_OK) { + return TCL_ERROR; + } + if (flags & (KEY_BUTTON_MOTION_VIRTUAL|CROSSING)) { + event.xkey.x = number; + /* + * Only modify rootx as well if it hasn't been changed. + */ + if (event.xkey.x_root == -1) { + int rootX, rootY; + + Tk_GetRootCoords(tkwin, &rootX, &rootY); + event.xkey.x_root = rootX + number; + } + } else if (flags & EXPOSE) { + event.xexpose.x = number; + } else if (flags & (CREATE|CONFIG|GRAVITY)) { + event.xcreatewindow.x = number; + } else if (flags & REPARENT) { + event.xreparent.x = number; + } else { + goto badopt; + } + break; + } + case EVENT_Y: { + if (Tk_GetPixelsFromObj(interp, tkwin, valuePtr, &number) + != TCL_OK) { + return TCL_ERROR; + } + if (flags & (KEY_BUTTON_MOTION_VIRTUAL|CROSSING)) { + event.xkey.y = number; + /* + * Only modify rooty as well if it hasn't been changed. + */ + if (event.xkey.y_root == -1) { + int rootX, rootY; + + Tk_GetRootCoords(tkwin, &rootX, &rootY); + event.xkey.y_root = rootY + number; + } + } else if (flags & EXPOSE) { + event.xexpose.y = number; + } else if (flags & (CREATE|CONFIG|GRAVITY)) { + event.xcreatewindow.y = number; + } else if (flags & REPARENT) { + event.xreparent.y = number; + } else { + goto badopt; + } + break; + } + } + continue; + + badopt: + Tcl_AppendResult(interp, name, " event doesn't accept \"", + Tcl_GetStringFromObj(optionPtr, NULL), "\" option", + (char *)NULL); + return TCL_ERROR; + } + if (!XSendEvent(display, window, False, pat.eventType, &event)) { + fprintf(stderr, "synthethic event failed\n"); + } + return TCL_OK; +} + +int +Blt_SendEventCmdInitProc(Tcl_Interp *interp) +{ + static Blt_InitCmdSpec cmdSpec = { + "sendevent", SendEventCmd, + }; + return Blt_InitCmd(interp, "::blt", &cmdSpec); +} |