callback.c
----------

History
-------
26th October 1992  J R C  Started

   The |callback_| module provides a dynamically extensible multi-way,
multi-level switch facility. It was initially coded as an experiment, to see
if the resulting, somewhat unconventional, data structure was useful. Use in
the two entirely different contexts of the |riscos_| module and the internal
working of the DrawFile module are indications that it is a useful tool.

   The module implements a data type, called a "callback list," which allows
functions to be associated with integer lists. Each callback list is
entirely unrelated to any others there may be: it is possible for a cluent
of |callback_| to use different callback lists for unrelated purposes.

   The use of integer lists as the unit of association is pragmatic, based
on what seemed like the likely set of uses for the module.

The callback list
--- -------- ----
   In abstract terms, if |Fn| is the type of a callback function (i e, a
function pointer type), and |SH|, |DH| are "handle" types, the the type of a
callback list is implemented as

      Type Callback == Leval;
      Type Level == List [Fn × SH] × List [Int × Level];
      Type List == Opt [Level] × DH;

      (where List [T] == Opt [T × List [T]] and Opt [T] is either a T or
         NULL)

This is implemented in C via

      Type Function == List [Fn × SH];
      Type Entry == List [Int × Level];

   In other words, a callback list is a list of (Fn, SH) pairs and a list of
(int, Level) pairs.

   The module has been implemented in such a way as to use no RISC
O S-specific features: in particular, it does not use |os_error *| values to
return errors. The only error that can be returned by any of the functions
described here is "not enough memory;" this is flagged by returning a NULL
pointer, in the way that |malloc| does.

   The entry points are |callback_new| to create an empty callback list and
|callback_delete| to dispose of one; and others to insert, delete and make
callbacks.

   |Callback_register| is used to add a new function into the list. Its
prototype is


      callback_l callback_register (callback_l l, callback_fn *fn, void *sh,
         int limit, ...)

The client provides a callback list to contain the new callback, the
function pointer to call back to, a handle to call it with, and an integer
list (the length of the list being given in |limit|). Extra levels are
created in the callback list as neccessary, and a callback record (of type
Fn × SH) is inserted.

   Callbacks may be deleted from the list with |callback_deregister. Its
prototype is

      void callback_deregister (callback_l l, void *sh, int limit, ...)

It deletes ALL callbacks that have the given handle, and whose integer list
starts with the |limit| integers provided. Callbacks with longer lists that
start in the same way as the one given are also deleted. (This means that
all callbacks on a given window can be deleted easily, for example.)

   To make the callbacks, the client must call |callback|. It has the
prototype

      void callback (callback_l l, void *dh, int limit, ...)

All callbacks which have lists starting with the |limit| integers provided
will be called, util one returss |TRUE|, to indicate that it has dealt with
the callback successfully.

   So the only communication between a called-back function and the rest of
the client programme is in

         the handle |sh| registered in the callback list, at the time the
      function was registered;

         the handle |dh| provided when the callback is made;

         the boolean value returned by the function.

   So the protype of a callback must be |callback_fn|, defined in callback.h
as

     typedef bool callback_fn (void *, void *);

   The two handles provided are again based on pragmatic evidence. The
|riscos_| module passes the Wimp_Block structure to the callback: this
allows functions called back from the main function |riscos| to get at all
the information in the block returned from Wimp_Poll (or Wimp_PollIdle). The
application is free to pass its own information to the function as well,
using the other handle. This will usually be a structure of window-specific
data. ViewDraw uses this technique successfully.

   On the other hand, the DrawData module has no data to be registered with
the functions - after all, when the module is initialised, it has no way of
finding out which files it will be asked to render - so all the functions in
|render_| or |declare_| are passed a null pointer as one of the handles. The
other handle is used to hold the per-diagram state, as documented with those
modules.
