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.
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_SELECTIONThese 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.
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.
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.
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_MATCHThe 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.)
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_SEPARATORThe 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.
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.
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.
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_AREAThe 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.
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.
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.
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
corresponding 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.
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_AREAThe 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.
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_MATCHThe 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().
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 XmNlistUpdated 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:
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_TYPEIf 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.
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.