/**
 * @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, read the file 'COPYING' for more information
 *
 */

#include "xml-tree.h"
#include "widgets/icon.h"
#include <gdk/gdkkeysyms.h>
#include <glibmm/i18n.h>
#include <gtkmm/stock.h>

#include "desktop.h"

#include "ui/dialog-events.h"
#include "document.h"
#include "document-undo.h"
#include "ui/tools/tool-base.h"
#include "helper/window.h"
#include "inkscape.h"
#include "ui/interface.h"
#include "macros.h"
#include "message-context.h"
#include "message-stack.h"
#include "preferences.h"
#include "selection.h"
#include "shortcuts.h"
#include "sp-root.h"
#include "sp-string.h"
#include "sp-tspan.h"
#include "ui/icon-names.h"
#include "verbs.h"
#include "widgets/icon.h"

#include "widgets/sp-xmlview-attr-list.h"
#include "widgets/sp-xmlview-content.h"
#include "widgets/sp-xmlview-tree.h"

namespace Inkscape {
namespace UI {
namespace Dialog {

XmlTree::XmlTree (void) :
    UI::Widget::Panel ("", "/dialogs/xml/", SP_VERB_DIALOG_XML_EDITOR),
    blocked (0),
    _message_stack (NULL),
    _message_context (NULL),
    current_desktop (NULL),
    current_document (NULL),
    selected_attr (0),
    selected_repr (NULL),
    tree (NULL),
    attributes (NULL),
    content (NULL),
    attr_name (),
    status (""),
    tree_toolbar(),
    xml_element_new_button ( _("New element node")),
    xml_text_new_button ( _("New text node")),
    xml_node_delete_button ( Q_("nodeAsInXMLdialogTooltip|Delete node")),
    xml_node_duplicate_button ( _("Duplicate node")),
    unindent_node_button(),
    indent_node_button(),
    raise_node_button(),
    lower_node_button(),
    attr_toolbar(),
    xml_attribute_delete_button (_("Delete attribute")),
    text_container (),
    attr_container (),
#if WITH_GTKMM_3_0
    attr_subpaned_container(Gtk::ORIENTATION_VERTICAL),
#else
    attr_subpaned_container(),
#endif
    set_attr (_("Set")),
    new_window(NULL)
{

    SPDesktop *desktop = SP_ACTIVE_DESKTOP;
    if (!desktop) {
        return;
    }

    Gtk::Box *contents = _getContents();
    contents->set_spacing(0);
    contents->set_size_request(320, 260);

    status.set_alignment( 0.0, 0.5);
    status.set_size_request(1, -1);
    status.set_markup("");
#if WITH_GTKMM_3_0
    status.set_line_wrap(true);
#endif
    status_box.pack_start( status, TRUE, TRUE, 0);
    contents->pack_end(status_box, false, false, 2);

    paned.set_position(256);
    contents->pack_start(paned, TRUE, TRUE, 0);

    _message_stack = new Inkscape::MessageStack();
    _message_context = 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 */
    paned.pack1(left_box);

    tree = SP_XMLVIEW_TREE(sp_xmlview_tree_new(NULL, NULL, NULL));
    gtk_widget_set_tooltip_text( GTK_WIDGET(tree), _("Drag to reorder nodes") );

    tree_toolbar.set_toolbar_style(Gtk::TOOLBAR_ICONS);
    xml_element_new_button.set_icon_widget(*Gtk::manage(Glib::wrap(
            sp_icon_new (Inkscape::ICON_SIZE_LARGE_TOOLBAR, INKSCAPE_ICON("xml-element-new")))) );
    xml_element_new_button.set_tooltip_text(_("New element node"));
    xml_element_new_button.set_sensitive(false);
    tree_toolbar.add(xml_element_new_button);

    xml_text_new_button.set_icon_widget(*Gtk::manage(Glib::wrap(
            sp_icon_new (Inkscape::ICON_SIZE_LARGE_TOOLBAR, INKSCAPE_ICON("xml-text-new")))));
    xml_text_new_button.set_tooltip_text(_("New text node"));
    xml_text_new_button.set_sensitive(false);
    tree_toolbar.add(xml_text_new_button);

    xml_node_duplicate_button.set_icon_widget(*Gtk::manage(Glib::wrap(
            sp_icon_new (Inkscape::ICON_SIZE_LARGE_TOOLBAR, INKSCAPE_ICON("xml-node-duplicate")))));
    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);

