Contents Previous Next

7 Selection Dialogs


This chapter describes the predefined Motif selection-style dialogs. These dialogs display a list of items, such as files or commands, and allow the user to select items.

In Chapter 5, Introduction to Dialogs, we introduced the idea that dialogs are transient windows that perform a single task in an application. Dialogs may perform tasks that range from displaying a simple message, to asking a question, to providing a highly interactive window that obtains information from the user. The previous chapter also introduced MessageDialogs and discussed how they are used by the Motif toolkit. This chapter discusses SelectionDialogs, which are at the next level of complexity in predefined Motif dialogs.

In general, SelectionDialogs are used to present the user with a list of choices. The user can also enter a new selection or edit an existing one by typing in a text area in the dialog. SelectionDialogs are appropriate when the user is supposed to respond to the dialog with more than just a simple yes or no answer. With respect to the action area, SelectionDialogs have the same default buttons as MessageBoxes (e.g., OK, Cancel, and Help ). The dialogs also provide an Apply button, but the button is not always managed by default. SelectionDialogs are meant to be less transient than MessageDialogs, since the user is expected to do more than read a message.

7.1 Types of SelectionDialogs

As explained in Chapter 5, Introduction to Dialogs, there are four kinds of SelectionDialogs. The SelectionDialog and the PromptDialog are compound objects composed of a SelectionBox and a DialogShell. To use these objects, you need to include the header file <Xm/SelectioB.h>. The FileSelectionDialog is another compound object made up of a FileSelectionBox and a DialogShell. The include file for this object is <Xm/FileSB.h>. The Command widget is somewhat different, in that it is typically used as part of a larger interface, rather than as a dialog. To use the Command widget, include the file <Xm/Command.h >. You can create each of these dialogs using the associated convenience routines:

   XmCreateSelectionBox()
   XmCreateSelectionDialog()
   XmCreatePromptDialog()
   XmCreateFileSelectionBox()
   XmCreateFileSelectionDialog()
   XmCreateCommand()
Like the MessageDialog convenience routines, each of the SelectionDialog routines creates a dialog widget. In addition, routines that end in Dialog automatically create a DialogShell as the parent of the dialog widget. Note that the Command widget does not provide a convenience routine that creates a DialogShell; to put a Command widget in a DialogShell, you must create the DialogShell yourself. All of the convenience functions use the standard format for Motif creation routines.

The SelectionBox resource XmNdialogType specifies the type of dialog that has been created. The resource is set automatically by the dialog convenience routines. Unlike the XmNdialogType resource for MessageDialogs, the SelectionBox resource cannot be changed once the dialog has been created. The resource can have one of the following values:

   XmDIALOG_WORK_AREA
   XmDIALOG_PROMPT
   XmDIALOG_SELECTION
   XmDIALOG_COMMAND
   XmDIALOG_FILE_SELECTION
These values should be self-explanatory, with the exception of XmDIALOG_WORK_AREA. This value is set when a SelectionBox is not the child of a DialogShell and it is not one of the other types of dialogs. In other words, if you create a SelectionDialog using XmCreateSelectionDialog(), the value is XmDIALOG_SELECTION , but if you use XmCreateSelectionBox(), the value is XmDIALOG_WORK_AREA. When a SelectionBox is created as the child of a DialogShell, the Apply button is automatically managed, except if XmNdialogType is set to XmDIALOG_PROMPT. Otherwise, the button is created but not managed.

The different types of SelectionDialogs are meant to be used for unique purposes. Each dialog provides different components that the user can interact with to perform a task. In the following sections, we examine each of the SelectionDialogs in turn.

7.2 SelectionDialogs

