Chapter 8. Events, Translations, and Accelerators

This chapter describes the complete syntax of translation tables, and describes a mechanism called accelerators for mapping events in one widget to actions in another. Motif mnemonics, which are keyboard shortcuts for invoking menu items in visible menus, are also discussed.

Events drive the application. They send a great variety of information from the server to the client and from the user to the application. More knowledge of events is necessary both to use existing widgets successfully in large applications and to write widgets.

Events are sufficiently central to X that we've devoted two chapters in this book to them. This chapter provides a closer look at translations and actions; Chapter 9, More Input Techniques, looks at lower-level event handlers, as well as at other sources of input. Appendix C, Event Reference, in Volume Five, X Toolkit Intrinsics Reference Manual, will also be useful when you need details about any of the event types.

The basic concept and use of translations and actions has already been described. But translation tables have a complicated syntax which can be used to do much more than the simple mappings you have seen until now. Translation tables can detect user-interface idioms such as double- and triple-button clicks or key combinations such as Shift-Meta-M. This chapter focuses on the more advanced features of translation tables.

Next, we discuss a variation of translations called accelerators. Accelerators bind events that occur in one widget to actions in another. This is a flexible feature with many uses. One common use is to supply a keyboard interface to a normally pointer-driven application. By adding accelerators to the top-level window of the application, a keyboard event typed with the pointer anywhere in the application can be translated into an action in the correct widget. The name “accelerator” comes from the fact that many advanced users find it faster to use keyboard shortcuts instead of menus.

Both translations and accelerators are resources. Therefore they are normally set by default in the widget code, but can be replaced or overridden by user-defined settings in resource files or with xrdb. In R5, a new “psuedo-resource” called XtNbaseTranslations provides a new level of flexibility in modifying translations from resource files. Be sure to see Section 10.2.12, "The XtNbaseTranslations Resource" for an explanation of how translation resources should be set. As mentioned in Chapter 3, More Techniques for Using Widgets, it is a very good idea to specify translation tables and accelerator tables in the app-defaults file instead of hardcoding them in the application, especially while an application is under development. This lets you develop the translations and accelerators without recompiling the application every time you want to make a change.

Translation Table Syntax

If you are reading this book in sequence, you've already seen translations used many times. However, we haven't given a formal description of their syntax or a complete listing of the events you can translate.

A translation table consists of an optional directive, which specifies how the table should be merged with any other existing translation tables, followed by a series of production rules, of the form:

[modifier_list]<event>[,<event>...][(count)][detail]: action([arguments])[ action...]

where brackets ([ ]) indicate optional elements, an ellipsis (...) indicates repetition, and italics indicate substitution of an actual modifier, event, detail, or action name.

At a minimum, a translation must specify at least one event, specified by a predefined event name or abbreviation enclosed in angle brackets; a colon separator; and at least one action. However, a sequence of events can be specified; likewise, more than one action can be invoked as a result. The scope of event matching can be limited by one or more optional modifiers, and, in the case of some events, by a “detail” field that specifies additional information about the event. (For example, for key events the detail field specifies which key has been pressed.) Repeated occurrences of the same event (e.g., a double-click) can be specified by a count value in parentheses. A colon and optional white space separates the translation and the action.

The examples below are all valid translations:

<Enter>:  doit()                    invoke doit() on an EnterWindow event

<Btn1Down>,<Btn1Up>:  doit()        invoke doit() on a click of Button 1

<Btn1Up>(2):  doit()                invoke doit() on a double-click of Button 1
    
Button1<Btn2Down>,<Btn2Up>:  doit()

        invoke doit() on a click of Button 2 while Button 1 is held down

Shift<BtnDown>:  doit()

        invoke doit() on a click of any button while the shift key is held down

<Key>y:  doit()                     invoke doit() when the y key is pressed

A translation table is a single string, even when composed of multiple translations. If a translation table consists of more than one translation, the actual newlines are escaped with a backslash (except for the last one), and character newlines are inserted with the \n escape sequence, as you've seen demonstrated in examples throughout this book.

The following sections provide additional detail on each of the elements of an event translation. We'll talk first about the directive, followed by event specifiers, details, modifiers, and counts. We'll also provide some pointers on the proper sequence of translations, and discuss what happens when translations overlap.

The Directive

As we've already seen, the three possible directives are #replace, #override, or #augment.

#replace says to completely replace the value of the translations resource in the widget. If no directive is specified, #replace is the default.

The difference between #override and #augment is more subtle. They differ in how they treat event combinations in the new translation that also appear in the old. With #override, the new translation takes priority, while with #augment, the old one does.

For example, say a widget has a default translation table that includes a translation for the Return key, and an application that uses this widget has an app-defaults file consisting of a translation table that applies to this widget. If the translation table in the app-defaults file began with #override, any translation for the Return key would take priority over the default translation for the Return key. On the other hand, if the translation table in the app-defaults file began with #augment, the default translations would take priority.

Remember that the difference between #augment and #override is only in the treatment of overlapping translations. Any translations added with either directive for events that do not already appear in the existing translation table will always be added.

Because of potential overlap between translations, when you are debugging your intended translations it is best to use #replace at first, then test with #augment or #override as appropriate once you are sure that the translations themselves have the desired effect.

Selecting the Events to Translate

An X event is a packet of data sent by the server to a client in response to user behavior or to window system changes resulting from interactions between windows. There are 33 different types of events defined by X. Most (though not all[54]) events can be thought of as occurring in a window: the pointer entering or leaving a window, pointer motion within a window, pointer button presses, key presses, and so on. However, most events are not sent to a window unless the window has explicitly selected that event type; events are selected on a per-window basis. In Xlib programming, events are selected by specifying an event mask as a window attribute. Some events, which are sent to all windows regardless of whether a window has selected them or not, are called non-maskable events. One of the most complex aspects of Xlib programming is designing the event loop, which must take into account all of the possible events that can occur in a window.

Xt's translation manager selects the events that are specified in the current translation table for each widget. In translations, events can be specified either by their actual names, as shown in Column 1 of Table 8-1, or by means of the abbreviations shown in Column 2. A complete reference to each event type is provided in Appendix C, Event Reference, in Volume Five, X Toolkit Intrinsics Reference Manual.

