Chapter 5. More About Motif

This chapter describes each widget in the Motif set that has not yet been introduced, and describes the features that Motif adds to the model provided by the Xt Intrinsics.

This book is primarily about how to program with the Xt Intrinsics, and has used the Motif widgets to demonstrate those techniques. It has so far not described many of the Motif widgets, and hasn't touched the surface of the topic of how to combine them to best advantage. Complete coverage of these topics is left to Volume Six, Motif Programming Manual. However, this chapter will acquaint you with the default appearance and general purpose of the Motif widgets we have not yet used. With this information, you should gain a general idea of the range of widgets you have to choose from.

Motif also provides many features that are roughly at the same level as those provided by Xt, but that go beyond those provided by Xt. Part of Motif, therefore, is a kind of extension of Xt. This chapter will provide sufficient introduction so that you know the purpose and capabilities of each of these features. This will enable you to determine whether these features will be useful in your application, and warrant further investigation. Again, more complete coverage of these features, with examples, will be left to Volume Six, Motif Programming Manual.

Note that Motif includes several components:

We do not discuss the Motif Window Manager or style guide in any detail in this book. See Volume Six, Motif Programming Manual for more information.

The Remaining Motif Widgets and Gadgets

Let's begin with the primitive widgets that you haven't yet seen in examples in this book. These are widgets that do not manage child widgets. Although we have not explicitly created ArrowButton and ScrollBar widgets, you have seen them as part of MainWindow and ScrolledWindow. ArrowButton is the widget used at the ends of ScrollBars. ScrollBars, in turn, are created automatically by MainWindow and ScrolledWindow.

Command widgets are for keeping track of typed commands. The upper window shows the history of past commands typed, and the lower window shows the command currently being typed. Figure 5-1 shows a Command widget.

Figure 5-1. A Command widget


The FileSelectionBox is a complex widget which allows the user to move through the file system to select a file. See Volume Three, X Window System User's Guide, Motif Edition, for a description of how the user manipulates this widget. A FileSelectionBox can be created by itself without the buttons at the bottom (with XmCreateFileSelectionBox()) or it can be created as part of a dialog with XmCreateFileSelectionDialog(). Figure 5-2 shows a FileSelectionDialog widget.

Figure 5-2. A FileSelectionDialog widget

A SelectionBox allows the user to choose from a list of alternatives. It provides a scrolled list, and a text area. The user can select from the list using the pointer, or type the name from the list. A SelectionBox can also be created as part of a dialog with XmCreateSelectionDialog(). Figure 5-3 shows a SelectionDialog widget.

Figure 5-3. A SelectionDialog widget


Text is a text editor in a single widget. It can edit single line or multiline text. By default it uses Emacs-style editing commands. TextField is a single line text editor with provision for verification that the user supplied acceptable data. The Text and TextField widgets provide a multitude of functions for accessing and manipulating their contents from the application. The XmCreateScrolledText() function is used for creating a Text widget with ScrollBars. Figure 5-4 shows a TextField widget. A Text widget is similar but includes multiline editing capabilities.

Figure 5-4. A TextField widget


The Separator widget is used to visually separate distinct areas within larger components. It just draws a line between widgets. Separators can be used to separate items in popup menus, action areas within dialog boxes, or simply adjacent geometry managing widgets. Usually you should use a SeparatorGadget instead. Figure 5-5 shows a menu containing elements separated with Separator widgets.

Figure 5-5. A menu containing buttons separated by Separator widgets


A ToggleButton sets a boolean value. ToggleButton is designed to be added in groups to a RadioBox or a CheckBox. See Figure 5-6. Both RadioBox and CheckBox are implemented using a RowColumn widget. The resources that distinguish these two types of user interface elements are automatically set when you use the XmCreateRadioBox() or XmCreateSimpleCheckBox() convenience functions. Each ToggleButton in a RadioBox has a diamond-shaped indicator, and only one ToggleButton at a time can be chosen. In a CheckBox, each ToggleButton has a square indicator and any number of the ToggleButtons can be selected at the same time.

Figure 5-6. ToggleButton widgets used in a RadioBox and a CheckBox

A DrawnButton is a widget that works like a PushButton but it is empty except for a button shadow. It provides callbacks for exposure and resize events, so that you can provide your own drawing code. DrawnButtons are not often used, since PushButtons allow you to supply either text or a pixmap as its label. A DrawnButton can be used to implement an animated button (where the contents change frequently), or to draw text that is beyond the capabilities of a PushButton. Since a DrawnButton has no distinctive appearance, one is not shown.

The Scale is really a slider widget, which can be used for input or output. A Scale is shown in Figure 5-7.

Figure 5-7. A Scale widget


The List widget provides an set of string choices. The List supports both single and multiple selection styles. Figure 5-8 shows a list widget containing numerous choices. XmCreateScrolledList() creates a specialized form of List that is for managing large lists.

Six types of simple widgets are also available as gadgets (window-less widgets): PushButtonGadget, LabelGadget, ArrowButtonGadget, CascadeButtonGadget, SeparatorGadget, and ToggleButtonGadget. However, as of R5, you should avoid using gadgets since they can result in increased network load.

Figure 5-8. A List widget containing selectable strings


Geometry Managing Widgets

All Motif geometry managing widgets are subclasses of the Manager class. Instances of Manager itself are never created. Manager serves the same purpose for geometry managing widgets as Primitive does for graphics widgets. It provides the features common to all Motif widgets, such as 3-D shadows, keyboard traversal, and so on.

Motif uses RowColumn very heavily. All of the following are not separate widget classes; they are RowColumn widgets with certain resource settings:

  • WorkArea--the default RowColumn type.

  • CheckBox--a box designed to contain ToggleButtons, in which several can be selected at the same time.

  • RadioBox--a box designed to contain ToggleButtons, in which only one can be selected.

  • OptionMenu--appears as a button and an associated menu. The button shows the current selection from the menu. The menu appears over the button.

  • PulldownMenu--a menu that appears just below a button that pops it up.

  • PopupMenu--a menu that pops up at the pointer position.

  • MenuBar--a horizontal bar across the top of the application's main window, containing CascadeButtons that post menus.

RowColumns do not have a three dimensional appearance, so they may be placed next to one another without giving the user the impression that their sub-elements are grouped independently. However, they can be placed within Frame to generate a 3-D appearance.

BulletinBoard is a geometry managing widget that has very simple rules. It is used by Motif in most dialog widgets. After resizing, it leaves child widgets where they were originally placed, or it moves them somewhat to avoid overlapping. Motif supplies XmCreateBulletinBoard() for general use and XmCreateBulletinBoardDialog() for building your own dialogs. Some applications use BulletinBoard as their work area, so that they have a static layout which does not change upon resizing. If a BulletinBoard is resized smaller than the current layout of the widgets it contains, the widgets are clipped and may not be completely visible.

