Chapter 6. Menus and Options

A menu is a widget that allows the user to make a choice among actions or states. When the menu is visible, the user makes a choice by activating a button in the menu, usually by pressing BSelect, KSelect, or KActivate on the button. Some buttons also have mnemonics that allow the user to activate them by pressing the mnemonic keys when the menu is visible. Buttons can also have accelerators, which activate the buttons whether or not the menu is visible.

Motif has four basic kinds of menu:

RowColumn is the widget that Motif uses as a menu. A RowColumn can also be a nonmenu WorkArea. One use for a WorkArea is to contain a set of ToggleButtons constituting a RadioBox or a CheckBox. When the user selects a ToggleButton, its state changes from on to off or from off to on. In a RadioBox, only one ToggleButton at a time can be on; in a CheckBox, more than one ToggleButton can be on.

RowColumn performs special geometry management to align and lay out its children in a variety of ways. An application can use a RowColumn WorkArea to take advantage of the RowColumn geometry management for a set of widgets. For details see Chapter 10, "Managing Geometry."

Menu Components: Buttons, RowColumn, MenuShell

A menu is a three-level hierarchy:

  • Buttons represent the menu selections.

  • A RowColumn widget is the manager that contains the buttons.

  • A MenuShell envelops each PulldownMenu and PopupMenu.

Buttons

The user makes a choice in a menu by activating one of the buttons in the menu. CascadeButtons, PushButtons, and ToggleButtons and their gadget variants are most commonly used in menus.


Note: Motif does not support DrawnButtons or ArrowButtons in menus, though they can appear in a RowColumn WorkArea. To give a menu button a distinctive appearance, use a PushButton with a label type of XmPIXMAP and supply XmNlabelPixmap and XmNlabelInsensitivePixmap resources.

The application learns of the user's choice through the appropriate button callback lists:

  • When the user activates a CascadeButton, the button calls the XmNcascadingCallback callbacks. If the button has an attached PulldownMenu after these callbacks return, the button posts the menu. Otherwise, the button calls the XmNactivateCallback callbacks.

  • When the user activates a PushButton, the button calls the XmNactivateCallback callbacks.

  • When the user activates a ToggleButton, the button calls the XmNvalueChangedCallback callbacks.

Buttons in a menu have translations and actions that arm, disarm, and activate the buttons. These actions also post and unpost menus in the hierarchy at appropriate times. The buttons inherit menu traversal translations and actions from XmLabel. These actions allow the user to move from button to button within a menu and from menu to menu within the menu hierarchy.

RowColumn

The parent of the buttons in a menu is a RowColumn widget. RowColumn interacts with its button children in these ways:

  • In a menu (but not a WorkArea), it ensures that all children are CascadeButtons, PushButtons, ToggleButtons, Labels, or Separators (or their gadget variants). If the XmNisHomogeneous resource is True, it ensures that all children are of the class specified by XmNentryClass.

  • It lays out its children and, if XmNisAligned is True, aligns the labels of children that are XmLabel or XmLabelGadget subclasses.

  • It stores the widget ID of the last menu item selected in the XmNmenuHistory resource.

  • It allows the application to supply a single callback list for all button children. If XmNentryCallback is not NULL, it disables the XmNactivateCallback and XmNvalueChangedCallback callbacks for its button children and arranges for the buttons to call the XmNentryCallback callbacks instead.

  • If XmNradioBehavior is True, it ensures that only one ToggleButton at a time is normally selected. It also changes the default values for XmNindicatorType and XmNvisibleWhenOff for its ToggleButton children to the one-of-many, always-displayed style.

  • It has additional resources for MenuBars and OptionMenus, described in the following sections.

In addition to XmNentryCallback, RowColumn also has XmNmapCallback and XmNunmapCallback callbacks. These callbacks apply only to PopupMenus and PulldownMenus. The XmNmapCallback callbacks are called just before the menu is posted, and the XmNunmapCallback callbacks are called just after the menu is unposted. They are useful for changing the menu to reflect the current state of the application. For example, an XmNmapCallback callback can use XtSetSensitive to make some menu items insensitive if they are not applicable in the current state of the program.

MenuShell

The windows associated with PopupMenus and PulldownMenus are top-level windows. That is, the parent window of such a menu is the root window of the screen, not the window associated with the parent widget. This allows the menu to appear anywhere on the screen without being clipped by the parent widget's window.

The parent widget of each PopupMenu and PulldownMenu RowColumn must be a MenuShell. It is actually the MenuShell's window that is the top-level window. XmMenuShell is a subclass of OverrideShell, so the window manager ignores MenuShell's windows.

