Chapter 7. Translations and Actions

This chapter explains what Motif widget writers need to know about translations and actions. A closely related concept, keyboard traversal, is also detailed in this chapter.

Defining Translations in the Class Record

All Motif widgets descend from Core. Therefore, all Motif widgets define keyboard translations through the following two fields of the Core class record:

  • The tm_table field, which holds the name of a string. This string contains information that maps event to action routine names.

  • The actions field, which holds the name of an array that maps the action routine names in the translation string to the action methods defined by your widget.

For example, the ExmMenuButton widget provides the following string for its tm_table field:

static char defaultTranslations[] =
"<EnterWindow>:                 MenuButtonEnter()\n\
<LeaveWindow>:                     MenuButtonLeave()\n\
<BtnDown>:                    BtnDown()\n\
<BtnUp>:                         BtnUp()\n\
:<Key>osfActivate:      ArmAndActivate()\n\
:<Key>osfCancel:           MenuEscape()\n\
:<Key>osfHelp:                     MenuButtonHelp()\n\
~s ~m ~a <Key>Return:        ArmAndActivate()\n\
~s ~m ~a <Key>space:    ArmAndActivate()";

and the following array for the actions field:

static XtActionsRec Actions[] = {
           {"ArmAndActivate",   ArmAndActivate},
           {"BtnDown",                BtnDown},
           {"BtnUp",                  BtnUp},
           {"MenuButtonEnter",  MenuButtonEnter},
           {"MenuButtonLeave",  MenuButtonLeave},
           {"MenuButtonHelp",   MenuButtonHelp}
};

In addition to the two fields of CoreClassPart, both PrimitiveClassPart and ManagerClassPart provide an additional field named translations. You can set the translations field to one of the following:

  • NULL, meaning that the translations described by the tm_table string of the CoreClassPart are the only translations defined in the class record.

  • XtInheritTranslations, meaning that you are inheriting the translations field of your superclass.

  • A translations string of your own devising. If your widget does do this, the Intrinsics will place the additional translations at the top of the translations table. Therefore, the additional translations take precedence over the translations defined in CoreClassPart. (See the ExmMenuButton demonstration widget for an example of an additional translations string.)

Most standard Motif widgets set the additional translation field either to NULL or to XtInheritTranslations. If you are subclassing the XmPrimitive widget and specify XtInheritTranslations, you will inherit the following actions:

<Unmap>:                         PrimitiveUnmap()\n\
<FocusIn>:                    PrimitiveFocusIn()\n\
<FocusOut>:                   PrimitiveFocusOut()\n\
:<Key>osfActivate:      PrimitiveParentActivate()\n\
:<Key>osfCancel:           PrimitiveParentCancel()\n\
:<Key>osfBeginLine:     PrimitiveTraverseHome()\n\
:<Key>osfUp:               PrimitiveTraverseUp()\n\
:<Key>osfDown:                     PrimitiveTraverseDown()\n\
:<Key>osfLeft:                     PrimitiveTraverseLeft()\n\
:<Key>osfRight:                 PrimitiveTraverseRight()\n\
~s ~m ~a <Key>Return:        PrimitiveParentActivate()\n\
s ~m ~a <Key>Tab:            PrimitivePrevTabGroup()\n\
~m ~a <Key>Tab:                 PrimitiveNextTabGroup()";