The ScrolledWindow widget is used to implement a “window” into a data object (such as text or graphics). If the data being viewed is larger than the ScrolledWindow, scrollbars are attached to enable the user to view portions of the window interactively. ScrolledWindow is capable of automatically creating and operating its own ScrollBar widget children. You have to provide ScrolledWindow with a work area to manage.

The MainWindow widget acts as the standard layout manager for the main application's window. It is designed specifically to contain a MenuBar, an optional command window, a work region, and scrollbars. This widget is an extension of the ScrolledWindow widget. You have to provide MainWindow with a work area and menu bar to manage.

The Frame widget manages one child within a visible border that the Frame draws. It is most often used to provide a 3-D border for RowColumn and Form widgets, which otherwise have no 3-D look.

The PanedWindow widget manages its children in a vertically tiled format only. Its width fluctuates with the widest widget in its list of managed children. The widget provides a sash to allow the user to adjust the height of each pane.

The Form widget has an elaborate means of linking the edges of its children to itself and to one another. Different layout criteria can be specified for each child. Form widgets are good managers to use as children of DialogShells that you build yourself. Like RowColumn widgets, Forms have no border, allowing them to be placed next to other geometry managing widgets without giving the appearance that their items are grouped separately. A Frame widget is used to give Forms visible boundaries.

Dialog Widgets

DialogShell and MenuShell perform the window manager interaction as parent widgets of dialog widgets and menus respectively. DialogShell is created alone only when you are creating a custom dialog box that is not based on BulletinBoard or Form (since XmCreateBulletinBoardDialog() and XmCreateFormDialog() exist). MenuShell is used when you are creating a menu that is not one of the standard types created by XmCreatePulldownMenu(), XmCreatePopupMenu(), XmCreateOptionMenu(), etc.

A simple Help dialog box was demonstrated in Section 3.4.2, "Creating a Basic Dialog Box." This was a MessageBox created as a dialog using XmCreateMessageDialog(). A MessageBox widget can be configured to display a symbol that represents the kind of information being displayed. Motif defines five symbols for this purpose, and provides a widget creation function that creates a MessageBox as a dialog for each symbol. They are XmCreateErrorDialog(), XmCreateInformationDialog(), XmCreateQuestionDialog(), XmCreateWorkingDialog(), and XmCreateWarningDialog(). Figure 5-9 shows these five types of dialogs.

Figure 5-9. Dialog boxes with the five standard symbols

As noted in Chapter 3, More Techniques for Using Widgets, there is no standard dialog for help information. However, the InformationDialog seems most appropriate, once its Cancel button and possibly its Help button are unmanaged.

Widget Creation Functions

You have already seen a few of the functions Motif provides for creating widgets. For example, in Chapter 3 we used XmCreatePulldownMenu(), XmCreateMenuBar(), and XmCreateMessageDialog() in xmainwindow. As you may recall, these are very similar to Xt's widget creation functions except that in the Motif routines:

  • The widget parent argument comes before the widget instance name argument (the reverse of the Xt functions).

  • No widget class pointer is necessary (it is implied by the function name).

  • A separate XtManageChild() call is required to display the widget.

There are many more of these functions. Some of them create single widgets, some create single widgets with certain special resource settings, and some create combinations of widgets.

For users of other widget sets such as Athena, the use of the IDs returned by Motif widget creation functions can be a bit confusing. The functions that create combinations of widgets don't always return the top level widget of the combination. For example, XmCreateMessageDialog() returns the ID of the MessageBox widget, not the ID of the DialogShell that XmCreateMessageDialog() creates as its parent. This allows you to easily set the resources of the MessageBox. When you want to pop up the dialog, you call XtManageChild() with the ID of the MessageBox, not its DialogShell parent. (Users of the Athena widget set popup widgets by calling XtPopup() on the Shell widget.)

Here is a complete list of the XmCreate* functions, and a brief description of what the object created is intended for.

Table 5-1. XmCreate Functions

Function

Description

XmCreateArrowButton

Create an ArrowButton widget.

XmCreateArrowButtonGadget

Create an ArrowButtonGadget.

XmCreateBulletinBoard

Create a BulletinBoard widget.

XmCreateBulletinBoardDialog

Create BulletinBoard widget with DialogShell parent.

XmCreateCascadeButton

Create a CascadeButton widget.

XmCreateCascadeButtonGadget

Create a CascadeButtonGadget.

XmCreateCommand

Create a Command widget.

XmCreateDialogShell

Create a DialogShell widget.

XmCreateDragIcon

Create a DragIcon widget.

XmCreateDrawingArea

Create a DrawingArea widget.

XmCreateDrawnButton

Create a DrawnButton widget.

XmCreateErrorDialog

Create a MessageBox with error symbol.

XmCreateFileSelectionBox

Create a FileSelectionBox widget.

XmCreateFileSelectionDialog

Create a FileSelectionBox with a DialogShell parent.

XmCreateForm

Create a Form widget.

XmCreateFormDialog

Create a Form widget with DialogShell parent.

XmCreateFrame

Create a Frame widget.

XmCreateInformationDialog

Create a MessageBox with information symbol.

XmCreateLabel

Create a Label widget.

XmCreateLabelGadget

Create a LabelGadget.

XmCreateList

Create a List widget.

XmCreateMainWindow

Create a MainWindow widget.

XmCreateMenuBar

Create a RowColumn widget with resource settings for being a menubar.

XmCreateMenuShell

Create a MenuShell widget.

XmCreateMessageBox

Create a MessageBox widget.

XmCreateMessageDialog

Create a MessageBox with a DialogShell parent.

XmCreateOptionMenu

Create a RowColumn widget with resource settings for being an option menu.

XmCreatePanedWindow

Create a PanedWindow widget.

XmCreatePopupMenu

Create a RowColumn widget with resource settings for being a popup menu.

XmCreatePromptDialog

Create a SelectionBox widget with a DialogShell parent.

XmCreatePulldownMenu

Create a RowColumn widget with resource settings for being a pulldown menu.

XmCreatePushButton

Create a PushButton widget.

XmCreatePushButtonGadget

Create a PushButtonGadget.

XmCreateQuestionDialog

Create a MessageBox with question symbol.

XmCreateRadioBox

Create a RowColumn widget with resource settings to be a radio box.

XmCreateRowColumn

Create a RowColumn widget.

XmCreateScale

Create a Scale widget.

XmCreateScrollBar

Create a ScrollBar widget.

XmCreateScrolledList

Create a ScrolledWindow widget containing a List widget.

XmCreateScrolledText

Create a Text widget.

XmCreateScrolledWindow

Create a ScrolledWindow widget.

XmCreateSelectionBox

Create a SelectionBox widget.

XmCreateSelectionDialog

Create a SelectionBox widget with a DialogShell parent.

XmCreateSeparator

Create a Separator widget.

XmCreateSeparatorGadget

Create a SeparatorGadget.

