Chapter 13. Menus, Gadgets, and Cascaded Popups

This chapter begins by describing how to use Motif Popup and cascaded menus. Then it describes how menus actually work, and several ways to create menu widgets. One of these ways involves the use of windowless widgets, or gadgets. This chapter also describes how to use more advanced features of the Xt pop up mechanism, including modal cascades, to implement cascading pop up menus and dialog boxes.

In Chapter 3, More Techniques for Using Widgets, we show a simple example that pops up a menu and a dialog box. We used Motif's widget creation functions to create these widgets and we let Motif handle their placement when they were popped up. We popped them up by creating a callback that called XtManageChild(). This chapter continues where Chapter 2 left off by describing how to create other types of menus, and how to create cascaded menus and dialog boxes with Motif.

Motif, however, is atypical since it hides the actual Xt mechanisms that support popup widgets. After describing how Motif handles popups, this chapter continues by exploring these underlying mechanisms. The examples in the second part of the chapter use the Athena widgets, since they expose Xt's underlying mechanisms effectively. Even though Motif hides these details, it is helpful to understand exactly what is happening underneath.

This chapter also discusses cascaded popups--popups that call other popups--and the event management necessary for them to shut out other input elsewhere in the application and system.

Finally, this chapter discusses windowless widgets called gadgets, which were originally designed to reduce X server memory consumption. Later improvements in the implementation of windows in the X server reduce the advantage of gadgets. Furthermore, gadgets greatly increase the network traffic caused by an application. Therefore, they should be avoided. However, Motif 1.1 and 1.2 still include gadgets for backwards compatibility, so for completeness they are covered here. Their most important use is to implement the panes in menu widgets. As an example of a widget that manages gadgets, we will show the R4 Athena SimpleMenu widget and its gadget children.

Also introduced in R4 is the object, which is another kind of windowless widget even simpler than a gadget. Objects are not usually used in menus, so we will reserve discussion of them until Chapter 14, Miscellaneous Toolkit Programming Techniques.

In this chapter, we use the term menu broadly to refer to any user-interface element that lists many options and allows the user to select one or more. A menu might consist of a list of commands, only one of which can be selected at a time, or a list of nonexclusive Boolean settings that can be turned on or off, or a list of exclusive choices (such as the colors or patterns for a paint palette). A menu that invokes commands will start in the same state each time, while the other two types may have different contents in any particular invocation, showing the settings invoked the previous time or all previous times, or a modified list of choices.

Menus are one of the most important user-interface elements in window-based applications. They offer the same feature as push buttons--a way for the user to invoke application functions or set parameters--but in a more organized and more easily accessible fashion when there are more than a few buttons.

Figure 13-1 compares a menu to a box full of buttons.

Figure 13-1. Athena Command widgets in an Athena Box widget, and the same commands as a SimpleMenu

The menu takes up less space because only its title is visible until it is called up.[89] As a result, you can have more menus than you could have permanent button boxes. Commands can be presented in smaller, more closely related groups. The user will spend less time searching for the desired command.

The commands in the menu are also easier to read because they are arranged one per row. The commands in the menu may even be easier to invoke because it is more natural to drag the mouse up and down than from side to side. And last but not least, menus avoid the worst problem with button boxes: when the application is resized, button boxes may place each command widget in a different position, making it more difficult for the user to find commands.[90]

Some of the applications in the core distribution from MIT use button boxes instead of menus because there was no menu widget in the Athena widget set until Release 4. And some of the applications that do use menus have implemented them directly with Xlib.

Menu Styles and Implementation

The conventions for the appearance and user interface of menus (look and feel) in widget sets probably varies more than any other aspect of the user interface.

There are several different styles of menus. As we've pointed out earlier, a button box is itself a style of menu. However, in this chapter we will be focusing on popup menus--menus that are not visible until the user presses a pointer button or a key-button combination.

There are several different styles of popup menu. Probably the most familiar is the pulldown menu popularized by the Apple Macintosh. A pulldown menu has a label permanently visible in the application, usually on a menu bar at the top. When the pointer is clicked on the label, and then dragged downwards, the menu is pulled down like a window shade, and remains displayed as long as the pointer button is depressed. The currently selected item (as indicated by the pointer position within the menu) is highlighted, and is executed when the pointer button is released.

The variation adopted by Motif and OPEN LOOK (possibly to avoid legal entanglements with Apple) is a menu in which the pointer need not be dragged down to display the menu. Instead, it appears below the menu title as soon as the button is depressed in the menu title. The distinction between pulldown and drop-down menus is a subtle one.

In some cases, selecting an item on the menu or moving off the right side of certain menu panes causes a second menu to appear next to the first (usually to the right). This is referred to as a cascading menu. (Another type of cascading popup is a dialog box that pops up another dialog box.)

Finally, there is the pure spring-loaded popup menu used by many of the standard X clients, which displays no menu label, and simply pops up at the pointer position, given the appropriate key or button press. For example, the menus in xterm pop up when you hold the Control key and press the first or second button while the pointer is anywhere in the xterm window. Motif calls this type of menu simply a popup menu (as opposed to pulldown or option menus). We will refer to these menus using the term spring-loaded since the term popup is too generic.

One can also imagine many other possible menu styles. For example, an effective user interface could be constructed using only horizontal menus, emulating the single-line menu popularized by Lotus for its character-based 1-2-3 spreadsheet. Any given button might either execute an action, or pop up a lower-level menu, which would overlay (and thus appear to replace) the first menu.

In this chapter, though, we will focus on the two styles of menu you are most likely to encounter in X applications: the pulldown menu and the pure spring-loaded popup menu.

In code, the difference between spring-loaded and pulldown menus is primarily the method by which the user invokes the menu and where the menu is placed; one menu widget class can usually work in either way. In Motif, both the pulldown and popup menu styles are actually RowColumn widgets inside Shell widgets, but they are popped up in slightly different ways.

All the differences described so far concern user-interface conventions. The Xt specification, however, classifies popups using a different criteria--while the menu is popped up, how is input dispatched to other parts of the application and to other applications? Also, is the menu subject to window management? The three styles are called modeless popups, modal popups, and spring-loaded popups.

Modeless popups are windows that, once popped up, are subject to window manager control, and for all intents and purposes act like regular applications in themselves. A help window that stayed up, and could be moved and resized like a regular window once popped up, is an example of this type of popup. It is referred to as “modeless” because it doesn't put the application into a special mode, in which only input to the popup is allowed.

A modal popup may or may not be visible to the window manager, but it always disables user-event processing by the application, except in the popup itself. A dialog box that requires the user to enter data or click on a button is an example of a modal popup. Input may still be possible to other applications.

As defined by Xt, a spring-loaded popup is invisible to the window manager and disables user input to all windows in all applications, except to the popup itself. The most important thing about spring-loaded popups is that they are invoked with a key or pointer button press, whereas another type of popup might be invoked as a routine part of application processing, or just because the pointer entered a particular window. Note that even though the term spring-loaded has been used in two different contexts above, both actually refer to the same kinds of widgets. The first use referred to a characteristic user-interface style, and the second to a characteristic absence of window management and disabling of input to other applications. Throughout this chapter, though, we use the term “spring-loaded popup” to refer to menus that pop up at the pointer position when a mouse button is pressed, such as that used by xterm, as opposed to popup or pulldown menus.

Using Motif Menus

Chapter 3, More Techniques for Using Widgets, showed how to create and use a Motif pulldown menu. This section demonstrates how to use a Motif popup menu, and then shows how to implement cascaded menus--menus that pop up other menus.

Volume Six, Motif Programming Manual, will describe more thoroughly how to use menus, such as how to add mnemonics and accelerators.

Popup Menus

Using a Motif popup menu is really not much different from using a Motif pulldown menu. The first difference, obviously, is that you use XmCreatePopupMenu() instead of XmCreatePulldownMenu(). The most significant difference, however, is that you are responsible for placing the popup. As you may recall, a pulldown menu is automatically positioned just below the button whose callback invoked the menu. A popup menu, on the other hand, is usually invoked by a button press in the custom window or main window of the application. The menu usually pops up at the pointer position.

A popup menu can be popped up in response to a button press in a particular widget only if the button press does not already have another meaning in that widget. For example, a Label widget can be used as the widget that will detect a button press and pop up a popup menu, because it doesn't otherwise use button presses for anything. Although a PushButton button widget could pop up a popup menu in its XmNactivateCallback, this would confuse the user because this callback is normally associated with executing a command.

Widgets that don't already use button presses usually don't have a callback triggered by them. Therefore, actions or event handlers are often used to place and to pop up menus. Example 13-1 shows the code to create, place, and pop up a popup menu using an event handler.

Example 13-1. Creating, placing, and popping up a popup menu

#include <Xm/Xm.h>

/*
 * Public include files for widgets used in this file.
 */
#include <Xm/RowColumn.h>

#include <Xm/PushB.h>

#include <Xm/Label.h>

/*
 * menu pane button callback function
 */
