Introduction to Motif Application Development

Part 6: Working with menu bars and dialog boxes

by Marshall Brain , brain@iftech.com
Interface Technologies, Inc.
(800) 224-4965
© Copyright 1994 by Marshall Brain. All rights reserved.
Version 2.01, 6/27/95

These tutorials are excerpted from the book "Motif Programming: The Essentials and More" , by Marshall Brain.


Most real applications use a menu bar and dialog boxes to get requested actions and information from the user. These, combined with pushbuttons,are the mainstays of a "point and click" user interface. This tutorial will show you how to build simple menu bars and how to use standard, Motif-defined dialog boxes.

Menu Bars

A simple menu bar is quite easy to create in Motif, although the coding and concepts are a little unusual the first time you see them. The following code demonstrates the general idea by creating a menu bar in a form widget. In this code, a menu bar is created so that as each menu item is selected, a message is sent to stdout.

Generally, if you were building an application containing a menu bar, the bottom part of the form widget would be filled with the other widgets of the user interface. Here this is represented by a simple label widget. Get this code running and see what it does before you try to decode what it is doing:

 #include <Xm/Xm.h>  #include <Xm/Label.h>   #include <Xm/Form.h>  #include <Xm/PushB.h> #include <Xm/RowColumn.h>  #include <Xm/CascadeB.h>    XtAppContext context; XmStringCharSet char_set=XmSTRING_DEFAULT_CHARSET;   Widget toplevel, form, label, menu_bar; Widget file_menu; Widget open_item; Widget close_item; Widget quit_item; Widget edit_menu; Widget copy_item; Widget cut_item; Widget paste_item;   void menuCB(w,client_data,call_data)     Widget w;     char *client_data;     XmAnyCallbackStruct *call_data; /* callback routine used for all menus */ {     printf("%s\n",client_data);     if (strcmp(client_data,"Quit")==0) /* if quit seen, then exit */         exit(0); }    Widget make_menu_item(item_name,client_data,menu)     char *item_name;     caddr_t client_data;     Widget menu; /* adds an item into a menu. */  {     int ac;     Arg al[10];     Widget item;       ac = 0;     XtSetArg(al[ac],XmNlabelString,         XmStringCreateLtoR(item_name,char_set)); ac++;       item=XmCreatePushButton(menu,item_name,al,ac);     XtManageChild(item);     XtAddCallback(item,XmNactivateCallback,menuCB,client_data);       XtSetSensitive(item,True);     return(item); }   Widget make_menu(menu_name,menu_bar)     char *menu_name;      Widget menu_bar; /* creates a menu on the menu bar */ {     int ac;     Arg al[10];     Widget menu, cascade;       menu=XmCreatePulldownMenu(menu_bar,menu_name,NULL,0);     ac=0;     XtSetArg (al[ac],XmNsubMenuId, menu); ac++;     XtSetArg(al[ac],XmNlabelString,         XmStringCreateLtoR(menu_name,char_set)); ac++;     cascade=XmCreateCascadeButton(menu_bar,menu_name,al,ac);       XtManageChild(cascade);      return(menu); }   void create_menus(menu_bar)     Widget menu_bar; /* creates all the menus for this program */ {     /* create the file menu */     file_menu=make_menu("File",menu_bar);     open_item=make_menu_item("Open","Open selected",file_menu);     close_item=make_menu_item("Close","Close selected",file_menu);     quit_item=make_menu_item("Quit","Quit",file_menu);       /* create the edit menu */     edit_menu=make_menu("Edit",menu_bar);     copy_item=make_menu_item("Copy","Copy selected",edit_menu);     cut_item=make_menu_item("Cut","Cut Selected",edit_menu);     paste_item=make_menu_item("Paste","Paste Selected",edit_menu); }   void main(argc,argv)     int argc;      char *argv[]; {     Arg al[10];     int ac;       /* create the toplevel shell */     toplevel = XtAppInitialize(&context,"",NULL,0,       &argc,argv,NULL,NULL,0);       /* resize the window */     ac=0;     XtSetArg(al[ac],XmNheight,200); ac++;     XtSetArg(al[ac],XmNwidth,200); ac++;     XtSetValues(toplevel,al,ac);       /* create a form widget */     ac=0;     form=XmCreateForm(toplevel,"form",al,ac);     XtManageChild(form);       /* create a label widget */     ac=0;     XtSetArg(al[ac],XmNlabelString,         XmStringCreate("I'm a label", char_set)); ac++;     label=XmCreateLabel(form,"label",al,ac);     XtManageChild(label);       /* create the menu bar */     ac=0;     menu_bar=XmCreateMenuBar(form,"menu_bar",al,ac);     XtManageChild(menu_bar);       /* attach the menu bar to the form */     ac=0;     XtSetArg(al[ac],XmNtopAttachment,XmATTACH_FORM); ac++;     XtSetArg(al[ac],XmNrightAttachment,XmATTACH_FORM); ac++;     XtSetArg(al[ac],XmNleftAttachment,XmATTACH_FORM); ac++;     XtSetValues(menu_bar,al,ac);       /* attach the label to the form */     ac=0;     XtSetArg(al[ac],XmNtopAttachment,XmATTACH_WIDGET); ac++;     XtSetArg(al[ac],XmNtopWidget,menu_bar); ac++;     XtSetArg(al[ac],XmNrightAttachment,XmATTACH_FORM); ac++;     XtSetArg(al[ac],XmNleftAttachment,XmATTACH_FORM); ac++;     XtSetArg(al[ac],XmNbottomAttachment,XmATTACH_FORM); ac++;     XtSetValues(label,al,ac);       create_menus(menu_bar);       XtRealizeWidget(toplevel);     XtAppMainLoop(context); } 