XmCreateSimpleCheckBox

Create a RowColumn widget with resource settings to function as a CheckBox, and children.

XmCreateSimpleMenuBar

Create a RowColumn widget with resource settings to function as a MenuBar, and children.

XmCreateSimpleOptionMenu

Create a RowColumn widget with resource settings to function as a OptionMenu, and children.

XmCreateSimplePopupMenu

Create a RowColumn with resource settings to function as a popupMenu, and children.

XmCreateSimplePulldownMenu

Create a RowColumn widget with resource settings to function as a PulldownMenu, and children.

XmCreateSimpleRadioBox

Create a RowColumn widget with resource settings to function as a RadioBox, and children.

XmCreateText

Create a Text widget.

XmCreateTemplateDialog

Create a MessageBox configured as a template.

XmCreateTextField

Create a TextField widget.

XmCreateToggleButton

Create a ToggleButton widget.

XmCreateToggleButtonGadget

Create a ToggleButtonGadget.

XmCreateWarningDialog

Create a MessageBox with warning symbol.

XmCreateWorkArea

Create a RowColumn widget with resources to function as a WorkArea.

XmCreateWorkingDialog

Create a MessageBox with working symbol.


Compound Strings

Compound strings are designed to address two issues frequently encountered by application designers: the use of foreign character sets to display text in other languages, and the use of multiple fonts when rendering text.

Many character sets used by foreign languages are too large for the characters to be represented by the char type, as English can be. Compound strings solve this problem by representing characters as 16 bit values (“words”) rather than the classic 8 bit values (“chars”). Also, since some languages such as Hebrew and Arabic are read from right-to-left, compound strings also store directional information for rendering purposes. Compound strings allow widgets to handle different languages transparently to the application. All the application has to do is not hard code any strings in the application. All strings should be external to the application, usually in resource files or XPG3 (X/Open) message catalogues, or if using UIL, in UID files. In this section, we demonstrate the simplest usages of compound strings, in a way not intended to be internationalized since the strings are hard coded.

A compound string is made of three elements: a font list element tag, a direction and text. Compound strings that incorporate multiple fonts are achieved by concatenating separate compound strings with different character sets together to produce a single string. A separator is placed between each compound string.

Simple Compound Strings

Almost all of the Motif widgets require a compound string when specifying text. Labels, PushButtons, Lists--all require their text to be given in compound string format, whether or not you require the additional flexibility compound strings provide.[38] Therefore, the most common use for compound strings in English-language applications is simply to convert standard C-style null-terminated text strings for use in a widget.

First of all, strings specified in resource files need not be converted, since Motif will automatically do the conversion.

To do the conversion for strings specified directly in the application code, we use the function XmStringCreateLocalized().

XmString
XmStringCreateLocalized(text)
    char *text;

The text parameter is a common C char string. The value returned is of type XmString which is an opaque type to the programmer.

As its name implies, XmStringCreateLocalized() converts a C string into a compound string using the current locale. You cannot specify a font, a string direction, or have multiple lines. As mentioned earlier, no strings should be specified in an internationalized application, although we do so here for simplicity.

XmString str = XmStringCreateLocalized("Push Me");
widget = XtVaCreateManagedWidget("widget_name",
    xmPushButtonGadgetClass, parent,
    XmNlabelString,  str,
    NULL);
XmStringFree(str);

XmStringCreateLocalized(), along with the other functions that create compound strings, allocates memory to store the strings created. Widgets whose resources take compound strings as values always allocate their own space and store copies of the compound string values you give them, so you must free your copy of the string after having set it in the widget resource. Compound strings are freed using XmStringFree().

This three-step process is typical of the type of interaction you will have with compound strings. You create a string, set it in a widget, then free the string (unless you want to use it in another widget). However, this process involves quite a bit of overhead; memory is allocated by the string creation function, then again by the internals of the widget for its own storage, and then your copy of the string must be deallocated (freeing memory is also an expensive operation).

This process can be simplified by using the XtVaTypedArg feature in Xt. This symbol is used in variable argument list specifications for functions like XtVaCreateManagedWidget() or XtVaSetValues(). It allows you to specify resources in any type--most likely a more convenient one--and have Xt do the conversion for you. In the case of compound strings, this method can be used convert between C strings and compound strings without having to do it yourself. The following code fragment has the same effect as the example shown above without the overhead of the three-step process.

widget = XtVaCreateManagedWidget("widget_name",
    xmPushButtonWidgetClass, parent,
    XtVaTypedArg,
      XmNlabelString, XmRString, "Push Me", strlen("Push Me") + 1,
    NULL);

XtVaTypedArg takes four additional parameters: the resource whose value is going to be set, the type of the value we are giving the resource, the value itself, and the size of the value's data type. (Note that this fourth parameter is the size of the value itself, not the size of the type!)

The resource is XmNlabelString, and its value is of type XmString (a compound string). We specify XmRString as the type of the value we are going to provide.[39] The string "Push Me" is that value, and it's length, including terminating NULL, is calculated with strlen.

Strings with Multiple Fonts

A common use of compound strings is to display a string using multiple fonts.

To incorporate more than one font in a single compound string, you must create a fontList that specifies multiple fonts or font sets. Then you must either create the compound text in segments using the function XmStringSegmentCreate() (a routine that allows you to control both the font list element tag and the direction of a compound string), or you must create separate compound strings using XmStringCreate() and then concatenate them with XmStringConcat(). This concatenated string will have information embedded in it to indicate font changes. Example 5-1 demonstrates how a compound string can be created to contain multiple fonts.

Example 5-1. xcomstring.c: code to use multiple fonts in a compound string

/*
 * Header files required for all Toolkit programs
 */
#include <X11/Intrinsic.h>     /* Intrinsics definitions */
#include <Xm/Xm.h>             /* Standard Motif definitions */
/*
 * Public header file for widgets we actually use in this file.
 */
#include <Xm/PushB.h>          /* Motif PushButton Widget */
main(argc, argv)
int argc;
char **argv;
{
    XtAppContext app_context;
    Widget topLevel;
    XmString text;
    XmString s1, s2, s3, s4;
    Widget hello;
    static String string1 = "Specify the ",
                  string2 = "character set ",
                  string3 = "in the code.";
    XtSetLanguageProc(NULL, (XtLanguageProc)NULL, NULL);
    topLevel = XtVaAppInitialize(
            &app_context,       /* Application context */
            "XComstring",       /* Application class */
            NULL, 0,            /* command line option list */
            &argc, argv,        /* command line args */
            NULL,               /* for missing app-defaults file */
            NULL);              /* terminate varargs list */
    s1 = XmStringCreate(string1, "tag1");
    s2 = XmStringCreate(string2, "tag2");
    s3 = XmStringCreate(string3, "tag1");
    s4 = XmStringConcat(s1, s2);
    XmStringFree(s1);
    XmStringFree(s2);
    text = XmStringConcat(s4, s3);
    XmStringFree(s3);
    XmStringFree(s4);
    hello = XtVaCreateManagedWidget(
	    "hello",			/* arbitrary widget name */
	    xmPushButtonWidgetClass,	/* widget class from PushButton.h */
	    topLevel,			/* parent widget */
            XmNlabelString, text,
	    NULL);              	/* terminate varargs list */
    XmStringFree(text);
    XtRealizeWidget(topLevel);
    XtAppMainLoop(app_context);
}