The SelectionDialog provides a ScrolledList that allows the user to select from a list of choices, as well as a TextField where the user can type in choices. When the user makes a selection from the list, the selected item is displayed in the text entry area. The user can also type new or existing choices into the text entry area directly. The dialog does not take any action until the user activates one of the buttons in the action area or presses the RETURN key. If the user double-clicks on an item in the List, the item is displayed in the text area and the OK button is automatically activated. the source code demonstrates the use of a SelectionDialog. XtSetLanguageProc() is only available in X11R5; there is no corresponding function in X11R4. XmStringCreateLocalized() is only available in Motif 1.2; XmStringCreateSimple() is the corresponding function in Motif 1.1. XmFONTLIST_DEFAULT_TAG replaces XmSTRING_DEFAULT_CHARSET in Motif 1.2.

   /* select_dlg.c -- display two pushbuttons: days and months.
    * When the user selections one of them, post a selection
    * dialog that displays the actual days or months accordingly.
    * When the user selects or types a selection, post a dialog
    * the identifies which item was selected and whether or not
    * the item is in the list.
    *
    * This program demonstrates how to use selection boxes,
    * methods for creating generic callbacks for action area
    * selections, abstraction of data structures, and a generic
    * MessageDialog posting routine.
    */
   #include <Xm/SelectioB.h>
   #include <Xm/RowColumn.h>
   #include <Xm/MessageB.h>
   #include <Xm/PushB.h>

   Widget PostDialog();

   char *days[] = {
       "Sunday", "Monday", "Tuesday", "Wednesday",
       "Thursday", "Friday", "Saturday"
   };
   char *months[] = {
       "January", "February", "March", "April", "May", "June",
       "July", "August", "September", "October", "November", "December"
   };
   typedef struct {
       char *label;
       char **strings;
       int size;
   } ListItem;

   ListItem month_items = { "Months", months, XtNumber (months) };
   ListItem days_items = { "Days", days, XtNumber (days) };

   /* main() --create two pushbuttons whose callbacks pop up a dialog */
   main(argc, argv)
   char *argv[];
   {
       Widget toplevel, button, rc;
       XtAppContext app;
       void pushed();

       XtSetLanguageProc (NULL, NULL, NULL);

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

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

       button = XtVaCreateManagedWidget (month_items.label,
           xmPushButtonWidgetClass, rc, NULL);
       XtAddCallback (button, XmNactivateCallback, pushed, &month_items);

       button = XtVaCreateManagedWidget (days_items.label,
           xmPushButtonWidgetClass, rc, NULL);
       XtAddCallback (button, XmNactivateCallback, pushed, &days_items);

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

   /* pushed() --the callback routine for the main app's pushbutton.
    * Create a dialog containing the list in the items parameter.
    */
   void
   pushed(widget, client_data, call_data)
   Widget widget;
   XtPointer client_data;
   XtPointer call_data;
   {
       Widget dialog;
       XmString t, *str;
       int i;
       extern void dialog_callback();
       ListItem *items = (ListItem *) client_data;

       str = (XmString *) XtMalloc (items->size * sizeof (XmString));
       t = XmStringCreateLocalized (items->label);
       for (i = 0; i < items->size; i++)
           str[i] = XmStringCreateLocalized (items->strings[i]);
       dialog = XmCreateSelectionDialog (widget, "selection", NULL, 0);
       XtVaSetValues (dialog,
           XmNlistLabelString, t,
           XmNlistItems,       str,
           XmNlistItemCount,   items->size,
           XmNmustMatch,       True,
           NULL);
       XtSetSensitive (
           XmSelectionBoxGetChild (dialog, XmDIALOG_HELP_BUTTON), False);
       XtAddCallback (dialog, XmNokCallback, dialog_callback, NULL);
       XtAddCallback (dialog, XmNnoMatchCallback, dialog_callback, NULL);
       XmStringFree (t);
       while (--i >= 0)
           XmStringFree (str[i]); /* free elements of array */
       XtFree (str); /* now free array pointer */
       XtManageChild (dialog);

       XtPopup (XtParent (dialog), XtGrabNone);
   }

   /* dialog_callback() --The OK button was selected or the user
    * input a name by himself.  Determine whether the result is
    * a valid name by looking at the "reason" field.
    */
   void
   dialog_callback(widget, client_data, call_data)
   Widget widget;
   XtPointer client_data;
   XtPointer call_data;
   {
       char msg[256], *prompt, *value;
       int dialog_type;
       XmSelectionBoxCallbackStruct *cbs =
           (XmSelectionBoxCallbackStruct *) call_data;

       switch (cbs->reason) {
           case XmCR_OK:
               prompt = "Selection: ";
               dialog_type = XmDIALOG_MESSAGE;
               break;
           case XmCR_NO_MATCH:
               prompt = "Not a valid selection: ";
               dialog_type = XmDIALOG_ERROR;
               break;
           default:
               prompt = "Unknown selection: ";
               dialog_type = XmDIALOG_ERROR;
       }
       XmStringGetLtoR (cbs->value, XmFONTLIST_DEFAULT_TAG, &value);
       sprintf (msg, "%s%s", prompt, value);
       XtFree (value);
       (void) PostDialog (XtParent (XtParent (widget)), dialog_type, msg);
       if (cbs->reason != XmCR_NO_MATCH) {
           XtPopdown (XtParent (widget));
           XtDestroyWidget (widget);
       }
   }

   /*
    * PostDialog() -- a generalized routine that allows the programmer
    * to specify a dialog type (message, information, error, help,
    * etc..), and the message to show.
    */
   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);
       XtUnmanageChild (
           XmMessageBoxGetChild (dialog, XmDIALOG_CANCEL_BUTTON));
       XtSetSensitive (
           XmMessageBoxGetChild (dialog, XmDIALOG_HELP_BUTTON), False);
       XtAddCallback (dialog, XmNokCallback, XtDestroyWidget, NULL);
       XtManageChild (dialog);
       return dialog;
   }
The output of the program is shown in the figure.

figs.eps/V6a.06.01.eps.png
Output of select_dlg.c

The program displays two PushButtons, one for months and one for the days of the week. When either button is activated, a SelectionDialog that displays the list of items corresponding to the button is popped up. In keeping with the philosophy of modular programming techniques, we have broken the application into three routines -- two callbacks and one general-purpose message posting function. The lists of day and month names are stored as arrays of strings. We have declared a data structure, ListItem, to store the label and the items for a list. Two instances of this data structure are initialized to the correct values for the lists of months and days. We pass these data structures as the client_data to the callback function pushed(). This callback routine is associated with both of the PushButtons.

The pushed() callback function creates the SelectionDialogs. Since the list of items for a SelectionDialog must be specified as an array of XmString values, the list passed in the client_data parameter must be converted. We create an array of compound strings the size of the list and copy each item into the new array using XmStringCreateLocalized(). The resulting list is used as the value for the XmNlistItems resource. The number of items in the list is specified as the value of the XmNlistItemCount resource. This value must be given for the list to be displayed. It must be less than or equal to the actual number of items in the list. We also set the XmNlistLabelString resource to specify the label for the list of items in the dialog. The SelectionDialog also provides the XmNlistVisibleItemCount resource for specifying the number of visible items in the list. We let the dialog use the default value for this resource.

The final resource that we set for the SelectionDialog is XmNmustMatch. This resource controls whether an item that the user types in the text entry area must match one of the items in the list. By setting the resource to True, we are specifying that the user cannot make up a month or day name. When the user activates the OK button or presses the RETURN key, the widget checks the item in the text entry area against those in the list. If the selection doesn't match any of the items in the list, the program pops up a dialog that indicates the error.

