Chapter 3. More Techniques for Using Widgets

This chapter describes how to use some of the more complex widgets found in applications, including composite widgets, constraint widgets, and popups. It also describes how to define application resources and command-line options, and how to hardcode the value of widget resources when you create a widget.

The techniques described in "Chapter 2, Introduction to the X Toolkit," will get you started writing applications. But there are more tools at your disposal. This chapter describes the following techniques:

Using Composite Widgets

The examples in "Chapter 2, Introduction to the X Toolkit," were atypical because they contained only one widget. Because any real application has several widgets, some way of laying them out is needed. This is tedious to do manually. Therefore, the first widget you create after calling XtAppInitialize() is usually a composite widget, whose job it is to manage the layout of a group of child widgets. The parent of this composite widget is the Shell widget created by XtAppInitialize(), usually called topLevel.

This chapter's first example is a small application, xrowcolumn, that creates two PushButton widgets contained in a RowColumn widget. Figure 3-1 shows how the application looks on the screen.

Figure 3-1. xrowcolumn: appearance on the screen

Example 3-1 shows the code that implements xrowcolumn.

Example 3-1. xrowcolumn.c: complete code

/*
 *  xbox1.c - simple button box
 */

/*
 *  So that we can use fprintf:
 */
#include <stdio.h>

/*
 * Standard Motif include file:
 */
#include <Xm/Xm.h>

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

/*
 * quit button callback function
 */
/*ARGSUSED*/
void Quit(w, client_data, call_data)
Widget w;
XtPointer client_data, call_data;
{
    exit(0);
}

/*
 * "Press me!" button callback function
 */
/*ARGSUSED*/
void PressMe(w, client_data, call_data)
Widget w;
XtPointer client_data, call_data;
{
    fprintf(stderr, "Thankyou!\n");
}

main(argc, argv)
int argc;
char **argv;
{
    XtAppContext app_context;
    Widget rowColumn, quit, pressme, topLevel;

        XtSetLanguageProc(NULL, (XtLanguageProc)NULL, NULL);

    topLevel = XtVaAppInitialize(
            &app_context,       /* Application context */
            "XRowColumn1",      /* Application class */
            NULL, 0,            /* command line option list */
            &argc, argv,        /* command line args */
            NULL,               /* for missing app-defaults file */
            NULL);              /* terminate varargs list */

    rowColumn = XtVaCreateManagedWidget(
            "rowColumn",            /* widget name */
            xmRowColumnWidgetClass, /* widget class */
            topLevel,               /* parent widget */
            NULL);                  /* terminate varargs list */

    quit = XtVaCreateManagedWidget(
            "quit",                  /* widget name */
            xmPushButtonWidgetClass, /* widget class */
            rowColumn,               /* parent widget */
            NULL);                   /* terminate varargs list */

    pressme = XtVaCreateManagedWidget(
            "pressme",               /* widget name */
            xmPushButtonWidgetClass, /* widget class */
            rowColumn,               /* parent widget */
            NULL);                   /* terminate varargs list */

    XtAddCallback(quit, XmNactivateCallback, Quit, 0);
    XtAddCallback(pressme, XmNactivateCallback, PressMe, 0);

    XtRealizeWidget(topLevel);

    XtAppMainLoop(app_context);
}


Example 3-1 creates a RowColumn widget called rowColumn as a child of topLevel, and then creates each PushButton widget as a child of rowColumn. Notice how the parent argument of each generation of widgets is used. Also notice that the Shell widget topLevel is exactly the same size as rowColumn and therefore is not visible.

Example 3-1 creates only two children of its RowColumn widget. If your application creates many children for a single widget, it may be preferable to create the children with XtVaCreateWidget(), and then manage all the children of that parent with a single call to XtManageChildren() (instead of calling XtVaCreateManagedWidget() for each child).[22]

At the same time, if you are creating a lot of widgets, it pays to consider using gadgets instead of widgets. A gadget is a windowless widget, with somewhat reduced features. Motif provides gadget versions of many of its smaller, simpler widgets, such as PushButton. The gadget version of PushButton is PushButtonGadget. For the most part, gadgets are created and used just like widgets. Since a gadget has no window, it loads the server and the network less and can attain better performance. For example, one of the Motif demos, xmfonts, creates over 100 PushButtonGadgets as children of a single RowColumn widget. In that case, it definitely makes sense to use gadgets. Probably the most common use of gadgets in applications, though, is for the panes in menus. Therefore, we will reserve our major discussion of gadgets until "Chapter 13, Menus, Gadgets, and Cascaded Popups."

Setting Resources for an Instance Hierarchy

You have already seen how an app-defaults file can set the string for a PushButton widget. However, xrowcolumn has an instance hierarchy that contains a RowColumn widget with two PushButton widgets as children. It is worth seeing how to set the PushButton widget labels in this new situation. (We will be returning often to the subject of setting resources, because it is so important to Toolkit programming. Each time, new ideas will be presented.)

Example 3-2 shows an app-defaults file for xrowcolumn.

Example 3-2. XRowColumn: app-defaults file

*pressme.labelString:      Press Me
*quit.labelString:         Quit
*XmPushButton.fontList:    variable
*XmRowColumn.background:   green4


When an application contains multiple widgets of the same class, resource specifications can either identify individual widget instances by name or can use wildcards or widget class names to reference more than one widget. The first two specifications in the example identify the pressme and quit widgets by instance name. The third specification uses the class name PushButton to set the font of both PushButton widgets in the application. This line shows that resources of groups of widgets can be set with a single line. The fourth specification uses the class name RowColumn to set the background of the RowColumn widget (but not the PushButton widgets) to the color green. Whenever you use a class name, it will match all widgets of that class in the application, even ones we add later in a later revision of the application.

By changing the period in the fourth specification to an asterisk, the specification will change the background color of not only all RowColumn widgets in the application, but also all their children, and their children, recursively. Remember from Chapter 2 that an asterisk matches zero or any number of intervening widget instance names or class names. In xrowcolumn, this setting will make the background of the RowColumn and PushButton widgets green. Try not to get careless with asterisks, though. They lead you to think you don't have to remember the instance hierarchy in your application, but this can backfire, because it is easy to set resources on more widgets than you realize.

Note that you need to know the instance name for each widget in the application in order to set its resources individually. This is true for all resource files, including the ones customized by the user. Therefore, in the documentation for your application, be sure to include the name and class of each widget in your instance hierarchy. To be thorough, also include a description of the resources of each class, and specify which resources the user can customize.

The first argument of each XtVaCreateManagedWidget() call is the widget instance name. This is the name used to set resources for this widget in the resource databases. Most programmers make the widget instance name the same as the variable name of type Widget that holds the widget ID. This lexical connection is not mandatory, but it is highly recommended because it reduces confusion by helping you to remember the connection between entries in the app-defaults file and the widget instances in the application.

Geometry Management in Practice

The purpose of geometry managing widgets is two-fold:

  1. They take most of the tedium out of determining the initial positions of widgets.

  2. They recalculate the positions of all the widgets when the user resizes the application, so that the application still looks good.

Build and run xrowcolumn, and then try resizing it to see how the RowColumn widget deals with various geometries. This is the default behavior of RowColumn--it can be customized with resources to use many different layout rules. Try adding the following resource settings to its app-defaults file, run it again, and try out its resizing characteristics.

*rowColumn.packing: XmPACK_TIGHT
*rowColumn.orientation: XmHORIZONTAL

Two of the resulting geometries are shown in Figure 3-2.

Figure 3-2. Two configurations of xrowcolumn


What happens if you use resource settings in the app-defaults file to directly set the size or position of a widget that is being managed by a geometry managing widget? Depending on the specific geometry managing widget, you may not get the desired effect. For example, trying to set the size of the PushButton widgets managed by RowColumn may not work. For example, if you added the resource setting *pressme.height: 400, it would have no effect on the height of the pressme widget unless allowShellResize is also set to True (if you are using mwm). This is because the RowColumn widget needs to ask its parent, the shell widget, to be resized, and this will be denied unless mwm allows the shell to resize itself. However, it is possible to set things like geometries in the application code, as will be demonstrated later in this chapter.

Every widget's size and position is ultimately under the control of the window manager.[23] A RowColumn widget attempts to make itself just big enough to hold its children, using the resources provided by the application as a guide, but the window manager can override anything the widget or the application does.

What happens when the user resizes an application is only part of the picture. The application itself may need to resize one of its widgets in order to display more widgets. Or the application may tell a widget to display more data and the widget will have to ask its parent to be resized. For example, what happens when the application changes the string in one of the PushButton widgets while the application is displayed? The PushButton widget attempts to resize itself to display the current string, by asking its parent for permission.

Whether this request is granted depends on the position of the widget in the instance hierarchy, the resizing policy imposed by each composite widget in the hierarchy, and the window manager. This is because each widget, from the PushButton widget on up, negotiates with its parent when the PushButton widget requests a new size. The PushButton widget tries to change size to accommodate the new string (larger or smaller), and the RowColumn widget must approve this change. Since the RowColumn widget is already the same size as the Shell widget, RowColumn can't get any larger without asking Shell. The Shell widget is responsible for negotiating with the window manager.

The mwm window managers will allow applications to resize themselves only if the user has set the allowShellResize resource to True. (Some other window managers, such as twm, never allow it.) Otherwise, the RowColumn widget will reject the resize request unless the original change made the PushButton widget smaller. If the new size is rejected, the PushButton widget will still display the new string, but it may not show all of the string (if the new string is longer than the old one) or there may be extra space left over (if the old string was longer).

Fortunately, all this negotiation is done by the widgets themselves. The application doesn't need to do anything. However, you should be aware that any widget resource change that results in a widget size change may not work unless there is enough room in the application for the change to be granted without resizing the top-level window.

The RowColumn widget is Motif's most comprehensive and powerful geometry managing widget. It is widely used for managing groups of small widgets in many settings. (Some of these uses of RowColumn are under other names, such as CheckBox, PullDownMenu, and MenuBar.) However, it treats all its children the same, and therefore isn't appropriate for widgets of radically different geometries. RowColumn's decisions about where to place the widgets can be inappropriate. Figure 3-3 shows the results upon resizing of a RowColumn widget that is attempting to manage two ScrollBar widgets and a BitmapEdit widget. [24]

Figure 3-3. Incorrect results upon resize of ScrollBar widgets inside a RowColumn widget

Those two large triangles are actually ArrowButtons on the ends of a very wide but short ScrollBar! It might be possible to use the RowColumn resources to make this application look better, but it would be difficult to keep it good looking after resizing.

Because no single geometry managing widget can satisfy all needs, there are several different types of composite widgets in most widget sets, each with different rules about how it places children. Many widget sets, including Motif, have a widget specifically designed to place scrollbars next to a main window. In Motif, this widget is called ScrolledWindow.

