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