From b3c6dbb65349b84aa78ef495743cdcf1b4d4375e Mon Sep 17 00:00:00 2001 From: gcramer Date: Sun, 21 Oct 2018 13:00:53 +0000 Subject: Bugfix [6e8afe516d]: rework of tkBind.c. --- doc/event.n | 3 +- generic/tkArray.h | 542 +++++++ generic/tkBind.c | 4689 +++++++++++++++++++++++++++++++---------------------- generic/tkDList.h | 546 +++++++ generic/tkFocus.c | 4 +- tests/bind.test | 657 ++++---- 6 files changed, 4199 insertions(+), 2242 deletions(-) create mode 100644 generic/tkArray.h create mode 100644 generic/tkDList.h diff --git a/doc/event.n b/doc/event.n index 54ad42e..4478b92 100644 --- a/doc/event.n +++ b/doc/event.n @@ -239,7 +239,8 @@ Similar to \fB%S\fR substitution for binding scripts. .TP \fB\-time\fI integer\fR \fIInteger\fR must be an integer value; it specifies the \fItime\fR field -for the event. +for the event. Additonally the special value \fBcurrent\fR is allowed, +this value will be substituted by the current event time. Valid for \fBKeyPress\fR, \fBKeyRelease\fR, \fBButtonPress\fR, \fBButtonRelease\fR, \fBEnter\fR, \fBLeave\fR, \fBMotion\fR, and \fBProperty\fR events. diff --git a/generic/tkArray.h b/generic/tkArray.h new file mode 100644 index 0000000..0e7547a --- /dev/null +++ b/generic/tkArray.h @@ -0,0 +1,542 @@ +/* + * tkArray.h -- + * + * An array is a sequence of items, stored in a contiguous memory region. + * Random access to any item is very fast. New items can be either appended + * or prepended. An array may be traversed in the forward or backward direction. + * + * Copyright (c) 2018 by Gregor Cramer. + * + * See the file "license.terms" for information on usage and redistribution of + * this file, and for a DISCLAIMER OF ALL WARRANTIES. + */ + +/* + * Note that this file will not be included in header files, it is the purpose + * of this file to be included in source files only. Thus we are not using the + * prefix "Tk_" here for functions, because all the functions have private scope. + */ + +/* + * Use the array in the following way: + * ------------------------------------------------------------------------------- + * typedef struct { int key, value; } Pair; + * TK_PTR_ARRAY_DEFINE(MyArray, Pair); + * MyArray *arr = NULL; + * if (MyArray_IsEmpty(arr)) { + * MyArray_Append(&arr, MakePair(1, 2)); + * MyArray_Append(&arr, MakePair(2, 3)); + * for (i = 0; i < MyArray_Size(arr); ++i) { + * const Pair *p = MyArray_Get(arr, i); + * printf("%d -> %d\n", p->key, p->value); + * ckfree(p); + * } + * MyArray_Free(&arr); + * assert(arr == NULL); + * } + * ------------------------------------------------------------------------------- + */ + +/*************************************************************************/ +/* + * Two array types will be provided: + * Use TK_ARRAY_DEFINE if your array is aggregating the elements. Use + * TK_PTR_ARRAY_DEFINE if your array contains pointers to elements. But + * in latter case the array is not responsible for the lifetime of the + * elements. + */ +/*************************************************************************/ +/* + * Array_ElemSize: Returns the memory size for one array element. + */ +/*************************************************************************/ +/* + * Array_BufferSize: Returns the memory size for given number of elements. + */ +/*************************************************************************/ +/* + * Array_IsEmpty: Array is empty? + */ +/*************************************************************************/ +/* + * Array_Size: Number of elements in array. + */ +/*************************************************************************/ +/* + * Array_Capacity: Capacity of given array. This is the maximal number of + * elements fitting into current array memory without resizing the buffer. + */ +/*************************************************************************/ +/* + * Array_SetSize: Set array size, new size must not exceed the capacity of + * the array. This function has to be used with care when increasing the + * array size. + */ +/*************************************************************************/ +/* + * Array_First: Returns position of first element in array. Given array + * may be NULL. + */ +/*************************************************************************/ +/* + * Array_Last: Returns position after last element in array. Given array + * may be empty. + */ +/*************************************************************************/ +/* + * Array_Front: Returns first element in array. Given array must not be + * empty. + */ +/*************************************************************************/ +/* + * Array_Back: Returns last element in array. Given array must not be + * empty. + */ +/*************************************************************************/ +/* + * Array_Resize: Resize buffer of array for given number of elements. The + * array may grow or shrink. Note that this function is not initializing + * the increased buffer. + */ +/*************************************************************************/ +/* + * Array_ResizeAndClear: Resize buffer of array for given number of + * elements. The array may grow or shrink. The increased memory will be + * filled with zeroes. + */ +/*************************************************************************/ +/* + * Array_Clear: Fill specified range with zeroes. + */ +/*************************************************************************/ +/* + * Array_Free: Resize array to size zero. This function will release the + * array buffer. + */ +/*************************************************************************/ +/* + * Array_Append: Insert given element after end of array. + */ +/*************************************************************************/ +/* + * Array_PopBack: Shrink array by one element. Given array must not be + * empty. + */ +/*************************************************************************/ +/* + * Array_Get: Random access to array element at given position. The given + * index must not exceed current array size. + */ +/*************************************************************************/ +/* + * Array_Set: Replace array element at given position with new value. The + * given index must not exceed current array size. + */ +/*************************************************************************/ +/* + * Array_Find: Return index position of element which matches given + * argument. If not found then -1 will be returned. + */ +/*************************************************************************/ + +#ifndef TK_ARRAY_DEFINED +#define TK_ARRAY_DEFINED + +#include +#include +#include + +#if defined(__GNUC__) || defined(__clang__) +# define __TK_ARRAY_UNUSED __attribute__((unused)) +#else +# define __TK_ARRAY_UNUSED +#endif + +#define TK_ARRAY_DEFINE(AT, ElemType) /* AT = type of array */ \ +/* ------------------------------------------------------------------------- */ \ +typedef struct AT { \ + size_t size; \ + size_t capacity; \ + ElemType buf[1]; \ +} AT; \ + \ +__TK_ARRAY_UNUSED \ +static size_t \ +AT##_ElemSize() \ +{ \ + return sizeof(ElemType); \ +} \ + \ +__TK_ARRAY_UNUSED \ +static size_t \ +AT##_BufferSize(size_t numElems) \ +{ \ + return numElems*sizeof(ElemType); \ +} \ + \ +__TK_ARRAY_UNUSED \ +static int \ +AT##_IsEmpty(struct AT *arr) \ +{ \ + assert(!arr || arr->size != 0xdeadbeef); \ + return !arr || arr->size == 0; \ +} \ + \ +__TK_ARRAY_UNUSED \ +static int \ +AT##_Size(const struct AT *arr) \ +{ \ + assert(!arr || arr->size != 0xdeadbeef); \ + return arr ? arr->size : 0; \ +} \ + \ +__TK_ARRAY_UNUSED \ +static int \ +AT##_Capacity(const struct AT *arr) \ +{ \ + assert(!arr || arr->size != 0xdeadbeef); \ + return arr ? arr->capacity : 0; \ +} \ + \ +__TK_ARRAY_UNUSED \ +static ElemType * \ +AT##_First(struct AT *arr) \ +{ \ + assert(!arr || arr->size != 0xdeadbeef); \ + return arr ? arr->buf : NULL; \ +} \ + \ +__TK_ARRAY_UNUSED \ +static ElemType * \ +AT##_Last(struct AT *arr) \ +{ \ + assert(!arr || arr->size != 0xdeadbeef); \ + return arr ? arr->buf + arr->size : NULL; \ +} \ + \ +__TK_ARRAY_UNUSED \ +static ElemType * \ +AT##_Front(struct AT *arr) \ +{ \ + assert(!arr || arr->size != 0xdeadbeef); \ + assert(!AT##_IsEmpty(arr)); \ + return &arr->buf[0]; \ +} \ + \ +__TK_ARRAY_UNUSED \ +static ElemType * \ +AT##_Back(struct AT *arr) \ +{ \ + assert(!arr || arr->size != 0xdeadbeef); \ + assert(!AT##_IsEmpty(arr)); \ + return &arr->buf[arr->size - 1]; \ +} \ + \ +__TK_ARRAY_UNUSED \ +static void \ +AT##_Resize(struct AT **arrp, size_t newSize) \ +{ \ + assert(arrp); \ + assert(!*arrp || (*arrp)->size != 0xdeadbeef); \ + if (newSize == 0) { \ + assert(!*arrp || ((*arrp)->size = 0xdeadbeef)); \ + ckfree(*arrp); \ + *arrp = NULL; \ + } else { \ + int init = *arrp == NULL; \ + *arrp = ckrealloc(*arrp, sizeof(AT) + (newSize - 1)*sizeof(ElemType)); \ + if (init) { \ + (*arrp)->size = 0; \ + } else if (newSize < (*arrp)->size) { \ + (*arrp)->size = newSize; \ + } \ + (*arrp)->capacity = newSize; \ + } \ +} \ + \ +__TK_ARRAY_UNUSED \ +static void \ +AT##_Clear(struct AT *arr, size_t from, size_t to) \ +{ \ + assert(!arr || arr->size != 0xdeadbeef); \ + assert(to <= AT##_Size(arr)); \ + assert(from <= to); \ + memset(arr->buf + from, 0, AT##_BufferSize(to - from)); \ +} \ + \ +__TK_ARRAY_UNUSED \ +static void \ +AT##_ResizeAndClear(struct AT **arrp, size_t newSize) \ +{ \ + size_t oldCapacity; \ + assert(arrp); \ + oldCapacity = *arrp ? (*arrp)->capacity : 0; \ + AT##_Resize(arrp, newSize); \ + if (newSize > oldCapacity) { \ + size_t memSize = AT##_BufferSize(newSize - oldCapacity); \ + memset((*arrp)->buf + oldCapacity, 0, memSize); \ + } \ +} \ + \ +__TK_ARRAY_UNUSED \ +static void \ +AT##_SetSize(struct AT *arr, size_t newSize) \ +{ \ + assert(arr); \ + assert(newSize <= AT##_Capacity(arr)); \ + assert(!arr || arr->size != 0xdeadbeef); \ + arr->size = newSize; \ +} \ + \ +__TK_ARRAY_UNUSED \ +static void \ +AT##_Append(struct AT **arrp, ElemType *elem) \ +{ \ + assert(arrp); \ + if (!*arrp) { \ + AT##_Resize(arrp, 1); \ + } else if ((*arrp)->size == (*arrp)->capacity) { \ + AT##_Resize(arrp, (*arrp)->capacity + ((*arrp)->capacity + 1)/2); \ + } \ + (*arrp)->buf[(*arrp)->size++] = *elem; \ +} \ + \ +__TK_ARRAY_UNUSED \ +static size_t \ +AT##_PopBack(struct AT *arr) \ +{ \ + assert(!AT##_IsEmpty(arr)); \ + return arr->size -= 1; \ +} \ + \ +__TK_ARRAY_UNUSED \ +static ElemType * \ +AT##_Get(struct AT *arr, size_t at) \ +{ \ + assert(at < AT##_Size(arr)); \ + return &arr->buf[at]; \ +} \ + \ +__TK_ARRAY_UNUSED \ +static void \ +AT##_Set(struct AT *arr, size_t at, ElemType *elem) \ +{ \ + assert(at < AT##_Size(arr)); \ + arr->buf[at] = *elem; \ +} \ + \ +__TK_ARRAY_UNUSED \ +static void \ +AT##_Free(struct AT **arrp) \ +{ \ + AT##_Resize(arrp, 0); \ +} \ +/* ------------------------------------------------------------------------- */ + +#define TK_PTR_ARRAY_DEFINE(AT, ElemType) /* AT = type of array */ \ +/* ------------------------------------------------------------------------- */ \ +typedef struct AT { \ + size_t size; \ + size_t capacity; \ + ElemType *buf[1]; \ +} AT; \ + \ +__TK_ARRAY_UNUSED \ +static size_t \ +AT##_ElemSize() \ +{ \ + return sizeof(ElemType); \ +} \ + \ +__TK_ARRAY_UNUSED \ +static size_t \ +AT##_BufferSize(size_t numElems) \ +{ \ + return numElems*sizeof(ElemType *); \ +} \ + \ +__TK_ARRAY_UNUSED \ +static int \ +AT##_IsEmpty(struct AT *arr) \ +{ \ + assert(!arr || arr->size != 0xdeadbeef); \ + return !arr || arr->size == 0; \ +} \ + \ +__TK_ARRAY_UNUSED \ +static ElemType ** \ +AT##_First(struct AT *arr) \ +{ \ + assert(!arr || arr->size != 0xdeadbeef); \ + return arr ? arr->buf : NULL; \ +} \ + \ +__TK_ARRAY_UNUSED \ +static ElemType ** \ +AT##_Last(struct AT *arr) \ +{ \ + assert(!arr || arr->size != 0xdeadbeef); \ + return arr ? arr->buf + arr->size : NULL; \ +} \ + \ +__TK_ARRAY_UNUSED \ +static ElemType * \ +AT##_Front(struct AT *arr) \ +{ \ + assert(!arr || arr->size != 0xdeadbeef); \ + assert(!AT##_IsEmpty(arr)); \ + return arr->buf[0]; \ +} \ + \ +__TK_ARRAY_UNUSED \ +static ElemType * \ +AT##_Back(struct AT *arr) \ +{ \ + assert(!arr || arr->size != 0xdeadbeef); \ + assert(!AT##_IsEmpty(arr)); \ + return arr->buf[arr->size - 1]; \ +} \ + \ +__TK_ARRAY_UNUSED \ +static int \ +AT##_Size(const struct AT *arr) \ +{ \ + assert(!arr || arr->size != 0xdeadbeef); \ + return arr ? arr->size : 0; \ +} \ + \ +__TK_ARRAY_UNUSED \ +static int \ +AT##_Capacity(const struct AT *arr) \ +{ \ + assert(!arr || arr->size != 0xdeadbeef); \ + return arr ? arr->capacity : 0; \ +} \ + \ +__TK_ARRAY_UNUSED \ +static void \ +AT##_Resize(struct AT **arrp, size_t newCapacity) \ +{ \ + assert(arrp); \ + assert(!*arrp || (*arrp)->size != 0xdeadbeef); \ + if (newCapacity == 0) { \ + assert(!*arrp || ((*arrp)->size = 0xdeadbeef)); \ + ckfree(*arrp); \ + *arrp = NULL; \ + } else { \ + int init = *arrp == NULL; \ + unsigned memSize = sizeof(AT) + (newCapacity - 1)*sizeof(ElemType *); \ + *arrp = ckrealloc(*arrp, memSize); \ + if (init) { \ + (*arrp)->size = 0; \ + } else if (newCapacity < (*arrp)->size) { \ + (*arrp)->size = newCapacity; \ + } \ + (*arrp)->capacity = newCapacity; \ + } \ +} \ + \ +__TK_ARRAY_UNUSED \ +static void \ +AT##_Clear(struct AT *arr, size_t from, size_t to) \ +{ \ + assert(to <= AT##_Size(arr)); \ + assert(from <= to); \ + memset(arr->buf + from, 0, PromArr_BufferSize(to - from)); \ +} \ + \ +__TK_ARRAY_UNUSED \ +static void \ +AT##_ResizeAndClear(struct AT **arrp, size_t newCapacity) \ +{ \ + size_t oldCapacity; \ + assert(arrp); \ + oldCapacity = *arrp ? (*arrp)->capacity : 0; \ + AT##_Resize(arrp, newCapacity); \ + if (newCapacity > oldCapacity) { \ + size_t memSize = PromArr_BufferSize(newCapacity - oldCapacity); \ + memset((*arrp)->buf + oldCapacity, 0, memSize); \ + } \ +} \ + \ +__TK_ARRAY_UNUSED \ +static void \ +AT##_SetSize(struct AT *arr, size_t newSize) \ +{ \ + assert(arr); \ + assert(newSize <= AT##_Capacity(arr)); \ + arr->size = newSize; \ +} \ + \ +__TK_ARRAY_UNUSED \ +static void \ +AT##_Append(struct AT **arrp, ElemType *elem) \ +{ \ + assert(arrp); \ + if (!*arrp) { \ + AT##_Resize(arrp, 1); \ + } else if ((*arrp)->size == (*arrp)->capacity) { \ + AT##_Resize(arrp, (*arrp)->capacity + ((*arrp)->capacity + 1)/2); \ + } \ + (*arrp)->buf[(*arrp)->size++] = elem; \ +} \ + \ +__TK_ARRAY_UNUSED \ +static size_t \ +AT##_PopBack(struct AT *arr) \ +{ \ + assert(!AT##_IsEmpty(arr)); \ + return arr->size -= 1; \ +} \ + \ +__TK_ARRAY_UNUSED \ +static ElemType * \ +AT##_Get(const struct AT *arr, size_t at) \ +{ \ + assert(at < AT##_Size(arr)); \ + return arr->buf[at]; \ +} \ + \ +__TK_ARRAY_UNUSED \ +static void \ +AT##_Set(struct AT *arr, size_t at, ElemType *elem) \ +{ \ + assert(at < AT##_Size(arr)); \ + arr->buf[at] = elem; \ +} \ + \ +__TK_ARRAY_UNUSED \ +static void \ +AT##_Free(struct AT **arrp) \ +{ \ + AT##_Resize(arrp, 0); \ +} \ + \ +__TK_ARRAY_UNUSED \ +static int \ +AT##_Find(const struct AT *arr, const ElemType *elem) \ +{ \ + assert(!arr || arr->size != 0xdeadbeef); \ + if (arr) { \ + ElemType * const * buf = arr->buf; \ + size_t i; \ + for (i = 0; i < arr->size; ++i) { \ + if (buf[i] == elem) { \ + return i; \ + } \ + } \ + } \ + return -1; \ +} \ +/* ------------------------------------------------------------------------- */ + +#endif /* TK_ARRAY_DEFINED */ + +/* + * Local Variables: + * mode: c + * c-basic-offset: 4 + * fill-column: 105 + * End: + * vi:set ts=8 sw=4: + */ diff --git a/generic/tkBind.c b/generic/tkBind.c index bfedfe0..4ae1274 100644 --- a/generic/tkBind.c +++ b/generic/tkBind.c @@ -7,12 +7,15 @@ * Copyright (c) 1989-1994 The Regents of the University of California. * Copyright (c) 1994-1997 Sun Microsystems, Inc. * Copyright (c) 1998 by Scriptics Corporation. + * Copyright (c) 2018 by Gregor Cramer. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tkInt.h" +#include "tkDList.h" +#include "tkArray.h" #ifdef _WIN32 #include "tkWinInt.h" @@ -22,6 +25,31 @@ #include "tkUnixInt.h" #endif +#include + +#if NDEBUG +# define DEBUG(expr) +#else +# define DEBUG(expr) expr +#endif + +#ifdef _MSC_VER +/* + * Earlier versions of MSVC don't know snprintf, but _snprintf is compatible. + * Note that sprintf is deprecated. + */ +# define snprintf _snprintf +#endif + +#define SIZE_OF_ARRAY(arr) sizeof(arr)/sizeof(arr[0]) + +/* + * Simple boolean type support. + */ + +#define bool int +enum { true = (bool) 1, false = (bool) 0 }; + /* * File structure: * @@ -29,8 +57,7 @@ * * Init/Free this package. * - * Tcl "bind" command (actually located in tkCmds.c) core implementation, plus - * helpers. + * Tcl "bind" command (actually located in tkCmds.c) core implementation, plus helpers. * * Tcl "event" command implementation, plus helpers. * @@ -40,21 +67,98 @@ */ /* + * In old implementation and <1><1> are equivalent sequences. This is not logical, + * in my opinion should have higher precedence, but we will keep the new implementation + * conform. If the decision about this point will change then PREFER_MOST_SPECIALIZED_EVENT + * should be set to 1. + */ + +#ifndef PREFER_MOST_SPECIALIZED_EVENT +# define PREFER_MOST_SPECIALIZED_EVENT 0 +#endif + +/* + * Traditionally motion events can be combined with buttons in this way: . + * I think it should be allowed to express this as . The latter syntax form + * is already implemented, but still commented out. + */ + +#ifndef SUPPORT_ADDITIONAL_MOTION_SYNTAX +# define SUPPORT_ADDITIONAL_MOTION_SYNTAX 0 /* set to 1 if wanted */ +#endif + +#ifndef PRINT_SHORT_MOTION_SYNTAX +# define PRINT_SHORT_MOTION_SYNTAX 0 /* set to 1 if wanted */ +#endif + +#if !SUPPORT_ADDITIONAL_MOTION_SYNTAX +# undef PRINT_SHORT_MOTION_SYNTAX +# define PRINT_SHORT_MOTION_SYNTAX 0 +#endif + +/* + * For debugging only, normally set to zero. + */ + +#ifdef SUPPORT_DEBUGGING +# undef SUPPORT_DEBUGGING +#endif +#define SUPPORT_DEBUGGING 0 + +/* * 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. */ + unsigned long info; /* This either corresponds to xkey.keycode, or to xbutton.button, + * or is meaningless, depending on event type. */ + Tk_Uid name; /* Tk_Uid of virtual event. */ } Detail; /* + * We need an extended event definition. + */ + +typedef struct { + XEvent xev; /* The original event from server. */ + Detail detail; /* Additional information (for hashing). */ + unsigned countAny; /* Count of multi-events, like multi-clicks, or repeated key pressing, + * this count does not depend on detail (keySym or button). */ + unsigned countDetailed; + /* Count of multi-events, like multi-clicks, or repeated key pressing, + * this count considers the detail (keySym or button). */ +} Event; + +/* + * We need a structure providing a list of pattern sequences. + */ + +struct PatSeq; /* forward declaration */ + +typedef struct PSEntry { + TK_DLIST_LINKS(PSEntry); /* Makes this struct a double linked list; must be first entry. */ + struct PatSeq* psPtr; /* Pointer to pattern sequence. */ + Window window; /* Window of last match. */ + unsigned expired:1; /* Whether this entry is expired, this means it has to be removed + * from promotion list. */ + unsigned isNew:1; /* Whether this entry is recently created. */ +} PSEntry; + +TK_DLIST_DEFINE(PSList, PSEntry);/* defining the whole PSList_* stuff (list of PSEntry items) */ + +/* + * Maps and lookup tables from an event to a list of patterns that match that event. + */ + +typedef struct { + Tcl_HashTable patternTable; /* Keys are PatternTableKey structs, values are (PatSeq *). */ + Tcl_HashTable listTable; /* Keys are PatternTableKey structs, values are (PSList *). */ + PSList entryPool; /* Contains free (unused) list items. */ + unsigned number; /* Needed for enumeration of pattern sequences. */ +} LookupTables; + +/* * 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 @@ -65,36 +169,22 @@ typedef union { * 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 +/* defining the whole Promotion_* stuff (array of PSList entries) */ +TK_ARRAY_DEFINE(PromArr, PSList); + 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. */ + Event eventInfo[TK_LASTEVENT]; + /* Containing the most recent event for every event type. */ + PromArr *promArr; /* Contains the promoted pattern sequences. */ + Event *curEvent; /* Pointing to most recent event. */ + unsigned long curModStateMask; + /* Containing the current modifier mask. */ + LookupTables lookupTables; /* Containing hash tables for fast lookup. */ + 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; /* @@ -111,15 +201,10 @@ typedef struct Tk_BindingTable_ { */ 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. */ + LookupTables lookupTables; /* Providing fast lookup tables to lists of pattern sequences. */ + 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 PhysOwned structs. */ } VirtualEventTable; /* @@ -138,14 +223,12 @@ typedef struct { */ 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. */ + 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. */ + unsigned type; /* Type of event (from X). */ + Detail detail; /* Additional information, such as keysym, button, Tk_Uid, or zero + * if nothing additional. */ } PatternTableKey; /* @@ -154,22 +237,28 @@ typedef struct { */ 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). */ + unsigned eventType; /* Type of X event, e.g. ButtonPress. */ + unsigned long modStateMask; /* Mask of modifiers that must be present (zero means no modifiers + * are required). */ + unsigned count; /* Multi-event count, e.g. double-clicks, triple-clicks, etc. */ + unsigned long info; /* Additional information that must match event. Normally this is zero, + * meaning no additional information must match. For KeyPress and + * KeyRelease events, it may be specified to select a particular + * keystroke (zero means any keystrokes). For button events, specifies + * a particular button (zero means any buttons are OK). */ + Tk_Uid name; /* Specifies the Tk_Uid of the virtual event name. NULL if not a + * virtual event. */ } TkPattern; /* + * The following structure keeps track of all the virtual events that are + * associated with a particular physical event. It is pointed to by the 'owners' + * field in a PatSeq in the patternTable of a virtual event table. + */ + +TK_PTR_ARRAY_DEFINE(VirtOwners, Tcl_HashEntry); /* define array of hash entries */ + +/* * 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 @@ -188,78 +277,70 @@ typedef struct { */ 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. */ + unsigned numPats; /* Number of patterns in sequence (usually 1). */ + unsigned count; /* Total number of repetition counts, summed over count in TkPattern. */ + unsigned number; /* Needed for the decision whether a binding is later than another, + * it is guaranteed that the most recently bound event has the + * highest number. */ + bool added; /* Is this pattern sequence already added to lookup table? */ + char *script; /* Binding script to evaluate when sequence matches (ckalloc()ed) */ + Tcl_Obj* object; /* Token for object with which binding is associated. For virtual + * event table this is NULL. */ + 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. */ + union { + VirtOwners *owners; /* In a binding table it has no meaning. In a virtual event table, + * identifies the array of virtual events that can be triggered + * by this event. */ + struct PatSeq *nextObj; /* 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 it has no meaning. */ + } ptr; + DEBUG(bool owned); /* For debugging purposes. */ + TkPattern pats[1]; /* Array of "numPats" patterns. Only one element is declared here + * but in actuality enough space will be allocated for "numPats" + * patterns (but usually 1). */ } 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. + * Constants that define how close together two events must be in milliseconds + * or pixels to meet the PAT_NEARBY constraint: */ -#define PAT_NEARBY 0x1 +#define NEARBY_PIXELS 5 +#define NEARBY_MS 500 /* - * Constants that define how close together two events must be in milliseconds - * or pixels to meet the PAT_NEARBY constraint: + * Constants for access to appropriate slot container. */ -#define NEARBY_PIXELS 5 -#define NEARBY_MS 500 +#define PRESS 0 /* for ButtonPress */ +#define RELEASE 1 /* for ButtonRelease */ +#define PRESSED 2 /* for MotionNotify */ /* - * 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. + * Constant for any (non-zero) button. + */ + +#define ALL_BUTTONS_MASK (Button1Mask|Button2Mask|Button3Mask|Button4Mask|Button5Mask) + +/* + * Needed as "no-number" constant for integers. The value of this constant is + * outside of integer range (type "int"). (Unfortunatly current version of + * Tcl/Tk does not support C99 integer support.) */ -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; +#define NO_NUMBER (((Tcl_WideUInt) 1) << (8*sizeof(Tcl_WideInt) - 1)) /* * 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; + +TK_PTR_ARRAY_DEFINE(PhysOwned, PatSeq); /* define array of pattern seqs */ /* * One of the following structures exists for each interpreter. This structure @@ -269,11 +350,9 @@ typedef struct { */ typedef struct { - TkDisplay *curDispPtr; /* Display for last binding command invoked in - * this application. */ + 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. */ + unsigned bindingDepth; /* Number of active instances of Tk_BindEvent in this application. */ } ScreenInfo; /* @@ -283,13 +362,13 @@ typedef struct { 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. */ + /* 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. */ + bool deleted; /* True if the application has been deleted but the structure has been + * preserved. */ + Time lastEventTime; /* Needed for time measurement. */ + Time lastCurrentTime; /* Needed for time measurement. */ } BindInfo; /* @@ -304,7 +383,7 @@ typedef struct TkBindInfo_ { #ifdef REDO_KEYSYM_LOOKUP typedef struct { - const char *name; /* Name of keysym. */ + const char *name; /* Name of keysym. */ KeySym value; /* Numeric identifier for keysym. */ } KeySymInfo; static const KeySymInfo keyArray[] = { @@ -313,19 +392,11 @@ static const KeySymInfo keyArray[] = { #endif {NULL, 0} }; -static Tcl_HashTable keySymTable; /* keyArray hashed by keysym value. */ -static Tcl_HashTable nameTable; /* keyArray hashed by keysym name. */ +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 @@ -333,29 +404,24 @@ TCL_DECLARE_MUTEX(bindMutex) */ 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. */ + const char *name; /* Name of modifier. */ + unsigned mask; /* Button/modifier mask value, such as Button1Mask. */ + unsigned 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. + * 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 +#define DOUBLE (1<<0) +#define TRIPLE (1<<1) +#define QUADRUPLE (1<<2) +#define MULT_CLICKS (DOUBLE|TRIPLE|QUADRUPLE) static const ModInfo modArray[] = { {"Control", ControlMask, 0}, @@ -403,9 +469,8 @@ static Tcl_HashTable modTable; 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. */ + unsigned type; /* Event type for X, such as ButtonPress. */ + unsigned eventMask; /* Mask bits (for XSelectInput) for this event type. */ } EventInfo; /* @@ -421,10 +486,8 @@ static const EventInfo eventArray[] = { {"KeyRelease", KeyRelease, KeyPressMask|KeyReleaseMask}, {"Button", ButtonPress, ButtonPressMask}, {"ButtonPress", ButtonPress, ButtonPressMask}, - {"ButtonRelease", ButtonRelease, - ButtonPressMask|ButtonReleaseMask}, - {"Motion", MotionNotify, - ButtonPressMask|PointerMotionMask}, + {"ButtonRelease", ButtonRelease, ButtonPressMask|ButtonReleaseMask}, + {"Motion", MotionNotify, ButtonPressMask|PointerMotionMask}, {"Enter", EnterNotify, EnterWindowMask}, {"Leave", LeaveNotify, LeaveWindowMask}, {"FocusIn", FocusIn, FocusChangeMask}, @@ -452,6 +515,8 @@ static const EventInfo eventArray[] = { }; static Tcl_HashTable eventTable; +static int eventArrayIndex[TK_LASTEVENT]; + /* * The defines and table below are used to classify events into various * groups. The reason for this is that logically identical fields (e.g. @@ -460,29 +525,29 @@ static Tcl_HashTable eventTable; * 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 (1<<0) +#define BUTTON (1<<1) +#define MOTION (1<<2) +#define CROSSING (1<<3) +#define FOCUS (1<<4) +#define EXPOSE (1<<5) +#define VISIBILITY (1<<6) +#define CREATE (1<<7) +#define DESTROY (1<<8) +#define UNMAP (1<<9) +#define MAP (1<<10) +#define REPARENT (1<<11) +#define CONFIG (1<<12) +#define GRAVITY (1<<13) +#define CIRC (1<<14) +#define PROP (1<<15) +#define COLORMAP (1<<16) +#define VIRTUAL (1<<17) +#define ACTIVATE (1<<18) +#define MAPREQ (1<<19) +#define CONFIGREQ (1<<20) +#define RESIZEREQ (1<<21) +#define CIRCREQ (1<<22) #define KEY_BUTTON_MOTION_VIRTUAL (KEY|BUTTON|MOTION|VIRTUAL) #define KEY_BUTTON_MOTION_CROSSING (KEY|BUTTON|MOTION|VIRTUAL|CROSSING) @@ -535,11 +600,11 @@ static const int flagArray[TK_LASTEVENT] = { */ static const TkStateMap queuePosition[] = { - {-1, "now"}, - {TCL_QUEUE_HEAD, "head"}, - {TCL_QUEUE_MARK, "mark"}, - {TCL_QUEUE_TAIL, "tail"}, - {-2, NULL} + {-1, "now"}, + {TCL_QUEUE_HEAD, "head"}, + {TCL_QUEUE_MARK, "mark"}, + {TCL_QUEUE_TAIL, "tail"}, + {-2, NULL} }; /* @@ -570,8 +635,8 @@ static const TkStateMap notifyDetail[] = { }; static const TkStateMap circPlace[] = { - {PlaceOnTop, "PlaceOnTop"}, - {PlaceOnBottom, "PlaceOnBottom"}, + {PlaceOnTop, "PlaceOnTop"}, + {PlaceOnBottom, "PlaceOnBottom"}, {-1, NULL} }; @@ -583,12 +648,12 @@ static const TkStateMap visNotify[] = { }; static const TkStateMap configureRequestDetail[] = { - {None, "None"}, - {Above, "Above"}, - {Below, "Below"}, - {BottomIf, "BottomIf"}, - {TopIf, "TopIf"}, - {Opposite, "Opposite"}, + {None, "None"}, + {Above, "Above"}, + {Below, "Below"}, + {BottomIf, "BottomIf"}, + {TopIf, "TopIf"}, + {Opposite, "Opposite"}, {-1, NULL} }; @@ -602,253 +667,795 @@ static const TkStateMap propNotify[] = { * 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 ChangeScreen(Tcl_Interp *interp, char *dispName, int screenIndex); +static bool 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 void ExpandPercents(TkWindow *winPtr, const char *before, Event *eventPtr, + unsigned scriptCount, Tcl_DString *dsPtr); +static PatSeq * FindSequence(Tcl_Interp *interp, LookupTables *lookupTables, + ClientData object, const char *eventString, bool create, + bool allowVirtual, unsigned long *maskPtr); +static void GetAllVirtualEvents(Tcl_Interp *interp, VirtualEventTable *vetPtr); +static const char * GetField(const char *p, char *copy, unsigned size); +static Tcl_Obj * GetPatternObj(const 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, +static PatSeq * MatchPatterns(TkDisplay *dispPtr, Tk_BindingTable bindPtr, PSList *psList, + PSList *psSuccList, unsigned patIndex, const Event *eventPtr, + ClientData object, PatSeq **physPtrPtr); +static bool 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 unsigned ParseEventDescription(Tcl_Interp *interp, const char **eventStringPtr, + TkPattern *patPtr, unsigned long *eventMaskPtr); static void DoWarp(ClientData clientData); +static PSList * GetLookupForEvent(LookupTables* lookupPtr, const Event *eventPtr, + Tcl_Obj *object, bool onlyConsiderDetailedEvents); +static void ClearLookupTable(LookupTables *lookupTables, ClientData object); +static void ClearPromotionLists(Tk_BindingTable bindPtr, ClientData object); +static PSEntry * MakeListEntry(PSList *pool, PatSeq *psPtr); +static void RemovePatSeqFromLookup(LookupTables *lookupTables, PatSeq *psPtr); +static void RemovePatSeqFromPromotionLists(Tk_BindingTable bindPtr, PatSeq *psPtr); +static PatSeq * DeletePatSeq(PatSeq *psPtr); +static void InsertPatSeq(LookupTables *lookupTables, PatSeq *psPtr); /* - *--------------------------------------------------------------------------- - * - * 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. - * - *--------------------------------------------------------------------------- + * Some useful helper functions. */ +#ifdef SUPPORT_DEBUGGING +static int N = 0; +#endif -void -TkBindInit( - TkMainInfo *mainPtr) /* The newly created application. */ +static unsigned Max(unsigned a, unsigned b) { return a < b ? b : a; } +static int Abs(int n) { return n < 0 ? -n : n; } +static bool IsOdd(int n) { return n & 1; } + +static const char* +SkipSpaces( + const char* s) { - BindInfo *bindInfoPtr; + assert(s); + while (isspace(UCHAR(*s))) + ++s; + return s; +} - if (sizeof(XEvent) < sizeof(XVirtualEvent)) { - Tcl_Panic("TkBindInit: virtual events can't be supported"); +static const char* +SkipFieldDelims( + const char* s) +{ + assert(s); + while (*s == '-' || isspace(UCHAR(*s))) { + ++s; } + return s; +} - /* - * Initialize the static data structures used by the binding package. They - * are only initialized once, no matter how many interps are created. - */ +static unsigned +GetButtonNumber( + const char *field) +{ + assert(field); - 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; + if (field[0] >= '1' && field[0] <= '5' && field[1] == '\0') { + return field[0] - '0'; + } + return 0; +} - 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 */ +static Time +CurrentTimeInMilliSecs() +{ + Tcl_Time now; + Tcl_GetTime(&now); + return ((Time) now.sec)*1000 + ((Time) now.usec)/1000; +} - Tcl_InitHashTable(&modTable, TCL_STRING_KEYS); - for (modPtr = modArray; modPtr->name != NULL; modPtr++) { - hPtr = Tcl_CreateHashEntry(&modTable, modPtr->name, &newEntry); - Tcl_SetHashValue(hPtr, modPtr); - } +#if PREFER_MOST_SPECIALIZED_EVENT +static unsigned +GetCount( + const PatSeq *psPtr, + unsigned index) +{ + assert(psPtr); + assert(psPtr->numPats >= 1); + assert(index < psPtr->numPats); - Tcl_InitHashTable(&eventTable, TCL_STRING_KEYS); - for (eiPtr = eventArray; eiPtr->name != NULL; eiPtr++) { - hPtr = Tcl_CreateHashEntry(&eventTable, eiPtr->name, &newEntry); - Tcl_SetHashValue(hPtr, eiPtr); + return psPtr->pats[index].count; +} +#endif + +static bool +MatchEventNearby( + const XEvent *lhs, + const XEvent *rhs) +{ + assert(lhs); + assert(rhs); + assert(lhs->type == ButtonPress || lhs->type == ButtonRelease || lhs->type == MotionNotify); + assert(lhs->type == rhs->type); + + /* assert: lhs->xbutton.time <= rhs->xbutton.time */ + + return rhs->xbutton.time - lhs->xbutton.time <= NEARBY_MS + && Abs(rhs->xbutton.x_root - lhs->xbutton.x_root) <= NEARBY_PIXELS + && Abs(rhs->xbutton.y_root - lhs->xbutton.y_root) <= NEARBY_PIXELS; +} + +static void +FreePatSeq( + PatSeq *psPtr) +{ + assert(psPtr); + assert(!psPtr->owned); + DEBUG(psPtr->added = 0xdeadbeef); + ckfree(psPtr->script); + if (!psPtr->object) { + VirtOwners_Free(&psPtr->ptr.owners); + } + ckfree(psPtr); +} + +static void +RemoveListEntry( + PSList *pool, + PSEntry *psEntry) +{ + assert(pool); + assert(psEntry); + + PSList_Remove(psEntry); + PSList_Append(pool, psEntry); +} + +static void +ClearList( + PSList *psList, + PSList *pool, + ClientData object) +{ + assert(psList); + assert(pool); + + if (object) { + PSEntry *psEntry; + PSEntry *psNext; + + for (psEntry = PSList_First(psList); psEntry; psEntry = psNext) { + psNext = PSList_Next(psEntry); + if (psEntry->psPtr->object == object) { + RemoveListEntry(pool, psEntry); } - initialized = 1; } - Tcl_MutexUnlock(&bindMutex); + } else { + PSList_Move(pool, psList); + } +} + +static unsigned +ResolveModifiers( + TkDisplay *dispPtr, + unsigned modMask) +{ + assert(dispPtr); + + if (dispPtr->metaModMask) { + if (modMask & META_MASK) { + modMask |= dispPtr->metaModMask; + modMask &= ~META_MASK; + } + } + if (dispPtr->altModMask) { + if (modMask & ALT_MASK) { + modMask |= dispPtr->altModMask; + modMask &= ~ALT_MASK; + } } - mainPtr->bindingTable = Tk_CreateBindingTable(mainPtr->interp); + return modMask; +} - 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; +static unsigned +ButtonNumberFromState( + unsigned state) +{ + if (!(state & ALL_BUTTONS_MASK)) { return 0; } + if (state & Button1Mask) { return 1; } + if (state & Button2Mask) { return 2; } + if (state & Button3Mask) { return 3; } + if (state & Button4Mask) { return 4; } + return 5; +} - TkpInitializeMenuBindings(mainPtr->interp, mainPtr->bindingTable); +#if SUPPORT_ADDITIONAL_MOTION_SYNTAX +static unsigned +ButtonNumberToMask( + unsigned button) +{ + assert(button > 0); + return Button1Mask << (button - 1); +} +#endif + +static void +SetupPatternKey( + PatternTableKey *key, + const PatSeq *psPtr) +{ + const TkPattern *patPtr; + + assert(key); + assert(psPtr); + + patPtr = psPtr->pats; + key->object = psPtr->object; + key->type = patPtr->eventType; + key->detail.name = patPtr->name; + if (patPtr->info) { + key->detail.info = patPtr->info; + } } /* - *--------------------------------------------------------------------------- + *-------------------------------------------------------------- * - * TkBindFree -- + * MakeListEntry -- * - * This function is called when an application is deleted. It deletes all - * the structures used by bindings and virtual events. + * Makes new entry item for lookup table. We are using a + * pool of items, this avoids superfluous memory allocation/ + * deallocation. * * Results: - * None. + * New entry item. * * Side effects: - * Memory freed. + * Memory allocated. * - *--------------------------------------------------------------------------- + *-------------------------------------------------------------- */ -void -TkBindFree( - TkMainInfo *mainPtr) /* The newly created application. */ +static PSEntry * +MakeListEntry( + PSList *pool, + PatSeq *psPtr) { - BindInfo *bindInfoPtr; + PSEntry *newEntry = NULL; - Tk_DeleteBindingTable(mainPtr->bindingTable); - mainPtr->bindingTable = NULL; + assert(pool); + assert(psPtr); + assert(psPtr->added != 0xdeadbeef); - bindInfoPtr = mainPtr->bindInfo; - DeleteVirtualEventTable(&bindInfoPtr->virtualEventTable); - bindInfoPtr->deleted = 1; - Tcl_EventuallyFree(bindInfoPtr, TCL_DYNAMIC); - mainPtr->bindInfo = NULL; + if (PSList_IsEmpty(pool)) { + newEntry = ckalloc(sizeof(PSEntry)); + } else { + newEntry = PSList_First(pool); + PSList_RemoveHead(pool); + } + + newEntry->psPtr = psPtr; + newEntry->window = None; + newEntry->expired = false; + newEntry->isNew = true; + DEBUG(psPtr->owned = false); + + return newEntry; } /* *-------------------------------------------------------------- * - * Tk_CreateBindingTable -- + * GetLookupForEvent -- * - * Set up a new domain in which event bindings may be created. + * Get specific pattern sequence table for given event. * * Results: - * The return value is a token for the new table, which must be passed to - * functions like Tk_CreateBinding. + * Specific pattern sequence table for given event. * * Side effects: - * Memory is allocated for the new table. + * None. * *-------------------------------------------------------------- */ -Tk_BindingTable -Tk_CreateBindingTable( - Tcl_Interp *interp) /* Interpreter to associate with the binding - * table: commands are executed in this - * interpreter. */ +static PSList * +GetLookupForEvent( + LookupTables* lookupTables, + const Event *eventPtr, + Tcl_Obj *object, + bool onlyConsiderDetailedEvents) { - BindingTable *bindPtr = ckalloc(sizeof(BindingTable)); - int i; + PatternTableKey key; + Tcl_HashEntry *hPtr; - /* - * Create and initialize a new binding table. - */ + assert(lookupTables); + assert(eventPtr); + + key.detail.name = NULL; + key.detail.info = 0; + + if (onlyConsiderDetailedEvents) { + switch (eventPtr->xev.type) { + case ButtonPress: + case ButtonRelease: + key.detail.info = eventPtr->xev.xbutton.button; + break; + case MotionNotify: + key.detail.info = ButtonNumberFromState(eventPtr->xev.xmotion.state); + break; + case KeyPress: + case KeyRelease: + key.detail.info = eventPtr->detail.info; + break; + case VirtualEvent: + key.detail.name = eventPtr->detail.name; + break; + } - for (i = 0; i < EVENT_BUFFER_SIZE; i++) { - bindPtr->eventRing[i].type = -1; + if (!key.detail.name) { + return NULL; + } } - bindPtr->curEvent = 0; - Tcl_InitHashTable(&bindPtr->patternTable, - sizeof(PatternTableKey)/sizeof(int)); - Tcl_InitHashTable(&bindPtr->objectTable, TCL_ONE_WORD_KEYS); - bindPtr->interp = interp; - return bindPtr; + + key.object = object; + key.type = eventPtr->xev.type; + hPtr = Tcl_FindHashEntry(&lookupTables->listTable, (char *) &key); + return hPtr ? Tcl_GetHashValue(hPtr) : NULL; } /* *-------------------------------------------------------------- * - * Tk_DeleteBindingTable -- + * ClearLookupTable -- * - * Destroy a binding table and free up all its memory. The caller should - * not use bindingTable again after this function returns. + * Clear lookup table in given binding table, but only those + * bindings associated to given object. If object is NULL + * then remove all entries. * * Results: * None. * * Side effects: - * Memory is freed. + * None. * *-------------------------------------------------------------- */ - -void -Tk_DeleteBindingTable( - Tk_BindingTable bindPtr) /* Token for the binding table to destroy. */ +static void +ClearLookupTable( + LookupTables *lookupTables, + ClientData object) { - PatSeq *psPtr, *nextPtr; - Tcl_HashEntry *hPtr; Tcl_HashSearch search; + Tcl_HashEntry *hPtr; + Tcl_HashEntry *nextPtr; + PSList *pool = &lookupTables->entryPool; - /* - * Find and delete all of the patterns associated with the binding table. - */ + assert(lookupTables); - 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); + for (hPtr = Tcl_FirstHashEntry(&lookupTables->listTable, &search); hPtr; hPtr = nextPtr) { + PSList *psList; + + nextPtr = Tcl_NextHashEntry(&search); + + if (object) { + const PatternTableKey *key = Tcl_GetHashKey(&lookupTables->listTable, hPtr); + if (key->object != object) { + continue; + } + Tcl_DeleteHashEntry(hPtr); + } + + psList = Tcl_GetHashValue(hPtr); + PSList_Move(pool, psList); + ckfree(psList); + } +} + +/* + *-------------------------------------------------------------- + * + * ClearPromotionLists -- + * + * Clear all the lists holding the promoted pattern + * sequences, which belongs to given object. If object + * is NULL then remove all patterns. + * + * Results: + * None. + * + * Side effects: + * None. + * + *-------------------------------------------------------------- + */ + +static void +ClearPromotionLists( + Tk_BindingTable bindPtr, + ClientData object) +{ + unsigned newArraySize = 0; + unsigned i; + + assert(bindPtr); + + for (i = 0; i < PromArr_Size(bindPtr->promArr); ++i) { + PSList *psList = PromArr_Get(bindPtr->promArr, i); + ClearList(psList, &bindPtr->lookupTables.entryPool, object); + if (!PSList_IsEmpty(psList)) { + newArraySize = Max(newArraySize, i + 1); + } + } + + PromArr_SetSize(bindPtr->promArr, newArraySize); +} + +/* + *--------------------------------------------------------------------------- + * + * 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. */ +{ + TCL_DECLARE_MUTEX(bindMutex); + static bool initialized = false; + + BindInfo *bindInfoPtr; + + assert(mainPtr); + + /* otherwise virtual events can't be supported */ + assert(sizeof(XEvent) >= sizeof(XVirtualEvent)); + + /* type of TkPattern.info is well defined? */ + assert(sizeof(unsigned long) >= sizeof(KeySym)); + + /* ensure that our matching algorithm is working (when testing detail) */ + assert(sizeof(Detail) == sizeof(Tk_Uid)); + + /* test expected indices of Button1..Button5, otherwise our button handling is not working */ + assert(Button1 == 1 && Button2 == 2 && Button3 == 3 && Button4 == 4 && Button5 == 5); + assert(Button2Mask == (Button1Mask << 1)); + assert(Button2Mask == (Button1Mask << 2)); + assert(Button3Mask == (Button1Mask << 3)); + assert(Button4Mask == (Button1Mask << 4)); + assert(Button5Mask == (Button1Mask << 5)); + + /* test expected values of button motion masks, otherwise our button handling is not working */ + assert(Button1MotionMask == Button1Mask); + assert(Button2MotionMask == Button2Mask); + assert(Button3MotionMask == Button3Mask); + assert(Button4MotionMask == Button4Mask); + assert(Button5MotionMask == Button5Mask); + + /* because we expect zero if keySym is empty */ + assert(NoSymbol == 0L); + + /* we use some constraints about X*Event */ + assert(Tk_Offset(XButtonEvent, time) == Tk_Offset(XMotionEvent, time)); + assert(Tk_Offset(XButtonEvent, x_root) == Tk_Offset(XMotionEvent, x_root)); + assert(Tk_Offset(XButtonEvent, y_root) == Tk_Offset(XMotionEvent, y_root)); + assert(Tk_Offset(XCreateWindowEvent, border_width) == Tk_Offset(XConfigureEvent, border_width)); + assert(Tk_Offset(XCreateWindowEvent, width) == Tk_Offset(XConfigureEvent, width)); + assert(Tk_Offset(XCreateWindowEvent, window) == Tk_Offset(XCirculateRequestEvent, window)); + assert(Tk_Offset(XCreateWindowEvent, window) == Tk_Offset(XConfigureEvent, window)); + assert(Tk_Offset(XCreateWindowEvent, window) == Tk_Offset(XGravityEvent, window)); + assert(Tk_Offset(XCreateWindowEvent, window) == Tk_Offset(XMapEvent, window)); + assert(Tk_Offset(XCreateWindowEvent, window) == Tk_Offset(XReparentEvent, window)); + assert(Tk_Offset(XCreateWindowEvent, window) == Tk_Offset(XUnmapEvent, window)); + assert(Tk_Offset(XCreateWindowEvent, x) == Tk_Offset(XConfigureEvent, x)); + assert(Tk_Offset(XCreateWindowEvent, x) == Tk_Offset(XGravityEvent, x)); + assert(Tk_Offset(XCreateWindowEvent, y) == Tk_Offset(XConfigureEvent, y)); + assert(Tk_Offset(XCreateWindowEvent, y) == Tk_Offset(XGravityEvent, y)); + assert(Tk_Offset(XKeyEvent, root) == Tk_Offset(XButtonEvent, root)); + assert(Tk_Offset(XKeyEvent, root) == Tk_Offset(XCrossingEvent, root)); + assert(Tk_Offset(XKeyEvent, root) == Tk_Offset(XMotionEvent, root)); + assert(Tk_Offset(XKeyEvent, state) == Tk_Offset(XButtonEvent, state)); + assert(Tk_Offset(XKeyEvent, state) == Tk_Offset(XMotionEvent, state)); + assert(Tk_Offset(XKeyEvent, subwindow) == Tk_Offset(XButtonEvent, subwindow)); + assert(Tk_Offset(XKeyEvent, subwindow) == Tk_Offset(XCrossingEvent, subwindow)); + assert(Tk_Offset(XKeyEvent, subwindow) == Tk_Offset(XMotionEvent, subwindow)); + assert(Tk_Offset(XKeyEvent, time) == Tk_Offset(XButtonEvent, time)); + assert(Tk_Offset(XKeyEvent, time) == Tk_Offset(XCrossingEvent, time)); + assert(Tk_Offset(XKeyEvent, time) == Tk_Offset(XMotionEvent, time)); + assert(Tk_Offset(XKeyEvent, x) == Tk_Offset(XButtonEvent, x)); + assert(Tk_Offset(XKeyEvent, x) == Tk_Offset(XCrossingEvent, x)); + assert(Tk_Offset(XKeyEvent, x) == Tk_Offset(XMotionEvent, x)); + assert(Tk_Offset(XKeyEvent, x_root) == Tk_Offset(XButtonEvent, x_root)); + assert(Tk_Offset(XKeyEvent, x_root) == Tk_Offset(XCrossingEvent, x_root)); + assert(Tk_Offset(XKeyEvent, x_root) == Tk_Offset(XMotionEvent, x_root)); + assert(Tk_Offset(XKeyEvent, y) == Tk_Offset(XButtonEvent, y)); + assert(Tk_Offset(XKeyEvent, y) == Tk_Offset(XCrossingEvent, y)); + assert(Tk_Offset(XKeyEvent, y) == Tk_Offset(XMotionEvent, y)); + assert(Tk_Offset(XKeyEvent, y_root) == Tk_Offset(XButtonEvent, y_root)); + assert(Tk_Offset(XKeyEvent, y_root) == Tk_Offset(XCrossingEvent, y_root)); + assert(Tk_Offset(XKeyEvent, y_root) == Tk_Offset(XMotionEvent, y_root)); + + /* + * 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; + bool newEntry; + unsigned i; +#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; ++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 */ + + for (i = 0; i < SIZE_OF_ARRAY(eventArrayIndex); ++i) { + eventArrayIndex[i] = -1; + } + for (i = 0; i < SIZE_OF_ARRAY(eventArray); ++i) { + unsigned type = eventArray[i].type; + assert(type < TK_LASTEVENT); + if (type > 0 && eventArrayIndex[type] == -1) { + eventArrayIndex[type] = i; + } + } + + Tcl_InitHashTable(&modTable, TCL_STRING_KEYS); + for (modPtr = modArray; modPtr->name; ++modPtr) { + hPtr = Tcl_CreateHashEntry(&modTable, modPtr->name, &newEntry); + Tcl_SetHashValue(hPtr, modPtr); + } + + Tcl_InitHashTable(&eventTable, TCL_STRING_KEYS); + for (eiPtr = eventArray; eiPtr->name; ++eiPtr) { + hPtr = Tcl_CreateHashEntry(&eventTable, eiPtr->name, &newEntry); + Tcl_SetHashValue(hPtr, eiPtr); + } + + initialized = true; + } + 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 = false; + bindInfoPtr->lastCurrentTime = CurrentTimeInMilliSecs(); + bindInfoPtr->lastEventTime = 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; + + assert(mainPtr); + + Tk_DeleteBindingTable(mainPtr->bindingTable); + mainPtr->bindingTable = NULL; + bindInfoPtr = mainPtr->bindInfo; + DeleteVirtualEventTable(&bindInfoPtr->virtualEventTable); + bindInfoPtr->deleted = true; + 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)); + unsigned i; + + assert(interp); + + /* + * Create and initialize a new binding table. + */ + + memset(bindPtr, 0, sizeof(BindingTable)); + for (i = 0; i < SIZE_OF_ARRAY(bindPtr->eventInfo); ++i) { + bindPtr->eventInfo[i].xev.type = -1; + } + bindPtr->curEvent = bindPtr->eventInfo; /* do not assign NULL */ + bindPtr->lookupTables.number = 0; + PromArr_ResizeAndClear(&bindPtr->promArr, 2); + Tcl_InitHashTable(&bindPtr->lookupTables.listTable, sizeof(PatternTableKey)/sizeof(int)); + Tcl_InitHashTable(&bindPtr->lookupTables.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. */ +{ + Tcl_HashEntry *hPtr; + Tcl_HashSearch search; + + assert(bindPtr); + + /* + * Find and delete all of the patterns associated with the binding table. + */ + + hPtr = Tcl_FirstHashEntry(&bindPtr->lookupTables.patternTable, &search); + for ( ; hPtr; hPtr = Tcl_NextHashEntry(&search)) { + PatSeq *nextPtr; + PatSeq *psPtr; + + for (psPtr = Tcl_GetHashValue(hPtr); psPtr; psPtr = nextPtr) { + nextPtr = psPtr->nextSeqPtr; + assert(psPtr->added != 0xdeadbeef); + FreePatSeq(psPtr); } } /* + * Don't forget to release lookup elements. + */ + + ClearLookupTable(&bindPtr->lookupTables, NULL); + ClearPromotionLists(bindPtr, NULL); + PromArr_Free(&bindPtr->promArr); + PSList_Clear(&bindPtr->lookupTables.entryPool); + + /* * Clean up the rest of the information associated with the binding table. */ - Tcl_DeleteHashTable(&bindPtr->patternTable); + Tcl_DeleteHashTable(&bindPtr->lookupTables.patternTable); + Tcl_DeleteHashTable(&bindPtr->lookupTables.listTable); Tcl_DeleteHashTable(&bindPtr->objectTable); + ckfree(bindPtr); } /* *-------------------------------------------------------------- * + * InsertPatSeq -- + * + * Insert given pattern sequence into lookup table for fast + * access. + * + * Results: + * None. + * + * Side effects: + * Memory allocated. + * + *-------------------------------------------------------------- + */ + +static void +InsertPatSeq( + LookupTables *lookupTables, + PatSeq *psPtr) +{ + assert(lookupTables); + assert(psPtr); + assert(psPtr->numPats >= 1); + assert(psPtr->added != 0xdeadbeef); + + if (!(psPtr->added)) { + PatternTableKey key; + Tcl_HashEntry *hPtr; + bool isNew; + PSList *psList; + PSEntry *psEntry; + + SetupPatternKey(&key, psPtr); + hPtr = Tcl_CreateHashEntry(&lookupTables->listTable, (char *) &key, &isNew); + + if (isNew) { + psList = ckalloc(sizeof(PSList)); + PSList_Init(psList); + Tcl_SetHashValue(hPtr, psList); + } else { + psList = Tcl_GetHashValue(hPtr); + } + + psEntry = MakeListEntry(&lookupTables->entryPool, psPtr); + PSList_Append(psList, psEntry); + psPtr->added = true; + } +} +/* + *-------------------------------------------------------------- + * * Tk_CreateBinding -- * * Add a binding to a binding table, so that future calls to Tk_BindEvent @@ -874,35 +1481,47 @@ 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. */ + 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. */ + bool append) /* False means replace any existing binding for eventString; + * True 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; + char *oldStr; + char *newStr; + + assert(bindPtr); + assert(object); + assert(eventString); + assert(script); if (!*script) { /* Silently ignore empty scripts -- see SF#3006842 */ return 1; } - psPtr = FindSequence(interp, &bindPtr->patternTable, object, eventString, - 1, 1, &eventMask); - if (psPtr == NULL) { + psPtr = FindSequence(interp, &bindPtr->lookupTables, object, eventString, true, true, &eventMask); + if (!psPtr) { return 0; } - if (psPtr->script == NULL) { - int isNew; + assert(psPtr->added != 0xdeadbeef); + + if (psPtr->numPats > PromArr_Capacity(bindPtr->promArr)) { + /* + * We have to increase the size of array containing the lists of promoted sequences. + * Normally the maximal size is 2, only in very seldom cases a bigger size is needed. + * Note that for technical reasons the capacity should be one higher than the expected + * maximal size. + */ + PromArr_ResizeAndClear(&bindPtr->promArr, psPtr->numPats); + } + + if (!psPtr->script) { Tcl_HashEntry *hPtr; + bool isNew; /* * This pattern sequence was just created. Link the pattern into the @@ -910,33 +1529,28 @@ Tk_CreateBinding( * 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); - } + hPtr = Tcl_CreateHashEntry(&bindPtr->objectTable, (char *) object, &isNew); + psPtr->ptr.nextObj = isNew ? NULL : Tcl_GetHashValue(hPtr); Tcl_SetHashValue(hPtr, psPtr); + InsertPatSeq(&bindPtr->lookupTables, psPtr); } oldStr = psPtr->script; - if ((append != 0) && (oldStr != NULL)) { - size_t length1 = strlen(oldStr), length2 = strlen(script); + if (append && oldStr) { + size_t length1 = strlen(oldStr); + size_t length2 = strlen(script); newStr = ckalloc(length1 + length2 + 2); memcpy(newStr, oldStr, length1); newStr[length1] = '\n'; - memcpy(newStr+length1+1, script, length2+1); + 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); + memcpy(newStr, script, length + 1); } + ckfree(oldStr); psPtr->script = newStr; return eventMask; } @@ -963,83 +1577,196 @@ 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. */ + 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; + PatSeq *psPtr; - psPtr = FindSequence(interp, &bindPtr->patternTable, object, eventString, - 0, 1, &eventMask); - if (psPtr == NULL) { + assert(bindPtr); + assert(object); + assert(eventString); + + psPtr = FindSequence(interp, &bindPtr->lookupTables, object, eventString, false, true, NULL); + if (!psPtr) { Tcl_ResetResult(interp); - return TCL_OK; - } + } else { + Tcl_HashEntry *hPtr; + PatSeq *prevPtr; - /* - * Unlink the binding from the list for its object, then from the list for - * its pattern. - */ + assert(psPtr->added != 0xdeadbeef); - 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; + /* + * Unlink the binding from the list for its object. + */ + + if (!(hPtr = Tcl_FindHashEntry(&bindPtr->objectTable, (char *) object))) { + Tcl_Panic("Tk_DeleteBinding couldn't find object table entry"); + } + prevPtr = Tcl_GetHashValue(hPtr); + if (prevPtr == psPtr) { + Tcl_SetHashValue(hPtr, psPtr->ptr.nextObj); + } else { + for ( ; ; prevPtr = prevPtr->ptr.nextObj) { + if (!prevPtr) { + Tcl_Panic("Tk_DeleteBinding couldn't find on object list"); + } + if (prevPtr->ptr.nextObj == psPtr) { + prevPtr->ptr.nextObj = psPtr->ptr.nextObj; + break; + } } } + + RemovePatSeqFromLookup(&bindPtr->lookupTables, psPtr); + RemovePatSeqFromPromotionLists(bindPtr, psPtr); + DeletePatSeq(psPtr); } - prevPtr = Tcl_GetHashValue(psPtr->hPtr); - if (prevPtr == psPtr) { - if (psPtr->nextSeqPtr == NULL) { - Tcl_DeleteHashEntry(psPtr->hPtr); - } else { - Tcl_SetHashValue(psPtr->hPtr, psPtr->nextSeqPtr); + + 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. */ +{ + const PatSeq *psPtr; + + assert(bindPtr); + assert(object); + assert(eventString); + + psPtr = FindSequence(interp, &bindPtr->lookupTables, object, eventString, false, true, NULL); + assert(!psPtr || psPtr->added != 0xdeadbeef); + return psPtr ? psPtr->script : NULL; +} + +/* + *-------------------------------------------------------------- + * + * 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. */ +{ + Tcl_HashEntry *hPtr; + + assert(bindPtr); + assert(object); + + if ((hPtr = Tcl_FindHashEntry(&bindPtr->objectTable, (char *) object))) { + const PatSeq *psPtr; + Tcl_Obj *resultObj = Tcl_NewObj(); + + /* + * For each binding, output information about each of the patterns in its sequence. + */ + + for (psPtr = Tcl_GetHashValue(hPtr); psPtr; psPtr = psPtr->ptr.nextObj) { + assert(psPtr->added != 0xdeadbeef); + Tcl_ListObjAppendElement(NULL, resultObj, GetPatternObj(psPtr)); } - } 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; + Tcl_SetObjResult(interp, resultObj); + } +} + +/* + *-------------------------------------------------------------- + * + * RemovePatSeqFromLookup -- + * + * Remove given pattern sequence from lookup tables. This + * can be required before deleting the pattern sequence. + * + * Results: + * None. + * + * Side effects: + * None. + * + *-------------------------------------------------------------- + */ + +static void +RemovePatSeqFromLookup( + LookupTables *lookupTables, /* Remove from this lookup tables. */ + PatSeq *psPtr) /* Remove this pattern sequence. */ +{ + PatternTableKey key; + Tcl_HashEntry *hPtr; + + assert(lookupTables); + assert(psPtr); + + SetupPatternKey(&key, psPtr); + + if ((hPtr = Tcl_FindHashEntry(&lookupTables->listTable, (char *) &key))) { + PSList *psList = Tcl_GetHashValue(hPtr); + PSEntry *psEntry; + + TK_DLIST_FOREACH(psEntry, psList) { + if (psEntry->psPtr == psPtr) { + psPtr->added = false; + RemoveListEntry(&lookupTables->entryPool, psEntry); + return; } } } - ckfree(psPtr->script); - ckfree(psPtr); - return TCL_OK; + assert(!"couldn't find pattern sequence in lookup"); } /* *-------------------------------------------------------------- * - * Tk_GetBinding -- + * RemovePatSeqFromPromotionLists -- * - * Return the script associated with a given event string. + * Remove given pattern sequence from promotion lists. This + * can be required before deleting the pattern sequence. * * 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. + * None. * * Side effects: * None. @@ -1047,72 +1774,87 @@ Tk_DeleteBinding( *-------------------------------------------------------------- */ -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. */ +static void +RemovePatSeqFromPromotionLists( + Tk_BindingTable bindPtr, /* Table in which to look for bindings. */ + PatSeq *psPtr) /* Remove this pattern sequence. */ { - PatSeq *psPtr; - unsigned long eventMask; + unsigned i; - psPtr = FindSequence(interp, &bindPtr->patternTable, object, eventString, - 0, 1, &eventMask); - if (psPtr == NULL) { - return NULL; + assert(bindPtr); + assert(psPtr); + + for (i = 0; i < PromArr_Size(bindPtr->promArr); ++i) { + PSList *psList = PromArr_Get(bindPtr->promArr, i); + PSEntry *psEntry; + + TK_DLIST_FOREACH(psEntry, psList) { + if (psEntry->psPtr == psPtr) { + RemoveListEntry(&bindPtr->lookupTables.entryPool, psEntry); + break; + } + } } - return psPtr->script; } /* *-------------------------------------------------------------- * - * Tk_GetAllBindings -- + * DeletePatSeq -- * - * Return a list of event strings for all the bindings associated with a - * given object. + * Delete given pattern sequence. Possibly it is required + * to invoke RemovePatSeqFromLookup(), and RemovePatSeqFromPromotionLists() + * before. * * 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. + * Pointer to succeeding pattern sequence. * * Side effects: - * None. + * Deallocation of memory. * *-------------------------------------------------------------- */ -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. */ +static PatSeq * +DeletePatSeq( + PatSeq *psPtr) /* Delete this pattern sequence. */ { - PatSeq *psPtr; - Tcl_HashEntry *hPtr; - Tcl_Obj *resultObj; + PatSeq *prevPtr; + PatSeq *nextPtr; - hPtr = Tcl_FindHashEntry(&bindPtr->objectTable, (char *) object); - if (hPtr == NULL) { - return; - } + assert(psPtr); + assert(!psPtr->added); + assert(!psPtr->owned); - 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. - */ + prevPtr = Tcl_GetHashValue(psPtr->hPtr); + nextPtr = psPtr->ptr.nextObj; + + /* + * 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. + */ - Tcl_ListObjAppendElement(NULL, resultObj, GetPatternObj(psPtr)); + if (prevPtr == psPtr) { + if (!psPtr->nextSeqPtr) { + Tcl_DeleteHashEntry(psPtr->hPtr); + } else { + Tcl_SetHashValue(psPtr->hPtr, psPtr->nextSeqPtr); + } + } else { + for ( ; ; prevPtr = prevPtr->nextSeqPtr) { + if (!prevPtr) { + Tcl_Panic("DeletePatSeq couldn't find on hash chain"); + } + if (prevPtr->nextSeqPtr == psPtr) { + prevPtr->nextSeqPtr = psPtr->nextSeqPtr; + break; + } + } } - Tcl_SetObjResult(interp, resultObj); + + FreePatSeq(psPtr); + return nextPtr; } /* @@ -1137,45 +1879,30 @@ Tk_DeleteAllBindings( Tk_BindingTable bindPtr, /* Table in which to delete bindings. */ ClientData object) /* Token for object. */ { - PatSeq *psPtr, *prevPtr; + PatSeq *psPtr; PatSeq *nextPtr; Tcl_HashEntry *hPtr; - hPtr = Tcl_FindHashEntry(&bindPtr->objectTable, (char *) object); - if (hPtr == NULL) { + assert(bindPtr); + assert(object); + + if (!(hPtr = Tcl_FindHashEntry(&bindPtr->objectTable, (char *) object))) { 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. - */ + /* + * Don't forget to clear lookup tables. + */ - 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); + ClearLookupTable(&bindPtr->lookupTables, object); + ClearPromotionLists(bindPtr, object); + + for (psPtr = Tcl_GetHashValue(hPtr); psPtr; psPtr = nextPtr) { + assert(psPtr->added != 0xdeadbeef); + DEBUG(psPtr->added = false); + nextPtr = DeletePatSeq(psPtr); } + Tcl_DeleteHashEntry(hPtr); } @@ -1204,42 +1931,90 @@ Tk_DeleteAllBindings( *--------------------------------------------------------------------------- */ +/* helper function */ +static bool +IsBetterMatch( + const PatSeq *fstMatchPtr, + const PatSeq *sndMatchPtr, /* this is a better match? */ + unsigned patIndex) +{ + if (!sndMatchPtr) { return false; } + if (!fstMatchPtr) { return true; } + if (sndMatchPtr->count > fstMatchPtr->count) { return true; } + if (sndMatchPtr->count < fstMatchPtr->count) { return false; } +#if PREFER_MOST_SPECIALIZED_EVENT + if (GetCount(sndMatchPtr, patIndex) > GetCount(fstMatchPtr, patIndex)) { return true; } + if (GetCount(sndMatchPtr, patIndex) < GetCount(fstMatchPtr, patIndex)) { return false; } + if (fstMatchPtr->numPats > sndMatchPtr->numPats) { return true; } + if (fstMatchPtr->numPats < sndMatchPtr->numPats) { return false; } +#endif + return sndMatchPtr->number > fstMatchPtr->number; +} + 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. */ + Tk_Window tkwin, /* Window on display where event occurred (needed in order to + * locate display information). */ + int numObjects, /* Number of objects at *objArr. */ + ClientData *objArr) /* Array of one or more objects to check for a matching binding. */ { - TkDisplay *dispPtr; + Tcl_Interp *interp; ScreenInfo *screenPtr; - BindInfo *bindInfoPtr; + TkDisplay *dispPtr; 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; + Event *curEvent; TkWindow *winPtr = (TkWindow *) tkwin; - PatternTableKey key; + BindInfo *bindInfoPtr; + Tcl_InterpState interpState; + LookupTables *physTables; + PatSeq *psPtr[2]; + PatSeq *matchPtrBuf[32]; + PatSeq **matchPtrArr = matchPtrBuf; + PSList *psl[2]; + Tcl_DString scripts; + const char *p; + const char *end; + unsigned scriptCount; + unsigned oldScreen; + unsigned flags; + unsigned arraySize; + unsigned newArraySize; + unsigned i, k; + + assert(bindPtr); + assert(eventPtr); + assert(tkwin); /* * 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) { + if (!winPtr->pathName) { + return; + } + + flags = flagArray[eventPtr->type]; + + /* + * Ignore event types which are not in flagArray and all zeroes there. + */ + + if (eventPtr->type >= TK_LASTEVENT || !flags) { return; } + if (flags & KEY_BUTTON_MOTION_VIRTUAL) { + bindPtr->curModStateMask = eventPtr->xkey.state; + } else if (flags & CROSSING) { + bindPtr->curModStateMask = eventPtr->xcrossing.state; + } + + dispPtr = ((TkWindow *) tkwin)->dispPtr; + bindInfoPtr = winPtr->mainPtr->bindInfo; + /* * Ignore the event completely if it is an Enter, Leave, FocusIn, or * FocusOut event with detail NotifyInferior. The reason for ignoring @@ -1247,205 +2022,346 @@ Tk_BindEvent( * 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. + * + * Also ignore modifier key events, these events cannot be bound. + * + * Furthermore we have to compute current time, needed for "event generate". */ - if ((eventPtr->type == EnterNotify) || (eventPtr->type == LeaveNotify)) { + switch (eventPtr->type) { + case EnterNotify: + case LeaveNotify: + if (eventPtr->xcrossing.time) { + bindInfoPtr->lastCurrentTime = CurrentTimeInMilliSecs(); + bindInfoPtr->lastEventTime = eventPtr->xcrossing.time; + } if (eventPtr->xcrossing.detail == NotifyInferior) { return; } - } - if ((eventPtr->type == FocusIn) || (eventPtr->type == FocusOut)) { + break; + case FocusIn: + case FocusOut: if (eventPtr->xfocus.detail == NotifyInferior) { return; } + break; + case KeyPress: + case KeyRelease: { + if (eventPtr->xkey.time) { + bindInfoPtr->lastCurrentTime = CurrentTimeInMilliSecs(); + bindInfoPtr->lastEventTime = eventPtr->xkey.time; + } + /* modifier keys are not influencing button events */ + for (i = 0; i < dispPtr->numModKeyCodes; ++i) { + if (dispPtr->modKeyCodes[i] == eventPtr->xkey.keycode) { + return; + } + } + /* reset repetition count for button events */ + bindPtr->eventInfo[ButtonPress].countAny = 0; + bindPtr->eventInfo[ButtonPress].countDetailed = 0; + bindPtr->eventInfo[ButtonRelease].countAny = 0; + bindPtr->eventInfo[ButtonRelease].countDetailed = 0; + break; + case ButtonPress: + case ButtonRelease: + /* reset repetition count for key events */ + bindPtr->eventInfo[KeyPress].countAny = 0; + bindPtr->eventInfo[KeyPress].countDetailed = 0; + bindPtr->eventInfo[KeyRelease].countAny = 0; + bindPtr->eventInfo[KeyRelease].countDetailed = 0; + // fallthru + case MotionNotify: + if (eventPtr->xmotion.time) { + bindInfoPtr->lastCurrentTime = CurrentTimeInMilliSecs(); + bindInfoPtr->lastEventTime = eventPtr->xmotion.time; + } + break; + case PropertyNotify: + if (eventPtr->xproperty.time) { + bindInfoPtr->lastCurrentTime = CurrentTimeInMilliSecs(); + bindInfoPtr->lastEventTime = eventPtr->xproperty.time; + } + break; } - - /* - * 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; + curEvent = bindPtr->eventInfo + eventPtr->type; /* - * 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). + * Now check whether this is a repeating event (multi-click, repeated key press, and so on). */ - 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; + /* NOTE: if curEvent is not yet set, then the following cannot match: */ + if (curEvent->xev.xany.window == eventPtr->xany.window) { + switch (eventPtr->type) { + case KeyPress: + case KeyRelease: + if (eventPtr->xkey.time - curEvent->xev.xkey.time <= NEARBY_MS) { + if (curEvent->xev.xkey.keycode == eventPtr->xkey.keycode) { + ++curEvent->countDetailed; + } else { + curEvent->countDetailed = 1; + } + ++curEvent->countAny; + } else { + curEvent->countAny = curEvent->countDetailed = 1; } - if (dispPtr->modKeyCodes[i] == eventPtr->xkey.keycode) { - break; + break; + case ButtonPress: + case ButtonRelease: + if (MatchEventNearby(&curEvent->xev, eventPtr)) { + if (curEvent->xev.xbutton.button == eventPtr->xbutton.button) { + ++curEvent->countDetailed; + } else { + curEvent->countDetailed = 1; + } + ++curEvent->countAny; + } else { + curEvent->countAny = curEvent->countDetailed = 1; } + break; + case MotionNotify: + if (MatchEventNearby(&curEvent->xev, eventPtr)) { + ++curEvent->countAny; + } else { + curEvent->countAny = 1; + } + break; + case EnterNotify: + case LeaveNotify: + if (eventPtr->xcrossing.time - curEvent->xev.xcrossing.time <= NEARBY_MS) { + ++curEvent->countAny; + } else { + curEvent->countAny = 1; + } + break; + case PropertyNotify: + if (eventPtr->xproperty.time - curEvent->xev.xproperty.time <= NEARBY_MS) { + ++curEvent->countAny; + } else { + curEvent->countAny = 1; + } + break; + default: + ++curEvent->countAny; + 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; - } + curEvent->countAny = curEvent->countDetailed = 1; } - ringPtr = &bindPtr->eventRing[bindPtr->curEvent]; - memcpy(ringPtr, eventPtr, sizeof(XEvent)); - detail.clientData = 0; - flags = flagArray[ringPtr->type]; + + /* + * Now update the details. + */ + + curEvent->xev = *eventPtr; if (flags & KEY) { - detail.keySym = TkpGetKeySym(dispPtr, ringPtr); - if (detail.keySym == NoSymbol) { - detail.keySym = 0; - } + curEvent->detail.info = TkpGetKeySym(dispPtr, eventPtr); } else if (flags & BUTTON) { - detail.button = ringPtr->xbutton.button; + curEvent->detail.info = eventPtr->xbutton.button; + } else if (flags & MOTION) { + curEvent->detail.info = ButtonNumberFromState(eventPtr->xmotion.state); } else if (flags & VIRTUAL) { - detail.name = ((XVirtualEvent *) ringPtr)->name; + curEvent->detail.name = ((XVirtualEvent *) eventPtr)->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). - */ + bindPtr->curEvent = curEvent; + physTables = &bindPtr->lookupTables; + scriptCount = 0; + arraySize = 0; + Tcl_DStringInit(&scripts); - vMatchDetailList = NULL; - vMatchNoDetailList = NULL; - memset(&key, 0, sizeof(key)); + if (numObjects > SIZE_OF_ARRAY(matchPtrBuf)) { + /* it's unrealistic that the buffer size is too small, but who knows? */ + matchPtrArr = ckalloc(numObjects*sizeof(matchPtrArr[0])); + } + memset(matchPtrArr, 0, numObjects*sizeof(matchPtrArr[0])); - if (ringPtr->type != VirtualEvent) { - Tcl_HashTable *veptPtr = &bindInfoPtr->virtualEventTable.patternTable; - Tcl_HashEntry *hPtr; + if (!PromArr_IsEmpty(bindPtr->promArr)) { + for (k = 0; k < numObjects; ++k) { + psl[1] = PromArr_Last(bindPtr->promArr); + psl[0] = psl[1] - 1; - key.object = NULL; - key.type = ringPtr->type; - key.detail = detail; + /* + * Loop over all promoted bindings, finding the longest matching one. + * + * Note that we must process all lists, because all matching patterns + * have to be promoted. Normally at most one list will be processed, and + * usually this list only contains one or two patterns. + */ - hPtr = Tcl_FindHashEntry(veptPtr, (char *) &key); - if (hPtr != NULL) { - vMatchDetailList = Tcl_GetHashValue(hPtr); - } + for (i = PromArr_Size(bindPtr->promArr); i > 0; --i, --psl[0], --psl[1]) { + psPtr[0] = MatchPatterns(dispPtr, bindPtr, psl[0], psl[1], i, curEvent, objArr[k], NULL); - if (key.detail.clientData != 0) { - key.detail.clientData = 0; - hPtr = Tcl_FindHashEntry(veptPtr, (char *) &key); - if (hPtr != NULL) { - vMatchNoDetailList = Tcl_GetHashValue(hPtr); + if (IsBetterMatch(matchPtrArr[k], psPtr[0], i)) { + /* we will process it later, because we still may find a pattern with better match */ + matchPtrArr[k] = psPtr[0]; + } + if (!PSList_IsEmpty(psl[1])) { + /* we have promoted sequences, adjust array size */ + arraySize = Max(i + 1, arraySize); + } } } } /* - * 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. + * 1. Look for bindings for the specific detail (button and key events). + * 2. Look for bindings without detail. */ - scriptCount = 0; - Tcl_DStringInit(&scripts); + for (k = 0; k < numObjects; ++k) { + PSList *psSuccList = PromArr_First(bindPtr->promArr); + PatSeq *bestPtr; - for ( ; numObjects > 0; numObjects--, objectPtr++) { - PatSeq *matchPtr = NULL, *sourcePtr = NULL; - Tcl_HashEntry *hPtr; + psl[0] = GetLookupForEvent(physTables, curEvent, objArr[k], true); + psl[1] = GetLookupForEvent(physTables, curEvent, objArr[k], false); - /* - * 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. - */ + assert(psl[0] == NULL || psl[0] != psl[1]); - 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); + psPtr[0] = MatchPatterns(dispPtr, bindPtr, psl[0], psSuccList, 0, curEvent, objArr[k], NULL); + psPtr[1] = MatchPatterns(dispPtr, bindPtr, psl[1], psSuccList, 0, curEvent, objArr[k], NULL); + + if (!PSList_IsEmpty(psSuccList)) { + /* we have promoted sequences, adjust array size */ + arraySize = Max(1, arraySize); } - if (vMatchDetailList != NULL) { - matchPtr = MatchPatterns(dispPtr, bindPtr, vMatchDetailList, - matchPtr, objectPtr, &sourcePtr); + bestPtr = psPtr[0] ? psPtr[0] : psPtr[1]; + + if (matchPtrArr[k]) { + if (IsBetterMatch(matchPtrArr[k], bestPtr, 0)) { + matchPtrArr[k] = bestPtr; + } else { + /* + * We've already found a higher level match, nevertheless it was required to + * process the level zero patterns because of possible promotions. + */ + } + + /* + * Now we have to catch up the processing of the script. + */ + + ExpandPercents(winPtr, matchPtrArr[k]->script, curEvent, scriptCount++, &scripts); + Tcl_DStringAppend(&scripts, "", 1); + continue; } /* - * If no match was found, look for a binding for all keys or buttons - * (detail of 0). Again, first match on a virtual event. + * We have to look whether we can find a better match in virtual table, provided that we + * don't have a higher level match. */ - 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); - } + matchPtrArr[k] = bestPtr; + + if (eventPtr->type != VirtualEvent) { + LookupTables *virtTables = &bindInfoPtr->virtualEventTable.lookupTables; + PatSeq *matchPtr = matchPtrArr[k]; + PatSeq *mPtr; + PSList *psl[2]; + + /* + * Note that virtual events cannot promote. + */ + + psl[0] = GetLookupForEvent(virtTables, curEvent, NULL, true); + psl[1] = GetLookupForEvent(virtTables, curEvent, NULL, false); - if (vMatchNoDetailList != NULL) { - matchPtr = MatchPatterns(dispPtr, bindPtr, vMatchNoDetailList, - matchPtr, objectPtr, &sourcePtr); + assert(psl[0] == NULL || psl[0] != psl[1]); + + mPtr = MatchPatterns(dispPtr, bindPtr, psl[0], NULL, 0, curEvent, objArr[k], &matchPtr); + if (mPtr) { + matchPtrArr[k] = matchPtr; + matchPtr = mPtr; + } + if (MatchPatterns(dispPtr, bindPtr, psl[1], NULL, 0, curEvent, objArr[k], &matchPtr)) { + matchPtrArr[k] = matchPtr; } } - if (matchPtr != NULL) { - ExpandPercents(winPtr, sourcePtr->script, eventPtr, - detail.keySym, scriptCount++, &scripts); + if (matchPtrArr[k]) { + ExpandPercents(winPtr, matchPtrArr[k]->script, curEvent, scriptCount++, &scripts); + /* nul is added to the scripts string to separate the various scripts */ + Tcl_DStringAppend(&scripts, "", 1); + } + } + + PromArr_SetSize(bindPtr->promArr, arraySize); + + /* + * Remove expired pattern sequences. + */ + + for (i = 0, newArraySize = 0; i < arraySize; ++i) { + PSList *psList = PromArr_Get(bindPtr->promArr, i); + PSEntry *psEntry; + PSEntry *psNext; + + for (psEntry = PSList_First(psList); psEntry; psEntry = psNext) { + const TkPattern *patPtr; + + assert(i + 1 < psEntry->psPtr->numPats); + + psNext = PSList_Next(psEntry); + patPtr = &psEntry->psPtr->pats[i + 1]; /* - * A "" is added to the scripts string to separate the various - * scripts that should be invoked. + * We have to remove the following entries from promotion list (but + * only if it is not added recently): + * ------------------------------------------------------------------ + * 1) It is marked as expired (see MatchPatterns()). + * 2) If we have a Key event, and current entry is matching a Button. + * 3) If we have a Button event, and current entry is matching a Key. + * 4) If we have a detailed event, current entry it is also detailed, + * we have matching event types, but the details are different. + * 5) Current entry has been matched with a different window. */ - Tcl_DStringAppend(&scripts, "", 1); + if (psEntry->isNew) { + /* this pattern has been added recently */ + assert(!psEntry->expired); + psEntry->isNew = false; + } else if (psEntry->expired + || psEntry->window != curEvent->xev.xany.window + || (patPtr->info + && curEvent->detail.info + && patPtr->eventType == curEvent->xev.type + && patPtr->info != curEvent->detail.info)) { + RemoveListEntry(&bindPtr->lookupTables.entryPool, psEntry); + } else { + switch (patPtr->eventType) { + case ButtonPress: + case ButtonRelease: + if (curEvent->xev.type == KeyPress || curEvent->xev.type == KeyRelease) { + RemoveListEntry(&bindPtr->lookupTables.entryPool, psEntry); + } + break; + case KeyPress: + case KeyRelease: + if (curEvent->xev.type == ButtonPress || curEvent->xev.type == ButtonRelease) { + RemoveListEntry(&bindPtr->lookupTables.entryPool, psEntry); + } + break; + } + } + } + + if (!PSList_IsEmpty(psList)) { + /* we still have promoted sequences, adjust array size */ + newArraySize = Max(i + 1, newArraySize); } } + + PromArr_SetSize(bindPtr->promArr, newArraySize); + + if (matchPtrArr != matchPtrBuf) { + ckfree(matchPtrArr); + } + if (Tcl_DStringLength(&scripts) == 0) { - return; + return; /* nothing to do */ } /* @@ -1476,16 +2392,13 @@ Tk_BindEvent( screenPtr = &bindInfoPtr->screenInfo; oldDispPtr = screenPtr->curDispPtr; oldScreen = screenPtr->curScreenIndex; - if ((dispPtr != screenPtr->curDispPtr) - || (Tk_ScreenNumber(tkwin) != 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 @@ -1493,12 +2406,13 @@ Tk_BindEvent( */ Tcl_Preserve(bindInfoPtr); - while (p < end) { - int len = (int) strlen(p); + + for (p = Tcl_DStringValue(&scripts), end = p + Tcl_DStringLength(&scripts); p < end; ) { + unsigned len = strlen(p); int code; if (!bindInfoPtr->deleted) { - screenPtr->bindingDepth++; + ++screenPtr->bindingDepth; } Tcl_AllowExceptions(interp); @@ -1506,38 +2420,30 @@ Tk_BindEvent( p += len + 1; if (!bindInfoPtr->deleted) { - screenPtr->bindingDepth--; + --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 { + if (code != TCL_OK && code != TCL_CONTINUE) { + if (code != TCL_BREAK) { Tcl_AddErrorInfo(interp, "\n (command bound to event)"); Tcl_BackgroundException(interp, code); - break; } + break; } } - if (!bindInfoPtr->deleted && (screenPtr->bindingDepth != 0) - && ((oldDispPtr != screenPtr->curDispPtr) - || (oldScreen != screenPtr->curScreenIndex))) { + 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_RestoreInterpState(interp, interpState); Tcl_DStringFree(&scripts); - Tcl_Release(bindInfoPtr); } @@ -1546,31 +2452,15 @@ Tk_BindEvent( * * 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 + * Given a list of pattern sequences and the recent event, return + * the pattern sequence that best matches this event, 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. + * + * The return value is NULL if no match is found. Otherwise the + * return value is the most specific pattern sequence among all + * those that match the event table. * * Side effects: * None. @@ -1578,314 +2468,185 @@ Tk_BindEvent( *---------------------------------------------------------------------- */ -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. */ +/* helper function */ +static bool +VirtPatIsBound( + Tk_BindingTable bindPtr, /* Table in which to look for bindings. */ + PatSeq *psPtr, /* Test this pattern. */ + ClientData object, /* Check for this binding tag. */ + PatSeq **physPtrPtr) /* Input: the best physical event. + * Output: the physical event associated with matching virtual event. */ { - 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]; - long timeDiff; - - timeDiff = ((long)firstPtr->xkey.time - - (long)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--; + PatternTableKey key; + const struct VirtOwners *owners; + unsigned i; + + assert(bindPtr); + assert(psPtr); + assert(!psPtr->object); + assert(physPtrPtr); + + if (*physPtrPtr) { + const TkPattern *physPatPtr = (*physPtrPtr)->pats; + const TkPattern *virtPatPtr = psPtr->pats; + + if (physPatPtr->info || !virtPatPtr->info) { + unsigned long physModMask = physPatPtr->modStateMask; + unsigned long virtModMask = virtPatPtr->modStateMask; + + if (physModMask == 0 + || virtModMask == physModMask + || (virtModMask & physModMask) != physModMask) { + return false; // we cannot surpass this match } - 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); + memset(&key, 0, sizeof(key)); + key.object = object; + key.type = VirtualEvent; + owners = psPtr->ptr.owners; - if ((virtMatchPtr->numPats != 1) - || (virtMatchPtr->nextSeqPtr != NULL)) { - Tcl_Panic("MatchPattern: badly constructed virtual event"); - } - sourcePtr = virtMatchPtr; - goto match; - } - } + for (i = 0; i < VirtOwners_Size(owners); ++i) { + Tcl_HashEntry *hPtr = VirtOwners_Get(owners, i); - /* - * The physical event matches a virtual event's definition, but - * the tag isn't interested in it. - */ + key.detail.name = (Tk_Uid) Tcl_GetHashKey(hPtr->tablePtr, hPtr); - goto nextSequence; + if ((hPtr = Tcl_FindHashEntry(&bindPtr->lookupTables.patternTable, (char *) &key))) { + /* The physical event matches this virtual event's definition. */ + *physPtrPtr = (PatSeq *) Tcl_GetHashValue(hPtr); + return true; } - match: + } - /* - * This sequence matches. If we've already got another match, pick - * whichever is most specific. Detail is most important, then - * needMods. - */ + return false; +} - if (bestPtr != NULL) { - TkPattern *patPtr2; +static PatSeq * +MatchPatterns( + TkDisplay *dispPtr, /* Display from which the event came. */ + Tk_BindingTable bindPtr, /* Table in which to look for bindings. */ + PSList *psList, /* List of potentially matching patterns, can be NULL. */ + PSList *psSuccList, /* Add all matching higher-level pattern sequences to this list. + * Can be NULL. */ + unsigned patIndex, /* Match only this tag in sequence. */ + const Event *curEvent, /* Match this event. */ + ClientData object, /* Check for this binding tag. */ + PatSeq **physPtrPtr) /* Input: the best physical event; NULL if we test physical events. + * Output: the associated physical event for the best matching virtual + * event; NULL when we match physical events. */ +{ + Window window; + PSEntry *psEntry; + PatSeq *bestPtr; + PatSeq *bestPhysPtr; + unsigned bestCount; + unsigned long bestModStateMask; + unsigned long bestInfo; + + assert(dispPtr); + assert(bindPtr); + assert(curEvent); + + if (!psList) { + return NULL; + } - 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; + bestModStateMask = 0; + bestCount = 0; + bestInfo = 0; + bestPtr = NULL; + bestPhysPtr = NULL; + window = curEvent->xev.xany.window; + + for (psEntry = PSList_First(psList); psEntry; psEntry = PSList_Next(psEntry)) { + if (patIndex == 0 || psEntry->window == window) { + PatSeq* psPtr = psEntry->psPtr; + + assert(psPtr->added != 0xdeadbeef); + assert((psPtr->object == NULL) == (physPtrPtr != NULL)); + assert(psPtr->object || patIndex == 0); + assert(psPtr->numPats > patIndex); + + if (psPtr->object + ? psPtr->object == object + : VirtPatIsBound(bindPtr, psPtr, object, physPtrPtr)) { + TkPattern *patPtr = psPtr->pats + patIndex; + + if (patPtr->eventType == curEvent->xev.type + && (curEvent->xev.type != CreateNotify + || curEvent->xev.xcreatewindow.parent == window) + && (!patPtr->name || patPtr->name == curEvent->detail.name) + && (patPtr->info == 0 + ? patPtr->count <= curEvent->countAny + : patPtr->info == curEvent->detail.info + && patPtr->count <= curEvent->countDetailed)) { + unsigned long modStateMask = patPtr->modStateMask; + unsigned long curModStateMask = bindPtr->curModStateMask; + + if (modStateMask) { + /* + * Resolve the modifier mask for Alt and Mod keys. Unfortunately this + * cannot be done in ParseEventDescription, otherwise this function would + * be the better place. + */ + modStateMask = ResolveModifiers(dispPtr, modStateMask); + curModStateMask = ResolveModifiers(dispPtr, curModStateMask); } - } - if (patPtr->needMods != patPtr2->needMods) { - if ((patPtr->needMods & patPtr2->needMods) - == patPtr->needMods) { - goto nextSequence; - } else if ((patPtr->needMods & patPtr2->needMods) - == patPtr2->needMods) { - goto newBest; + + if ((modStateMask & ~curModStateMask) == 0) { + /* + * This pattern is finally matching. + */ + if (psPtr->numPats == patIndex + 1) { + /* + * This is also a final pattern. + * We always prefer the pattern with higher repetition count. + * + * In case of same repetition count: + * 1. Choose pattern with more specialized info (Key or Button). + * 2. Choose pattern with more specialized modifier state. + * 3. Best match don't has modifier states, or this pattern has one, + * and it is not a real subset. + */ + if (bestCount < patPtr->count + || (bestCount == patPtr->count + && (!curEvent->detail.info || !bestInfo || patPtr->info) + && (!bestModStateMask + || (modStateMask + && (modStateMask != bestModStateMask + && (bestModStateMask & modStateMask) != modStateMask))))) { + bestPtr = psPtr; + bestCount = patPtr->count; + bestInfo = patPtr->info; + bestModStateMask = modStateMask; + if (physPtrPtr) { + bestPhysPtr = *physPtrPtr; + } + } + psEntry->expired = true; /* remove it from promotion list */ + } else if (psSuccList) { + PSEntry *psEntry; + + /* + * Not a final pattern, but matching, so promote it to next level. + */ + + assert(!patPtr->name); + psEntry = MakeListEntry(&bindPtr->lookupTables.entryPool, psPtr); + assert(psEntry->isNew); + PSList_Append(psSuccList, psEntry); + psEntry->window = window; /* bind to current window */ + } } } } - - /* - * 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; + if (bestPhysPtr) { + *physPtrPtr = bestPhysPtr; + } return bestPtr; } @@ -1909,49 +2670,43 @@ MatchPatterns( 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. */ + TkWindow *winPtr, /* Window where event occurred: needed to get input context. */ + const char *before, /* Command containing percent expressions to be replaced. */ + Event *eventPtr, /* Event containing information to be used in % replacements. */ + unsigned 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; + unsigned flags; Tcl_DString buf; - char numStorage[NUM_SIZE+1]; + XEvent *evPtr; + + assert(winPtr); + assert(before); + assert(eventPtr); + assert(dsPtr); Tcl_DStringInit(&buf); + evPtr = &eventPtr->xev; + flags = (evPtr->type < TK_LASTEVENT) ? flagArray[evPtr->type] : 0; - if (eventPtr->type < TK_LASTEVENT) { - flags = flagArray[eventPtr->type]; - } else { - flags = 0; - } + while (true) { + char numStorage[TCL_INTEGER_SPACE]; + const char *string; + Tcl_WideInt number; - 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. */ - } + for (string = before; *string && *string != '%'; ++string) + ; if (string != before) { - Tcl_DStringAppend(dsPtr, before, (int) (string-before)); + Tcl_DStringAppend(dsPtr, before, string - before); before = string; } - if (*before == 0) { + if (!*before) { break; } @@ -1959,328 +2714,275 @@ ExpandPercents( * There's a percent sequence here. Process it. */ - number = 0; + number = NO_NUMBER; string = "??"; + switch (before[1]) { case '#': - number = eventPtr->xany.serial; - goto doNumber; + number = evPtr->xany.serial; + break; case 'a': if (flags & CONFIG) { - TkpPrintWindowId(numStorage, eventPtr->xconfigure.above); + TkpPrintWindowId(numStorage, evPtr->xconfigure.above); string = numStorage; } - goto doString; + break; case 'b': if (flags & BUTTON) { - number = eventPtr->xbutton.button; - goto doNumber; + number = evPtr->xbutton.button; } - goto doString; + break; case 'c': if (flags & EXPOSE) { - number = eventPtr->xexpose.count; - goto doNumber; + number = evPtr->xexpose.count; } - goto doString; + break; case 'd': if (flags & (CROSSING|FOCUS)) { - if (flags & FOCUS) { - number = eventPtr->xfocus.detail; - } else { - number = eventPtr->xcrossing.detail; - } - string = TkFindStateString(notifyDetail, number); + int detail = (flags & FOCUS) ? evPtr->xfocus.detail : evPtr->xcrossing.detail; + string = TkFindStateString(notifyDetail, detail); } else if (flags & CONFIGREQ) { - if (eventPtr->xconfigurerequest.value_mask & CWStackMode) { - string = TkFindStateString(configureRequestDetail, - eventPtr->xconfigurerequest.detail); + if (evPtr->xconfigurerequest.value_mask & CWStackMode) { + string = TkFindStateString(configureRequestDetail, evPtr->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 = ""; - } + XVirtualEvent *vePtr = (XVirtualEvent *) evPtr; + string = vePtr->user_data ? Tcl_GetString(vePtr->user_data) : ""; } - goto doString; + break; case 'f': if (flags & CROSSING) { - number = eventPtr->xcrossing.focus; - goto doNumber; + number = evPtr->xcrossing.focus; } - goto doString; + break; case 'h': if (flags & EXPOSE) { - number = eventPtr->xexpose.height; + number = evPtr->xexpose.height; } else if (flags & CONFIG) { - number = eventPtr->xconfigure.height; + number = evPtr->xconfigure.height; } else if (flags & CREATE) { - number = eventPtr->xcreatewindow.height; + number = evPtr->xcreatewindow.height; } else if (flags & CONFIGREQ) { - number = eventPtr->xconfigurerequest.height; + number = evPtr->xconfigurerequest.height; } else if (flags & RESIZEREQ) { - number = eventPtr->xresizerequest.height; - } else { - goto doString; + number = evPtr->xresizerequest.height; } - goto doNumber; + break; case 'i': if (flags & CREATE) { - TkpPrintWindowId(numStorage, eventPtr->xcreatewindow.window); + TkpPrintWindowId(numStorage, evPtr->xcreatewindow.window); } else if (flags & CONFIGREQ) { - TkpPrintWindowId(numStorage, - eventPtr->xconfigurerequest.window); + TkpPrintWindowId(numStorage, evPtr->xconfigurerequest.window); } else if (flags & MAPREQ) { - TkpPrintWindowId(numStorage, eventPtr->xmaprequest.window); + TkpPrintWindowId(numStorage, evPtr->xmaprequest.window); } else { - TkpPrintWindowId(numStorage, eventPtr->xany.window); + TkpPrintWindowId(numStorage, evPtr->xany.window); } string = numStorage; - goto doString; + break; case 'k': - if ((flags & KEY) && (eventPtr->type != MouseWheelEvent)) { - number = eventPtr->xkey.keycode; - goto doNumber; + if ((flags & KEY) && evPtr->type != MouseWheelEvent) { + number = evPtr->xkey.keycode; } - goto doString; + break; case 'm': if (flags & CROSSING) { - number = eventPtr->xcrossing.mode; - string = TkFindStateString(notifyMode, number); + string = TkFindStateString(notifyMode, evPtr->xcrossing.mode); } else if (flags & FOCUS) { - number = eventPtr->xfocus.mode; - string = TkFindStateString(notifyMode, number); + string = TkFindStateString(notifyMode, evPtr->xfocus.mode); } - goto doString; + break; case 'o': if (flags & CREATE) { - number = eventPtr->xcreatewindow.override_redirect; + number = evPtr->xcreatewindow.override_redirect; } else if (flags & MAP) { - number = eventPtr->xmap.override_redirect; + number = evPtr->xmap.override_redirect; } else if (flags & REPARENT) { - number = eventPtr->xreparent.override_redirect; + number = evPtr->xreparent.override_redirect; } else if (flags & CONFIG) { - number = eventPtr->xconfigure.override_redirect; - } else { - goto doString; + number = evPtr->xconfigure.override_redirect; } - goto doNumber; + break; case 'p': if (flags & CIRC) { - string = TkFindStateString(circPlace, - eventPtr->xcirculate.place); + string = TkFindStateString(circPlace, evPtr->xcirculate.place); } else if (flags & CIRCREQ) { - string = TkFindStateString(circPlace, - eventPtr->xcirculaterequest.place); + string = TkFindStateString(circPlace, evPtr->xcirculaterequest.place); } - goto doString; + break; case 's': if (flags & KEY_BUTTON_MOTION_VIRTUAL) { - number = eventPtr->xkey.state; - goto doNumber; + number = evPtr->xkey.state; } else if (flags & CROSSING) { - number = eventPtr->xcrossing.state; - goto doNumber; + number = evPtr->xcrossing.state; } else if (flags & PROP) { - string = TkFindStateString(propNotify, - eventPtr->xproperty.state); + string = TkFindStateString(propNotify, evPtr->xproperty.state); } else if (flags & VISIBILITY) { - string = TkFindStateString(visNotify, - eventPtr->xvisibility.state); + string = TkFindStateString(visNotify, evPtr->xvisibility.state); } - goto doString; + break; case 't': if (flags & KEY_BUTTON_MOTION_VIRTUAL) { - number = (int) eventPtr->xkey.time; + number = (int) evPtr->xkey.time; } else if (flags & CROSSING) { - number = (int) eventPtr->xcrossing.time; + number = (int) evPtr->xcrossing.time; } else if (flags & PROP) { - number = (int) eventPtr->xproperty.time; - } else { - goto doString; + number = (int) evPtr->xproperty.time; } - goto doNumber; + break; case 'v': - number = eventPtr->xconfigurerequest.value_mask; - goto doNumber; + number = evPtr->xconfigurerequest.value_mask; + break; case 'w': if (flags & EXPOSE) { - number = eventPtr->xexpose.width; + number = evPtr->xexpose.width; } else if (flags & CONFIG) { - number = eventPtr->xconfigure.width; + number = evPtr->xconfigure.width; } else if (flags & CREATE) { - number = eventPtr->xcreatewindow.width; + number = evPtr->xcreatewindow.width; } else if (flags & CONFIGREQ) { - number = eventPtr->xconfigurerequest.width; + number = evPtr->xconfigurerequest.width; } else if (flags & RESIZEREQ) { - number = eventPtr->xresizerequest.width; - } else { - goto doString; + number = evPtr->xresizerequest.width; } - goto doNumber; + break; case 'x': if (flags & KEY_BUTTON_MOTION_VIRTUAL) { - number = eventPtr->xkey.x; + number = evPtr->xkey.x; } else if (flags & CROSSING) { - number = eventPtr->xcrossing.x; + number = evPtr->xcrossing.x; } else if (flags & EXPOSE) { - number = eventPtr->xexpose.x; + number = evPtr->xexpose.x; } else if (flags & (CREATE|CONFIG|GRAVITY)) { - number = eventPtr->xcreatewindow.x; + number = evPtr->xcreatewindow.x; } else if (flags & REPARENT) { - number = eventPtr->xreparent.x; + number = evPtr->xreparent.x; } else if (flags & CREATE) { - number = eventPtr->xcreatewindow.x; + number = evPtr->xcreatewindow.x; } else if (flags & CONFIGREQ) { - number = eventPtr->xconfigurerequest.x; - } else { - goto doString; + number = evPtr->xconfigurerequest.x; } - goto doNumber; + break; case 'y': if (flags & KEY_BUTTON_MOTION_VIRTUAL) { - number = eventPtr->xkey.y; + number = evPtr->xkey.y; } else if (flags & EXPOSE) { - number = eventPtr->xexpose.y; + number = evPtr->xexpose.y; } else if (flags & (CREATE|CONFIG|GRAVITY)) { - number = eventPtr->xcreatewindow.y; + number = evPtr->xcreatewindow.y; } else if (flags & REPARENT) { - number = eventPtr->xreparent.y; + number = evPtr->xreparent.y; } else if (flags & CROSSING) { - number = eventPtr->xcrossing.y; + number = evPtr->xcrossing.y; } else if (flags & CREATE) { - number = eventPtr->xcreatewindow.y; + number = evPtr->xcreatewindow.y; } else if (flags & CONFIGREQ) { - number = eventPtr->xconfigurerequest.y; - } else { - goto doString; + number = evPtr->xconfigurerequest.y; } - goto doNumber; + break; case 'A': - if ((flags & KEY) && (eventPtr->type != MouseWheelEvent)) { + if ((flags & KEY) && evPtr->type != MouseWheelEvent) { Tcl_DStringFree(&buf); - string = TkpGetString(winPtr, eventPtr, &buf); + string = TkpGetString(winPtr, evPtr, &buf); } - goto doString; + break; case 'B': if (flags & CREATE) { - number = eventPtr->xcreatewindow.border_width; + number = evPtr->xcreatewindow.border_width; } else if (flags & CONFIGREQ) { - number = eventPtr->xconfigurerequest.border_width; + number = evPtr->xconfigurerequest.border_width; } else if (flags & CONFIG) { - number = eventPtr->xconfigure.border_width; - } else { - goto doString; + number = evPtr->xconfigure.border_width; } - goto doNumber; + break; case 'D': /* * This is used only by the MouseWheel event. */ - - if ((flags & KEY) && (eventPtr->type == MouseWheelEvent)) { - number = eventPtr->xkey.keycode; - goto doNumber; + if ((flags & KEY) && evPtr->type == MouseWheelEvent) { + number = evPtr->xkey.keycode; } - goto doString; + break; case 'E': - number = (int) eventPtr->xany.send_event; - goto doNumber; + number = (int) evPtr->xany.send_event; + break; case 'K': - if ((flags & KEY) && (eventPtr->type != MouseWheelEvent)) { - const char *name = TkKeysymToString(keySym); - - if (name != NULL) { + if ((flags & KEY) && evPtr->type != MouseWheelEvent) { + const char *name = TkKeysymToString(eventPtr->detail.info); + if (name) { string = name; } } - goto doString; + break; case 'M': number = scriptCount; - goto doNumber; + break; case 'N': - if ((flags & KEY) && (eventPtr->type != MouseWheelEvent)) { - number = (int) keySym; - goto doNumber; + if ((flags & KEY) && evPtr->type != MouseWheelEvent) { + number = (int) eventPtr->detail.info; } - goto doString; + break; case 'P': if (flags & PROP) { - string = Tk_GetAtomName((Tk_Window) winPtr, - eventPtr->xproperty.atom); + string = Tk_GetAtomName((Tk_Window) winPtr, evPtr->xproperty.atom); } - goto doString; + break; case 'R': if (flags & KEY_BUTTON_MOTION_CROSSING) { - TkpPrintWindowId(numStorage, eventPtr->xkey.root); + TkpPrintWindowId(numStorage, evPtr->xkey.root); string = numStorage; } - goto doString; + break; case 'S': if (flags & KEY_BUTTON_MOTION_CROSSING) { - TkpPrintWindowId(numStorage, eventPtr->xkey.subwindow); + TkpPrintWindowId(numStorage, evPtr->xkey.subwindow); string = numStorage; } - goto doString; + break; case 'T': - number = eventPtr->type; - goto doNumber; + number = evPtr->type; + break; 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; + Tk_Window tkwin = Tk_IdToWindow(evPtr->xany.display, evPtr->xany.window); + string = tkwin ? Tk_PathName(tkwin) : "??"; + break; } case 'X': if (flags & KEY_BUTTON_MOTION_CROSSING) { - - number = eventPtr->xkey.x_root; - Tk_IdToWindow(eventPtr->xany.display, - eventPtr->xany.window); - goto doNumber; + number = evPtr->xkey.x_root; } - goto doString; + break; case 'Y': if (flags & KEY_BUTTON_MOTION_CROSSING) { - - number = eventPtr->xkey.y_root; - Tk_IdToWindow(eventPtr->xany.display, - eventPtr->xany.window); - goto doNumber; + number = evPtr->xkey.y_root; } - goto doString; + break; default: numStorage[0] = before[1]; numStorage[1] = '\0'; string = numStorage; - goto doString; + break; } - 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; + if (number != NO_NUMBER) { + snprintf(numStorage, sizeof(numStorage), "%d", (int) number); + string = numStorage; + } + { /* local scope */ + int cvtFlags; + unsigned spaceNeeded = Tcl_ScanElement(string, &cvtFlags); + unsigned 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); } @@ -2311,15 +3013,13 @@ ChangeScreen( char *dispName, /* Name of new display. */ int screenIndex) /* Index of new screen. */ { - Tcl_Obj *cmdObj = Tcl_ObjPrintf("::tk::ScreenChanged %s.%d", - dispName, screenIndex); + 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_AddErrorInfo(interp, "\n (changing screen in event binding)"); Tcl_BackgroundException(interp, code); } Tcl_DecrRefCount(cmdObj); @@ -2352,38 +3052,48 @@ Tk_EventObjCmd( 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 - }; + Tk_Window tkwin; + TkBindInfo bindInfo; + VirtualEventTable *vetPtr; + + static const char *const optionStrings[] = { "add", "delete", "generate", "info", NULL }; + enum options { EVENT_ADD, EVENT_DELETE, EVENT_GENERATE, EVENT_INFO }; + assert(clientData); 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) { + if (Tcl_GetIndexFromObjStruct( + interp, objv[1], optionStrings, sizeof(char *), "option", 0, &index) != TCL_OK) { +#ifdef SUPPORT_DEBUGGING + if (strcmp(Tcl_GetString(objv[1]), "debug") == 0) { + if (objc < 3) { + Tcl_WrongNumArgs(interp, 1, objv, "debug number"); + return TCL_ERROR; + } + Tcl_GetIntFromObj(interp, objv[2], &N); + return TCL_OK; + } +#endif return TCL_ERROR; } + tkwin = (Tk_Window) clientData; + bindInfo = ((TkWindow *) tkwin)->mainPtr->bindInfo; + vetPtr = &bindInfo->virtualEventTable; + switch ((enum options) index) { case EVENT_ADD: if (objc < 4) { - Tcl_WrongNumArgs(interp, 2, objv, - "virtual sequence ?sequence ...?"); + Tcl_WrongNumArgs(interp, 2, objv, "virtual sequence ?sequence ...?"); return TCL_ERROR; } name = Tcl_GetString(objv[2]); - for (i = 3; i < objc; i++) { + for (i = 3; i < objc; ++i) { event = Tcl_GetString(objv[i]); - if (CreateVirtualEvent(interp, vetPtr, name, event) != TCL_OK) { + if (!CreateVirtualEvent(interp, vetPtr, name, event)) { return TCL_ERROR; } } @@ -2397,7 +3107,7 @@ Tk_EventObjCmd( if (objc == 3) { return DeleteVirtualEvent(interp, vetPtr, name, NULL); } - for (i = 3; i < objc; i++) { + for (i = 3; i < objc; ++i) { event = Tcl_GetString(objv[i]); if (DeleteVirtualEvent(interp, vetPtr, name, event) != TCL_OK) { return TCL_ERROR; @@ -2406,8 +3116,7 @@ Tk_EventObjCmd( break; case EVENT_GENERATE: if (objc < 4) { - Tcl_WrongNumArgs(interp, 2, objv, - "window event ?-option value ...?"); + Tcl_WrongNumArgs(interp, 2, objv, "window event ?-option value ...?"); return TCL_ERROR; } return HandleEventGenerate(interp, tkwin, objc - 2, objv + 2); @@ -2415,12 +3124,12 @@ Tk_EventObjCmd( if (objc == 2) { GetAllVirtualEvents(interp, vetPtr); return TCL_OK; - } else if (objc == 3) { + } + if (objc == 3) { return GetVirtualEvent(interp, vetPtr, objv[2]); - } else { - Tcl_WrongNumArgs(interp, 2, objv, "?virtual?"); - return TCL_ERROR; } + Tcl_WrongNumArgs(interp, 2, objv, "?virtual?"); + return TCL_ERROR; } return TCL_OK; } @@ -2444,12 +3153,14 @@ Tk_EventObjCmd( static void InitVirtualEventTable( - VirtualEventTable *vetPtr) /* Pointer to virtual event table. Memory is - * supplied by the caller. */ + VirtualEventTable *vetPtr) /* Pointer to virtual event table. Memory is supplied by the caller. */ { - Tcl_InitHashTable(&vetPtr->patternTable, - sizeof(PatternTableKey) / sizeof(int)); + assert(vetPtr); + memset(vetPtr, 0, sizeof(*vetPtr)); + Tcl_InitHashTable(&vetPtr->lookupTables.patternTable, sizeof(PatternTableKey)/sizeof(int)); + Tcl_InitHashTable(&vetPtr->lookupTables.listTable, sizeof(PatternTableKey)/sizeof(int)); Tcl_InitHashTable(&vetPtr->nameTable, TCL_ONE_WORD_KEYS); + PSList_Init(&vetPtr->lookupTables.entryPool); } /* @@ -2475,24 +3186,32 @@ DeleteVirtualEventTable( { 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) { + assert(vetPtr); + + hPtr = Tcl_FirstHashEntry(&vetPtr->lookupTables.patternTable, &search); + for ( ; hPtr; hPtr = Tcl_NextHashEntry(&search)) { + PatSeq *nextPtr; + PatSeq *psPtr; + + for (psPtr = Tcl_GetHashValue(hPtr); psPtr; psPtr = nextPtr) { nextPtr = psPtr->nextSeqPtr; - ckfree(psPtr->voPtr); - ckfree(psPtr); + assert(psPtr->added != 0xdeadbeef); + DEBUG(psPtr->owned = false); + FreePatSeq(psPtr); } } - Tcl_DeleteHashTable(&vetPtr->patternTable); + Tcl_DeleteHashTable(&vetPtr->lookupTables.patternTable); hPtr = Tcl_FirstHashEntry(&vetPtr->nameTable, &search); - for ( ; hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) { + for ( ; hPtr; hPtr = Tcl_NextHashEntry(&search)) { ckfree(Tcl_GetHashValue(hPtr)); } Tcl_DeleteHashTable(&vetPtr->nameTable); + + ClearLookupTable(&vetPtr->lookupTables, NULL); + Tcl_DeleteHashTable(&vetPtr->lookupTables.listTable); + PSList_Clear(&vetPtr->lookupTables.entryPool); } /* @@ -2515,36 +3234,35 @@ DeleteVirtualEventTable( *---------------------------------------------------------------------- */ -static int +static bool 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. */ + 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; + PhysOwned *owned; Tk_Uid virtUid; - virtUid = GetVirtualEventUid(interp, virtString); - if (virtUid == NULL) { - return TCL_ERROR; + assert(vetPtr); + assert(virtString); + assert(eventString); + + if (!(virtUid = GetVirtualEventUid(interp, virtString))) { + return false; } /* * Find/create physical event */ - psPtr = FindSequence(interp, &vetPtr->patternTable, NULL, eventString, - 1, 0, &eventMask); - if (psPtr == NULL) { - return TCL_ERROR; + if (!(psPtr = FindSequence(interp, &vetPtr->lookupTables, NULL, eventString, true, false, NULL))) { + return false; } + assert(psPtr->added != 0xdeadbeef); /* * Find/create virtual event. @@ -2556,47 +3274,18 @@ CreateVirtualEvent( * 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. - */ + owned = Tcl_GetHashValue(vhPtr); - voPtr = psPtr->voPtr; - if (voPtr == NULL) { - voPtr = ckalloc(sizeof(VirtualOwners)); - voPtr->numOwners = 0; - } else { - voPtr = ckrealloc(voPtr, sizeof(VirtualOwners) - + voPtr->numOwners * sizeof(Tcl_HashEntry *)); + if (PhysOwned_Find(owned, psPtr) == -1) { + PhysOwned_Append(&owned, psPtr); + Tcl_SetHashValue(vhPtr, owned); + DEBUG(psPtr->owned = true); + InsertPatSeq(&vetPtr->lookupTables, psPtr); + /* Make physical event so it can trigger the virtual event. */ + VirtOwners_Append(&psPtr->ptr.owners, vhPtr); } - psPtr->voPtr = voPtr; - voPtr->owners[voPtr->numOwners] = vhPtr; - voPtr->numOwners++; - return TCL_OK; + return true; } /* @@ -2626,32 +3315,32 @@ 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. */ + 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; + PhysOwned *owned; + const PatSeq *eventPSPtr; + PatSeq *lastElemPtr; - virtUid = GetVirtualEventUid(interp, virtString); - if (virtUid == NULL) { + assert(vetPtr); + assert(virtString); + + if (!(virtUid = GetVirtualEventUid(interp, virtString))) { return TCL_ERROR; } - vhPtr = Tcl_FindHashEntry(&vetPtr->nameTable, virtUid); - if (vhPtr == NULL) { + if (!(vhPtr = Tcl_FindHashEntry(&vetPtr->nameTable, virtUid))) { return TCL_OK; } - poPtr = Tcl_GetHashValue(vhPtr); + owned = Tcl_GetHashValue(vhPtr); eventPSPtr = NULL; - if (eventString != NULL) { - unsigned long eventMask; + if (eventString) { + LookupTables *lookupTables = &vetPtr->lookupTables; /* * Delete only the specific physical event associated with the virtual @@ -2659,102 +3348,78 @@ DeleteVirtualEvent( * 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) { + eventPSPtr = FindSequence(interp, lookupTables, NULL, eventString, false, false, NULL); + if (!eventPSPtr) { const char *string = Tcl_GetString(Tcl_GetObjResult(interp)); - - return (string[0] != '\0') ? TCL_ERROR : TCL_OK; + return string[0] ? TCL_ERROR : TCL_OK; } } - for (iPhys = poPtr->numOwned; --iPhys >= 0; ) { - PatSeq *psPtr = poPtr->patSeqs[iPhys]; + for (iPhys = PhysOwned_Size(owned); --iPhys >= 0; ) { + PatSeq *psPtr = PhysOwned_Get(owned, iPhys); - if ((eventPSPtr == NULL) || (psPtr == eventPSPtr)) { - int iVirt; - VirtualOwners *voPtr; + assert(psPtr->added != 0xdeadbeef); - /* - * Remove association between this physical event and the given - * virtual event that it triggers. - */ + if (!eventPSPtr || psPtr == eventPSPtr) { + VirtOwners *owners = psPtr->ptr.owners; + int iVirt = VirtOwners_Find(owners, vhPtr); - voPtr = psPtr->voPtr; - for (iVirt = 0; iVirt < voPtr->numOwners; iVirt++) { - if (voPtr->owners[iVirt] == vhPtr) { - break; - } - } - if (iVirt == voPtr->numOwners) { + if (iVirt == -1) { 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); + /* + * Remove association between this physical event and the given + * virtual event that it triggers. + */ - 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 { + if (VirtOwners_Size(owners) > 1) { /* * 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]; + VirtOwners_Set(owners, iVirt, VirtOwners_Back(owners)); + VirtOwners_PopBack(owners); + } else { + /* + * Removed last reference to this physical event, so remove it + * from physical->virtual map. And don't forget to remove it + * from lookup tables. + */ + DEBUG(psPtr->owned = false); + RemovePatSeqFromLookup(&vetPtr->lookupTables, psPtr); + DeletePatSeq(psPtr); } /* * Now delete the virtual event's reference to the physical event. */ - poPtr->numOwned--; - if (eventPSPtr != NULL && poPtr->numOwned != 0) { + lastElemPtr = PhysOwned_Back(owned); + + if (PhysOwned_PopBack(owned) > 0 && eventPSPtr) { /* * Just deleting this one physical event. Consolidate list of * owned physical events and return. */ - - poPtr->patSeqs[iPhys] = poPtr->patSeqs[poPtr->numOwned]; + if (iPhys < PhysOwned_Size(owned)) { + PhysOwned_Set(owned, iPhys, lastElemPtr); + } return TCL_OK; } } } - if (poPtr->numOwned == 0) { + if (PhysOwned_IsEmpty(owned)) { /* * 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); + PhysOwned_Free(&owned); Tcl_DeleteHashEntry(vhPtr); } return TCL_OK; @@ -2789,26 +3454,26 @@ GetVirtualEvent( Tcl_Obj *virtName) /* String describing virtual event. */ { Tcl_HashEntry *vhPtr; - int iPhys; - PhysicalsOwned *poPtr; + unsigned iPhys; + const PhysOwned *owned; Tk_Uid virtUid; Tcl_Obj *resultObj; - virtUid = GetVirtualEventUid(interp, Tcl_GetString(virtName)); - if (virtUid == NULL) { + assert(vetPtr); + assert(virtName); + + if (!(virtUid = GetVirtualEventUid(interp, Tcl_GetString(virtName)))) { return TCL_ERROR; } - vhPtr = Tcl_FindHashEntry(&vetPtr->nameTable, virtUid); - if (vhPtr == NULL) { + if (!(vhPtr = Tcl_FindHashEntry(&vetPtr->nameTable, virtUid))) { 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])); + owned = Tcl_GetHashValue(vhPtr); + for (iPhys = 0; iPhys < PhysOwned_Size(owned); ++iPhys) { + Tcl_ListObjAppendElement(NULL, resultObj, GetPatternObj(PhysOwned_Get(owned, iPhys))); } Tcl_SetObjResult(interp, resultObj); @@ -2842,11 +3507,13 @@ GetAllVirtualEvents( Tcl_HashSearch search; Tcl_Obj *resultObj; + assert(vetPtr); + 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))); + for ( ; hPtr; hPtr = Tcl_NextHashEntry(&search)) { + Tcl_Obj* msg = Tcl_ObjPrintf("<<%s>>", (char *) Tcl_GetHashKey(hPtr->tablePtr, hPtr)); + Tcl_ListObjAppendElement(NULL, resultObj, msg); } Tcl_SetObjResult(interp, resultObj); } @@ -2893,16 +3560,24 @@ HandleEventGenerate( int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { - union {XEvent general; XVirtualEvent virtual;} event; + union { XEvent general; XVirtualEvent virtual; } event; + const char *p; - const char *name, *windowName; - int count, flags, synch, i, number, warp; + const char *name; + const char *windowName; Tcl_QueuePosition pos; TkPattern pat; - Tk_Window tkwin, tkwin2; + Tk_Window tkwin; + Tk_Window tkwin2; TkWindow *mainPtr; unsigned long eventMask; Tcl_Obj *userDataObj; + bool synch; + bool warp; + unsigned count; + unsigned flags; + int number; + unsigned i; static const char *const fieldStrings[] = { "-when", "-above", "-borderwidth", "-button", @@ -2912,7 +3587,7 @@ HandleEventGenerate( "-place", "-root", "-rootx", "-rooty", "-sendevent", "-serial", "-state", "-subwindow", "-time", "-warp", "-width", "-window", - "-x", "-y", NULL + "-x", "-y", NULL }; enum field { EVENT_WHEN, EVENT_ABOVE, EVENT_BORDER, EVENT_BUTTON, @@ -2925,42 +3600,38 @@ HandleEventGenerate( EVENT_X, EVENT_Y }; + assert(mainWin); + windowName = Tcl_GetString(objv[0]); if (!windowName[0]) { tkwin = mainWin; - } else if (NameToWindow(interp, mainWin, objv[0], &tkwin) != TCL_OK) { + } else if (!NameToWindow(interp, mainWin, objv[0], &tkwin)) { return TCL_ERROR; } mainPtr = (TkWindow *) mainWin; - if ((tkwin == NULL) - || (mainPtr->mainPtr != ((TkWindow *) tkwin)->mainPtr)) { + if (!tkwin || 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); + 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) { + if ((count = ParseEventDescription(interp, &p, &pat, &eventMask)) == 0) { return TCL_ERROR; } if (count != 1) { - Tcl_SetObjResult(interp, Tcl_NewStringObj( - "Double or Triple modifier not allowed", -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)); + if (*p) { + Tcl_SetObjResult(interp, Tcl_NewStringObj("only one event specification allowed", -1)); Tcl_SetErrorCode(interp, "TK", "EVENT", "MULTIPLE", NULL); return TCL_ERROR; } @@ -2972,8 +3643,7 @@ HandleEventGenerate( 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.window = RootWindow(Tk_Display(tkwin), Tk_ScreenNumber(tkwin)); } event.general.xany.display = Tk_Display(tkwin); @@ -2982,18 +3652,17 @@ HandleEventGenerate( /* * 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); + event.general.xkey.state = pat.modStateMask; + if ((flags & KEY) && event.general.xany.type != MouseWheelEvent) { + TkpSetKeycodeAndState(tkwin, pat.info, &event.general); } else if (flags & BUTTON) { - event.general.xbutton.button = pat.detail.button; + event.general.xbutton.button = pat.info; } else if (flags & VIRTUAL) { - event.virtual.name = pat.detail.name; + event.virtual.name = pat.name; } } if (flags & (CREATE|UNMAP|MAP|REPARENT|CONFIG|GRAVITY|CIRC)) { @@ -3005,21 +3674,21 @@ HandleEventGenerate( event.general.xkey.y_root = -1; } - if (event.general.xany.type == FocusIn - || event.general.xany.type == FocusOut) { + 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. + * Process the remaining arguments to fill in additional fields of the event. */ - synch = 1; + synch = true; warp = 0; pos = TCL_QUEUE_TAIL; + for (i = 2; i < objc; i += 2) { Tcl_Obj *optionPtr, *valuePtr; + bool badOpt = false; int index; optionPtr = objv[i]; @@ -3029,7 +3698,7 @@ HandleEventGenerate( sizeof(char *), "option", TCL_EXACT, &index) != TCL_OK) { return TCL_ERROR; } - if (objc & 1) { + if (IsOdd(objc)) { /* * This test occurs after Tcl_GetIndexFromObj() so that "event * generate