gnucash/libgnucash/app-utils/gnc-component-manager.c
Robert Fewell f1dd8cfaf1 Component not found error when accounts are deleted
When accounts are deleted that have an open register window a component
not found error is triggered. After the account is destroyed, a call to
'gnc_resume_gui_refresh' calls 'gnc_gui_refresh_internal' and this then
calls 'find_component_ids_by_class' which is used in order resulting in
'register-single' being unregistered before 'GncPluginPageRegister' and
hence 'ld' being freed but the register not knowing this. Reversing the
list fixes this.
2018-03-21 13:11:39 +00:00

945 lines
21 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);
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);
}