NULL);
XtRealizeWidget (toplevel);
XtAppMainLoop (app);
}
The majority of this program is composed of the new version of BuildPulldownMenu() and the menu and
submenu declarations. All the menus and menu items are declared in reverse order because the cascading menu
declaration must exist before the menu is actually referenced. The output of the program is shown in the figure.
Output of build_menu.c
All we have to do to get BuildPulldownMenu() to create a cascading menu is add code that checks whether or
not the current menu has a submenu. If it does, the routine calls itself to create the submenu. Because the function
creates and returns a CascadeButton, the return value can be used as the menu item in the menu that is currently being
built. We have to create the cascading menu first because it has to exist before it can be attached to a CascadeButton.
Recursion handles this problem for us by creating the deepest submenus first, which ensures that all the necessary
submenus are built before their CascadeButtons require them.
We also added support for ToggleButtons to this version of BuildPulldownMenu(), even though our menus do
not contain any ToggleButtons. The only change that we have to make here involves the callback function. Since
ToggleButtons have an XmNvalueChangedCallback, while PushButtons have an XmNactivateCallback,
we check the class of the item being added and specify the appropriate callback resource in our call to
XtAddCallback().
16.4.3 Building Popup Menus
To further demonstrate the flexibility of our design and to exploit the similarities between PulldownMenus,
PopupMenus, and cascading menus, we can easily modify the BuildPulldownMenu() routine to support any of
these menu types. We only need to specify a new parameter indicating which of the two menu types to use. Since
Motif already defines the values XmMENU_PULLDOWN and XmMENU_POPUP in <Xm/Xm.h>, we use those values.
We have also given the function a more generic name, BuildMenu(), as shown in the source code
XmStringCreateLocalized() is only available in Motif 1.2; XmStringCreateSimple() is the
corresponding function in Motif 1.1. The XmNtearOffModel resource is only available in Motif 1.2; it should not
be specified in Motif 1.1.
Widget
BuildMenu(parent, menu_type, menu_title, menu_mnemonic, tear_off, items)
16 Menus 16.4.3 Building Popup Menus
459
Widget parent;
int menu_type;
char *menu_title, menu_mnemonic;
Boolean tear_off;
MenuItem *items;
{
Widget menu, cascade, widget;
int i;
XmString str;
if (menu_type == XmMENU_PULLDOWN)
menu = XmCreatePulldownMenu (parent, "_pulldown", NULL, 0);
else
menu = XmCreatePopupMenu (parent, "_popup", NULL, 0);
if (tear_off)
XtVaSetValues (menu, XmNtearOffModel, XmTEAR_OFF_ENABLED, NULL);
if (menu_type == XmMENU_PULLDOWN) {
str = XmStringCreateLocalized (menu_title);
cascade = XtVaCreateManagedWidget (menu_title,
xmCascadeButtonGadgetClass, parent,
XmNsubMenuId, menu,
XmNlabelString, str,
XmNmnemonic, menu_mnemonic,
NULL);
XmStringFree (str);
}
/* Now add the menu items */
for (i = 0; items[i].label != NULL; i++) {
/* If subitems exist, create the pull−right menu by calling this
* function recursively. Since the function returns a cascade
* button, the widget returned is used..
*/
if (items[i].subitems)
widget = BuildMenu (menu, XmMENU_PULLDOWN, items[i].label,
items[i].mnemonic, tear_off, items[i].subitems);
else
widget = XtVaCreateManagedWidget (items[i].label,
*items[i].class, menu,
NULL);
/* Whether the item is a real item or a cascade button with a
* menu, it can still have a mnemonic.
*/
if (items[i].mnemonic)
XtVaSetValues (widget, XmNmnemonic, items[i].mnemonic, NULL);
/* any item can have an accelerator, except cascade menus. But,
* we don't worry about that; we know better in our declarations.
*/
if (items[i].accelerator) {
str = XmStringCreateLocalized (items[i].accel_text);
XtVaSetValues(widget,
XmNaccelerator, items[i].accelerator,
XmNacceleratorText, str,
NULL);
XmStringFree (str);
}
/* again, anyone can have a callback −− however, this is an
* activate−callback. This may not be appropriate for all items.
*/
if (items[i].callback)
16 Menus 16.4.3 Building Popup Menus
460
XtAddCallback(widget,
(items[i].class == &xmToggleButtonWidgetClass ||
items[i].class == &xmToggleButtonGadgetClass) ?
XmNvalueChangedCallback : /* ToggleButton class */
XmNactivateCallback, /* PushButton class */
items[i].callback, items[i].callback_data);
}
return menu_type == XmMENU_POPUP ? menu : cascade;
}
All of the original functionality is maintained; we only added a couple of lines to support popup menus. Namely,
when XmMENU_POPUP is passed as the menu_type parameter, the function XmCreatePopupMenu() is called,
and the menu itself is returned. Otherwise the routine returns a CascadeButton. If any of the menu items have
cascading menus, we continue what we were doing before for submenus.
In order to use this routine in an application, we would have to create the PopupMenu as the child of another widget
and set up a callback routine to post the menu, just as we did with the simple menu creation routine. Since mnemonics
are not typically used for PopupMenus, the mnemonic fields in the data structure should be specified as NULL.
Now we can build PopupMenus, but what we really need to talk about is when you should use PopupMenus in an
application. The Motif Style Guide has very little to say about when and how popup menus should be used. One
guideline is that PopupMenus should only be used as a redundant means of activating application functionality, since
they do not make themselves apparent to the user. The single requirement is that PopupMenus use the third mouse
button, which leads to the question: how do you get the necessary events on an arbitrary widget so that you can pop up
a menu?
In our previous PopupMenu examples, we have used the DrawingArea widget because of its ability to track such input
events through a callback routine. However, for all other widgets, the solution is not so simple. Unfortunately, the
design of PopupMenus in the Motif toolkit requires you to dig into lower−level Xt event−handling mechanisms in
order to post a PopupMenu. We can continue to build menus in the same way; it's just that we have to do a bit of work
to pop them up.
the source code demonstrates how to display a PopupMenu for an arbitrary widget. Here, we use events in a
PushButton widget to display a PopupMenu, but the menu could be triggered from any type of widget. This program
uses the BuildMenu() routine from the source code so we do not show it in this example.
XtSetLanguageProc() is only available in X11R5; there is no corresponding function in X11R4.
/* popups.c −− demonstrate the use of a popup menus in an arbitrary
* widget. Display two PushButtons. The second one has a popup
* menu attached to it that is activated with the third
* mouse button.
*/
#include <Xm/LabelG.h>
#include <Xm/PushBG.h>
#include <Xm/PushB.h>
#include <Xm/ToggleBG.h>
#include <Xm/ToggleB.h>
#include <Xm/SeparatoG.h>
#include <Xm/RowColumn.h>
#include <Xm/FileSB.h>
#include <Xm/CascadeBG.h>
Widget toplevel;
extern void exit();
void open_dialog_box();
16 Menus 16.4.3 Building Popup Menus
461
/* callback for pushbutton activation */
void
put_string(w, client_data, call_data)
Widget w;
XtPointer client_data;
XtPointer call_data;
{
String str = (String) client_data;
puts (str);
}
typedef struct _menu_item {
char *label;
WidgetClass *class;
char mnemonic;
char *accelerator;
char *accel_text;
void (*callback)();
XtPointer callback_data;
struct _menu_item *subitems;
} MenuItem;
MenuItem file_items[] = {
{ "File Items", &xmLabelGadgetClass, NULL, NULL, NULL, NULL, NULL, NULL },
{ "_sep1", &xmSeparatorGadgetClass, NULL, NULL, NULL, NULL, NULL, NULL },
{ "New", &xmPushButtonGadgetClass, 'N', NULL, NULL,
put_string, "New", NULL },
{ "Open...", &xmPushButtonGadgetClass, 'O', NULL, NULL,
open_dialog_box, (XtPointer) XmCreateFileSelectionDialog, NULL },
{ "Save", &xmPushButtonGadgetClass, 'S', NULL, NULL,
put_string, "Save", NULL },
{ "Save As...", &xmPushButtonGadgetClass, 'A', NULL, NULL,
open_dialog_box, (XtPointer) XmCreateFileSelectionDialog, NULL },
{ "Exit", &xmPushButtonGadgetClass, 'x', "Ctrl<Key>C", "Ctrl+C",
exit, NULL, NULL },
NULL,
};
main(argc, argv)
int argc;
char *argv[];
{
Widget BuildMenu(), button, rowcol, popup;
XtAppContext app;
extern void PostIt();
XtSetLanguageProc (NULL, NULL, NULL);
toplevel = XtVaAppInitialize (&app, "Demos", NULL, 0,
&argc, argv, NULL, NULL);
/* Build a RowColumn to contain two PushButtons */
rowcol = XtVaCreateManagedWidget ("rowcol",
xmRowColumnWidgetClass, toplevel,
NULL);
/* The first PushButton is a −gadget−, so we cannot popup a menu
* from here!
*/
16 Menus 16.4.3 Building Popup Menus
462
button = XtVaCreateManagedWidget ("Button 1",
xmPushButtonGadgetClass, rowcol, NULL);
XtAddCallback (button, XmNactivateCallback, put_string, "Button 1");
/* This PushButton is a widget, so it has its own window, so
* we can pop up a menu from here by adding an event handler
* specifically for the 3rd mouse button (motif compliance).
*/
button = XtVaCreateManagedWidget ("Button 2",
xmPushButtonWidgetClass, rowcol,
NULL);
/* it can still have its callback! */
XtAddCallback (button, XmNactivateCallback, put_string, "Button 2");
/* build the menu... */
popup = BuildMenu(button, XmMENU_POPUP, "Stuff", NULL,
True, file_items);
/* Add the event handler (PostIt()) and pass the newly created menu
* as the client_data. This is done to avoid using unnecessary globals.
*/
XtAddEventHandler (button, ButtonPressMask, False, PostIt, popup);
XtRealizeWidget (toplevel);
XtAppMainLoop (app);
}
/* PostIt() −− event handler for the 3rd mouse button on the
* PushButton widget's window.
*/
void
PostIt(pb, client_data, event)
Widget pb;
XtPointer client_data;
XEvent *event;
{
Widget popup = (Widget) client_data;
XButtonPressedEvent *bevent = (XButtonPressedEvent *) event;
if (bevent−>button != 3)
return;
/* position the menu at the location of the button press. If we wanted
* to position it elsewhere, we could change the x,y fields of the
* event structure.
*/
XmMenuPosition (popup, bevent);
XtManageChild (popup);
}
/* open_dialog_box() −− callback for some of the menu items declared
* in the MenuItem struct. The client data is the creation function
* for the dialog. Associate the dialog with the menu
* item via XmNuserData so we don't have to keep a global and
* don't have to repeatedly create one.
*/
void
open_dialog_box(w, client_data, call_data)
Widget w;
XtPointer client_data;
XtPointer call_data;
{
Widget (*func)(); = client_data;
16 Menus 16.4.3 Building Popup Menus
463
Get Volume 6A: Motif Programming Manual now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.