gnucash/libgnucash/app-utils/gnc-component-manager.c
Robert Fewell 64778fcad5 Fix error when options dialogue is open when app closed
With the options dialogue open when Gnucash is closed this error is
logged in the trace file, gnc_close_gui_component() component not found.

This is fixed by reversing the components list found for session so the
options dialogue is closed before the report window.
2018-07-22 13:07:57 +01:00

948 lines
22 KiB
C

/********************************************************************\
* gnc-component-manager.h - GUI component manager interface *
* Copyright (C) 2000 Dave Peticolas <dave@krondo.com> *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License as *
* published by the Free Software Foundation; either version 2 of *
* the License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License*
* along with this program; if not, write to the Free Software *
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. *
\********************************************************************/
#include <config.h>
#include <stdio.h>
#include "gnc-component-manager.h"
#include "qof.h"
#include "gnc-ui-util.h"
/** Declarations ****************************************************/
#define CM_DEBUG 0
typedef struct
{
QofIdType entity_type;
QofEventId event_mask;
} EntityTypeEventInfo;
typedef struct
{
GHashTable * event_masks;
GHashTable * entity_events;
gboolean match;
} ComponentEventInfo;
typedef struct
{
GNCComponentRefreshHandler refresh_handler;
GNCComponentCloseHandler close_handler;
gpointer user_data;
ComponentEventInfo watch_info;
char *component_class;
gint component_id;
gpointer session;
} ComponentInfo;
/** Static Variables ************************************************/
static guint suspend_counter = 0;
/* Some code foolishly uses 0 instead of NO_COMPONENT, so we start with 1. */
static gint next_component_id = 1;
static GList *components = NULL;
static ComponentEventInfo changes = { NULL, NULL, FALSE };
static ComponentEventInfo changes_backup = { NULL, NULL, FALSE };
/* This static indicates the debugging module that this .o belongs to. */
static QofLogModule log_module = GNC_MOD_GUI;
/** Prototypes ******************************************************/
static void gnc_gui_refresh_internal (gboolean force);
static GList * find_component_ids_by_class (const char *component_class);
static gboolean got_events = FALSE;
/** Implementations *************************************************/
#if CM_DEBUG
static void
dump_components (void)
{
GList *node;
fprintf (stderr, "Components:\n");
for (node = components; node; node = node->next)
{
ComponentInfo *ci = node->data;
fprintf (stderr, " %s:\t%d\n",
ci->component_class ? ci->component_class : "(null)",
ci->component_id);
}
fprintf (stderr, "\n");
}
#endif
static void
clear_mask_hash_helper (gpointer key, gpointer value, gpointer user_data)
{
QofEventId * et = value;
*et = 0;
}
/* clear a hash table of the form string --> QofEventId,
* where the values are g_malloced and the keys are in the engine
* string cache. */
static void
clear_mask_hash (GHashTable *hash)
{
if (hash == NULL)
return;
g_hash_table_foreach (hash, clear_mask_hash_helper, NULL);
}
static gboolean
destroy_mask_hash_helper (gpointer key, gpointer value, gpointer user_data)
{
qof_string_cache_remove (key);
g_free (value);
return TRUE;
}
static void
destroy_mask_hash (GHashTable *hash)
{
g_hash_table_foreach_remove (hash, destroy_mask_hash_helper, NULL);
g_hash_table_destroy (hash);
}
static gboolean
destroy_event_hash_helper (gpointer key, gpointer value, gpointer user_data)
{
GncGUID *guid = key;
EventInfo *ei = value;
guid_free (guid);
g_free (ei);
return TRUE;
}
/* clear a hash table of the form GncGUID --> EventInfo, where
* both keys and values are g_malloced */
static void
clear_event_hash (GHashTable *hash)
{
if (hash == NULL)
return;
g_hash_table_foreach_remove (hash, destroy_event_hash_helper, NULL);
}
static void
destroy_event_hash (GHashTable *hash)
{
clear_event_hash (hash);
g_hash_table_destroy (hash);
}
static void
clear_event_info (ComponentEventInfo *cei)
{
if (!cei)
return;
clear_mask_hash (cei->event_masks);
clear_event_hash (cei->entity_events);
}
static void
add_event (ComponentEventInfo *cei, const GncGUID *entity,
QofEventId event_mask, gboolean or_in)
{
GHashTable *hash;
if (!cei || !cei->entity_events || !entity)
return;
hash = cei->entity_events;
if (event_mask == 0)
{
gpointer key;
gpointer value;
if (or_in)
return;
if (g_hash_table_lookup_extended (hash, entity, &key, &value))
{
g_hash_table_remove (hash, entity);
guid_free (key);
g_free (value);
}
}
else
{
EventInfo *ei;
ei = g_hash_table_lookup (hash, entity);
if (ei == NULL)
{
GncGUID *key;
key = guid_malloc ();
*key = *entity;
ei = g_new (EventInfo, 1);
ei->event_mask = 0;
g_hash_table_insert (hash, key, ei);
}
if (or_in)
ei->event_mask |= event_mask;
else
ei->event_mask = event_mask;
}
}
static void
add_event_type (ComponentEventInfo *cei, QofIdTypeConst entity_type,
QofEventId event_mask, gboolean or_in)
{
QofEventId *mask;
g_return_if_fail (cei);
g_return_if_fail (cei->event_masks);
g_return_if_fail (entity_type);
mask = g_hash_table_lookup (cei->event_masks, entity_type);
if (!mask)
{
char * key = qof_string_cache_insert ((gpointer) entity_type);
mask = g_new0 (QofEventId, 1);
g_hash_table_insert (cei->event_masks, key, mask);
}
if (or_in)
*mask |= event_mask;
else
*mask = event_mask;
}
static void
gnc_cm_event_handler (QofInstance *entity,
QofEventId event_type,
gpointer user_data,
gpointer event_data)
{
const GncGUID *guid = qof_entity_get_guid(entity);
#if CM_DEBUG
gchar guidstr[GUID_ENCODING_LENGTH+1];
guid_to_string_buff (guid, guidstr);
fprintf (stderr, "event_handler: event %d, entity %p, guid %s\n", event_type,
entity, guidstr);
#endif
add_event (&changes, guid, event_type, TRUE);
if (QOF_CHECK_TYPE(entity, GNC_ID_SPLIT))
{
/* split events are never generated by the engine, but might
* be generated by a backend (viz. the postgres backend.)
* Handle them like a transaction modify event. */
add_event_type (&changes, GNC_ID_TRANS, QOF_EVENT_MODIFY, TRUE);
}
else
add_event_type (&changes, entity->e_type, event_type, TRUE);
got_events = TRUE;
if (suspend_counter == 0)
gnc_gui_refresh_internal (FALSE);
}
static gint handler_id;
void
gnc_component_manager_init (void)
{
if (changes.entity_events)
{
PERR ("component manager already initialized");
return;
}
changes.event_masks = g_hash_table_new (g_str_hash, g_str_equal);
changes.entity_events = guid_hash_table_new ();
changes_backup.event_masks = g_hash_table_new (g_str_hash, g_str_equal);
changes_backup.entity_events = guid_hash_table_new ();
handler_id = qof_event_register_handler (gnc_cm_event_handler, NULL);
}
void
gnc_component_manager_shutdown (void)
{
if (!changes.entity_events)
{
PERR ("component manager not initialized");
return;
}
destroy_mask_hash (changes.event_masks);
changes.event_masks = NULL;
destroy_event_hash (changes.entity_events);
changes.entity_events = NULL;
destroy_mask_hash (changes_backup.event_masks);
changes_backup.event_masks = NULL;
destroy_event_hash (changes_backup.entity_events);
changes_backup.entity_events = NULL;
qof_event_unregister_handler (handler_id);
}
static ComponentInfo *
find_component (gint component_id)
{
GList *node;
for (node = components; node; node = node->next)
{
ComponentInfo *ci = node->data;
if (ci->component_id == component_id)
return ci;
}
return NULL;
}
static GList *
find_components_by_data (gpointer user_data)
{
GList *list = NULL;
GList *node;
for (node = components; node; node = node->next)
{
ComponentInfo *ci = node->data;
if (ci->user_data == user_data)
list = g_list_prepend (list, ci);
}
return list;
}
static GList *
find_components_by_session (gpointer session)
{
GList *list = NULL;
GList *node;
for (node = components; node; node = node->next)
{
ComponentInfo *ci = node->data;
if (ci->session == session)
list = g_list_prepend (list, ci);
}
return list;
}
static ComponentInfo *
gnc_register_gui_component_internal (const char * component_class)
{
ComponentInfo *ci;
gint component_id;
g_return_val_if_fail (component_class, NULL);
/* look for a free handler id */
component_id = next_component_id;
/* design warning: if we ever get 2^32-1 components,
this loop is infinite. Instead of fixing it, we'll just
complain when (if) we get half way there (probably never).
*/
while (find_component (component_id))
if (++component_id == NO_COMPONENT)
component_id++;
if (component_id < 0)
PERR("Amazing! Half way to running out of component_ids.");
/* found one, add the handler */
ci = g_new0 (ComponentInfo, 1);
ci->watch_info.event_masks = g_hash_table_new (g_str_hash, g_str_equal);
ci->watch_info.entity_events = guid_hash_table_new ();
ci->component_class = g_strdup (component_class);
ci->component_id = component_id;
ci->session = NULL;
components = g_list_prepend (components, ci);
/* update id for next registration */
next_component_id = component_id + 1;
#if CM_DEBUG
fprintf (stderr, "Register component %d in class %s\n",
component_id, component_class ? component_class : "(null)");
dump_components ();
#endif
return ci;
}
gint
gnc_register_gui_component (const char *component_class,
GNCComponentRefreshHandler refresh_handler,
GNCComponentCloseHandler close_handler,
gpointer user_data)
{
ComponentInfo *ci;
/* sanity check */
if (!component_class)
{
PERR ("no class specified");
return NO_COMPONENT;
}
ci = gnc_register_gui_component_internal (component_class);
g_return_val_if_fail (ci, NO_COMPONENT);
ci->refresh_handler = refresh_handler;
ci->close_handler = close_handler;
ci->user_data = user_data;
return ci->component_id;
}
void
gnc_gui_component_watch_entity (gint component_id,
const GncGUID *entity,
QofEventId event_mask)
{
ComponentInfo *ci;
if (entity == NULL)
return;
ci = find_component (component_id);
if (!ci)
{
PERR ("component not found");
return;
}
add_event (&ci->watch_info, entity, event_mask, FALSE);
}
void
gnc_gui_component_watch_entity_type (gint component_id,
QofIdTypeConst entity_type,
QofEventId event_mask)
{
ComponentInfo *ci;
ci = find_component (component_id);
if (!ci)
{
PERR ("component not found");
return;
}
add_event_type (&ci->watch_info, entity_type, event_mask, FALSE);
}
const EventInfo *
gnc_gui_get_entity_events (GHashTable *changes, const GncGUID *entity)
{
if (!changes || !entity)
return QOF_EVENT_NONE;
return g_hash_table_lookup (changes, entity);
}
void
gnc_gui_component_clear_watches (gint component_id)
{
ComponentInfo *ci;
ci = find_component (component_id);
if (!ci)
{
PERR ("component not found");
return;
}
clear_event_info (&ci->watch_info);
}
void
gnc_unregister_gui_component (gint component_id)
{
ComponentInfo *ci;
ci = find_component (component_id);
if (!ci)
{
PERR ("component %d not found", component_id);
return;
}
#if CM_DEBUG
fprintf (stderr, "Unregister component %d in class %s\n",
ci->component_id,
ci->component_class ? ci->component_class : "(null)");
#endif
gnc_gui_component_clear_watches (component_id);
components = g_list_remove (components, ci);
destroy_mask_hash (ci->watch_info.event_masks);
ci->watch_info.event_masks = NULL;
destroy_event_hash (ci->watch_info.entity_events);
ci->watch_info.entity_events = NULL;
g_free (ci->component_class);
ci->component_class = NULL;
g_free (ci);
#if CM_DEBUG
dump_components ();
#endif
}
void
gnc_unregister_gui_component_by_data (const char *component_class,
gpointer user_data)
{
GList *list;
GList *node;
list = find_components_by_data (user_data);
for (node = list; node; node = node->next)
{
ComponentInfo *ci = node->data;
if (component_class &&
g_strcmp0 (component_class, ci->component_class) != 0)
continue;
gnc_unregister_gui_component (ci->component_id);
}
g_list_free (list);
}
void
gnc_suspend_gui_refresh (void)
{
suspend_counter++;
if (suspend_counter == 0)
{
PERR ("suspend counter overflow");
}
}
void
gnc_resume_gui_refresh (void)
{
if (suspend_counter == 0)
{
PERR ("suspend counter underflow");
return;
}
suspend_counter--;
if (suspend_counter == 0)
gnc_gui_refresh_internal (FALSE);
}
static void
match_type_helper (gpointer key, gpointer value, gpointer user_data)
{
ComponentEventInfo *cei = user_data;
QofIdType id_type = key;
QofEventId * et = value;
QofEventId * et_2;
et_2 = g_hash_table_lookup (cei->event_masks, id_type);
if (!et_2)
return;
if (*et & *et_2)
cei->match = TRUE;
}
static void
match_helper (gpointer key, gpointer value, gpointer user_data)
{
GncGUID *guid = key;
EventInfo *ei_1 = value;
EventInfo *ei_2;
ComponentEventInfo *cei = user_data;
ei_2 = g_hash_table_lookup (cei->entity_events, guid);
if (!ei_2)
return;
if (ei_1->event_mask & ei_2->event_mask)
cei->match = TRUE;
}
static gboolean
changes_match (ComponentEventInfo *cei, ComponentEventInfo *changes)
{
ComponentEventInfo *big_cei;
GHashTable *smalltable;
if (cei == NULL)
return FALSE;
/* check types first, for efficiency */
cei->match = FALSE;
g_hash_table_foreach (changes->event_masks, match_type_helper, cei);
if (cei->match)
return TRUE;
if (g_hash_table_size (cei->entity_events) <=
g_hash_table_size (changes->entity_events))
{
smalltable = cei->entity_events;
big_cei = changes;
}
else
{
smalltable = changes->entity_events;
big_cei = cei;
}
big_cei->match = FALSE;
g_hash_table_foreach (smalltable, match_helper, big_cei);
return big_cei->match;
}
static void
gnc_gui_refresh_internal (gboolean force)
{
GList *list;
GList *node;
if (!got_events && !force)
return;
gnc_suspend_gui_refresh ();
{
GHashTable *table;
table = changes_backup.event_masks;
changes_backup.event_masks = changes.event_masks;
changes.event_masks = table;
table = changes_backup.entity_events;
changes_backup.entity_events = changes.entity_events;
changes.entity_events = table;
}
#if CM_DEBUG
fprintf (stderr, "%srefresh!\n", force ? "forced " : "");
#endif
list = find_component_ids_by_class (NULL);
// reverse the list so class GncPluginPageRegister is before register-single
list = g_list_reverse (list);
for (node = list; node; node = node->next)
{
ComponentInfo *ci = find_component (GPOINTER_TO_INT (node->data));
if (!ci)
continue;
if (!ci->refresh_handler)
{
#if CM_DEBUG
fprintf (stderr, "no handlers for %s:%d\n", ci->component_class, ci->component_id);
#endif
continue;
}
if (force)
{
if (ci->refresh_handler)
{
#if CM_DEBUG
fprintf (stderr, "calling %s:%d C handler\n", ci->component_class, ci->component_id);
#endif
ci->refresh_handler (NULL, ci->user_data);
}
}
else if (changes_match (&ci->watch_info, &changes_backup))
{
if (ci->refresh_handler)
{
#if CM_DEBUG
fprintf (stderr, "calling %s:%d C handler\n", ci->component_class, ci->component_id);
#endif
ci->refresh_handler (changes_backup.entity_events, ci->user_data);
}
}
else
{
#if CM_DEBUG
fprintf (stderr, "no match for %s:%d\n", ci->component_class, ci->component_id);
#endif
}
}
clear_event_info (&changes_backup);
got_events = FALSE;
g_list_free (list);
gnc_resume_gui_refresh ();
}
void
gnc_gui_refresh_all (void)
{
if (suspend_counter != 0)
{
PERR ("suspend counter not zero");
return;
}
gnc_gui_refresh_internal (TRUE);
}
gboolean
gnc_gui_refresh_suspended (void)
{
return suspend_counter != 0;
}
void
gnc_close_gui_component (gint component_id)
{
ComponentInfo *ci;
ci = find_component (component_id);
if (!ci)
{
PERR ("component not found");
return;
}
if (!ci->close_handler)
return;
if (ci->close_handler)
ci->close_handler (ci->user_data);
}
void
gnc_close_gui_component_by_data (const char *component_class,
gpointer user_data)
{
GList *list;
GList *node;
list = find_components_by_data (user_data);
for (node = list; node; node = node->next)
{
ComponentInfo *ci = node->data;
if (component_class &&
g_strcmp0 (component_class, ci->component_class) != 0)
continue;
gnc_close_gui_component (ci->component_id);
}
g_list_free (list);
}
void
gnc_gui_component_set_session (gint component_id, gpointer session)
{
ComponentInfo *ci;
ci = find_component (component_id);
if (!ci)
{
PERR ("component not found");
return;
}
ci->session = session;
}
void
gnc_close_gui_component_by_session (gpointer session)
{
GList *list;
GList *node;
list = find_components_by_session (session);
// reverse the list so class like dialog-options close before window-report
list = g_list_reverse (list);
for (node = list; node; node = node->next)
{
ComponentInfo *ci = node->data;
gnc_close_gui_component (ci->component_id);
}
g_list_free (list);
}
GList *
gnc_find_gui_components (const char *component_class,
GNCComponentFindHandler find_handler,
gpointer find_data)
{
GList *list = NULL;
GList *node;
if (!component_class)
return NULL;
for (node = components; node; node = node->next)
{
ComponentInfo *ci = node->data;
if (g_strcmp0 (component_class, ci->component_class) != 0)
continue;
if (find_handler && !find_handler (find_data, ci->user_data))
continue;
list = g_list_prepend (list, ci->user_data);
}
return list;
}
gpointer
gnc_find_first_gui_component (const char *component_class,
GNCComponentFindHandler find_handler,
gpointer find_data)
{
GList *list;
gpointer user_data;
#if CM_DEBUG
fprintf (stderr, "find: class %s, fn %p, data %p\n", component_class,
find_handler, find_data);
#endif
if (!component_class)
return NULL;
list = gnc_find_gui_components (component_class, find_handler, find_data);
if (!list)
return NULL;
user_data = list->data;
g_list_free (list);
#if CM_DEBUG
fprintf (stderr, "found: data %p\n", user_data);
#endif
return user_data;
}
static GList *
find_component_ids_by_class (const char *component_class)
{
GList *list = NULL;
GList *node;
for (node = components; node; node = node->next)
{
ComponentInfo *ci = node->data;
if (component_class &&
g_strcmp0 (component_class, ci->component_class) != 0)
continue;
list = g_list_prepend (list, GINT_TO_POINTER (ci->component_id));
}
return list;
}
gint
gnc_forall_gui_components (const char *component_class,
GNCComponentHandler handler,
gpointer iter_data)
{
GList *list;
GList *node;
gint count = 0;
if (!handler)
return(0);
/* so components can be destroyed during the forall */
list = find_component_ids_by_class (component_class);
for (node = list; node; node = node->next)
{
ComponentInfo *ci = find_component (GPOINTER_TO_INT (node->data));
if (!ci)
continue;
if (handler (ci->component_class, ci->component_id, ci->user_data, iter_data))
count++;
}
g_list_free (list);
return(count);
}