source: trunk/third/bonobo/bonobo/bonobo-ui-xml.c @ 16750

Revision 16750, 26.8 KB checked in by ghudson, 23 years ago (diff)
This commit was generated by cvs2svn to compensate for changes in r16749, which included commits to RCS files with non-trunk default branches.
Line 
1/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2/*
3 * bonobo-ui-xml.c: A module for merging, overlaying and de-merging XML
4 *
5 * Author:
6 *      Michael Meeks (michael@helixcode.com)
7 *
8 * Copyright 2000 Helix Code, Inc.
9 */
10#include "config.h"
11#include <string.h>
12#include <gtk/gtksignal.h>
13#include <bonobo/bonobo-ui-xml.h>
14
15#include <gnome-xml/tree.h>
16#include <gnome-xml/parser.h>
17
18
19#undef UI_XML_DEBUG
20#undef BONOBO_UI_XML_DUMP
21
22#ifdef BONOBO_UI_XML_DUMP
23#       define DUMP_XML(a,b,c) (bonobo_ui_xml_dump ((a), (b), (c)))
24#else
25#       define DUMP_XML(a,b,c)
26#endif
27
28static void watch_update  (BonoboUIXml  *tree,
29                           BonoboUINode *node);
30static void watch_destroy (gpointer      data);
31
32/*
33 * Change these to not have a cast in order to find all
34 * bad hack casts
35 */
36#define XML_NODE(x) ((xmlNode*)(x))
37#define BNODE(x)    ((BonoboUINode*)(x))
38
39static GtkObjectClass *bonobo_ui_xml_parent_class;
40
41enum {
42        OVERRIDE,
43        REPLACE_OVERRIDE,
44        REINSTATE,
45        RENAME,
46        REMOVE,
47        LAST_SIGNAL
48};
49static guint signals[LAST_SIGNAL] = { 0 };
50
51inline static gboolean
52identical (BonoboUIXml *tree, gpointer a, gpointer b)
53{
54        gboolean val;
55
56        if (tree->compare)
57                val = tree->compare (a, b);
58        else
59                val = (a == b);
60
61/*      fprintf (stderr, "Identical ? '%p' '%p' : %d\n", a, b, val);*/
62
63        return val;
64}
65
66/* This logicaly belongs in bonobo-ui-node.c */
67void bonobo_ui_xml_strip (BonoboUINode **node);
68void
69bonobo_ui_xml_strip (BonoboUINode **node)
70{
71        bonobo_ui_node_strip (node);
72}
73
74/**
75 * bonobo_ui_xml_get_data:
76 * @tree: the tree
77 * @node: the node
78 *
79 * This function gets the data pointer associated with @node
80 * and if there is no data pointer, constructs it using a user supplied
81 * callback.
82 *
83 * Return value: a valid Data pointer - never NULL.
84 **/
85gpointer
86bonobo_ui_xml_get_data (BonoboUIXml *tree, BonoboUINode *node)
87{
88        if (!bonobo_ui_node_get_data (node)) {
89                if (tree && tree->data_new)
90                        bonobo_ui_node_set_data (node, tree->data_new ());
91                else {
92                        g_warning ("Error: No tree, and no data on node; leaking");
93                        bonobo_ui_node_set_data (node, g_new0 (BonoboUIXmlData, 1));
94                }
95        }
96
97        return bonobo_ui_node_get_data (node);
98}
99
100/**
101 * bonobo_ui_xml_clean:
102 * @tree: the tree
103 * @node: the node
104 *
105 * This function marks the entire @tree from @node down
106 * ( all its child nodes ) as being clean.
107 **/
108void
109bonobo_ui_xml_clean (BonoboUIXml  *tree,
110                     BonoboUINode *node)
111{
112        BonoboUIXmlData *data;
113        BonoboUINode    *l;
114
115        data = bonobo_ui_xml_get_data (tree, node);
116        data->dirty = FALSE;
117
118        for (l = bonobo_ui_node_children (node); l;
119             l = bonobo_ui_node_next (l))
120                bonobo_ui_xml_clean (tree, l);
121}
122
123static void
124set_children_dirty (BonoboUIXml *tree, BonoboUINode *node)
125{
126        BonoboUINode *l;
127
128        if (!node)
129                return;
130
131        for (l = bonobo_ui_node_children (node); l;
132             l = bonobo_ui_node_next (l)) {
133                BonoboUIXmlData *data;
134
135                data = bonobo_ui_xml_get_data (tree, l);
136                data->dirty = TRUE;
137               
138                set_children_dirty (tree, l);
139        }
140}
141
142/**
143 * bonobo_ui_xml_set_dirty:
144 * @tree: the tree
145 * @node: the node
146 *
147 * This function sets a node as being dirty, along with all
148 * its children. However more than this, it also sets its parent
149 * dirty, and bubbles this up while the parent is a placeholder,
150 * so as to allow a re-generate to be forced for its real visible
151 * parent.
152 **/
153void
154bonobo_ui_xml_set_dirty (BonoboUIXml *tree, BonoboUINode *node)
155{
156        int i;
157        BonoboUINode *l;
158
159        l = node;
160        for (i = 0; (i < 2) && l; i++) {
161                BonoboUIXmlData *data;
162
163                /*
164                 * FIXME: the placeholder functionality is broken and should
165                 * live in bonobo-win.c for cleanliness and never in this
166                 * more generic code.
167                 */
168
169                if (!strcmp (bonobo_ui_node_get_name (l), "placeholder"))
170                        i--;
171
172                data = bonobo_ui_xml_get_data (tree, l);
173                data->dirty = TRUE;
174
175                l = bonobo_ui_node_parent (l);
176        }
177
178        /* Too conservative in some cases.*/
179        set_children_dirty (tree, node);
180}
181
182/**
183 * bonobo_ui_xml_get_parent_path:
184 * @path: the path
185 *
186 * This function lops one level off a path, much
187 * like appending '..' to a Unix directory path.
188 *
189 * Return value: the parent's path, use g_free to release it
190 **/
191char *
192bonobo_ui_xml_get_parent_path (const char *path)
193{
194        const char *p;
195        char *ret;
196
197        if ((p = strrchr (path, '/')))
198                ret = g_strndup (path, p - path);
199        else
200                ret = g_strdup (path);
201
202        return ret;
203}
204
205static void node_free (BonoboUIXml *tree, BonoboUINode *node);
206
207static void
208free_nodedata (BonoboUIXml *tree, BonoboUIXmlData *data,
209               gboolean do_overrides)
210{
211        if (data) {
212                if (data->overridden) {
213                        if (do_overrides) {
214                                GSList *l;
215
216                                for (l = data->overridden; l; l = l->next)
217                                        node_free (tree, l->data);
218                                g_slist_free (data->overridden);
219                        } else
220                                /*
221                                 *  This indicates a serious error in the
222                                 * overriding logic.
223                                 */
224                                g_warning ("Leaking overridden nodes");
225                }
226
227                if (tree->data_free)
228                        tree->data_free (data);
229                else
230                        g_free (data);
231        }
232}
233
234static void
235free_nodedata_tree (BonoboUIXml *tree, BonoboUINode *node, gboolean do_overrides)
236{
237        BonoboUINode *l;
238
239        if (node == NULL)
240                return;
241
242        free_nodedata (tree, bonobo_ui_node_get_data (node), do_overrides);
243
244        for (l = bonobo_ui_node_children (node); l;
245             l = bonobo_ui_node_next (l))
246                free_nodedata_tree (tree, l, do_overrides);
247}
248
249static void
250node_free (BonoboUIXml *tree, BonoboUINode *node)
251{
252        free_nodedata_tree (tree, node, TRUE);
253        bonobo_ui_node_unlink (node);
254        bonobo_ui_node_free (node);
255}
256
257static void
258do_set_id (BonoboUIXml *tree, BonoboUINode *node, gpointer id)
259{
260        BonoboUIXmlData *data;
261
262        if (!node)
263                return;
264
265        data = bonobo_ui_xml_get_data (tree, node);
266
267        data->id = id;
268
269        /* Do some basic validation here ? */
270        {
271                char *p, *name;
272               
273                if ((name = bonobo_ui_node_get_attr (node, "name"))) {
274                        /*
275                         *  The consequences of this are so unthinkable
276                         * an assertion is warrented.
277                         */
278                        for (p = name; *p; p++)
279                                g_assert (*p != '/' && *p != '#');
280
281                        bonobo_ui_node_free_string (name);
282                }
283        }
284
285        for (node = bonobo_ui_node_children (node); node;
286             node = bonobo_ui_node_next (node))
287                do_set_id (tree, node, id);
288}
289
290static void
291set_id (BonoboUIXml *tree, BonoboUINode *node, gpointer id)
292{
293        for (; node; node = bonobo_ui_node_next (node))
294                do_set_id (tree, node, id);
295}
296
297static void
298dump_internals (BonoboUIXml *tree, BonoboUINode *node)
299{
300        int i;
301        char *txt;
302        BonoboUINode *l;
303        static int indent = -4;
304        BonoboUIXmlData *data = bonobo_ui_xml_get_data (tree, node);
305
306        indent += 2;
307
308        for (i = 0; i < indent; i++)
309                fprintf (stderr, " ");
310
311        fprintf (stderr, "%10s name=\"%10s\" ", bonobo_ui_node_get_name (node),
312                 (txt = bonobo_ui_node_get_attr (node, "name")) ? txt : "NULL");
313        bonobo_ui_node_free_string (txt);
314
315        if ((txt = bonobo_ui_node_get_content (node)))
316                fprintf (stderr, "['%s']", txt);
317        bonobo_ui_node_free_string (txt);
318
319        fprintf (stderr, "%d len %d", data->dirty,
320                 g_slist_length (data->overridden));
321        if (tree->dump)
322                tree->dump (tree, node);
323        else
324                fprintf (stderr, "\n");
325
326        if (data->overridden) {
327                GSList *l;
328                int     old_indent;
329                old_indent = indent;
330                for (l = data->overridden; l; l = l->next) {
331                        for (i = 0; i < indent; i++)
332                                fprintf (stderr, " ");
333                        fprintf (stderr, "`--->");
334                        dump_internals (tree, l->data);
335                        indent += 4;
336                }
337                indent = old_indent;
338        }
339
340        for (l = bonobo_ui_node_children (node); l; l = bonobo_ui_node_next (l))
341                dump_internals (tree, l);
342
343        indent -= 2;
344}
345
346/**
347 * bonobo_ui_xml_dump:
348 * @tree: the tree node
349 * @bnode: the base node to start dumping from
350 * @descr: a description string to print.
351 *
352 * This debug function dumps the contents of a BonoboUIXml tree
353 * to stderr, it is used by BonoboUIEngine to provide some of the
354 * builtin BonoboUIDump verb functionality.
355 **/
356void
357bonobo_ui_xml_dump (BonoboUIXml  *tree,
358                    BonoboUINode *bnode,
359                    const char   *descr)
360{
361        /* XML_NODE() hack used here since there's no public "dump node" API */
362        xmlDoc *doc;
363        xmlNode *node = XML_NODE (bnode);
364
365        doc = xmlNewDoc ("1.0");
366        doc->xmlRootNode = node;
367       
368        fprintf (stderr, "%s\n", descr);
369
370        xmlDocDump (stderr, doc);
371
372        doc->xmlRootNode = NULL;
373        xmlFreeDoc (doc);
374        fprintf (stderr, "--- Internals ---\n");
375        dump_internals (tree, BNODE (node));
376        fprintf (stderr, "---\n");
377}
378
379/*
380 * Re-parenting should be in libxml but isn't
381 */
382static void
383move_children (BonoboUINode *from, BonoboUINode *to)
384{
385        BonoboUINode *l, *next;
386
387        g_return_if_fail (to != NULL);
388        g_return_if_fail (from != NULL);
389        g_return_if_fail (bonobo_ui_node_children (to) == NULL);
390
391        for (l = bonobo_ui_node_children (from); l; l = next) {
392                next = bonobo_ui_node_next (l);
393               
394                bonobo_ui_node_unlink (l);
395                bonobo_ui_node_add_child (to, l);
396        }
397
398        g_assert (bonobo_ui_node_children (from) == NULL);
399}
400
401static void
402prune_overrides_by_id (BonoboUIXml *tree, BonoboUIXmlData *data, gpointer id)
403{
404        GSList *l, *next;
405
406        for (l = data->overridden; l; l = next) {
407                BonoboUIXmlData *o_data;
408                               
409                next = l->next;
410                o_data = bonobo_ui_xml_get_data (tree, l->data);
411
412                if (identical (tree, o_data->id, id)) {
413                        node_free (tree, l->data);
414
415                        data->overridden =
416                                g_slist_remove_link (data->overridden, l);
417                        g_slist_free_1 (l);
418                }
419        }
420}
421
422static void merge (BonoboUIXml *tree, BonoboUINode *current, BonoboUINode **new);
423
424static void
425override_node_with (BonoboUIXml *tree, BonoboUINode *old, BonoboUINode *new)
426{
427        BonoboUIXmlData *data = bonobo_ui_xml_get_data (tree, new);
428        BonoboUIXmlData *old_data = bonobo_ui_xml_get_data (tree, old);
429        gboolean         same, transparent, override;
430
431        /* Is it just a path / grouping simplifying entry with no content ? */
432        transparent = bonobo_ui_node_transparent (new);
433
434        same = identical (tree, data->id, old_data->id);
435
436        g_assert (data->id);
437
438        override = !same && !transparent;
439
440        if (override) {
441
442                gtk_signal_emit (GTK_OBJECT (tree),
443                                 signals [OVERRIDE], new, old);
444
445                data->overridden = g_slist_prepend (old_data->overridden, old);
446                prune_overrides_by_id (tree, data, data->id);
447        } else {
448                if (transparent)
449                        data->id = old_data->id;
450
451                data->overridden = old_data->overridden;
452                gtk_signal_emit (GTK_OBJECT (tree),
453                                 signals [REPLACE_OVERRIDE], new, old);
454
455/*              fprintf (stderr, "Replace override of '%s' '%s' with '%s' '%s'",
456                         old->name, bonobo_ui_node_get_attr (old, "name"),
457                         new->name, bonobo_ui_node_get-attr (new, "name"));*/
458        }
459
460        old_data->overridden = NULL;
461
462        /* This code doesn't work right with opaque BonoboUINode object */
463        if (bonobo_ui_node_children (new))
464                merge (tree, old, (BonoboUINode**)&XML_NODE (new)->xmlChildrenNode);
465
466        move_children (old, new);
467
468        xmlReplaceNode (XML_NODE (old), XML_NODE (new));
469
470        g_assert (bonobo_ui_node_children (old) == NULL);
471
472        if (transparent)
473                bonobo_ui_node_copy_attrs (old, new);
474
475        bonobo_ui_xml_set_dirty (tree, new);
476
477        if (!override)
478                node_free (tree, old);
479
480        watch_update (tree, new);
481}
482
483static void
484reinstate_old_node (BonoboUIXml *tree, BonoboUINode *node)
485{
486        BonoboUIXmlData *data = bonobo_ui_xml_get_data (tree, node);
487        BonoboUINode  *old;
488
489        g_return_if_fail (data != NULL);
490
491        if (data->overridden) { /* Something to re-instate */
492                BonoboUIXmlData *old_data;
493
494                g_return_if_fail (data->overridden->data != NULL);
495
496                /* Get Old node from overridden list */
497                old = data->overridden->data;
498                old_data = bonobo_ui_xml_get_data (tree, old);
499
500/*              fprintf (stderr, "Reinstating override '%s' '%s' with '%s' '%s'",
501                         node->name, xmlGetProp (node, "name"),
502                         old->name, xmlGetProp (old, "name"));*/
503               
504                /* Update Overridden list */
505                old_data->overridden = g_slist_next (data->overridden);
506                g_slist_free_1 (data->overridden);
507                data->overridden = NULL;
508
509                /* Fire remove while still in tree */
510                gtk_signal_emit (GTK_OBJECT (tree), signals [REMOVE], node);
511               
512                /* Move children across */
513                move_children (node, old);
514
515                /* Switch node back into tree */
516                bonobo_ui_node_replace (old, node);
517
518                /* Mark dirty */
519                bonobo_ui_xml_set_dirty (tree, old);
520
521                gtk_signal_emit (GTK_OBJECT (tree), signals [REINSTATE], old);
522
523                watch_update (tree, old);
524
525        } else if (bonobo_ui_node_children (node)) { /* We need to leave the node here */
526                /* Re-tag the node */
527                BonoboUIXmlData *child_data =
528                        bonobo_ui_xml_get_data (tree, bonobo_ui_node_children (node));
529                data->id = child_data->id;
530
531                /* Mark dirty */
532                bonobo_ui_xml_set_dirty (tree, node);
533               
534                gtk_signal_emit (GTK_OBJECT (tree), signals [RENAME], node);
535                return;
536        } else {
537                /* Mark parent & up dirty */
538                bonobo_ui_xml_set_dirty (tree, node);
539
540                gtk_signal_emit (GTK_OBJECT (tree), signals [REMOVE], node);
541        }
542
543/*              fprintf (stderr, "destroying node '%s' '%s'\n",
544                node->name, bonobo_ui_node_get_attr (node, "name"));*/
545                       
546        bonobo_ui_node_unlink (node);
547       
548        if (node == tree->root) /* Ugly special case */
549                tree->root = NULL;
550
551        /* Destroy the old node */
552        node_free (tree, node);
553}
554
555static BonoboUINode *
556find_child (BonoboUINode *node, const char *name)
557{
558        BonoboUINode *l, *ret = NULL;
559
560        g_return_val_if_fail (name != NULL, NULL);
561        g_return_val_if_fail (node != NULL, NULL);
562
563        for (l = bonobo_ui_node_children (node); l && !ret; l = bonobo_ui_node_next (l)) {
564                char *txt;
565
566                if ((txt = bonobo_ui_node_get_attr (l, "name"))) {
567                        if (!strcmp (txt, name))
568                                ret = l;
569
570                        bonobo_ui_node_free_string (txt);
571                }
572
573                if (!ret && bonobo_ui_node_has_name (l, name))
574                        ret = l;
575        }
576
577        return ret;
578}
579
580/*
581 *  This monumental waste of time chewed 2 hours of my life
582 * and was to try and help Eazel not have to change their
583 * code at all; These routines worked fine, the compat ones
584 * were duff.
585 */
586/*
587char *
588bonobo_ui_xml_path_escape (const char *path)
589{
590        char *ret, *dest;
591        int   len = 0;
592        const char *p;
593
594        for (p = path; p && *p; p++) {
595                if (*p == '/')
596                        len++;
597                len++;
598        }
599       
600        dest = ret = g_malloc (len + 1);
601
602        for (p = path; p && *p; p++) {
603                if (*p == '/' ||
604                    *p == '\\')
605                        *dest++ = '\\';
606                *dest++ = *p;
607        }
608        dest [len] = '\0';
609
610        return ret;
611}
612
613char *
614bonobo_ui_xml_path_unescape (const char *path)
615{
616        char *ret, *dest;
617        const char *p;
618       
619        dest = ret = g_malloc (strlen (path) + 1);
620
621        for (p = path; p && *p; p++) {
622                if (*p == '\\')
623                        p++;
624                *dest++ = *p;
625        }
626        *dest = '\0';
627       
628        return ret;
629}
630*/
631
632/*
633 * bonobo_ui_xml_path_split:
634 * @path: the path to split
635 *
636 * This function splits a path on the '/' character
637 * there is no escaping in place in the path - thus
638 * do not use the '/' character in a node name.
639 *
640 * Return value: the split path.
641 */
642static char **
643bonobo_ui_xml_path_split (const char *path)
644{
645        return g_strsplit (path, "/", -1);
646/*      GSList *string_list = NULL, *l;
647        char *chunk, **ret;
648        int   i, chunks;
649        const char *p;
650
651        g_return_val_if_fail (path != NULL, NULL);
652
653        chunk = g_malloc (strlen (path) + 2);
654        p = path;
655
656        string_list = g_slist_prepend (string_list, chunk);
657        chunks = 1;
658        for (i = 0; p && *p; i++) {
659
660                if (*p == '\\') {
661                        p++;
662                        chunk [i] = *p++;
663                } else if (*p == '/') {
664                        chunk [i] = '\0';
665                        p++;
666                        if (*p != '\0') {
667                                string_list = g_slist_prepend (
668                                        string_list, &chunk [i] + 1);
669                                chunks++;
670                        }
671                } else
672                        chunk [i] = *p++;
673        }
674        chunk [i] = '\0';
675        g_assert (i < strlen (path) + 2);
676
677        ret = g_new (char *, chunks + 1);
678        ret [chunks] = NULL;
679
680        l = string_list;
681        for (i = chunks - 1; i >= 0; i--) {
682                ret [i] = l->data;
683                l = bonobo_ui_node_next (l);
684        }
685        g_assert (l == NULL);
686        g_slist_free (string_list);
687        g_assert (ret [0] == chunk);
688
689        fprintf (stderr, "Split path '%s' to:\n", path);
690        for (i = 0; ret [i]; i++)
691                fprintf (stderr, "> %s\n", ret [i]);
692
693                return ret;*/
694}
695
696static void
697bonobo_ui_xml_path_freev (char **split)
698{
699        g_strfreev (split);
700
701/*      if (split)
702                g_free (*split);
703
704                g_free (split);*/
705}
706
707static BonoboUINode *
708xml_get_path (BonoboUIXml *tree, const char *path,
709              gboolean allow_wild, gboolean *wildcard)
710{
711        BonoboUINode *ret;
712        char   **names;
713        int      i;
714       
715        g_return_val_if_fail (tree != NULL, NULL);
716        g_return_val_if_fail (!allow_wild || wildcard != NULL, NULL);
717
718#ifdef UI_XML_DEBUG
719        fprintf (stderr, "Find path '%s'\n", path);
720#endif
721        DUMP_XML (tree, tree->root, "Before find path");
722
723        if (allow_wild)
724                *wildcard = FALSE;
725        if (!path || path [0] == '\0')
726                return tree->root;
727
728        if (path [0] != '/')
729                g_warning ("non-absolute path brokenness '%s'", path);
730
731        names = bonobo_ui_xml_path_split (path);
732
733        ret = tree->root;
734        for (i = 0; names && names [i]; i++) {
735                if (names [i] [0] == '\0')
736                        continue;
737
738/*              g_warning ("Path element '%s'", names [i]);*/
739
740                if (allow_wild &&
741                    names [i] [0] == '*' &&
742                    names [i] [1] == '\0')
743                        *wildcard = TRUE;
744
745                else if (!(ret = find_child (ret, names [i]))) {
746                        bonobo_ui_xml_path_freev (names);
747                        return NULL;
748                }
749        }
750               
751        bonobo_ui_xml_path_freev (names);
752
753        DUMP_XML (tree, tree->root, "After clean find path");
754
755        return ret;
756}
757
758/**
759 * bonobo_ui_xml_get_path:
760 * @tree: the tree
761 * @path: the path
762 *
763 * This function returns the node at path @path inside the
764 * internal XML tree.
765 *
766 * Return value: a pointer to the node at @path
767 **/
768BonoboUINode *
769bonobo_ui_xml_get_path (BonoboUIXml *tree, const char *path)
770{
771        return xml_get_path (tree, path, FALSE, NULL);
772}
773
774/**
775 * bonobo_ui_xml_get_path_wildcard:
776 * @tree: the tree
777 * @path: the path
778 * @wildcard: whether to allow '*' as a wildcard
779 *
780 * This does a wildcard path match, the only
781 * wildcard character is '*'. This is only really
782 * used by the _rm command and the _exists functionality.
783 *
784 * Return value: TRUE if the path matches.
785 **/
786BonoboUINode *
787bonobo_ui_xml_get_path_wildcard (BonoboUIXml *tree, const char *path,
788                                 gboolean    *wildcard)
789{
790        return xml_get_path (tree, path, TRUE, wildcard);
791}
792
793/**
794 * bonobo_ui_xml_make_path:
795 * @node: the node.
796 *
797 * This generates a path name for a node in a tree.
798 *
799 * Return value: the path name, use g_free to release.
800 **/
801char *
802bonobo_ui_xml_make_path  (BonoboUINode *node)
803{
804        GString *path;
805        char    *tmp;
806
807        g_return_val_if_fail (node != NULL, NULL);
808
809        path = g_string_new ("");
810        while (node && bonobo_ui_node_parent (node)) {
811
812                if ((tmp = bonobo_ui_node_get_attr (node, "name"))) {
813                        g_string_prepend (path, tmp);
814                        g_string_prepend (path, "/");
815                        bonobo_ui_node_free_string (tmp);
816                } else {
817                        g_string_prepend (path, bonobo_ui_node_get_name (node));
818                        g_string_prepend (path, "/");
819                }
820
821                node = bonobo_ui_node_parent (node);
822        }
823
824        tmp = path->str;
825        g_string_free (path, FALSE);
826
827/*      fprintf (stderr, "Make path: '%s'\n", tmp);*/
828        return tmp;
829}
830
831static void
832reinstate_node (BonoboUIXml *tree, BonoboUINode *node,
833                gpointer id, gboolean nail_me)
834{
835        BonoboUINode *l, *next;
836                       
837        for (l = bonobo_ui_node_children (node); l; l = next) {
838                next = bonobo_ui_node_next (l);
839                reinstate_node (tree, l, id, TRUE);
840        }
841
842        if (nail_me) {
843                BonoboUIXmlData *data;
844
845                data = bonobo_ui_xml_get_data (tree, node);
846
847                if (identical (tree, data->id, id))
848                        reinstate_old_node (tree, node);
849                else
850                        prune_overrides_by_id (tree, data, id);
851        }
852}
853
854static void
855do_insert (BonoboUINode *parent,
856           BonoboUINode *child,
857           BonoboUINode *insert)
858{
859        char    *pos;
860        gboolean added = FALSE;
861
862        if ((pos = bonobo_ui_node_get_attr (child, "pos"))) {
863                if (pos [0] == 't') {
864                        bonobo_ui_node_insert_before (
865                                bonobo_ui_node_children (parent),
866                                child);
867                        added = TRUE;
868                }
869                bonobo_ui_node_free_string (pos);
870        }
871
872        if (!added) {
873                if (insert)
874                        bonobo_ui_node_insert_before (
875                                insert, child);
876                else
877                        bonobo_ui_node_add_child (parent, child);
878        }
879}
880
881static void
882merge (BonoboUIXml *tree, BonoboUINode *current, BonoboUINode **new)
883{
884        BonoboUINode *a, *b, *nexta, *nextb, *insert = NULL;
885
886        for (a = bonobo_ui_node_children (current); a; a = nexta) {
887                BonoboUINode *result;
888                xmlChar *a_name;
889                xmlChar *b_name = NULL;
890                       
891                nexta = bonobo_ui_node_next (a);
892                nextb = NULL;
893
894                a_name = bonobo_ui_node_get_attr (a, "name");
895
896                for (b = *new; b; b = nextb) {
897                        nextb = bonobo_ui_node_next (b);
898
899                        bonobo_ui_node_free_string (b_name);
900                        b_name = NULL;
901
902/*                      printf ("'%s' '%s' with '%s' '%s'\n",
903                                a->name, bonobo_ui_node_get_attr (a, "name"),
904                                b->name, bonobo_ui_node_get_attr (b, "name"));*/
905                       
906                        b_name = bonobo_ui_node_get_attr (b, "name");
907
908                        if (!a_name && !b_name &&
909                            bonobo_ui_node_has_name (
910                                    a, bonobo_ui_node_get_name (b)))
911                                break;
912
913                        if (!a_name || !b_name)
914                                continue;
915
916                        if (!strcmp (a_name, b_name))
917                                break;
918                }
919                bonobo_ui_node_free_string (b_name);
920
921                if (b == *new)
922                        *new = nextb;
923
924                if (b) /* Merger candidate */
925                        override_node_with (tree, a, b);
926
927                result = b ? b : a;
928
929                if (!insert && !a_name &&
930                    bonobo_ui_node_has_name (result, "placeholder"))
931                        insert = result;
932
933                bonobo_ui_node_free_string (a_name);
934        }
935
936        for (b = *new; b; b = nextb) {
937                BonoboUIXmlData *data;
938               
939                nextb = bonobo_ui_node_next (b);
940               
941/*              fprintf (stderr, "Transfering '%s' '%s' into '%s' '%s'\n",
942                         b->name, bonobo_ui_node_get_attr (b, "name"),
943                         current->name, bonobo_ui_node_get_attr (current, "name"));*/
944
945                bonobo_ui_node_unlink (b);
946
947                do_insert (current, b, insert);
948
949                if (tree->add_node)
950                        tree->add_node (current, b, tree->user_data);
951               
952                bonobo_ui_xml_set_dirty (tree, b);
953
954                /* FIXME: this looks redundant */
955                data = bonobo_ui_xml_get_data (tree, current);
956                data->dirty = TRUE;
957
958                watch_update (tree, b);
959
960/*              DUMP_XML (tree, current, "After transfer");*/
961        }
962
963        *new = NULL;
964/*      DUMP_XML (tree, current, "After all"); */
965}
966
967/**
968 * bonobo_ui_xml_merge:
969 * @tree: the tree
970 * @path: the path to merge into
971 * @nodes: the nodes
972 * @id: the id to merge with.
973 *
974 * Merges new xml nodes into the internal tree, overriding
975 * where appropriate. Merging is by id, ie. overriding only
976 * occurs where there is an id mismatch.
977 *
978 * Return value: an error flag.
979 **/
980BonoboUIError
981bonobo_ui_xml_merge (BonoboUIXml  *tree,
982                     const char   *path,
983                     BonoboUINode *nodes,
984                     gpointer      id)
985{
986        BonoboUINode *current;
987
988        g_return_val_if_fail (BONOBO_IS_UI_XML (tree), BONOBO_UI_ERROR_BAD_PARAM);
989
990        if (nodes == NULL)
991                return BONOBO_UI_ERROR_OK;
992
993        bonobo_ui_node_strip (&nodes);
994        set_id (tree, nodes, id);
995
996        current = bonobo_ui_xml_get_path (tree, path);
997        if (!current) {
998                xmlNode *l, *next;
999
1000                for (l = XML_NODE (nodes); l; l = next) {
1001                        next = l->next;
1002                        node_free (tree, BNODE (l));
1003                }
1004
1005                return BONOBO_UI_ERROR_INVALID_PATH;
1006        }
1007
1008#ifdef UI_XML_DEBUG
1009        {
1010                char *txt;
1011                fprintf (stderr, "\n\n\nPATH: '%s' '%s\n", current->name,
1012                         (txt = bonobo_ui_node_get_attr (current, "name")));
1013                bonobo_ui_node_free_string (txt);
1014        }
1015#endif
1016
1017        DUMP_XML (tree, tree->root, "Merging in");
1018        DUMP_XML (tree, nodes, "this load");
1019
1020        merge (tree, current, &nodes);
1021
1022#ifdef UI_XML_DEBUG
1023        bonobo_ui_xml_dump (tree, tree->root, "Merged to");
1024#endif
1025
1026        return BONOBO_UI_ERROR_OK;
1027}
1028
1029BonoboUIError
1030bonobo_ui_xml_rm (BonoboUIXml *tree,
1031                  const char  *path,
1032                  gpointer     id)
1033{
1034        BonoboUINode *current;
1035        gboolean      wildcard;
1036
1037        current = bonobo_ui_xml_get_path_wildcard (
1038                tree, path, &wildcard);
1039
1040/*      fprintf (stderr, "remove stuff from '%s' (%d) -> '%p'\n",
1041        path, wildcard, current);*/
1042
1043        if (current)
1044                reinstate_node (tree, current, id, !wildcard);
1045        else
1046                return BONOBO_UI_ERROR_INVALID_PATH;
1047
1048        DUMP_XML (tree, tree->root, "After remove");
1049
1050        return BONOBO_UI_ERROR_OK;
1051}
1052
1053static void
1054bonobo_ui_xml_destroy (GtkObject *object)
1055{
1056        BonoboUIXml *tree = BONOBO_UI_XML (object);
1057
1058        if (tree) {
1059                GSList *l;
1060
1061                if (tree->root) {
1062                        free_nodedata_tree (tree, tree->root, TRUE);
1063                        bonobo_ui_node_free (tree->root);
1064                        tree->root = NULL;
1065                }
1066
1067                for (l = tree->watches; l; l = l->next)
1068                        watch_destroy (l->data);
1069                g_slist_free (tree->watches);
1070                tree->watches = NULL;
1071        }
1072}
1073
1074static void
1075bonobo_ui_xml_class_init (BonoboUIXmlClass *klass)
1076{
1077        GtkObjectClass *object_class = (GtkObjectClass *) klass;
1078       
1079        bonobo_ui_xml_parent_class = gtk_type_class (
1080                gtk_object_get_type ());
1081
1082        object_class->destroy = bonobo_ui_xml_destroy;
1083
1084        signals [OVERRIDE] = gtk_signal_new (
1085                "override", GTK_RUN_FIRST,
1086                object_class->type,
1087                GTK_SIGNAL_OFFSET (BonoboUIXmlClass, override),
1088                gtk_marshal_NONE__POINTER_POINTER,
1089                GTK_TYPE_NONE, 2, GTK_TYPE_POINTER, GTK_TYPE_POINTER);
1090
1091        signals [REPLACE_OVERRIDE] = gtk_signal_new (
1092                "replace_override", GTK_RUN_FIRST,
1093                object_class->type,
1094                GTK_SIGNAL_OFFSET (BonoboUIXmlClass, replace_override),
1095                gtk_marshal_NONE__POINTER_POINTER,
1096                GTK_TYPE_NONE, 2, GTK_TYPE_POINTER, GTK_TYPE_POINTER);
1097
1098        signals [REINSTATE] = gtk_signal_new (
1099                "reinstate", GTK_RUN_FIRST,
1100                object_class->type,
1101                GTK_SIGNAL_OFFSET (BonoboUIXmlClass, reinstate),
1102                gtk_marshal_NONE__POINTER,
1103                GTK_TYPE_NONE, 1, GTK_TYPE_POINTER);
1104
1105        signals [RENAME] = gtk_signal_new (
1106                "rename", GTK_RUN_FIRST,
1107                object_class->type,
1108                GTK_SIGNAL_OFFSET (BonoboUIXmlClass, rename),
1109                gtk_marshal_NONE__POINTER,
1110                GTK_TYPE_NONE, 1, GTK_TYPE_POINTER);
1111
1112        signals [REMOVE] = gtk_signal_new (
1113                "remove", GTK_RUN_FIRST,
1114                object_class->type,
1115                GTK_SIGNAL_OFFSET (BonoboUIXmlClass, remove),
1116                gtk_marshal_NONE__POINTER,
1117                GTK_TYPE_NONE, 1, GTK_TYPE_POINTER);
1118
1119        gtk_object_class_add_signals (object_class, signals, LAST_SIGNAL);
1120}
1121
1122/**
1123 * bonobo_cmd_model_get_type:
1124 *
1125 * Returns the GtkType for the BonoboCmdModel class.
1126 */
1127GtkType
1128bonobo_ui_xml_get_type (void)
1129{
1130        static GtkType type = 0;
1131
1132        if (!type) {
1133                GtkTypeInfo info = {
1134                        "BonoboUIXml",
1135                        sizeof (BonoboUIXml),
1136                        sizeof (BonoboUIXmlClass),
1137                        (GtkClassInitFunc) bonobo_ui_xml_class_init,
1138                        (GtkObjectInitFunc) NULL,
1139                        NULL, /* reserved 1 */
1140                        NULL, /* reserved 2 */
1141                        (GtkClassInitFunc) NULL
1142                };
1143
1144                type = gtk_type_unique (gtk_object_get_type (), &info);
1145        }
1146
1147        return type;
1148}
1149
1150BonoboUIXml *
1151bonobo_ui_xml_new (BonoboUIXmlCompareFn   compare,
1152                   BonoboUIXmlDataNewFn   data_new,
1153                   BonoboUIXmlDataFreeFn  data_free,
1154                   BonoboUIXmlDumpFn      dump,
1155                   BonoboUIXmlAddNode     add_node,
1156                   gpointer               user_data)
1157{
1158        BonoboUIXml *tree;
1159
1160        tree = gtk_type_new (BONOBO_UI_XML_TYPE);
1161
1162        tree->compare = compare;
1163        tree->data_new = data_new;
1164        tree->data_free = data_free;
1165        tree->dump = dump;
1166        tree->add_node = add_node;
1167        tree->user_data = user_data;
1168
1169        tree->root = bonobo_ui_node_new ("Root");
1170
1171        tree->watches = NULL;
1172
1173        return tree;
1174}
1175
1176typedef struct {
1177        char    *path;
1178        gpointer user_data;
1179} Watch;
1180
1181static void
1182watch_update (BonoboUIXml *tree, BonoboUINode *node)
1183{
1184        GSList *l;
1185        char   *path;
1186
1187        if (!tree->watch)
1188                return;
1189
1190        /* FIXME: for speed we only check root nodes for now */
1191        if (bonobo_ui_node_parent (node) !=
1192            tree->root)
1193                return;
1194
1195        path = bonobo_ui_xml_make_path (node);
1196
1197        for (l = tree->watches; l; l = l->next) {
1198                Watch *w = l->data;
1199
1200                if (!strcmp (w->path, path)) {
1201/*                      fprintf (stderr, "Found watch on '%s'", path);*/
1202                        tree->watch (tree, path, node, w->user_data);
1203                }
1204        }
1205        g_free (path);
1206}
1207
1208void
1209bonobo_ui_xml_add_watch (BonoboUIXml  *tree,
1210                         const char   *path,
1211                         gpointer      user_data)
1212{
1213        Watch *w = g_new0 (Watch, 1);
1214
1215        g_return_if_fail (BONOBO_IS_UI_XML (tree));
1216
1217        w->path = g_strdup (path);
1218        w->user_data = user_data;
1219
1220        tree->watches = g_slist_append (
1221                tree->watches, w);
1222}
1223
1224void
1225bonobo_ui_xml_remove_watch_by_data (BonoboUIXml *tree,
1226                                    gpointer     user_data)
1227{
1228        GSList *l;
1229        GSList *next;
1230
1231        g_return_if_fail (BONOBO_IS_UI_XML (tree));
1232
1233        for (l = tree->watches; l; l = next) {
1234                Watch *w = l->data;
1235
1236                next = l->next;
1237
1238                if (w->user_data == user_data) {
1239                        tree->watches = g_slist_remove (
1240                                tree->watches, w);
1241                        watch_destroy (w);
1242                }
1243        }
1244}
1245
1246static void
1247watch_destroy (gpointer data)
1248{
1249        Watch *w = data;
1250       
1251        if (w) {
1252                g_free (w->path);
1253                g_free (w);
1254        }
1255}
1256
1257void
1258bonobo_ui_xml_set_watch_fn (BonoboUIXml       *tree,
1259                            BonoboUIXmlWatchFn watch)
1260{
1261        g_return_if_fail (BONOBO_IS_UI_XML (tree));
1262
1263        tree->watch = watch;
1264}
Note: See TracBrowser for help on using the repository browser.