    xml_node_delete_button.set_icon_widget(*Gtk::manage(Glib::wrap(
            sp_icon_new (Inkscape::ICON_SIZE_LARGE_TOOLBAR, INKSCAPE_ICON("xml-node-delete")))));
    xml_node_delete_button.set_tooltip_text(Q_("nodeAsInXMLdialogTooltip|Delete node"));
    xml_node_delete_button.set_sensitive(false);
    tree_toolbar.add(xml_node_delete_button);

    tree_toolbar.add(separator2);

    unindent_node_button.set_icon_widget(*Gtk::manage(Glib::wrap(
            sp_icon_new (Inkscape::ICON_SIZE_LARGE_TOOLBAR, INKSCAPE_ICON("format-indent-less")))));
    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);

    indent_node_button.set_icon_widget(*Gtk::manage(Glib::wrap(
            sp_icon_new (Inkscape::ICON_SIZE_LARGE_TOOLBAR, INKSCAPE_ICON("format-indent-more")))));
    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);

    raise_node_button.set_icon_widget(*Gtk::manage(Glib::wrap(
            sp_icon_new (Inkscape::ICON_SIZE_LARGE_TOOLBAR, INKSCAPE_ICON("go-up")))));
    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);

    lower_node_button.set_icon_widget(*Gtk::manage(Glib::wrap(
            sp_icon_new (Inkscape::ICON_SIZE_LARGE_TOOLBAR, INKSCAPE_ICON("go-down")))));
    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);

    left_box.pack_start(tree_toolbar, FALSE, TRUE, 0);

    Gtk::ScrolledWindow *tree_scroller = new Gtk::ScrolledWindow();
    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))));

    left_box.pack_start(*tree_scroller);

    /* node view */
    paned.pack2(right_box);

    /* attributes */
    right_box.pack_start( attr_container, TRUE, TRUE, 0 );

    attributes = SP_XMLVIEW_ATTR_LIST(sp_xmlview_attr_list_new(NULL));

    attr_toolbar.set_toolbar_style(Gtk::TOOLBAR_ICONS);
    xml_attribute_delete_button.set_icon_widget(*Gtk::manage(Glib::wrap(sp_icon_new (Inkscape::ICON_SIZE_LARGE_TOOLBAR, INKSCAPE_ICON("xml-attribute-delete")))));
    xml_attribute_delete_button.set_tooltip_text(_("Delete attribute"));
    xml_attribute_delete_button.set_sensitive(false);
    attr_toolbar.add(xml_attribute_delete_button);

    attr_container.pack_start( attr_toolbar, FALSE, TRUE, 0 );
    attr_container.pack_start( attr_subpaned_container, TRUE, TRUE, 0 );

    Gtk::ScrolledWindow *attr_scroller = new Gtk::ScrolledWindow();
    attr_scroller->set_policy( Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC );
    attr_scroller->set_shadow_type(Gtk::SHADOW_IN);
    attr_scroller->set_size_request(0, 60);

    attr_subpaned_container.pack1( *attr_scroller );
    attr_scroller->add(*Gtk::manage(Glib::wrap(GTK_WIDGET(attributes))));

    attr_vbox.pack_start( attr_hbox, FALSE, TRUE, 0);

    attr_name.set_tooltip_text(_("Attribute name") ); // TRANSLATORS: "Attribute" is a noun here
    attr_name.set_width_chars (10);
    attr_hbox.pack_start( attr_name, TRUE, TRUE, 0);

    set_attr.set_sensitive(FALSE);
    attr_hbox.pack_start(set_attr, FALSE, FALSE, 0);

    Gtk::ScrolledWindow *scroller = new Gtk::ScrolledWindow();
    scroller->set_policy( Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC );
    scroller->set_shadow_type(Gtk::SHADOW_IN);

    attr_vbox.pack_start(*scroller, TRUE, TRUE, 0);

    attr_value.set_size_request(0, 60);
    attr_value.set_wrap_mode(Gtk::WRAP_CHAR);
    attr_value.set_tooltip_text( _("Attribute value") );// TRANSLATORS: "Attribute" is a noun here
    attr_value.set_editable(TRUE);
    scroller->add(attr_value);

    attr_subpaned_container.pack2( attr_vbox, FALSE, TRUE );

    /* text */
    text_container.set_policy( Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC );
    right_box.pack_start(text_container, TRUE, TRUE, 0);

    content = SP_XMLVIEW_CONTENT(sp_xmlview_content_new(NULL));
    text_container.add(*Gtk::manage(Glib::wrap(GTK_WIDGET(content))));

    /* Signal handlers */
    GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(tree));
    g_signal_connect (G_OBJECT(selection), "changed", G_CALLBACK (on_tree_select_row), this);
    g_signal_connect_after( G_OBJECT(tree), "tree_move", G_CALLBACK(after_tree_move), this);

    selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(attributes));
    g_signal_connect (G_OBJECT(selection), "changed", G_CALLBACK (on_attr_select_row), this);
    g_signal_connect( G_OBJECT(attributes), "row-value-changed", G_CALLBACK(on_attr_row_changed), 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));
    xml_attribute_delete_button.signal_clicked().connect(sigc::mem_fun(*this, &XmlTree::cmd_delete_attr));
    set_attr.signal_clicked().connect(sigc::mem_fun(*this, &XmlTree::cmd_set_attr));
    attr_name.signal_changed().connect(sigc::mem_fun(*this, &XmlTree::onNameChanged));
    attr_value.signal_key_press_event().connect(sigc::mem_fun(*this, &XmlTree::sp_xml_tree_key_press), false);

    desktopChangeConn = deskTrack.connectDesktopChanged( sigc::mem_fun(*this, &XmlTree::set_tree_desktop) );
    deskTrack.connect(GTK_WIDGET(gobj()));

    /* initial show/hide */
    show_all();

