diff options
Diffstat (limited to 'src/ui/dialog/xml-tree.cpp')
-rw-r--r-- | src/ui/dialog/xml-tree.cpp | 952 |
1 files changed, 952 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..1c82a11 --- /dev/null +++ b/src/ui/dialog/xml-tree.cpp @@ -0,0 +1,952 @@ +// 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 + * + * 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 <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 "ui/dialog-events.h" +#include "ui/icon-loader.h" +#include "ui/icon-names.h" +#include "ui/tools/tool-base.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) +{ + paned.child_property_resize(*paned.get_child1()) = vertical; + assert(paned.child_property_resize(*paned.get_child2())); + paned.set_orientation(vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL); +} +} // namespace + +namespace Inkscape { +namespace UI { +namespace Dialog { + +XmlTree::XmlTree() + : DialogBase("/dialogs/xml/", "XMLEditor") + , blocked(0) + , _message_stack(nullptr) + , _message_context(nullptr) + , selected_attr(0) + , selected_repr(nullptr) + , tree(nullptr) + , status("") + , new_window(nullptr) + , _updating(false) + , node_box(Gtk::ORIENTATION_VERTICAL) + , status_box(Gtk::ORIENTATION_HORIZONTAL) +{ + Gtk::Box *contents = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL)); + status.set_halign(Gtk::ALIGN_START); + status.set_valign(Gtk::ALIGN_CENTER); + status.set_size_request(1, -1); + status.set_markup(""); + status.set_line_wrap(true); + status.get_style_context()->add_class("inksmall"); + status_box.pack_start( status, TRUE, TRUE, 0); + contents->pack_start(_paned, true, true, 0); + contents->set_valign(Gtk::ALIGN_FILL); + contents->child_property_fill(_paned); + + _paned.set_vexpand(true); + _message_stack = std::make_shared<Inkscape::MessageStack>(); + _message_context = std::unique_ptr<Inkscape::MessageContext>(new Inkscape::MessageContext(_message_stack)); + _message_changed_connection = _message_stack->connectChanged( + sigc::bind(sigc::ptr_fun(_set_status_message), GTK_WIDGET(status.gobj()))); + + /* 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") ); + + tree_toolbar.set_toolbar_style(Gtk::TOOLBAR_ICONS); + + auto xml_element_new_icon = Gtk::manage(sp_get_icon_image("xml-element-new", Gtk::ICON_SIZE_LARGE_TOOLBAR)); + + xml_element_new_button.set_icon_widget(*xml_element_new_icon); + xml_element_new_button.set_label(_("New element node")); + xml_element_new_button.set_tooltip_text(_("New element node")); + xml_element_new_button.set_sensitive(false); + tree_toolbar.add(xml_element_new_button); + + auto xml_text_new_icon = Gtk::manage(sp_get_icon_image("xml-text-new", Gtk::ICON_SIZE_LARGE_TOOLBAR)); + + xml_text_new_button.set_icon_widget(*xml_text_new_icon); + xml_text_new_button.set_label(_("New text node")); + xml_text_new_button.set_tooltip_text(_("New text node")); + xml_text_new_button.set_sensitive(false); + tree_toolbar.add(xml_text_new_button); + + auto xml_node_duplicate_icon = Gtk::manage(sp_get_icon_image("xml-node-duplicate", Gtk::ICON_SIZE_LARGE_TOOLBAR)); + + xml_node_duplicate_button.set_icon_widget(*xml_node_duplicate_icon); + xml_node_duplicate_button.set_label(_("Duplicate node")); + xml_node_duplicate_button.set_tooltip_text(_("Duplicate node")); + xml_node_duplicate_button.set_sensitive(false); + tree_toolbar.add(xml_node_duplicate_button); + + tree_toolbar.add(separator); + + auto xml_node_delete_icon = Gtk::manage(sp_get_icon_image("xml-node-delete", Gtk::ICON_SIZE_LARGE_TOOLBAR)); + + xml_node_delete_button.set_icon_widget(*xml_node_delete_icon); + xml_node_delete_button.set_label(_("Delete node")); + xml_node_delete_button.set_tooltip_text(_("Delete node")); + xml_node_delete_button.set_sensitive(false); + tree_toolbar.add(xml_node_delete_button); + + tree_toolbar.add(separator2); + + auto format_indent_less_icon = Gtk::manage(sp_get_icon_image("format-indent-less", Gtk::ICON_SIZE_LARGE_TOOLBAR)); + + unindent_node_button.set_icon_widget(*format_indent_less_icon); + unindent_node_button.set_label(_("Unindent node")); + unindent_node_button.set_tooltip_text(_("Unindent node")); + unindent_node_button.set_sensitive(false); + tree_toolbar.add(unindent_node_button); + + auto format_indent_more_icon = Gtk::manage(sp_get_icon_image("format-indent-more", Gtk::ICON_SIZE_LARGE_TOOLBAR)); + + indent_node_button.set_icon_widget(*format_indent_more_icon); + indent_node_button.set_label(_("Indent node")); + indent_node_button.set_tooltip_text(_("Indent node")); + indent_node_button.set_sensitive(false); + tree_toolbar.add(indent_node_button); + + auto go_up_icon = Gtk::manage(sp_get_icon_image("go-up", Gtk::ICON_SIZE_LARGE_TOOLBAR)); + + raise_node_button.set_icon_widget(*go_up_icon); + raise_node_button.set_label(_("Raise node")); + raise_node_button.set_tooltip_text(_("Raise node")); + raise_node_button.set_sensitive(false); + tree_toolbar.add(raise_node_button); + + auto go_down_icon = Gtk::manage(sp_get_icon_image("go-down", Gtk::ICON_SIZE_LARGE_TOOLBAR)); + + lower_node_button.set_icon_widget(*go_down_icon); + lower_node_button.set_label(_("Lower node")); + lower_node_button.set_tooltip_text(_("Lower node")); + lower_node_button.set_sensitive(false); + tree_toolbar.add(lower_node_button); + + node_box.pack_start(tree_toolbar, FALSE, TRUE, 0); + + Gtk::ScrolledWindow *tree_scroller = new Gtk::ScrolledWindow(); + tree_scroller->set_overlay_scrolling(false); + tree_scroller->set_policy( Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC ); + tree_scroller->set_shadow_type(Gtk::SHADOW_IN); + tree_scroller->add(*Gtk::manage(Glib::wrap(GTK_WIDGET(tree)))); + fix_inner_scroll(tree_scroller); + + node_box.pack_start(*Gtk::manage(tree_scroller)); + + node_box.pack_end(status_box, false, false, 2); + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + bool attrtoggler = prefs->getBool("/dialogs/xml/attrtoggler", true); + bool dir = prefs->getBool("/dialogs/xml/vertical", true); + attributes = new AttrDialog(); + _paned.set_wide_handle(true); + _paned.pack1(node_box, false, false); + /* attributes */ + Gtk::Box *actionsbox = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL)); + actionsbox->set_valign(Gtk::ALIGN_START); + Gtk::Label *attrtogglerlabel = Gtk::manage(new Gtk::Label(_("Show attributes"))); + attrtogglerlabel->set_margin_end(5); + _attrswitch.get_style_context()->add_class("inkswitch"); + _attrswitch.get_style_context()->add_class("rawstyle"); + _attrswitch.property_active() = attrtoggler; + _attrswitch.property_active().signal_changed().connect(sigc::mem_fun(*this, &XmlTree::_attrtoggler)); + attrtogglerlabel->get_style_context()->add_class("inksmall"); + actionsbox->pack_start(*attrtogglerlabel, Gtk::PACK_SHRINK); + actionsbox->pack_start(_attrswitch, Gtk::PACK_SHRINK); + Gtk::RadioButton::Group group; + Gtk::RadioButton *_horizontal = Gtk::manage(new Gtk::RadioButton()); + Gtk::RadioButton *_vertical = Gtk::manage(new Gtk::RadioButton()); + _horizontal->set_image_from_icon_name(INKSCAPE_ICON("horizontal")); + _vertical->set_image_from_icon_name(INKSCAPE_ICON("vertical")); + _horizontal->set_group(group); + _vertical->set_group(group); + _vertical->set_active(dir); + _vertical->signal_toggled().connect(sigc::bind(sigc::mem_fun(*this, &XmlTree::_toggleDirection), _vertical)); + _horizontal->property_draw_indicator() = false; + _vertical->property_draw_indicator() = false; + actionsbox->pack_end(*_horizontal, false, false, 0); + actionsbox->pack_end(*_vertical, false, false, 0); + _paned.pack2(*attributes, true, false); + paned_set_vertical(_paned, dir); + contents->pack_start(*actionsbox, false, false, 0); + /* Signal handlers */ + GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(tree)); + _selection_changed = g_signal_connect (G_OBJECT(selection), "changed", G_CALLBACK (on_tree_select_row), this); + _tree_move = g_signal_connect_after( G_OBJECT(tree), "tree_move", G_CALLBACK(after_tree_move), this); + + 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); + set_size_request(320, -1); + 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)); + + tree_reset_context(); + pack_start(*Gtk::manage(contents), true, true); +} + +void XmlTree::_resized() +{ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + + prefs->setInt("/dialogs/xml/panedpos", _paned.property_position()); +} + +void XmlTree::_toggleDirection(Gtk::RadioButton *vertical) +{ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + bool dir = vertical->get_active(); + prefs->setBool("/dialogs/xml/vertical", dir); + paned_set_vertical(_paned, dir); + prefs->setInt("/dialogs/xml/panedpos", _paned.property_position()); +} + +void XmlTree::_attrtoggler() +{ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + bool attrtoggler = !prefs->getBool("/dialogs/xml/attrtoggler", true); + prefs->setBool("/dialogs/xml/attrtoggler", attrtoggler); + if (attrtoggler) { + attributes->show(); + } else { + attributes->hide(); + } +} + +XmlTree::~XmlTree () +{ + // disconnect signals, they can fire after we leave destructor when 'tree' gets deleted + GtkTreeSelection* selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(tree)); + g_signal_handler_disconnect(G_OBJECT(selection), _selection_changed); + g_signal_handler_disconnect(G_OBJECT(tree), _tree_move); + + unsetDocument(); + _message_changed_connection.disconnect(); +} + +/** + * Sets the XML status bar when the tree is selected. + */ +void XmlTree::tree_reset_context() +{ + _message_context->set(Inkscape::NORMAL_MESSAGE, + _("<b>Click</b> to select nodes, <b>drag</b> to rearrange.")); +} + +void XmlTree::unsetDocument() +{ + document_uri_set_connection.disconnect(); + if (deferred_on_tree_select_row_id != 0) { + g_source_destroy(g_main_context_find_source_by_id(nullptr, deferred_on_tree_select_row_id)); + deferred_on_tree_select_row_id = 0; + } +} + +void XmlTree::documentReplaced() +{ + unsetDocument(); + if (auto document = getDocument()) { + // TODO: Why is this a document property? + document->setXMLDialogSelectedObject(nullptr); + + document_uri_set_connection = + document->connectFilenameSet(sigc::bind(sigc::ptr_fun(&on_document_uri_set), document)); + on_document_uri_set(document->getDocumentFilename(), document); + 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) +{ + 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); + gtk_tree_view_set_cursor(GTK_TREE_VIEW(tree), path, NULL, false); + 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 = dynamic_cast<SPGroup const *>(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 (SP_IS_GROUP(object->parent)) { + getDesktop()->layerManager().setCurrentLayer(object->parent); + } + + getSelection()->set(SP_ITEM(object)); + } + + document->setXMLDialogSelectedObject(object); + blocked--; +} + + +void XmlTree::on_tree_select_row(GtkTreeSelection *selection, gpointer data) +{ + XmlTree *self = static_cast<XmlTree *>(data); + + if (self->blocked || !self->getDesktop()) { + return; + } + + // Defer the update after all events have been processed. Allows skipping + // of invalid intermediate selection states, like the automatic next row + // selection after `gtk_tree_store_remove`. + if (self->deferred_on_tree_select_row_id == 0) { + self->deferred_on_tree_select_row_id = // + g_idle_add(XmlTree::deferred_on_tree_select_row, data); + } +} + +gboolean XmlTree::deferred_on_tree_select_row(gpointer data) +{ + XmlTree *self = static_cast<XmlTree *>(data); + + self->deferred_on_tree_select_row_id = 0; + + GtkTreeIter iter; + GtkTreeModel *model; + + if (self->selected_repr) { + Inkscape::GC::release(self->selected_repr); + self->selected_repr = nullptr; + } + + GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(self->tree)); + + if (!gtk_tree_selection_get_selected (selection, &model, &iter)) { + // Nothing selected, update widgets + self->propagate_tree_select(nullptr); + self->set_dt_select(nullptr); + self->on_tree_unselect_row_disable(); + return FALSE; + } + + Inkscape::XML::Node *repr = sp_xmlview_tree_node_get_repr(model, &iter); + g_assert(repr != nullptr); + + + self->selected_repr = repr; + Inkscape::GC::anchor(self->selected_repr); + + self->propagate_tree_select(self->selected_repr); + + self->set_dt_select(self->selected_repr); + + self->tree_reset_context(); + + self->on_tree_select_row_enable(&iter); + + return FALSE; +} + + +void XmlTree::after_tree_move(SPXMLViewTree * /*tree*/, gpointer value, gpointer data) +{ + XmlTree *self = static_cast<XmlTree *>(data); + guint val = GPOINTER_TO_UINT(value); + + if (val) { + DocumentUndo::done(self->getDocument(), Q_("Undo History / XML dialog|Drag XML subtree"), INKSCAPE_ICON("dialog-xml-editor")); + } else { + DocumentUndo::cancel(self->getDocument()); + } +} + +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::on_document_uri_set(gchar const * /*uri*/, SPDocument * /*document*/) +{ +/* + * Seems to be no way to set the title on a docked dialog +*/ +} + +gboolean XmlTree::quit_on_esc (GtkWidget *w, GdkEventKey *event, GObject */*tbl*/) +{ + switch (Inkscape::UI::Tools::get_latin_keyval (event)) { + case GDK_KEY_Escape: // defocus + gtk_widget_destroy(w); + return TRUE; + case GDK_KEY_Return: // create + case GDK_KEY_KP_Enter: + gtk_widget_destroy(w); + return TRUE; + } + return FALSE; +} + +void XmlTree::cmd_new_element_node() +{ + auto document = getDocument(); + if (!document) + return; + + Gtk::Dialog dialog; + Gtk::Entry entry; + + dialog.get_content_area()->pack_start(entry); + dialog.add_button("Cancel", Gtk::RESPONSE_CANCEL); + dialog.add_button("Create", Gtk::RESPONSE_OK); + dialog.show_all(); + + int result = dialog.run(); + if (result == Gtk::RESPONSE_OK) { + Glib::ustring new_name = entry.get_text(); + if (!new_name.empty()) { + Inkscape::XML::Document *xml_doc = document->getReprDoc(); + Inkscape::XML::Node *new_repr; + new_repr = xml_doc->createElement(new_name.c_str()); + Inkscape::GC::release(new_repr); + selected_repr->appendChild(new_repr); + set_tree_select(new_repr); + set_dt_select(new_repr); + + DocumentUndo::done(document, Q_("Undo History / XML dialog|Create new element node"), INKSCAPE_ICON("dialog-xml-editor")); + } + } +} // end of cmd_new_element_node() + + +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 dialog|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 dialog|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 dialog|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 dialog|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 dialog|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 dialog|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 dialog|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 (SP_IS_ITEM(child)) { + SPObject const * const parent = child->parent; + if (parent == nullptr) { + g_assert(SP_IS_ROOT(child)); + if (child == &item) { + // item is root + return false; + } + return true; + } + child = parent; + } + g_assert(!SP_IS_ROOT(child)); + return false; +} + +void XmlTree::desktopReplaced() { + // subdialog does not receive desktopReplace calls, we need to propagate desktop change + if (attributes) { + attributes->setDesktop(getDesktop()); + } +} + +} +} +} + +/* + 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 : |