Contents Previous Next

6 Introduction to Dialogs


This chapter describes the fundamental concepts that underly all Motif dialogs. It provides a foundation for the more advanced material in the following chapters. In the course of the introduction, the chapter also provides information about Motif's predefined MessageDialog classes.

In Chapter 4, The Main Window, we discussed the top-level windows that are managed by the window manager and that provide the overall framework for an application. Most applications are too complex to do everything in one main top-level window. Situations arise that call for secondary windows, or transient windows, that serve specific purposes. These windows are commonly referred to as dialog boxes, or more simply as dialogs.

Dialog boxes play an integral role in a GUI-based interface such as Motif. The examples in this book use dialogs in many ways, so just about every chapter can be used to learn more about dialogs. We've already explored some of the basic concepts in Chapter 2, The Motif Programming Model, and Chapter 3, Overview of the Motif Toolkit. However, the use of dialogs in Motif is quite complex, so we need more detail to proceed further.

The Motif Style Guide makes a set of generic recommendations about how all dialogs should look. The Style Guide also specifies precisely how certain dialogs should look, how they should respond to user events, and under what circumstances the dialogs should be used. We refer to these dialogs as predefined Motif dialogs, since the Motif toolkit implements each of them for you. These dialogs are completely self-sufficient, opaque objects that require very little interaction from your application. In most situations, you can create the necessary dialog using a single convenience routine and you're done. If you need more functionality than what is provided by a predefined Motif dialog, you may have to create your own customized dialog. In this case, building and handling the dialog requires a completely different approach.

There are three chapters on basic dialog usage in this book--two on the predefined Motif dialogs and one on customized dialogs. There is also an additional chapter later in the book that deals with more advanced dialog topics. This first chapter discusses the most common class of Motif dialogs, called MessageDialogs. These are the simplest kinds of dialogs; they typically display a short message and use a small set of standard responses, such as OK, Yes, or No. These dialogs are transient, in that they are intended to be used immediately and then dismissed. MessageDialogs define resources and attributes that are shared by most of the other dialogs in the Motif toolkit, so they provide a foundation for us to build upon in the later dialog chapters. Although Motif dialogs are meant to be opaque objects, we will examine their implementation and behavior in order to understand how they really work. This information can help you understand not only what is happening in your application, but also how to create customized dialogs.

Chapter 6, Selection Dialogs, describes another set of predefined Motif dialogs, called SelectionDialogs. Since these dialogs are the next step in the evolution of dialogs, most of the material in this chapter is applicable there as well. SelectionDialogs typically provide the user with a list of choices. These dialogs can remain displayed on the screen so that they can be used repeatedly. Chapter 7, Custom Dialogs, addresses the issues of creating customized dialogs, and Chapter 21, Advanced Dialog Programming, discusses some advanced topics in X and Motif programming using dialogs as a backdrop.

6.1 The Purpose of Dialogs

For most applications, it is impossible to develop an interface that provides the full functionality of the application in a single main window. As a result, the interface is typically broken up into discrete functional modules, where the interface for each module is provided in a separate dialog box.

As an example, consider an electronic mail application. The broad range of different functions includes searching for messages according to patterns, composing messages, editing an address book, reporting error messages, and so on. Dialog boxes are used to display simple messages, as shown in the figure. They are also used to prompt the user to answer simple questions, as shown in the figure. A dialog box can also present a more complicated interaction, as shown in the figure.

figs.eps/V6a.05.01.eps.png
A message dialog


figs.eps/V6a.05.02.eps.png
A question dialog


figs.eps/V6a.05.03.eps.png
A custom dialog box

In the figure, many different widget classes are used to provide an interface that allows the user to save e-mail messages in different folders. The purpose of a dialog is to focus on one particular task in an application. Since the scope of these tasks is usually quite limited, an application usually provides them in dialog boxes, rather than in its main window.

There is actually no such thing as a dialog widget class in the Motif toolkit. A dialog is actually made up of a DialogShell widget and a manager widget child that implements the visible part of the dialog. The DialogShell interacts with the window manager to provide the transient window behavior required of dialogs. When we refer to a dialog widget, we are really talking about the manager widget and all of its children collectively.

When you write a custom dialog, you simply create and manage the children of the DialogShell in the same way that you create and manage the children of a top-level application shell. The predefined Motif dialogs follow the same approach, except that the toolkit creates the manager widget and all of its children internally. Most of the standard Motif dialogs are composed of a DialogShell and either a MessageBox or SelectionBox widget. Each of these widget classes creates and manages a number of internal widgets without application intervention. See Chapter 3, Overview of the Motif Toolkit, to review the various types of predefined Motif dialogs.

All of the predefined Motif dialogs are subclassed from the BulletinBoard widget class. As such, a BulletinBoard can be thought of as the generic dialog widget class, although it can certainly be used as generic manager widget (see Chapter 8, Manager Widgets). Indeed, a dialog widget is a manager widget, but it is usually not treated as such by the application. The BulletinBoard widget provides the keyboard traversal mechanisms that support gadgets, as well as a number of dialog-specific resources.

It is important to note that for the predefined Motif dialogs, each dialog is implemented as a single widget class, even though there are smaller, primitive widgets under the hood. When you create a MessageBox widget, you automatically get a set of Labels and PushButtons that are laid out as described in the Motif Style Guide. What is not created automatically is the DialogShell widget that manages the MessageBox widget. You can either create the shell yourself and place the MessageBox in it or use a Motif convenience routine that creates both the shell and its dialog widget child.

The Motif toolkit uses the DialogShell widget class as the parent for all of the predefined Motif dialogs. In this context, a MessageBox widget combined with a DialogShell widget creates what the Motif toolkit calls a MessageDialog. A careful look at terminology can help you to distinguish between actual widget class and Motif compound objects. The name of the actual widget class ends in Box, while the name of the compound object made up of the widget and a DialogShell ends in Dialog. For example, the convenience routine XmCreateMessageBox() creates a MessageBox widget, which you need to place inside of a DialogShell yourself. Alternatively, XmCreateMessageDialog() creates a MessageDialog composed of a MessageBox and a DialogShell.

Another point about terminology involves the commonly-used term dialog box. When we say dialog box, we are referring to a compound object composed of a DialogShell and a dialog widget, not the dialog widget alone. This terminology can be confusing, since the Motif toolkit also provides widget classes that end in box.

One subtlety in the use of MessageBox and SelectionBox widgets is that certain types of behavior depend on whether or not the widget is a direct child of a DialogShell. For example, the Motif Style Guide says that clicking on the OK button in the action area of a MessageDialog invokes the action of the dialog and then dismisses the dialog. Furthermore, pressing the RETURN key anywhere in the dialog is equivalent to clicking on the OK button. However, none of this takes place when the MessageBox widget is not a direct child of a DialogShell.

Perhaps the most important thing to remember is how the Motif toolkit treats dialogs. Once a dialog widget is placed in a DialogShell, the toolkit tends to treat the entire combination as a single entity. In fact, as we move on, you'll find that the toolkit's use of convenience routines, callback functions, and popup widget techniques all hide the fact that the dialog is composed of these discrete elements. While the Motif dialogs are really composed of many primitive widgets, such as PushButtons and TextFields, the single-entity approach implies that you never access the subwidgets directly. If you want to change the label for a button, you set a resource specific to the dialog class, rather than getting a handle to the button widget and changing its resource. Similarly, you always install callbacks on the dialog widget itself, instead of installing them directly on buttons in the control or action areas.

This approach may be confusing for those already familiar with Xt programming, but not yet familiar with the Motif toolkit. Similarly, those who learn Xt programming through experiences with the Motif toolkit might get a misconception of what Xt programming is all about. We try to point out the inconsistencies between the two approaches so that you will understand the boundaries between the Motif toolkit and its Xt foundations.

6.2 The Anatomy of a Dialog

As described in Chapter 3, Overview of the Motif Toolkit, dialogs are typically broken down into two regions known as the control and action areas. The control area is also referred to as the work area. The control area contains the widgets that provide the functionality of the dialog, such as Labels, ToggleButtons, and List widgets. The action area contains PushButtons whose callback routines actually perform the action of the dialog box. While most dialogs follow this pattern, it is important to realize that these two regions represent user-interface concepts and do not necessarily reflect how Motif dialogs are implemented.

the figure shows these areas in a sample dialog box.

figs.eps/V6a.05.04.eps.png
A sample dialog box

The Motif Style Guide describes in a general fashion how the control and action areas for all dialogs should be laid out. For predefined Motif dialogs, the control area is rigidly specified. For customized dialogs, there is only a general set of guidelines to follow. The guidelines for the action area specify a number of common actions that can be used in both predefined Motif dialogs and customized dialogs. These actions have standard meanings that help ensure consistency between different Motif applications.

By default, the predefined Motif MessageDialogs provide three action buttons, which are normally labeled OK, Cancel, and Help, respectively. SelectionDialogs provide a fourth button, normally labeled Apply, which is placed between the OK and Cancel buttons. This button is created but not managed, so it is not visible unless the application explicitly manages it. The Style Guide specifies that the OK button applies the action of the dialog and dismisses it, while the Apply button applies the action but does not dismiss the dialog. The Cancel button dismisses the dialog without performing any action and the Help button provides any help that is available for the dialog. When you are creating custom dialogs, or even when you are using the predefined Motif dialogs, you may need to provide actions other than the default ones. If so, you should change the labels on the buttons so that the actions are obvious. You should try to use the common actions defined by the Motif Style Guide if they are appropriate, since these actions have standard meanings. We will address this issue further as it comes up in discussion; it is not usually a problem until you create your own customized dialogs, as described in Chapter 7, Custom Dialogs.

6.3 Creating Motif Dialogs

Under most circumstances, creating a predefined Motif dialog box is very simple. All Motif dialog types have corresponding convenience routines that simplify the task of creating and managing them. For example, a standard MessageDialog can be created as shown in the following code fragment:

   #include <Xm/MessageB.h>

   extern Widget parent;
   Widget dialog;
   Arg arg[5];
   XmString t;
   int n = 0;

   t = XmStringCreateLocalized ("Hello World");
   XtSetArg (arg[n], XmNmessageString, t); n++;
   dialog = XmCreateMessageDialog (parent, "message", arg, n);
   XmStringFree (t);