Again, it's important to note that an internationalized application could not contain hardcoded strings as shown here. Instead, all strings would be external to the application (in resource files or message catalogues).

The output of this code fragment is shown in Figure 5-10.

Figure 5-10. xcomstring.c: how it looks on the screen


The XmNfontList resource is specified to contain two fonts as shown in Example 5-2. Each string is created using XmStringCreate() with the appropriate text and font list element tag specified. Then, the strings are concatenated together using XmStringConcat(), two at a time, until we have a single compound string that contains all the text and includes all the font changes.

It is possible to specify compound text strings (such as the XmNlabelString resource of the PushButton widget) in resource files as normal strings. This is possible because Motif takes care of the conversion to compound strings. However, when you want font changes within a string, you need to create compound strings or segments explicitly within the application as illustrated in Example 5-1.

Here is the app-defaults file for xcomstring.

Example 5-2. xcomstring: app-defaults file

*hello.fontList:	*times-medium-r*180*=tag1,\
                    	*times-medium-i*180*=tag2
*foreground: black
*background: white
*bottomShadowPixmap: 75_foreground
*topShadowPixmap: 25_foreground
*highlightPixmap: 50_foreground


Manipulating Compound Strings

Most C programmers are used to dealing with functions such as strcpy(), strcmp(), and strcat(), to copy, compare and modify strings. However, these functions are ineffective with compound strings since compound strings are not based on a byte-per-character format and they have other types of information embedded within them (character sets, directions and separators). In order to accomplish some of these common tasks, you can either convert the compound string back into C strings or use the functions Motif provides to manipulate compound strings directly. Your choice depends largely on the complexity of the compound strings you have and the complexity of the manipulations you need to do.

We've already seen how XmStringConcat() is used to concatenate compound strings. The following utility functions are also provided:


Boolean
XmStringByteCompare(str1, str2)
    XmString str1, str2;

Boolean
XmStringCompare(str1, str2);
    XmString str1, str2;

XmString
XmStringConcat(str1, str2)
    XmString str1, str2;

XmString
XmStringNConcat(str1, str2, n)
    XmString str1, str2;
    int n;

XmString
XmStringCopy(str)
    XmString str;

XmString
XmStringNCopy(str, n)
    XmString str;
    int n;

int
XmStringHasSubstring(string, substring)
    XmString string, substring;

int
XmStringLength(str)
    XmString str;

