motifdeveloper.com
Sponsored by IST Limited
Formerly MW3: Motif on the World Wide Web [MW3 Logo]
Last Updated
November 18, 2002
 


X-Designer - The Leading X/Motif GUI Builder - Click to download a FREE evaluation
 

motifdeveloper.com
Home
About the site
Bulletin Board
News Archive
OpenGroup News
Events
Search
Frequently Asked Questions
The Motif FAQ
X/Motif FAQs
General FAQs
Ask Antony
Latest Q & A
All Q & As
Submit Question
Tips & Pointers
Code Examples
Software
OpenMotif
Widget Sets
GUI Toolkits & Libraries
Motif suppliers
Non-commercial S/W
Commercial Software
Multimedia
Miscellaneous
Organizations
Docs & Pubs
X/Motif Newsgroups
Security
Internationalization
Feedback
Feedback Form
Contributors
 

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?

Introduction

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.

Creating a Popup Menu

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 a Popup Menu

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);

Popping up the Menu (Motif 1.2)

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.

Positioning the Popup Menu

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 */
	  ...
     }

Displaying the Popup Menu

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.

A Specimen Popup Handler

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);
     }

An Example

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.

Popping up the Menu (Motif 2.1)

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.

RowColumn Enhancements

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.

Manager and Primitive Enhancements

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 ;
     }

An Example

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

 

[IST Limited]
IST Limited is the
proud sponsor of motifdeveloper.com.
 

Thanks to all the contributors to these pages.

Privacy Policy

 
"Motif" is a registered trademark of The Open Group. All other trademarks are the property of their respective owners.

Some articles/papers here have their own copyright notice. All other information is copyright ©1999-2008 IST Limited

[CommerceOne] MW3 site originally by Ken Sall of Commerce One (formerly Century Computing).