The convenience routine does almost everything automatically. The only thing that we have to do is specify the message that we want to display.

6.3.1 Dialog Header Files

As we mentioned earlier, there are two basic types of predefined Motif dialog boxes: MessageDialogs and SelectionDialogs. MessageDialogs present a simple message, to which a yes (OK) or no (Cancel) response usually suffices. There are six types of MessageDialogs: ErrorDialog, InformationDialog, QuestionDialog, TemplateDialog, WarningDialog, and WorkingDialog. These types are not actually separate widget classes, but rather instances of the generic MessageDialog that are configured to display different graphic symbols. All of the MessageDialogs are compound objects that are composed of a MessageBox widget and a DialogShell. When using MessageDialogs, you must include the file <Xm/MessageB.h>.

SelectionDialogs allow for more complicated interactions. The user can select an item from a list or type an entry into a TextField widget before acting on the dialog. There are essentially four types of SelectionDialogs, although the situation is a bit more complex than for MessageDialogs. The PromptDialog is a specially configured SelectionDialog; both of these dialogs are compound objects that are composed of a SelectionBox widget and a DialogShell. The Command widget and the FileSelectionDialog are based on separate widget classes. However, they are both subclassed from the SelectionBox and share many of its features. When we use the general term "selection dialogs," we are referring to these three widget classes plus their associated dialog shells. To use a SelectionDialog, you must include the file <Xm/SelectioB.h>. Yes, you read that right. It does, in fact, read SelectioB.h. The reason for the missing n is there is a fourteen-character filename limit on UNIX System V machines. For FileSelectionDialogs, the appropriate include file is <Xm/FileSB.h>, and for the Command widget it is <Xm/Command.h>.

6.3.2 Creating a Dialog

You can use any of the following convenience routines to create a dialog box. They are listed according to the header file in which they are declared:

<Xm/MessageB.h>:

   XmCreateMessageBox()
   XmCreateMessageDialog()
   XmCreateErrorDialog()
   XmCreateInformationDialog()
   XmCreateQuestionDialog()
   XmCreateTemplateDialog()
   XmCreateWarningDialog()
   XmCreateWorkingDialog()
<Xm/SelectioB.h>:
   XmCreateSelectionBox()
   XmCreateSelectionDialog()
   XmCreatePromptDialog()
<Xm/FileSB.h>:
   XmCreateFileSelectionBox()
   XmCreateFileSelectionDialog()
<Xm/Command.h>:
   XmCreateCommand()
Each of these routines creates a dialog widget. In addition, the routines that end in Dialog automatically create a DialogShell as the parent of the dialog widget. All of the convenience functions for creating dialogs use the standard Motif creation routine format. For example, XmCreateMessageDialog() takes the following form:
   Widget
   XmCreateMessageDialog(parent, name, arglist, argcount)
       Widget parent;
       String   *name;
       ArgList  arglist;
       Cardinal argcount;
In this case, we are creating a common MessageDialog, which is a MessageBox with a DialogShell parent. The parent parameter specifies the widget that acts as the owner or parent of the DialogShell. Note that the parent must not be a gadget, since the parent must have a window associated with it. The dialog widget itself is a child of the DialogShell. You are returned a handle to the newly created dialog widget, not the DialogShell parent. For the routines that just create a dialog widget, the parent parameter is simply a manager widget that contains the dialog.

The arglist and argcount parameters for the convenience routines specify resources using the old-style ArgList format, just like the rest of the Motif convenience routines. A varargs-style interface is not available for creating dialogs. However, you can use the varargs-style interface for setting resources on a dialog after is has been created by using XtVaSetValues().

6.3.3 Setting Resources

There are a number of resources and callback functions that apply to almost all of the Motif dialogs. These resources deal with the action area buttons in the dialogs. Other resources only apply to specific types of dialogs; they deal with the different control area components such as Labels, TextFields, and List widgets. The different resources are listed below, grouped according to the type of dialogs that they affect:

General dialog resources:

   XmNokLabelString            XmNokCallback
   XmNcancelLabelString        XmNcancelCallback
   XmNhelpLabelString          XmNhelpCallback
MessageDialog resources:
   XmNmessageString            XmNsymbolPixmap
SelectionDialog resources:
   XmNapplyLabelString         XmNapplyCallback
   XmNselectionLabelString     XmNlistLabelString
FileSelectionDialog resources:
   XmNfilterLabelString      XmNdirListLabelString
   XmNfileListLabelString
Command resources:
   XmNpromptString
The labels and callbacks of the various buttons in the action area are specified by resources based on the standard Motif dialog button names. For example, the XmNokLabelString resource is used to set the label for the OK button. XmNokCallback is used to specify the callback routine that the dialog should call when that button is activated. As discussed earlier, it may be appropriate to change the labels of these buttons, but the resource and callback names will always have names that correspond to their default labels.

The XmNmessageString resource specifies the message that is displayed by the MessageDialog. The XmNsymbolPixmap resource specifies the iconic symbol that is associated with each of the MessageDialog types. This resource is rarely changed, so discussion of it is deferred until Chapter 21, Advanced Dialog Programming.

The other resources apply to the different types of selection dialogs. For example, ­XmNselectionLabelString sets the label that is placed above the list area in SelectionDialog. These resources are discussed in Chapter 6, Selection Dialogs.

All of these resources apply to the Labels and PushButtons in the different dialogs. It is important to note that they are different from the usual resources for Labels and PushButtons. For example, the Label resource XmNlabelString would normally be used to specify the label for both Label and PushButton widgets. Dialogs use their own resources to maintain the abstraction of the dialog widget as a discrete user-interface object.

Another important thing to remember about the resources that refer to widget labels is that their values must be specified as compound strings. Compound strings allow labels to be rendered in arbitrary fonts and to span multiple lines. See Chapter 19, Compound Strings, for more information.

The following code fragment demonstrates how to specify dialog resources and callback routines:

   Widget dialog;
   XmString msg, yes, no;
   extern void my_callback();

   dialog = XmCreateQuestionDialog (parent, "dialog", NULL, 0);
   yes = XmStringCreateLocalized ("Yes");
   no = XmStringCreateLocalized ("No");
   msg = XmStringCreateLocalized ("Do you want to quit?");

XtVaSetValues (dialog, XmNmessageString, msg, XmNokLabelString, yes, XmNcancelLabelString, no, NULL); XtAddCallback (dialog, XmNokCallback, my_callback, NULL); XtAddCallback (dialog, XmNcancelCallback, my_callback, NULL); XmStringFree (yes); XmStringFree (no); XmStringFree (msg);

6.3.4 Dialog Management

None of the Motif toolkit convenience functions manage the widgets that they create, so the application must call XtManageChild() explicitly. It just so happens that managing a dialog widget that is the immediate child of a DialogShell causes the entire dialog to pop up. Similarly, unmanaging the same dialog widget causes it and its DialogShell parent to pop down. This behavior is consistent with the Motif toolkit's treatment of the dialog/shell combination as a single object abstraction. The toolkit is treating its own dialog widgets as opaque objects and trying to hide the fact that there are DialogShells associated with them. The toolkit is also making the assumption that when the programmer manages a dialog, she wants it to pop up immediately.

This practice is somewhat presumptuous and it conflicts directly with the specifications for the X Toolkit Intrinsics. These specifications say that when the programmer wants to display a popup shell on the screen, she should use XtPopup(). Similarly, when the dialog is to be dismissed, the programmer should call XtPopdown(). The fact that XtManageChild() happens to pop up the shell and XtUnmanageChild() causes it to pop down is misleading to the new Motif programmer and confusing to the experienced Xt programmer.

You should understand that this discussion of managing dialogs does not apply to customized dialogs that you create yourself. It only applies to the predefined Motif dialog widgets that are created as immediate children of DialogShells. The Motif toolkit uses this method because it has been around for a long time and it must be supported for backwards compatibility with older versions. Furthermore, using XtPopup() requires access to the DialogShell parent of a dialog widget, which breaks the single-object abstraction.

There are two ways to manage Motif dialogs. You can follow the Motif toolkit conventions of using XtManageChild() and XtUnmanageChild() to pop up and pop down dialog widgets or you can use XtPopup() and XtPopdown() on the dialog's parent to do the same job. Whatever you do, it is good practice to pick one method and be consistent throughout an application. It is possible to mix and match the methods, but there may be some undesirable side effects, which we will address in the next few sections.

In an effort to make our applications easier to port to other Xt-based toolkits, we follow the established convention of using XtPopup(). This technique can coexist easily with XtManageChild(), since popping up an already popped-up shell has no effect. XtPopup() takes the following form:

   void
   XtPopup(shell, grab_kind)
       Widget shell;
       XtGrabKind grab_kind;
The shell parameter to the function must be a shell widget; in this case it happens to be a DialogShell. If you created the dialog using one of the Motif convenience routines, you can get a handle to the DialogShell by calling XtParent() on the dialog widget.

The grab_kind parameter can be one of XtGrabNone, XtGrabNonexclusive, or XtGrabExclusive. We almost always use XtGrabNone, since the other values imply a server grab, which means that other windows on the desktop are locked out. Grabbing the server results in what is called modality; it implies that the user cannot interact with anything but the dialog. While a grab may be desirable in some cases, the Motif toolkit provides some predefined resources that handle the grab for you automatically. The advantage of using this alternate method is that it allows the client to communicate more closely with the Motif Window Manager (mwm) and it provides for different kinds of modality. These methods are discussed in Section #smodaldlg. For detailed information on XtPopup() and the different uses of grab_kind, see Volume Four, X Toolkit Intrinsics Programming Manual.

If you call XtPopup() on a dialog widget that has already been popped up using XtManageChild(), the routine has no effect. As a result, if you attempt to specify grab_kind as something other than XtGrabNone, it also has no effect.

The counterpart to XtPopup() is XtPopdown(). Any time you want to pop down a shell, you can use this function, which has the following form:

   void
   XtPopdown(shell)
       Widget shell;
