/**
 * @file
 * 3d box aux toolbar
 */
/* Authors:
 *   MenTaLguY <mental@rydia.net>
 *   Lauris Kaplinski <lauris@kaplinski.com>
 *   bulia byak <buliabyak@users.sf.net>
 *   Frank Felfe <innerspace@iname.com>
 *   John Cliff <simarilius@yahoo.com>
 *   David Turner <novalis@gnu.org>
 *   Josh Andler <scislac@scislac.com>
 *   Jon A. Cruz <jon@joncruz.org>
 *   Maximilian Albert <maximilian.albert@gmail.com>
 *   Tavmjong Bah <tavmjong@free.fr>
 *   Abhishek Sharma
 *   Kris De Gussem <Kris.DeGussem@gmail.com>
 *
 * Copyright (C) 2004 David Turner
 * Copyright (C) 2003 MenTaLguY
 * Copyright (C) 1999-2011 authors
 * Copyright (C) 2001-2002 Ximian, Inc.
 *
 * Released under GNU GPL, read the file 'COPYING' for more information
 */

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include "ui/widget/spinbutton.h"
#include <glibmm/i18n.h>

#include "toolbox.h"
#include "box3d-toolbar.h"

#include "../desktop.h"
#include "../desktop-handles.h"
#include "document-undo.h"
#include "../verbs.h"
#include "../inkscape.h"
#include "../selection-chemistry.h"
#include "../selection.h"

#include "../ege-adjustment-action.h"
#include "../ege-output-action.h"
#include "../ege-select-one-action.h"
#include "../ink-action.h"
#include "../ink-comboboxentry-action.h"

#include "../widgets/button.h"
#include "../widgets/spinbutton-events.h"
#include "../widgets/spw-utilities.h"
#include "../widgets/widget-sizes.h"
#include "../xml/node-event-vector.h"
#include "../xml/repr.h"
#include "ui/uxmanager.h"

#include "../ui/icon-names.h"

#include "../helper/unit-menu.h"
#include "../helper/units.h"
#include "../helper/unit-tracker.h"

#include "../pen-context.h"

#include "../box3d-context.h"
#include "../box3d.h"

using Inkscape::UnitTracker;
using Inkscape::UI::UXManager;
using Inkscape::DocumentUndo;
using Inkscape::UI::ToolboxFactory;
using Inkscape::UI::PrefPusher;

//########################
//##       3D Box       ##
//########################

// normalize angle so that it lies in the interval [0,360]
static double box3d_normalize_angle (double a) {
    double angle = a + ((int) (a/360.0))*360;
    if (angle < 0) {
        angle += 360.0;
    }
    return angle;
}

static void box3d_set_button_and_adjustment(Persp3D *persp,
                                            Proj::Axis axis,
                                            GtkAdjustment *adj,
                                            GtkAction *act,
                                            GtkToggleAction *tact)
{
    // TODO: Take all selected perspectives into account but don't touch the state button if not all of them
    //       have the same state (otherwise a call to box3d_vp_z_state_changed() is triggered and the states
    //       are reset).
    bool is_infinite = !persp3d_VP_is_finite(persp->perspective_impl, axis);

    if (is_infinite) {
        gtk_toggle_action_set_active(tact, TRUE);
        gtk_action_set_sensitive(act, TRUE);

        double angle = persp3d_get_infinite_angle(persp, axis);
        if (angle != Geom::infinity()) { // FIXME: We should catch this error earlier (don't show the spinbutton at all)
            gtk_adjustment_set_value(adj, box3d_normalize_angle(angle));
        }
    } else {
        gtk_toggle_action_set_active(tact, FALSE);
        gtk_action_set_sensitive(act, FALSE);
    }
}

