Next: , Previous: , Up: Part I Doing Interaction   [Contents][Index]


4.3 Periodic Events and Non-blocking Interaction

The interaction mentioned above is adequate for many application programs but not for all. When the program also has to perform tasks when no user action takes place (e.g., redrawing a rotating image all the time), some other means of interaction are needed.

There exist two different, but somewhat similar, mechanisms in the library that are designed specifically for generating and handling periodic events or achieving non-blocking interaction. Depending on the application, one method may be more appropriate than the other.

For periodic tasks, e.g., rotating an image, checking the status of some external device or application state etc., interaction via an idle callback comes in very handy. An idle callback is an application function that is registered with the system and is called whenever there are no events pending for forms (or application windows).

To register an idle callback, use the following routine

FL_APPEVENT_CB fl_set_idle_callback(FL_APPEVENT_CB callback,
                                    void *user_data);

After the registration, whenever the main loop (fl_do_forms()) is idle, i.e., no user action or light user action, the callback function of type FL_APPEVENT_CB is called

typedef int (*FL_APPEVENT_CB)(XEvent *xev, void *user_data);

i.e., a function with the signature

int idle_callback(XEvent *xev, void *user_data);

where user_data is the void pointer passed to the system in fl_set_idle_callback() through which some information about the application can be passed. The return value of the callback function is currently not used. xev is a pointer to a synthetic7 MotionNotify event from which some information about mouse position etc. can be obtained. To remove the idle callback, use fl_set_idle_callback() with callback set to NULL.

Timeouts are similar to idle callbacks but with somewhat more accurate timing. Idle callbacks are called whenever the system is idle, the time interval between any two invocations of the idle callback can vary a great deal depending upon many factors. Timeout callbacks, on the other hand, will never be called before the specified time is elapsed. You can think of timeouts as regularized idle callbacks, and further you can have more than one timeout callbacks.

To add a timeout callback, use the following routine

typedef void (*FL_TIMEOUT_CALLBACK)(int, void *);
int fl_add_timeout(long msec, FL_TIMEOUT_CALLBACK callback,
                   void *data);

The function returns the timeout’s ID8. When the time interval specified by msec (in milli-seconds) has elapsed the timeout is removed, then the callback function is called. The timeout ID is passed to the callback function as the first parameter. The second parameter the callback function is passed is the data pointer that was passed to fl_add_timeout().

To remove a timeout before it triggers, use the following routine

void fl_remove_timeout(int id);

where id is the timeout ID returned by fl_add_timeout(). There is also an FL_OBJECT, the FL_TIMER object, especially the invisible type, that can be used to do timeout. Since it is a proper Forms Library object, it may be easier to use simply because it has the same API as any other GUI elements and is supported by the Form Designer. See Timer Object, for complete information on the FL_TIMER object.

Note that idle callback and timeout are not appropriate for tasks that block or take a long time to finish because during the busy or blocked period, no interaction with the GUI can take place (both idle callback and timeout are invoked by the main loop, blockage or busy executing application code prevents the main loop from performing its tasks).

So what to do in situations where the application program does require a lengthy computation while still wanting to have the ability to interact with the user interface (for example, a Stop button to terminate the lengthy computation)?

In these situations, the following routine can be used:

FL_OBJECT *fl_check_forms(void);

This function is similar to fl_do_forms() in that it takes care of handling events and appropriate callbacks, but it does not block. Instead it always returns to the application program immediately. If a change has occurred in some object the object is returned as with fl_do_forms(). But when no change has occurred control is also returned but this time a NULL object is returned. Thus, by inserting this statement in the middle of the computation in appropriate places in effect "polls" the user interface. The downside of using this function is that if used excessively, as with all excessive polls, it can chew up considerable CPU cycles. Therefore, it should only be used outside the inner most loops of the computation. If all objects have callbacks bound to them, fl_check_forms() always returns NULL, otherwise, code similar to the following is needed:

obj = fl_check_forms();
if (obj == obj1)
    /* handle it */
...

Depending on the applications, it may be possible to partition the computation into smaller tasks that can be performed within an idle callback one after another, thus eliminating the need of using fl_check_forms().

Handling intensive computation while maintaining user interface responsiveness can be tricky and by no means the above methods are the only options. You can, for example, fork a child process to do some of the tasks and communicate with the interface via pipes and/or signals, both of which can be handled with library routines documented later, or use multi-thread (but be careful to limit Xserver access within one thread). Be creative and have fun.

For running external executables while maintaining responsiveness of the interface, see fl_exe_command() and fl_popen() documented later in Command Log.


Footnotes

(7)

I.e., xev->xmotion.send_event is true.

(8)

The function will not return 0 or -1 as timeout IDs, so the application program can use these values to tag invalid or expired timeouts.


Next: , Previous: , Up: Part I Doing Interaction   [Contents][Index]