Contents Previous Next

5 The Main Window


This chapter describes the Motif MainWindow widget, which can be used to frame many types of applications. The MainWindow is a manager widget that provides a menu bar, a scrollable work area, and various other optional display and control areas.

As discussed in Chapter 3, Overview of the Motif Toolkit, the main window of an application is the most visible and the most used of all the windows in an application. It is the focal point of the user's interactions with the program, and it is typically the place where the application provides most of its visual feedback. To encourage consistency across the desktop, the Motif Style Guide suggests a generic main window layout, which can vary from application to application, but is generally followed by most Motif applications. Such a layout is shown in the figure. As described in Section #smainwindow, a main window can provide a menu bar, a work area, horizontal and vertical scrollbars, a command area, and a message area.

figs.eps/V6a.04.01.eps.png
The main window of a Motif program


In an effort to facilitate the task of building a main window, the Motif toolkit provides the MainWindow widget. This widget supports the different areas of the generic main window layout. However, the MainWindow widget is not the only way to handle the layout of the main window of your application. You are not required to use the MainWindow widget and you should not feel that you need to follow the Motif specifications to the letter. While the Style Guide strongly recommends using the main window layout, many applications simply do not fit the standard GUI design model. For example, a clock application, a terminal emulator, a calculator, and a host of other desktop applications do not follow the Motif specifications in this regard, but they can still have Motif elements within them and can still be regarded as Motif-compliant. If you already have an application in mind, chances are you already know whether or not the main window layout is suited to the application; if you are in doubt, your best bet is to comply with the Motif Style Guide.

Before we start discussing the MainWindow widget, you should realize that this widget class does not create any of the widgets it manages. It merely facilitates managing the widgets in a way that is consistent with the Style Guide. In order to discuss the MainWindow widget, we are going to have to discuss a number of other widget classes and use them in examples. As a beginning chapter in a large book on Motif programming, this may seem like a bit much to handle, especially if you are completely unfamiliar with the Motif toolkit. We encourage you to branch off into other chapters whenever you find it necessary to do so. However, it is not our intention to explain these other widgets ahead of time, nor is it our assumption that you already understand them. The lack of an understanding of the other widgets should not interfere with our goal of describing the MainWindow widget and how it fits into the design of an application.

5.1 Creating a MainWindow

The MainWindow widget class is defined in < Xm/MainW.h>, which must be included whenever you create a MainWindow widget. As mentioned in Chapter 2, The Motif Programming Model, you should probably use an ApplicationShell or TopLevelShell widget as the parent of a MainWindow. If the MainWindow is being used as the main application window, the ApplicationShell returned by XtVaAppInitialize() (or another similar toolkit initialization function) is typically used as the parent. The function XtVaCreateManagedWidget() can be used to create an instance of a MainWindow widget, as shown in the following code fragment:

   #include <Xm/MainW.h>
   ...
   main(argc, argv)
   int argc;
   char *argv[];
   {
       Widget toplevel, main_w;
       XtAppContext app;

       XtSetLanguageProc (NULL, NULL, NULL);
       toplevel = XtAppInitialize (&app, "App-Class",
           NULL, 0, &argc, argv, NULL, NULL);
       main_w = XtVaCreateManagedWidget ("mw",
           xmMainWindowWidgetClass, toplevel,
           resource-value-list,
           NULL);

       XtRealizeWidget(toplevel);
       XtAppMainLoop(app);
   }
The MainWindow class is subclassed from the ScrolledWindow class, which means that it inherits all the attributes of a ScrolledWindow, including its resources. A ScrolledWindow allows the user to view an arbitrary widget of any size by attaching horizontal and vertical ScrollBars to it. You can think of a MainWindow as a ScrolledWindow with the additional ability to have an optional menu bar, command area, and message area. Because the MainWindow is subclassed from the ScrolledWindow widget, we will be referring to some ScrolledWindow resources and disclosing some facts about the ScrolledWindow. For more information about the ScrolledWindow, see Chapter 9, ScrolledWindows and ScrollBars. You may eventually need to learn more about the ScrolledWindow widget to best make use of the MainWindow, but this chapter tries to present the fundamentals of the MainWindow widget, rather than focus on the ScrolledWindow.

While a MainWindow does control the sizes and positions of its widget children like any manager widget, the geometry management it performs is not the classic management style of other manager widgets. The MainWindow is a special-case object that handles only certain types of children and performs only simple widget positioning. It is designed to support the generic main window layout specified by the Motif Style Guide.

