Component Manager
*****************

This whole document is completely outdated. Don't read this. All function names and every described structure has changed completely. Only read this if you want to know how gnucash looked like in 1999. This is completely outdated!

The Component Manager (hereafter referred to as the CM) is a framework for managing GUI objects in GnuCash. The CM provides several services.

First, components may request automatic invocation of a 'refresh' handler that is called when a component may need to be redrawn. This mechanism is tied into the Engine's Event mechanism (see Events), so that GUI components are notified when relevant Engine entities are created, modified, or destroyed.

Components may also provide a 'close' handler so that the component may be closed through the CM API.

The CM also provides the ability to search for existing components.

Introduction
============

The GnuCash GUI must manage many different components (registers, reconcile windows, reports, graphs, main window, etc.).  Functions which need to be performed on or with GUI components include:

  * Refreshing components. When Engine objects (Accounts, Transactions, Splits, etc.) are created, modified, or destroyed, the GUI components which reference those objects must be refreshed.

  * Searching for existing components. For example, when the user chooses to 'Reconcile' an account with an existing reconcile dialog, that dialog should be raised to the top in lieu of creating a new reconcile dialog.

  * Closing components.

In particular, keeping components updated in the face of changes in the Engine is a difficult problem. Requirements for handling refreshes include:

  * The Engine should be able to inform user code when any account or transaction is changed (including changing their respective splits). This requirement is satisfied by the Engine Events.

  * The refresh mechanism should not have 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 simplicity.

  * 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.

  * The refresh mechanism should be extendable to a multi-user situation in which changes can come from external components.

  * Components should be able to specify which Engine entities can cause refreshes. This requirement allows the implementation to avoid unnecessary refreshing.

Refresh Mechanism
=================

The major design decisions of the CM relate to the refresh mechanism. The refresh mechanism consists of two parts, the Engine component and the GUI component. The Engine component is the Event mechanism (see Events), while the GUI component is the Component Manager, which provide refresh functionality as well as other services.

The diagram below illustrated the design of the GnuCash refresh mechanism.

                                 ----------
                                 |        |
                                 | Engine |
                                 |        |
                                 ----------
                                     /|\
                                      |
                                      |--- Events (from Engine)
                                      |
                                     \|/
                         -------------------------
                         |                       |
                         |   Component Manager   |
                         |                       |
                         -------------------------
                        /            /|\          \     GUI Commands
                       /              |            \--- including refresh
                      /              \|/            \   invocations (from CM)
     -----------------         -----------------
     |               |         |               |
     | GUI Component |         | GUI Component |           ...
     |               |         |               |
     -----------------         -----------------

The top-level component is the Engine, which emits Events to the Component Manager. In fact, the Engine will send events to any registered handler, but in GnuCash, only the CM registers with the engine. All other GUI components register with the CM.

The CM invokes the refresh handlers of GUI components based on the Engine events received the CM has received as well as information provided by the GUI components (such as which specific Engine entities the components are 'watching').

Initialization and Shutdown
===========================

 -- Function: void gnc_component_manager_init (void)
     Initialize the Component Manager. This should be called before using an other CM functions.

 -- Function: void gnc_component_manager_shutdown (void)
     Shutdown the Component Manager. This should be called to release Component Manager resources.

Refresh Handlers
================

When a component registers itself with the CM, it may specify two different handlers: a refresh handler and a close handler. A refresh handler is a function with the following type signature:

 -- Data type: GNCComponentRefreshHandler void (*) (GHashTable *CHANGES, gpointer USER_DATA)
     This is the type signature of a refresh handler. The CHANGES hash describes the Engine events which have occurred since the last refresh. It is used to determine whether a refresh is actually needed. It may, however, be NULL, meaning the component must perform a refresh. The user_data pointer is the data pointer supplied when the component was registered.

When a refresh handler is invoked, it should perform the following actions:

 1. Check if the component should be closed. When a refresh handler is invoked, any and all of the Engine objects which the component was referencing may have been destroyed, possibly making the component obsolete. For example, a dialog to edit the parameters of a specific Account should be automatically closed when the account is deleted. On the other hand, a list of all Accounts in a hierarchy should be updated when an Account is deleted, but not necessarily closed.

     Components must test for the destruction of critical Engine objects in two ways.

      1. Use the GUID lookup functions (such as xaccAccountLookup), to determine if the engine object is still bound to its GUID. Of course, this means that components should store the GUIDs of critical Engine objects instead of simply storing their C pointers.

      2. If the first test succeeds and the CHANGES hash table of the refresh handler is non-NULL, the component should use the hash to determine of the GNC_EVENT_DESTROY event has occurred for the Engine object in question. The CHANGES hash is a mapping from GUIDs to EventInfo structures. An EventInfo structure has a single member, event_mask, of type GNCEngineEventType. The event_mask is a logical or of the GNC_EVENT_CREATE, GNC_EVENT_MODIFY, and GNC_EVENT_DESTROY event types. Since refreshes may not occur with every Engine event, event_mask may have all three values.

          There is a utility function for accessing the CHANGES hash:

           -- Function: const EventInfo * gnc_gui_get_entity_events (GHashTable * CHANGES, const GUID * ENTITY)
               Return the event info for the entity specified by ENTITY. If there are no events for that entity, NULL is returned.

     If the CHANGES hash is NULL, then the first test is sufficient to determine whether an object has been destroyed.

     If the refresh handler determines the component should be destroyed, it should destroy the component and return.

 2. Check if the component should be refreshed. If the CHANGES hash is NULL, then the component must refresh itself. Otherwise, it may use the CHANGES hash to determine whether or not a refresh is actually necessary. However, since the component may specify which particular Engine objects are relevant (see "Watching Components" below), generally a component will simply refresh unconditionally.

 3. Refresh the component if necessary. This includes updating the GUI as well as internal structures to reflect the new state of Engine objects.