Table 8-1. Event Type Abbreviations in Translation Tables

Event Type

Abbreviations

Description

ButtonPress

BtnDown

Any pointer button pressed

Btn1Down

Pointer button 1 pressed

Btn2Down

Pointer button 2 pressed

Btn3Down

Pointer button 3 pressed

Btn4Down

Pointer button 4 pressed

Btn5Down

Pointer button 5 pressed

ButtonRelease

BtnUp

Any pointer button released

Btn1Up

Pointer button 1 released

Btn2Up

Pointer button 2 released

Btn3Up

Pointer button 3 released

Btn4Up

Pointer button 4 released

Btn5Up

Pointer button 5 released

KeyPress

Key

Key pressed

KeyDown

Key pressed

Ctrl

KeyPress with Ctrl modifier

Meta

KeyPress with Meta modifier

Shift

KeyPress with Shift modifier

KeyRelease

KeyUp

Key released

MotionNotify

Motion

Pointer moved

PtrMoved

Pointer moved

MouseMoved

Pointer moved

BtnMotion

Pointer moved with any button held down

Btn1Motion

Pointer moved with button 1 held down

Btn2Motion

Pointer moved with button 2 held down

Btn3Motion

Pointer moved with button 3 held down

Btn4Motion

Pointer moved with button 4 held down

Btn5Motion

Pointer moved with button 5 held down

EnterNotify

Enter

Pointer entered window

EnterWindow

Pointer entered window

LeaveNotify

Leave

Pointer left window

LeaveWindow

Pointer left window

FocusIn

FocusIn

This window is now keyboard focus

FocusOut

FocusOut

This window lost keyboard focus

KeymapNotify

Keymap

Keyboard mappings changed

Expose

Expose

Part of window needs redrawing

GraphicsExpose

GrExp

Source of copy unavailable

NoExpose

NoExp

Source of copy available

ColormapNotify

Clrmap

Window's colormap changed

PropertyNotify

Prop

Property value changed

VisibilityNotify

Visible

Window has been obscured

ResizeRequest

ResReq

Redirect resize request to window manager

CirculateNotify

Circ

Stacking order modified

ConfigureNotify

Configure

Window resized or moved

DestroyNotify

Destroy

Window destroyed

GravityNotify

Grav

Window moved due to win gravity attribute

MapNotify

Map

Window mapped

CreateNotify

Create

Window created

ReparentNotify

Reparent

Window reparented

UnmapNotify

Unmap

Window unmapped

CirculateRequest

CircRec

Redirect stacking order change to window manager

ConfigureRequest

ConfigureReq

Redirect move or resize request to window manager

MapRequest

MapReq

Redirect window map request to window manager

MappingNotify

Mapping

Keyboard mapping changed

ClientMessage

Message

Client-dependent

SelectionClear

SelClr

Current owner is losing selection

SelectionNotify

Select

Selection is ready for requestor

SelectionRequest

SelReq

Request for selection to current owner


Many of these events are handled automatically by the Toolkit separately from the translation mechanism. For example, Expose events are automatically sent to the expose method of the appropriate widget. When there is also a translation for Expose events, the action is called in addition to the expose method. The only reason you would need a translation for Expose events is to add drawing to a Core widget. Because the Core widget doesn't have an expose method, there is rarely, if ever, a case where there is both an expose method and a translation for Expose events for the same widget. The point to remember is that any event can be specified in a translation, even when that event is also used in some other way by Xt.

GraphicsExpose and NoExpose events are useful when your application copies from a visible window with XCopyArea() or XCopyPlane(). They notify the application when part of the source of the copy is obscured. If you want GraphicsExpose and NoExpose events, you must explicitly select them by setting the graphics_exposures GC component in the GC used for the copy. Then you can provide a translation for them, or if you are writing a widget you can have them sent to your expose method by setting the compress_exposure field of the Core structure to a special value, as described in Section 9.7.2, "Event Filters." (While xbitmap and BitmapEdit use XCopyArea() and XCopyPlane(), they copy from an off-screen pixmap that cannot be obscured, and therefore GraphicsExpose and NoExpose events are not needed. For a description of these events, see Chapter 5, The Graphics Context, in Volume One, Xlib Programming Manual.)

Several of the *Notify events are automatically handled by the Toolkit. Xt places this information in internal structures so that it can satisfy queries for widget positions and geometries without querying the server. MappingNotify events are automatically handled; Xt gets the current keyboard mapping from the server. VisibilityNotify events are handled automatically if the visible_interest field in the Core structure is set to True (see Section 9.5, "Visibility Interest").

With the exception of SelectionRequest, the *Request events are intended for use only by window managers. The selection events are described in Chapter 11, Interclient Communications.

The keyboard traversal code built into the Motif Primitive class handles EnterNotify, LeaveNotify, FocusIn, and FocusOut events.

Whether or not they are already handled by Xt or in translations, events can also be selected explicitly by installing event handlers in a widget (as described in Chapter 9, More Input Techniques).

Details in Keyboard Events

As you've seen, Xt provides special event abbreviations to specify which pointer button was pressed or released. With key events, this approach would be a little impractical, since there are so many keys. Instead, the Translation Manager allows you to follow the event specification with an optional detail field. That is:

<Key>:  doit()

means that you want the doit() action to be invoked when any key has been pressed, while:

<Key>y:  doit()

means that you want the doit() action to be invoked only if the y key has been pressed.

What you actually specify as the detail field of a Key event is a keysym, as defined in the header file <X11/keysymdef.h>.[55] Keysyms begin with an XK_ prefix, which is omitted when they are used in translations.

Before we explain any further, we need to review X's keyboard model, which, like many things in X, is complicated by the design goal of allowing applications to run equally well on machines with very different hardware.

The keyboard generates KeyPress events, and may or may not also generate KeyRelease events. These events contain a server-dependent code that describes the key pressed, called a keycode. Modifier keys, such as Shift and Control, generate events just like every other key. In addition, all key events also contain information about which modifier keys and pointer buttons were held down at the time the event occurred.

