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


4.5 Using Callback Functions

As stated earlier, the recommended method of interaction is to use callback functions. A callback function is a function supplied to the library by the application program that binds a specific condition (e.g., a button is pushed) to the invocation of the function by the system.

The application program can bind a callback routine to any object. Once a callback function is bound and the specified condition is met, fl_do_forms() or fl_check_forms() invokes the callback function instead of returning the object.

To bind a callback routine to an object, use the following

typedef void (*FL_CALLBACKPTR)(FL_OBJECT *obj, long argument);
FL_CALLBACKPTR fl_set_object_callback(FL_OBJECT *obj,
                                      FL_CALLBACKPTR callback,
                                      long argument);

where callback is the callback function. argument is an argument that is passed to the callback routine so that it can take different actions for different objects. The function returns the old callback routine already bound to the object. You can change the callback routine anytime using this function. See, for example, demo program timer.c.

The callback routine should have the form

void callback(FL_OBJECT *obj, long argument);

The first argument to every callback function is the object to which the callback is bound. The second parameter is the argument specified by the application program in the call to fl_set_object_callback().

See program yesno_cb.c for an example of the use of callback routines. Note that callback routines can be combined with normal objects. It is possible to change the callback routine at any moment.

Sometimes it is necessary to access other objects on the form from within the callback function. This presents a difficult situation that calls for global variables for all the objects on the form. This runs against good programming methodology and can make a program hard to maintain. Forms Library solves (to some degree) this problem by creating three fields, void *u_vdata, char *u_cdata and long u_ldata, in the FL_OBJECT structure that you can use to hold the necessary data to be used in the callback function. A better and more general solution to the problem is detailed in Part II of this documentation where all objects on a form is are grouped into a single structure which can then be "hang" off of u_vdata or some field in the FL_FORM structure.

Another communication problem might arise when the callback function is called and, from within the callback function, some other objects’ state is explicitly changed, say, via fl_set_button(), fl_set_input() etc. You probably don’t want to put the state change handling code of these objects in another object’s callback. To handle this situation, you can simply call

void fl_call_object_callback(FL_OBJECT *obj);

When dealing with multiple forms, the application program can also bind a callback routine to an entire form. To this end it should use the routine

void fl_set_form_callback(FL_FORM *form,
                          void (*callback)(FL_OBJECT *, void *),
                          void *data);

Whenever fl_do_forms() or fl_check_forms() would return an object in form they call the routine callback instead, with the object as an argument. So the callback should have the form

void callback(FL_OBJECT *obj, void *data);

With each form you can associate its own callback routine. For objects that have their own callbacks the object callbacks have priority over the form callback.

When the application program also has its own windows (via Xlib or Xt), it most likely also wants to know about XEvents for the window. As explained earlier, this can be accomplished by checking for FL_EVENT objects. Another (and better) way is to add an event callback routine. This routine will be called whenever an XEvent is pending for the application’s own window. To setup an event callback routine (of type FL_APPEVENT_CB use the call

typedef int (*FL_APPEVENT_CB)(XEvent *, void *);
FL_APPEVENT_CB fl_set_event_callback(int (*callback)(XEvent *ev,
                                                     void *data),
                                     void *data);

Whenever an event happens the callback function is invoked with the event as the first argument and a pointer to data you want it to receive. So the callback should have the form

int callback(XEvent *xev, void *data);

This assumes the application program solicits the events and further, the callback routine should be prepared to handle all XEvent for all non-form windows. The callback function normally should return 0 unless the event isn’t for one of the applcation-managed windows.

This could be undesirable if more than one application window is active. To further partition and simplify the interaction, callbacks for a specific event on a specific window can be registered:

FL_APPEVENT_CB fl_add_event_callback(Window window, int xev_type,
                                     FL_APPEVENT_CB callback,
                                     void *user_data);

where window is the window for which the callback routine is to be registered. xev_type is the XEvent type you’re interested in, e.g., Expose etc. If xev_type is 0, it is taken to mean that the callback routine will handle all events for the window. The newly installed callback replaces the callback already installed. Note that this function only works for windows created directly by the application program (i.e., it won’t work for forms’ windows or windows created by the canvas object). It is possible to access the raw events that happen on a form’s window via fl_register_raw_callback() discussed in Form Events.

fl_add_event_callback() does not alter the window’s event mask nor does it solicit events for you. That’s mainly for the reason that an event type does not always correspond to a unique event mask, also in this way, the user can solicit events at window’s creation and use 0 to register all the event handlers.

To let XForms handle solicitation for you, call the following routine

void fl_activate_event_callbacks(Window win);

This function activates the default mapping of events to event masks built-in in the Forms Library, and causes the system to solicit the events for you. Note however, the mapping of events to masks are not unique and depending on applications, the default mapping may or may not be the one you want. For example, MotionNotify event can be mapped into ButtonMotionMask or PointerMotionMask. Forms Library will use both.

It is possible to control the masks you want precisely by using the following function, which can also be used to add or remove solicited event masks on the fly without altering other masks already selected:

long fl_addto_selected_xevent(Window win, long mask);
long fl_remove_selected_xevent(Window win, long mask);

Both functions return the resulting event masks that are currently selected. If event callback functions are registered via both fl_set_event_callback() and fl_add_event_callback(), the callback via the latter is invoked first and the callback registered via fl_set_event_callback() is called only if the first attempt is unsuccessful, that is, the handler for the event is not present. For example, after the following sequence

fl_add_event_callback(winID, Expose, expose_cb, 0);
fl_set_event_callback(event_callback);

and all Expose events on window winID are consumed by expose_cb then event_callback() would never be invoked as a result of an Expose event.

To remove a callback, use the following routine

void fl_remove_event_callback(Window win, int xev_type);

All parameters have the usual meaning. Again, this routine does not modify the window’s event mask. If you like to change the events the window is sensitive to after removing the callback, use fl_activate_event_callbacks(). If xev_type is 0, all callbacks for window win are removed. This routine is called automatically if fl_winclose() is called to unmap and destroy a window. Otherwise, you must call this routine explicitly to remove all event callbacks before destroying a window using XDestroyWindow().

A program using all of these has the following basic form:

void event_cb(XEvent *xev, void *mydata1) {
    /* Handles an X-event. */
}

void expose_cb(XEvent *xev, void *mydata2) {
    /* handle expose */
}

void form1_cb(FL_OBJECT *obj) {
    /* Handles object obj in form1. */
}

void form2_cb(FL_OBJECT *obj) {
    /* Handles object obj in form2. */
}

main(int argc, char *argv[]) {
    /* initialize */
    /* create form1 and form2 and display them */
    fl_set_form_callback(form1, form1cb);
    fl_set_form_callback(form2, form2cb);

    /* create your own window, winID and show it */
    fl_addto_selected_xevent(winID,
                             ExposureMask | ButtonPressMask |... );
    fl_winshow(winID);
    fl_set_event_callback(event_cb, whatever);
    fl_add_event_callback(winID, Expose, expose_cb, data);
    fl_do_forms();
    return 0;
}

The routine fl_do_forms() will never return in this case. See demo27.c for a program that works this way.

It is recommended that you set up your programs using callback routines (either for the objects or for entire forms). This ensures that no events are missed, events are treated in the correct order, etc. Note that different event callback routines can be written for different stages of the program and they can be switched when required. This provides a progressive path for building up programs.

Another possibility is to use a free object so that the application window is handled automatically by the internal event processing mechanism just like any other forms.


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