Close Handlers
==============

A close handler is a function with the following type signature:

 -- Data type: GNCComponentCloseHandler void (*) (gpointer USER_DATA)
     This is the type signature of a close handler. The user_data pointer is the data pointer supplied when the component was registered.

The invocation of a close handler is a command to the component to close itself. The component must close itself -- the handler should not be ignored. The component is still responsible for unregistering itself with the Component Manager.

Registering and Unregistering Components
========================================

 -- Function: gint gnc_register_gui_component (const char * COMPONENT_CLASS, GNCComponentRefreshHandler REFRESH_HANDLER, GNCComponentCloseHandler CLOSE_HANDLER, gpointer USER_DATA)
     Register a gui component with the Component Manager.

     The variable COMPONENT_CLASS is a string specifying a class of components. Certain CM functions can be performed on all components in a class. For that reason, components in the same class should all use the same type for USER_DATA.

     REFRESH_HANDLER and CLOSE_HANDLER specify the refresh and close handlers, respectively. Either or both may be NULL.

     The USER_DATA is supplied as an argument when the handlers are invoked.

     The function returns the id of the newly-registered component, or NO_COMPONENT if there was an error.

After a refresh handler is registered, the component must use the API calls under "Watching Engine Objects" below to inform the Component Manager which engine entities are being watched, i.e., which engine entities may cause the component to need refreshing. When a component is first registered, it is not watching anything, and thus will not receive refresh events.

 -- Function: void gnc_unregister_gui_component (gint COMPONENT_ID)
     Unregister the component with id COMPONENT from the CM.

 -- Function: void gnc_unregister_gui_component_by_data (const char * COMPONENT_CLASS, gpointer USER_DATA)
     Unregister all gui components in the class COMPONENT_CLASS which have USER_DATA as a user data pointer.

Watching Engine Objects
=======================

 -- Function: void gnc_gui_component_watch_entity (gint COMPONENT_ID, const GUID * ENTITY, GNCEngineEventType EVENT_MASK)
     If EVENT_MASK is non-zero, add the Engine entity specified by ENTITY to the list of entities being watched by the component with id COMPONENT_ID. Only the events specified by EVENTS are watched. If EVENT_MASK is 0, the call turns off watching for the entity.

 -- Function: void gnc_gui_component_watch_entity_type (gint COMPONENT_ID, GNCIdType ENTITY_TYPE, GNCEngineEventType EVENT_MASK)
     if EVENT_MASK, turn on watching for all entities of ENTITY_TYPE. Only events specified by EVENT_MASK are watched. If EVENT_MASK is 0, turns off watching for the entity type.

 -- Function: void gnc_gui_component_clear_watches (gint COMPONENT_ID)
     Clear all watches for the component with id COMPONENT_ID.

Controlling Refreshes
=====================

 -- Function: void gnc_suspend_gui_refresh (void)
     Suspend the invocation of refresh handlers by the Component Manager. This routine may be called multiple times. Each call increases the suspend counter which starts at zero. When refreshes are not suspended, any engine event causes a refresh cycle in which the refresh handler for every component watching for that event is invoked.

 -- Function: void gnc_resume_gui_refresh (void)
     Resume the invocation of refresh handlers by the Component Manager. Each call reduces the suspend counter by one. When the counter reaches zero, all events which have occurred since the last refresh are collected and passed to refresh handlers via the CHANGES argument. Refresh handlers will still be excluded based on their watches.

 -- Function: void gnc_gui_refresh_all (void)
     Force all components to refresh, i.e., invoke all refresh handlers with a NULL value for CHANGES.

     This routine may only be invoked when the suspend counter is zero. It should never be mixed with the suspend/resume refresh routines.

 -- Function: gboolean gnc_gui_refresh_suspended (void)
     Returns TRUE if GUI refreshing is currently suspended.

Finding Components
==================

The Component Manager API provides two functions that allow components to be searched for. Each function uses a find handler to perform the actual search work. A find handler is a function with the following signature:

 -- Data type: GNCComponentFindHandler gboolean (*) (gpointer FIND_DATA, gpointer USER_DATA)
     A find handler is invoked with the FIND_DATA specified in the search API call, and the USER_DATA of a particular component. The handler should return TRUE if the component matches the search criteria and FALSE otherwise.

 -- Function: GList * gnc_find_gui_components (const char * COMPONENT_CLASS, GNCComponentFindHandler FIND_HANDLER, gpointer FIND_DATA)
     Search for all components in class COMPONENT_CLASS using FIND_HANDLER. Return a GList of the user data pointers of matching components.

 -- Function: gpointer gnc_find_first_gui_component (const char * COMPONENT_CLASS, GNCComponentFindHandler FIND_HANDLER, gpointer FIND_DATA)
     Like gnc_find_gui_components above, but return the user data pointer of the first matching component, or NULL if there are no matching components.

Iterating over Components
=========================

The Component Manager API provides a function for iterating over all components in a class as well as all registered components regardless of class.

In either case, a generic component handler is invoked for each component. The handler has the following signature:

 -- Data type: GNCComponentHandler void (*) (const char * CLASS, gint COMPONENT_ID, gpointer ITER_DATA)
     The component handler is invoked with the CLASS, COMPONENT_ID of a particular component, as well as the ITER_DATA supplied in the iteration API call.

 -- Function: void gnc_forall_gui_components (const char * COMPONENT_CLASS, GNCComponentHandler HANDLER, gpointer ITER_DATA)
     Apply HANDLER to every component in COMPONENT_CLASS. If COMPONENT_CLASS is NULL, then iteration is performed over every registered component. ITER_DATA is supplied to HANDLER as the third argument.