diff options
Diffstat (limited to 'tk8.6/generic/tkBind.c')
| -rw-r--r-- | tk8.6/generic/tkBind.c | 4351 |
1 files changed, 4351 insertions, 0 deletions
diff --git a/tk8.6/generic/tkBind.c b/tk8.6/generic/tkBind.c new file mode 100644 index 0000000..285b3f7 --- /dev/null +++ b/tk8.6/generic/tkBind.c @@ -0,0 +1,4351 @@ +/* + * tkBind.c -- + * + * This file provides functions that associate Tcl commands with X events + * or sequences of X events. + * + * Copyright (c) 1989-1994 The Regents of the University of California. + * Copyright (c) 1994-1997 Sun Microsystems, Inc. + * Copyright (c) 1998 by Scriptics Corporation. + * + * See the file "license.terms" for information on usage and redistribution of + * this file, and for a DISCLAIMER OF ALL WARRANTIES. + */ + +#include "tkInt.h" + +#ifdef _WIN32 +#include "tkWinInt.h" +#elif defined(MAC_OSX_TK) +#include "tkMacOSXInt.h" +#else +#include "tkUnixInt.h" +#endif + +/* + * File structure: + * + * Structure definitions and static variables. + * + * Init/Free this package. + * + * Tcl "bind" command (actually located in tkCmds.c) core implementation, plus + * helpers. + * + * Tcl "event" command implementation, plus helpers. + * + * Package-specific common helpers. + * + * Non-package-specific helpers. + */ + +/* + * 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 structure below represents a binding table. A binding table represents + * a domain in which event bindings may occur. It includes a space of objects + * relative to which events occur (usually windows, but not always), a history + * of recent events in the domain, and a set of mappings that associate + * particular Tcl commands with sequences of events in the domain. Multiple + * binding tables may exist at once, either because there are multiple + * applications open, or because there are multiple domains within an + * application with separate event bindings for each (for example, each canvas + * widget has a separate binding table for associating events with the items + * in the canvas). + * + * Note: it is probably a bad idea to reduce EVENT_BUFFER_SIZE much below 30. + * To see this, consider a triple mouse button click while the Shift key is + * down (and auto-repeating). There may be as many as 3 auto-repeat events + * after each mouse button press or release (see the first large comment block + * within Tk_BindEvent for more on this), for a total of 20 events to cover + * the three button presses and two intervening releases. If you reduce + * EVENT_BUFFER_SIZE too much, shift multi-clicks will be lost. + */ + +#define EVENT_BUFFER_SIZE 30 +typedef struct Tk_BindingTable_ { + XEvent eventRing[EVENT_BUFFER_SIZE]; + /* Circular queue of recent events (higher + * indices are for more recent events). */ + Detail detailRing[EVENT_BUFFER_SIZE]; + /* "Detail" information (keySym, button, + * Tk_Uid, or 0) for each entry in + * eventRing. */ + int curEvent; /* Index in eventRing of most recent event. + * Newer events have higher indices. */ + Tcl_HashTable patternTable; /* Used to map from an event to a list of + * patterns that may match that event. Keys + * are PatternTableKey structs, values are + * (PatSeq *). */ + Tcl_HashTable objectTable; /* Used to map from an object to a list of + * patterns associated with that object. Keys + * are ClientData, values are (PatSeq *). */ + Tcl_Interp *interp; /* Interpreter in which commands are + * executed. */ +} BindingTable; + +/* + * The following structure represents virtual event table. A virtual event + * table provides a way to map from platform-specific physical events such as + * button clicks or key presses to virtual events such as <<Paste>>, + * <<Close>>, or <<ScrollWindow>>. + * + * A virtual event is usually never part of the event stream, but instead is + * synthesized inline by matching low-level events. However, a virtual event + * may be generated by platform-specific code or by Tcl commands. In that case, + * no lookup of the virtual event will need to be done using this table, + * because the virtual event is actually in the event stream. + */ + +typedef struct { + Tcl_HashTable patternTable; /* Used to map from a physical event to a list + * of patterns that may match that event. Keys + * are PatternTableKey structs, values are + * (PatSeq *). */ + Tcl_HashTable nameTable; /* Used to map a virtual event name to the + * array of physical events that can trigger + * it. Keys are the Tk_Uid names of the + * virtual events, values are PhysicalsOwned + * structs. */ +} VirtualEventTable; + +/* + * The following structure is used as a key in a patternTable for both binding + * tables and a virtual event tables. + * + * In a binding table, the object field corresponds to the binding tag for the + * widget whose bindings are being accessed. + * + * In a virtual event table, the object field is always NULL. Virtual events + * are a global definiton and are not tied to a particular binding tag. + * + * The same key is used for both types of pattern tables so that the helper + * functions that traverse and match patterns will work for both binding + * tables and virtual event tables. + */ + +typedef struct { + ClientData object; /* For binding table, identifies the binding + * tag of the object (or class of objects) + * relative to which the event occurred. For + * virtual event table, always NULL. */ + int type; /* Type of event (from X). */ + Detail detail; /* Additional information, such as keysym, + * button, Tk_Uid, or 0 if nothing + * additional. */ +} PatternTableKey; + +/* + * 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). */ +} TkPattern; + +/* + * The following structure defines a pattern sequence, which consists of one + * or more patterns. In order to trigger, a pattern sequence must match the + * most recent X events (first pattern to most recent event, next pattern to + * next event, and so on). It is used as the hash value in a patternTable for + * both binding tables and virtual event tables. + * + * In a binding table, it is the sequence of physical events that make up a + * binding for an object. + * + * In a virtual event table, it is the sequence of physical events that define + * a virtual event. + * + * The same structure is used for both types of pattern tables so that the + * helper functions that traverse and match patterns will work for both + * binding tables and virtual event tables. + */ + +typedef struct PatSeq { + int numPats; /* Number of patterns in sequence (usually + * 1). */ + char *script; /* Binding script to evaluate when sequence + * matches (ckalloc()ed) */ + int flags; /* Miscellaneous flag values; see below for + * definitions. */ + struct PatSeq *nextSeqPtr; /* Next in list of all pattern sequences that + * have the same initial pattern. NULL means + * end of list. */ + Tcl_HashEntry *hPtr; /* Pointer to hash table entry for the initial + * pattern. This is the head of the list of + * which nextSeqPtr forms a part. */ + struct VirtualOwners *voPtr;/* In a binding table, always NULL. In a + * virtual event table, identifies the array + * of virtual events that can be triggered by + * this event. */ + struct PatSeq *nextObjPtr; /* In a binding table, next in list of all + * pattern sequences for the same object (NULL + * for end of list). Needed to implement + * Tk_DeleteAllBindings. In a virtual event + * table, always NULL. */ + TkPattern pats[1]; /* Array of "numPats" patterns. Only one + * element is declared here but in actuality + * enough space will be allocated for + * "numPats" patterns. To match, pats[0] must + * match event n, pats[1] must match event + * n-1, etc. */ +} PatSeq; + +/* + * Flag values for PatSeq structures: + * + * PAT_NEARBY 1 means that all of the events matching this sequence + * must occur with nearby X and Y mouse coordinates and + * close in time. This is typically used to restrict + * multiple button presses. + */ + +#define PAT_NEARBY 0x1 + +/* + * Constants that define how close together two events must be in milliseconds + * or pixels to meet the PAT_NEARBY constraint: + */ + +#define NEARBY_PIXELS 5 +#define NEARBY_MS 500 + +/* + * The following structure keeps track of all the virtual events that are + * associated with a particular physical event. It is pointed to by the voPtr + * field in a PatSeq in the patternTable of a virtual event table. + */ + +typedef struct VirtualOwners { + int numOwners; /* Number of virtual events to trigger. */ + Tcl_HashEntry *owners[1]; /* Array of pointers to entries in nameTable. + * Enough space will actually be allocated for + * numOwners hash entries. */ +} VirtualOwners; + +/* + * The following structure is used in the nameTable of a virtual event table + * to associate a virtual event with all the physical events that can trigger + * it. + */ +typedef struct { + int numOwned; /* Number of physical events owned. */ + PatSeq *patSeqs[1]; /* Array of pointers to physical event + * patterns. Enough space will actually be + * allocated to hold numOwned. */ +} PhysicalsOwned; + +/* + * One of the following structures exists for each interpreter. This structure + * keeps track of the current display and screen in the interpreter, so that a + * command can be invoked whenever the display/screen changes (the command does + * things like point tk::Priv at a display-specific structure). + */ + +typedef struct { + TkDisplay *curDispPtr; /* Display for last binding command invoked in + * this application. */ + int curScreenIndex; /* Index of screen for last binding command */ + int bindingDepth; /* Number of active instances of Tk_BindEvent + * in this application. */ +} ScreenInfo; + +/* + * The following structure keeps track of all the information local to the + * binding package on a per interpreter basis. + */ + +typedef struct TkBindInfo_ { + VirtualEventTable virtualEventTable; + /* The virtual events that exist in this + * interpreter. */ + ScreenInfo screenInfo; /* Keeps track of the current display and + * screen, so it can be restored after a + * binding has executed. */ + int deleted; /* 1 the application has been deleted but the + * structure has been preserved. */ +} BindInfo; + +/* + * In X11R4 and earlier versions, XStringToKeysym is ridiculously slow. The + * data structure and hash table below, along with the code that uses them, + * implement a fast mapping from strings to keysyms. In X11R5 and later + * releases XStringToKeysym is plenty fast so this stuff isn't needed. The + * #define REDO_KEYSYM_LOOKUP is normally undefined, so that XStringToKeysym + * gets used. It can be set in the Makefile to enable the use of the hash + * table below. + */ + +#ifdef REDO_KEYSYM_LOOKUP +typedef struct { + const char *name; /* Name of keysym. */ + KeySym value; /* Numeric identifier for keysym. */ +} KeySymInfo; +static const KeySymInfo keyArray[] = { +#ifndef lint +#include "ks_names.h" +#endif + {NULL, 0} +}; +static Tcl_HashTable keySymTable; /* keyArray hashed by keysym value. */ +static Tcl_HashTable nameTable; /* keyArray hashed by keysym name. */ +#endif /* REDO_KEYSYM_LOOKUP */ + +/* + * Set to non-zero when the package-wide static variables have been + * initialized. + */ + +static int initialized = 0; +TCL_DECLARE_MUTEX(bindMutex) + +/* + * A hash table is kept to map from the string names of event modifiers to + * information about those modifiers. The structure for storing this + * information, and the hash table built at initialization time, are defined + * below. + */ + +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. */ +} ModInfo; + +/* + * Flags for ModInfo 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 +#define TRIPLE 2 +#define QUADRUPLE 4 +#define MULT_CLICKS 7 + +static const ModInfo modArray[] = { + {"Control", ControlMask, 0}, + {"Shift", ShiftMask, 0}, + {"Lock", LockMask, 0}, + {"Meta", META_MASK, 0}, + {"M", META_MASK, 0}, + {"Alt", ALT_MASK, 0}, + {"Extended", EXTENDED_MASK, 0}, + {"B1", Button1Mask, 0}, + {"Button1", Button1Mask, 0}, + {"B2", Button2Mask, 0}, + {"Button2", Button2Mask, 0}, + {"B3", Button3Mask, 0}, + {"Button3", Button3Mask, 0}, + {"B4", Button4Mask, 0}, + {"Button4", Button4Mask, 0}, + {"B5", Button5Mask, 0}, + {"Button5", Button5Mask, 0}, + {"Mod1", Mod1Mask, 0}, + {"M1", Mod1Mask, 0}, + {"Command", Mod1Mask, 0}, + {"Mod2", Mod2Mask, 0}, + {"M2", Mod2Mask, 0}, + {"Option", Mod2Mask, 0}, + {"Mod3", Mod3Mask, 0}, + {"M3", Mod3Mask, 0}, + {"Mod4", Mod4Mask, 0}, + {"M4", Mod4Mask, 0}, + {"Mod5", Mod5Mask, 0}, + {"M5", Mod5Mask, 0}, + {"Double", 0, DOUBLE}, + {"Triple", 0, TRIPLE}, + {"Quadruple", 0, QUADRUPLE}, + {"Any", 0, 0}, /* Ignored: historical relic */ + {NULL, 0, 0} +}; +static Tcl_HashTable modTable; + +/* + * This module also keeps a hash table mapping from event names to information + * about those events. The structure, an array to use to initialize the hash + * table, and the hash table are all defined below. + */ + +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 const EventInfo eventArray[] = { + {"Key", KeyPress, KeyPressMask}, + {"KeyPress", KeyPress, KeyPressMask}, + {"KeyRelease", KeyRelease, KeyPressMask|KeyReleaseMask}, + {"Button", ButtonPress, ButtonPressMask}, + {"ButtonPress", ButtonPress, ButtonPressMask}, + {"ButtonRelease", ButtonRelease, + ButtonPressMask|ButtonReleaseMask}, + {"Motion", MotionNotify, + ButtonPressMask|PointerMotionMask}, + {"Enter", EnterNotify, EnterWindowMask}, + {"Leave", LeaveNotify, LeaveWindowMask}, + {"FocusIn", FocusIn, FocusChangeMask}, + {"FocusOut", FocusOut, FocusChangeMask}, + {"Expose", Expose, ExposureMask}, + {"Visibility", VisibilityNotify, VisibilityChangeMask}, + {"Destroy", DestroyNotify, StructureNotifyMask}, + {"Unmap", UnmapNotify, StructureNotifyMask}, + {"Map", MapNotify, StructureNotifyMask}, + {"Reparent", ReparentNotify, StructureNotifyMask}, + {"Configure", ConfigureNotify, StructureNotifyMask}, + {"Gravity", GravityNotify, StructureNotifyMask}, + {"Circulate", CirculateNotify, StructureNotifyMask}, + {"Property", PropertyNotify, PropertyChangeMask}, + {"Colormap", ColormapNotify, ColormapChangeMask}, + {"Activate", ActivateNotify, ActivateMask}, + {"Deactivate", DeactivateNotify, ActivateMask}, + {"MouseWheel", MouseWheelEvent, MouseWheelMask}, + {"CirculateRequest", CirculateRequest, SubstructureRedirectMask}, + {"ConfigureRequest", ConfigureRequest, SubstructureRedirectMask}, + {"Create", CreateNotify, SubstructureNotifyMask}, + {"MapRequest", MapRequest, SubstructureRedirectMask}, + {"ResizeRequest", ResizeRequest, ResizeRedirectMask}, + {NULL, 0, 0} +}; +static Tcl_HashTable eventTable; + +/* + * 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|VIRTUAL|CROSSING) + +static const int flagArray[TK_LASTEVENT] = { + /* 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, + /* VirtualEvent */ VIRTUAL, + /* Activate */ ACTIVATE, + /* Deactivate */ ACTIVATE, + /* MouseWheel */ KEY +}; + +/* + * The following table is used to map between the location where an generated + * event should be queued and the string used to specify the location. + */ + +static const TkStateMap queuePosition[] = { + {-1, "now"}, + {TCL_QUEUE_HEAD, "head"}, + {TCL_QUEUE_MARK, "mark"}, + {TCL_QUEUE_TAIL, "tail"}, + {-2, NULL} +}; + +/* + * The following tables are used as a two-way map between X's internal numeric + * values for fields in an XEvent and the strings used in Tcl. The tables are + * used both when constructing an XEvent from user input and when providing + * data from an XEvent to the user. + */ + +static const TkStateMap notifyMode[] = { + {NotifyNormal, "NotifyNormal"}, + {NotifyGrab, "NotifyGrab"}, + {NotifyUngrab, "NotifyUngrab"}, + {NotifyWhileGrabbed, "NotifyWhileGrabbed"}, + {-1, NULL} +}; + +static const TkStateMap notifyDetail[] = { + {NotifyAncestor, "NotifyAncestor"}, + {NotifyVirtual, "NotifyVirtual"}, + {NotifyInferior, "NotifyInferior"}, + {NotifyNonlinear, "NotifyNonlinear"}, + {NotifyNonlinearVirtual, "NotifyNonlinearVirtual"}, + {NotifyPointer, "NotifyPointer"}, + {NotifyPointerRoot, "NotifyPointerRoot"}, + {NotifyDetailNone, "NotifyDetailNone"}, + {-1, NULL} +}; + +static const TkStateMap circPlace[] = { + {PlaceOnTop, "PlaceOnTop"}, + {PlaceOnBottom, "PlaceOnBottom"}, + {-1, NULL} +}; + +static const TkStateMap visNotify[] = { + {VisibilityUnobscured, "VisibilityUnobscured"}, + {VisibilityPartiallyObscured, "VisibilityPartiallyObscured"}, + {VisibilityFullyObscured, "VisibilityFullyObscured"}, + {-1, NULL} +}; + +static const TkStateMap configureRequestDetail[] = { + {None, "None"}, + {Above, "Above"}, + {Below, "Below"}, + {BottomIf, "BottomIf"}, + {TopIf, "TopIf"}, + {Opposite, "Opposite"}, + {-1, NULL} +}; + +static const TkStateMap propNotify[] = { + {PropertyNewValue, "NewValue"}, + {PropertyDelete, "Delete"}, + {-1, NULL} +}; + +/* + * Prototypes for local functions defined in this file: + */ + +static void ChangeScreen(Tcl_Interp *interp, char *dispName, + int screenIndex); +static int CreateVirtualEvent(Tcl_Interp *interp, + VirtualEventTable *vetPtr, char *virtString, + const char *eventString); +static int DeleteVirtualEvent(Tcl_Interp *interp, + VirtualEventTable *vetPtr, char *virtString, + const char *eventString); +static void DeleteVirtualEventTable(VirtualEventTable *vetPtr); +static void ExpandPercents(TkWindow *winPtr, const char *before, + XEvent *eventPtr,KeySym keySym, + unsigned int scriptCount, Tcl_DString *dsPtr); +static PatSeq * FindSequence(Tcl_Interp *interp, + Tcl_HashTable *patternTablePtr, ClientData object, + const char *eventString, int create, + int allowVirtual, unsigned long *maskPtr); +static void GetAllVirtualEvents(Tcl_Interp *interp, + VirtualEventTable *vetPtr); +static char * GetField(char *p, char *copy, int size); +static Tcl_Obj * GetPatternObj(PatSeq *psPtr); +static int GetVirtualEvent(Tcl_Interp *interp, + VirtualEventTable *vetPtr, Tcl_Obj *virtName); +static Tk_Uid GetVirtualEventUid(Tcl_Interp *interp, + char *virtString); +static int HandleEventGenerate(Tcl_Interp *interp, Tk_Window main, + int objc, Tcl_Obj *const objv[]); +static void InitVirtualEventTable(VirtualEventTable *vetPtr); +static PatSeq * MatchPatterns(TkDisplay *dispPtr, + BindingTable *bindPtr, PatSeq *psPtr, + PatSeq *bestPtr, ClientData *objectPtr, + PatSeq **sourcePtrPtr); +static int NameToWindow(Tcl_Interp *interp, Tk_Window main, + Tcl_Obj *objPtr, Tk_Window *tkwinPtr); +static int ParseEventDescription(Tcl_Interp *interp, + const char **eventStringPtr, TkPattern *patPtr, + unsigned long *eventMaskPtr); +static void DoWarp(ClientData clientData); + +/* + *--------------------------------------------------------------------------- + * + * TkBindInit -- + * + * This function is called when an application is created. It initializes + * all the structures used by bindings and virtual events. It must be + * called before any other functions in this file are called. + * + * Results: + * None. + * + * Side effects: + * Memory allocated. + * + *--------------------------------------------------------------------------- + */ + +void +TkBindInit( + TkMainInfo *mainPtr) /* The newly created application. */ +{ + BindInfo *bindInfoPtr; + + if (sizeof(XEvent) < sizeof(XVirtualEvent)) { + Tcl_Panic("TkBindInit: virtual events can't be supported"); + } + + /* + * Initialize the static data structures used by the binding package. They + * are only initialized once, no matter how many interps are created. + */ + + if (!initialized) { + Tcl_MutexLock(&bindMutex); + if (!initialized) { + Tcl_HashEntry *hPtr; + const ModInfo *modPtr; + const EventInfo *eiPtr; + int newEntry; +#ifdef REDO_KEYSYM_LOOKUP + const KeySymInfo *kPtr; + + Tcl_InitHashTable(&keySymTable, TCL_STRING_KEYS); + Tcl_InitHashTable(&nameTable, TCL_ONE_WORD_KEYS); + for (kPtr = keyArray; kPtr->name != NULL; kPtr++) { + hPtr = Tcl_CreateHashEntry(&keySymTable, kPtr->name, &newEntry); + Tcl_SetHashValue(hPtr, kPtr->value); + hPtr = Tcl_CreateHashEntry(&nameTable, (char *) kPtr->value, + &newEntry); + if (newEntry) { + Tcl_SetHashValue(hPtr, kPtr->name); + } + } +#endif /* REDO_KEYSYM_LOOKUP */ + + Tcl_InitHashTable(&modTable, TCL_STRING_KEYS); + for (modPtr = modArray; modPtr->name != NULL; modPtr++) { + hPtr = Tcl_CreateHashEntry(&modTable, modPtr->name, &newEntry); + Tcl_SetHashValue(hPtr, modPtr); + } + + Tcl_InitHashTable(&eventTable, TCL_STRING_KEYS); + for (eiPtr = eventArray; eiPtr->name != NULL; eiPtr++) { + hPtr = Tcl_CreateHashEntry(&eventTable, eiPtr->name, &newEntry); + Tcl_SetHashValue(hPtr, eiPtr); + } + initialized = 1; + } + Tcl_MutexUnlock(&bindMutex); + } + + mainPtr->bindingTable = Tk_CreateBindingTable(mainPtr->interp); + + bindInfoPtr = ckalloc(sizeof(BindInfo)); + InitVirtualEventTable(&bindInfoPtr->virtualEventTable); + bindInfoPtr->screenInfo.curDispPtr = NULL; + bindInfoPtr->screenInfo.curScreenIndex = -1; + bindInfoPtr->screenInfo.bindingDepth = 0; + bindInfoPtr->deleted = 0; + mainPtr->bindInfo = bindInfoPtr; + + TkpInitializeMenuBindings(mainPtr->interp, mainPtr->bindingTable); +} + +/* + *--------------------------------------------------------------------------- + * + * TkBindFree -- + * + * This function is called when an application is deleted. It deletes all + * the structures used by bindings and virtual events. + * + * Results: + * None. + * + * Side effects: + * Memory freed. + * + *--------------------------------------------------------------------------- + */ + +void +TkBindFree( + TkMainInfo *mainPtr) /* The newly created application. */ +{ + BindInfo *bindInfoPtr; + + Tk_DeleteBindingTable(mainPtr->bindingTable); + mainPtr->bindingTable = NULL; + + bindInfoPtr = mainPtr->bindInfo; + DeleteVirtualEventTable(&bindInfoPtr->virtualEventTable); + bindInfoPtr->deleted = 1; + Tcl_EventuallyFree(bindInfoPtr, TCL_DYNAMIC); + mainPtr->bindInfo = NULL; +} + +/* + *-------------------------------------------------------------- + * + * Tk_CreateBindingTable -- + * + * Set up a new domain in which event bindings may be created. + * + * Results: + * The return value is a token for the new table, which must be passed to + * functions like Tk_CreateBinding. + * + * Side effects: + * Memory is allocated for the new table. + * + *-------------------------------------------------------------- + */ + +Tk_BindingTable +Tk_CreateBindingTable( + Tcl_Interp *interp) /* Interpreter to associate with the binding + * table: commands are executed in this + * interpreter. */ +{ + BindingTable *bindPtr = ckalloc(sizeof(BindingTable)); + int i; + + /* + * Create and initialize a new binding table. + */ + + for (i = 0; i < EVENT_BUFFER_SIZE; i++) { + bindPtr->eventRing[i].type = -1; + } + bindPtr->curEvent = 0; + Tcl_InitHashTable(&bindPtr->patternTable, + sizeof(PatternTableKey)/sizeof(int)); + Tcl_InitHashTable(&bindPtr->objectTable, TCL_ONE_WORD_KEYS); + bindPtr->interp = interp; + return bindPtr; +} + +/* + *-------------------------------------------------------------- + * + * Tk_DeleteBindingTable -- + * + * Destroy a binding table and free up all its memory. The caller should + * not use bindingTable again after this function returns. + * + * Results: + * None. + * + * Side effects: + * Memory is freed. + * + *-------------------------------------------------------------- + */ + +void +Tk_DeleteBindingTable( + Tk_BindingTable bindPtr) /* Token for the binding table to destroy. */ +{ + PatSeq *psPtr, *nextPtr; + Tcl_HashEntry *hPtr; + Tcl_HashSearch search; + + /* + * Find and delete all of the patterns associated with the binding table. + */ + + for (hPtr = Tcl_FirstHashEntry(&bindPtr->patternTable, &search); + hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) { + for (psPtr = Tcl_GetHashValue(hPtr); psPtr != NULL; psPtr = nextPtr) { + nextPtr = psPtr->nextSeqPtr; + ckfree(psPtr->script); + ckfree(psPtr); + } + } + + /* + * Clean up the rest of the information associated with the binding table. + */ + + Tcl_DeleteHashTable(&bindPtr->patternTable); + Tcl_DeleteHashTable(&bindPtr->objectTable); + ckfree(bindPtr); +} + +/* + *-------------------------------------------------------------- + * + * Tk_CreateBinding -- + * + * Add a binding to a binding table, so that future calls to Tk_BindEvent + * may execute the command in the binding. + * + * Results: + * The return value is 0 if an error occurred while setting up the + * binding. In this case, an error message will be left in the interp's + * result. If all went well then the return value is a mask of the event + * types that must be made available to Tk_BindEvent in order to properly + * detect when this binding triggers. This value can be used to determine + * what events to select for in a window, for example. + * + * Side effects: + * An existing binding on the same event sequence may be replaced. The + * new binding may cause future calls to Tk_BindEvent to behave + * differently than they did previously. + * + *-------------------------------------------------------------- + */ + +unsigned long +Tk_CreateBinding( + Tcl_Interp *interp, /* Used for error reporting. */ + Tk_BindingTable bindPtr, /* Table in which to create binding. */ + ClientData object, /* Token for object with which binding is + * associated. */ + const char *eventString, /* String describing event sequence that + * triggers binding. */ + const char *script, /* Contains Tcl script to execute when + * binding triggers. */ + int append) /* 0 means replace any existing binding for + * eventString; 1 means append to that + * binding. If the existing binding is for a + * callback function and not a Tcl command + * string, the existing binding will always be + * replaced. */ +{ + PatSeq *psPtr; + unsigned long eventMask; + char *newStr, *oldStr; + + if (!*script) { + /* Silently ignore empty scripts -- see SF#3006842 */ + return 1; + } + psPtr = FindSequence(interp, &bindPtr->patternTable, object, eventString, + 1, 1, &eventMask); + if (psPtr == NULL) { + return 0; + } + if (psPtr->script == NULL) { + int isNew; + Tcl_HashEntry *hPtr; + + /* + * This pattern sequence was just created. Link the pattern into the + * list associated with the object, so that if the object goes away, + * these bindings will all automatically be deleted. + */ + + hPtr = Tcl_CreateHashEntry(&bindPtr->objectTable, (char *) object, + &isNew); + if (isNew) { + psPtr->nextObjPtr = NULL; + } else { + psPtr->nextObjPtr = Tcl_GetHashValue(hPtr); + } + Tcl_SetHashValue(hPtr, psPtr); + } + + oldStr = psPtr->script; + if ((append != 0) && (oldStr != NULL)) { + size_t length1 = strlen(oldStr), length2 = strlen(script); + + newStr = ckalloc(length1 + length2 + 2); + memcpy(newStr, oldStr, length1); + newStr[length1] = '\n'; + memcpy(newStr+length1+1, script, length2+1); + } else { + size_t length = strlen(script); + + newStr = ckalloc(length + 1); + memcpy(newStr, script, length+1); + } + if (oldStr != NULL) { + ckfree(oldStr); + } + psPtr->script = newStr; + return eventMask; +} + +/* + *-------------------------------------------------------------- + * + * Tk_DeleteBinding -- + * + * Remove an event binding from a binding table. + * + * Results: + * The result is a standard Tcl return value. If an error occurs then the + * interp's result will contain an error message. + * + * Side effects: + * The binding given by object and eventString is removed from + * bindingTable. + * + *-------------------------------------------------------------- + */ + +int +Tk_DeleteBinding( + Tcl_Interp *interp, /* Used for error reporting. */ + Tk_BindingTable bindPtr, /* Table in which to delete binding. */ + ClientData object, /* Token for object with which binding is + * associated. */ + const char *eventString) /* String describing event sequence that + * triggers binding. */ +{ + PatSeq *psPtr, *prevPtr; + unsigned long eventMask; + Tcl_HashEntry *hPtr; + + psPtr = FindSequence(interp, &bindPtr->patternTable, object, eventString, + 0, 1, &eventMask); + if (psPtr == NULL) { + Tcl_ResetResult(interp); + return TCL_OK; + } + + /* + * Unlink the binding from the list for its object, then from the list for + * its pattern. + */ + + hPtr = Tcl_FindHashEntry(&bindPtr->objectTable, (char *) object); + if (hPtr == NULL) { + Tcl_Panic("Tk_DeleteBinding couldn't find object table entry"); + } + prevPtr = Tcl_GetHashValue(hPtr); + if (prevPtr == psPtr) { + Tcl_SetHashValue(hPtr, psPtr->nextObjPtr); + } else { + for ( ; ; prevPtr = prevPtr->nextObjPtr) { + if (prevPtr == NULL) { + Tcl_Panic("Tk_DeleteBinding couldn't find on object list"); + } + if (prevPtr->nextObjPtr == psPtr) { + prevPtr->nextObjPtr = psPtr->nextObjPtr; + break; + } + } + } + prevPtr = Tcl_GetHashValue(psPtr->hPtr); + if (prevPtr == psPtr) { + if (psPtr->nextSeqPtr == NULL) { + Tcl_DeleteHashEntry(psPtr->hPtr); + } else { + Tcl_SetHashValue(psPtr->hPtr, psPtr->nextSeqPtr); + } + } else { + for ( ; ; prevPtr = prevPtr->nextSeqPtr) { + if (prevPtr == NULL) { + Tcl_Panic("Tk_DeleteBinding couldn't find on hash chain"); + } + if (prevPtr->nextSeqPtr == psPtr) { + prevPtr->nextSeqPtr = psPtr->nextSeqPtr; + break; + } + } + } + + ckfree(psPtr->script); + ckfree(psPtr); + return TCL_OK; +} + +/* + *-------------------------------------------------------------- + * + * Tk_GetBinding -- + * + * Return the script associated with a given event string. + * + * Results: + * The return value is a pointer to the script associated with + * eventString for object in the domain given by bindingTable. If there + * is no binding for eventString, or if eventString is improperly formed, + * then NULL is returned and an error message is left in the interp's + * result. The return value is semi-static: it will persist until the + * binding is changed or deleted. + * + * Side effects: + * None. + * + *-------------------------------------------------------------- + */ + +const char * +Tk_GetBinding( + Tcl_Interp *interp, /* Interpreter for error reporting. */ + Tk_BindingTable bindPtr, /* Table in which to look for binding. */ + ClientData object, /* Token for object with which binding is + * associated. */ + const char *eventString) /* String describing event sequence that + * triggers binding. */ +{ + PatSeq *psPtr; + unsigned long eventMask; + + psPtr = FindSequence(interp, &bindPtr->patternTable, object, eventString, + 0, 1, &eventMask); + if (psPtr == NULL) { + return NULL; + } + return psPtr->script; +} + +/* + *-------------------------------------------------------------- + * + * Tk_GetAllBindings -- + * + * Return a list of event strings for all the bindings associated with a + * given object. + * + * Results: + * There is no return value. The interp's result is modified to hold a + * Tcl list with one entry for each binding associated with object in + * bindingTable. Each entry in the list contains the event string + * associated with one binding. + * + * Side effects: + * None. + * + *-------------------------------------------------------------- + */ + +void +Tk_GetAllBindings( + Tcl_Interp *interp, /* Interpreter returning result or error. */ + Tk_BindingTable bindPtr, /* Table in which to look for bindings. */ + ClientData object) /* Token for object. */ +{ + PatSeq *psPtr; + Tcl_HashEntry *hPtr; + Tcl_Obj *resultObj; + + hPtr = Tcl_FindHashEntry(&bindPtr->objectTable, (char *) object); + if (hPtr == NULL) { + return; + } + + resultObj = Tcl_NewObj(); + for (psPtr = Tcl_GetHashValue(hPtr); psPtr != NULL; + psPtr = psPtr->nextObjPtr) { + /* + * For each binding, output information about each of the patterns in + * its sequence. + */ + + Tcl_ListObjAppendElement(NULL, resultObj, GetPatternObj(psPtr)); + } + Tcl_SetObjResult(interp, resultObj); +} + +/* + *-------------------------------------------------------------- + * + * Tk_DeleteAllBindings -- + * + * Remove all bindings associated with a given object in a given binding + * table. + * + * Results: + * All bindings associated with object are removed from bindingTable. + * + * Side effects: + * None. + * + *-------------------------------------------------------------- + */ + +void +Tk_DeleteAllBindings( + Tk_BindingTable bindPtr, /* Table in which to delete bindings. */ + ClientData object) /* Token for object. */ +{ + PatSeq *psPtr, *prevPtr; + PatSeq *nextPtr; + Tcl_HashEntry *hPtr; + + hPtr = Tcl_FindHashEntry(&bindPtr->objectTable, (char *) object); + if (hPtr == NULL) { + return; + } + for (psPtr = Tcl_GetHashValue(hPtr); psPtr != NULL; + psPtr = nextPtr) { + nextPtr = psPtr->nextObjPtr; + + /* + * Be sure to remove each binding from its hash chain in the pattern + * table. If this is the last pattern in the chain, then delete the + * hash entry too. + */ + + prevPtr = Tcl_GetHashValue(psPtr->hPtr); + if (prevPtr == psPtr) { + if (psPtr->nextSeqPtr == NULL) { + Tcl_DeleteHashEntry(psPtr->hPtr); + } else { + Tcl_SetHashValue(psPtr->hPtr, psPtr->nextSeqPtr); + } + } else { + for ( ; ; prevPtr = prevPtr->nextSeqPtr) { + if (prevPtr == NULL) { + Tcl_Panic("Tk_DeleteAllBindings couldn't find on hash chain"); + } + if (prevPtr->nextSeqPtr == psPtr) { + prevPtr->nextSeqPtr = psPtr->nextSeqPtr; + break; + } + } + } + ckfree(psPtr->script); + ckfree(psPtr); + } + Tcl_DeleteHashEntry(hPtr); +} + +/* + *--------------------------------------------------------------------------- + * + * Tk_BindEvent -- + * + * This function is invoked to process an X event. The event is added to + * those recorded for the binding table. Then each of the objects at + * *objectPtr is checked in order to see if it has a binding that matches + * the recent events. If so, the most specific binding is invoked for + * each object. + * + * Results: + * None. + * + * Side effects: + * Depends on the script associated with the matching binding. + * + * All Tcl binding scripts for each object are accumulated before the + * first binding is evaluated. If the action of a Tcl binding is to + * change or delete a binding, or delete the window associated with the + * binding, all the original Tcl binding scripts will still fire. + * + *--------------------------------------------------------------------------- + */ + +void +Tk_BindEvent( + Tk_BindingTable bindPtr, /* Table in which to look for bindings. */ + XEvent *eventPtr, /* What actually happened. */ + Tk_Window tkwin, /* Window on display where event occurred + * (needed in order to locate display + * information). */ + int numObjects, /* Number of objects at *objectPtr. */ + ClientData *objectPtr) /* Array of one or more objects to check for a + * matching binding. */ +{ + TkDisplay *dispPtr; + ScreenInfo *screenPtr; + BindInfo *bindInfoPtr; + TkDisplay *oldDispPtr; + XEvent *ringPtr; + PatSeq *vMatchDetailList, *vMatchNoDetailList; + int flags, oldScreen; + unsigned int scriptCount; + Tcl_Interp *interp; + Tcl_DString scripts; + Tcl_InterpState interpState; + Detail detail; + char *p, *end; + TkWindow *winPtr = (TkWindow *) tkwin; + PatternTableKey key; + + /* + * Ignore events on windows that don't have names: these are windows like + * wrapper windows that shouldn't be visible to the application. + */ + + if (winPtr->pathName == NULL) { + return; + } + + /* + * Ignore the event completely if it is an Enter, Leave, FocusIn, or + * FocusOut event with detail NotifyInferior. The reason for ignoring + * these events is that we don't want transitions between a window and its + * children to visible to bindings on the parent: this would cause + * problems for mega-widgets, since the internal structure of a + * mega-widget isn't supposed to be visible to people watching the parent. + */ + + if ((eventPtr->type == EnterNotify) || (eventPtr->type == LeaveNotify)) { + if (eventPtr->xcrossing.detail == NotifyInferior) { + return; + } + } + if ((eventPtr->type == FocusIn) || (eventPtr->type == FocusOut)) { + if (eventPtr->xfocus.detail == NotifyInferior) { + return; + } + } + + /* + * Ignore event types which are not in flagArray and all zeroes there. + * Most notably, NoExpose events can fill the ring buffer and disturb + * (thus masking out) event sequences of interest. + */ + + if ((eventPtr->type >= TK_LASTEVENT) || !flagArray[eventPtr->type]) { + return; + } + + dispPtr = ((TkWindow *) tkwin)->dispPtr; + bindInfoPtr = winPtr->mainPtr->bindInfo; + + /* + * Add the new event to the ring of saved events for the binding table. + * Two tricky points: + * + * 1. Combine consecutive MotionNotify events. Do this by putting the new + * event *on top* of the previous event. + * 2. If a modifier key is held down, it auto-repeats to generate + * continuous KeyPress and KeyRelease events. These can flush the event + * ring so that valuable information is lost (such as repeated button + * clicks). To handle this, check for the special case of a modifier + * KeyPress arriving when the previous two events are a KeyRelease and + * KeyPress of the same key. If this happens, mark the most recent + * event (the KeyRelease) invalid and put the new event on top of the + * event before that (the KeyPress). + */ + + if ((eventPtr->type == MotionNotify) + && (bindPtr->eventRing[bindPtr->curEvent].type == MotionNotify)) { + /* + * Don't advance the ring pointer. + */ + } else if (eventPtr->type == KeyPress) { + int i; + + for (i = 0; ; i++) { + if (i >= dispPtr->numModKeyCodes) { + goto advanceRingPointer; + } + if (dispPtr->modKeyCodes[i] == eventPtr->xkey.keycode) { + break; + } + } + ringPtr = &bindPtr->eventRing[bindPtr->curEvent]; + if ((ringPtr->type != KeyRelease) + || (ringPtr->xkey.keycode != eventPtr->xkey.keycode)) { + goto advanceRingPointer; + } + if (bindPtr->curEvent <= 0) { + i = EVENT_BUFFER_SIZE - 1; + } else { + i = bindPtr->curEvent - 1; + } + ringPtr = &bindPtr->eventRing[i]; + if ((ringPtr->type != KeyPress) + || (ringPtr->xkey.keycode != eventPtr->xkey.keycode)) { + goto advanceRingPointer; + } + bindPtr->eventRing[bindPtr->curEvent].type = -1; + bindPtr->curEvent = i; + } else { + + advanceRingPointer: + bindPtr->curEvent++; + if (bindPtr->curEvent >= EVENT_BUFFER_SIZE) { + bindPtr->curEvent = 0; + } + } + ringPtr = &bindPtr->eventRing[bindPtr->curEvent]; + memcpy(ringPtr, eventPtr, sizeof(XEvent)); + detail.clientData = 0; + flags = flagArray[ringPtr->type]; + if (flags & KEY) { + detail.keySym = TkpGetKeySym(dispPtr, ringPtr); + if (detail.keySym == NoSymbol) { + detail.keySym = 0; + } + } else if (flags & BUTTON) { + detail.button = ringPtr->xbutton.button; + } else if (flags & VIRTUAL) { + detail.name = ((XVirtualEvent *) ringPtr)->name; + } + bindPtr->detailRing[bindPtr->curEvent] = detail; + + /* + * Find out if there are any virtual events that correspond to this + * physical event (or sequence of physical events). + */ + + vMatchDetailList = NULL; + vMatchNoDetailList = NULL; + memset(&key, 0, sizeof(key)); + + if (ringPtr->type != VirtualEvent) { + Tcl_HashTable *veptPtr = &bindInfoPtr->virtualEventTable.patternTable; + Tcl_HashEntry *hPtr; + + key.object = NULL; + key.type = ringPtr->type; + key.detail = detail; + + hPtr = Tcl_FindHashEntry(veptPtr, (char *) &key); + if (hPtr != NULL) { + vMatchDetailList = Tcl_GetHashValue(hPtr); + } + + if (key.detail.clientData != 0) { + key.detail.clientData = 0; + hPtr = Tcl_FindHashEntry(veptPtr, (char *) &key); + if (hPtr != NULL) { + vMatchNoDetailList = Tcl_GetHashValue(hPtr); + } + } + } + + /* + * Loop over all the binding tags, finding the binding script or callback + * for each one. Append all of the binding scripts, with %-sequences + * expanded, to "scripts", with null characters separating the scripts for + * each object. + */ + + scriptCount = 0; + Tcl_DStringInit(&scripts); + + for ( ; numObjects > 0; numObjects--, objectPtr++) { + PatSeq *matchPtr = NULL, *sourcePtr = NULL; + Tcl_HashEntry *hPtr; + + /* + * Match the new event against those recorded in the pattern table, + * saving the longest matching pattern. For events with details + * (button and key events), look for a binding for the specific key or + * button. First see if the event matches a physical event that the + * object is interested in, then look for a virtual event. + */ + + key.object = *objectPtr; + key.type = ringPtr->type; + key.detail = detail; + hPtr = Tcl_FindHashEntry(&bindPtr->patternTable, (char *) &key); + if (hPtr != NULL) { + matchPtr = MatchPatterns(dispPtr, bindPtr, Tcl_GetHashValue(hPtr), + matchPtr, NULL, &sourcePtr); + } + + if (vMatchDetailList != NULL) { + matchPtr = MatchPatterns(dispPtr, bindPtr, vMatchDetailList, + matchPtr, objectPtr, &sourcePtr); + } + + /* + * If no match was found, look for a binding for all keys or buttons + * (detail of 0). Again, first match on a virtual event. + */ + + if ((detail.clientData != 0) && (matchPtr == NULL)) { + key.detail.clientData = 0; + hPtr = Tcl_FindHashEntry(&bindPtr->patternTable, (char *) &key); + if (hPtr != NULL) { + matchPtr = MatchPatterns(dispPtr, bindPtr, + Tcl_GetHashValue(hPtr), matchPtr, NULL, &sourcePtr); + } + + if (vMatchNoDetailList != NULL) { + matchPtr = MatchPatterns(dispPtr, bindPtr, vMatchNoDetailList, + matchPtr, objectPtr, &sourcePtr); + } + } + + if (matchPtr != NULL) { + ExpandPercents(winPtr, sourcePtr->script, eventPtr, + detail.keySym, scriptCount++, &scripts); + + /* + * A "" is added to the scripts string to separate the various + * scripts that should be invoked. + */ + + Tcl_DStringAppend(&scripts, "", 1); + } + } + if (Tcl_DStringLength(&scripts) == 0) { + return; + } + + /* + * Now go back through and evaluate the binding for each object, in order, + * dealing with "break" and "continue" exceptions appropriately. + * + * There are two tricks here: + * 1. Bindings can be invoked from in the middle of Tcl commands, where + * the interp's result is significant (for example, a widget might be + * deleted because of an error in creating it, so the result contains + * an error message that is eventually going to be returned by the + * creating command). To preserve the result, we save it in a dynamic + * string. + * 2. The binding's action can potentially delete the binding, so bindPtr + * may not point to anything valid once the action completes. Thus we + * have to save bindPtr->interp in a local variable in order to restore + * the result. + */ + + interp = bindPtr->interp; + + /* + * Save information about the current screen, then invoke a script if the + * screen has changed. + */ + + interpState = Tcl_SaveInterpState(interp, TCL_OK); + screenPtr = &bindInfoPtr->screenInfo; + oldDispPtr = screenPtr->curDispPtr; + oldScreen = screenPtr->curScreenIndex; + if ((dispPtr != screenPtr->curDispPtr) + || (Tk_ScreenNumber(tkwin) != screenPtr->curScreenIndex)) { + screenPtr->curDispPtr = dispPtr; + screenPtr->curScreenIndex = Tk_ScreenNumber(tkwin); + ChangeScreen(interp, dispPtr->name, screenPtr->curScreenIndex); + } + + p = Tcl_DStringValue(&scripts); + end = p + Tcl_DStringLength(&scripts); + + /* + * Be careful when dereferencing screenPtr or bindInfoPtr. If we evaluate + * something that destroys ".", bindInfoPtr would have been freed, but we + * can tell that by first checking to see if winPtr->mainPtr == NULL. + */ + + Tcl_Preserve(bindInfoPtr); + while (p < end) { + int len = (int) strlen(p); + int code; + + if (!bindInfoPtr->deleted) { + screenPtr->bindingDepth++; + } + Tcl_AllowExceptions(interp); + + code = Tcl_EvalEx(interp, p, len, TCL_EVAL_GLOBAL); + p += len + 1; + + if (!bindInfoPtr->deleted) { + screenPtr->bindingDepth--; + } + if (code != TCL_OK) { + if (code == TCL_CONTINUE) { + /* + * Do nothing: just go on to the next command. + */ + } else if (code == TCL_BREAK) { + break; + } else { + Tcl_AddErrorInfo(interp, "\n (command bound to event)"); + Tcl_BackgroundException(interp, code); + break; + } + } + } + + if (!bindInfoPtr->deleted && (screenPtr->bindingDepth != 0) + && ((oldDispPtr != screenPtr->curDispPtr) + || (oldScreen != screenPtr->curScreenIndex))) { + /* + * Some other binding script is currently executing, but its screen is + * no longer current. Change the current display back again. + */ + + screenPtr->curDispPtr = oldDispPtr; + screenPtr->curScreenIndex = oldScreen; + ChangeScreen(interp, oldDispPtr->name, oldScreen); + } + (void) Tcl_RestoreInterpState(interp, interpState); + Tcl_DStringFree(&scripts); + + Tcl_Release(bindInfoPtr); +} + +/* + *---------------------------------------------------------------------- + * + * MatchPatterns -- + * + * Given a list of pattern sequences and a list of recent events, return + * the pattern sequence that best matches the event list, if there is + * one. + * + * This function is used in two different ways. In the simplest use, + * "object" is NULL and psPtr is a list of pattern sequences, each of + * which corresponds to a binding. In this case, the function finds the + * pattern sequences that match the event list and returns the most + * specific of those, if there is more than one. + * + * In the second case, psPtr is a list of pattern sequences, each of + * which corresponds to a definition for a virtual binding. In order for + * one of these sequences to "match", it must match the events (as above) + * but in addition there must be a binding for its associated virtual + * event on the current object. The "object" argument indicates which + * object the binding must be for. + * + * Results: + + * The return value is NULL if bestPtr is NULL and no pattern matches the + * recent events from bindPtr. Otherwise the return value is the most + * specific pattern sequence among bestPtr and all those at psPtr that + * match the event list and object. If a pattern sequence other than + * bestPtr is returned, then *bestCommandPtr is filled in with a pointer + * to the command from the best sequence. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static PatSeq * +MatchPatterns( + TkDisplay *dispPtr, /* Display from which the event came. */ + BindingTable *bindPtr, /* Information about binding table, such as + * ring of recent events. */ + PatSeq *psPtr, /* List of pattern sequences. */ + PatSeq *bestPtr, /* The best match seen so far, from a previous + * call to this function. NULL means no prior + * best match. */ + ClientData *objectPtr, /* If NULL, the sequences at psPtr correspond + * to "normal" bindings. If non-NULL, the + * sequences at psPtr correspond to virtual + * bindings; in order to match each sequence + * must correspond to a virtual binding for + * which a binding exists for object in + * bindPtr. */ + PatSeq **sourcePtrPtr) /* Filled with the pattern sequence that + * contains the eventProc and clientData + * associated with the best match. If this + * differs from the return value, it is the + * virtual event that most closely matched the + * return value (a physical event). Not + * modified unless a result other than bestPtr + * is returned. */ +{ + PatSeq *matchPtr, *bestSourcePtr, *sourcePtr; + + bestSourcePtr = *sourcePtrPtr; + + /* + * Iterate over all the pattern sequences. + */ + + for ( ; psPtr != NULL; psPtr = psPtr->nextSeqPtr) { + XEvent *eventPtr = &bindPtr->eventRing[bindPtr->curEvent]; + Detail *detailPtr = &bindPtr->detailRing[bindPtr->curEvent]; + TkPattern *patPtr = psPtr->pats; + Window window = eventPtr->xany.window; + int patCount, ringCount, flags, state, modMask, i; + + /* + * Iterate over all the patterns in a sequence to be sure that they + * all match. + */ + + patCount = psPtr->numPats; + ringCount = EVENT_BUFFER_SIZE; + while (patCount > 0) { + if (ringCount <= 0) { + goto nextSequence; + } + if (eventPtr->xany.type != patPtr->eventType) { + /* + * Most of the event types are considered superfluous in that + * they are ignored if they occur in the middle of a pattern + * sequence and have mismatching types. The only ones that + * cannot be ignored are ButtonPress and ButtonRelease events + * (if the next event in the pattern is a KeyPress or + * KeyRelease) and KeyPress and KeyRelease events (if the next + * pattern event is a ButtonPress or ButtonRelease). Here are + * some tricky cases to consider: + * 1. Double-Button or Double-Key events. + * 2. Double-ButtonRelease or Double-KeyRelease events. + * 3. The arrival of various events like Enter and Leave and + * FocusIn and GraphicsExpose between two button presses or + * key presses. + * 4. Modifier keys like Shift and Control shouldn't generate + * conflicts with button events. + */ + + if ((patPtr->eventType == KeyPress) + || (patPtr->eventType == KeyRelease)) { + if ((eventPtr->xany.type == ButtonPress) + || (eventPtr->xany.type == ButtonRelease)) { + goto nextSequence; + } + } else if ((patPtr->eventType == ButtonPress) + || (patPtr->eventType == ButtonRelease)) { + if ((eventPtr->xany.type == KeyPress) + || (eventPtr->xany.type == KeyRelease)) { + /* + * Ignore key events if they are modifier keys. + */ + + for (i = 0; i < dispPtr->numModKeyCodes; i++) { + if (dispPtr->modKeyCodes[i] + == eventPtr->xkey.keycode) { + /* + * This key is a modifier key, so ignore it. + */ + + goto nextEvent; + } + } + goto nextSequence; + } + } + goto nextEvent; + } + if (eventPtr->xany.type == CreateNotify + && eventPtr->xcreatewindow.parent != window) { + goto nextSequence; + } else if (eventPtr->xany.window != window) { + goto nextSequence; + } + + /* + * Note: it's important for the keysym check to go before the + * modifier check, so we can ignore unwanted modifier keys before + * choking on the modifier check. + */ + + if ((patPtr->detail.clientData != 0) + && (patPtr->detail.clientData != detailPtr->clientData)) { + /* + * The detail appears not to match. However, if the event is a + * KeyPress for a modifier key then just ignore the event. + * Otherwise event sequences like "aD" never match because the + * shift key goes down between the "a" and the "D". + */ + + if (eventPtr->xany.type == KeyPress) { + for (i = 0; i < dispPtr->numModKeyCodes; i++) { + if (dispPtr->modKeyCodes[i] == eventPtr->xkey.keycode) { + goto nextEvent; + } + } + } + goto nextSequence; + } + flags = flagArray[eventPtr->type]; + if (flags & KEY_BUTTON_MOTION_VIRTUAL) { + state = eventPtr->xkey.state; + } else if (flags & CROSSING) { + state = eventPtr->xcrossing.state; + } else { + state = 0; + } + if (patPtr->needMods != 0) { + modMask = patPtr->needMods; + if ((modMask & META_MASK) && (dispPtr->metaModMask != 0)) { + modMask = (modMask & ~META_MASK) | dispPtr->metaModMask; + } + if ((modMask & ALT_MASK) && (dispPtr->altModMask != 0)) { + modMask = (modMask & ~ALT_MASK) | dispPtr->altModMask; + } + + if ((state & META_MASK) && (dispPtr->metaModMask != 0)) { + state = (state & ~META_MASK) | dispPtr->metaModMask; + } + if ((state & ALT_MASK) && (dispPtr->altModMask != 0)) { + state = (state & ~ALT_MASK) | dispPtr->altModMask; + } + + if ((state & modMask) != modMask) { + goto nextSequence; + } + } + if (psPtr->flags & PAT_NEARBY) { + XEvent *firstPtr = &bindPtr->eventRing[bindPtr->curEvent]; + int timeDiff; + + timeDiff = (Time) firstPtr->xkey.time - eventPtr->xkey.time; + if ((firstPtr->xkey.x_root + < (eventPtr->xkey.x_root - NEARBY_PIXELS)) + || (firstPtr->xkey.x_root + > (eventPtr->xkey.x_root + NEARBY_PIXELS)) + || (firstPtr->xkey.y_root + < (eventPtr->xkey.y_root - NEARBY_PIXELS)) + || (firstPtr->xkey.y_root + > (eventPtr->xkey.y_root + NEARBY_PIXELS)) + || (timeDiff > NEARBY_MS)) { + goto nextSequence; + } + } + patPtr++; + patCount--; + nextEvent: + if (eventPtr == bindPtr->eventRing) { + eventPtr = &bindPtr->eventRing[EVENT_BUFFER_SIZE-1]; + detailPtr = &bindPtr->detailRing[EVENT_BUFFER_SIZE-1]; + } else { + eventPtr--; + detailPtr--; + } + ringCount--; + } + + matchPtr = psPtr; + sourcePtr = psPtr; + + if (objectPtr != NULL) { + int iVirt; + VirtualOwners *voPtr; + PatternTableKey key; + + /* + * The sequence matches the physical constraints. Is this object + * interested in any of the virtual events that correspond to this + * sequence? + */ + + voPtr = psPtr->voPtr; + + memset(&key, 0, sizeof(key)); + key.object = *objectPtr; + key.type = VirtualEvent; + key.detail.clientData = 0; + + for (iVirt = 0; iVirt < voPtr->numOwners; iVirt++) { + Tcl_HashEntry *hPtr = voPtr->owners[iVirt]; + + key.detail.name = (Tk_Uid) Tcl_GetHashKey(hPtr->tablePtr, + hPtr); + hPtr = Tcl_FindHashEntry(&bindPtr->patternTable, + (char *) &key); + if (hPtr != NULL) { + /* + * This tag is interested in this virtual event and its + * corresponding physical event is a good match with the + * virtual event's definition. + */ + + PatSeq *virtMatchPtr = Tcl_GetHashValue(hPtr); + + if ((virtMatchPtr->numPats != 1) + || (virtMatchPtr->nextSeqPtr != NULL)) { + Tcl_Panic("MatchPattern: badly constructed virtual event"); + } + sourcePtr = virtMatchPtr; + goto match; + } + } + + /* + * The physical event matches a virtual event's definition, but + * the tag isn't interested in it. + */ + + goto nextSequence; + } + match: + + /* + * This sequence matches. If we've already got another match, pick + * whichever is most specific. Detail is most important, then + * needMods. + */ + + if (bestPtr != NULL) { + TkPattern *patPtr2; + + if (matchPtr->numPats != bestPtr->numPats) { + if (bestPtr->numPats > matchPtr->numPats) { + goto nextSequence; + } else { + goto newBest; + } + } + for (i = 0, patPtr = matchPtr->pats, patPtr2 = bestPtr->pats; + i < matchPtr->numPats; i++, patPtr++, patPtr2++) { + if (patPtr->detail.clientData != patPtr2->detail.clientData) { + if (patPtr->detail.clientData == 0) { + goto nextSequence; + } else { + goto newBest; + } + } + if (patPtr->needMods != patPtr2->needMods) { + if ((patPtr->needMods & patPtr2->needMods) + == patPtr->needMods) { + goto nextSequence; + } else if ((patPtr->needMods & patPtr2->needMods) + == patPtr2->needMods) { + goto newBest; + } + } + } + + /* + * Tie goes to current best pattern. + * + * (1) For virtual vs. virtual, the least recently defined virtual + * wins, because virtuals are examined in order of definition. + * This order is _not_ guaranteed in the documentation. + * + * (2) For virtual vs. physical, the physical wins because all the + * physicals are examined before the virtuals. This order is + * guaranteed in the documentation. + * + * (3) For physical vs. physical pattern, the most recently + * defined physical wins, because physicals are examined in + * reverse order of definition. This order is guaranteed in the + * documentation. + */ + + goto nextSequence; + } + newBest: + bestPtr = matchPtr; + bestSourcePtr = sourcePtr; + + nextSequence: + continue; + } + + *sourcePtrPtr = bestSourcePtr; + return bestPtr; +} + +/* + *-------------------------------------------------------------- + * + * ExpandPercents -- + * + * Given a command and an event, produce a new command by replacing % + * constructs in the original command with information from the X event. + * + * Results: + * The new expanded command is appended to the dynamic string given by + * dsPtr. + * + * Side effects: + * None. + * + *-------------------------------------------------------------- + */ + +static void +ExpandPercents( + TkWindow *winPtr, /* Window where event occurred: needed to get + * input context. */ + const char *before, /* Command containing percent expressions to + * be replaced. */ + XEvent *eventPtr, /* X event containing information to be used + * in % replacements. */ + KeySym keySym, /* KeySym: only relevant for KeyPress and + * KeyRelease events). */ + unsigned int scriptCount, /* The number of script-based binding patterns + * matched so far for this event. */ + Tcl_DString *dsPtr) /* Dynamic string in which to append new + * command. */ +{ + int spaceNeeded, cvtFlags; /* Used to substitute string as proper Tcl + * list element. */ + int number, flags, length; +#define NUM_SIZE 40 + const char *string; + Tcl_DString buf; + char numStorage[NUM_SIZE+1]; + + Tcl_DStringInit(&buf); + + if (eventPtr->type < TK_LASTEVENT) { + flags = flagArray[eventPtr->type]; + } else { + flags = 0; + } + + while (1) { + /* + * Find everything up to the next % character and append it to the + * result string. + */ + + for (string = before; (*string != 0) && (*string != '%'); string++) { + /* Empty loop body. */ + } + if (string != before) { + Tcl_DStringAppend(dsPtr, before, (int) (string-before)); + before = string; + } + if (*before == 0) { + break; + } + + /* + * There's a percent sequence here. Process it. + */ + + number = 0; + string = "??"; + switch (before[1]) { + case '#': + number = eventPtr->xany.serial; + goto doNumber; + case 'a': + if (flags & CONFIG) { + TkpPrintWindowId(numStorage, eventPtr->xconfigure.above); + string = numStorage; + } + goto doString; + case 'b': + if (flags & BUTTON) { + number = eventPtr->xbutton.button; + goto doNumber; + } + goto doString; + case 'c': + if (flags & EXPOSE) { + number = eventPtr->xexpose.count; + goto doNumber; + } + goto doString; + case 'd': + if (flags & (CROSSING|FOCUS)) { + if (flags & FOCUS) { + number = eventPtr->xfocus.detail; + } else { + number = eventPtr->xcrossing.detail; + } + string = TkFindStateString(notifyDetail, number); + } else if (flags & CONFIGREQ) { + if (eventPtr->xconfigurerequest.value_mask & CWStackMode) { + string = TkFindStateString(configureRequestDetail, + eventPtr->xconfigurerequest.detail); + } else { + string = ""; + } + } else if (flags & VIRTUAL) { + XVirtualEvent *vePtr = (XVirtualEvent *) eventPtr; + + if (vePtr->user_data != NULL) { + string = Tcl_GetString(vePtr->user_data); + } else { + string = ""; + } + } + goto doString; + case 'f': + if (flags & CROSSING) { + number = eventPtr->xcrossing.focus; + goto doNumber; + } + goto doString; + case 'h': + if (flags & EXPOSE) { + number = eventPtr->xexpose.height; + } else if (flags & CONFIG) { + number = eventPtr->xconfigure.height; + } else if (flags & CREATE) { + number = eventPtr->xcreatewindow.height; + } else if (flags & CONFIGREQ) { + number = eventPtr->xconfigurerequest.height; + } else if (flags & RESIZEREQ) { + number = eventPtr->xresizerequest.height; + } else { + goto doString; + } + goto doNumber; + case 'i': + if (flags & CREATE) { + TkpPrintWindowId(numStorage, eventPtr->xcreatewindow.window); + } else if (flags & CONFIGREQ) { + TkpPrintWindowId(numStorage, + eventPtr->xconfigurerequest.window); + } else if (flags & MAPREQ) { + TkpPrintWindowId(numStorage, eventPtr->xmaprequest.window); + } else { + TkpPrintWindowId(numStorage, eventPtr->xany.window); + } + string = numStorage; + goto doString; + case 'k': + if ((flags & KEY) && (eventPtr->type != MouseWheelEvent)) { + number = eventPtr->xkey.keycode; + goto doNumber; + } + goto doString; + case 'm': + if (flags & CROSSING) { + number = eventPtr->xcrossing.mode; + string = TkFindStateString(notifyMode, number); + } else if (flags & FOCUS) { + number = eventPtr->xfocus.mode; + string = TkFindStateString(notifyMode, number); + } + goto doString; + case 'o': + if (flags & CREATE) { + number = eventPtr->xcreatewindow.override_redirect; + } else if (flags & MAP) { + number = eventPtr->xmap.override_redirect; + } else if (flags & REPARENT) { + number = eventPtr->xreparent.override_redirect; + } else if (flags & CONFIG) { + number = eventPtr->xconfigure.override_redirect; + } else { + goto doString; + } + goto doNumber; + case 'p': + if (flags & CIRC) { + string = TkFindStateString(circPlace, + eventPtr->xcirculate.place); + } else if (flags & CIRCREQ) { + string = TkFindStateString(circPlace, + eventPtr->xcirculaterequest.place); + } + goto doString; + case 's': + if (flags & KEY_BUTTON_MOTION_VIRTUAL) { + number = eventPtr->xkey.state; + goto doNumber; + } else if (flags & CROSSING) { + number = eventPtr->xcrossing.state; + goto doNumber; + } else if (flags & PROP) { + string = TkFindStateString(propNotify, + eventPtr->xproperty.state); + } else if (flags & VISIBILITY) { + string = TkFindStateString(visNotify, + eventPtr->xvisibility.state); + } + goto doString; + case 't': + if (flags & KEY_BUTTON_MOTION_VIRTUAL) { + number = (int) eventPtr->xkey.time; + } else if (flags & CROSSING) { + number = (int) eventPtr->xcrossing.time; + } else if (flags & PROP) { + number = (int) eventPtr->xproperty.time; + } else { + goto doString; + } + goto doNumber; + case 'v': + number = eventPtr->xconfigurerequest.value_mask; + goto doNumber; + case 'w': + if (flags & EXPOSE) { + number = eventPtr->xexpose.width; + } else if (flags & CONFIG) { + number = eventPtr->xconfigure.width; + } else if (flags & CREATE) { + number = eventPtr->xcreatewindow.width; + } else if (flags & CONFIGREQ) { + number = eventPtr->xconfigurerequest.width; + } else if (flags & RESIZEREQ) { + number = eventPtr->xresizerequest.width; + } else { + goto doString; + } + goto doNumber; + case 'x': + if (flags & KEY_BUTTON_MOTION_VIRTUAL) { + number = eventPtr->xkey.x; + } else if (flags & CROSSING) { + number = eventPtr->xcrossing.x; + } else if (flags & EXPOSE) { + number = eventPtr->xexpose.x; + } else if (flags & (CREATE|CONFIG|GRAVITY)) { + number = eventPtr->xcreatewindow.x; + } else if (flags & REPARENT) { + number = eventPtr->xreparent.x; + } else if (flags & CREATE) { + number = eventPtr->xcreatewindow.x; + } else if (flags & CONFIGREQ) { + number = eventPtr->xconfigurerequest.x; + } else { + goto doString; + } + goto doNumber; + case 'y': + if (flags & KEY_BUTTON_MOTION_VIRTUAL) { + number = eventPtr->xkey.y; + } else if (flags & EXPOSE) { + number = eventPtr->xexpose.y; + } else if (flags & (CREATE|CONFIG|GRAVITY)) { + number = eventPtr->xcreatewindow.y; + } else if (flags & REPARENT) { + number = eventPtr->xreparent.y; + } else if (flags & CROSSING) { + number = eventPtr->xcrossing.y; + } else if (flags & CREATE) { + number = eventPtr->xcreatewindow.y; + } else if (flags & CONFIGREQ) { + number = eventPtr->xconfigurerequest.y; + } else { + goto doString; + } + goto doNumber; + case 'A': + if ((flags & KEY) && (eventPtr->type != MouseWheelEvent)) { + Tcl_DStringFree(&buf); + string = TkpGetString(winPtr, eventPtr, &buf); + } + goto doString; + case 'B': + if (flags & CREATE) { + number = eventPtr->xcreatewindow.border_width; + } else if (flags & CONFIGREQ) { + number = eventPtr->xconfigurerequest.border_width; + } else if (flags & CONFIG) { + number = eventPtr->xconfigure.border_width; + } else { + goto doString; + } + goto doNumber; + case 'D': + /* + * This is used only by the MouseWheel event. + */ + + if ((flags & KEY) && (eventPtr->type == MouseWheelEvent)) { + number = eventPtr->xkey.keycode; + goto doNumber; + } + goto doString; + case 'E': + number = (int) eventPtr->xany.send_event; + goto doNumber; + case 'K': + if ((flags & KEY) && (eventPtr->type != MouseWheelEvent)) { + const char *name = TkKeysymToString(keySym); + + if (name != NULL) { + string = name; + } + } + goto doString; + case 'M': + number = scriptCount; + goto doNumber; + case 'N': + if ((flags & KEY) && (eventPtr->type != MouseWheelEvent)) { + number = (int) keySym; + goto doNumber; + } + goto doString; + case 'P': + if (flags & PROP) { + string = Tk_GetAtomName((Tk_Window) winPtr, + eventPtr->xproperty.atom); + } + goto doString; + case 'R': + if (flags & KEY_BUTTON_MOTION_CROSSING) { + TkpPrintWindowId(numStorage, eventPtr->xkey.root); + string = numStorage; + } + goto doString; + case 'S': + if (flags & KEY_BUTTON_MOTION_CROSSING) { + TkpPrintWindowId(numStorage, eventPtr->xkey.subwindow); + string = numStorage; + } + goto doString; + case 'T': + number = eventPtr->type; + goto doNumber; + case 'W': { + Tk_Window tkwin; + + tkwin = Tk_IdToWindow(eventPtr->xany.display, + eventPtr->xany.window); + if (tkwin != NULL) { + string = Tk_PathName(tkwin); + } else { + string = "??"; + } + goto doString; + } + case 'X': + if (flags & KEY_BUTTON_MOTION_CROSSING) { + + number = eventPtr->xkey.x_root; + Tk_IdToWindow(eventPtr->xany.display, + eventPtr->xany.window); + goto doNumber; + } + goto doString; + case 'Y': + if (flags & KEY_BUTTON_MOTION_CROSSING) { + + number = eventPtr->xkey.y_root; + Tk_IdToWindow(eventPtr->xany.display, + eventPtr->xany.window); + goto doNumber; + } + goto doString; + default: + numStorage[0] = before[1]; + numStorage[1] = '\0'; + string = numStorage; + goto doString; + } + + doNumber: + sprintf(numStorage, "%d", number); + string = numStorage; + + doString: + spaceNeeded = Tcl_ScanElement(string, &cvtFlags); + length = Tcl_DStringLength(dsPtr); + Tcl_DStringSetLength(dsPtr, length + spaceNeeded); + spaceNeeded = Tcl_ConvertElement(string, + Tcl_DStringValue(dsPtr) + length, + cvtFlags | TCL_DONT_USE_BRACES); + Tcl_DStringSetLength(dsPtr, length + spaceNeeded); + before += 2; + } + Tcl_DStringFree(&buf); +} + +/* + *---------------------------------------------------------------------- + * + * ChangeScreen -- + * + * This function is invoked whenever the current screen changes in an + * application. It invokes a Tcl command named "tk::ScreenChanged", + * passing it the screen name as argument. tk::ScreenChanged does things + * like making the tk::Priv variable point to an array for the current + * display. + * + * Results: + * None. + * + * Side effects: + * Depends on what tk::ScreenChanged does. If an error occurs then + * bgerror will be invoked. + * + *---------------------------------------------------------------------- + */ + +static void +ChangeScreen( + Tcl_Interp *interp, /* Interpreter in which to invoke command. */ + char *dispName, /* Name of new display. */ + int screenIndex) /* Index of new screen. */ +{ + Tcl_Obj *cmdObj = Tcl_ObjPrintf("::tk::ScreenChanged %s.%d", + dispName, screenIndex); + int code; + + Tcl_IncrRefCount(cmdObj); + code = Tcl_EvalObjEx(interp, cmdObj, TCL_EVAL_GLOBAL); + if (code != TCL_OK) { + Tcl_AddErrorInfo(interp, + "\n (changing screen in event binding)"); + Tcl_BackgroundException(interp, code); + } + Tcl_DecrRefCount(cmdObj); +} + +/* + *---------------------------------------------------------------------- + * + * Tk_EventCmd -- + * + * This function is invoked to process the "event" Tcl command. It is + * used to define and generate events. + * + * Results: + * A standard Tcl result. + * + * Side effects: + * See the user documentation. + * + *---------------------------------------------------------------------- + */ + +int +Tk_EventObjCmd( + ClientData clientData, /* Main window associated with interpreter. */ + Tcl_Interp *interp, /* Current interpreter. */ + int objc, /* Number of arguments. */ + Tcl_Obj *const objv[]) /* Argument objects. */ +{ + int index, i; + char *name; + const char *event; + Tk_Window tkwin = clientData; + TkBindInfo bindInfo = ((TkWindow *) tkwin)->mainPtr->bindInfo; + VirtualEventTable *vetPtr = &bindInfo->virtualEventTable; + static const char *const optionStrings[] = { + "add", "delete", "generate", "info", + NULL + }; + enum options { + EVENT_ADD, EVENT_DELETE, EVENT_GENERATE, EVENT_INFO + }; + + + if (objc < 2) { + Tcl_WrongNumArgs(interp, 1, objv, "option ?arg?"); + return TCL_ERROR; + } + if (Tcl_GetIndexFromObjStruct(interp, objv[1], optionStrings, + sizeof(char *), "option", 0, &index) != TCL_OK) { + return TCL_ERROR; + } + + switch ((enum options) index) { + case EVENT_ADD: + if (objc < 4) { + Tcl_WrongNumArgs(interp, 2, objv, + "virtual sequence ?sequence ...?"); + return TCL_ERROR; + } + name = Tcl_GetString(objv[2]); + for (i = 3; i < objc; i++) { + event = Tcl_GetString(objv[i]); + if (CreateVirtualEvent(interp, vetPtr, name, event) != TCL_OK) { + return TCL_ERROR; + } + } + break; + case EVENT_DELETE: + if (objc < 3) { + Tcl_WrongNumArgs(interp, 2, objv, "virtual ?sequence ...?"); + return TCL_ERROR; + } + name = Tcl_GetString(objv[2]); + if (objc == 3) { + return DeleteVirtualEvent(interp, vetPtr, name, NULL); + } + for (i = 3; i < objc; i++) { + event = Tcl_GetString(objv[i]); + if (DeleteVirtualEvent(interp, vetPtr, name, event) != TCL_OK) { + return TCL_ERROR; + } + } + break; + case EVENT_GENERATE: + if (objc < 4) { + Tcl_WrongNumArgs(interp, 2, objv, + "window event ?-option value ...?"); + return TCL_ERROR; + } + return HandleEventGenerate(interp, tkwin, objc - 2, objv + 2); + case EVENT_INFO: + if (objc == 2) { + GetAllVirtualEvents(interp, vetPtr); + return TCL_OK; + } else if (objc == 3) { + return GetVirtualEvent(interp, vetPtr, objv[2]); + } else { + Tcl_WrongNumArgs(interp, 2, objv, "?virtual?"); + return TCL_ERROR; + } + } + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * InitVirtualEventTable -- + * + * Given storage for a virtual event table, set up the fields to prepare + * a new domain in which virtual events may be defined. + * + * Results: + * None. + * + * Side effects: + * *vetPtr is now initialized. + * + *--------------------------------------------------------------------------- + */ + +static void +InitVirtualEventTable( + VirtualEventTable *vetPtr) /* Pointer to virtual event table. Memory is + * supplied by the caller. */ +{ + Tcl_InitHashTable(&vetPtr->patternTable, + sizeof(PatternTableKey) / sizeof(int)); + Tcl_InitHashTable(&vetPtr->nameTable, TCL_ONE_WORD_KEYS); +} + +/* + *--------------------------------------------------------------------------- + * + * DeleteVirtualEventTable -- + * + * Delete the contents of a virtual event table. The caller is + * responsible for freeing any memory used by the table itself. + * + * Results: + * None. + * + * Side effects: + * Memory is freed. + * + *--------------------------------------------------------------------------- + */ + +static void +DeleteVirtualEventTable( + VirtualEventTable *vetPtr) /* The virtual event table to delete. */ +{ + Tcl_HashEntry *hPtr; + Tcl_HashSearch search; + PatSeq *psPtr, *nextPtr; + + hPtr = Tcl_FirstHashEntry(&vetPtr->patternTable, &search); + for ( ; hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) { + psPtr = Tcl_GetHashValue(hPtr); + for ( ; psPtr != NULL; psPtr = nextPtr) { + nextPtr = psPtr->nextSeqPtr; + ckfree(psPtr->voPtr); + ckfree(psPtr); + } + } + Tcl_DeleteHashTable(&vetPtr->patternTable); + + hPtr = Tcl_FirstHashEntry(&vetPtr->nameTable, &search); + for ( ; hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) { + ckfree(Tcl_GetHashValue(hPtr)); + } + Tcl_DeleteHashTable(&vetPtr->nameTable); +} + +/* + *---------------------------------------------------------------------- + * + * CreateVirtualEvent -- + * + * Add a new definition for a virtual event. If the virtual event is + * already defined, the new definition augments those that already exist. + * + * Results: + * The return value is TCL_ERROR if an error occured while creating the + * virtual binding. In this case, an error message will be left in the + * interp's result. If all went well then the return value is TCL_OK. + * + * Side effects: + * The virtual event may cause future calls to Tk_BindEvent to behave + * differently than they did previously. + * + *---------------------------------------------------------------------- + */ + +static int +CreateVirtualEvent( + Tcl_Interp *interp, /* Used for error reporting. */ + VirtualEventTable *vetPtr, /* Table in which to augment virtual event. */ + char *virtString, /* Name of new virtual event. */ + const char *eventString) /* String describing physical event that + * triggers virtual event. */ +{ + PatSeq *psPtr; + int dummy; + Tcl_HashEntry *vhPtr; + unsigned long eventMask; + PhysicalsOwned *poPtr; + VirtualOwners *voPtr; + Tk_Uid virtUid; + + virtUid = GetVirtualEventUid(interp, virtString); + if (virtUid == NULL) { + return TCL_ERROR; + } + + /* + * Find/create physical event + */ + + psPtr = FindSequence(interp, &vetPtr->patternTable, NULL, eventString, + 1, 0, &eventMask); + if (psPtr == NULL) { + return TCL_ERROR; + } + + /* + * Find/create virtual event. + */ + + vhPtr = Tcl_CreateHashEntry(&vetPtr->nameTable, virtUid, &dummy); + + /* + * Make virtual event own the physical event. + */ + + poPtr = Tcl_GetHashValue(vhPtr); + if (poPtr == NULL) { + poPtr = ckalloc(sizeof(PhysicalsOwned)); + poPtr->numOwned = 0; + } else { + /* + * See if this virtual event is already defined for this physical + * event and just return if it is. + */ + + int i; + + for (i = 0; i < poPtr->numOwned; i++) { + if (poPtr->patSeqs[i] == psPtr) { + return TCL_OK; + } + } + poPtr = ckrealloc(poPtr, sizeof(PhysicalsOwned) + + poPtr->numOwned * sizeof(PatSeq *)); + } + Tcl_SetHashValue(vhPtr, poPtr); + poPtr->patSeqs[poPtr->numOwned] = psPtr; + poPtr->numOwned++; + + /* + * Make physical event so it can trigger the virtual event. + */ + + voPtr = psPtr->voPtr; + if (voPtr == NULL) { + voPtr = ckalloc(sizeof(VirtualOwners)); + voPtr->numOwners = 0; + } else { + voPtr = ckrealloc(voPtr, sizeof(VirtualOwners) + + voPtr->numOwners * sizeof(Tcl_HashEntry *)); + } + psPtr->voPtr = voPtr; + voPtr->owners[voPtr->numOwners] = vhPtr; + voPtr->numOwners++; + + return TCL_OK; +} + +/* + *-------------------------------------------------------------- + * + * DeleteVirtualEvent -- + * + * Remove the definition of a given virtual event. If the event string is + * NULL, all definitions of the virtual event will be removed. + * Otherwise, just the specified definition of the virtual event will be + * removed. + * + * Results: + * The result is a standard Tcl return value. If an error occurs then the + * interp's result will contain an error message. It is not an error to + * attempt to delete a virtual event that does not exist or a definition + * that does not exist. + * + * Side effects: + * The virtual event given by virtString may be removed from the virtual + * event table. + * + *-------------------------------------------------------------- + */ + +static int +DeleteVirtualEvent( + Tcl_Interp *interp, /* Used for error reporting. */ + VirtualEventTable *vetPtr, /* Table in which to delete event. */ + char *virtString, /* String describing event sequence that + * triggers binding. */ + const char *eventString) /* The event sequence that should be deleted, + * or NULL to delete all event sequences for + * the entire virtual event. */ +{ + int iPhys; + Tk_Uid virtUid; + Tcl_HashEntry *vhPtr; + PhysicalsOwned *poPtr; + PatSeq *eventPSPtr; + + virtUid = GetVirtualEventUid(interp, virtString); + if (virtUid == NULL) { + return TCL_ERROR; + } + + vhPtr = Tcl_FindHashEntry(&vetPtr->nameTable, virtUid); + if (vhPtr == NULL) { + return TCL_OK; + } + poPtr = Tcl_GetHashValue(vhPtr); + + eventPSPtr = NULL; + if (eventString != NULL) { + unsigned long eventMask; + + /* + * Delete only the specific physical event associated with the virtual + * event. If the physical event doesn't already exist, or the virtual + * event doesn't own that physical event, return w/o doing anything. + */ + + eventPSPtr = FindSequence(interp, &vetPtr->patternTable, NULL, + eventString, 0, 0, &eventMask); + if (eventPSPtr == NULL) { + const char *string = Tcl_GetString(Tcl_GetObjResult(interp)); + + return (string[0] != '\0') ? TCL_ERROR : TCL_OK; + } + } + + for (iPhys = poPtr->numOwned; --iPhys >= 0; ) { + PatSeq *psPtr = poPtr->patSeqs[iPhys]; + + if ((eventPSPtr == NULL) || (psPtr == eventPSPtr)) { + int iVirt; + VirtualOwners *voPtr; + + /* + * Remove association between this physical event and the given + * virtual event that it triggers. + */ + + voPtr = psPtr->voPtr; + for (iVirt = 0; iVirt < voPtr->numOwners; iVirt++) { + if (voPtr->owners[iVirt] == vhPtr) { + break; + } + } + if (iVirt == voPtr->numOwners) { + Tcl_Panic("DeleteVirtualEvent: couldn't find owner"); + } + voPtr->numOwners--; + if (voPtr->numOwners == 0) { + /* + * Removed last reference to this physical event, so remove it + * from physical->virtual map. + */ + + PatSeq *prevPtr = Tcl_GetHashValue(psPtr->hPtr); + + if (prevPtr == psPtr) { + if (psPtr->nextSeqPtr == NULL) { + Tcl_DeleteHashEntry(psPtr->hPtr); + } else { + Tcl_SetHashValue(psPtr->hPtr, + psPtr->nextSeqPtr); + } + } else { + for ( ; ; prevPtr = prevPtr->nextSeqPtr) { + if (prevPtr == NULL) { + Tcl_Panic("DeleteVirtualEvent couldn't find on hash chain"); + } + if (prevPtr->nextSeqPtr == psPtr) { + prevPtr->nextSeqPtr = psPtr->nextSeqPtr; + break; + } + } + } + ckfree(psPtr->voPtr); + ckfree(psPtr); + } else { + /* + * This physical event still triggers some other virtual + * event(s). Consolidate the list of virtual owners for this + * physical event so it no longer triggers the given virtual + * event. + */ + + voPtr->owners[iVirt] = voPtr->owners[voPtr->numOwners]; + } + + /* + * Now delete the virtual event's reference to the physical event. + */ + + poPtr->numOwned--; + if (eventPSPtr != NULL && poPtr->numOwned != 0) { + /* + * Just deleting this one physical event. Consolidate list of + * owned physical events and return. + */ + + poPtr->patSeqs[iPhys] = poPtr->patSeqs[poPtr->numOwned]; + return TCL_OK; + } + } + } + + if (poPtr->numOwned == 0) { + /* + * All the physical events for this virtual event were deleted, either + * because there was only one associated physical event or because the + * caller was deleting the entire virtual event. Now the virtual event + * itself should be deleted. + */ + + ckfree(poPtr); + Tcl_DeleteHashEntry(vhPtr); + } + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * GetVirtualEvent -- + * + * Return the list of physical events that can invoke the given virtual + * event. + * + * Results: + * The return value is TCL_OK and the interp's result is filled with the + * string representation of the physical events associated with the + * virtual event; if there are no physical events for the given virtual + * event, the interp's result is filled with and empty string. If the + * virtual event string is improperly formed, then TCL_ERROR is returned + * and an error message is left in the interp's result. + * + * Side effects: + * None. + * + *--------------------------------------------------------------------------- + */ + +static int +GetVirtualEvent( + Tcl_Interp *interp, /* Interpreter for reporting. */ + VirtualEventTable *vetPtr, /* Table in which to look for event. */ + Tcl_Obj *virtName) /* String describing virtual event. */ +{ + Tcl_HashEntry *vhPtr; + int iPhys; + PhysicalsOwned *poPtr; + Tk_Uid virtUid; + Tcl_Obj *resultObj; + + virtUid = GetVirtualEventUid(interp, Tcl_GetString(virtName)); + if (virtUid == NULL) { + return TCL_ERROR; + } + + vhPtr = Tcl_FindHashEntry(&vetPtr->nameTable, virtUid); + if (vhPtr == NULL) { + return TCL_OK; + } + + resultObj = Tcl_NewObj(); + poPtr = Tcl_GetHashValue(vhPtr); + for (iPhys = 0; iPhys < poPtr->numOwned; iPhys++) { + Tcl_ListObjAppendElement(NULL, resultObj, + GetPatternObj(poPtr->patSeqs[iPhys])); + } + Tcl_SetObjResult(interp, resultObj); + + return TCL_OK; +} + +/* + *-------------------------------------------------------------- + * + * GetAllVirtualEvents -- + * + * Return a list that contains the names of all the virtual event + * defined. + * + * Results: + * There is no return value. The interp's result is modified to hold a + * Tcl list with one entry for each virtual event in nameTable. + * + * Side effects: + * None. + * + *-------------------------------------------------------------- + */ + +static void +GetAllVirtualEvents( + Tcl_Interp *interp, /* Interpreter returning result. */ + VirtualEventTable *vetPtr) /* Table containing events. */ +{ + Tcl_HashEntry *hPtr; + Tcl_HashSearch search; + Tcl_Obj *resultObj; + + resultObj = Tcl_NewObj(); + hPtr = Tcl_FirstHashEntry(&vetPtr->nameTable, &search); + for ( ; hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) { + Tcl_ListObjAppendElement(NULL, resultObj, Tcl_ObjPrintf( + "<<%s>>", (char *) Tcl_GetHashKey(hPtr->tablePtr, hPtr))); + } + Tcl_SetObjResult(interp, resultObj); +} + +/* + *--------------------------------------------------------------------------- + * + * 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 synchronously or asynchronously, depending on + * the value specified by the optional "-when" option. The default + * setting is synchronous. + * + *--------------------------------------------------------------------------- + */ + +static int +HandleEventGenerate( + Tcl_Interp *interp, /* Interp for errors return and name lookup. */ + Tk_Window mainWin, /* Main window associated with interp. */ + int objc, /* Number of arguments. */ + Tcl_Obj *const objv[]) /* Argument objects. */ +{ + union {XEvent general; XVirtualEvent virtual;} event; + const char *p; + const char *name, *windowName; + int count, flags, synch, i, number, warp; + Tcl_QueuePosition pos; + TkPattern pat; + Tk_Window tkwin, tkwin2; + TkWindow *mainPtr; + unsigned long eventMask; + Tcl_Obj *userDataObj; + + static const char *const fieldStrings[] = { + "-when", "-above", "-borderwidth", "-button", + "-count", "-data", "-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_DATA, 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 + }; + + windowName = Tcl_GetString(objv[0]); + if (!windowName[0]) { + tkwin = mainWin; + } else if (NameToWindow(interp, mainWin, objv[0], &tkwin) != TCL_OK) { + return TCL_ERROR; + } + + mainPtr = (TkWindow *) mainWin; + if ((tkwin == NULL) + || (mainPtr->mainPtr != ((TkWindow *) tkwin)->mainPtr)) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "window id \"%s\" doesn't exist in this application", + Tcl_GetString(objv[0]))); + Tcl_SetErrorCode(interp, "TK", "LOOKUP", "WINDOW", + Tcl_GetString(objv[0]), NULL); + return TCL_ERROR; + } + + name = Tcl_GetString(objv[1]); + + p = name; + eventMask = 0; + userDataObj = NULL; + count = ParseEventDescription(interp, &p, &pat, &eventMask); + if (count == 0) { + return TCL_ERROR; + } + if (count != 1) { + Tcl_SetObjResult(interp, Tcl_NewStringObj( + "Double or Triple modifier not allowed", -1)); + Tcl_SetErrorCode(interp, "TK", "EVENT", "BAD_MODIFIER", NULL); + return TCL_ERROR; + } + if (*p != '\0') { + Tcl_SetObjResult(interp, Tcl_NewStringObj( + "only one event specification allowed", -1)); + Tcl_SetErrorCode(interp, "TK", "EVENT", "MULTIPLE", NULL); + return TCL_ERROR; + } + + memset(&event, 0, sizeof(event)); + event.general.xany.type = pat.eventType; + event.general.xany.serial = NextRequest(Tk_Display(tkwin)); + event.general.xany.send_event = False; + if (windowName[0]) { + event.general.xany.window = Tk_WindowId(tkwin); + } else { + event.general.xany.window = + RootWindow(Tk_Display(tkwin), Tk_ScreenNumber(tkwin)); + } + event.general.xany.display = Tk_Display(tkwin); + + flags = flagArray[event.general.xany.type]; + if (flags & DESTROY) { + /* + * Event DestroyNotify should be generated by destroying the window. + */ + + Tk_DestroyWindow(tkwin); + return TCL_OK; + } + if (flags & KEY_BUTTON_MOTION_VIRTUAL) { + event.general.xkey.state = pat.needMods; + if ((flags & KEY) && (event.general.xany.type != MouseWheelEvent)) { + TkpSetKeycodeAndState(tkwin, pat.detail.keySym, &event.general); + } else if (flags & BUTTON) { + event.general.xbutton.button = pat.detail.button; + } else if (flags & VIRTUAL) { + event.virtual.name = pat.detail.name; + } + } + if (flags & (CREATE|UNMAP|MAP|REPARENT|CONFIG|GRAVITY|CIRC)) { + event.general.xcreatewindow.window = event.general.xany.window; + } + + if (flags & KEY_BUTTON_MOTION_CROSSING) { + event.general.xkey.x_root = -1; + event.general.xkey.y_root = -1; + } + + if (event.general.xany.type == FocusIn + || event.general.xany.type == FocusOut) { + event.general.xany.send_event = GENERATED_FOCUS_EVENT_MAGIC; + } + + /* + * Process the remaining arguments to fill in additional fields of the + * event. + */ + + synch = 1; + warp = 0; + pos = TCL_QUEUE_TAIL; + for (i = 2; i < objc; i += 2) { + Tcl_Obj *optionPtr, *valuePtr; + int index; + + optionPtr = objv[i]; + valuePtr = objv[i + 1]; + + if (Tcl_GetIndexFromObjStruct(interp, optionPtr, fieldStrings, + sizeof(char *), "option", TCL_EXACT, &index) != TCL_OK) { + return TCL_ERROR; + } + if (objc & 1) { + /* + * 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_SetObjResult(interp, Tcl_ObjPrintf( + "value for \"%s\" missing", Tcl_GetString(optionPtr))); + Tcl_SetErrorCode(interp, "TK", "EVENT", "MISSING_VALUE", 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: + pos = (Tcl_QueuePosition) TkFindStateNumObj(interp, optionPtr, + queuePosition, valuePtr); + if ((int) pos < -1) { + return TCL_ERROR; + } + synch = 0; + if ((int) pos == -1) { + synch = 1; + } + break; + case EVENT_ABOVE: + if (NameToWindow(interp, tkwin, valuePtr, &tkwin2) != TCL_OK) { + return TCL_ERROR; + } + if (flags & CONFIG) { + event.general.xconfigure.above = Tk_WindowId(tkwin2); + } else { + goto badopt; + } + break; + case EVENT_BORDER: + if (Tk_GetPixelsFromObj(interp,tkwin,valuePtr,&number) != TCL_OK) { + return TCL_ERROR; + } + if (flags & (CREATE|CONFIG)) { + event.general.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.general.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.general.xexpose.count = number; + } else { + goto badopt; + } + break; + case EVENT_DATA: + if (flags & VIRTUAL) { + /* + * Do not increment reference count until after parsing + * completes and we know that the event generation is really + * going to happen. + */ + + userDataObj = valuePtr; + } else { + goto badopt; + } + break; + case EVENT_DELTA: + if (Tcl_GetIntFromObj(interp, valuePtr, &number) != TCL_OK) { + return TCL_ERROR; + } + if ((flags & KEY) && (event.general.xkey.type == MouseWheelEvent)) { + event.general.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.general.xfocus.detail = number; + } else if (flags & CROSSING) { + event.general.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.general.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.general.xexpose.height = number; + } else if (flags & CONFIG) { + event.general.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.general.xkey.type != MouseWheelEvent)) { + event.general.xkey.keycode = number; + } else { + goto badopt; + } + break; + case EVENT_KEYSYM: { + KeySym keysym; + const char *value; + + value = Tcl_GetString(valuePtr); + keysym = TkStringToKeysym(value); + if (keysym == NoSymbol) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "unknown keysym \"%s\"", value)); + Tcl_SetErrorCode(interp, "TK", "LOOKUP", "KEYSYM", value, + NULL); + return TCL_ERROR; + } + + TkpSetKeycodeAndState(tkwin, keysym, &event.general); + if (event.general.xkey.keycode == 0) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "no keycode for keysym \"%s\"", value)); + Tcl_SetErrorCode(interp, "TK", "LOOKUP", "KEYCODE", value, + NULL); + return TCL_ERROR; + } + if (!(flags & KEY) + || (event.general.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.general.xcrossing.mode = number; + } else if (flags & FOCUS) { + event.general.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.general.xcreatewindow.override_redirect = number; + } else if (flags & MAP) { + event.general.xmap.override_redirect = number; + } else if (flags & REPARENT) { + event.general.xreparent.override_redirect = number; + } else if (flags & CONFIG) { + event.general.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.general.xcirculate.place = number; + } else { + goto badopt; + } + break; + case EVENT_ROOT: + if (NameToWindow(interp, tkwin, valuePtr, &tkwin2) != TCL_OK) { + return TCL_ERROR; + } + if (flags & KEY_BUTTON_MOTION_CROSSING) { + event.general.xkey.root = Tk_WindowId(tkwin2); + } else { + goto badopt; + } + break; + case EVENT_ROOTX: + if (Tk_GetPixelsFromObj(interp,tkwin,valuePtr,&number) != TCL_OK) { + return TCL_ERROR; + } + if (flags & KEY_BUTTON_MOTION_CROSSING) { + event.general.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_CROSSING) { + event.general.xkey.y_root = number; + } else { + goto badopt; + } + break; + case EVENT_SEND: { + const char *value; + + value = Tcl_GetString(valuePtr); + 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.general.xany.send_event = number; + break; + } + case EVENT_SERIAL: + if (Tcl_GetIntFromObj(interp, valuePtr, &number) != TCL_OK) { + return TCL_ERROR; + } + event.general.xany.serial = number; + break; + case EVENT_STATE: + if (flags & KEY_BUTTON_MOTION_CROSSING) { + if (Tcl_GetIntFromObj(interp, valuePtr, &number) != TCL_OK) { + return TCL_ERROR; + } + if (flags & KEY_BUTTON_MOTION_VIRTUAL) { + event.general.xkey.state = number; + } else { + event.general.xcrossing.state = number; + } + } else if (flags & VISIBILITY) { + number = TkFindStateNumObj(interp, optionPtr, visNotify, + valuePtr); + if (number < 0) { + return TCL_ERROR; + } + event.general.xvisibility.state = number; + } else { + goto badopt; + } + break; + case EVENT_SUBWINDOW: + if (NameToWindow(interp, tkwin, valuePtr, &tkwin2) != TCL_OK) { + return TCL_ERROR; + } + if (flags & KEY_BUTTON_MOTION_CROSSING) { + event.general.xkey.subwindow = Tk_WindowId(tkwin2); + } else { + goto badopt; + } + break; + case EVENT_TIME: + if (Tcl_GetIntFromObj(interp, valuePtr, &number) != TCL_OK) { + return TCL_ERROR; + } + if (flags & KEY_BUTTON_MOTION_CROSSING) { + event.general.xkey.time = (Time) number; + } else if (flags & PROP) { + event.general.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.general.xexpose.width = number; + } else if (flags & (CREATE|CONFIG)) { + event.general.xcreatewindow.width = number; + } else { + goto badopt; + } + break; + case EVENT_WINDOW: + if (NameToWindow(interp, tkwin, valuePtr, &tkwin2) != TCL_OK) { + return TCL_ERROR; + } + if (flags & (CREATE|UNMAP|MAP|REPARENT|CONFIG|GRAVITY|CIRC)) { + event.general.xcreatewindow.window = Tk_WindowId(tkwin2); + } else { + goto badopt; + } + break; + case EVENT_X: + if (Tk_GetPixelsFromObj(interp,tkwin,valuePtr,&number) != TCL_OK) { + return TCL_ERROR; + } + if (flags & KEY_BUTTON_MOTION_CROSSING) { + event.general.xkey.x = number; + + /* + * Only modify rootx as well if it hasn't been changed. + */ + + if (event.general.xkey.x_root == -1) { + int rootX, rootY; + + Tk_GetRootCoords(tkwin, &rootX, &rootY); + event.general.xkey.x_root = rootX + number; + } + } else if (flags & EXPOSE) { + event.general.xexpose.x = number; + } else if (flags & (CREATE|CONFIG|GRAVITY)) { + event.general.xcreatewindow.x = number; + } else if (flags & REPARENT) { + event.general.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_CROSSING) { + event.general.xkey.y = number; + + /* + * Only modify rooty as well if it hasn't been changed. + */ + + if (event.general.xkey.y_root == -1) { + int rootX, rootY; + + Tk_GetRootCoords(tkwin, &rootX, &rootY); + event.general.xkey.y_root = rootY + number; + } + } else if (flags & EXPOSE) { + event.general.xexpose.y = number; + } else if (flags & (CREATE|CONFIG|GRAVITY)) { + event.general.xcreatewindow.y = number; + } else if (flags & REPARENT) { + event.general.xreparent.y = number; + } else { + goto badopt; + } + break; + } + continue; + + badopt: + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "%s event doesn't accept \"%s\" option", + name, Tcl_GetString(optionPtr))); + Tcl_SetErrorCode(interp, "TK", "EVENT", "BAD_OPTION", NULL); + return TCL_ERROR; + } + + /* + * Don't generate events for windows that don't exist yet. + */ + + if (!event.general.xany.window) { + goto done; + } + + if (userDataObj != NULL) { + + /* + * Must be virtual event to set that variable to non-NULL. Now we want + * to install the object into the event. Note that we must incr the + * refcount before firing it into the low-level event subsystem; the + * refcount will be decremented once the event has been processed. + */ + + event.virtual.user_data = userDataObj; + Tcl_IncrRefCount(userDataObj); + } + + /* + * Now we have constructed the event, inject it into the event handling + * code. + */ + + if (synch != 0) { + Tk_HandleEvent(&event.general); + } else { + Tk_QueueWindowEvent(&event.general, pos); + } + + /* + * We only allow warping if the window is mapped. + */ + + if ((warp != 0) && Tk_IsMapped(tkwin)) { + TkDisplay *dispPtr = TkGetDisplay(event.general.xmotion.display); + +Tk_Window warpWindow = Tk_IdToWindow(dispPtr->display, + event.general.xmotion.window); + + if (!(dispPtr->flags & TK_DISPLAY_IN_WARP)) { + Tcl_DoWhenIdle(DoWarp, dispPtr); + dispPtr->flags |= TK_DISPLAY_IN_WARP; + } + + if (warpWindow != dispPtr->warpWindow) { + if (warpWindow) { + Tcl_Preserve(warpWindow); + } + if (dispPtr->warpWindow) { + Tcl_Release(dispPtr->warpWindow); + } + dispPtr->warpWindow = warpWindow; + } + dispPtr->warpMainwin = mainWin; + dispPtr->warpX = event.general.xmotion.x; + dispPtr->warpY = event.general.xmotion.y; + } + + done: + Tcl_ResetResult(interp); + return TCL_OK; +} + +static int +NameToWindow( + Tcl_Interp *interp, /* Interp for error return and name lookup. */ + Tk_Window mainWin, /* Main window of application. */ + Tcl_Obj *objPtr, /* Contains name or id string of window. */ + Tk_Window *tkwinPtr) /* Filled with token for window. */ +{ + const char *name = Tcl_GetString(objPtr); + Tk_Window tkwin; + + if (name[0] == '.') { + tkwin = Tk_NameToWindow(interp, name, mainWin); + if (tkwin == NULL) { + return TCL_ERROR; + } + } else { + Window id; + + /* + * Check for the winPtr being valid, even if it looks ok to + * TkpScanWindowId. [Bug #411307] + */ + + if (TkpScanWindowId(NULL, name, &id) != TCL_OK) { + goto badWindow; + } + tkwin = Tk_IdToWindow(Tk_Display(mainWin), id); + if (tkwin == NULL) { + goto badWindow; + } + } + *tkwinPtr = tkwin; + return TCL_OK; + + badWindow: + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "bad window name/identifier \"%s\"", name)); + Tcl_SetErrorCode(interp, "TK", "LOOKUP", "WINDOW_ID", name, NULL); + return TCL_ERROR; +} + +/* + *------------------------------------------------------------------------- + * + * DoWarp -- + * + * Perform Warping of X pointer. Executed as an idle handler only. + * + * Results: + * None + * + * Side effects: + * X Pointer will move to a new location. + * + *------------------------------------------------------------------------- + */ + +static void +DoWarp( + ClientData clientData) +{ + TkDisplay *dispPtr = clientData; + + /* + * DoWarp was scheduled only if the window was mapped. It needs to be + * still mapped at the time the present idle callback is executed. Also + * one needs to guard against window destruction in the meantime. + * Finally, the case warpWindow == NULL is special in that it means + * the whole screen. + */ + + if ((dispPtr->warpWindow == NULL) || + (Tk_IsMapped(dispPtr->warpWindow) + && (Tk_WindowId(dispPtr->warpWindow) != None))) { + TkpWarpPointer(dispPtr); + XForceScreenSaver(dispPtr->display, ScreenSaverReset); + } + + if (dispPtr->warpWindow) { + Tcl_Release(dispPtr->warpWindow); + dispPtr->warpWindow = None; + } + dispPtr->flags &= ~TK_DISPLAY_IN_WARP; +} + +/* + *------------------------------------------------------------------------- + * + * GetVirtualEventUid -- + * + * Determine if the given string is in the proper format for a virtual + * event. + * + * Results: + * The return value is NULL if the virtual event string was not in the + * proper format. In this case, an error message will be left in the + * interp's result. Otherwise the return value is a Tk_Uid that + * represents the virtual event. + * + * Side effects: + * None. + * + *------------------------------------------------------------------------- + */ + +static Tk_Uid +GetVirtualEventUid( + Tcl_Interp *interp, + char *virtString) +{ + Tk_Uid uid; + size_t length; + + length = strlen(virtString); + + if (length < 5 || virtString[0] != '<' || virtString[1] != '<' || + virtString[length - 2] != '>' || virtString[length - 1] != '>') { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "virtual event \"%s\" is badly formed", virtString)); + Tcl_SetErrorCode(interp, "TK", "EVENT", "VIRTUAL", "MALFORMED", NULL); + return NULL; + } + virtString[length - 2] = '\0'; + uid = Tk_GetUid(virtString + 2); + virtString[length - 2] = '>'; + + return uid; +} + +/* + *---------------------------------------------------------------------- + * + * FindSequence -- + * + * Find the entry in the pattern table that corresponds to a particular + * pattern string, and return a pointer to that entry. + * + * Results: + * The return value is normally a pointer to the PatSeq in patternTable + * that corresponds to eventString. If an error was found while parsing + * eventString, or if "create" is 0 and no pattern sequence previously + * existed, then NULL is returned and the interp's result contains a + * message describing the problem. If no pattern sequence previously + * existed for eventString, then a new one is created with a NULL command + * field. In a successful return, *maskPtr is filled in with a mask of + * the event types on which the pattern sequence depends. + * + * Side effects: + * A new pattern sequence may be allocated. + * + *---------------------------------------------------------------------- + */ + +static PatSeq * +FindSequence( + Tcl_Interp *interp, /* Interpreter to use for error reporting. */ + Tcl_HashTable *patternTablePtr, + /* Table to use for lookup. */ + ClientData object, /* For binding table, token for object with + * which binding is associated. For virtual + * event table, NULL. */ + const char *eventString, /* String description of pattern to match on. + * See user documentation for details. */ + int create, /* 0 means don't create the entry if it + * doesn't already exist. Non-zero means + * create. */ + int allowVirtual, /* 0 means that virtual events are not allowed + * in the sequence. Non-zero otherwise. */ + unsigned long *maskPtr) /* *maskPtr is filled in with the event types + * on which this pattern sequence depends. */ +{ + TkPattern pats[EVENT_BUFFER_SIZE]; + int numPats, virtualFound; + const char *p; + TkPattern *patPtr; + PatSeq *psPtr; + Tcl_HashEntry *hPtr; + int flags, count, isNew; + size_t sequenceSize; + unsigned long eventMask; + PatternTableKey key; + + /* + *------------------------------------------------------------- + * Step 1: parse the pattern string to produce an array of Patterns. The + * array is generated backwards, so that the lowest-indexed pattern + * corresponds to the last event that must occur. + *------------------------------------------------------------- + */ + + p = eventString; + flags = 0; + eventMask = 0; + virtualFound = 0; + + patPtr = &pats[EVENT_BUFFER_SIZE-1]; + for (numPats = 0; numPats < EVENT_BUFFER_SIZE; numPats++, patPtr--) { + while (isspace(UCHAR(*p))) { + p++; + } + if (*p == '\0') { + break; + } + + count = ParseEventDescription(interp, &p, patPtr, &eventMask); + if (count == 0) { + return NULL; + } + + if (eventMask & VirtualEventMask) { + if (allowVirtual == 0) { + Tcl_SetObjResult(interp, Tcl_NewStringObj( + "virtual event not allowed in definition of another virtual event", + -1)); + Tcl_SetErrorCode(interp, "TK", "EVENT", "VIRTUAL", "INNER", + NULL); + return NULL; + } + virtualFound = 1; + } + + /* + * Replicate events for DOUBLE, TRIPLE, QUADRUPLE. + */ + + while ((count-- > 1) && (numPats < EVENT_BUFFER_SIZE-1)) { + flags |= PAT_NEARBY; + patPtr[-1] = patPtr[0]; + patPtr--; + numPats++; + } + } + + /* + *------------------------------------------------------------- + * Step 2: find the sequence in the binding table if it exists, and add a + * new sequence to the table if it doesn't. + *------------------------------------------------------------- + */ + + if (numPats == 0) { + Tcl_SetObjResult(interp, Tcl_NewStringObj( + "no events specified in binding", -1)); + Tcl_SetErrorCode(interp, "TK", "EVENT", "NO_EVENTS", NULL); + return NULL; + } + if ((numPats > 1) && (virtualFound != 0)) { + Tcl_SetObjResult(interp, Tcl_NewStringObj( + "virtual events may not be composed", -1)); + Tcl_SetErrorCode(interp, "TK", "EVENT", "VIRTUAL", "COMPOSITION", + NULL); + return NULL; + } + + patPtr = &pats[EVENT_BUFFER_SIZE-numPats]; + memset(&key, 0, sizeof(key)); + key.object = object; + key.type = patPtr->eventType; + key.detail = patPtr->detail; + hPtr = Tcl_CreateHashEntry(patternTablePtr, (char *) &key, &isNew); + sequenceSize = numPats*sizeof(TkPattern); + if (!isNew) { + for (psPtr = Tcl_GetHashValue(hPtr); psPtr != NULL; + psPtr = psPtr->nextSeqPtr) { + if ((numPats == psPtr->numPats) + && ((flags & PAT_NEARBY) == (psPtr->flags & PAT_NEARBY)) + && (memcmp(patPtr, psPtr->pats, sequenceSize) == 0)) { + goto done; + } + } + } + if (!create) { + if (isNew) { + Tcl_DeleteHashEntry(hPtr); + } + + /* + * No binding exists for the sequence, so return an empty error. This + * is a special error that the caller will check for in order to + * silently ignore this case. This is a hack that maintains backward + * compatibility for Tk_GetBinding but the various "bind" commands + * silently ignore missing bindings. + */ + + return NULL; + } + psPtr = ckalloc(sizeof(PatSeq) + (numPats-1)*sizeof(TkPattern)); + psPtr->numPats = numPats; + psPtr->script = NULL; + psPtr->flags = flags; + psPtr->nextSeqPtr = Tcl_GetHashValue(hPtr); + psPtr->hPtr = hPtr; + psPtr->voPtr = NULL; + psPtr->nextObjPtr = NULL; + Tcl_SetHashValue(hPtr, psPtr); + + memcpy(psPtr->pats, patPtr, sequenceSize); + + done: + *maskPtr = eventMask; + return psPtr; +} + +/* + *--------------------------------------------------------------------------- + * + * ParseEventDescription -- + * + * Fill Pattern buffer with information about event from event string. + * + * Results: + * Leaves error message in interp and returns 0 if there was an error due + * to a badly formed event string. Returns 1 if proper event was + * specified, 2 if Double modifier was used in event string, or 3 if + * Triple was used. + * + * Side effects: + * On exit, eventStringPtr points to rest of event string (after the + * closing '>', so that this function can be called repeatedly to parse + * all the events in the entire sequence. + * + *--------------------------------------------------------------------------- + */ + +static int +ParseEventDescription( + Tcl_Interp *interp, /* For error messages. */ + const char **eventStringPtr,/* On input, holds a pointer to start of event + * string. On exit, gets pointer to rest of + * string after parsed event. */ + TkPattern *patPtr, /* Filled with the pattern parsed from the + * event string. */ + unsigned long *eventMaskPtr)/* Filled with event mask of matched event. */ +{ + char *p; + unsigned long eventMask; + int count, eventFlags; +#define FIELD_SIZE 48 + char field[FIELD_SIZE]; + Tcl_HashEntry *hPtr; + 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 = TkStringToKeysym(string); + if (patPtr->detail.keySym == NoSymbol) { + if (isprint(UCHAR(*p))) { + patPtr->detail.keySym = *p; + } else { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "bad ASCII character 0x%x", UCHAR(*p))); + Tcl_SetErrorCode(interp, "TK", "EVENT", "BAD_CHAR", NULL); + count = 0; + goto done; + } + } + p++; + goto end; + } + + /* + * A fancier event description. This can be either a virtual event or a + * physical event. + * + * A virtual event description consists of: + * + * 1. double open angle brackets. + * 2. virtual event name. + * 3. double close angle brackets. + * + * 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++; + if (*p == '<') { + /* + * This is a virtual event: soak up all the characters up to the next + * '>'. + */ + + char *field = p + 1; + + p = strchr(field, '>'); + if (p == field) { + Tcl_SetObjResult(interp, Tcl_NewStringObj( + "virtual event \"<<>>\" is badly formed", -1)); + Tcl_SetErrorCode(interp, "TK", "EVENT", "VIRTUAL", "MALFORMED", + NULL); + count = 0; + goto done; + } + if ((p == NULL) || (p[1] != '>')) { + Tcl_SetObjResult(interp, Tcl_NewStringObj( + "missing \">\" in virtual binding", -1)); + Tcl_SetErrorCode(interp, "TK", "EVENT", "VIRTUAL", "MALFORMED", + NULL); + count = 0; + goto done; + } + *p = '\0'; + patPtr->eventType = VirtualEvent; + eventMask = VirtualEventMask; + patPtr->detail.name = Tk_GetUid(field); + *p = '>'; + + p += 2; + goto end; + } + + while (1) { + ModInfo *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; + } + hPtr = Tcl_FindHashEntry(&modTable, field); + if (hPtr == NULL) { + break; + } + modPtr = Tcl_GetHashValue(hPtr); + 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; + hPtr = Tcl_FindHashEntry(&eventTable, field); + if (hPtr != NULL) { + const EventInfo *eiPtr = Tcl_GetHashValue(hPtr); + + patPtr->eventType = eiPtr->type; + eventFlags = flagArray[eiPtr->type]; + eventMask = eiPtr->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)) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "specified button \"%s\" for non-button event", + field)); + Tcl_SetErrorCode(interp, "TK", "EVENT", "NON_BUTTON", NULL); + count = 0; + goto done; + } + patPtr->detail.button = (*field - '0'); + } else { + + getKeysym: + patPtr->detail.keySym = TkStringToKeysym(field); + if (patPtr->detail.keySym == NoSymbol) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "bad event type or keysym \"%s\"", field)); + Tcl_SetErrorCode(interp, "TK", "LOOKUP", "KEYSYM", field, + NULL); + count = 0; + goto done; + } + if (eventFlags == 0) { + patPtr->eventType = KeyPress; + eventMask = KeyPressMask; + } else if (!(eventFlags & KEY)) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "specified keysym \"%s\" for non-key event", field)); + Tcl_SetErrorCode(interp, "TK", "EVENT", "NON_KEY", NULL); + count = 0; + goto done; + } + } + } else if (eventFlags == 0) { + Tcl_SetObjResult(interp, Tcl_NewStringObj( + "no event type or button # or keysym", -1)); + Tcl_SetErrorCode(interp, "TK", "EVENT", "UNMODIFIABLE", NULL); + count = 0; + goto done; + } + + while ((*p == '-') || isspace(UCHAR(*p))) { + p++; + } + if (*p != '>') { + while (*p != '\0') { + p++; + if (*p == '>') { + Tcl_SetObjResult(interp, Tcl_NewStringObj( + "extra characters after detail in binding", -1)); + Tcl_SetErrorCode(interp, "TK", "EVENT", "PAST_DETAIL", NULL); + count = 0; + goto done; + } + } + Tcl_SetObjResult(interp, Tcl_NewStringObj( + "missing \">\" in binding", -1)); + Tcl_SetErrorCode(interp, "TK", "EVENT", "MALFORMED", NULL); + count = 0; + goto done; + } + p++; + + end: + *eventStringPtr += (p - Tcl_DStringValue(©)); + *eventMaskPtr |= eventMask; + done: + Tcl_DStringFree(©); + return count; +} + +/* + *---------------------------------------------------------------------- + * + * 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( + 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; +} + +/* + *--------------------------------------------------------------------------- + * + * GetPatternObj -- + * + * Produce a string version of the given event, for displaying to the + * user. + * + * Results: + * The string is returned as a Tcl_Obj. + * + * Side effects: + * It is the caller's responsibility to arrange for the object to be + * released; it starts with a refCount of zero. + * + *--------------------------------------------------------------------------- + */ + +static Tcl_Obj * +GetPatternObj( + PatSeq *psPtr) +{ + TkPattern *patPtr; + int patsLeft, needMods; + const ModInfo *modPtr; + const EventInfo *eiPtr; + Tcl_Obj *patternObj = Tcl_NewObj(); + + /* + * The order of the patterns in the sequence is backwards from the order + * in which they must be output. + */ + + for (patsLeft = psPtr->numPats, patPtr = &psPtr->pats[psPtr->numPats - 1]; + patsLeft > 0; patsLeft--, patPtr--) { + /* + * Check for simple case of an ASCII character. + */ + + if ((patPtr->eventType == KeyPress) + && !(psPtr->flags & PAT_NEARBY) + && (patPtr->needMods == 0) + && (patPtr->detail.keySym < 128) + && isprint(UCHAR(patPtr->detail.keySym)) + && (patPtr->detail.keySym != '<') + && (patPtr->detail.keySym != ' ')) { + char c = (char) patPtr->detail.keySym; + + Tcl_AppendToObj(patternObj, &c, 1); + continue; + } + + /* + * Check for virtual event. + */ + + if (patPtr->eventType == VirtualEvent) { + Tcl_AppendPrintfToObj(patternObj, "<<%s>>", patPtr->detail.name); + continue; + } + + /* + * It's a more general event specification. First check for "Double", + * "Triple", "Quadruple", then modifiers, then event type, then keysym + * or button detail. + */ + + Tcl_AppendToObj(patternObj, "<", 1); + + if ((psPtr->flags & PAT_NEARBY) && (patsLeft > 1) + && (memcmp(patPtr, patPtr-1, sizeof(TkPattern)) == 0)) { + patsLeft--; + patPtr--; + if ((patsLeft > 1) && + (memcmp(patPtr, patPtr-1, sizeof(TkPattern)) == 0)) { + patsLeft--; + patPtr--; + if ((patsLeft > 1) && + (memcmp(patPtr, patPtr-1, sizeof(TkPattern)) == 0)) { + patsLeft--; + patPtr--; + Tcl_AppendToObj(patternObj, "Quadruple-", 10); + } else { + Tcl_AppendToObj(patternObj, "Triple-", 7); + } + } else { + Tcl_AppendToObj(patternObj, "Double-", 7); + } + } + + for (needMods = patPtr->needMods, modPtr = modArray; + needMods != 0; modPtr++) { + if (modPtr->mask & needMods) { + needMods &= ~modPtr->mask; + Tcl_AppendPrintfToObj(patternObj, "%s-", modPtr->name); + } + } + + for (eiPtr = eventArray; eiPtr->name != NULL; eiPtr++) { + if (eiPtr->type == patPtr->eventType) { + Tcl_AppendToObj(patternObj, eiPtr->name, -1); + if (patPtr->detail.clientData != 0) { + Tcl_AppendToObj(patternObj, "-", 1); + } + break; + } + } + + if (patPtr->detail.clientData != 0) { + if ((patPtr->eventType == KeyPress) + || (patPtr->eventType == KeyRelease)) { + const char *string = TkKeysymToString(patPtr->detail.keySym); + + if (string != NULL) { + Tcl_AppendToObj(patternObj, string, -1); + } + } else { + Tcl_AppendPrintfToObj(patternObj, "%d", patPtr->detail.button); + } + } + + Tcl_AppendToObj(patternObj, ">", 1); + } + + return patternObj; +} + +/* + *---------------------------------------------------------------------- + * + * TkStringToKeysym -- + * + * This function finds the keysym associated with a given keysym name. + * + * Results: + * The return value is the keysym that corresponds to name, or NoSymbol + * if there is no such keysym. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +KeySym +TkStringToKeysym( + const char *name) /* Name of a keysym. */ +{ +#ifdef REDO_KEYSYM_LOOKUP + Tcl_HashEntry *hPtr = Tcl_FindHashEntry(&keySymTable, name); + + if (hPtr != NULL) { + return (KeySym) Tcl_GetHashValue(hPtr); + } + if (strlen(name) == 1) { + KeySym keysym = (KeySym) (unsigned char) name[0]; + + if (TkKeysymToString(keysym) != NULL) { + return keysym; + } + } +#endif /* REDO_KEYSYM_LOOKUP */ + return XStringToKeysym(name); +} + +/* + *---------------------------------------------------------------------- + * + * TkKeysymToString -- + * + * This function finds the keysym name associated with a given keysym. + * + * Results: + * The return value is a pointer to a static string containing the name + * of the given keysym, or NULL if there is no known name. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +const char * +TkKeysymToString( + KeySym keysym) +{ +#ifdef REDO_KEYSYM_LOOKUP + Tcl_HashEntry *hPtr = Tcl_FindHashEntry(&nameTable, (char *)keysym); + + if (hPtr != NULL) { + return Tcl_GetHashValue(hPtr); + } +#endif /* REDO_KEYSYM_LOOKUP */ + + return XKeysymToString(keysym); +} + +/* + *---------------------------------------------------------------------- + * + * TkpGetBindingXEvent -- + * + * This function returns the XEvent associated with the currently + * executing binding. This function can only be invoked while a binding + * is executing. + * + * Results: + * Returns a pointer to the XEvent that caused the current binding code + * to be run. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +XEvent * +TkpGetBindingXEvent( + Tcl_Interp *interp) /* Interpreter. */ +{ + TkWindow *winPtr = (TkWindow *) Tk_MainWindow(interp); + BindingTable *bindPtr = winPtr->mainPtr->bindingTable; + + return &(bindPtr->eventRing[bindPtr->curEvent]); +} + +/* + *---------------------------------------------------------------------- + * + * TkpCancelWarp -- + * + * This function cancels an outstanding pointer warp and + * is called during tear down of the display. + * + * Results: + * None. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +void +TkpCancelWarp( + TkDisplay *dispPtr) +{ + if (dispPtr->flags & TK_DISPLAY_IN_WARP) { + Tcl_CancelIdleCall(DoWarp, dispPtr); + dispPtr->flags &= ~TK_DISPLAY_IN_WARP; + } +} + +/* + * Local Variables: + * mode: c + * c-basic-offset: 4 + * fill-column: 78 + * End: + */ |