Xt provides a routine that translates keycode and modifier key information from an event into a portable symbolic constant called a keysym. A keysym represents the meaning of the key pressed, which is usually the symbol that appears on the cap of the key. For example, when the a key is pressed, the keysym is XK_a; when the same key is pressed while the Shift key is held down, the resulting keysym is XK_A (uppercase). Note that even though both the a and the A events have the same keycode, they generate a different keysym because they occurred with different modifier keys engaged. (The mapping between keycodes and keysyms is discussed further in Section 14.5, "Keyboard Interpretation.")

You may specify either the keysym name, omitting the XK_ prefix, or its hexadecimal value (also shown in <X11/keysymdef.h>), prefixed by 0x or 0X (zero followed by x or X).

This is fairly straightforward, though there are several provisos:

  • The keysym for nonalphanumeric keyboard keys may not always be obvious; you will need to look in <X11/keysymdef.h>. For example, the keyboard for the Sun workstation has keys labeled “Left” and “Right.” Their keysyms are XK_Meta_L and XK_Meta_R, respectively. Fortunately, most common named keys have mnemonic keysym names (XK_Return, XK_Linefeed, XK_BackSpace, XK_Tab, XK_Delete, and so on) so you can often get by without looking them up. Notice, though, the small things that can trip you up: BackSpace, not Backspace, but Linefeed, not LineFeed. You can't count on consistency.

  • The definitions in <X11/keysymdef.h> spell out keysym names for punctuation and other special characters. For example, you'll see XK_question rather than XK_?, and so on. Nonetheless, you need not use this long keysym name; the Translation Manager will accept the equivalent single character, as long as it is a printing character. This is also true of digits. There are a few exceptions, namely the characters that have special meaning in a translation table. If you ever need a translation for colon, comma, angle brackets, or backslash, you'll have to use the keysym name rather than the single printing character.

  • The keycode-to-keysym translator built into the Translation Manager makes case distinctions only if the key event is prefaced with the colon (:) modifier. This means that <Key>a and <Key>A will be treated identically by default. We'll return to this subject when we discuss modifiers.

In sum, the following are all valid key event translations:

<Key>q:  quit()	              invoke quit() when q or Q is pressed
:<Key>?:  help()	             invoke help() when ? (but not /) is pressed
<Key>Return:  newline()       invoke newline() when the Return key is pressed
<Key>:  insert_char()	        invoke insert_char() when any key is pressed

Details in Other Event Types

Key events are the most likely to require details, but details are also available for several other event types. They simplify your action function because they instruct Xt to call the action only when the event contains the matching detail. One type of detail, for MotionNotify, actually affects the selection of events.

Trivially, the detail values Button1 through Button5 can be supplied as details for ButtonPress or ButtonRelease events; because of the available abbreviations, though, these button detail values should not often be necessary. It is easier to say:

<Btn1Down>: quit()

than:

<BtnDown>Button1:  quit()

The EnterNotify, LeaveNotify, FocusIn, and FocusOut events can take as details any of the notify mode values shown in Table 8-2.

Table 8-2. Notify Mode Values for Enter, Leave, and Focus Events

Detail

Description

Normal

Event occurred as a result of a normal window crossing.

Grab

Event occurred as a result of a grab.

Ungrab

Event occurred when a grab was released.


Normally, EnterNotify and LeaveNotify events are generated when the pointer enters or leaves a window, and FocusIn and FocusOut events are generated when a click-to-type window manager changes the keyboard focus window--the window that receives all keyboard input regardless of pointer position. In these cases the detail is Normal. But these events are also generated when the keyboard or pointer is grabbed by an application with a call to XtGrabPointer(), XtGrabButton(), XtGrabKeyboard(), or XtGrabKey(). A grab, which is usually invoked by a popup window, also causes keyboard and/or pointer input to be constrained to a particular window. When these events are generated because of a grab or the release of a grab, the detail is Grab or Ungrab.

Details for additional events were added in Release 4. For MotionNotify events you can now select normal motion events or motion hints by specifying a detail of Normal or Hint. Normal motion events are used when you need a complete record of the path of the pointer. The compress_motion Core field, described in Chapter 9, is for use with normal motion events. Motion hints are used when you need only periodic pointer position updates. Each time you receive a motion hint you call XQueryPointer() (an Xlib call) to get the current pointer position. Note that the MotionNotify detail is the only one that affects event selection.

For PropertyNotify, SelectionClear, SelectionRequest, SelectionNotify, and ClientMessage events, the detail is an atom described in Table 8-3.

Table 8-3. Atom Details for Various Events

Event Type

Detail Atom

PropertyNotify

Property that is changing.

SelectionClear

Selection atom (such as PRIMARY).

SelectionRequest

Selection atom (such as PRIMARY).

SelectionNotify

Selection atom (such as PRIMARY).

ClientMessage

Message type.

For MappingNotify, the detail can be Modifier, Keyboard, or Pointer. Xt automatically does what is normally required to handle keyboard (and modifier) mapping changes. However, the Pointer detail might be used to make sure that the user has not disabled (by mismapping) any pointer button that is necessary for the safe operation of the application. You would simply write a translations such as <Mapping> Pointer: ButtonMapCheck and then call XGetPointerMapping() in the ButtonMapCheck action to make sure the pointer mapping is still acceptable.

The detail values for each event type corresponds to certain members of the associated event structure, as shown in Table 8-4. See Appendix C, Event Reference, in Volume Five, X Toolkit Intrinsics Reference Manual, for more details.

Table 8-4. Event Structure Fields Used As Translation Table Hints

Event Type

Event Structure Field

ButtonPress, ButtonRelease

button

MotionNotify

is_hint

EnterNotify, LeaveNotify

mode

FocusIn, FocusOut

mode

PropertyNotify

atom

SelectionClear

selection

SelectionRequest

selection

SelectionNotify

selection

ClientMessage

message_type

MappingNotify

request


Modifiers

Certain events include as part of their data the state of the pointer buttons and special keyboard modifier keys at the time the event was generated. The events for which this state is available include ButtonPress, ButtonRelease, MotionNotify, KeyPress, KeyRelease, EnterNotify, and LeaveNotify.

For these events, you can specify a desired modifier state using one or more of the modifier keywords listed in Table 8-5. An error is generated if you specify modifiers with any other types of events.