static void box3d_resync_toolbar(Inkscape::XML::Node *persp_repr, GObject *data)
{
    if (!persp_repr) {
        g_print ("No perspective given to box3d_resync_toolbar().\n");
        return;
    }

    GtkWidget *tbl = GTK_WIDGET(data);
    GtkAdjustment *adj = 0;
    GtkAction *act = 0;
    GtkToggleAction *tact = 0;
    Persp3D *persp = persp3d_get_from_repr(persp_repr);
    if (!persp) {
        // Hmm, is it an error if this happens?
        return;
    }
    {
        adj = GTK_ADJUSTMENT(g_object_get_data(G_OBJECT(tbl), "box3d_angle_x"));
        act = GTK_ACTION(g_object_get_data(G_OBJECT(tbl), "box3d_angle_x_action"));
        tact = &INK_TOGGLE_ACTION(g_object_get_data(G_OBJECT(tbl), "box3d_vp_x_state_action"))->action;

        box3d_set_button_and_adjustment(persp, Proj::X, adj, act, tact);
    }
    {
        adj = GTK_ADJUSTMENT(g_object_get_data(G_OBJECT(tbl), "box3d_angle_y"));
        act = GTK_ACTION(g_object_get_data(G_OBJECT(tbl), "box3d_angle_y_action"));
        tact = &INK_TOGGLE_ACTION(g_object_get_data(G_OBJECT(tbl), "box3d_vp_y_state_action"))->action;

        box3d_set_button_and_adjustment(persp, Proj::Y, adj, act, tact);
    }
    {
        adj = GTK_ADJUSTMENT(g_object_get_data(G_OBJECT(tbl), "box3d_angle_z"));
        act = GTK_ACTION(g_object_get_data(G_OBJECT(tbl), "box3d_angle_z_action"));
        tact = &INK_TOGGLE_ACTION(g_object_get_data(G_OBJECT(tbl), "box3d_vp_z_state_action"))->action;

        box3d_set_button_and_adjustment(persp, Proj::Z, adj, act, tact);
    }
}

static void box3d_persp_tb_event_attr_changed(Inkscape::XML::Node *repr,
                                              gchar const * /*name*/,
                                              gchar const * /*old_value*/,
                                              gchar const * /*new_value*/,
                                              bool /*is_interactive*/,
                                              gpointer data)
{
    GtkWidget *tbl = GTK_WIDGET(data);

    // quit if run by the attr_changed or selection changed listener
    if (g_object_get_data(G_OBJECT(tbl), "freeze")) {
        return;
    }

    // set freeze so that it can be caught in box3d_angle_z_value_changed() (to avoid calling
    // SPDocumentUndo::maybeDone() when the document is undo insensitive)
    g_object_set_data(G_OBJECT(tbl), "freeze", GINT_TO_POINTER(TRUE));

    // TODO: Only update the appropriate part of the toolbar
//    if (!strcmp(name, "inkscape:vp_z")) {
        box3d_resync_toolbar(repr, G_OBJECT(tbl));
//    }

    Persp3D *persp = persp3d_get_from_repr(repr);
    persp3d_update_box_reprs(persp);

    g_object_set_data(G_OBJECT(tbl), "freeze", GINT_TO_POINTER(FALSE));
}

static Inkscape::XML::NodeEventVector box3d_persp_tb_repr_events =
{
    NULL, /* child_added */
    NULL, /* child_removed */
    box3d_persp_tb_event_attr_changed,
    NULL, /* content_changed */
    NULL  /* order_changed */
};

/**
 *  \param selection Should not be NULL.
 */
