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 Accelerators defined in one dialog work in others?

7-Aug-01 16:00 GMT

How can I make Accelerators defined in one dialog work in other dialogs of my application ?

Question: How can I transfer controls from one window to another, such that the short cut keys defined for the functionality in the toplevel shell should work on the child windows that I create.

We can generalize here: given any object which has associated with it a well-defined simple accelerator, we can arrange for that accelerator to work across all dialogs of the application, regardless of the dialog in which the object resides.

We need to make some assumptions here. Firstly, the scheme I outline below assumes a simple accelerator sequence: a single key, which may or may not be subjected to Control, Meta, Shift, or Alt modification. We disallow multiple key sequences of the above.

Basic Mechanisms

We can break the problem down into a number of basic steps:

  • Determine the Accelerator associated with a given object, the source, whose behavior we want to replicate throughout the interface. That is, if we want to replicate a key-sequence behavior across all our dialogs, we need to know what the sequence is for a given object.
  • Deduce the KeySym/Modifier combination which describes the Accelerator sequence. Basically, we convert the accelerator into an internal form for use with subsequent routines.
  • Convert the KeySym into a KeyCode. Again, certain routines require a KeyCode for their operation.
  • Install an asynchronous (passive) Grab for the KeyCode/Modifier combination on the dialog(s) where we replicate the behavior. We register interest in the key sequence if it is issued over a given dialog.
  • Install an Event Handler on the dialog(s) to capture KeyPress events. We add a routine to process the sequence on behalf of the given shell.
  • In the event handler, call the Action associated with the source, thereby replicating the behavior from where the widget actually resides in the widget hierarchy.

In essence, the plan is to intercept the event sequence on the target dialog, and then call an appropriate Action associated with the source, thereby mimicking its activation. The appropriate Action depends on the nature of the source; for a PushButton, we would call the Activate or ArmAndActivate action. We call the Action directly, because the event sequence has not been directed through the source. We could envisage an alternative scheme whereby we use XSendEvent() to replay the captured events directly through the source. There is therefore more than one way to go about this. We prefer the simple direct calling of the widget Action table.

First Steps

Firstly, we need to define a structure to encapsulate what we know about the source of the event. In particular, given any object whose behavior we want to replicate across dialogs, we need to know and cache:

  • The KeyCode/Modifier associated with the object's accelerator. That is, what is the sequence of key events we are capturing on behalf of the source object.
  • The widget ID of the source itself. This is so we can call XtCallActionProc() on the source, with an appropriate action as parameter.
  • The Action to perform on the source when the KeyCode/Modifier is intercepted.

We therefore define the following simple structure:

    typedef struct Accelerator_s
    {
	KeyCode    keycode ;
	Modifier   modifiers ;
	Widget     source ;
	String     action ;
    } Accelerator_t, *Accelerator_p ;

Determining the Accelerator

This is straightforward: we fetch the XmNaccelerator resource from the source:

    extern Widget source ; /* A PushButton in a Menu, say */

    String accelerator = (String) 0 ;

    XtVaGetValues(source, XmNaccelerator, &accelerator, NULL) ;

Deducing the KeySym/Modifier from the Accelerator

There are a number of ways of performing this step, but the simplest is to use the internal Motif routines _XmMapKeyEvent()/_XmMapKeyEvents():

    #include <Xm/XmP.h>
    extern Boolean _XmMapKeyEvent(String        string,
				  int          *eventType,
				  unsigned     *keysym,
				  unsigned int *modifiers) ;

    extern int _XmMapKeyEvents(String      string,
			       int       **eventType,
			       KeySym    **keysym,
			       Modifiers **modifiers) ;

Note that _XmMapKeyEvent() is marked as deprecated from Motif 2.0 onwards. It is possible for the routine to have been compiled out by your operating system vendor from the native toolkit. The difference between the two implementations is that the former assumes that the accelerator string consists of a single simple key, whereas the latter allows for multiple key sequences. _XmMapKeyEvent() handles multiple key sequences by throwing away all but the first key event. We give both implementations for completeness:

    #if (XmVERSION < 2)
	int       type ;
	KeySym    keysym ;
	Modifiers modifiers ;
	Boolean   retcode ;

	retcode = _XmMapKeyEvent(accelerator, &type, &keysym, &modifiers) ;
    #else  /* (XmVERSION < 2) */
	int       *types_list ;
	KeySym    *keysyms_list ;
	Modifiers *modifiers_list ;
	int        count ;
	KeySym     keysym ;
	Modifiers  modifiers ;

	count = _XmMapKeyEvents(accelerator, &types_list, &keysyms_list, &modifiers_list) ;

	if (count > 0) {
	    /*
	    ** We are only interested in a single key sequence, so
	    ** take the head of the returned list.
	    */

	    keysym    = *keysyms_list ;
	    modifiers = *modifiers_list ;
	}
    #endif /* (XmVERSION < 2) */

Note that _XmMapKeyEvent() may or may not have a function prototype defined in <Xm/XmP.h> - it depends on your version of Motif. Further, _XmMapKeyEvents() generally has a function prototype defined in MapEventsI.h - an internal header not publically installed from the release. If you are using a compiler with strict ANSI prototyping, you may need to declare the relevant prototype yourself.

Converting from a KeySym to a KeyCode

For this, we use the low level Xlib routine XKeysymToKeyCode():

    #include <X11/Xlib.h>
    extern KeyCode XKeysymToKeyCode(Display *, KeySym) ;

    KeyCode keycode = XKeysymToKeyCode(XtDisplay(source), keysym) ;

