This chapter draws on many of the features of UIL in
developing a fully functional text editor application. It shows how the
various components of UIL and Mrm come together in a real application.
In this chapter, we examine a real application that
uses UIL. Although we have shown you a simple application and a number
of UIL modules in the preceding chapters, we have not put everything
together yet. This chapter provides some examples of common uses of UIL
in an application, including the definition of a menu system and some
dialogs.
Rather than start from scratch, we are going to use the simple text editor application from Chapter 14, Text Widgets . This program reads and writes text files, provides the usual cut, copy, and paste operations on the Edit menu, and performs search and replace operations from the Search menu. The interface of the editor program appears in the figure.
The UIL version of the text editor differs from the
non-UIL version in several ways. The most obvious change is that the
user interface must be described in UIL, instead of C. The program code
also needs to be modified to create the interface with Mrm. The
application callbacks need to be connected to procedures in UIL, and
some global variables need to be initialized when widgets are created.
We have also enhanced the program so that it distinguishes between
status and error messages. Status messages are still displayed in the
message area below the Text widget, while error messages are displayed
in an ErrorDialog.
Since we are going to create the user interface in
UIL, we can remove many of the Motif and Xt function calls from the
application code. We can break the interface description into three
modules, so that the modules are smaller and easier to manage. The
logical division is to describe the main application in one module, the
menu system in another, and the dialogs in a third module.
The main application window for the editor consists
of a MainWindow widget that contains a MenuBar, the text-editing area,
TextFields for entering search and replace text, and a message area.
the source code shows the UIL module that describes this interface.
! editor.uil - editor application main user interface definition module editor include file 'procedures.uih'; include file 'identifiers.uih'; object menubar : imported XmMenuBar; object main_window : XmMainWindow { controls { XmMenuBar menubar; XmForm form; }; }; object form : XmForm { controls { XmRowColumn search_panel; XmTextField text_output; XmScrolledText text_edit; }; }; list attachments : arguments { XmNtopAttachment = XmATTACH_FORM; XmNbottomAttachment = XmATTACH_FORM; XmNleftAttachment = XmATTACH_FORM; XmNrightAttachment = XmATTACH_FORM; }; object search_panel : exported XmRowColumn { controls { search_prompt : XmLabel gadget { arguments { XmNlabelString = "Search Pattern:"; }; }; search_text : XmTextField { callbacks { MrmNcreateCallback = procedure register_widget (w_search_text); }; }; replace_prompt : XmLabel gadget { arguments { XmNlabelString = " Replace Pattern:"; }; }; replace_text : XmTextField { callbacks { MrmNcreateCallback = procedure register_widget (w_replace_text); }; }; }; arguments { XmNorientation = XmHORIZONTAL; XmNpacking = XmPACK_TIGHT; arguments attachments; XmNbottomAttachment = XmATTACH_NONE; }; }; object text_edit : XmScrolledText { arguments { XmNrows = 10; XmNcolumns = 80; XmNeditMode = XmMULTI_LINE_EDIT; arguments attachments; XmNtopAttachment = XmATTACH_WIDGET; XmNtopWidget = search_panel; XmNbottomAttachment = XmATTACH_WIDGET; XmNbottomWidget = text_output; }; callbacks { MrmNcreateCallback = procedure register_widget (w_text_edit); }; }; object text_output : XmTextField { arguments { XmNeditable = false; XmNcursorPositionVisible = false; XmNshadowThickness = 0; arguments attachments; XmNtopAttachment = XmATTACH_NONE; }; callbacks { MrmNcreateCallback = procedure register_widget (w_text_output); }; }; end module;
The module begins by including two files:
procedures.uih and identifiers.uih. The procedures.uih
file contains the callback declarations for the interface. The file
also defines some arguments for the callback routines. This file is
shown in the source code
! procedures.uih - declarations of editor callbacks and their arguments procedure register_widget (any); procedure file_cb (integer); file_select_cb (integer); value FILE_OPEN : 0; FILE_SAVE : 1; FILE_EXIT : 2; procedure edit_cb (integer); value EDIT_CUT : 0; EDIT_COPY : 1; EDIT_PASTE : 2; EDIT_CLEAR : 3; procedure search_cb (integer); value SEARCH_FIND_NEXT : 0; SEARCH_SHOW_ALL : 1; SEARCH_REPLACE : 2; SEARCH_CLEAR : 3; procedure popdown_cb();The callback routines for the menu items and the FileSelectionDialog take integer arguments. The file defines the possible argument values for each of the callback routines. These values correspond to enumeration values in the application code; they indicate which action the callback should perform when it is invoked. We could write a separate procedure for each callback, but instead we use a single callback for each menu because the actions are similar. The callbacks that use these definitions are in the menubar.uil and dialogs.uil modules described later in this chapter.
The identifiers.uih file contains the
declarations for several global widget variables. These variables are
set in the UIL module by MrmNcreateCallback. This file is
listed in the source code
! identifiers.uih - declarations of application defined data identifier w_search_text; w_replace_text; w_text_edit; w_text_output;After the include directives at the top of editor.uil, the module declares the MenuBar, because it is used in this module but defined in another. You must declare a widget that is defined in another module before you can use it. The declaration of the MainWindow portion of the interface comes next. The MainWindow is at the top of the hierarchy; it manages the MenuBar and a Form. The Form is the work area. It contains the other main sections of the window, which are a RowColumn that contains the search and replace TextFields, the ScrolledText editing area, and the TextField message area.
We specify the Form attachments using the
attachments list, which specifies an attachment for each side of a
widget. In the individual widget definitions, we override the necessary
attachments to arrange the components properly. Since UIL allows
forward references, we should be able to list the three children in the
Form's controls subsection in the same order that they appear
in the user interface. However, forward references may not always work
correctly in early releases of Motif 1.2 due to an Mrm bug. To work
around this problem we need to specify the children in a particular
order. If one child is attached to another, the child that specifies
the attachment should be listed after the widget to which it is
attached. For example, in the editor.uil module, the
text_edit contains attachments to both of its siblings, so we list
it after the other two widgets in the controls subsection of
the form. The actual widget definitions can occur in any order in the
UIL module.
In addition to the Form attachments, each widget
definition contains other resource settings that are necessary for the
interface. The search_panel RowColumn contains two Labels and
two TextField widgets. Since the definitions of these widgets are
short, they are defined in the body of the search_panel
definition instead of in separate object sections. In
contrast, the size of the search_panel itself makes it too
large to place in the Form parent without making the module unreadable.
Even though the MenuBar for this application
contains only a few entries, there are still enough widgets to make it
worthwhile to define the menu system in a separate UIL module. This
technique makes sense for most applications. The general widget
hierarchy for a MenuBar is basically the same for all applications. The
top-level widget is the MenuBar; it contains a CascadeButton for each
menu. The editor application provides File, Edit, and
Search menus. A PulldownMenu is associated with each CascadeButton
and contains the individual menu entries. The definition of the menu
system for the editor program is shown in the source code
! menubar.uil - editor application main window MenuBar definitions module editor_menubar objects = { XmLabel = gadget; XmCascadeButton = gadget; XmPushButton = gadget; XmToggleButton = gadget; XmSeparator = gadget; } include file 'procedures.uih'; object menubar : exported XmMenuBar { controls { XmCascadeButton file; XmCascadeButton edit; XmCascadeButton search; }; }; object file : XmCascadeButton { controls { file_menu : XmPulldownMenu { controls { XmPushButton open; XmPushButton save; XmSeparator { }; XmPushButton exit; }; }; }; arguments { XmNlabelString = "File"; XmNmnemonic = keysym ('F'); }; }; object open : XmPushButton { arguments { XmNlabelString = "Open..."; XmNmnemonic = keysym ('O'); }; callbacks { XmNactivateCallback = procedure file_cb (FILE_OPEN); }; }; object save : XmPushButton { arguments { XmNlabelString = "Save..."; XmNmnemonic = keysym ('S'); }; callbacks { XmNactivateCallback = procedure file_cb (FILE_SAVE); }; }; object exit : XmPushButton { arguments { XmNlabelString = "Exit"; XmNmnemonic = keysym ('x'); XmNaccelerator = 'Ctrl<Key>c'; XmNacceleratorText = "Ctrl+C"; }; callbacks { XmNactivateCallback = procedure file_cb (FILE_EXIT); }; }; object edit : XmCascadeButton { controls { edit_menu : XmPulldownMenu { controls { XmPushButton cut; XmPushButton copy; XmPushButton paste; XmSeparator { }; XmPushButton eclear; }; }; }; arguments { XmNlabelString = "Edit"; XmNmnemonic = keysym ('E'); }; }; object cut : XmPushButton { arguments { XmNlabelString = "Cut"; XmNmnemonic = keysym ('t'); XmNaccelerator = 'Shift<Key>Delete'; XmNacceleratorText = "Shift+Del"; }; callbacks { XmNactivateCallback = procedure edit_cb (EDIT_CUT); }; }; object copy : XmPushButton { arguments { XmNlabelString = "Copy"; XmNmnemonic = keysym ('C'); XmNaccelerator = 'Ctrl<Key>Insert'; XmNacceleratorText = "Ctrl+Ins"; }; callbacks { XmNactivateCallback = procedure edit_cb (EDIT_COPY); }; }; object paste : XmPushButton { arguments { XmNlabelString = "Paste"; XmNmnemonic = keysym ('P'); XmNaccelerator = 'Shift<Key>Insert'; XmNacceleratorText = "Shift+Ins"; }; callbacks { XmNactivateCallback = procedure edit_cb (EDIT_PASTE); }; }; object eclear : XmPushButton { arguments { XmNlabelString = "Clear"; XmNmnemonic = keysym ('l'); }; callbacks { XmNactivateCallback = procedure edit_cb (EDIT_CLEAR); }; }; object search : XmCascadeButton { controls { search_menu : XmPulldownMenu { controls { XmPushButton find_next; XmPushButton show_all; XmPushButton replace; XmSeparator { }; XmPushButton sclear; }; }; }; arguments { XmNlabelString = "Search"; XmNmnemonic = keysym ('S'); }; }; object find_next : XmPushButton { arguments { XmNlabelString = "Find Next"; XmNmnemonic = keysym ('N'); XmNaccelerator = 'Ctrl<Key>N'; XmNacceleratorText = "Ctrl+N"; }; callbacks { XmNactivateCallback = procedure search_cb (SEARCH_FIND_NEXT); }; }; object show_all : XmPushButton { arguments { XmNlabelString = "Show All"; XmNmnemonic = keysym ('A'); XmNaccelerator = 'Ctrl<Key>A'; XmNacceleratorText = "Ctrl+A"; }; callbacks { XmNactivateCallback = procedure search_cb (SEARCH_SHOW_ALL); }; }; object replace : XmPushButton { arguments { XmNlabelString = "Replace Text"; XmNmnemonic = keysym ('R'); }; callbacks { XmNactivateCallback = procedure search_cb (SEARCH_REPLACE); }; }; object sclear : XmPushButton { arguments { XmNlabelString = "Clear"; XmNmnemonic = keysym ('C'); }; callbacks { XmNactivateCallback = procedure search_cb (SEARCH_CLEAR); }; }; end module;We set the objects option at the beginning of this module so that all of the menu entries are created as gadgets. Setting this option at the top of the module saves you from accidentally forgetting to use the gadget version of one of the buttons in a widget definition. Even though our menus do not include Labels or ToggleButtons, these objects are included in the option setting in case we decide to add some later.
Once again, the widget definitions in this module
are organized in a top-down manner. The first widget definition is the
MenuBar, which contains a CascadeButton for each menu. In UIL, you
declare the PulldownMenu associated with a CascadeButton as a child of
the button, instead of as a child of the MenuBar, like in C code. The
UIL method is more intuitive. At run-time, Mrm creates each menu as a
child of the MenuBar and sets the XmNsubMenuId resource of the
appropriate CascadeButton to satisfy the Motif requirements.
For each menu, we define PulldownMenu in-line, since
it only contains a list of child widgets. This convention makes the
definitions easier to read and modify. We define the buttons
separately, however, since the definitions are longer, and they would
be too large in-line. Since there are no resource settings for the
Separators, we define these components in-line and do not name them, as
it is unlikely that users will specify resources for them.
There is a single callback routine associated with
each PulldownMenu, so the callback resource for each menu item is set
to the appropriate routine. The action taken by the callback procedure
when it is invoked is determined by the argument passed to the
callback. The possible arguments are defined in procedures.uih
along with the callback procedures. The arguments correspond to
enumeration values defined in the application source code.
The editor_uil interface uses some predefined
Motif dialogs that are defined in the dialogs.uil module. Unlike
the MenuBar definition, these dialogs are not imported by the main
editor.uil module. Instead, the dialogs are fetched by the
application when they are needed. The application uses two
FileSelectionDialogs, one for opening files and one for saving files.
It also uses an ErrorDialog for displaying error messages. The
definitions of these widgets are shown in the source code
! dialogs.uil - editor application dialog definitions module editor_dialogs include file 'procedures.uih'; object open_dialog : XmFileSelectionDialog { arguments { XmNdialogTitle = "Open File"; XmNokLabelString = "Open"; }; callbacks { XmNcancelCallback = procedure popdown_cb(); XmNokCallback = procedure file_select_cb (FILE_OPEN); }; }; object save_dialog : XmFileSelectionDialog { arguments { XmNdialogTitle = "Save File"; XmNokLabelString = "Save"; }; callbacks { XmNcancelCallback = procedure popdown_cb(); XmNokCallback = procedure file_select_cb (FILE_SAVE); }; }; object error_dialog : XmErrorDialog { controls { Xm_Cancel unmanaged { }; Xm_Help unmanaged { }; }; arguments { XmNdialogTitle = "Error"; XmNdialogStyle = XmDIALOG_FULL_APPLICATION_MODAL; }; }; end module;Each FileSelectionDialog has the same form. The titles and the labels and callbacks for the OK buttons are set for the different purposes of each dialog. Both dialogs use the same Cancel callback, file_select_cb(). This routine uses the same arguments as the file_cb() callback.
The definition of the ErrorDialog is quite simple,
since specifying an ErrorDialog causes most of the necessary MessageBox
resources to be set appropriately. We make the dialog modal, so the
user is forced to acknowledge an error before continuing, and we
unmanage the Cancel and Help buttons so the user can only acknowledge
an error message. The dialog needs a few other changes, but they cannot
be made in UIL. The XmNmessageString must be set each time an
error is displayed. This change is handled in the application code,
which we explain in the next section.
The original editor.c program needs several
changes before it can work with the UIL user interface we have defined.
Like any application that uses UIL, the widget creation is now handled
by Mrm. The callbacks also need a few minor changes that are related to
the use of Mrm. We have added a new callback that lets the application
obtain the widget IDs of Mrm-created widgets. The new version of the
application is shown in the source code Compared to the original
version, the editor_uil.c program is about 50 lines shorter.
Most of the shrinkage comes from main(), in which the Motif
widget creation calls are replaced by Mrm calls.
/* editor_uil.c -- create a full-blown Motif editor application complete * with a menubar, facilities to read and write files, text search * and replace, clipboard support and so forth. */ #include <Mrm/MrmAppl.h> #include <Xm/Text.h> #include <Xm/MessageB.h> #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> MrmHierarchy hierarchy; Cardinal status; MrmType class_code; static char buf[256]; static String uid_files[] = { "editor", "menubar", "dialogs" }; XtAppContext app_context; Widget toplevel, text_edit, search_text, replace_text, text_output; static MrmRegisterArg widgets_list[] = { { "w_text_edit", (XtPointer) &text_edit }, { "w_search_text", (XtPointer) &search_text }, { "w_replace_text", (XtPointer) &replace_text }, { "w_text_output", (XtPointer) &text_output }, }; void register_widget(), file_cb(), edit_cb(), search_cb(), file_select_cb(); void popdown_cb(); /* These definitions depend on the order of the menu entries and are also defined in the procedures.uih file with the callback decls. */ typedef enum { FILE_OPEN, FILE_SAVE, FILE_EXIT } FileOp; typedef enum { EDIT_CUT, EDIT_COPY, EDIT_PASTE, EDIT_CLEAR } EditOp; typedef enum { SEARCH_FIND_NEXT, SEARCH_SHOW_ALL, SEARCH_REPLACE, SEARCH_CLEAR } SearchOp; static MrmRegisterArg callbacks_list[] = { { "register_widget", (XtPointer) register_widget }, { "file_cb", (XtPointer) file_cb }, { "edit_cb", (XtPointer) edit_cb }, { "search_cb", (XtPointer) search_cb }, { "file_select_cb", (XtPointer) file_select_cb }, { "popdown_cb", (XtPointer) popdown_cb }, }; main(argc, argv) int argc; char *argv[]; { Widget main_window; XtSetLanguageProc (NULL, NULL, NULL); MrmInitialize(); toplevel = XtVaAppInitialize (&app_context, "Demos", NULL, 0, &argc, argv, NULL, NULL); status = MrmOpenHierarchyPerDisplay (XtDisplay(toplevel), XtNumber(uid_files), uid_files, NULL, &hierarchy); if (status != MrmSUCCESS) { XtAppError (app_context, "MrmOpenHierarchyPerDisplay failed"); exit (1); } MrmRegisterNames (widgets_list, XtNumber (widgets_list)); MrmRegisterNames (callbacks_list, XtNumber (callbacks_list)); status = MrmFetchWidget (hierarchy, "main_window", toplevel, &main_window, &class_code); if (status != MrmSUCCESS) { XtAppError (app_context, "MrmFetchWidget failed"); exit (1); } XtManageChild (main_window); XtRealizeWidget (toplevel); XtAppMainLoop (app_context); } /* routine to display an error dialog */ void show_error (message) char *message; { static Widget dialog; XmString s; if (dialog == NULL) { MrmFetchWidget (hierarchy, "error_dialog", toplevel, &dialog, &class_code); if (dialog == NULL || ! XmIsMessageBox (dialog)) { XtAppError (app_context, "Creation of error dialog failed."); exit (1); } } s = XmStringCreateLocalized (message); XtVaSetValues (dialog, XmNmessageString, s, NULL); XmStringFree (s); XtManageChild (dialog); } /* callback routine for "OK" button in FileSelectionDialogs */ void file_select_cb (dialog, client_data, call_data) Widget dialog; XtPointer client_data; XtPointer call_data; { char *filename, *text; struct stat statb; long len; FILE *fp; FileOp reason = *((FileOp *) client_data); XmFileSelectionBoxCallbackStruct *cbs = (XmFileSelectionBoxCallbackStruct *) call_data; XmTextSetString (text_output, NULL); /* clear the message area */ if (!XmStringGetLtoR (cbs->value, XmFONTLIST_DEFAULT_TAG, &filename)) return; /* must have been an internal error */ if (*filename == NULL) { XtFree (filename); XBell (XtDisplay (text_edit), 50); XmTextSetString (text_output, "Choose a file."); return; /* nothing typed */ } if (reason == FILE_SAVE) { long bytes_written; if (!(fp = fopen (filename, "w"))) { perror (filename); sprintf (buf, "Can't save to %s.", filename); show_error (buf); XtFree (filename); return; } /* saving -- get text from Text widget... */ text = XmTextGetString (text_edit); len = XmTextGetLastPosition (text_edit); /* write it to file (check for error) */ bytes_written = fwrite (text, sizeof (char), len, fp); if (bytes_written != len) { strcpy (buf, "Warning: did not write entire file!"); show_error (buf); } else { /* make sure a newline terminates file */ if (text[len-1] != '0) fputc ('0, fp); sprintf (buf, "Saved %ld bytes to %s.", len, filename); XmTextSetString (text_output, buf); } } else { /* reason == FILE_OPEN */ /* make sure the file is a regular text file and open it */ if (stat (filename, &statb) == -1 || (statb.st_mode & S_IFMT) != S_IFREG || !(fp = fopen (filename, "r"))) { perror (filename); sprintf (buf, "Can't read %s.", filename); show_error (buf); XtFree (filename); return; } /* put the contents of the file in the Text widget by * allocating enough space for the entire file, reading the * file into the space, and using XmTextSetString() to show * the file. */ len = statb.st_size; if (!(text = XtMalloc ((unsigned)(len+1)))) { /* +1 for NULL */ sprintf (buf, "%s: XtMalloc(%ld) failed.", len, filename); show_error (buf); } else { long bytes_read = fread (text, sizeof(char), len, fp); if (bytes_read != len) { sprintf (buf, "Did not read entire file!"); show_error (buf); } sprintf (buf, "Loaded %ld bytes from %s.", bytes_read, filename); XmTextSetString (text_output, buf); text[len] = 0; /* NULL-terminate */ XmTextSetString (text_edit, text); } } /* free all allocated space. */ XtFree (text); XtFree (filename); fclose (fp); XtUnmanageChild (dialog); } /* a menu item from the "File" pulldown menu was selected */ void file_cb (w, client_data, call_data) Widget w; XtPointer client_data; XtPointer call_data; { static Widget open_dialog, save_dialog; FileOp reason = *((FileOp *) client_data); if (reason == FILE_EXIT) { MrmCloseHierarchy (hierarchy); exit (0); } XmTextSetString (text_output, NULL); /* clear the message area */ if (reason == FILE_OPEN) { if (open_dialog == NULL) MrmFetchWidget (hierarchy, "open_dialog", toplevel, &open_dialog, &class_code); if (open_dialog) XtManageChild (open_dialog); else show_error ("Creation of the open dialog failed."); } else { /* reason == FILE_SAVE */ if (save_dialog == NULL) MrmFetchWidget (hierarchy, "save_dialog", toplevel, &save_dialog, &class_code); if (save_dialog) XtManageChild (save_dialog); else show_error ("Creation of the save dialog failed."); } } /* a menu item from the "Search" pulldown menu was selected */ void search_cb (w, client_data, call_data) Widget w; XtPointer client_data; XtPointer call_data; { char *search_pat, *p, *string, *new_pat; XmTextPosition pos = 0; int len, nfound = 0; int search_len, pattern_len; SearchOp reason = *((SearchOp *) client_data); Boolean found = False; XmTextSetString (text_output, NULL); /* clear the message area */ if (reason == SEARCH_CLEAR) { pos = XmTextGetLastPosition (text_edit); XmTextSetHighlight (text_edit, 0, pos, XmHIGHLIGHT_NORMAL); return; } if (!(string = XmTextGetString (text_edit)) || !*string) { show_error ("No text to search."); return; } if (!(search_pat = XmTextGetString (search_text)) || !*search_pat) { XmTextSetString (text_output, "Specify a search pattern."); XtFree (string); return; } new_pat = XmTextGetString (replace_text); search_len = strlen (search_pat); pattern_len = strlen (new_pat); if (reason == SEARCH_FIND_NEXT) { pos = XmTextGetCursorPosition (text_edit) + 1; found = XmTextFindString (text_edit, pos, search_pat, XmTEXT_FORWARD, &pos); if (!found) found = XmTextFindString (text_edit, 0, search_pat, XmTEXT_FORWARD, &pos); if (found) nfound++; } else { /* reason == SHOW_ALL || reason == SEARCH_REPLACE */ do { found = XmTextFindString (text_edit, pos, search_pat, XmTEXT_FORWARD, &pos); if (found) { nfound++; if (reason == SEARCH_SHOW_ALL) XmTextSetHighlight (text_edit, pos, pos + search_len, XmHIGHLIGHT_SELECTED); else XmTextReplace (text_edit, pos, pos + search_len, new_pat); pos++; } } while (found); } if (nfound == 0) { XmTextSetString (text_output, "Pattern not found"); } else { switch (reason) { case SEARCH_FIND_NEXT : sprintf (buf, "Pattern found at position %ld.", pos); XmTextSetInsertionPosition (text_edit, pos); break; case SEARCH_SHOW_ALL : sprintf (buf, "Found %d occurrences.", nfound); break; case SEARCH_REPLACE : sprintf (buf, "Made %d replacements.", nfound); } XmTextSetString (text_output, buf); } XtFree (string); XtFree (search_pat); XtFree (new_pat); } /* the callback routine for the items in the edit menu */ void edit_cb (widget, client_data, call_data) Widget widget; XtPointer client_data; XtPointer call_data; { Boolean result = True; EditOp reason = *((EditOp *) client_data); XEvent *event = ((XmPushButtonCallbackStruct *) call_data)->event; Time when; XmTextSetString (text_output, NULL); /* clear the message area */ if (event != NULL && reason == EDIT_CUT || reason == EDIT_COPY || reason == EDIT_CLEAR) { switch (event->type) { case ButtonRelease : when = event->xbutton.time; break; case KeyRelease : when = event->xkey.time; break; default: when = CurrentTime; break; } } switch (reason) { case EDIT_CUT : result = XmTextCut (text_edit, when); break; case EDIT_COPY : result = XmTextCopy (text_edit, when); break; case EDIT_PASTE : result = XmTextPaste (text_edit); case EDIT_CLEAR : XmTextClearSelection (text_edit, when); break; } if (result == False) XmTextSetString (text_output, "There is no selection."); } void popdown_cb (w, client_data, call_data) Widget w; XtPointer client_data; XtPointer call_data; { XtUnmanageChild (w); } void register_widget (w, client_data, call_data) Widget w; XtPointer client_data; XtPointer call_data; { Widget *w_ptr = (Widget *) client_data; *w_ptr = w; }In order to create the interface that we defined in UIL, the application must use Mrm. Like any other application that uses Mrm, this application calls MrmInitialize(), MrmOpenHierarchyPerDisplay(), and MrmFetchWidget(). It also registers its callbacks and some global widget variables with MrmRegisterNames(). While it is possible to register both types of data with one call, we use two separate calls to distinguish between the two types of data. We create the main window of the application in main() and leave the creation of the dialogs until they are needed.
When you create an interface by calling the Motif
creation functions directly, you get the ID of each widget that you
create. With Mrm, many widgets are created with a single call, and all
you get back is the ID of the widget at the top of the hierarchy. In
most cases, application programs need the IDs of other widgets in the
hierarchy; the editor_uil program is no exception. It is easy to
get the necessary widget IDs by using an MrmNcreateCallback.
The register_widget() routine is used as the creation callback
for all of the widgets for which we need an ID.
The register_widget() callback takes a
pointer to a widget as its client_data. The routine assigns
the ID of the widget that was just created to this pointer. The
widget_list array declared at the beginning of the program
specifies a list of UIL identifiers and global widget pointers. The
application registers these identifiers with Mrm, so they are available
in the UIL modules for use as callback arguments. Each identifier is
prefixed with w_, so that the name does not conflict with the
actual widget name defined in the UIL module. The identifiers are
declared in identifiers.uih, which is included by editor.uil
.
The editor_uil application registers its
callbacks with Mrm by calling MrmRegisterNames() with the
callbacks_list array. As with identifiers, you must declare any
exported callbacks that you use in a UIL module. We use the convention
of placing these declarations in a file named procedures.uih,
which is included by all of our UIL modules.
The application uses a single callback for each
PulldownMenu. The client_data argument to each callback
specifies the action that the callback should perform. The
procedures.uih file contains the callback declarations, as well as
value definitions for the callback arguments. These values are defined
with the same values used in the application, so if any changes are
made to the values in the application program, the UIL definitions must
be updated as well.
Most of the callbacks in our application are the
same as in the original. Only the file_cb() callback has been
changed; it now includes code that creates part of the user interface,
namely the FileSelectionDialogs. The creation routines are replaced by
calls to MrmFetchWidget(). The application creates each dialog
the first time it is needed and keeps the widget pointer in a static
variable so the dialog can be reused. This type of delayed widget
creation can make a program start up faster and it can save memory. In
order to allow delayed widget creation, the application does not close
the MrmHierarchy before calling XtAppMainLoop(). The
hierarchy is closed when the user exits the program, which is an action
that is also handled by file_cb().
Originally, the TextField message area at the bottom of the main application window displayed both status and error messages. However, we believe making error messages as explicit as possible is a good idea. As an enhancement to the original editor program, we have added an ErrorDialog to display error messages. The show_error() routine creates and displays the ErrorDialog shown in the figure.
The show_error() routine creates the
ErrorDialog with a call to MrmFetchWidget() the first time an
error occurs. The standard Motif ErrorDialog includes three
PushButtons: OK, Cancel, and Help. Since the OK
button is sufficient for our purposes, the routine unmanages the other
two PushButtons after fetching the dialog. Unfortunately, you cannot
unmanage automatically-created children directly in UIL, which means
that the program must handle this step.
The program also updates the XmNmessageString
of the ErrorDialog each time it is used to display an error message.
The error strings are hard-coded into this application. You can make
your programs more open to internationalization if you place the
strings in a UIL module. Then you can easily set string resources by
replacing calls to XtVaSetValues() with calls to
MrmFetchSetValues().
Creating an application that uses UIL and Mrm is
very similar to creating an application that just uses Motif. The main
difference is that the user interface of the application is defined in
one or more UIL modules, instead of being created in application code
by Motif and Xt creation procedures. Using UIL tends to simplify the
application code by separating the interface from the code itself. In
some situations, provisions must be made to pass widget IDs from a UIL
module to the application, so that the application can modify a widget
dynamically while it is running. While using UIL certainly affects the
creation of a user interface, application callbacks and other internal
operations remain much the same.