1 #define __SP_XMLVIEW_TREE_C__
3 /*
4 * Specialization of GtkCTree for the XML tree view
5 *
6 * Authors:
7 * MenTaLguY <mental@rydia.net>
8 *
9 * Copyright (C) 2002 MenTaLguY
10 *
11 * Released under the GNU GPL; see COPYING for details
12 */
14 #include <cstring>
15 #include <string>
17 #include "../xml/node-event-vector.h"
18 #include "sp-xmlview-tree.h"
20 struct NodeData {
21 SPXMLViewTree * tree;
22 GtkCTreeNode * node;
23 Inkscape::XML::Node * repr;
24 };
26 #define NODE_DATA(node) ((NodeData *)(GTK_CTREE_ROW ((node))->row.data))
28 static void sp_xmlview_tree_class_init (SPXMLViewTreeClass * klass);
29 static void sp_xmlview_tree_init (SPXMLViewTree * tree);
30 static void sp_xmlview_tree_destroy (GtkObject * object);
32 static NodeData * node_data_new (SPXMLViewTree * tree, GtkCTreeNode * node, Inkscape::XML::Node * repr);
33 static void node_data_free (gpointer data);
35 static GtkCTreeNode * add_node (SPXMLViewTree * tree, GtkCTreeNode * parent, GtkCTreeNode * before, Inkscape::XML::Node * repr);
37 static void element_child_added (Inkscape::XML::Node * repr, Inkscape::XML::Node * child, Inkscape::XML::Node * ref, gpointer data);
38 static void element_attr_changed (Inkscape::XML::Node * repr, const gchar * key, const gchar * old_value, const gchar * new_value, bool is_interactive, gpointer data);
39 static void element_child_removed (Inkscape::XML::Node * repr, Inkscape::XML::Node * child, Inkscape::XML::Node * ref, gpointer data);
40 static void element_order_changed (Inkscape::XML::Node * repr, Inkscape::XML::Node * child, Inkscape::XML::Node * oldref, Inkscape::XML::Node * newref, gpointer data);
42 static void text_content_changed (Inkscape::XML::Node * repr, const gchar * old_content, const gchar * new_content, gpointer data);
43 static void comment_content_changed (Inkscape::XML::Node * repr, const gchar * old_content, const gchar * new_content, gpointer data);
45 static void tree_move (GtkCTree * tree, GtkCTreeNode * node, GtkCTreeNode * new_parent, GtkCTreeNode * new_sibling);
47 static gboolean check_drag (GtkCTree * tree, GtkCTreeNode * node, GtkCTreeNode * new_parent, GtkCTreeNode * new_sibling);
49 static GtkCTreeNode * ref_to_sibling (GtkCTreeNode * parent, Inkscape::XML::Node * ref);
50 static GtkCTreeNode * repr_to_child (GtkCTreeNode * parent, Inkscape::XML::Node * repr);
51 static Inkscape::XML::Node * sibling_to_ref (GtkCTreeNode * parent, GtkCTreeNode * sibling);
53 static gint match_node_data_by_repr(gconstpointer data_p, gconstpointer repr);
55 static const Inkscape::XML::NodeEventVector element_repr_events = {
56 element_child_added,
57 element_child_removed,
58 element_attr_changed,
59 NULL, /* content_changed */
60 element_order_changed
61 };
63 static const Inkscape::XML::NodeEventVector text_repr_events = {
64 NULL, /* child_added */
65 NULL, /* child_removed */
66 NULL, /* attr_changed */
67 text_content_changed,
68 NULL /* order_changed */
69 };
71 static const Inkscape::XML::NodeEventVector comment_repr_events = {
72 NULL, /* child_added */
73 NULL, /* child_removed */
74 NULL, /* attr_changed */
75 comment_content_changed,
76 NULL /* order_changed */
77 };
79 static GtkCTreeClass * parent_class = NULL;
81 GtkWidget *
82 sp_xmlview_tree_new (Inkscape::XML::Node * repr, void * factory, void * data)
83 {
84 SPXMLViewTree * tree;
86 tree = (SPXMLViewTree*)g_object_new (SP_TYPE_XMLVIEW_TREE, "n_columns", 1, "tree_column", 0, NULL);
88 gtk_clist_column_titles_hide (GTK_CLIST (tree));
89 gtk_ctree_set_line_style (GTK_CTREE (tree), GTK_CTREE_LINES_NONE);
90 gtk_ctree_set_expander_style (GTK_CTREE (tree), GTK_CTREE_EXPANDER_TRIANGLE);
91 gtk_clist_set_column_auto_resize (GTK_CLIST (tree), 0, TRUE);
92 gtk_clist_set_reorderable (GTK_CLIST (tree), TRUE);
93 gtk_ctree_set_drag_compare_func (GTK_CTREE (tree), check_drag);
95 sp_xmlview_tree_set_repr (tree, repr);
97 return (GtkWidget *) tree;
98 }
100 void
101 sp_xmlview_tree_set_repr (SPXMLViewTree * tree, Inkscape::XML::Node * repr)
102 {
103 if ( tree->repr == repr ) return;
104 gtk_clist_freeze (GTK_CLIST (tree));
105 if (tree->repr) {
106 gtk_clist_clear (GTK_CLIST (tree));
107 Inkscape::GC::release(tree->repr);
108 }
109 tree->repr = repr;
110 if (repr) {
111 GtkCTreeNode * node;
112 Inkscape::GC::anchor(repr);
113 node = add_node (tree, NULL, NULL, repr);
114 gtk_ctree_expand (GTK_CTREE (tree), node);
115 }
116 gtk_clist_thaw (GTK_CLIST (tree));
117 }
119 GtkType
120 sp_xmlview_tree_get_type (void)
121 {
122 static GtkType type = 0;
124 if (!type) {
125 static const GtkTypeInfo info = {
126 "SPXMLViewTree",
127 sizeof (SPXMLViewTree),
128 sizeof (SPXMLViewTreeClass),
129 (GtkClassInitFunc) sp_xmlview_tree_class_init,
130 (GtkObjectInitFunc) sp_xmlview_tree_init,
131 NULL, NULL, NULL
132 };
133 type = gtk_type_unique (GTK_TYPE_CTREE, &info);
134 }
136 return type;
137 }
139 void
140 sp_xmlview_tree_class_init (SPXMLViewTreeClass * klass)
141 {
142 GtkObjectClass * object_class;
144 object_class = (GtkObjectClass *) klass;
145 parent_class = (GtkCTreeClass *) gtk_type_class (GTK_TYPE_CTREE);
147 GTK_CTREE_CLASS (object_class)->tree_move = tree_move;
149 object_class->destroy = sp_xmlview_tree_destroy;
150 }
152 void
153 sp_xmlview_tree_init (SPXMLViewTree * tree)
154 {
155 tree->repr = NULL;
156 tree->blocked = 0;
157 }
159 void
160 sp_xmlview_tree_destroy (GtkObject * object)
161 {
162 SPXMLViewTree * tree;
164 tree = SP_XMLVIEW_TREE (object);
166 sp_xmlview_tree_set_repr (tree, NULL);
168 GTK_OBJECT_CLASS (parent_class)->destroy (object);
169 }
171 GtkCTreeNode *
172 add_node (SPXMLViewTree * tree, GtkCTreeNode * parent, GtkCTreeNode * before, Inkscape::XML::Node * repr)
173 {
174 NodeData * data;
175 GtkCTreeNode * node;
176 const Inkscape::XML::NodeEventVector * vec;
177 static const gchar *default_text[] = { "???" };
179 g_assert (tree != NULL);
180 g_assert (repr != NULL);
182 node = gtk_ctree_insert_node (GTK_CTREE (tree), parent, before, (gchar **)default_text, 2, NULL, NULL, NULL, NULL, ( repr->type() != Inkscape::XML::ELEMENT_NODE ), FALSE);
183 g_assert (node != NULL);
185 data = node_data_new (tree, node, repr);
186 g_assert (data != NULL);
188 gtk_ctree_node_set_row_data_full (GTK_CTREE (tree), data->node, data, node_data_free);
190 if ( repr->type() == Inkscape::XML::TEXT_NODE ) {
191 vec = &text_repr_events;
192 } else if ( repr->type() == Inkscape::XML::COMMENT_NODE ) {
193 vec = &comment_repr_events;
194 } else if ( repr->type() == Inkscape::XML::ELEMENT_NODE ) {
195 vec = &element_repr_events;
196 } else {
197 vec = NULL;
198 }
200 if (vec) {
201 gtk_clist_freeze (GTK_CLIST (tree));
202 /* cheat a little to get the id upated properly */
203 if (repr->type() == Inkscape::XML::ELEMENT_NODE) {
204 element_attr_changed (repr, "id", NULL, NULL, false, data);
205 }
206 sp_repr_add_listener (repr, vec, data);
207 sp_repr_synthesize_events (repr, vec, data);
208 gtk_clist_thaw (GTK_CLIST (tree));
209 }
211 return node;
212 }
214 NodeData *
215 node_data_new (SPXMLViewTree * tree, GtkCTreeNode * node, Inkscape::XML::Node * repr)
216 {
217 NodeData * data;
218 data = g_new (NodeData, 1);
219 data->tree = tree;
220 data->node = node;
221 data->repr = repr;
222 Inkscape::GC::anchor(repr);
223 return data;
224 }
226 void
227 node_data_free (gpointer ptr) {
228 NodeData * data;
229 data = (NodeData *) ptr;
230 sp_repr_remove_listener_by_data (data->repr, data);
231 g_assert (data->repr != NULL);
232 Inkscape::GC::release(data->repr);
233 g_free (data);
234 }
236 void
237 element_child_added (Inkscape::XML::Node * repr, Inkscape::XML::Node * child, Inkscape::XML::Node * ref, gpointer ptr)
238 {
239 NodeData * data;
240 GtkCTreeNode * before;
242 data = (NodeData *) ptr;
244 if (data->tree->blocked) return;
246 before = ref_to_sibling (data->node, ref);
248 add_node (data->tree, data->node, before, child);
249 }
251 void
252 element_attr_changed (Inkscape::XML::Node * repr, const gchar * key, const gchar * old_value, const gchar * new_value, bool is_interactive, gpointer ptr)
253 {
254 NodeData * data;
255 gchar *label;
257 data = (NodeData *) ptr;
259 if (data->tree->blocked) return;
261 if (strcmp (key, "id")) return;
263 if (new_value) {
264 label = g_strdup_printf ("<%s id=\"%s\">", repr->name(), new_value);
265 } else {
266 label = g_strdup_printf ("<%s>", repr->name());
267 }
268 gtk_ctree_node_set_text (GTK_CTREE (data->tree), data->node, 0, label);
269 g_free (label);
270 }
272 void
273 element_child_removed (Inkscape::XML::Node * repr, Inkscape::XML::Node * child, Inkscape::XML::Node * ref, gpointer ptr)
274 {
275 NodeData * data;
277 data = (NodeData *) ptr;
279 if (data->tree->blocked) return;
281 gtk_ctree_remove_node (GTK_CTREE (data->tree), repr_to_child (data->node, child));
282 }
284 void
285 element_order_changed (Inkscape::XML::Node * repr, Inkscape::XML::Node * child, Inkscape::XML::Node * oldref, Inkscape::XML::Node * newref, gpointer ptr)
286 {
287 NodeData * data;
288 GtkCTreeNode * before, * node;
290 data = (NodeData *) ptr;
292 if (data->tree->blocked) return;
294 before = ref_to_sibling (data->node, newref);
295 node = repr_to_child (data->node, child);
297 if ( before == node ) before = GTK_CTREE_ROW (before)->sibling;
299 parent_class->tree_move (GTK_CTREE (data->tree), node, data->node, before);
300 }
302 void
303 text_content_changed (Inkscape::XML::Node * repr, const gchar * old_content, const gchar * new_content, gpointer ptr)
304 {
305 NodeData *data;
306 gchar *label;
308 data = (NodeData *) ptr;
310 if (data->tree->blocked) return;
312 label = g_strdup_printf ("\"%s\"", new_content);
313 gtk_ctree_node_set_text (GTK_CTREE (data->tree), data->node, 0, label);
314 g_free (label);
315 }
317 void
318 comment_content_changed (Inkscape::XML::Node *repr, const gchar * old_content, const gchar *new_content, gpointer ptr)
319 {
320 NodeData *data;
321 gchar *label;
323 data = (NodeData *) ptr;
325 if (data->tree->blocked) return;
327 label = g_strdup_printf ("<!--%s-->", new_content);
328 gtk_ctree_node_set_text (GTK_CTREE (data->tree), data->node, 0, label);
329 g_free (label);
330 }
332 void
333 tree_move (GtkCTree * tree, GtkCTreeNode * node, GtkCTreeNode * new_parent, GtkCTreeNode * new_sibling)
334 {
335 GtkCTreeNode * old_parent;
336 Inkscape::XML::Node * ref;
338 old_parent = GTK_CTREE_ROW (node)->parent;
339 if ( !old_parent || !new_parent ) return;
341 ref = sibling_to_ref (new_parent, new_sibling);
343 gtk_clist_freeze (GTK_CLIST (tree));
345 SP_XMLVIEW_TREE (tree)->blocked++;
346 if (new_parent == old_parent) {
347 NODE_DATA (old_parent)->repr->changeOrder(NODE_DATA (node)->repr, ref);
348 } else {
349 NODE_DATA (old_parent)->repr->removeChild(NODE_DATA (node)->repr);
350 NODE_DATA (new_parent)->repr->addChild(NODE_DATA (node)->repr, ref);
351 }
352 SP_XMLVIEW_TREE (tree)->blocked--;
354 parent_class->tree_move (tree, node, new_parent, new_sibling);
356 gtk_clist_thaw (GTK_CLIST (tree));
357 }
359 GtkCTreeNode *
360 ref_to_sibling (GtkCTreeNode * parent, Inkscape::XML::Node * ref)
361 {
362 if (ref) {
363 GtkCTreeNode * before;
364 before = repr_to_child (parent, ref);
365 g_assert (before != NULL);
366 before = GTK_CTREE_ROW (before)->sibling;
367 return before;
368 } else {
369 return GTK_CTREE_ROW (parent)->children;
370 }
371 }
373 GtkCTreeNode *
374 repr_to_child (GtkCTreeNode * parent, Inkscape::XML::Node * repr)
375 {
376 GtkCTreeNode * child;
377 child = GTK_CTREE_ROW (parent)->children;
378 while ( child && NODE_DATA (child)->repr != repr ) {
379 child = GTK_CTREE_ROW (child)->sibling;
380 }
381 return child;
382 }
384 Inkscape::XML::Node *
385 sibling_to_ref (GtkCTreeNode * parent, GtkCTreeNode * sibling)
386 {
387 GtkCTreeNode * child;
388 child = GTK_CTREE_ROW (parent)->children;
389 if ( child == sibling ) return NULL;
390 while ( child && GTK_CTREE_ROW (child)->sibling != sibling ) {
391 child = GTK_CTREE_ROW (child)->sibling;
392 }
393 return NODE_DATA (child)->repr;
394 }
396 gboolean
397 check_drag (GtkCTree * tree, GtkCTreeNode * node, GtkCTreeNode * new_parent, GtkCTreeNode * new_sibling)
398 {
399 GtkCTreeNode * old_parent;
401 old_parent = GTK_CTREE_ROW (node)->parent;
403 if (!old_parent || !new_parent) return FALSE;
404 if (NODE_DATA (new_parent)->repr->type() != Inkscape::XML::ELEMENT_NODE) return FALSE;
406 /* fixme: we need add_child/remove_child/etc repr events without side-effects, so we can check here and give better visual feedback */
408 return TRUE;
409 }
411 Inkscape::XML::Node *
412 sp_xmlview_tree_node_get_repr (SPXMLViewTree * tree, GtkCTreeNode * node)
413 {
414 return NODE_DATA (node)->repr;
415 }
417 GtkCTreeNode *
418 sp_xmlview_tree_get_repr_node (SPXMLViewTree * tree, Inkscape::XML::Node * repr)
419 {
420 return gtk_ctree_find_by_row_data_custom (GTK_CTREE (tree), NULL, repr, match_node_data_by_repr);
421 }
423 gint
424 match_node_data_by_repr(gconstpointer data_p, gconstpointer repr)
425 {
426 return ((const NodeData *)data_p)->repr != (const Inkscape::XML::Node *)repr;
427 }