/*
    // hide() doesn't seem to work in the constructor, so moved this to present()
    text_container.hide();
    attr_container.hide();
*/

    tree_reset_context();

    g_assert(desktop != NULL);
    set_tree_desktop(desktop);

}

void XmlTree::present()
{
    text_container.hide();
    attr_container.hide();

    set_tree_select(get_dt_select());

    UI::Widget::Panel::present();
}

XmlTree::~XmlTree (void)
{
    set_tree_desktop(NULL);

    _message_changed_connection.disconnect();
    delete _message_context;
    _message_context = NULL;
    Inkscape::GC::release(_message_stack);
    _message_stack = NULL;
    _message_changed_connection.~connection();
}

void XmlTree::setDesktop(SPDesktop *desktop)
{
    Panel::setDesktop(desktop);
    deskTrack.setBase(desktop);
}

/**
 * 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."));
}


/**
 * Sets the XML status bar, depending on which attr is selected.
 */
void XmlTree::attr_reset_context(gint attr)
{
    if (attr == 0) {
        _message_context->set(Inkscape::NORMAL_MESSAGE,
                              _("<b>Click</b> attribute to edit."));
    }
    else {
        const gchar *name = g_quark_to_string(attr);
        gchar *message = g_strdup_printf(_("Attribute <b>%s</b> selected. Press <b>Ctrl+Enter</b> when done editing to commit changes."), name);
        _message_context->set(Inkscape::NORMAL_MESSAGE, message);
        g_free(message);
    }
}

bool XmlTree::sp_xml_tree_key_press(GdkEventKey *event)
{
    unsigned int shortcut = Inkscape::UI::Tools::get_group0_keyval (event) |
        ( event->state & GDK_SHIFT_MASK ?
          SP_SHORTCUT_SHIFT_MASK : 0 ) |
        ( event->state & GDK_CONTROL_MASK ?
          SP_SHORTCUT_CONTROL_MASK : 0 ) |
        ( event->state & GDK_MOD1_MASK ?
          SP_SHORTCUT_ALT_MASK : 0 );

    /* fixme: if you need to add more xml-tree-specific callbacks, you should probably upgrade
     * the sp_shortcut mechanism to take into account windows. */
    if (shortcut == (SP_SHORTCUT_CONTROL_MASK | GDK_KEY_Return)) {
        cmd_set_attr();
        return true;
    }
    return false;
}

void XmlTree::set_tree_desktop(SPDesktop *desktop)
{
    if ( desktop == current_desktop ) {
        return;
    }

    if (current_desktop) {
        sel_changed_connection.disconnect();
        document_replaced_connection.disconnect();
    }
    current_desktop = desktop;
    if (desktop) {
        sel_changed_connection = desktop->getSelection()->connectChanged(sigc::hide(sigc::mem_fun(this, &XmlTree::on_desktop_selection_changed)));
        document_replaced_connection = desktop->connectDocumentReplaced(sigc::mem_fun(this, &XmlTree::on_document_replaced));

        set_tree_document(desktop->getDocument());
    } else {
        set_tree_document(NULL);
    }

} // end of set_tree_desktop()


void XmlTree::set_tree_document(SPDocument *document)
{
    if (document == current_document) {
        return;
    }

    if (current_document) {
        document_uri_set_connection.disconnect();
    }
    current_document = document;
    if (current_document) {

        document_uri_set_connection = current_document->connectURISet(sigc::bind(sigc::ptr_fun(&on_document_uri_set), current_document));
        on_document_uri_set( current_document->getURI(), current_document );
        set_tree_repr(current_document->getReprRoot());
    } else {
        set_tree_repr(NULL);
    }
}



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(NULL);
    }

    propagate_tree_select(selected_repr);

}



void XmlTree::set_tree_select(Inkscape::XML::Node *repr)
{
    if (selected_repr) {
        Inkscape::GC::release(selected_repr);
    }

    selected_repr = repr;
    if (repr) {
        GtkTreeIter node;

        Inkscape::GC::anchor(selected_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_expand_to_path (GTK_TREE_VIEW(tree), path);
            gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(tree), path, NULL, TRUE, 0.66, 0.0);
            gtk_tree_path_free(path);

            gtk_tree_selection_select_iter(selection, &node);

        } else {
            g_message("XmlTree::set_tree_select : Couldnt 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();
        on_tree_unselect_row_hide();
    }
    propagate_tree_select(repr);
}



void XmlTree::propagate_tree_select(Inkscape::XML::Node *repr)
{
    if (repr && (repr->type() == Inkscape::XML::ELEMENT_NODE)) {
        sp_xmlview_attr_list_set_repr(attributes, repr);
    } else {
        sp_xmlview_attr_list_set_repr(attributes, NULL);
    }

    if (repr && ( repr->type() == Inkscape::XML::TEXT_NODE || repr->type() == Inkscape::XML::COMMENT_NODE || repr->type() == Inkscape::XML::PI_NODE ) ) {
        sp_xmlview_content_set_repr(content, repr);
    } else {
        sp_xmlview_content_set_repr(content, NULL);
    }
}


Inkscape::XML::Node *XmlTree::get_dt_select()
{
    if (!current_desktop) {
        return NULL;
    }
    return current_desktop->getSelection()->singleRepr();
}



void XmlTree::set_dt_select(Inkscape::XML::Node *repr)
{
    if (!current_desktop) {
        return;
    }

    Inkscape::Selection *selection = current_desktop->getSelection();

    SPObject *object;
    if (repr) {
        while ( ( repr->type() != Inkscape::XML::ELEMENT_NODE )
                && repr->parent() )
        {
            repr = repr->parent();
        } // end of while loop

        object = current_desktop->getDocument()->getObjectByRepr(repr);
    } else {
        object = NULL;
    }

    blocked++;
    if ( object && in_dt_coordsys(*object)
         && !(SP_IS_STRING(object) ||
                SP_IS_ROOT(object)     ) )
    {
            /* We cannot set selection to root or string - they are not items and selection is not
             * equipped to deal with them */
            selection->set(SP_ITEM(object));
    }
    blocked--;

} // end of set_dt_select()


/*void XmlTree::on_tree_select_row(GtkCTree *tree,
                                 GtkCTreeNode *node,
                                 gint column,
                                 gpointer data)*/
void XmlTree::on_tree_select_row(GtkTreeSelection *selection, gpointer data)
{
    XmlTree *self = static_cast<XmlTree *>(data);

    GtkTreeIter   iter;
    GtkTreeModel *model;

    if (self->selected_repr) {
        Inkscape::GC::release(self->selected_repr);
        self->selected_repr = NULL;
    }


    if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
        // Nothing selected, update widgets
        self->propagate_tree_select(NULL);
        self->set_dt_select(NULL);
        self->on_tree_unselect_row_disable();
        self->on_tree_unselect_row_hide();
        self->on_attr_unselect_row_clear_text();
        return;
    }

    Inkscape::XML::Node *repr = sp_xmlview_tree_node_get_repr(model, &iter);
    g_assert(repr != NULL);


    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);
}


void XmlTree::after_tree_move(SPXMLViewTree * /*attributes*/, gpointer value, gpointer data)
{
    XmlTree *self = static_cast<XmlTree *>(data);
    guint val = GPOINTER_TO_UINT(value);

    if (val) {
        DocumentUndo::done(self->current_document, SP_VERB_DIALOG_XML_EDITOR,
                                   _("Drag XML subtree"));
    } else {
        //DocumentUndo::cancel(self->current_document);
        /*
         * There was a problem with drag & drop,
         * data is probably not synchronized, so reload the tree
         */
        SPDocument *document = self->current_document;
        self->set_tree_document(NULL);
        self->set_tree_document(document);
    }
}

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::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::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);
        }
    }

    //on_tree_select_row_show_if_element
    if (repr->type() == Inkscape::XML::ELEMENT_NODE) {
        attr_container.show();
    } else {
        attr_container.hide();
    }

    //on_tree_select_row_show_if_text
    if ( repr->type() == Inkscape::XML::TEXT_NODE || repr->type() == Inkscape::XML::COMMENT_NODE || repr->type() == Inkscape::XML::PI_NODE ) {
        text_container.show();
    } else {
        text_container.hide();
    }

}


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);
    xml_attribute_delete_button.set_sensitive(false);
}

void XmlTree::on_tree_unselect_row_hide()
{
    attr_container.hide();
    text_container.hide();
}

void XmlTree::on_attr_select_row(GtkTreeSelection *selection, gpointer data)
{
    XmlTree *self = static_cast<XmlTree *>(data);

    GtkTreeIter   iter;
    GtkTreeModel *model;

    if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
        // Nothing selected
        self->selected_attr = 0;
        self->attr_reset_context(self->selected_attr);

        self->xml_attribute_delete_button.set_sensitive(false);
        self->on_attr_unselect_row_clear_text();
        return;
    }

    gchar *name = 0;
    gchar *value = 0;
    guint attr = 0;
    gtk_tree_model_get (model, &iter, ATTR_COL_NAME, &name, ATTR_COL_VALUE, &value, ATTR_COL_ATTR, &attr, -1);

    self->attr_name.set_text(name);
    self->attr_value.get_buffer()->set_text(value);

    self->attr_value.grab_focus ();
    self->xml_attribute_delete_button.set_sensitive(true);

    self->selected_attr = attr;
    self->attr_reset_context(self->selected_attr);

    if (name) {
        g_free(name);
    }

    if (value) {
        g_free(value);
    }
}

