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 "../xml/node-event-vector.h"
15 #include "sp-xmlview-tree.h"
17 struct NodeData {
18 SPXMLViewTree * tree;
19 GtkCTreeNode * node;
20 Inkscape::XML::Node * repr;
21 };
23 #define NODE_DATA(node) ((NodeData *)(GTK_CTREE_ROW ((node))->row.data))
25 static void sp_xmlview_tree_class_init (SPXMLViewTreeClass * klass);
26 static void sp_xmlview_tree_init (SPXMLViewTree * tree);
27 static void sp_xmlview_tree_destroy (GtkObject * object);
29 static NodeData * node_data_new (SPXMLViewTree * tree, GtkCTreeNode * node, Inkscape::XML::Node * repr);
30 static void node_data_free (gpointer data);
32 static GtkCTreeNode * add_node (SPXMLViewTree * tree, GtkCTreeNode * parent, GtkCTreeNode * before, Inkscape::XML::Node * repr);
34 static void element_child_added (Inkscape::XML::Node * repr, Inkscape::XML::Node * child, Inkscape::XML::Node * ref, gpointer data);
35 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);
36 static void element_child_removed (Inkscape::XML::Node * repr, Inkscape::XML::Node * child, Inkscape::XML::Node * ref, gpointer data);
37 static void element_order_changed (Inkscape::XML::Node * repr, Inkscape::XML::Node * child, Inkscape::XML::Node * oldref, Inkscape::XML::Node * newref, gpointer data);
39 static void text_content_changed (Inkscape::XML::Node * repr, const gchar * old_content, const gchar * new_content, gpointer data);
40 static void comment_content_changed (Inkscape::XML::Node * repr, const gchar * old_content, const gchar * new_content, gpointer data);
42 static void tree_move (GtkCTree * tree, GtkCTreeNode * node, GtkCTreeNode * new_parent, GtkCTreeNode * new_sibling);
44 // JON: Must keep this as gboolean legacy
45 // TODO: convert to use bool
46 static gboolean check_drag (GtkCTree * tree, GtkCTreeNode * node, GtkCTreeNode * new_parent, GtkCTreeNode * new_sibling);
48 static GtkCTreeNode * ref_to_sibling (GtkCTreeNode * parent, Inkscape::XML::Node * ref);
49 static GtkCTreeNode * repr_to_child (GtkCTreeNode * parent, Inkscape::XML::Node * repr);
50 static Inkscape::XML::Node * sibling_to_ref (GtkCTreeNode * parent, GtkCTreeNode * sibling);
52 static gint match_node_data_by_repr(gconstpointer data_p, gconstpointer repr);
54 static const Inkscape::XML::NodeEventVector element_repr_events = {
55 element_child_added,
56 element_child_removed,
57 element_attr_changed,
58 NULL, /* content_changed */
59 element_order_changed
60 };
62 static const Inkscape::XML::NodeEventVector text_repr_events = {
63 NULL, /* child_added */
64 NULL, /* child_removed */
65 NULL, /* attr_changed */
66 text_content_changed,
67 NULL /* order_changed */
68 };
70 static const Inkscape::XML::NodeEventVector comment_repr_events = {
71 NULL, /* child_added */
72 NULL, /* child_removed */
73 NULL, /* attr_changed */
74 comment_content_changed,
75 NULL /* order_changed */
76 };
78 static GtkCTreeClass * parent_class = NULL;
80 GtkWidget *
81 sp_xmlview_tree_new (Inkscape::XML::Node * repr, void * factory, void * data)
82 {
83 SPXMLViewTree * tree;
85 tree = (SPXMLViewTree*)g_object_new (SP_TYPE_XMLVIEW_TREE, "n_columns", 1, "tree_column", 0, NULL);
87 gtk_clist_column_titles_hide (GTK_CLIST (tree));
88 gtk_ctree_set_line_style (GTK_CTREE (tree), GTK_CTREE_LINES_NONE);
89 gtk_ctree_set_expander_style (GTK_CTREE (tree), GTK_CTREE_EXPANDER_TRIANGLE);
90 gtk_clist_set_column_auto_resize (GTK_CLIST (tree), 0, TRUE);
91 gtk_clist_set_reorderable (GTK_CLIST (tree), TRUE);
92 gtk_ctree_set_drag_compare_func (GTK_CTREE (tree), check_drag);
94 sp_xmlview_tree_set_repr (tree, repr);
96 return (GtkWidget *) tree;
97 }
99 void
100 sp_xmlview_tree_set_repr (SPXMLViewTree * tree, Inkscape::XML::Node * repr)
101 {
102 if ( tree->repr == repr ) return;
103 gtk_clist_freeze (GTK_CLIST (tree));
104 if (tree->repr) {
105 gtk_clist_clear (GTK_CLIST (tree));
106 Inkscape::GC::release(tree->repr);
107 }
108 tree->repr = repr;
109 if (repr) {
110 GtkCTreeNode * node;
111 Inkscape::GC::anchor(repr);
112 node = add_node (tree, NULL, NULL, repr);
113 gtk_ctree_expand (GTK_CTREE (tree), node);
114 }
115 gtk_clist_thaw (GTK_CLIST (tree));
116 }
118 GtkType
119 sp_xmlview_tree_get_type (void)
120 {
121 static GtkType type = 0;
123 if (!type) {
124 static const GtkTypeInfo info = {
125 "SPXMLViewTree",
126 sizeof (SPXMLViewTree),
127 sizeof (SPXMLViewTreeClass),
128 (GtkClassInitFunc) sp_xmlview_tree_class_init,
129 (GtkObjectInitFunc) sp_xmlview_tree_init,
130 NULL, NULL, NULL
131 };
132 type = gtk_type_unique (GTK_TYPE_CTREE, &info);
133 }
135 return type;
136 }
138 void
139 sp_xmlview_tree_class_init (SPXMLViewTreeClass * klass)
140 {
141 GtkObjectClass * object_class;
143 object_class = (GtkObjectClass *) klass;
144 parent_class = (GtkCTreeClass *) gtk_type_class (GTK_TYPE_CTREE);
146 GTK_CTREE_CLASS (object_class)->tree_move = tree_move;
148 object_class->destroy = sp_xmlview_tree_destroy;
149 }
151 void
152 sp_xmlview_tree_init (SPXMLViewTree * tree)
153 {
154 tree->repr = NULL;
155 tree->blocked = 0;
156 }
158 void
159 sp_xmlview_tree_destroy (GtkObject * object)
160 {
161 SPXMLViewTree * tree;
163 tree = SP_XMLVIEW_TREE (object);
165 sp_xmlview_tree_set_repr (tree, NULL);
167 GTK_OBJECT_CLASS (parent_class)->destroy (object);
168 }
170 GtkCTreeNode *
171 add_node (SPXMLViewTree * tree, GtkCTreeNode * parent, GtkCTreeNode * before, Inkscape::XML::Node * repr)
172 {
173 NodeData * data;
174 GtkCTreeNode * node;
175 const Inkscape::XML::NodeEventVector * vec;
176 static const gchar *default_text[] = { "???" };
178 g_assert (tree != NULL);
179 g_assert (repr != NULL);
181 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);
182 g_assert (node != NULL);
184 data = node_data_new (tree, node, repr);
185 g_assert (data != NULL);
187 gtk_ctree_node_set_row_data_full (GTK_CTREE (tree), data->node, data, node_data_free);
189 if ( repr->type() == Inkscape::XML::TEXT_NODE ) {
190 vec = &text_repr_events;
191 } else if ( repr->type() == Inkscape::XML::COMMENT_NODE ) {
192 vec = &comment_repr_events;
193 } else if ( repr->type() == Inkscape::XML::ELEMENT_NODE ) {
194 vec = &element_repr_events;
195 } else {
196 vec = NULL;
197 }
199 if (vec) {
200 gtk_clist_freeze (GTK_CLIST (tree));
201 /* cheat a little to get the id upated properly */
202 if (repr->type() == Inkscape::XML::ELEMENT_NODE) {
203 element_attr_changed (repr, "id", NULL, NULL, false, data);
204 }
205 sp_repr_add_listener (repr, vec, data);
206 sp_repr_synthesize_events (repr, vec, data);
207 gtk_clist_thaw (GTK_CLIST (tree));
208 }
210 return node;
211 }
213 NodeData *
214 node_data_new (SPXMLViewTree * tree, GtkCTreeNode * node, Inkscape::XML::Node * repr)
215 {
216 NodeData * data;
217 data = g_new (NodeData, 1);
218 data->tree = tree;
219 data->node = node;
220 data->repr = repr;
221 Inkscape::GC::anchor(repr);
222 return data;
223 }
225 void
226 node_data_free (gpointer ptr) {
227 NodeData * data;
228 data = (NodeData *) ptr;
229 sp_repr_remove_listener_by_data (data->repr, data);
230 g_assert (data->repr != NULL);
231 Inkscape::GC::release(data->repr);
232 g_free (data);
233 }
235 void
236 element_child_added (Inkscape::XML::Node * repr, Inkscape::XML::Node * child, Inkscape::XML::Node * ref, gpointer ptr)
237 {
238 NodeData * data;
239 GtkCTreeNode * before;
241 data = (NodeData *) ptr;
243 if (data->tree->blocked) return;
245 before = ref_to_sibling (data->node, ref);
247 add_node (data->tree, data->node, before, child);
248 }
250 void
251 element_attr_changed (Inkscape::XML::Node * repr, const gchar * key, const gchar * old_value, const gchar * new_value, bool is_interactive, gpointer ptr)
252 {
253 NodeData * data;
254 gchar *label;
256 data = (NodeData *) ptr;
258 if (data->tree->blocked) return;
260 if (strcmp (key, "id")) return;
262 if (new_value) {
263 label = g_strdup_printf ("<%s id=\"%s\">", repr->name(), new_value);
264 } else {
265 label = g_strdup_printf ("<%s>", repr->name());
266 }
267 gtk_ctree_node_set_text (GTK_CTREE (data->tree), data->node, 0, label);
268 g_free (label);
269 }
271 void
272 element_child_removed (Inkscape::XML::Node * repr, Inkscape::XML::Node * child, Inkscape::XML::Node * ref, gpointer ptr)
273 {
274 NodeData * data;
276 data = (NodeData *) ptr;
278 if (data->tree->blocked) return;
280 gtk_ctree_remove_node (GTK_CTREE (data->tree), repr_to_child (data->node, child));
281 }
283 void
284 element_order_changed (Inkscape::XML::Node * repr, Inkscape::XML::Node * child, Inkscape::XML::Node * oldref, Inkscape::XML::Node * newref, gpointer ptr)
285 {
286 NodeData * data;
287 GtkCTreeNode * before, * node;
289 data = (NodeData *) ptr;
291 if (data->tree->blocked) return;
293 before = ref_to_sibling (data->node, newref);
294 node = repr_to_child (data->node, child);
296 if ( before == node ) before = GTK_CTREE_ROW (before)->sibling;
298 parent_class->tree_move (GTK_CTREE (data->tree), node, data->node, before);
299 }
301 void
302 text_content_changed (Inkscape::XML::Node * repr, const gchar * old_content, const gchar * new_content, gpointer ptr)
303 {
304 NodeData *data;
305 gchar *label;
307 data = (NodeData *) ptr;
309 if (data->tree->blocked) return;
311 label = g_strdup_printf ("\"%s\"", new_content);
312 gtk_ctree_node_set_text (GTK_CTREE (data->tree), data->node, 0, label);
313 g_free (label);
314 }
316 void
317 comment_content_changed (Inkscape::XML::Node *repr, const gchar * old_content, const gchar *new_content, gpointer ptr)
318 {
319 NodeData *data;
320 gchar *label;
322 data = (NodeData *) ptr;
324 if (data->tree->blocked) return;
326 label = g_strdup_printf ("<!--%s-->", new_content);
327 gtk_ctree_node_set_text (GTK_CTREE (data->tree), data->node, 0, label);
328 g_free (label);
329 }
331 void
332 tree_move (GtkCTree * tree, GtkCTreeNode * node, GtkCTreeNode * new_parent, GtkCTreeNode * new_sibling)
333 {
334 GtkCTreeNode * old_parent;
335 Inkscape::XML::Node * ref;
337 old_parent = GTK_CTREE_ROW (node)->parent;
338 if ( !old_parent || !new_parent ) return;
340 ref = sibling_to_ref (new_parent, new_sibling);
342 gtk_clist_freeze (GTK_CLIST (tree));
344 SP_XMLVIEW_TREE (tree)->blocked++;
345 if (new_parent == old_parent) {
346 NODE_DATA (old_parent)->repr->changeOrder(NODE_DATA (node)->repr, ref);
347 } else {
348 NODE_DATA (old_parent)->repr->removeChild(NODE_DATA (node)->repr);
349 NODE_DATA (new_parent)->repr->addChild(NODE_DATA (node)->repr, ref);
350 }
351 SP_XMLVIEW_TREE (tree)->blocked--;
353 parent_class->tree_move (tree, node, new_parent, new_sibling);
355 gtk_clist_thaw (GTK_CLIST (tree));
356 }
358 GtkCTreeNode *
359 ref_to_sibling (GtkCTreeNode * parent, Inkscape::XML::Node * ref)
360 {
361 if (ref) {
362 GtkCTreeNode * before;
363 before = repr_to_child (parent, ref);
364 g_assert (before != NULL);
365 before = GTK_CTREE_ROW (before)->sibling;
366 return before;
367 } else {
368 return GTK_CTREE_ROW (parent)->children;
369 }
370 }
372 GtkCTreeNode *
373 repr_to_child (GtkCTreeNode * parent, Inkscape::XML::Node * repr)
374 {
375 GtkCTreeNode * child;
376 child = GTK_CTREE_ROW (parent)->children;
377 while ( child && NODE_DATA (child)->repr != repr ) {
378 child = GTK_CTREE_ROW (child)->sibling;
379 }
380 return child;
381 }
383 Inkscape::XML::Node *
384 sibling_to_ref (GtkCTreeNode * parent, GtkCTreeNode * sibling)
385 {
386 GtkCTreeNode * child;
387 child = GTK_CTREE_ROW (parent)->children;
388 if ( child == sibling ) return NULL;
389 while ( child && GTK_CTREE_ROW (child)->sibling != sibling ) {
390 child = GTK_CTREE_ROW (child)->sibling;
391 }
392 return NODE_DATA (child)->repr;
393 }
395 gboolean
396 check_drag (GtkCTree * tree, GtkCTreeNode * node, GtkCTreeNode * new_parent, GtkCTreeNode * new_sibling)
397 {
398 GtkCTreeNode * old_parent;
400 old_parent = GTK_CTREE_ROW (node)->parent;
402 if (!old_parent || !new_parent) return FALSE;
403 if (NODE_DATA (new_parent)->repr->type() != Inkscape::XML::ELEMENT_NODE) return FALSE;
405 /* fixme: we need add_child/remove_child/etc repr events without side-effects, so we can check here and give better visual feedback */
407 return TRUE;
408 }
410 Inkscape::XML::Node *
411 sp_xmlview_tree_node_get_repr (SPXMLViewTree * tree, GtkCTreeNode * node)
412 {
413 return NODE_DATA (node)->repr;
414 }
416 GtkCTreeNode *
417 sp_xmlview_tree_get_repr_node (SPXMLViewTree * tree, Inkscape::XML::Node * repr)
418 {
419 return gtk_ctree_find_by_row_data_custom (GTK_CTREE (tree), NULL, repr, match_node_data_by_repr);
420 }
422 gint
423 match_node_data_by_repr(gconstpointer data_p, gconstpointer repr)
424 {
425 return ((const NodeData *)data_p)->repr != (const Inkscape::XML::Node *)repr;
426 }