(All these actions are documented in the reference page for XmPrimitive; see the Motif Programmer's Reference for details.) By specifying XtInheritTranslations in a subclass of XmPrimitive, your widget will automatically inherit the standard keyboard traversal translations for primitive widgets.

If you are subclassing the XmManager widget and specify XtInheritTranslations, your subclass will inherit the following actions:

<EnterWindow>:                  ManagerEnter()\n\
<LeaveWindow>:                  ManagerLeave()\n\
<FocusOut>:                ManagerFocusOut()\n\
<FocusIn>:                 ManagerFocusIn()\n\
:<Key>osfBeginLine:  ManagerGadgetTraverseHome()\n\
:<Key>osfUp:            ManagerGadgetTraverseUp()\n\
:<Key>osfDown:                  ManagerGadgetTraverseDown()\n\
:<Key>osfLeft:                  ManagerGadgetTraverseLeft()\n\
:<Key>osfRight:              ManagerGadgetTraverseRight()\n\
s ~m ~a <Key>Tab:       ManagerGadgetPrevTabGroup()\n\
~m ~a <Key>Tab:              ManagerGadgetNextTabGroup()";

(All these actions are documented in the reference page for XmManager; see the Motif Programmer's Reference for details.)

Conflicts Between tm_table and translations

The translations defined by the translations field take precedence over the translations defined in the tm_table field. In other words, if the same event appears in both translations and tm_table, then the translation appearing in tm_table will override the translation appearing in translations. This rather simple rule can be the source of subtle problems in widget writing.

Virtual Keysyms

Xlib and the Intrinsics provide a level of mapping between physical keys and keysyms. Motif adds one more level of mapping on top of keysyms; Motif can map keysyms to virtual keysyms. Table 7-1 describes the purpose of some common Motif virtual keysyms.

Table 7-1. Purpose of Common Motif Virtual Keysyms

Virtual KeysymsPurpose
osfActivateActivates a default action
osfAddModeToggles the selection mode between Normal and Add mode
osfBackSpaceDeletes the previous character
osfBeginLineMoves the cursor to the beginning of the line
osfCancelCancels the current operation
osfClearClears the current selection
osfCopyCopies to the clipboard
osfCutCuts to the clipboard
osfDeleteDeletes data
osfDeselectAllDeselects the current selection
osfDownMoves the cursor down
osfEndLineMoves the cursor to the end of the line
osfHelpCalls the function specified by the XmNhelpCallback resource
osfInsertToggles between Replace and Insert mode (if specified without modifiers)
osfLeftMoves the cursor left
osfMenuActivates the Popup Menu
osfMenuBarTraverses to the MenuBar
osfPageDownMoves down one page
osfPageLeftMoves left one page
osfPageRightMoves right one page
osfPageUpMoves up one page
osfPastePastes from the clipboard
osfPrimaryPastePastes the primary selection
osfRestoreRestores a previous setting
osfRightMoves the cursor right
osfSelectEstablishes the current selection
osfSelectAllSelects an entire block of data
osfSwitchDirectionToggles the string layout direction
osfUndoUndoes the most recent action
osfUpMoves the cursor up

We recommend that the translations string of Motif widgets use Motif virtual keysyms instead of traditional keysyms whenever possible. For example, the following well-defined translations string uses five different Motif virtual keysyms:

static char defaultTranslations[] = "\
:<Key>osfActivate:      PrimitiveParentActivate()\n\
:<Key>osfCancel:        PrimitiveParentCancel()\n\
:<Key>osfHelp:          PrimitiveHelp()\n\
:<Key>osfDelete:            MyDeletionMethod()\n\
:<Key>osfInsert:            MyInsertionMethod()\n\

Virtual keysyms let your widget provide consistent behavior across a wide variety of keyboards. (For more information on virtual keysyms, refer to the Motif Style Guide and to the VirtualBindings reference page of the Motif Programmer's Reference.)

The following subsections take a closer look at some commonly used bindings for certain virtual keysyms.

The osfActivate and osfCancel Virtual Keysyms

The PrimitiveParentActivate action is ordinarily bound to the osfActivate virtual keysym. The PrimitiveParentCancel action is ordinarily bound to the osfCancel virtual keysym.

When activated, the PrimitiveParentActivate action routine typically does the following:

  • Examines the value of the XmNdefaultButton resource in the managing widget. This resource holds the widget ID of one of the widgets that it is managing.

  • Invokes the arm_and_activate method of the widget named by XmNdefaultButton.

In other words, the ultimate responsibility for handling the PrimitiveParentActivate action belongs to the widget named by XmNdefaultButton.

The PrimitiveParentCancel action routine works like PrimitiveParentActivate. The only difference is that PrimitiveParentCancel depends on the XmNcancelButton resource rather than XmNdefaultButton.

(See Chapter 4 for more details on the parent_process method.)

The osfHelp Virtual Keysym

You may associate any action routine with the osfHelp virtual keysym. One interesting choice of an action routine is PrimitiveHelp.

Specifying PrimitiveHelp causes Motif to invoke the callback associated with the widget's XmNhelpCallback resource. It is quite possible, however, that your widget does not define such a callback. In that case, PrimitiveHelp looks for a help callback inside the widget that is managing your widget.

For example, the ExmString widget binds the PrimitiveHelp action to the osfHelp virtual keysym. Suppose that an XmForm widget is managing several ExmString widgets. Furthermore, suppose that none of the ExmString widgets define a help callback. In this case, PrimitiveHelp automatically tries to find a help callback inside the XmForm widget. Therefore, the help callback of XmForm can serve as the help callback for all the widgets that it manages. If the XmForm widget does not define a help callback, then Motif looks for a help callback in the widget that manages the XmForm widget. Motif keeps going up the parent chain until it finds a help callback.

The osfMenuBar and osfMenu Virtual Keysyms

You may associate any action with the virtual keysyms osfMenuBar and osfMenu; however, there is a good possibility that Motif will never execute the action you specify.

If your application defines a menu bar, the menu bar will grab any osfMenuBar events before your widget can process it. Similarly, if your application defines any popup menus, the popup menu widget will grab any osfMenu events before your widget can process it.

The osfBeginLine, osfEndLine, osfLeft, osfRight, osfUp, and osfDown Virtual Keysyms

You should be very careful about defining any of the following virtual keysyms inside the tm_table string of the CoreClassPart:

  • osfBeginLine

  • osfEndLine

  • osfLeft

  • osfRight

  • osfUp

  • osfDown

As we mentioned earlier in this chapter, the translations field of the PrimitiveClassPart takes precedence over the tm_table field of the CoreClassPart. The PrimitiveClassPart of XmPrimitive defines actions for all six of the preceding virtual keysyms. Therefore, if your widget inherits these translations (by specifying XtInheritTranslations), then Motif will ignore any translations defined for these virtual keysyms in the tm_table field. Therefore, if you really do want to provide a non-default binding for any of these virtual keysyms, you should set the translations field of the PrimitiveClassPart to either NULL or to the name of a new translations string. Another possibility is to update the translations string as part of your widget's initialize method.

Enter Actions

The X Window System generates an EnterWindow event whenever the pointer crosses into your widget. In order to get the proper traversal behavior, whatever action is bound to EnterWindow needs to call the PrimitiveEnter action of XmPrimitive. This action encapsulates the appropriate Motif response to an EnterWindow event. In short, this action does the following:

  • Gives this widget the keyboard focus if the keyboard focus policy is XmImplicit. (If the keyboard focus policy is XmExplicit, the PrimitiveEnter method does not change the keyboard focus.)

  • Highlights the widget if primitive.highlight_on_enter is set to True.

The simplest way to invoke PrimitiveEnter is to associate it with EnterWindow in the actions array as follows:

static XtActionsRec ActionsList[] = {
      {"Enter",         PrimitiveEnter},
      ...
}

If your widget requires special behavior on an EnterWindow, then your widget's EnterWindow action should envelop PrimitiveEnter. For example, the following action reacts properly:

MyEnterEventMethod(Widget    w,
                   XEvent   *event,
                   String   *params,
                   Cardinal *num_params)
{
 /* special behavior on <EnterWindow> */
   ...

 /* Call the PrimitiveEnter function. */
   XtCallActionProc(w, "PrimitiveEnter", event, params, num_params);
}

Leave Actions

The X Window System generates a LeaveWindow event whenever the pointer passes out of your widget. In order for traversal to work properly, your widget must call the PrimitiveLeave action of XmPrimitive. This action encapsulates the appropriate Motif response to a LeaveWindow event. In short, this action does the following:

  • Removes the keyboard focus from this widget if the keyboard focus policy is XmImplicit. (If the keyboard focus policy is XmExplicit, the PrimitiveLeave action does not change the keyboard focus.)

  • Unhighlights the widget if primitive.highlight_on_enter is set to True.

You can invoke PrimitiveLeave either from within the actions array or by enveloping it inside an action.

Mouse Bindings

The Motif Style Guide defines a model for mouse button operations. By following this model, your widget's behavior will be consistent with other Motif widgets. A brief description of the bindings for a 3-button mouse appears in Table 7-2:

Table 7-2. Motif Mouse Keysyms

Physical KeyVirtual ButtonsPurpose
Button1SelectSelects and activates
Button2TransferTransfers data, including primary paste and drag operations
Button3MenuActivates Popup Menus

For a more detailed description of mouse button bindings, see the Motif Style Guide. Note that, unlike virtuals keysyms, virtual button bindings may not be used in translation tables.

Keyboard Traversal

Most Motif users move a mouse in order to move the cursor from one widget to another. However, the Motif Style Guide insists that Motif applications be usable even when a mouse is not available. For that reason, most Motif applications allow users to traverse between widgets by pressing the tab key or by pressing one of the arrow keys.

If you are writing a Motif widget, then you need do very little in order to implement keyboard traversal. Code in the superclasses XmPrimitive and XmManager handle keyboard traversal for the vast majority of widgets. In order to tap into this code, the widget need only inherit the traversal translations of the appropriate superclass. To do this, simply specify XtInheritTranslations for the translations field of the PrimitiveClassPart or ManagerClassPart.

Widget writers may also wish to override the default values of two superclass resources, XmNtraversalOn and XmNnavigationType.

If the XmNtraversalOn resource is set to True (as it is by default), then traversal is activated for this widget. However, output-only widgets like ExmString should not have keyboard traversal activated. Therefore, widgets like ExmString override the default value of XmNtraversalOn, and set it to False instead.

The XmNnavigationType resource determines whether the widget is a tab group. The XmPrimitive widget establishes a default value of XmNONE for this resource. By contrast, the XmManager widget sets the default to XmTabGroup. Widget writers may want to set different defaults. (See the Motif Programmer's Guide for complete details on the various kinds of tab groups.)

The preceding suggestions tell you everything you really need to know in order to handle keyboard traversal in most widgets. However, a few specialized widgets require special care. For example, in the standard Motif widget set, the XmText widget falls into this "special care" category because it reserves the tab key and the arrow keys for purposes other than widget traversal. If you are writing a widget that falls into this category, you will not be able to inherit the traversal translations of XmPrimitive and XmManager. Instead, your widget will have to provide its own actions.