Let's take a look at how the MainWindow can be used in an actual application. the source code demonstrates how the MainWindow widget fits into a typical application design. XtSetLanguageProc() is only available in X11R5; there is no corresponding function in X11R4.

   /* show_pix.c -- A minimal example of a MainWindow.  Use a Label as the
    * workWindow to display a bitmap specified on the command line.
    */
   #include <Xm/MainW.h>
   #include <Xm/Label.h>

   main(argc, argv)
   int argc;
   char *argv[];
   {
       Widget toplevel, main_w, label;
       XtAppContext app;
       Pixmap pixmap;

       XtSetLanguageProc (NULL, NULL, NULL);

       toplevel = XtVaAppInitialize (&app, "Demos",
           NULL, 0, &argc, argv, NULL, NULL);

       if (!argv[1]) {
           printf ("usage: %s bitmap-file0, *argv);
           exit (1);
       }

       main_w = XtVaCreateManagedWidget ("main_window",
           xmMainWindowWidgetClass, toplevel,
           XmNscrollBarDisplayPolicy, XmAS_NEEDED,
           XmNscrollingPolicy,        XmAUTOMATIC,
           NULL);

       /* Load bitmap given in argv[1] */
       pixmap = XmGetPixmap (XtScreen (toplevel), argv[1],
           BlackPixelOfScreen (XtScreen (toplevel)),
           WhitePixelOfScreen (XtScreen (toplevel)));

       if (pixmap == XmUNSPECIFIED_PIXMAP) {
           printf ("can't create pixmap from %s0, argv[1]);
           exit (1);
       }

       /* Now create label using pixmap */
       label = XtVaCreateManagedWidget ("label", xmLabelWidgetClass, main_w,
           XmNlabelType,   XmPIXMAP,
           XmNlabelPixmap, pixmap,
           NULL);

       /* set the label as the "work area" of the main window */
       XtVaSetValues (main_w,
           XmNworkWindow, label,
           NULL);

       XtRealizeWidget (toplevel);
       XtAppMainLoop (app);
   }
In this example, the MainWindow widget is not used to its full potential. It only contains one other widget, a Label widget, that is used to display a bitmap from the file specified as the first argument on the command line (argv[1]). XtVaAppInitialize() parses the command-line arguments that are used when the program is run. The command-line options that are specific to Xlib or Xt are evaluated and removed from the argument list. What is not parsed is left in argv; our program reads argv[1] as the name of a bitmap to display in the MainWindow. The Label widget is used as the work area window for the MainWindow. We did this intentionally to focus your attention on the scrolled-window aspect of the MainWindow widget. The following command line:
   % show_pix /usr/include/X11/bitmaps/xlogo64
produces the output shown in the figure.

The file specified on the command line should contain X11 bitmap data, so that the application can create a pixmap. The pixmap is displayed in a Label widget, which has been specified as the XmNworkWindow of the MainWindow. As shown in the figure, the bitmap is simply displayed in the window. However, if a larger bitmap is specified, only a portion of the bitmap can be displayed, so ScrollBars are provided to allow the user to view the entire bitmap. The output of the command:

   % show_pix /usr/include/X11/bitmaps/escherknot
is shown in the figure.

figs.eps/V6a.04.02.eps.png
Output of show_pix xlogo64

figs.eps/V6a.04.03.eps.png
Output of show_pix escherknot

The bitmap is obviously too large to be displayed in the MainWindow without either clipping the image or enlarging the window. Rather than resize its own window to an unreasonable size, the MainWindow can display ScrollBars. This behavior is enabled by setting the MainWindow resources XmNscrollBarDisplayPolicy to XmAS_NEEDED and XmN­scrollingPolicy to XmAUTOMATIC . These values automate the process whereby ScrollBars are managed when they are needed. If there is enough room for the entire bitmap to be displayed, the ScrollBars are not provided. Try resizing the show_pix window and see how the ScrollBars appear and disappear as needed. This behavior occurs as a result of setting XmNscrollBarDisplayPolicy to XmAS_NEEDED.

Since we do not specify a size for the MainWindow, the toolkit sets both the width and height to be 100 pixels. These default values are not a documented feature. Both the MainWindow and the ScrolledWindow suffer from the same problem: if you do not specifically set the ­XmNwidth and XmNheight resources, the default size of the widget is not very useful.

The XmNscrollBarDisplayPolicy and XmNscrollingPolicy resources are inherited from the ScrolledWindow widget class. Because XmNscrollingPolicy is set to XmAUTOMATIC, the toolkit creates and manages the ScrollBars automatically. Another possible value for the resource is XmAPPLICATION_DEFINED, which implies that the application is going to create and manage the ScrollBars for the MainWindow and control all of the aspects of their functionality. Application-defined scrolling is the default style for the MainWindow widget, but it is unlikely that you will want to leave it that way, since automatic scrolling is far easier to manage at this stage of the game. For complete details on the different scrolling styles, see Chapter 9, ScrolledWindows and ScrollBars.

Using the application-defined scrolling policy does not necessarily require you to provide your own scrolling mechanisms. It simply relieves the MainWindow widget of the responsibility of handling the scrolling mechanisms. If you use a ScrolledList or ScrolledText widget as the work area, you should definitely leave the XmNscrollingPolicy as XmAPPLICATION_DEFINED, since these widgets manage their own ScrollBars. They will handle the scrolling behavior instead of the MainWindow. the source code shows an example of a program that uses a ScrolledList for the work area in a MainWindow widget. XtSetLanguageProc() is only available in X11R5; there is no corresponding function in X11R4.

   /* main_list.c -- Use the ScrolledList window as the feature
    * component of a MainWindow widget.
    */
   #include <Xm/MainW.h>
   #include <Xm/List.h>

   main(argc, argv)
   char *argv[];
   {
       Widget toplevel, main_w, list_w;
       XtAppContext app;
       Pixmap pixmap;

       XtSetLanguageProc (NULL, NULL, NULL);

       toplevel = XtVaAppInitialize (&app, "Demos",
           NULL, 0, &argc, argv, NULL, NULL);

       main_w = XtVaCreateManagedWidget ("main_window",
           xmMainWindowWidgetClass,   toplevel,
           NULL);

       list_w = XmCreateScrolledList (main_w, "main_list", NULL, 0);
       XtVaSetValues (list_w,
           XtVaTypedArg, XmNitems, XmRString,
               "Red, Green, Blue, Orange, Maroon, Grey, Black, White", 53,
           XmNitemCount,           8,
           XmNvisibleItemCount,    5,
           NULL);
       XtManageChild (list_w);

       /* set the list_w as the "work area" of the main window */
       XtVaSetValues (main_w, XmNworkWindow, XtParent (list_w), NULL);

       XtRealizeWidget (toplevel);
       XtAppMainLoop (app);
   }
In order to simplify the application, we specified the items in the ScrolledList as a single string:
   XtVaSetValues(list_w,
       XtVaTypedArg, XmNitems, XmRString,
           "Red, Green, Blue, Orange, Maroon, Grey, Black, White", 53,
       XmNitemCount,         8,
       XmNvisibleItemCount,  5,
       NULL);
This technique provides the easiest way to specify a list for a List widget. The items in a List widget must be specified as an array of compound strings. If we took the time to create each list item separately, we would have to create each compound string, assemble the array of XmString objects and specify it as the XmNitems resource, and then free each string separately after the widget was created. By using XtVaTypedArg, the whole list can be created in one line using the List widget's type converter to convert the string into a list of compound strings. We use this form of resource specification frequently in the book to simplify examples. See Volume Four, X Toolkit Intrinsics Programming Manual, for a complete discussion on how this kind of type conversion is done. See Chapter 12, The List Widget, for details on the List widget; see Chapter 19, Compound Strings, for details on compound strings.

It is important to note that while XmCreateScrolledList() creates both a ScrolledWindow widget and a List widget, it returns the List widget. As a result, we must use XtParent() to get access to the ScrolledWindow widget, so that it can be specified as the work area of the MainWindow. A common programming error with a ScrolledText or a ScrolledList widget is using the actual Text or List widget rather than its ScrolledWindow parent. Again, we refer you to Chapter 9, ScrolledWindows and ScrollBars , for a complete discussion of the use of ScrolledText and ScrolledList compound objects.

5.2 The MenuBar

Creating a MenuBar is a fairly complex operation, and one that is completely independent of the MainWindow itself. However, one of the principal reasons for using the MainWindow widget is that it manages the layout of a MenuBar. In this section, we demonstrate the simplest means of creating a MenuBar. Once a MenuBar has been created, you simply tell the MainWindow to include it in the window layout by specifying the MenuBar as the value of the XmNmenuBar resource for the MainWindow.

In the Motif toolkit, a MenuBar is not implemented as a separate widget, but as a set of CascadeButtons arranged horizontally in a RowColumn widget. Each CascadeButton is associated with a PulldownMenu that can contain PushButtons, ToggleButtons, Labels, and Separators. The managing RowColumn widget has a resource setting indicating that it is being used as a MenuBar. You do not need to know any specific details about any of these widgets in order to create a functional MenuBar, since Motif provides convenience routines that allow you to create self-sufficient menu systems. While the specifics on creating PopupMenus, PulldownMenus, and MenuBars are covered in more detail in Chapter 15, Menus, the basic case that we present in this section is quite simple.

There are a variety of methods that you can use to create and manage a MenuBar, but the easiest method is to use the convenience menu creation routine provided by the Motif toolkit: XmVaCreateSimpleMenuBar(). There is also a non-varargs version of this function. It requires you to create each of the buttons in the MenuBar individually and associate it with a PulldownMenu via resources. The varargs function is much easier to use. This function is demonstrated in the following code fragment:

   XmString file, edit, help;
   Widget menubar, main_w;
   ...
   /* Create a simple MenuBar that contains three menus */
   file = XmStringCreateLocalized ("File");
   edit = XmStringCreateLocalized ("Edit");
   help = XmStringCreateLocalized ("Help");
   menubar = XmVaCreateSimpleMenuBar (main_w, "menubar",
       XmVaCASCADEBUTTON, file, 'F',
       XmVaCASCADEBUTTON, edit, 'E',
       XmVaCASCADEBUTTON, help, 'H',
       NULL);
   XmStringFree (file);
   XmStringFree (edit);
   XmStringFree (help);
   ...
The output generated by this code is shown in the figure.

Like XtVaSetValues() and XtVaCreateWidget(), XmVaCreateSimpleMenuBar() takes a variable-length argument list of configuration parameters. In addition to resource/value pairs, it also takes special arguments that specify the items in the MenuBar. You can specify RowColumn-specific resource/value pairs just as you would for any varargs routine. Once all the items in a MenuBar have been created, it must be managed using XtManageChild().

figs.eps/V6a.04.04.eps.png
A simple MenuBar

If you are specifying an item in the MenuBar, the first parameter is a symbolic constant that identifies the type of the item. Since CascadeButtons are the only elements that can display PulldownMenus, the first parameter should always be set to XmVaCASCADEBUTTON. The label of the CascadeButton is given by the second parameter, which must be a compound string. In the above example, the variable file contains a compound string that contains the text File. The third parameter specifies an optional mnemonic character for the CascadeButton that can be used to post the menu from the keyboard. The mnemonic for the File menu is F. By convention, the first letter of a menu or menu item label is used as the mnemonic.

We use the compound string creation function, XmStringCreateLocalized(), to create the compound strings for the menu labels. This function creates a compound string with the text encoded in the current locale. XmStringCreateLocalized() is a new routine in Motif 1.2; it replaces XmStringCreateSimple(), which creates a compound string using the default character set associated with the widget in which the string is rendered. For a complete discussion of compound strings, see Chapter 19, Compound Strings.

Since you are not creating each CascadeButton using the normal creation routines, you are not returned a handle to each button. You might think that the label string that you assign to each button is used as the widget's name, but this is not the case. The buttons are created sequentially, so the MenuBar assigns the name button_n to each button. The value n is the position of the button in the MenuBar, where positions are numbered starting with 0 (zero). We will discuss how you can specify resources for items on the MenuBar later in the chapter.

Do not attempt to install callback routines on the CascadeButtons themselves. If you need to know when a particular menu is popped up, you should use the XmNpopupCallback on the MenuShell that contains the PulldownMenu associated with the CascadeButton. The popup and popdown callback lists are described briefly in Chapter 7, Custom Dialogs; for more information, see Volume Four, X Toolkit Intrinsics Programming Manual.

5.2.1 Creating a PulldownMenu

Every CascadeButton in a MenuBar must have a PulldownMenu associated with it. You can create the items in a PulldownMenu using a method that is similar to the one for creating a MenuBar. A PulldownMenu can be created using the function XmVaCreateSimplePulldownMenu(). This routine is slightly more involved than XmVaCreateSimpleMenuBar(). The routine takes the following form:

   Widget
   XmVaCreateSimplePulldownMenu (parent, name, post_from_button,
                                 callback, ...)
       Widget     parent;
       String     name;
       int        post_from_button;
       XtCallbackProc callback;
       ...
The post_from_button parameter specifies the CascadeButton that posts the PulldownMenu. This parameter is an index (starting at zero) into the array of CascadeButtons in the parent widget, which should be a MenuBar. The name parameter specifies the widget name for the RowColumn widget that is the PulldownMenu. This name is not the title of the CascadeButton associated with the menu. The MenuShell that contains the PulldownMenu uses the same name with _popup appended to it. The callback parameter specifies a function that is invoked whenever the user activates any of the items in the menu. The rest of the arguments to XmVaCreateSimplePulldownMenu() are either RowColumn resource/value pairs or special arguments that specify the items in the PulldownMenu.

You should not manage a PulldownMenu after you create it because you do not want it to appear until it is posted by the user. The CascadeButton that posts the menu handles ­managing the menu when it needs to be displayed. The following code fragment shows the use of XmVaCreateSimplePulldownMenu() to create a PulldownMenu:

   XmString open, save, quit, quit_acc;
   Widget menubar, menu;
   ...
   /* First menu is the File menu -- callback is file_cb() */
   open = XmStringCreateLocalized ("Open...");
   save = XmStringCreateLocalized ("Save...");
   quit = XmStringCreateLocalized ("Quit");
   quit_acc = XmStringCreateLocalized ("Ctrl-C");
   menu = XmVaCreateSimplePulldownMenu (menubar, "file_menu", 0, file_cb,
       XmVaPUSHBUTTON, open, 'O', NULL, NULL,
       XmVaPUSHBUTTON, save, 'S', NULL, NULL,
       XmVaSEPARATOR,
       XmVaPUSHBUTTON, quit, 'Q', "Ctrl<Key>c", quit_acc,
       NULL);
   XmStringFree (open);
   XmStringFree (save);
   XmStringFree (quit);
   XmStringFree (quit_acc);
   ...

Unlike a MenuBar, which can only contain CascadeButtons, a PulldownMenu can contain a number of different types of elements. As with XmVaCreateSimpleMenuBar(), these elements are specified by a symbolic constant that identifies the type of the item. The symbolic constant is followed by a variable number of additional parameters that depend on the type of the menu item. You can use the following values to specify the items in a PulldownMenu:

XmVaPUSHBUTTON

The item is a PushButton. It takes four additional parameters: a compound string label, a mnemonic, an accelerator, and a compound string that contains a text representation of the accelerator. When the PushButton is selected, the callback routine is called. It is passed an integer value as client_data that indicates the item on the PulldownMenu that was activated. The value is an index into the menu that ranges from 0 to n-1; if client_data is two, then the third item in the menu was selected.
XmVaTOGGLEBUTTON

The item is a ToggleButton. It takes the same four additional parameters as described for XmVaPUSHBUTTON. When the ToggleButton is selected, the value of the button is toggled and the callback routine is called. The client_data that is passed to the callback routine is handled the same as for PushButtons.
XmVaCHECKBUTTON

This value is identical to XmVaTOGGLEBUTTON.
XmVaRADIOBUTTON

The item is a ToggleButton with RadioBox characteristics, which means that only one item in the menu can be set at a time. The PulldownMenu does not enforce this behavior, so you must either handle it yourself or specify other RowColumn resources to make the menu function like a RadioBox. We demonstrate creating a menu with RadioBox behavior later in the chapter. This value takes the same additional parameters and deals with the callback routine in the same way as ToggleButtons.
XmVaCASCADEBUTTON

The item is a CascadeButton, which is usually associated with a pullright menu. The value takes two additional parameters: a compound string label and a mnemonic. Pullright menus are, ironically, easier to implement and manage using the not-so-simple menu creation routines described in Chapter 15, Menus.
XmVaSEPARATOR

The item is a Separator and it does not take any additional parameters. Since separators cannot be selected, the callback routine is not called for this item. Adding a separator does not affect the item count with respect to the client_data values that are passed to the callback routine for other menu items.
XmVaSINGLE_SEPARATOR

This value is identical to XmVaSEPARATOR.
XmVaDOUBLE_SEPARATOR

This value is identical to XmVaSEPARATOR, except that the separator widget displays a double line instead of a single line.
XmVaTITLE

The item is a Label that is used to create a title in a menu. It takes one additional parameter: a compound string label. The item is not selectable, so it does not have a mnemonic associated with it and it does not call the callback routine. Adding a title does not affect the item count with respect to the client_data values that are passed to the callback routine for other menu items.

Just as with the CascadeButtons in a MenuBar, the labels associated with each menu item are not the names of the widgets themselves. The names of the buttons are button_n , where n is the position of the button in the menu (starting with zero). Similarly, the names of the separators and the titles are separator_n and label_n, respectively. We will discuss how you can use resources to specify labels, mnemonics, and accelerators for menus and menu items later in the chapter.

Menus are not intended to be changed dynamically. You should not add, delete, or modify the menus on the MenuBar or the menu items in PulldownMenus once an application is running. Rather than delete an item on a menu when it is not appropriate, you should change the sensitivity of the item using XmNsensitive. The menus in an application should be static in the user's eyes; changing the menus would be like changing the functionality of the program while the user is running it. The one exception to this guideline involves menu items that correspond to dynamic objects. For example, if you have a menu that contains an item for each application that is running on a display, it is acceptable for the items on the menu to change to reflect the current state of the display.

5.2.2 SimpleMenu Callback Routines

The callback routine associated with the File menu shown earlier is invoked whenever the user selects any of the buttons in the menu. Just like any callback, the routine takes the form of an XtCallbackProc:

   void
   file_cb (widget, client_data, call_data)
       Widget widget;
       XtPointer client_data;
       XtPointer call_data;
The widget parameter is a handle to the widget that was selected in the menu. The ­client_data parameter is the index of the menu item in the menu. The call_data parameter is a pointer to a callback structure that contains data about callback. Both the ­client_data and call_data parameters should be cast to their appropriate types before the data that they contain is accessed.

Every Motif callback routine has a callback structure associated with it. The simplest such structure is of type XmAnyCallbackStruct, which has the following form:

   typedef struct {
       int     reason;
       XEvent *event;
   } XmAnyCallbackStruct;
All of the Motif callback structures have these two fields, but they also contain more detailed information about why the callback function was invoked. The callback routine for the File menu would be passed an XmPushButtonCallbackStruct, since all of the menu items are PushButtons. This structure has the following form:
   typedef struct {
       int     reason;
       XEvent *event;
       int     click_count;
   } XmPushButtonCallbackStruct;
The click_count field is not normally used when a PushButton is in a menu. If one of the items in the menu were a ToggleButton, the call_data parameter would be of type XmToggleButtonCallbackStruct, which has the following form:
   typedef struct {
       int     reason;
       XEvent *event;
       int     set;
   } XmToggleButtonCallbackStruct;
The set field indicates whether the item was selected (turned on) or deselected (turned off).

When a menu contains both PushButtons and ToggleButtons, you can determine which of the two callback structures the call_data parameter points to by examining the reason field. Since all callback structures have this field, it is always safe to query it. As its name implies, this field indicates why the callback routine was invoked. The value of this field may also indicate the type of the widget that invoked the callback. While we can always determine the type of the widget parameter by using the macro XtIsSubClass(), using the reason field is more straightforward. The PushButton widget uses the value XmCR_ACTIVATE to indicate that it has been activated, while the ToggleButton uses XmCR_VALUE_CHANGED to indicate that its value has been changed. In our example, the reason will always be XmCR_ACTIVATE, since there are only PushButtons in the menu. If there were also ToggleButtons in the menu, we would know that the callback was invoked by a ToggleButton if the value were XmCR_VALUE_CHANGED.

The event field in all of the callback structures is a pointer to an XEvent structure. The XEvent identifies the actual event that caused the callback routine to be invoked. In this example, the event is not of particular interest.

In the callback function, you can choose to do whatever is appropriate for the item that was selected. The callback structure is probably not going to be of that much help in most cases. However, the client_data passed to the function can be used to identify which of the menu items was selected. The following code fragment demonstrates the use of ­client_data:

   /* a menu item from the "File" pulldown menu was selected */
   void
   file_cb(widget, client_data, call_data)
   Widget widget;
   XtPointer client_data;
   XtPointer call_data;
   {
       extern void OpenNewFile(), SaveFile();
       int item_no = (int) client_data;

       if (item_no == 0)   /* the "new" button */
           OpenNewFile ();
       else if (item_no == 1)  /* the "save" button */
           SaveFile();
       else        /* the "Quit" button */
           exit (0);
   }
The callback routines for menu items should be as simple as possible from a structural point of view. A well-designed application should have application-specific entry points such as OpenNewFile() and SaveFile(), as shown in the previous example. These routines should be defined in separate files that are not necessarily associated with the user-interface portion of the program. The use of modular programming techniques helps considerably when an application is being maintained by a large group of people or when it needs to be ported to other user-interface platforms.

5.2.3 A Sample Application

Let's examine an example program that integrates what we have discussed so far. Example 4-3 modifies the behavior of our first example, which displayed an arbitrary pixmap, by allowing the user to change the bitmap dynamically using a Motif FileSelectionDialog. The program also allows the user to dynamically change the color of the bitmap using a PulldownMenu. As you can see by the size of the program, adding these two simple features is not trivial. Many functions and widgets are required in order to make the program functional. As you read the example, don't worry about unknown widgets or details that we haven't addressed just yet; we will discuss them afterwards. For now, just try to identify the familiar parts and see how everything works together. XtSetLanguageProc() is only available in X11R5; there is no corresponding function in X11R4. XmStringCreateLocalized() is only available in Motif 1.2; XmStringCreateSimple() is the corresponding function in Motif 1.1. XmFONTLIST_DEFAULT_TAG replaces XmSTRING_DEFAULT_CHARSET in Motif 1.2.

   /* dynapix.c -- Display a bitmap in a MainWindow, but allow the user
    * to change the bitmap and its color dynamically.  The design of the
    * program is structured on the pulldown menus of the menubar and the
    * callback routines associated with them.  To allow the user to choose
    * a new bitmap, the "Open" button pops up a FileSelectionDialog where
    * a new bitmap file can be chosen.
    */
   #include <Xm/MainW.h>
   #include <Xm/Label.h>
   #include <Xm/MessageB.h>
   #include <Xm/FileSB.h>

   /* Globals: the toplevel window/widget and the label for the bitmap.
    * "colors" defines the colors we use, "cur_color" is the current
    * color being used, and "cur_bitmap" references the current bitmap file.
    */
   Widget toplevel, label;
   String colors[] = { "Black", "Red", "Green", "Blue" };
   Pixel cur_color;
   char cur_bitmap[1024] = "xlogo64"; /* make large enough for full pathnames */

   main(argc, argv)
   int argc;
   char *argv[];
   {
       Widget main_w, menubar, menu, widget;
       XtAppContext app;
       Pixmap pixmap;
       XmString file, edit, help, open, quit, red, green, blue, black;
       void file_cb(), change_color(), help_cb();

       XtSetLanguageProc (NULL, NULL, NULL);

       /* Initialize toolkit and parse command line options. */
       toplevel = XtVaAppInitialize (&app, "Demos",
           NULL, 0, &argc, argv, NULL, NULL);

       /* main window contains a MenuBar and a Label displaying a pixmap */
       main_w = XtVaCreateManagedWidget ("main_window",
           xmMainWindowWidgetClass,   toplevel,
           XmNscrollBarDisplayPolicy, XmAS_NEEDED,
           XmNscrollingPolicy,        XmAUTOMATIC,
           NULL);

       /* Create a simple MenuBar that contains three menus */
       file = XmStringCreateLocalized ("File");
       edit = XmStringCreateLocalized ("Edit");
       help = XmStringCreateLocalized ("Help");
       menubar = XmVaCreateSimpleMenuBar (main_w, "menubar",
           XmVaCASCADEBUTTON, file, 'F',
           XmVaCASCADEBUTTON, edit, 'E',
           XmVaCASCADEBUTTON, help, 'H',
           NULL);
       XmStringFree (file);
       XmStringFree (edit);
       /* don't free "help" compound string yet -- reuse it later */

       /* Tell the menubar which button is the help menu  */
       if (widget = XtNameToWidget (menubar, "button_2"))
           XtVaSetValues (menubar, XmNmenuHelpWidget, widget, NULL);

       /* First menu is the File menu -- callback is file_cb() */
       open = XmStringCreateLocalized ("Open...");
       quit = XmStringCreateLocalized ("Quit");
       XmVaCreateSimplePulldownMenu (menubar, "file_menu", 0, file_cb,
           XmVaPUSHBUTTON, open, 'N', NULL, NULL,
           XmVaSEPARATOR,
           XmVaPUSHBUTTON, quit, 'Q', NULL, NULL,
           NULL);
       XmStringFree (open);
       XmStringFree (quit);

       /* Second menu is the Edit menu -- callback is change_color() */
       black = XmStringCreateLocalized (colors[0]);
       red = XmStringCreateLocalized (colors[1]);
       green = XmStringCreateLocalized (colors[2]);
       blue = XmStringCreateLocalized (colors[3]);
       menu = XmVaCreateSimplePulldownMenu (menubar, "edit_menu", 1, change_color,
           XmVaRADIOBUTTON, black, 'k', NULL, NULL,
           XmVaRADIOBUTTON, red, 'R', NULL, NULL,
           XmVaRADIOBUTTON, green, 'G', NULL, NULL,
           XmVaRADIOBUTTON, blue, 'B', NULL, NULL,
           XmNradioBehavior, True,     /* RowColumn resources to enforce */
           XmNradioAlwaysOne, True,    /* radio behavior in Menu */
           NULL);
       XmStringFree (black);
       XmStringFree (red);
       XmStringFree (green);
       XmStringFree (blue);

       /* Initialize menu so that "black" is selected. */
       if (widget = XtNameToWidget (menu, "button_0"))
           XtVaSetValues (widget, XmNset, True, NULL);

       /* Third menu is the help menu -- callback is help_cb() */
       XmVaCreateSimplePulldownMenu (menubar, "help_menu", 2, help_cb,
           XmVaPUSHBUTTON, help, 'H', NULL, NULL,
           NULL);
       XmStringFree (help); /* we're done with it; now we can free it */

       XtManageChild (menubar);

       /* user can still specify the initial bitmap */
       if (argv[1])
           strcpy (cur_bitmap, argv[1]);
       /* initialize color */
       cur_color = BlackPixelOfScreen (XtScreen (toplevel)),

       /* create initial bitmap */
       pixmap = XmGetPixmap (XtScreen (toplevel), cur_bitmap,
           cur_color, WhitePixelOfScreen (XtScreen (toplevel)));

       if (pixmap == XmUNSPECIFIED_PIXMAP) {
           puts ("can't create initial pixmap");
           exit (1);
       }

       /* Now create label using pixmap */
       label = XtVaCreateManagedWidget ("label", xmLabelWidgetClass, main_w,
           XmNlabelType,   XmPIXMAP,
           XmNlabelPixmap, pixmap,
           NULL);

       /* set the label as the "work area" of the main window */
       XtVaSetValues (main_w,
           XmNmenuBar,    menubar,
           XmNworkWindow, label,
           NULL);

       XtRealizeWidget (toplevel);
       XtAppMainLoop (app);
   }

   /* Any item the user selects from the File menu calls this function.
    * It will either be "Open" (item_no == 0) or "Quit" (item_no == 1).
    */
   void
   file_cb(widget, client_data, call_data)
   Widget widget;        /* menu item that was selected */
   XtPointer client_data;  /* the index into the menu */
   XtPointer call_data;  /* unused */
   {
       static Widget dialog; /* make it static for reuse */
       extern void load_pixmap();
       int item_no = (int) client_data;

       if (item_no == 1) /* the "quit" item */
           exit (0);

       /* "Open" was selected.  Create a Motif FileSelectionDialog w/callback */
       if (!dialog) {
           dialog = XmCreateFileSelectionDialog (toplevel, "file_sel", NULL, 0);
           XtAddCallback (dialog, XmNokCallback, load_pixmap, NULL);
           XtAddCallback (dialog, XmNcancelCallback, XtUnmanageChild, NULL);
       }
       XtManageChild (dialog);
       XtPopup (XtParent (dialog), XtGrabNone);
   }

   /* The OK button was selected from the FileSelectionDialog (or, the user
    * double-clicked on a file selection).  Try to read the file as a bitmap.
    * If the user changed colors, we call this function directly from change_color()
    * to reload the pixmap.  In this case, we pass NULL as the callback struct
    * so we can identify this special case.
    */
   void
   load_pixmap(dialog, client_data, call_data)
   Widget dialog;  
   XtPointer client_data;
   XtPointer call_data;
   {
       Pixmap pixmap;
       char *file = NULL;
       XmFileSelectionBoxCallbackStruct *cbs =
           (XmFileSelectionBoxCallbackStruct *) call_data;

       if (cbs) {
           if (!XmStringGetLtoR (cbs->value, XmFONTLIST_DEFAULT_TAG, &file))
               return; /* internal error */
           (void) strcpy (cur_bitmap, file);
           XtFree (file); /* free allocated data from XmStringGetLtoR() */
       }

       pixmap = XmGetPixmap (XtScreen (toplevel), cur_bitmap,
           cur_color, WhitePixelOfScreen (XtScreen (toplevel)));

       if (pixmap == XmUNSPECIFIED_PIXMAP)
           printf ("Can't create pixmap from %s0, cur_bitmap);
       else {
           Pixmap old;
           XtVaGetValues (label, XmNlabelPixmap, &old, NULL);
           XmDestroyPixmap (XtScreen (toplevel), old);
           XtVaSetValues (label,
               XmNlabelType,   XmPIXMAP,
               XmNlabelPixmap, pixmap,
               NULL);
       }
   }

   /* called from any of the "Edit" menu items.  Change the color of the
    * current bitmap being displayed.  Do this by calling load_pixmap().
    */
   void
   change_color(widget, client_data, call_data)
   Widget widget;     /* menu item that was selected */
   XtPointer client_data; /* the index into the menu */
   XtPointer call_data;  /* unused */
   {
       XColor xcolor, unused;
       Display *dpy = XtDisplay (label);
       Colormap cmap = DefaultColormapOfScreen (XtScreen (label));
       int item_no = (int) client_data;

       if (XAllocNamedColor (dpy, cmap, colors[item_no], &xcolor, &unused) == 0 ||
           cur_color == xcolor.pixel)
           return;

       cur_color = xcolor.pixel;
       load_pixmap (widget, NULL, NULL);
   }

   #define MSG "Use the FileSelection dialog to find bitmap files to0isplay in the scrolling area in the main window.  Use0he edit menu to display the bitmap in different colors."

   /* The help button in the help menu from the menubar was selected.
    * Display help information defined above for how to use the program.
    * This is done by creating a Motif information dialog box.  Again,
    * make the dialog static so we can reuse it.
    */
   void
   help_cb(widget, client_data, call_data)
   Widget widget;     
   XtPointer client_data; 
   XtPointer call_data;  
   {
       static Widget dialog;

       if (!dialog) {
           Arg args[5];
           int n = 0;
           XmString msg = XmStringCreateLtoR (MSG, XmFONTLIST_DEFAULT_TAG);
           XtSetArg (args[n], XmNmessageString, msg); n++;
           dialog = XmCreateInformationDialog (toplevel, "help_dialog", args, n);
       }
       XtManageChild (dialog);
       XtPopup (XtParent (dialog), XtGrabNone);
   }

The output of the program is shown in the figure.

figs.eps/V6a.04.05.eps.png
Output of dynapix.c

The beginning of the program is pretty much as expected. After the toolkit is initialized, the MainWindow and the MenuBar are created the same way as in the previous examples. Just after the MenuBar is created, however, we make the following calls:

   if (widget = XtNameToWidget (menubar, "button_2"))
       XtVaSetValues(menubar, XmNmenuHelpWidget, widget, NULL);
The purpose of these statements is to inform the MenuBar which of its CascadeButtons contains the Help menu. Setting the MenuBar's XmNmenuHelpWidget resource to the CascadeButton returned by XtNameToWidget() causes the MenuBar to position the menu specially. The Help menu is placed at the far right on the MenuBar; this position is necessary for the application to conform to Motif sytle guidelines. For details on how to support a help system, see Chapter 7, Custom Dialogs, and Chapter 21, Advanced Dialog Programming.

PulldownMenus are created next in the expected manner. The only variation is for the Edit menu, where each item in the menu represents a color. Since only one color can be used at a time, the color that is currently being used is marked with a diamond-shape indicator. In order to get this radio-box behavior, each menu item in the PulldownMenu is a XmVaRADIOBUTTON and the menu is told to treat the items as a RadioBox. The analogy is that of an old car radio, where selecting a new station causes the other selectors to pop out. Just as you can only have the radio tuned to one station at a time, you may only have one color set at a time. The RadioBox functionality is managed automatically by the RowColumn widget that is used to implement the PulldownMenu. Setting the XmNradioBehavior and XmN­radioAlwaysOne RowColumn resources to True provides the RadioBox behavior. See Chapter 11, Labels and Buttons, for a complete description and further examples of this type of behavior. the figure shows the RadioBox-style Edit menu.

figs.eps/V6a.04.06.eps.png
The Edit menu for dynapix.c


Although the RowColumn manages the RadioBox automatically, we need to turn the radio on by setting the initial color. After the PulldownMenu is created, the menu (RadioBox) is initialized so that its first item is selected, since we know that we are using black as the initial color. XtNameToWidget() is used again to get the appropriate button from the menu. Since the menu items were created using XmVaRADIOBUTTON, the widget that is returned is a ToggleButton. The XmNset resource is used to turn the button on. Once the menu has been initialized, the Motif toolkit handles everything automatically.

Note that when we create the Help menu, there is only one item in the menu. You might think that it is redundant to have a single Help item in the Help menu, but this design is an element of Motif style. The Motif Style Guide states that items on the MenuBar should always post PulldownMenu, not perform application actions directly.

It is important to note that XmVaCreateSimplePulldownMenu() returns the RowColumn widget that contains the items in the menu, even though the routine creates both the RowColumn widget and its MenuShell parent. The routine does not return the MenuShell widget that is actually popped up and down when the menu posted. To get a handle to that widget, you need to use XtParent() on the RowColumn widget. This design makes sense, since you need access to the RowColumn widget much more often than you need access to the MenuShell.

Once all of the items have been installed, the MenuBar is managed using XtManageChild(). The approach to creating MenuBars, PulldownMenus, menu items, and their associated callback routines that we have described here is meant to be simple and straightforward. In some cases, you may find that these techniques are too limiting. For example, you cannot specify different callback routines for different items in the same menu, you cannot pass different client data for different items, and you cannot name the widgets individually. The most inconvenient aspect of this method, however, is that it requires so much redundant code in order to build a realistically sized MenuBar. Our intent here is to introduce the basic concepts of menus and to demonstrate the recommended design approach for applications. We describe how the menu creation process can be generalized for large menu systems in Chapter 15, Menus.

The rest of the source code is composed of callback routines that are used by the PulldownMenu items. For example, when the user selects either of the items in the File menu, the function file_cb() is called. If the Quit item is selected, the ­ client_data parameter is 1 and the program exits. If the Open item is selected, client_data is 0 and a FileSelectionDialog is popped up to allow the user to select a new bitmap file. The dialog is created using the convenience routine XmCreateFileSelectionDialog(), which produces the results shown in the figure. Two callback routines are installed for the dialog: load_pixmap(), which is called when the user presses the OK button, and ­XtUnmanageChild(), which is called when the user selects the Cancel button. For more detailed information on the FileSelectionDialog, see Chapter 6, Selection Dialogs.

The load_pixmap() function loads a new bitmap from a file and displays it in the Label widget. This function uses the same method for loading a pixmap as was used earlier in main(). Since the function is invoked as a callback by the FileSelectionDialog, we need to get the value of the file selection. The value is taken from the value field of the FileSelectionDialog's callback structure, XmFileSelectionBoxCallbackStruct. Since the filename is represented as a compound string, it must be converted to a character string. The conversion is done using XmStringGetLtoR(), which creates a regular C string for use by XmGetPixmap(). The load_pixmap() routine is also called directly from change_color(), so we need to check the call_data parameter. This parameter is NULL if the routine is not invoked as a callback.

If XmGetPixmap() succeeds, we get the old pixmap and destroy it using XmDestroyPixmap() before we install the new pixmap. XmGetPixmap() loads and caches a pixmap. If the function is called more than once for a given image, it returns the cached image, which saves space because a new version of the pixmap is not allocated for each call. XmDestroyPixmap() decrements the reference count for the image; if the reference count reaches to zero, the pixmap is actually destroyed. Otherwise, another reference to it may exist, so nothing is done. It is important to use these two functions in conjunction with each other. However, if you use other pixmap-loading functions to create pixmaps, you cannot use XmDestroyPixmap() to free them.

figs.eps/V6a.04.07.eps.png
The FileSelectionDialog for dynapix.c

The function change_color() is used as the callback routine for items in the Edit menu. The names of the colors are stored in the colors array. The index of a color in this array is the same as the index of the corresponding menu item in the menu. The color name is parsed and loaded using XAllocNamedColor(), provided that the string exists in the RGB database (usually /usr/lib/X11/rgb.txt). If the routine is successful, it returns a non-zero status and the XColor structure is filled with the RGB data and pixel value. In this case, load_pixmap() is called to reload the pixmap with the new color. If XAllocNamedColor() returns zero, or if the returned pixel value is the same as the current one, change_color() returns, as there is no point in reloading an identical pixmap. For additional information about loading and using colors, see Volume One, Xlib Programming Manual, and Volume Two, Xlib Reference Manual.

The help_cb() function is the callback routine for the Help menu item on the Help menu. It simply displays an InformationDialog that contains a message describing how to use the program. See Chapter 5, Introduction to Dialogs, and Chapter 21, Advanced Dialog Programming, for a complete description of these dialogs and suggestions on implementing a functional help system.

5.3 The Command and Message Areas

We have already covered most of what you need to know about the MainWindow of an application in this chapter and Chapter 3, Overview of the Motif Toolkit. The material in the rest of the chapter is considered somewhat advanced, so you could skip the remaining sections and be relatively secure in moving on to the next chapter. The remaining material provides details about the MainWindow widget that need to be discussed in order to make this chapter complete.

The greatest difficulty with the command and message areas of the MainWindow is that these objects are better defined in the Motif specification than in the Motif toolkit. The command area is intended to support a tty-style command-line interface to an application. The command area is not supposed to act like xterm or any sort of terminal emulator; it is just a single-line text area for entering individually typed commands for an application. The message area is just an output-only area that is used for error and status messages as needed by an application. While both of these areas are optional MainWindow elements, the message area is usually more common than the command area. Nevertheless, let's begin by discussing the command area.

A command area is especially convenient for applications that are being converted from a tty-style interface to a graphical user interface. Properly converted, such applications can do rather well as GUI-based programs, although the conversion can be more difficult than you might expect. For example, a PostScript interpreter could be implemented using a command area in the MainWindow. However, since PostScript is a verbose language, it does not work well with single-line text entry fields.

the source code shows how the command area can be used to allow the user to input standard UNIX commands. The output of the commands is displayed in the ScrolledText object, which is the work area of the MainWindow. For simplicity, we've kept the MenuBar small so as to dedicate most of the program to the use of the command area. XtSetLanguageProc() is only available in X11R5; there is no corresponding function in X11R4. XmStringCreateLocalized() is only available in Motif 1.2; XmStringCreateSimple() is the corresponding function in Motif 1.1. XmFONTLIST_DEFAULT_TAG replaces XmSTRING_DEFAULT_CHARSET in Motif 1.2.

   /* cmd_area.c -- use a ScrolledText object to view the
    * output of commands input by the user in a Command window.
    */
   #include <Xm/Text.h>
   #include <Xm/MainW.h>
   #include <Xm/Command.h>
   #include <stdio.h>         /* For popen() */

   /* main() -- initialize toolkit, create a main window, menubar,
    * a Command Area and a ScrolledText to view the output of commands.
    */
   main(argc, argv)
   int argc;
   char *argv[];
   {
       Widget        top, main_w, menubar, menu, command_w, text_w;
       XtAppContext  app;
       XmString      file, quit;
       extern void   exec_cmd(), exit();
       Arg           args[5];
       int           n = 0;

       XtSetLanguageProc (NULL, NULL, NULL);

       /* initialize toolkit and create toplevel shell */
       top = XtVaAppInitialize (&app, "Demos",
           NULL, 0, &argc, argv, NULL, NULL);

       (void) close (0); /* don't let commands read from stdin */

       /* MainWindow for the application -- contains menubar, ScrolledText
        * and CommandArea (which prompts for filename).
        */
       main_w = XtVaCreateManagedWidget ("main_w",
           xmMainWindowWidgetClass, top,
           XmNcommandWindowLocation, XmCOMMAND_BELOW_WORKSPACE,
           NULL);

       /* Create a simple MenuBar that contains one menu */
       file = XmStringCreateLocalized ("File");
       menubar = XmVaCreateSimpleMenuBar (main_w, "menubar",
           XmVaCASCADEBUTTON, file, 'F',
           NULL);
       XmStringFree (file);

       /* "File" menu has only one item (Quit), so make callback exit() */
       quit = XmStringCreateLocalized ("Quit");
       menu = XmVaCreateSimplePulldownMenu (menubar, "file_menu", 0, exit,
           XmVaPUSHBUTTON, quit, 'Q', NULL, NULL,
           NULL);
       XmStringFree (quit);

       /* Menubar is done -- manage it */
       XtManageChild (menubar);

       /* Create ScrolledText -- this is work area for the MainWindow */
       XtSetArg (args[n], XmNrows,      24); n++;
       XtSetArg (args[n], XmNcolumns,   80); n++;
       XtSetArg (args[n], XmNeditable,  False); n++;
       XtSetArg (args[n], XmNeditMode,  XmMULTI_LINE_EDIT); n++;
       text_w = XmCreateScrolledText (main_w, "text_w", args, n);
       XtManageChild (text_w);

       /* store text_w as user data in "File" menu for file_cb() callback */
       XtVaSetValues (menu, XmNuserData, text_w, NULL);

       /* Create the command area -- this must be a Command class widget */
       file = XmStringCreateLocalized ("Command:");
       command_w = XtVaCreateWidget ("command_w", xmCommandWidgetClass, main_w,
           XmNpromptString, file,
           NULL);
       XmStringFree (file);
       XtAddCallback (command_w, XmNcommandEnteredCallback, exec_cmd, text_w);
       XtManageChild (command_w);

       XmMainWindowSetAreas (main_w, menubar, command_w,
           NULL, NULL, XtParent (text_w));
       XtRealizeWidget (top);
       XtAppMainLoop (app);
   }

   /* execute the command and redirect output to the ScrolledText window */
   void
   exec_cmd (cmd_widget, client_data, call_data)
   Widget cmd_widget;  /* the command widget itself, not its Text widget */
   XtPointer client_data; /* passed the text_w as client_data */
   XtPointer call_data;
   {
       char *cmd, buf[BUFSIZ];
       XmTextPosition pos;
       FILE *pp, *popen();
       Widget text_w = (Widget) client_data;
       XmCommandCallbackStruct *cbs =
           (XmCommandCallbackStruct *) call_data;

       XmStringGetLtoR (cbs->value, XmFONTLIST_DEFAULT_TAG, &cmd);

       if (!cmd || !*cmd) { /* nothing typed? */
           if (cmd)
               XtFree (cmd);
           return;
       }

       /* make sure the file is a regular text file and open it */
       if (!(pp = popen (cmd, "r")))
           perror (cmd);
       XtFree (cmd);
       if (!pp)
           return;

       /* put the output of the command in the Text widget by reading
        * until EOF (meaning that the command has terminated).
        */
       for (pos = 0; fgets (buf, sizeof buf, pp); pos += strlen (buf))
           XmTextReplace (text_w, pos, pos, buf);

       pclose (pp);
   }
This example uses a Command widget for the command area. The output of the program is shown in the figure. The Command widget provides a command entry area and a command history area. However, you do not necessarily have to use a Command widget for the command area. A TextField widget can be used instead to provide a simple command area.

When we created the MainWindow, we set the XmNcommandWindowLocation resource to XmCOMMAND_BELOW_WORKSPACE , which caused the command area to be placed below the work window. Although the default value of the resource is XmCOMMAND_ ABOVE_WORKSPACE, the Style Guide recommends that the command area be positioned beneath the work window, rather than above it. You need to explicitly set the value of XmN­commandWindowLocation to ensure that the command area is positioned appropriately.

Note that we use the ScrolledWindow that is created by XmCreateScrolledText() for the work window, rather than the scrolling area provided by the MainWindow. Since XmCreateScrolledText() returns a Text widget, we are careful to use the parent of the Text widget for the XmNworkWindow resource of the MainWindow. We set the areas of the MainWindow using XmMainWindowSetAreas(), which is a convenience function that tells the MainWindow which of its child widgets should be used for its different predefined areas. The routine takes the following form:

   void
   XmMainWindowSetAreas (main_w, menubar, cmd_w, h_scroll,
                         v_scroll, work_w)
       Widget main_w;
       Widget menubar;
       Widget cmd_w;
       Widget h_scroll;
       Widget v_scroll;
       Widget work_w;

figs.eps/V6a.04.08.eps.png
Output of cmd_area.c

The function is really a front end for XmScrolledWindowSetAreas(). Basically, both of these functions manage the appropriate widgets so that they appear in the correct locations in the MainWindow, while making sure there is enough space for all of them to be visible. Neither function is entirely necessary, though. When you create a widget as a child of a MainWindow widget, the MainWindow checks the type of the widget you are adding. If the new widget is a RowColumn that is being used as a MenuBar ( XmNrowColumnType is XmMENU_BAR), the MainWindow automatically uses it for the menu bar. This same check is performed for a Command widget, which is automatically used as the command area. The MainWindow also provides resources for its different areas that you can set using XtVaSetValues(). The resources you can use are:

   XmNmenuBar
   XmNcommandWindow
   XmNverticalScrollBar
   XmNhorizonalScrollBar
   XmNworkWindow
   XmNmessageWindow
Once one of these values is set, it cannot be reset to NULL, although it can be reset to another widget. However, XmMainWindowSetAreas() can be used to set the different areas to NULL. You should only use this routine when you are doing the initial layout of your application; changing the major elements of the MainWindow while an application is running would be quite disruptive.

You might notice that XmMainWindowSetAreas() does not have a parameter to specify the widget that is used as the message area. There is, however, a resource to support the message area. The message area is important in most applications, since it is typically the place where brief status and informational messages are displayed. The message area can be implemented using different widgets, such as a read-only Text widget, a read-only ScrolledText object, or a Label widget. Using a Label widget as the message area is quite simple and really doesn't require any explanation. Chapter 14, Text Widgets , describes how to use a read-only text area for the message area in a MainWindow in Section #soutputtext.

If you specify the XmNmessageWindow resource, the message area is positioned across the bottom of the MainWindow. If you are not satisfied with how the MainWindow handles the layout of the message area, you can make the message area widget a child of the work area manager widget and handle the layout yourself.

5.4 Using Resources

Resources specific to the MainWindow and its sub-elements can be useful when configuring the default appearance of your application. If you set these resources in an app-defaults file, the specifications can also provide a framework for users to follow when they want to set their own configuration parameters. Even users who are sophisticated enough to figure out how X resource files work still copy existing files and modify them to their own tastes. To assist users, the app-defaults file for an application should be informative and complete, even though it might be lengthy.

Of course, the first step in specifying resources in an app-defaults file is to determine exactly which aspects of the program you want to be configurable. Remember, consistency is the only way to keep from completely confusing a user. Once you have decided which portions of the application are going to be configurable, you can set resource values by specifying complete widget hierarchies. As an example, let's specify some resources for the menu system from dynapix.c. The application creates the File menu in the following way:

   XmVaCreateSimplePulldownMenu(menubar, "file_menu", 0, file_cb,
       XmVaPUSHBUTTON, open, 'O', NULL, NULL,
       XmVaSEPARATOR,
       XmVaPUSHBUTTON, quit, 'Q', NULL, NULL,
       NULL);
We can add accelerators to both the Open and Quit menu items using the following resource specifications:
   dynapix.main_window.menubar*button_0.accelerator: Ctrl<Key>O
   dynapix.main_window.menubar*button_0.acceleratorText: Ctrl+O
   dynapix.main_window.menubar*button_1.accelerator: Ctrl<Key>C
   dynapix.main_window.menubar*button_1.acceleratorText: Ctrl+C
The result is shown in the figure.

figs.eps/V6a.04.09.eps.png
The File menu for dynapix.c with accelerators

These resource settings work because XmNaccelerator and XmNacceleratorText were not hard-coded by the application. By the same token, the labels of the MenuBar titles and the menu items in the PulldownMenus are hard-coded values that cannot be modified through resources. To relax this restriction, you could try setting the label and mnemonic parameters to NULL in calls to XmVaCreateSimplePulldownMenu(). Unfortunately, this technique makes resource specification awfully messy, since the CascadeButtons in the MenuBar and the various PulldownMenus all have names of the form button_n . The other alternative is to use the more advanced methods of menu creation that are described in Chapter 15, Menus.

The MainWindow provides a few other resources that control different visual attributes: ­XmNshowSeparator, XmNmainWindowMarginWidth, and XmNmainWindowMarginHeight. The XmNshowSeparator resource controls whether or not Separator widgets are displayed between the different areas of a MainWindow. The margin resources specify the width and height of the MainWindow's margins. Generally, these resources should not be set by the application, but left to the user to specify. For example:

   *XmMainWindow.showSeparator: True
   *XmMainWindow.mainWindowMarginWidth: 10
   *XmMainWindow.mainWindowMarginHeight: 10
The class name for the MainWindow widget is XmMainWindow. If these resource settings were specified in an app-defaults file, they would affect all of the MainWindow widgets in the application. If a user makes these specifications in his .Xdefaults file, they would apply to all MainWindow widgets in all applications.

5.5 Summary

This chapter introduced you to the concepts involved in creating the main window of an application. To a lesser degree, we showed you how the MainWindow widget can be used to accomplish some of the necessary tasks. We identified the areas involved in a MainWindow and used some convenience routines to build some adequate prototypes.

The MainWindow can be difficult to understand because of its capabilities as a ScrolledWindow and because it supports the management of so many other objects. The work area of a MainWindow usually contains a manager widget that contains other widgets. Although the MainWindow can handle the layout of its different areas, we do not necessarily encourage you to use all its of its features. For larger, production-style applications, you would probably be better off using the MainWindow for the sake of the MenuBar, while placing the rest of the layout in the hands of a more general-purpose manager widget. These are described in Chapter 8, Manager Widgets.

You could also decide not to use the MainWindow widget at all. If done properly, you could probably use one of the manager widget classes described in Chapter 8 and still be Motif-compliant. Depending on your application, you might find this technique easier to deal with than the MainWindow widget.

5.6 Exercises

Based on the material in this chapter, you should be able to do the following exercises:


Contents Previous Next