You can see in this code that a menu bar capability is provided by a Motif through the XmCreateMenuBar function. The menu bar is appropriately attached to the form. Once the menu bar has been created and attached, the create_menus function creates the menus and the menu items using two functions named make_menu and make_menu_item.

On the menu bar are placed "cascade button" widgets, which are created in make_menu. Cascade buttons are another form of button widget specifically designed for the creation of menus. Each cascade button holds a pointer to a menu pane, and the behavior of the cascade button is to manage the menu pane when the user clicks the cascade button. The menu pane then holds several standard pushbuttons that act as the menu choices. These pushbuttons are created in make_menu_item and use normal callbacks whenever they are selected. Also notice that in this example the "sensitivity" of all buttons is set to true, but they can also be set to false to prevent selection of that option by the user. When a button is insensitive, it is "greyed out" in the menu.

You will notice that several "convenience functions" are used here. For example, XmCreateMenuBar creates a menu bar for you. It is called a convenience function because this is a much easier way to create a menu bar than setting it up yourself. The convenience function does not manage the widget however, so this must be done separately with the call to XtManageChild. We will see more of this in the discussion of dialog boxes.

You will also notice the "Note" comment right before the menu options are created. In many simple applications, like this one, there is no need to turn the sensitivity of menu items on and off, or to change menu labels. But if there is such a need, then you should take the result of the call to make_menu_item and place it in a valiable declared as a Widget. This variable can then be used later to change the characteristics of the button in the menu. For example, you might have a button named "Open" on a menu that you wish to make insensitive while a file is open (the user uses the menu option to open the file, but needs to be prevented from opening more than one file at once). You might say:

 	open_button=make_menu_option("Open File",OPEN_OPTION,menu); 

and then later say:

 	XtSetSensitive(open_button,False); 

A menu can contain separators and labels along with pushbuttons. These extra widgets cannot be selected. They are created and added to the menu in the same way that a pushbutton menu item is by the make_menu_option code.

To create your own menus, simply follow the code presented here and change the create_menus function to implement the menu structure that you desire.

Message Dialog boxes

A programmer frequently wants to ask the user questions for which a yes/no response is appropriate. Motif provides a dialog box (called a message dialog) that makes this easy to do. The following code demonstrates the process. When this code is executed, a pushbutton appears in the application window. When it is pushed, the dialog box appears. The result of the user's interaction with the dialog box is shown in stdout.

 #include <Xm/Xm.h>       #include <Xm/PushB.h>  #include <Xm/MessageB.h>   #define OK        1      #define CANCEL    2   XtAppContext context; XmStringCharSet char_set=XmSTRING_DEFAULT_CHARSET;   Widget toplevel, button, dialog;   void dialogCB(w,client_data,call_data)     Widget w;     int client_data;     XmAnyCallbackStruct *call_data; /* callback function for the dialog box. */ {     switch (client_data)     {         case OK:                          printf("OK selected\n");             break;         case CANCEL:             printf("CANCEL selected\n");             break;     }     /* make the dialog box invisible */      XtUnmanageChild(w); }   void buttonCB(w,client_data,call_data)     Widget w;     XtPointer client_data;     XmPushButtonCallbackStruct *call_data; /* callback function for the pushbutton */ {     /* make the dialog box visible */     XtManageChild(dialog); }   void main(argc,argv)     int argc;      char *argv[]; {     Arg al[10];     int ac;       /* create the toplevel shell */     toplevel = XtAppInitialize(&context,"",NULL,0,       &argc,argv,NULL,NULL,0);       /* create and manage pushbutton */     ac=0;     XtSetArg(al[ac],XmNlabelString,         XmStringCreate("Push Me",char_set)); ac++;           button=XmCreatePushButton(toplevel,"button",al,ac);     XtManageChild(button);     XtAddCallback (button, XmNactivateCallback, buttonCB, NULL);       /* create (but DO NOT manage) the message dialog */     ac=0;     XtSetArg(al[ac], XmNmessageString,         XmStringCreateLtoR("Is everything OK?",char_set)); ac++;     dialog=XmCreateMessageDialog(toplevel,"dialog",al,ac);     XtAddCallback(dialog,XmNokCallback,dialogCB,OK);     XtAddCallback(dialog,XmNcancelCallback,dialogCB,CANCEL);     XtUnmanageChild(XmMessageBoxGetChild(dialog,XmDIALOG_HELP_BUTTON));       XtRealizeWidget(toplevel);     XtAppMainLoop(context); } 