Table 8-5. Modifiers Used in Translation Tables

Modifier

Abbreviation

Description

Ctrl

c

Control key is held down.

Shift

s

Shift key is held down.

Lock

l

Caps Lock is in effect.

Meta

m

Meta key is held down.

Hyper

h

Hyper key is held down.

Super

su

Super key is held down.

Alt

a

Alt key is held down.

Mod1

Mod1 key is held down.

Mod2

Mod2 key is held down.

Mod3

Mod3 key is held down.

Mod4

Mod4 key is held down.

Mod5

Mod5 key is held down.

Button1

Pointer Button 1 is held down.

Button2

Pointer Button 2 is held down.

Button3

Pointer Button 3 is held down.

Button4

Pointer Button 4 is held down.

Button5

Pointer Button 5 is held down.


Physical Keys Used as Modifiers

The meaning of the Meta, Hyper, Super, Alt, and Mod1 through Mod5 keywords may differ from server to server. Not every keyboard has keys with these names, and even if keysyms are defined for them in <X11/keysymdef.h>, they may not be available on the physical keyboard.[56]

For example, the file <X11/keysymdef.h> includes the following definitions:

/* Modifiers */
#define XK_Shift_L              0xFFE1  /* Left shift */
#define XK_Shift_R              0xFFE2  /* Right shift */
#define XK_Control_L            0xFFE3  /* Left control */
#define XK_Control_R            0xFFE4  /* Right control */
#define XK_Caps_Lock            0xFFE5  /* Caps lock */
#define XK_Shift_Lock           0xFFE6  /* Shift lock */
#define XK_Meta_L               0xFFE7  /* Left meta */
#define XK_Meta_R               0xFFE8  /* Right meta */
#define XK_Alt_L                0xFFE9  /* Left alt */
#define XK_Alt_R                0xFFEA  /* Right alt */
#define XK_Super_L              0xFFEB  /* Left super */
#define XK_Super_R              0xFFEC  /* Right super */
#define XK_Hyper_L              0xFFED  /* Left hyper */
#define XK_Hyper_R              0xFFEE  /* Right hyper */

There are two things you must learn before you can use these modifiers:

  1. Which physical key generates a given keysym, if it is not obvious from the name.

  2. Which modifiers are valid on your server.

The best way to find out what keysym a key generates is with the xev client, which prints detailed information about every event happening in its window. To find out a keysym, run xev, move the pointer into its window, and then press the key in question.

On the Sun SparcStation, there is only one control key, on the left side of the keyboard. It generates the keysym XK_Control_L. There are two shift keys, one on each side, which generate the keysyms XK_Shift_L and XK_Shift_R. The Meta modifier is mapped to the keys labeled “Left” and “Right.” Even though there is an Alternate keyboard key, which generates the keysym XK_Alt_R, this key is not recognized as a modifier. There are no Super and Hyper keys.

The list of valid modifiers can be displayed with the xmodmap client, as follows:

isla% xmodmap
xmodmap:  up to 2 keys per modifier, (keycodes in parentheses):
shift       Shift_L (0x6a),  Shift_R (0x75)
lock        Caps_Lock (0x7e)
control     Control_L (0x53)
mod1        Meta_L (0x7f),  Meta_R (0x81)
mod2
mod3
mod4
mod5

That is, either of the two shift keys will be recognized as the Shift modifier, the Caps Lock key as the Lock modifier, and either the Left or Right keys as the Meta modifier. The Left and Right keys are also mapped to the Mod1 modifier. The Alt key is not recognized as a modifier.

xmodmap also allows you to add keysyms to be recognized as the given modifier. For example, the following command would cause the F1 function key to be recognized as mod2:

isla% xmodmap -e 'add mod2 = F1'

For more information on xmodmap and xev, see Chapter 11 of Volume Three, X Window System User's Guide, Motif Edition.

Default Interpretation of the Modifier List

If no modifiers are specified in a translation, the state of the modifier keys and pointer buttons makes no difference to a translation. For example, the translation:

<Key>q:    quit()

will cause the quit action to be invoked when the q key is pressed, regardless of whether the Shift, Ctrl, Meta, or Lock key is also held, and regardless of the state of the pointer buttons.

Likewise, if a modifier is specified, there is nothing to prohibit other modifiers from being present as well. For example, the translation:

Shift<Key>q:    quit()

will take effect even if the Ctrl key is held down at the same time as the Shift key (and the q key).

There are a number of special modifier symbols that can be used to change this forgiving state of affairs. These symbols are shown in Table 8-6.

Table 8-6. Modifier Symbols

Symbol

Description

None

No modifiers may be present.

!

No modifiers except those explicitly specified may be present.

:

Apply shift (and lock) modifier to key event before comparing.

~

The modifier immediately following cannot be present.

The syntax of these special modifiers symbols is somewhat inconsistent, and made clearest by example.

Prohibiting a Modifier

The tilde (~) is used to negate a modifier. It says that the specified modifier may not be present. For example:

Button1<Key>:  doit()

says that doit() should be invoked by a press of any key when button 1 is being held down, while:

~Button1<Key>:  doit()

says that doit() should be invoked by a press of any key except when button 1 is being held down.

A ~ applies only to the modifier that immediately follows it. For example:

~Shift Ctrl<Key>:  doit()

says that doit() should be invoked when Ctrl is held down in conjunction with any key, except if Shift is depressed at the same time.

Requiring an Exact Match

An exclamation point at the start of a modifier list states that only the modifiers in that list may be present, and must match the list exactly. For example, if the translation is specified as:

!Shift<Key>q:  quit()

the translation will take effect only if the Shift key is the only modifier present; if Caps Lock were in effect, or if a pointer button were depressed at the same time, the translation would no longer work.

The modifier None is the same as ! with no modifiers specified. That is:

None<Key>q:  quit()

or:

!<Key>q:  quit()

will invoke quit only if no modifier keys at all are pressed.

Paying Attention to the Case of Keysyms

The : modifier, like !, goes at the beginning of the modifier list and affects the entire list. This one is really in a category by itself, since it applies only to Key events.

Normally, the translations:

