summaryrefslogtreecommitdiffstats
path: root/src/ui/dialog/xml-tree.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 11:50:49 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 11:50:49 +0000
commitc853ffb5b2f75f5a889ed2e3ef89b818a736e87a (patch)
tree7d13a0883bb7936b84d6ecdd7bc332b41ed04bee /src/ui/dialog/xml-tree.cpp
parentInitial commit. (diff)
downloadinkscape-c853ffb5b2f75f5a889ed2e3ef89b818a736e87a.tar.xz
inkscape-c853ffb5b2f75f5a889ed2e3ef89b818a736e87a.zip
Adding upstream version 1.3+ds.upstream/1.3+dsupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/ui/dialog/xml-tree.cpp')
-rw-r--r--src/ui/dialog/xml-tree.cpp928
1 files changed, 928 insertions, 0 deletions
diff --git a/src/ui/dialog/xml-tree.cpp b/src/ui/dialog/xml-tree.cpp
new file mode 100644
index 0000000..242a5d4
--- /dev/null
+++ b/src/ui/dialog/xml-tree.cpp
@@ -0,0 +1,928 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/**
+ * @file
+ * XML editor.
+ */
+/* Authors:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * MenTaLguY <mental@rydia.net>
+ * bulia byak <buliabyak@users.sf.net>
+ * Johan Engelen <goejendaagh@zonnet.nl>
+ * David Turner
+ * Jon A. Cruz <jon@joncruz.org>
+ * Abhishek Sharma
+ * Mike Kowalski
+ *
+ * Copyright (C) 1999-2006 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ *
+ */
+
+#include "xml-tree.h"
+
+#include <glibmm/i18n.h>
+#include <glibmm/ustring.h>
+#include <gtkmm/button.h>
+#include <gtkmm/enums.h>
+#include <gtkmm/image.h>
+#include <gtkmm/menubutton.h>
+#include <gtkmm/object.h>
+#include <gtkmm/paned.h>
+#include <gtkmm/radiomenuitem.h>
+#include <gtkmm/scrolledwindow.h>
+#include <memory>
+
+#include "desktop.h"
+#include "document-undo.h"
+#include "document.h"
+#include "inkscape.h"
+#include "layer-manager.h"
+#include "message-context.h"
+#include "message-stack.h"
+
+#include "object/sp-root.h"
+#include "object/sp-string.h"
+
+#include "preferences.h"
+#include "ui/builder-utils.h"
+#include "ui/dialog-events.h"
+#include "ui/icon-loader.h"
+#include "ui/icon-names.h"
+#include "ui/tools/tool-base.h"
+#include "ui/syntax.h"
+
+#include "util/trim.h"
+#include "widgets/sp-xmlview-tree.h"
+
+namespace {
+/**
+ * Set the orientation of `paned` to vertical or horizontal, and make the first child resizable
+ * if vertical, and the second child resizable if horizontal.
+ * @pre `paned` has two children
+ */
+void paned_set_vertical(Gtk::Paned &paned, bool vertical)
+{
+ auto& first = *paned.get_child1();
+ auto& second = *paned.get_child2();
+ const int space = 1;
+ paned.child_property_resize(first) = vertical;
+ first.set_margin_bottom(vertical ? space : 0);
+ first.set_margin_end(vertical ? 0 : space);
+ second.set_margin_top(vertical ? space : 0);
+ second.set_margin_start(vertical ? 0 : space);
+ assert(paned.child_property_resize(second));
+ paned.set_orientation(vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL);
+}
+} // namespace
+
+namespace Inkscape::UI::Dialog {
+
+XmlTree::XmlTree()
+ : DialogBase("/dialogs/xml/", "XMLEditor")
+ , _builder(create_builder("dialog-xml.glade"))
+ , _paned(get_widget<Gtk::Paned>(_builder, "pane"))
+ , xml_element_new_button(get_widget<Gtk::Button>(_builder, "new-elem"))
+ , xml_text_new_button(get_widget<Gtk::Button>(_builder, "new-text"))
+ , xml_node_delete_button(get_widget<Gtk::Button>(_builder, "del"))
+ , xml_node_duplicate_button(get_widget<Gtk::Button>(_builder, "dup"))
+ , unindent_node_button(get_widget<Gtk::Button>(_builder, "unindent"))
+ , indent_node_button(get_widget<Gtk::Button>(_builder, "indent"))
+ , lower_node_button(get_widget<Gtk::Button>(_builder, "lower"))
+ , raise_node_button(get_widget<Gtk::Button>(_builder, "raise"))
+ , _syntax_theme("/theme/syntax-color-theme")
+ , _mono_font("/dialogs/xml/mono-font", false)
+{
+ /* tree view */
+ tree = SP_XMLVIEW_TREE(sp_xmlview_tree_new(nullptr, nullptr, nullptr));
+ gtk_widget_set_tooltip_text( GTK_WIDGET(tree), _("Drag to reorder nodes") );
+
+ Gtk::ScrolledWindow& tree_scroller = get_widget<Gtk::ScrolledWindow>(_builder, "tree-wnd");
+ _treemm = Gtk::manage(Glib::wrap(GTK_TREE_VIEW(tree)));
+ tree_scroller.add(*Gtk::manage(Glib::wrap(GTK_WIDGET(tree))));
+ fix_inner_scroll(&tree_scroller);
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+
+ /* attributes */
+ attributes = Gtk::make_managed<AttrDialog>();
+ attributes->set_margin_top(0);
+ attributes->set_margin_bottom(0);
+ attributes->set_margin_start(0);
+ attributes->set_margin_end(0);
+ attributes->get_scrolled_window().set_shadow_type(Gtk::SHADOW_IN);
+ attributes->show();
+ attributes->get_status_box().hide();
+ attributes->get_status_box().set_no_show_all();
+ _paned.pack2(*attributes, true, false);
+
+ /* Signal handlers */
+ _treemm->get_selection()->signal_changed().connect([=]() {
+ if (blocked || !getDesktop())
+ return;
+ if (!_tree_select_idle) {
+ // Defer the update after all events have been processed.
+ _tree_select_idle = Glib::signal_idle().connect(sigc::mem_fun(*this, &XmlTree::deferred_on_tree_select_row));
+ }
+ });
+ tree->connectTreeMove([=]() {
+ if (auto doc = getDocument()) {
+ DocumentUndo::done(doc, Q_("Undo History / XML Editor|Drag XML subtree"), INKSCAPE_ICON("dialog-xml-editor"));
+ }
+ });
+
+ xml_element_new_button.signal_clicked().connect(sigc::mem_fun(*this, &XmlTree::cmd_new_element_node));
+ xml_text_new_button.signal_clicked().connect(sigc::mem_fun(*this, &XmlTree::cmd_new_text_node));
+ xml_node_duplicate_button.signal_clicked().connect(sigc::mem_fun(*this, &XmlTree::cmd_duplicate_node));
+ xml_node_delete_button.signal_clicked().connect(sigc::mem_fun(*this, &XmlTree::cmd_delete_node));
+ unindent_node_button.signal_clicked().connect(sigc::mem_fun(*this, &XmlTree::cmd_unindent_node));
+ indent_node_button.signal_clicked().connect(sigc::mem_fun(*this, &XmlTree::cmd_indent_node));
+ raise_node_button.signal_clicked().connect(sigc::mem_fun(*this, &XmlTree::cmd_raise_node));
+ lower_node_button.signal_clicked().connect(sigc::mem_fun(*this, &XmlTree::cmd_lower_node));
+
+ set_name("XMLAndAttributesDialog");
+ set_spacing(0);
+ show_all();
+
+ int panedpos = prefs->getInt("/dialogs/xml/panedpos", 200);
+ _paned.property_position() = panedpos;
+ _paned.property_position().signal_changed().connect(sigc::mem_fun(*this, &XmlTree::_resized));
+
+ pack_start(get_widget<Gtk::Box>(_builder, "main"), true, true);
+
+ int min_width = 0, dummy;
+ get_preferred_width(min_width, dummy);
+
+ auto auto_arrange_panels = [=](Gtk::Allocation const &alloc) {
+ // skip bogus sizes
+ if (alloc.get_width() < 10 || alloc.get_height() < 10) return;
+
+ // minimal width times fudge factor to arrive at "narrow" dialog with automatic vertical layout:
+ const bool narrow = alloc.get_width() < min_width * 1.5;
+ paned_set_vertical(_paned, narrow);
+ };
+
+ auto arrange_panels = [=](DialogLayout layout){
+ switch (layout) {
+ case Auto:
+ auto_arrange_panels(get_allocation());
+ break;
+ case Horizontal:
+ paned_set_vertical(_paned, false);
+ break;
+ case Vertical:
+ paned_set_vertical(_paned, true);
+ break;
+ }
+ // ensure_size();
+ };
+
+ signal_size_allocate().connect([=] (Gtk::Allocation const &alloc) {
+ arrange_panels(_layout);
+ });
+
+ auto& popup = get_widget<Gtk::MenuButton>(_builder, "layout-btn");
+ popup.set_has_tooltip();
+ popup.signal_query_tooltip().connect([=](int x, int y, bool kbd, const Glib::RefPtr<Gtk::Tooltip>& tooltip){
+ auto tip = "";
+ switch (_layout) {
+ case Auto: tip = _("Automatic panel layout:\nchanges with dialog size");
+ break;
+ case Horizontal: tip = _("Horizontal panel layout");
+ break;
+ case Vertical: tip = _("Vertical panel layout");
+ break;
+ }
+ tooltip->set_text(tip);
+ return true;
+ });
+
+ auto set_layout = [=](DialogLayout layout){
+ Glib::ustring icon = "layout-auto";
+ if (layout == Horizontal) {
+ icon = "layout-horizontal";
+ } else if (layout == Vertical) {
+ icon = "layout-vertical";
+ }
+ get_widget<Gtk::Image>(_builder, "layout-img").set_from_icon_name(icon + "-symbolic", Gtk::ICON_SIZE_SMALL_TOOLBAR);
+ prefs->setInt("/dialogs/xml/layout", layout);
+ arrange_panels(layout);
+ _layout = layout;
+ };
+
+ auto menu_items = get_widget<Gtk::Menu>(_builder, "menu-popup").get_children();
+
+ DialogLayout layouts[] = {Auto, Horizontal, Vertical};
+ int index = 0;
+ for (auto item : menu_items) {
+ g_assert(index < 3);
+ auto layout = layouts[index++];
+ static_cast<Gtk::RadioMenuItem*>(item)->signal_activate().connect([=](){ set_layout(layout); });
+ }
+
+ _layout = static_cast<DialogLayout>(prefs->getIntLimited("/dialogs/xml/layout", Auto, Auto, Vertical));
+ static_cast<Gtk::RadioMenuItem*>(menu_items.at(_layout))->set_active();
+ set_layout(_layout);
+ // establish initial layout to prevent unwanted panels resize in auto layout mode
+ paned_set_vertical(_paned, true);
+
+ _syntax_theme.action = [=]() {
+ setSyntaxStyle(Inkscape::UI::Syntax::build_xml_styles(_syntax_theme));
+ // rebuild tree to change markup
+ rebuildTree();
+ };
+
+ setSyntaxStyle(Inkscape::UI::Syntax::build_xml_styles(_syntax_theme));
+
+ _mono_font.action = [=]() {
+ Glib::ustring mono("mono-font");
+ if (_mono_font) {
+ _treemm->get_style_context()->add_class(mono);
+ } else {
+ _treemm->get_style_context()->remove_class(mono);
+ }
+ attributes->set_mono_font(_mono_font);
+ };
+ _mono_font.action();
+
+ tree->renderer->signal_editing_canceled().connect([=]() {
+ stopNodeEditing(false, "", "");
+ });
+ tree->renderer->signal_edited().connect([=](const Glib::ustring& path, const Glib::ustring& name) {
+ stopNodeEditing(true, path, name);
+ });
+ tree->renderer->signal_editing_started().connect([=](Gtk::CellEditable* cell, const Glib::ustring& path) {
+ startNodeEditing(cell, path);
+ });
+}
+
+XmlTree::~XmlTree()
+{
+ unsetDocument();
+}
+
+void XmlTree::rebuildTree()
+{
+ sp_xmlview_tree_set_repr(tree, nullptr);
+ if (auto document = getDocument()) {
+ set_tree_repr(document->getReprRoot());
+ }
+}
+
+void XmlTree::_resized()
+{
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setInt("/dialogs/xml/panedpos", _paned.property_position());
+}
+
+void XmlTree::unsetDocument()
+{
+ _tree_select_idle.disconnect();
+}
+
+void XmlTree::documentReplaced()
+{
+ unsetDocument();
+ if (auto document = getDocument()) {
+ // TODO: Why is this a document property?
+ document->setXMLDialogSelectedObject(nullptr);
+
+ set_tree_repr(document->getReprRoot());
+ } else {
+ set_tree_repr(nullptr);
+ }
+}
+
+void XmlTree::selectionChanged(Selection *selection)
+{
+ if (!blocked++) {
+ Inkscape::XML::Node *node = get_dt_select();
+ set_tree_select(node);
+ }
+ blocked--;
+}
+
+void XmlTree::set_tree_repr(Inkscape::XML::Node *repr)
+{
+ if (repr == selected_repr) {
+ return;
+ }
+
+ sp_xmlview_tree_set_repr(tree, repr);
+ if (repr) {
+ set_tree_select(get_dt_select());
+ } else {
+ set_tree_select(nullptr);
+ }
+
+ propagate_tree_select(selected_repr);
+}
+
+/**
+ * Expand all parent nodes of `repr`
+ */
+static void expand_parents(SPXMLViewTree *tree, Inkscape::XML::Node *repr)
+{
+ auto parentrepr = repr->parent();
+ if (!parentrepr) {
+ return;
+ }
+
+ expand_parents(tree, parentrepr);
+
+ GtkTreeIter node;
+ if (sp_xmlview_tree_get_repr_node(tree, parentrepr, &node)) {
+ GtkTreePath *path = gtk_tree_model_get_path(GTK_TREE_MODEL(tree->store), &node);
+ if (path) {
+ gtk_tree_view_expand_row(GTK_TREE_VIEW(tree), path, false);
+ }
+ }
+}
+
+void XmlTree::set_tree_select(Inkscape::XML::Node *repr, bool edit)
+{
+ if (selected_repr) {
+ Inkscape::GC::release(selected_repr);
+ }
+ selected_repr = repr;
+ if (selected_repr) {
+ Inkscape::GC::anchor(selected_repr);
+ }
+ if (auto document = getDocument()) {
+ document->setXMLDialogSelectedObject(nullptr);
+ }
+ if (repr) {
+ GtkTreeIter node;
+
+ Inkscape::GC::anchor(selected_repr);
+
+ expand_parents(tree, repr);
+
+ if (sp_xmlview_tree_get_repr_node(SP_XMLVIEW_TREE(tree), repr, &node)) {
+
+ GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree));
+ gtk_tree_selection_unselect_all (selection);
+
+ GtkTreePath* path = gtk_tree_model_get_path(GTK_TREE_MODEL(tree->store), &node);
+ gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(tree), path, nullptr, TRUE, 0.66, 0.0);
+ gtk_tree_selection_select_iter(selection, &node);
+ auto col = gtk_tree_view_get_column(&tree->tree, 0);
+ gtk_tree_view_set_cursor(GTK_TREE_VIEW(tree), path, edit ? col : nullptr, edit);
+ gtk_tree_path_free(path);
+
+ } else {
+ g_message("XmlTree::set_tree_select : Couldn't find repr node");
+ }
+ } else {
+ GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree));
+ gtk_tree_selection_unselect_all (selection);
+
+ on_tree_unselect_row_disable();
+ }
+ propagate_tree_select(repr);
+}
+
+
+
+void XmlTree::propagate_tree_select(Inkscape::XML::Node *repr)
+{
+ if (repr &&
+ (repr->type() == Inkscape::XML::NodeType::ELEMENT_NODE ||
+ repr->type() == Inkscape::XML::NodeType::TEXT_NODE ||
+ repr->type() == Inkscape::XML::NodeType::COMMENT_NODE))
+ {
+ attributes->setRepr(repr);
+ } else {
+ attributes->setRepr(nullptr);
+ }
+}
+
+
+Inkscape::XML::Node *XmlTree::get_dt_select()
+{
+ if (auto selection = getSelection()) {
+ return selection->singleRepr();
+ }
+ return nullptr;
+}
+
+
+/**
+ * Like SPDesktop::isLayer(), but ignores SPGroup::effectiveLayerMode().
+ */
+static bool isRealLayer(SPObject const *object)
+{
+ auto group = cast<SPGroup>(object);
+ return group && group->layerMode() == SPGroup::LAYER;
+}
+
+void XmlTree::set_dt_select(Inkscape::XML::Node *repr)
+{
+ auto document = getDocument();
+ if (!document)
+ return;
+
+ SPObject *object;
+ if (repr) {
+ while ( ( repr->type() != Inkscape::XML::NodeType::ELEMENT_NODE )
+ && repr->parent() )
+ {
+ repr = repr->parent();
+ } // end of while loop
+
+ object = document->getObjectByRepr(repr);
+ } else {
+ object = nullptr;
+ }
+
+ blocked++;
+
+ if (!object || !in_dt_coordsys(*object)) {
+ // object not on canvas
+ } else if (isRealLayer(object)) {
+ getDesktop()->layerManager().setCurrentLayer(object);
+ } else {
+ if (is<SPGroup>(object->parent)) {
+ getDesktop()->layerManager().setCurrentLayer(object->parent);
+ }
+
+ getSelection()->set(cast<SPItem>(object));
+ }
+
+ document->setXMLDialogSelectedObject(object);
+ blocked--;
+}
+
+bool XmlTree::deferred_on_tree_select_row()
+{
+ GtkTreeIter iter;
+ GtkTreeModel *model;
+
+ if (selected_repr) {
+ Inkscape::GC::release(selected_repr);
+ selected_repr = nullptr;
+ }
+
+ GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree));
+
+ if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
+ // Nothing selected, update widgets
+ propagate_tree_select(nullptr);
+ set_dt_select(nullptr);
+ on_tree_unselect_row_disable();
+ return false;
+ }
+
+ Inkscape::XML::Node *repr = sp_xmlview_tree_node_get_repr(model, &iter);
+ g_assert(repr != nullptr);
+
+
+ selected_repr = repr;
+ Inkscape::GC::anchor(selected_repr);
+
+ propagate_tree_select(selected_repr);
+ set_dt_select(selected_repr);
+ on_tree_select_row_enable(&iter);
+
+ return FALSE;
+}
+
+void XmlTree::_set_status_message(Inkscape::MessageType /*type*/, const gchar *message, GtkWidget *widget)
+{
+ if (widget) {
+ gtk_label_set_markup(GTK_LABEL(widget), message ? message : "");
+ }
+}
+
+void XmlTree::on_tree_select_row_enable(GtkTreeIter *node)
+{
+ if (!node) {
+ return;
+ }
+
+ Inkscape::XML::Node *repr = sp_xmlview_tree_node_get_repr(GTK_TREE_MODEL(tree->store), node);
+ Inkscape::XML::Node *parent=repr->parent();
+
+ //on_tree_select_row_enable_if_mutable
+ xml_node_duplicate_button.set_sensitive(xml_tree_node_mutable(node));
+ xml_node_delete_button.set_sensitive(xml_tree_node_mutable(node));
+
+ //on_tree_select_row_enable_if_element
+ if (repr->type() == Inkscape::XML::NodeType::ELEMENT_NODE) {
+ xml_element_new_button.set_sensitive(true);
+ xml_text_new_button.set_sensitive(true);
+
+ } else {
+ xml_element_new_button.set_sensitive(false);
+ xml_text_new_button.set_sensitive(false);
+ }
+
+ //on_tree_select_row_enable_if_has_grandparent
+ {
+ GtkTreeIter parent;
+ if (gtk_tree_model_iter_parent(GTK_TREE_MODEL(tree->store), &parent, node)) {
+ GtkTreeIter grandparent;
+ if (gtk_tree_model_iter_parent(GTK_TREE_MODEL(tree->store), &grandparent, &parent)) {
+ unindent_node_button.set_sensitive(true);
+ } else {
+ unindent_node_button.set_sensitive(false);
+ }
+ } else {
+ unindent_node_button.set_sensitive(false);
+ }
+ }
+ // on_tree_select_row_enable_if_indentable
+ gboolean indentable = FALSE;
+
+ if (xml_tree_node_mutable(node)) {
+ Inkscape::XML::Node *prev;
+
+ if ( parent && repr != parent->firstChild() ) {
+ g_assert(parent->firstChild());
+
+ // skip to the child just before the current repr
+ for ( prev = parent->firstChild() ;
+ prev && prev->next() != repr ;
+ prev = prev->next() ){};
+
+ if (prev && (prev->type() == Inkscape::XML::NodeType::ELEMENT_NODE)) {
+ indentable = TRUE;
+ }
+ }
+ }
+
+ indent_node_button.set_sensitive(indentable);
+
+ //on_tree_select_row_enable_if_not_first_child
+ {
+ if ( parent && repr != parent->firstChild() ) {
+ raise_node_button.set_sensitive(true);
+ } else {
+ raise_node_button.set_sensitive(false);
+ }
+ }
+
+ //on_tree_select_row_enable_if_not_last_child
+ {
+ if ( parent && (parent->parent() && repr->next())) {
+ lower_node_button.set_sensitive(true);
+ } else {
+ lower_node_button.set_sensitive(false);
+ }
+ }
+}
+
+
+gboolean XmlTree::xml_tree_node_mutable(GtkTreeIter *node)
+{
+ // top-level is immutable, obviously
+ GtkTreeIter parent;
+ if (!gtk_tree_model_iter_parent(GTK_TREE_MODEL(tree->store), &parent, node)) {
+ return false;
+ }
+
+
+ // if not in base level (where namedview, defs, etc go), we're mutable
+ GtkTreeIter child;
+ if (gtk_tree_model_iter_parent(GTK_TREE_MODEL(tree->store), &child, &parent)) {
+ return true;
+ }
+
+ Inkscape::XML::Node *repr;
+ repr = sp_xmlview_tree_node_get_repr(GTK_TREE_MODEL(tree->store), node);
+ g_assert(repr);
+
+ // don't let "defs" or "namedview" disappear
+ if ( !strcmp(repr->name(),"svg:defs") ||
+ !strcmp(repr->name(),"sodipodi:namedview") ) {
+ return false;
+ }
+
+ // everyone else is okay, I guess. :)
+ return true;
+}
+
+
+
+void XmlTree::on_tree_unselect_row_disable()
+{
+ xml_text_new_button.set_sensitive(false);
+ xml_element_new_button.set_sensitive(false);
+ xml_node_delete_button.set_sensitive(false);
+ xml_node_duplicate_button.set_sensitive(false);
+ unindent_node_button.set_sensitive(false);
+ indent_node_button.set_sensitive(false);
+ raise_node_button.set_sensitive(false);
+ lower_node_button.set_sensitive(false);
+}
+
+void XmlTree::onCreateNameChanged()
+{
+ Glib::ustring text = name_entry->get_text();
+ /* TODO: need to do checking a little more rigorous than this */
+ create_button->set_sensitive(!text.empty());
+}
+
+void XmlTree::cmd_new_element_node()
+{
+ auto document = getDocument();
+ if (!document)
+ return;
+
+ // enable in-place node name editing
+ tree->renderer->property_editable() = true;
+
+ auto dummy = ""; // this element has no corresponding SP* object and its construction is silent
+ auto xml_doc = document->getReprDoc();
+ _dummy = xml_doc->createElement(dummy); // create dummy placeholder so we can have a new temporary row in xml tree
+ _node_parent = selected_repr; // remember where the node is inserted
+ selected_repr->appendChild(_dummy);
+ set_tree_select(_dummy, true); // enter in-place node name editing
+}
+
+void XmlTree::startNodeEditing(Gtk::CellEditable* cell, const Glib::ustring& path)
+{
+ if (!cell) {
+ return;
+ }
+ // remove dummy element name so user can start with an empty name
+ auto entry = dynamic_cast<Gtk::Entry *>(cell);
+ entry->get_buffer()->set_text("");
+}
+
+void XmlTree::stopNodeEditing(bool ok, const Glib::ustring& path, Glib::ustring element)
+{
+ tree->renderer->property_editable() = false;
+
+ auto document = getDocument();
+ if (!document) {
+ return;
+ }
+ // delete dummy node
+ if (_dummy) {
+ document->setXMLDialogSelectedObject(nullptr);
+
+ auto parent = _dummy->parent();
+ Inkscape::GC::release(_dummy);
+ sp_repr_unparent(_dummy);
+ if (parent) {
+ auto parentobject = document->getObjectByRepr(parent);
+ if (parentobject) {
+ parentobject->requestDisplayUpdate(SP_OBJECT_CHILD_MODIFIED_FLAG);
+ }
+ }
+
+ _dummy = nullptr;
+ }
+
+ Util::trim(element);
+ if (!ok || element.empty() || !_node_parent) {
+ return;
+ }
+
+ Inkscape::XML::Document* xml_doc = document->getReprDoc();
+ // Extract tag name
+ {
+ static auto const extract_tagname = Glib::Regex::create("^<?\\s*(\\w[\\w:\\-\\d]*)");
+ Glib::MatchInfo match_info;
+ extract_tagname->match(element, match_info);
+ if (!match_info.matches()) {
+ return;
+ }
+ element = match_info.fetch(1);
+ }
+
+ // prepend "svg:" namespace if none is given
+ if (element.find(':') == Glib::ustring::npos) {
+ element = "svg:" + element;
+ }
+ auto repr = xml_doc->createElement(element.c_str());
+ Inkscape::GC::release(repr);
+ _node_parent->appendChild(repr);
+ set_dt_select(repr);
+ set_tree_select(repr, true);
+ _node_parent = nullptr;
+
+ DocumentUndo::done(document, Q_("Undo History / XML Editor|Create new element node"), INKSCAPE_ICON("dialog-xml-editor"));
+}
+
+void XmlTree::cmd_new_text_node()
+{
+ auto document = getDocument();
+ if (!document)
+ return;
+
+ g_assert(selected_repr != nullptr);
+
+ Inkscape::XML::Document *xml_doc = document->getReprDoc();
+ Inkscape::XML::Node *text = xml_doc->createTextNode("");
+ selected_repr->appendChild(text);
+
+ DocumentUndo::done(document, Q_("Undo History / XML Editor|Create new text node"), INKSCAPE_ICON("dialog-xml-editor"));
+
+ set_tree_select(text);
+ set_dt_select(text);
+}
+
+void XmlTree::cmd_duplicate_node()
+{
+ auto document = getDocument();
+ if (!document)
+ return;
+
+ g_assert(selected_repr != nullptr);
+
+ Inkscape::XML::Node *parent = selected_repr->parent();
+ Inkscape::XML::Node *dup = selected_repr->duplicate(parent->document());
+ parent->addChild(dup, selected_repr);
+
+ DocumentUndo::done(document, Q_("Undo History / XML Editor|Duplicate node"), INKSCAPE_ICON("dialog-xml-editor"));
+
+ GtkTreeIter node;
+
+ if (sp_xmlview_tree_get_repr_node(SP_XMLVIEW_TREE(tree), dup, &node)) {
+ GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree));
+ gtk_tree_selection_select_iter(selection, &node);
+ }
+}
+
+void XmlTree::cmd_delete_node()
+{
+ auto document = getDocument();
+ if (!document)
+ return;
+
+ g_assert(selected_repr != nullptr);
+
+ document->setXMLDialogSelectedObject(nullptr);
+
+ Inkscape::XML::Node *parent = selected_repr->parent();
+
+ sp_repr_unparent(selected_repr);
+
+ if (parent) {
+ auto parentobject = document->getObjectByRepr(parent);
+ if (parentobject) {
+ parentobject->requestDisplayUpdate(SP_OBJECT_CHILD_MODIFIED_FLAG);
+ }
+ }
+
+ DocumentUndo::done(document, Q_("Undo History / XML Editor|Delete node"), INKSCAPE_ICON("dialog-xml-editor"));
+}
+
+void XmlTree::cmd_raise_node()
+{
+ auto document = getDocument();
+ if (!document)
+ return;
+
+ g_assert(selected_repr != nullptr);
+
+ Inkscape::XML::Node *parent = selected_repr->parent();
+ g_return_if_fail(parent != nullptr);
+ g_return_if_fail(parent->firstChild() != selected_repr);
+
+ Inkscape::XML::Node *ref = nullptr;
+ Inkscape::XML::Node *before = parent->firstChild();
+ while (before && (before->next() != selected_repr)) {
+ ref = before;
+ before = before->next();
+ }
+
+ parent->changeOrder(selected_repr, ref);
+
+ DocumentUndo::done(document, Q_("Undo History / XML Editor|Raise node"), INKSCAPE_ICON("dialog-xml-editor"));
+
+ set_tree_select(selected_repr);
+ set_dt_select(selected_repr);
+}
+
+
+
+void XmlTree::cmd_lower_node()
+{
+ auto document = getDocument();
+ if (!document)
+ return;
+
+ g_assert(selected_repr != nullptr);
+
+ g_return_if_fail(selected_repr->next() != nullptr);
+ Inkscape::XML::Node *parent = selected_repr->parent();
+
+ parent->changeOrder(selected_repr, selected_repr->next());
+
+ DocumentUndo::done(document, Q_("Undo History / XML Editor|Lower node"), INKSCAPE_ICON("dialog-xml-editor"));
+
+ set_tree_select(selected_repr);
+ set_dt_select(selected_repr);
+}
+
+void XmlTree::cmd_indent_node()
+{
+ auto document = getDocument();
+ if (!document)
+ return;
+
+ Inkscape::XML::Node *repr = selected_repr;
+ g_assert(repr != nullptr);
+
+ Inkscape::XML::Node *parent = repr->parent();
+ g_return_if_fail(parent != nullptr);
+ g_return_if_fail(parent->firstChild() != repr);
+
+ Inkscape::XML::Node* prev = parent->firstChild();
+ while (prev && (prev->next() != repr)) {
+ prev = prev->next();
+ }
+ g_return_if_fail(prev != nullptr);
+ g_return_if_fail(prev->type() == Inkscape::XML::NodeType::ELEMENT_NODE);
+
+ Inkscape::XML::Node* ref = nullptr;
+ if (prev->firstChild()) {
+ for( ref = prev->firstChild() ; ref->next() ; ref = ref->next() ){};
+ }
+
+ parent->removeChild(repr);
+ prev->addChild(repr, ref);
+
+ DocumentUndo::done(document, Q_("Undo History / XML Editor|Indent node"), INKSCAPE_ICON("dialog-xml-editor"));
+ set_tree_select(repr);
+ set_dt_select(repr);
+
+} // end of cmd_indent_node()
+
+
+
+void XmlTree::cmd_unindent_node()
+{
+ auto document = getDocument();
+ if (!document)
+ return;
+
+ Inkscape::XML::Node *repr = selected_repr;
+ g_assert(repr != nullptr);
+
+ Inkscape::XML::Node *parent = repr->parent();
+ g_return_if_fail(parent);
+ Inkscape::XML::Node *grandparent = parent->parent();
+ g_return_if_fail(grandparent);
+
+ parent->removeChild(repr);
+ grandparent->addChild(repr, parent);
+
+ DocumentUndo::done(document, Q_("Undo History / XML Editor|Unindent node"), INKSCAPE_ICON("dialog-xml-editor"));
+
+ set_tree_select(repr);
+ set_dt_select(repr);
+
+} // end of cmd_unindent_node()
+
+/** Returns true iff \a item is suitable to be included in the selection, in particular
+ whether it has a bounding box in the desktop coordinate system for rendering resize handles.
+
+ Descendents of <defs> nodes (markers etc.) return false, for example.
+*/
+bool XmlTree::in_dt_coordsys(SPObject const &item)
+{
+ /* Definition based on sp_item_i2doc_affine. */
+ SPObject const *child = &item;
+ while (is<SPItem>(child)) {
+ SPObject const * const parent = child->parent;
+ if (parent == nullptr) {
+ g_assert(is<SPRoot>(child));
+ if (child == &item) {
+ // item is root
+ return false;
+ }
+ return true;
+ }
+ child = parent;
+ }
+ g_assert(!is<SPRoot>(child));
+ return false;
+}
+
+void XmlTree::desktopReplaced() {
+ // subdialog does not receive desktopReplace calls, we need to propagate desktop change
+ if (attributes) {
+ attributes->setDesktop(getDesktop());
+ }
+}
+
+void XmlTree::setSyntaxStyle(Inkscape::UI::Syntax::XMLStyles const &new_style)
+{
+ tree->formatter->setStyle(new_style);
+}
+
+} // namespace Inkscape::UI::Dialog
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :