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