A MenuShell is often invisible to the application. The Motif convenience routines for creating PopupMenus and PulldownMenus automatically create MenuShell parents for these menus. When a PulldownMenu is the child of a PopupMenu or another PulldownMenu, the child's MenuShell is actually the child of the parent's MenuShell. The convenience routines for creating PulldownMenus manage these relations automatically.

Motif arranges for the RowColumn's window to coincide with the MenuShell's window. Setting XmNheight, XmNwidth, or XmNborderWidth for either a MenuShell or its child sets that resource to the same value in both the parent and the child. For a child of a MenuShell, setting XmNx or XmNy sets the corresponding resource of the parent but does not change the child's position relative to the parent. XtGetValues for the child's XmNx or XmNy yields the value of the corresponding resource in the parent. The x and y coordinates of the child's upper left outside corner relative to the parent's upper left inside corner are both zero minus the value of XmNborderWidth.

To change any geometry-related resources of a PopupMenu or PulldownMenu, an application should always specify these resources for the RowColumn child, not the MenuShell parent.

If an application needs to create a MenuShell explicitly, it should create the MenuShell as a popup child of its parent (using XtCreatePopupShell or XtVaCreatePopupShell). All Motif convenience routines that create MenuShells do this automatically, and an application rarely needs to create a MenuShell directly.

MenuBar

All children of a MenuBar must be CascadeButtons or CascadeButtonGadgets. The MenuBar attempts to place its button children in a single row. If it does not have enough room, it tries to wrap the remaining children into additional rows.

An application should treat specially the button, if any, that pulls down a help menu. The application should set the MenuBar RowColumn's XmNmenuHelpWidget to the widget ID of this button. The MenuBar attempts to place this button at one of the lower corners of the MenuBar, as specified by the OSF/Motif Style Guide.