void XmlTree::on_attr_row_changed(SPXMLViewAttrList *attributes, const gchar * name, gpointer /*data*/)
{
    // Reselect the selected row if the data changes to refresh the attribute and value edit boxes.
    GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(attributes));
    GtkTreeIter   iter;
    GtkTreeModel *model;
    gchar *attr_name = 0;
    if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
        gtk_tree_model_get (model, &iter, 0, &attr_name, -1);
        if (gtk_list_store_iter_is_valid(GTK_LIST_STORE(model), &iter) ) {
            if (!strcmp(name, attr_name)) {
                gtk_tree_selection_unselect_all(selection);
                gtk_tree_selection_select_iter(selection, &iter);
            }
        }
    }

    if (attr_name) {
        g_free(attr_name);
        attr_name = 0;
    }
}

void XmlTree::on_attr_unselect_row_clear_text()
{
    attr_name.set_text("");
    attr_value.get_buffer()->set_text("", 0);
}

void XmlTree::onNameChanged()
{
    Glib::ustring text = attr_name.get_text();
    /* TODO: need to do checking a little more rigorous than this */
    set_attr.set_sensitive(!text.empty());
}

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_desktop_selection_changed()
{
    if (!blocked++) {
        Inkscape::XML::Node *node = get_dt_select();
        set_tree_select(node);
        if (!node) {
            on_attr_unselect_row_clear_text();
        }
    }
    blocked--;
}

void XmlTree::on_document_replaced(SPDesktop *dt, SPDocument *doc)
{
    if (current_desktop)
        sel_changed_connection.disconnect();

    sel_changed_connection = dt->getSelection()->connectChanged(sigc::hide(sigc::mem_fun(this, &XmlTree::on_desktop_selection_changed)));
    set_tree_document(doc);
}

void XmlTree::on_document_uri_set(gchar const * /*uri*/, SPDocument * /*document*/)
{
/*
 * Seems to be no way to set the title on a docked dialog
    gchar title[500];
    sp_ui_dialog_title_string(Inkscape::Verb::get(SP_VERB_DIALOG_XML_EDITOR), title);
    gchar *t = g_strdup_printf("%s: %s", document->getName(), title);
    //gtk_window_set_title(GTK_WINDOW(dlg), t);
    g_free(t);
*/
}

gboolean XmlTree::quit_on_esc (GtkWidget *w, GdkEventKey *event, GObject */*tbl*/)
{
    switch (Inkscape::UI::Tools::get_group0_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()
{
    GtkWidget *cancel, *vbox, *bbox, *sep;

    g_assert(selected_repr != NULL);

    new_window = sp_window_new(NULL, TRUE);
    gtk_container_set_border_width(GTK_CONTAINER(new_window), 4);
    gtk_window_set_title(GTK_WINDOW(new_window), _("New element node..."));
    gtk_window_set_resizable(GTK_WINDOW(new_window), FALSE);
    gtk_window_set_position(GTK_WINDOW(new_window), GTK_WIN_POS_CENTER);
    gtk_window_set_transient_for(GTK_WINDOW(new_window), GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(gobj()))));
    gtk_window_set_modal(GTK_WINDOW(new_window), TRUE);
    g_signal_connect(G_OBJECT(new_window), "destroy", gtk_main_quit, NULL);
    g_signal_connect(G_OBJECT(new_window), "key-press-event", G_CALLBACK(quit_on_esc), new_window);

#if GTK_CHECK_VERSION(3,0,0)
    vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 4);
    gtk_box_set_homogeneous(GTK_BOX(vbox), FALSE);
#else
    vbox = gtk_vbox_new(FALSE, 4);
#endif

    gtk_container_add(GTK_CONTAINER(new_window), vbox);

    name_entry = new Gtk::Entry();
    gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(name_entry->gobj()), FALSE, TRUE, 0);