Again, the shell parameter should be the XtParent() of the dialog widget. If you use XtUnmanageChild() to pop down a dialog, it is not necessary to call XtPopdown(), although we advise it for correctness and good form. However, it is important to note that if you use XtUnmanageChild() to pop down a dialog, you must use XtManageChild() to redisplay it again. Don't forget that the dialog widget itself is not a shell, so managing or unmanaging it still takes place when you use the manage and unmanage functions.

Let's take a closer look at how dialogs are really used in an application. Examining the overall design and the mechanics that are involved will help to clarify a number of issues about managing and unmanaging dialogs and DialogShells. The program listed in the source code displays an InformationDialog when the user presses a PushButton in the application's main window. 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.

   /* hello_dialog.c -- your typical Hello World program using
    * an InformationDialog.
    */
   #include <Xm/RowColumn.h>
   #include <Xm/MessageB.h>
   #include <Xm/PushB.h>

   main(argc, argv)
   int argc;
   char *argv[];
   {
       XtAppContext app;
       Widget toplevel, rc, pb;
       extern void popup(); /* callback for the pushbuttons -- pops up dialog */
       extern void exit();

       XtSetLanguageProc (NULL, NULL, NULL);

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

       rc = XtVaCreateWidget ("rowcol",
           xmRowColumnWidgetClass, toplevel, NULL);
       pb = XtVaCreateManagedWidget ("Hello",
           xmPushButtonWidgetClass, rc, NULL);
       XtAddCallback (pb, XmNactivateCallback, popup, "Hello World");
       pb = XtVaCreateManagedWidget ("Goodbye",
           xmPushButtonWidgetClass, rc, NULL);
       XtAddCallback (pb, XmNactivateCallback, exit, NULL);

       XtManageChild (rc);
       XtRealizeWidget (toplevel);
       XtAppMainLoop (app);
   }

   /* callback for the PushButtons.  Popup an InformationDialog displaying
    * the text passed as the client data parameter.
    */
   void
   popup(button, client_data, call_data)
   Widget button;
   XtPointer client_data;
   XtPointer call_data;
   {
       Widget dialog;
       XmString xm_string;
       extern void activate();
       Arg args[5];
       int n = 0;
       char *text = (char *) client_data;

       /* set the label for the dialog */
       xm_string = XmStringCreateLocalized (text);
       XtSetArg (args[n], XmNmessageString, xm_string); n++;

       /* Create the InformationDialog as child of button */
       dialog = XmCreateInformationDialog (button, "info", args, n);

       /* no longer need the compound string, free it */
       XmStringFree (xm_string);

       /* add the callback routine */
       XtAddCallback (dialog, XmNokCallback, activate, NULL);

       /* manage the dialog */
       XtManageChild (dialog);
       XtPopup (XtParent (dialog), XtGrabNone);
   }

   /* callback routine for when the user presses the OK button.
    * Yes, despite the fact that the OK button was pressed, the
    * widget passed to this callback routine is the dialog!
    */
   void
   activate(dialog, client_data, call_data)
   Widget dialog;
   XtPointer client_data;
   XtPointer call_data;
   {
       puts ("OK was pressed.");
   }

The output of this program is shown in the figure.

figs.eps/V6a.05.05.eps.png
Output of hello_dialog.c


Dialogs are often invoked from callback routines attached to PushButtons or other interactive widgets. Once the dialog is created and popped up, control of the program is returned to the main event-handling loop (XtAppMainLoop()), where normal event processing resumes. At this point, if the user interacts with the dialog by selecting a control or activating one of the action buttons, a callback routine for the dialog is invoked. In the source code we happen to use an InformationDialog, but the type of dialog used is irrelevant to the model.

When the PushButton in the main window is pressed, popup() is called. A text string that is used as the message to display in the InformationDialog is passed as client data. The dialog uses a single callback routine, activate(), for the XmNokCallback resource. This function is invoked when the user presses the OK button. The callback simply prints a message to standard output that the button has been pressed. Similar callback routines could be installed for the Cancel and Help buttons through the XmNcancelCallback and ­XmNhelpCallback resources.

6.3.5 Closing Dialogs

You might notice that activating either the OK or the Cancel button in the previous example causes the dialog to be automatically popped down. The Motif Style Guide says that when any button in the action area of a predefined Motif dialog is pressed, except for the Help button, the dialog should be dismissed. The Motif toolkit takes this specification at face value and enforces the behavior, which is consistent with the idea that Motif dialogs are self-contained, self-sufficient objects. They manage everything about themselves from their displays to their interactions with the user. And when it's time to go away, they unmanage themselves. Your application does not have to do anything to cause any of the behavior to occur.

Unfortunately, this behavior does not take into account error conditions or other exceptional events that may not necessarily justify the dialog's dismissal. For example, if pressing OK causes a file to be updated, but the operation fails, you may not want the dialog to be dismissed. If the dialog is still displayed, the user can try again without having to repeat the actions that led to popping up the dialog.

The XmNautoUnmanage resource provides a way around the situation. This resource controls whether the dialog box is automatically unmanaged when the user selects an action area button other than the Help button. If XmNautoUnmanage is True, after the callback routine for the button is invoked, the DialogShell is popped down and the dialog widget is unmanaged automatically. However, if the resource is set to False, the dialog is not automatically unmanaged. The value of this resource defaults to True for MessageDialogs and SelectionDialogs; it defaults to False for FileSelectionDialogs.

Since it is not always appropriate for a dialog box to unmanage itself automatically, it turns out to be easier to set XmNautoUnmanage to False in most circumstances. This technique makes dialog management easier, since it keeps the toolkit from indiscriminately dismissing a dialog simply because an action button has been activated. While it is true that we could program around this situation by calling XtPopup() or XtManageChild() from a callback routine in error conditions, this type of activity is confusing because of the double-negative action it implies. In other words, programming around the situation is just undoing something that should not have been done in the first place.

This discussion brings up some issues about when a dialog should be unmanaged and when it should be destroyed. If you expect the user to have an abundant supply of computer memory, you may reuse a dialog by retaining a handle to the dialog, as shown in Example 5-4 later in this chapter. There are also performance considerations that may affect whether you choose to destroy or reuse dialogs. It takes less time to reuse a dialog than it does to create a new one, provided that your application is not so large that it is consuming all of the system's resources. If you do not retain a handle to a dialog, and if you need to conserve memory and other resources, you should destroy the dialog whenever you pop it down.

Another method the user might use to close a dialog is to select the Close item from the window menu. This menu can be pulled down from the title bar of a window. Since the menu belongs to the window manager, rather than the shell widget or the application, you cannot install any callback routines for its menu items. However, you can use the XmNdeleteResponse resource to control how the DialogShell responds to a Close action. The Motif VendorShell, from which the DialogShell is subclassed, is responsible for trapping the notification and determining what to do next, based on the value of the resource. It can have one of the following values:

XmUNMAP

This value causes the dialog to be unmapped. The dialog disappears from the screen, but it is not destroyed, nor is it iconified. The dialog widget and its windows are still intact and can be redisplayed using XtPopup(). This value is the default for DialogShells.
XmDESTROY

This value destroys the DialogShell and calls its XmNdestroyCallback. Note that all of the shell's children are also destroyed, including the dialog widget and its subwidgets. When the dialog is destroyed, you cannot redisplay the dialog or reference its handle again. If you need the dialog again, you have to create another one. Examples of using the XmNdestroyCallback are presented in Chapter 21, Advanced Dialog Programming.
XmDO_NOTHING

This value causes the toolkit to take no action. The value should only be specified in circumstances where you want to handle the event on your own. However, handling the event involves much more than installing a simple callback routine. It requires building a lower-level mechanism that interprets the proper events when they are sent by the window manager. The most common thing to do in such cases is to activate the default action of the dialog or to interpose a prompting mechanism to verify the user's action. This topic is discussed in Chapter 16, Interacting With the Window Manager.

It may be convenient for your application to know when a dialog has been popped up or down. If so, you can install callbacks that are invoked whenever either of these events take place. The actions of popping up and down dialogs can be monitored through the ­XmNpopupCallback and XmNpopdownCallback callback routines. For example, when the function associated with a XmNpopupCallback is invoked, you could position the dialog automatically, rather than allowing the window manager to control the placement. See Chapter 7, Custom Dialogs, for more information on these callbacks.

6.3.6 Generalizing Dialog Creation

Posting dialogs that display informative messages is something just about every application is going to do frequently. Rather than write a separate routine for each case where a message needs to be displayed, we can generalize the process by writing a single routine that handles most, if not all, cases. the source code shows the PostDialog() routine. This routine creates a MessageDialog of a given type and displays an arbitrary message. Rather than use the convenience functions provided by Motif for each of the MessageDialog types, the routine uses the generic function XmCreateMessageDialog() and configures the symbol to be displayed by setting the XmNdialogType resource. XmStringCreateLocalized() is only available in Motif 1.2; XmStringCreateSimple() is the corresponding function in Motif 1.1.

   /*
    * PostDialog() -- a generalized routine that allows the programmer
    * to specify a dialog type (message, information, error, help, etc..),
    * and the message to display.
    */
   Widget
   PostDialog(parent, dialog_type, msg)
   Widget parent;
   int dialog_type;
   char *msg;
   {
       Widget dialog;
       XmString text;

       dialog = XmCreateMessageDialog (parent, "dialog", NULL, 0);
       text = XmStringCreateLocalized (msg);
       XtVaSetValues (dialog,
           XmNdialogType,    dialog_type,
           XmNmessageString, text,
           NULL);
       XmStringFree (text);

       XtManageChild (dialog);
       XtPopup (XtParent (dialog), XtGrabNone);

       return dialog;
   }
This routine allows the programmer to specify several parameters: the parent widget, the type of dialog that is to be used, and the message that is to be displayed. The function returns the new dialog widget, so that the calling routine can modify it, unmanage it, or keep a handle to it. You may have additional requirements that this simplified example does not satisfy. For instance, the routine does not allow you to specify callback functions for the buttons in the action area and it does not handle the destruction of the widget when it is no longer needed. You could extend the routine to handle these issues, or you could control them outside the context of the function. You may also want to extend the routine so that it reuses the same dialog each time it is called and so that it allows you to disable the different action area buttons. All of these issues are discussed again in Chapter 6, Selection Dialogs, and in Chapter 21, Advanced Dialog Programming.

