// SPDX-License-Identifier: GPL-2.0-or-later /** * @file * XML editor. */ /* Authors: * Lauris Kaplinski * MenTaLguY * bulia byak * Johan Engelen * David Turner * Jon A. Cruz * 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 #include #include #include #include #include #include #include #include #include #include #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(_builder, "pane")) , xml_element_new_button(get_widget(_builder, "new-elem")) , xml_text_new_button(get_widget(_builder, "new-text")) , xml_node_delete_button(get_widget(_builder, "del")) , xml_node_duplicate_button(get_widget(_builder, "dup")) , unindent_node_button(get_widget(_builder, "unindent")) , indent_node_button(get_widget(_builder, "indent")) , lower_node_button(get_widget(_builder, "lower")) , raise_node_button(get_widget(_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(_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(); 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(_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(_builder, "layout-btn"); popup.set_has_tooltip(); popup.signal_query_tooltip().connect([=](int x, int y, bool kbd, const Glib::RefPtr& 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(_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(_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(item)->signal_activate().connect([=](){ set_layout(layout); }); } _layout = static_cast(prefs->getIntLimited("/dialogs/xml/layout", Auto, Auto, Vertical)); static_cast(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(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(object->parent)) { getDesktop()->layerManager().setCurrentLayer(object->parent); } getSelection()->set(cast(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(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("^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 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(child)) { SPObject const * const parent = child->parent; if (parent == nullptr) { g_assert(is(child)); if (child == &item) { // item is root return false; } return true; } child = parent; } g_assert(!is(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 :