Chapter 9. More Input Techniques

This chapter describes how to handle events with event handlers, and how to use information from the event structure inside an event handler or action routine. It also describes how to get file, pipe or socket input, how to use timeouts to call a function after a delay or at particular intervals, and how to use work procedures to do background processing. Finally, it discusses some low-level features of Xt for directly interacting with the event queue.

In addition to translations, there is a lower-level mechanism called event handlers. Event handlers can be used from application or widget code. An event handler is a function registered with XtAddEventHandler() to be called in response to a particular event or group of events (but not an event sequence or detail, as is possible with translations). This is a low-level, non-user-configurable means of getting events. Event handlers provide no additional capabilities over translations and actions. But they allow Xt to skip the step of searching a translation table before dispatching the event to an event handler. This speed advantage is possibly significant for events that come in large numbers, such as pointer motion events, or in applications with large translation tables. (Despite this speed difference, good performance in tracking pointer motion can be achieved with translations.)

Both event handlers and actions are passed as an argument the actual event structure that caused them to be invoked. Many event handlers and actions use data in the event. For example, a routine handling motion events may want to obtain the current pointer position from the event structure. After describing how to add and remove event handlers, we discuss the event structures and show a routine that uses specific event data.

Next, this chapter discusses three ways to register functions Xt will call for input other than events, since some programs do not live by X events alone.

Finally, we provide more background on how the Toolkit dispatches events from XtAppMainLoop(), and describe the low-level routines that can be used to construct your own main loop. We also describe Xt's event filters, which are controlled by flags in the Core class structure. These filters tell Xt whether or not to compress multiple motion, enter, leave, or expose events occurring in the same widget.

Event Handlers

An event handler is a function you provide to handle a particular type of event or group of event types. You register this function with a call to XtAddEventHandler() or XtInsertEventHandler(), specifying the widget in which the events are to be monitored. The difference between these two functions is described below.

You can later stop the function from being called with XtRemoveEventHandler(). On any widget, you can register as many event handlers as you want, each for the same or for different types of events. When more than one routine is registered for an event, the order in which they are invoked is undefined.

Within Motif applications, event handlers have one common use; they are used to pop up Motif popup menus. A popup menu is mapped where the user presses a mouse button in a particular widget, usually the application's main window. Since the application's main window probably doesn't have an XmNactivateCallback resource, the function that pops up the menu can't be registered as a callback. It can be registered as an event handler, however, because this requires no prior agreement from the widget. This function could also be added as an action from the application, but the required translations could interfere with the operation of the main window if it already uses ButtonPress events. Event handlers have the advantage that they work in parallel with Xt's other event dispatching mechanisms, and therefore they don't interfere. Example 9-1 shows how a typical application uses an event handler. The meaning of each argument will be described in a moment.

Example 9-1. Using an event hander to pop up a popup menu

static void
PostMenu (w, client_data, event)
Widget         w;
XtPointer      client_data;
XEvent         *event;
{
    Widget popup = (Widget) client_data;
    if (event->button != Button3)
        return;
    XmMenuPosition(popup, event);
    XtManageChild(popup);
}
main(argc, argv)
int argc;
char **argv;
{
    Widget mainW, Menu;
      .
      .
      .
    Menu = XmCreatePopupMenu(mainW, "popMenu", NULL, 0);
    XtAddEventHandler(mainW,     /* events sent to this widget */
            ButtonPressMask,     /* events desired */
            False,               /* non-maskable events */
            PostMenu,            /* function */
            Menu);               /* client_data passed to function */
      .
      .
      .
}

Within widget code, event handlers are used for various special requirements of individual widgets. For example, RowColumn uses an event handler to implement keyboard mnemonics in menus. Manager uses event handlers to generate synthetic events for its gadget children. (Since gadgets do not have windows, they would normally not get EnterNotify and LeaveNotify events. So the Manager widget class keeps track of the pointer position, and when the pointer enters one of the gadget children, Manager sends that child a synthetic EnterNotify event.)

In general, however, event handlers are infrequently used in application or widget code. None of the Athena widgets use them, and, out of the 40 applications in MIT's core distribution, only xterm and xman use them. As mentioned earlier, event handlers are useful for handling high-volume events, such as MotionNotify events, with maximum speed. An event handler for motion events would probably be the best way to implement a drawing program. However, translation tables provide good speed even for motion events on most systems.

Event handlers would also be useful when the type of events being handled changes frequently, because there is some overhead involved in compiling and merging translation tables. Event handlers can also be used to handle events for which the user-configurability of translations is not needed or wanted, such as EnterWindow, LeaveWindow, FocusIn, and FocusOut events.

Another possible use of event handlers is to speed the handling of certain events when there is a very large translation table. For example, editors typically have a large number of translations for various key combinations. If an editor was to accept pointer motion as well (to allow drawing of simple graphics), it might pay to handle motion events in an event handler instead of through translations.

The call to XtAddEventHandler() specifies which events trigger the event handler function. This is done with an event mask argument.[58] In an event mask, each bit represents one or more event types. Symbolic constants are defined by Xlib for each event mask bit. Multiple types of events can be selected at the same time by ORing together different masks with the bitwise OR operator (|). Note that there is not a one-to-one correspondence between event masks and event types. Some event masks select only one event, but others select multiple events. Furthermore, several of the masks select the same event type, but specify that it be delivered only when it occurs under special conditions. Table 9-1 shows the event masks and the event types they select.

