When designing a GUI, care must be taken in the presentation of the application's primary window. This window is important as it is likely to be the major focus of the application -- the most visible and most used window in the application. In order to facilitate consistency amongst many different applications, Motif provides a general framework: the MainWindow Widget, for an application developer to work with. The Motif Style Guide (Chapter 18) recommends that, whenever applicable, the MainWindow window should be used. It should be noted, however, that the general framework of the MainWindow is not always applicable to every application front end GUI. A text editor application front end is an example that might easily map into the MainWindow framework, a simple calculator application most certainly would not fit the prescribed framework.
A MainWindow widget can manage up to five specialised child widgets (Fig 8.1):
The MainWindow basically provides an efficient method of managing widgets as recommended by the Motif Style Guide (Chapter 18). In particular, the provision of a menu bar and work area is a convenient mechanism with which to drive many applications. It should be noted that the work area can be any widget (or composite hierarchy of widgets). The scrollbars, command and message area are optional.
In this Chapter, we will mainly concentrate our study on the relationship between the MainWindow and certain types of menus. We will also see how to put widgets in the work area MainWindow. We will see further applications of the MainWindow widget in the remainder of the book. The MainWindow will be used as a container widget in many example programs throughout the book.
As we have previously stated, the MainWindow is typically used as the top level container for an application's child widgets. The simplest functional MainWindow may consist of a menu bar along the top of the application and some work area below, this is illustrated in Fig. 8.2. We omit the scroll bars and command and message areas for the time being.
Fig. 8.2 menu_cascade.c output
Menu bar items are placed within the menu bar . You can use menu bar items to perform selections directly. However, more usually a PullDown menu is attached to a menu bar item allowing greater selection opportunities.
The function of the work area is to perform the main application's functions. The work area can be any widget class. We will look at aspects of this in later sections when we have studied more widget classes.
Initially, we will concentrate on how to create menus in a MainWindow.
The creation of a fully functional pulldown MenuBar typical of most MainWindow based applications is fairly complex. We will, therefore, break it down into two stages.
A MenuBar widget is really a RowColumn widget under another name. The way we create a MenuBar is to use one of the (several) XmCreate or XtVaCreate Menu options. For most of our applications, we will use the XmCreateMenuBar() function.
Having created a MenuBar we create MenuBar items by attaching widgets to the MenuBar in exactly the same fashion as described for the RowColumn widget (Chapter 7). The widgets we usually attach are CascadeButton widgets since we can hang pull down menus off these widgets.
Fig. 8.2 shows the output of the menu_cascade.c program that simply creates a simple MenuBar widget and attaches two CascadeButton widgets - help and quit. Minimal callback functions are associated with each CascadeButton.
The full program listing of menu_cascade.c is:
#include <Xm/Xm.h> #include <Xm/MainW.h> #include <Xm/CascadeB.h> /* Prototype callbacks */ void quit_call(void), help_call(void); main(int argc, char **argv) { Widget top_wid, main_w, menu_bar, quit, help; XtAppContext app; Arg arg[1]; /* create application, main and menubar widgets */ top_wid = XtVaAppInitialize(&app, "menu_cascade", NULL, 0, &argc, argv, NULL, NULL); main_w = XtVaCreateManagedWidget("main_window", xmMainWindowWidgetClass, top_wid, NULL); menu_bar = XmCreateMenuBar(main_w, "main_list", NULL, 0); XtManageChild(menu_bar); /* create quit widget + callback */ quit = XtVaCreateManagedWidget( "Quit", xmCascadeButtonWidgetClass, menu_bar, XmNmnemonic, 'Q', NULL); XtAddCallback(quit, XmNactivateCallback, quit_call, NULL); /* create help widget + callback */ help = XtVaCreateManagedWidget( "Help", xmCascadeButtonWidgetClass, menu_bar, XmNmnemonic, 'H', NULL); XtAddCallback(help, XmNactivateCallback, help_call, NULL); /* Tell the menubar which button is the help menu */ XtSetArg(arg[0],XmNmenuHelpWidget,help); XtSetValues(menu_bar,arg,1); XtRealizeWidget(top_wid); XtAppMainLoop(app); } void quit_call() { printf("Quitting program\n"); exit(0); } void help_call() { printf("Sorry, I'm Not Much Help\n"); }
The first steps of the main function should now be familiar -- we initialise the application and create the top_wid top level widget.
A MainWindow widget , main_win, is then created as is a MenuBar , menu_bar. The menu_bar widget is a child of main_win. Finally, we attach two CascadeButton widgets, quit and help, as children of menu_bar. Note there is no work area created in this program.
A CascadeButton widget is usually used to attach a pulldown menu to the MenuBar. This CascadeButton widget is similar to the PushButton widget and can have callback functions attached to its activateCallback function -- illustrated in the menu_cascade.c program. However, it should be noted, that the main use of CascadeButton is to link a menu bar with a menu.
The program above simply attaches callback functions to the CascadeButtons. The callback functions do not do much except quit the program and print to standard output.
You can also associate a mnemonic to a particular menu selection. This means that you can use ``hot keys'' on the keyboard as a short cut to selection. You need to press Meta key plus the key in question.
In this program, we allow Meta-Q and Meta-H for the selection of Quit and Help. Note that Motif displays the appropriate Meta key by underlining the letter concerned on the menu bar (or menu item). The XmNmnemonic resource is used to select the appropriate keyboard short cut.
Apart from prescribing the use of the Meta key for the selection of menu items, the Motif Style Guide also insists that the Help MenuBar widget should always be placed on the right most side of the MenuBar (Fig. 8.2). This makes for easy location and selection of the help facility, should it exist.
The MenuBar resource, XmNmenuHelpWidget , is used to store the ID of the the appropriate widget (help in the above program).
Let us now develop things a little further by adding pulldown menus to our MenuBar. A pulldown menu looks like this:
Fig. 8.3 menu_pull.c output
In order to see how we create and use pulldown menu items we will develop a program, menu_pull.c that will create two pulldown menus:
We also attach the Help Cascade button as in the previous menu_cascade.c program.
We should now be familiar with the first few steps of this program: We create the top level widget hierarchy as usual, with the MainWindow widget being a child of the application and a MenuBar widget a child of the MainWindow.
The complete listing of menu_pull.c is as follows:
#include <Xm/Xm.h> #include <Xm/MainW.h> #include <Xm/CascadeB.h> #include <Xm/Label.h> /* prototype functions */ void quit_call(Widget , int), menu_call(Widget , int), help_call(void); Widget label; String food[] = { "Chicken", "Beef", "Pork", "Lamb", "Cheese"}; main(int argc, char **argv) { Widget top_wid, main_w, help; Widget menubar, menu, widget; XtAppContext app; XColor back, fore, spare; XmString quit, menu_str, help_str, chicken, beef, pork, lamb, cheese, label_str; int n = 0; Arg args[2]; /* Initialize toolkit */ top_wid = XtVaAppInitialize(&app, "Demos", NULL, 0, &argc, argv, NULL, NULL); /* main window will contain a MenuBar and a Label */ main_w = XtVaCreateManagedWidget("main_window", xmMainWindowWidgetClass, top_wid, XmNwidth, 300, XmNheight, 300, NULL); /* Create a simple MenuBar that contains three menus */ quit = XmStringCreateLocalized("Quit"); menu_str = XmStringCreateLocalized("Menu"); help_str = XmStringCreateLocalized("Help"); menubar = XmVaCreateSimpleMenuBar(main_w, "menubar", XmVaCASCADEBUTTON, quit, 'Q', XmVaCASCADEBUTTON, menu_str, 'M', XmVaCASCADEBUTTON, help_str, 'H', NULL); XmStringFree(menu_str); /* finished with this so free */ XmStringFree(help_str); /* First menu is the quit menu -- callback is quit_call() */ XmVaCreateSimplePulldownMenu(menubar, "quit_menu", 0, quit_call, XmVaPUSHBUTTON, quit, 'Q', NULL, NULL, NULL); XmStringFree(quit); /* Second menu is the food menu -- callback is menu_call() */ chicken = XmStringCreateLocalized(food[0]); beef = XmStringCreateLocalized(food[1]); pork = XmStringCreateLocalized(food[2]); lamb = XmStringCreateLocalized(food[3]); cheese = XmStringCreateLocalized(food[4]); menu = XmVaCreateSimplePulldownMenu(menubar, "edit_menu", 1, menu_call, XmVaRADIOBUTTON, chicken, 'C', NULL, NULL, XmVaRADIOBUTTON, beef, 'B', NULL, NULL, XmVaRADIOBUTTON, pork, 'P', NULL, NULL, XmVaRADIOBUTTON, lamb, 'L', NULL, NULL, XmVaRADIOBUTTON, cheese, 'h', NULL, NULL, /* RowColumn resources to enforce */ XmNradioBehavior, True, /* select radio behavior in Menu */ XmNradioAlwaysOne, True, NULL); XmStringFree(chicken); XmStringFree(beef); XmStringFree(pork); XmStringFree(lamb); XmStringFree(cheese); /* Initialize menu so that "chicken" is selected. */ if (widget = XtNameToWidget(menu, "button_1")) { XtSetArg(args[n],XmNset, True); n++; XtSetValues(widget, args, n); } n=0; /* reset n */ /* get help widget ID to add callback */ help = XtVaCreateManagedWidget( "Help", xmCascadeButtonWidgetClass, menubar, XmNmnemonic, 'H', NULL); XtAddCallback(help, XmNactivateCallback, help_call, NULL); /* Tell the menubar which button is the help menu */ XtSetArg(args[n],XmNmenuHelpWidget,help); n++; XtSetValues(menubar,args,n); n=0; /* reset n */ XtManageChild(menubar); /* create a label text widget that will be "work area" selections from "Menu" menu change label default label is item 0 */ label_str = XmStringCreateLocalized(food[0]); label = XtVaCreateManagedWidget("main_window", xmLabelWidgetClass, main_w, XmNlabelString, label_str, NULL); XmStringFree(label_str); /* set the label as the "work area" of the main window */ XtVaSetValues(main_w, XmNmenuBar, menubar, XmNworkWindow, label, NULL); XtRealizeWidget(top_wid); XtAppMainLoop(app); } /* Any item the user selects from the File menu calls this function. It will "Quit" (item_no == 0). */ void quit_call(Widget w, int item_no) /* w = menu item that was selected item_no = the index into the menu */ { if (item_no == 0) /* the "quit" item */ exit(0); } /* Called from any of the food "Menu" items. Change the XmNlabelString of the label widget. Note: we have to use dynamic setting with setargs(). */ void menu_call(Widget w, int item_no) { int n =0; Arg args[1]; XmString label_str; label_str = XmStringCreateLocalized(food[item_no]); XtSetArg(args[n],XmNlabelString, label_str); ++n; XtSetValues(label, args, n); } void help_call() { printf("Sorry, I'm Not Much Help\n"); }
In this program we create the MenuBar widget with the convenience function XmVaCreateSimpleMenuBar() :
In menu_pull.c, we set up two
CascadeButtons by setting the
XmVACASCADEBUTTON resource. Two arguments are
associated with this resource:
Note: we convert a String to an XmString with the
XmStringCreateLocalized() function.
It is good practice to free an XmString as soon as you have finished using it with the XmStringFree() command. Several ``open'' XmStrings can occupy a significant amount of application memory.
The creation of a pulldown menu is now relatively straightforward:
Let us now look at how we create the Quit menu:
quit_w = XmVaCreateSimplePulldownMenu(menubar, "quit_menu", 0, quit_call, XmVaPUSHBUTTON, quit, 'Q', NULL, NULL, NULL);
We have used the Motif convenience function XmVaCreateSimplePulldownMenu() to return a PulldownMenu widget. This function has several arguments:
Thus, in this program an integer ID of 0 is attached to the Quit button and an ID of 1 would be attached to the Menu button.
Note: We do not specify a callback with an XtAddCallback() call in this instance.
To specify a PushButton menu item, set a XmVaPUSHBUTTON resource list item and corresponding XmString label and Meta key accordingly for the list entry. XmVaPUSHBUTTON actually takes four arguments, the last two are only needed for advanced Motif use, so are not considered here -- they are just set to NULL.
The Menu menu is created in a similar way except that we have 5 menu items.
We may have one, minor, problem when assigning Meta keys. This is illustrated for the Menu items since we cannot have the same Meta key for two menu selections. So Meta-B is chosen for Beef and Meta-L for Lamb, etc. However, Chicken and Cheese must be assigned different Meta keys, so we allocate Meta-C for Chicken and Meta-h for cheese selections.
The last thing we need to look at is how we find out which selection has been made in our program.
Each PulldownMenu has an associated callback function . The callback function of a pulldown has two parameters, which we must define.
So, in the Quit callback, quit_call(), we only have one possible selection (item_no must equal zero).
In the Menu callback, menu_call(), the index corresponds to a food item setting of Chicken, Beef, etc..
Chapter 18 discusses aspects of the Motif Style guidelines for menus which also incorporate some general menu design issues.
Tear-off menus allow the user to remove (or tear) a menu off the MenuBar and keep it displayed in a small dialog window on the screen until the user closes it from the window menu. The Motif Style Guide (Chapter 18) prescribes this for menus that are frequently used in order to ease menu selection In order to make a menu a tear-off variety the XmNtearOffModel resource for a PullDownMenu widget needs to set to XmTEAR_OFF_ENABLED. If a menu is XmTEAR_OFF_ENABLED then its appearance is modified to include a small perforated line at the top of the menu.
TextField, Label or Command widgets are typically employed as the command and message areas.
Fig. 8.4 The test_for_echo.c program output The test_for_echo.c program achieves this by:
The complete program listing for test_for_echo.c is as follows:
#include <Xm/MainW.h> #include <Xm/Label.h> #include <Xm/Command.h> #include <Xm/TextF.h> #include <stdio.h> #include <string.h> /* For String Handling */ #define MAX_STR_LEN 30 /* Max Char length of Text Field */ /* Callback function prototypes */ void cmd_cbk(), quit_cbk(); Widget msg_wid; char *cmd_label = "Command Area: "; char *msg_label = "Message Area: "; int cmd_label_length; int msg_label_length; main(int argc, char **argv) { Widget top_wid, main_win, menubar, menu, label, cmd_wid; XtAppContext app; XmString label_str, quit; cmd_label_length = strlen(cmd_label); msg_label_length = strlen(msg_label); /* initialize toolkit and create top_widlevel shell */ top_wid = XtVaAppInitialize(&app, "Main Window", NULL, 0, &argc, argv, NULL, NULL); /* Create MainWindow */ main_win = XtVaCreateWidget("main_w", xmMainWindowWidgetClass, top_wid, XmNcommandWindowLocation, XmCOMMAND_BELOW_WORKSPACE, NULL); /* Create a simple MenuBar that contains one menu */ quit = XmStringCreateLocalized("Quit"); menubar = XmVaCreateSimpleMenuBar(main_win, "menubar", XmVaCASCADEBUTTON, quit, 'Q', NULL); menu = XmVaCreateSimplePulldownMenu(menubar, "file_menu", 0, quit_cbk, XmVaPUSHBUTTON, quit, 'Q', NULL, NULL, NULL); XmStringFree(quit); /* Manage Menubar */ XtManageChild(menubar); /* create a label text widget that wil be work area */ label_str = XmStringCreateLocalized("Work Area"); label = XtVaCreateManagedWidget("main_window", xmLabelWidgetClass, main_win, XmNlabelString, label_str, XmNwidth, 1000, XmNheight, 800, NULL); XmStringFree(label_str); /* Create the command area */ cmd_wid = XtVaCreateWidget( "Command", xmTextFieldWidgetClass, main_win, XmNmaxLength, MAX_STR_LEN, NULL); XmTextSetString(cmd_wid,cmd_label); XmTextSetInsertionPosition(cmd_wid, cmd_label_length); XtAddCallback(cmd_wid, XmNactivateCallback, cmd_cbk); XtManageChild(cmd_wid); /* Create the message area */ msg_wid= XtVaCreateWidget( "Message:", xmTextFieldWidgetClass, main_win, XmNeditable, False, XmNmaxLength, MAX_STR_LEN, NULL); XmTextSetString(msg_wid,msg_label); XtManageChild(msg_wid); /* set the label as the work, command and message areas of the main window */ XtVaSetValues(main_win, XmNmenuBar, menubar, XmNworkWindow, label, XmNcommandWindow, cmd_wid, XmNmessageWindow,msg_wid, NULL); XtManageChild(main_win); XtRealizeWidget(top_wid); XtAppMainLoop(app); } /* execute the command and redirect message area */ void cmd_cbk(Widget cmd_widget, XtPointer *client_data, XmAnyCallbackStruct *cbs) { char cmd[MAX_STR_LEN],msg[MAX_STR_LEN]; XmTextGetSubstring(cmd_widget,cmd_label_length, MAX_STR_LEN - cmd_label_length, MAX_STR_LEN ,cmd); /* Append input message to Message area */ XmTextReplace(msg_wid,msg_label_length, MAX_STR_LEN,cmd); /* Reset Command Area label and insertion point*/ XmTextSetString(cmd_widget, cmd_label); XmTextSetInsertionPosition(cmd_widget, cmd_label_length); } void quit_cbk(Widget w, int item_no) { if (item_no == 0) /* the "quit" item */ exit(0); }