#if GTK_CHECK_VERSION(3,0,0)
    sep = gtk_separator_new(GTK_ORIENTATION_HORIZONTAL);
#else
    sep = gtk_hseparator_new();
#endif

    gtk_box_pack_start(GTK_BOX(vbox), sep, FALSE, TRUE, 0);

#if GTK_CHECK_VERSION(3,0,0)
    bbox = gtk_button_box_new(GTK_ORIENTATION_HORIZONTAL);
#else
    bbox = gtk_hbutton_box_new();
#endif

    gtk_container_set_border_width(GTK_CONTAINER(bbox), 4);
    gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END);
    gtk_box_pack_start(GTK_BOX(vbox), bbox, FALSE, TRUE, 0);

    cancel = gtk_button_new_with_label(_("Cancel"));
    g_signal_connect_swapped( G_OBJECT(cancel), "clicked",
                                G_CALLBACK(gtk_widget_destroy),
                                G_OBJECT(new_window) );
    gtk_container_add(GTK_CONTAINER(bbox), cancel);

    create_button = new Gtk::Button(_("Create"));
    create_button->set_sensitive(FALSE);

    name_entry->signal_changed().connect(sigc::mem_fun(*this, &XmlTree::onCreateNameChanged));

    g_signal_connect_swapped( G_OBJECT(create_button->gobj()), "clicked",
                                G_CALLBACK(gtk_widget_destroy),
                                G_OBJECT(new_window) );
    create_button->set_can_default( TRUE );
    create_button->set_receives_default( TRUE );
    gtk_container_add(GTK_CONTAINER(bbox), GTK_WIDGET(create_button->gobj()));

    gtk_widget_show_all(GTK_WIDGET(new_window));
    //gtk_window_set_default(GTK_WINDOW(window), GTK_WIDGET(create));
    name_entry->grab_focus();

    gtk_main();

    gchar *new_name = g_strdup(name_entry->get_text().c_str());

    if (new_name) {
        Inkscape::XML::Document *xml_doc = current_document->getReprDoc();
        Inkscape::XML::Node *new_repr;
        new_repr = xml_doc->createElement(new_name);
        Inkscape::GC::release(new_repr);
        g_free(new_name);
        selected_repr->appendChild(new_repr);
        set_tree_select(new_repr);
        set_dt_select(new_repr);

        DocumentUndo::done(current_document, SP_VERB_DIALOG_XML_EDITOR,
                           _("Create new element node"));
    }

} // end of cmd_new_element_node()



void XmlTree::cmd_new_text_node()
{
    g_assert(selected_repr != NULL);

    Inkscape::XML::Document *xml_doc = current_document->getReprDoc();
    Inkscape::XML::Node *text = xml_doc->createTextNode("");
    selected_repr->appendChild(text);

    DocumentUndo::done(current_document, SP_VERB_DIALOG_XML_EDITOR,
                       _("Create new text node"));

    set_tree_select(text);
    set_dt_select(text);

    gtk_window_set_focus(GTK_WINDOW(new_window), GTK_WIDGET(content));

}