Table 9-1. Event Masks and Event Types

Event Mask

Event Type

KeyPressMask

KeyPress

KeyReleaseMask

KeyRelease

ButtonPressMask

ButtonPress

ButtonReleaseMask

ButtonRelease

OwnerGrabButtonMask

n/a

KeymapStateMask

KeymapNotify

PointerMotionMask

MotionNotify

PointerMotionHintMask

ButtonMotionMask

Button1MotionMask

Button2MotionMask

Button3MotionMask

Button4MotionMask

Button5MotionMask

EnterWindowMask

EnterNotify

LeaveWindowMask

LeaveNotify

FocusChangeMask

FocusIn

FocusOut

ExposureMask

Expose

(selected in GC by

GraphicsExpose

graphics_expose component)

NoExpose

ColormapChangeMask

ColormapNotify

PropertyChangeMask

PropertyNotify

VisibilityChangeMask

VisibilityNotify

ResizeRedirectMask

ResizeRequest

StructureNotifyMask

CirculateNotify

ConfigureNotify

DestroyNotify

GravityNotify

MapNotify

ReparentNotify

UnmapNotify

SubstructureNotifyMask

CirculateNotify

ConfigureNotify

CreateNotify

DestroyNotify

GravityNotify

MapNotify

ReparentNotify

UnmapNotify

SubstructureRedirectMask

CirculateRequest

ConfigureRequest

MapRequest

(always selected)

MappingNotify

(always selected)

ClientMessage

(always selected)

SelectionClear

(always selected)

SelectionNotify

(always selected)

SelectionRequest


The events that are always selected are called nonmaskable events. These can also be handled with an event handler, but not by specifying them in the event mask. An argument to the XtAddEventHandler() call, non_maskable, is a Boolean value that, if True, specifies that the event handler should be called for nonmaskable events. This event handler then must branch according to which of the seven types of nonmaskable events it is passed. A typical nonmaskable event handler is shown in Section 9.1.2, "Adding Nonmaskable Event Handlers."

Adding Event Handlers

Event handlers are added with a call to XtAddEventHandler() or XtInsertEventHandler(). XtAddEventHandler() takes five arguments: the widget for which the handler is being added, an event mask, a flag that specifies whether or not this handler is for nonmaskable events (see below), the name of the handler, and optional client data. XtInsertEventHandler() takes these and one additional argument: the position, either XtListTail or XtListHead.

A list of event handlers can be registered for the same event; but the same function will appear only once in the list with the same client_data. If the same function/client_ data pair is registered again with XtAddEventHandler(), nothing will happen except that the event mask for that function may change. But if the same function/client_data pair is registered again with XtInsertEventHandler(), the function will be moved to the beginning or the end of the function list.

XtAddEventHandler() or XtInsertEventHandler() may be called before or after a widget is realized. In application code, this means the call can appear anywhere before XtAppMainLoop(). In a widget, XtAddEventHandler() or XtInsertEventHandler() calls are placed in the initialize or realize methods.

Example 9-2 shows the code from xterm that registers an event handler for FocusIn and FocusOut events, and a gutted version of the event handler itself.

Example 9-2. Registering an event handler, and the handler function itself

extern void HandleFocusChange();
static void VTInitialize (request, new)
XtermWidget request, new;
{
    .
    .
    .
    XtAddEventHandler(topLevel,    /* widget */
            FocusChangeMask,       /* event mask */
            False,                 /* non-maskable events */
            HandleFocusChange,     /* event handler */
            (Opaque)NULL);         /* client_data */
    .
    .
    .
}
/*ARGSUSED*/
void HandleFocusChange(w, unused, event, continue_to_dispatch)
Widget w;
register XFocusChangeEvent *event;
XtPointer unused;                  /* client_data */
Boolean *continue_to_dispatch;
{
    if (event->type == FocusIn) {
                                   /* process FocusIn */
    .
    .
    .
    }
    else {
                                   /* process FocusOut */
    .
    .
    .
    }
    /*
     * If subsequent event handlers registered for this event
     * should not be called, set *continue_to_dispatch = False;
     * This is not recommended.
     */
}

In typical usage, either the event mask argument is a mask and the non_maskable argument is set to False, or the event mask argument is set to zero and non_maskable is set to True. Example 9-1 demonstrated the former case; now we'll look at the latter.

Adding Nonmaskable Event Handlers

The non_maskable argument of XtAddEventHandler() specifies whether the specified event handler should be called in response to the events that can't be selected as described above. The nonmaskable events are GraphicsExpose, NoExpose, MappingNotify, SelectionClear, SelectionRequest, SelectionNotify, and ClientMessage. The first two of these events are selected using the graphics_exposure component of the GC, and the rest are always sent to the client whenever they occur. MappingNotify is automatically handled by Xt, so it isn't passed to event handlers and you don't need to worry about it. The selection events are described in Chapter 11, Interclient Communications.

Because there are several nonmaskable event types, a nonmaskable event handler must be sure to branch according to the type of event, and throw away any event types not handled. You need not have all the code to handle all the types in a single event handler. Instead, you can handle each type in a separate handler, each registered separately. However, each handler would still need to check the event type because the entire list of them would be called for every nonmaskable event.

Example 9-3 shows the registration of a nonmaskable event handler and the handler itself, from xman.