The XmStringNConcat() and XmStringNCopy() functions differ from their “N-less” brethren in that they concatenate or copy, respectively, only n characters from the specified string. Otherwise, the purpose of these functions is readily apparent from their names. Note however that since a compound string includes various tags, direction indicators and segment separators, XmStringLength() cannot be used to get the length of the text represented by the compound string (i.e., it's not the same as strlen()).

Converting Compound Strings to Text

If the functions provided by the Motif routines in the previous section are inadequate, you can convert compound strings back into C strings. This process can be simple or complicated depending on the complexity of the compound string to be converted. If the compound string has one font list element tag associated with it and it has a left-to-right orientation, the process is quite simple. Fortunately, this is likely to be the case most of the time.

To make the conversion, you can use the function:

Boolean
XmStringGetLtoR(string, tag, text)
    XmString            string;
    char               *tag;
    char              **text;

The function takes a compound string and a font list element tag and converts it back into a C character string. If successful, the function returns True and the text parameter will point to a newly allocated pointer to a string.

The function only gets the text from the compound string that is associated with the font list element tag specified in tag. If the string contains multiple fonts, only the text associated with those segments with the same font is copied into the returned text.

As its name implies, this function gets only left-to-right oriented text. In order to convert compound strings that have either a right-to-left orientation or multiple fonts, you will have to scan through the elements of a compound string segment by segment (a process described in Volume Six, Motif Programming Manual).

Rendering Compound Strings

Motif always renders compound strings automatically within its widgets. More precisely, the widgets themselves manage compound string rendering. However, Motif provides convenient functions for doing the rendering which you may find useful if you are writing your own Motif-compatible widgets or extensions to Motif widgets.

For more information on this topic, see Volume Six, Motif Programming Manual.

Pixmap and Image Caching Functions

As you by now know, a pixmap is an off-screen drawable maintained by the server. It is accessed from the application using an ID. A pixmap can be created using a pattern defined in file, or drawn into, and copied to a window.

Motif's pixmap caching has a simple use and a more complicated use. The simple use is described in this paragraph. Motif provides XmGetPixmap(), which reads a bitmap file from disk and creates a pixmap of the default depth of the screen. XmGetPixmap() caches the pixmap so that subsequent requests for the same filename will return the original pixmap. The only thing slightly confusing about XmGetPixmap() is that you specify your filename (including path) in the image-name argument.

X also defines a client-side representation of a window area, called an image. An image is usually generated by grabbing a portion of an existing window or pixmap using XGetImage(). An image is a structure that contains all the data from a window area, using the server's natural data format. The structure also includes fields that describe the combination of byte-order, bit-order, and image format used by the server. Unlike many other Xlib structures, the image structure is public--its fields are documented--since it is stored on the client side. Because of the many permutations of server formats that are possible, Xlib provides generalized functions for manipulating image data, though they are rather slow because they always convert the data into the client's natural format before processing it. An image can be dumped into a window or pixmap using XPutImage().

Motif uses images to allow pixmaps to be specified in resource files, and to allow multiple instances of a widget to share one pixmap stored in the server. For example, you know that the MessageBox widget has several variations, with different symbols for information, warning, error, question, and working. Each of these symbols is created by MessageBox using XmInstallImage(). (This happens before any instances of the widget class are created, in the class_initialize method, which will be described later.) To install each image, first an image structure is created and filled with data in standard X11 bitmap file format. Then this structure is passed to XmInstallImage(). Also passed is the string name for this image, which will be used to used to refer to the image in resource files. For example, the question symbol is called default_xm_question.

When an instance of MessageBox is created using a call such as XmCreateQuestionDialog(), the XmNdialogType resource is automatically set to XmDIALOG_QUESTION. In a switch statement, MessageBox maps this symbol into the corresponding image name, “default_xm_question.” MessageBox calls XmGetPixmap() to get the pixmap specified by this image name. If the pixmap already exists because another QuestionDialog has already been created, then this pixmap is set into the XmNsymbolPixmap resource. If the pixmap does not yet exist, it is created, cached for later use by widgets of this type, and set into XmNsymbolPixmap. (You can also bypass this entire facility from the application by creating your own pixmap and setting XmNsymbolPixmap directly.)

Motif keeps reference counts in order to free these cached pixmaps when they are no longer needed. Therefore, MessageBox calls XmDestroyPixmap() when the XmNsymbolPixmap is set directly, when the dialog type is changed, or when the widget itself is destroyed. XmDestroyPixmap() just decrements the reference count to a pixmap--the pixmap is destroyed only if no other widgets are using it.

Motif defines a group of images that are pre-installed in the image cache. Each of these is converted into pixmaps when they are first used to set a widget resource, using the current foreground and background colors for that first widget. Table 5-2 lists and describes these pixmaps.

Table 5-2. Built-in Motif Pixmaps

Image Name

Description

background

Solid background.

25_foreground

25% foreground, 75% background.

50_foreground

50% foreground, 50% background.

75_foreground

75% foreground, 25% background.

horizontal

Horizontal lines of foreground and background.

vertical

Vertical lines of foreground and background.

slant_right

Up to right lines of foreground and background.

slant_left

Up to left lines of foreground and background.

Any of these pre-installed images or the images installed by Motif widgets such as MessageBox can be specified in resource files. For example, the following resource setting could appear in an app-defaults file:

*XmCascadeButton.backgroundPixmap: 25_foreground

This resource setting makes the background of CascadeButtons slightly darker than they usually are, which makes their outline visible over the menubar.

Dynamic Resource Defaulting

Most resources have a fixed default value determined by the widget class. However, several of the basic appearance resources supported by Primitive have defaults that are set at run time. Namely, the foreground, background, and the two shadow colors used by a widget are set dynamically depending on the type of screen being used and on the settings of each other. The highlight color, used to redraw the background when the user clicks on a PushButton, is also dynamically defaulted.

By default on a color screen, Motif chooses a light blue background, a black foreground, a very light blue-gray top shadow, a dark blue-gray bottom shadow, and a medium blue highlight color. These are carefully chosen to strengthen the 3-D effect of the interface. If the user sets the background color to sea green, Motif adjusts the foreground, background, shadow colors, and highlight color so that they work well with a green background. If the user chooses a dark color for the background, white instead of black is used for the text. However, the user should be informed that light colors work best for the background, since the highlight color must be darker than the background color to make buttons look pressed in.

On a gray-scale screen, Motif selects a black foreground, white background, and uses light gray and dark gray for the shadows. Medium gray is used for the highlight color. On a black and white screen, the same colors are used, but light gray and dark gray are simulated using pixmaps containing different percentages of white and black.

Dynamic resource defaults are implemented using a standard feature of the R4 Xt Intrinsics called XmRCallProc (called XtRCallProc in standard Xt). In the resource list, which has not yet been introduced, you can either specify a default value or a function to be called to calculate the default value. Motif does the latter for these resources.

Resolution Independence

As you know, the units for all Xlib and Xt calls are pixels. Motif allows you to set some resources using units such as inches, so that the size and spacing of widgets will appear the same size on any display, regardless of the pixel spacing on that display.

This resolution-independence is built into all position, dimension, spacing, padding, offset, and thickness resources of all Motif widgets. The interpretation of dimensions within each widget is controlled by the XmNunitType resource, which can be set anywhere where resources can be set, including in resource files.

For some applications, such as desktop publishing, resolution independence is important. For others, it is irrelevant, or in fact harmful, because while size of graphics will be the same on all screens, the quality of those graphics will vary. What is clear on a 1280 by 1024 screen may not be clear on a 640 by 480 screen if the graphics are the same size.

XmNunitType can be set to the following values:

  • XmPIXELS--all values provided to the widget are treated as normal pixel values.

  • Xm100TH_MILLIMETERS--all values provided to the widget are treated as 1/100 millimeter.

  • Xm1000TH_INCHES--all values provided to the widget are treated as 1/1000 inch.

  • Xm100TH_POINTS--all values provided to the widget are treated as 1/100 point. A point is a unit used in text processing applications and is defined as 1/72 inch.

  • Xm100TH_FONT_UNITS--all values provided to the widget are treated as 1/100 of a font unit.

The font unit allows you to specify sizes based on a font that will be chosen at run time according to your specifications. The font used is the one the user specifies with the -font command-line option or by setting the XmNfont resource (not XmNfontList) in a resource file. (XmNfont is used only for this in Motif.) The font unit is calculated from the dimensions of the specified font. A font unit can also be set with XmSetFontUnits(). Note that once XmNunitType is set to Xm100TH_FONT_UNITS, and the font set, the actual numbers used to set resources are integral and generally large.

Unless XmNunitType is explicitly set, all widgets that are subclasses of Manager inherit the unit type of their parent. All widgets that are subclasses of Primitive have a default unit type of XmPIXELS. (They do not inherit their parent's unit type.)

Once a unit type is set, you can set resources using this unit type, and when you query them with XtVaGetValues(), you will get values that use this unit type. A one-time rounding error may occur, though, which could make the value you query slightly different from the value you set. Repeated queries and sets will not make the rounding error worse. (Internally, widgets still store all their values in pixels. This becomes important to you only when you begin to write widgets.)

Motif also provides XmConvertUnits() for converting between units. If you need to write your own widget that implements resolution independent custom graphics, you can use XmConvertUnits(), or you can use Xlib macros that give you the number of pixels and the size of the screen.

Keyboard Traversal and Focus

All Motif widgets are able to operate with keyboard input instead of pointer input. This is primarily so that people can use the keyboard to navigate forms conveniently, but it is also so that Motif applications can be run on systems that don't have a mouse.

Xt directs keyboard input to only one window at a time; this window has the keyboard focus. All Motif widgets and gadgets have code that gives them the ability to draw a highlight rectangle around themselves when they have the keyboard focus. (In practice, geometry managing widgets do not draw a highlight rectangle because they do not take user input.) The parent tells a gadget when to highlight, since gadgets can never get the focus themselves since they have no window.

The traversal works in two levels. The first level is movement between tab groups, and the second level is movement within a tab group. A tab group is one or more widgets. As you build the instance hierarchy of an application, Motif automatically links the widgets together into groups. By default, most simple widgets are a tab group by themselves. Most manager widgets are also tab groups, with all their children in the same tab group. For example, a MessageBox widget is really a Form widget containing a Label widget and three PushButton widgets; the Form, the Label, and the three pushbutton widgets are a tab group. However, since the Form and Label widgets set XmNtraversalOn to False since they don't take user input, they are not active in the tab group.

You can move the keyboard focus around in a tab group by using the arrow keys (also called navigation keys, since other keys may be mapped to this function).

Pressing the Tab key moves the focus to the first widget in the next tab group. Pressing Tab while holding the Shift key moves the focus to the first widget in the previous tab group.

Without doing anything, you should be able to negotiate your entire application using the keyboard, since Motif sets up default tab groups. However, you may want to tailor the order in which widgets are traversed within each tab group, and also the order in which tab groups are traversed.

The order of traversal of the widgets within a tab group is the same as the order in which the widgets were created. If a widget is normally included in a tab group, but you don't want it to be, set its XmNtraversalOn resources to False.

In Motif 1.1 you could modify the order of traversal between tab groups using XmAddTabGroup() and XmRemoveTabGroup(). Both functions just set the specified widget's XmNnavigationType resource. These functions are obsolete in Motif 1.2: now you just set the XmNnavigationType resource yourself to XmEXCLUSIVE_TAB_GROUP to add a widget to a tab group, or XmNONE to remove one. There are various other settings for this resource that you can use if you set the resource directly instead of using these functions.

You can also move the focus by clicking the pointer in a widget that can accept the focus (has XmNtraversalOn set to True). Within Motif applications, you can choose between a click-to-type model, which supports keyboard traversal, and a pointer-following model, which does not support keyboard traversal. The default is the click-to-type model. You can change this by setting the XmNkeyboardFocusPolicy resource of a Shell widget, but this is not recommended. Note that this resource sets the keyboard focus model within this one application; this is completely separate from the model you choose for mwm, although the two choices available in each case are the same.

List, ScrollBar, multiline Text widgets, and Option menus must be sole members of a tab group, because they use the arrow keys for other purposes.

Because keyboard traversal depends on Motif moving the keyboard focus around, applications should not set the keyboard focus explicitly. In other words, you should never call XtSetKeyboardFocus() or XSetInputFocus(). Instead you can call XmProcessTraversal().

Even Motif menus can be operated using the keyboard. A menu can be mapped by a mnemonic such as Alt-F typed anywhere in the application. Once mapped, the menu can be traversed with the arrow keys, and an item selected with the Return key. Alternately, if the strings in the menu have mnemonics defined, they will have letters underlined, and the user can type these letters to select the item. Finally, menu items can be selected without the menu being mapped by using accelerator keys, which are described in Chapter 8, Events, Translations, and Accelerators.

Motif Virtual Keyboard Bindings

The main alphabetic area of keyboards on different systems is very uniform, but the modifier keys such as Control, Alt, Meta, Hyper, and Super, and the function keys, are not uniform. The X keyboard model hides many of the differences between the different keyboards that are available on different systems. Each X server contains a mapping between the keycodes for each physical key, and keysyms, which are symbolic constants each of which represents a standardized meaning of a key combination. For example, there are separate keysyms XK_A and XK_a which represent the “a” key with and without the Shift key pressed. See Volume One, Xlib Programming Manual, for an extensive discussion of this model.

Motif adds another level of mapping to this system. Translation tables for Motif widgets reference keysyms not defined by the Xlib standard. For example, a line in the translation table of every Motif widget reads as follows:

static char defaultTranslation[] =
     .
     .
   <Key>osfHelp:    Help()";

The string osfHelp is in the position normally occupied by a keysym, but in this case it is a Motif virtual keysym. Motif then internally maps this string into a real keysym.

Motif maps virtual keysyms into real keysyms using the file XKeysymDB, which is usually in /usr/lib/X11 on UNIX-based systems. The contents of this file are different for each system, and installing this file is part of installing Motif. For example, on systems that have a Help key, osfHelp will be mapped to that key. On systems that don't have a Help key, some other key is chosen. The advantage of the Motif virtual binding system is that you can be assured that there actually is a key that performs the Help function, even if it isn't marked, and that all Motif applications on that system will use the same key. Also, on single user systems this gives the user the option of changing which key is used for each function.

Motif 1.2 provides a new function, XmTranslateKey(), to translate a keycode into a virtual key. This function allows applications that override the default XtKeyProc to handle Motif virtual keys. Motif 1.2 also includes a new client, xmbind, that configures the virtual key bindings for Motif applications. This action is performed by the Motif Window Manager (mwm) at startup, so you only need to use xmbind if you are not using mwm or if you want to reconfigure the bindings without restarting mwm.

Drag and Drop

Motif 1.2 introduces a new user interface for transferring data within an application or between applications, called drag and drop. From the user's perspective, data can be moved or copied by dragging an iconic representation of the data.[40]

Mouse button 2 is reserved for drag-and-drop. When the user begins a drag with button two over a “drag source”, the pointer is replaced with a special drag icon. This icon indicates the type of data being dragged (text, pixmap, etc.), the drag operation (copy, move, or “link”), and whether the pointer is over a valid “drop site”, an invalid drop site or no drop site. The drag icon may change as it enters and leaves drop sites, and drop sites themselves may change their visuals when the drop icon is within them. These changing graphics in drag icon and drop site are referred to as “drag over” and “drag under” visuals, respectively.

The type of data being dragged and the default operation (copy, move, or link) depends on the drag source. If the drag source supports multiple operations, the user can select an operation other than the default with the Shift and Ctrl modifiers.

The user can cancel a drag by pressing the Escape key. The user can request help on a drop site before dropping by pressing the F1 key.

Text, List, Label and Button widgets support drag and drop as drag sources, which means that text can be dragged from them. Text and TextField widget support drag and drop as drop sites, which means that text can be dragged to them. The Label class also supports dragging of Pixmap data. If you use existing widgets, therefore, there is no extra programming necessary to provide drag and drop. Custom drag and drop support can be added to any widget. Custom drag icons and drop site visuals can also be implemented. The details of implementing drag and drop in an application are beyond the scope of this book. See the OSF/Motif Programming Manual for more information.

Drag Protocol

In order to implement drag-over effects, the originator of the drag must know when it has entered a valid drop site. In order to implement customized drag-under effects, the process controlling the drop site must know when there is a drag icon inside of it. It is not possible to gracefully satisfy all of these requirements with X. To work around this, Motif drag-and-drop supports two different drag protocols with different tradeoffs. The protocol used depends on the preference of the user and on which protocols are supported by the initiating client and the drop site.

Preregister Protocol. In the “preregister” protocol, the client that initiated the drag is in complete control of the drag, and the clients controlling drop sites are never notified when the drag icon enters those drop sites. Therefore this protocol does not allow a drop site to implement custom drag-under visuals. In this protocol, the client that initiated the drag grabs the X server and implements the drag icon with a shaped window it moves around the screen. When the icon enters a top-level window, the initiating client reads a server property to determine the (preregistered) list of valid drop sites within that window. The initiating client can implement drag-over visuals when the icon enters a drop site, and can also implement simple drag-under visuals (such as highlighting the drop-site border) for the drop site.

Dynamic Protocol. In the “dynamic” drag protocol, the initiating client communicates (dynamically) with other clients to determine the list of valid drop sites and to notify then when the drag icon enters one of these drop sites. This notification allows these clients to implement custom drag-under visuals (which can include animation). The initiating client does not perform a server grab in this protocol, and the drag icon is implemented by specifying it as the hardware pointer. Many X servers restrict the maximum size of the pointer cursor, so this protocol may not work with large drag icons.

Drop Protocol

The drop protocol is primarily concerned with transferring the data from initiator to receiver, and is much simpler than the drag protocol. It is based directly on the Xt Selection mechanism used to implement standard Xt cut-and-paste.

The drag initiator registers a “convert proc” (of type XtConvertSelectionIncrProc) when it starts the drag with XmDragStart(). In Xt selection terms, starting a drag is analogous to calling XtOwnSelection().

The receiving process registers each drop site along with a callback to invoke when something is dropped on that site. This callback registers a “transfer proc” (of type XtSelectionCallbackProc) as well as a list of target types that it would like the dragged data to be converted to and calls XmDropTransferStart(). In terms of the Xt selection mechanism, XmDropTransferStart() is analogous to XtGetSelectionValues(). XmDropTransferStart() uses the underlying Xt Selection mechanism to transfer the data. The drag source's convert proc is called once for each target type, and the drop site's transfer proc is called once with the converted data for each target.

The drop protocol is also responsible for providing help. Each drop site should recognize when the user has requested help and post a dialog box explaining the consequences of dropping on that site. This dialog box must give the user the explicit options to continue with or cancel the drop.

Operations

Motif allows three drag-and-drop operations: copy, move, and link. The precise semantics of each operation will of course depend on the dragged data and the drop site. After a copy operation there should be two copies of the dragged data or object.

After a move, there should only be one copy. Note that the drop site is responsible for telling the drag source to delete its copy of moved data. It does this by sending a request to convert the data to the special target “DELETE”. (This target is standardized by the ICCCM, described in Chapter 11.)

The semantics of the “link” operation are vague: “At the end of the operation, there is only one copy of the data, belonging to the initiator, but both applications have access to it”. In the absence of shared memory, it is not clear what this means, but the link operation should presumably be used to establish any sort of “live link”, or to enable dynamic data sharing between applications.

Application Programming Interface

Initiating a drag or registering a drop site or initiating a transfer once a drop has occurred each require a number of parameters (many of which are optional) to be specified. To avoid having functions with large number of arguments, Motif uses Xt Objects and allows these parameters to be specified with standard Xt arglists. Starting a drag with XmDragStart() creates an object of type XmDragContext. Registering a drop site with XmDropSiteRegister() creates an object of type XmDropSite. Initiating a data transfer with XmDropTransferStart() creates an object of type XmDropTransfer. These objects have resources that control many of the visual effects of drag-and-drop, and also have callback list resources that provide hooks at many points during both the drag and the drop.

Summary: Drag and Drop in Applications

Programming with Motif drag-and-drop can become almost arbitrarily complex (the code for a single example in OSF's book is 65 pages). There are three general approaches to incorporating drag-and-drop into applications:

Do Nothing. Many of the standard Motif primitive widgets support drag-and-drop. Even if you do nothing, users will be able to perform basic drag-and-drop of text.

Add New Drag Sources and Drop Types. You can support additional drag sources by adding a translation/action pair that calls XmDragStart() and registers a convert proc to handle requests for the data once it is dropped. For example you might want to allow the user to drag the value from a Slider widget, or if you implemented a custom spreadsheet widget, you might want to allow the user to drag a column of numbers. To add a new drop site, call XmDropSiteRegister() and specify a callback to be called when a drop occurs. This callback should register a transfer proc to handle the dropped data and then call XmDropTransferStart() to fetch that data.

Customize Drag-and-Drop. There are many ways that you can customize drag-and-drop, most of which can be controlled through resources and callbacks. You can change the components of the default drag icon, or specify custom drag icon components for a particular drag source. You can also change the way that the components of the drag icon are combined or “blended.” Using the XtNdragCallback callback list of the XmDragContext and XmDropSite objects, you can implements custom drag-over and drag-under visuals. If a drag site implements custom visuals, you can set resources to express a preference for the dynamic drag protocol which allows these visuals. You can specify a non-rectangular outline for a drag site, and specify the sort of highlighting should be done on that border when a drag icon enters it. You can specifying a stacking order for overlapping drop sites. You can only have one drop site for a widget, but with some extra coding work, you can implement a single drop site that appears and functions as a number of separate sites within the widget.

Tear-off Menus

Motif 1.2 supports tear-off menus. A tear-off menu appears with a dashed line across its top. Selecting this dashed line tears the menu off and allows the user to position it with the mouse. The torn-off menu becomes a top-level window subject to window manager control. Traversing to the torn-off menu follows standard window manager policy.

By default, menus do not allow themselves to be torn-off. To enable this behavior, set the new RowColumn resource XmNtearOffModel to XmTEAR_OFF_ENABLED. To disable it, set the resource to XmTEAR_OFF_DISABLED. XmRepTypeInstallTearOffModelConverter() registers the converter that allows this resource to be set from resource files. Note that some existing applications depend on receiving a callback when a menu is mapped; since torn-off menus are always mapped, these applications might fail if a user is able to enable tear-off menus from a resource file.

The Motif User Interface Language

Motif is a hybrid of technology developed by DEC, Hewlett Packard, IBM, and Microsoft. DEC's contribution was the Application Programming Interface (API) used in DECWindows. This included a tool called the User Interface Language, or UIL.

UIL is a language for describing a Motif user interface. It looks similar to a programming language except that it has no control flow. Part of it, stored in a separate library, is called the Motif Resource Manager (MRM). You specify your user interface in a .uil file, and then write the callback functions in a .c file. The .c file is compiled in the standard fashion, and linked with the -lMrm library (in addition to -lXt, -lXm, and -lX11). The .uil file has to be compiled with a program called uil, to form a .uid file.

The .c file is similar to a standard Xt application, but after XtAppInitialize() you call MrmOpenHierarchyPerDisplay(), which reads the .uid file to create the hierarchy of widgets. The .c file is primarily boilerplate, except for the callback functions for the widgets in your application. Example 5-3 shows the .c file for a simple application built with UIL.

Example 5-3. The .c file for a simple UIL application

#include <stdio.h>
/*
 * Declare our callback functions.
 */
static void button_selected();
/*
 * Miscellaneous UIL boilerplate: next 8 lines
 */
#include <Xm/Xm.h>                  /* Motif Toolkit */
#include <Mrm/MrmPublic.h>          /* Motif Resource Manager */
static MrmHierarchy    s_MrmHierarchy;    /* MRM database hierarchy ID */
static char    *vec[]={"hellomotif.uid"}; /* MRM database file list */
static MrmCode        class ;
static MrmCount        regnum = 1 ;
static MrmRegisterArg  regvec[] = {
    {"button_selected",(XtPointer) button_selected }
};
/*
 * Define our callback functions.
 */
static void button_selected(widget, call_data, client_data)
    Widget    widget;
    XtPointer client_data;
    XtPointer call_data;
{
    XmAnyCallbackStruct *data = (XmAnyCallbackStruct *) call_data;
    char *tag = (char *) client_data;
    printf("button selected\n");
}
int main(argc, argv)
int argc;
char **argv;
{
    XtAppContext      app_context;
    Widget topLevel, appMain;
	XtSetLanguageProc(NULL, (XtLanguageProc)NULL, NULL);
    /*
     *  Initialize the MRM
     */
    MrmInitialize ();
    topLevel = XtVaAppInitialize(
           &app_context,       /* Application context */
           "XUilDemo",         /* application class name */
           NULL, 0,            /* command line option list */
           &argc, argv,        /* command line args */
           NULL,               /* for missing app-defaults file */
           NULL);              /* terminate varargs list */
    /*
     *  Define the Mrm.hierarchy (only 1 file)
     */
    if (MrmOpenHierarchyPerDisplay(XtDisplay(topLevel),
            1,                 /* number of files */
            vec,               /* files */
            NULL,
            &s_MrmHierarchy)   /* ptr to returned ID */
            != MrmSUCCESS) {
        printf ("Mrm can't open hierarchy\n");
    }
    /*
     *     Register our callback routines so that the resource
     *     manager can resolve them at widget-creation time.
     */
    if (MrmRegisterNames (regvec, regnum)
            != MrmSUCCESS)
         printf("Mrm can't register names\n");
    /*
     *  Call MRM to fetch and create the pushbutton and its container
     */
    if (MrmFetchWidget (s_MrmHierarchy,
            "helloworld_main",
            topLevel,
            &appMain,
            &class) != MrmSUCCESS)
        printf("Mrm can't fetch interface\n");
    /*  Manage the main window of the hierarchy created by Mrm. */
    XtManageChild(appMain);
    XtRealizeWidget(topLevel);
    XtAppMainLoop(app_context);
}

This .c file stays essentially the same for a much more complicated application, with the exception of added callback functions and added event handlers for popping up popup widgets. The description of the hierarchy of widgets lies in one or more .uil modules. Example 5-4 shows the .uil module for the application whose C code is shown above.

Example 5-4. The .uil file for a simple UIL application

module helloworld
    version = 'v1.0'
    names = case_sensitive
procedure
    button_selected();
object
    helloworld_main : XmRowColumn {
    controls {
        XmLabel our_label;
        XmPushButton    our_button;
    };
    };
object
    our_button : XmPushButton {
    arguments {
        XmNlabelString = compound_string('Hello',separate=true)
                & 'World!';
    };
    callbacks {
        XmNactivateCallback = procedure button_selected();
    };
    };
object
    our_label : XmLabel {
    arguments {
        XmNlabelString =
                compound_string('Press button once',separate=true) &
                compound_string('to change label;',separate=true) &
                'twice to exit.';
    };
    };
end module;

The question is: Should you develop your application with pure C code or use UIL?

The following is an attempt to weigh the benefits and disadvantages of each programming method.

UIL's benefits seem to be:

  • More complete separation of user interface description from application code. The former is in the .uil file, and the latter in the .c file. Theoretically, different programmers can be working on each type of file, since they only interact in the names of callback functions.

  • Better compile time checking than standard Xt programming. UIL checks your user-interface specification at compile time to make sure you don't try to set resources that don't exist on the widget you are setting them on, and checks that you are using the right type of value. It also issues a warning if you create a child under a parent that doesn't support that type of child.

  • Forward references to widgets that have not yet been created are allowed. This can clarify a program, since the children of each widget are listed where the widget is created. In standard C code, you have to find out the variable of type Widget for the geometry managing widget, and then search throughout the code for widgets that use that widget as parent.

  • Since the source code is boilerplate with the exception of callback functions, the application needs compiling and linking less often. The user interface can be modified by changing the .uil file, and compiling it with uil, which is fast.

  • Complete description of the widgets UIL knows about in a syntax called the Widget Meta Language (WML). As of Motif 1.2, UIL now supports an extension that allows binary databases (WMD files) containing WML information to be read at run time. So custom widgets can be included in UIL files, and they are subject to the same runtime checking.

UIL's disadvantages are:

  • You have to learn the UIL syntax. If you want to add custom widgets, you have to learn another syntax, WML, as well.

  • Increase in executable file size. While a Motif “hello, world” program using pure C programming is about 820K bytes in size on my system,[41] the same application is 1400K in size using UIL. This size penalty is serious from the point of view of disk space, loading time, and memory utilization. On systems with shared libraries, however, there will be only one copy of the libraries in memory. This solves two of the above complaints, leaving only a slower linking time at run time.

  • When the callbacks in the .c module must be changed, you have to recompile and relink, which takes longer (without shared libraries) than linking the same application using pure C programming. In my experiment, using the time(1) utility built into csh and /bin/time, the UIL version takes about 50 percent longer. But as mentioned earlier, this is balanced by the fact that the .c module has to be recompiled less often.

  • Longer startup time of the finished application.

When deciding whether to use UIL or standard C, it is also relevant to consider the other programming tools available. First of all there are User Interface Management Systems (UIMSs) and Interface Development Tools (IDTs) on the market. These are programs that let you build your user interface interactively, and then output either C code or UIL code that describes the interface. These eliminate the tedium of building the interface in C, and eliminate the need to learn UIL's syntax. UIMSs and IDTs also contain lists of Motif widgets and resources, but since they output C code you can easily add your own custom widgets.

Another tool worth researching is the Widget Creation Library (WCL), written by David Smyth and Martin Brunecky. This library performs a similar job as UIL, but allows you to specify the interface in resource files instead of using UIL syntax. This resource file is read at run time and therefore does not require compiling. The WCL library is much smaller than UIL. There is a WCL tutorial in The X Resource, Issue 2, published by O'Reilly and Associates.[42]



[38] As of Motif 1.2 the Text widget does not use compound strings.

[39] This terminology can get confusing. Xt uses the typedef String for char *. The representation type used by Xt resource converters for this datatype is XtRString (XmRString in Motif). A compound string, on the other hand, is of type XmString, with the representation type used with resource converters specified as XmRXmString. You just have to read the symbols carefully. And it helps if you have a good understanding of the underlying mechanism of resource converters, which are described in detail in Chapter 9, More Input Techniques.

[40] Dragging is the act of pressing a mouse button with the pointer over the object, moving the mouse with the button held down, and releasing the object in a new location by releasing the mouse button.

[41] A Sony NWS-841 workstation running BSD 4.3. Note that this is already quite big enough!

[42] WCL is available via anonymous ftp from expo.lcs.mit.edu. As of publication, the contrib directory on expo, contains the following files:

Wcl.2.2.tar.Z
Wcl.ps.Z

The .ps files contain documentation in PostScript format. All files must be uncompressed, as described in the Preface.