Once the dialog is created, we desensitize its Help button because we are not providing help. We install a callback routine for the OK button using the XmNokCallback . To handle the case when the user types an item that does not match, we also install a callback routine for the XmNnoMatchCallback. The dialog_callback() routine is used to handle both cases. We use the reason field of the callback structure to determine why the callback was called and act accordingly. The value field of the callback structure contains the selected item. If the item is valid, we use the value to create a dialog that confirms the selection. Otherwise, we post an error dialog that indicates the invalid selection. In both cases we use the generalized function, PostDialog(), to display the MessageDialog. If the selection is valid, the routine pops down and destroys the SelectionDialog. Otherwise, we leave the dialog posted so that the user can make another selection.

Just as a point of discussion, you should realize that it was an arbitrary decision to have the PostDialog() function accept a char strings rather than an XmString . The routine could be modified to use an XmString, but doing so doesn't buy us anything. If you find that your application deals with one string format more often than the other, you may want to modify your routines accordingly. You should be aware that converting from one type of string to the other is expensive; if it is done frequently, you may see an effect on performance. Another option is for your routine to accept both string types as different parameters. You can pass a valid value for one parameter and NULL for the other parameter and deal with them accordingly. For more information on handling compound strings, see Chapter 19, Compound Strings.

7.2.1 Callback Routines

The SelectionDialog provides callbacks for its action buttons in the same way as the MessageDialog. Instead of accessing the PushButton widgets to install callbacks, you use the resources XmNokCallback, XmNapplyCallback, XmNcancelCallback, and XmNhelpCallback on the dialog widget itself. These callbacks correspond to each of the four buttons, OK, Apply, Cancel, and Help. The SelectionDialog also provides the XmNnoMatchCallback for handling the case when the item in the text entry area does not match an item in the list.

All of these callback routines take three parameters, just like any standard callback routine. The callback structure that is passed to all of the callback routines in the call_data parameter is of type XmSelectionBoxCallbackStruct . This structure is similar to the one used by MessageDialogs, but it has more fields. The structure is declared as follows:

   typedef struct {
       int          reason;
       XEvent      *event;
       XmString     value;
       int          length;
   } XmSelectionBoxCallbackStruct;

The value of the reason field is an integer value that specifies the reason that the callback routine was invoked. The field can be one of the following values:

   XmCR_OK
   XmCR_APPLY
   XmCR_CANCEL
   XmCR_HELP
   XmCR_NO_MATCH
The value and length fields represent the compound string version of the item that the user selected from the list or typed into the text entry area. In order to get the actual character string for the item, you have to use XmStringGetLtoR() to convert the compound string into a character string. (See Chapter 19, Compound Strings, for a discussion of compound strings.)

7.2.2 Internal Widgets

