gnucash/libgnucash/doc/design/component-manager.texi
Geert Janssens 83d14e1c1c Restructure the src directory
It is split into
- /libgnucash (for the non-gui bits)
- /gnucash (for the gui)
- /common (misc source files used by both)
- /bindings (currently only holds python bindings)

This is the first step in restructuring the code. It will need much
more fine tuning later on.
2017-08-10 18:45:00 +02:00

397 lines
16 KiB
Plaintext

@node Component Manager
@chapter Component Manager
@cindex Component Manager
@strong{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 (@pxref{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.
@menu
* Component Manager Introduction::
* Refresh Mechanism::
* CM Initialization and Shutdown::
* Refresh Handlers::
* Close Handlers::
* Registering and Unregistering Components::
* Watching Engine Objects::
* Controlling Refreshes::
* Finding Components::
* Iterating over Components::
@end menu
@node Component Manager Introduction, Refresh Mechanism, Component Manager, Component Manager
@section 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:
@itemize
@item Refreshing components. When Engine objects (Accounts,
Transactions, Splits, etc.) are created, modified, or destroyed,
the GUI components which reference those objects must be refreshed.
@item 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.
@item Closing components.
@end itemize
In particular, keeping components updated in the face of changes in the
Engine is a difficult problem. Requirements for handling refreshes
include:
@itemize
@item 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.
@item 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.
@item 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.
@item 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.
@item The Engine should not be managing GUI components.
@item The refresh mechanism should be extendable to a multi-user
situation in which changes can come from external components.
@item Components should be able to specify which Engine entities
can cause refreshes. This requirement allows the implementation
to avoid unnecessary refreshing.
@end itemize
@node Refresh Mechanism, CM Initialization and Shutdown, Component Manager Introduction, Component Manager
@section Refresh Mechanism
@cindex 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 (@pxref{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.
@example
----------
| |
| Engine |
| |
----------
/|\
|
|--- Events (from Engine)
|
\|/
-------------------------
| |
| Component Manager |
| |
-------------------------
/ /|\ \ GUI Commands
/ | \--- including refresh
/ \|/ \ invocations (from CM)
----------------- -----------------
| | | |
| GUI Component | | GUI Component | ...
| | | |
----------------- -----------------
@end example
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').
@node CM Initialization and Shutdown, Refresh Handlers, Refresh Mechanism, Component Manager
@section Initialization and Shutdown
@deftypefun void gnc_component_manager_init (void)
Initialize the Component Manager. This should be called
before using an other CM functions.
@end deftypefun
@deftypefun void gnc_component_manager_shutdown (void)
Shutdown the Component Manager. This should be called
to release Component Manager resources.
@end deftypefun
@node Refresh Handlers, Close Handlers, CM Initialization and Shutdown, Component Manager
@section Refresh Handlers
@tindex EventInfo
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:
@deftp {Data type} GNCComponentRefreshHandler void (*) (GHashTable *@var{changes}, gpointer @var{user_data})
This is the type signature of a refresh handler. The @var{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 @code{NULL}, meaning the component must perform a refresh.
The @code{user_data} pointer is the data pointer supplied when the
component was registered.
@end deftp
When a refresh handler is invoked, it should perform the following actions:
@enumerate
@item 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.
@enumerate
@item Use the @code{GUID} lookup functions (such as
@code{xaccAccountLookup}), to determine if the engine object is still
bound to its @code{GUID}. Of course, this means that components should
store the @code{GUID}s of critical Engine objects instead of simply
storing their C pointers.
@item If the first test succeeds and the @var{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 ocurred for the Engine
object in question. The @var{changes} hash is a mapping from
@code{GUID}s to @code{EventInfo} structures. An @code{EventInfo}
structure has a single member, @code{event_mask}, of type
@code{GNCEngineEventType}. The @code{event_mask} is a logical or of the
@code{GNC_EVENT_CREATE}, @code{GNC_EVENT_MODIFY}, and
@code{GNC_EVENT_DESTROY} event types. Since refreshes may not occur with
every Engine event, @code{event_mask} may have all three values.
There is a utility function for accessing the @var{changes} hash:
@deftypefun {const EventInfo *} gnc_gui_get_entity_events (GHashTable * @var{changes}, const GUID * @var{entity})
Return the event info for the entity specified by @var{entity}. If there
are no events for that entity, @code{NULL} is returned.
@end deftypefun
@end enumerate
If the @var{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.
@item Check if the component should be refreshed. If the @var{changes}
hash is @code{NULL}, then the component must refresh itself. Otherwise,
it may use the @var{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.
@item Refresh the component if necessary. This includes updating the
GUI as well as internal structures to reflect the new state of Engine
objects.
@end enumerate
@node Close Handlers, Registering and Unregistering Components, Refresh Handlers, Component Manager
@section Close Handlers
A close handler is a function with the following type signature:
@deftp {Data type} GNCComponentCloseHandler void (*) (gpointer @var{user_data})
This is the type signature of a close handler. The @code{user_data}
pointer is the data pointer supplied when the component was registered.
@end deftp
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.
@node Registering and Unregistering Components, Watching Engine Objects, Close Handlers, Component Manager
@section Registering and Unregistering Components
@deftypefun gint gnc_register_gui_component (const char * @var{component_class}, GNCComponentRefreshHandler @var{refresh_handler}, GNCComponentCloseHandler @var{close_handler}, gpointer @var{user_data})
Register a gui component with the Component Manager.
The variable @var{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 @var{user_data}.
@var{refresh_handler} and @var{close_handler} specify the refresh and close
handlers, respectively. Either or both may be @code{NULL}.
The @var{user_data} is supplied as an argument when the handlers are invoked.
The function returns the id of the newly-registered component, or
@code{NO_COMPONENT} if there was an error.
@end deftypefun
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.
@deftypefun void gnc_unregister_gui_component (gint @var{component_id})
Unregister the component with id @var{component} from the CM.
@end deftypefun
@deftypefun void gnc_unregister_gui_component_by_data (const char * @var{component_class}, gpointer @var{user_data})
Unregister all gui components in the class @var{component_class} which have
@var{user_data} as a user data pointer.
@end deftypefun
@node Watching Engine Objects, Controlling Refreshes, Registering and Unregistering Components, Component Manager
@section Watching Engine Objects
@deftypefun void gnc_gui_component_watch_entity (gint @var{component_id}, const GUID * @var{entity}, GNCEngineEventType @var{event_mask})
If @var{event_mask} is non-zero, add the Engine entity specified by
@var{entity} to the list of entities being watched by the component with
id @var{component_id}. Only the events specified by @var{events} are
watched. If @var{event_mask} is 0, the call turns off watching for the
entity.
@end deftypefun
@deftypefun void gnc_gui_component_watch_entity_type (gint @var{component_id}, GNCIdType @var{entity_type}, GNCEngineEventType @var{event_mask})
if @var{event_mask}, turn on watching for all entities of @var{entity_type}.
Only events specified by @var{event_mask} are watched. If @var{event_mask}
is 0, turns off watching for the entity type.
@end deftypefun
@deftypefun void gnc_gui_component_clear_watches (gint @var{component_id})
Clear all watches for the component with id @var{component_id}.
@end deftypefun
@node Controlling Refreshes, Finding Components, Watching Engine Objects, Component Manager
@section Controlling Refreshes
@deftypefun 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.
@end deftypefun
@deftypefun 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 @var{changes} argument. Refresh
handlers will still be excluded based on their watches.
@end deftypefun
@deftypefun void gnc_gui_refresh_all (void)
Force all components to refresh, i.e., invoke all refresh handlers
with a @code{NULL} value for @var{changes}.
This routine may only be invoked when the suspend counter is zero. It
should never be mixed with the suspend/resume refresh routines.
@end deftypefun
@deftypefun gboolean gnc_gui_refresh_suspended (void)
Returns TRUE if GUI refreshing is currently suspended.
@end deftypefun
@node Finding Components, Iterating over Components, Controlling Refreshes, Component Manager
@section 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:
@deftp {Data type} GNCComponentFindHandler gboolean (*) (gpointer @var{find_data}, gpointer @var{user_data})
A find handler is invoked with the @var{find_data} specified in the search
API call, and the @var{user_data} of a particular component. The handler
should return TRUE if the component matches the search criteria and FALSE
otherwise.
@end deftp
@deftypefun {GList *} gnc_find_gui_components (const char * @var{component_class}, GNCComponentFindHandler @var{find_handler}, gpointer @var{find_data})
Search for all components in class @var{component_class} using @var{find_handler}. Return a @code{GList} of the user data pointers of matching components.
@end deftypefun
@deftypefun gpointer gnc_find_first_gui_component (const char * @var{component_class}, GNCComponentFindHandler @var{find_handler}, gpointer @var{find_data})
Like @code{gnc_find_gui_components} above, but return the user data pointer
of the first matching component, or @code{NULL} if there are no matching
components.
@end deftypefun
@node Iterating over Components, , Finding Components, Component Manager
@section 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:
@deftp {Data type} GNCComponentHandler void (*) (const char * @var{class}, gint @var{component_id}, gpointer @var{iter_data})
The component handler is invoked with the @var{class},
@var{component_id} of a particular component, as well as the
@var{iter_data} supplied in the iteration API call.
@end deftp
@deftypefun void gnc_forall_gui_components (const char * @var{component_class}, GNCComponentHandler @var{handler}, gpointer @var{iter_data})
Apply @var{handler} to every component in @var{component_class}. If
@var{component_class} is @code{NULL}, then iteration is performed over
every registered component. @var{iter_data} is supplied to @var{handler}
as the third argument.
@end deftypefun