Of course, applications are not limited to using only one composite widget. It is quite common for the application's main window to be a large composite widget which contains several smaller composite widgets, each of which in turn contains certain groups of related widgets. You'll need to design the layout of widgets in your application, decide where in the instance hierarchy to place composite widgets, and experiment to find out which composite widgets provide the best appearance when the application is resized. The following application shows how several kinds of geometry managing widgets can be used together in an application.

Figure 3-4 shows the xmh application and the instance hierarchy used to create it. This application is built using the Athena widgets. However, analogues of all these widgets are available in Motif, and the application could easily be ported. The top composite widget in xmh is of the Athena Paned class (equivalent to PanedWindow). The Paned widget creates several horizontal panels, or panes, one for each child, with a sash positioned on the line between each pane. Each pane contains a different functional area of the application.

Figure 3-4. The xmh application and its instance hierarchy


  • The top pane is a menubar (implemented with a Box widget, a simpler version of RowColumn) containing oval Athena MenuButton widgets (equivalent to CascadeButton).

  • The next pane is a Label widget (equivalent to Label).

  • The third pane is another Box widget containing Command widgets (equivalent to PushButton). The fourth pane is another Label widget.

  • The fifth pane is a geometry managing widget called a Viewport (equivalent to ScrolledWindow) containing a Text widget (equivalent to Text).

So, you see, there are three different kinds of geometry managing widgets in this application: Paned, Box, and Viewport. In a Motif port of this application, they would be PanedWindow, RowColumn, and ScrolledWindow.

Box and Viewport are relatively simple geometry managing widgets that are subclasses of the Composite class. The Paned widget is a more complex kind of geometry managing widget which is a subclass of the Constraint class. Both Composite and Constraint are basic classes defined by the Xt Intrinsics.

Using Constraint Widgets

Constraint widgets, like composite widgets, manage the layout of children. The difference is that constraint widgets let the application provide layout information for each child. This is a more powerful way to arrange children because it allows you to provide different rules for how each child will be laid out.

In an application, you create a constraint widget just as you would any other widget. But you don't just set resources of the constraint widget to specify how each child is laid out. You also set resources of each child of the constraint widget. Once any child widget is placed under the management of a constraint widget, the child suddenly has a special set of resources defined by the constraint widget, which controls its layout. As usual, these resources can be set from the app-defaults file, from any other resource database file, or in the application code.

We'll demonstrate how constraint widgets work by replacing the RowColumn widget in xrowcolumn (Example 3-1) with a Motif Form widget (a constraint widget). This example is called xform in the example source code. We won't show this entire code, because the differences from xrowcolumn1 are slight. The basic change is that all occurrences of rowColumn and RowColumn are changed to form and Form, respectively. The real difference between xrowcolumn and xform lies in the setting of resources.

The Form widget defines a number of constraint resources, which to the user appear to be resources of the child widgets managed by the Form. Looking at these resources gives you a good idea of the kinds of things that can be done with constraints.[25] Each edge of each child can be positioned in one of four ways. Different children can use different rules at the same time, and in fact different edges of the same child can be positioned using different rules. The following is a summary of what you can do with Form constraints. For details, see the Motif Reference Manual, because the complete behavior of Form is quite complicated.

For brevity, the following lists the various ways the left edge of a child can be positioned. You can substitute right, top, and bottom in any of the following.

  1. Relative to one edge of the Form widget. To do this, you set the XmNleftAttachment to XmATTACH_FORM. This child edge will then be positioned the default distance from the same edge of the Form. You can specify a different distance from the edge of Form with the XmNleftPosition or XmNleftOffset resources. The latter is a fixed distance that won't change after resizing.

  2. Relative to an edge of one of the other children. To do this, you set the XmNleftAttachment to XmATTACH_WIDGET, and set XmNleftWidget to the Widget ID of the other child you want this child to be next to. XmNleftWidget can only be set in the application code, not in resource files.

  3. To a percentage position within the Form widget (i.e., 50% would place the edge in the center of the Form widget, and upon resizing the edge would always be centered.) Set XmNleftAttachment to XmATTACH_POSITION, then set XmNleftPosition and XmNfractionBase so that XmNleftPosition /XmNfractionBase is the fraction of the width of the Form where the edge should be located.

  4. To a percentage position established by the child's initial position within the Form widget. For example, if the application code initially positioned the child edge 20 pixels horizontally from the Form origin, and XmNfractionBase is set to 100 to represent the initial width of the Form, then the child edge will always be kept at 20% of the width of the Form. Set XmNleftAttachment to XmATTACH_SELF, then set XmNfractionBase so that the current XmNx position divided by XmNfractionBase is the fraction of the width of the Form where the edge should be located. The difference between XmATTACH_POSITION and XmATTACH_SELF is only that the former requires the setting of XmNleftPosition, while the latter takes the initial position directly from the child.

Note that the XmNresizable resource is a constraint that controls whether a child is allowed to resize itself, or be resized by the application. It does not affect whether the Form widget will resize that widget--Form is always allowed to resize any of its children.

The Form also has a number of normal resources (that are set on the Form, as opposed to constraint resources that are set on its children) that control default distances and so on. One of these is XmNrubberPositioning, which governs how unattached edges of children behave.

OSF in Motif 1.2 has made more Form constraints settable from the app-defaults file. In particular, they have added a String-To-Widget converter which allows the setting of resources such as XmNleftWidget. Testing and debugging constraints is much easier when done from resource files since you avoid recompiling every time a new combination of Form constraints is to be tried. Example 3-3 shows the resource settings in a resource file.

*quit.label:	Quit
*Command.background:	orange

*pressme.label:	Press Me
*pressme.leftAttachment: XmATTACH_WIDGET
*pressme.leftWidget: quit

Note: Note that the order of creation of widgets affects the constraint resource settings that will position them. For the above resource file to work properly, quit must be created before pressme, because the resource setting *pressme.leftWidget: quit is evaluated when as the pressme widget is created. Since this resource setting refers to quit, other wise you will get the message:
X Toolkit Warning: Cannot convert string "quit" to type Widget
and the pressme widget will not be positioned properly.


Example 3-3 shows the resource settings in C code (xform.c) that position the two PushButton widgets within the Form. This technique for setting resources in code is described in Section 2.3.4, "To Hardcode or Not to Hardcode."

Example 3-3. xform: constraint resource settings added

}
    Widget quit, pressme;
      .
      .
               /* create quit widget here */
      .
    pressme = XtVaCreateManagedWidget("pressme", /* widget name */
            xmPushButtonWidgetClass,             /* widget class */
            form,                                /* parent widget */
            XmNleftAttachment, XmATTACH_WIDGET,  /* resource setting */
            XmNleftWidget, quit,                 /* resource setting */
            NULL);   /* terminate varargs list  of resource settings */
               /* (Must not create quit here, or above resource setting will fail.) */
      .
      .
      .
}


Notice that since these are constraints, these resources are being set on the quit child of Form, but the Form will actually use this information. It is actually an instruction for the Form widget about where to place the quit widget relative to the pressme widget. The effect of this resource setting is shown in Figure 3-5. Note that both widgets referenced in a constraint must be child widgets of the same constraint widget, in this case the Form widget. If they are not, Form will print a message at run time.

If you run this program, you can compare its behavior on resize with the behavior of xrowcolumn, and you can experiment with different resource settings for the Form widget.

Figure 3-5. Effect of the Form XmATTACH_WIDGET constraint resource

Both the Form and RowColumn widgets are able to resize the PushButton widgets in addition to or instead of moving them. In the Athena widget set there is a composite widget, Box, that moves but never resizes its children. Note that the difference between composite and constraint widgets is not their ability to resize children; it is that constraint widgets allow different layout rules for each child.

Without constraint settings, Form widgets pile up their children. This can also happen if you make an error in setting the constraints. Sometimes this can make it appear that one of the widgets has completely disappeared!

The Standard Motif Instance Hierarchy

Most Motif applications have a standard screen layout, which leads to a certain amount of boilerplate code in every application. This section describes that code. Figure 3-6 shows an example application, xmainwindow, which has this standard layout. Of course, in a real application the lower area would be more than an empty box.

Figure 3-6. xmainwindow: an application with standard Motif screen layout

Customarily, the first widget created in a Motif application (after the one created by XtAppInitialize()) is a MainWindow widget. A MainWindow is a geometry managing widget designed to manage a work area (often a custom window) with scrollbars, and optional menubar and Command widgets. In a drawing application, the drawing area would be the custom window, and MainWindow would automatically manage scrollbars that would allow the user to draw in an area larger than the application's total screen area. In our first example that includes a MainWindow, the work area will be simulated with a Frame widget, which is an empty box with a 3-D appearance.

Most Motif applications also have a menubar across the top of the main window. Visually, the menubar contains labels which indicate the titles of the menus available. In widget terms, each of these labels is a CascadeButton, which is similar to a PushButton except that CascadeButton is specifically designed to invoke a pulldown menu or dialog box instead of an application function. Two of the most common labels visible in a menubar are File and Help. The File button brings up a menu that includes the commands for reading and writing files, if any, and also the command to quit the application. According to the Motif style guide, the File button has the position at the far left in the menubar, and the Help button has the far right.[26] The Help button brings up a menu containing topics; selecting a topic brings up a dialog box which contains help text appropriate for the current situation.

A Command widget is another horizontal band across the application, just below the work area. A Command widget contains two windows, one that allows you to type in commands, and the other that shows you the history of prior commands. The Command widget is optional in a MainWindow and is less common than the menubar.

Motif Widget Creation Routines