<Key>a:  doit()

and:

<Key>A:  doit()

have identical results: both will match either a lowercase or an uppercase A. Preceding the translation with a colon makes the case of the keysym significant. For example, to create commands like vi editor, you might specify the following translations:

:<Key>a:  append()  \n\
:<Key>A:  appendToEndOfLine()

In this case, a and A are distinct. You could achieve somewhat the same result by specifying:

~Shift<Key>a:  append()  \n\
Shift<Key>a:  appendToEndOfLine()

However, this second example is both more complex and less effective. While the colon syntax will match an uppercase character generated as a result of either the Shift or Lock modifiers, the example shown immediately above will handle only Shift. You could use the following translation to prohibit both Shift and Lock from being asserted, but there is no way to specify that either Shift or Lock may be present:

~Shift ~Lock<Key>a:  append()  \n\
Shift<Key>a:  appendToEndOfLine()

Note that you cannot specify both the Shift or Lock modifier and an uppercase keysym. For example:

Shift<Key>A:  doit()

will have no effect, since you cannot further shift an uppercase character. The sequence !: at the beginning of a translation means that the listed modifiers must be in the correct state and that no other modifiers except the Shift and Lock keys may be asserted. In other words, !: by itself means the translation is triggered only if the specified key is pressed, considering case, and no other modifier is pressed.

For more details on how the Toolkit handles the ins and outs of case conversion, see Chapter 14, Miscellaneous Toolkit Programming Techniques. A more rigorous discussion of how the colon modifier works is also given in Appendix F, Translation Table Syntax, in Volume Five, X Toolkit Intrinsics Reference Manual.

Event Sequences

The left-hand side of a translation may specify a single event or a sequence of events that must occur for the action to be invoked. For example, we might want a certain action to be invoked only if the first button is clicked twice in succession.

Each event specified in the sequence is one of the abbreviations for an event type enclosed in angle brackets, optionally led by any set of modifiers and special characters, as described in Section 8.1.5, "Modifiers." Each modifier-list/event pair in the sequence is separated by a comma.

For example, the translation to perform an action in response to a button press followed by a release of the same button (a button click) is the following:

<BtnDown>,<BtnUp> : doit()

Button press events may be specified in the translation table followed by a count in parentheses to distinguish a multiple click. The translation to detect a double click is:

<Btn1Down>(2) : doit()

or:

<Btn1Up>(2) : doit()

Notice that though they have the same effect for the user, these two translations are actually interpreted differently. The first is equivalent to saying:

<Btn1Down>,<Btn1Up>,<Btn1Down>:  doit()

while the second is equivalent to:

<Btn1Down>,<Btn1Up>,<Btn1Down>,<Btn1Up>:  doit()

A plus (+) may appear immediately after the count inside the parentheses to indicate that any number of clicks greater than the specified count should trigger the action.

The following translation detects two or more clicks:

<Btn1Down>(2+) : doit()

The maximum count that can be specified is 9.

Xt uses a timing technique to detect whether two or more clicks should be considered a multi-click or separate single clicks. The time is controlled by the XmNmultiClickTime application resource, which defaults to 200 milliseconds. If 200 milliseconds or more of idle time passes between clicks, Xt does not consider that a match. You can set this resource to different values for each display using XtSetMultiClickTime().

For key events, you need to be careful when you want to make a particular key sequence invoke one action, and a subset of that key sequence to invoke another. For example, consider the following translation:

 <Key>a,<Key>b : something()
 <Key>         : else()

One might expect <Key>b events not preceded by <Key>a events to invoke the else() action. But instead, such <Key>b events are discarded. To get what you expected, use:

 <Key>a,<Key>b : something()
 <Key>b        : else()
 <Key>         : else()

Special Considerations Involving Motion Events

Beware of interaction between pointer motion events and double clicking translations in the same table. If no motion events are specified in a translation table, these events are never selected, so there is no problem if they occur between other events. This allows a double click to be detected even if the user inadvertently jiggled the pointer in the course of the click. However, if motion events are selected for anywhere in the table, they may interfere with the expansion of events specified with the repeat notation. The suggested way to handle both motion events and double clicks in the same translation table is to have all clicks sent to one action procedure, then use the timestamps in the events to implement your own algorithm for differentiating single clicks from double clicks. For example:

<Btn1Down>:  handleClicks()
Button1<Motion>: handleMotif()

The handleClicks action would handle both single and double clicks. As described in Section 8.1.7.3, this type of implementation also is useful when you want different semantics for single and double clicks.

Multiple motion events will match any single motion selected event in the translation. That is:

<Motion>:  doit()

will cause doit() to be invoked many times, once for each motion event generated by the pointer movement, unless the compress_motion event filter is turned on in the Core class structure. (This filter is described in Section 9.7.2.)

Modifiers and Event Sequences

A modifier list at the start of an event sequence applies to all events in the sequence. That is:

Shift<BtnDown>,<BtnUp>:  doit()

is equivalent to:

Shift<BtnDown>,Shift<BtnUp>:  doit()

However, if modifiers and events are interspersed, the modifier applies only to the event immediately following. As an extreme case to demonstrate this behavior, consider the following translation:

Ctrl<Btn2Down>,~Ctrl<Btn2Up>: doit()

which requires that the Ctrl key be depressed when pointer button 2 is pressed; however, the key must not be depressed when the button is released.

Using Modifiers to Specify Button Event Sequences

Remember that there are modifiers for specifying the state of pointer buttons as well as modifier keys. These modifiers provide another way of expressing some event sequences. For example, the following translation would invoke the action when the F1 key is pressed while button 1 is being held down:

Button1<Key>F1:     doit()

This is equivalent to:

<Btn1Down>,<Key>F1:  doit()

The following translations could be used to call an action when both the first and second pointer buttons are down, regardless of the order in which they are pressed.

!Button1<Button2>:    doC() \n\
!Button2<Button1>:    doC()

Key Event Sequences

The Translation Manager specification provides a special syntax for specifying a sequence of Key events--as a string. That is:

"yes":  doit()

is theoretically equivalent to:

<Key>y,<Key>e,<Key>s:  doit()

