diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 11:50:49 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 11:50:49 +0000 |
commit | c853ffb5b2f75f5a889ed2e3ef89b818a736e87a (patch) | |
tree | 7d13a0883bb7936b84d6ecdd7bc332b41ed04bee /src/ui/dialog/xml-tree.cpp | |
parent | Initial commit. (diff) | |
download | inkscape-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.cpp | 928 |
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 : |