In a MenuBar, all buttons typically have associated PulldownMenus. Each PulldownMenu associated with a button in a MenuBar must be a child of the MenuBar. (More precisely, each PulldownMenu's MenuShell must be a child of the MenuBar.) Each button's XmNsubMenuId resource must be set to the widget ID of the associated PulldownMenu. Set XmNsubMenuId to the widget ID of the PulldownMenu RowColumn, not of the PulldownMenu's MenuShell.

The routines XmCreateMenuBar, XmCreateSimpleMenuBar, and XmVaCreateSimpleMenuBar all create MenuBars.

PopupMenu

A PopupMenu is normally invisible. When the user takes some action—usually pressing BMenu or KMenu—in a widget that has a PopupMenu, the menu is posted. The user moves from item to item in the menu by dragging BMenu or, when keyboard traversal is enabled, by keyboard traversal actions. Motif unposts the menu when the user activates an item in the menu system (other than a CascadeButton), presses KCancel, or releases or clicks BMenu outside a menu item.

A PopupMenu RowColumn must have a MenuShell parent. The parent of the MenuShell is the widget with which the PopupMenu is associated. Because the MenuShell is a popup child of its parent, the parent can be any widget (but not a gadget); it does not have to be a subclass of Composite. The Motif convenience routines that create PopupMenus automatically create a MenuShell as the parent of the PopupMenu RowColumn.

The PopupMenu's XmNmenuPost resource specifies the button event that posts the menu. The event can be any button press, possibly with modifiers. To allow the user to post a PopupMenu using the mouse, the application has to take these actions:

  • Provide an event handler (using XtAddEventHandler) for button press events for the widget with which the PopupMenu is associated. The second argument (the client_data argument) to the event handler should be the PopupMenu RowColumn.

  • In the event handler, call XmMenuPosition to locate the PopupMenu at the point where the user pressed the mouse button, or position the menu itself.

  • In the event handler, manage the PopupMenu RowColumn. If the button event matches the event description in the RowColumn's XmNmenuPost resource, Motif makes the PopupMenu visible when the application manages it. Otherwise, Motif unmanages the PopupMenu and does not post it.

The PopupMenu is realized, if necessary, the first time it is posted.

Following is an example:

void ButtonEventHandler(Widget widget, XtPointer popup,
     XEvent *event, Boolean *continue)
 {
   XmMenuPosition((Widget) popup, (XButtonPressedEvent *)
       event);
   XtManageChild((Widget) popup);
 }
...
   Widget       parent, popup;
   popup = XmCreatePopupMenu(parent, "popup", args, n);
   XtAddEventHandler(parent, ButtonPressMask, False,
                     (XtEventHandler) ButtonEventHandler,
                     (XtPointer) popup);
...

Posting a PopupMenu through the keyboard is controlled by the PopupMenu's XmNmenuAccelerator and XmNpopupEnabled resources. XmNmenuAccelerator specifies a key event that may post the menu. XmNpopupEnabled specifies whether or not this event actually posts the menu. It also determines whether or not accelerators and mnemonics in the PopupMenu and its submenus are enabled.

An application can have only one active PopupMenu at a time for a particular widget. If the widget has more than one PopupMenu, the application should set XmNpopupEnabled to True for the active menu and set XmNpopupEnabled to False for all inactive menus. The application must also arrange for its button event handler to manage the proper PopupMenu on a popup button event. One possible implementation is for the event handler to call a function that returns the appropriate PopupMenu, depending on the state of the application.

PulldownMenu

A PulldownMenu is always associated with another RowColumn. It becomes visible when the user activates a CascadeButton in the associated RowColumn. It becomes invisible when the user traverses upward or laterally in the menu hierarchy, activates a button in the hierarchy (other than a CascadeButton in the menu or a descendant), presses KCancel, or clicks or releases a mouse button outside a menu item.

A PulldownMenu must have the following relations with other widgets:

  • It must be the value of the XmNsubMenuId resource of the CascadeButton that is to post the menu.

  • It must have a MenuShell as its parent. The Motif convenience routines that create PulldownMenus create MenuShell parents automatically.

  • The MenuShell must have the proper parent, depending on the kind of RowColumn with which the PulldownMenu is associated. The MenuShell is a popup child of its own parent. Following are the required parents of the MenuShell:

    • If the PulldownMenu is to be pulled down from a MenuBar, the parent must be the MenuBar.

    • If the PulldownMenu is to be pulled down from a PopupMenu or another PulldownMenu, the parent must be that PopupMenu or PulldownMenu. Actually, the parent is the other menu's MenuShell; but the parent parameter to the Motif convenience routines that create PopupMenus must be the other menu itself (the RowColumn), not its MenuShell parent.

    • If the PulldownMenu is to be pulled down from an OptionMenu, the parent must be the parent of the OptionMenu.

OptionMenu

An OptionMenu lets the user choose among a set of usually mutually exclusive options. The OptionMenu is always visible. It consists of a label (a LabelGadget), a selection area (a CascadeButtonGadget), and an associated PulldownMenu. The label of the CascadeButtonGadget displays the currently selected option, one of the items in the PulldownMenu. When the user activates the CascadeButtonGadget, the PulldownMenu becomes visible with the currently selected item directly above the selection area. When the user activates an item in the PulldownMenu, the PulldownMenu is unposted and the item the user chose becomes the currently selected option.

The PulldownMenu normally contains only PushButtons. It must not contain any ToggleButtons, and Motif does not support CascadeButtons.

RowColumn has a number of resources for use specifically with an OptionMenu:

XmNlabelString  


The text of the label. Setting this resource also sets the XmNlabelString of the LabelGadget.

XmNmnemonic  


A keysym that, when pressed along with the MAlt modifier, posts the PulldownMenu. Motif underlines the first character in the label string that matches the mnemonic and that is in a segment whose font list element tag matches XmNmnemonicCharSet. Setting this resource also sets the XmNmnemonic of the LabelGadget.

XmNmnemonicCharSet  


The font list element tag used for underlining the mnemonic. Setting this resource also sets the XmNmnemonicCharSet of the LabelGadget.

XmNsubMenuId  


The widget ID of the PulldownMenu. Setting this resource also sets the XmNsubMenuId of the CascadeButtonGadget.

If the application needs to get or set any of these four resources for the LabelGadget or CascadeButtonGadget, it should always get or set it in the OptionMenu RowColumn, not the gadget itself. To get or set other resources for the gadgets, the application should use XmOptionLabelGadget or XmOptionButtonGadget and then call XtGetValues or XtSetValues on the returned widget ID. A user or application can also specify resource values in resource files by using the names of the gadgets, "OptionLabel" and "OptionButton".

Setting the XmNmenuHistory resource also has a special effect in OptionMenus. Setting XmNmenuHistory to an item in the PulldownMenu makes that item the currently selected option. It updates the label of the CascadeButtonGadget and causes the PulldownMenu to appear, when posted, with the selected item over the CascadeButtonGadget.

XmCreateOptionMenu creates an OptionMenu RowColumn and its LabelGadget and CascadeButtonGadget children. It does not create the associated PulldownMenu.

The following example creates a simple OptionMenu with three options:

  Widget         parent, pulldown, option, pb1, pb2, pb3;
  Arg            args[10];
  Cardinal       n;
...
  n = 0;
  pulldown = XmCreatePulldownMenu(parent, "option_pd",
                                    args, n);
  pb1 = XmCreatePushButtonGadget(pulldown, "option_pb1",
                                    args, n);
  pb2 = XmCreatePushButtonGadget(pulldown, "option_pb2",
                                    args, n);
  pb3 = XmCreatePushButtonGadget(pulldown, "option_pb3",
                                    args, n);
  XtSetArg(args[n], XmNsubMenuId, pulldown);       n++;
  XtSetArg(args[n], XmNmenuHistory, pb2);          n++;
  option = XmCreateOptionMenu(parent, "option_rc", args, n);
...

The following application-class defaults file provides labels and mnemonics for an English-language locale:

*option_pb1.labelString   :   Option 1
*option_pb2.labelString   :   Option 2
*option_pb3.labelString   :   Option 3
*option_rc.labelString    :   Options
*option_rc.mnemonic       :   O

RadioBox and CheckBox

RadioBoxes and CheckBoxes are collections of ToggleButtons. The difference is that in a RadioBox only one ToggleButton at a time can be set; in a CheckBox more than one ToggleButton can be set.

RadioBoxes and CheckBoxes are usually implemented as WorkAreas, though it is possible to implement them as menus. Usually the application intends for the box to remain visible after the user sets a ToggleButton, particularly in a CheckBox. The application can implement a transient RadioBox or CheckBox by placing a WorkArea inside a dialog.

The following RowColumn resources specifically control the behavior of a RadioBox or CheckBox:

XmNradioBehavior  


When True, the RowColumn ensures that at most one ToggleButton is set at a time. Setting this resource to True also causes the ToggleButton resource XmNindicatorType to default to XmONE_OF_MANY and XmNvisibleWhenOff to default to True.

XmNradioAlwaysOne  


When both this resource and XmNradioBehavior are True, RowColumn ensures that one ToggleButton is always set. The user is not allowed to unset a ToggleButton when no other ToggleButton is set.

For a RadioBox implemented as a WorkArea, the default value for XmNisHomogeneous is True, and by default RowColumn allows only ToggleButton and ToggleButtonGadget children.

Note that the application can foil the RowColumn's enforcement of XmNradioBehavior and XmNradioAlwaysOne, even when these resources are True. The application can use XtSetValues to set the state of the ToggleButtons, and it can manage and unmanage ToggleButtons regardless of their state. The behavior of a RadioBox is undefined if the application takes actions that contradict XmNradioBehavior or XmNradioAlwaysOne.

XmCreateRadioBox creates a WorkArea RadioBox and initializes XmNradioBehavior to True.

A CheckBox is most often a collection of ToggleButtons in a WorkArea with XmNradioBehavior set to False. By default, the ToggleButton XmNindicatorType is XmN_OF_MANY and XmNvisibleWhenOff is True.

TearOffMenus

An application can allow the user to "tear off" a PulldownMenu or PopupMenu. When the user tears off a menu, Motif unposts that menu and any posted menu descendants. It gives the menu a TransientShell parent and then maps the parent as a top-level window. The torn-off menu has window-manager decorations, and its title is the label of the CascadeButton that posts the menu in the original menu system.

The user can interact with the torn-off menu just as in the menu hierarchy. When the user activates buttons in a torn-off menu, the actions take effect but the torn-off menu remains posted. When the user takes an action that unposts the torn-off menu, such as pressing KCancel, the menu returns to its original position in the menu hierarchy. If the user reposts the original menu from the menu hierarchy while the torn-off menu is posted, an inactive representation of the torn-off menu remains visible, but the menu itself is unposted and then reposted within the menu hierarchy.

When a menu in a menu system can be torn off, a distinctive tear-off button appears at the beginning of the menu. The user can tear off the menu by activating the tear-off button as with any other button in the menu. The user can also tear off the menu by pressing BTransfer in the tear-off button. The user can then drag the torn-off menu to another position on the screen and fix its position by releasing BTransfer.

Menus cannot be torn off by default. The application must allow the user to tear off a menu by setting the RowColumn resource XmNtearOffModel to XmTEAR_OFF_ENABLED. When the user tears off a menu, the XmNtearOffMenuActivateCallback callbacks are invoked just before the XmNmapCallback callbacks. When the user unposts a torn-off menu, the XmNtearOffMenuDeactivateCallback callbacks are invoked just after the XmNunmapCallback callbacks.