We say “theoretically” because, at least in R4 and R5 sample implementations from MIT, the “yes” syntax does not work from resource files. It may or may not work in other implementations.

Within a sequence of key events expressed as a string (on systems where the “yes” syntax does work, if any), the special character ^ (circumflex) means Ctrl and $ means Meta. These two special characters, as well as double quotes and backslashes, must be escaped with a backslash if they are to appear literally in one of these strings.

Interactions Between Translations

One of the most difficult things to learn about translations is how to predict the interactions between overlapping translations.

Translations in Multiple Resource Files

Much more will be said about resource files and resource matching in Chapter 10, Resource Management and Type Conversion. However, it is important to make one point here.

If several resource files contain translation tables for the same widget, only the one that takes priority according to file precedence and resource matching will have any effect even if all of them specify #augment or #override. This is because the translation table is the value of the resource, and only one resource value will override all other values.

In other words, even if all the translation tables specify #augment or #override, a maximum of two translation tables are merged by Xt: the widget's default translation table and the one translation table that takes priority as determined by the process of reading and merging all the resource files (and the command line) and then the process of resource matching. If you call XtOverrideTranslations() or XtAugmentTranslations() in the application, this is a third level of merging, but it is unrelated to the resource files.

Order of Translations

The order of translations in a table is important, because the table is searched from the top when each event arrives, and the first match is taken. Therefore, more specific events must appear before more general ones. Otherwise, the more specific ones will never be reached.

For example, consider a text entry widget that wants to close itself and dispatch a callback once the user presses the Return key, indicating that entry is complete. The widget's translations might be:

<Key>Return:  gotData()    \n\
<Key>:        insertChar()

If the translations were specified in the reverse order:

<Key>:        insertChar()  \n\
<Key>Return:  gotData()

insertChar() would be called in response to a press of the Return key, and gotData() would never be called.

Keeping track of translation order is made more complicated when translations are merged. For example, if one of the above lines is in the default translations of a widget class, and the other is in a translation table in a resource file, in what order are the resulting translations? Though not documented in the Xt specification, it appears that the MIT implementation of Xt places the more specific translation first.

Event Sequences Sharing Initial Events

An exception to this rule of more specific translations being placed earlier in the translation table occurs with event sequences. One translation can contain an event or event sequence that appears in another translation. If the leading event or event sequence is in common, both will operate correctly. For example, the translations:

<Btn1Down>,<Btn1Up>:    actionB() \n\
<Btn1Down>:             actionA()

execute actionA() when button 1 is pressed and actionB() when it is then released. This is true regardless of which translation appears first in the table.

This is the reason, alluded to in Chapter 2, Introduction to the X Toolkit and Motif, why it is not possible to bind two different actions to a single and a double click of the same button in the same widget. Specifying:

<Btn1Up>(2):  quit()  \n\
<Btn1Down>,<Btn1Up>:  confirm()

will invoke the confirm() action on a single click and both the confirm() and quit() actions on a double click.

This behavior was a design decision on the part of the X Consortium. Otherwise, the Intrinsics could never dispatch the single-click action until the double-click interval had passed, since it would have no way of knowing if a second click was coming. Applications needing to have single and double clicks in the same widget must do so by designing the two actions appropriately, rather than relying on the Translation Manager to make the distinction.

Event Sequences Sharing Noninitial Events

If a noninitial event sequence is common between two translations, each translation will match only event sequences where the preceding event does not match the other translation. Consider the translation:

<Btn1Down>,<Btn1Up>:    actionB() \n\
<Btn1Up>:               actionA()

When a Btn1Up event occurs, it triggers actionA() only if not preceded by Btn1Down.

The way this works is that Xt keeps a history of recent events. When each event arrives, Xt compares this event and its immediate history to the event sequences in a translation table. An event in the sequence may have already triggered an action, but that is irrelevant. Any translation whose final event in the event sequence matches the current event and whose earlier event sequence matches the event history is matched. If there are two translations that can match a particular event sequence, then the translations are considered overlapping, the latter will override the former, and Xt will print a run-time warning message at startup (not waiting for the overlapping actions to be invoked). This is an exception to the rule stated earlier that the translation manager executes the first match.

Accelerators and Mnemonics

As defined by Xt, accelerators are simply translation tables registered to map event sequences in one widget to actions in another. This is a general mechanism, but its name is based on a common use--adding keyboard shortcuts for widgets that are normally triggered by mouse button presses, such as PushButton widgets--because for advanced users a keyboard interface is often faster.

What Motif calls accelerators are implemented using Xt's accelerators, but the two are not at all the same. Motif implements a more convenient interface for providing keyboard shortcuts in menus, which it calls accelerators, but at the same time disables the more general accelerators feature in standard Xt. We will begin with a discussion of accelerators as defined by Xt, using the Athena widget set in the examples since they will not work in Motif. Then we will proceed with how Motif accelerators and mnemonics work. If you are interested only in how to program with Motif, then you can skip to Section 8.2.5, "Motif Accelerators and Mnemonics."

Xt Accelerators

Every widget has an XtNaccelerators resource, inherited from the Core widget class. This resource contains an accelerator table, which is identical in format to a translation table; it maps event sequences to action sequences. Accelerator tables can be set into the XtNaccelerators resource in all the same ways translation tables can. The widget on which the XtNaccelerators resource is set is the widget whose actions are to be invoked from events in other widgets.

Once the XtNaccelerators resource of a widget is set, the application must call XtInstallAccelerators() or XtInstallAllAccelerators() to specify which widget will be the source of the events to invoke the actions specified in the accelerator table.

The MIT Intrinsics documentation refers to these two widgets as the source and destination. This can be confusing, especially since the arguments are referenced in reverse order in the call to XtInstallAccelerators():

void XtInstallAccelerators(destination, source)
     Widget destination;
     Widget source;

Just remember that the source is the widget whose actions you want executed, and the destination is the widget you want the events to be gathered in. To understand the use of the phrase install accelerators, think of the accelerators as the XtNaccelerators resource of the source widget, together with the actions of that widget, and the destination as the widget to which these actions are transplanted (i.e., “installed”).