The SelectionDialog is obviously composed of primitive subwidgets, like PushButtons, Labels, a ScrolledList, and a TextField widget. For most tasks, it is possible to treat the dialog as a single entity because the dialog provides resources that manage the different components. However, there are some situations where it is useful to be able to get a handle to the widgets internal to the dialog. The Motif toolkit provides the XmSelectionBoxGetChild() routine to allow you to access the internal widgets. This routine takes the following form:

   Widget
   XmSelectionBoxGetChild(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_APPLY_BUTTON
   XmDIALOG_CANCEL_BUTTON
   XmDIALOG_HELP_BUTTON
   XmDIALOG_DEFAULT_BUTTONX
   XmDIALOG_LIST
   XmDIALOG_LIST_LABEL
   XmDIALOG_SELECTION_LABEL
   XmDIALOG_TEXT
   XmDIALOG_WORK_AREA
   XmDIALOG_SEPARATOR
The values refer to the different widgets in a SelectionDialog and they should be self-explanatory, with the exception of XmDIALOG_WORK_AREA. A SelectionDialog can manage a work area child; this value returns the work area child. You can customize the operation of a SelectionDialog by adding a work area that contains other components. For a detailed discussion of this technique, see Chapter 7, Custom Dialogs.

One use of XmSelectionBoxGetChild() is to get a handle to the Apply button so that you can manage it. When you create a SelectionBox that is not a child of a DialogShell, the toolkit creates the Apply button, but it is unmanaged by default. The Apply button is available to the PromptDialog, but it is unmanaged by default. To use the button, you must manage it and specify a callback routine, as in the following code fragment:

   XtAddCallback (dialog, XmNapplyCallback, dialog_callback, NULL);
   XtManageChild (XmSelectionBoxGetChild (dialog, XmDIALOG_APPLY_BUTTON));
The callback routine is the same as the one we set for the OK button, but the reason field in the callback structure will indicate that it was called as a result of the Apply button being activated.

7.3 PromptDialogs

The PromptDialog is unique among the SelectionDialogs, in that it does not create a ScrolledList object. This dialog allows the user to type a text string in the text entry area and then enter it by selecting the OK button or by pressing the RETURN key. the source code shows an example of creating and using a PromptDialog. 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.

   /* prompt_dlg.c -- prompt the user for a string.  Two PushButtons
    * are displayed.  When one is selected, a PromptDialog is displayed
    * allowing the user to type a string.  When done, the PushButton's
    * label changes to the string.
    */
   #include <Xm/SelectioB.h>
   #include <Xm/RowColumn.h>
   #include <Xm/PushB.h>

   main(argc, argv)
   char *argv[];
   {
       XtAppContext app;
       Widget toplevel, rc, button;
       void pushed();

       XtSetLanguageProc (NULL, NULL, NULL);

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

       /* RowColumn managed both PushButtons */
       rc = XtVaCreateWidget ("rowcol", xmRowColumnWidgetClass, toplevel,
           NULL);
       /* Create two pushbuttons -- both have the same callback */
       button = XtVaCreateManagedWidget ("PushMe 1",
           xmPushButtonWidgetClass, rc, NULL);
       XtAddCallback (button, XmNactivateCallback, pushed, NULL);
       button = XtVaCreateManagedWidget ("PushMe 2",
           xmPushButtonWidgetClass, rc, NULL);
       XtAddCallback (button, XmNactivateCallback, pushed, NULL);

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

   /* pushed() --the callback routine for the main app's pushbuttons.
    * Create a dialog that prompts for a new button name.
    */
   void
   pushed(widget, client_data, call_data)
   Widget widget;
   XtPointer client_data;
   XtPointer call_data;
   {
       Widget dialog;
       XmString t = XmStringCreateLocalized ("Enter New Button Name:");
       extern void read_name();
       Arg args[5];
       int n = 0;

       /* Create the dialog -- the PushButton acts as the DialogShell's
        * parent (not the parent of the PromptDialog).
        */
       XtSetArg (args[n], XmNselectionLabelString, t); n++;
       XtSetArg (args[n], XmNautoUnmanage, False); n++;
       dialog = XmCreatePromptDialog (widget, "prompt", args, n);
       XmStringFree (t); /* always destroy compound strings when done */

       /* When the user types the name, call read_name() ... */
       XtAddCallback (dialog, XmNokCallback, read_name, widget);

       /* If the user selects cancel, just destroy the dialog */
       XtAddCallback (dialog, XmNcancelCallback, XtDestroyWidget, NULL);

       /* No help is available... */
       XtSetSensitive (
           XmSelectionBoxGetChild (dialog, XmDIALOG_HELP_BUTTON), False);
       XtManageChild (dialog);

       XtPopup (XtParent (dialog), XtGrabNone);
   }

   /* read_name() --the text field has been filled in. */
   void
   read_name(widget, client_data, call_data)
   Widget widget;
   XtPointer client_data;
   XtPointer call_data;
   {
       Widget push_button = (Widget) client_data;
       XmSelectionBoxCallbackStruct *cbs =
           (XmSelectionBoxCallbackStruct *) call_data;

       XtVaSetValues (push_button, XmNlabelString, cbs->value, NULL);
       /* Name's fine -- go ahead and enter it */
       XtDestroyWidget(widget);
   }
The output of the program is shown in the figure.

figs.eps/V6a.06.02.eps.png
Output of prompt_dlg.c

The callback routine for each of the PushButtons, pushed(), creates a PromptDialog that prompts the user to enter a new name for the PushButton. The PushButton is passed as the client_data to the XmNokCallback routine, read_name() , so that the routine can set the label of the PushButton directly from inside the callback. The read_name() function destroys the dialog once it has set the label, since the dialog is no longer needed.

If the Cancel button is pressed, the text is not needed, so we can simply destroy the dialog. Since the first parameter to a dialog callback routine is the dialog widget, we can use XtDestroyWidget as the callback routine. Since the function only takes one parameter, and the widget that is to be destroyed is passed as the first parameter, no client data is needed. We set XmNautoUnmanage to False for the dialog because the application is assuming the responsibility of managing the dialog. There is no help for the dialog so the Help button is disabled by setting it insensitive.

The text area in the PromptDialog is a TextField widget, so you can get a handle to it and set TextField widget resources accordingly. Use XmSelectionBoxGetChild() to access the widget. In order to promote the single-entity abstraction, the dialog provides two resources that affect the TextField widget. You can set the XmNtextString resource to change the value of the text string in the widget. Like other string resources, the value for this resource must be a compound string. The XmNtextColumns resource specifies the width of the TextField in columns.

In Motif 1.1, one frustrating feature of the predefined SelectionDialogs is that when they are popped up, the TextField widget does not receive the keyboard focus by default. If the user is not paying attention, starts typing, and then presses the RETURN key, all of the keystrokes will be thrown away except the RETURN, which will activate the OK button. Motif 1.2 solves this problem by introducing the XmNinitialFocus resource. This resource specifies the widget that has the keyboard focus the first time that the dialog is popped up. The text entry area is the default value of the resource for SelectionDialogs. If you are using Motif 1.1, you need to warn your users about the problem. You can also program around the problem by using XmProcessTraversal() to set the focus to a particular widget.

7.4 The Command Widget

A Command widget allows the user to enter commands and have them saved in a history list widget for later reference. The Command widget is composed of a text entry area and a command history list. Unlike all of the other predefined Motif dialogs, this widget does not provide any action area buttons. The widget does provide a convenient interface for applications that have a command-driven interface, such as a debugger.

You can use the convenience routine XmCreateCommand() to create a Command widget or you can use XtVaCreateWidget() with the class xmCommandWidgetClass. Motif does not provide a convenience routine for creating a Command widget in a DialogShell. The rationale is that the Command widget is intended to be used on a more permanent basis, since it accumulates a history of command input. A Command widget is typically used as part of a larger interface, such as in a MainWindow, which is why it does not have action buttons. (See Chapter 4, The Main Window, for an example.) If you want to create a CommandDialog, you will have to create the DialogShell widget yourself and make the Command widget its immediate child. See Section #sdialogshl in Chapter 5, Introduction to Dialogs, for more information about DialogShells.

The Command widget class is subclassed from SelectionBox. There are similarities between the two widgets, in that the user has the ability to select items from a list. However, the list is composed of the commands that have been previously entered. When the user enters a command, it is added to the list. If the user selects an item from the command history list, the command is displayed in the text entry area. Although the Command widget inherits resources from the SelectionBox, many of the resources are not applicable since the Command widget does not have any action area buttons. All of the SelectionBox resources for setting the labels and callbacks of the buttons do not apply to the Command widget.

The Command widget provides a number of resources that can be used to control the command history list. The XmNhistoryItems and XmNhistoryItemCount resources specify the list of commands and the number of commands in the list. The XmNhistoryVisibleItemCount resource controls the number of items that are visible in the command history. XmNhistoryMaxItems specifies the maximum number of items in the history list. When the maximum value is reached, a command is removed from the beginning of the list to make room for each new command that is entered.

The Command widget provides two callback resources, XmNcommandEnteredCallback and XmNcommandChangedCallback, for the text entry area. When the user changes the text in the command entry area, the XmNcommandChangedCallback is invoked. If the user presses the RETURN key or double-clicks on an item in the command history list, the XmNcommandEnteredCallback is called. The callback routine for each of the callbacks takes the usual three parameters. The callback structure passed to the routines in the call_data parameter is of type XmCommandCallbackStruct, which is identical to the XmSelectionBoxCallbackStruct. The possible values for the reason field in the structure are XmCR_COMMAND_ENTERED and XmCR_COMMAND_CHANGED.

You can get a handle to the subwidgets of the Command widget using function XmCommandGetChild(). The function takes the following form:

   Widget
   XmCommandGetChild(widget, child)
       Widget           widget;
       unsigned char    child;
The widget parameter is a handle to a dialog widget. 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_COMMAND_TEXT
   XmDIALOG_HISTORY_LIST
   XmDIALOG_PROMPT_LABEL
   XmDIALOG_WORK_AREA
The values refer to the different widgets in the Command widget and they should be self-explanatory.

In order to support the idea that the dialog is a single widget, the toolkit also provides a number of convenience routines that you can use to modify the Command widget. The function XmCommandSetValue() sets the text in the command entry area of the dialog. The function takes the following form:

   void
   XmCommandSetValue(widget, command)
       Widget            widget;
       XmString          command;
The command is displayed in the command entry area. The Command widget resource XmNcommand specifies the text for the command entry area, so you can also set this resource directly. Alternatively, you can use XmTextSetString() on the Text widget in the dialog to set the command. However, note that the string you specify to this function is a regular character string, not a compound string.

If you want to append some text to the string in the command entry area, you can use the routine XmCommandAppendValue() , which takes the following form:

   void
   XmCommandAppendValue(widget, command)
       Widget            widget;
       XmString          command;

The command is added to the end of the string in the command entry area. The function XmCommandError() displays an error message in the history area of the Command widget. The function takes the following form:

   void
   XmCommandError(widget, message)
       Widget            widget;
       XmString          message;
The error message is displayed until the user enters the next command.

7.5 FileSelectionDialogs

Like the Command widget, the FileSelectionBox is subclassed from SelectionBox. The FileSelectionDialog looks somewhat different than the other selection dialogs because of its complexity and its unusual widget layout and architecture. Functionally, the FileSelectionDialog allows the user to move through the file system and select a file or a directory for use by the application. The dialog also lets the user specify a filter that controls the files that are displayed in the dialog. This filter is generally specified as a regular expression reminiscent of the classic UNIX meta-characters (e.g., * matches all files, while *.c matches all files that end in .c). the figure shows a FileSelectionDialog.

figs.eps/V6a.06.03.eps.png
A typical FileSelectionDialog


The control area of the FileSelectionDialog has four components. The filter text entry area specifies the directory and the filter. The directories list displays the directories in the current directory specified by the filter. If the user selects a directory, the filter is modified to reflect the selection. The files list shows the files in the current directory. The selection text entry area specifies the file selected by the user. If the user selects a file from the file list, the full pathname is displayed in the selection text entry area.

The FileSelectionDialog has four buttons in its action area. The OK, Cancel, and Help buttons are the same as for other SelectionDialogs. The Filter button acts on the directory and pattern specified in the filter text entry area. For example, the user could enter /usr/src/motif/lib/Xm/* as the filter. In this case, the directory is /usr/src/motif/lib/Xm and the pattern is the "*". When the user selects the Filter button or presses RETURN in the Text widget, the directory part of the filter is searched and all of the directories within that directory are displayed in the directories list. The pattern part is then used to find all of the matching files in the directory and the files are shown in the files list. Only files are placed in this list; directories are excluded since they are listed separately.

While this process seems straightforward, it can become confusing for users and programmers alike because of the way that the widget parses the filter. For example, consider the following string: /usr/src/motif/lib/Xm. This pathname appears to be a common directory path, but in fact, the widget interprets the filter so that the directory is /usr/src/motif/lib and the pattern is Xm. If searched, the directories list will contain all the directories in /usr/src/motif/lib and the files list won't contain anything because Xm is a directory, not a pattern that matches any files. Since users frequently make this mistake when using the FileSelectionDialog, you should be sure to explain the operation of the dialog in the documentation for your application.

The convention that the widget follows is to use the last / in the filter to separate the directory part from the pattern part. Fortunately, the FileSelectionDialog provides resources and other mechanisms to retrieve the proper parts of the filter specification. We will demonstrate how to use these mechanisms in the next few subsections.

7.5.1 Creating a FileSelectionDialog

The convenience function for creating a FileSelectionDialog is XmCreateFileSelectionDialog(). The routine is declared in <Xm/FileSB.h>. The function creates a FileSelectionBox widget and its DialogShell parent and returns the FileSelectionBox. Alternatively, you can create a FileSelectionBox widget using either XmCreateFileSelectionBox() or XtVaCreateWidget() with the widget class specified as xmFileSelectionBoxWidgetClass. In this case, you could use the widget as part of a larger interface, or put it in a DialogShell yourself.

the source code demonstrates how a FileSelectionDialog can be created. This program produces the dialog shown in the figure. The intent of the program is to display a single FileSelectionDialog and print the selection that is made. We will provide a more realistic example shortly. For now, you should notice how little code is actually required to create the dialog. XtSetLanguageProc() is only available in X11R5; there is no corresponding function in X11R4. XmStringCreateLocalized() is only available in ­Motif 1.2; XmStringCreateSimple() is the cor­re­sponding function in ­Motif 1.1. XmFONTLIST_DEFAULT_TAG replaces XmSTRING_DEFAULT_CHARSET in ­Motif 1.2.

   /* show_files.c -- introduce FileSelectionDialog; print the file
    * selected by the user.
    */
   #include <Xm/FileSB.h>

   main(argc, argv)
   int argc;
   char *argv[];
   {
       Widget        toplevel, text_w, dialog;
       XtAppContext  app;
       extern void   exit(), echo_file();

       XtSetLanguageProc (NULL, NULL, NULL);

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

       /* Create a simple FileSelectionDialog -- no frills */
       dialog = XmCreateFileSelectionDialog (toplevel, "filesb", NULL, 0);
       XtAddCallback (dialog, XmNcancelCallback, exit, NULL);
       XtAddCallback (dialog, XmNokCallback, echo_file, NULL);
       XtManageChild (dialog);

       XtAppMainLoop (app);
   }

   /* callback routine when the user selects OK in the FileSelection
    * Dialog.  Just print the file name selected.
    */
   void
   echo_file(widget, client_data, call_data)
   Widget widget;  /* file selection box */
   XtPointer client_data;
   XtPointer call_data;
   {
       char *filename;
       XmFileSelectionBoxCallbackStruct *cbs =
           (XmFileSelectionBoxCallbackStruct *) call_data;

       if (!XmStringGetLtoR (cbs->value, XmFONTLIST_DEFAULT_TAG, &filename))
           return; /* must have been an internal error */

       if (!*filename) { /* nothing typed? */
           puts ("No file selected.");
           XtFree( filename); /* even "" is an allocated byte */
           return;
       }

       printf ("Filename given:
       XtFree (filename);
   }
The program simply prints the selected file when the user activates the OK button. The user can change the file by selecting an item from the files list or by typing directly in the selection text entry area. The user can also activate the dialog by double-clicking on an item in the files list. The FileSelectionDialog itself is very simple to create; most of the work in the program is done by the callback routine for the OK button.

7.5.2 Internal Widgets

A FileSelectionDialog is made up of a number of subwidgets, including Text, List, and PushButton widgets. You can get the handles to these children using the routine XmFileSelectionBoxGetChild(), which takes the following form:

   Widget
   XmFileSelectionBoxGetChild(widget, child)
       XmFileSelectionBox  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_APPLY_BUTTON
   XmDIALOG_CANCEL_BUTTON
   XmDIALOG_DEFAULT_BUTTON
   XmDIALOG_DIR_LIST
   XmDIALOG_DIR_LIST_LABEL
   XmDIALOG_FILTER_LABEL
   XmDIALOG_FILTER_TEXT
   XmDIALOG_HELP_BUTTON
   XmDIALOG_LIST
   XmDIALOG_LIST_LABEL
   XmDIALOG_OK_BUTTON
   XmDIALOG_SELECTION_LABEL
   XmDIALOG_SEPARATOR
   XmDIALOG_TEXT
   XmDIALOG_WORK_AREA
The values refer to the different widgets in a FileSelectionDialog and they should be self-explanatory, with the exception of XmDIALOG_WORK_AREA. A FileSelectionDialog can manage a work area child; this value returns the work area child. You can customize the operation of a FileSelectionDialog by adding a work area that contains other components. For a detailed discussion of this technique, see Chapter 7, Custom Dialogs.

When you use XmFileSelectionBoxGetChild(), you should not assume that the returned widget is of any particular class, so you should treat it as an opaque object as much as possible. Getting the children of a FileSelectionDialog is not necessary in most cases because the Motif toolkit provides FileSelectionDialog resources that access most of the important resources of the children. You should only get handles to the children if you need to change resources that are not involved in the file selection mechanisms.

7.5.3 Callback Routines

The XmNokCallback, XmNcancelCallback , XmNapplyCallback, XmNhelpCallback, and XmNnoMatchCallback callbacks can be specified for a FileSelectionDialog as they are for SelectionDialog. The callback routines take the usual parameters, but the callback structure passed in the call_data parameter is of type XmFileSelectionBoxCallbackStruct. The structure is declared as follows:

   typedef struct {
       int         reason;
       XEvent     *event;
       XmString    value;
       int         length;
       XmString    mask;
       int         mask_length;
       XmString    dir;
       int         dir_length;
       XmString    pattern;
       int         pattern_length;
   } XmFileSelectionBoxCallbackStruct;
The value of the reason field is an integer value that specifies the reason that the callback routine was invoked. The possible values are the same as those for a SelectionDialog:
   XmCR_OK
   XmCR_APPLY
   XmCR_CANCEL
   XmCR_HELP
   XmCR_NO_MATCH
The value field contains the item that the user selected from the files list or typed into the selection text entry area. The value corresponds to the XmNdirSpec resource and it does not necessarily have to match an item in the directories or files lists. The mask field corresponds to the XmNdirMask resource; it represents a combination of the entire pathname specification in the filter. The dir and pattern fields represent the two components that make up the mask. All of these fields are compound strings; they can be converted to character strings using XmStringGetLtoR().

7.5.4 File Searching

You can force a FileSelectionDialog to reinitialize the directory and file lists by calling XmFileSelectionDoSearch() . This routine reads the directory filter and scans the ­specified directory, which is useful if you set the mask directly. The function takes the following form:

   void
   XmFileSelectionDoSearch(widget, dirmask)
       XmFileSelectionBoxWidget  widget;
       XmString                  dirmask;
When the routine is called, the widget invokes its directory search procedure and sets the text in the filter text entry area to the dirmask parameter. Calling XmFileSelectionDoSearch() has the same effect as setting the filter and selecting the Filter button.

By default, the FileSelectionDialog searches the directory specified in the mask according to its internal searching algorithm. You can replace this file searching procedure with your own routine by specifying a callback routine for the XmNfileSearchProc resource. This resource is not a callback list, so you do not install it by calling XtAddCallback(). Since the resource is just a single procedure, you specify it as a value like you would any other resource, as shown in the following code fragment:

   extern void my_search_proc();

   XtVaSetValues (file_selection_dialog,
       XmNfileSearchProc, my_search_proc,
       NULL);
If you specify a search procedure, it is used to generate the list of filenames for the files list. A file search routine takes the following form:
   void
   (* XmSearchProc) (widget, search_data)
       Widget     widget;
       XtPointer  *search_data;
The widget parameter is the actual FileSelectionBox widget and search_data is a callback structure of type XmFileSelectionBoxCallbackStruct. This structure is just like the one used in the callback routines discussed in the previous section. Do not be concerned with the value of the reason field in this situation because none of the routines along the way use the value. The search function should scan the directory specified by the dir field of the search_data parameter. The pattern should be used to filter the files within the directory. You can get the complete filter from the mask field.

After the search procedure has determined the new list of files that it is going to use, it must set the XmNfileListItems and XmNfileListItemCount resources to store the list into the List widget used by the FileSelectionDialog. The routine must also set the XmN­listUpdated resource to True to indicate that it has indeed done something, whether or not any files are found. The function can also set the XmNdirSpec resource to reflect the full file specification in the selection text entry area, so that if the user selects the OK button, the specified file is used. Although this step is optional, we recommend doing it in case the old value is no longer valid.

To understand why it may be necessary to have your own file search procedure, consider how you would customize a FileSelectionDialog so that it only displays the writable files in an arbitrary directory. This customization might come in handy for a save operation in an electronic mail application, where the user invokes a Save action that displays a FileSelectionDialog that lists the files in which the user can save messages. Files that are not writable should not be displayed in the dialog. the source code shows an example of how a file search procedure can be used to implement this type of dialog. XtSetLanguageProc() is only available in X11R5; there is no corresponding function in X11R4. XmStringCreateLocalized() is only available in Motif 1.2; XmStringCreateSimple() is the corresponding function in Motif 1.1. XmFONTLIST_DEFAULT_TAG replaces XmSTRING_DEFAULT_CHARSET in Motif 1.2.

   /* file_sel.c -- file selection dialog displays a list of all the writable
    * files in the directory described by the XmNmask of the dialog.
    * This program demonstrates how to use the XmNfileSearchProc for
    * file selection dialog widgets.
    */
   #include <stdio.h>
   #include <Xm/Xm.h>
   #include <Xm/FileSB.h>
   #include <Xm/DialogS.h>
   #include <Xm/PushBG.h>
   #include <Xm/PushB.h>
   #include <X11/Xos.h>
   #include <sys/stat.h>

   void do_search(), new_file_cb();

   /* routine to determine if a file is accessible, a directory,
    * or writable.  Return -1 on all errors or if the file is not
    * writable.  Return 0 if it's a directory or 1 if it's a plain
    * writable file.
    */
   int
   is_writable(file)
   char *file;
   {
       struct stat s_buf;

       /* if file can't be accessed (via stat()) return. */
       if (stat (file, &s_buf) == -1)
           return -1;
       else if ((s_buf.st_mode & S_IFMT) == S_IFDIR)
           return 0; /* a directory */
       else if (!(s_buf.st_mode & S_IFREG) || access (file, W_OK) == -1)
           /* not a normal file or it is not writable */
           return -1;
       /* legitimate file */
       return 1;
   }

   /* main() -- create a FileSelectionDialog */
   main(argc, argv)
   int argc;
   char *argv[];
   {
       Widget toplevel, dialog;
       XtAppContext app;
       extern void exit();
       Arg args[5];
       int n = 0;

       XtSetLanguageProc (NULL, NULL, NULL);

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

       XtSetArg (args[n], XmNfileSearchProc, do_search); n++;
       dialog = XmCreateFileSelectionDialog (toplevel, "Files", args, n);
       XtSetSensitive (
           XmFileSelectionBoxGetChild (dialog, XmDIALOG_HELP_BUTTON), False);
       /* if user presses OK button, call new_file_cb() */
       XtAddCallback (dialog, XmNokCallback, new_file_cb, NULL);
       /* if user presses Cancel button, exit program */
       XtAddCallback (dialog, XmNcancelCallback, exit, NULL);

       XtManageChild (dialog);

       XtAppMainLoop (app);
   }

   /* a new file was selected -- check to see if it's readable and not
    * a directory.  If it's not readable, report an error.  If it's a
    * directory, scan it just as tho the user had typed it in the mask
    * Text field and selected "Search".
    */
   void
   new_file_cb(widget, client_data, call_data)
   Widget widget;
   XtPointer client_data;
   XtPointer call_data;
   {
       char *file;
       XmFileSelectionBoxCallbackStruct *cbs =
           (XmFileSelectionBoxCallbackStruct *) call_data;

       /* get the string typed in the text field in char * format */
       if (!XmStringGetLtoR (cbs->value, XmFONTLIST_DEFAULT_TAG, &file))
           return;
       if (*file != '/') {
           /* if it's not a directory, determine the full pathname
            * of the selection by concatenating it to the "dir" part
            */
           char *dir, *newfile;
           if (XmStringGetLtoR (cbs->dir, XmFONTLIST_DEFAULT_TAG, &dir)) {
               newfile = XtMalloc (strlen (dir) + 1 + strlen (file) + 1);
               sprintf (newfile, "%s/%s", dir, file);
               XtFree( file);
               XtFree (dir);
               file = newfile;
           }
       }
       switch (is_writable (file)) {
           case 1 :
               puts (file); /* or do anything you want */
               break;
           case 0 : {
               /* a directory was selected, scan it */
               XmString str = XmStringCreateLocalized (file);
               XmFileSelectionDoSearch (widget, str);
               XmStringFree (str);
               break;
           }
           case -1 :
               /* a system error on this file */
               perror (file);
       }
       XtFree (file);
   }

   /* do_search() -- scan a directory and report only those files that
    * are writable.  Here, we let the shell expand the (possible)
    * wildcards and return a directory listing by using popen().
    * A *real* application should -not- do this; it should use the
    * system's directory routines: opendir(), readdir() and closedir().
    */
   void
   do_search(widget, search_data)
   Widget widget; /* file selection box widget */
   XtPointer search_data;
   {
       char          *mask, buf[BUFSIZ], *p;
       XmString       names[256]; /* maximum of 256 files in dir */
       int            i = 0;
       FILE          *pp, *popen();
       XmFileSelectionBoxCallbackStruct *cbs =
           (XmFileSelectionBoxCallbackStruct *) search_data;

       if (!XmStringGetLtoR (cbs->mask, XmFONTLIST_DEFAULT_TAG, &mask))
           return; /* can't do anything */

       sprintf (buf, "/bin/ls %s", mask);
       XtFree (mask);
       /* let the shell read the directory and expand the filenames */
       if (!(pp = popen (buf, "r")))
           return;
       /* read output from popen() -- this will be the list of files */
       while (fgets (buf, sizeof buf, pp)) {
           if (p = index (buf, '0))
               *p = 0;
           /* only list files that are writable and not directories */
           if (is_writable (buf) == 1 &&
               (names[i] = XmStringCreateLocalized (buf)))
               i++;
       }
       pclose (pp);
       if (i) {
           XtVaSetValues (widget,
               XmNfileListItems,      names,
               XmNfileListItemCount,  i,
               XmNdirSpec,            names[0],
               XmNlistUpdated,        True,
               NULL);
           while (i > 0)
               XmStringFree (names[--i]);
       } else
           XtVaSetValues (widget,
               XmNfileListItems,      NULL,
               XmNfileListItemCount,  0,
               XmNlistUpdated,        True,
               NULL);
   }
The program simply displays a FileSelectionDialog that only lists the files that are writable by the user. The directories listed may or may not be writable. We are not testing that case here as it is handled by another routine that deals specifically with directories, which are discussed in the next section. The XmNfileSearchProc is set to do_search(), which is our own routine that creates the list of files for the files List widget. The function calls is_writable() to determine if a file is accessible and if it is a directory or a regular file that is writable.

The callback routine for the OK button is set to new_file_cb() through the XmNokCallback resource. This routine is called when a new file is selected in from the files list or new text is entered in the selection text entry area and the OK button is pressed. The specified file is evaluated using is_writable() and acted on accordingly. If it is a directory, the directory is scanned as if it had been entered in the filter text entry area. If the file cannot be read, an error message is printed. Otherwise, the file is a legitimate selection and, for demonstration purposes, the filename is printed to stdout.

Obviously, a real application would do something more appropriate in each case; errors would be reported using ErrorDialogs and legitimate values would be used by the application. An example of such a program is given in Chapter 14, Text Widgets, as file_browser.c. This program is an extension of the source code that takes a more realistic approach to using a FileSelectionDialog. Of course, the intent of that program is to show how Text widgets work, but its use of dialogs is consistent with the approach we are taking here. The FileSelectionDialog also provides a directory searching function that is analogous to the file searching function. While file searching may be necessary for some applications, it is less likely that customized directory searching will be as useful, since the default action taken by the toolkit should cover all common usages. However, since it is impossible to second-guess the requirements of all applications, Motif allows you to specify a directory searching function through the XmNdirSearchProc resource.

The procedure is used to create the list of directories. The method used by the procedure is virtually identical to the one used for files, except that the routine must set different resources. The routine must set the XmNdirListItems and XmNdirListItemCount resources to store the list of directories in the List widget. The value for XmNlistUpdated must be set just as it was for the file selection routine and XmNdirectoryValid must also be set to either True or False. If the directory cannot be read, XmNdirectoryValid is set to False to prevent the XmNfileSearchProc from being called. In this way, the file searching procedure is protected from getting invalid directories from the directory searching procedure. In order to fully customize the directory and file searching functions in a FileSelectionDialog, it is important to understand exactly how the dialog works. This material is advanced and is intended for programmers who need to write advanced file and/or directory searching routines. When the user or the application invokes a directory search, the FileSelectionDialog performs the following tasks:

Just as for the directory and file search routines, you can write your own qualify search procedure and install it as the value for the XmNqualifySearchProc resource. The routine takes the following form:
   void
   (* XmQualifyProc) (widget, input_data, output_data)
       Widget     widget;
       XtPointer  *input_data;
       XtPointer  *output_data;
The widget parameter is the actual FileSelectionBox widget; input_data and output_data are callback structures of type XmFileSelectionBoxCallbackStruct. input_data contains the directory information that needs to be qualified. The routine uses this information to fill in the output_data callback structure that is then passed to the directory and file search procedures.

The XmNfileTypeMask resource indicates the types of files for which a particular search routine should be looking. The resource can be set to one of the following values:

   XmFILE_REGULAR
   XmFILE_DIRECTORY
   XmFILE_ANY_TYPE
If you are using the same routine for both the XmNdirSearchProc and the XmNfileSearchProc, you can query this resource to determine the type of file to search for.

7.6 Summary

This chapter described the different types of selection dialogs provided by the Motif toolkit. These dialogs implement some common functionality that is needed by many different applications. This chapter builds on the material in Chapter 5, Introduction to Dialogs, which introduced the concept of dialogs and discussed the basic mechanisms that implement them. While the dialogs are designed to be used as single-entity abstractions, they can be customized to provide additional functionality as necessary. We describe how to customize the dialogs and how to create your own dialogs in Chapter 7, Custom Dialogs.


Contents Previous Next