Installing an Asynchronous Grab on a Dialog

For this, we use the X Toolkit Intrinsics routine XtGrabKey():

    #include <X11/Intrinsic.h>
    extern void XtGrabKey(Widget    widget,
			  KeyCode   keycode,
			  Modifiers modifiers,
			  Boolean   owner_events,
			  int       pointer_mode,
			  int       keyboard_mode) ;

    extern Widget shell ; /* Where we want events replicated */

    XtGrabKey(shell, keycode, modifiers, False, GrabModeAsync, GrabModeAsync) ;

Adding an Event Handler to process incoming Events

Now that we have set the system up so that specific key sequences are trapped by the target shell, we need to process them. To do this, we firstly need to allocate the necessary data structure which controls the source of the event, and then add an event handler.

    Accelerator_p accelerator = (Accelerator_p) XtMalloc((unsigned) sizeof(Accelerator_t)) ;

    accelerator->keycode   = keycode ;
    accelerator->modifiers = modifiers ;
    accelerator->source    = source ;
    accelerator->action    = XtNewString(action) ;

    /*
    ** Install the Event Handler, HandleAccelerator, defined below
    */

    XtAddEventHandler(shell, KeyPressMask, False, HandleAccelerator, (XtPointer) accelerator) ;

    /*
    ** Clean up after ourselves if source goes away
    */

    XtAddCallback(source, XmNdestroyCallback, FreeAccelerator, (XtPointer) accelerator) ;

Calling the required Action

This is performed in the installed event handler, which checks that the incoming event sequence matches the recorded KeyCode/Modifiers of the source:

    static void HandleAccelerator(Widget widget, XtPointer client_data,
				  XEvent *event, Boolean *cont)
    {
	/*
	** Check that we have the right key combination
	*/

	register Accelerator_p accelerator = (Accelerator_p) client_data ;

	if ((event->xkey.state == accelerator->modifiers) &&
			  (event->xkey.keycode == accelerator->keycode)) {
	    if (XtIsSensitive(accelerator->source)) {
		/*
		** Call the required source action.
		** Assumption: the action takes no parameters.
		** We could extend the scheme to cache parameters in the
		** Accelerator_t structure, but this is not generally required
		** for simple objects like PushButtons in Menus,
		** where the ArmAndActivate action is not parameterized.
		*/

		XtCallActionProc(accelerator->source, accelerator->action,
				 event, (String *) 0, (Cardinal) 0) ;
	    }
	}
    }

Freeing Allocated Memory

Straightforwards:

    void FreeAccelerator(Widget widget, XtPointer client_data,
			 XtPointer call_data)
    {
	register Accelerator_p accelerator = (Accelerator_p) client_data ;

	if (accelerator != (Accelerator_p) 0) {
	    XtRemoveEventHandler(widget, KeyPressMask, False,
				 HandleAccelerator, (XtPointer) accelerator) ;

	    XtFree(accelerator->action) ;
	    XtFree((char *) accelerator) ;
	}
    }

An Example: Putting it all Together

The following example creates two shells, the first of which has a typical File menu. The File menu has Open/Read/Save/Save As buttons, with accelerators Ctrl-O, Ctrl-R, Ctrl-S, Ctrl-A respectively. These accelerators are installed onto the second shell using the scheme outlined above. Typing the sequences in either shell causes the application to pop up a further FileSelectionBox.

The code outlined above is encapsulated into the routine TransferAccelerator(), defined in the file accelerator_stubs.c. This routine takes three parameters:

  • The shell where the action is required
  • The object whose action is to be replicated into the shell above
  • The action to call when the accelerator associated with the object is received

For example, given a shell called "dialog_shell", where we want the accelerator associated with a PushButton called "main_fm_open" defined in another dialog's menu system, we activate action transference by the following means:

    TransferAccelerator(dialog_shell, main_fm_open, "ArmAndActivate")

That is, we call the "ArmAndActivate" action of the PushButton - and this will invoke any application-defined XmNactivateCallback routines as a side effect.

The sources consist of:

  • accelerator_code.c - the generated source code for the interface
  • accelerator.c - the main module code. Calls the generated source code, and installs accelerators
  • accelerator_stubs.c - the event handler, grab, and action code for the interface
  • accelerator.h - a header file with the extern widget declarations
  • Makefile - a general purpose Makefile for the application. Its 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.
  • accelerator.xd - the X-Designer design save file. Feel free to ignore this.

Alternatively download a tar archive, accelerators.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.

Bibliography

For a good general-purpose introduction to Translations, Accelerators, and Events, the following is recommended: in particular, Chapter 11 covers the ground given in the text above:

	Advanced Motif Programming Techniques,
	Alistair George and Mark Riches,
	Prentice Hall,
	ISBN: 0-13-219965-3
	Unfortunately this book is out of print,
	but you may be able to buy it from Amazon.com

For reference materials on the routines mentioned in the text, the following are recommended:

	X Toolkit Intrinsics Programming Manual,
	Adrian Nye and Tim O'Reilly,
	O'Reilly and Associates,
	ISBN: 1-56592-013-9
	Buy it from Amazon.com or Amazon.co.uk
	X Toolkit Intrinsics Reference Manual,
	Edited by David Flanaghan,
	O'Reilly and Associates,
	ISBN: 1-56592-007-4
	Buy it from Amazon.com or Amazon.co.uk
	Xlib Reference Manual,
	Edited by Adrian Nye,
	O'Reilly and Associates,
	ISBN: 1-56592-006-6
	Buy it from Amazon.com or Amazon.co.uk

 



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