The code starts by creating a push button. It then creates a message box using a convenience function (note that it is NOT managed, just created). The message box's OK and Cancel callbacks are directed into the dialogCB function, and constants are passed through the client_data parameter so that the procedure can distinguish which button was pressed. Notice that the dialog box has a third button (the help button) which is disabled (and also made invisible) by a call to unmanage it. If you want to provide help, add a callback for the button instead of unmanaging it. Or you can change the name displayed on the help button and use it for something else. To do this you would change the helpLabelString resource and then add a helpCallback. To make a message dialog that has only an OK button, unmanage the cancel button as well.

When the pushbutton is pushed, the buttonCB procedure is called. This procedure "manages" the messageDialog. It is the act of managing the dialog that causes it to appear on the screen (when it is unmanaged, it will dissappear). Once the user has made a selection it is unmanaged to make it go away again. It continues to exist, but it must be managed to make it visible and active again.

Prompt Dialog

Motif also supports a dialog box for getting strings from the user. It is called a prompt dialog. It's use is almost identical to that of a message dialog (including OK, Cancel and Help buttons that are accessed the same way), but you have to jump through a hoop to get the string out once the dialog's OK callback is activated. Here's some example code:

 #include <Xm/Xm.h>  #include <Xm/PushB.h>  #include <Xm/SelectioB.h>  /* a prompt dialog is made from a stripped-down selection box. */   #define    OK        1 #define    CANCEL    2   XtAppContext context; XmStringCharSet char_set = XmSTRING_DEFAULT_CHARSET;   Widget toplevel, button, dialog;   void dialogCB(w,client_data,call_data)     Widget w;     int client_data;     XmSelectionBoxCallbackStruct *call_data; /* callback function for the dialog box */ {     char *s;       switch (client_data)     {                  case OK:             /* get the string from the event structure. */             XmStringGetLtoR(call_data->value,char_set,&s);             printf("string='%s'\n",s);             XtFree(s);             break;                  case CANCEL:             printf("CANCEL selected\n");             break;     }     /* make the dialog box invisible */     XtUnmanageChild(w); }   void buttonCB(w,client_data,call_data)     Widget w;     XtPointer client_data;     XmPushButtonCallbackStruct *call_data; /* callback function for the dialog box */ {     /* make the dialog box visible */     XtManageChild(dialog); }   void main(argc,argv)     int argc;      char *argv[]; {     Arg al[10];     int ac;       /* create the toplevel shell */     toplevel = XtAppInitialize(&context,"",NULL,0,       &argc,argv,NULL,NULL,0);       /* create the dialog box. */     ac=0;     XtSetArg(al[ac], XmNselectionLabelString,          XmStringCreateLtoR("Type in a string. ",char_set)); ac++;     dialog = XmCreatePromptDialog(toplevel,"dialog",al,ac);     XtAddCallback(dialog,XmNokCallback,dialogCB,OK);     XtAddCallback(dialog,XmNcancelCallback,dialogCB,CANCEL);     XtUnmanageChild(XmSelectionBoxGetChild(dialog,         XmDIALOG_HELP_BUTTON));       /* create the pushbutton */     ac=0;            XtSetArg(al[ac],XmNlabelString,         XmStringCreate("Push Me",char_set)); ac++;     button=XmCreatePushButton(toplevel,"label",al,ac);     XtManageChild(button);     XtAddCallback (button, XmNactivateCallback, buttonCB, NULL);       XtRealizeWidget(toplevel);     XtAppMainLoop(context); } 

The code in the "Case OK:" for the dialogCB routine extracts the string. It gets it from the event record generated for the dialog, passed in call_data. Besides this special string-extraction code, and the use of a different convenience function to create a different dialog box, the rest of the program is identical to the code used to demonstrate the message dialog.

Selection Box Dialogs

