source: trunk/third/bonobo/bonobo/bonobo-ui-engine.c @ 17169

Revision 17169, 65.9 KB checked in by ghudson, 23 years ago (diff)
This commit was generated by cvs2svn to compensate for changes in r17168, which included commits to RCS files with non-trunk default branches.
Line 
1/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
2/*
3 * bonobo-ui-engine.c: The Bonobo UI/XML Sync engine.
4 *
5 * Author:
6 *      Michael Meeks (michael@helixcode.com)
7 *
8 * Copyright 2000 Helix Code, Inc.
9 */
10
11#include <config.h>
12#include <stdlib.h>
13#include <gtk/gtksignal.h>
14#include <stdio.h>
15
16#include <bonobo/bonobo-widget.h>
17#include <bonobo/bonobo-ui-xml.h>
18#include <bonobo/bonobo-ui-util.h>
19#include <bonobo/bonobo-ui-engine.h>
20#include <bonobo/bonobo-ui-engine-config.h>
21#include <bonobo/bonobo-ui-engine-private.h>
22#include <bonobo/bonobo-exception.h>
23
24/* Various debugging output defines */
25#undef STATE_SYNC_DEBUG
26#undef WIDGET_SYNC_DEBUG
27#undef XML_MERGE_DEBUG
28
29#define PARENT_TYPE gtk_object_get_type ()
30
31static GtkObjectClass *parent_class = NULL;
32
33enum {
34        ADD_HINT,
35        REMOVE_HINT,
36        EMIT_VERB_ON,
37        EMIT_EVENT_ON,
38        LAST_SIGNAL
39};
40
41guint signals [LAST_SIGNAL] = { 0 };
42
43struct _BonoboUIEnginePrivate {
44        BonoboUIXml  *tree;
45        int           frozen;
46        GSList       *syncs;
47        GSList       *state_updates;
48        GSList       *components;
49        BonoboObject *container;
50
51        BonoboUIEngineConfig *config;
52
53        GHashTable   *cmd_to_node;
54};
55
56/*
57 *  Mapping from nodes to their synchronization
58 * class information functions.
59 */
60
61static BonoboUISync *
62find_sync_for_node (BonoboUIEngine *engine,
63                    BonoboUINode   *node)
64{
65        GSList       *l;
66        BonoboUISync *ret = NULL;
67
68        if (!node) {
69/*              fprintf (stderr, "Could not find sync for some path\n");*/
70                return NULL;
71        }
72
73        for (l = engine->priv->syncs; l; l = l->next) {
74                if (bonobo_ui_sync_can_handle (l->data, node)) {
75                        ret = l->data;
76                        break;
77                }
78        }
79
80        if (ret) {
81/*              fprintf (stderr, "Found sync '%s' for path '%s'\n",
82                         gtk_type_name (GTK_OBJECT (ret)->klass->type),
83                         bonobo_ui_xml_make_path (node));*/
84                return ret;
85        }
86
87        return find_sync_for_node (
88                engine, bonobo_ui_node_parent (node));
89}
90
91/**
92 * bonobo_ui_engine_add_sync:
93 * @engine: the enginer
94 * @sync: the synchronizer
95 *
96 * Add a #BonoboUISync synchronizer to the engine
97 **/
98void
99bonobo_ui_engine_add_sync (BonoboUIEngine *engine,
100                           BonoboUISync   *sync)
101{
102        g_return_if_fail (BONOBO_IS_UI_ENGINE (engine));
103
104        if (g_slist_find (engine->priv->syncs, sync))
105                g_warning ("Already added this Synchronizer %p", sync);
106        else
107                engine->priv->syncs = g_slist_append (
108                        engine->priv->syncs, sync);
109}
110
111/**
112 * bonobo_ui_engine_remove_sync:
113 * @engine: the engine
114 * @sync: the sync
115 *
116 * Remove a specified #BonoboUISync synchronizer from the engine
117 **/
118void
119bonobo_ui_engine_remove_sync (BonoboUIEngine *engine,
120                              BonoboUISync   *sync)
121{
122        g_return_if_fail (BONOBO_IS_UI_ENGINE (engine));
123
124        engine->priv->syncs = g_slist_remove (
125                engine->priv->syncs, sync);
126}
127
128/**
129 * bonobo_ui_engine_get_syncs:
130 * @engine: the engine
131 *
132 * Retrieve a list of available synchronizers.
133 *
134 * Return value: a GSList of #BonoboUISync s
135 **/
136GSList *
137bonobo_ui_engine_get_syncs (BonoboUIEngine *engine)
138{
139        g_return_val_if_fail (BONOBO_IS_UI_ENGINE (engine), NULL);
140
141        return g_slist_copy (engine->priv->syncs);
142}
143
144/*
145 * Cmd -> Node mapping functionality.
146 */
147
148typedef struct {
149        char   *name;
150        GSList *nodes;
151} CmdToNode;
152
153static char *
154node_get_id (BonoboUINode *node)
155{
156        char *txt;
157        char *ret;
158
159        g_return_val_if_fail (node != NULL, NULL);
160
161        if (!(txt = bonobo_ui_node_get_attr (node, "id"))) {
162                txt = bonobo_ui_node_get_attr (node, "verb");
163                if (txt && txt [0] == '\0') {
164                        bonobo_ui_node_free_string (txt);
165                        txt = bonobo_ui_node_get_attr (node, "name");
166                }
167        }
168
169        if (txt) {
170                ret = g_strdup (txt);
171                bonobo_ui_node_free_string (txt);
172        } else
173                ret = NULL;
174
175        return ret;
176}
177
178static void
179cmd_to_node_add_node (BonoboUIEngine *engine,
180                      BonoboUINode   *node,
181                      gboolean        recurse)
182{
183        CmdToNode *ctn;
184        char      *name;
185
186        if (recurse) {
187                BonoboUINode *l;
188
189                for (l = bonobo_ui_node_children (node); l;
190                     l = bonobo_ui_node_next (l))
191                        cmd_to_node_add_node (engine, l, TRUE);
192        }
193
194        name = node_get_id (node);
195        if (!name)
196                return;
197
198        ctn = g_hash_table_lookup (
199                engine->priv->cmd_to_node, name);
200
201        if (!ctn) {
202                ctn = g_new (CmdToNode, 1);
203
204                ctn->name = name;
205                ctn->nodes = NULL;
206                g_hash_table_insert (
207                        engine->priv->cmd_to_node, ctn->name, ctn);
208        } else
209                g_free (name);
210
211/*      fprintf (stderr, "Adding %d'th '%s'\n",
212        g_slist_length (ctn->nodes), ctn->name);*/
213
214        ctn->nodes = g_slist_prepend (ctn->nodes, node);
215}
216
217static void
218cmd_to_node_remove_node (BonoboUIEngine *engine,
219                         BonoboUINode   *node,
220                         gboolean        recurse)
221{
222        CmdToNode *ctn;
223        char      *name;
224
225        if (recurse) {
226                BonoboUINode *l;
227
228                for (l = bonobo_ui_node_children (node); l;
229                     l = bonobo_ui_node_next (l))
230                        cmd_to_node_remove_node (engine, l, TRUE);
231        }
232
233        name = node_get_id (node);
234        if (!name)
235                return;
236
237        ctn = g_hash_table_lookup (
238                engine->priv->cmd_to_node, name);
239
240        if (!ctn)
241                g_warning ("Removing non-registered name '%s'", name);
242        else {
243/*              fprintf (stderr, "Removing %d'th '%s'\n",
244                g_slist_length (ctn->nodes), name);*/
245                ctn->nodes = g_slist_remove (ctn->nodes, node);
246        }
247
248        /*
249         * NB. we leave the CmdToNode structures around
250         * for future use.
251         */
252        g_free (name);
253}
254
255static int
256cmd_to_node_clear_hash (gpointer key,
257                        gpointer value,
258                        gpointer user_data)
259{
260        CmdToNode *ctn = value;
261
262        g_free (ctn->name);
263        g_slist_free (ctn->nodes);
264        g_free (ctn);
265
266        return TRUE;
267}
268
269static const GSList *
270cmd_to_nodes (BonoboUIEngine *engine,
271              const char     *name)
272{
273        CmdToNode *ctn;
274
275        if (!name)
276                return NULL;
277
278        ctn = g_hash_table_lookup (
279                engine->priv->cmd_to_node, name);
280
281        return ctn ? ctn->nodes : NULL;
282}
283
284#define NODE_IS_ROOT_WIDGET(n)   ((n->type & ROOT_WIDGET) != 0)
285#define NODE_IS_CUSTOM_WIDGET(n) ((n->type & CUSTOM_WIDGET) != 0)
286
287typedef enum {
288        ROOT_WIDGET   = 0x1,
289        CUSTOM_WIDGET = 0x2
290} NodeType;
291
292typedef struct {
293        BonoboUIXmlData parent;
294
295        int             type;
296        GtkWidget      *widget;
297        Bonobo_Unknown  object;
298} NodeInfo;
299
300/*
301 * BonoboUIXml impl functions
302 */
303
304static BonoboUIXmlData *
305info_new_fn (void)
306{
307        NodeInfo *info = g_new0 (NodeInfo, 1);
308
309        info->object = CORBA_OBJECT_NIL;
310
311        return (BonoboUIXmlData *) info;
312}
313
314static void
315info_free_fn (BonoboUIXmlData *data)
316{
317        NodeInfo *info = (NodeInfo *) data;
318
319        if (info->object != CORBA_OBJECT_NIL) {
320                bonobo_object_release_unref (info->object, NULL);
321                info->object = CORBA_OBJECT_NIL;
322        }
323        info->widget = NULL;
324
325        g_free (data);
326}
327
328static void
329info_dump_fn (BonoboUIXml *tree, BonoboUINode *node)
330{
331        NodeInfo *info = bonobo_ui_xml_get_data (tree, node);
332
333        if (info) {
334                fprintf (stderr, " '%15s' object %8p type %d ",
335                         (char *)info->parent.id, info->object, info->type);
336
337                if (info->widget) {
338                        BonoboUINode *attached_node =
339                                bonobo_ui_engine_widget_get_node (info->widget);
340
341                        fprintf (stderr, "widget '%8p' with node '%8p' attached ",
342                                 info->widget, attached_node);
343
344                        if (attached_node == NULL)
345                                fprintf (stderr, "is NULL\n");
346
347                        else if (attached_node != node)
348                                fprintf (stderr, "Serious mismatch attaches should be '%8p'\n",
349                                         node);
350                        else {
351                                if (info->widget->parent)
352                                        fprintf (stderr, "and matching; parented\n");
353                                else
354                                        fprintf (stderr, "and matching; BUT NO PARENT!\n");
355                        }
356                } else
357                        fprintf (stderr, " no associated widget\n");
358        } else
359                fprintf (stderr, " very weird no data on node '%p'\n", node);
360}
361
362static void
363add_node_fn (BonoboUINode *parent,
364             BonoboUINode *child,
365             gpointer      user_data)
366{
367        cmd_to_node_add_node (user_data, child, TRUE);
368}
369
370/*
371 * BonoboUIXml signal functions
372 */
373
374static void
375custom_widget_unparent (NodeInfo *info)
376{
377        GtkContainer *container;
378
379        g_return_if_fail (info != NULL);
380
381        if (!info->widget)
382                return;
383
384        g_return_if_fail (GTK_IS_WIDGET (info->widget));
385
386        if (info->widget->parent) {
387                container = GTK_CONTAINER (info->widget->parent);
388                g_return_if_fail (container != NULL);
389
390                gtk_widget_ref (info->widget);
391                gtk_container_remove (container, info->widget);
392        }
393}
394
395static void
396sync_widget_set_node (BonoboUISync *sync,
397                      GtkWidget    *widget,
398                      BonoboUINode *node)
399{
400        GtkWidget *attached;
401
402        if (!widget)
403                return;
404
405        g_return_if_fail (sync != NULL);
406
407        bonobo_ui_engine_widget_attach_node (widget, node);
408
409        attached = bonobo_ui_sync_get_attached (sync, widget, node);
410
411        if (attached)
412                bonobo_ui_engine_widget_attach_node (attached, node);
413}
414
415static void
416replace_override_fn (GtkObject      *object,
417                     BonoboUINode   *new,
418                     BonoboUINode   *old,
419                     BonoboUIEngine *engine)
420{
421        NodeInfo  *info = bonobo_ui_xml_get_data (engine->priv->tree, new);
422        NodeInfo  *old_info = bonobo_ui_xml_get_data (engine->priv->tree, old);
423        GtkWidget *old_widget;
424
425        g_return_if_fail (info != NULL);
426        g_return_if_fail (old_info != NULL);
427
428        cmd_to_node_remove_node (engine, old, FALSE);
429        cmd_to_node_add_node    (engine, new, FALSE);
430
431/*      g_warning ("Replace override on '%s' '%s' widget '%p'",
432                   old->name, bonobo_ui_node_get_attr (old, "name"), old_info->widget);
433        info_dump_fn (old_info);
434        info_dump_fn (info);*/
435
436        /* Copy useful stuff across */
437        old_widget = old_info->widget;
438        old_info->widget = NULL;
439
440        info->type = old_info->type;
441        info->widget = old_widget;
442
443        /* Re-stamp the widget - get sync from old node actually in tree */
444        {
445                BonoboUISync *sync = find_sync_for_node (engine, old);
446
447                sync_widget_set_node (sync, info->widget, new);
448        }
449
450        /* Steal object reference */
451        info->object = old_info->object;
452        old_info->object = CORBA_OBJECT_NIL;
453}
454
455static void
456prune_node (BonoboUIEngine *engine,
457            BonoboUINode   *node,
458            gboolean        save_custom)
459{
460        NodeInfo     *info;
461
462        if (!node)
463                return;
464
465        info = bonobo_ui_xml_get_data (engine->priv->tree, node);
466
467        if (info->widget) {
468                gboolean save;
469               
470                save = NODE_IS_CUSTOM_WIDGET (info) && save_custom;
471
472                if (!NODE_IS_ROOT_WIDGET (info) && !save) {
473                        BonoboUISync *sync;
474                        GtkWidget    *item;
475
476                        item = info->widget;
477
478                        if ((sync = find_sync_for_node (engine, node))) {
479                                GtkWidget *attached;
480
481                                attached = bonobo_ui_sync_get_attached (
482                                        sync, item, node);
483
484                                if (attached) {
485#ifdef XML_MERGE_DEBUG
486                                        fprintf (stderr, "Got '%p' attached to '%p' for node '%s'\n",
487                                                 attached, item,
488                                                 bonobo_ui_xml_make_path (node));
489#endif
490
491                                        item = attached;
492                                }
493                        }
494
495#ifdef XML_MERGE_DEBUG
496                        fprintf (stderr, "Destroy widget '%s' '%p'\n",
497                                 bonobo_ui_xml_make_path (node), item);
498#endif
499
500                        gtk_widget_destroy (item);
501
502                } else {
503                        if (save)
504                                custom_widget_unparent (info);
505/*                      printf ("leave widget '%s'\n",
506                        bonobo_ui_xml_make_path (node));*/
507                }
508
509                if (!save)
510                        info->widget = NULL;
511        }
512}
513
514/**
515 * bonobo_ui_engine_prune_widget_info:
516 * @engine: the engine
517 * @node: the node
518 * @save_custom: whether to save custom widgets
519 *
520 * This function destroys any widgets associated with
521 * @node and all its children, if @save_custom, any widget
522 * that is a custom widget ( such as a control ) will be
523 * preserved. All widgets flagged ROOT are preserved always.
524 **/
525void
526bonobo_ui_engine_prune_widget_info (BonoboUIEngine *engine,
527                                    BonoboUINode   *node,
528                                    gboolean        save_custom)
529{
530        BonoboUINode *l;
531
532        if (!node)
533                return;
534
535        for (l = bonobo_ui_node_children (node); l;
536             l = bonobo_ui_node_next (l))
537                bonobo_ui_engine_prune_widget_info (
538                        engine, l, TRUE);
539
540        prune_node (engine, node, save_custom);
541}
542
543static void
544override_fn (GtkObject      *object,
545             BonoboUINode   *new,
546             BonoboUINode   *old,
547             BonoboUIEngine *engine)
548{
549#ifdef XML_MERGE_DEBUG
550        fprintf (stderr, "Override '%s'\n",
551                 bonobo_ui_xml_make_path (old));
552#endif
553
554        if (!strcmp (bonobo_ui_node_get_name (new),
555                     bonobo_ui_node_get_name (old))) {
556                replace_override_fn (object, new, old, engine);
557        } else {
558                bonobo_ui_engine_prune_widget_info (engine, old, TRUE);
559
560                cmd_to_node_remove_node (engine, old, FALSE);
561                cmd_to_node_add_node    (engine, new, FALSE);
562        }
563}
564
565static void
566reinstate_fn (GtkObject      *object,
567              BonoboUINode   *node,
568              BonoboUIEngine *engine)
569{
570#ifdef XML_MERGE_DEBUG
571        fprintf (stderr, "Reinstate '%s'\n",
572                 bonobo_ui_xml_make_path (node));
573/*      bonobo_ui_engine_dump (engine, stderr, "pre reinstate_fn");*/
574#endif
575
576        bonobo_ui_engine_prune_widget_info (engine, node, TRUE);
577
578        cmd_to_node_add_node (engine, node, TRUE);
579}
580
581static void
582rename_fn (GtkObject      *object,
583           BonoboUINode   *node,
584           BonoboUIEngine *engine)
585{
586#ifdef XML_MERGE_DEBUG
587        fprintf (stderr, "Rename '%s'\n",
588                 bonobo_ui_xml_make_path (node));
589#endif
590}
591
592static void
593remove_fn (GtkObject      *object,
594           BonoboUINode   *node,
595           BonoboUIEngine *engine)
596{
597#ifdef XML_MERGE_DEBUG
598        fprintf (stderr, "Remove on '%s'\n",
599                 bonobo_ui_xml_make_path (node));
600
601/*      bonobo_ui_engine_dump (engine, stderr, "before remove_fn");*/
602#endif
603        bonobo_ui_engine_prune_widget_info (engine, node, FALSE);
604
605        if (bonobo_ui_node_parent (node) == engine->priv->tree->root) {
606                BonoboUISync *sync = find_sync_for_node (engine, node);
607
608                if (sync)
609                        bonobo_ui_sync_remove_root (sync, node);
610        }
611
612        cmd_to_node_remove_node (engine, node, TRUE);
613}
614
615/*
616 * Sub Component management functions
617 */
618
619/*
620 *  This indirection is needed so we can serialize user settings
621 * on a per component basis in future.
622 */
623typedef struct {
624        char          *name;
625        Bonobo_Unknown object;
626} SubComponent;
627
628
629static SubComponent *
630sub_component_get (BonoboUIEngine *engine, const char *name)
631{
632        SubComponent *component;
633        GSList       *l;
634
635        g_return_val_if_fail (name != NULL, NULL);
636        g_return_val_if_fail (BONOBO_IS_UI_ENGINE (engine), NULL);
637
638        for (l = engine->priv->components; l; l = l->next) {
639                component = l->data;
640               
641                if (!strcmp (component->name, name))
642                        return component;
643        }
644       
645        component = g_new (SubComponent, 1);
646        component->name = g_strdup (name);
647        component->object = CORBA_OBJECT_NIL;
648
649        engine->priv->components = g_slist_prepend (
650                engine->priv->components, component);
651
652        return component;
653}
654
655static SubComponent *
656sub_component_get_by_ref (BonoboUIEngine *engine, CORBA_Object obj)
657{
658        GSList *l;
659        SubComponent *component = NULL;
660        CORBA_Environment ev;
661
662        g_return_val_if_fail (obj != CORBA_OBJECT_NIL, NULL);
663        g_return_val_if_fail (BONOBO_IS_UI_ENGINE (engine), NULL);
664
665        CORBA_exception_init (&ev);
666
667        for (l = engine->priv->components; l; l = l->next) {
668                gboolean equiv;
669                component = l->data;
670
671                equiv = CORBA_Object_is_equivalent (component->object, obj, &ev);
672
673                if (BONOBO_EX (&ev)) { /* Something very badly wrong */
674                        component = NULL;
675                        break;
676                } else if (equiv)
677                        break;
678        }
679
680        CORBA_exception_free (&ev);
681
682        return component;
683}
684
685static Bonobo_Unknown
686sub_component_objref (BonoboUIEngine *engine, const char *name)
687{
688        SubComponent *component = sub_component_get (engine, name);
689
690        g_return_val_if_fail (component != NULL, CORBA_OBJECT_NIL);
691
692        return component->object;
693}
694
695static void
696sub_components_dump (BonoboUIEngine *engine, FILE *out)
697{
698        GSList *l;
699
700        g_return_if_fail (engine != NULL);
701        g_return_if_fail (engine->priv != NULL);
702
703        fprintf (out, "Component mappings:\n");
704
705        for (l = engine->priv->components; l; l = l->next) {
706                SubComponent *component = l->data;
707               
708                fprintf (out, "\t'%s' -> '%p'\n",
709                         component->name, component->object);
710        }
711}
712
713/* Use the pointer identity instead of a costly compare */
714static char *
715sub_component_cmp_name (BonoboUIEngine *engine, const char *name)
716{
717        SubComponent *component;
718
719        /*
720         * NB. For overriding if we get a NULL we just update the
721         * node without altering the id.
722         */
723        if (!name || name [0] == '\0') {
724                g_warning ("This should never happen");
725                return NULL;
726        }
727
728        component = sub_component_get (engine, name);
729        g_return_val_if_fail (component != NULL, NULL);
730
731        return component->name;
732}
733
734static void
735sub_component_destroy (BonoboUIEngine *engine, SubComponent *component)
736{
737        if (engine->priv->container)
738                gtk_signal_disconnect_by_data (
739                        GTK_OBJECT (engine->priv->container), engine);
740
741        engine->priv->container = NULL;
742
743        engine->priv->components = g_slist_remove (
744                engine->priv->components, component);
745
746        if (component) {
747                g_free (component->name);
748                if (component->object != CORBA_OBJECT_NIL)
749                        bonobo_object_release_unref (component->object, NULL);
750                g_free (component);
751        }
752}
753
754/**
755 * bonobo_ui_engine_deregister_dead_components:
756 * @engine: the engine
757 *
758 * Detect any components that have died and deregister
759 * them - unmerging their UI elements.
760 **/
761void
762bonobo_ui_engine_deregister_dead_components (BonoboUIEngine *engine)
763{
764        SubComponent      *component;
765        GSList            *l, *next;
766        CORBA_Environment  ev;
767
768        g_return_if_fail (BONOBO_IS_UI_ENGINE (engine));
769
770        for (l = engine->priv->components; l; l = next) {
771                next = l->next;
772
773                component = l->data;
774                CORBA_exception_init (&ev);
775
776                if (CORBA_Object_non_existent (component->object, &ev))
777                        bonobo_ui_engine_deregister_component (
778                                engine, component->name);
779
780                CORBA_exception_free (&ev);
781        }
782}
783
784/**
785 * bonobo_ui_engine_get_component_names:
786 * @engine: the engine
787 *
788 * accesses the node's component names.
789 *
790 * Return value: the names of all registered components
791 **/
792GList *
793bonobo_ui_engine_get_component_names (BonoboUIEngine *engine)
794{
795        GSList *l;
796        GList  *retval;
797
798        g_return_val_if_fail (BONOBO_IS_UI_ENGINE (engine), NULL);
799
800        retval = NULL;
801
802        for (l = engine->priv->components; l; l = l->next) {
803                SubComponent *component = l->data;
804       
805                retval = g_list_prepend (retval, component->name);
806        }
807
808        return retval;
809}
810
811/**
812 * bonobo_ui_engine_get_component:
813 * @engine: the engine
814 * @name: the name of the component to fetch
815 *
816 * accesses the components associated with the engine.
817 *
818 * Return value: the component with name @name
819 **/
820Bonobo_Unknown
821bonobo_ui_engine_get_component (BonoboUIEngine *engine,
822                                const char     *name)
823{
824        GSList *l;
825
826        g_return_val_if_fail (name != NULL, CORBA_OBJECT_NIL);
827        g_return_val_if_fail (BONOBO_IS_UI_ENGINE (engine), CORBA_OBJECT_NIL);
828               
829        for (l = engine->priv->components; l; l = l->next) {
830                SubComponent *component = l->data;
831               
832                if (!strcmp (component->name, name))
833                        return component->object;
834        }
835
836        return CORBA_OBJECT_NIL;
837}
838
839/**
840 * bonobo_ui_engine_register_component:
841 * @engine: the engine
842 * @name: a name to associate a component with
843 * @component: the component
844 *
845 * Registers @component with @engine by @name.
846 **/
847void
848bonobo_ui_engine_register_component (BonoboUIEngine *engine,
849                                     const char     *name,
850                                     Bonobo_Unknown  component)
851{
852        SubComponent *subcomp;
853
854        g_return_if_fail (BONOBO_IS_UI_ENGINE (engine));
855
856        if ((subcomp = sub_component_get (engine, name))) {
857                if (subcomp->object != CORBA_OBJECT_NIL)
858                        bonobo_object_release_unref (subcomp->object, NULL);
859        }
860
861        if (component != CORBA_OBJECT_NIL)
862                subcomp->object = bonobo_object_dup_ref (component, NULL);
863        else
864                subcomp->object = CORBA_OBJECT_NIL;
865}
866
867/**
868 * bonobo_ui_engine_deregister_component:
869 * @engine: the engine
870 * @name: the component name
871 *
872 * Deregisters component of @name from @engine.
873 **/
874void
875bonobo_ui_engine_deregister_component (BonoboUIEngine *engine,
876                                       const char     *name)
877{
878        SubComponent *component;
879
880        g_return_if_fail (BONOBO_IS_UI_ENGINE (engine));
881
882        if ((component = sub_component_get (engine, name))) {
883                bonobo_ui_engine_xml_rm (engine, "/", component->name);
884                sub_component_destroy (engine, component);
885        } else
886                g_warning ("Attempting to deregister non-registered "
887                           "component '%s'", name);
888}
889
890/**
891 * bonobo_ui_engine_deregister_component_by_ref:
892 * @engine: the engine
893 * @ref: the ref.
894 *
895 * Deregisters component with reference @ref from @engine.
896 **/
897void
898bonobo_ui_engine_deregister_component_by_ref (BonoboUIEngine *engine,
899                                              Bonobo_Unknown  ref)
900{
901        SubComponent *component;
902
903        g_return_if_fail (BONOBO_IS_UI_ENGINE (engine));
904
905        if ((component = sub_component_get_by_ref (engine, ref))) {
906                bonobo_ui_engine_xml_rm (engine, "/", component->name);
907                sub_component_destroy (engine, component);
908        } else
909                g_warning ("Attempting to deregister non-registered "
910                           "component");
911}
912
913/*
914 * State update signal queueing functions
915 */
916
917typedef struct {
918        BonoboUISync *sync;
919        GtkWidget    *widget;
920        char         *state;
921} StateUpdate;
922
923/*
924 * Update the state later, but other aspects of the widget right now.
925 * It's dangerous to update the state now because we can reenter if we
926 * do that.
927 */
928static StateUpdate *
929state_update_new (BonoboUISync *sync,
930                  GtkWidget    *widget,
931                  BonoboUINode *node)
932{
933        char *hidden, *sensitive, *state;
934        StateUpdate   *su;
935
936        g_return_val_if_fail (node != NULL, NULL);
937        g_return_val_if_fail (GTK_IS_WIDGET (widget), NULL);
938
939        hidden = bonobo_ui_node_get_attr (node, "hidden");
940        if (hidden && atoi (hidden))
941                gtk_widget_hide (widget);
942        else
943                gtk_widget_show (widget);
944        bonobo_ui_node_free_string (hidden);
945
946        sensitive = bonobo_ui_node_get_attr (node, "sensitive");
947        if (sensitive)
948                gtk_widget_set_sensitive (widget, atoi (sensitive));
949        else
950                gtk_widget_set_sensitive (widget, TRUE);
951        bonobo_ui_node_free_string (sensitive);
952
953        if ((state = bonobo_ui_node_get_attr (node, "state"))) {
954                su = g_new0 (StateUpdate, 1);
955               
956                su->sync   = sync;
957                su->widget = widget;
958                gtk_widget_ref (su->widget);
959                su->state  = state;
960        } else
961                su = NULL;
962
963        return su;
964}
965
966static void
967state_update_destroy (StateUpdate *su)
968{
969        if (su) {
970                gtk_widget_unref (su->widget);
971                bonobo_ui_node_free_string (su->state);
972
973                g_free (su);
974        }
975}
976
977static void
978state_update_now (BonoboUIEngine *engine,
979                  BonoboUINode   *node,
980                  GtkWidget      *widget)
981{
982        StateUpdate  *su;
983        BonoboUISync *sync;
984
985        if (!widget)
986                return;
987
988        sync = find_sync_for_node (engine, node);
989        g_return_if_fail (sync != NULL);
990
991        su = state_update_new (sync, widget, node);
992       
993        if (su) {
994                bonobo_ui_sync_state_update (su->sync, su->widget, su->state);
995                state_update_destroy (su);
996        }
997}
998
999/**
1000 * bonobo_ui_engine_xml_get_prop:
1001 * @engine: the engine
1002 * @path: the path into the tree
1003 * @prop: The property
1004 *
1005 * This function fetches the property @prop at node
1006 * at @path in the internal structure.
1007 *
1008 * Return value: the XML string - use bonobo_ui_node_free_string to free
1009 **/
1010CORBA_char *
1011bonobo_ui_engine_xml_get_prop (BonoboUIEngine *engine,
1012                               const char     *path,
1013                               const char     *prop)
1014{
1015        char         *str;
1016        BonoboUINode *node;
1017        CORBA_char   *ret;
1018 
1019        g_return_val_if_fail (BONOBO_IS_UI_ENGINE (engine), NULL);
1020 
1021        node = bonobo_ui_xml_get_path (engine->priv->tree, path);
1022        if (!node)
1023                return NULL;
1024        else {
1025                str = bonobo_ui_node_get_attr (node, prop);
1026                if (!str)
1027                        return NULL;
1028                ret = CORBA_string_dup (str);
1029                bonobo_ui_node_free_string (str);
1030                return ret;
1031        }
1032}
1033
1034/**
1035 * bonobo_ui_engine_xml_get:
1036 * @engine: the engine
1037 * @path: the path into the tree
1038 * @node_only: just the node, or children too.
1039 *
1040 * This function fetches the node at @path in the
1041 * internal structure, and if @node_only dumps the
1042 * node to an XML string, otherwise it dumps it and
1043 * its children.
1044 *
1045 * Return value: the XML string - use bonobo_ui_node_free_string to free
1046 **/
1047CORBA_char *
1048bonobo_ui_engine_xml_get (BonoboUIEngine *engine,
1049                          const char     *path,
1050                          gboolean        node_only)
1051{
1052        char         *str;
1053        BonoboUINode *node;
1054        CORBA_char   *ret;
1055 
1056        g_return_val_if_fail (BONOBO_IS_UI_ENGINE (engine), NULL);
1057 
1058        node = bonobo_ui_xml_get_path (engine->priv->tree, path);
1059        if (!node)
1060                return NULL;
1061        else {         
1062                str = bonobo_ui_node_to_string (node, !node_only);
1063                ret = CORBA_string_dup (str);
1064                bonobo_ui_node_free_string (str);
1065                return ret;
1066        }
1067}
1068
1069/**
1070 * bonobo_ui_engine_xml_node_exists:
1071 * @engine: the engine
1072 * @path: the path into the tree
1073 *
1074 * Return value: true if the node at @path exists
1075 **/
1076gboolean
1077bonobo_ui_engine_xml_node_exists (BonoboUIEngine   *engine,
1078                                  const char       *path)
1079{
1080        BonoboUINode *node;
1081        gboolean      wildcard;
1082
1083        g_return_val_if_fail (BONOBO_IS_UI_ENGINE (engine), FALSE);
1084
1085        node = bonobo_ui_xml_get_path_wildcard (
1086                engine->priv->tree, path, &wildcard);
1087
1088        if (!wildcard)
1089                return (node != NULL);
1090        else
1091                return (node != NULL &&
1092                        bonobo_ui_node_children (node) != NULL);
1093}
1094
1095/**
1096 * bonobo_ui_engine_object_set:
1097 * @engine: the engine
1098 * @path: the path into the tree
1099 * @object: an object reference
1100 * @ev: CORBA exception environment
1101 *
1102 * This associates a CORBA Object reference with a node
1103 * in the tree, most often this is done to insert a Control's
1104 * reference into a 'control' element.
1105 *
1106 * Return value: flag if success
1107 **/
1108BonoboUIError
1109bonobo_ui_engine_object_set (BonoboUIEngine   *engine,
1110                             const char       *path,
1111                             Bonobo_Unknown    object,
1112                             CORBA_Environment *ev)
1113{
1114        NodeInfo     *info;
1115        BonoboUINode *node;
1116
1117        g_return_val_if_fail (BONOBO_IS_UI_ENGINE (engine),
1118                              BONOBO_UI_ERROR_BAD_PARAM);
1119
1120        node = bonobo_ui_xml_get_path (engine->priv->tree, path);
1121        if (!node)
1122                return BONOBO_UI_ERROR_INVALID_PATH;
1123
1124        info = bonobo_ui_xml_get_data (engine->priv->tree, node);
1125
1126        if (info->object != CORBA_OBJECT_NIL) {
1127                bonobo_object_release_unref (info->object, ev);
1128                if (info->widget)
1129                        gtk_widget_destroy (info->widget);
1130                info->widget = NULL;
1131        }
1132
1133        if (object != CORBA_OBJECT_NIL)
1134                info->object = bonobo_object_dup_ref (object, ev);
1135        else
1136                info->object = CORBA_OBJECT_NIL;
1137
1138        bonobo_ui_xml_set_dirty (engine->priv->tree, node);
1139
1140/*      fprintf (stderr, "Object set '%s'\n", path);
1141        bonobo_ui_engine_dump (win, "Before object set updatew");*/
1142
1143        bonobo_ui_engine_update (engine);
1144
1145/*      bonobo_ui_engine_dump (win, "After object set updatew");*/
1146
1147        return BONOBO_UI_ERROR_OK;
1148       
1149}
1150
1151/**
1152 * bonobo_ui_engine_object_get:
1153 * @engine: the engine
1154 * @path: the path into the tree
1155 * @object: an pointer to an object reference
1156 * @ev: CORBA exception environment
1157 *
1158 * This extracts a CORBA object reference associated with
1159 * the node at @path in @engine, and returns it in the
1160 * reference pointed to by @object.
1161 *
1162 * Return value: flag if success
1163 **/
1164BonoboUIError
1165bonobo_ui_engine_object_get (BonoboUIEngine    *engine,
1166                             const char        *path,
1167                             Bonobo_Unknown    *object,
1168                             CORBA_Environment *ev)
1169{
1170        NodeInfo     *info;
1171        BonoboUINode *node;
1172
1173        g_return_val_if_fail (object != NULL,
1174                              BONOBO_UI_ERROR_BAD_PARAM);
1175        g_return_val_if_fail (BONOBO_IS_UI_ENGINE (engine),
1176                              BONOBO_UI_ERROR_BAD_PARAM);
1177
1178        *object = CORBA_OBJECT_NIL;
1179
1180        node = bonobo_ui_xml_get_path (engine->priv->tree, path);
1181
1182        if (!node)
1183                return BONOBO_UI_ERROR_INVALID_PATH;
1184
1185        info = bonobo_ui_xml_get_data (engine->priv->tree, node);
1186
1187        if (info->object != CORBA_OBJECT_NIL)
1188                *object = bonobo_object_dup_ref (info->object, ev);
1189
1190        return BONOBO_UI_ERROR_OK;
1191}
1192
1193static char *
1194get_parent_path (const char *path)
1195{
1196        int i, last_slash = 0;
1197        char *ret;
1198
1199        for (i = 0; path [i]; i++) {
1200                if (path [i] == '/')
1201                        last_slash = i;
1202        }
1203
1204        if (!last_slash)
1205                return NULL;
1206
1207        ret = g_malloc (last_slash + 1);
1208        memcpy (ret, path, last_slash);
1209        ret [last_slash] = '\0';
1210
1211        return ret;
1212}
1213
1214/**
1215 * bonobo_ui_engine_xml_set_prop:
1216 * @engine: the engine
1217 * @path: the path into the tree
1218 * @property: The property to set
1219 * @value: The new value of the property
1220 * @component: the component ID associated with the nodes.
1221 *
1222 * This function sets the property of a node in the internal tree
1223 * representation at @path in @engine.
1224 *
1225 * Return value: flag on error
1226 **/
1227BonoboUIError
1228bonobo_ui_engine_xml_set_prop (BonoboUIEngine    *engine,
1229                               const char        *path,
1230                               const char        *property,
1231                               const char        *value,
1232                               const char        *component)
1233{
1234        char *parent_path;
1235        BonoboUINode *copy;
1236        BonoboUINode *original;
1237       
1238        g_return_val_if_fail (BONOBO_IS_UI_ENGINE (engine),
1239                              BONOBO_UI_ERROR_BAD_PARAM);
1240
1241        original = bonobo_ui_engine_get_path (engine, path);
1242
1243        if (!original)
1244                return BONOBO_UI_ERROR_INVALID_PATH;
1245
1246        copy = bonobo_ui_node_new (bonobo_ui_node_get_name (original));
1247        bonobo_ui_node_copy_attrs (original, copy);
1248        bonobo_ui_node_set_attr (copy, property, value);
1249
1250        parent_path = get_parent_path (path);
1251        bonobo_ui_engine_xml_merge_tree (
1252                engine, parent_path, copy, component);
1253        g_free (parent_path);
1254       
1255        return BONOBO_UI_ERROR_OK;
1256}
1257
1258/**
1259 * bonobo_ui_engine_xml_merge_tree:
1260 * @engine: the engine
1261 * @path: the path into the tree
1262 * @tree: the nodes
1263 * @component: the component ID associated with these nodes.
1264 *
1265 * This function merges the XML @tree into the internal tree
1266 * representation as children of the node at @path in @engine.
1267 *
1268 * Return value: flag on error
1269 **/
1270BonoboUIError
1271bonobo_ui_engine_xml_merge_tree (BonoboUIEngine    *engine,
1272                                 const char        *path,
1273                                 BonoboUINode      *tree,
1274                                 const char        *component)
1275{
1276        BonoboUIError err;
1277       
1278        g_return_val_if_fail (BONOBO_IS_UI_ENGINE (engine),
1279                              BONOBO_UI_ERROR_BAD_PARAM);
1280
1281        if (!tree || !bonobo_ui_node_get_name (tree))
1282                return BONOBO_UI_ERROR_OK;
1283
1284        bonobo_ui_node_strip (&tree);
1285
1286        if (!tree) {
1287                g_warning ("Stripped tree to nothing");
1288                return BONOBO_UI_ERROR_OK;
1289        }
1290
1291        /*
1292         *  Because peer to peer merging makes the code hard, and
1293         * paths non-inituitive and since we want to merge root
1294         * elements as peers to save lots of redundant CORBA calls
1295         * we special case root.
1296         */
1297        if (bonobo_ui_node_has_name (tree, "Root")) {
1298                err = bonobo_ui_xml_merge (
1299                        engine->priv->tree, path, bonobo_ui_node_children (tree),
1300                        sub_component_cmp_name (engine, component));
1301                bonobo_ui_node_free (tree);
1302        } else
1303                err = bonobo_ui_xml_merge (
1304                        engine->priv->tree, path, tree,
1305                        sub_component_cmp_name (engine, component));
1306
1307#ifdef XML_MERGE_DEBUG
1308/*      bonobo_ui_engine_dump (engine, stderr, "after merge");*/
1309#endif
1310
1311        bonobo_ui_engine_update (engine);
1312
1313        return err;
1314}
1315
1316/**
1317 * bonobo_ui_engine_xml_rm:
1318 * @engine: the engine
1319 * @path: the path into the tree
1320 * @by_component: whether to remove elements from only a
1321 * specific component
1322 *
1323 * Remove a chunk of the xml tree pointed at by @path
1324 * in @engine, if @by_component then only remove items
1325 * associated with that component - possibly revealing
1326 * other overridden items.
1327 *
1328 * Return value: flag on error
1329 **/
1330BonoboUIError
1331bonobo_ui_engine_xml_rm (BonoboUIEngine *engine,
1332                         const char     *path,
1333                         const char     *by_component)
1334{
1335        BonoboUIError err;
1336
1337        g_return_val_if_fail (BONOBO_IS_UI_ENGINE (engine),
1338                              BONOBO_UI_ERROR_BAD_PARAM);
1339
1340        err = bonobo_ui_xml_rm (
1341                engine->priv->tree, path,
1342                sub_component_cmp_name (engine, by_component));
1343
1344        bonobo_ui_engine_update (engine);
1345
1346        return err;
1347}
1348
1349static void
1350blank_container (BonoboUIContainer *container,
1351                 BonoboUIEngine    *engine)
1352{
1353        if (engine->priv)
1354                engine->priv->container = NULL;
1355}
1356
1357/**
1358 * bonobo_ui_engine_set_ui_container:
1359 * @engine: the engine
1360 * @ui_container: a UI Container bonobo object.
1361 *
1362 * Associates a given UI Container with this BonoboUIEngine.
1363 **/
1364void
1365bonobo_ui_engine_set_ui_container (BonoboUIEngine *engine,
1366                                   BonoboObject   *ui_container)
1367{
1368        g_return_if_fail (BONOBO_IS_UI_ENGINE (engine));
1369        g_return_if_fail (ui_container == CORBA_OBJECT_NIL ||
1370                          BONOBO_IS_UI_CONTAINER (ui_container));
1371
1372        engine->priv->container = ui_container;
1373
1374        if (ui_container)
1375                gtk_signal_connect (GTK_OBJECT (ui_container), "destroy",
1376                                    (GtkSignalFunc) blank_container, engine);
1377}
1378
1379static void
1380real_exec_verb (BonoboUIEngine *engine,
1381                const char     *component_name,
1382                const char     *verb)
1383{
1384        Bonobo_UIComponent component;
1385
1386        g_return_if_fail (verb != NULL);
1387        g_return_if_fail (component_name != NULL);
1388        g_return_if_fail (BONOBO_IS_UI_ENGINE (engine));
1389
1390        gtk_object_ref (GTK_OBJECT (engine));
1391
1392        component = sub_component_objref (engine, component_name);
1393
1394        if (component != CORBA_OBJECT_NIL) {
1395                CORBA_Environment ev;
1396
1397                CORBA_exception_init (&ev);
1398
1399                Bonobo_UIComponent_execVerb (
1400                        component, verb, &ev);
1401
1402                if (engine->priv->container)
1403                        bonobo_object_check_env (
1404                                engine->priv->container, component, &ev);
1405
1406                if (BONOBO_EX (&ev))
1407                        g_warning ("Exception executing verb '%s' '%s'"
1408                                   "major %d, %s",
1409                                   verb, component_name, ev._major, ev._repo_id);
1410               
1411                CORBA_exception_free (&ev);
1412        }
1413
1414        gtk_object_unref (GTK_OBJECT (engine));
1415}
1416
1417static void
1418impl_emit_verb_on (BonoboUIEngine *engine,
1419                   BonoboUINode   *node)
1420{
1421        CORBA_char      *verb;
1422        BonoboUIXmlData *data;
1423       
1424        g_return_if_fail (node != NULL);
1425
1426        data = bonobo_ui_xml_get_data (NULL, node);
1427        g_return_if_fail (data != NULL);
1428
1429        verb = node_get_id (node);
1430        if (!verb)
1431                return;
1432
1433        /* Builtins */
1434        if (!strcmp (verb, "BonoboCustomize"))
1435                bonobo_ui_engine_config_configure (engine->priv->config);
1436
1437        else if (!strcmp (verb, "BonoboUIDump"))
1438                bonobo_ui_engine_dump (engine, stderr, "from verb");
1439
1440        else {
1441                if (!data->id) {
1442                        g_warning ("Weird; no ID on verb '%s'", verb);
1443                        bonobo_ui_node_free_string (verb);
1444                        return;
1445                }
1446
1447                real_exec_verb (engine, data->id, verb);
1448        }
1449
1450        g_free (verb);
1451}
1452
1453static BonoboUINode *
1454cmd_get_node (BonoboUIEngine *engine,
1455              BonoboUINode   *from_node)
1456{
1457        char         *path;
1458        BonoboUINode *ret;
1459        char         *cmd_name;
1460
1461        g_return_val_if_fail (engine != NULL, NULL);
1462
1463        if (!from_node)
1464                return NULL;
1465
1466        if (!(cmd_name = node_get_id (from_node)))
1467                return NULL;
1468
1469        path = g_strconcat ("/commands/", cmd_name, NULL);
1470        ret  = bonobo_ui_xml_get_path (engine->priv->tree, path);
1471
1472        if (!ret) {
1473                BonoboUIXmlData *data_from;
1474                BonoboUINode *commands;
1475                BonoboUINode *node;
1476
1477                commands = bonobo_ui_node_new ("commands");
1478                node     = bonobo_ui_node_new_child (commands, "cmd");
1479
1480                bonobo_ui_node_set_attr (node, "name", cmd_name);
1481
1482                data_from = bonobo_ui_xml_get_data (
1483                        engine->priv->tree, from_node);
1484
1485                bonobo_ui_xml_merge (
1486                        engine->priv->tree, "/",
1487                        commands, data_from->id);
1488               
1489                ret = bonobo_ui_xml_get_path (
1490                        engine->priv->tree, path);
1491                g_assert (ret != NULL);
1492        }
1493
1494        g_free (path);
1495        g_free (cmd_name);
1496
1497        return ret;
1498}
1499
1500static GSList *
1501make_updates_for_command (BonoboUIEngine *engine,
1502                          GSList         *list,
1503                          BonoboUINode   *state,
1504                          const char     *search_id)
1505{
1506        const GSList *l;
1507
1508        l = cmd_to_nodes (engine, search_id);
1509
1510        if (!l)
1511                return list;
1512
1513/*      printf ("Update cmd state if %s == %s on node '%s'\n", search_id, id,
1514        bonobo_ui_xml_make_path (search));*/
1515
1516        for (; l; l = l->next) {
1517
1518                NodeInfo *info = bonobo_ui_xml_get_data (
1519                        engine->priv->tree, l->data);
1520
1521                if (info->widget) {
1522                        BonoboUISync *sync;
1523                        StateUpdate  *su;
1524
1525                        sync = find_sync_for_node (engine, l->data);
1526                        g_return_val_if_fail (sync != NULL, list);
1527
1528                        su = state_update_new (sync, info->widget, state);
1529
1530                        if (su)
1531                                list = g_slist_prepend (list, su);
1532                }
1533        }
1534
1535        return list;
1536}
1537
1538static void
1539execute_state_updates (GSList *updates)
1540{
1541        GSList *l;
1542
1543        for (l = updates; l; l = l->next) {
1544                StateUpdate *su = l->data;
1545
1546                bonobo_ui_sync_state_update (su->sync, su->widget, su->state);
1547        }
1548
1549        for (l = updates; l; l = l->next)
1550                state_update_destroy (l->data);
1551
1552        g_slist_free (updates);
1553}
1554
1555/*
1556 * set_cmd_attr:
1557 *   Syncs cmd / widgets on events [ event flag set ]
1558 *   or helps evil people who set state on menu /
1559 *      toolbar items instead of on the associated verb / id.
1560 **/
1561static void
1562set_cmd_attr (BonoboUIEngine *engine,
1563              BonoboUINode   *node,
1564              const char     *prop,
1565              const char     *value,
1566              gboolean        event)
1567{
1568        BonoboUINode *cmd_node;
1569
1570        g_return_if_fail (prop != NULL);
1571        g_return_if_fail (node != NULL);
1572        g_return_if_fail (value != NULL);
1573        g_return_if_fail (BONOBO_IS_UI_ENGINE (engine));
1574
1575        if (!(cmd_node = cmd_get_node (engine, node))) { /* A non cmd widget */
1576                NodeInfo *info = bonobo_ui_xml_get_data (
1577                        engine->priv->tree, node);
1578
1579                bonobo_ui_node_set_attr (node, prop, value);
1580                state_update_now (engine, node, info->widget);
1581                return;
1582        }
1583
1584#ifdef STATE_SYNC_DEBUG
1585        fprintf (stderr, "Set '%s' : '%s' to '%s'",
1586                 bonobo_ui_node_get_attr (cmd_node, "name"),
1587                 prop, value);
1588#endif
1589
1590        bonobo_ui_node_set_attr (cmd_node, prop, value);
1591
1592        if (event) {
1593                GSList *updates;
1594                char   *cmd_name = bonobo_ui_node_get_attr (
1595                        cmd_node, "name");
1596
1597
1598                updates = make_updates_for_command (
1599                        engine, NULL, cmd_node, cmd_name);
1600
1601                execute_state_updates (updates);
1602
1603                bonobo_ui_node_free_string (cmd_name);
1604        } else {
1605                BonoboUIXmlData *data =
1606                        bonobo_ui_xml_get_data (
1607                                engine->priv->tree, cmd_node);
1608
1609                data->dirty = TRUE;
1610        }
1611}
1612
1613static void
1614real_emit_ui_event (BonoboUIEngine *engine,
1615                    const char     *component_name,
1616                    const char     *id,
1617                    int             type,
1618                    const char     *new_state)
1619{
1620        Bonobo_UIComponent component;
1621
1622        g_return_if_fail (id != NULL);
1623        g_return_if_fail (new_state != NULL);
1624
1625        if (!component_name) /* Auto-created entry, no-one can listen to it */
1626                return;
1627
1628        gtk_object_ref (GTK_OBJECT (engine));
1629
1630        component = sub_component_objref (engine, component_name);
1631
1632        if (component != CORBA_OBJECT_NIL) {
1633                CORBA_Environment ev;
1634
1635                CORBA_exception_init (&ev);
1636
1637                Bonobo_UIComponent_uiEvent (
1638                        component, id, type, new_state, &ev);
1639
1640                if (engine->priv->container)
1641                        bonobo_object_check_env (
1642                                engine->priv->container,
1643                                component, &ev);
1644
1645                if (BONOBO_EX (&ev))
1646                        g_warning ("Exception emitting state change to %d '%s' '%s'"
1647                                   "major %d, %s",
1648                                   type, id, new_state, ev._major, ev._repo_id);
1649               
1650                CORBA_exception_free (&ev);
1651        }
1652
1653        gtk_object_unref (GTK_OBJECT (engine));
1654}
1655
1656static void
1657impl_emit_event_on (BonoboUIEngine *engine,
1658                    BonoboUINode   *node,
1659                    const char     *state)
1660{
1661        char            *id;
1662        BonoboUIXmlData *data;
1663        char            *component_id;
1664
1665        g_return_if_fail (node != NULL);
1666
1667        if (!(id = node_get_id (node)))
1668                return;
1669
1670        data = bonobo_ui_xml_get_data (NULL, node);
1671        g_return_if_fail (data != NULL);
1672
1673        component_id = g_strdup (data->id);
1674
1675        /* This could invoke a CORBA method that might de-register the component */
1676        set_cmd_attr (engine, node, "state", state, TRUE);
1677
1678        real_emit_ui_event (engine, component_id, id,
1679                            Bonobo_UIComponent_STATE_CHANGED,
1680                            state);
1681
1682        g_free (component_id);
1683        g_free (id);
1684}
1685
1686static void
1687impl_destroy (GtkObject *object)
1688{
1689        BonoboUIEngine *engine;
1690        BonoboUIEnginePrivate *priv;
1691        GSList *l;
1692
1693        engine = BONOBO_UI_ENGINE (object);
1694        priv = engine->priv;
1695
1696        gtk_object_unref (GTK_OBJECT (priv->config));
1697
1698        while (priv->components)
1699                sub_component_destroy (
1700                        engine, priv->components->data);
1701
1702        gtk_object_unref (GTK_OBJECT (priv->tree));
1703        priv->tree = NULL;
1704
1705        for (l = priv->syncs; l; l = l->next)
1706                gtk_object_unref (GTK_OBJECT (l->data));
1707        g_slist_free (priv->syncs);
1708        priv->syncs = NULL;
1709
1710        g_hash_table_foreach_remove (
1711                priv->cmd_to_node,
1712                cmd_to_node_clear_hash,
1713                NULL);
1714        g_hash_table_destroy (priv->cmd_to_node);
1715        priv->cmd_to_node = NULL;
1716
1717        parent_class->destroy (object);
1718}
1719
1720static void
1721impl_finalize (GtkObject *object)
1722{
1723        BonoboUIEngine *engine;
1724        BonoboUIEnginePrivate *priv;
1725
1726        engine = BONOBO_UI_ENGINE (object);
1727        priv = engine->priv;
1728       
1729        g_free (priv);
1730
1731        parent_class->finalize (object);
1732}
1733
1734static void
1735class_init (BonoboUIEngineClass *engine_class)
1736{
1737        GtkObjectClass *object_class;
1738
1739        parent_class = gtk_type_class (PARENT_TYPE);
1740
1741        object_class = GTK_OBJECT_CLASS (engine_class);
1742        object_class->destroy  = impl_destroy;
1743        object_class->finalize = impl_finalize;
1744
1745        engine_class->emit_verb_on  = impl_emit_verb_on;
1746        engine_class->emit_event_on = impl_emit_event_on;
1747
1748        signals [ADD_HINT]
1749                = gtk_signal_new ("add_hint",
1750                                  GTK_RUN_LAST,
1751                                  object_class->type,
1752                                  GTK_SIGNAL_OFFSET (BonoboUIEngineClass, add_hint),
1753                                  gtk_marshal_NONE__STRING,
1754                                  GTK_TYPE_NONE, 1, GTK_TYPE_STRING);
1755
1756        signals [REMOVE_HINT]
1757                = gtk_signal_new ("remove_hint",
1758                                  GTK_RUN_LAST,
1759                                  object_class->type,
1760                                  GTK_SIGNAL_OFFSET (BonoboUIEngineClass, remove_hint),
1761                                  gtk_marshal_NONE__NONE,
1762                                  GTK_TYPE_NONE, 0);
1763
1764        signals [EMIT_VERB_ON]
1765                = gtk_signal_new ("emit_verb_on",
1766                                  GTK_RUN_LAST,
1767                                  object_class->type,
1768                                  GTK_SIGNAL_OFFSET (BonoboUIEngineClass, emit_verb_on),
1769                                  gtk_marshal_NONE__POINTER,
1770                                  GTK_TYPE_NONE, 1, GTK_TYPE_POINTER);
1771
1772        signals [EMIT_EVENT_ON]
1773                = gtk_signal_new ("emit_event_on",
1774                                  GTK_RUN_LAST,
1775                                  object_class->type,
1776                                  GTK_SIGNAL_OFFSET (BonoboUIEngineClass, emit_event_on),
1777                                  gtk_marshal_NONE__POINTER_POINTER,
1778                                  GTK_TYPE_NONE, 2, GTK_TYPE_POINTER, GTK_TYPE_STRING);
1779
1780        gtk_object_class_add_signals (object_class, signals, LAST_SIGNAL);
1781}
1782
1783static void
1784init (BonoboUIEngine *engine)
1785{
1786        BonoboUIEnginePrivate *priv;
1787
1788        priv = g_new0 (BonoboUIEnginePrivate, 1);
1789
1790        engine->priv = priv;
1791
1792        priv->cmd_to_node = g_hash_table_new (
1793                g_str_hash, g_str_equal);
1794}
1795
1796GtkType
1797bonobo_ui_engine_get_type (void)
1798{
1799        static GtkType type = 0;
1800
1801        if (type == 0) {
1802                static const GtkTypeInfo info = {
1803                        "BonoboUIEngine",
1804                        sizeof (BonoboUIEngine),
1805                        sizeof (BonoboUIEngineClass),
1806                        (GtkClassInitFunc)  class_init,
1807                        (GtkObjectInitFunc) init,
1808                        /* reserved_1 */ NULL,
1809                        /* reserved_2 */ NULL,
1810                        (GtkClassInitFunc) NULL,
1811                };
1812
1813                type = gtk_type_unique (PARENT_TYPE, &info);
1814        }
1815
1816        return type;
1817}
1818
1819static void
1820add_node (BonoboUINode *parent, const char *name)
1821{
1822        BonoboUINode *node = bonobo_ui_node_new (name);
1823
1824        bonobo_ui_node_add_child (parent, node);
1825}
1826
1827static void
1828build_skeleton (BonoboUIXml *xml)
1829{
1830        g_return_if_fail (BONOBO_IS_UI_XML (xml));
1831
1832        add_node (xml->root, "keybindings");
1833        add_node (xml->root, "commands");
1834}
1835
1836/**
1837 * bonobo_ui_engine_construct:
1838 * @engine: the engine.
1839 *
1840 * Construct a new bonobo_ui_engine
1841 *
1842 * Return value: the constructed engine.
1843 **/
1844BonoboUIEngine *
1845bonobo_ui_engine_construct (BonoboUIEngine *engine)
1846{
1847        BonoboUIEnginePrivate *priv;
1848
1849        g_return_val_if_fail (BONOBO_IS_UI_ENGINE (engine), NULL);
1850
1851        priv = engine->priv;
1852
1853        priv->tree = bonobo_ui_xml_new (
1854                NULL, info_new_fn, info_free_fn,
1855                info_dump_fn, add_node_fn, engine);
1856       
1857        priv->config = bonobo_ui_engine_config_new (engine);
1858
1859        build_skeleton (priv->tree);
1860
1861        gtk_signal_connect (GTK_OBJECT (priv->tree), "override",
1862                            (GtkSignalFunc) override_fn, engine);
1863
1864        gtk_signal_connect (GTK_OBJECT (priv->tree), "replace_override",
1865                            (GtkSignalFunc) replace_override_fn, engine);
1866
1867        gtk_signal_connect (GTK_OBJECT (priv->tree), "reinstate",
1868                            (GtkSignalFunc) reinstate_fn, engine);
1869
1870        gtk_signal_connect (GTK_OBJECT (priv->tree), "rename",
1871                            (GtkSignalFunc) rename_fn, engine);
1872
1873        gtk_signal_connect (GTK_OBJECT (priv->tree), "remove",
1874                            (GtkSignalFunc) remove_fn, engine);
1875
1876        return engine;
1877}
1878
1879
1880/**
1881 * bonobo_ui_engine_new:
1882 * @void:
1883 *
1884 * Create a new #BonoboUIEngine structure
1885 *
1886 * Return value: the new UI Engine.
1887 **/
1888BonoboUIEngine *
1889bonobo_ui_engine_new (void)
1890{
1891        BonoboUIEngine *engine = gtk_type_new (BONOBO_TYPE_UI_ENGINE);
1892
1893        return bonobo_ui_engine_construct (engine);
1894}
1895
1896
1897static void
1898hide_all_widgets (BonoboUIEngine *engine,
1899                  BonoboUINode   *node)
1900{
1901        NodeInfo *info;
1902        BonoboUINode *child;
1903       
1904        info = bonobo_ui_xml_get_data (engine->priv->tree, node);
1905        if (info->widget)
1906                gtk_widget_hide (info->widget);
1907       
1908        for (child = bonobo_ui_node_children (node);
1909             child != NULL;
1910             child = bonobo_ui_node_next (child))
1911                hide_all_widgets (engine, child);
1912}
1913
1914static gboolean
1915contains_visible_widget (BonoboUIEngine *engine,
1916                         BonoboUINode   *node)
1917{
1918        BonoboUINode *child;
1919        NodeInfo     *info;
1920       
1921        for (child = bonobo_ui_node_children (node);
1922             child != NULL;
1923             child = bonobo_ui_node_next (child)) {
1924                info = bonobo_ui_xml_get_data (engine->priv->tree, child);
1925                if (info->widget && GTK_WIDGET_VISIBLE (info->widget))
1926                        return TRUE;
1927                if (contains_visible_widget (engine, child))
1928                        return TRUE;
1929        }
1930
1931        return FALSE;
1932}
1933
1934static void
1935hide_placeholder_if_empty_or_hidden (BonoboUIEngine *engine,
1936                                     BonoboUINode   *node)
1937{
1938        NodeInfo *info;
1939        char *txt;
1940        gboolean hide_placeholder_and_contents;
1941        gboolean has_visible_separator;
1942
1943        txt = bonobo_ui_node_get_attr (node, "hidden");
1944        hide_placeholder_and_contents = txt && atoi (txt);
1945        bonobo_ui_node_free_string (txt);
1946
1947        info = bonobo_ui_xml_get_data (engine->priv->tree, node);
1948        has_visible_separator = info && info->widget
1949                && GTK_WIDGET_VISIBLE (info->widget);
1950
1951        if (hide_placeholder_and_contents)
1952                hide_all_widgets (engine, node);
1953
1954        else if (has_visible_separator
1955                 && !contains_visible_widget (engine, node))
1956                gtk_widget_hide (info->widget);
1957}
1958
1959static gboolean
1960node_is_dirty (BonoboUIEngine *engine,
1961               BonoboUINode   *node)
1962{
1963        BonoboUIXmlData *data = bonobo_ui_xml_get_data (
1964                engine->priv->tree, node);
1965
1966        if (!data)
1967                return TRUE;
1968        else
1969                return data->dirty;
1970}
1971
1972static void
1973bonobo_ui_engine_sync (BonoboUIEngine   *engine,
1974                       BonoboUISync     *sync,
1975                       BonoboUINode     *node,
1976                       GtkWidget        *parent,
1977                       GList           **widgets,
1978                       int              *pos)
1979{
1980        BonoboUINode *a;
1981        GList        *b, *nextb;
1982
1983#ifdef WIDGET_SYNC_DEBUG
1984        printf ("In sync to pos %d with widgets:\n", *pos);
1985        for (b = *widgets; b; b = b->next) {
1986                BonoboUINode *node = bonobo_ui_engine_widget_get_node (b->data);
1987
1988                if (node)
1989                        printf ("\t'%s'\n", bonobo_ui_xml_make_path (node));
1990                else
1991                        printf ("\tno node ptr\n");
1992        }
1993#endif
1994
1995        b = *widgets;
1996        for (a = node; a; b = nextb) {
1997                gboolean same;
1998
1999                nextb = b ? b->next : NULL;
2000
2001                if (b && bonobo_ui_sync_ignore_widget (sync, b->data)) {
2002                        (*pos)++;
2003                        continue;
2004                }
2005
2006                same = (b != NULL) &&
2007                        (bonobo_ui_engine_widget_get_node (b->data) == a);
2008
2009#ifdef WIDGET_SYNC_DEBUG
2010                printf ("Node '%s(%p)' Dirty '%d' same %d on b %d widget %p\n",
2011                        bonobo_ui_xml_make_path (a), a,
2012                        node_is_dirty (engine, a), same, b != NULL, b?b->data:NULL);
2013#endif
2014
2015                if (node_is_dirty (engine, a)) {
2016                        BonoboUISyncStateFn ss;
2017                        BonoboUISyncBuildFn bw;
2018                        BonoboUINode       *cmd_node;
2019                       
2020                        if (bonobo_ui_node_has_name (a, "placeholder")) {
2021                                ss = bonobo_ui_sync_state_placeholder;
2022                                bw = bonobo_ui_sync_build_placeholder;
2023                        } else {
2024                                ss = bonobo_ui_sync_state;
2025                                bw = bonobo_ui_sync_build;
2026                        }
2027
2028                        cmd_node = bonobo_ui_engine_get_cmd_node (engine, a);
2029
2030                        if (same) {
2031#ifdef WIDGET_SYNC_DEBUG
2032                                printf ("-- just syncing state --\n");
2033#endif
2034                                ss (sync, a, cmd_node, b->data, parent);
2035                                (*pos)++;
2036                        } else {
2037                                NodeInfo   *info;
2038                                GtkWidget  *widget;
2039
2040                                info = bonobo_ui_xml_get_data (
2041                                        engine->priv->tree, a);
2042
2043#ifdef WIDGET_SYNC_DEBUG
2044                                printf ("re-building widget\n");
2045#endif
2046
2047                                widget = bw (sync, a, cmd_node, pos, parent);
2048
2049#ifdef WIDGET_SYNC_DEBUG
2050                                printf ("Built item '%p' '%s' and inserted at '%d'\n",
2051                                        widget, bonobo_ui_node_get_name (a), *pos);
2052#endif
2053
2054                                info->widget = widget;
2055                                if (widget) {
2056                                        bonobo_ui_engine_widget_set_node (
2057                                                sync->engine, widget, a);
2058
2059                                        ss (sync, a, cmd_node, widget, parent);
2060                                }
2061#ifdef WIDGET_SYNC_DEBUG
2062                                else
2063                                        printf ("Failed to build widget\n");
2064#endif
2065
2066                                nextb = b; /* NB. don't advance 'b' */
2067                        }
2068
2069                } else {
2070                        if (!same) {
2071                                BonoboUINode *bn = b ? bonobo_ui_engine_widget_get_node (b->data) : NULL;
2072                                NodeInfo   *info;
2073
2074                                info = bonobo_ui_xml_get_data (engine->priv->tree, a);
2075
2076                                if (!info->widget) {
2077                                        /*
2078                                         *  A control that hasn't been filled out yet
2079                                         * and thus has no widget in 'b' list but has
2080                                         * a node in 'a' list, thus we want to stick
2081                                         * on this 'b' node until a more favorable 'a'
2082                                         */
2083                                        nextb = b;
2084                                        (*pos)--;
2085                                        g_assert (info->type | CUSTOM_WIDGET);
2086#ifdef WIDGET_SYNC_DEBUG
2087                                        printf ("not dirty & not same, but has no widget\n");
2088#endif
2089                                } else {
2090                                        g_warning ("non dirty node, but widget mismatch "
2091                                                   "a: '%s:%s', b: '%s:%s' '%p'",
2092                                                   bonobo_ui_node_get_name (a),
2093                                                   bonobo_ui_node_get_attr (a, "name"),
2094                                                   bn ? bonobo_ui_node_get_name (bn) : "NULL",
2095                                                   bn ? bonobo_ui_node_get_attr (bn, "name") : "NULL",
2096                                                   info->widget);
2097                                }
2098                        }
2099#ifdef WIDGET_SYNC_DEBUG
2100                        else
2101                                printf ("not dirty & same: no change\n");
2102#endif
2103                        (*pos)++;
2104                }
2105
2106                if (bonobo_ui_node_has_name (a, "placeholder")) {
2107                        bonobo_ui_engine_sync (
2108                                engine, sync,
2109                                bonobo_ui_node_children (a),
2110                                parent, &nextb, pos);
2111
2112                        hide_placeholder_if_empty_or_hidden (engine, a);
2113                }
2114
2115                a = bonobo_ui_node_next (a);
2116        }
2117
2118        while (b && bonobo_ui_sync_ignore_widget (sync, b->data))
2119                b = b->next;
2120
2121        *widgets = b;
2122}
2123
2124static void
2125check_excess_widgets (BonoboUISync *sync, GList *wptr)
2126{
2127        if (wptr) {
2128                GList *b;
2129                int warned = 0;
2130
2131                for (b = wptr; b; b = b->next) {
2132                        BonoboUINode *node;
2133
2134                        if (bonobo_ui_sync_ignore_widget (sync, b->data))
2135                                continue;
2136                       
2137                        if (!warned++)
2138                                g_warning ("Excess widgets at the "
2139                                           "end of the container; weird");
2140
2141                        node = bonobo_ui_engine_widget_get_node (b->data);
2142                        g_message ("Widget type '%s' with node: '%s'",
2143                                   gtk_type_name (GTK_OBJECT (b->data)->klass->type),
2144                                   node ? bonobo_ui_xml_make_path (node) : "NULL");
2145                }
2146        }
2147}
2148
2149static void
2150do_sync (BonoboUIEngine *engine,
2151         BonoboUISync   *sync,
2152         BonoboUINode   *node)
2153{
2154#ifdef WIDGET_SYNC_DEBUG
2155        fprintf (stderr, "Syncing ('%s') on node '%s'\n",
2156                 gtk_type_name (GTK_OBJECT (sync)->klass->type),
2157                 bonobo_ui_xml_make_path (node));
2158#endif
2159        if (bonobo_ui_node_parent (node) == engine->priv->tree->root)
2160                bonobo_ui_sync_update_root (sync, node);
2161
2162        /* FIXME: it would be nice to sub-class again to get rid of this */
2163        if (bonobo_ui_sync_has_widgets (sync)) {
2164                int       pos;
2165                GList    *widgets, *wptr;
2166
2167                wptr = widgets = bonobo_ui_sync_get_widgets (sync, node);
2168
2169                pos = 0;
2170                bonobo_ui_engine_sync (
2171                        engine, sync, bonobo_ui_node_children (node),
2172                        bonobo_ui_engine_node_get_widget (engine, node),
2173                        &wptr, &pos);
2174               
2175                check_excess_widgets (sync, wptr);
2176               
2177                g_list_free (widgets);
2178        }
2179
2180        bonobo_ui_xml_clean (engine->priv->tree, node);
2181}
2182
2183static void
2184seek_dirty (BonoboUIEngine *engine,
2185            BonoboUISync   *sync,
2186            BonoboUINode   *node)
2187{
2188        BonoboUIXmlData *info;
2189
2190        if (!node)
2191                return;
2192
2193        info = bonobo_ui_xml_get_data (engine->priv->tree, node);
2194        if (info->dirty) { /* Rebuild tree from here down */
2195
2196                do_sync (engine, sync, node);
2197
2198        } else {
2199                BonoboUINode *l;
2200
2201                for (l = bonobo_ui_node_children (node); l;
2202                     l = bonobo_ui_node_next (l))
2203                        seek_dirty (engine, sync, l);
2204        }
2205}
2206
2207/**
2208 * bonobo_ui_engine_update_node:
2209 * @engine: the engine
2210 * @node: the node to start updating.
2211 *
2212 * This function is used to write recursive synchronizers
2213 * and is intended only for internal / privilaged use.
2214 **/
2215void
2216bonobo_ui_engine_update_node (BonoboUIEngine *engine,
2217                              BonoboUINode   *node)
2218{
2219        BonoboUISync *sync;
2220
2221        if ((sync = find_sync_for_node (engine, node))) {
2222                if (bonobo_ui_sync_is_recursive (sync))
2223                        seek_dirty (engine, sync, node);
2224                else
2225                        do_sync (engine, sync, node);
2226        }
2227#ifdef WIDGET_SYNC_DEBUG
2228        else if (!bonobo_ui_node_has_name (node, "commands"))
2229                g_warning ("No syncer for '%s'",
2230                           bonobo_ui_xml_make_path (node));
2231#endif
2232}
2233
2234static void
2235dirty_by_cmd (BonoboUIEngine *engine,
2236              const char     *search_id)
2237{
2238        const GSList *l;
2239
2240        g_return_if_fail (search_id != NULL);
2241
2242/*      printf ("Dirty node by cmd if %s == %s on node '%s'\n", search_id, id,
2243        bonobo_ui_xml_make_path (search));*/
2244
2245        for (l = cmd_to_nodes (engine, search_id); l; l = l->next)
2246                bonobo_ui_xml_set_dirty (engine->priv->tree, l->data);
2247}
2248
2249static void
2250move_dirt_cmd_to_widget (BonoboUIEngine *engine)
2251{
2252        BonoboUINode *cmds, *l;
2253
2254        cmds = bonobo_ui_xml_get_path (engine->priv->tree, "/commands");
2255
2256        if (!cmds)
2257                return;
2258
2259        for (l = bonobo_ui_node_children (cmds); l;
2260             l = bonobo_ui_node_next (l)) {
2261                BonoboUIXmlData *data = bonobo_ui_xml_get_data (engine->priv->tree, l);
2262
2263                if (data->dirty) {
2264                        char *cmd_name;
2265
2266                        cmd_name = bonobo_ui_node_get_attr (l, "name");
2267                        if (!cmd_name)
2268                                g_warning ("Serious error, cmd without name");
2269                        else
2270                                dirty_by_cmd (engine, cmd_name);
2271
2272                        bonobo_ui_node_free_string (cmd_name);
2273                }
2274        }
2275}
2276
2277static void
2278update_commands_state (BonoboUIEngine *engine)
2279{
2280        BonoboUINode *cmds, *l;
2281        GSList       *updates = NULL;
2282
2283        cmds = bonobo_ui_xml_get_path (engine->priv->tree, "/commands");
2284
2285/*      g_warning ("Update commands state!");
2286        bonobo_ui_engine_dump (priv->win, "before update");*/
2287
2288        if (!cmds)
2289                return;
2290
2291        for (l = bonobo_ui_node_children (cmds); l;
2292             l = bonobo_ui_node_next (l)) {
2293                BonoboUIXmlData *data = bonobo_ui_xml_get_data (
2294                        engine->priv->tree, l);
2295                char *cmd_name;
2296
2297                cmd_name = bonobo_ui_node_get_attr (l, "name");
2298                if (!cmd_name)
2299                        g_warning ("Internal error; cmd with no id");
2300
2301                else if (data->dirty)
2302                        updates = make_updates_for_command (
2303                                engine, updates, l, cmd_name);
2304
2305                data->dirty = FALSE;
2306                bonobo_ui_node_free_string (cmd_name);
2307        }
2308
2309        execute_state_updates (updates);
2310}
2311
2312static void
2313process_state_updates (BonoboUIEngine *engine)
2314{
2315        while (engine->priv->state_updates) {
2316                StateUpdate *su = engine->priv->state_updates->data;
2317
2318                engine->priv->state_updates = g_slist_remove (
2319                        engine->priv->state_updates, su);
2320
2321                bonobo_ui_sync_state_update (
2322                        su->sync, su->widget, su->state);
2323
2324                state_update_destroy (su);
2325        }
2326}
2327
2328/**
2329 * bonobo_ui_engine_update:
2330 * @engine: the engine.
2331 *
2332 * This function is called to update the entire
2333 * UI model synchronizing any changes in it with
2334 * the widget tree where neccessary
2335 **/
2336void
2337bonobo_ui_engine_update (BonoboUIEngine *engine)
2338{
2339        BonoboUINode *node;
2340        GSList *l;
2341
2342        g_return_if_fail (BONOBO_IS_UI_ENGINE (engine));
2343
2344        if (engine->priv->frozen)
2345                return;
2346
2347        for (l = engine->priv->syncs; l; l = l->next)
2348                bonobo_ui_sync_stamp_root (l->data);
2349
2350        move_dirt_cmd_to_widget (engine);
2351
2352/*      bonobo_ui_engine_dump (priv->win, "before update");*/
2353
2354        for (node = bonobo_ui_node_children (engine->priv->tree->root);
2355             node; node = bonobo_ui_node_next (node)) {
2356
2357                if (!bonobo_ui_node_get_name (node))
2358                        continue;
2359
2360                bonobo_ui_engine_update_node (engine, node);
2361        }
2362
2363        update_commands_state (engine);
2364
2365        process_state_updates (engine);
2366
2367/*      bonobo_ui_engine_dump (priv->win, "after update");*/
2368}
2369
2370/**
2371 * bonobo_ui_engine_queue_update:
2372 * @engine: the engine
2373 * @widget: the widget to update later
2374 * @node: the node
2375 * @cmd_node: the associated command's node
2376 *
2377 * This function is used to queue a state update on
2378 * @widget, essentialy transfering any state from the
2379 * XML model into the widget view. This is queued to
2380 * avoid re-enterancy problems.
2381 **/
2382void
2383bonobo_ui_engine_queue_update (BonoboUIEngine   *engine,
2384                               GtkWidget        *widget,
2385                               BonoboUINode     *node,
2386                               BonoboUINode     *cmd_node)
2387{
2388        StateUpdate  *su;
2389        BonoboUISync *sync;
2390       
2391        g_return_if_fail (node != NULL);
2392
2393        sync = find_sync_for_node (engine, node);
2394        g_return_if_fail (sync != NULL);
2395
2396        su = state_update_new (
2397                sync, widget,
2398                cmd_node != NULL ? cmd_node : node);
2399
2400        if (su)
2401                engine->priv->state_updates = g_slist_prepend (
2402                        engine->priv->state_updates, su);
2403}     
2404
2405/**
2406 * bonobo_ui_engine_build_control:
2407 * @engine: the engine
2408 * @node: the control node.
2409 *
2410 * A helper function for synchronizers, this creates a control
2411 * if possible from the node's associated object, stamps the
2412 * node as containing a control and sets its widget.
2413 *
2414 * Return value: a Control's GtkWidget.
2415 **/
2416GtkWidget *
2417bonobo_ui_engine_build_control (BonoboUIEngine *engine,
2418                                BonoboUINode   *node)
2419{
2420        GtkWidget *control = NULL;
2421        NodeInfo  *info = bonobo_ui_xml_get_data (
2422                engine->priv->tree, node);
2423
2424/*      fprintf (stderr, "Control '%p', type '%d' object '%p'\n",
2425        info->widget, info->type, info->object);*/
2426
2427        if (info->widget) {
2428                control = info->widget;
2429                g_assert (info->widget->parent == NULL);
2430
2431        } else if (info->object != CORBA_OBJECT_NIL) {
2432
2433                control = bonobo_widget_new_control_from_objref
2434                        (bonobo_object_dup_ref (info->object, NULL),
2435                         CORBA_OBJECT_NIL);
2436                g_return_val_if_fail (control != NULL, NULL);
2437               
2438                info->type |= CUSTOM_WIDGET;
2439        }
2440
2441        bonobo_ui_sync_do_show_hide (NULL, node, NULL, control);
2442
2443/*      fprintf (stderr, "Type on '%s' '%s' is %d widget %p\n",
2444                 bonobo_ui_node_get_name (node),
2445                 bonobo_ui_node_get_attr (node, "name"),
2446                 info->type, info->widget);*/
2447
2448        return control;
2449}
2450
2451/* Node info accessors */
2452
2453/**
2454 * bonobo_ui_engine_stamp_custom:
2455 * @engine: the engine
2456 * @node: the node
2457 *
2458 * Marks a node as containing a custom widget.
2459 **/
2460void
2461bonobo_ui_engine_stamp_custom (BonoboUIEngine *engine,
2462                               BonoboUINode   *node)
2463{
2464        NodeInfo *info;
2465       
2466        info = bonobo_ui_xml_get_data (engine->priv->tree, node);
2467
2468        info->type |= CUSTOM_WIDGET;
2469}
2470
2471/**
2472 * bonobo_ui_engine_node_get_object:
2473 * @engine: the engine
2474 * @node: the node
2475 *
2476 * Return value: the CORBA_Object associated with a @node
2477 **/
2478CORBA_Object
2479bonobo_ui_engine_node_get_object (BonoboUIEngine   *engine,
2480                                  BonoboUINode     *node)
2481{
2482        NodeInfo *info;
2483       
2484        info = bonobo_ui_xml_get_data (engine->priv->tree, node);
2485
2486        return info->object;
2487}
2488
2489/**
2490 * bonobo_ui_engine_node_is_dirty:
2491 * @engine: the engine
2492 * @node: the node
2493 *
2494 * accesses the node's dirty flag.
2495 *
2496 * Return value: whether the @node is marked dirty
2497 **/
2498gboolean
2499bonobo_ui_engine_node_is_dirty (BonoboUIEngine *engine,
2500                                BonoboUINode   *node)
2501{
2502        BonoboUIXmlData *data;
2503       
2504        data = bonobo_ui_xml_get_data (engine->priv->tree, node);
2505
2506        return data->dirty;
2507}
2508
2509/**
2510 * bonobo_ui_engine_node_get_id:
2511 * @engine: the engine
2512 * @node: the node
2513 *
2514 * Each component has an associated textual id or name - see
2515 * bonobo_ui_engine_register_component
2516 *
2517 * Return value: the component id associated with the node
2518 **/
2519const char *
2520bonobo_ui_engine_node_get_id (BonoboUIEngine *engine,
2521                              BonoboUINode   *node)
2522{
2523        BonoboUIXmlData *data;
2524       
2525        data = bonobo_ui_xml_get_data (engine->priv->tree, node);
2526
2527        return data->id;
2528}
2529
2530/**
2531 * bonobo_ui_engine_node_set_dirty:
2532 * @engine: the engine
2533 * @node: the node
2534 * @dirty: whether the node should be dirty.
2535 *
2536 * Set @node s dirty bit to @dirty.
2537 **/
2538void
2539bonobo_ui_engine_node_set_dirty (BonoboUIEngine *engine,
2540                                 BonoboUINode   *node,
2541                                 gboolean        dirty)
2542{
2543        BonoboUIXmlData *data;
2544       
2545        data = bonobo_ui_xml_get_data (engine->priv->tree, node);
2546
2547        data->dirty = dirty;
2548}
2549
2550/**
2551 * bonobo_ui_engine_node_get_widget:
2552 * @engine: the engine
2553 * @node: the node
2554 *
2555 * Gets the widget associated with @node
2556 *
2557 * Return value: the widget
2558 **/
2559GtkWidget *
2560bonobo_ui_engine_node_get_widget (BonoboUIEngine   *engine,
2561                                  BonoboUINode     *node)
2562{
2563        NodeInfo *info;
2564
2565        g_return_val_if_fail (engine != NULL, NULL);
2566       
2567        info = bonobo_ui_xml_get_data (engine->priv->tree, node);
2568
2569        return info->widget;
2570}
2571
2572
2573/* Helpers */
2574
2575/**
2576 * bonobo_ui_engine_get_attr:
2577 * @node: the node
2578 * @cmd_node: the command's node
2579 * @attr: the attribute name
2580 *
2581 * This function is used to get node attributes in many
2582 * UI synchronizers, it first attempts to get the attribute
2583 * from @node, and if this fails falls back to @cmd_node.
2584 *
2585 * Return value: the attr or NULL if it doesn't exist.
2586 **/
2587char *
2588bonobo_ui_engine_get_attr (BonoboUINode *node,
2589                           BonoboUINode *cmd_node,
2590                           const char   *attr)
2591{
2592        char *txt;
2593
2594        if ((txt = bonobo_ui_node_get_attr (node, attr)))
2595                return txt;
2596
2597        if (cmd_node && (txt = bonobo_ui_node_get_attr (cmd_node, attr)))
2598                return txt;
2599
2600        return NULL;
2601}
2602
2603/**
2604 * bonobo_ui_engine_add_hint:
2605 * @engine: the engine
2606 * @str: the hint string
2607 *
2608 * This fires the 'add_hint' signal.
2609 **/
2610void
2611bonobo_ui_engine_add_hint (BonoboUIEngine   *engine,
2612                           const char       *str)
2613{
2614        gtk_signal_emit (GTK_OBJECT (engine),
2615                         signals [ADD_HINT], str);
2616}
2617
2618/**
2619 * bonobo_ui_engine_remove_hint:
2620 * @engine: the engine
2621 *
2622 * This fires the 'remove_hint' signal
2623 **/
2624void
2625bonobo_ui_engine_remove_hint (BonoboUIEngine *engine)
2626{
2627        gtk_signal_emit (GTK_OBJECT (engine),
2628                         signals [REMOVE_HINT]);
2629}
2630
2631/**
2632 * bonobo_ui_engine_emit_verb_on:
2633 * @engine: the engine
2634 * @node: the node
2635 *
2636 * This fires the 'emit_verb' signal
2637 **/
2638void
2639bonobo_ui_engine_emit_verb_on (BonoboUIEngine   *engine,
2640                               BonoboUINode     *node)
2641{
2642        gtk_signal_emit (GTK_OBJECT (engine),
2643                         signals [EMIT_VERB_ON], node);
2644}
2645
2646/**
2647 * bonobo_ui_engine_emit_event_on:
2648 * @engine: the engine
2649 * @node: the node
2650 * @state: the new state of the node
2651 *
2652 * This fires the 'emit_event_on' signal
2653 **/
2654void
2655bonobo_ui_engine_emit_event_on (BonoboUIEngine   *engine,
2656                                BonoboUINode     *node,
2657                                const char       *state)
2658{
2659        gtk_signal_emit (GTK_OBJECT (engine),
2660                         signals [EMIT_EVENT_ON],
2661                         node, state);
2662}
2663
2664#define WIDGET_NODE_KEY "BonoboUIEngine:NodeKey"
2665
2666/**
2667 * bonobo_ui_engine_widget_get_node:
2668 * @widget: the widget
2669 *
2670 * accesses a widget's associated node.
2671 *
2672 * Return value: the #BonoboUINode associated with this widget
2673 **/
2674BonoboUINode *
2675bonobo_ui_engine_widget_get_node (GtkWidget *widget)
2676{
2677        g_return_val_if_fail (widget != NULL, NULL);
2678       
2679        return gtk_object_get_data (GTK_OBJECT (widget),
2680                                    WIDGET_NODE_KEY);
2681}
2682
2683/**
2684 * bonobo_ui_engine_widget_attach_node:
2685 * @widget: the widget
2686 * @node: the node
2687 *
2688 * Associate @node with @widget
2689 **/
2690void
2691bonobo_ui_engine_widget_attach_node (GtkWidget    *widget,
2692                                     BonoboUINode *node)
2693{
2694        if (widget)
2695                gtk_object_set_data (GTK_OBJECT (widget),
2696                                     WIDGET_NODE_KEY, node);
2697}
2698
2699/**
2700 * bonobo_ui_engine_widget_set_node:
2701 * @engine: the engine
2702 * @widget: the widget
2703 * @node: the node
2704 *
2705 * Used internaly to associate a widget with a node,
2706 * some synchronisers need to be able to execute code
2707 * on widget creation.
2708 **/
2709void
2710bonobo_ui_engine_widget_set_node (BonoboUIEngine *engine,
2711                                  GtkWidget      *widget,
2712                                  BonoboUINode   *node)
2713{
2714        BonoboUISync *sync;
2715
2716        /* FIXME: this looks broken. why is it public ?
2717         * and why is it re-looking up the sync - v. slow */
2718
2719        if (!widget)
2720                return;
2721
2722        sync = find_sync_for_node (engine, node);
2723
2724        sync_widget_set_node (sync, widget, node);
2725}
2726
2727/**
2728 * bonobo_ui_engine_get_cmd_node:
2729 * @engine: the engine
2730 * @from_node: the node
2731 *
2732 * This function seeks the command node associated
2733 * with @from_node in @engine 's internal tree.
2734 *
2735 * Return value: the command node or NULL
2736 **/
2737BonoboUINode *
2738bonobo_ui_engine_get_cmd_node (BonoboUIEngine *engine,
2739                               BonoboUINode   *from_node)
2740{
2741        char         *path;
2742        BonoboUINode *ret;
2743        char         *cmd_name;
2744
2745        g_return_val_if_fail (engine != NULL, NULL);
2746
2747        if (!from_node)
2748                return NULL;
2749
2750        if (!(cmd_name = node_get_id (from_node)))
2751                return NULL;
2752
2753        path = g_strconcat ("/commands/", cmd_name, NULL);
2754        ret  = bonobo_ui_xml_get_path (engine->priv->tree, path);
2755
2756        if (!ret) {
2757                BonoboUIXmlData *data_from;
2758                BonoboUINode *commands;
2759                BonoboUINode *node;
2760
2761                commands = bonobo_ui_node_new ("commands");
2762                node     = bonobo_ui_node_new_child (commands, "cmd");
2763
2764                bonobo_ui_node_set_attr (node, "name", cmd_name);
2765
2766                data_from   = bonobo_ui_xml_get_data (
2767                        engine->priv->tree, from_node);
2768
2769                bonobo_ui_xml_merge (
2770                        engine->priv->tree, "/",
2771                        commands, data_from->id);
2772               
2773                ret = bonobo_ui_xml_get_path (engine->priv->tree, path);
2774                g_assert (ret != NULL);
2775        }
2776
2777        g_free (path);
2778        g_free (cmd_name);
2779
2780        return ret;
2781}
2782
2783/**
2784 * bonobo_ui_engine_emit_verb_on_w:
2785 * @engine: the engine
2786 * @widget: the widget
2787 *
2788 * This function looks up the node from @widget and
2789 * emits the 'emit_verb_on' signal on that node.
2790 **/
2791void
2792bonobo_ui_engine_emit_verb_on_w (BonoboUIEngine *engine,
2793                                 GtkWidget      *widget)
2794{
2795        BonoboUINode *node = bonobo_ui_engine_widget_get_node (widget);
2796
2797        gtk_signal_emit (GTK_OBJECT (engine),
2798                         signals [EMIT_VERB_ON], node);
2799}
2800
2801/**
2802 * bonobo_ui_engine_emit_event_on_w:
2803 * @engine: the engine
2804 * @widget: the widget
2805 * @state: the new state
2806 *
2807 * This function looks up the node from @widget and
2808 * emits the 'emit_event_on' signal on that node
2809 * passint @state as the new state.
2810 **/
2811void
2812bonobo_ui_engine_emit_event_on_w (BonoboUIEngine *engine,
2813                                  GtkWidget      *widget,
2814                                  const char     *state)
2815{
2816        BonoboUINode *node = bonobo_ui_engine_widget_get_node (widget);
2817
2818        gtk_signal_emit (GTK_OBJECT (engine),
2819                         signals [EMIT_EVENT_ON], node, state);
2820}
2821
2822/**
2823 * bonobo_ui_engine_stamp_root:
2824 * @engine: the engine
2825 * @node: the node
2826 * @widget: the root widget
2827 *
2828 * This stamps @node with @widget which is marked as
2829 * being a ROOT node, so the engine will never destroy
2830 * it.
2831 **/
2832void
2833bonobo_ui_engine_stamp_root (BonoboUIEngine *engine,
2834                             BonoboUINode   *node,
2835                             GtkWidget      *widget)
2836{
2837        NodeInfo *info;
2838
2839        if (!node)
2840                return;
2841
2842        info = bonobo_ui_xml_get_data (engine->priv->tree, node);
2843
2844        info->widget = widget;
2845        info->type |= ROOT_WIDGET;
2846
2847        bonobo_ui_engine_widget_attach_node (widget, node);
2848}
2849
2850/**
2851 * bonobo_ui_engine_get_path:
2852 * @engine: the engine.
2853 * @path: the path into the tree
2854 *
2855 * This routine gets a node from the internal XML tree
2856 * pointed at by @path
2857 *
2858 * Return value: the node.
2859 **/
2860BonoboUINode *
2861bonobo_ui_engine_get_path (BonoboUIEngine *engine,
2862                           const char     *path)
2863{
2864        g_return_val_if_fail (BONOBO_IS_UI_ENGINE (engine), NULL);
2865
2866        return bonobo_ui_xml_get_path (engine->priv->tree, path);
2867}
2868
2869/**
2870 * bonobo_ui_engine_freeze:
2871 * @engine: the engine
2872 *
2873 * This increments the freeze count on the tree, while
2874 * this count > 0 no syncronization between the internal
2875 * XML model and the widget views occurs. This means that
2876 * many simple merges can be glupped together with little
2877 * performance impact and overhead.
2878 **/
2879void
2880bonobo_ui_engine_freeze (BonoboUIEngine *engine)
2881{
2882        g_return_if_fail (BONOBO_IS_UI_ENGINE (engine));
2883
2884        engine->priv->frozen++;
2885}
2886
2887/**
2888 * bonobo_ui_engine_thaw:
2889 * @engine: the engine
2890 *
2891 * This decrements the freeze count and if it is 0
2892 * causes the UI widgets to be re-synched with the
2893 * XML model, see also bonobo_ui_engine_freeze
2894 **/
2895void
2896bonobo_ui_engine_thaw (BonoboUIEngine *engine)
2897{
2898        g_return_if_fail (BONOBO_IS_UI_ENGINE (engine));
2899       
2900        if (--engine->priv->frozen <= 0) {
2901                bonobo_ui_engine_update (engine);
2902                engine->priv->frozen = 0;
2903        }
2904}
2905
2906/**
2907 * bonobo_ui_engine_dump:
2908 * @engine: the engine
2909 * @out: the FILE stream to dump to
2910 * @msg: user visible message
2911 *
2912 * This is a debugging function mostly for internal
2913 * and testing use, it dumps the XML tree, including
2914 * the assoicated, and overridden nodes in a wierd
2915 * hackish format to the @out stream with the
2916 * helpful @msg prepended.
2917 **/
2918void
2919bonobo_ui_engine_dump (BonoboUIEngine *engine,
2920                       FILE           *out,
2921                       const char     *msg)
2922{
2923        g_return_if_fail (BONOBO_IS_UI_ENGINE (engine));
2924
2925        fprintf (out, "Bonobo UI Engine : frozen '%d'\n",
2926                 engine->priv->frozen);
2927
2928        sub_components_dump (engine, out);
2929
2930        /* FIXME: propagate the FILE * */
2931        bonobo_ui_xml_dump (engine->priv->tree,
2932                            engine->priv->tree->root, msg);
2933}
2934
2935/**
2936 * bonobo_ui_engine_dirty_tree:
2937 * @engine: the engine
2938 * @node: the node
2939 *
2940 * Mark all the node's children as being dirty and needing
2941 * a re-synch with their widget views.
2942 **/
2943void
2944bonobo_ui_engine_dirty_tree (BonoboUIEngine *engine,
2945                             BonoboUINode   *node)
2946{
2947        g_return_if_fail (BONOBO_IS_UI_ENGINE (engine));
2948
2949#ifdef WIDGET_SYNC_DEBUG
2950        fprintf (stderr, "Dirty tree '%s'",
2951                 bonobo_ui_xml_make_path (node));
2952#endif
2953        if (node) {
2954                bonobo_ui_xml_set_dirty (engine->priv->tree, node);
2955
2956                bonobo_ui_engine_update (engine);
2957        }
2958}
2959
2960/**
2961 * bonobo_ui_engine_clean_tree:
2962 * @engine: the engine
2963 * @node: the node
2964 *
2965 * This cleans the tree, marking the node and its children
2966 * as not needing a re-synch with their widget views.
2967 **/
2968void
2969bonobo_ui_engine_clean_tree (BonoboUIEngine *engine,
2970                             BonoboUINode   *node)
2971{
2972        g_return_if_fail (BONOBO_IS_UI_ENGINE (engine));
2973
2974        if (node)
2975                bonobo_ui_xml_clean (engine->priv->tree, node);
2976}
2977
2978/**
2979 * bonobo_ui_engine_get_xml:
2980 * @engine: the engine
2981 *
2982 * Private - internal API
2983 *
2984 * Return value: the #BonoboUIXml engine used for
2985 * doing the XML merge logic
2986 **/
2987BonoboUIXml *
2988bonobo_ui_engine_get_xml (BonoboUIEngine *engine)
2989{
2990        g_return_val_if_fail (BONOBO_IS_UI_ENGINE (engine), NULL);
2991       
2992        return engine->priv->tree;
2993}
2994
2995/**
2996 * bonobo_ui_engine_get_config:
2997 * @engine: the engine
2998 *
2999 * Private - internal API
3000 *
3001 * Return value: the associated configuration engine
3002 **/
3003BonoboUIEngineConfig *
3004bonobo_ui_engine_get_config (BonoboUIEngine *engine)
3005{
3006        g_return_val_if_fail (BONOBO_IS_UI_ENGINE (engine), NULL);
3007       
3008        return engine->priv->config;
3009}
Note: See TracBrowser for help on using the repository browser.