refreshing the GUI

Dave Peticolas dave@krondo.com
Wed, 29 Nov 2000 01:59:22 -0800


------- =_aaaaaaaaaa0
Content-Type: text/plain; charset="us-ascii"
Content-ID: <19545.975491946.1@krondo.com>

The Problem -- Keeping the GUI up to date
-----------------------------------------

The GnuCash GUI contains many elements (registers, reconcile windows,
reports, future graphs, main window, etc.) which must be updated when
engine objects (accounts, transactions, splits) are added, deleted or
changed.

Currently, this is handled in a somewhat ad-hoc manner in which each
module which makes changes to the engine must invoke all the relevant
refresh routines. Clearly, this solution is cumbersome and does not
scale well.

Further, as Derek Atkins pointed out, when/if we move to a multi-user
database setup, the engine must notify the GUI when the database has
been changed by another process.


Requirements
------------

 + The engine should be able to inform user code when any account
   or transaction is changed (including changing their respective
   splits).

 + The refresh mechanism should not have any specific knowledge of
   individual windows and other gui elements. It should be easy to
   add new refreshable elements to the mechanism.

 + Changes should be batchable with respect to refreshes, so that
   a sequence of changes only cause a refresh after the last change
   in a batch. Batching should be nestable, for coding simplicitly.

 + For very large batches of changes (loading files) we need to be
   able to turn off the mechanism entirely, perform the changes,
   and then perform a global, forced refresh. This should also
   be nestable.

 + The engine should not be managing gui components.


Nice Features to Have
---------------------

 + Given that some part of the mechanism will have to be a database
   of gui components which need refresh callbacks, it would be nice
   to be able to use this mechanism in lieu of the (also ad-hoc)
   window management macros.


Design
------

The refresh mechanism will be designed in two parts, an engine
component and a GUI component. The engine component will be a simple
change callback mechanism, while the GUI component will be a GUI
entity manager that includes refresh management, as well as other
functionality.


                              ----------
                              |        |
                              | Engine |
                              |        |
                              ----------
                                   |
                                   | Change Callbacks
                                   |
                                  \|/
                      -------------------------
                      |                       |
                      | GUI Component Manager |
                      |                       |
                      -------------------------
                     /            /|\          \     GUI Commands
                    /              |            \--- including refresh
                   /              \|/            \   instructions
  -----------------         -----------------
  |               |         |               |
  | GUI Component |         | GUI Component |           ...
  |               |         |               |
  -----------------         -----------------


Attached are two header files defining the engine and GUI component
manager interfaces.



------- =_aaaaaaaaaa0
Content-Type: text/plain; charset="us-ascii"
Content-ID: <19545.975491946.2@krondo.com>
Content-Description: gnc-change-cb.h

/* Public Interface */

typedef enum
{
  GNC_CHANGE_CREATE  = 1 << 0,
  GNC_CHANGE_MODIFY, = 1 << 1,
  GNC_CHANGE_DESTROY = 1 << 2
} GNCEngineChangeType;

/* GNCEngineChangeCallback
 *   Callback invoked when an engine entity is changed.
 *
 * changed_entity: GUID of changed entity
 * change_type:    one of GNCEngineChangeType, not a combination
 * user_data:      user_data supplied when callback was registered.
 */
typedef void (*GNCEngineChangeCallback) (GUID *changed_entity,
                                         GNCEngineChangeType change_type,
                                         gpointer user_data);

/* gnc_engine_register_change_callback
 *   Register a callback for changes to engine entities.
 *
 * callback:  callback function to register
 * user_data: data provided in future callbacks
 *
 * Returns: id identifying callback
 */
gint gnc_engine_register_change_callback (GNCEngineChangeCallback callback,
                                          gpointer user_data);

/* gnc_engine_unregister_change_callback
 *   Unregister an engine change callback.
 *
 * id: the id of the callback to unregister
 */
void gnc_engine_unregister_change_callback (gint id);

/* gnc_engine_suspend_change_callbacks
 *   Suspend all change callbacks. This function may be
 *   called multiple times. To resume callbacks, an equal
 *   number of calls to gnc_engine_resume_change_callbacks
 *   must be made.
 */
void gnc_engine_suspend_change_callbacks (void);

/* gnc_engine_resume_change_callbacks
 *   Resume change callbacks.
 */
void gnc_engine_resume_change_callbacks (void);


/* Private engine-only interface */

/* gnc_engine_invoke_change_callbacks
 *   Invoke all registered change callbacks using the given arguments.
 *
 *   GNC_CHANGE_CREATE callbacks should be invoked after the object has
 *     been created and registered in the engine entity table.
 *   GNC_CHANGE_MODIFY callbacks should be invoked whenever any data
 *     member or submember (i.e., splits) is changed.
 *   GNC_CHANGE_DESTROY callbacks should be called before the object
 *     has been destroyed or removed from the entity table.
 *
 * changed_entity: the GUID of the changed entity
 * change_type:    the type of change -- this should be one of the
 *                   GNCEngineChangeType values, not a combination.
 */
void gnc_engine_invoke_change_callbacks (GUID *changed_entity,
                                         GNCEngineChangeType change_type);

