1 /********************************************************************\
2  * gnc-component-manager.h - GUI component manager interface        *
3  * Copyright (C) 2000 Dave Peticolas <dave@krondo.com>              *
4  *                                                                  *
5  * This program is free software; you can redistribute it and/or    *
6  * modify it under the terms of the GNU General Public License as   *
7  * published by the Free Software Foundation; either version 2 of   *
8  * the License, or (at your option) any later version.              *
9  *                                                                  *
10  * This program is distributed in the hope that it will be useful,  *
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
13  * GNU General Public License for more details.                     *
14  *                                                                  *
15  * You should have received a copy of the GNU General Public License*
16  * along with this program; if not, write to the Free Software      *
17  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.        *
18 \********************************************************************/
19 
20 #include <config.h>
21 
22 #include <stdio.h>
23 
24 #include "gnc-component-manager.h"
25 #include "qof.h"
26 #include "gnc-ui-util.h"
27 
28 
29 /** Declarations ****************************************************/
30 
31 #define CM_DEBUG 0
32 
33 typedef struct
34 {
35     QofIdType entity_type;
36     QofEventId event_mask;
37 } EntityTypeEventInfo;
38 
39 typedef struct
40 {
41     GHashTable * event_masks;
42     GHashTable * entity_events;
43 
44     gboolean match;
45 } ComponentEventInfo;
46 
47 typedef struct
48 {
49     GNCComponentRefreshHandler refresh_handler;
50     GNCComponentCloseHandler close_handler;
51     gpointer user_data;
52 
53     ComponentEventInfo watch_info;
54 
55     char *component_class;
56     gint component_id;
57     gpointer session;
58 } ComponentInfo;
59 
60 
61 /** Static Variables ************************************************/
62 static guint  suspend_counter = 0;
63 /* Some code foolishly uses 0 instead of NO_COMPONENT, so we start with 1. */
64 static gint   next_component_id = 1;
65 static GList *components = NULL;
66 
67 static ComponentEventInfo changes = { NULL, NULL, FALSE };
68 static ComponentEventInfo changes_backup = { NULL, NULL, FALSE };
69 
70 
71 /* This static indicates the debugging module that this .o belongs to.  */
72 static QofLogModule log_module = GNC_MOD_GUI;
73 
74 
75 /** Prototypes ******************************************************/
76 static void gnc_gui_refresh_internal (gboolean force);
77 static GList * find_component_ids_by_class (const char *component_class);
78 static gboolean got_events = FALSE;
79 
80 
81 /** Implementations *************************************************/
82 
83 #if CM_DEBUG
84 static void
dump_components(void)85 dump_components (void)
86 {
87     GList *node;
88 
89     fprintf (stderr, "Components:\n");
90 
91     for (node = components; node; node = node->next)
92     {
93         ComponentInfo *ci = node->data;
94 
95         fprintf (stderr, "  %s:\t%d\n",
96                  ci->component_class ? ci->component_class : "(null)",
97                  ci->component_id);
98     }
99 
100     fprintf (stderr, "\n");
101 }
102 #endif
103 
104 static void
clear_mask_hash_helper(gpointer key,gpointer value,gpointer user_data)105 clear_mask_hash_helper (gpointer key, gpointer value, gpointer user_data)
106 {
107     QofEventId * et = value;
108 
109     *et = 0;
110 }
111 
112 /* clear a hash table of the form string --> QofEventId,
113  * where the values are g_malloced and the keys are in the engine
114  * string cache. */
115 static void
clear_mask_hash(GHashTable * hash)116 clear_mask_hash (GHashTable *hash)
117 {
118     if (hash == NULL)
119         return;
120 
121     g_hash_table_foreach (hash, clear_mask_hash_helper, NULL);
122 }
123 
124 static gboolean
destroy_mask_hash_helper(gpointer key,gpointer value,gpointer user_data)125 destroy_mask_hash_helper (gpointer key, gpointer value, gpointer user_data)
126 {
127     qof_string_cache_remove (key);
128     g_free (value);
129 
130     return TRUE;
131 }
132 
133 static void
destroy_mask_hash(GHashTable * hash)134 destroy_mask_hash (GHashTable *hash)
135 {
136     g_hash_table_foreach_remove (hash, destroy_mask_hash_helper, NULL);
137     g_hash_table_destroy (hash);
138 }
139 
140 static gboolean
destroy_event_hash_helper(gpointer key,gpointer value,gpointer user_data)141 destroy_event_hash_helper (gpointer key, gpointer value, gpointer user_data)
142 {
143     GncGUID *guid = key;
144     EventInfo *ei = value;
145 
146     guid_free (guid);
147     g_free (ei);
148 
149     return TRUE;
150 }
151 
152 /* clear a hash table of the form GncGUID --> EventInfo, where
153  * both keys and values are g_malloced */
154 static void
clear_event_hash(GHashTable * hash)155 clear_event_hash (GHashTable *hash)
156 {
157     if (hash == NULL)
158         return;
159 
160     g_hash_table_foreach_remove (hash, destroy_event_hash_helper, NULL);
161 }
162 
163 static void
destroy_event_hash(GHashTable * hash)164 destroy_event_hash (GHashTable *hash)
165 {
166     clear_event_hash (hash);
167     g_hash_table_destroy (hash);
168 }
169 
170 static void
clear_event_info(ComponentEventInfo * cei)171 clear_event_info (ComponentEventInfo *cei)
172 {
173     if (!cei)
174         return;
175 
176     clear_mask_hash (cei->event_masks);
177     clear_event_hash (cei->entity_events);
178 }
179 
180 static void
add_event(ComponentEventInfo * cei,const GncGUID * entity,QofEventId event_mask,gboolean or_in)181 add_event (ComponentEventInfo *cei, const GncGUID *entity,
182            QofEventId event_mask, gboolean or_in)
183 {
184     GHashTable *hash;
185 
186     if (!cei || !cei->entity_events || !entity)
187         return;
188 
189     hash = cei->entity_events;
190 
191     if (event_mask == 0)
192     {
193         gpointer key;
194         gpointer value;
195 
196         if (or_in)
197             return;
198 
199         if (g_hash_table_lookup_extended (hash, entity, &key, &value))
200         {
201             g_hash_table_remove (hash, entity);
202             guid_free (key);
203             g_free (value);
204         }
205     }
206     else
207     {
208         EventInfo *ei;
209 
210         ei = g_hash_table_lookup (hash, entity);
211         if (ei == NULL)
212         {
213             GncGUID *key;
214 
215             key = guid_malloc ();
216             *key = *entity;
217 
218             ei = g_new (EventInfo, 1);
219             ei->event_mask = 0;
220 
221             g_hash_table_insert (hash, key, ei);
222         }
223 
224         if (or_in)
225             ei->event_mask |= event_mask;
226         else
227             ei->event_mask = event_mask;
228     }
229 }
230 
231 static void
add_event_type(ComponentEventInfo * cei,QofIdTypeConst entity_type,QofEventId event_mask,gboolean or_in)232 add_event_type (ComponentEventInfo *cei, QofIdTypeConst entity_type,
233                 QofEventId event_mask, gboolean or_in)
234 {
235     QofEventId *mask;
236 
237     g_return_if_fail (cei);
238     g_return_if_fail (cei->event_masks);
239     g_return_if_fail (entity_type);
240 
241     mask = g_hash_table_lookup (cei->event_masks, entity_type);
242     if (!mask)
243     {
244         const char * key = qof_string_cache_insert ((gpointer) entity_type);
245         mask = g_new0 (QofEventId, 1);
246         g_hash_table_insert (cei->event_masks, (gpointer)key, mask);
247     }
248 
249     if (or_in)
250         *mask |= event_mask;
251     else
252         *mask = event_mask;
253 }
254 
255 static void
gnc_cm_event_handler(QofInstance * entity,QofEventId event_type,gpointer user_data,gpointer event_data)256 gnc_cm_event_handler (QofInstance *entity,
257                       QofEventId event_type,
258                       gpointer user_data,
259                       gpointer event_data)
260 {
261     const GncGUID *guid = qof_entity_get_guid(entity);
262 #if CM_DEBUG
263     gchar guidstr[GUID_ENCODING_LENGTH+1];
264     guid_to_string_buff (guid, guidstr);
265     fprintf (stderr, "event_handler: event %d, entity %p, guid %s\n", event_type,
266              entity, guidstr);
267 #endif
268     add_event (&changes, guid, event_type, TRUE);
269 
270     if (QOF_CHECK_TYPE(entity, GNC_ID_SPLIT))
271     {
272         /* split events are never generated by the engine, but might
273          * be generated by a backend (viz. the postgres backend.)
274          * Handle them like a transaction modify event. */
275         add_event_type (&changes, GNC_ID_TRANS, QOF_EVENT_MODIFY, TRUE);
276     }
277     else
278         add_event_type (&changes, entity->e_type, event_type, TRUE);
279 
280     got_events = TRUE;
281 
282     if (suspend_counter == 0)
283         gnc_gui_refresh_internal (FALSE);
284 }
285 
286 static gint handler_id;
287 
288 void
gnc_component_manager_init(void)289 gnc_component_manager_init (void)
290 {
291     if (changes.entity_events)
292     {
293         PERR ("component manager already initialized");
294         return;
295     }
296 
297     changes.event_masks = g_hash_table_new (g_str_hash, g_str_equal);
298     changes.entity_events = guid_hash_table_new ();
299 
300     changes_backup.event_masks = g_hash_table_new (g_str_hash, g_str_equal);
301     changes_backup.entity_events = guid_hash_table_new ();
302 
303     handler_id = qof_event_register_handler (gnc_cm_event_handler, NULL);
304 }
305 
306 void
gnc_component_manager_shutdown(void)307 gnc_component_manager_shutdown (void)
308 {
309     if (!changes.entity_events)
310     {
311         PERR ("component manager not initialized");
312         return;
313     }
314 
315     destroy_mask_hash (changes.event_masks);
316     changes.event_masks = NULL;
317 
318     destroy_event_hash (changes.entity_events);
319     changes.entity_events = NULL;
320 
321     destroy_mask_hash (changes_backup.event_masks);
322     changes_backup.event_masks = NULL;
323 
324     destroy_event_hash (changes_backup.entity_events);
325     changes_backup.entity_events = NULL;
326 
327     qof_event_unregister_handler (handler_id);
328 }
329 
330 static ComponentInfo *
find_component(gint component_id)331 find_component (gint component_id)
332 {
333     GList *node;
334 
335     for (node = components; node; node = node->next)
336     {
337         ComponentInfo *ci = node->data;
338 
339         if (ci->component_id == component_id)
340             return ci;
341     }
342 
343     return NULL;
344 }
345 
346 static GList *
find_components_by_data(gpointer user_data)347 find_components_by_data (gpointer user_data)
348 {
349     GList *list = NULL;
350     GList *node;
351 
352     for (node = components; node; node = node->next)
353     {
354         ComponentInfo *ci = node->data;
355 
356         if (ci->user_data == user_data)
357             list = g_list_prepend (list, ci);
358     }
359 
360     return list;
361 }
362 
363 static GList *
find_components_by_session(gpointer session)364 find_components_by_session (gpointer session)
365 {
366     GList *list = NULL;
367     GList *node;
368 
369     for (node = components; node; node = node->next)
370     {
371         ComponentInfo *ci = node->data;
372 
373         if (ci->session == session)
374             list = g_list_prepend (list, ci);
375     }
376 
377     return list;
378 }
379 
380 static ComponentInfo *
gnc_register_gui_component_internal(const char * component_class)381 gnc_register_gui_component_internal (const char * component_class)
382 {
383     ComponentInfo *ci;
384     gint component_id;
385 
386     g_return_val_if_fail (component_class, NULL);
387 
388     /* look for a free handler id */
389     component_id = next_component_id;
390 
391     /* design warning: if we ever get 2^32-1 components,
392        this loop is infinite.  Instead of fixing it, we'll just
393        complain when (if) we get half way there (probably never).
394     */
395     while (find_component (component_id))
396         if (++component_id == NO_COMPONENT)
397             component_id++;
398 
399     if (component_id < 0)
400         PERR("Amazing! Half way to running out of component_ids.");
401 
402     /* found one, add the handler */
403     ci = g_new0 (ComponentInfo, 1);
404 
405     ci->watch_info.event_masks = g_hash_table_new (g_str_hash, g_str_equal);
406     ci->watch_info.entity_events = guid_hash_table_new ();
407 
408     ci->component_class = g_strdup (component_class);
409     ci->component_id = component_id;
410     ci->session = NULL;
411 
412     components = g_list_prepend (components, ci);
413 
414     /* update id for next registration */
415     next_component_id = component_id + 1;
416 
417 #if CM_DEBUG
418     fprintf (stderr, "Register component %d in class %s\n",
419              component_id, component_class ? component_class : "(null)");
420     dump_components ();
421 #endif
422 
423     return ci;
424 }
425 
426 gint
gnc_register_gui_component(const char * component_class,GNCComponentRefreshHandler refresh_handler,GNCComponentCloseHandler close_handler,gpointer user_data)427 gnc_register_gui_component (const char *component_class,
428                             GNCComponentRefreshHandler refresh_handler,
429                             GNCComponentCloseHandler close_handler,
430                             gpointer user_data)
431 {
432     ComponentInfo *ci;
433 
434     /* sanity check */
435     if (!component_class)
436     {
437         PERR ("no class specified");
438         return NO_COMPONENT;
439     }
440 
441     ci = gnc_register_gui_component_internal (component_class);
442     g_return_val_if_fail (ci, NO_COMPONENT);
443 
444     ci->refresh_handler = refresh_handler;
445     ci->close_handler = close_handler;
446     ci->user_data = user_data;
447 
448     return ci->component_id;
449 }
450 
451 void
gnc_gui_component_watch_entity(gint component_id,const GncGUID * entity,QofEventId event_mask)452 gnc_gui_component_watch_entity (gint component_id,
453                                 const GncGUID *entity,
454                                 QofEventId event_mask)
455 {
456     ComponentInfo *ci;
457 
458     if (entity == NULL)
459         return;
460 
461     ci = find_component (component_id);
462     if (!ci)
463     {
464         PERR ("component not found");
465         return;
466     }
467 
468     add_event (&ci->watch_info, entity, event_mask, FALSE);
469 }
470 
471 void
gnc_gui_component_watch_entity_type(gint component_id,QofIdTypeConst entity_type,QofEventId event_mask)472 gnc_gui_component_watch_entity_type (gint component_id,
473                                      QofIdTypeConst entity_type,
474                                      QofEventId event_mask)
475 {
476     ComponentInfo *ci;
477 
478     ci = find_component (component_id);
479     if (!ci)
480     {
481         PERR ("component not found");
482         return;
483     }
484 
485     add_event_type (&ci->watch_info, entity_type, event_mask, FALSE);
486 }
487 
488 const EventInfo *
gnc_gui_get_entity_events(GHashTable * changes,const GncGUID * entity)489 gnc_gui_get_entity_events (GHashTable *changes, const GncGUID *entity)
490 {
491     if (!changes || !entity)
492         return QOF_EVENT_NONE;
493 
494     return g_hash_table_lookup (changes, entity);
495 }
496 
497 void
gnc_gui_component_clear_watches(gint component_id)498 gnc_gui_component_clear_watches (gint component_id)
499 {
500     ComponentInfo *ci;
501 
502     ci = find_component (component_id);
503     if (!ci)
504     {
505         PERR ("component not found");
506         return;
507     }
508 
509     clear_event_info (&ci->watch_info);
510 }
511 
512 void
gnc_unregister_gui_component(gint component_id)513 gnc_unregister_gui_component (gint component_id)
514 {
515     ComponentInfo *ci;
516 
517     ci = find_component (component_id);
518     if (!ci)
519     {
520         PERR ("component %d not found", component_id);
521         return;
522     }
523 
524 #if CM_DEBUG
525     fprintf (stderr, "Unregister component %d in class %s\n",
526              ci->component_id,
527              ci->component_class ? ci->component_class : "(null)");
528 #endif
529 
530     gnc_gui_component_clear_watches (component_id);
531 
532     components = g_list_remove (components, ci);
533 
534     destroy_mask_hash (ci->watch_info.event_masks);
535     ci->watch_info.event_masks = NULL;
536 
537     destroy_event_hash (ci->watch_info.entity_events);
538     ci->watch_info.entity_events = NULL;
539 
540     g_free (ci->component_class);
541     ci->component_class = NULL;
542 
543     g_free (ci);
544 
545 #if CM_DEBUG
546     dump_components ();
547 #endif
548 }
549 
550 void
gnc_unregister_gui_component_by_data(const char * component_class,gpointer user_data)551 gnc_unregister_gui_component_by_data (const char *component_class,
552                                       gpointer user_data)
553 {
554     GList *list;
555     GList *node;
556 
557     list = find_components_by_data (user_data);
558 
559     for (node = list; node; node = node->next)
560     {
561         ComponentInfo *ci = node->data;
562 
563         if (component_class &&
564                 g_strcmp0 (component_class, ci->component_class) != 0)
565             continue;
566 
567         gnc_unregister_gui_component (ci->component_id);
568     }
569 
570     g_list_free (list);
571 }
572 
573 void
gnc_suspend_gui_refresh(void)574 gnc_suspend_gui_refresh (void)
575 {
576     suspend_counter++;
577 
578     if (suspend_counter == 0)
579     {
580         PERR ("suspend counter overflow");
581     }
582 }
583 
584 void
gnc_resume_gui_refresh(void)585 gnc_resume_gui_refresh (void)
586 {
587     if (suspend_counter == 0)
588     {
589         PERR ("suspend counter underflow");
590         return;
591     }
592 
593     suspend_counter--;
594 
595     if (suspend_counter == 0)
596         gnc_gui_refresh_internal (FALSE);
597 }
598 
599 static void
match_type_helper(gpointer key,gpointer value,gpointer user_data)600 match_type_helper (gpointer key, gpointer value, gpointer user_data)
601 {
602     ComponentEventInfo *cei = user_data;
603     QofIdType id_type = key;
604     QofEventId * et = value;
605     QofEventId * et_2;
606 
607     et_2 = g_hash_table_lookup (cei->event_masks, id_type);
608     if (!et_2)
609         return;
610 
611     if (*et & *et_2)
612         cei->match = TRUE;
613 }
614 
615 static void
match_helper(gpointer key,gpointer value,gpointer user_data)616 match_helper (gpointer key, gpointer value, gpointer user_data)
617 {
618     GncGUID *guid = key;
619     EventInfo *ei_1 = value;
620     EventInfo *ei_2;
621     ComponentEventInfo *cei = user_data;
622 
623     ei_2 = g_hash_table_lookup (cei->entity_events, guid);
624     if (!ei_2)
625         return;
626 
627     if (ei_1->event_mask & ei_2->event_mask)
628         cei->match = TRUE;
629 }
630 
631 static gboolean
changes_match(ComponentEventInfo * cei,ComponentEventInfo * changes)632 changes_match (ComponentEventInfo *cei, ComponentEventInfo *changes)
633 {
634     ComponentEventInfo *big_cei;
635     GHashTable *smalltable;
636 
637     if (cei == NULL)
638         return FALSE;
639 
640     /* check types first, for efficiency */
641     cei->match = FALSE;
642     g_hash_table_foreach (changes->event_masks, match_type_helper, cei);
643     if (cei->match)
644         return TRUE;
645 
646     if (g_hash_table_size (cei->entity_events) <=
647             g_hash_table_size (changes->entity_events))
648     {
649         smalltable = cei->entity_events;
650         big_cei = changes;
651     }
652     else
653     {
654         smalltable = changes->entity_events;
655         big_cei = cei;
656     }
657 
658     big_cei->match = FALSE;
659 
660     g_hash_table_foreach (smalltable, match_helper, big_cei);
661 
662     return big_cei->match;
663 }
664 
665 static void
gnc_gui_refresh_internal(gboolean force)666 gnc_gui_refresh_internal (gboolean force)
667 {
668     GList *list;
669     GList *node;
670 
671     if (!got_events && !force)
672         return;
673 
674     gnc_suspend_gui_refresh ();
675 
676     {
677         GHashTable *table;
678 
679         table = changes_backup.event_masks;
680         changes_backup.event_masks = changes.event_masks;
681         changes.event_masks = table;
682 
683         table = changes_backup.entity_events;
684         changes_backup.entity_events = changes.entity_events;
685         changes.entity_events = table;
686     }
687 
688 #if CM_DEBUG
689     fprintf (stderr, "%srefresh!\n", force ? "forced " : "");
690 #endif
691 
692     list = find_component_ids_by_class (NULL);
693     // reverse the list so class GncPluginPageRegister is before register-single
694     list = g_list_reverse (list);
695 
696     for (node = list; node; node = node->next)
697     {
698         ComponentInfo *ci = find_component (GPOINTER_TO_INT (node->data));
699 
700         if (!ci)
701             continue;
702 
703         if (!ci->refresh_handler)
704         {
705 #if CM_DEBUG
706             fprintf (stderr, "no handlers for %s:%d\n", ci->component_class, ci->component_id);
707 #endif
708             continue;
709         }
710 
711         if (force)
712         {
713             if (ci->refresh_handler)
714             {
715 #if CM_DEBUG
716                 fprintf (stderr, "calling %s:%d C handler\n", ci->component_class, ci->component_id);
717 #endif
718                 ci->refresh_handler (NULL, ci->user_data);
719             }
720         }
721         else if (changes_match (&ci->watch_info, &changes_backup))
722         {
723             if (ci->refresh_handler)
724             {
725 #if CM_DEBUG
726                 fprintf (stderr, "calling %s:%d C handler\n", ci->component_class, ci->component_id);
727 #endif
728                 ci->refresh_handler (changes_backup.entity_events, ci->user_data);
729             }
730         }
731         else
732         {
733 #if CM_DEBUG
734             fprintf (stderr, "no match for %s:%d\n", ci->component_class, ci->component_id);
735 #endif
736         }
737     }
738 
739     clear_event_info (&changes_backup);
740     got_events = FALSE;
741 
742     g_list_free (list);
743 
744     gnc_resume_gui_refresh ();
745 }
746 
747 void
gnc_gui_refresh_all(void)748 gnc_gui_refresh_all (void)
749 {
750     if (suspend_counter != 0)
751     {
752         PERR ("suspend counter not zero");
753         return;
754     }
755 
756     gnc_gui_refresh_internal (TRUE);
757 }
758 
759 gboolean
gnc_gui_refresh_suspended(void)760 gnc_gui_refresh_suspended (void)
761 {
762     return suspend_counter != 0;
763 }
764 
765 void
gnc_close_gui_component(gint component_id)766 gnc_close_gui_component (gint component_id)
767 {
768     ComponentInfo *ci;
769 
770     ci = find_component (component_id);
771     if (!ci)
772     {
773         PERR ("component not found");
774         return;
775     }
776 
777     if (!ci->close_handler)
778         return;
779 
780     if (ci->close_handler)
781         ci->close_handler (ci->user_data);
782 }
783 
784 void
gnc_close_gui_component_by_data(const char * component_class,gpointer user_data)785 gnc_close_gui_component_by_data (const char *component_class,
786                                  gpointer user_data)
787 {
788     GList *list;
789     GList *node;
790 
791     list = find_components_by_data (user_data);
792 
793     for (node = list; node; node = node->next)
794     {
795         ComponentInfo *ci = node->data;
796 
797         if (component_class &&
798                 g_strcmp0 (component_class, ci->component_class) != 0)
799             continue;
800 
801         gnc_close_gui_component (ci->component_id);
802     }
803 
804     g_list_free (list);
805 }
806 
807 void
gnc_gui_component_set_session(gint component_id,gpointer session)808 gnc_gui_component_set_session (gint component_id, gpointer session)
809 {
810     ComponentInfo *ci;
811 
812     ci = find_component (component_id);
813     if (!ci)
814     {
815         PERR ("component not found");
816         return;
817     }
818 
819     ci->session = session;
820 }
821 
822 void
gnc_close_gui_component_by_session(gpointer session)823 gnc_close_gui_component_by_session (gpointer session)
824 {
825     GList *list;
826     GList *node;
827 
828     list = find_components_by_session (session);
829 
830     // reverse the list so class like dialog-options close before window-report
831     list = g_list_reverse (list);
832 
833     for (node = list; node; node = node->next)
834     {
835         ComponentInfo *ci = node->data;
836 
837         gnc_close_gui_component (ci->component_id);
838     }
839 
840     g_list_free (list);
841 }
842 
843 GList *
gnc_find_gui_components(const char * component_class,GNCComponentFindHandler find_handler,gpointer find_data)844 gnc_find_gui_components (const char *component_class,
845                          GNCComponentFindHandler find_handler,
846                          gpointer find_data)
847 {
848     GList *list = NULL;
849     GList *node;
850 
851     if (!component_class)
852         return NULL;
853 
854     for (node = components; node; node = node->next)
855     {
856         ComponentInfo *ci = node->data;
857 
858         if (g_strcmp0 (component_class, ci->component_class) != 0)
859             continue;
860 
861         if (find_handler && !find_handler (find_data, ci->user_data))
862             continue;
863 
864         list = g_list_prepend (list, ci->user_data);
865     }
866 
867     return list;
868 }
869 
870 gpointer
gnc_find_first_gui_component(const char * component_class,GNCComponentFindHandler find_handler,gpointer find_data)871 gnc_find_first_gui_component (const char *component_class,
872                               GNCComponentFindHandler find_handler,
873                               gpointer find_data)
874 {
875     GList *list;
876     gpointer user_data;
877 
878 #if CM_DEBUG
879     fprintf (stderr, "find: class %s, fn %p, data %p\n", component_class,
880              find_handler, find_data);
881 #endif
882     if (!component_class)
883         return NULL;
884 
885     list = gnc_find_gui_components (component_class, find_handler, find_data);
886     if (!list)
887         return NULL;
888 
889     user_data = list->data;
890 
891     g_list_free (list);
892 
893 #if CM_DEBUG
894     fprintf (stderr, "found: data %p\n", user_data);
895 #endif
896     return user_data;
897 }
898 
899 static GList *
find_component_ids_by_class(const char * component_class)900 find_component_ids_by_class (const char *component_class)
901 {
902     GList *list = NULL;
903     GList *node;
904 
905     for (node = components; node; node = node->next)
906     {
907         ComponentInfo *ci = node->data;
908 
909         if (component_class &&
910                 g_strcmp0 (component_class, ci->component_class) != 0)
911             continue;
912 
913         list = g_list_prepend (list, GINT_TO_POINTER (ci->component_id));
914     }
915 
916     return list;
917 }
918 
919 gint
gnc_forall_gui_components(const char * component_class,GNCComponentHandler handler,gpointer iter_data)920 gnc_forall_gui_components (const char *component_class,
921                            GNCComponentHandler handler,
922                            gpointer iter_data)
923 {
924     GList *list;
925     GList *node;
926     gint count = 0;
927 
928     if (!handler)
929         return(0);
930 
931     /* so components can be destroyed during the forall */
932     list = find_component_ids_by_class (component_class);
933 
934     for (node = list; node; node = node->next)
935     {
936         ComponentInfo *ci = find_component (GPOINTER_TO_INT (node->data));
937 
938         if (!ci)
939             continue;
940 
941         if (handler (ci->component_class, ci->component_id, ci->user_data, iter_data))
942             count++;
943     }
944 
945     g_list_free (list);
946     return(count);
947 }
948