6.4 Dialog Resources

The following sections discuss resources that are specific to Motif dialogs. In most cases, these resources are BulletinBoard widget resources, since all Motif dialogs are subclassed from this class. However, they are not intended to be used by generic BulletinBoard widgets. The resources only apply when the widget is an immediate child of a DialogShell widget; they are really intended to be used exclusively by the predefined Motif dialog classes. Remember that the resources must be set on the dialog widget, not the DialogShell. See Chapter 8, Manager Widgets, for details on the generic BulletinBoard resources.

6.4.1 The Default Button

All predefined Motif dialogs have a default button in their action area. The default button is activated when the user presses the RETURN key in the dialog. The OK button is normally the default button, but once the dialog is displayed, the user can change the default button by using the arrow keys to traverse the action buttons. The action button with the keyboard focus is always the default button. Since the default button can be changed by the user, the button that is the default is only important when the dialog is initially popped up. The importance of the default button lies in its ability to influence the user's default response to the dialog.

You can change the default button for a MessageDialog by setting the XmNdefaultButtonType resource on the dialog widget. This resource is specific to MessageDialogs; it cannot be set for the various types of selection dialogs. The resource can have one of the following values:

XmDIALOG_OK_BUTTON

This value specifies that the default button is the furthest button on the left of the dialog. By default, this button is the OK button, although its label may have been changed to another string.
XmDIALOG_CANCEL_BUTTON

This value specifies that the Cancel button is the default button. This value is appropriate in situations where the action of the dialog is destructive, such as for a WarningDialog that is posted in order to warn the user of a possibly dangerous action.
XmDIALOG_HELP_BUTTON

This value specifies the Help button, which is always the furthest button on the right of a Motif dialog. This button is rarely set as the default button.
XmDIALOG_NONE

This value specifies that there is no default button.

The values for XmNdefaultButtonType come up again later, when we discuss XmMessageBoxGetChild() and again in Chapter 6, Selection Dialogs, for XmSelectionBoxGetChild(). An example of how the default button type can be used is shown in the source code XmStringCreateLocalized() is only available in Motif 1.2; XmStringCreateSimple() is the corresponding function in Motif 1.1.

   /*
    * WarningMsg() -- Inform the user that she is about to embark on a
    * dangerous mission and give her the opportunity to back out.
    */
   void
   WarningMsg(parent, client_data, call_data)
   Widget parent;
   XtPointer client_data;
   XtPointer call_data;
   {
       static Widget dialog;
       XmString text, ok_str, cancel_str;
       char *msg = (char *) client_data;

       if (!dialog)
           dialog = XmCreateWarningDialog (parent, "warning", NULL, 0);
       text = XmStringCreateLtoR (msg, XmFONTLIST_DEFAULT_TAG);
       ok_str = XmStringCreateLocalized ("Yes");
       cancel_str = XmStringCreateLocalized ("No");
       XtVaSetValues (dialog,
           XmNmessageString,     text,
           XmNokLabelString,     ok_str,
           XmNcancelLabelString, cancel_str,
           XmNdefaultButtonType, XmDIALOG_CANCEL_BUTTON,
           NULL);
       XmStringFree (text);
       XmStringFree (ok_str);
       XmStringFree (cancel_str);

       XtManageChild (dialog);
       XtPopup (XtParent (dialog), XtGrabNone);
   }
The intent of this function is to create a dialog that tries to discourage the user from performing a destructive action. By using a WarningDialog and by making the Cancel button the default choice, we have given the user adequate warning that the action may have dangerous consequences. The output of a program running this code fragment is shown in the figure.

figs.eps/V6a.05.06.eps.png
Output of WarningMsg()


You can also set the default button for a dialog by the setting the BulletinBoard resource ­XmNdefaultButton. This technique works for both MessageDialogs and SelectionDialogs. The resource value must be a widget ID, which means that you have to get a handle to a subwidget in the dialog to set the resource. You can get the handle to subwidgets using XmMessageBoxGetChild() or XmSelectionBoxGetChild(). Since this method breaks the Motif dialog abstraction, we describe it later in Section #sinternwid.

6.4.2 Initial Keyboard Focus

When a dialog widget is popped up, one of the internal widgets in the dialog has the keyboard focus. This widget is typically the default button for the dialog, which makes sense in most cases. However, there are situations where it is appropriate for another widget to have the initial keyboard focus. For example, when a PromptDialog is popped up, it makes sense for the TextField to have the keyboard focus so that the user can immediately start typing a response.

In Motif 1.1, it is not easy to set the initial keyboard focus in a dialog widget to anything other than a button in the action area. Motif 1.2 has introduced the XmNinitialFocus resource to deal with this situation. Since this resource is a Manager widget resource, it can be used for both MessageDialogs and SelectionDialogs, although it is normally only used for SelectionDialogs. The resource specifies the subwidget that has the keyboard focus the first time that the dialog is popped up. If the dialog is popped down and popped up again later, it remembers the widget that had the keyboard focus when it was popped down and that widget is given the keyboard focus again. The resource value must again be a widget ID. The default value of XmNinitialFocus for MessageDialogs is the subwidget that is also the XmNdefaultButton for the dialog. For SelectionDialogs, the text entry area is the default value for the resource.

6.4.3 Button Sizes

The XmNminimizeButtons resource controls how the dialog sets the widths of the action area buttons. If the resource is set to True, the width of each button is set so that it is as small as possible while still enclosing the entire label, which means that each button will have a different width. The default value of False specifies that the width of each button is set to the width of the widest button, so that all buttons have the same width.

6.4.4 The Dialog Title

When a new shell widget is mapped to the screen, the window manager creates its own window that contains the title bar, resize handles, and other window decorations and makes the window of the DialogShell the child of this new window. This technique is called reparenting a window; it is only done by the window manager in order to add window decorations to a shell window. The window manager reparents instances of all of the shell widget classes except OverrideShell. These shells are used for menus and thus should not have window manager decorations.

Most window managers that reparent shell windows display titles in the title bars of their windows. For predefined Motif dialogs, the Motif toolkit sets the default title to the name of the dialog widget with the string _popup appended. Since this string is almost certainly not an appropriate title for the window, you can change the title explicitly using the ­XmNdialogTitle BulletinBoard resource. (Do not confuse this title with the message displayed in MessageDialog, which is set by XmNmessageString.) The value for ­XmNdialogTitle must be a compound string. The BulletinBoard in turn sets the ­XmNtitle resource of the DialogShell; the value of this resource is a regular C string.

So, you can set the title for a dialog window in one of two ways. The following code fragment shows how to set the title using the ­XmNdialogTitle resource:

   XmString title_string;

   title_string = XmStringCreateLocalized ("Dialog Box");
   dialog = XmCreateMessageDialog (parent, "dialog_name", NULL, 0);
   XtVaSetValues (dialog,
       XmNdialogTitle, title_string,
       NULL);
   XmStringFree (title_string);
This technique requires creating a compound string. If you set the XmNtitle resource directly on the DialogShell, you can use a regular C string, as in the following code fragment:
   dialog = XmCreateMessageDialog (parent, "dialog_name", NULL, 0);
   XtVaSetValues (XtParent (dialog),
       XmNtitle, "Dialog Box",
       NULL);
While the latter method is easier and does not require creating and freeing a compound string, it does break the abstraction of treating the dialog as a single entity.

6.4.5 Dialog Resizing

The XmNnoResize resource controls whether or not the window manager allows the dialog to be resized. If the resource is set to True, the window manager does not display resize handles in the window manager frame for the dialog. The default value of False specifies that the window manager should provide resize handles. Since some dialogs cannot handle resize events very well, you may find it better aesthetically to prevent the user from resizing them.

This resource is an attribute of the BulletinBoard widget, even though it only affects the shell widget parent of a dialog widget. The resource is provided as a convenience to the programmer, so that she is not required to get a handle to the DialogShell. The resource only affects the presence of resize handles in the window manager frame; it does not deal with other window manager controls. See Chapter 16, Interacting With the Window Manager, for details on how to specify the window manager controls for a DialogShell, or any shell widget, directly.

6.4.6 Dialog Fonts

The BulletinBoard widget provides resources that enable you to specify the fonts that are used for all of the button, Label, and Text widget descendants of the BulletinBoard. Since Motif dialog widgets are subclassed from the BulletinBoard, you can use these resources to make sure that the fonts that are used within a dialog are consistent. The XmNbuttonFontList resource specifies the font list that is used for all of the button descendants of the dialog. The resource is set on the dialog widget itself, not on its individual children. Similarly, the XmNlabelFontList resource is used to set the font list for all of the Label descendants of the dialog and XmNtextFontList is used for all of the Text and TextField descendants.

If one of these resources is not set, the toolkit determines the font list by searching up the widget hierarchy for an ancestor that is a subclass of BulletinBoard, VendorShell, or MenuShell. If an ancestor is found, the font list resource is set to the value of that font list resource in the ancestor widget. See Chapter 19, Compound Strings, for more information on font lists.

You can override the XmNbuttonFontList, XmNlabelFontList, and XmNtextFontList resources on a per-widget basis by setting the XmNfontList resource directly on individual widgets. Of course, you must break the dialog abstraction and retrieve the widgets internal to the dialog itself to set this resource. While we describe how to do this in the following section, we do not recommend configuring dialogs down to this level of detail.

6.5 Dialog Callback Routines

As mentioned earlier, the predefined Motif dialogs have their own resources to reference the labels and callback routines for the action area PushButtons. Instead of accessing the PushButton widgets in the action area to install callbacks, you use the resources XmNokCallback, XmNcancelCallback, and XmNhelpCallback on the dialog widget itself. These callbacks correspond to each of the three buttons, OK, Cancel, and Help.