------- =_aaaaaaaaaa0
Content-Type: text/plain; charset="us-ascii"
Content-ID: <19545.975491946.3@krondo.com>
Content-Description: gnc-component-manager.h

/* GNCComponentRefreshCallback
 *   Callback invoked to inform the component that a refresh
 *   may be needed.
 *
 * changes: if NULL, the component should perform a refresh.
 *
 *          if non-NULL, changes is a GUID hash that maps
 *          GUIDs to GNCEngineChangeType bitmasks describing
 *          how the mapped entity was changed. Entities not
 *          in the hash have not been changed. Entities which
 *          have been destroyed may not exist.
 *
 *          Note since refreshes may not occur with every change,
 *          an entity may have all three change values.
 *
 *          The component should use 'changes' to determine whether
 *          or not a refresh is needed. The hash table must not be
 *          changed.
 *
 * Notes on refreshing: when the callback is invoked any engine
 *                      entities used by the component may be
 *                      deleted. 'Refreshing' the component may
 *                      require closing the component.
 *
 * user_data: user_data supplied when component was registered.
 */
typedef gboolean (*GNCComponentRefreshCallback) (GHashTable *changes,
                                                 gpointer user_data);

/* GNCComponentCloseCallback
 *   Callback invoked to close the component.
 *
 * user_data: user_data supplied when component was registered.
 *
 * Notes on closing: components are not automatically unregistered
 *                   when the close callback is invoked. Components
 *                   may not ignore the callback.
 */
typedef void (*GNCComponentCloseCallback) (gpointer user_data);


/* GNCComponentFindCallback
 *   Callback invoked when searching for a component.
 *
 * find_data: find_data supplied when search was started.
 * user_data: user_data supplied when component was registered.
 *
 * Return: TRUE if the component matches the search criteria.
 */
typedef gboolean (*GNCComponentFindCallback) (gpointer find_data,
                                              gpointer user_data);

/* GNCComponentCallback
 *   Generic callback used in iterating over components.
 *
 * class: class of component
 * id:    id of component
 */
typedef void (*GNCComponentCallback) (const char *class, gint id);

/* gnc_register_gui_component
 *   Register a GUI component with the manager.
 *
 * component_class: a string defining a class of components
 *                  certain component functions can be performed
 *                  on all objects in a class. For that reason,
 *                  components in the same class should all use
 *                  the same type for user_data.
 *
 * refresh_cb:      refresh handler, may be NULL
 * close_cb:        close handler, may be NULL
 * user_data:       user_data argument for callbacks
 *
 * Return:          id of component
 */
gint gnc_register_gui_component (const char *component_class,
                                 GNCComponentRefreshCallback refresh_cb,
                                 GNCComponentCloseCallback close_cb,
                                 gpointer user_data);

/* gnc_unregister_gui_component
 *   Unregister a gui component from the manager.
 *
 * id: id of component to unregister
 */
void gnc_unregister_gui_component (gint id);

/* gnc_unregister_gui_component_by_data
 *   Unregister a gui component using the user_data pointer.
 *
 * component_class: class component is in
 * user_data:       user_data pointer of component to unregister
 *                  all components with that user_data in the
 *                  class are unregistered.
 */
void gnc_unregister_gui_component_by_data (const char *component_class,
                                           gpointer user_data);

/* gnc_suspend_gui_refresh
 *   Suspend refresh callbacks by the component manager.
 *   This routine may be called multiple times. Each call
 *   increases the suspend counter (starts at zero).
 */
void gnc_suspend_gui_refresh (void);

/* gnc_resume_gui_refresh
 *   Resume refresh callbacks by the component manager.
 *   Each call reduces the suspend counter by one. When
 *   the counter reaches zero, all changes which have
 *   occured since the last refresh are collected and
 *   passed to the components in refresh callbacks.
 */
void gnc_resume_gui_refresh (void);

/* gnc_gui_refresh_all
 *   Force all components to refresh.
 *
 *   This routine may only be invoked when the suspend counter
 *   is zero. It should never be mixed with the suspend/resume
 *   refresh routines.
 */
void gnc_gui_refresh_all (void);

/* gnc_close_gui_component
 *   Invoke the close callback for the indicated component.
 *
 * id: id of component to close
 */
void gnc_close_gui_component (gint id);

/* gnc_close_gui_component_by_data
 *   Invoke the close callback for components in the given
 *   class with the given user_data.
 *
 * component_class: class to close components in
 * user_data:       user_data of component to close
 *                  all components with that user_data
 *                  are closed.
 */
void gnc_close_gui_component_by_data (const char *component_class,
                                      gpointer user_data);

/* gnc_find_gui_component
 *   Search for a component in the specified class.
 *
 * component_class:     the class to search for components in
 * find_cb:             the callback used to search for the component
 * find_data:           find_data passed to find_cb
 *
 * Returns: user_data of found component, or NULL if none found
 */
gpointer gnc_find_gui_component (const char *component_class,
                                 GNCComponentFindCallback find_cb,
                                 gpointer find_data);

/* gnc_forall_gui_components
 *   Invoke 'callback' for every component in the database.
 *
 * callback:  callback to invoke
 * iter_data: data passed to callback
 */
void gnc_forall_gui_components (GNCComponentCallback callback,
                                gpointer iter_data);

------- =_aaaaaaaaaa0--