// FIXME: This should rather be put into persp3d-reference.cpp or something similar so that it reacts upon each
//        Change of the perspective, and not of the current selection (but how to refer to the toolbar then?)
static void box3d_toolbox_selection_changed(Inkscape::Selection *selection, GObject *tbl)
{
    // Here the following should be done: If all selected boxes have finite VPs in a certain direction,
    // disable the angle entry fields for this direction (otherwise entering a value in them should only
    // update the perspectives with infinite VPs and leave the other ones untouched).

    Inkscape::XML::Node *persp_repr = NULL;
    purge_repr_listener(tbl, tbl);

    SPItem *item = selection->singleItem();
    if (item && SP_IS_BOX3D(item)) {
        // FIXME: Also deal with multiple selected boxes
        SPBox3D *box = SP_BOX3D(item);
        Persp3D *persp = box3d_get_perspective(box);
        persp_repr = persp->getRepr();
        if (persp_repr) {
            g_object_set_data(tbl, "repr", persp_repr);
            Inkscape::GC::anchor(persp_repr);
            sp_repr_add_listener(persp_repr, &box3d_persp_tb_repr_events, tbl);
            sp_repr_synthesize_events(persp_repr, &box3d_persp_tb_repr_events, tbl);
        }

        inkscape_active_document()->setCurrentPersp3D(persp3d_get_from_repr(persp_repr));
        Inkscape::Preferences *prefs = Inkscape::Preferences::get();
        prefs->setString("/tools/shapes/3dbox/persp", persp_repr->attribute("id"));

        g_object_set_data(tbl, "freeze", GINT_TO_POINTER(TRUE));
        box3d_resync_toolbar(persp_repr, tbl);
        g_object_set_data(tbl, "freeze", GINT_TO_POINTER(FALSE));
    }
}

static void box3d_angle_value_changed(GtkAdjustment *adj, GObject *dataKludge, Proj::Axis axis)
{
    SPDesktop *desktop = static_cast<SPDesktop *>(g_object_get_data( dataKludge, "desktop" ));
    SPDocument *document = sp_desktop_document(desktop);

    // quit if run by the attr_changed or selection changed listener
    if (g_object_get_data( dataKludge, "freeze" )) {
        return;
    }

    // in turn, prevent listener from responding
    g_object_set_data(dataKludge, "freeze", GINT_TO_POINTER(TRUE));

    std::list<Persp3D *> sel_persps = sp_desktop_selection(desktop)->perspList();
    if (sel_persps.empty()) {
        // this can happen when the document is created; we silently ignore it
        return;
    }
    Persp3D *persp = sel_persps.front();

    persp->perspective_impl->tmat.set_infinite_direction (axis,
            gtk_adjustment_get_value(adj));
    persp->updateRepr();

    // TODO: use the correct axis here, too
    DocumentUndo::maybeDone(document, "perspangle", SP_VERB_CONTEXT_3DBOX, _("3D Box: Change perspective (angle of infinite axis)"));

    g_object_set_data( dataKludge, "freeze", GINT_TO_POINTER(FALSE) );
}


static void box3d_angle_x_value_changed(GtkAdjustment *adj, GObject *dataKludge)
{
    box3d_angle_value_changed(adj, dataKludge, Proj::X);
}

static void box3d_angle_y_value_changed(GtkAdjustment *adj, GObject *dataKludge)
{
    box3d_angle_value_changed(adj, dataKludge, Proj::Y);
}

static void box3d_angle_z_value_changed(GtkAdjustment *adj, GObject *dataKludge)
{
    box3d_angle_value_changed(adj, dataKludge, Proj::Z);
}


static void box3d_vp_state_changed( GtkToggleAction *act, GtkAction * /*box3d_angle*/, Proj::Axis axis )
{
    // TODO: Take all selected perspectives into account
    std::list<Persp3D *> sel_persps = sp_desktop_selection(inkscape_active_desktop())->perspList();
    if (sel_persps.empty()) {
        // this can happen when the document is created; we silently ignore it
        return;
    }
    Persp3D *persp = sel_persps.front();

    bool set_infinite = gtk_toggle_action_get_active(act);
    persp3d_set_VP_state (persp, axis, set_infinite ? Proj::VP_INFINITE : Proj::VP_FINITE);
}

static void box3d_vp_x_state_changed( GtkToggleAction *act, GtkAction *box3d_angle )
{
    box3d_vp_state_changed(act, box3d_angle, Proj::X);
}

static void box3d_vp_y_state_changed( GtkToggleAction *act, GtkAction *box3d_angle )
{
    box3d_vp_state_changed(act, box3d_angle, Proj::Y);
}

static void box3d_vp_z_state_changed( GtkToggleAction *act, GtkAction *box3d_angle )
{
    box3d_vp_state_changed(act, box3d_angle, Proj::Z);
}

