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 static gboolean check_drag (GtkCTree * tree, GtkCTreeNode * node, GtkCTreeNode * new_parent, GtkCTreeNode * new_sibling);
46 static GtkCTreeNode * ref_to_sibling (GtkCTreeNode * parent, Inkscape::XML::Node * ref);
47 static GtkCTreeNode * repr_to_child (GtkCTreeNode * parent, Inkscape::XML::Node * repr);
48 static Inkscape::XML::Node * sibling_to_ref (GtkCTreeNode * parent, GtkCTreeNode * sibling);
50 static gint match_node_data_by_repr(gconstpointer data_p, gconstpointer repr);
52 static const Inkscape::XML::NodeEventVector element_repr_events = {
53 element_child_added,
54 element_child_removed,
55 element_attr_changed,
56 NULL, /* content_changed */
57 element_order_changed
58 };
60 static const Inkscape::XML::NodeEventVector text_repr_events = {
61 NULL, /* child_added */
62 NULL, /* child_removed */
63 NULL, /* attr_changed */
64 text_content_changed,
65 NULL /* order_changed */
66 };
68 static const Inkscape::XML::NodeEventVector comment_repr_events = {
69 NULL, /* child_added */
70 NULL, /* child_removed */
71 NULL, /* attr_changed */
72 comment_content_changed,
73 NULL /* order_changed */
74 };
76 static GtkCTreeClass * parent_class = NULL;
78 GtkWidget *
79 sp_xmlview_tree_new (Inkscape::XML::Node * repr, void * factory, void * data)
80 {
81 SPXMLViewTree * tree;
83 tree = (SPXMLViewTree*)g_object_new (SP_TYPE_XMLVIEW_TREE, "n_columns", 1, "tree_column", 0, NULL);
85 gtk_clist_column_titles_hide (GTK_CLIST (tree));
86 gtk_ctree_set_line_style (GTK_CTREE (tree), GTK_CTREE_LINES_NONE);
87 gtk_ctree_set_expander_style (GTK_CTREE (tree), GTK_CTREE_EXPANDER_TRIANGLE);
88 gtk_clist_set_column_auto_resize (GTK_CLIST (tree), 0, TRUE);
89 gtk_clist_set_reorderable (GTK_CLIST (tree), TRUE);
90 gtk_ctree_set_drag_compare_func (GTK_CTREE (tree), check_drag);
92 sp_xmlview_tree_set_repr (tree, repr);
94 return (GtkWidget *) tree;
95 }
97 void
98 sp_xmlview_tree_set_repr (SPXMLViewTree * tree, Inkscape::XML::Node * repr)
99 {
100 if ( tree->repr == repr ) return;
101 gtk_clist_freeze (GTK_CLIST (tree));
102 if (tree->repr) {
103 gtk_clist_clear (GTK_CLIST (tree));
104 Inkscape::GC::release(tree->repr);
105 }
106 tree->repr = repr;
107 if (repr) {
108 GtkCTreeNode * node;
109 Inkscape::GC::anchor(repr);
110 node = add_node (tree, NULL, NULL, repr);
111 gtk_ctree_expand (GTK_CTREE (tree), node);
112 }
113 gtk_clist_thaw (GTK_CLIST (tree));
114 }
116 GtkType
117 sp_xmlview_tree_get_type (void)
118 {
119 static GtkType type = 0;
121 if (!type) {
122 static const GtkTypeInfo info = {
123 "SPXMLViewTree",
124 sizeof (SPXMLViewTree),
125 sizeof (SPXMLViewTreeClass),
126 (GtkClassInitFunc) sp_xmlview_tree_class_init,
127 (GtkObjectInitFunc) sp_xmlview_tree_init,
128 NULL, NULL, NULL
129 };
130 type = gtk_type_unique (GTK_TYPE_CTREE, &info);
131 }
133 return type;
134 }
136 void
137 sp_xmlview_tree_class_init (SPXMLViewTreeClass * klass)
138 {
139 GtkObjectClass * object_class;
141 object_class = (GtkObjectClass *) klass;
142 parent_class = (GtkCTreeClass *) gtk_type_class (GTK_TYPE_CTREE);
144 GTK_CTREE_CLASS (object_class)->tree_move = tree_move;
146 object_class->destroy = sp_xmlview_tree_destroy;
147 }
149 void
150 sp_xmlview_tree_init (SPXMLViewTree * tree)
151 {
152 tree->repr = NULL;
153 tree->blocked = 0;
154 }
156 void
157 sp_xmlview_tree_destroy (GtkObject * object)
158 {
159 SPXMLViewTree * tree;
161 tree = SP_XMLVIEW_TREE (object);
163 sp_xmlview_tree_set_repr (tree, NULL);
165 GTK_OBJECT_CLASS (parent_class)->destroy (object);
166 }
168 GtkCTreeNode *
169 add_node (SPXMLViewTree * tree, GtkCTreeNode * parent, GtkCTreeNode * before, Inkscape::XML::Node * repr)
170 {
171 NodeData * data;
172 GtkCTreeNode * node;
173 const Inkscape::XML::NodeEventVector * vec;
174 static const gchar *default_text[] = { "???" };
176 g_assert (tree != NULL);
177 g_assert (repr != NULL);
179 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);
180 g_assert (node != NULL);
182 data = node_data_new (tree, node, repr);
183 g_assert (data != NULL);
185 gtk_ctree_node_set_row_data_full (GTK_CTREE (tree), data->node, data, node_data_free);
187 if ( repr->type() == Inkscape::XML::TEXT_NODE ) {
188 vec = &text_repr_events;
189 } else if ( repr->type() == Inkscape::XML::COMMENT_NODE ) {
190 vec = &comment_repr_events;
191 } else if ( repr->type() == Inkscape::XML::ELEMENT_NODE ) {
192 vec = &element_repr_events;
193 } else {
194 vec = NULL;
195 }
197 if (vec) {
198 gtk_clist_freeze (GTK_CLIST (tree));
199 /* cheat a little to get the id upated properly */
200 if (repr->type() == Inkscape::XML::ELEMENT_NODE) {
201 element_attr_changed (repr, "id", NULL, NULL, false, data);
202 }
203 sp_repr_add_listener (repr, vec, data);
204 sp_repr_synthesize_events (repr, vec, data);
205 gtk_clist_thaw (GTK_CLIST (tree));
206 }
208 return node;
209 }
211 NodeData *
212 node_data_new (SPXMLViewTree * tree, GtkCTreeNode * node, Inkscape::XML::Node * repr)
213 {
214 NodeData * data;
215 data = g_new (NodeData, 1);
216 data->tree = tree;
217 data->node = node;
218 data->repr = repr;
219 Inkscape::GC::anchor(repr);
220 return data;
221 }
223 void
224 node_data_free (gpointer ptr) {
225 NodeData * data;
226 data = (NodeData *) ptr;
227 sp_repr_remove_listener_by_data (data->repr, data);
228 g_assert (data->repr != NULL);
229 Inkscape::GC::release(data->repr);
230 g_free (data);
231 }
233 void
234 element_child_added (Inkscape::XML::Node * repr, Inkscape::XML::Node * child, Inkscape::XML::Node * ref, gpointer ptr)
235 {
236 NodeData * data;
237 GtkCTreeNode * before;
239 data = (NodeData *) ptr;
241 if (data->tree->blocked) return;
243 before = ref_to_sibling (data->node, ref);
245 add_node (data->tree, data->node, before, child);
246 }
248 void
249 element_attr_changed (Inkscape::XML::Node * repr, const gchar * key, const gchar * old_value, const gchar * new_value, bool is_interactive, gpointer ptr)
250 {
251 NodeData * data;
252 gchar *label;
254 data = (NodeData *) ptr;
256 if (data->tree->blocked) return;
258 if (strcmp (key, "id")) return;
260 if (new_value) {
261 label = g_strdup_printf ("<%s id=\"%s\">", repr->name(), new_value);
262 } else {
263 label = g_strdup_printf ("<%s>", repr->name());
264 }
265 gtk_ctree_node_set_text (GTK_CTREE (data->tree), data->node, 0, label);
266 g_free (label);
267 }
269 void
270 element_child_removed (Inkscape::XML::Node * repr, Inkscape::XML::Node * child, Inkscape::XML::Node * ref, gpointer ptr)
271 {
272 NodeData * data;
274 data = (NodeData *) ptr;
276 if (data->tree->blocked) return;
278 gtk_ctree_remove_node (GTK_CTREE (data->tree), repr_to_child (data->node, child));
279 }
281 void
282 element_order_changed (Inkscape::XML::Node * repr, Inkscape::XML::Node * child, Inkscape::XML::Node * oldref, Inkscape::XML::Node * newref, gpointer ptr)
283 {
284 NodeData * data;
285 GtkCTreeNode * before, * node;
287 data = (NodeData *) ptr;
289 if (data->tree->blocked) return;
291 before = ref_to_sibling (data->node, newref);
292 node = repr_to_child (data->node, child);
294 if ( before == node ) before = GTK_CTREE_ROW (before)->sibling;
296 parent_class->tree_move (GTK_CTREE (data->tree), node, data->node, before);
297 }
299 void
300 text_content_changed (Inkscape::XML::Node * repr, const gchar * old_content, const gchar * new_content, gpointer ptr)
301 {
302 NodeData *data;
303 gchar *label;
305 data = (NodeData *) ptr;
307 if (data->tree->blocked) return;
309 label = g_strdup_printf ("\"%s\"", new_content);
310 gtk_ctree_node_set_text (GTK_CTREE (data->tree), data->node, 0, label);
311 g_free (label);
312 }
314 void
315 comment_content_changed (Inkscape::XML::Node *repr, const gchar * old_content, const gchar *new_content, gpointer ptr)
316 {
317 NodeData *data;
318 gchar *label;
320 data = (NodeData *) ptr;
322 if (data->tree->blocked) return;
324 label = g_strdup_printf ("<!--%s-->", new_content);
325 gtk_ctree_node_set_text (GTK_CTREE (data->tree), data->node, 0, label);
326 g_free (label);
327 }
329 void
330 tree_move (GtkCTree * tree, GtkCTreeNode * node, GtkCTreeNode * new_parent, GtkCTreeNode * new_sibling)
331 {
332 GtkCTreeNode * old_parent;
333 Inkscape::XML::Node * ref;
335 old_parent = GTK_CTREE_ROW (node)->parent;
336 if ( !old_parent || !new_parent ) return;
338 ref = sibling_to_ref (new_parent, new_sibling);
340 gtk_clist_freeze (GTK_CLIST (tree));
342 SP_XMLVIEW_TREE (tree)->blocked++;
343 if (new_parent == old_parent) {
344 NODE_DATA (old_parent)->repr->changeOrder(NODE_DATA (node)->repr, ref);
345 } else {
346 NODE_DATA (old_parent)->repr->removeChild(NODE_DATA (node)->repr);
347 NODE_DATA (new_parent)->repr->addChild(NODE_DATA (node)->repr, ref);
348 }
349 SP_XMLVIEW_TREE (tree)->blocked--;
351 parent_class->tree_move (tree, node, new_parent, new_sibling);
353 gtk_clist_thaw (GTK_CLIST (tree));
354 }
356 GtkCTreeNode *
357 ref_to_sibling (GtkCTreeNode * parent, Inkscape::XML::Node * ref)
358 {
359 if (ref) {
360 GtkCTreeNode * before;
361 before = repr_to_child (parent, ref);
362 g_assert (before != NULL);
363 before = GTK_CTREE_ROW (before)->sibling;
364 return before;
365 } else {
366 return GTK_CTREE_ROW (parent)->children;
367 }
368 }
370 GtkCTreeNode *
371 repr_to_child (GtkCTreeNode * parent, Inkscape::XML::Node * repr)
372 {
373 GtkCTreeNode * child;
374 child = GTK_CTREE_ROW (parent)->children;
375 while ( child && NODE_DATA (child)->repr != repr ) {
376 child = GTK_CTREE_ROW (child)->sibling;
377 }
378 return child;
379 }
381 Inkscape::XML::Node *
382 sibling_to_ref (GtkCTreeNode * parent, GtkCTreeNode * sibling)
383 {
384 GtkCTreeNode * child;
385 child = GTK_CTREE_ROW (parent)->children;
386 if ( child == sibling ) return NULL;
387 while ( child && GTK_CTREE_ROW (child)->sibling != sibling ) {
388 child = GTK_CTREE_ROW (child)->sibling;
389 }
390 return NODE_DATA (child)->repr;
391 }
393 gboolean
394 check_drag (GtkCTree * tree, GtkCTreeNode * node, GtkCTreeNode * new_parent, GtkCTreeNode * new_sibling)
395 {
396 GtkCTreeNode * old_parent;
398 old_parent = GTK_CTREE_ROW (node)->parent;
400 if (!old_parent || !new_parent) return FALSE;
401 if (NODE_DATA (new_parent)->repr->type() != Inkscape::XML::ELEMENT_NODE) return FALSE;
403 /* fixme: we need add_child/remove_child/etc repr events without side-effects, so we can check here and give better visual feedback */
405 return TRUE;
406 }
408 Inkscape::XML::Node *
409 sp_xmlview_tree_node_get_repr (SPXMLViewTree * tree, GtkCTreeNode * node)
410 {
411 return NODE_DATA (node)->repr;
412 }
414 GtkCTreeNode *
415 sp_xmlview_tree_get_repr_node (SPXMLViewTree * tree, Inkscape::XML::Node * repr)
416 {
417 return gtk_ctree_find_by_row_data_custom (GTK_CTREE (tree), NULL, repr, match_node_data_by_repr);
418 }
420 gint
421 match_node_data_by_repr(gconstpointer data_p, gconstpointer repr)
422 {
423 return ((const NodeData *)data_p)->repr != (const Inkscape::XML::Node *)repr;
424 }