Example 9-3. Adding a nonmaskable event handler

static void
Realize(w, valueMask, attributes)
register Widget w;
Mask *valueMask;
XSetWindowAttributes *attributes;
{
    .
    .
    .
    XtAddEventHandler(w, 0, True,
            GExpose, NULL); /* Get Graphics Exposures */
} /* Realize */
/* ARGSUSED */
static void
GExpose(w, client_data, event)
Widget w;
XtPointer client_data;
XEvent *event;
{
    if (event->type == GraphicsExpose)
        Redisplay(w, event, NULL); /* call the expose method directly */
}


This event handler is sometimes used because Xt does not normally call the expose method in response to GraphicsExpose events. But in R4, another way to accomplish this has been introduced. If the compress_exposure field in the Core structure is set to (XtExposeCompressMultiple | XtExposeGraphicsExpose), Xt will call the expose method with these events.

Removing Event Handlers

XtRemoveEventHandler() takes the same arguments as XtAddEventHandler(); if there are parameter mismatches, the call is quietly ignored. For example, the client data argument may be used to distinguish between different event handlers; if the client data argument does not match that which was passed in the XtAddEventHandler(), then XtRemoveEventHandler() will do nothing. XtRemoveEventHandler() is also silent about failing to remove a handler that was never added or a handler that was incorrectly specified.

Adding Raw Event Handlers