XtInstallAccelerators() is always called from the application. (Widgets never install accelerators, because by definition they don't know about any other widgets.) In applications, accelerators can be installed any time, before or after the widgets are realized. Just before the XtAppMainLoop() call is a good place.

As an example, we'll add accelerators to the xbox1 application, which is the Athena version of xrowcolumn from Chapter 3, More Techniques for Using Widgets. This application displays two Command widgets in a Box, as shown in Figure 8-1.

Figure 8-1. xbox1: two Command widgets in a Box


To add accelerators requires a single line of code--a call to XtInstallAccelerators(), as shown in Example 8-1.

Example 8-1. Installing accelerators in an application

main(argc, argv)
int argc;
char **argv;
{
    XtAppContext app_context;
    Widget topLevel, box, quit;
      .
      .
      .
    /* allow quit widget's actions to be invoked by events in box. */
    XtInstallAccelerators(box, quit);
    XtAppMainLoop(app_context);
}

The actual value of the accelerator table is set through the resource mechanism. The XtNaccelerators resource is set for the widget whose actions are to be invoked from another widget. Xt stores this accelerator table, in compiled form, in the widget's instance structure when the widget is created. When the application calls XtInstallAccelerators(), the accelerator table stored in the widget is merged with the translation table of the widget that will be capturing the events. (If XtRemoveAccelerators is called, the original translation table, prior to merging with the accelerators from the source widget, is restored.)

Example 8-2 shows a possible app-defaults file.

Example 8-2. Specifying the XtNaccelerators resource from the app-defaults file

*quit.label:  Quit
*pressme.label:  Press me
*quit.accelerators: \n\
        <KeyPress>q:    set() notify()

This resource setting will allow a q typed anywhere in the Box widget that comprises the xbox1 application window to invoke the quit widget's notify() action (which, as you may recall from Section 2.4, "Connecting Widgets to Application Code," in turn invokes the Quit callback installed by the application). Notice that the accelerators are specified as a resource of the source widget, even though the events that invoke them will come from the destination widget.

There are some differences in the translation tables that can be used for accelerators.

  • Abbreviations for event types are not valid. Only the event names that were shown in Column 1 of Table 8-1 may be used (although Key works in place of KeyPress).

  • The None, Meta, Hyper, Super, Alt, or ANY modifier symbols are not valid.

  • The default directive for accelerator tables is #augment rather than #replace. If specified, the #replace directive is ignored. It is important to realize that accelerators are merged with the source widget's translations. #override thus says that the accelerators take priority over any matching translations in the source widget, and #augment says that the accelerators have lower priority.

  • Parameters passed to actions must be quoted with double quotes.

Event Propagation

If you install accelerators in the Box widget as shown in Example 8-2, you will notice that even though the destination widget is Box, keyboard input will also be accepted in the children of box, including not only the quit widget but also the pressme widget. This is because of a characteristic of X called event propagation.

The server sends to the client only the types of events the client selects. Internally, Xt selects events using the Xlib call XSelectInput(). From a Toolkit application, this is hidden because the translation mechanism automatically selects the appropriate event types specified in the translation table. (As we will see in Chapter 9, More Input Techniques, Xt provides a low-level event-handling mechanism that exposes event selection more directly.)

Event selection reduces the amount of network traffic, the number of events the server has to generate, and the number of unneeded events the client must process only to throw away.

Most event types are selected on a per-window basis. Each selected event that arrives at the client contains the window ID of the window it was selected for. (Non-maskable events, the last five event types in Table 8-1, are the exception; they are selected using a different mechanism or are not selected at all.)

Pointer and keyboard events propagate through windows that have not selected them, as shown below in Figure 8-2. That is, if the pressme widget's window has not selected KeyPress events, any key events that occur in that window will be passed on to its parent, the Box widget. If the Box widget's window didn't select them either, they would be passed on to the window created by the top-level Shell widget, and so on. Events propagate through the window hierarchy all the way to the root window, until they reach a window that selected them. (If no window has selected them, they are never sent by the server.) Xt treats an event that originally occurred in some descendent widget just as if it actually happened in the widget that selected the event.

Figure 8-2. Key event propagation in xbox

Propagation is a characteristic of ButtonPress, ButtonRelease, KeyPress, KeyRelease, MotionNotify, EnterNotify, and LeaveNotify events only. As mentioned earlier, keyboard events are the events most often used as accelerators.

In the version of xbox we just looked at, keyboard events propagate through the Command widgets to the Box widget, because they are not selected by the Command widgets. When they reach the Box widget, they match the events specified in the accelerator table and invoke actions from the quit widget.

The Command widgets have a default translation table that includes pointer button events and enter/leave events. Xt, therefore, selects these events for each Command widget. Before accelerators are installed, the Box widget has no translation table and therefore has no events selected for it. When the accelerators are installed, the accelerator table is merged with the (nonexistent) translation table of the Box widget and becomes the Box's translation table. Xt then selects keyboard events for the Box widget.

But consider the resource settings in Example 8-3.

Example 8-3. Conflicting translations and accelerators

*quit.label:  Quit
*pressme.label:  Press me
*pressme.translations:  #override \n\
        <KeyPress>p:    set() notify()
*quit.accelerators: \n\
        <KeyPress>q:    set() notify()


No key event will propagate through a widget that has a translation for any key event. With the resource settings in Example 8-3, since pressme has a translation for the p key, the q key will not propagate through the widget. Therefore, it is often better to specify all key events as accelerators and install them on a common ancestor.

Accelerators are just merged into translations--they are not a completely different mechanism. The #augment (default) or #override directives used in accelerator tables specify whether the accelerator should override or augment existing translations for the destination widget. For example, if box had a translation of its own that matched the event sequence in the accelerator installed on the same widget, whether the translation or the accelerator would be invoked depends on whether #augment (the default) or #override had been specified as the accelerator directive. (This example is a little farfetched, since the Box widget defines no actions or translations--but it illustrates the point.)

When using or writing widgets, event propagation is usually important only for accelerators, because widgets are rarely layered in such a way that any of the ones that accept input are obscured.

Installing Accelerators in Multiple Widgets

If you want to install accelerators from more than one widget, you can call XtInstallAccelerators() once for every widget whose actions you want executable from the destination. Alternatively, you can call XtInstallAllAccelerators() just once for a whole application, specifying the application's top-level window as both the destination and the source. XtInstallAllAccelerators() recursively traverses the widget tree rooted at the source, and installs the accelerators of each widget onto the destination.

For example, to install accelerators from both the quit and pressme widgets onto box, you could replace the call to XtInstallAccelerators() shown in Example 8-3 with:[57]

Example 8-4. Installing accelerators from both command widgets

XtInstallAllAccelerators(box, box);

You could then define an app-defaults file such as the following:

Example 8-5. Accelerators resource settings for two widgets

*quit.label:  Quit
*pressme.label:  Press me
*pressme.accelerators:   \n\
        <KeyPress>p:    set() notify()
*quit.accelerators:      \n\
        <KeyPress>q:    set() notify()

Because you would be most likely to specify distinct KeyPress events to invoke the actions in each Command widget, you would normally specify each accelerator resource separately, as shown above. But in the unlikely case that you wanted actions of multiple Command widgets to be invoked by the exact same event, you could do this instead. For example:

*Command.accelerators:   \n\
        <KeyPress>:  set() notify()

would invoke the set and notify actions of every Command widget, but not of widgets belonging to other classes.

Or assuming that there were multiple Box widgets in an application, one containing options, and the other commands, you might specify something like this:

*optionsbox*accelerators:   \n\
        <KeyPress>:  set() notify()

to invoke the actions of every Command widget in optionsbox only.

The point is that, like any resource setting, an accelerator table can apply to only one widget, to a group of children of a widget, or to an entire class, depending on how you identify the widgets for which you are setting the XtNaccelerators resource.

Defining the Accelerator Table in the Code

If you want to install a default accelerator table from within a program, you must follow similar steps to those used for translations, but with the following differences:

  • You must set the XtNaccelerators resource instead of XtNtranslations.

  • Instead of using XtParseTranslationTable() to convert the ASCII translation table into Xt's internal form, use XtParseAcceleratorTable().

  • There are no accelerator equivalents of XtOverrideTranslations() or XtAugmentTranslations(). Instead you can use XtVaSetValues() and use the accelerator directive by specifying #augment or #override.

Motif Accelerators and Mnemonics

Motif accelerators are keyboard shortcuts for menus, but they are used in a way that is completely different from that of the standard Xt accelerators just described.

When a RowColumn widget has been configured to operate as a popup menu or MenuBar, its XmNmenuAccelerator resource is activated. This resource can be set to a keysym which, when pressed, will pop up the popup menu or move the focus to the first item in the MenuBar. For example, the effective default value for XmNmenuAccelerator for the MenuBar is <KeyUp>F10.

RowColumn also has a subpart which has an XmNbuttonAccelerators resource. The XmNbuttonAccelerators resource is set to an array of keysyms, which when pressed will trigger the respective buttons. The XmNbuttonAcceleratorText resource is an array that lets you provide text that will appear in each button, to show the user the accelerator for each button.

All the resources that begin with XmNbutton can only be used when a menu is created with a function that begins with XmCreateSimple (there is one for each of the six types of Motif menus). These functions create a menu and populate it with children. Here are the other RowColumn subresources that configure the children as they are created:

  • XmNbuttonCount specifies the total number of menu buttons, separators, and titles to create.

  • XmNbuttons specifies a list of compound strings to use as labels for the buttons created. The list contains one element for each button, separator, and title created.

  • XmNbuttonType specifies a list of button types associated with the buttons to be created. The list contains one element for each button, separator, and title created. If this resource is not specified, each button in a MenuBar is a CascadeButtonGadget; each button in any other type of RowColumn widget is a PushButtonGadget. Each entry in the button type array can have several possible values such as XmPUSHBUTTON.

These resources work only when set while creating a menu using XmCreateSimple*Menu such as XmCreateSimplePopupMenu(). Setting them at any later time will have no effect. Some resources that configure the children of menus are not listed here.

Mnemonics

A mnemonic is a keyboard shortcut as well. However, a mnemonic is available only when a menu is visible, while an accelerator can invoke a menu entry even when the menu is not visible. Therefore, accelerators must be unique application-wide, while mnemonics only need to be unique within a single menu. Consequently, a mnemonic is a particular key regardless of modifiers, while accelerators are usually key combinations such as Ctrl-C.

A mnemonic is indicated in a button by underlining a character in the button label. Motif does this automatically as long as the mnemonic you choose appears in the label. The XmNbuttonMnemonics subresource of RowColumn specifies a mnemonic for each button created, but can only be used while creating the menu with one of the XmCreateSimple*Menu routines. The list contains one element for each button, separator, and title created. The mnemonic for a particular button can also be set using the XmNmnemonic resource of any kind of button widget.

Further discussion of mnemonics is provided in the forthcoming Volume Six, Motif Programming Manual.

The display_accelerators Method

Xt calls the source widget's display_accelerators method when the accelerators are installed. The purpose of this method is to display to the user the installed accelerators. The method is passed a string representation of the accelerator table, which the method can theoretically manipulate into a form understandable by the user.

All Motif widgets just set this method to NULL. All of the Athena widgets inherit the Core widget's display_accelerators method by initializing the appropriate member of the class structure to XtInheritDisplayAccelerators. This default method, as implemented in the MIT distribution, does nothing.



[54] Others reflect internal changes in the window system not related to any window. For example, a mapping Notify event occurs in response to a change in the mappings of keysyms (portable key symbols) to keycodes (actual codes generated by physical keys).

[55] Note, however, that this file includes foreign language keysym sets that are not always available. Only the MISCELLANY, LATIN1 through LATIN4, and GREEK sets are always available.

[56] The contents of <X11/keysymdef.h> are the same on every machine. What is different is the default mapping of keysyms to physical keycodes, which occurs in the server source. The only way to find out the mappings is through documentation (which is usually not available) or experimentation (as described in Chapter 11 of Volume Three, X Window System User's Guide, Second or Third Edition).

[57] In a more complex application, where the Box widget was not the main window but only a subarea containing command buttons, you might instead use the call:

XtInstallAllAccelerators(topLevel, box);

which would make the actions from only the widgets contained in box available from anywhere in the application.