void XmlTree::cmd_duplicate_node()
{
    g_assert(selected_repr != NULL);

    Inkscape::XML::Node *parent = selected_repr->parent();
    Inkscape::XML::Node *dup = selected_repr->duplicate(parent->document());
    parent->addChild(dup, selected_repr);

    DocumentUndo::done(current_document, SP_VERB_DIALOG_XML_EDITOR,
                       _("Duplicate node"));

    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()
{
    g_assert(selected_repr != NULL);
    sp_repr_unparent(selected_repr);

    reinterpret_cast<SPObject *>(current_desktop->currentLayer())->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
    DocumentUndo::done(current_document, SP_VERB_DIALOG_XML_EDITOR,
                       Q_("nodeAsInXMLinHistoryDialog|Delete node"));
}



void XmlTree::cmd_delete_attr()
{
    g_assert(selected_repr != NULL);
    g_assert(selected_attr != 0);

    selected_repr->setAttribute(g_quark_to_string(selected_attr), NULL);

    SPObject *updated = current_document->getObjectByRepr(selected_repr);
    if (updated) {
        // force immediate update of dependant attributes
        updated->updateRepr();
    }

    DocumentUndo::done(current_document, SP_VERB_DIALOG_XML_EDITOR,
                       _("Delete attribute"));
}



void XmlTree::cmd_set_attr()
{
    g_assert(selected_repr != NULL);

    gchar *name = g_strdup(attr_name.get_text().c_str());
    gchar *value = g_strdup(attr_value.get_buffer()->get_text().c_str());

    selected_repr->setAttribute(name, value, false);

    g_free(name);
    g_free(value);

    SPObject *updated = current_document->getObjectByRepr(selected_repr);
    if (updated) {
        // force immediate update of dependant attributes
        updated->updateRepr();
    }

    reinterpret_cast<SPObject *>(current_desktop->currentLayer())->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
    DocumentUndo::done(current_document, SP_VERB_DIALOG_XML_EDITOR,
                       _("Change attribute"));

    /* TODO: actually, the row won't have been created yet.  why? */
    sp_xmlview_attr_list_select_row_by_key(attributes, name);
}


void XmlTree::cmd_raise_node()
{
    g_assert(selected_repr != NULL);


    Inkscape::XML::Node *parent = selected_repr->parent();
    g_return_if_fail(parent != NULL);
    g_return_if_fail(parent->firstChild() != selected_repr);

    Inkscape::XML::Node *ref = NULL;
    Inkscape::XML::Node *before = parent->firstChild();
    while (before && (before->next() != selected_repr)) {
        ref = before;
        before = before->next();
    }

    parent->changeOrder(selected_repr, ref);

    DocumentUndo::done(current_document, SP_VERB_DIALOG_XML_EDITOR,
                       _("Raise node"));

    set_tree_select(selected_repr);
    set_dt_select(selected_repr);
}



void XmlTree::cmd_lower_node()
{
    g_assert(selected_repr != NULL);

    g_return_if_fail(selected_repr->next() != NULL);
    Inkscape::XML::Node *parent = selected_repr->parent();

    parent->changeOrder(selected_repr, selected_repr->next());

    DocumentUndo::done(current_document, SP_VERB_DIALOG_XML_EDITOR,
                       _("Lower node"));

    set_tree_select(selected_repr);
    set_dt_select(selected_repr);
}

void XmlTree::cmd_indent_node()
{
    Inkscape::XML::Node *repr = selected_repr;
    g_assert(repr != NULL);

    Inkscape::XML::Node *parent = repr->parent();
    g_return_if_fail(parent != NULL);
    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 != NULL);
    g_return_if_fail(prev->type() == Inkscape::XML::ELEMENT_NODE);

    Inkscape::XML::Node* ref = NULL;
    if (prev->firstChild()) {
        for( ref = prev->firstChild() ; ref->next() ; ref = ref->next() ){};
    }

    parent->removeChild(repr);
    prev->addChild(repr, ref);

    DocumentUndo::done(current_document, SP_VERB_DIALOG_XML_EDITOR,
                       _("Indent node"));
    set_tree_select(repr);
    set_dt_select(repr);

} // end of cmd_indent_node()



void XmlTree::cmd_unindent_node()
{
    Inkscape::XML::Node *repr = selected_repr;
    g_assert(repr != NULL);

    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(current_document, SP_VERB_DIALOG_XML_EDITOR,
                       _("Unindent node"));
    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;
    g_return_val_if_fail(child != NULL, false);
    for(;;) {
        if (!SP_IS_ITEM(child)) {
            return false;
        }
        SPObject const * const parent = child->parent;
        if (parent == NULL) {
            break;
        }
        child = parent;
    }
    g_assert(SP_IS_ROOT(child));
    /* Relevance: Otherwise, I'm not sure whether to return true or false. */
    return true;
}

}
}
}

/*
  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 :