Installing callbacks for a dialog is no different than installing them for any other type of Motif widget; it may just seem different because the dialog widgets contain so many subwidgets. The following code fragment demonstrates the installation of simple callback for all of the buttons in a MessageDialog:

       ...
       dialog = XmCreateMessageDialog (w, "notice", NULL, 0);
       ...
       XtAddCallback (dialog, XmNokCallback, ok_pushed, "Hi");
       XtAddCallback (dialog, XmNcancelCallback, cancel_pushed, "Foo");
       XtAddCallback (dialog, XmNhelpCallback, help_pushed, NULL);
       XtManageChild (dialog);
       ...

   /* ok_pushed() --the OK button was selected.  */
   void
   ok_pushed(widget, client_data, call_data)
   Widget widget;
   XtPointer client_data;

XtPointer call_data; { char *message = (char *) client_data; printf ("OK was selected: %s0, message); } /* cancel_pushed() --the Cancel button was selected. */ void cancel_pushed(widget, client_data, call_data) Widget widget; XtPointer client_data;

XtPointer call_data; { char *message = (char *) client_data; printf ("Cancel was selected: %s0, message); } /* help_pushed() --the Help button was selected. */ void help_pushed(widget, client_data, call_data) Widget widget; XtPointer client_data;

XtPointer call_data; { printf ("Help was selected0); } In this example, a dialog is created and callback routines for each of the three responses are added using XtAddCallback(). We also provide simple client data to demonstrate how the data is passed to the callback routines. These callback routines simply print the fact that they have been activated; the messages they print are taken from the client data.

All of the dialog callback routines take three parameters, just like any standard callback routine. The widget parameter is the dialog widget that contains the button that was selected; it is not the DialogShell widget or the PushButton that the user selected from the action area. The second parameter is the client_data, which is supplied to XtAddCallback(), and the third is the call_data, which is provided by the internals of the widget that invoked the callback.

The client_data parameter is of type XtPointer, which means that you can pass arbitrary values to the function, depending on what is necessary. However, you cannot pass a float or a double value or an actual data structure. If you need to pass such values, you must pass the address of the variable or a pointer to the data structure. In keeping with the philosophy of abstracting and generalizing code, you should use the client_data parameter as much as possible because it eliminates the need for some global variables and it keeps the structure of an application modular.

For the predefined Motif dialogs, the call_data parameter is a pointer to a data structure that is filled in by the dialog box when the callback is invoked. The data structure contains a callback reason and the event that invoked the callback. The structure is of type XmAnyCallbackStruct, which is declared as follows:

   typedef struct {
       int     reason;
       XEvent  *event;
   } XmAnyCallbackStruct;
The value of the reason field is an integer value that can be any one of XmCR_HELP, XmCR_OK, or XmCR_CANCEL . The value specifies the button that the user pressed in the dialog box. The values for the reason field remain the same, no matter how you change the button labels for a dialog. For example, you can change the label for the OK button to say Help, using the resource XmNokLabelString, but the reason parameter will still be XmCR_OK when the button is activated.

Because the reason field provides information about the user's response to the dialog in terms of the button that was pushed, we can simplify the previous code fragment and use one callback function for all of the possible actions. The callback function can determine which button was selected by examining reason. the source code demonstrates this simplification. 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.

   /* reason.c -- examine the reason field of the callback structure
    * passed as the call_data of the callback function.  This field
    * indicates which action area button in the dialog was pressed.
    */
   #include <Xm/RowColumn.h>
   #include <Xm/MessageB.h>
   #include <Xm/PushB.h>

   /* main() --create a pushbutton whose callback pops up a dialog box */
   main(argc, argv)
   char *argv[];
   {
       XtAppContext app;
       Widget toplevel, rc, pb;
       extern void pushed();

       XtSetLanguageProc (NULL, NULL, NULL);

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

       rc = XtVaCreateWidget ("rowcol", xmRowColumnWidgetClass, toplevel, NULL);

       pb = XtVaCreateManagedWidget ("Hello",
           xmPushButtonWidgetClass, rc, NULL);
       XtAddCallback (pb, XmNactivateCallback, pushed, "Hello World");

       pb = XtVaCreateManagedWidget ("Goodbye",
           xmPushButtonWidgetClass, rc, NULL);
       XtAddCallback (pb, XmNactivateCallback, pushed, "Goodbye World");

       XtManageChild (rc);
       XtRealizeWidget (toplevel);
       XtAppMainLoop (app);
   }

   /* pushed() --the callback routine for the main app's pushbuttons.
    * Create and popup a dialog box that has callback functions for
    * the OK, Cancel and Help buttons.
    */
   void
   pushed(widget, client_data, call_data)
   Widget widget;
   XtPointer client_data;
   XtPointer call_data;
   {
       static Widget dialog;
       char *message = (char *) client_data;
       XmString t = XmStringCreateLocalized (message);

       /* See if we've already created this dialog -- if so,
        * we don't need to create it again.  Just set the message
        * and manage it (repop it up).
        */
       if (!dialog) {
           extern void callback();
           Arg args[5];
           int n = 0;

           XtSetArg (args[n], XmNautoUnmanage, False); n++;
           dialog = XmCreateMessageDialog (widget, "notice", args, n);
           XtAddCallback (dialog, XmNokCallback, callback, "Hi");
           XtAddCallback (dialog, XmNcancelCallback, callback, "Foo");
           XtAddCallback (dialog, XmNhelpCallback, callback, "Bar");
       }
       XtVaSetValues (dialog, XmNmessageString, t, NULL);
       XmStringFree (t);
       XtManageChild (dialog);

       XtPopup (XtParent (dialog), XtGrabNone);
   }

   /* callback() --One of the dialog buttons was selected.
    * Determine which one by examining the "reason" parameter.
    */
   void
   callback(widget, client_data, call_data)
   Widget widget;
   XtPointer client_data;
   XtPointer call_data;
   {
       char *button;
       char *message = (char *) client_data;
       XmAnyCallbackStruct *cbs = (XmAnyCallbackStruct *) call_data;

       switch (cbs->reason) {
           case XmCR_OK : button = "OK"; break;
           case XmCR_CANCEL : button = "Cancel"; break;
           case XmCR_HELP : button = "Help";
       }
       printf ("%s was selected: %s0, button, message);
       if (cbs->reason != XmCR_HELP) {
           /* the ok and cancel buttons "close" the widget */
           XtPopdown (XtParent (widget));
       }
   }
Another interesting change in this application is the way pushed() determines if the dialog has already been created. By making the dialog widget handle static to the pushed() callback function, we retain a handle to this object across multiple button presses. For each invocation of the callback, the dialog's message is reset and it is popped up again.

Considering style guide issues again, it is important to know when it is appropriate to dismiss a dialog. As noted earlier, the toolkit automatically unmanages a dialog whenever any of the action area buttons are activated, except for the Help button. This behavior is controlled by XmNautoUnmanage, which defaults to True. However, if you set this resource to False, the callback routines for the buttons in the action area have to control the behavior on their own. In the source code the callback routine pops down the dialog when the reason is XmCR_OK or XmCR_CANCEL, but not when it is XmCR_HELP.

6.6 Piercing the Dialog Abstraction

As described earlier, Motif treats dialogs as if they are single user-interface objects. However, there are times when you need to break this abstraction and work with some of the individual widgets that make up a dialog. This section describes how the dialog convenience routines work, how to work directly with the DialogShell, and how to access the widgets that are internal to dialogs.

6.6.1 Convenience Routines

The fact that Motif dialogs are self-sufficient does not imply that they are black boxes that perform magic that you cannot perform yourself. For example, the convenience routines for the MessageDialog types follow these basic steps:

The XmNdialogType resource can be set to one of the following values:
   XmDIALOG_ERROR
   XmDIALOG_INFORMATION
   XmDIALOG_MESSAGE
   XmDIALOG_QUESTION
   XmDIALOG_TEMPLATE
   XmDIALOG_WARNING
   XmDIALOG_WORKING
The type of the dialog does not affect the kind of widget that is created. The only thing the type affects is the graphical symbol that is displayed in the control area of the dialog. The convenience routines set the resource based on the routine that is called (e.g. XmCreateErrorDialog() sets the resource to XmDIALOG_ERROR ). The widget automatically sets the graphical symbol based on the dialog type. You can change the type of a dialog after it is created using XtVaSetValues(); modifying the type also changes the dialog symbol that is displayed.

The Motif dialog convenience routines create DialogShells internally to support the single-object dialog abstraction. With these routines, the toolkit is responsible for the DialogShell, so the dialog widget uses its XmNdestroyCallback to destroy its parent upon its own destruction. If the dialog is unmapped or unmanaged, so is its DialogShell parent. The convenience routines do not add any resources or call any functions to support the special relationship between the dialog widget and the DialogShell, since most of the code that handles the interaction is written into the internals of the BulletinBoard.

6.6.2 The DialogShell

As your programs become more complex, you may eventually have to access the DialogShell parent of a dialog widget in order to get certain things done. This section examines DialogShells as independent widgets and describes how they are different from other shell widgets. There are three main features of a DialogShell that differentiate it from an ApplicationShell and a TopLevelShell.

The DialogShell is subclassed from the TransientShell and VendorShell classes. A shell that is subclassed from TransientShell cannot be iconified independently of its parent. However, if the parent of a DialogShell is iconified or unmapped, the DialogShell is unmapped as well. If the parent is destroyed, so is the DialogShell and the dialog within it. Remember, the parent of the DialogShell is another widget somewhere in the application, such as a Label, a PushButton, as ApplicationShell, or even another DialogShell. For example, if the callback for PushButton creates a dialog, the PushButton might be designated as the owner of the dialog. If the shell that contains the PushButton is iconified, the dialog is also withdrawn from the screen. If the PushButton's shell or the PushButton itself is destroyed, the dialog is destroyed as well.

The parent-child relationship between a DialogShell and its parent is different from the classic case, where the parent actually contains the child within its geometrical bounds. The DialogShell widget is a popup child of its parent, which means that the usual geometry-management relationship does not apply. Nonetheless, the parent widget must be managed in order for the child to be displayed. If a widget has popup children, those children are not mapped to the screen if the parent is not managed, which means that you must never make a menu item the parent of a DialogShell.

Assuming that the parent is displayed, the window manager attempts to place the DialogShell based on the value of the XmNdefaultPosition BulletinBoard resource. The default value of this resource is True, which means that the window manager positions the DialogShell so that it is centered on top of its parent. If the resource is set to False, the application and the window manager negotiate about where the dialog is placed. This resource is only relevant when the BulletinBoard is the immediate child of a DialogShell, which is always the case for Motif dialogs. If you want, you can position the dialog by setting the XmNx and XmNy resources for the dialog widget. Positioning the dialog on the screen must be done through a XmNmapCallback routine, which is called whenever the application calls XtManageChild(). See Chapter 7, Custom Dialogs, for a discussion about dialog positioning.

The Motif Window Manager imposes an additional constraint on the stacking order of the DialogShell and its parent. mwm always forces the DialogShell to be directly on top of its parent in the stacking order. The result is that the shell that contains the widget acting as the parent of the DialogShell cannot be placed on top of the dialog. This behavior is defined by the Motif Style Guide and is enforced by the Motif Window Manager and the Motif toolkit. Many end-users have been known to report the behavior as an application-design bug, so you may want to describe this behavior explicitly in the documentation for your application, in order to prepare the user ahead of time.

Internally, DialogShell widgets communicate frequently with dialog widgets in order to support the single-entity abstraction promoted by the Motif toolkit. However, you may find that you need to access the DialogShell part of a Motif dialog in order to query information from the shell or to perform certain actions on it. The include file <Xm/DialogS.h> provides a convenient macro for identifying whether or not a particular widget is a DialogShell:

   #define XmIsDialogShell(w)   XtIsSubclass(w, xmDialogShellWidgetClass)
If you need to use this macro, or you want to create a DialogShell using XmCreateDialogShell(), you need to include < Xm/DialogS.h>.

The macro is useful if you want to determine whether or not a dialog widget is the direct child of a DialogShell. For example, earlier in this chapter, we mentioned that the Motif Style Guide suggests that if the user activates the OK button in a MessageDialog, the entire dialog should be popped down. If you have created a MessageDialog without using XmCreateMessageDialog() and you want to be sure that the same thing happens when the user presses the OK button in that dialog, you need to test whether or not the parent is a DialogShell before you pop down the dialog. The following code fragment shows the use of the macro in this type of situation:

   /* traverse up widget tree till we find a window manager shell */
   Widget
   GetTopShell(widget)
   Widget widget;
   {
       while (widget && !XmIsWMShell (widget))
           widget = XtParent (widget));

       return widget;
   }

   void
   ok_callback(dialog, client_data, call_data)
   Widget dialog;
   XtPointer client_data;
   XtPointer call_data;
   {
       /*  do whatever the callback needs to do ... */

/* if immediate parent is not a DialogShell, mimic the same * behavior as if it were (i.e., pop down the parent.) */ if (!XmIsDialogShell (XtParent (dialog))) XtPopdown (GetTopShell (dialog)); } The Motif toolkit defines similar macros for all of its widget classes. For example, <Xm/MessageB.h> defines the macro XmIsMessageBox():

   #define XmIsMessageBox(w)   XtIsSubclass (w, xmMessageBoxWidgetClass)
This macro determines whether or not a particular widget is subclassed from the MessageBox widget class. Since all of the MessageDialogs are really instances of the MessageBox class, the macro covers all of the different types of MessageDialogs. If the widget is a MessageBox, the macro returns True whether or not the widget is an immediate child of a DialogShell. Note that this macro does not return True if the widget is a DialogShell.

6.6.3 Internal Widgets

All of the Motif dialog widgets are composed of primitive subwidgets such as Labels, PushButtons, and TextField widgets. For most tasks, it is possible to treat a dialog as a single entity. However, there are some situations when it is useful to be able to get a handle to the widgets internal to the dialog. For example, one way to set the default button for a dialog is to use the XmNdefaultButton resource. The value that you specify for this resource must be a widget ID, so this is one of those times when it is necessary to get a handle to the actual subwidgets contained within a dialog.

The Motif toolkit provides routines that allow you to access the internal widgets. For MessageDialogs, you can retrieve the subwidgets using XmMessageBoxGetChild(), which has the following form:

   Widget
   XmMessageBoxGetChild(widget, child)
       Widget          widget;
       unsigned char   child;
The widget parameter is a handle to a dialog widget, not its DialogShell parent. The child parameter is an enumerated value that specifies a particular subwidget in the dialog. The parameter can have any one of the following values:
   XmDIALOG_OK_BUTTON
   XmDIALOG_CANCEL_BUTTON
   XmDIALOG_HELP_BUTTON
   XmDIALOG_DEFAULT_BUTTON
   XmDIALOG_MESSAGE_LABEL
   XmDIALOG_SEPARATOR
   XmDIALOG_SYMBOL_LABEL
The values refer to the different widgets in a MessageDialog and they should be self-explanatory. For SelectionDialogs, the toolkit provides the XmSelectionBoxGetChild() routine. This routine is identical to XmMessageBoxGetChild(), except that it takes different values for the different widgets in a SelectionDialog. The routine is discussed in Chapter 6, Selection Dialogs.

One method that you can use to customize the predefined Motif dialogs is to unmanage the subwidgets that are inappropriate for your purposes. To get the widget ID for a widget, so that you can pass it to XtUnmanageChild(), you need to call XmMessageBoxGetChild(). You can also use this routine to get a handle to a widget that you want to temporarily disable. These techniques are demonstrated in the following code fragment:

   text = XmStringCreateLocalized ("You have new mail.");
   XtSetArg (args[0], XmNmessageString, text);
   dialog = XmCreateInformationDialog (parent, "message", args, 1);
   XmStringFree (text);

   XtSetSensitive (
       XmMessageBoxGetChild (dialog, XmDIALOG_HELP_BUTTON), False);
   XtUnmanageChild (
       XmMessageBoxGetChild (dialog, XmDIALOG_CANCEL_BUTTON));
The output of a program using this code fragment is shown in the figure.

figs.eps/V6a.05.07.eps.png
MessageDialog with an unmanaged Cancel button and an insensitive Help button

Since the message in this dialog is so simple, it does not make sense to have both an OK and a Cancel button, so we unmanage the latter. On the other hand, it does make sense to have a Help button. However, there is currently no help available, so we make the button unselectable by desensitizing it using XtSetSensitive().

6.7 Dialog Modality

The concept of forcing the user to respond to a dialog is known as modality. Modality governs whether or not the user can interact with other windows on the desktop while a particular dialog is active. Dialogs are either modal or modeless. There are three levels of modality: primary application modal, full application modal, and system modal. In all cases, the user must interact with a modal dialog before control is released and normal input is re­sumed. In a system modal dialog, the user is prevented from interacting with any other window on the display. Full application modal dialogs allow the user to interact with any window on the desktop except those that are part of the same application as the modal window. Primary application modal dialogs allow the user to interact with any other window on the display except for the window that is acting as the parent for this particular dialog.

For example, if the user selected an action that caused an error dialog to be displayed, the dialog could be primary application modal, so that the user would have to acknowledge the error before she interacts with the same window again. This type of modality does not restrict her ability to interact with another window in the same application, provided that the other window is not the one acting as the parent for the modal dialog.

Modal dialogs are perhaps the most frequently misused feature of a graphical user interface. Programmers who fail to grasp the concept of event-driven programming and design, whereby the user is in control, often fall into the convenient escape route that modal dialogs provide. This problem is difficult to detect, let alone cure, because there are just as many right ways to invoke modal dialogs as there are wrong ways. Modality should be used in moderation, but it should also be used consistently. Let's examine a common scenario. Note that this example does not necessarily favor using modal dialogs; it is presented as a reference point for the types of things that people are used to doing in tty-based programs.

A text editor has a function that allows the user to save its text to a file. In order to save the text, the program needs a filename. Once it has a filename, the program needs to check that the user has sufficient permission to open or create the file and it also needs to see if there is already some text in the file. If an error condition occurs, the program needs to notify the user of the error, ask for a new filename, or get permission to overwrite the file's contents. Whatever the case, some interaction with the user is necessary in order to proceed. If this were a typical terminal-based application, the program flow would be similar to that in the following code fragment:

   FILE *fp;
   char buf[BUFSIZ], file[BUFSIZ];
   extern char *index();

   printf ("What file would you like to use? ");
   if (!(fgets (file, sizeof file, stdin)) || file[0] == 0) {
       puts ("Cancelled.");
       return;
   }

   *(index (file, '0)) = 0; /* get rid of newline terminator */

   /* "a+" creates file if it doesn't exist */
   if (!(fp = fopen (file, "a+"))) {
       perror (file);
       return;
   }

   if (ftell (fp) > 0) { /* There's junk in the file already */
       printf ("Overwrite contents of %s? ", file);
       buf[0] = 0;
       if (!(fgets (buf, sizeof buf, stdin)) || buf[0] == 0 ||
               buf[0] == 'n' || buf[0] == 'N') {
           puts ("Cancelled.");
           fclose (fp);
           return;
       }
   }

   rewind (fp);
This style of program flow is still possible with a graphical user interface system using modal dialogs. In fact, the style is frequently used by engineers who are trying to port tty-based applications to Motif. It is also a logical approach to programming, since it does one task followed by another, asking only for information that it needs when it needs it.

However, in an event-driven environment, where the user can interact with many different parts of the program simultaneously, displaying a series of modal dialogs is not the best way to handle input and frequently it's just plain wrong as a design approach. You must adopt a new paradigm in interface design that conforms to the capabilities of the window system and meets the expectations of the user. It is essential that you understand the event-driven model if you want to create well-written, easy-to-use applications.

Window-based applications should be modeled on the behavior of a person filling out a form, such as an employment application or a medical questionnaire. Under this scenario, you are given a form asking various questions. You take it to your seat and fill it out however you choose. If it asks for your license number, you can get out your driver's license and copy down the number. If it asks for your checking account number, you can examine your checkbook for that information. The order in which you fill out the application is entirely up to you. You are free to examine the entire form and fill out whatever portions you like, in whatever order you like.

When the form is complete, you return it to the person who gave it to you. The attendant can check it over to see if you forgot something. If there are errors, you typically take it back and continue until it's right. The attendant can simply ask you the question straight out and write down whatever you say, but this prevents him from doing other work or dealing with other people. Furthermore, if you don't know the answer to the question right away, then you have to take the form back and fill it out the way you were doing it before. No matter how you look at it, this process is not an interview where you are asked questions in sequence and must answer them that way. You are supposed to prepare the form off-line, without requiring interaction from anyone else.

Window-based applications should be treated no differently. Each window, or dialog, can be considered to be a form of some sort. Allow the user to fill out the form at her own convenience and however she chooses. If she wants to interact with other parts of the application or other programs on the desktop, she should be allowed to do so. When the user selects one of the buttons in the action area, this action is her way of returning the form. At this time, you may either accept it or reject it. At no point in the process so far have we needed a modal dialog.

Once the form has been submitted, you can take whatever action is appropriate. If there are errors in any section of the dialog, you may need to notify the user of the error. Here is where a modal dialog can be used legitimately. For example, if the user is using a FileSelectionDialog to specify the file she wants to read and the file is unreadable, then you must notify her so that she can make another selection. In this case, the notification is usually in the form of an ErrorDialog, with a message that explains the error and an OK button. The user can read the message and press the button to acknowledge the error.

It is often difficult to judge what types of questions or how much information is appropriate in modal dialogs. The rule of thumb is that questions in modal dialogs should be limited to simple, yes/no questions. You should not prompt for any information that is already available through an existing dialog, but instead bring up that dialog and instruct the user to provide the necessary information there. You should also avoid posting modal dialogs that prompt for a filename or anything else that requires typing. You should be requesting this type of information through the text fields of modeless dialog boxes.

As for the issue of forcing the user to fill out forms in a particular order, it may be perfectly reasonable to require this type of interaction. You should implement these restrictions by managing and unmanaging separate dialogs, rather than by using modal dialogs to prevent interaction with all but a single dialog.

All of these admonitions are not to suggest that modal dialogs are rare or that you should avoid using them at all costs. On the contrary, they are extremely useful in certain situations, are quite common, and are used in a wide variety of ways--even those that we might not recommend. We have presented all of these warnings because modal dialogs are frequently misused and programs that use fewer of them are usually better than those that use more of them. Modal dialogs interrupt the user and disrupt the flow of work in an application. There is no sanity checking to prevent you from misusing dialogs so it is up to you to keep the use of modal dialogs to a minimum.

6.7.1 Implementing Modal Dialogs

Once you have determined that you need to implement a modal dialog, you can use the ­XmNdialogStyle resource to set the modality of the dialog. This resource is defined by the BulletinBoard widget class; it is only relevant when the widget is an immediate child of a DialogShell. The resource can be set to one of the following values: The value XmDIALOG_APPLICATION_MODAL is used for backwards compatibility with Motif 1.0; it is defined to be the same as XmDIALOG_PRIMARY_APPLICATION_MODAL.

   XmDIALOG_MODELESS
   XmDIALOG_PRIMARY_APPLICATION_MODAL
   XmDIALOG_FULL_APPLICATION_MODAL
   XmDIALOG_SYSTEM_MODAL
XmDIALOG_MODELESS is the default value for the resource, so unless you change the value any dialog that you create will be modeless.

When you use one of the modal values, the user has no choice but to respond to your dialog box before continuing to interact with the application. If you use modality at all, you should probably avoid using XmDIALOG_SYSTEM_MODAL, since it is rarely necessary to restrict the user from interacting with all of the other applications on the desktop. This style of modality is typically reserved for system-level interactions. Under the Motif Window Manager, when a system modal dialog is popped up, if the user moves the mouse outside of the modal dialog, the cursor turns into the international "do not enter" symbol. Attempts to interact with other windows cause the server to beep.

the source code shows a sample program that displays a dialog box that the user must reply to before continuing to interact with the application. 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.

   /* modal.c -- demonstrate modal dialogs.  Display two pushbuttons
    * each activating a modal dialog.
    */
   #include <Xm/RowColumn.h>
   #include <Xm/MessageB.h>
   #include <Xm/PushB.h>

   /* main() --create a pushbutton whose callback pops up a dialog box */
   main(argc, argv)
   char *argv[];
   {
       XtAppContext app;
       Widget toplevel, button, rowcolumn;
       void pushed();

       XtSetLanguageProc (NULL, NULL, NULL);

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

       rowcolumn = XtCreateManagedWidget ("rowcolumn",
           xmRowColumnWidgetClass, toplevel, NULL, 0);

       button = XtCreateManagedWidget ("Application Modal",
           xmPushButtonWidgetClass, rowcolumn, NULL, 0);
       XtAddCallback (button, XmNactivateCallback,
           pushed, XmDIALOG_FULL_APPLICATION_MODAL);
       button = XtCreateManagedWidget ("System Modal",
           xmPushButtonWidgetClass, rowcolumn, NULL, 0);
       XtAddCallback (button, XmNactivateCallback, pushed,
           XmDIALOG_SYSTEM_MODAL);

       XtRealizeWidget (toplevel);
       XtAppMainLoop (app);
   }

   /* pushed() --the callback routine for the main app's pushbutton.
    * Create either a full-application or system modal dialog box.
    */
   void
   pushed(widget, client_data, call_data)
   Widget widget;
   XtPointer client_data;
   XtPointer call_data;
   {
       static Widget dialog;
       XmString t;
       extern void dlg_callback();
       unsigned char modality = (unsigned char) client_data;

       /* See if we've already created this dialog -- if so,
        * we don't need to create it again.  Just re-pop it up.
        */
       if (!dialog) {
           Arg args[5];
           int n = 0;
           XmString ok = XmStringCreateLocalized ("OK");
           XtSetArg(args[n], XmNautoUnmanage, False); n++;
           XtSetArg(args[n], XmNcancelLabelString, ok); n++;
           dialog = XmCreateInformationDialog (widget, "notice", args, n);
           XtAddCallback (dialog, XmNcancelCallback, dlg_callback, NULL);
           XtUnmanageChild (
               XmMessageBoxGetChild (dialog, XmDIALOG_OK_BUTTON));
           XtUnmanageChild (
               XmMessageBoxGetChild (dialog, XmDIALOG_HELP_BUTTON));
       }
       t = XmStringCreateLocalized ("You must reply to this message now!");
       XtVaSetValues (dialog,
           XmNmessageString,    t,
           XmNdialogStyle,      modality,
           NULL);
       XmStringFree (t);
       XtManageChild (dialog);
       XtPopup  (XtParent (dialog), XtGrabNone);
   }

   void
   dlg_callback(dialog, client_data, call_data)
   Widget dialog;
   XtPointer client_data;
   XtPointer call_data;
   {
       XtPopdown (XtParent (dialog));
   }
The output of this program is shown in the figure.

figs.eps/V6a.05.08.eps.png
Output of modal.c

This program demonstrates both application modal and system modal dialogs. The value for the XmNdialogType resource is passed as client data to the callback routine that posts the dialog.

6.7.2 Forcing an Immediate Response

In the source code once the dialog is posted, the function returns so that XtAppMainLoop() can continue to process the events. If the function does not return, the application will not respond to user events and, for that matter, the dialog will not even be displayed. Just because a dialog is realized and managed does not mean that it is displayed on the screen, as events must be processed in order for it to appear. See Chapter 21, Advanced Dialog Programming, for a discussion of this phenomenon. (See Volume One, Xlib Programming Manual, for more information on event processing.)

However, there are situations where it would be nice not to have to return from the function and break its flow of control. As an example, consider a function that allows the user to perform a particularly dangerous action, such as removing or overwriting a file. What you'd like to do is prompt the user first and allow her to reconsider the action before proceeding. If she confirms the action, you'd like to continue from within the same function without having to return in order to process events.

In order to write this type of function, we need to find a way to process the events that display and manage the dialog without returning to the main loop. The user also needs to be able to respond to the dialog, so we really need to allow normal event processing to continue in the context of the function. Let's assume that there is a hypothetical function, AskUser(), that we can use in the following way:

   if (AskUser ("Are you sure you want to do this?") == YES) {
       /* proceed with action... */
   }
The function AskUser() should post a full application modal MessageDialog, wait for the user to respond to the dialog, and return a predefined value for either YES or NO. The magic of the function is to get around the requirement that events can only be read and processed directly from XtAppMainLoop(). The code for such a function is shown in the source code XmStringCreateLocalized() is only available in Motif 1.2; XmStringCreateSimple() is the corresponding function in Motif 1.1.
   #define YES  1
   #define NO   2

   /*
    * AskUser() -- a generalized routine that asks the user a question
    * and returns the Yes/No response.
    */
   int
   AskUser(parent, question)
   Widget parent;
   char *question;
   {
       static Widget dialog;
       XmString text, yes, no;
       static int answer;
       extern void response();
       extern XtAppContext app;

       if (!dialog) {
           dialog = XmCreateQuestionDialog (parent, "dialog", NULL, 0);
           yes = XmStringCreateLocalized ("Yes");
           no = XmStringCreateLocalized ("No");
           XtVaSetValues (dialog,
               XmNdialogStyle,        XmDIALOG_FULL_APPLICATION_MODAL,
               XmNokLabelString,      yes,
               XmNcancelLabelString,  no,
               NULL);
           XtSetSensitive (
               XmMessageBoxGetChild (dialog, XmDIALOG_HELP_BUTTON),
               False);
           XtAddCallback (dialog, XmNokCallback, response, &answer);
           XtAddCallback (dialog, XmNcancelCallback, response, &answer);
           XmStringFree (yes);
           XmStringFree (no);
       }
       answer = 0;
       text = XmStringCreateLocalized (question);
       XtVaSetValues (dialog,
           XmNmessageString,      text,
           NULL);
       XmStringFree (text);
       XtManageChild (dialog);
       XtPopup (XtParent (dialog), XtGrabNone);

       /* while the user hasn't provided an answer, simulate main loop.
        * The answer changes as soon as the user selects one of the
        * buttons and the callback routine changes its value.
        */
       while (answer == 0)
           XtAppProcessEvent (app, XtIMAll);

       XtPopdown (XtParent (dialog));
       return answer;
   }

   /* response() --The user made some sort of response to the
    * question posed in AskUser().  Set the answer (client_data)
    * accordingly and destroy the dialog.
    */
   void
   response(widget, client_data, call_data)
   Widget widget;
   XtPointer client_data;
   XtPointer call_data;
   {
       int *answer = (int *) client_data;
       XmAnyCallbackStruct *cbs = (XmAnyCallbackStruct *) call_data;

       switch (cbs->reason) {
           case XmCR_OK:
               *answer = YES;
               break;
           case XmCR_CANCEL:
               *answer = NO;
               break;
     default:
         return;
       }
   }
The first parameter to the function is the widget that acts as the parent of the new dialog. It is important to choose this widget wisely. The parent widget must not be a gadget or an unrealized widget; it should be a widget that is currently mapped to the screen. Widgets that are menu items are not good candidates, since they are not mapped to the screen for very long. The top-level shell widget of the widget that caused the callback function to be invoked is typically a good choice. The second parameter is the string that is displayed in the dialog.

The routine is intended to be used to display a dialog that asks a Yes/No question, so we change the OK and Cancel labels to say Yes and No, respectively. The routine creates a QuestionDialog as a static Widget, which allows us to reuse the dialog, rather than create it each time the function is called. This technique may improve performance on some machines. The modality of the dialog and the labels for the PushButtons in the action area are set at creation time, but the actual message string is set each time that the function is called, since the message can change. When we install the callback routines for the buttons, we use the address of the answer variable as the client data. As a result, when the user responds to the question by selecting the Yes or No button, the callback routine has access to the variable and can change its value accordingly.

The while loop is where the application waits for the user to make a selection. The loop exits when the variable answer is changed from its initial value (0) to either YES (1) or NO (2) by the callback routine. By using XtAppProcessEvent(), we have effectively reproduced the XtAppMainLoop() function that is used in the main application. Rather than returning to that level and breaking our flow of control, we have introduced a miniature main loop in the function itself.

While the AskUser() routine in the source code is useful as it is written, there are a number of enhancements that will make it even more useful. By using what we've learned in this chapter, we can come up with a simple, yet extremely robust interface for prompting the user for responses to questions without breaking the natural flow of control in the application. the source code demonstrates a generalized version of AskUser() in a complete application. The program ask_user.c allows the user to execute UNIX commands that create and remove a temporary file. 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.

   /* ask_user.c -- the user is presented with two pushbuttons.
    * The first creates a file (/tmp/foo) and the second removes it.
    * In each case, a dialog pops up asking for verification of the action.
    *
    * This program is intended to demonstrate an advanced implementation
    * of the AskUser() function.  This time, the function is passed the
    * strings to use for the OK button and the Cancel button as well as
    * the button to use as the default value.
    */
   #include <Xm/DialogS.h>
   #include <Xm/SelectioB.h>
   #include <Xm/RowColumn.h>
   #include <Xm/MessageB.h>
   #include <Xm/PushB.h>

   #define YES 1
   #define NO  2

   /* Generalize the question/answer process by creating a data structure
    * that has the necessary labels, questions and everything needed to
    * execute a command.
    */
   typedef struct {
       char *label;    /* label for pushbutton used to invoke cmd */
       char *question; /* question for dialog box to confirm cmd */
       char *yes;      /* what the "OK" button says */
       char *no;       /* what the "Cancel" button says */
       int   dflt;     /* which should be the default answer */
       char *cmd;      /* actual command to execute (using system()) */
   } QandA;

   QandA touch_foo = {
       "Create", "Create /tmp/foo?", "Yes", "No", YES, "touch /tmp/foo"
   };
   QandA rm_foo = {
       "Remove", "Remove /tmp/foo?", "Yes", "No", NO, "rm /tmp/foo"
   };

   XtAppContext app;

   main(argc, argv)
   int argc;
   char *argv[];
   {
       Widget toplevel, button, rowcolumn;
       XmString label;
       void pushed();

       XtSetLanguageProc (NULL, NULL, NULL);

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

       rowcolumn = XtVaCreateManagedWidget ("rowcolumn",
           xmRowColumnWidgetClass, toplevel, NULL);

       label = XmStringCreateLocalized (touch_foo.label);
       button = XtVaCreateManagedWidget ("button",
           xmPushButtonWidgetClass, rowcolumn,
           XmNlabelString,          label,
           NULL);
       XtAddCallback (button, XmNactivateCallback, pushed, &touch_foo);
       XmStringFree (label);

       label = XmStringCreateLocalized (rm_foo.label);
       button = XtVaCreateManagedWidget ("button",
           xmPushButtonWidgetClass, rowcolumn,
           XmNlabelString,          label,
           NULL);
       XtAddCallback (button, XmNactivateCallback, pushed, &rm_foo);
       XmStringFree (label);

       XtManageChild (rowcolumn);
       XtRealizeWidget (toplevel);
       XtAppMainLoop (app);
   }

   /* pushed() --when a button is pressed, ask the question described
    * by the QandA parameter (client_data).  Execute the cmd if YES.
    */
   void
   pushed(widget, client_data, call_data)
   Widget widget;
   XtPointer client_data;
   XtPointer call_data;
   {
       QandA *quest = (QandA *) client_data;

       if (AskUser (widget, quest->question, quest->yes, quest->no,
               quest->dflt) == YES) {
           printf ("Executing: %s0, quest->cmd);
           system (quest->cmd);
       } else
           printf ("Not executing: %s0, quest->cmd);
   }

   /*
    * AskUser() -- a generalized routine that asks the user a question
    * and returns a response.  Parameters are: the question, the labels
    * for the "Yes" and "No" buttons, and the default selection to use.
    */
   AskUser(parent, question, ans1, ans2, default_ans)
   Widget parent;
   char *question, *ans1, *ans2;
   int default_ans;
   {
       static Widget dialog; /* static to avoid multiple creation */
       XmString text, yes, no;
       static int answer;
       extern void response();

       if (!dialog) {
           dialog = XmCreateQuestionDialog (parent, "dialog", NULL, 0);
           XtVaSetValues (dialog,
               XmNdialogStyle,        XmDIALOG_FULL_APPLICATION_MODAL,
               NULL);
           XtSetSensitive (
               XmMessageBoxGetChild (dialog, XmDIALOG_HELP_BUTTON),
               False);
           XtAddCallback (dialog, XmNokCallback, response, &answer);
           XtAddCallback (dialog, XmNcancelCallback, response, &answer);
       }
       answer = 0;
       text = XmStringCreateLocalized (question);
       yes = XmStringCreateLocalized (ans1);
       no = XmStringCreateLocalized (ans2);
       XtVaSetValues (dialog,
           XmNmessageString,      text,
           XmNokLabelString,      yes,
           XmNcancelLabelString,  no,
           XmNdefaultButtonType,  default_ans == YES ?
               XmDIALOG_OK_BUTTON : XmDIALOG_CANCEL_BUTTON,
           NULL);
       XmStringFree (text);
       XmStringFree (yes);
       XmStringFree (no);
       XtManageChild (dialog);
       XtPopup (XtParent (dialog), XtGrabNone);

       while (answer == 0)
           XtAppProcessEvent (app, XtIMAll);

       XtPopdown (XtParent (dialog));
       /* make sure the dialog goes away before returning. Sync with server
        * and update the display.
        */
       XSync (XtDisplay (dialog), 0);
       XmUpdateDisplay (parent);

       return answer;
   }

   /* response() --The user made some sort of response to the
    * question posed in AskUser().  Set the answer (client_data)
    * accordingly.
    */
   void
   response(widget, client_data, call_data)
   Widget widget;
   XtPointer client_data;
   XtPointer call_data;
   {
       int *answer = (int *) client_data;
       XmAnyCallbackStruct *cbs = (XmAnyCallbackStruct *) call_data;

       if (cbs->reason == XmCR_OK)
           *answer = YES;
       else if (cbs->reason == XmCR_CANCEL)
           *answer = NO;
   }

The new version of AskUser() is more dynamic than before, since more of the dialog is configurable upon each invocation of the function. The routine now allows you to specify the message, the labels for the OK and Cancel buttons, and the default button for the dialog. The flexibility of the routine is achieved at the cost of a few more lines of source code and additional parameters to the function. The performance of the function is completely unaffected.

One case that the new version of AskUser() does not deal with is the need for additional buttons in the action area of the dialog. For example, what if you need to provide a Cancel button in addition to the Yes and No answers? Let's say that the user has selected the Quit menu item in a text editor application. Since the user has yet to update the changes to the file that she has been editing, the application posts a dialog that asks her if she wants to update her changes before exiting. There are three possible responses:

One easy way to provide these three choices is to set the label for the Help button to Cancel using the XmNhelpLabelString resource. Then you just need to modify the callback function so that it handles the XmCR_HELP reason and returns a new value for the Cancel button.

However, this solution does not work if you want to provide help in addition to these choices. The default MessageDialog only provides three buttons in the action area, although in Motif 1.2 you can add additional action area buttons to the dialog. For more information on how to handle this situation, see Chapter 7, Custom Dialogs.

6.8 Summary

Dialogs are used extensively in all window-oriented applications and their uses are quite diverse. As a result, it is impossible to provide numerous examples of the use of any one particular style of dialog. This chapter introduced the implementation of Motif dialogs by using the predefined MessageDialogs as examples. We described how to create the dialogs, how to set various dialog resources, how to handle dialog callback routines, and how to implement modal dialogs. Although our examples used MessageDialogs, much of the discussion is applicable to other types of Motif dialogs.

The next chapter deals with the predefined Motif selection dialogs. These dialogs allow you to provide the user with a group of choices from which to make a selection. Chapter 7, Custom Dialogs, discusses how you can break away from the predefined Motif dialogs and build dialogs on your own. Chapter 21, Advanced Dialog Programming, gets into advanced topics in Xt and Motif programming, using various types of MessageDialogs as examples.


Contents Previous Next