Contents Previous Next

26 Building an Application With UIL


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.

figs.eps/V6a.25.01.eps.png
The editor_uil application

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.

26.1 Defining the User Interface

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.

26.1.1 The Main Application Window

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.

26.1.2 The Menu System

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.

26.1.3 Dialog Boxes

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.

26.2 Creating the Application

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.

26.2.1 Widget IDs

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 .

26.2.2 Callbacks

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().

26.2.3 The Error Dialog

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.

figs.eps/V6a.25.02.eps.png
The editor_uil ErrorDialog

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().

26.3 Summary

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.


Contents Previous Next