/**
 * @file Object properties dialog.
 */
/* 
 * Inkscape, an Open Source vector graphics editor
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 *
 * Copyright (C) 2012 Kris De Gussem <Kris.DeGussem@gmail.com>
 * c++ version based on former C-version (GPL v2) with authors:
 *   Lauris Kaplinski <lauris@kaplinski.com>
 *   bulia byak <buliabyak@users.sf.net>
 *   Johan Engelen <goejendaagh@zonnet.nl>
 *   Abhishek Sharma
 */

#include "object-properties.h"
#include "widgets/sp-attribute-widget.h"
#include "desktop-handles.h"
#include "document.h"
#include "document-undo.h"
#include "verbs.h"
#include "inkscape.h"
#include "selection.h"
#include "desktop.h"
#include "sp-item.h"
#include "sp-image.h"
#include "xml/repr.h"
#include <glibmm/i18n.h>

#if WITH_GTKMM_3_0
# include <gtkmm/grid.h>
#else
# include <gtkmm/table.h>
#endif


namespace Inkscape {
namespace UI {
namespace Dialog {

ObjectProperties::ObjectProperties()
    : UI::Widget::Panel ("", "/dialogs/object/", SP_VERB_DIALOG_ITEM)
    , _blocked (false)
    , _current_item(NULL)
    , _label_id(_("_ID:"), 1)
    , _label_label(_("_Label:"), 1)
    , _label_title(_("_Title:"), 1)
    , _label_image_rendering(_("_Image Rendering:"), 1)
    , _cb_hide(_("_Hide"), 1)
    , _cb_lock(_("L_ock"), 1)
    , _attr_table(Gtk::manage(new SPAttributeTable()))
    , _desktop(NULL)
{
    //initialize labels for the table at the bottom of the dialog
    _int_attrs.push_back("onclick");
    _int_attrs.push_back("onmouseover");
    _int_attrs.push_back("onmouseout");
    _int_attrs.push_back("onmousedown");
    _int_attrs.push_back("onmouseup");
    _int_attrs.push_back("onmousemove");
    _int_attrs.push_back("onfocusin");
    _int_attrs.push_back("onfocusout");
    _int_attrs.push_back("onload");

    _int_labels.push_back("onclick:");
    _int_labels.push_back("onmouseover:");
    _int_labels.push_back("onmouseout:");
    _int_labels.push_back("onmousedown:");
    _int_labels.push_back("onmouseup:");
    _int_labels.push_back("onmousemove:");
    _int_labels.push_back("onfocusin:");
    _int_labels.push_back("onfocusout:");
    _int_labels.push_back("onload:");

    _desktop_changed_connection = _desktop_tracker.connectDesktopChanged(
        sigc::mem_fun(*this, &ObjectProperties::_setTargetDesktop)
    );
    _desktop_tracker.connect(GTK_WIDGET(gobj()));

    _init();
}

ObjectProperties::~ObjectProperties()
{
    _subselection_changed_connection.disconnect();
    _selection_changed_connection.disconnect();
    _desktop_changed_connection.disconnect();
    _desktop_tracker.disconnect();
}

void ObjectProperties::_init()
{
    Gtk::Box *contents = _getContents();
    contents->set_spacing(0);

#if WITH_GTKMM_3_0
    Gtk::Grid *grid_top = Gtk::manage(new Gtk::Grid());
    grid_top->set_row_spacing(4);
    grid_top->set_column_spacing(0);
#else
    Gtk::Table *grid_top = Gtk::manage(new Gtk::Table(4, 4));
    grid_top->set_row_spacings(4);
    grid_top->set_col_spacings(0);
#endif

    grid_top->set_border_width(4);

    contents->pack_start(*grid_top, false, false, 0);


    /* Create the label for the object id */
    _label_id.set_label(_label_id.get_label() + " ");
    _label_id.set_alignment(1, 0.5);

#if WITH_GTKMM_3_0
    _label_id.set_valign(Gtk::ALIGN_CENTER);
    grid_top->attach(_label_id, 0, 0, 1, 1);
#else
    grid_top->attach(_label_id, 0, 1, 0, 1,
                     Gtk::SHRINK | Gtk::FILL,
                     Gtk::AttachOptions(), 0, 0 );
#endif


    /* Create the entry box for the object id */
    _entry_id.set_tooltip_text(_("The id= attribute (only letters, digits, and the characters .-_: allowed)"));
    _entry_id.set_max_length(64);

#if WITH_GTKMM_3_0
    _entry_id.set_valign(Gtk::ALIGN_CENTER);
    grid_top->attach(_entry_id, 1, 0, 1, 1);
#else
    grid_top->attach(_entry_id, 1, 2, 0, 1,
                     Gtk::EXPAND | Gtk::FILL,
                     Gtk::AttachOptions(), 0, 0 );
#endif

    _label_id.set_mnemonic_widget(_entry_id);

    // pressing enter in the id field is the same as clicking Set:
    _entry_id.signal_activate().connect(sigc::mem_fun(this, &ObjectProperties::_labelChanged));
    // focus is in the id field initially:
    _entry_id.grab_focus();


    /* Create the label for the object label */
    _label_label.set_label(_label_label.get_label() + " ");
    _label_label.set_alignment(1, 0.5);

#if WITH_GTKMM_3_0
    _label_label.set_valign(Gtk::ALIGN_CENTER);
    grid_top->attach(_label_label, 0, 1, 1, 1);
#else
    grid_top->attach(_label_label, 0, 1, 1, 2,
                     Gtk::SHRINK | Gtk::FILL,
                     Gtk::AttachOptions(), 0, 0 );
#endif


    /* Create the entry box for the object label */
    _entry_label.set_tooltip_text(_("A freeform label for the object"));
    _entry_label.set_max_length(256);

#if WITH_GTKMM_3_0
    _entry_label.set_hexpand();
    _entry_label.set_valign(Gtk::ALIGN_CENTER);
    grid_top->attach(_entry_label, 1, 1, 1, 1);
#else
    grid_top->attach(_entry_label, 1, 2, 1, 2,
                     Gtk::EXPAND | Gtk::FILL,
                     Gtk::AttachOptions(), 0, 0 );
#endif

    _label_label.set_mnemonic_widget(_entry_label);

    // pressing enter in the label field is the same as clicking Set:
    _entry_label.signal_activate().connect(sigc::mem_fun(this, &ObjectProperties::_labelChanged));


    /* Create the label for the object title */
    _label_title.set_label(_label_title.get_label() + " ");
    _label_title.set_alignment (1, 0.5);

#if WITH_GTKMM_3_0
    _label_title.set_valign(Gtk::ALIGN_CENTER);
    grid_top->attach(_label_title, 0, 2, 1, 1);
#else
    grid_top->attach(_label_title, 0, 1, 2, 3,
                     Gtk::SHRINK | Gtk::FILL,
                     Gtk::AttachOptions(), 0, 0 );
#endif

    /* Create the entry box for the object title */
    _entry_title.set_sensitive (FALSE);
    _entry_title.set_max_length (256);

#if WITH_GTKMM_3_0
    _entry_title.set_hexpand();
    _entry_title.set_valign(Gtk::ALIGN_CENTER);
    grid_top->attach(_entry_title, 1, 2, 1, 1);
#else
    grid_top->attach(_entry_title, 1, 2, 2, 3,
                     Gtk::EXPAND | Gtk::FILL,
                     Gtk::AttachOptions(), 0, 0 );
#endif

    _label_title.set_mnemonic_widget(_entry_title);
    // pressing enter in the label field is the same as clicking Set:
    _entry_title.signal_activate().connect(sigc::mem_fun(this, &ObjectProperties::_labelChanged));

    /* Create the frame for the object description */
    Gtk::Label *label_desc = Gtk::manage(new Gtk::Label(_("_Description:"), 1));
    UI::Widget::Frame *frame_desc = Gtk::manage(new UI::Widget::Frame("", FALSE));
    frame_desc->set_label_widget(*label_desc);
    frame_desc->set_padding (0,0,0,0);
    contents->pack_start(*frame_desc, true, true, 0);

    /* Create the text view box for the object description */
    _ft_description.set_border_width(4);
    _ft_description.set_sensitive(FALSE);
    frame_desc->add(_ft_description);
    _ft_description.set_shadow_type(Gtk::SHADOW_IN);

    _tv_description.set_wrap_mode(Gtk::WRAP_WORD);
    _tv_description.get_buffer()->set_text("");
    _ft_description.add(_tv_description);
    _tv_description.add_mnemonic_label(*label_desc);

    /* Image rendering */
    /* Create the label for the object ImageRendering */
    _label_image_rendering.set_label(_label_image_rendering.get_label() + " ");
    _label_image_rendering.set_alignment(1, 0.5);

#if WITH_GTKMM_3_0
    _label_image_rendering.set_valign(Gtk::ALIGN_CENTER);
    grid_top->attach(_label_image_rendering, 0, 3, 1, 1);
#else
    grid_top->attach(_label_image_rendering, 0, 1, 3, 4,
                     Gtk::SHRINK | Gtk::FILL,
                     Gtk::AttachOptions(), 0, 0 );
#endif

    /* Create the combo box text for the 'image-rendering' property  */
    _combo_image_rendering.append( "auto" );
    _combo_image_rendering.append( "optimizeQuality" );
    _combo_image_rendering.append( "optimizeSpeed" );
    _combo_image_rendering.set_tooltip_text(_("The 'image-rendering' property can influence how a bitmap is up-scaled:\n\t'auto' no preference;\n\t'optimizeQuality' smooth;\n\t'optimizeSpeed' blocky.\nNote that this behaviour is not defined in the SVG 1.1 specification and not all browsers follow this interpretation."));

#if WITH_GTKMM_3_0
    _combo_image_rendering.set_valign(Gtk::ALIGN_CENTER);
    grid_top->attach(_combo_image_rendering, 1, 3, 1, 1);
#else
    grid_top->attach(_combo_image_rendering, 1, 2, 3, 4,
                     Gtk::EXPAND | Gtk::FILL,
                     Gtk::AttachOptions(), 0, 0 );
#endif

    _label_image_rendering.set_mnemonic_widget(_combo_image_rendering);

    _combo_image_rendering.signal_changed().connect(
        sigc::mem_fun(this, &ObjectProperties::_imageRenderingChanged)
    );

    /* Check boxes */
    Gtk::HBox *hb_checkboxes = Gtk::manage(new Gtk::HBox());
    contents->pack_start(*hb_checkboxes, FALSE, FALSE, 0);

#if WITH_GTKMM_3_0
    Gtk::Grid *grid_cb = Gtk::manage(new Gtk::Grid());
    grid_cb->set_row_homogeneous();
    grid_cb->set_column_homogeneous(true);
#else
    Gtk::Table *grid_cb = Gtk::manage(new Gtk::Table(1, 2, true));
#endif

    grid_cb->set_border_width(4);
    hb_checkboxes->pack_start(*grid_cb, true, true, 0);

    /* Hide */
    _cb_hide.set_tooltip_text (_("Check to make the object invisible"));

#if WITH_GTKMM_3_0
    _cb_hide.set_hexpand();
    _cb_hide.set_valign(Gtk::ALIGN_CENTER);
    grid_cb->attach(_cb_hide, 0, 0, 1, 1);
#else
    grid_cb->attach(_cb_hide, 0, 1, 0, 1,
                       Gtk::EXPAND | Gtk::FILL,
                       Gtk::AttachOptions(), 0, 0 );
#endif

    _cb_hide.signal_toggled().connect(sigc::mem_fun(this, &ObjectProperties::_hiddenToggled));

    /* Lock */
    // TRANSLATORS: "Lock" is a verb here
    _cb_lock.set_tooltip_text(_("Check to make the object insensitive (not selectable by mouse)"));

#if WITH_GTKMM_3_0
    _cb_lock.set_hexpand();
    _cb_lock.set_valign(Gtk::ALIGN_CENTER);
    grid_cb->attach(_cb_lock, 1, 0, 1, 1);
#else
    grid_cb->attach(_cb_lock, 1, 2, 0, 1,
                    Gtk::EXPAND | Gtk::FILL,
                    Gtk::AttachOptions(), 0, 0 );
#endif

    _cb_lock.signal_toggled().connect(sigc::mem_fun(this, &ObjectProperties::_sensitivityToggled));


    /* Button for setting the object's id, label, title and description. */
    Gtk::Button *btn_set = Gtk::manage(new Gtk::Button(_("_Set"), 1));
#if WITH_GTKMM_3_0
    btn_set->set_hexpand();
    btn_set->set_valign(Gtk::ALIGN_CENTER);
    grid_cb->attach(*btn_set, 2, 0, 1, 1);
#else
    grid_cb->attach(*btn_set, 2, 3, 0, 1,
                    Gtk::EXPAND | Gtk::FILL,
                    Gtk::AttachOptions(), 0, 0 );
#endif

    btn_set->signal_clicked().connect(sigc::mem_fun(this, &ObjectProperties::_labelChanged));

    /* Create the frame for interactivity options */
    Gtk::Label *label_interactivity = Gtk::manage(new Gtk::Label(_("_Interactivity"), 1));
    _exp_interactivity.set_label_widget(*label_interactivity);
    contents->pack_start(_exp_interactivity, FALSE, FALSE, 0);

    show_all();
    update();
}

void ObjectProperties::update()
{
    if (_blocked || !_desktop) {
        return;
    }
    if (SP_ACTIVE_DESKTOP != _desktop) {
        return;
    }

    Inkscape::Selection *selection = sp_desktop_selection(SP_ACTIVE_DESKTOP);
    Gtk::Box *contents = _getContents();

    if (!selection->singleItem()) {
        contents->set_sensitive (false);
        _current_item = NULL;
        //no selection anymore or multiple objects selected, means that we need
        //to close the connections to the previously selected object
        _attr_table->clear();
        return;
    } else {
        contents->set_sensitive (true);
    }
    
    SPItem *item = selection->singleItem();
    if (_current_item == item)
    {
        //otherwise we would end up wasting resources through the modify selection
        //callback when moving an object (endlessly setting the labels and recreating _attr_table)
        return;
    }
    _blocked = true;
    
    _cb_lock.set_active(item->isLocked());           /* Sensitive */
    _cb_hide.set_active(item->isExplicitlyHidden()); /* Hidden */
    
    if (item->cloned) {
        /* ID */
        _entry_id.set_text("");
        _entry_id.set_sensitive(FALSE);
        _label_id.set_text(_("Ref"));

        /* Label */
        _entry_label.set_text("");
        _entry_label.set_sensitive(FALSE);
        _label_label.set_text(_("Ref"));

    } else {
        SPObject *obj = static_cast<SPObject*>(item);

        /* ID */
        _entry_id.set_text(obj->getId());
        _entry_id.set_sensitive(TRUE);
        _label_id.set_markup_with_mnemonic(_("_ID:") + Glib::ustring(" "));

        /* Label */
        _entry_label.set_text(obj->defaultLabel());
        _entry_label.set_sensitive(TRUE);

        /* Title */
        gchar *title = obj->title();
        if (title) {
            _entry_title.set_text(title);
            g_free(title);
        }
        else {
            _entry_title.set_text("");
        }
        _entry_title.set_sensitive(TRUE);

        /* Image Rendering */
        if (SP_IS_IMAGE(item)) {
            _combo_image_rendering.show();
            _label_image_rendering.show();
            char const *str = obj->getStyleProperty( "image-rendering", "auto" );
            if (strcmp( str, "auto" ) == 0) {
                _combo_image_rendering.set_active(0);
            } else if (strcmp(str, "optimizeQuality") == 0) {
                _combo_image_rendering.set_active(1);
            } else {
                _combo_image_rendering.set_active(2);
            }
        } else {
            _combo_image_rendering.hide();
            _combo_image_rendering.unset_active();
            _label_image_rendering.hide();
        }

        /* Description */
        gchar *desc = obj->desc();
        if (desc) {
            _tv_description.get_buffer()->set_text(desc);
            g_free(desc);
        } else {
            _tv_description.get_buffer()->set_text("");
        }
        _ft_description.set_sensitive(TRUE);
        
        if (_current_item == NULL) {
            _attr_table->set_object(obj, _int_labels, _int_attrs, (GtkWidget*) _exp_interactivity.gobj());
        } else {
            _attr_table->change_object(obj);
        }
        _attr_table->show_all();
    }
    _current_item = item;
    _blocked = false;
}

void ObjectProperties::_labelChanged()
{
    if (_blocked) {
        return;
    }
    
    SPItem *item = sp_desktop_selection(SP_ACTIVE_DESKTOP)->singleItem();
    g_return_if_fail (item != NULL);

    _blocked = true;

    /* Retrieve the label widget for the object's id */
    gchar *id = g_strdup(_entry_id.get_text().c_str());
    g_strcanon(id, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.:", '_');
    if (strcmp(id, item->getId()) == 0) {
        _label_id.set_markup_with_mnemonic(_("_ID:"));
    } else if (!*id || !isalnum (*id)) {
        _label_id.set_text(_("Id invalid! "));
    } else if (SP_ACTIVE_DOCUMENT->getObjectById(id) != NULL) {
        _label_id.set_text(_("Id exists! "));
    } else {
        SPException ex;
        _label_id.set_markup_with_mnemonic(_("_ID:"));
        SP_EXCEPTION_INIT(&ex);
        item->setAttribute("id", id, &ex);
        DocumentUndo::done(SP_ACTIVE_DOCUMENT, SP_VERB_DIALOG_ITEM, _("Set object ID"));
    }
    g_free(id);

    /* Retrieve the label widget for the object's label */
    Glib::ustring label = _entry_label.get_text();

    /* Give feedback on success of setting the drawing object's label
     * using the widget's label text
     */
    SPObject *obj = static_cast<SPObject*>(item);
    if (label.compare(obj->defaultLabel())) {
        obj->setLabel(label.c_str());
        DocumentUndo::done(SP_ACTIVE_DOCUMENT, SP_VERB_DIALOG_ITEM,
                _("Set object label"));
    }

    /* Retrieve the title */
    if (obj->setTitle(_entry_title.get_text().c_str())) {
        DocumentUndo::done(SP_ACTIVE_DOCUMENT, SP_VERB_DIALOG_ITEM,
                _("Set object title"));
    }

    /* Retrieve the description */
    Gtk::TextBuffer::iterator start, end;
    _tv_description.get_buffer()->get_bounds(start, end);
    Glib::ustring desc = _tv_description.get_buffer()->get_text(start, end, TRUE);
    if (obj->setDesc(desc.c_str())) {
        DocumentUndo::done(SP_ACTIVE_DOCUMENT, SP_VERB_DIALOG_ITEM,
                _("Set object description"));
    }
    
    _blocked = false;
}

void ObjectProperties::_imageRenderingChanged()
{
    if (_blocked) {
        return;
    }
    
    SPItem *item = sp_desktop_selection(SP_ACTIVE_DESKTOP)->singleItem();
    g_return_if_fail (item != NULL);

    _blocked = true;

    Glib::ustring scale = _combo_image_rendering.get_active_text();

    // We should unset if the parent computed value is auto and the desired value is auto.
    SPCSSAttr *css = sp_repr_css_attr_new();
    sp_repr_css_set_property(css, "image-rendering", scale.c_str());
    Inkscape::XML::Node *image_node = item->getRepr();
    if( image_node ) {
        sp_repr_css_change(image_node, css, "style");
    }
    sp_repr_css_attr_unref( css );
        
    _blocked = false;
}

void ObjectProperties::_sensitivityToggled()
{
    if (_blocked) {
        return;
    }

    SPItem *item = sp_desktop_selection(SP_ACTIVE_DESKTOP)->singleItem();
    g_return_if_fail(item != NULL);

    _blocked = true;
    item->setLocked(_cb_lock.get_active());
    DocumentUndo::done(SP_ACTIVE_DOCUMENT, SP_VERB_DIALOG_ITEM,
                       _cb_lock.get_active() ? _("Lock object") : _("Unlock object"));
    _blocked = false;
}

void ObjectProperties::_hiddenToggled()
{
    if (_blocked) {
        return;
    }

    SPItem *item = sp_desktop_selection(SP_ACTIVE_DESKTOP)->singleItem();
    g_return_if_fail(item != NULL);

    _blocked = true;
    item->setExplicitlyHidden(_cb_hide.get_active());
    DocumentUndo::done(SP_ACTIVE_DOCUMENT, SP_VERB_DIALOG_ITEM,
               _cb_hide.get_active() ? _("Hide object") : _("Unhide object"));
    _blocked = false;
}

void ObjectProperties::_setDesktop(SPDesktop *desktop)
{
    Panel::setDesktop(desktop);
    _desktop_tracker.setBase(desktop);
}

void ObjectProperties::_setTargetDesktop(SPDesktop *desktop)
{
    if (this->_desktop != desktop) {
        if (this->_desktop) {
            _subselection_changed_connection.disconnect();
            _selection_changed_connection.disconnect();
        }
        this->_desktop = desktop;
        if (desktop && desktop->selection) {
            _selection_changed_connection = desktop->selection->connectChanged(
                sigc::hide(sigc::mem_fun(*this, &ObjectProperties::update))
            );
            _subselection_changed_connection = desktop->connectToolSubselectionChanged(
                sigc::hide(sigc::mem_fun(*this, &ObjectProperties::update))
            );
        }
        update();
    }
}

}
}
}

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