Motif provides a dialog box that can be used to let the user select an item from a list of possible items. This is called a selection box. The use of a selection box is nearly identical to the use of a prompt dialog, except that some extra code is needed to set up the list of possible items for the user to choose from. The following code demonstrates the process:

 #include <Xm/Xm.h>  #include <Xm/PushB.h>  #include <Xm/SelectioB.h>  #include <Xm/List.h>    #define OK        1 #define CANCEL    2   XtAppContext context; XmStringCharSet char_set=XmSTRING_DEFAULT_CHARSET;   Widget toplevel, button, dialog; char *animals[]={"cat","dog","cow","goat","horse","mouse","pig",                  "sheep","rat","donkey","elephant","squirrel"};   void dialogCB(w,client_data,call_data)     Widget w;     int client_data;     XmSelectionBoxCallbackStruct *call_data; /* callback function for the dialog box. */ {     char *s;       switch (client_data)     {         case OK:             /* get the string from the event structure. */             XmStringGetLtoR(call_data->value,char_set,&s);             printf("string='%s'\n",s);             XtFree(s);             break;         case CANCEL:             printf("CANCEL selected\n");             break;     }     XtUnmanageChild(w); }   void buttonCB(w,client_data,call_data)     Widget w;     XtPointer client_data;     XmPushButtonCallbackStruct *call_data; /* callback function for the pushbutton */ {     Arg al[10];     int ac;     Widget list;     int list_cnt;     XmString s;       /* Add items to selection box list. */     list=XmSelectionBoxGetChild(dialog, XmDIALOG_LIST);     XmListDeleteAllItems(list);     for (list_cnt=0; list_cnt<XtNumber(animals); list_cnt++)     {         s=XmStringCreate(animals[list_cnt],char_set);         XmListAddItem(list,s,0);         XmStringFree(s);     }       XtManageChild(dialog); }   void main(argc,argv)     int argc;      char *argv[]; {     Arg al[10];     int ac;       /* create the toplevel shell */     toplevel = XtAppInitialize(&context,"",NULL,0,       &argc,argv,NULL,NULL,0);       /* Note that the selection box dialog is not created here.         See the buttonCB function for an explanation. */       /* create the pushbutton */     ac=0;     XtSetArg(al[ac],XmNlabelString,          XmStringCreate("Push Me",char_set)); ac++;     button=XmCreatePushButton(toplevel,"button",al,ac);     XtManageChild(button);     XtAddCallback (button, XmNactivateCallback, buttonCB, NULL);       /* create the selection box widget */     ac = 0;     /* the following line is commented out to make a point. Read               more about it in the text description.                       */     /* XtSetArg(al[ac],XmNautoUnmanage,False); ac++;             */            XtSetArg(al[ac],XmNmustMatch,True); ac++;     XtSetArg(al[ac],XmNselectionLabelString,         XmStringCreateLtoR("Pick an animal. ",char_set));  ac++;     dialog=XmCreateSelectionDialog(toplevel,"dialog",al,ac);     XtAddCallback(dialog,XmNokCallback,dialogCB,OK);     XtAddCallback(dialog,XmNcancelCallback,dialogCB,CANCEL);     XtUnmanageChild(XmSelectionBoxGetChild(dialog,         XmDIALOG_HELP_BUTTON));       XtRealizeWidget(toplevel);     XtAppMainLoop(context); } 

The lower part of buttonCB is involved with creating the list for the selection box to display. The selection box contains a list widget. This list is extracted using XmSelectionBoxGet Child, and the cleared and refilled. Since this list never changes this action is redundant, but it demonstrates how to refill the list in a changing environment. The functions that fill the list are standard list widget convenience functions.

The mustMatch resource is set to true here simply to make you aware of its existence. The selection box contains a field that the user can use to enter a word not in the list. But what if the items in the list are the only valid entries? Then you don't want the user typing in garbage. The mustMatch resource forces the user to choose something that matches an entry in the item list. Try running the code as is, and type some garbage into the string area of the selection box. The selection box will go away, and the callback will not get triggered. Now change the code and set mustMatch to false. Now when you type in garbage, the garbage will be returned.

When mustMatch is true, the fact that the dialog box goes away on its own can be rather disconcerting. If the user enters garbage, the widget goes away without any notification to the program (through the callback), nor a message to the user. What you would like instead, when mustMatch is true, is to have the selection box just sit there waiting for a valid response, and not accepting anything other than a valid response.

To create this effect, you can make use of the autoUnmanage resource that is part of a bulletinBoard widget (the selection box is made up of a bunch of separate widgets attached to a bulletinBoard.). It turns out that this autoUnmanage resource (when true) causes a bulletin board to automatically unmanage (ie - dissappear) whenever the OK, Cancel or Help button is pressed. Here we don't want that to happen. To change the behavior, uncomment the line that sets the autoUnmanage resource and rerun the program (making sure that mustMatch is true). Now when you enter garbage, the dialog box will stick on the screen waiting for appropriate input. [An interesting sidelight - try moving the autoUnmanage line to another point in the program and rerun it. It will have no effect. AutoUnmanage only works if it is set False at the time of widget CREATION.]

Now you know enough to begin creating applications. The next two tutorials will demonstrate two applications, and introduce a few more widgets in the process.