How can I make context-sensitive dynamic menu popups in a drawing area?
|
21-Dec-00 10:00 GMT
|
Question: How can I make dynamic menu popups with different options (push buttons) depending on the
position where I press button over the same drawing area? How do I destroy the popup afterwards?
Creating and subsequently destroying a popup menu is straightforward, and examples will be given below.
Where there are minor complications are in the way the menu is popped up.
In Motif 1.2, it is necessary to add an event handler to receive button
press notification; inside the event handler the decision regarding which menu to display is made, and various
toolkit routines are called in order to actually display the required menu. The code to do all this is
presented.
In Motif 2.0 and later, this is no longer necessary: new callbacks and menu resources are available which
ease the menu selection process, and which arrange to automatically pop up the required menu on the programmer's behalf.
Note that the older Motif 1.2 popup methodology will still work in a Motif 2.1 environment.
A Popup Menu consists of a Popup Shell, a RowColumn, and various Buttons/Labels/Separators added as children to the
RowColumn. It is entirely possible to create this piecemeal using XtVaCreatePopupShell(), XmCreateRowColumn(),
XmCreatePushButton(), and so forth.
This however is slightly messy, since you would have to configure the Popup Shell suitably (XmNoverrideRedirect => True,
XmNallowShellResize => True), as well as the RowColumn (XmNrowColumnType => XmMENU_POPUP). This can
be made to work perfectly well, but there is a better alternative.
It is much better to simply use the Motif toolkit convenience routine XmCreatePopupMenu(). This creates a Popup Shell
with the required configuration internally, as well as creating and correctly configuring a RowColumn for popup use. The routine hides
the Popup Shell from the application programmer - you simply don't need access to it - it is the RowColumn which is returned.
The following code fragment illustrates a simple usage:
extern Widget parent ;
Arg args[MAX_ARGS];
int n ;
Widget popup_menu;
/* Any resources you want for the popup menu */
n = 0;
XtSetArg (args[n], XmNspacing, 2); n++;
/* Create the Popup */
popup_menu = XmCreatePopupMenu (parent, "myPopup", args, n) ;
Thereafter, add your menu contents by creating children of the menu which the routine returns: this code will
be very little different, if at all, to code which creates Buttons, Labels, Separators in other non-menu contexts.
Widget button;
XmString xms ;
/* Again, set some resources */
n = 0;
xms = XmStringCreateLocalized("Press Me!");
XtSetArg (args[n], XmNlabelString, xms); n++;
/* Add the button to the menu */
button = XmCreatePushButton (popup_menu, "my_button", args, n);
/* Manage it */
XtManageChild (button);
/* Clean up */
XmStringFree(xms);
Destroying the menu is straightforward. The routine XtDestroyWidget() will destroy the menu,
as well as recursively destroying all children. The following code is all you need:
extern Widget popup_menu;
XtDestroyWidget (popup_menu);
In Motif 1.2, you need to add an event handler which processes ButtonPress events, if you want to
pop up a dynamic menu. The routine required is XtAddEventHandler(), which will need to be described in
some detail. It has the following functional prototype:
void XtAddEventHandler (Widget widget,
EventMask mask,
Boolean non_maskable,
XtEventHandler handler,
XtPointer client_data)
The widget parameter is where you wish to receive event notification: since the X mechanisms
rely on a Window for event dispatch, this routine cannot be used on a Gadget. The mask
parameter is a bitwise OR'ing of the various event kinds for which you are interested in receiving notification.
The possible values which can be combined are:
NoEventMask KeyPressMask
KeyReleaseMask ButtonPressMask
ButtonReleaseMask EnterWindowMask
LeaveWindowMask PointerMotionMask
PointerMotionHintMask Button1MotionMask
Button2MotionMask Button3MotionMask
Button4MotionMask Button5MotionMask
ButtonMotionMask KeymapStateMask
ExposureMask VisibilityChangeMask
StructureNotifyMask ResizeRedirectMask
SubstructureNotifyMask SubstructureRedirectMask
FocusChangeMask PropertyChangeMask
ColormapChangeMask OwnerGrabButtonMask
X does not define bit masks for every type of event which it is possible to receive. For example,
there are also things like ClientMessage events which processes can send to each other. These event
types, for which there is no specific filter mask available, are called non_maskable. If you
wish to receive notification of these as well as for event types specified in the mask parameter,
you should set the non_maskable value to True. This is exceptional processing, and only particular
kinds of tasks need this: typically, the non_maskable parameter is set to False for routine application
event handling. It is False when handling menu popup events.
The handler parameter is the routine we want to be called by the toolkit when the event occurs, and
the client_data parameter is any application-specific data we want the handler to be passed when invoked.
An XtEventHandler has the following signature:
void (*XtEventHandler)(Widget widget,
XtPointer client_data,
XEvent *event,
Boolean *continue_to_dispatch)
When invoked, the event handler parameters have the following meaning: the widget parameter is where the event occurred;
client_data is simply that supplied to the XtAddEventHandler() routine when interest in the event was registered; the
event parameter describes the event for which you requested notification; the continue_to_dispatch parameter
is concerned with event propagation: the handler should set this depending on if it has any interest in controlling
whether the event received should be passed up the widget hierarchy or through other handling mechanisms after current processing.
This parameter is very rarely used for normal application programming. It won't be used here.
Suppose we have received the button event in a handler. The first task is obviously
to choose the menu to display. This is entirely application-specific, so no general
guidelines can be given here. What will be required in all cases is to position the
desired menu at the current mouse/cursor location. The routine to do this is
XmMenuPosition(), which has the following signature:
void XmMenuPosition (Widget menu, XButtonPressedEvent *event)
This is simple to use: we pass through the event given to us as a parameter to
our event handler straight into the routine. For example:
void popup_handler (Widget widget,
XtPointer closure,
XEvent *event,
Boolean *continue_to_dispatch)
{
Widget menu;
/* Chose the menu... */
menu = blah_blah();
/* Position the menu at the cursor */
XmMenuPosition (menu, (XButtonPressedEvent *) event) ;
/* Whatever else this routine does */
...
}
Again, straightforward. A Simple call to XtManageChild() will do the trick:
XtManageChild (menu) ;
Note that we don't need to use XtPopup() on any shell widget: the Popup Menu
abstraction hides the shell from us: the RowColumn will arrange to popup up the
shell for us as a side effect of being managed.
As far as menu popup handing is concerned, the only event type which is of any interest
is ButtonPress, and so the following kind of code is all that is required to register
the popup event handler:
extern Widget drawing_area;
extern void popup_handler(Widget, XtPointer, XEvent *, Boolean *);
XtAddEventHandler (drawing_area, ButtonPressMask, False, popup_handler, NULL);
And a typical Popup Event Handler has the following kind of algorithm:
void popup_handler (Widget widget,
XtPointer closure,
XEvent *event,
Boolean *continue_to_dispatch)
{
XButtonPressedEvent *be = (XButtonPressedEvent *) event ;
Widget menu_to_post;
/* Firstly, make sure this is the right event type */
if (be->type != ButtonPress) {
/* Woops. Should we be here at all ? */
return ;
}
/* Secondly, we can filter out the wrong button press if we want to */
if (be->button != Button3) {
/* Wrong type of Button Press. Allow menus on Button3 only */
return ;
}
/* Now choose our menu. We **could** create it here. */
/* It might be better to pre-create for performance reasons */
/* Typically, we look at be->x, be->y to make our decision. */
menu_to_post = create_or_pick_some_menu(...);
/* Position it at the cursor */
XmMenuPosition(menu_to_post, be);
/* Display it */
XtManageChild (menu_to_post);
}
A simple application which contains a Shell, Form, DrawingArea, and two popup menus is presented
in the following code. The menu which is displayed in the DrawingArea depends arbitrarily on the
x, y coordinates of the mouse event. If x + y is even, the first menu is displayed, otherwise the second.
Clearly, this method of choosing the menu is where you would refine the algorithm for your own application.
The application has been generated using X-Designer for speed, although you don't need the GUI builder
in any way to read, understand, or build the program. The design file is included with the sample sources,
in case anyone is interested or has use of this. The example consists of the following code:
-
popups12.c
- the code which creates the widget hierarchy
-
popups12.h
- widget declarations
-
popups12_stubs.c
- the event handler code
-
Makefile
- a general purpose Makefile for the application. It is configured for Solaris,
but at the top of the file are rules for various platforms: simply comment out the Solaris
section, and uncomment your platform as required.
-
popups12.xd
- the X-Designer design save file. Feel free to ignore this.
Alternatively download a tar archive,
popups12.tar, of all the above source
files.
If you are using Netscape Navigator you may have to hold down
the Shift key when you click on the file name in order to
ensure that you are prompted to save the file.
In Motif 2.0 and later, there are two enhancements to the system which make the creation and management
of popup menus easier for the application programmer. Firstly, the RowColumn widget class is enhanced
to allow for the automatic popup of menus. Secondly, the Manager and Primitive widget classes support
a new callback, which can be used to choose a menu to display when a ButtonPress event happens in a widget.
There is therefore no longer any need to add an event handler to process Button events: all that is
required is that a suitable resource is set on the menu to enable the new functionality, and that a callback
is optionally added to choose the menu. This is not always necessary, because the RowColumn has new algorithms
which will search for a default popup to display in the current widget context.
The resource XmNpopupEnabled is extended from the simple Boolean in Motif 1.2, to an enumerated
type. The resource can have any of the following values:
XmPOPUP_DISABLED
XmPOPUP_KEYBOARD
XmPOPUP_AUTOMATIC
XmPOPUP_AUTOMATIC_RECURSIVE
The value XmPOPUP_DISABLED is equivalent to the old Motif 1.2 value False: keyboard shortcuts
(accelerators and mnemonics) to popup the menu are disabled. The value XmPOPUP_KEYBOARD is equivalent
to the old Motif 1.2 value True: keyboard shortcuts are enabled. The new Motif 2.1 value XmPOPUP_AUTOMATIC
also adds event handlers to process ButtonPress events: this internalizes the explicit XtAddEventHandler()
code required for Motif 1.2. The RowColumn will search for a default popup to display from amongst the current
immediate children of the widget where the ButtonPress event occurred. XmPOPUP_AUTOMATIC_RECURSIVE is similar,
except that the RowColumn algorithms for determining the default popup to display is not restricted to immediate children.
Note that the RowColumn search algorithms are not defined purely in terms of Widgets. It is possible to define
a context sensitive popup menu which is associated with a Gadget in Motif 2.1.
There is a new callback defined for these classes, the XmNpopupHandlerCallback. This is invoked
by the (now) automatically installed event handlers, and the callback data for this callback type has elements
whereby the programmer can explicitly specify the popup to display. It is called before the menu is posted,
and is your chance to override the RowColumn internal algorithms. An XmPopupHandlerCallback is passed
the following data structure when invoked:
typedef struct
{
int reason ;
XEvent *event ;
Widget menuToPost ;
Boolean postIt ;
Widget target ;
} XmPopupHandlerCallbackStruct ;
The reason and event fields are common to all callback structures. Here, the reason field
will have the value XmCR_POST or XmCR_REPOST. XmCR_POST is the normal value, although the value
XmCR_REPOST can occur if the menu is unposted because of event replay.
The menuToPost element is initially
filled in by the RowColumn class: it is the suggested menu to post, deduced by the internal search algorithms: the
RowColumn fills this in with the menu which it believes closest fits the current widget context. The programmer
can change this during the callback to alter the menu to display.
The postIt element can be used to indicate whether the posting operation is to continue after the callback
terminates. By default it is True, but you can set it to False if no context sensitive menu is operative at the
current time.
The target element is filled in by the RowColumn to represent what it believes is the closest object
to the source of the event. In other words, it is the nearest Widget or Gadget to the x, y coordinates where
the ButtonPress event occurred. The algorithm performs a recursive descent, matching the received event against
the known location of managed children.
A typical Popup Callback Handler has the following kind of algorithm:
void PopupHandler (Widget w, XtPointer client_data, XtPointer call_data)
{
XButtonPressedEvent *be = (XButtonPressedEvent *) call_data->event ;
Widget menu_to_post ;
/* No need to discriminate on the event type */
/* to check it is the right mouse button, etc. */
/* We only need to look at be->x, be->y, or so */
/* to choose the appropriate menu. */
/* Or, we can simply leave the widgetToPost */
/* element as is and let the RowColumn decide */
menu_to_post = create_or_pick_some_menu(...);
/* And we do not have to position or manage the menu either */
/* We simply tell the system the menu we want to show */
call_data->menuToPost = menu_to_post ;
}
The code in this example has an identical widget hierarchy to the previous one. This time, the popup menus are posted
using the Motif 2.1 mechanisms. Each popup has the XmNpopupEnabled resource set to XmPOPUP_AUTOMATIC,
and an XmPopupHandlerCallback is added to the DrawingArea to perform the menu choice. There is no call
to XtAddEventHandler() anywhere in the source code.
Again, the application has been generated using X-Designer. This time, it is using X-Designer 6.0
which is the Motif 2.1 version. Again, you don't need the product in any way to read, understand,
or build the program. The design file is also included with the sample sources.
The example consists of the following code:
-
popups21.c
- the code which creates the widget hierarchy
-
popups21.h
- widget declarations
-
popups21_stubs.c
- the popup handler code
-
Makefile
a general purpose Makefile for the application. Again, it is configured for Solaris,
although this time you will need to get hold of something later than Solaris 2.6 as this
is a Motif 1.2 platform. Again, at the top of the file are rules for various platforms: simply
comment out the Solaris section, and uncomment your platform as required.
-
popups21.xd
- the X-Designer design save file. Feel free to ignore this.
Alternatively download a tar archive,
popups21.tar, of all the above source
files.
If you are using Netscape Navigator you may have to hold down
the Shift key when you click on the file name in order to
ensure that you are prompted to save the file.
Sponsored
by X-Designer - The Leading X/Motif GUI Builder
- Click to download a FREE evaluation
Goto top of page
|