/*ARGSUSED*/
void PaneChosen(w, client_data, call_data)
Widget w;
XtPointer client_data;   /* cast to pane_number */
XtPointer call_data;
{
    int pane_number = (int) client_data;
    printf("Pane %d chosen., pane_number);
}
static void
PostMenu (w, client_data, event)
Widget         w;
XtPointer      client_data;
XEvent        *event;
{
    Widget popup = (Widget) client_data;
    XmMenuPosition(popup, event);
    XtManageChild (popup);
}
main(argc, argv)
int argc;
char **argv;
{
      .
      .
      .
    Widget menu, label, menupane[10];
      .
      .
      .
    label = XtVaCreateManagedWidget(
        "label",              /* widget name */
        xmLabelWidgetClass,   /* widget class */
        box,                  /* parent widget */
        NULL                  /* terminate argument list */
        );
    menu = XmCreatePopupMenu(label, "menu", NULL, 0);
    XtAddEventHandler(label, ButtonPressMask, False, PostMenu, menu);
    for (i = 0; i < 10; i++) {
        sprintf(buf, "menupane%d", i);
            menupane[i] = XtVaCreateManagedWidget(buf, /* widget name */
                    xmPushButtonWidgetClass, menu, NULL);
        XtAddCallback(menupane[i], XmNactivateCallback,
                PaneChosen, i);
    }
    XtRealizeWidget(topLevel);
    XtAppMainLoop(app_context);
}

The standard way to place a popup menu is to call XmMenuPosition() just before calling XtManageChild() in the function that pops up the widget. This places the popup with the center of its top edge at the pointer position, but forces the entire menu on the screen (if part would otherwise extend off the screen).

Note that by default Motif will only pop up a popup menu in response to mouse button 3. This can be changed by setting the XmNmenuPost resource of the popup menu.

Cascaded Menus

Cascaded menus in Motif are always pulldown menus, regardless of whether the parent menu is popup or pulldown. The first step in implementing a submenu is to create it with XmCreatePulldownMenu(). Next, you create a CascadeButtonWidget as a child of the main menu, and set its XmNsubMenuId resource to the ID of the submenu. Then populate the submenu with gadget entries and register their callback functions. Example 13-2 shows the code needed to add a submenu to Example 13-1.

Example 13-2. Adding a cascading submenu

#include <Xm/CascadeB.h>

main(argc, argv)
int argc;
char **argv;
{
       .
       .
       .
     Widget menu, cascade, submenu, subentry[10];
       .
       .
     /* create main menu */
       .
       .
     submenu = XmCreatePulldownMenu(menu, "submenu", NULL, 0);
     cascade = XtVaCreateManagedWidget("cascade",
              xmCascadeButtonWidgetClass, menu,
              XmNsubMenuId, submenu,
              NULL);
     for (i = 0; i < 10; i++) {
         sprintf(buf, "subentry%d", i);
         subentry[i] = XtVaCreateManagedWidget(buf, /* widget name */
                 xmPushButtonGadgetClass, submenu, NULL);
         XtAddCallback(subentry[i], XmNactivateCallback,
                 PaneChosen, i);
     }
       .
       .
}

Note that the submenu is created before the CascadeButtonWidget that will invoke the submenu. This makes it possible to set XmNsubMenuId while creating the CascadeButtonWidget.

Basic Xt Popup Support

As mentioned above, Motif hides Xt's mechanisms that support popups. This section exposes these mechanisms by describing how to create and use a menu using the Athena widgets in two different menu styles: spring-loaded and pulldown. The purpose of this exercise is to expose some of the issues involved in event management of popups. Seeing how to do the event management explicitly should help you to use popups more effectively.

The challenge of creating a popup with Box and Command buttons is to make it pop up and down at the right times, and to control its event handling to fit the menu style. We will also experiment with creating a cascaded menu, in which one menu pane in a main menu invokes a submenu.

Finally, this section describes how to create a menu using the R4 Athena SimpleMenu widget and its gadget children.

A Spring-loaded Menu: Pointer Grabbing

A spring-loaded menu should pop up when a button press occurs in a particular widget; usually the application's main window. The menu should stay visible as long as the user holds down that button, and disappear when the button is released. If the button is released in a menu pane, the function registered for that pane should be invoked. If the button is released outside the menu, no function should be invoked but the menu should still be popped down.

The only tricky part of implementing a spring-loaded menu is getting the menu to pop down when the button is released outside the menu. Since this occurs outside the menu and possibly outside the application, the X server will not send the button release event to the application unless a grab is in effect. Normally, user events are sent to the window that contains the pointer. But after an application makes a grab, the X server sends all events of particular types to the window that made the grab, even if the pointer is no longer in the window.

The X server defines several types of grab: keyboard grabs, pointer grabs, and server grabs. Keyboard and pointer grabs control only input from the indicated device, while server grabs make the server act on requests from one application exclusively. (Server grabs are mainly used by window managers.) Pointer grabs are used for controlling events in popups when a pointer button pops up the popup, and keyboard grabs are used when a key press pops up the popup. We will discuss pointer grabs since keyboard grabs are analogous (and keyboard-triggered popups are less common).

There are two types of pointer grabs: passive grabs and active grabs. An active grab is invoked directly with the Xt function XtGrabPointer(). This function tells the server that you want the grab to begin right away and to continue until specifically released with XtUngrabPointer(). Active grabs are not normally used for popups.[91]

A passive grab tells the server that you want a grab to begin when a certain key or button combination is pressed in a certain window (the combination that is to pop up the popup). The grab continues until the button in the combination is released. This is perfect for menus because we need the grab only until the button is released. (Also, as you'll see in the section on pulldown menus, you can register several passive grabs for the same key-button combination as long as each grab is initiated by a press in a different window. This technique lets you have as many pulldown menus as you want. Since spring-loaded popups are generally invoked by a press in the same window--the main window of the application--you will need to use a different key-button combination for each different menu.)

Passive grabs of a key or button and active grabs of the pointer or the keyboard we will call global grabs, since they affect not only this application but prevent distribution of the grabbed events to other applications running on the same server. This terminology is to distinguish the global grab from the effects of Xt's local grab mode, which simulates a global grab but requires no call to the server and affects only the distribution of events within the application. The Xt grab mode cannot commandeer events that occur outside the application like a global grab can. When an Xt grab is in effect, Xt redirects user events to the popups even if they occur somewhere else in the application. (Non-user events continue to be dispatched to widgets so that they can redraw themselves).

The Xt grab mode can be either exclusive or nonexclusive. Exclusive and nonexclusive Xt grabs differ only when a popup has popped up another popup--a so-called cascaded popup. An exclusive Xt grab redirects all user events that occur within the application to the latest popup in the cascade. A nonexclusive Xt grab redirects events to whichever popup the pointer is in, or the latest popup if the pointer is outside all the popups (but still in the application).

Here are examples of the two kinds of Xt grab modes. Consider an application that pops up a dialog box to get a filename from the user. The application wants to read the file. If the file can't be opened, the application pops up another dialog telling this to the user. This error popup takes no input, so input is still desired in the filename entry popup. This situation calls for a nonexclusive grab. By contrast, consider an application that uses the same filename entry popup to save a file. If the file exists, it would pop up a dialog that would ask whether the existing file should be overwritten. This popup must be answered before a new filename is chosen. This situation would call for an exclusive grab. In brief, an exclusive grab constrains input to the the latest widget in the cascade, while a nonexclusive grab allows input to any widget in the cascade. We'll talk more about the Xt grab mode in Section 13.3.3, when we talk about popup cascades.

Xt provides three ways of popping widgets up and down:

  • There are three built-in callback functions: XtCallbackNone(), XtCallbackExclusive(), and XtCallbackNonexclusive(). Each of these functions pops up a widget with a different type of Xt grab mode, as indicated by its name. XtCallbackNone() makes no grab at all. XtCallbackExclusive() makes an exclusive Xt grab, while XtCallbackNonexclusive() makes a nonexclusive grab.

    XtCallbackPopdown() is the corresponding built-in callback function to pop down a widget.

  • There are two built-in actions, XtMenuPopup() and XtMenuPopdown(), that pop up or pop down a widget. You can use these actions in translation tables in the app-defaults file or application code. XtMenuPopup() always asserts an exclusive Xt grab and a passive global grab if the pointer or keyboard invoked it.

  • There are three functions, XtPopup(), XtPopupSpringLoaded(), and XtPopdown(), that you can call directly in your application code to pop up or pop down a widget. XtPopup() has a grab_kind argument that lets you specify whether to assert an exclusive or nonexclusive grab mode, or no grab. XtPopupSpringLoaded() always asserts an exclusive Xt grab and makes an global pointer grab so that a button release outside a menu can be used to trigger the popping down of the menu.

Each of these ways is appropriate for different situations, and they are often used in combination. Each is described in the sections below.

Passive global grabs can be invoked directly using the Xt functions XtGrabButton() and XtGrabKey(). However, Xt takes care of making the appropriate passive global grab if you use XtPopupSpringLoaded() or the XtMenuPopup() action to pop up your shell widget. As we will see, the other ways to pop up a widget do not make any global grab, which makes them inappropriate for popping up main menus.[92]However, they are still useful for some types of dialog boxes and for cascading submenus, if the main menu has used XtMenuPopup() to assert an exclusive grab. We'll return to this subject in Section 13.3.3, "Cascaded Menus."

A popup menu globally grabs the pointer to force the user to make a menu choice before leaving the menu, or to pop down the menu if no choice is made. However, this global grab is necessary for another reason as well. The X server automatically grabs the pointer beginning at a button press and ending at the release of the same button. Since the initial button press pops up the menu, the next button release would also arrive at the same widget--the application main window or the menu title--even if the pointer were already in the menu. Furthermore, the application main window would get all EnterNotify and LeaveNotify events, so the Command widgets in the menu wouldn't get any of them. When the menu is popped up with XtMenuPopup() action, the passive global grab it makes cancels the automatic global grab. (Again, pointer grabs redirect only the events caused directly by the pointer; ButtonPress, ButtonRelease, EnterNotify, LeaveNotify, and MotionNotify. All other events (most notably Expose events) occur and are delivered normally.)

With that background, let's take a look at an application that provides a spring-loaded menu. The xmenu1 application's permanent appearance is a variation of xbox; it displays a large Label widget that we are using to simulate an application's main window and a Command widget for quitting. Pressing any button in the Label widget calls up the menu, which operates as described at the beginning of this section. xmenu1 is shown in Figure 13-2. As usual, we suggest you compile and run this example now.

Figure 13-2. xmenu1: application with spring-loaded popup menu

The relevant code in xmenu1 consists of an action routine to place the popup, code to add the action, a callback routine to handle when a menu item has been chosen, and code to create the Box populated with Command widgets that will act as the menu. Example 13-3 shows the complete code.

Example 13-3. xmenu1: complete code

/*
 *  xmenu1.c - simple spring-loaded menu
 */
#include <stdio.h>

/*
 * Standard Toolkit include files:
 */
#include <X11/Intrinsic.h>

#include <X11/StringDefs.h>

#include <X11/Shell.h>

/*
 * Public include files for widgets used in this file.
 */
#include <X11/Xaw/Command.h>

#include <X11/Xaw/Box.h>

#include <X11/Xaw/Label.h>

/*
 * The popup shell ID is global because both dialog and pshell
 * are needed in the dialogDone callback, and both can't be
 * passed in without creating a structure.
 */
Widget pshell;
/*ARGSUSED*/
void PlaceMenu(w, event)
Widget w;
XButtonEvent *event;
{
    /* should make sure coordinates allow menu to fit on screen */
    /* move submenu shell to slightly left and above button
     * press position */
    XtVaSetValues(pshell,
            XtNx, event->x_root - 10,
            XtNy, event->y_root - 10,
            NULL);
}
/*
 * quit button callback function
 */
/*ARGSUSED*/
void Quit(w, client_data, call_data)
Widget w;
XtPointer client_data, call_data;
{
    exit(0);
}
/*
 * menu pane button callback function
 */
/*ARGSUSED*/
void PaneChosen(w, client_data, call_data)
Widget w;
XtPointer client_data;   /* cast to pane_number */
XtPointer call_data;
{
    int pane_number = (int) client_data;
    printf("Pane %d chosen., pane_number);
    XtPopdown(pshell);
}
main(argc, argv)
int argc;
char **argv;
{
    XtAppContext app_context;
    Widget topLevel, box, quit, label, menulabel, menubox,
            menupane[10];
    int i;
    String buf[50];
    static XtActionsRec trial_actions[] = {
        {"placeMenu", PlaceMenu},
    };
	XtSetLanguageProc(NULL, (XtLanguageProc)NULL, NULL);
    topLevel = XtVaAppInitialize(
        &app_context,       /* Application context */
        "XMenu1",           /* application class name */
        NULL, 0,            /* command line option list */
        &argc, argv,        /* command line args */
        NULL,               /* for missing app-defaults file */
        NULL);              /* terminate varargs list */
    box = XtCreateManagedWidget(
        "box",              /* widget name */
        boxWidgetClass,     /* widget class */
        topLevel,           /* parent widget */
        NULL,               /* argument list */
        0                   /* arglist size */
        );
    label = XtCreateManagedWidget(
        "label",            /* widget name */
        labelWidgetClass,   /* widget class */
        box,                /* parent widget */
        NULL,               /* argument list */
        0                   /* arglist size */
        );
    quit = XtCreateManagedWidget(
        "quit",             /* widget name */
        commandWidgetClass, /* widget class */
        box,                /* parent widget */
        NULL,               /* argument list */
        0                   /* arglist size */
        );
    pshell = XtCreatePopupShell(
        "pshell",
        transientShellWidgetClass,
        topLevel,
        NULL,
        0
        );
    menubox = XtCreateManagedWidget(
        "menubox",         /* widget name */
        boxWidgetClass,    /* widget class */
        pshell,            /* parent widget */
        NULL,              /* argument list */
        0                  /* arglist size */
        );
    menulabel = XtCreateManagedWidget(
        "menulabel",       /* widget name */
        labelWidgetClass,  /* widget class */
        menubox,           /* parent widget */
        NULL,              /* argument list */
        0                  /* arglist size */
        );
    for (i = 0; i < 10; i++) {
        sprintf(buf, "menupane%d", i);
        menupane[i] = XtCreateManagedWidget(buf, /* widget name */
                commandWidgetClass, menubox, NULL, 0);
        XtAddCallback(menupane[i], XtNcallback, PaneChosen, i);
    }
    XtAppAddActions(app_context, trial_actions,
            XtNumber(trial_actions));
    XtAddCallback(quit, XtNcallback, Quit, NULL);
    XtRealizeWidget(topLevel);
    XtAppMainLoop(app_context);
}


The PlacePopup action just places the popup slightly to the left and above the position where the pointer button that popped it up was clicked, using the coordinates reported in the button event. The offset of ten pixels from the pointer position simply helps to make sure that the pointer is inside the menu.[93]Remember that the window created by a popup shell widget is a child of the root window and therefore is placed relative to the root window. The ButtonPress event pointer coordinates relative to the root window are used.

The PaneChosen callback function is a stub function used in this example as the notify callback for all the menu panes. In this example, it simply prints the name of the chosen pane to stdout and then pops down the menu using XtPopdown(). In a real application, a different callback function would probably be registered for each pane.

Instead of calling XtPopdown() in each of these separate callback functions, you could write a single additional callback function that calls XtPopdown(), and then add it to the callback list for the Command widget that makes up each menu pane.

As usual, the popup is created by first creating a popup shell, then a Box widget as its child, and then a series of Label and Command widgets as children of Box. The popup shell and the box are invisible. As with all menus, what you actually see is the array of children.

Note that this program does not include any code that would pop up the menu. We've done that from the app-defaults file shown in Example 13-4. The translations we have defined for the Label widget invoke Xt's built-in XtMenuPopup() action.

Example 13-4. XMenu1: the app-defaults file

!
! Appearance Resources
!
*quit.label:    Quit
*label.label:    This is a Pretend Main Window; Press in here.
*menulabel.label:    MAIN MENU
!
! make all entries in menu same width
! (needs adjusting for longest entry)
!
*menulabel.width:    135
*menubox.Command.width:  135
!
! Pane Strings
!
*menupane0.label:  View Next
*menupane1.label:  View Previous
*menupane2.label:  Delete
*menupane3.label:  Move
*menupane4.label:  Copy
*menupane5.label:  Unmark
*menupane6.label:  View In New
*menupane7.label:  Reply
*menupane8.label:  Forward
*menupane9.label:  Use As Comp
!
! make Box leave no space around Command widgets in menu
!
*pshell.Box.hSpace: 0
*pshell.Box.vSpace: 0
!
! Functional Resources
!
*menubox.Command.translations:\
   <EnterWindow>:     highlight()              \n\
   <LeaveWindow>:     reset()                  \n\
   <BtnUp>:          set() notify() unset()
*label.translations:\
    <BtnDown>: placeMenu() XtMenuPopup(pshell)
*pshell.translations:\
    <BtnUp>: XtMenuPopdown(pshell)


There are a number of settings designed to give the Box widget a characteristic menu appearance: all of the Command widgets are forced to have the same size (rather than the size of their label), and the Box widget is forced to leave no space between the command widgets.

However, it is the new translations that are the critical part of this app-defaults file. The Label widget must be given translations so that a button press will pop up the widget. The supplied translation maps a button press into a call to the application action placeMenu and then to Xt's predefined XtMenuPopup() action. The argument to XtMenuPopup() in the translation is the instance name of the popup shell. Xt converts this string name into the widget ID of the popup shell before it can pop up the widget.

We are replacing rather than overriding this widget's translations because it is a Label widget and has no default translations.

The menu panes are Command widgets, but we need to adjust their event response so that they will be triggered on a button release with no corresponding press (since the press that popped up the menu occurred in the application main window). We are replacing their translations to get rid of the translation for ButtonPress (which would still be present if we used the #augment directive, and we would have to create an action that did nothing in order to replace it with #override). The translation for ButtonRelease (abbreviated BtnUp in the translation table) calls all the actions that usually occur in Command widgets with both press and release.

Perhaps least obvious is the translation we have added to pop down the menu when the pointer button is released outside the menu. As mentioned earlier, Xt makes a passive global pointer grab on the popup shell (pshell) in the XtMenuPopup() action. When the pointer is inside the menu, the Command widgets intercept these grabbed events, because they are descendants of pshell and they have a translation for ButtonRelease events. This invokes the actions in the selected Command widget. But when the pointer is outside the menu, the grabbed events are sent directly to the widget that was specified in the grab call, namely pshell. Therefore, the translation to pop down the menu on button release must be added to pshell. (Again, this translation table is simply replaced because the popup shell normally has no translations.)

A Pulldown Menu

What are the desired characteristics of a pulldown menu? There is a Command widget or the like permanently visible in the application, with a label indicating some common characteristic of the items in the menu. When a button is pressed in this widget, the menu should pop up on or just below the button. Dragging the pointer down through the menu with the button still held should highlight the entry that is pointed to. Releasing the button in an entry should invoke or set that entry and pop down the menu. Moving out of the menu should not change this behavior, except that if the button is released anywhere outside of a menu pane, the menu should pop down without executing any entry.

If you compile and run xmenu2 you can try out this style of menu. The appearance of this application is shown in Figure 13-3.

Figure 13-3. xmenu2: a pulldown menu

Invoking a menu as a pulldown is a simple enhancement of the spring-loaded invocation method just shown. We can do everything exactly the same as in the spring-loaded example, except that a pulldown menu should appear just below the pressme widget, not at the position of the pointer. Therefore, all we need to change is the placement code. However, since the coordinates in the event are not necessary for placing the popups, we can use a callback function instead of an action to place the popup. (In general, it is better to use an existing callback than to add an action to do the same thing.)

Popup shell widgets have XtNpopupCallback and XtNpopdownCallback callback resources; the functions on these callback lists are called whenever the popup is popped up or down using any of the Xt mechanisms.

In the last example we created an action called PlaceMenu, that moves the popup shell before it was actually popped up. We included it in a translation along with the standard action XtMenuPopup(), which was actually used to pop up the widget. xmenu2 also uses the standard action XtMenuPopup() to pop up the widget, but it uses the XtNpopupCallback resource to provide the code to place the widget. Using the callback saves having to reference the placement action in the translation table. This is preferable, since the placement code should almost always be hardcoded rather than user-configurable. Another advantage of the popup and popdown callbacks is that you may arrange for a popup to be popped up or down in more than one way, and it may be convenient to have certain code called automatically in all cases.

(You can also use the XtNpopupCallback resource to specify a callback function to create a popup widget the first time it is popped up, instead of at application startup. The one problem is that the functions on the callback list are invoked every time the widget is popped up. To make sure that your function callback creates the popup only once (the first time), the callback function should remove itself from the callback list by calling XtRemoveCallback().)

There is not enough difference between xmenu1 and xmenu2 to merit showing the complete code. All we have done is changed the PlaceMenu function from an action into a callback and changed its placement logic to place the popup relative to the invoking Command widget. We have then modified the app-defaults file accordingly. Example 13-5 shows the PlaceMenu routine (now a callback, not an action) and the code to register it as a callback.

Example 13-5. xmenu2: code to place pulldown menu

Widget pressme; /* button that invokes menu */
Widget pshell; /* menu shell */
/*ARGSUSED*/
void PlaceMenu(w, client_data, call_data)
Widget w;
XtPointer client_data;
XtPointer call_data;
{
    Position x, y;
    Dimension height;
    /*
     * translate coordinates in invoking Command widget
     * into coordinates from root window origin.
     */
    XtTranslateCoords(pressme,   /* Widget */
            (Position) 0,        /* x */
            (Position) 0,        /* y */
            &x, &y);             /* coords on root window */
    /* get height of pressme so that menu is positioned below */
    XtVaGetValues(pressme,
            XtNheight, &height,
            NULL);
    /* move popup shell one pixel above and left of this position
     * (it's not visible yet) */
    XtVaSetValues(pshell,
            XtNx, x - 1,
            XtNy, y + height,
            NULL);
}
main(argc, argv)
int argc;
char **argv;
{
      .
      .
      .
    XtAddCallback(pshell, XtNpopupCallback, PlaceMenu, NULL);
      .
      .
      .
}


Xt calls the functions on the XtNpopupCallback list before it pops up the widget. This means that PlaceMenu is called before the XtMenuPopup() action, placing the widget before it is popped up.

Note that the XtTranslateCoords() routine determines the root window coordinates at the origin of the pressme widget. Because of the reparenting done by most window managers, this information cannot be obtained by using XtGetValues() to read the XtNx and XtNy resources.[94]

Example 13-6 shows the translation portion of the app-defaults file.

Example 13-6. XMenu2: translation portion of the app-defaults file

!
! Translation resources
!
*pressme.translations:\
   <EnterWindow>:     highlight()              \n\
   <LeaveWindow>:     reset()                  \n\
   <BtnDown>:         set() XtMenuPopup(pshell) reset()
!
*pshell.translations:\
   <BtnUp>:           XtMenuPopdown(pshell)
!
*menubox.Command.translations:\
   <EnterWindow>:     set()                    \n\
   <LeaveWindow>:     unset()                  \n\
   <BtnUp>:           notify() unset()


These translations are different from those for xmenu1 only in that pressme is a Command widget, which already has its own translation table, rather than a widget without existing translations such as Label (which we used as a fake main window). We have modified the translations of pressme to be suitable for this use. Note that the translation no longer calls PlaceMenu as an action because it is now a callback.

The translations for the menu pane Command widgets are also somewhat different from xmenu1, but only for cosmetic reasons. This iteration of the xmenu example uses the set and unset actions instead of highlight and reset or unhighlight to make the Command widgets highlight their entire box instead of just an area near the border. (Although this modification makes the menu look more like a typical menu, it also seems to make it slower.)

To create several menus you simply need to replicate the code shown here, changing the variable names for each menu. The passive global grabs invoked by Xt for each menu do not interfere with each other even if they specify the same key/button combination, because they specify different windows in which the key/button combination will begin the grab.

It is sometimes useful to be able to get a list of children in a menu, especially if you add and subtract menu entries. You can do this by querying the XtNchildren and XtNnumChildren resources of the parent. The value of XtNchildren is a list of the widget ID's of the children. This technique allows you to eliminate maintaining global variables for every pane of every menu.

Cascaded Menus

A cascaded menu is a menu in which one or more panes do not invoke functions but instead bring up additional menus.

The techniques used to bring up cascaded menus can also be used to have dialog boxes bring up other dialog boxes. However, cascaded menus are more challenging because they rely on the passive global pointer grab to receive the ButtonRelease event that occurs outside the menu and application.

You can implement a cascaded menu the same way for both spring-loaded and pulldown menus simply by adding to the code we've already written to implement a single menu. We'll show you xmenu5, the spring-loaded version, since it is slightly shorter. (xmenu4 is the equivalent pulldown version.) Both are included in the example source code. In this example, only one menu pane will be used to invoke a submenu. However, this technique can be generalized to have additional panes bring up additional submenus.

First, let's describe exactly how we expect the cascaded menu to work. Figure 13-4 shows both menus popped up. (Compile the program and try it.)

Figure 13-4. xmenu5: cascaded spring-loaded menus

The main menu works as described above. However, one of the panes--the one that brings up the submenu--has an arrow pointing to the right after its label. This pane does not highlight itself when the pointer moves inside (telling the user that this pane is different). Instead, when the user moves the pointer out through the right edge of the pane, the submenu pops up. The submenu operates just like the main menu. When the button is released inside either menu, the callback function associated with the chosen pane will be invoked. When the button is released outside of either menu, both menus pop down. If the pointer is moved back out of the submenu into the main menu, only the submenu pops down.

To create the submenu, we create a new popup shell, Box widget, and a set of Command widgets, and add callbacks for each function the submenu panes will invoke (in this example, one common callback). Then we write three actions: PlaceMenu (which you have already seen), CheckRightAndPopupSubmenu (which places and pops up the submenu if the pointer leaves the main menu pane through its right side), and PopdownSubmenu (which pops down the submenu if the pointer leaves the submenu). These actions are shown in Example 13-7.

Example 13-7. xmenu5: actions that place, pop up, and pop down main menus and submenus

/*ARGSUSED*/
void PlaceMenu(w, event, params, num_params)
Widget w;
XEvent *event;
String *params;
Cardinal *num_params;
{
    XButtonEvent *bevent = (XButtonEvent *) event;
    /* should make sure coordinates allow menu to fit on screen */
    /* move submenu shell to slightly left and above button
     * press position */
    XtVaSetValues(pshell,
            XtNx, bevent->x_root - 10,
            XtNy, bevent->y_root - 10,
            NULL);
}
/*ARGSUSED*/
void CheckRightAndPopupSubmenu(w, event, params, num_params)
Widget w;
XEvent *event;
String *params;
Cardinal *num_params;
{
    XLeaveWindowEvent *leave_event = (XLeaveWindowEvent *) event;
    Dimension height, width;
    XtVaGetValues(w,
        XtNheight, &height,
        XtNwidth, &width,
        NULL);
    if ((leave_event->x > width) && (leave_event->y > 0)
            && (leave_event->y < height)) {
        /* move submenu shell to start just right of pane,
         * using an arbitrary offset to place pointer in
         * first item. */
        XtVaSetValues(subshell,
                XtNx, leave_event->x_root,
                XtNy, leave_event->y_root - 12,
                NULL);
        XtPopup(subshell, XtGrabNonexclusive);
    }
}
/*ARGSUSED*/
void PopdownSubmenu(w, event, params, num_params)
Widget w;
XEvent *event;
String *params;
Cardinal *num_params;
{
    XtPopdown(subshell);
}


As usual, the app-defaults file specifies which events trigger these actions. We'll show this file in a moment, but for now you need to know just that CheckRightAndPopupSubmenu and PopdownSubmenu are triggered by LeaveNotify events. We want the submenu to pop up and down only when the pointer leaves through certain parts of certain sides of the widget--the entire right side of a pane for popping it up, and the part of the submenu touching the main menu on the left side for popping it down. These two actions are called in response to all LeaveNotify events, and they check if the pointer left through the correct parts before popping up and down the submenu.

As you may recall, we mentioned earlier that no matter what the arguments, XtPopup() and all other Xt facilities for grabbing, except XtMenuPopup() and XtPopupSpringLoaded(), make no passive global grab, and therefore can't be used for spring-loaded or pulldown main menus. It turns out that for submenus the opposite is true--XtPopup() and XtCallback* work fine, but XtMenuPopup() is inappropriate because no new passive grab is needed. The original grab directs events normally to all widgets in the application, including the submenu, and directs all events that occur outside the application to pshell.

The CheckRightAndPopupSubmenu action calls XtPopup() with a grab mode of XtGrabNonexclusive. This grab mode controls Xt's event dispatching within the application--it has nothing to do with the passive global grab that Xt makes from the XtMenuPopup() action. XtGrabNonexclusive means that widgets in the cascade but outside of the submenu will continue to get events normally. The grab mode specified in the call to XtPopup(), or specified by the standard popup callback function selected (XtCallbackExclusive() or XtCallbackNonexclusive()) merely control the event dispatching within the application.

As an exercise, you may want to modify the example so that CheckRightAndPopupSubmenu calls XtPopup() with a grab mode of XtGrabExclusive, and see how the menus work as a result. The XtGrabExclusive mode means that only the most recent popup popped up will get events, while XtGrabNonexclusive means that all popups in a popped-up cascade will get events. In this case, when the submenu is popped up, it alone will get pointer events if you use grab mode XtGrabExclusive, while both it and the main menu will get pointer events if you use grab mode XtGrabNonexclusive. Because of the logic that pops down the submenu when the pointer leaves it through the portion adjoining the main menu, you can see this difference only if you move out through another part of the submenu and then around into the main menu again. (In the example code distribution, xmenu5.c uses XtGrabNonexclusive and xmenu4.c uses XtGrabExclusive, so that you can compare the results of these two flags.)

The user-interface conventions for a particular widget set usually specify which kinds of popups should have exclusive grabs and which nonexclusive. Note that the effect of these two grab modes is the same unless there is more than one popup widget in a cascade visible.

CheckRightAndPopupSubmenu places the submenu itself (instead of using a separate action) because the menu should be placed only when it is first popped up. If the placement code were a separate action, it would be called every time a LeaveWindow event arrived, even if not through the correct border of the widget. (Remember that this code is in an action rather than a callback because it uses the contents of the event.)

The translation portion of the app-defaults file for xmenu5 is shown in Example 13-8.

Example 13-8. XMenu5: translation portion of app-defaults file

!
! Appearance resources
!
*menupane5.label:  Copy To ==>
  .
  .   (other appearance resources not shown)
  .
!
! Translation resources
!
!  popping down both menus
*pshell.translations:\
    <BtnUp>: XtMenuPopdown(subshell) XtMenuPopdown(pshell)
!
!  popping up main menu
*label.translations:\
    <BtnDown>: placeMenu() XtMenuPopup(pshell)
!
!  popping down submenu
*menubox.menupane5.translations:\
   <LeaveWindow>:     checkRightAndPopupSubmenu()
!
! Main Menu translations
*menubox.Command.translations:\
   <EnterWindow>:     highlight()              \n\
   <LeaveWindow>:     reset()                  \n\
   <BtnUp>:           set() notify() unset()
!
! Sub Menu translations
*subbox.translations:\
   <LeaveWindow>:     popdownSubmenu(subbox)
*subbox.Command.translations:\
   <EnterWindow>:     highlight()              \n\
   <LeaveWindow>:     reset()                  \n\
   <BtnUp>:           set() notify() unset()

The first three translation tables handle popping up the main menu and making the menu Command widgets work as expected. We've seen these in previous examples.

The translation table for pshell pops down one or both menus; no error or warning is caused if only the main menu is up. This translation table works because the button release is sent to pshell if it occurs outside a menu regardless of whether just the main menu or both menus are up.

In this case, menu pane 5 is the pane that will pop up the submenu. The label for this pane is shown at the top of Example 13-8. The translation for this pane replaces all the normal translations for highlighting and notifying with a single translation for LeaveWindow events. These events in this widget trigger the CheckRightAndPopupSubmenu action which has already been described.

The translations for the subbox widget invoke PopdownSubmenu action to check whether the pointer left the submenu. In this case, the submenu is popped down, but the original popup remains visible.

The pane that will pop up the submenu and its event handling characteristics is controlled from the app-defaults file. Therefore, it may seem like you can change which menu pane invokes the submenu simply by changing the app-defaults file. This is true here, because the menu actions are nonfunctional and simply call a common callback. But it would not be possible if each pane invoked its own callback. To give the user the freedom to rearrange the menu, you would have to use actions instead of callbacks.

Note that you can define accelerators for any of the menus shown up to this point simply by placing settings for the XtNaccelerators resource in the app-defaults file. You would set this resource for every Command widget in the menus. This provides keyboard shortcuts for popping up the menu and choosing a pane. However, remember to make sure that each key combination is unique.

For completeness, here are some rules that apply when multiple cascaded popups are invoked with specified grab modes.

  • When a grab is removed, all grabs added after that one are also removed.

  • When a shell is popped up with XtGrabExclusive or XtGrabNonexclusive a grab is added to the list.

  • When a shell is popped down, its grab is removed from the list - hence removing all the later shell grabs.

  • When the later shells are popped down, they are no longer on the grab list but the grab is attempted to be removed.

Using the R4 SimpleMenu Widget

Once you have R4, you can use a real menu widget: the R4 SimpleMenu widget. The internals of this widget and its children, which are gadgets, are described later. This section describes a simple application that uses the SimpleMenu widget.

R4 supplies three types of panes to be used with the SimpleMenu widget: SmeBSB (an entry composed of a bitmap, a string, and another bitmap), SmeLine (a horizontal line between entries), and Sme (a blank entry). The SimpleMenu widget is itself a subclass of the popup shell widget, and therefore no separate popup shell needs to be created.

R4 also provides a MenuButton widget, which is a subclass of Command with built-in placement and popup code. Using a MenuButton to invoke a menu makes it even simpler to implement a pulldown menu, since the popup and placement code can be eliminated from the application.

The xmenu7 application shown in Example 13-9 creates a SimpleMenu widget with panes of all three types. The menu is invoked in pulldown style using the R4 MenuButton widget.[95]

As an additional enhancement, the menu marks or unmarks each item when it is selected in addition to calling a callback function. This iteration marks entries with the X logo, which is available as a standard bitmap in /usr/include/X11/bitmaps (on UNIX systems).

Figure 13-5 shows the appearance of the program.

Figure 13-5. xmenu7: a menu using the Athena SimpleMenu widget


Example 13-9. xmenu7: using the SimpleMenu widget and its children

/* xmenu7.c */
#include <stdio.h>


#include <X11/Intrinsic.h>

#include <X11/StringDefs.h>

#include <X11/bitmaps/xlogo16>


#include <X11/Xaw/MenuButton.h>

#include <X11/Xaw/SimpleMenu.h>

#include <X11/Xaw/SmeBSB.h>

#include <X11/Xaw/SmeLine.h>


#define NUM_MENU_ITEMS 12

static String menu_entry_names[] = {
  "quit",
  "item1",
  "item2",
  "item3",
  "line",
  "item5",
  "item6",
  "item7",
  "blank",
  "menu1",
  "menu2",
  "menu3",
};
static Boolean status[NUM_MENU_ITEMS];
static Pixmap mark;
/* ARGSUSED */
static void
MenuSelect(w, client_data, garbage)
Widget w;
XtPointer client_data;
XtPointer garbage;          /* call_data */
{
    int pane_num = (int) client_data;
    printf("Menu item %s has been selected. \n", XtName(w));
    if (pane_num == 0)      /* quit selected. */
        exit(0);
    if (status[pane_num])
        XtVaSetValues(w,
                XtNleftBitmap, None,
                NULL);
    else
        XtVaSetValues(w,
                XtNleftBitmap, mark,
                NULL);
    status[pane_num] = !status[pane_num];
}
void
main(argc, argv)
char **argv;
int argc;
{
    XtAppContext app_context;
    Widget topLevel, menu, button, entry;
    int i;
    Arg arglist[1];
	XtSetLanguageProc(NULL, (XtLanguageProc)NULL, NULL);
    topLevel = XtVaAppInitialize(
        &app_context,       /* Application context */
        "XMenu7",           /* Application class */
        NULL, 0,            /* command line option list */
        &argc, argv,        /* command line args */
        NULL,               /* for missing app-defaults file */
        NULL);              /* terminate varargs list */
    button = XtCreateManagedWidget("menuButton",
            menuButtonWidgetClass, topLevel,
            arglist, (Cardinal) 0);
    menu = XtCreatePopupShell("menu", simpleMenuWidgetClass,
            button, NULL, 0);
    for (i = 0; i < NUM_MENU_ITEMS ; i++) {
        String item = menu_entry_names[i];
        if (i == 4)   /* use a line pane */
            entry = XtCreateManagedWidget(item,
                    smeLineObjectClass, menu,
                    NULL, 0);
        else if (i == 8) /* blank entry */
            entry = XtCreateManagedWidget(item, smeObjectClass, menu,
                    NULL, 0);
        else {
            entry = XtCreateManagedWidget(item, smeBSBObjectClass,
                    menu, NULL, 0);
            XtAddCallback(entry, XtNcallback, MenuSelect,
                    (XtPointer) i);
        }
    }
    mark = XCreateBitmapFromData(XtDisplay(topLevel),
            RootWindowOfScreen(XtScreen(topLevel)),
            xlogo16_bits, xlogo16_width, xlogo16_height);
    XtRealizeWidget(topLevel);
    XtAppMainLoop(app_context);
}


You will notice that each pane has an XtNleftBitmap resource, which is alternately set to the X logo or to nothing each time that item is selected.

The app-defaults file for xmenu7 is shown in Example 13-10.

Example 13-10. XMenu7: app-defaults file

!
! For Color workstations only.
!
*SimpleMenu*foreground:            SteelBlue
*SimpleMenu*menuLabel.foreground:    Gold
*SimpleMenu*line.foreground:        Grey
*MenuButton.label:        Click here for menu
*SimpleMenu*menuLabel.vertSpace:        100
*SimpleMenu*menuLabel.leftMargin:        70
*SimpleMenu.label: Main Menu
*SimpleMenu*item1*label: Show Scrollbar
*SimpleMenu*item2*label: Enable Reverse Video
*SimpleMenu*item3*label: Enable Bell
*SimpleMenu*item4*label: Disable Auto-Repeat
*SimpleMenu*item5*label: Copy
*SimpleMenu*item6*label: Unmark
*SimpleMenu*item7*label: View in New
*SimpleMenu*menu1*label: Reply
*SimpleMenu*menu2*label: Forward
*SimpleMenu*menu3*label: Print
*SimpleMenu*quit*label:           Quit
*SimpleMenu*RowHeight:            16
*SimpleMenu*item7*sensitive:      off
*SimpleMenu*HorizontalMargins:    30
! Just for fun:
*font: *times*medium*18*iso8859-1
!*item7*font: *helv*medium*24*iso8859-1
!*item8*font: *helv*bold*24*iso8859-1
*quit*accelerators:\
    <Key>q: notify()
*item1*accelerators:\
    <Key>1: notify()
*item2*accelerators:\
    <Key>2: notify()
*item3*accelerators:\
    <Key>3: notify()
*item5*accelerators:\
    <Key>5: notify()
*item6*accelerators:\
    <Key>6: notify()
*item7*accelerators:\
    <Key>7: notify()
*menu1*accelerators:\
    <Key>m: notify()

This file simply sets various cosmetic features of the menu. (See Appendix C, Naming Conventions, for information on font-naming conventions.) Naturally, you could easily set the strings for each menu entry in this file. Note that there are no translation tables in this file because MenuButton and SimpleMenu are doing exactly what they were designed to do.

Accelerators can be defined for menus with gadget children, but not in the usual sense. They cannot be defined to invoke the actions of the gadget children, but they can invoke global application actions, which for menus is usually good enough. For example, in the R4 xmh, one item on one of the menus incorporates new mail. From the widget, the notify action of the menu pane gadget calls the DoIncorporateNewMail callback function. The XtNaccelerators resource for the SimpleMenu widget itself (not the gadgets) maps a Meta-I key event into a call to the XmhIncorporateNewMail global action. XmhIncorporateNewMail then calls DoIncorporateNewMail. This use of accelerators depends on having both a callback and an action form of each function.

Delayed Popup Creation

As we've seen, a popup may consist of a single widget, or it may be a shell widget which contains a composite widget which contains a number of children. In the latter case, creating all those widgets (or gadgets) takes time. It may be beneficial to create those widgets using idle time in the application instead of delaying startup. In either case, it may make sense to create the popup only when it is needed, to minimize wasted resources.

If your goal is to speed startup, and you want all menus created even if some are never used, you can register a work procedure to create each popup, as described in Section 8.5. As you may recall, a work procedure uses idle time in the application to call a function, which must return swiftly. If you use this technique, you add one work procedure for each popup you need to create, and you need to add code to make sure that the popup has been created before the user is allowed to use it.

If your goal is to create only the required popups, you can create the popup in a callback function or action routine that you have registered to place or pop up the popup. In this case, you would have a static variable in the callback or action to make sure that the popup widgets are only created the first time the popup is popped up. You need to have created the popup shell before this can work.

There is also another way to create only the required popups. Shell widgets have an XtNcreatePopupChildProc resource which you can set to a function that creates the Shell's children. See XtCreatePopupChildProc(2) in Volume Five, X Toolkit Intrinsics Reference Manual, for the calling sequence of this function type. The Xt specification does not say whether an XtCreatePopupChildProc is called just once when the shell is first popped up or every time it is popped up. But the MIT implementation of Xt calls it every time the shell is popped up, so you will again need a static variable to make sure the children are only created once.

About Dialog Boxes

Although we have been talking so far exclusively about menus, much that has been said is also true of dialog boxes. Both menus and dialog boxes that get user input usually need to get that input before other application functions can be invoked. Of course, one way to disable all other application functions is to make all other widgets insensitive with XtSetSensitive() (passing it False). Setting the sensitivity of one common ancestor does this efficiently, but even this is too slow because all the widgets redraw themselves dimmed or grayed. It is much faster to use a global grab. Unlike menus, which require the global grab in order to get button release events outside the application so they can pop down properly, dialog boxes do not, strictly speaking, need a grab. But they sometimes make the grab anyway to disable other application functions.

Dialog boxes can also invoke other dialog boxes. For example, a dialog box that gets input might check the validity of the input before popping down the dialog, and if incorrect, pop up a message telling the user the problem with the input. Cascaded dialog boxes are implemented the same way as cascaded menus. Note that, as a general rule, sub-dialog boxes are popped up with grab mode XtGrabExclusive, which means that the user must satisfy the most deeply nested dialog first. However, it is often desirable to leave a Help dialog on the screen until after the dialog it provides help for has been removed.

Some popups do not need to disable other application functions. For example, imagine a dialog box that informed the user of some fact without requiring confirmation. This kind of popup would be popped up with grab mode XtGrabNone, allowing the user to continue with other application functions.

We pointed out earlier that the built-in callback functions for popping up a widget are not useful for menus because they make no passive global pointer grab. However, they come in handy for dialog boxes. The functions XtCallbackNone(), XtCallbackExclusive(), and XtCallbackNonexclusive() can be used to pop up dialog boxes, as long as the position of the dialog box need not depend on information in an event.

We haven't shown how to use Xt's standard callback for popping down a widget: XtCallbackPopdown(). Instead of calling XtPopdown() in the callback functions for each menu entry, we can add XtCallbackPopdown() to the callback list after the existing callback function. XtCallbackPopdown() requires an XtPopdownId structure to be passed as the client_data argument. This structure must contain the popup shell and the widget that invoked the popup (the MenuButton or Command widget).

All three of the standard popup callbacks set the invoking widget to insensitive mode before popping up the widget. XtCallbackPopdown() resets the invoking widget to sensitive mode. Therefore, if you use XtCallbackNone(), XtCallbackNonexclusive(), or XtCallbackExclusive() without also using XtCallbackPopdown(), remember to set the widget to sensitive mode yourself. This feature is useless but also harmless when the popup is spring-loaded, because the invoking widget is often the main application window and that widget rarely responds to sensitivity.

In certain rare cases, you may want to use XtAddGrab() and XtRemoveGrab() directly to append a widget to or remove a widget from the current popup cascade. These functions are called internally by the Xt facilities that pop widgets up and down, and should not be necessary on their own. Note that these functions never make a request to the server to start or release a passive global pointer grab--they affect only Xt's internal event dispatching. (However, the functions XtGrabKey(), XtGrabKeyboard(), XtGrabButton(), and XtGrabPointer() do initiate global grabs. These functions are described in Chapter 14, Miscellaneous Toolkit Programming Techniques.)

Gadgets

When an application includes many different menus with many fields each, the server memory consumption of having separate widgets for every menu pane was once thought to be a problem. So the window-less widget, called a gadget, was invented. A gadget depends on special behavior of its parent to get events and operate much like a widget. (Gadgets also consume slightly less client-side memory.)

Now that the server implementation has been improved, the memory consumption of a window is much less significant now. In fact, the use of Motif Button gadgets in particular greatly increases network traffic during the crucial user-interaction phase of the application. This is because the Motif Button gadget parent requires MotionNotify events to track whether the pointer has entered or left a gadget (since gadgets have no window to cause EnterNotify events). These MotionNotify events allow the gadget to highlight its border when the pointer enters, just like a regular widget. A gadget is fully configurable using the resource database just like a widget, and can have its own callback list.

This network traffic problem is much more serious than the memory problem gadgets were originally thought to improve. Therefore, gadgets should be avoided in uses where they cause this problem.

Gadgets also have other limitations. Gadgets have to draw on their parent's window, and they share this space with the parent and with all other gadget children. The gadgets and the parent must agree to draw in certain areas only. For this reason, gadgets must be used with a special composite widget parent that is prepared to manage them properly.

The Athena SimpleMenu widget is such a parent. It is a composite widget designed to manage gadget children. The gadgets provided by are Sme, SmeBSB, and SmeLine (where Sme stands for Simple Menu Entry). These provide a blank entry (and generic menu entry superclass), an entry which can contain a bitmap, a string, and another bitmap (thus BSB), and an entry that draws a horizontal line. We will use and describe this widget and these gadgets both to show how menu widgets are built and to demonstrate how the parent and the gadgets work together.

Gadgets do not handle events automatically as widgets do, and because they have no windows, the server does not handle overlapping between them. This places certain demands on the parent. All the gadgets that are children of a particular parent share that parent's window. The parent is responsible for coordinating the gadget children, telling them about events by calling their functions. Therefore, the composite widget that manages a group of gadgets must be specially designed for that purpose, not a general-purpose composite or constraint widget such as Box or Form. It is possible for a composite widget to manage both gadget and widget children, but its code has to be more involved to do this.

Like normal widgets, gadgets provide their own code to redraw themselves in their expose method. However, since gadgets do not receive events, they depend on the parent to directly call their expose method. The parent keeps track of the geometry of each child, and when the parent's expose method is called, this method calculates whether the area exposed overlaps any of the gadget children. If the area exposed does overlap a gadget, the parent's expose method calls that gadget's expose method, which redraws the area.

A gadget's actions also have to work differently from widget actions because of the fact that gadgets don't get events. A gadget defines its actions as methods--as fields in its class part structure--instead of in an action list and translation table. It initializes these fields directly to pointers to functions during class initialization. The parent widget has corresponding actions that are defined and operate like normal actions, except that they determine which gadget the event that invoked the action occurred in and call the gadget method corresponding to that action. In other words, the parent has actions that operate the gadget children.

One weakness of a menu composed of gadget panes is that gadgets cannot have an accelerator table. Therefore, accelerators cannot be used to provide a keyboard equivalent that would invoke each menu pane.

The parent of gadgets has to position the gadget children so that they do not overlap, or take care of the consequences if they do overlap. Since the gadgets draw on the parent's window, if they did overlap they would draw over each other's graphics, with unpredictable results. The parent would have to calculate the area of overlap between two gadgets, and clear this area before letting one of the gadgets draw itself. (A gadget could clear its own area before drawing, but this would be unnecessary in many cases, and would cause flashing.)

Gadgets are subclasses of RectObj, one of the invisible superclasses of Core that we have so far ignored because for widgets it is safe to assume that Core is the top of the widget class hierarchy.[96] The actual class hierarchy leading up to Core is shown in Figure 13-6.

Figure 13-6. Class hierarchy derivation of Core

The “unnamed class” actually has a name (WindowObj) but this class is intentionally undocumented in the Xt specification so that its characteristics can be changed in later releases without compatibility problems. You should never create subclasses directly from the unnamed class.

The superclasses of Core are not real classes in the sense that they do not play by all the rules we have described in Chapter 6, Inside a Widget. For one thing, each shares what we call the Core class structure instead of adding its own part structure. Applications are never intended to create instances of these superclasses--they are really just part of the implementation of Xt. Instead of developing all the characteristics of widgets in one large base class Core, it made more sense to implement Xt in object-oriented fashion by dividing the implementation into separate pseudo-classes. It is important to know what each early class defines simply so that you know what characteristics are available in gadgets and which are available only in widgets. Each class defines the following features:

  • Object defines only the XtNdestroyCallback resource and the underlying support for callbacks in general.

  • RectObj defines the geometry resources (XtNx, XtNy, XtNwidth, XtNheight, and XtNborder_width) and the resources that control sensitivity: XtNancestorSensitive and XtNsensitive. RectObj itself doesn't use the sensitivity resources. They are provided at this level in the hierarchy so that sensitivity can be set for gadgets. Gadgets draw themselves according to these resources (gray if insensitive), and check these resources in their action routines (stored in their class part methods), invoking their callback function only if sensitive.

  • The unnamed class (WindowObj) adds many window-oriented resources: ones that control window attributes such as the background pixmap, permanent window features such as the window depth, and event resources such as translations and accelerators. It also includes the XtNmappedWhenManaged resource. This class is known as the unnamed class because it never appears in widget, gadget, or application code. Widgets are subclassed from Core, while gadgets are subclassed from RectObj. The exact features of unnamed class are subject to change and should not be relied upon.

The Core class structure actually is inherited all the way from RectObj. Therefore, the class structure in gadgets is the Core class structure you are already familiar with. All the event-related fields in the Core class part structure of gadgets are unused. The only exception is the expose method, which is present, but draws on the parent's window and is not called in the usual way by Xt because the gadget receives no events. The remaining non-event-related fields have the same purpose as for widgets, including all the remaining methods.

Without further ado, let's take a look at a gadget, and then at a gadget parent.

Inside a Gadget

Many portions of a gadget's code are exactly the same as those of a widget, as described in Chapter 6, Inside a Widget. This section summarizes the parts that are identical so that you know that nothing is left out, and describes the differences in detail.

The code for gadgets and widgets includes the same three implementation files with the same naming conventions. As in Chapter 6, we'll take the three implementation files one at a time, beginning with the private header file, then the code file, and then the public header file.

As in widget code, many of the conventions described here are automatically taken care of for you when you are writing a new gadget if you copy all three files of an existing gadget and then globally change the gadget class name.

The Athena menu pane gadgets are implemented in two class levels:

  • Sme (Simple Menu Entry) defines the callback for an entry, the actions to highlight, unhighlight, and notify, and the code that allows subclasses to inherit or replace these actions (because they are defined as methods). The actual functions for highlight and unhighlight are empty, while the notify action calls the callback. The expose method of this gadget is also empty. This gadget can be used by itself to create a blank entry.

  • SmeBSB and SmeLine are each subclasses of Sme. SmeLine replaces only the expose method of its superclass. SmeBSB replaces both the expose method and the highlight and unhighlight actions of the superclass. (Sme can be subclassed to create new types of menu entries.)

The following sections describe both Sme and SmeBSB.

Private Header File

The private header file for a gadget is identical in format to the private header file for a widget. It defines a class part structure for this class of gadget and then a complete class structure including the class parts of superclasses and this class. The only difference is that a gadget inherits its features from Object and RectObj, whereas a widget inherits from Core. Example 13-11 shows the complete class structure of the R4 Athena Sme gadget.

Example 13-11. Sme gadget: class part and complete class structure declaration

typedef struct _SmeClassPart {
    void (*highlight)();
    void (*unhighlight)();
    void (*notify)();
    XtPointer extension;
} SmeClassPart;
/* Full class record declaration */
typedef struct _SmeClassRec {
    RectObjClassPart    rect_class;
    SmeClassPart  sme_class;
} SmeClassRec;
#define XtInheritHighlight   ((_XawEntryVoidFunc) _XtInherit)
#define XtInheritUnhighlight XtInheritHighlight
#define XtInheritNotify      XtInheritHighlight

Notice that the complete class structure declaration does not include the class part for the Object class, even though it is a superclass. This is because all the superclasses of Core share the same class part structure.

The class part structure for Sme defines three methods--these are essentially the gadget's actions, but they will be invoked by the gadget parent's actions, not directly by Xt. The extension field allows fields to be added to this structure in a future version while retaining binary compatibility. In a future version this field could be changed to point to an extension structure.[97]

Any class that defines methods must provide code to allow them to be inherited or replaced by subclasses. The Sme class therefore must define the XtInherit constants that allow the methods to be inherited. The .c code file provides the class_part_init method that allows them to be replaced. (See Section 12.4.5, "The class_part_init Method.")

In the private header file for SmeBSB, the class part structure would contain only the extension field, because SmeBSB will be using the highlight, unhighlight, and notify fields defined by Sme. However, the .c file will initialize these fields to point to its own functions.

The instance part structure and complete instance structure of Sme are shown in Example 13-12.

Example 13-12. Sme gadget: instance part and complete instance structure declaration

typedef struct {
    /* resources */
    XtCallbackList callbacks;   /* The callback list */
} SmePart;
typedef struct _SmeRec {
    ObjectPart     object;
    RectObjPart    rectangle;
    SmePart  sme;
} SmeRec;


The SmePart adds a callbacks resource. The complete SmeRec includes the prior elements in the widget hierarchy: ObjectPart and RectObjPart. Note that unlike the class structure, the Object class does appear in the complete instance structure, because the superclasses of Core do not share instance structures.

The instance part structure for SmeBSB includes the usual fields to maintain the graphics state of the entry, including the label, colors, font, GCs, and positioning information. The complete instance structure for SmeBSB is the same as the one for Sme but with the SmeBSBPart structure added at the end.

The Gadget Source File

The source file for a gadget is identical in form to a widget source file. The only differences are that the superclass of a gadget in the class structure initialization is rectObjClassRec, and the complete instance structure type is called SmeObject for a gadget where it would have been SmeWidget if the entry was a true widget. Therefore, SmeObject is the type into which you cast the pointer to the instance structure before accessing the structure's fields in all the widget methods.

In addition, several of the Core class structure fields that might be used in a widget are never used in gadgets. The following is the complete list of fields that are always initialized to a certain value in a gadget:

  • realize set to NULL

  • actions set to NULL

  • num_actions set to 0 (zero)

  • compress_motion set to False

  • compress_exposure set to False

  • compress_enterleave set to False

  • visible_interest set to False

  • resize set to NULL

  • display_accelerators set to NULL

Setting these fields otherwise (of the right type) probably won't cause the gadget to crash, but won't accomplish anything useful either.

Gadgets, like widgets, should always define the query_geometry method, and either define set_values_almost or initialize it to XtInheritSetValuesAlmost. The remainder of the fields and methods have the same purpose and are used in the same way as for widgets.

There are, however, slight differences in the code for certain gadget methods. The expose method checks not only its own sensitivity but also its parent's sensitivity before deciding whether to draw the entry in normal colors or grayed. When creating GCs using XCreateGC() or creating any other server resources from the initialize method using an Xlib call, you must remember to use the parent's window, since the gadget has no window. (XtWindow() is not smart enough to give you the parent's window ID in the case of gadgets.) Also, the parent's resource values, such as background_pixel, may be used to provide data in common among all instances of a subclass like SmeBSB.

The Public Header File

The only difference in the public header file between widgets and gadgets is that what would have been Widget for a widget is Object for a gadget. As mentioned previously, if you are writing a gadget you should start by copying the files for an existing gadget and then globally change names. Then you will start with the proper conventions already in place.

The Gadget Parent

A gadget parent is a composite widget designed to manage gadget children. Gadget parents perform all the geometry management tasks that all composite widgets perform, which are described in Chapter 12, Geometry Management. Gadgets also follow all the rules of normal widget children. However, gadget parents also have the added responsibility of managing the overlap of gadgets or making sure they don't overlap, and of handling events for the gadgets and calling gadget code. This section describes the gadget-managing role of the gadget parent.

The Athena SimpleMenu widget is designed to manage the gadget children already described, Sme, SmeLine, and SmeBSB (and any other subclass of Sme that is written later). It forms a vertical menu with horizontal panes. It is quite a large widget because it contains all the geometry management code in addition to code for managing events for the gadgets. We'll concentrate just on the code that manages events for the gadgets, since the geometry management code is described in Chapter 12, Geometry Management.

Let's begin with the expose method. SimpleMenu's expose method does no drawing of its own. It simply calls the expose methods of the gadget children. However, it compares the region passed into its expose method to determine which gadgets need redrawing. Example 13-13 shows SimpleMenu's expose method.

Example 13-13. SimpleMenu: expose method calling gadget children's expose methods

#define ForAllChildren(smw, childP) \
    for ( (childP) = (SmeObject *) (smw)->composite.children ; \
        (childP) < (SmeObject *) ( (smw)->composite.children + \
        (smw)->composite.num_children );  (childP)++ )
/* ARGSUSED */
static void
Redisplay(w, event, region)
Widget w;
XEvent * event;
Region region;
{
    SimpleMenuWidget smw = (SimpleMenuWidget) w;
    SmeObject * entry;
    SmeObjectClass class;
    if (region == NULL)
        XClearWindow(XtDisplay(w), XtWindow(w));
    /*
     * Check and Paint each of the entries - including the label.
     */
    ForAllChildren(smw, entry) {
        if (!XtIsManaged ( (Widget) *entry))
            continue;
        if (region != NULL)
            switch(XRectInRegion(region, (int) (*entry)->rectangle.x,
                    (int) (*entry)->rectangle.y,
                    (unsigned int) (*entry)->rectangle.width,
                    (unsigned int) (*entry)->rectangle.height)) {
                case RectangleIn:
                case RectanglePart:
                    break;
                default:
                    continue;
                }
        class = (SmeObjectClass) (*entry)->object.widget_class;
        if (class->rect_class.expose != NULL)
            (class->rect_class.expose)((Widget) *entry, NULL, NULL);
    }
}


Note that this expose method is also called from elsewhere in the widget code (specifically, from the resize and geometry_manager methods) to redraw the gadgets. In these cases, the region passed in is set to NULL, and the method clears its window and redraws all the gadgets.

Also note how this expose method invokes the expose methods of its children. All expose methods (and in fact all methods) are stored in the class structure, not the instance structure. Composite widgets keep only a list of the instance structures of their children. However, one field in each instance structure points to the class structure for that child. This is the widget_class field of the Object instance part.[98] In this example, the entry counter variable is a pointer to the gadget ID (opaque pointer to the instance structure) of one of the children. Another variable, class, declared as a pointer to the SmeObjectClass class structure (the expected class of the children), is set to the widget_class field in the instance structure of one of the children. Then the expose field of this class structure is checked to see if it is NULL, and if not it is invoked.

Note that the class of the children is hardcoded in this method. This widget can manage only Sme widgets and its subclasses.

The resize method of SimpleMenu must resize the children when it is resized itself. (Actually, this is unlikely, since the SimpleMenu widget itself is a subclass of Shell and is therefore not managed by any parent.) This method is invoked only when the user resizes the menu using the window manager. Since this widget has the authority to determine the geometry of its children, it can simply resize them. This particular resize method (shown in Example 13-14) simply sets their width to be the same as its own.

Example 13-14. SimpleMenu: resize method

static void
Resize(w)
Widget w;
{
    SimpleMenuWidget smw = (SimpleMenuWidget) w;
    SmeObject * entry;
    if ( !XtIsRealized(w) ) return;
    ForAllChildren(smw, entry)  /* reset width of all entries. */
        if (XtIsManaged( (Widget) *entry))
            (*entry)->rectangle.width = smw->core.width;
    Redisplay(w, (XEvent *) NULL, (Region) NULL);
}


Notice that this resize method invokes the expose method (Redisplay) because the gadgets don't have resize methods, and will not redraw themselves in response to their size change.[99]

Now let's look at SimpleMenu's actions. Their only purpose is to call the gadgets' actions when the appropriate events arrive. These actions are added in the usual way: they are declared at the top of the .c file, then registered with an action list that is entered into the class structure initialization, and then defined. One of the three actions is shown in Example 13-15.

Example 13-15. SimpleMenu: the Notify action routine

/* ARGSUSED */
static void
Notify(w, event, params, num_params)
Widget w;
XEvent * event;
String * params;
Cardinal * num_params;
{
    SimpleMenuWidget smw = (SimpleMenuWidget) w;
    SmeObject entry = smw->simple_menu.entry_set;
    SmeObjectClass class;
    if ( (entry == NULL) || !XtIsSensitive((Widget) entry) )
        return;
    class = (SmeObjectClass) entry->object.widget_class;
    (class->sme_class.notify)( (Widget) entry );
}


This action determines whether the chosen entry is sensitive and, if so, calls the notify method of that gadget. As described above in the section on the gadget children, gadgets define their actions as methods so that they can conveniently be called by their parent. Since these methods are stored in the class structure not the instance structure, this is done using the technique described above for the expose method.

Although not critical to its handling of gadgets, SimpleMenu does one more interesting thing. It registers the PositionMenuAction action in the global application action list (as opposed to the internal widget action list) so that the application or app-defaults file can refer to this action in translation tables without needing to register the action. This action can be triggered by any type of event in the widget and positions the menu according to data in the event type. (SimpleMenu has a resource that controls whether this placement process makes sure that the menu is not off the screen.)

A widget can add an action to the global action list by calling XtAddAction() just like an application would, but from its class_initialize method.

Any composite widget that is capable of managing gadgets must declare a Composite extension structure in the .c file and set the accepts_objects field of that structure to True. It must then set the pointer to the extension structure into the Composite class part structure in the class_part_initialize method. Extension structures were introduced in Chapter 6, Inside a Widget, and are discussed further in Chapter 14, Miscellaneous Toolkit Programming Techniques. Example 13-16 shows this code from SimpleMenu.c.

Example 13-16. SimpleMenu.c: Setting accepts_objects in the Composite extension structure

CompositeClassExtensionRec extension_rec = {
    /* next_extension */  NULL,
    /* record_type */     NULLQUARK,
    /* version */         XtCompositeExtensionVersion,
    /* record_size */     sizeof(CompositeClassExtensionRec),
    /* accepts_objects */ True,
};
static void
ClassPartInitialize(wc)
WidgetClass wc;
{
    SimpleMenuWidgetClass smwc = (SimpleMenuWidgetClass) wc;
/*
 * Make sure that our subclass gets the extension rec too.
 */
    extension_rec.next_extension = smwc->composite_class.extension;
    smwc->composite_class.extension = (XtPointer) &extension_rec;
}

This code, with names changed, will appear in all gadget parents.



[89] Some menus don't even display a title--they simply pop up at the pointer position in response to a particular pointer button/keypress combination. This is the behavior of the menus provided by xterm and the system menu of mwm. However, this is not very desirable behavior from a user-interface point of view, since it gives the user no visual feedback that a menu is available or how to invoke it. The user needs the manual--something graphical user interfaces are designed to avoid.

[90] To be fair, there is something to be said for the fact that all the available commands are always visible in an application that uses button boxes. You can invoke a button in a box with just a button click, while in a menu it requires a press, a drag, and a release. When there are only a small number of commands, putting the command widgets in a box is probably better than using a menu.

[91] One reason that XtGrabPointer() is rarely used for popups is that it requires that the window that will receive the grabbed events be visible. This is often not the case. In a menu, for example, the window that you want to grab the events may be hidden by the menu panes even when the menu is popped up. Another reason is that you need to call XtUngrabPointer() to release the grab when finished. Passive grabs match the task better.

[92] With XtPopupSpringLoaded() you can write callback functions that are the equivalent of XtCallback*, except that they will pop up a menu with the necessary passive global grab. You can also write your own version of the XtMenuPopup() action if it doesn't do what you want. You will need XtRegisterGrabAction() to do this. This function tells Xt to automatically start a passive global grab whenever a certain action is invoked.

[93] It is important to provide a consistent user interface, so you should use the same offset in all menus. Menus in commercial widget sets such as the OPEN LOOK widgets have carefully designed and documented policies about popup window placement. This allows the user's “pointer reflexes” to be trained, so that using menus becomes as automatic and easy as possible.

[94] Incidentally, XTranslateCoordinates(), the Xlib equivalent of XtTranslateCoords(), gets the same information and a little more by querying the server. XtTranslatCoords does not have to make a server request because Xt stores this data locally. Each time a window in this application is moved, Xt receives this information as an event and updates its knowledge of the position of each window. This is an important optimization, because server queries are subject to network delays and tend to slow applications.

[95] This example was written by Chris Peterson of MIT Project Athena and modified only slightly by the authors.

[96] Only the header files for RectObj are public, so that you can write and compile gadgets.

[97] Note that the extension field is defined as type XtPointer. In R4, all occurrences of caddr_t have been replaced with XtPointer. On most systems, XtPointer will be defined to be caddr_t. But for some architectures, caddr_t is too small to hold a pointer to a function. On such systems, XtPointer will be defined to be larger. The caddr_t type will continue to work on most systems, but you are advised to use XtPointer instead for maximum portability.

[98] The Core instance part structure (not complete) is the concatenation of the instance parts of the three superclasses Object, RectObj, and the unnamed class. Therefore, it also includes a widget_class field. Since composite widgets do not normally need to invoke the methods of their children, you shouldn't need to access this field.

[99] The gadget children could have resize methods, and this resize method could call the children's resize methods. The gadget's resize methods would simply call their expose method. However, this does exactly the same thing as the code shown while being more complicated.