1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /**
3  * Specialization of GtkTreeView for the XML tree view
4  *
5  * Authors:
6  *   MenTaLguY <mental@rydia.net>
7  *
8  * Copyright (C) 2002 MenTaLguY
9  *
10  * Released under GNU GPL v2+, read the file 'COPYING' for more information.
11  */
12 
13 #include <cstring>
14 #include <gmodule.h>
15 
16 #include "xml/node-event-vector.h"
17 #include "sp-xmlview-tree.h"
18 
19 namespace {
20 struct NodeData {
21 	SPXMLViewTree * tree;
22 	GtkTreeRowReference  *rowref;
23 	Inkscape::XML::Node * repr;
24     bool expanded = false; //< true if tree view has been expanded to this node
25     bool dragging = false;
26 
27     NodeData(SPXMLViewTree *tree, GtkTreeIter *node, Inkscape::XML::Node *repr);
28     ~NodeData();
29 };
30 
31 // currently dragged node
32 Inkscape::XML::Node *dragging_repr = nullptr;
33 } // namespace
34 
35 enum { STORE_TEXT_COL = 0, STORE_DATA_COL, STORE_N_COLS };
36 
37 static void sp_xmlview_tree_destroy(GtkWidget * object);
38 
39 static NodeData *sp_xmlview_tree_node_get_data(GtkTreeModel *model, GtkTreeIter *iter);
40 
41 static void add_node(SPXMLViewTree *tree, GtkTreeIter *parent, GtkTreeIter *before, Inkscape::XML::Node *repr);
42 
43 static void element_child_added (Inkscape::XML::Node * repr, Inkscape::XML::Node * child, Inkscape::XML::Node * ref, gpointer data);
44 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);
45 static void element_child_removed (Inkscape::XML::Node * repr, Inkscape::XML::Node * child, Inkscape::XML::Node * ref, gpointer data);
46 static void element_order_changed (Inkscape::XML::Node * repr, Inkscape::XML::Node * child, Inkscape::XML::Node * oldref, Inkscape::XML::Node * newref, gpointer data);
47 static void element_name_changed (Inkscape::XML::Node* repr, gchar const* oldname, gchar const* newname, gpointer data);
48 static void element_attr_or_name_change_update(Inkscape::XML::Node* repr, NodeData* data);
49 
50 static void text_content_changed (Inkscape::XML::Node * repr, const gchar * old_content, const gchar * new_content, gpointer data);
51 static void comment_content_changed (Inkscape::XML::Node * repr, const gchar * old_content, const gchar * new_content, gpointer data);
52 static void pi_content_changed (Inkscape::XML::Node * repr, const gchar * old_content, const gchar * new_content, gpointer data);
53 
54 static gboolean ref_to_sibling (NodeData *node, Inkscape::XML::Node * ref, GtkTreeIter *);
55 static gboolean repr_to_child (NodeData *node, Inkscape::XML::Node * repr, GtkTreeIter *);
56 static GtkTreeRowReference *tree_iter_to_ref(SPXMLViewTree *, GtkTreeIter *);
57 static gboolean tree_ref_to_iter (SPXMLViewTree * tree, GtkTreeIter* iter, GtkTreeRowReference  *ref);
58 
59 static gboolean search_equal_func(GtkTreeModel *, gint column, const gchar *key, GtkTreeIter *, gpointer search_data);
60 static gboolean foreach_func(GtkTreeModel *, GtkTreePath *, GtkTreeIter *, gpointer user_data);
61 
62 static void on_row_changed(GtkTreeModel *, GtkTreePath *, GtkTreeIter *, gpointer user_data);
63 static void on_drag_begin(GtkWidget *, GdkDragContext *, gpointer userdata);
64 static void on_drag_end(GtkWidget *, GdkDragContext *, gpointer userdata);
65 static gboolean do_drag_motion(GtkWidget *, GdkDragContext *, gint x, gint y, guint time, gpointer user_data);
66 
67 static const Inkscape::XML::NodeEventVector element_repr_events = {
68         element_child_added,
69         element_child_removed,
70         element_attr_changed,
71         nullptr, /* content_changed */
72         element_order_changed,
73         element_name_changed
74 };
75 
76 static const Inkscape::XML::NodeEventVector text_repr_events = {
77         nullptr, /* child_added */
78         nullptr, /* child_removed */
79         nullptr, /* attr_changed */
80         text_content_changed,
81         nullptr  /* order_changed */,
82         nullptr  /* element_name_changed */
83 };
84 
85 static const Inkscape::XML::NodeEventVector comment_repr_events = {
86         nullptr, /* child_added */
87         nullptr, /* child_removed */
88         nullptr, /* attr_changed */
89         comment_content_changed,
90         nullptr  /* order_changed */,
91         nullptr  /* element_name_changed */
92 };
93 
94 static const Inkscape::XML::NodeEventVector pi_repr_events = {
95         nullptr, /* child_added */
96         nullptr, /* child_removed */
97         nullptr, /* attr_changed */
98         pi_content_changed,
99         nullptr  /* order_changed */,
100         nullptr  /* element_name_changed */
101 };
102 
103 /**
104  * Get an iterator to the first child of `data`
105  * @param data handle which references a row
106  * @param[out] child_iter On success: valid iterator to first child
107  * @return False if the node has no children
108  */
get_first_child(NodeData * data,GtkTreeIter * child_iter)109 static bool get_first_child(NodeData *data, GtkTreeIter *child_iter)
110 {
111     GtkTreeIter iter;
112     return tree_ref_to_iter(data->tree, &iter, data->rowref) &&
113            gtk_tree_model_iter_children(GTK_TREE_MODEL(data->tree->store), child_iter, &iter);
114 }
115 
116 /**
117  * @param iter First dummy row on that level
118  * @pre all rows on the same level are dummies
119  * @pre iter is valid
120  * @post iter is invalid
121  * @post level is empty
122  */
remove_dummy_rows(GtkTreeStore * store,GtkTreeIter * iter)123 static void remove_dummy_rows(GtkTreeStore *store, GtkTreeIter *iter)
124 {
125     do {
126         g_assert(nullptr == sp_xmlview_tree_node_get_data(GTK_TREE_MODEL(store), iter));
127         gtk_tree_store_remove(store, iter);
128     } while (gtk_tree_store_iter_is_valid(store, iter));
129 }
130 
on_test_expand_row(GtkTreeView * tree_view,GtkTreeIter * iter,GtkTreePath * path,gpointer)131 static gboolean on_test_expand_row( //
132     GtkTreeView *tree_view,         //
133     GtkTreeIter *iter,              //
134     GtkTreePath *path,              //
135     gpointer)
136 {
137     auto tree = SP_XMLVIEW_TREE(tree_view);
138     auto model = GTK_TREE_MODEL(tree->store);
139 
140     GtkTreeIter childiter;
141     bool has_children = gtk_tree_model_iter_children(model, &childiter, iter);
142     g_assert(has_children);
143 
144     if (sp_xmlview_tree_node_get_repr(model, &childiter) == nullptr) {
145         NodeData *data = sp_xmlview_tree_node_get_data(model, iter);
146 
147         remove_dummy_rows(tree->store, &childiter);
148 
149         // insert real rows
150         data->expanded = true;
151         sp_repr_synthesize_events(data->repr, &element_repr_events, data);
152     }
153 
154     return false;
155 }
156 
sp_xmlview_tree_new(Inkscape::XML::Node * repr,void *,void *)157 GtkWidget *sp_xmlview_tree_new(Inkscape::XML::Node * repr, void * /*factory*/, void * /*data*/)
158 {
159     SPXMLViewTree *tree = SP_XMLVIEW_TREE(g_object_new (SP_TYPE_XMLVIEW_TREE, nullptr));
160 
161     gtk_tree_view_set_headers_visible (GTK_TREE_VIEW(tree), FALSE);
162     gtk_tree_view_set_reorderable (GTK_TREE_VIEW(tree), TRUE);
163     gtk_tree_view_set_enable_search (GTK_TREE_VIEW(tree), TRUE);
164     gtk_tree_view_set_search_equal_func (GTK_TREE_VIEW(tree), search_equal_func, nullptr, nullptr);
165 
166     GtkCellRenderer *renderer = gtk_cell_renderer_text_new ();
167     GtkTreeViewColumn *column = gtk_tree_view_column_new_with_attributes ("", renderer, "text", STORE_TEXT_COL, NULL);
168     gtk_tree_view_append_column (GTK_TREE_VIEW (tree), column);
169     gtk_cell_renderer_set_padding (renderer, 2, 0);
170     gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
171 
172     sp_xmlview_tree_set_repr (tree, repr);
173 
174     g_signal_connect(GTK_TREE_VIEW(tree), "drag-begin", G_CALLBACK(on_drag_begin), tree);
175     g_signal_connect(GTK_TREE_VIEW(tree), "drag-end", G_CALLBACK(on_drag_end), tree);
176     g_signal_connect(GTK_TREE_VIEW(tree), "drag-motion",  G_CALLBACK(do_drag_motion), tree);
177     g_signal_connect(GTK_TREE_VIEW(tree), "test-expand-row", G_CALLBACK(on_test_expand_row), nullptr);
178 
179     return GTK_WIDGET(tree);
180 }
181 
182 G_DEFINE_TYPE(SPXMLViewTree, sp_xmlview_tree, GTK_TYPE_TREE_VIEW);
183 
sp_xmlview_tree_class_init(SPXMLViewTreeClass * klass)184 void sp_xmlview_tree_class_init(SPXMLViewTreeClass * klass)
185 {
186     auto widget_class = GTK_WIDGET_CLASS(klass);
187     widget_class->destroy = sp_xmlview_tree_destroy;
188 
189     // Signal for when a tree drag and drop has completed
190     g_signal_new (  "tree_move",
191         G_TYPE_FROM_CLASS(klass),
192         G_SIGNAL_RUN_FIRST,
193         0,
194         nullptr, nullptr,
195         g_cclosure_marshal_VOID__UINT,
196         G_TYPE_NONE, 1,
197         G_TYPE_UINT);
198 }
199 
200 void
sp_xmlview_tree_init(SPXMLViewTree * tree)201 sp_xmlview_tree_init (SPXMLViewTree * tree)
202 {
203 	tree->repr = nullptr;
204 	tree->blocked = 0;
205 }
206 
sp_xmlview_tree_destroy(GtkWidget * object)207 void sp_xmlview_tree_destroy(GtkWidget * object)
208 {
209 	SPXMLViewTree * tree = SP_XMLVIEW_TREE (object);
210 
211 	sp_xmlview_tree_set_repr (tree, nullptr);
212 
213 	GTK_WIDGET_CLASS(sp_xmlview_tree_parent_class)->destroy (object);
214 }
215 
216 /*
217  * Add a new row to the tree
218  */
219 void
add_node(SPXMLViewTree * tree,GtkTreeIter * parent,GtkTreeIter * before,Inkscape::XML::Node * repr)220 add_node (SPXMLViewTree * tree, GtkTreeIter *parent, GtkTreeIter *before, Inkscape::XML::Node * repr)
221 {
222 	const Inkscape::XML::NodeEventVector * vec;
223 
224 	g_assert (tree != nullptr);
225 
226     if (before && !gtk_tree_store_iter_is_valid(tree->store, before)) {
227         before = nullptr;
228     }
229 
230 	GtkTreeIter iter;
231     gtk_tree_store_insert_before (tree->store, &iter, parent, before);
232 
233     if (!gtk_tree_store_iter_is_valid(tree->store, &iter)) {
234         return;
235     }
236 
237     if (!repr) {
238         // no need to store any data
239         return;
240     }
241 
242     auto data = new NodeData(tree, &iter, repr);
243 
244     g_assert (data != nullptr);
245 
246     gtk_tree_store_set(tree->store, &iter, STORE_DATA_COL, data, -1);
247 
248 	if ( repr->type() == Inkscape::XML::NodeType::TEXT_NODE ) {
249 		vec = &text_repr_events;
250 	} else if ( repr->type() == Inkscape::XML::NodeType::COMMENT_NODE ) {
251 		vec = &comment_repr_events;
252 	} else if ( repr->type() == Inkscape::XML::NodeType::PI_NODE ) {
253 		vec = &pi_repr_events;
254 	} else if ( repr->type() == Inkscape::XML::NodeType::ELEMENT_NODE ) {
255 		vec = &element_repr_events;
256 	} else {
257 		vec = nullptr;
258 	}
259 
260 	if (vec) {
261 		/* cheat a little to get the text updated on nodes without id */
262         if (repr->type() == Inkscape::XML::NodeType::ELEMENT_NODE && repr->attribute("id") == nullptr) {
263 			element_attr_changed (repr, "id", nullptr, nullptr, false, data);
264 		}
265 		sp_repr_add_listener (repr, vec, data);
266 		sp_repr_synthesize_events (repr, vec, data);
267 	}
268 }
269 
remove_all_listeners(GtkTreeModel * model,GtkTreePath *,GtkTreeIter * iter,gpointer)270 static gboolean remove_all_listeners(GtkTreeModel *model, GtkTreePath *, GtkTreeIter *iter, gpointer)
271 {
272     NodeData *data = sp_xmlview_tree_node_get_data(model, iter);
273     delete data;
274     return false;
275 }
276 
NodeData(SPXMLViewTree * tree,GtkTreeIter * iter,Inkscape::XML::Node * repr)277 NodeData::NodeData(SPXMLViewTree *tree, GtkTreeIter *iter, Inkscape::XML::Node *repr)
278     : tree(tree)
279     , rowref(tree_iter_to_ref(tree, iter))
280     , repr(repr)
281 {
282     if (repr) {
283         Inkscape::GC::anchor(repr);
284     }
285 }
286 
~NodeData()287 NodeData::~NodeData()
288 {
289     if (repr) {
290         sp_repr_remove_listener_by_data(repr, this);
291         Inkscape::GC::release(repr);
292     }
293     gtk_tree_row_reference_free(rowref);
294 }
295 
element_child_added(Inkscape::XML::Node *,Inkscape::XML::Node * child,Inkscape::XML::Node * ref,gpointer ptr)296 void element_child_added (Inkscape::XML::Node * /*repr*/, Inkscape::XML::Node * child, Inkscape::XML::Node * ref, gpointer ptr)
297 {
298     NodeData *data = static_cast<NodeData *>(ptr);
299     GtkTreeIter before;
300 
301     if (data->tree->blocked) return;
302 
303     if (!ref_to_sibling (data, ref, &before)) {
304         return;
305     }
306 
307     GtkTreeIter data_iter;
308     tree_ref_to_iter(data->tree, &data_iter,  data->rowref);
309 
310     if (!data->expanded) {
311         auto model = GTK_TREE_MODEL(data->tree->store);
312         GtkTreeIter childiter;
313         if (!gtk_tree_model_iter_children(model, &childiter, &data_iter)) {
314             // no children yet, add a dummy
315             child = nullptr;
316         } else if (sp_xmlview_tree_node_get_repr(model, &childiter) == nullptr) {
317             // already has a dummy child
318             return;
319         }
320     }
321 
322     add_node (data->tree, &data_iter, &before, child);
323 }
324 
element_attr_changed(Inkscape::XML::Node * repr,gchar const * key,gchar const *,gchar const *,bool,gpointer ptr)325 void element_attr_changed(
326         Inkscape::XML::Node* repr, gchar const* key,
327         gchar const* /*old_value*/, gchar const* /*new_value*/, bool /*is_interactive*/,
328         gpointer ptr)
329 {
330     if (0 != strcmp (key, "id") && 0 != strcmp (key, "inkscape:label"))
331         return;
332     element_attr_or_name_change_update(repr, static_cast<NodeData*>(ptr));
333 }
334 
element_name_changed(Inkscape::XML::Node * repr,gchar const *,gchar const *,gpointer ptr)335 void element_name_changed(
336         Inkscape::XML::Node* repr,
337         gchar const* /*oldname*/, gchar const* /*newname*/, gpointer ptr)
338 {
339     element_attr_or_name_change_update(repr, static_cast<NodeData*>(ptr));
340 }
341 
element_attr_or_name_change_update(Inkscape::XML::Node * repr,NodeData * data)342 void element_attr_or_name_change_update(Inkscape::XML::Node* repr, NodeData* data)
343 {
344     if (data->tree->blocked) {
345         return;
346     }
347 
348     gchar const* node_name = repr->name();
349     gchar const* id_value = repr->attribute("id");
350     gchar const* label_value = repr->attribute("inkscape:label");
351     gchar* display_text;
352 
353     if (id_value && label_value) {
354         display_text = g_strdup_printf ("<%s id=\"%s\" inkscape:label=\"%s\">", node_name, id_value, label_value);
355     } else if (id_value) {
356         display_text = g_strdup_printf ("<%s id=\"%s\">", node_name, id_value);
357     } else if (label_value) {
358         display_text = g_strdup_printf ("<%s inkscape:label=\"%s\">", node_name, label_value);
359     } else {
360         display_text = g_strdup_printf ("<%s>", node_name);
361     }
362 
363     GtkTreeIter iter;
364     if (tree_ref_to_iter(data->tree, &iter,  data->rowref)) {
365         gtk_tree_store_set (GTK_TREE_STORE(data->tree->store), &iter, STORE_TEXT_COL, display_text, -1);
366     }
367 
368     g_free(display_text);
369 }
370 
element_child_removed(Inkscape::XML::Node * repr,Inkscape::XML::Node * child,Inkscape::XML::Node *,gpointer ptr)371 void element_child_removed(Inkscape::XML::Node *repr, Inkscape::XML::Node *child, Inkscape::XML::Node * /*ref*/,
372                            gpointer ptr)
373 {
374     NodeData *data = static_cast<NodeData *>(ptr);
375 
376     if (data->tree->blocked) return;
377 
378     GtkTreeIter iter;
379     if (repr_to_child(data, child, &iter)) {
380         delete sp_xmlview_tree_node_get_data(GTK_TREE_MODEL(data->tree->store), &iter);
381         gtk_tree_store_remove(data->tree->store, &iter);
382     } else if (!repr->firstChild() && get_first_child(data, &iter)) {
383         // remove dummy when all children gone
384         remove_dummy_rows(data->tree->store, &iter);
385     } else {
386         return;
387     }
388 
389 #ifndef GTK_ISSUE_2510_IS_FIXED
390     // https://gitlab.gnome.org/GNOME/gtk/issues/2510
391     gtk_tree_selection_unselect_all(gtk_tree_view_get_selection(GTK_TREE_VIEW(data->tree)));
392 #endif
393 }
394 
element_order_changed(Inkscape::XML::Node *,Inkscape::XML::Node * child,Inkscape::XML::Node *,Inkscape::XML::Node * newref,gpointer ptr)395 void element_order_changed(Inkscape::XML::Node * /*repr*/, Inkscape::XML::Node * child, Inkscape::XML::Node * /*oldref*/, Inkscape::XML::Node * newref, gpointer ptr)
396 {
397     NodeData *data = static_cast<NodeData *>(ptr);
398     GtkTreeIter before, node;
399 
400     if (data->tree->blocked) return;
401 
402     ref_to_sibling (data, newref, &before);
403     repr_to_child (data, child, &node);
404 
405     if (gtk_tree_store_iter_is_valid(data->tree->store, &before)) {
406         gtk_tree_store_move_before (data->tree->store, &node, &before);
407     } else {
408         repr_to_child (data, newref, &before);
409         gtk_tree_store_move_after (data->tree->store, &node, &before);
410     }
411 }
412 
413 /**
414  * Truncate `val` to `maxlen` unicode characters and replace newlines and tabs
415  * with placeholder symbols. The string is modified in place.
416  * @param[in,out] val String in UTF-8 encoding
417  */
sp_remove_newlines_and_tabs(std::string & val,size_t const maxlen=200)418 static void sp_remove_newlines_and_tabs(std::string &val, size_t const maxlen = 200)
419 {
420     if (g_utf8_strlen(val.data(), maxlen * 2) > maxlen) {
421         size_t newlen = g_utf8_offset_to_pointer(val.data(), maxlen - 3) - val.data();
422         val.resize(newlen);
423         val.append("…");
424     }
425 
426     struct
427     {
428         const char *query;
429         const char *replacement;
430     } replacements[] = {
431         {"\r\n", "⏎"},
432         {"\n", "⏎"},
433         {"\t", "⇥"},
434     };
435 
436     for (auto const &item : replacements) {
437         for (size_t pos = 0; (pos = val.find(item.query, pos)) != std::string::npos;) {
438             val.replace(pos, strlen(item.query), item.replacement);
439         }
440     }
441 }
442 
text_content_changed(Inkscape::XML::Node *,const gchar *,const gchar * new_content,gpointer ptr)443 void text_content_changed(Inkscape::XML::Node * /*repr*/, const gchar * /*old_content*/, const gchar * new_content, gpointer ptr)
444 {
445     NodeData *data = static_cast<NodeData *>(ptr);
446 
447     if (data->tree->blocked) return;
448 
449     auto nolinecontent = std::string("\"").append(new_content).append("\"");
450     sp_remove_newlines_and_tabs(nolinecontent);
451 
452     GtkTreeIter iter;
453     if (tree_ref_to_iter(data->tree, &iter,  data->rowref)) {
454         gtk_tree_store_set(GTK_TREE_STORE(data->tree->store), &iter, STORE_TEXT_COL, nolinecontent.c_str(), -1);
455     }
456 }
457 
comment_content_changed(Inkscape::XML::Node *,const gchar *,const gchar * new_content,gpointer ptr)458 void comment_content_changed(Inkscape::XML::Node * /*repr*/, const gchar * /*old_content*/, const gchar *new_content, gpointer ptr)
459 {
460     NodeData *data = static_cast<NodeData*>(ptr);
461 
462     if (data->tree->blocked) return;
463 
464     auto nolinecontent = std::string("<!--").append(new_content).append("-->");
465     sp_remove_newlines_and_tabs(nolinecontent);
466 
467     GtkTreeIter iter;
468     if (tree_ref_to_iter(data->tree, &iter,  data->rowref)) {
469         gtk_tree_store_set(GTK_TREE_STORE(data->tree->store), &iter, STORE_TEXT_COL, nolinecontent.c_str(), -1);
470     }
471 }
472 
pi_content_changed(Inkscape::XML::Node * repr,const gchar *,const gchar * new_content,gpointer ptr)473 void pi_content_changed(Inkscape::XML::Node *repr, const gchar * /*old_content*/, const gchar *new_content, gpointer ptr)
474 {
475     NodeData *data = static_cast<NodeData *>(ptr);
476 
477     if (data->tree->blocked) return;
478 
479     auto nolinecontent = std::string("<?").append(repr->name()).append(" ").append(new_content).append("?>");
480     sp_remove_newlines_and_tabs(nolinecontent);
481 
482     GtkTreeIter iter;
483     if (tree_ref_to_iter(data->tree, &iter,  data->rowref)) {
484         gtk_tree_store_set(GTK_TREE_STORE(data->tree->store), &iter, STORE_TEXT_COL, nolinecontent.c_str(), -1);
485     }
486 }
487 
488 /*
489  * Save the source path on drag start, will need it in on_row_changed() when moving a row
490  */
on_drag_begin(GtkWidget *,GdkDragContext *,gpointer userdata)491 void on_drag_begin(GtkWidget *, GdkDragContext *, gpointer userdata)
492 {
493     SPXMLViewTree *tree = static_cast<SPXMLViewTree *>(userdata);
494     if (!tree) {
495         return;
496     }
497 
498     GtkTreeModel *model = nullptr;
499     GtkTreeIter iter;
500     GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree));
501     if (gtk_tree_selection_get_selected(selection, &model, &iter)) {
502         NodeData *data = sp_xmlview_tree_node_get_data(model, &iter);
503         if (data) {
504             data->dragging = true;
505             dragging_repr = data->repr;
506         }
507     }
508 }
509 
510 /**
511  * Finalize what happended in `on_row_changed` and clean up what was set up in `on_drag_begin`
512  */
on_drag_end(GtkWidget *,GdkDragContext *,gpointer userdata)513 void on_drag_end(GtkWidget *, GdkDragContext *, gpointer userdata)
514 {
515     if (!dragging_repr)
516         return;
517 
518     auto tree = static_cast<SPXMLViewTree *>(userdata);
519     auto selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree));
520     bool failed = false;
521 
522     GtkTreeIter iter;
523     if (sp_xmlview_tree_get_repr_node(tree, dragging_repr, &iter)) {
524         NodeData *data = sp_xmlview_tree_node_get_data(GTK_TREE_MODEL(tree->store), &iter);
525 
526         if (data && data->dragging) {
527             // dragging flag was not cleared in `on_row_changed`, this indicates a failed drag
528             data->dragging = false;
529             failed = true;
530         } else {
531             // Reselect the dragged row
532             gtk_tree_selection_select_iter(selection, &iter);
533         }
534     } else {
535 #ifndef GTK_ISSUE_2510_IS_FIXED
536         // https://gitlab.gnome.org/GNOME/gtk/issues/2510
537         gtk_tree_selection_unselect_all(selection);
538 #endif
539     }
540 
541     dragging_repr = nullptr;
542 
543     if (!failed) {
544         // Signal that a drag and drop has completed successfully
545         g_signal_emit_by_name(G_OBJECT(tree), "tree_move", GUINT_TO_POINTER(1));
546     }
547 }
548 
549 /*
550  * Main drag & drop function
551  * Get the old and new paths, and change the Inkscape::XML::Node repr's
552  */
on_row_changed(GtkTreeModel * tree_model,GtkTreePath * path,GtkTreeIter * iter,gpointer user_data)553 void on_row_changed(GtkTreeModel *tree_model, GtkTreePath *path, GtkTreeIter *iter, gpointer user_data)
554 {
555     NodeData *data = sp_xmlview_tree_node_get_data(tree_model, iter);
556 
557     if (!data || !data->dragging) {
558         return;
559     }
560     data->dragging = false;
561 
562     SPXMLViewTree *tree = SP_XMLVIEW_TREE(user_data);
563 
564     gtk_tree_row_reference_free(data->rowref);
565     data->rowref = tree_iter_to_ref(tree, iter);
566 
567     GtkTreeIter new_parent;
568     if (!gtk_tree_model_iter_parent(tree_model, &new_parent, iter)) {
569         //No parent of drop location
570         return;
571     }
572 
573     Inkscape::XML::Node *repr = sp_xmlview_tree_node_get_repr(tree_model, iter);
574     Inkscape::XML::Node *before_repr = nullptr;
575 
576     // Find the sibling node before iter
577     GtkTreeIter before_iter = *iter;
578     if (gtk_tree_model_iter_previous(tree_model, &before_iter)) {
579         before_repr = sp_xmlview_tree_node_get_repr(tree_model, &before_iter);
580     }
581 
582     // Drop onto oneself causes assert in changeOrder() below, ignore
583     if (repr == before_repr)
584         return;
585 
586     auto repr_old_parent = repr->parent();
587     auto repr_new_parent = sp_xmlview_tree_node_get_repr(tree_model, &new_parent);
588 
589     tree->blocked++;
590 
591     if (repr_old_parent == repr_new_parent) {
592         repr_old_parent->changeOrder(repr, before_repr);
593     } else {
594         repr_old_parent->removeChild(repr);
595         repr_new_parent->addChild(repr, before_repr);
596     }
597 
598     NodeData *data_new_parent = sp_xmlview_tree_node_get_data(tree_model, &new_parent);
599     if (data_new_parent && data_new_parent->expanded) {
600         // Reselect the dragged row in `on_drag_end` instead of here, because of
601         // https://gitlab.gnome.org/GNOME/gtk/-/issues/2510
602     } else {
603         // convert to dummy node
604         delete data;
605         gtk_tree_store_set(tree->store, iter, STORE_DATA_COL, nullptr, -1);
606     }
607 
608     tree->blocked--;
609 }
610 
611 /*
612  * Set iter to ref or node data's child with the same repr or first child
613  */
ref_to_sibling(NodeData * data,Inkscape::XML::Node * repr,GtkTreeIter * iter)614 gboolean ref_to_sibling (NodeData *data, Inkscape::XML::Node *repr, GtkTreeIter *iter)
615 {
616 	if (repr) {
617 		if (!repr_to_child (data, repr, iter)) {
618             return false;
619         }
620         gtk_tree_model_iter_next (GTK_TREE_MODEL(data->tree->store), iter);
621 	} else {
622 	    GtkTreeIter data_iter;
623 	    if (!tree_ref_to_iter(data->tree, &data_iter,  data->rowref)) {
624 	        return false;
625 	    }
626 	    gtk_tree_model_iter_children(GTK_TREE_MODEL(data->tree->store), iter, &data_iter);
627 	}
628 	return true;
629 }
630 
631 /*
632  * Set iter to the node data's child with the same repr
633  */
repr_to_child(NodeData * data,Inkscape::XML::Node * repr,GtkTreeIter * iter)634 gboolean repr_to_child (NodeData *data, Inkscape::XML::Node * repr, GtkTreeIter *iter)
635 {
636     GtkTreeIter data_iter;
637     GtkTreeModel *model = GTK_TREE_MODEL(data->tree->store);
638     gboolean valid = false;
639 
640     if (!tree_ref_to_iter(data->tree, &data_iter, data->rowref)) {
641         return false;
642     }
643 
644     /*
645      * The node we are looking for is likely to be the last one, so check it first.
646      */
647     gint n_children = gtk_tree_model_iter_n_children (model, &data_iter);
648     if (n_children > 1) {
649         valid = gtk_tree_model_iter_nth_child (model, iter, &data_iter, n_children-1);
650         if (valid && sp_xmlview_tree_node_get_repr (model, iter) == repr) {
651             //g_message("repr_to_child hit %d", n_children);
652             return valid;
653         }
654     }
655 
656     valid = gtk_tree_model_iter_children(model, iter, &data_iter);
657     while (valid && sp_xmlview_tree_node_get_repr (model, iter) != repr) {
658         valid = gtk_tree_model_iter_next(model, iter);
659 	}
660 
661     return valid;
662 }
663 
664 /*
665  * Get a matching GtkTreeRowReference for a GtkTreeIter
666  */
tree_iter_to_ref(SPXMLViewTree * tree,GtkTreeIter * iter)667 GtkTreeRowReference  *tree_iter_to_ref (SPXMLViewTree * tree, GtkTreeIter* iter)
668 {
669     GtkTreePath* path = gtk_tree_model_get_path(GTK_TREE_MODEL(tree->store), iter);
670     GtkTreeRowReference  *ref = gtk_tree_row_reference_new(GTK_TREE_MODEL(tree->store), path);
671     gtk_tree_path_free(path);
672 
673     return ref;
674 }
675 
676 /*
677  * Get a matching GtkTreeIter for a GtkTreeRowReference
678  */
tree_ref_to_iter(SPXMLViewTree * tree,GtkTreeIter * iter,GtkTreeRowReference * ref)679 gboolean tree_ref_to_iter (SPXMLViewTree * tree, GtkTreeIter* iter, GtkTreeRowReference  *ref)
680 {
681     GtkTreePath* path = gtk_tree_row_reference_get_path(ref);
682     if (!path) {
683         return false;
684     }
685     gboolean const valid = //
686         gtk_tree_model_get_iter(GTK_TREE_MODEL(tree->store), iter, path);
687     gtk_tree_path_free(path);
688 
689     return valid;
690 }
691 
692 /*
693  * Disable drag and drop target on : root node and non-element nodes
694  */
do_drag_motion(GtkWidget * widget,GdkDragContext * context,gint x,gint y,guint time,gpointer user_data)695 gboolean do_drag_motion(GtkWidget *widget, GdkDragContext *context, gint x, gint y, guint time, gpointer user_data)
696 {
697     GtkTreePath *path = nullptr;
698     GtkTreeViewDropPosition pos;
699     gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW(widget), x, y, &path, &pos);
700 
701     int action = 0;
702 
703     if (!dragging_repr) {
704         goto finally;
705     }
706 
707     if (path) {
708         SPXMLViewTree *tree = SP_XMLVIEW_TREE(user_data);
709         GtkTreeIter iter;
710         gtk_tree_model_get_iter(GTK_TREE_MODEL(tree->store), &iter, path);
711         auto repr = sp_xmlview_tree_node_get_repr(GTK_TREE_MODEL(tree->store), &iter);
712 
713         bool const drop_into = pos != GTK_TREE_VIEW_DROP_BEFORE && //
714                                pos != GTK_TREE_VIEW_DROP_AFTER;
715 
716         // 0. don't drop on self (also handled by on_row_changed but nice to not have drop highlight for it)
717         if (repr == dragging_repr) {
718             goto finally;
719         }
720 
721         // 1. only xml elements can have children
722         if (drop_into && repr->type() != Inkscape::XML::NodeType::ELEMENT_NODE) {
723             goto finally;
724         }
725 
726         // 3. elements must be at least children of the root <svg:svg> element
727         if (gtk_tree_path_get_depth(path) < 2) {
728             goto finally;
729         }
730 
731         // 4. drag node specific limitations
732         {
733             // nodes which can't be re-parented (because the document holds pointers to them which must stay valid)
734             static GQuark const CODE_sodipodi_namedview = g_quark_from_static_string("sodipodi:namedview");
735             static GQuark const CODE_svg_defs = g_quark_from_static_string("svg:defs");
736 
737             bool const no_reparenting = dragging_repr->code() == CODE_sodipodi_namedview || //
738                                         dragging_repr->code() == CODE_svg_defs;
739 
740             if (no_reparenting && (drop_into || dragging_repr->parent() != repr->parent())) {
741                 goto finally;
742             }
743         }
744 
745         action = GDK_ACTION_MOVE;
746     }
747 
748 finally:
749     if (action == 0) {
750         // remove drop highlight
751         gtk_tree_view_set_drag_dest_row(GTK_TREE_VIEW(widget), nullptr, pos /* ignored */);
752     }
753 
754     gtk_tree_path_free(path);
755     gdk_drag_status (context, (GdkDragAction)action, time);
756 
757     return (action == 0);
758 }
759 
760 /*
761  * Set the tree selection and scroll to the row with the given repr
762  */
763 void
sp_xmlview_tree_set_repr(SPXMLViewTree * tree,Inkscape::XML::Node * repr)764 sp_xmlview_tree_set_repr (SPXMLViewTree * tree, Inkscape::XML::Node * repr)
765 {
766     if ( tree->repr == repr ) return;
767 
768     if (tree->store) {
769         gtk_tree_view_set_model(GTK_TREE_VIEW(tree), nullptr);
770         gtk_tree_model_foreach(GTK_TREE_MODEL(tree->store), remove_all_listeners, nullptr);
771         g_object_unref(tree->store);
772         tree->store = nullptr;
773     }
774 
775     if (tree->repr) {
776         Inkscape::GC::release(tree->repr);
777     }
778     tree->repr = repr;
779     if (repr) {
780         tree->store = gtk_tree_store_new(STORE_N_COLS, G_TYPE_STRING, G_TYPE_POINTER);
781 
782         Inkscape::GC::anchor(repr);
783         add_node(tree, nullptr, nullptr, repr);
784 
785         // Set the tree model here, after all data is inserted
786         gtk_tree_view_set_model (GTK_TREE_VIEW(tree), GTK_TREE_MODEL(tree->store));
787         g_signal_connect(G_OBJECT(tree->store), "row-changed", G_CALLBACK(on_row_changed), tree);
788 
789         GtkTreePath *path = gtk_tree_path_new_from_indices(0, -1);
790         gtk_tree_view_expand_to_path (GTK_TREE_VIEW(tree), path);
791         gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW(tree), path, nullptr, true, 0.5, 0.0);
792         gtk_tree_path_free(path);
793     }
794 }
795 
796 /*
797  * Return the node data at a given GtkTreeIter position
798  */
sp_xmlview_tree_node_get_data(GtkTreeModel * model,GtkTreeIter * iter)799 NodeData *sp_xmlview_tree_node_get_data(GtkTreeModel *model, GtkTreeIter *iter)
800 {
801     NodeData *data = nullptr;
802     gtk_tree_model_get(model, iter, STORE_DATA_COL, &data, -1);
803     return data;
804 }
805 
806 /*
807  * Return the repr at a given GtkTreeIter position
808  */
809 Inkscape::XML::Node *
sp_xmlview_tree_node_get_repr(GtkTreeModel * model,GtkTreeIter * iter)810 sp_xmlview_tree_node_get_repr (GtkTreeModel *model, GtkTreeIter * iter)
811 {
812     NodeData *data = sp_xmlview_tree_node_get_data(model, iter);
813     return data ? data->repr : nullptr;
814 }
815 
816 struct IterByReprData {
817     const Inkscape::XML::Node *repr; //< in
818     GtkTreeIter *iter;               //< out
819 };
820 
821 /*
822  * Find a GtkTreeIter position in the tree by repr
823  * @return True if the node was found
824  */
825 gboolean
sp_xmlview_tree_get_repr_node(SPXMLViewTree * tree,Inkscape::XML::Node * repr,GtkTreeIter * iter)826 sp_xmlview_tree_get_repr_node (SPXMLViewTree * tree, Inkscape::XML::Node * repr, GtkTreeIter *iter)
827 {
828     iter->stamp = 0; // invalidate iterator
829     IterByReprData funcdata = { repr, iter };
830     gtk_tree_model_foreach(GTK_TREE_MODEL(tree->store), foreach_func, &funcdata);
831     return iter->stamp != 0;
832 }
833 
foreach_func(GtkTreeModel * model,GtkTreePath *,GtkTreeIter * iter,gpointer user_data)834 gboolean foreach_func(GtkTreeModel *model, GtkTreePath * /*path*/, GtkTreeIter *iter, gpointer user_data)
835 {
836     auto funcdata = static_cast<IterByReprData *>(user_data);
837     if (sp_xmlview_tree_node_get_repr(model, iter) == funcdata->repr) {
838         *funcdata->iter = *iter;
839         return TRUE;
840     }
841 
842     return FALSE;
843 }
844 
845 /*
846  * Callback function for string searches in the tree
847  * Return a match on any substring
848  */
search_equal_func(GtkTreeModel * model,gint,const gchar * key,GtkTreeIter * iter,gpointer)849 gboolean search_equal_func(GtkTreeModel *model, gint /*column*/, const gchar *key, GtkTreeIter *iter, gpointer /*search_data*/)
850 {
851     gchar *text = nullptr;
852     gtk_tree_model_get(model, iter, STORE_TEXT_COL, &text, -1);
853 
854     gboolean match = (strstr(text, key) != nullptr);
855 
856     g_free(text);
857 
858     return !match;
859 }
860 
861 /*
862   Local Variables:
863   mode:c++
864   c-file-style:"stroustrup"
865   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
866   indent-tabs-mode:nil
867   fill-column:99
868   End:
869 */
870 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=4:softtabstop=4:fileencoding=utf-8 :
871