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);
44 static void pi_content_changed (Inkscape::XML::Node * repr, const gchar * old_content, const gchar * new_content, gpointer data);
46 static void tree_move (GtkCTree * tree, GtkCTreeNode * node, GtkCTreeNode * new_parent, GtkCTreeNode * new_sibling);
48 static gboolean check_drag (GtkCTree * tree, GtkCTreeNode * node, GtkCTreeNode * new_parent, GtkCTreeNode * new_sibling);
50 static GtkCTreeNode * ref_to_sibling (GtkCTreeNode * parent, Inkscape::XML::Node * ref);
51 static GtkCTreeNode * repr_to_child (GtkCTreeNode * parent, Inkscape::XML::Node * repr);
52 static Inkscape::XML::Node * sibling_to_ref (GtkCTreeNode * parent, GtkCTreeNode * sibling);
54 static gint match_node_data_by_repr(gconstpointer data_p, gconstpointer repr);
56 static const Inkscape::XML::NodeEventVector element_repr_events = {
57 element_child_added,
58 element_child_removed,
59 element_attr_changed,
60 NULL, /* content_changed */
61 element_order_changed
62 };
64 static const Inkscape::XML::NodeEventVector text_repr_events = {
65 NULL, /* child_added */
66 NULL, /* child_removed */
67 NULL, /* attr_changed */
68 text_content_changed,
69 NULL /* order_changed */
70 };
72 static const Inkscape::XML::NodeEventVector comment_repr_events = {
73 NULL, /* child_added */
74 NULL, /* child_removed */
75 NULL, /* attr_changed */
76 comment_content_changed,
77 NULL /* order_changed */
78 };
80 static const Inkscape::XML::NodeEventVector pi_repr_events = {
81 NULL, /* child_added */
82 NULL, /* child_removed */
83 NULL, /* attr_changed */
84 pi_content_changed,
85 NULL /* order_changed */
86 };
88 static GtkCTreeClass * parent_class = NULL;
90 GtkWidget *
91 sp_xmlview_tree_new (Inkscape::XML::Node * repr, void * /*factory*/, void * /*data*/)
92 {
93 SPXMLViewTree * tree;
95 tree = (SPXMLViewTree*)g_object_new (SP_TYPE_XMLVIEW_TREE, "n_columns", 1, "tree_column", 0, NULL);
97 gtk_clist_column_titles_hide (GTK_CLIST (tree));
98 gtk_ctree_set_line_style (GTK_CTREE (tree), GTK_CTREE_LINES_NONE);
99 gtk_ctree_set_expander_style (GTK_CTREE (tree), GTK_CTREE_EXPANDER_TRIANGLE);
100 gtk_clist_set_column_auto_resize (GTK_CLIST (tree), 0, TRUE);
101 gtk_clist_set_reorderable (GTK_CLIST (tree), TRUE);
102 gtk_ctree_set_drag_compare_func (GTK_CTREE (tree), check_drag);
104 sp_xmlview_tree_set_repr (tree, repr);
106 return (GtkWidget *) tree;
107 }
109 void
110 sp_xmlview_tree_set_repr (SPXMLViewTree * tree, Inkscape::XML::Node * repr)
111 {
112 if ( tree->repr == repr ) return;
113 gtk_clist_freeze (GTK_CLIST (tree));
114 if (tree->repr) {
115 gtk_clist_clear (GTK_CLIST (tree));
116 Inkscape::GC::release(tree->repr);
117 }
118 tree->repr = repr;
119 if (repr) {
120 GtkCTreeNode * node;
121 Inkscape::GC::anchor(repr);
122 node = add_node (tree, NULL, NULL, repr);
123 gtk_ctree_expand (GTK_CTREE (tree), node);
124 }
125 gtk_clist_thaw (GTK_CLIST (tree));
126 }
128 GtkType
129 sp_xmlview_tree_get_type (void)
130 {
131 static GtkType type = 0;
133 if (!type) {
134 static const GtkTypeInfo info = {
135 "SPXMLViewTree",
136 sizeof (SPXMLViewTree),
137 sizeof (SPXMLViewTreeClass),
138 (GtkClassInitFunc) sp_xmlview_tree_class_init,
139 (GtkObjectInitFunc) sp_xmlview_tree_init,
140 NULL, NULL, NULL
141 };
142 type = gtk_type_unique (GTK_TYPE_CTREE, &info);
143 }
145 return type;
146 }
148 void
149 sp_xmlview_tree_class_init (SPXMLViewTreeClass * klass)
150 {
151 GtkObjectClass * object_class;
153 object_class = (GtkObjectClass *) klass;
154 parent_class = (GtkCTreeClass *) gtk_type_class (GTK_TYPE_CTREE);
156 GTK_CTREE_CLASS (object_class)->tree_move = tree_move;
158 object_class->destroy = sp_xmlview_tree_destroy;
159 }
161 void
162 sp_xmlview_tree_init (SPXMLViewTree * tree)
163 {
164 tree->repr = NULL;
165 tree->blocked = 0;
166 }
168 void
169 sp_xmlview_tree_destroy (GtkObject * object)
170 {
171 SPXMLViewTree * tree;
173 tree = SP_XMLVIEW_TREE (object);
175 sp_xmlview_tree_set_repr (tree, NULL);
177 GTK_OBJECT_CLASS (parent_class)->destroy (object);
178 }
180 GtkCTreeNode *
181 add_node (SPXMLViewTree * tree, GtkCTreeNode * parent, GtkCTreeNode * before, Inkscape::XML::Node * repr)
182 {
183 NodeData * data;
184 GtkCTreeNode * node;
185 const Inkscape::XML::NodeEventVector * vec;
186 static const gchar *default_text[] = { "???" };
188 g_assert (tree != NULL);
189 g_assert (repr != NULL);
191 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);
192 g_assert (node != NULL);
194 data = node_data_new (tree, node, repr);
195 g_assert (data != NULL);
197 gtk_ctree_node_set_row_data_full (GTK_CTREE (tree), data->node, data, node_data_free);
199 if ( repr->type() == Inkscape::XML::TEXT_NODE ) {
200 vec = &text_repr_events;
201 } else if ( repr->type() == Inkscape::XML::COMMENT_NODE ) {
202 vec = &comment_repr_events;
203 } else if ( repr->type() == Inkscape::XML::PI_NODE ) {
204 vec = &pi_repr_events;
205 } else if ( repr->type() == Inkscape::XML::ELEMENT_NODE ) {
206 vec = &element_repr_events;
207 } else {
208 vec = NULL;
209 }
211 if (vec) {
212 gtk_clist_freeze (GTK_CLIST (tree));
213 /* cheat a little to get the id upated properly */
214 if (repr->type() == Inkscape::XML::ELEMENT_NODE) {
215 element_attr_changed (repr, "id", NULL, NULL, false, data);
216 }
217 sp_repr_add_listener (repr, vec, data);
218 sp_repr_synthesize_events (repr, vec, data);
219 gtk_clist_thaw (GTK_CLIST (tree));
220 }
222 return node;
223 }
225 NodeData *
226 node_data_new (SPXMLViewTree * tree, GtkCTreeNode * node, Inkscape::XML::Node * repr)
227 {
228 NodeData * data;
229 data = g_new (NodeData, 1);
230 data->tree = tree;
231 data->node = node;
232 data->repr = repr;
233 Inkscape::GC::anchor(repr);
234 return data;
235 }
237 void
238 node_data_free (gpointer ptr) {
239 NodeData * data;
240 data = (NodeData *) ptr;
241 sp_repr_remove_listener_by_data (data->repr, data);
242 g_assert (data->repr != NULL);
243 Inkscape::GC::release(data->repr);
244 g_free (data);
245 }
247 void
248 element_child_added (Inkscape::XML::Node * repr, Inkscape::XML::Node * child, Inkscape::XML::Node * ref, gpointer ptr)
249 {
250 NodeData * data;
251 GtkCTreeNode * before;
253 data = (NodeData *) ptr;
255 if (data->tree->blocked) return;
257 before = ref_to_sibling (data->node, ref);
259 add_node (data->tree, data->node, before, child);
260 }
262 void
263 element_attr_changed (Inkscape::XML::Node * repr, const gchar * key, const gchar * old_value, const gchar * new_value, bool is_interactive, gpointer ptr)
264 {
265 NodeData * data;
266 gchar *label;
268 data = (NodeData *) ptr;
270 if (data->tree->blocked) return;
272 if (strcmp (key, "id")) return;
274 if (new_value) {
275 label = g_strdup_printf ("<%s id=\"%s\">", repr->name(), new_value);
276 } else {
277 label = g_strdup_printf ("<%s>", repr->name());
278 }
279 gtk_ctree_node_set_text (GTK_CTREE (data->tree), data->node, 0, label);
280 g_free (label);
281 }
283 void
284 element_child_removed (Inkscape::XML::Node * repr, Inkscape::XML::Node * child, Inkscape::XML::Node * ref, gpointer ptr)
285 {
286 NodeData * data;
288 data = (NodeData *) ptr;
290 if (data->tree->blocked) return;
292 gtk_ctree_remove_node (GTK_CTREE (data->tree), repr_to_child (data->node, child));
293 }
295 void
296 element_order_changed (Inkscape::XML::Node * repr, Inkscape::XML::Node * child, Inkscape::XML::Node * oldref, Inkscape::XML::Node * newref, gpointer ptr)
297 {
298 NodeData * data;
299 GtkCTreeNode * before, * node;
301 data = (NodeData *) ptr;
303 if (data->tree->blocked) return;
305 before = ref_to_sibling (data->node, newref);
306 node = repr_to_child (data->node, child);
308 if ( before == node ) before = GTK_CTREE_ROW (before)->sibling;
310 parent_class->tree_move (GTK_CTREE (data->tree), node, data->node, before);
311 }
313 void
314 text_content_changed (Inkscape::XML::Node * repr, const gchar * old_content, const gchar * new_content, gpointer ptr)
315 {
316 NodeData *data;
317 gchar *label;
319 data = (NodeData *) ptr;
321 if (data->tree->blocked) return;
323 label = g_strdup_printf ("\"%s\"", new_content);
324 gtk_ctree_node_set_text (GTK_CTREE (data->tree), data->node, 0, label);
325 g_free (label);
326 }
328 void
329 comment_content_changed (Inkscape::XML::Node *repr, const gchar * old_content, const gchar *new_content, gpointer ptr)
330 {
331 NodeData *data;
332 gchar *label;
334 data = (NodeData *) ptr;
336 if (data->tree->blocked) return;
338 label = g_strdup_printf ("<!--%s-->", new_content);
339 gtk_ctree_node_set_text (GTK_CTREE (data->tree), data->node, 0, label);
340 g_free (label);
341 }
343 void
344 pi_content_changed(Inkscape::XML::Node *repr, const gchar * old_content, const gchar *new_content, gpointer ptr)
345 {
346 NodeData *data;
347 gchar *label;
349 data = (NodeData *) ptr;
351 if (data->tree->blocked) return;
353 label = g_strdup_printf ("<?%s %s?>", repr->name(), new_content);
354 gtk_ctree_node_set_text (GTK_CTREE (data->tree), data->node, 0, label);
355 g_free (label);
356 }
357 void
358 tree_move (GtkCTree * tree, GtkCTreeNode * node, GtkCTreeNode * new_parent, GtkCTreeNode * new_sibling)
359 {
360 GtkCTreeNode * old_parent;
361 Inkscape::XML::Node * ref;
363 old_parent = GTK_CTREE_ROW (node)->parent;
364 if ( !old_parent || !new_parent ) return;
366 ref = sibling_to_ref (new_parent, new_sibling);
368 gtk_clist_freeze (GTK_CLIST (tree));
370 SP_XMLVIEW_TREE (tree)->blocked++;
371 if (new_parent == old_parent) {
372 NODE_DATA (old_parent)->repr->changeOrder(NODE_DATA (node)->repr, ref);
373 } else {
374 NODE_DATA (old_parent)->repr->removeChild(NODE_DATA (node)->repr);
375 NODE_DATA (new_parent)->repr->addChild(NODE_DATA (node)->repr, ref);
376 }
377 SP_XMLVIEW_TREE (tree)->blocked--;
379 parent_class->tree_move (tree, node, new_parent, new_sibling);
381 gtk_clist_thaw (GTK_CLIST (tree));
382 }
384 GtkCTreeNode *
385 ref_to_sibling (GtkCTreeNode * parent, Inkscape::XML::Node * ref)
386 {
387 if (ref) {
388 GtkCTreeNode * before;
389 before = repr_to_child (parent, ref);
390 g_assert (before != NULL);
391 before = GTK_CTREE_ROW (before)->sibling;
392 return before;
393 } else {
394 return GTK_CTREE_ROW (parent)->children;
395 }
396 }
398 GtkCTreeNode *
399 repr_to_child (GtkCTreeNode * parent, Inkscape::XML::Node * repr)
400 {
401 GtkCTreeNode * child;
402 child = GTK_CTREE_ROW (parent)->children;
403 while ( child && NODE_DATA (child)->repr != repr ) {
404 child = GTK_CTREE_ROW (child)->sibling;
405 }
406 return child;
407 }
409 Inkscape::XML::Node *
410 sibling_to_ref (GtkCTreeNode * parent, GtkCTreeNode * sibling)
411 {
412 GtkCTreeNode * child;
413 child = GTK_CTREE_ROW (parent)->children;
414 if ( child == sibling ) return NULL;
415 while ( child && GTK_CTREE_ROW (child)->sibling != sibling ) {
416 child = GTK_CTREE_ROW (child)->sibling;
417 }
418 return NODE_DATA (child)->repr;
419 }
421 gboolean
422 check_drag (GtkCTree * tree, GtkCTreeNode * node, GtkCTreeNode * new_parent, GtkCTreeNode * new_sibling)
423 {
424 GtkCTreeNode * old_parent;
426 old_parent = GTK_CTREE_ROW (node)->parent;
428 if (!old_parent || !new_parent) return FALSE;
429 if (NODE_DATA (new_parent)->repr->type() != Inkscape::XML::ELEMENT_NODE) return FALSE;
431 /* fixme: we need add_child/remove_child/etc repr events without side-effects, so we can check here and give better visual feedback */
433 return TRUE;
434 }
436 Inkscape::XML::Node *
437 sp_xmlview_tree_node_get_repr (SPXMLViewTree * tree, GtkCTreeNode * node)
438 {
439 return NODE_DATA (node)->repr;
440 }
442 GtkCTreeNode *
443 sp_xmlview_tree_get_repr_node (SPXMLViewTree * tree, Inkscape::XML::Node * repr)
444 {
445 return gtk_ctree_find_by_row_data_custom (GTK_CTREE (tree), NULL, repr, match_node_data_by_repr);
446 }
448 gint
449 match_node_data_by_repr(gconstpointer data_p, gconstpointer repr)
450 {
451 return ((const NodeData *)data_p)->repr != (const Inkscape::XML::Node *)repr;
452 }