All the previous examples have used XtVaCreateManagedWidget() to create widgets. As you may recall, you specify which type of widget you want by supplying an argument such as xmPushButtonWidgetClass. But Motif also provides its own functions for creating widgets. To create a PushButton, Motif's function is XmCreatePushButton(). Note, however, that Motif's function does not manage the widget, so you need to call XtManageChild() separately. (If you forget, the widget won't be visible.) Also, as you will see later, it is harder to set widget resources with the Motif widget creation functions. In the cases where the two techniques are identical, our examples stick with the basic Xt routine.[27]

In some cases, however, the Motif widget creation routines have code beyond just calling the Xt widget creation routine. Some set resources in particular ways. For example, XmCreateMenuBar() actually creates a RowColumn widget, but with some special resource settings. It is a lot more convenient to use XmCreateMenuBar() than to use the Xt widget creation routine and set the resources yourself.

As in the case of XmCreateMenuBar(), the Motif widget creation routines often have names that are different from the widgets they create. There is no such thing as a MenuBar widget. Remember this when you look up a widget's resources in the Motif reference manual. If you are unsure of the widget class, look on the page for the Motif widget creation function first.

Other Motif widget-creation functions actually create a combination of widgets. More will be said about this later.

Building a Main Window

With that introduction, we are ready to show you the code needed to implement a standard Motif MainWindow and its usual children. Example 3-4 shows a portion of xmainwindow.c, the code for implementing the application shown in Figure 3-6.

Example 3-4. xmainwindow.c: code for implementing standard Motif screen layout

/*
 *  xmainwindow.c - main window with help and quit
 */
/* Standard Motif include files: */
#include <Xm/Xm.h>  
#include <Xm/RepType.h>  
/*
 * Public header files for widgets used in this file.
 */
#include <Xm/MainW.h>       /* MainWindow */
#include <Xm/RowColumn.h>   /* for MenuBar (actually a RowColumn) */
#include <Xm/Frame.h>       /* Frame (simulated custom widget) */
#include <Xm/PushB.h>       /* PushButton (for menu buttons) */
#include <Xm/CascadeB.h>    /* CascadeButton (for menubar labels) */
#include <Xm/MessageB.h>    /* MessageBox dialog (for help) */
 /* callback functions defined here */
main(argc, argv)
int argc;
char **argv;
{
    XtAppContext app_context;
    Widget topLevel, mainWindow, menuBar, frame;
    Widget fileButton, fileMenu, quit, help, helpButton,
            helpMenu, helpBox;
    XtSetLanguageProc(NULL, (XtLanguageProc)NULL, NULL);
    topLevel = XtVaAppInitialize(
            &app_context,     /* Application context */
            "XMainWindow",    /* application class name */
            NULL, 0,          /* command line option list */
            &argc, argv,      /* command line args */
            NULL,             /* for missing app-defaults file */
            NULL);            /* terminate varargs list */
    /* create main window */
    mainWindow = XtVaCreateManagedWidget(
            "mainWindow",     /* widget name */
            xmMainWindowWidgetClass, /* widget class */
            topLevel,         /* parent widget */
            NULL);            /* terminate varargs list */
    /* register converter for setting tearoff menus from resource files */
    XmRepTypeInstallTearOffModelConverter();
    /* create menubar along top inside of main window */
    menuBar = XmCreateMenuBar(
            mainWindow, /* parent widget */
            "menuBar",  /* widget name */
            NULL,       /* no arguments needed */
            0);         /* no arguments needed */
    XtManageChild(menuBar);
    frame = XtVaCreateManagedWidget(
            "frame",            /* widget name */
            xmFrameWidgetClass, /* widget class */
            mainWindow,         /* parent widget */
            NULL);              /* terminate varargs list */
    /* Set MainWindow areas */
    XmMainWindowSetAreas (mainWindow, menuBar, NULL, NULL, NULL,
            frame);
      .
      .
      .
}


This code creates a MainWindow that contains two widgets: a menubar (which is actually a kind of RowColumn configured as a menubar via resources automatically set by the convenience function used to create the widget) and a Frame. Note that the parent arguments of functions that create the menubar and the Frame are both mainWindow.

We use XtVaCreateManagedWidget() to create the main window, but XmCreateMenuBar() to create the menubar, for the reasons described above. Notice that widget name and widget parent arguments are in a different order in the two functions.

The menubar must be managed with a separate call to XtManageChild(), because XmCreateMenuBar() does not manage it.

XmMainWindowSetAreas() tells the main window which of its children should be treated as the menubar (placed across the top of the application), and which as the work area (at the bottom, with scrollbars if needed). The work area in this case is the Frame widget. The three unused arguments, here set to NULL, allow you to tell MainWindow which widget is the Command window, and which widgets are the scrollbars that will control the work area.

You might ask, why does MainWindow need to be told which widget is which, since it has access to its list of child widgets? This is done mainly for added flexibility, so that you can switch work areas or other children.

The purpose of the scrollbar arguments in XmMainWindowSetAreas() is a common point of confusion. MainWindow is a subclass of ScrolledWindow. That means that MainWindow has all the features of ScrolledWindow, plus more. Therefore, MainWindow automatically creates its own scrollbars and uses these scrollbars to control the work area. Why, then, should XmMainWindowSetAreas() allow you to tell MainWindow which scrollbars to use, when MainWindow already has its own scrollbars and knows how to use them? The answer is that in some cases an application wants to provide its own code to handle the callbacks that connect its scrollbars with its work region. In practically all cases, however, an application can use the scrollbars created automatically by MainWindow or ScrolledWindow, even if it wishes to manually connect those widgets with the work area.

A resource setting is necessary in order to make MainWindow display scrollbars and use them to control the custom window. Here is the portion of the XMainWindow app-defaults file that applies to the code shown so far:

! Set initial size of application
*XmMainWindow.width:  200
! Frame will be bigger that application, thus requiring scrollbars
*XmFrame.width:  300
*XmFrame.height:  300
! Make MainWindow display scrollbars and use them to
! pan around in the frame.
*XmMainWindow.scrollingPolicy:  XmAUTOMATIC

The remaining part of the boilerplate code in a standard Motif application is to build the File menu and Help button in the menubar, that you see in xmainwindow. This is described in the next section.

Using Popups

A popup is a widget that is displayed on the screen for short periods, instead of being permanently visible.

The two most common kinds of popup widgets are menus and dialog boxes. Menus are familiar to most people, but dialog boxes are less so. A dialog box is a box on the screen that contains a message, or asks for user input, or both. Sometimes a dialog box provides a way to get input that is needed only occasionally and therefore doesn't deserve a place in the permanently visible user interface. Fitting into this category is the Motif file selection box, which is commonly used as a dialog box. At other times, a dialog box requires immediate response by the user before the application can continue. For example, many applications that write files are capable of displaying a dialog box that says “Warning: file exists. Do you wish to overwrite?”

A menu becomes visible when the user presses a mouse button while the pointer is in one of the CascadeButtons in the menubar. It becomes invisible again once the user has selected an item from the menu, released the button outside the menu, or moved into another CascadeButton in the menubar. Dialog boxes usually become visible either because a button such as Help was clicked on by the user, or because the program needs occasional user input that is best supplied through a temporary window instead of as a permanently visible part of the application user interface. Dialog boxes become invisible when the user clicks on the OK or Confirm button in the dialog box, or provides the input that the dialog box is requesting.

Popups are not a kind of widget, but rather a way of using widgets. Any widget can be used as a popup. However, some classes of widgets are more commonly used as popups, and Motif has simplified the interface by providing routines that not only create these widgets, but also prepare them for use as popups. For example, Motif provides XmCreatePopupMenu() for creating an empty popup menu. Internally, this routine creates a special parent widget called an OverrideShell, and then creates a RowColumn widget inside it, with special resource settings. It is then your job to create widgets (or gadgets) to appear in the menu.

A Motif menubar, as described in Section 3.3, "The Standard Motif Instance Hierarchy," usually contains a File menu and a Help button that brings up a dialog box. We'll discuss creating the File menu first, and then the Help dialog box.

Creating a Basic Menu

Motif provides three different types of menu: Popup, Pulldown, and Option. Popup menus have no on-screen presence until you press a certain button or key-button combination while in a certain widget. Pulldown menus and Option menus have buttons on the screen--when you press the button the menu appears below (for Pulldown) or over (for Option) the button. Option menus remember the most recent previous selection. RadioBox and CheckBox can also be thought of as kinds of menus. The menus that are invoked from the menubar are Pulldown menus.

It's also worth mentioning that Motif 1.2 supports tear-off menus. This allows the user to move any menu which has its XmNtearOffModel resource set to TEAR_OFF_ENABLED from the location where it first appears anywhere on the screen, and semi-permanently post it there. This makes it quicker to use commonly-used menus (especially since the menus mnemonics--keyboard shortcuts--are only active while a menu is displayed.

The application can call XmRepTypeInstallTearOffModelConverter() to support setting of the XmNtearOffModel resource from resource files. This allows menus to be torn off and posted permanently.

Figure 3-7 shows xmainwindow with the File pulldown menu displayed. The techniques for creating all three types of menus are very similar, but we'll reserve discussion of the other types until "Chapter 13, Menus, Gadgets, and Cascaded Popups."

Figure 3-7. xmainwindow with the File pulldown menu displayed

Creating a pulldown menu is a four-step process:

  1. Create a CascadeButton as a child of the menubar. This will be the button in which the user will click in order to pull down the menu.

  2. Create an empty menu with XmCreatePulldownMenu() as a child of the menubar. This actually creates a RowColumn with certain resource settings.

  3. Create PushButtons or PushButtonGadgets for each menu item, as children of the pulldown menu.

  4. Tell the CascadeButton that just created the ID of the menu, it should pop up. This can't be done until step 2 is done.

In Motif, the RowColumn widget created by XmCreatePulldownMenu() is called a menu pane, and the buttons contained by that menu are called menu items.

This process sets up the popup widget, but does not put it on the screen. Somewhere in your code you need to call XtManageChild() to pop up the widget. This is typically done in the XmNactivateCallback callback routine of the CascadeButton. Motif takes care of popping down the menu at the appropriate times according to its conventions.

Example 3-5 shows the code needed to add a File menu containing only a quit button to an existing menubar.

Example 3-5. Adding a File menu to an existing menubar

/*
 * quit button callback function
 */
/*ARGSUSED*/
void Quit(w, client_data, call_data)
Widget w;
XtPointer client_data, call_data;
{
    exit(0);
}
main(argc, argv)
int argc;
char **argv;
{
    Widget fileMenu, fileButton, quit;
     .
     .
     .
    /*
     *  CREATE FILE MENU AND CHILDREN
     */
    /* create the File button in the menubar */
    fileButton = XtVaCreateManagedWidget(
            "fileButton",                /* widget name */
            xmCascadeButtonWidgetClass,  /* widget class */
            menuBar,                     /* parent widget */
            NULL);                       /* terminate varargs list */
    /* create menu (really a Shell widget
     * and RowColumn widget combo) */
    fileMenu = XmCreatePulldownMenu(
            menuBar,                     /* parent widget */
            "fileMenu",                  /* widget name */
            NULL,                        /* no argument list needed */
            0);                          /* no argument list needed */
    /* Notice that fileMenu is intentionally NOT managed here */
    /* create button in menu that exits application */
    quit = XtVaCreateManagedWidget(
            "quit",                     /* widget name */
            xmPushButtonWidgetClass,    /* widget class */
            fileMenu,                   /* parent widget */
            NULL);                      /* terminate varargs list */
    /*
     * Specify which menu the fileButton will pop up.
     */
    XtVaSetValues(fileButton,
            XmNsubMenuId, fileMenu,
            NULL);
    /* arrange for quit button to call function that exits. */
    XtAddCallback(quit, XmNactivateCallback, Quit, 0);
      .
      .
      .
}

This code sample exactly matches the four steps listed above for creating a menu. First it creates the button that pops up the menu, followed by the menu pane itself, followed by a button that will appear in the menu. Finally, it tells fileButton that it should pop up fileMenu whenever activated.

There are several important things to remember about this example. Note that fileMenu is not managed after it is created, unlike the widgets in all previous examples. Motif itself will manage the widget when it comes time to pop it up. Also note how, by setting the XmNsubMenuId resource, we connect the fileButton widget with fileMenu. Finally, note that both fileButton and fileMenu are children of the menubar, while quit is a child of fileMenu. This instance hierarchy is important.

Creating a Basic Dialog Box

Motif has even more different types of dialog boxes than it has menus. Luckily, they are also more uniform in the way they are created! This section will demonstrate creating a dialog box to provide help. "Chapter 13, Menus, Gadgets, and Cascaded Popups," describes the variations in how to use other types of Motif dialog boxes. Figure 3-8 shows xmainwindow with the Help dialog box displayed.

Figure 3-8. xmainwindow with the Help dialog box displayed

This dialog box is a MessageBox widget. Motif doesn't provide a dialog widget that is ideal for providing help, since MessageBox provides Help buttons and Cancel buttons that are normally not needed in Help dialog boxes.

A Help button is created just like the fileButton in the previous example. It is a CascadeButton created as a child of the menubar. The dialog box that will contain the text is created with XmCreateMessageDialog().

Example 3-6 shows the code required to add a Help button and dialog box to xmainwindow. Note that the OSF/Motif Style Guide specifies that the menubar can contain only menus. Therefore, we cannot just put a help button on the menubar. Instead, we need to create a help menu, and then put the help button on the menu. In a real application, the help menu might list various topics for which help is available.

Example 3-6. xmainwindow: creating a Help button and Help dialog box

  .
  .
/*
 * callback to pop up help dialog widget (or any other dialog)
 */
/*ARGSUSED*/
void ShowHelp(w, client_data, call_data)
Widget w;
XtPointer client_data;
XtPointer call_data;
{
    Widget dialog = (Widget) client_data;
    XtManageChild(dialog);
}
  .
  .
  .
main(argc, argv)
int argc;
char **argv;
{
    XtAppContext app_context;
    Widget topLevel, mainWindow, menuBar, frame;
    Widget fileButton, fileMenu, quit, helpButton, helpMenu,
            help, helpBox;
    Widget temp;
      .
      .
      .
    /*
     *  CREATE HELP BUTTON AND BOX
     */
    /* create button that will bring up help popup */
    helpButton = XtVaCreateManagedWidget(
            "helpButton",                /* widget name */
            xmCascadeButtonWidgetClass,  /* widget class */
            menuBar,                     /* parent widget */
            NULL);                       /* terminate varargs list */
    /* tell menuBar which is the Help button
     * (will be specially positioned. */
    XtVaSetValues(menuBar,
            XmNmenuHelpWidget, helpButton,
            NULL);
    /* create menu (really a Shell widget with a RowColumn child) */
    helpMenu = XmCreatePulldownMenu(
            menuBar,    /* parent widget */
            "helpMenu", /* widget name */
            NULL,       /* terminate argument list (none needed) */
            0);         /* terminate argument list (none needed) */
    /* create help button in the menu */
    help = XtVaCreateManagedWidget(
            "helpMenu"  /* widget name */
            XmPushButtonWidgetClass,  /* widget name */<
            helpMenu,    /* parent widget */
            NULL);      /* terminate argument list (none needed) */
    /* Specify which menu helpButton will pop up */
    XtVaSetValues(helpButton,
            XmNsubMenuId, helpMenu,
            NULL);
    /* create popup that will contain help */
    helpBox = XmCreateMessageDialog(
            help,    /* parent widget */
            "helpBox",  /* widget name */
            NULL,       /* no arguments needed */
            0);         /* no arguments needed */
    temp = XmMessageBoxGetChild (helpBox, XmDIALOG_CANCEL_BUTTON);
    XtUnmanageChild (temp);
    temp = XmMessageBoxGetChild (helpBox, XmDIALOG_HELP_BUTTON);
    XtUnmanageChild (temp);
    /* arrange for help button to pop up helpBox */
    XtAddCallback(help,          /* widget that takes user input */
            XmNactivateCallback, /* callback resource */
            ShowHelp,            /* callback function */
                                 /* data passed as client_data
                                  * to callback function */
      .
      .
      .
}


As shown in Example 3-6, the first step in creating a Help feature is to create a CascadeButton widget as a child of the menubar. Then, you set the XmNmenuHelpWidget resource of the menubar to tell the menubar that this particular CascadeButton is the Help button. This is necessary because the menubar treats the Help button specially; it is a Motif convention that the Help button appears at the extreme right of the menubar. Third, you create the dialog box with XmCreateMessageDialog(). Finally, you register a callback function, here called ShowHelp, with the CascadeButton widget. ShowHelp simply calls XtManageChild() to put the dialog widget on the screen. (If you have several dialog widgets, they can all share this callback function because it doesn't have any code that depends on any particular type of dialog widget.)

If you get a run-time error that says “Error: Attempt to manage a child when parent is not Composite,” it may mean that you have used XmCreateMessageBox() instead of XmCreateMessageDialog(). XmCreateMessageBox() creates a widget with the same appearance, but it does not create the Shell widget parent that allows the message box to be used as a popup. This can also happen with file selection boxes, radio boxes, selection boxes, and check boxes.

The parent of the dialog box can be any widget in the application, since it is only used to determine on which screen to create the dialog. However, it is useful to use some consistent system of parenting, to make it easier to keep track of the purpose of each dialog. In this case, the parent is the CascadeButton widget that pops up the dialog, getHelp.

XmCreateMessageDialog() actually creates two widgets for you, an OverrideShell and a MessageBox as its child. The OverrideShell handles communication with the window manager, and the MessageBox displays the help text and creates the OK button that pops down the dialog. By default, a MessageBox (and most Motif dialogs) contains three buttons, that read by default OK, Cancel, and Help. Since we are providing help, we have no need for the Cancel and Help buttons. Unfortunately, Motif does not provide a dialog widget with just the OK button. But it is easy enough to take the Cancel and Help widgets off the dialog by unmanaging them. We get their IDs with the Motif function XmMessageBoxGetChild(), and then unmanage them with XtUnmanageChild(). The MessageBox widget then automatically centers the OK button.

Note that the help text is not specified anywhere in the application code. At least until the application is final, it can be specified in the app-defaults file. Example 3-7 shows this portion of the app-defaults file for xmainwindow.

Example 3-7. XMainWindow: setting help text in the app-defaults file

*helpBox.messageString:  This is help text. \n\
But it is only a few words. \n\
If you really need help, \n\
I'm afraid you'll have to read the manual.
! The following used if the window manager titles the dialog.
*helpBox.dialogTitle:  Help


By ending lines with \n\, you can use a multi-line string as a resource value.

Popup Window Hierarchy

You may notice that dialog boxes can extend outside the application. This is because their Shell widgets are child windows of the root window, not the top-level window of the application, no matter which widget you supply as parent. By default in Xt, a child of the root is positioned at the top-left corner of the screen. However, Motif automatically positions most menus and dialog boxes in a suitable location in or over the application before popping them up. Dialog boxes or menus that are not triggered by a mouse button press, though, need to be positioned by the application. How to do this will be described in "Chapter 13, Menus, Gadgets, and Cascaded Popups."

Figure 3-9 shows the widget instance hierarchy of xmainwindow, and its X window hierarchy. Note that the only difference between the two is the parentage of the dialog and menu widgets.

Figure 3-9. The widget instance and X window hierarchies are different only for popups

Note that when you create a PulldownMenu, you are actually creating a Shell widget with the menu as its child. The same is true of MessageDialog. Each separate window hierarchy has a shell widget at the top.

If you have a popup widget that might never be used, or whose characteristics are not known until just before it is popped up, you can create the popup shell and widget just before popping it up, in the callback function or action that pops up the widget. This technique makes the startup time of the application marginally faster, but slows the pop up time the first time the popup is used.

Popups, and menus in particular, will be described in much more detail in "Chapter 13, Menus, Gadgets, and Cascaded Popups."

More About Callbacks

As you may recall, a callback is a function that your application wants called in response to a certain occurrence in a certain widget. The application simply declares the callback function and then calls XtAddCallback(). While "Chapter 2, Introduction to the X Toolkit," discussed the concept of callbacks and demonstrated the most common use, it did not completely describe all the useful tricks. You can pass application data to callback functions. You can arrange for more than one callback function to be called (in a particular order) when the callback is triggered, and add and remove functions from this list at will. You can declare callbacks statically using a callback list instead of calling XtAddCallback() or XtAddCallbacks().

Passing Data to Callback Functions

A callback function is called with three arguments: widget, client_data, and call_data.

The widget argument is necessary if you have registered the same callback function for two widgets, and you need to know which widget called the function. It also may be used in routine ways, such as for the argument of macros. The application determines the use of client_data, whereas the widget determines the use of call_data.

The client_data Argument

You may pass a single piece of data as the client_data argument, or pass a pointer to a structure containing several pieces of data. You may ask, “Why bother with passing data to a callback when I can just declare a global variable?” For one thing, it is a general principle of good C coding to use a global variable only where the variable is needed in several functions and the arguments would otherwise prove unwieldy. Secondly, using the client_data argument makes clear the purpose of your variable, whereas it is difficult to trace where a global variable is set or referenced. However, if you find it necessary to change the client_data argument in a number of functions in the application, you will need a global variable anyway, and there is nothing that says you must use client_data (but be sure to document what you are doing!).

Example 3-8 demonstrates how to pass a single piece of data into a callback function.

Example 3-8. Passing a single value to a callback function

/*ARGSUSED*/
void PressMe(w, client_data, call_data)
Widget w;
XtPointer client_data, call_data;
{
    fprintf(stderr, "%s, client_data);
}
main(argc, argv)
int argc;
char **argv;
{
    Widget pressme;
      .
      .
    /* XtAppInitialize, create pressme widget */
      .
      .
    /* last argument is client_data */
    XtAddCallback(pressme, XmNactivateCallback, PressMe, "Thanks");
      .
      .
}


Example 3-9 demonstrates passing a pointer to a structure into a callback function. XtPointer is a generic pointer type with an implementation-dependent definition. It is used just like the standard C-type caddr_t.

Example 3-9. Passing a pointer to a structure to a callback function

typedef struct {
    String name;
    String street;
    String city;
} app_stuff;
/*ARGSUSED*/
void PressMe(w, client_data, call_data)
Widget w;
XtPointer client_data;   /* to be cast to app_stuff */
XtPointer call_data;
{
    /* cast required in ANSI C */
    app_stuff *address = (app_stuff) client_data;
    fprintf(stderr, "%s\n%s\n%s\n", address->name, address->street,
            address->city);
}
main(argc, argv)
int argc;
char **argv;
{
    XtAppContext app_context;
    Widget box, quit, pressme, topLevel;
    static app_stuff stuff = {
        "John Doe",
        "1776 Constitution Way",
        "Philadelphia, PA 01029"
    };
      .
      .
    /* XtVaAppInitialize, create pressme widget */
      .
      .
    XtAddCallback(pressme, XmNactivateCallback, PressMe, &stuff);
      .
      .
}


Note that two coding conventions are required by ANSI C and should be followed, even though many compilers will not complain if they are not used. The first is that all three arguments of the callback must be declared, even if the trailing ones are not used. Many compilers will work fine if unused trailing arguments are omitted from the definition of the callback function. The second is that many compilers will automatically cast XtPointer to the type you want if you declare the callback function arguments using that type. The cast on the first line of the PressMe callback is required by ANSI C. Example 3-10 shows a definition of the above callback function that works on many compilers but is not ANSI C conformant. Compare Example 3-10 with Example 3-9.

Example 3-10. ANSI non-conformant callback function definition

/*ARGSUSED*/
void PressMe(w, address /* call_data omitted */)
Widget w;
app_stuff *address;     /* ANSI non-conformant cast */
/* third arg omitted - ANSI non-conformant */
{
    fprintf(stderr, "%s\n%s\n%s\n, address->name, address->street,
            address->city);
}


The call_data Argument

The call_data argument is passed in to the callback function from the widget itself. This argument's value is described in the documentation for the widget class unless it is not used. In the Motif widgets, this argument is usually a structure named after the widget that is performing the callback. For example, the structure passed by the PushButton widget is XmPushButtonCallbackStruct. Example 3-11 shows this structure.

Example 3-11. The XmPushButtonCallbackStruct structure

typedef struct {
    int     reason;
    XEvent  *event;
    int     click_count;
} XmPushButtonCallbackStruct;

Every Motif callback structure contains reason and event fields. Remember that widgets often have several callback resources. The reason field tells you which callback resource was triggered. For example, the PushButton widget has three callback resources, each of which has a different reason value:

  • XmNactivateCallback, a commonly used resource, triggered when the user presses and releases the first mouse button on the PushButton. The callback function invoked is passed a call_data structure with the reason field set to XmCR_ACTIVATE.

  • XmNarmCallback resource, which is triggered when the first mouse button is pressed and held on the PushButton. The callback function invoked is passed a call_data structure with the reason field set to XmCR_ARM.

  • XmNdisarmCallback resource, which is triggered when the first mouse button is released outside the PushButton. The callback function invoked is passed a call_data structure with the reason field set to XmCR_DISARM.

Application callback functions rarely have to read the reason field since separate functions are usually registered for the different callback resources. In other words, an application that registers callback functions for all three of PushButton's callback resources would not need to use the reason field in any of the callback functions unless it registered the same function for two or more of the callbacks.

Most callback functions don't use the event field passed to the callback function either, but it is potentially useful, since it contains a lot of data about the user event that triggered the callback. If a button press or release triggered the callback, this event structure will be an XButtonEvent structure. This structure can be passed to XmMenuPosition() to position a menu created with XmCreatePopupMenu(), as will be described in "Chapter 13, Menus, Gadgets, and Cascaded Popups." If the triggering event was a key press or release, an XKeyEvent would be passed. In some cases it may be useful to process this event to determine which key was pressed. Remember that due to Motif's keyboard interface, most callback functions can be invoked by either a keyboard or a mouse command. When this is the case, your callback function must be written so that it can handle either type of event.

Other widget sets do not use the call_data argument as heavily as Motif. For example, the Athena Widgets rarely use it at all.

Callback Lists

You may register any number of callback functions for a single widget callback resource. In other words, when a callback is triggered by a user event, all the functions in the current callback list for that callback for that widget instance will be called, one at a time. Multiple callback functions are not needed in many applications, but can be useful if applied carefully.

There is no guarantee that the functions on a callback list will be called in the specified order. Therefore, each callback function must be independent of all other callback functions on the list. This dramatically reduces the usefulness of multiple callback functions.

Remember that most widgets also have more than one callback list, each triggered by a different occurrence. What we are talking about here is that you can register a series of functions to be called in response to one occurrence.

Let's take an example of how a callback list might be used. Perhaps you have the functions A, B, and C, and you need to be able to call them separately in response to events in three different PushButton widgets, or to call all of them in response to events in a fourth PushButton widget. How should we structure the callback for the fourth PushButton widget? The first approach is to have a callback list including the functions A, B, and C. This is preferred if A, B, and C can be called in any order, since you can pass a different piece of data into each routine. The other approach is to register a single callback, function D, that calls A, B, and C. In this case you are assured that A, B, and C will be called in the desired order.

You can also call the same function more than once in a callback list.


Note: Never use XtRemoveAllCallbacks(). Widget sets such as Motif add their own callback functions to many of the widgets, and depend on these functions to operate properly.

One way to add more than one callback function is to call XtAddCallback() more than once. Another way is to call XtAddCallbacks(), which takes an XtCallbackRec array as an argument. This array is usually initialized at compile time, as shown in Example 3-12. The final NULL, NULL entry terminates the list. (This particular list registers the functions do_A and then_B, and passes them both 0 as client_data.)

Example 3-12. Initializing a callback list

XtCallbackRec quit_callback_list[]={
    {do_A, 0},
    {then_B, 0},
    {(XtCallbackProc) NULL, (XtPointer) NULL}
};

This form of XtCallbackRec list can also be used to replace a callback list with XtSetValues() (but not to get a callback list, because Xt compiles the list into an internal form). An XtCallbackRec list can also be used to register one or more callbacks when creating a widget.

Application Resources

You already know that widgets declare resources that can be configured through the resource mechanism. In addition, the application itself may have variables that it wants the user to be able to configure from the resource databases, even though these variables have nothing to do with widgets. These are called application resources.

Application resources are just like widget resources except that they apply to application code, not to the widgets it creates.

There are three steps for adding application resources. You must:

  1. Create an application data structure containing the variables to be set via the resource mechanism.

  2. Create a resource table defining the type and default value for each variable.

  3. Call XtGetApplicationResources() with pointers to the application data structure and resource table as arguments. When this function returns, the application data structure will contain the current settings from the resource databases.

To demonstrate how to get application resources, we will jump ahead and describe a portion of the code for the xbitmap4 bitmap editor example described in "Chapter 4, An Example Application." Because this application draws into a widget using Xlib, it needs two colors with which to draw. The bitmap editor also allows the user to specify the dimensions of the bitmap in cells, and the size of each cell in pixels. And, for general utility, it includes a debug flag that can be set in a resource file to invoke or ignore debugging code without recompiling.

The Application Data Structure

The structure type that contains all the application variables to be set through resources is commonly called AppData. Once this structure type is declared, memory can be allocated for a structure called app_data. Example 3-13 shows the code that defines the structure type and then allocates memory for the actual structure.

Example 3-13. xbitmap: getting application resources

typedef struct {
        Pixel copy_fg;
        Pixel copy_bg;
        int pixmap_width_in_cells;
        int pixmmap_height_in_cells;
        int cell_size_in_pixels;
        Boolean debug;
} AppData;

AppData app_data;


As usual in C, the members of the app_data structure will be referenced throughout the application code using the dot format. For example, the value of the debug field of app_data will be referenced with app_data.debug.

The Resource List

The resource list looks complicated, but it is easy to understand and even easier to write because it conforms to a consistent pattern. Each field in the application data structure has an entry in the resource list. Each resource entry in turn has seven fields, which describe the name of the resource, its type and default value, and various other information.

The resource list controls Xt's value conversion facilities. Since user resources are always strings, and application data structure fields can be any type, a conversion may have to take place. Xt has built-in converters to convert from string to most common types needed by applications. These types are called representation types by Xt, and they are indicated by constants starting with XmR in Motif, or XtR in standard Xt. The representation type of a string (char *) is XmRString. A Motif compound string is XmRXmString. You control the conversion simply by specifying a resource as a certain representation type in the resource list.

It is possible to represent the same value in several different representation types. For example, a color may be represented as an ASCII color name such as “blue,” as a structure containing Red, Green, and Blue values, or as a pixel value (an index into a colormap). Also note that a representation type is different from a C-Language type. It is also possible for two different representations of something to both use the same C-Language type. For example, two hypothetical representation types might be XtRInch and XtRMeter. Both represent distances and both would probably be integers or floating point numbers, but each would have a different value for the same distance.

See "Chapter 10, Resource Management and Type Conversion," for a description of the standard representation types, as well as information on how to write your own converter routine.

Example 3-14 shows the resource list for xbitmap4, followed by a description of each of the fields in each entry.

Example 3-14. The resource list for xbitmap4

/*
 * The following could be placed in a "xbitmap.h" file.
 */
#define XtNdebug "debug"
#define XtCDebug "Debug"
#define XtNpixmapWidthInCells "pixmapWidthInCells"
#define XtCPixmapWidthInCells "PixmapWidthInCells"
#define XtNpixmapHeightInCells "pixmapHeightInCells"
#define XtCPixmapHeightInCells "PixmapHeightInCells"
#define XtNcellSizeInPixels "cellSizeInPixels"
#define XtCCellSizeInPixels "CellSizeInPixels"

static XtResource resources[] = {
    {
        XtNforeground,
        XtCForeground,
        XtRPixel,
        sizeof(Pixel),
        XtOffsetOf(AppData, copy_fg),
        XtRString,
        XtDefaultForeground
    },
    {
        XtNbackground,
        XtCBackground,
        XtRPixel,
        sizeof(Pixel),
        XtOffsetOf(AppData, copy_bg),
        XtRString,
        XtDefaultBackground
    },
    {
        XtNpixmapWidthInCells,
        XtCPixmapWidthInCells,
        XtRInt,
        sizeof(int),
        XtOffsetOf(AppData, pixmap_width_in_cells),
        XtRImmediate,
        (XtPointer) 32,
    },
    {
        XtNpixmapHeightInCells,
        XtCPixmapHeightInCells,
        XtRInt,
        sizeof(int),
        XtOffsetOf(AppData, pixmap_height_in_cells),
        XtRImmediate,
        (XtPointer) 32,
    },

    {
        XtNcellSizeInPixels,
        XtCCellSizeInPixels,
        XtRInt,
        sizeof(int),
        XtOffsetOf(AppData, cell_size_in_pixels),
        XtRImmediate,
        (XtPointer) 30,
    },
    {
        XtNdebug,
        XtCDebug,
        XtRBoolean,
        sizeof(Boolean),
        XtOffsetOf(AppData, debug),
        XtRImmediate,
        (XtPointer) False,
    },
};


A resource list entry has the following fields:

  • The first two fields of each entry are the name and class of the resource; both strings. These are specified as symbolic constants to improve compile-time checking, and should be selected from <Xm/Xm.h> if any there have an appropriate name, or they can be defined in your application's own include file. For the debug resource entry, the resource name and class strings would be “debug” and “Debug”, respectively defined in the application's include file as XtNdebug and XtCDebug. Note that in this case we have chosen to spell the symbols XtN instead of XmN. This is just to highlight the fact that these are not built-in Motif symbols. You can use XtN, XmN, or your own prefix.

  • The third field is the representation type. A representation type is a symbolic constant, beginning with XtR or XmR, that defines the data type of a resource. Because user resource specifications are always in the form of strings while the actual resources may be of any type, Xt uses resource converters to convert the string representation to the actual target type. If necessary, you can define your own representation type in your application include file, but you will also have to provide Xt with a way to convert from string to this type with a type converter function, as described in "Chapter 10, Resource Management and Type Conversion". Table 3-1 lists the correspondence between some of the representation types defined in <Xm/Xm.h> and actual C data types and structures.

  • The fourth field, the size of the representation, is specified using the C macro sizeof with the actual C type as an argument. For example, when the representation type is XmRBoolean, the size field is sizeof(Boolean).

  • The fifth field identifies the place in your application's data structure where Xt is to place the converted value. In Example 3-15, the structure is called AppData. This field for the debug resource entry is specified using the XtOffsetOf() macro as XtOffsetOf(AppData, debug). Note that the field name in the application data structure is often the same as the resource name, except with all word transitions marked with an underscore instead of a capital letter.

  • The sixth field specifies the representation type of the default value (the seventh field). Xt has routines for converting data between types. They are used to translate strings from the resource databases into certain representation types. Therefore, you can specify the default value as a string (among other things) and if the default value is needed Xt will convert it to the representation type you specified in field three. If the representation type in field three is not standard in Xt or Motif, you will have to write the conversion routine that converts from XmRString to that representation, as described in "Chapter 10, Resource Management and Type Conversion." You can also use the special constant XmRImmediate here, which indicates that the value in field seven can be used without conversion. Using XmRImmediate should lead to faster startup.

  • The seventh field is the default value, using the representation type specified in field six. This default value will be set into the application data structure field if there is no setting for this resource in the resource database.

Table 3-1 lists Motif and standard Xt resource types and the C data types they represent.

Table 3-1. Motif Resource Type Strings

Motif Resource Type

Standard Xt Resource Type

Data Type

XmRAcceleratorTable

XtRAcceleratorTable

XtAccelerators

XmRBoolean

XtRBoolean

Boolean

XmRBool

XtRBool

Bool

XmRCallback

XtRCallback

XtCallbackList

XmRColor

XtRColor

XColor

XmRCursor

XtRCursor

Cursor

XmRDimension

XtRDimension

Dimension

XmRDisplay

XtRDisplay

Display*

XmRFile

XtRFile

FILE*

XmRFloat

XtRFloat

float

XmRFont

XtRFont

Font

XmRFontStruct

XtRFontStruct

XFontStruct*

XmRFunction

XtRFunction

(*)()

XmRInt

XtRInt

int

XmRPixel

XtRPixel

Pixel

XmRPixmap

XtRPixmap

Pixmap

XmRPointer

XtRPointer

XtPointer

XmRPosition

XtRPosition

Position

XmRShort

XtRShort

short

XmRString

XtRString

char*

XmRTranslationTable

XtRTranslationTable

XtTranslations

XmRUnsignedChar

XtRUnsignedChar

unsigned char

XmRWidget

XtRWidget

Widget

XmRWindow

XtRWindow

Window

XmRXmString

XmString


Getting the Resources

Once the application data structure and resource list are set up, you pass pointers to them to XtGetApplicationResources(), just after calling XtAppInitialize(). XtGetApplicationResources() will search the databases for any matching resource settings and set the fields in the application data structure.

The last requirement is that you check the values specified by the user to make sure they are acceptable. Example 3-15 shows these two steps from xbitmap4.

Example 3-15. Calling XtGetApplicationResources and checking values

     .
     .
     .

AppData app_data;
     .
     .
     .

main(argc, argv)
int argc;
char *argv[];
{
    XtAppContext app_context;
    Widget toplevel, vpane, buttonbox, quit, output;
        .
        .
        .

    /* call XtAppInitialize here */

    XtVaGetApplicationResources(toplevel,
            &app_data,
            resources,
            XtNumber(resources),
            /* varargs list here for making
             * application resources
             * non-user-configurable */
            NULL);        /* terminate varargs list */

    /*
     * We must check the application resource values here.
     * Otherwise, user could supply out of range values.
     * Conversion routines do this automatically, so
     * colors are already checked.
     */
    if ((app_data.pixmap_width_in_cells > MAXBITMAPWIDTH) ||
            (app_data.pixmap_width_in_cells < MINBITMAPWIDTH) ||
            (app_data.pixmap_height_in_cells > MAXBITMAPHEIGHT) ||
            (app_data.pixmap_height_in_cells < MINBITMAPHEIGHT)) {
        fprintf(stderr, "xbitmap: error in resource settings:",
                "bitmap dimension must be between %d and %d cells\n",
                MINBITMAPWIDTH, MAXBITMAPHEIGHT);
        exit(1);
    }

    if ((app_data.cell_size_in_pixels < MINCELLSIZE) ||
            (app_data.cell_size_in_pixels > MAXCELLSIZE)) {
        fprintf(stderr, "xbitmap: error in resource settings:",
                "cell size must be between %d and %d pixels\n",
                MINCELLSIZE, MAXCELLSIZE);
        exit(1);
    }
        .
        .
        .
}


Because Xt automatically converts the user's color specifications (such as “blue”) into the form required by X, it warns the user when an unknown color is specified.[28] Therefore, your code doesn't need to check this kind of validity for values that Xt or Motif converts. However, you may need to check for other kinds of validity of the same values. For example, you may wish to check that two colors are not the same by comparing values after conversion. In this case, only the bitmap dimensions and cell size are checked, since they are critical to the application's operation.

Command-line Options

You already know that Xt automatically customizes widgets according to the resource database, but this is not the whole story. Users often expect command-line arguments for the most important aspects of an application. By default, XtAppInitialize() understands only a minimal set of command-line arguments; more need to be added so that application resources and certain widget resources can be set from the command line.

There is no point, however, in trying to make every widget resource settable from the command line, because that's the purpose of the resource database. You should concentrate on the resources that the user is most likely to want to have different between two simultaneous instances of the application (since it is difficult to arrange this with the resource database).

Before describing how to define your own command-line arguments, we need to describe what command-line arguments XtAppInitialize() already handles.

Standard Command-line Options

First of all, XtAppInitialize() recognizes the -xrm option for setting any widget or application resource. For example:

spike%  xhello -xrm '*background: blue'

This command-line argument sets all widget resources and application resources that are named background to blue. This option style is awkward. Not only is it long, but csh users must quote the string with right-handed single quotes so that the asterisk (*) is not interpreted by the shell.

XtAppInitialize() also understands some command-line options that were considered so basic that they should be the same for all applications. The above -xrm command line can be replaced by:

spike%  xhello -background blue

or:

spike%  xhello -bg blue

XtAppInitialize() also understands any unique abbreviation of an option name, such as:

spike%  xhello -backg blue

These resources will work with any application written using the X Toolkit--with any widget set. Try the command line above if you have a color screen. If not, try specifying a different font, using the -fn or -font option.

Table 3-2 lists the complete set of standard options.[29] Resources starting with a dot rather than an asterisk indicate that that option affects only the application's top-level Shell.

Table 3-2. Standard Command-line Parameters

Option

Resource

Value

Sets

-bg

*background

Next argument

Background color

-background

*background

Next argument

Background color

-bd

*borderColor

Next argument

Border color

-bw

.borderWidth

Next argument

Width of border in pixels

-borderwidth

.borderWidth

Next argument

Width of border in pixels

-bordercolor

*borderColor

Next argument

Color of border

-display

.display

Next argument

Server to use

-fg

*foreground

Next argument

Foreground color

-fn

*font

Next argument

Font name

-font

*font

Next argument

Font name

-foreground

*foreground

Next argument

Foreground color

-geometry

.geometry

Next argument

Size and position

-iconic

.iconic

"on"

Start as an icon

-name

.name

Next argument

Name of application

-reverse

*reverseVideo

"on"

Reverse video

-rv

*reverseVideo

"on"

Reverse video

+rv

*reverseVideo

"off"

No Reverse Video

-selectionTimeout

.selectionTimeout

Null

Selection timeout

-synchronous

*synchronous

"on"

Synchronous debug mode

+synchronous

*synchronous

"off"

Synchronous debug mode

-title

.title

Next argument

Title of application

-xrm

value of argument

Next argument

Depends on argument


Note that many of these command-line options set the resource of every widget in the application to the same value. A few of them set the resources only of the application's top-level Shell widget.

Also note that there is no standard command-line option for the XtNfontSet resource (new in R5). You must use -xrm to set this on the command line.

Defining Your Own Command-line Options

Supplying your own command-line options allows you to simplify the customization of your application. It also allows the user to customize things that are difficult with the resource database (such as setting different values for the same resource in two instances of the application).

You make XtAppInitialize() understand additional command-line options by initializing a XrmOptionDescRec structure (called the options table) and passing it as an argument to XtAppInitialize(). Example 3-16 shows the code that implements command-line options for the application resources added in Example 3-14.

Example 3-16. xbitmap: specifying command-line options

/* include files, etc. */
      .
      .
      .
#define MINBITMAPWIDTH  2
#define MAXBITMAPWIDTH  1000
#define MINBITMAPHEIGHT  2
#define MAXBITMAPHEIGHT  1000
#define MINCELLSIZE  4
#define MAXCELLSIZE  100

static XrmOptionDescRec options[] = {
    {"-pw",           "*pixmapWidthInCells",  XrmoptionSepArg, NULL},
    {"-pixmapwidth",  "*pixmapWidthInCells",  XrmoptionSepArg, NULL},
    {"-ph",           "*pixmapHeightInCells", XrmoptionSepArg, NULL},
    {"-pixmapheight", "*pixmapHeightInCells", XrmoptionSepArg, NULL},
    {"-cellsize",     "*cellSizeInPixels",    XrmoptionSepArg, NULL},
    {"-fg",           "*foreground",          XrmoptionSepArg, NULL},
    {"-foreground",   "*foreground",          XrmoptionSepArg, NULL},
    {"-debug",        "*debug",               XrmoptionNoArg, "True"},
};

static void Syntax(argc, argv)
int argc;
char * argv[];
{
    int i;
    static int errs = False;

    /* first argument is program name - skip that */
    for (i = 1; i < argc; i++) {
        if (!errs++) /* do first time through */
            fprintf(stderr, "xbitmap4: command line option unknown:\n");

        fprintf(stderr, "option: %s\n", argv[i]);
    }

    fprintf(stderr, "xbitmap understands all standard Xt\
            command-line options.\n");

    fprintf(stderr,"Additional options are as follows:\n");
    fprintf(stderr,"Option        Valid Range\n");
    fprintf(stderr,"-pw           MINBITMAPWIDTH to\
            MAXBITMAPWIDTH\n");
    fprintf(stderr,"-pixmapwidth  MINBITMAPWIDTH to\
            MAXBITMAPWIDTH\n");
    fprintf(stderr,"-ph           MINBITMAPHEIGHT to\
            MAXBITMAPHEIGHT\n");
    fprintf(stderr,"-pixmapheight MINBITMAPHEIGHT to\
            MAXBITMAPHEIGHT\n");
    fprintf(stderr,"-cellsize     MINCELLSIZE to\
            MAXCELLSIZE\n");
    fprintf(stderr,"-fg           color name\n");
    fprintf(stderr,"-foreground   color name\n");
    fprintf(stderr,"-debug        no value necessary\n");
}

main(argc, argv)
int argc;
char *argv[];
{
    XtAppContext app_context;
    Widget toplevel, vpane, buttonbox, quit, output;
      .
      .
      .

    toplevel = XtVaAppInitialize(&app_context;
            "XBitmap4",
            options,             /* command line option table */
            XtNumber(options),
            &argc,
            argv,

            NULL,
            NULL);

    /* XtVaAppInitialize always leaves at least prog name in args */
    if (argc > 1)
        Syntax(argc, argv);
        .
        .
        .
}


Each options table entry consists of four fields:

  • The option to be searched for on the command line. As with standard command-line options, Xt will automatically accept any unique abbreviation of the option specified here. For example, the option -pixmapWidthInPixels will be recognized if typed on the command line as -pixmapW. However, if you wanted the option -pw to set the same resource, then you would need another entry, since pw is not the leading string of pixmapWidthInPixels.

  • The resource specification. This must identify a widget resource or an application resource, but not provide a value. Since it has the same form as allowed in the resource databases, it may apply to a single widget or to many widgets. If it applies to no widgets, no error message will be issued.

  • The argument style. This field is one of seven constants describing how the option is to be interpreted. These constants are described below in Table 3-3.

  • The value. This field is the value to be used for the resource if the argument style is XrmOptionNoArg. This field is not used otherwise. Note that this value must already be converted to the value expected for the resource (often not a string). You may be able to use Xt's type converter routines explicitly to convert this data to the right type (see Section 10.3.5, "Explicitly Invoking a Converter").

The enum constants that specify the various command-line argument styles are as shown in Table 3-3.

Table 3-3. XrmOptionKind: Command-line Option Style Constants

Constant

Meaning

XrmoptionNoArg

Take the value in the value field of the options table. For example, this is used for Boolean fields, where the option might be -debug and the default value False .

XrmoptionIsArg

The flag itself is the value without any additional information. For example, if the option were -on , the value would be "on." This constant is infrequently used, because the desired value such as "on" is usually not descriptive enough when used as an option (-on ).

XrmoptionStickyArg

The value is the characters immediately following the option with no white space intervening. This is the style of arguments for some UNIX utilities such as uucico where -sventure means to call system venture .

XrmoptionSepArg

The next item after the white space after this flag is the value. For example, -fg blue would indicate that "blue" is the value for the resource specified by -fg .

XrmoptionResArg

The resource name and its value are the next argument in argv after the white space after this flag. For example, the flag might be:

-res `basecalc*background:white';

then the resource name/value pair would be used as is. This form is rarely used because it is equivalent to -xrm, and because the C shell requires that special characters such as * be quoted.

XrmoptionSkipNArgs

Ignore this option and the next N arguments in argv , where N is the value in the last field of this option table entry.

XrmoptionSkipArg

Ignore this option and the next argument in argv .

XrmoptionSkipLine

Ignore this option and the rest of argv .


The options table is passed to XtAppInitialize() as its third argument, and the number of options table entries as the fourth. The XtNumber() macro is a convenient way to count the number of entries (this is only one of many contexts in which you'll see this macro used).

Note that you cannot override the standard options by providing options with the same names in your own parsing table. If you try this, your options with the same names will simply not be set to the values specified on the command line. Instead, the standard options will be set to these values. This was a design decision in Xt, one of the few cases where a user-interface policy is enforced. Uniformity in this basic area was deemed more valuable than flexibility.

Also note that there is no way to instruct Xt to interpret more than one argument format for the same option. For example, you cannot arrange for -size 3 and -size3 both to work.

XtAppInitialize() removes all the arguments it recognizes (including those in your options table) from argv and argc. If all goes well, only the application name will remain in argv, and argc will equal one. It is important to check whether there is more than one argument left after XtAppInitialize() has returned. Command-line options that XtAppInitialize() doesn't recognize will be left in argv and argc. This is your chance to catch this error and tell the user.[30] The Syntax function shown in Example 3-16 demonstrates code that informs the user of the proper syntax and the option that was not understood. (In response to incorrect command-line options, UNIX programs traditionally print only the correct calling sequence. However, you can be even nicer to the user by printing the option or options that were in error, by passing argv and argc into your Syntax function, as is done in Example 3-16.

Experienced UNIX programmers will note that Xt applications can (but usually don't) use the single-letter command-line arguments mandated by POSIX and the System V Interface Definition. As mentioned earlier, Xt automatically matches any unique abbreviation for any command-line option. For example, by default the -display option can be specified as -d, but only if you haven't included any other option in the options table that also begins with d. You can define the meaning of single-letter options simply by including them verbatim in the options table. In other words, if you specify that -d turns on a debugging resource, Xt will no longer try to match any other, longer option that also begins with d.

Note that the argc and argv arguments of XtAppInitialize() are in the same order as in the call to main. This is the opposite order of arrays and array lengths throughout other Motif, Xt and Xlib routine calls. Also note that the address of argc, not argc itself, is passed to XtAppInitialize(). This is so that XtAppInitialize() can decrement the count to reflect recognized options. Watch out for these snags.

Preventing User Customization of Widget Resources

Although user customization is good to a certain degree, some resources need to be hardcoded to make sure that an application will run properly regardless of user configuration. This is particularly true of widget geometry resources and constraints. For example, it is easy to make a mistake with Form widget constraints such that some important widget is hidden behind others.

You prevent user customization of particular resources by setting them in the calls to XtVaCreateManagedWidget() or XtCreateManagedWidget(), using a varargs list or an argument list as was shown in XtVaSetValues() and XtSetValues() calls in Section 7.4, "The set_values Method." When you use a Motif widget creation function such as XmCreateMenuBar(), the easiest way to hardcode that widget's resources is to follow the creation call with a call to XtVaSetValues(). This section will review those techniques and show a few more tricks.

When resources are set in the calls to create widgets, the application can still modify these resources any time later using XtVaSetValues() or XtSetValues(). Therefore, this is hardcoding only from the user's perspective. Also note that Xt will not tell the user that you have hardcoded certain resources--your application documentation should do this.

The time to hardcode widget resources is when development of an application is almost finished, because until then the resource settings are still in flux and it is quicker to edit the app-defaults file than it is to recompile the application.

Motif itself prevents user customization of certain resources, because it does not allow them to be set from resource files.[31] These resources must be set in the code if they are to be set at all. This is unfortunate because these resource settings can't be debugged in the usual way, by modifying the app-defaults files or using editres (see Chapter 14). This leads to time-consuming recompilation.

Selecting which resources to hardcode is something we can't help you with, because there isn't enough experience to go on yet. Some application developers will hardcode almost everything, while others will hardcode almost nothing. Only time will tell where the proper balance lies. For example, there is a tradeoff between hardcoding strings such as “Quit” so that the user can't change them, and setting them in the app-defaults file so that they can be easily changed for operation in a different language.

Using the Varargs Interfaces

Setting widget resources in XtVaCreateManagedWidget() is the cleanest and most powerful way to do it. You have already seen the style of call shown in Example 3-17, but pay particular attention to the XtVaTypedArg entry. This example also shows the special technique required to set a Motif compound string in the code.

Example 3-17. The R4 varargs interface to creating a widget

main(argc, argv)
int argc;
char **argv;
{
    XtAppContext app_context;
    Widget topLevel, layout, quit;
    XmString text;
      .
      .
      .
    layout = XtVaCreateManagedWidget("layout", formWidgetClass,
            topLevel, NULL);
    text = XmStringCreateLtoR("Quit", XmFONTLIST_DEFAULT_TAG);
    quit = XtVaCreateManagedWidget("quit", xmPushButtonWidgetClass,
            topLevel,
            XmNwidth, 200,
            XtVaTypedArg,     XmNbackground,
            XmRString,        "red", strlen("red")+1,  /* cont'd */
            XmNsensitivity,   True,
            XmNlabelString,   text,
            NULL);
    XmStringFree(text);
      .
      .
      .
}

The standard varargs list entry is a resource name/value pair. The value must be already converted into the type required for that resource (documented on the widget's reference page). XtVaTypedArg (not Xm...) is a special symbol used just before a resource name, which, when encountered, indicates that the next four arguments specify a resource that needs to be converted.[32] In Example 3-17, the value “red” needs to be converted into a pixel value, which is the index to the colormap register that contains the correct RGB values to display on the screen. The argument XmNbackground is the resource to be set. XmRString is the type of the value to be converted. The final two arguments are the pointer to the value (or the pointer to the pointer to the value if not a string) and the size of the value pointed to in bytes. XtVaTypedArg can be used to convert any string that is valid in a resource file but is otherwise difficult to set in the source file. But don't use XtVaTypedArg unnecessarily, since it adds overhead.

The varargs interfaces will also accept an arglist as one item in the list, using the symbol XtVaNestedList as the resource name. XtVaNestedList allows you to create one argument list and pass it to a series of widget creation routines, instead of listing all the same resource settings in each widget creation routine. For more details on this, see "Chapter 10, Resource Management and Type Conversion."

There is some extra overhead involved in using the varargs interfaces because they massage the arguments into an argument list and call XtCreateWidget(), XtCreateManagedWidget(), XtSetValues(), or XtGetValues(). However, the added convenience seems worth it. Furthermore, the XtVaTypedArg feature is not supported in the ArgList style of call (described in the next section).

Unless you use the XtVaTypedArg feature, no diagnostic is printed when you specify a resource value incorrectly in the argument list. Therefore, make sure you specify the resource values correctly, and recognize this as a possible place to begin looking for bugs. (This is another reason to hardcode resources only when the application is almost ready for release. If you do it all at once in a methodical fashion, you are less likely to make mistakes than if you are always adding, subtracting, and changing values in the argument lists.)


Note: If you use the varargs style of arguments, but forget to type the Va in the function name, you can get various types of errors. If you specify resources in the call, you will get a core dump at run time, but you can detect the problem with lint, which will note the function as having a variable number of arguments. If you don't specify resources in the call, the application will run but you will get the message:

Warning: argument count > 0 on NULL argument list


Using the Argument List Interfaces

Widget resources can also be hardcoded by creating an argument list containing the resources to be set and their values, and passing it to XtCreateManagedWidget(). This method of setting resources is less convenient that the varargs technique just described, but it was the only way until R4, and therefore many existing applications use it.

An argument list is just an array of Arg structures, of the same type as you set up to call XtSetValues() or XtGetValues(). Once set, this array is used as an argument to XtCreateManagedWidget(). Each Arg structure contains a resource/value pair. Attributes specified here override the same ones specified from the command line or from resource databases.

Example 3-18 shows an argument list that hardcodes the sensitivity of a PushButton widget and its callback list. Sensitivity is a good thing to hardcode for PushButton widgets, because if accidentally set by the user it could disable an application.[33] The callback list cannot be specified by the user anyway; setting it here is just an alternative to calling XtAddCallback().

The XtArgVal type used in the callback list aids in porting Toolkit programs to different architectures. It allows the system manufacturer to select the type in the typedef for XtArgVal, so that the application program need not worry about it. The value field of the Arg structure may be a number or a pointer.

Example 3-18. An argument list

Arg quit_args[] = {
    XmNsensitive,     (XtArgVal) True,
    XmNactivateCallback,      (XtArgVal) quit_callback_list,
};


An argument list can be used as an argument in the call to create a widget, as shown in Example 3-19.

Example 3-19. Using an argument list in widget creation

/* define quit_args */
main(argc, argv)
int argc;
char *argv[];
{
    Widget quit, box;
    /* create box */
      .
      .
      .
    quit = XtCreateManagedWidget(
            "quit",                     /* widget name */
            xmPushButtonWidgetClass,    /* widget class */
            box,                        /* parent widget */
            quit_args,                  /* argument list */
            XtNumber(quit_args)         /* arglist size */
            );
      .
      .
      .
}


Note the use of the XtNumber() macro to calculate how many arguments there are in the statically initialized argument list. This macro eliminates the need to keep track of the number of resources you have set.

Note also that the value field in the argument list must be in the correct representation type for that resource, which is often not a string. You may need to call XtConvertAndStore() to arrive at the right representation of the data to be placed in the argument list. For details on calling XtConvertAndStore(), see "Chapter 10, Resource Management and Type Conversion." An easier way to do this is to use the XtVaTypedArg feature supported by the varargs interfaces.

As mentioned earlier, unless you invoke a converter, no diagnostic is printed when you specify a resource value incorrectly in the argument list. Therefore, make sure you specify these correctly.

Another Way to Set Arguments

Instead of creating the argument list as a static array, you can allocate storage at run time and use the XtSetArg() macro to set values into the storage. This is the coding style favored in Motif 1.0 applications.

XtSetArg() sets a single argument to a resource identifying a constant and a value. Example 3-20 shows the code that would create an argument list with the same contents as the one created in Example 3-18 above. Some people prefer this technique because it places the argument list setting closer to the XtCreateWidget() call, making the code easier to read. (However, it is still more difficult to read than when using the varargs interfaces.)

Example 3-20. Setting the argument list with XtSetArg

int i;
Arg args[10];
/* XtAppInitialize may be called before or after the XtSetArg calls */
i = 0;
XtSetArg(args[i], XmNactivateCallback, (XtArgVal)
        quit_callback_list); i++;
XtSetArg(args[i], XmNsensitive, (XtArgVal) True); i++;
banner = XtCreateManagedWidget(
        banner,                    /* widget name */
        xmPushButtonWidgetClass,   /* widget class from Label.h */
        toplevel,                  /* parent widget */
        args,                      /* argument list */
        i);                        /* arg list size from XtSetArg counter */


Notice that i is used as the number of arguments in the XtCreateManagedWidget() call, not XtNumber(). XtNumber() would in this case return the value 10, the total length of the Arg array.

Remember that i must be incremented outside the macro, because XtSetArg() references its first argument twice. The following code will not work because XtNresource2 will be set into widget_args[2] instead of widget_args[1], as desired. Example 3-21 shows you how not to use XtSetArg().

Example 3-21. Incorrectly setting the argument list with XtSetArg

Arg arg[10];
int num_args;
/* This example will NOT work correctly! */
num_args = 0;
/* The next two lines are wrong! */
XtSetArg(args[num_args++], XtNresource1, (XtArgVal) 10);
XtSetArg(args[num_args++], XtNresource2, (XtArgVal) 40);
      .
      .
      .

The XtSetArg() method has three advantages over static initialization:

  • It moves the code that sets the argument list closer to where it is really used in the call to create a widget.

  • It allows run-time value modification using the same method.

  • The same storage can be used for multiple argument lists.

The disadvantages of the XtSetArg() method are as follows:

  • It performs all assignments at run time, which slows startup slightly.

  • It is possible to try to set more arguments than space has been allocated for, leading to a core dump.

  • It is possible to misplace the counter increment, leading to improper operation.

  • An argument list and a counter variable must be kept until the call to create the widget instance; the static method requires keeping only the argument list.

  • It may waste a small amount of storage since the argument list array is usually initialized larger than the required size.

  • Lots of XtSetArg() calls make for ugly code.

As you can see, these differences are mostly subjective. However, the varargs interfaces have many of the advantages of the XtSetArg() method, but none of its disadvantages. The only disadvantage of the varargs interfaces is the slight overhead they add. You may use whichever method you prefer, or some combination.

Merging Argument Lists

XtMergeArgLists() takes two argument lists and counts all arguments, allocates the storage for a single argument list big enough to hold them, and stores all the entries from both in the returned argument list. It does not remove duplicate entries. The calling application can use XtNumber() to determine the resulting argument count (or can add the original counts).

More About Application Contexts

We have used application contexts in the examples so far, but not discussed what they really are or what they are for.

An application context is a structure maintained by Xt that stores all the data associated with the application, including the functions registered by the application and other information about the application. Primarily, its purpose is to allow Xt applications to run without modification on certain operating systems that do not have a separate address space for each process. These systems include the Xerox Cedar environment, the Symbolics Genera environment, and the TI Explorer system. Although systems like this are not common, the goal of all X software in C is to be portable to any system that supports C.[34]

Why then is the XtAppContext exposed in the programming interface? It is possible, though difficult, to create more than one application context within a single program. This is rarely done and its implications are complex, so we will reserve discussion of it until Section 14.10, "Application Contexts."

The important thing to remember is that for maximum portability, you need to use the versions of routines that begin with XtApp instead of those that don't. For instance, use XtAppInitialize() instead of XtInitialize() and XtAppMainLoop(), not XtMainLoop().

Of the routines you have seen so far in this book, only XtVaAppInitialize(), XtAppInitialize(), XtAppMainLoop(), and XtAppAddActions() use explicit application contexts. The complete list of routines that have equivalents is presented in Section 14.10, "Application Contexts."

Throughout this book we will continue to use the routines that use the explicit application context.



[22] Just a reminder that all statements about functions such as XtVaCreateWidget() also apply to the other version of these same functions, such as XtCreateWidget(), unless specifically stated otherwise.

[23] BulletinBoard creates an exception to this rule. Since BulletinBoard never resizes its children, the window manager controls which of these children are visible, but does not resize them.

[24] The BitmapEdit widget is not part of the Motif widget set. It is used to build an application in "Chapter 4, An Example Application," and written from scratch in "Chapter 6, Inside a Widget," and "Chapter 7, Basic Widget Methods."

[25] Note that even though the Motif Form widget and the Athena Form widget are both constraint widgets and have similar names, they have radically different constraint resources.

[26] The menubar and the File menu are common features of window-based graphical applications using other widget sets and other window systems, including the Macintosh.

[27] This is less verbose, less prone to error, and saves one function call.

[28] That is, if the resource in a database file is specified properly, but the value is not, Xt will warn the user. However, if the resource is not specified properly, Xt has no way of knowing that the resource was intended to specify a color, and therefore, no message will be issued. For example, “*background: grein” will elicit a warning message because grein is an unknown color, but “*background: green” will not, because the resource identifier is unknown. Unknown resource identifiers are simply ignored, because they could apply to some other widget or even some other application.

[29] There is no legitimate way for the application to access this parse table, which is necessary to show the user an error in a parameter. Furthermore, this table is in the source for Xt and therefore not available online to some Toolkit users. However, this parse table is not expected to change radically in new releases. Therefore it can probably be safely copied from this document into application code to provide a synopsis of valid options, if desired. Most current applications simply say “This application understands all standard X Toolkit command-line options.”

[30] You can, of course, intentionally treat the arguments remaining after XtAppInitialize() as filenames or other pieces of data.

[31] Unfortunately, exactly which Motif resources can't be set from resource files is poorly documented. One sure sign is when Motif prints an error message telling you that there is no converter for that resource.

[32] Note that XtVaTypedArg does not begin with Xm. It is a symbol defined by Xt.

[33] Sensitivity is a basic resource of all widgets. When set to True, the default, a widget accepts input and operates normally. When False, a widget displays itself in gray (or otherwise indicates insensitivity) and does not act on user input. The purpose of sensitivity is to allow the application to disable certain commands when they are not valid. If sensitivity were left configurable, the user could turn it off on some widgets and effectively cripple an application. It is hard to imagine the user doing this by accident, but it is not worth taking a gamble.

[34] It is almost a maxim that there is no such thing as portable software, only software that has been ported. The goal (and more or less the reality) of X is that the porting process should be much easier than it has traditionally been, and, most important, that only one version of a particular piece of software should need to be maintained. The various idiosyncrasies of particular compilers can be dealt with using conditional preprocessor directives (#ifdef). X features were designed specifically to provide ways to handle differences in screens and keyboards. The application context is an effort to provide a way to handle odd operating systems.