void box3d_toolbox_prep(SPDesktop *desktop, GtkActionGroup* mainActions, GObject* holder)
{
    Inkscape::Preferences *prefs = Inkscape::Preferences::get();
    EgeAdjustmentAction* eact = 0;
    SPDocument *document = sp_desktop_document (desktop);
    Persp3DImpl *persp_impl = document->getCurrentPersp3DImpl();

    EgeAdjustmentAction* box3d_angle_x = 0;
    EgeAdjustmentAction* box3d_angle_y = 0;
    EgeAdjustmentAction* box3d_angle_z = 0;

    /* Angle X */
    {
        gchar const* labels[] = { 0, 0, 0, 0, 0, 0, 0 };
        gdouble values[] = {-90, -60, -30, 0, 30, 60, 90};
        eact = create_adjustment_action( "3DBoxAngleXAction",
                                         _("Angle in X direction"), _("Angle X:"),
                                         // Translators: PL is short for 'perspective line'
                                         _("Angle of PLs in X direction"),
                                         "/tools/shapes/3dbox/box3d_angle_x", 30,
                                         GTK_WIDGET(desktop->canvas), NULL, holder, TRUE, "altx-box3d",
                                         -360.0, 360.0, 1.0, 10.0,
                                         labels, values, G_N_ELEMENTS(labels),
                                         box3d_angle_x_value_changed );
        gtk_action_group_add_action( mainActions, GTK_ACTION(eact) );
        g_object_set_data( holder, "box3d_angle_x_action", eact );
        box3d_angle_x = eact;
    }

    if (!persp_impl || !persp3d_VP_is_finite(persp_impl, Proj::X)) {
        gtk_action_set_sensitive( GTK_ACTION(eact), TRUE );
    } else {
        gtk_action_set_sensitive( GTK_ACTION(eact), FALSE );
    }


    /* VP X state */
    {
        InkToggleAction* act = ink_toggle_action_new( "3DBoxVPXStateAction",
                                                      // Translators: VP is short for 'vanishing point'
                                                      _("State of VP in X direction"),
                                                      _("Toggle VP in X direction between 'finite' and 'infinite' (=parallel)"),
                                                      INKSCAPE_ICON("perspective-parallel"),
                                                      Inkscape::ICON_SIZE_DECORATION );
        gtk_action_group_add_action( mainActions, GTK_ACTION( act ) );
        g_object_set_data( holder, "box3d_vp_x_state_action", act );
        g_signal_connect_after( G_OBJECT(act), "toggled", G_CALLBACK(box3d_vp_x_state_changed), box3d_angle_x );
        gtk_action_set_sensitive( GTK_ACTION(box3d_angle_x), !prefs->getBool("/tools/shapes/3dbox/vp_x_state", true) );
        gtk_toggle_action_set_active( GTK_TOGGLE_ACTION(act), prefs->getBool("/tools/shapes/3dbox/vp_x_state", true) );
    }

    /* Angle Y */
    {
        gchar const* labels[] = { 0, 0, 0, 0, 0, 0, 0 };
        gdouble values[] = {-90, -60, -30, 0, 30, 60, 90};
        eact = create_adjustment_action( "3DBoxAngleYAction",
                                         _("Angle in Y direction"), _("Angle Y:"),
                                         // Translators: PL is short for 'perspective line'
                                         _("Angle of PLs in Y direction"),
                                         "/tools/shapes/3dbox/box3d_angle_y", 30,
                                         GTK_WIDGET(desktop->canvas), NULL, holder, FALSE, NULL,
                                         -360.0, 360.0, 1.0, 10.0,
                                         labels, values, G_N_ELEMENTS(labels),
                                         box3d_angle_y_value_changed );
        gtk_action_group_add_action( mainActions, GTK_ACTION(eact) );
        g_object_set_data( holder, "box3d_angle_y_action", eact );
        box3d_angle_y = eact;
    }

    if (!persp_impl || !persp3d_VP_is_finite(persp_impl, Proj::Y)) {
        gtk_action_set_sensitive( GTK_ACTION(eact), TRUE );
    } else {
        gtk_action_set_sensitive( GTK_ACTION(eact), FALSE );
    }

    /* VP Y state */
    {
        InkToggleAction* act = ink_toggle_action_new( "3DBoxVPYStateAction",
                                                      // Translators: VP is short for 'vanishing point'
                                                      _("State of VP in Y direction"),
                                                      _("Toggle VP in Y direction between 'finite' and 'infinite' (=parallel)"),
                                                      INKSCAPE_ICON("perspective-parallel"),
                                                      Inkscape::ICON_SIZE_DECORATION );
        gtk_action_group_add_action( mainActions, GTK_ACTION( act ) );
        g_object_set_data( holder, "box3d_vp_y_state_action", act );
        g_signal_connect_after( G_OBJECT(act), "toggled", G_CALLBACK(box3d_vp_y_state_changed), box3d_angle_y );
        gtk_action_set_sensitive( GTK_ACTION(box3d_angle_y), !prefs->getBool("/tools/shapes/3dbox/vp_y_state", true) );
        gtk_toggle_action_set_active( GTK_TOGGLE_ACTION(act), prefs->getBool("/tools/shapes/3dbox/vp_y_state", true) );
    }

    /* Angle Z */
    {
        gchar const* labels[] = { 0, 0, 0, 0, 0, 0, 0 };
        gdouble values[] = {-90, -60, -30, 0, 30, 60, 90};
        eact = create_adjustment_action( "3DBoxAngleZAction",
                                         _("Angle in Z direction"), _("Angle Z:"),
                                         // Translators: PL is short for 'perspective line'
                                         _("Angle of PLs in Z direction"),
                                         "/tools/shapes/3dbox/box3d_angle_z", 30,
                                         GTK_WIDGET(desktop->canvas), NULL, holder, FALSE, NULL,
                                         -360.0, 360.0, 1.0, 10.0,
                                         labels, values, G_N_ELEMENTS(labels),
                                         box3d_angle_z_value_changed );
        gtk_action_group_add_action( mainActions, GTK_ACTION(eact) );
        g_object_set_data( holder, "box3d_angle_z_action", eact );
        box3d_angle_z = eact;
    }

    if (!persp_impl || !persp3d_VP_is_finite(persp_impl, Proj::Z)) {
        gtk_action_set_sensitive( GTK_ACTION(eact), TRUE );
    } else {
        gtk_action_set_sensitive( GTK_ACTION(eact), FALSE );
    }

    /* VP Z state */
    {
        InkToggleAction* act = ink_toggle_action_new( "3DBoxVPZStateAction",
                                                      // Translators: VP is short for 'vanishing point'
                                                      _("State of VP in Z direction"),
                                                      _("Toggle VP in Z direction between 'finite' and 'infinite' (=parallel)"),
                                                      INKSCAPE_ICON("perspective-parallel"),
                                                      Inkscape::ICON_SIZE_DECORATION );
        gtk_action_group_add_action( mainActions, GTK_ACTION( act ) );
        g_object_set_data( holder, "box3d_vp_z_state_action", act );
        g_signal_connect_after( G_OBJECT(act), "toggled", G_CALLBACK(box3d_vp_z_state_changed), box3d_angle_z );
        gtk_action_set_sensitive( GTK_ACTION(box3d_angle_z), !prefs->getBool("/tools/shapes/3dbox/vp_z_state", true) );
        gtk_toggle_action_set_active( GTK_TOGGLE_ACTION(act), prefs->getBool("/tools/shapes/3dbox/vp_z_state", true) );
    }

    sigc::connection *connection = new sigc::connection(
        sp_desktop_selection(desktop)->connectChanged(sigc::bind(sigc::ptr_fun(box3d_toolbox_selection_changed), G_OBJECT(holder)))
       );
    g_signal_connect(holder, "destroy", G_CALLBACK(delete_connection), connection);
    g_signal_connect(holder, "destroy", G_CALLBACK(purge_repr_listener), holder);
}

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