Xt also allows you to add event handlers without actually selecting events. The main purpose of this feature is for Xt to register functions before widgets are realized (because events can't be selected until windows are created during realization). Event handlers registered without selecting events are called raw event handlers, and are added with XtAddRawEventHandler() or XtInsertRawEventHandler(), which have the same calling sequences as XtAddEventHandler() and XtInsertRawEventHandler(). The event mask indicates which events the handler will be called in response to, but only when these events are selected elsewhere. Raw event handlers are supported mostly because they are used inside Xt. They are mentioned here only for completeness--you are unlikely to need them.

A raw event handler might be used to “shadow” another event handler (both added with the same event mask), such that until a primary event handler is added, the shadow handler will never be called. The primary handler will be added with XtAddEventHandler(), which will select events, and then both handlers will be called when the appropriate events occur.

However, the “shadowing” technique is not necessary to assure that multiple calls to XtAddEventHandler() don't result in wasted XSelectInput() calls in which the event mask has not changed. Xt keeps a cache of the event masks of each widget, and calls XSelectInput() only when it is necessary to change the window's event mask attribute in the server.

Raw event handlers are removed with a call to XtRemoveRawEventHandler().

Writing Routines That Use Specific Event Data

An event is a packet of information that the server sends to the client. Xlib takes this packet from the network and places it into an XEvent structure and places it on a queue until the client program requests it. Xt requests events from this queue and dispatches the event to the appropriate action routine or event handler for the widget in which the event occurred. The event itself is passed as an argument to the routine.[59]

Actually, XEvent is a C-Language union of many event structures all the same size but with some different field names. The first member of the union, and of any of the individual event structures, is the event type. Table 9-2, later in this section, lists the event types and the matching event structure types.

Many action routines are intentionally written not to depend on the detailed information inside any particular type of event, so that the user can specify translations to call the action in response to different types of events. For example, it is useful for an action routine normally triggered by a pointer click to work when called in response to a key instead. Such an action should not depend on the event structure fields unique to button events.

However, many other action routines, and most event handlers, do use the detailed information inside event structures. The first member, type, identifies which type of event this structure represents, and hence implies which other fields are present in the structure.

To access event structure fields other than type you need to cast XEvent into the appropriate event structure type. If you are expecting only one type of event to trigger this action, then you can simply declare the argument as the appropriate type, as shown in Example 9-4.

Example 9-4. Casting the event structure by declaring action routine arguments

/*ARGSUSED*/
static void
ActionA(w, event, params, num_params)
Widget w;
XEvent *event;
String *params;
Cardinal *num_params;
{
   if ((event->type != ButtonPress) && (event->type != KeyPress)) {
       XtWarning("ActionA invoked by wrong event type.");
      /* possible exit here */
   }
      .
      .
}

When an action routine or event handler depends on the fields in a particular event structure, it is a good practice to check the event type in that action unless you are sure that the user can't change the translation (and thus the events used to invoke the action).

If you want the same code called for two event types, then you would do better to create two separate translations and two separate actions that each call a common routine. However, it is sometimes more convenient to have an action called by two different events. Example 9-5 shows the ToggleCell action from the BitmapEdit widget, which is called in response to either MotionNotify or ButtonPress events. This action inverts a pixel in the bitmap either if the pointer is clicked on a cell in the widget, or if it is dragged across the cell with the pointer buttons held down.

Example 9-5. Handling multiple event types in an action routine

static void
ToggleCell(w, event)
Widget w;
XEvent *event;
{
   BitmapEditWidget cw = (BitmapEditWidget) w;
   static int oldx = -1, oldy = -1;
   GC gc;
   int mode;
   int newx, newy;
   if (event->type == ButtonPress) {
       newx = (w->bitmapEdit.cur_x + ((XButtonEvent *)event)->x) /
               w->bitmapEdit.cell_size_in_pixels;
       newy = (w->bitmapEdit.cur_y + ((XButtonEvent *)event)->y) /
               w->bitmapEdit.cell_size_in_pixels;
   }
   else if  (event->type == MotionNotify) {
       newx = (w->bitmapEdit.cur_x + ((XMotionEvent *)event)->x) /
               w->bitmapEdit.cell_size_in_pixels;
       newy = (w->bitmapEdit.cur_y + ((XMotionEvent *)event)->y) /
               w->bitmapEdit.cell_size_in_pixels;
   }
   else
       XtWarning("BitmapEdit: ToggleCell called with wrong event\
               type);
   .
   .
   .
}


Notice that some code is repeated to cast the event structure to the two different event types. With the current MIT implementation of Xlib, the positions of the x and y fields in the XButtonEvent and XMotionEvent structures are the same, and therefore this casting is unnecessary on many compilers. However, for strict ANSI C conformance these casts are necessary, and furthermore it is improper to depend on any particular implementation of Xlib. The order of the fields in one of these events could be different in some vendor's implementation of Xlib.

Event Types and Structure Names

Table 9-2 lists the event types and the matching event structure types. The event descriptions in the table will give you a general idea of what each event is for. Many of these events are not often used in applications, and more of them are automatically handled by Xt. We've already discussed how to use the most common event types and their abbreviations in translation tables in Chapter 8, Events, Translations, and Accelerators. Appendix C, Event Reference, in Volume Five, X Toolkit Intrinsics Reference Manual, provides a complete reference to the circumstances under which each event is generated, what it is for, and the fields in each of the event structures. You will need this information to write action routines that use event-specific data.

Table 9-2. Event Types and Event Structures

Event Type

Structure

Description

KeyPress

XKeyPressedEvent

Key pressed.

KeyRelease

XKeyReleasedEvent

Key released.

ButtonPress

XButtonPressedEvent

Pointer button pressed.

ButtonRelease

XButtonReleasedEvent

Pointer button released.

KeymapNotify

XKeymapEvent

State of all keys when pointer entered.

MotionNotify

XPointerMovedEvent

Pointer motion.

EnterNotify

XEnterWindowEvent

Pointer entered window.

LeaveNotify

XLeaveWindowEvent

Pointer left window.

FocusIn

XFocusInEvent

This window is now keyboard focus.

FocusOut

XFocusOutEvent

This window was keyboard focus.

Expose

XExposeEvent

Part of window needs redrawing.

GraphicsExpose

XGraphicsExposeEvent

Source of copy unavailable.

NoExpose

XNoExposeEvent

Source of copy available.

ColormapNotify

XColormapEvent

Window's colormap changed.

PropertyNotify

XPropertyEvent

Property value changed.

VisibilityNotify

XVisibilityEvent

Window has been obscured.

ResizeRequest

XResizeRequestEvent

Redirect resize request to window manager.

CirculateNotify

XCirculateEvent

Stacking order modified.

ConfigureNotify

XConfigureEvent

Window resized or moved.

DestroyNotify

XDestroyWindowEvent

Window destroyed.

GravityNotify

XGravityEvent

Window moved due to win gravity attribute.

MapNotify

XMapEvent

Window mapped.

ReparentNotify

XReparentEvent

Window reparented.

UnmapNotify

XUnmapEvent

Window unmapped.

CirculateRequest

XCirculateRequestEvent

Redirect stacking order change to window manager.

ConfigureRequest

XConfigureRequestEvent

Redirect move or resize request to window manager.

MapRequest

XMapRequestEvent

Redirect window map request to window manager.

MappingNotify

XMappingEvent

Keyboard mapping changed.

ClientMessage

XClientMessageEvent

Client-dependent.

SelectionClear

XSetSelectClearEvent

Current owner is losing selection.

SelectionNotify

XSelectionEvent

Selection is ready for requestor.

SelectionRequest

XSelectionRequestEvent

Request for selection to current owner.


File, Pipe, and Socket Input

XtAppAddInput() allows a program to obtain input from a file. This is not merely reading the file once, but monitoring it for further activity. Under UNIX this can be used to get input from pipes and sockets, since they are variations of files. We will demonstrate getting file and pipe input in this section.

The XtAppAddInput() routine takes four arguments: a file descriptor, a flag (see below), your function, and client_data.

XtAppAddInput() returns an ID that uniquely identifies the XtAppAddInput() request. You can use the ID to cancel the request later with XtRemoveInput().

One argument of XtAppAddInput() is a file descriptor (this file must be open before calling XtAppAddInput()). Since implementation of files varies between operating systems, the actual contents of the parameter passed as the file descriptor argument to these routines is operating system-dependent. Therefore, this code is inherently nonportable.

Possible values for the mask and their meanings are as shown in Table 9-3.

Table 9-3. Other Input Source Masks

Mask

Description

XtInputReadMask

File descriptor has data available.

XtInputWriteMask

File descriptor available for writing.

XtInputExceptMask

I/O errors have occurred (exceptions).

XtInputNoneMask

Never call function registered.

Calling these argument values masks is something of a misnomer, since they cannot be ORed together. However, you can call XtAppAddInput() additional times to register a separate function (or the same function) for each of these masks on the same file descriptor.

Getting File Input

In Example 9-6, a program called xfileinput reads new characters from a file whenever they appear. In other words, the program will initially print to the standard output the contents of the file specified on the command line, and it will print any characters that are later appended to that file. Try the program xfileinput as follows:

echo "test string" > testfile
xfileinput testfile &
echo "more text" >> testfile

A program such as this functions similarly to the UNIX command tail -f. It could be used to monitor system log files, or other similar files that grow.

The code shown in Example 9-6 opens the file and calls XtAppAddInput() in main. The get_file_input function registered with XtAppAddInput() reads and prints characters from the file.[60]

Example 9-6. Getting file input with XtAppAddInput

/* header files */
  .
  .
  .
/* ARGSUSED */
get_file_input(client_data, fid, id)
XtPointer client_data;    /* unused */
int *fid;
XtInputId *id;
{
    char buf[BUFSIZ];
    int nbytes;
    int i;
    if ((nbytes = read(*fid, buf, BUFSIZ)) == -1)
        perror("get_file_input");
    if (nbytes)
        for (i = 0; i < nbytes; i++)
            putchar(buf[i]);
}
main(argc, argv)
int argc;
char **argv;
{
    XtAppContext app_context;
    Widget topLevel, goodbye;
    FILE *fid;
    String filename;
	XtSetLanguageProc(NULL, (XtLanguageProc)NULL, NULL);
    topLevel = XtVaAppInitialize(&app_context, "XFileInput", NULL,
            0, &argc, argv, NULL, NULL);
    if (argv[1] == NULL) {
        fprintf(stderr, "xfileinput: filename must be specified on\
                command line.); \n"
        exit(1);
    }
    filename = argv[1];
      .
      .
      .
    /* open file */
    if ((fid = fopen(filename, "r")) == NULL)
        fprintf(stderr, "xfileinput: couldn't open input file. \n");
    /* register function to handle that input, NULL arg
     * is client_data */
    XtAppAddInput(app_context, fileno(fid), XtInputReadMask,
            get_file_input, NULL);
    XtRealizeWidget(topLevel);
    XtAppMainLoop(app_context);
}


The function registered with XtAppAddInput() is called with client_data (used for passing in any application data), a pointer to the file descriptor, and the ID of the XtAppAddInput() request. You can use a call to XtRemoveInput() in the function registered with XtAppAddInput() if that function is only to be called once. One argument of the XtRemoveInput() call is the ID of the XtAppAddInput() request.

Under some operating systems, the function registered with XtAppAddInput() is called very frequently even when no new input is available. This is because Xt makes a system call to detect whether the file is “ready for reading,” and some operating systems say the file is ready even when there is nothing new to read. The example shown above works, but it loads down the system much more than necessary. You may wish to check the file every quarter second instead of continuously, by adding and removing your input handler periodically using timeouts (as described in Section 9.4, "Timeouts"). Under UNIX, this problem should happen only for files, not for pipes or sockets.

Getting Pipe Input

The code to get pipe input is almost identical to the code just shown that gets file input. The only difference is that we use popen instead of fopen, and change the various error messages. Now instead of treating the command-line argument as a filename, it is treated as a program run under a shell. This program's output is piped into our application. For example, here is an example of how to invoke this version of xpipeinput:

spike% xpipeinput "cal 11 1989"
    November 1989
 S  M Tu  W Th  F  S
          1  2  3  4
 5  6  7  8  9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30
(Program continues to monitor pipe for further input until application exits.)

Note that xpipeinput is reading the string “cal 11 1989” from the command line, invoking a shell, running the command specified by the string under this shell, reading the output of the shell, and then printing it on the standard output. This is an easy way to use all kinds of shell scripts and utilities from within a program.

If you want your application to accept standard input, this is even easier. Remove the code that reads the filename from the command line and remove the popen call to open the pipe, since the pipe from stdin is always open. Then use the XtAppAddInput() function as shown in Example 9-7.

Example 9-7. Reading stdin from an Xt application

    XtAppAddInput(app_context, fileno(stdin), XtInputReadMask,
            get_file_input, NULL);

Once you have done this, you can invoke xpipeinput as follows:

spike% cal 11 1989 | xpipeinput
    November 1989
 S  M Tu  W Th  F  S
          1  2  3  4
 5  6  7  8  9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30
(Program continues to monitor pipe for further input until application exits.)

Note that in this case, xpipeinput is reading directly from stdin, and then printing the output to stdout. With more code, it could display this calendar in a Text widget instead.

Timeouts

A program may wish to be notified when a period of time has elapsed, while being able to do other things in the meantime. For example, a clock widget requires a periodic nudge to change the time it is displaying, but must also be able to redisplay itself at any time in case of exposure.

This is done by using XtAppAddTimeOut(). This routine is passed a time interval in milliseconds, and the address of a function to be invoked when the time interval expires. As usual, a client_data argument can also be registered. The XtAppAddTimeOut() routine returns a handle that can be used to cancel the timeout before it triggers, if necessary.

A timeout is automatically removed when the registered function is called. Therefore, to have a function called repeatedly, every N milliseconds, the registered function must add the timeout again by calling XtAppAddTimeOut().

One of the major applications of timeouts other than clocks is in real-time games. Figure 9-1 shows the appearance of a game called xtetris after it has been played for a couple of minutes.

Figure 9-1. xtetris in play

The object of the game is to steer falling blocks and rotate them so that they fit well into the existing fallen blocks.[61] The game is over when the blocks pile up to the top of the window. Every time a row is completely filled, it is removed and all the blocks above it move down one row. The window in which the blocks fall is a specialized widget. This game uses timeouts to time the falling of the blocks.

Example 9-8 is an excerpt from a widget used by xtetris that adds the timeout. The timeout function itself is also shown.

Example 9-8. xtetris: registering a timeout and the timeout function

static XtIntervalId timer;
static void
StartBlock(w)
TetrisWidget w;
{
    w->tetris.cur_x = 9;
    w->tetris.cur_y = 0;
    w->tetris.type = PickType();
    DrawBlock(w, DRAW);
    timer = XtAppAddTimeOut(XtWidgetToApplicationContext(w),
            w->tetris.delay, MoveDown, w);
}
static void
MoveDown(w, id)
BitmapEditWidget w;      /* client_data */
XtIntervalID *id;
{
    if (CanMoveDown(w)) {
        drawBlock(w, UNDRAW);
        w->tetris.cur_y++;
        DrawBlock(w, DRAW);
        CopyBlock(w);
        timer = XtAppAddTimeOut(XtWidgetToApplicationContext(w),
                w->tetris.delay, MoveDown, w);
    }
    else { /*block has hit bottom or other stationary blocks*/
        UpdateCellArray(w);
        KillRows(w);
        Score(w);
        w->tetris.delay -= 5;
        StartBlock(w);
    }
}


Notice that in widget code, the application context is specified using XtWidgetToApplicationContext().

Notice also that a timeout function is called with only one argument, client_data. Inside a widget, this argument is commonly used to pass in the widget instance pointer. Also notice that every time a block hits the bottom, the instance variable delay is decremented by 5, which reduces the number of milliseconds of delay used when XtAppAddTimeOut() is next called. In other words, the blocks fall progressively faster.

xtetris also needs to remove a timeout in one of its routines. The user can “drop” a block to score extra points (if there is enough time). Whenever a block is dropped, the block is immediately moved down as far as it will go, and a new block is started. If the Drop action did not remove the timeout, the new block would be started with a new timeout while an existing timeout was already in force. This would mean that the MoveDown timeout function would be invoked twice in quick succession when each of these timeouts expired. Example 9-9 shows the XtRemoveTimeOut() call in the Drop action.

Example 9-9. xtetris: calling XtRemoveTimeOut

/*ARGSUSED*/
static void
Drop(tw, tevent, params, num_params)
Widget tw;
XEvent *tevent;
String *params;
Cardinal *num_params;
{
     TetrisWidget w = (TetrisWidget) tw;
     XButtonEvent *event = (XButtonEvent *) tevent;
     XtRemoveTimeOut(timer);
     while (CanMoveDown(w)) {
         DrawBlock(w, UNDRAW);
         w->tetris.cur_y++;
         DrawBlock(w, DRAW);
         CopyBlock(w);
     }
     UpdateCellArray(w);
     KillRows(w);
     score++;
     Score(w);
     w->tetris.delay -= 5;
     StartBlock(w);
}


Notice that the timer ID returned from the calls to XtAppAddTimeOut() is a global variable. Xt calls the timeout function with only one argument, and that argument passes in the widget instance pointer. We could have created a structure containing the widget instance pointer and the timer ID and passed its pointer to the timeout function. But this wouldn't help, because the action routine in which we remove the timeout is passed with no client_data argument. (It has string parameters, but these are hardcoded in the actions table.) Therefore, we are forced to have a global variable for the timer ID.

Note that between the time when the timeout is registered and when it triggers, the application processes events in XtAppMainLoop(). Therefore, all the widget's actions and expose method are in operation between the invocations of the timeout function.

Visibility Interest

Timeouts operate regardless of the visibility of the application. Since it is pointless for most games to continue operating while obscured, it makes sense to remove the game's timeouts when the game is partially or fully obscured (or iconified). To do this, you can set the visible_interest field in the Core class structure to True, and then check the visible field of the Core instance structure periodically. When the application is fully obscured, you add a separate timeout to continue testing the visibility status. When the visibility status is satisfactory once again, the game can add its timeout again. All these changes are in the widget's .c file. First we set the visible_interest field to True in the Core structure:

TetrisClassRec tetrisClassRec  = {
     /* core_class fields */
    .
    .
    .
     /* visible_interest            */ True,
    .
    .
    .
}

Second we change:

timer = XtAppAddTimeOut(XtWidgetToApplicationContext
    (w), w->tetris.delay, MoveDown, w);

to:

if (w->core.visible == False)
            timer = XtAppAddTimeOut(XtWidgetToApplicationContext
                (w) 250, CheckVisibility, w);
        else
            timer = XtAppAddTimeOut(XtWidgetToApplicationContext
(w)
w->tetris.delay, MoveDown, w);

And finally, we add the timeout function that continues to check the visibility status.

static void
CheckVisibility(w)
BitmapEditWidget w;    /* client_data */
{
    if (w->core.visible == False)
        timer = XtAppAddTimeOut(250, CheckVisibility, w);
    else
        timer = XtAppAddTimeOut(w->tetris.delay,
                MoveDown, w);
}

Unfortunately, the Core visible field is True even if a tiny sliver of the widget is visible. The only way to get around this is to add an event handler (or translation) for VisibilityNotify events and to add an instance variable to maintain the visibility state. The event handler or action would check the state field of the event, and put the game into hibernation if the window is only partially obscured. However, this approach has the opposite problem; it disables the game even when only a sliver is obscured.

There is nothing you can do about the game continuing to run while being moved or resized with the window manager. However, using the Core visible_interest field does stop the game when it is iconified.

Work Procedures

A work procedure is an application-supplied function that is executed while an application is idle waiting for an event. Work procedures are registered with XtAppAddWorkProc(). They can perform any calculation that is short enough that the routine will return in a small fraction of a second. If the work procedure is too long, the user's response time will suffer.

If a work procedure returns True, then Xt will remove it and it will not be called again. But if one returns False, it will be called repeatedly every time there is idle time, until the application calls XtRemoveWorkProc(). A work procedure would return True if it performs a one-time setup such as creating a popup widget. It would return False if it were continuously updating a disk file as security against a system crash or server connection failure.

You can register multiple work procedures, and they will be performed one at a time. The most recent work procedure added has the highest priority. Therefore, for example, if you want to create ten popup widgets during idle time, you should add ten work procedures. The popup that you expect to need first should be added in the last work procedure registered.

The call to register a work procedure is shown in Example 9-10.

Example 9-10. Registering an Xt work procedure

static Boolean create_popup();
  .
  .
  .
main(argc, argv)
int argc;
char **argv;
{
    XtAppContext app_context;
    XtWorkProcId popup_work_ID;
    Widget topLevel;
       .
       .
    /* XtAppInitialize, create widgets, etc. */
       .
       .
    popup_work_ID = XtAppAddWorkProc(app_context,
            create_popup, topLevel);
       .
       .
    /*
     * popup_work_ID not actually needed because work proc
     * unregisters itself by returning True.
     */
    XtRealizeWidget(topLevel);
    XtAppMainLoop(app_context);
}

Notice that XtAppAddWorkProc() returns an ID of type XtWorkProcId, which is used only in any subsequent call to XtRemoveWorkProc(). You can cast the returned value to void if you do not intend to explicitly remove the work procedure.

The client_data argument passes application data into the work procedure. It is used just like the same argument in callback functions. Example 9-11 shows a work procedure to create a popup widget.

Example 9-11. A work procedure to create a popup widget

Widget getHelp;
/* work procedure */
Boolean
create_popup(client_data)
XtPointer client_data;
{
    Widget parent = (Widget) client_data;
    Widget helpBox;
    Widget temp;
    helpBox = XmCreateMessageDialog(parent, "message", NULL, 0);
    temp = XmMessageBoxGetChild (helpBox, XmDIALOG_CANCEL_BUTTON);
    XtUnmanageChild (temp);
    temp = XmMessageBoxGetChild (helpBox, XmDIALOG_HELP_BUTTON);
    XtUnmanageChild (temp);
    /* arrange for getHelp button to pop up helpBox */
    XtAddCallback(getHelp, XmNactivateCallback, PopupDialog, helpBox);
    return(True);   /* makes Xt remove this work proc automatically */
}

Remember that Xt cannot interrupt a work procedure while it is running; the procedure must voluntarily give up control by returning, and it must do so quickly to avoid slowing user response.

If your application has any big jobs that it must do, the only way to do them without resulting in long delays is to write the code that does the big job in a way that voluntarily interrupts itself and saves its state so that it can be restarted where it left off. One way to run such a task is as a work procedure, but this is only useful for tasks that need not be done before other application tasks can begin. If you want Expose processing to continue but no other application task to begin until your task is done, you would use the same type of code but place low-level event management routines in it, or make the rest of the application insensitive until the task is done.

Low-level Management of the Event Queue

As you know, an X Toolkit application simply calls XtAppMainLoop() to begin processing events. XtAppMainLoop() itself is quite simple: it consists of an infinite loop calling two lower-level routines, XtAppNextEvent() and XtDispatchEvent(). XtAppNextEvent() extracts the next event from the application's event queue; XtDispatchEvent() actually uses the event to invoke the appropriate actions or event handlers. (The functions registered by XtAppAddInput() and XtAppAddTimeOut() are dispatched directly by XtAppNextEvent(); if no events are available, XtAppNextEvent() flushes the X output buffer, and calls any work procedures registered by XtAppAddWorkProc().)

An application can provide its own version of this loop, as shown in Example 9-12. For example, it might test some application-dependent global flag or other termination condition before looping back and calling XtAppNextEvent(). Or for fine-grained debugging, it might be worthwhile to insert a routine that prints out the type of each event dispatched.

Example 9-12. Skeleton of a custom main loop

void MyMainLoop(app_con)
XtAppContext app_con;
{
     XEvent event;
     for (;;) {
         XtAppNextEvent(app_con, &event);
         XtDispatchEvent(&event);
    /* Do application-specific processing here */
     }
}


XtPending and XtPeekEvent

All event sources depend on idle time in the application to return to XtAppMainLoop() where Xt can check to see if input is available from any of the various sources. If an application has long calculations to make, the program may not return to XtAppMainLoop() frequently enough to detect important input in a timely fashion. The application itself should, if possible, suspend lengthy calculations for a moment to check whether input is available. Then it can determine whether to process the input before continuing, or finish the calculation.

To detect whether input from any input source is available, you can call XtPending(). This function returns a mask composed of a bitwise OR of the symbolic constants XtIMXEvent, XtIMTimer, and XtIMAlternateInput. These constants refer to X events, timer events, and alternate input events, respectively.

To find out what the first event in the queue contains, you can call XtPeekEvent(). This function returns an event structure without removing the event from Xlib's queue.

It is also possible to remove and process a single event. XtAppProcessEvent() combines some (but not all) of the functions from XtAppNextEvent() and XtDispatchEvent(). That is, while XtAppNextEvent() takes the next event from the queue, whatever it is, XtAppProcessEvent() allows you to specify as a mask a bitwise OR of the symbolic constants XtIMXEvent, XtIMTimer, and XtIMAlternateInput. This lets you select only some of these event types for processing. In addition, XtAppProcessEvent() actually calls XtDispatchEvent() to dispatch X events, so only this one call is necessary.

Event Filters

As you saw in Chapter 6, Inside a Widget, the class structure contains three Boolean fields that control Xt's event filters. These are compress_motion, compress_enterleave, and compress_exposure. Widgets set these fields to True when repeated events of these types are unwanted. Each would be used in different situations. If turned on, they tell Xt to search Xlib's queue for a certain event sequence and then remove repeated occurrences of those events from the queue.

When the compress_motion filter is set to True, and there is a series of MotionNotify events on the queue (which occurs when the application gets behind in processing them), the filter throws out all but the last one (the most recent position). This is useful for widgets that need the most up-to-date position but do not need a complete history of pointer positions.

The compress_enterleave filter throws out all EnterNotify/LeaveNotify pairs on the same window in which there are no intervening events. This would be used by a widget that is interested in enter and leave events, but not if the application falls behind. For example, even the Command widget sets compress_enterleave to True. It highlights its border when the pointer enters, and clears it when the pointer leaves. But if for some reason the widget falls behind and has not highlighted the border by the time the LeaveNotify event arrives with no intervening events, the border will not be highlighted. To see this, move the pointer quickly across a large panel of Command widgets such as in xmh, and you will see that not all of them draw and then undraw the border.

The symbols used for setting the compress_exposure filter have changed in R4. If the field of this name in the Core class structure is set to False or XtExposeNoCompress, a widget's expose method is called once in response to each Expose event in a contiguous series. Each event specifies a different rectangle of the widget that needs redrawing.

With compress_exposure set to True or XtExposeCompressSeries, however, a contiguous series of events resulting from one user action is compressed into a single modified Expose event and the expose method is called only once. This modified Expose event contains the bounding rectangle of the union of all the rectangles in the individual events. In this case the expose method is also passed an Xlib Region that describes in detail the area exposed. Probably the most useful value for compress_exposure is XtExposeCompressMultiple, which compresses all the contiguous events resulting from multiple contiguous user actions.

When ORed with any of the XtCompress* symbols, the XtExposeGraphicsExpose symbol causes Xt to call the expose method with any GraphicsExpose events that occur. Remember that you must set the graphics_exposures component to True in the GC used in XCopyArea() or XCopyPlane() in order to get GraphicsExpose events. XtExposeGraphicsExposeMerged, when ORed with an XtCompress* symbol, merges contiguous Expose and GraphicsExpose events together before calling the expose method.

The XtExposeNoExpose symbol causes Xt to dispatch NoExpose events to the expose method. This doesn't make much sense; if you need NoExpose events it is better to add an event handler or translation to handle them.

The remaining symbol is XtExposeCompressMaximal. This symbol is dangerous and usually should not be used: it merges non-continuous Expose events into one event before calling the expose method. This is unwise because the intervening events could be ConfigureNotify events that change the size of the window. When this happens, the application will redraw itself, then receive the ConfigureNotify, but then it will not redraw itself in the new size because the Expose event that would trigger the drawing has already been removed from the queue.

Almost all widgets except those that display a large amount of text should set this filter to XtExposeCompressMultiple. Text widgets can very efficiently redraw only the needed parts of the window because each character is in a fixed location. (Characters are in fixed locations in the Text widget because it uses fixed-width fonts--this is not applicable to widgets that display proportional fonts.) Therefore, it can efficiently process all the Expose events one at a time.

Input Sensitivity

There are times when some widgets should be insensitive to events in which they are usually interested. For example, a Command widget should be insensitive when the command that it executes is already in operation.

Widget sensitivity is inherited. For example, if a parent widget is insensitive, then its children are too. In other words, an entire box full of widgets can be set insensitive by simply setting the box widget insensitive. Note, however, that this process can be a little slow because all the widgets in the box that honor sensitivity will redraw themselves dimmed or grayed. A widget is made insensitive from an application by calling XtSetSensitive() with the sensitive argument set to False, or using XtVaSetValues() on the XmNsensitive resource (XtSetSensitive() is slightly faster).

Any widget that may need to be disabled for a time by the application should change its visible appearance when insensitive.

The widget that has one of the XtCallback* standard popup callback functions registered on its callback list will automatically be set insensitive when the callback is triggered. If the XtCallbackPopdown() callback function is registered on this widget it will automatically be set sensitive again when this callback is invoked.



[58] The event mask used in XtAddEventHandler() is the same as the one used in the Xlib call XSelectInput().

[59] We'll look at the low-level routines Xt provides for directly manipulating the event queue later in this chapter.

[60] Note that the code for opening and reading files is probably not portable to operating systems other than UNIX.

[61] This game is provided with the example source code. It is an X version of a game available on the Macintosh called Tetris, trademark of AcademySoft-ELORG, copyright and trademark licensed to Andromeda Software Ltd. The original concept of the game is by Alexi Pazhitnov and Vadim Gerasimov.