How can I make Accelerators defined in one dialog work in others?
|
7-Aug-01 16:00 GMT
|
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.
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.
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 ;
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) ;
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.
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) ;
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) ;
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) ;
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) ;
}
}
}
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) ;
}
}
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.
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
|