/*   EXTRAITS DE LA LICENCE
	Copyright CEA, contributeurs : Luc BILLARD et Damien
	CALISTE, laboratoire L_Sim, (2001-2005)
  
	Adresse mèl :
	BILLARD, non joignable par mèl ;
	CALISTE, damien P caliste AT cea P fr.

	Ce logiciel est un programme informatique servant à visualiser des
	structures atomiques dans un rendu pseudo-3D. 

	Ce logiciel est régi par la licence CeCILL soumise au droit français et
	respectant les principes de diffusion des logiciels libres. Vous pouvez
	utiliser, modifier et/ou redistribuer ce programme sous les conditions
	de la licence CeCILL telle que diffusée par le CEA, le CNRS et l'INRIA 
	sur le site "http://www.cecill.info".

	Le fait que vous puissiez accéder à cet en-tête signifie que vous avez 
	pris connaissance de la licence CeCILL, et que vous en avez accepté les
	termes (cf. le fichier Documentation/licence.fr.txt fourni avec ce logiciel).
*/

/*   LICENCE SUM UP
	Copyright CEA, contributors : Luc BILLARD et Damien
	CALISTE, laboratoire L_Sim, (2001-2005)

	E-mail address:
	BILLARD, not reachable any more ;
	CALISTE, damien P caliste AT cea P fr.

	This software is a computer program whose purpose is to visualize atomic
	configurations in 3D.

	This software is governed by the CeCILL  license under French law and
	abiding by the rules of distribution of free software.  You can  use, 
	modify and/ or redistribute the software under the terms of the CeCILL
	license as circulated by CEA, CNRS and INRIA at the following URL
	"http://www.cecill.info". 

	The fact that you are presently reading this means that you have had
	knowledge of the CeCILL license and that you accept its terms. You can
	find a copy of this licence shipped with this software at Documentation/licence.en.txt.
*/

#include <math.h>
#include <string.h>

#include <GL/gl.h>
#include <GL/glu.h> 

#include "view.h"
#include "objectList.h"
#include <visu_tools.h>
#include <renderingMethods/iface_nodeArrayRenderer.h>
#include <renderingBackend/visu_actionInterface.h>
#include <visu_configFile.h>


#include "interactive.h"

#define FLAG_PARAMETER_OBSERVE_METHOD "opengl_observe_method"
#define DESC_PARAMETER_OBSERVE_METHOD "Choose the observe method ; integer (0: constrained mode, 1: walker mode)"

#define FLAG_PARAMETER_CAMERA_SETTINGS "opengl_prefered_camera_orientation"
#define DESC_PARAMETER_CAMERA_SETTINGS "Saved prefered camera position ; three angles, two shifts, zoom and perspective level"
static float cameraData[7];

/**
 * SECTION:interactive
 * @short_description: Gives tools to interact with the rendered
 * area.
 * 
 * <para>When one wants some interactions on the rendering area
 * (either from the mouse or from the keyboard), one should create a new
 * interactive object using visu_interactive_new(). Then, during the
 * interactive session, several modes have been implemented:
 * <itemizedlist>
 * <listitem><para>
 * #interactive_mark, is a mode where the mouse can select
 * a node and this nodes become highlighted.
 * </para></listitem>
 * <listitem><para>
 * #interactive_move, the mouse is then used to move
 * picked nodes. Moves are possible in the plane of the screen or
 * constrained along axis of the bounding box.
 * </para></listitem>
 * <listitem><para>
 * #interactive_observe, in this mode, the mouse is used
 * to change the position of camera, its orientation and its zoom
 * characteristics.
 * </para></listitem>
 * <listitem><para>
 * #interactive_pick, this mode is the most complex
 * picking mode. It can select atoms up to two references and measure
 * distances and angles.
 * </para></listitem>
 * <listitem><para>
 * #interactive_pickAndObserve, this mode is a simplified
 * mix of obervation and picking.
 * </para></listitem>
 * </itemizedlist>
 * The observe mode has two different moving algorithms that can be
 * changed using visu_interactive_class_setPreferedObserveMethod(). The
 * first, called 'constrained' (cf. #interactive_constrained)
 * corresponds to movements along parallels and meridians. When the
 * mouse is moved along x axis, the camera raotates along a
 * parallel. When the camera is moved along y axis, the camera rotate
 * along a meridian. The top is always pointing to the north pole (in
 * fact, omega is always forced to 0 in this mode). This mode has a
 * 'strange' behavior when the observer is near a pole: moving mouse
 * along x axis make the box rotates on itself. It is normal, because
 * movements on x axis is equivalent to movements on parallel and near
 * the poles, parallel are small circle around the z axis. This can be
 * unnatural in some occasion and the other mode, called 'walker' (see
 * #interactive_walker) can be used instead of the 'constrained'
 * mode. In the former, the moving is done has if the observer was a
 * walking ant on a sphere : moving the mouse along y axis makes the
 * ant go on or forward ; and x axis movements makes the ant goes on
 * its left or on it right. This is a more natural way to move the box
 * but it has the inconvient that it is hard to return in a given
 * position (omega has never the right value).
 * </para>
 */

/**
 * VisuInteractiveClass:
 * @parent: the parent.
 * @preferedObserveMethod: the prefered method to move the camera.
 * @savedCameras: the list of saved cameras.
 * @lastCamera: a pointer on the last selected camera.
 *
 * An opaque structure representing the class of #VisuInteractive objects.
 */
/**
 * VisuInteractive:
 *
 * All fields are private.
 */

struct _VisuInteractive
{
  VisuObject parent;

  /* Internal object gestion. */
  gboolean dispose_has_run;

  /* The kind of interactive (pick, observe...) */
  VisuInteractiveId id;

  /* Data needed in case of pick. */
  /* The last selected node. */
  gint idSelected;
  /* The current first reference. */
  gint idRef1;
  /* The current second reference. */
  gint idRef2;
  /* */
  GArray *idRegion;
  /* The position of the pick. */
  int xOrig, yOrig;
  int xPrev, yPrev;

  /* Data for the move action. */
  gboolean movingPicked;
  GArray *movingNodes;
  float movingAxe[3];

  /* The OpenGL list used to identify nodes. */
  VisuGlExtNodes *nodeList;
  gulong data_sig;

  /* Signals to listen to. */
  VisuData *dataObj;
  gulong popDec_signal;

  /* Copy of last event. */
  ToolSimplifiedEvents ev;

  /* Some message explaining the purpose of the interactive session. */
  gchar *message;
};

enum
  {
    INTERACTIVE_OBSERVE_SIGNAL,
    INTERACTIVE_PICK_ERROR_SIGNAL,
    INTERACTIVE_PICK_NODE_SIGNAL,
    INTERACTIVE_PICK_REGION_SIGNAL,
    INTERACTIVE_PICK_MENU_SIGNAL,
    INTERACTIVE_START_MOVE_SIGNAL,
    INTERACTIVE_MOVE_SIGNAL,
    INTERACTIVE_STOP_MOVE_SIGNAL,
    INTERACTIVE_STOP_SIGNAL,
/*     INTERACTIVE_EVENT_SIGNAL, */
    N_INTERACTIVE_SIGNALS
  };

static VisuInteractiveClass *local_class = NULL;

/* Internal variables. */
static guint interactive_signals[N_INTERACTIVE_SIGNALS] = { 0 };

/* Object gestion methods. */
static void visu_interactive_dispose (GObject* obj);
static void visu_interactive_finalize(GObject* obj);

/* Local methods. */
static void exportParameters(GString *data, VisuData *dataObj);
static void onReadCamera(VisuConfigFile *obj, VisuConfigFileEntry *entry, VisuInteractiveClass *klass);
static void onPopulationChange(VisuInteractive *inter, GArray *nodes, VisuData *dataObj);
static void _setData(VisuInteractive *inter, VisuData *dataObj);
static gboolean observe(VisuInteractive *inter, VisuGlView *view,
			ToolSimplifiedEvents *ev);
static gboolean pick(VisuInteractive *inter, ToolSimplifiedEvents *ev);
static gboolean move(VisuInteractive *inter, VisuGlView *view, ToolSimplifiedEvents *ev);
static gboolean mark(VisuInteractive *inter, ToolSimplifiedEvents *ev);
static gboolean pickAndObserve(VisuInteractive *inter,
                               VisuGlView *view, ToolSimplifiedEvents *ev);
static gboolean drag(VisuInteractive *inter, VisuGlView *view, ToolSimplifiedEvents *ev);

G_DEFINE_TYPE(VisuInteractive, visu_interactive, VISU_TYPE_OBJECT)

static void g_cclosure_marshal_NODE_SELECTION(GClosure *closure,
                                              GValue *return_value _U_,
                                              guint n_param_values,
                                              const GValue *param_values,
                                              gpointer invocation_hint _U_,
                                              gpointer marshal_data)
{
  typedef void (*callbackFunc)(gpointer data1, guint kind, VisuNodeArray *arr,
                               VisuNode *node1, VisuNode *node2, VisuNode *node3,
                               gpointer data2);
  register callbackFunc callback;
  register GCClosure *cc = (GCClosure*)closure;
  register gpointer data1, data2;

  g_return_if_fail(n_param_values == 6);

  if (G_CCLOSURE_SWAP_DATA(closure))
    {
      data1 = closure->data;
      data2 = g_value_peek_pointer(param_values + 0);
    }
  else
    {
      data1 = g_value_peek_pointer(param_values + 0);
      data2 = closure->data;
    }
  callback = (callbackFunc)(size_t)(marshal_data ? marshal_data : cc->callback);

  DBG_fprintf(stderr, "Interactive: marshall callback for node-selection"
              " with nodes %p, %p, %p.\n",
              (gpointer)g_value_get_boxed(param_values + 3),
              (gpointer)g_value_get_boxed(param_values + 4),
              (gpointer)g_value_get_boxed(param_values + 5));
  callback(data1, g_value_get_uint(param_values + 1),
           g_value_get_object(param_values + 2),
           (VisuNode*)g_value_get_boxed(param_values + 3),
           (VisuNode*)g_value_get_boxed(param_values + 4),
           (VisuNode*)g_value_get_boxed(param_values + 5),
           data2);
}
static void g_cclosure_marshal_NODE_MENU(GClosure *closure,
                                         GValue *return_value _U_,
                                         guint n_param_values,
                                         const GValue *param_values,
                                         gpointer invocation_hint _U_,
                                         gpointer marshal_data)
{
  typedef void (*callbackFunc)(gpointer data1, gint x, gint y,
                               VisuNode *node, gpointer data2);
  register callbackFunc callback;
  register GCClosure *cc = (GCClosure*)closure;
  register gpointer data1, data2;

  g_return_if_fail(n_param_values == 4);

  if (G_CCLOSURE_SWAP_DATA(closure))
    {
      data1 = closure->data;
      data2 = g_value_peek_pointer(param_values + 0);
    }
  else
    {
      data1 = g_value_peek_pointer(param_values + 0);
      data2 = closure->data;
    }
  callback = (callbackFunc)(size_t)(marshal_data ? marshal_data : cc->callback);

  DBG_fprintf(stderr, "Interactive: marshall callback for menu at %dx%d"
              " with node %p.\n",
              g_value_get_int(param_values + 1),
              g_value_get_int(param_values + 2),
              (gpointer)g_value_get_boxed(param_values + 3));
  callback(data1, g_value_get_int(param_values + 1),
           g_value_get_int(param_values + 2),
           (VisuNode*)g_value_get_boxed(param_values + 3),
           data2);
}

static void visu_interactive_class_init(VisuInteractiveClass *klass)
{
  GType paramBool[1] = {G_TYPE_BOOLEAN};
  GType paramUInt[1] = {G_TYPE_UINT};
  VisuConfigFileEntry *resourceEntry;
  int rgMethod[2] = {interactive_constrained, interactive_walker};
  float rgCamera[2] = {-G_MAXFLOAT, G_MAXFLOAT};

  local_class = klass;

  DBG_fprintf(stderr, "visu Interactive: creating the class of the object.\n");

  DBG_fprintf(stderr, "                - adding new signals ;\n");
  /**
   * VisuInteractive::observe:
   * @obj: the object emitting the signal.
   * @bool: a boolean.
   *
   * This signal is emitted each time an observe session is start
   * (@bool is TRUE) or finished (@bool is FALSE).
   *
   * Since: 3.6
   */
  interactive_signals[INTERACTIVE_OBSERVE_SIGNAL] =
    g_signal_newv("observe", G_TYPE_FROM_CLASS(klass),
		  G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE,
		  NULL, NULL, NULL, g_cclosure_marshal_VOID__BOOLEAN,
		  G_TYPE_NONE, 1, paramBool);

  /**
   * VisuInteractive::selection-error:
   * @obj: the object emitting the signal.
   * @err: an error value.
   *
   * This signal is emitted each time a selection fails, providing the
   * error in @err (see #VisuInteractivePickError).
   *
   * Since: 3.6
   */
  interactive_signals[INTERACTIVE_PICK_ERROR_SIGNAL] =
    g_signal_newv("selection-error", G_TYPE_FROM_CLASS(klass),
		  G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS,
		  NULL, NULL, NULL, g_cclosure_marshal_VOID__UINT,
		  G_TYPE_NONE, 1, paramUInt);

  /**
   * VisuInteractive::node-selection:
   * @obj: the object emitting the signal.
   * @kind: a flag, see #VisuInteractivePick.
   * @array: (type VisuNodeArray*) (transfer none): the #VisuNodeArray
   * hosting @node1, @node2 and @node3.
   * @node1: (type VisuNode*) (transfer none): the primary node.
   * @node2: (type VisuNode*) (transfer none): the secondary
   * node, if any.
   * @node3: (type VisuNode*) (transfer none): the tertiary
   * node, if any.
   *
   * This signal is emitted each time a single node selection succeed, providing the
   * kind in @kind (see #VisuInteractivePick). The corresponding nodes
   * are stored in @node1, @node2 and @node3.
   *
   * Since: 3.6
   */
  interactive_signals[INTERACTIVE_PICK_NODE_SIGNAL] =
    g_signal_new("node-selection", G_TYPE_FROM_CLASS(klass),
                 G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE,
                 0, NULL, NULL, g_cclosure_marshal_NODE_SELECTION,
                 G_TYPE_NONE, 5, G_TYPE_UINT, VISU_TYPE_NODE_ARRAY, VISU_TYPE_NODE,
                 VISU_TYPE_NODE, VISU_TYPE_NODE, NULL);

  /**
   * VisuInteractive::region-selection:
   * @obj: the object emitting the signal.
   * @nodes: (element-type guint): an array of node ids.
   *
   * This signal is emitted each time a region selection succeed. The corresponding nodes
   * are stored in @nodes.
   *
   * Since: 3.6
   */
  interactive_signals[INTERACTIVE_PICK_REGION_SIGNAL] =
    g_signal_new("region-selection", G_TYPE_FROM_CLASS(klass),
                 G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS,
                 0, NULL, NULL, g_cclosure_marshal_VOID__BOXED,
                 G_TYPE_NONE, 1, G_TYPE_ARRAY);

  /**
   * VisuInteractive::menu:
   * @obj: the object emitting the signal.
   * @x: the x coordinate.
   * @y: the y coordinate.
   * @node: (type VisuNode*) (transfer none) (allow-none): a #VisuNode.
   *
   * This signal is emitted each time a menu key stroke is done.
   *
   * Since: 3.7
   */
  interactive_signals[INTERACTIVE_PICK_MENU_SIGNAL] =
    g_signal_new("menu", G_TYPE_FROM_CLASS(klass),
                 G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE,
                 0, NULL, NULL, g_cclosure_marshal_NODE_MENU,
                 G_TYPE_NONE, 3, G_TYPE_INT, G_TYPE_INT, VISU_TYPE_NODE);

  /**
   * VisuInteractive::start-move:
   * @obj: the object emitting the signal.
   * @nodes: (element-type guint): an array of node ids.
   *
   * This signal is emitted each time a set of nodes are clicked to be
   * moved. The corresponding nodes are stored in @nodes.
   *
   * Since: 3.6
   */
  interactive_signals[INTERACTIVE_START_MOVE_SIGNAL] =
    g_signal_new("start-move", G_TYPE_FROM_CLASS(klass),
                 G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS,
                 0, NULL, NULL, g_cclosure_marshal_VOID__BOXED,
                 G_TYPE_NONE, 1, G_TYPE_ARRAY);
  
  /**
   * VisuInteractive::move:
   * @obj: the object emitting the signal.
   * @delta: the delta of applied translation.
   *
   * This signal is emitted each time a set of nodes are moved. The
   * corresponding movement translation is stored in delta.
   *
   * Since: 3.6
   */
  interactive_signals[INTERACTIVE_MOVE_SIGNAL] =
    g_signal_new("move", G_TYPE_FROM_CLASS(klass),
                 G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS,
                 0, NULL, NULL, g_cclosure_marshal_VOID__BOXED,
                 G_TYPE_NONE, 1, TOOL_TYPE_VECTOR);
  /**
   * VisuInteractive::stop-move:
   * @obj: the object emitting the signal.
   * @delta: the delta of applied translation.
   *
   * This signal is emitted when a set of nodes are finished moving. The
   * corresponding movement complete translation is stored in delta.
   *
   * Since: 3.8
   */
  interactive_signals[INTERACTIVE_STOP_MOVE_SIGNAL] =
    g_signal_new("stop-move", G_TYPE_FROM_CLASS(klass),
                 G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS,
                 0, NULL, NULL, g_cclosure_marshal_VOID__BOXED,
                 G_TYPE_NONE, 1, TOOL_TYPE_VECTOR);
  /**
   * VisuInteractive::stop:
   * @obj: the object emitting the signal.
   *
   * This signal is emitted each time a set of nodes are stopped to be
   * moved.
   *
   * Since: 3.6
   */
  interactive_signals[INTERACTIVE_STOP_SIGNAL] =
    g_signal_newv("stop", G_TYPE_FROM_CLASS(klass),
		  G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS,
		  NULL, NULL, NULL, g_cclosure_marshal_VOID__VOID,
		  G_TYPE_NONE, 0, NULL);

  /* Connect freeing methods. */
  G_OBJECT_CLASS(klass)->dispose  = visu_interactive_dispose;
  G_OBJECT_CLASS(klass)->finalize = visu_interactive_finalize;

  DBG_fprintf(stderr, "                - add the resources.\n");
  visu_config_file_addIntegerArrayEntry(VISU_CONFIG_FILE_PARAMETER,
                                        FLAG_PARAMETER_OBSERVE_METHOD,
                                        DESC_PARAMETER_OBSERVE_METHOD,
                                        1, (int*)&klass->preferedObserveMethod, rgMethod, FALSE);
  resourceEntry = visu_config_file_addFloatArrayEntry(VISU_CONFIG_FILE_PARAMETER,
                                                      FLAG_PARAMETER_CAMERA_SETTINGS,
                                                      DESC_PARAMETER_CAMERA_SETTINGS,
                                                      7, cameraData, rgCamera, FALSE);
  visu_config_file_entry_setVersion(resourceEntry, 3.7f);
  visu_config_file_addExportFunction(VISU_CONFIG_FILE_PARAMETER,
                                     exportParameters);

  g_signal_connect(VISU_CONFIG_FILE_PARAMETER, "parsed::" FLAG_PARAMETER_CAMERA_SETTINGS,
                   G_CALLBACK(onReadCamera), klass);

  /* Set local variables to default. */
  DBG_fprintf(stderr, "                - setup the local variables.\n");
  klass->preferedObserveMethod = interactive_constrained;
}

static void visu_interactive_init(VisuInteractive *obj)
{
  DBG_fprintf(stderr, "Visu Interactive: creating a new interactive session (%p).\n",
	      (gpointer)obj);

  obj->dispose_has_run = FALSE;
  obj->idRef1          = -99;
  obj->idRef2          = -99;
  obj->idSelected      = -99;
  obj->idRegion        = (GArray*)0;
  obj->movingAxe[0]    = 1.f;
  obj->movingAxe[1]    = 0.f;
  obj->movingAxe[2]    = 0.f;
  obj->movingPicked    = FALSE;
  obj->movingNodes     = (GArray*)0;

  obj->nodeList        = (VisuGlExtNodes*)0;

  obj->dataObj         = (VisuData*)0;
  obj->popDec_signal   = (gulong)0;

  obj->message = (gchar*)0;
}

static void onPopulationChange(VisuInteractive *inter, GArray *nodes, VisuData *dataObj _U_)
{
  guint i;

  DBG_fprintf(stderr, "Visu Interactive:  remove the specific nodes.\n");
  for (i = 0; i < nodes->len; i++)
    {
      if (inter->idRef1 == g_array_index(nodes, gint, i))
	inter->idRef1 = -1;
      if (inter->idRef2 == g_array_index(nodes, gint, i))
	inter->idRef2 = -1;
      if (inter->idSelected == g_array_index(nodes, gint, i))
	inter->idSelected = -1;
    }
  if (inter->idRegion)
    g_array_unref(inter->idRegion);
  inter->idRegion = (GArray*)0;
}

/* This method can be called several times.
   It should unref all of its reference to
   GObjects. */
static void visu_interactive_dispose(GObject* obj)
{
  DBG_fprintf(stderr, "Visu Interactive: dispose object %p.\n", (gpointer)obj);

  if (VISU_INTERACTIVE(obj)->dispose_has_run)
    return;

  VISU_INTERACTIVE(obj)->dispose_has_run = TRUE;

  visu_interactive_setNodeList(VISU_INTERACTIVE(obj), (VisuGlExtNodes*)0);

  /* Chain up to the parent class */
  G_OBJECT_CLASS(visu_interactive_parent_class)->dispose(obj);
}
/* This method is called once only. */
static void visu_interactive_finalize(GObject* obj)
{
  g_return_if_fail(obj);

  DBG_fprintf(stderr, "Visu Interactive: finalize object %p.\n", (gpointer)obj);

  if (VISU_INTERACTIVE(obj)->movingNodes)
    g_array_unref(VISU_INTERACTIVE(obj)->movingNodes);
  if (VISU_INTERACTIVE(obj)->idRegion)
    g_array_unref(VISU_INTERACTIVE(obj)->idRegion);
  g_free(VISU_INTERACTIVE(obj)->message);

  /* Chain up to the parent class */
  G_OBJECT_CLASS(visu_interactive_parent_class)->finalize(obj);
}

/**
 * visu_interactive_new:
 * @type: a #VisuInteractiveId flag.
 *
 * Creates a new interactive session of the given @type.
 *
 * Returns: (transfer full): a newly created object.
 */
VisuInteractive* visu_interactive_new(VisuInteractiveId type)
{
  VisuInteractive *inter;

  inter = VISU_INTERACTIVE(g_object_new(VISU_TYPE_INTERACTIVE, NULL));
  g_return_val_if_fail(inter, (VisuInteractive*)0);

  DBG_fprintf(stderr, "Visu Interactive: start new interactive session %p (%d).\n",
	      (gpointer)inter, type);

  inter->id = type;
  
  return inter;
}
/**
 * visu_interactive_getType:
 * @inter: a #VisuInteractive object.
 *
 * It returns the kind of interactive session.
 *
 * Returns: a #VisuInteractiveId value.
 */
VisuInteractiveId visu_interactive_getType(VisuInteractive *inter)
{
  g_return_val_if_fail(VISU_IS_INTERACTIVE(inter), interactive_none);

  return inter->id;
}
/**
 * visu_interactive_setType:
 * @inter: a #VisuInteractive object.
 * @id: a #VisuInteractiveId.
 *
 * It changes the kind of interactive session.
 *
 * Returns: TRUE if indeed changed.
 */
gboolean visu_interactive_setType(VisuInteractive *inter, VisuInteractiveId id)
{
  g_return_val_if_fail(VISU_IS_INTERACTIVE(inter), FALSE);

  if (inter->id == id)
    return FALSE;

  inter->id = id;
  return TRUE;
}

static void onDataNotified(VisuInteractive *inter, GParamSpec *pspec _U_, VisuGlExtNodes *nodes)
{
  _setData(inter, VISU_DATA(visu_node_array_renderer_getNodeArray(VISU_NODE_ARRAY_RENDERER(nodes))));
}

/**
 * visu_interactive_setNodeList:
 * @inter: a #VisuInteractive object.
 * @nodes: (transfer full) (allow-none): a #VisuGlExtNodes object.
 *
 * Associate a #VisuGlExtNodes object for node selection. This is
 * mandatory for move, pick and mark actions.
 *
 * Since: 3.7
 **/
void visu_interactive_setNodeList(VisuInteractive *inter, VisuGlExtNodes *nodes)
{
  g_return_if_fail(VISU_IS_INTERACTIVE(inter));

  if (inter->nodeList == nodes)
    return;

  if (inter->nodeList)
    {
      g_signal_handler_disconnect(inter->nodeList, inter->data_sig);
      g_object_unref(inter->nodeList);
    }
  if (nodes)
    {
      g_object_ref(nodes);
      inter->data_sig = g_signal_connect_swapped(nodes, "notify::data",
                                                 G_CALLBACK(onDataNotified), inter);
    }
  inter->nodeList = nodes;
  _setData(inter, (nodes) ? VISU_DATA(visu_node_array_renderer_getNodeArray(VISU_NODE_ARRAY_RENDERER(nodes))) : (VisuData*)0);
}

static void _setData(VisuInteractive *inter, VisuData *dataObj)
{
  if (inter->idRef1 >= 0 &&
      (!dataObj || !visu_node_array_getFromId(VISU_NODE_ARRAY(dataObj), inter->idRef1)))
    inter->idRef1 = -99;
  if (inter->idRef2 >= 0 &&
      (!dataObj || !visu_node_array_getFromId(VISU_NODE_ARRAY(dataObj), inter->idRef2)))
    inter->idRef2 = -99;
  if (inter->idSelected >= 0 &&
      (!dataObj || !visu_node_array_getFromId(VISU_NODE_ARRAY(dataObj), inter->idSelected)))
    inter->idSelected = -99;
  DBG_fprintf(stderr, "Visu Interactive: specific nodes are now:\n");
  DBG_fprintf(stderr, " - selected    %d.\n", inter->idSelected);
  DBG_fprintf(stderr, " - ref. 1      %d.\n", inter->idRef1);
  DBG_fprintf(stderr, " - ref. 2      %d.\n", inter->idRef2);

  if (inter->idRegion)
    g_array_unref(inter->idRegion);
  inter->idRegion = (GArray*)0;

  if (inter->dataObj == dataObj)
    return;

  if (inter->dataObj)
    {
      g_signal_handler_disconnect(inter->dataObj, inter->popDec_signal);
      g_object_unref(inter->dataObj);
    }
  if (dataObj)
    {
      g_object_ref(dataObj);
      inter->popDec_signal =
        g_signal_connect_swapped(dataObj, "PopulationDecrease",
                                 G_CALLBACK(onPopulationChange), (gpointer)inter);
    }
  inter->dataObj = dataObj;
}

/**
 * visu_interactive_popSavedCamera:
 * @inter: a #VisuInteractive object.
 *
 * @inter object stores camera settings as a ring. This routine goes
 * to the next camera in the ring and returns the current one. The
 * popped camera is not actually removed from the ring.
 *
 * Since: 3.6
 *
 * Returns: a pointer to the previously current #VisuGlCamera. It
 * is owned by V_Sim and should not be touched.
 */
VisuGlCamera* visu_interactive_popSavedCamera(VisuInteractive *inter)
{
  VisuGlCamera *cur;
  VisuInteractiveClass *klass;

  klass = VISU_INTERACTIVE_GET_CLASS(inter);
  g_return_val_if_fail(klass, (VisuGlCamera*)0);

  if (!klass->lastCamera)
    return (VisuGlCamera*)0;

  cur = (VisuGlCamera*)klass->lastCamera->data;

  klass->lastCamera = g_list_next(klass->lastCamera);
  if (!klass->lastCamera)
    klass->lastCamera = klass->savedCameras;

  DBG_fprintf(stderr, "Interactive: pop, pointing now at camera %d.\n",
	      g_list_position(klass->savedCameras, klass->lastCamera));

  return cur;
}
/**
 * visu_interactive_getSavedCameras:
 * @inter: a #VisuInteractive object.
 * @cameras: (out) (element-type VisuGlCamera*): a location to store a list of cameras.
 * @head: (out) (element-type VisuGlCamera*): a location to store a list of cameras.
 *
 * @inter object stores camera settings as a ring. One can access the
 * set of saved cameras thanks to @cameras or to the current position
 * in the ring thanks to @head. @cameras or @head are not copied and
 * are owned by V_Sim. They should be considered read-only.
 *
 * Since: 3.6
 */
void visu_interactive_getSavedCameras(VisuInteractive *inter,
                                      GList **cameras, GList **head)
{
  VisuInteractiveClass *klass;

  klass = VISU_INTERACTIVE_GET_CLASS(inter);
  g_return_if_fail(klass);
  
  *cameras = klass->savedCameras;
  *head    = klass->lastCamera;
}
static gboolean cmpCameras(VisuGlCamera *c1, VisuGlCamera *c2)
{
  return (c1 == c2) ||
    (c1->theta == c2->theta &&
     c1->phi == c2->phi &&
     c1->omega == c2->omega &&
     c1->xs == c2->xs &&
     c1->ys == c2->ys/*  && */
     /* c1->gross == c2->gross && */
     /* c1->d_red == c2->d_red */);
}
static void _pushSavedCamera(VisuInteractiveClass *klass, VisuGlCamera *camera)
{
  VisuGlCamera *tmp;

  g_return_if_fail(klass && camera);

  for (klass->lastCamera = klass->savedCameras ;
       klass->lastCamera &&
	 !cmpCameras((VisuGlCamera*)klass->lastCamera->data, camera);
       klass->lastCamera = g_list_next(klass->lastCamera));

  /* Case we don't find it, we add. */
  if (!klass->lastCamera || (VisuGlCamera*)klass->lastCamera->data != camera)
    {
      tmp = g_malloc(sizeof(VisuGlCamera));
      *tmp = *camera;
      klass->savedCameras = g_list_prepend(klass->savedCameras, (gpointer)tmp);
    }
  klass->lastCamera = klass->savedCameras;
  DBG_fprintf(stderr, "Interactive: push, storing now %d cameras.\n",
	      g_list_length(klass->savedCameras));
}
/**
 * visu_interactive_pushSavedCamera:
 * @inter: a #VisuInteractive object.
 * @camera: a #VisuGlCamera object.
 *
 * @inter object stores camera settings as a ring. The given @camera
 * is copied in the ring if its values not already exist. The current camera
 * is set to this new one.
 *
 * Since: 3.6
 */
void visu_interactive_pushSavedCamera(VisuInteractive *inter, VisuGlCamera *camera)
{
  VisuInteractiveClass *klass;

  klass = VISU_INTERACTIVE_GET_CLASS(inter);
  _pushSavedCamera(klass, camera);
}
gboolean visuInteractiveRemove_savedCamera(VisuInteractive *inter, VisuGlCamera *camera)
{
  VisuInteractiveClass *klass;
  GList *lst;

  klass = VISU_INTERACTIVE_GET_CLASS(inter);
  g_return_val_if_fail(klass, FALSE);

  for (lst = klass->savedCameras ;
       lst && !cmpCameras((VisuGlCamera*)lst->data, camera);
       lst = g_list_next(lst));

  /* Case we don't find it, we return. */
  if (!lst)
    return FALSE;

  g_free(lst->data);
  klass->savedCameras = g_list_delete_link(klass->savedCameras, lst);
  if (klass->lastCamera == lst)
    klass->lastCamera = lst->next;
  if (!klass->lastCamera)
    klass->lastCamera = klass->savedCameras;
  DBG_fprintf(stderr, "Interactive: remove, storing now %d cameras.\n",
	      g_list_length(klass->savedCameras));

  return TRUE;
}


/**
 * visu_interactive_handleEvent:
 * @inter: a #VisuInteractive object ;
 * @view: a #VisuGlView object the interaction happened on.
 * @ev: a simplified event.
 *
 * This routine should be called by the rendering window when some
 * event is raised on the rendering surface.
 */
void visu_interactive_handleEvent(VisuInteractive *inter,
                                  VisuGlView *view, ToolSimplifiedEvents *ev)
{
  gboolean stop;

  g_return_if_fail(VISU_IS_INTERACTIVE(inter));

  DBG_fprintf(stderr, "Visu Interactive: handle event at %dx%d (%d %d).\n", ev->x, ev->y,
              ev->button, ev->buttonType);
  inter->ev = *ev;
  switch (inter->id)
    {
    case interactive_observe:
      stop = observe(inter, view, ev);
      break;
    case interactive_measureAndObserve:
      stop = pickAndObserve(inter, view, ev);
      break;
    case interactive_measure:
    case interactive_pick:
      stop = pick(inter, ev);
      break;
    case interactive_move:
      stop = move(inter, view, ev);
      break;
    case interactive_mark:
      stop = mark(inter, ev);
      break;
    case interactive_drag:
      stop = drag(inter, view, ev);
      break;
    default:
      stop = FALSE;
      break;
    }

  if (stop)
    g_signal_emit(G_OBJECT(inter),
		  interactive_signals[INTERACTIVE_STOP_SIGNAL], 0, NULL);
}
/**
 * visu_interactive_setReferences:
 * @inter: a #VisuInteractive object.
 * @from: another #VisuInteractive object.
 *
 * Copies all node ids used as reference from @from to @inter.
 */
void visu_interactive_setReferences(VisuInteractive *inter, VisuInteractive *from)
{
  g_return_if_fail(VISU_IS_INTERACTIVE(inter) && VISU_IS_INTERACTIVE(from));

  inter->idSelected = from->idSelected;
  inter->idRef1     = from->idRef1;
  inter->idRef2     = from->idRef2;
}
/**
 * visu_interactive_getEvent:
 * @inter: a #VisuInteractive object.
 *
 * This routine can be called in callbacks of @inter to get some
 * details about the event that raise signals like
 * VisuInteractive::node-selection.
 *
 * Since: 3.7
 *
 * Returns: (transfer none): a location with details on the last event.
 **/
ToolSimplifiedEvents* visu_interactive_getEvent(VisuInteractive *inter)
{
  g_return_val_if_fail(VISU_IS_INTERACTIVE(inter), (ToolSimplifiedEvents*)0);

  return &inter->ev;
}

/******************************************************************************/

static gboolean pickAndObserve(VisuInteractive *inter,
                               VisuGlView *view, ToolSimplifiedEvents *ev)
{
  /* If button 1 or 2, use observe mode */
  if (ev->button != 3 && ev->specialKey != Key_Menu)
    return observe(inter, view, ev);
  /* If button 3, use pick mode with button 3 as button 1 */
  else
    {
      if (ev->shiftMod && !ev->controlMod)
	{
	  ev->button = 2;
	}
      else if (!ev->shiftMod && ev->controlMod)
	{
	  ev->button = 2;
	}
      else if (ev->shiftMod && ev->controlMod)
	{
	  ev->shiftMod = FALSE;
	  ev->controlMod = TRUE;
	  ev->button = 1;
	}
      else
	ev->button = 1;
      ev->motion = FALSE;
      
      return pick(inter, ev);
    }
}

static gboolean observe(VisuInteractive *inter, VisuGlView *view,
			ToolSimplifiedEvents *ev)
{
  int sign_theta;
  int dx, dy;
  VisuGlCamera *camera;
  float angles[3], ratio;
  gdouble gross, persp;
  gboolean zoom;

  g_return_val_if_fail(ev && inter, TRUE);
  if (ev->button == 3)
    return (ev->buttonType == TOOL_BUTTON_TYPE_PRESS);
  /* If the realese event is triggered, we exit only if it's
     not a scroll event (button 4 and 5). */
  if (ev->button > 0 && ev->buttonType == TOOL_BUTTON_TYPE_RELEASE)
    if (ev->button != 4 && ev->button != 5 )
      {
	if (ev->button == 1)
          g_signal_emit(G_OBJECT(inter),
                        interactive_signals[INTERACTIVE_OBSERVE_SIGNAL],
                        0 , FALSE, NULL);
	return FALSE;
      }

  g_return_val_if_fail(view, FALSE);

/*   fprintf(stderr, "%d %d, %d\n", ev->x, ev->y, ev->button); */
/*   fprintf(stderr, "%d %d, %d\n", ev->shiftMod, ev->controlMod, ev->motion); */
  /* Support de la roulette en zoom et perspective. */
  if (ev->specialKey == Key_Page_Up || ev->specialKey == Key_Page_Down ||
      ev->button == 4 || ev->button == 5)
    {
      g_object_get(view, "zoom", &gross, "perspective", &persp, NULL);
      if ((ev->button == 4 || ev->specialKey == Key_Page_Up) && !ev->shiftMod)
        g_object_set(view, "zoom", MIN(gross * 1.1, 999.), NULL);
      else if ((ev->button == 4 || ev->specialKey == Key_Page_Up) && ev->shiftMod)
        g_object_set(view, "perspective", MAX(persp / 1.1, 1.1), NULL);
      else if ((ev->button == 5 || ev->specialKey == Key_Page_Down) && !ev->shiftMod)
        g_object_set(view, "zoom", MAX(gross / 1.1, 0.2), NULL);
      else if ((ev->button == 5 || ev->specialKey == Key_Page_Down) && ev->shiftMod)
        g_object_set(view, "perspective", MIN(persp * 1.1, 100.), NULL);
    }
  else if (ev->button && !ev->motion)
    {
      inter->xOrig = ev->x;
      inter->yOrig = ev->y;
      if (ev->button == 1)
	g_signal_emit(G_OBJECT(inter),
		      interactive_signals[INTERACTIVE_OBSERVE_SIGNAL],
		      0 , TRUE, NULL);
    }
  else if (ev->motion ||
	   ev->specialKey == Key_Arrow_Down  || ev->specialKey == Key_Arrow_Up ||
	   ev->specialKey == Key_Arrow_Right || ev->specialKey == Key_Arrow_Left)
    {
      if (ev->motion)
	{
	  dx =   ev->x - inter->xOrig;
	  dy = -(ev->y - inter->yOrig);
	}
      else
	{
          g_signal_emit(G_OBJECT(inter),
                        interactive_signals[INTERACTIVE_OBSERVE_SIGNAL],
                        0 , TRUE, NULL);
	  dx = 0;
	  dy = 0;
	  if (ev->specialKey == Key_Arrow_Left)
	    dx = -10;
	  else if (ev->specialKey == Key_Arrow_Right)
	    dx = +10;
	  else if (ev->specialKey == Key_Arrow_Down)
	    dy = -10;
	  else if (ev->specialKey == Key_Arrow_Up)
	    dy = +10;
	}
      zoom = (ev->button == 2);
      if(!zoom && !ev->shiftMod && !ev->controlMod)
	{
	  if (local_class->preferedObserveMethod == interactive_constrained)
	    {
	      if(view->camera.theta > 0.0)
		sign_theta = 1;
	      else
		sign_theta = -1;
	      visu_gl_view_rotateBox(view, dy * 180.0f / view->window.height,
				   -dx * 180.0f / view->window.width * sign_theta, angles);
              if (ev->motion)
                visu_gl_view_setThetaPhiOmega(view, angles[0], angles[1], 0.,
                                              VISU_GL_CAMERA_THETA |
                                              VISU_GL_CAMERA_PHI);
              else
                g_object_set(view, "theta", angles[0], "phi", angles[1], NULL);
	    }
	  else if (local_class->preferedObserveMethod == interactive_walker)
	    {
	      visu_gl_view_rotateCamera(view, dy * 180.0f / view->window.height,
				      -dx * 180.0f / view->window.width, angles);
              if (ev->motion)
                visu_gl_view_setThetaPhiOmega(view, angles[0], angles[1], angles[2],
                                              VISU_GL_CAMERA_THETA |
                                              VISU_GL_CAMERA_PHI |
                                              VISU_GL_CAMERA_OMEGA);
              else
                g_object_set(view, "theta", angles[0], "phi", angles[1],
                             "omega", angles[2], NULL);
	    }
	}
      else if(!zoom && ev->shiftMod && !ev->controlMod)
	{
	  ratio = 1. / MIN(view->window.width, view->window.height) /
	    view->camera.gross * (view->camera.d_red - 1.f) / view->camera.d_red;
          visu_gl_view_setXsYs(view,
                               view->camera.xs + (float)dx * ratio,
                               view->camera.ys + (float)dy * ratio,
                               VISU_GL_CAMERA_XS | VISU_GL_CAMERA_YS);
	}
      else if(!zoom && ev->controlMod && !ev->shiftMod)
	{
	  if (ABS(dx) > ABS(dy))
	    visu_gl_view_setThetaPhiOmega(view, 0., 0.,
                                          view->camera.omega + dx *
                                          180.0f / view->window.width,
                                          VISU_GL_CAMERA_OMEGA);
	  else
	    visu_gl_view_setThetaPhiOmega(view, 0., 0.,
                                          view->camera.omega + dy *
                                          180.0f / view->window.height,
                                          VISU_GL_CAMERA_OMEGA);
	}
      else if(zoom && !ev->shiftMod)
	visu_gl_view_setGross(view, view->camera.gross *
                              (1. + (float)dy *
                               3.0f / view->window.height));
      else if(zoom && ev->shiftMod)
	visu_gl_view_setPersp(view, view->camera.d_red *
                              (1. - (float)dy *
                               5.0f / view->window.height));
      if (ev->motion)
	{
	  inter->xOrig = ev->x;
	  inter->yOrig = ev->y;
	}
      else
        g_signal_emit(G_OBJECT(inter),
                      interactive_signals[INTERACTIVE_OBSERVE_SIGNAL],
                      0 , FALSE, NULL);
    }
  else if (ev->letter == 'r')
    {
      camera = visu_interactive_popSavedCamera(inter);
      if (camera)
	{
          g_object_set(view, "theta", camera->theta, "phi", camera->phi,
                       "omega", camera->omega, "zoom", camera->gross,
                       "perspective", camera->d_red, NULL);
          visu_gl_view_setXsYs(view, camera->xs, camera->ys,
                               VISU_GL_CAMERA_XS | VISU_GL_CAMERA_YS);
	}
    }
  else if (ev->letter == 's' && !ev->shiftMod && !ev->controlMod)
    visu_interactive_pushSavedCamera(inter, &view->camera);
  else if (ev->letter == 's' && ev->shiftMod && !ev->controlMod)
    visuInteractiveRemove_savedCamera(inter, &view->camera);
  else if (ev->letter == 'x')
    visu_gl_view_alignToAxis(view, TOOL_XYZ_X);
  else if (ev->letter == 'y')
    visu_gl_view_alignToAxis(view, TOOL_XYZ_Y);
  else if (ev->letter == 'z')
    visu_gl_view_alignToAxis(view, TOOL_XYZ_Z);
  return FALSE;
}
static void glDrawSelection(VisuInteractive *inter, int x, int y)
{
  int viewport[4];

  glPushAttrib(GL_ENABLE_BIT);
  glDisable(GL_FOG);
  glEnable(GL_BLEND);
  glDisable(GL_DEPTH_TEST);
  glDisable(GL_LIGHTING);
  glBlendFunc(GL_ONE_MINUS_DST_COLOR, GL_ONE_MINUS_SRC_COLOR);
  glGetIntegerv(GL_VIEWPORT, viewport);
  glMatrixMode(GL_PROJECTION);
  glPushMatrix();
  glLoadIdentity();
  gluOrtho2D(0.0, (float)viewport[2], 0., (float)viewport[3]);   
  glMatrixMode(GL_MODELVIEW);
  glPushMatrix();
  glLoadIdentity();
  glColor4f(1.f, 1.f, 1.f, 1.f);
  glLineWidth(2);
  glDrawBuffer(GL_FRONT);
  /* We erase the previous selecting rectangle. */
  glBegin(GL_LINE_LOOP);
  glVertex3f(inter->xOrig, (float)viewport[3] - inter->yOrig, 0.f);
  glVertex3f(inter->xPrev, (float)viewport[3] - inter->yOrig, 0.f);
  glVertex3f(inter->xPrev, (float)viewport[3] - inter->yPrev, 0.f);
  glVertex3f(inter->xOrig, (float)viewport[3] - inter->yPrev, 0.f);
  glEnd();
  glFlush();

  /* We draw the new selecting rectangle. */
  if (x > 0 && y > 0)
    {
      glBegin(GL_LINE_LOOP);
      glVertex3f(inter->xOrig, (float)viewport[3] - inter->yOrig, 0.f);
      glVertex3f(x, (float)viewport[3] - inter->yOrig, 0.f);
      glVertex3f(x, (float)viewport[3] - y, 0.f);
      glVertex3f(inter->xOrig, (float)viewport[3] - y, 0.f);
      glEnd();
      glFlush();
    }
  glDrawBuffer(GL_BACK);
  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
  glPopMatrix();
  glMatrixMode(GL_PROJECTION);
  glPopMatrix();
  glMatrixMode(GL_MODELVIEW);
  glPopAttrib();
}

static gboolean pick(VisuInteractive *inter, ToolSimplifiedEvents *ev)
{
  int nodeId;
  gboolean pickOnly;
  VisuInteractivePick pick;
  VisuInteractivePickError error;
  VisuNodeArray *nodeArray;
  VisuNode *nodes[3];

  g_return_val_if_fail(ev && inter, TRUE);
  /* We store the pickInfo into the VisuData on press,
     but we apply it only at release if there was no drag action. */
  if (ev->button == 3)
    {
      if (ev->buttonType == TOOL_BUTTON_TYPE_PRESS)
	return TRUE;
      else
	return FALSE;
    }

  pickOnly = (inter->id == interactive_pick);

  if (ev->button == 1 && ev->motion && !pickOnly)
    {
      /* We drag a selecting area. */
      /* A nodeInfo should have been stored, we retrieve it. */
      DBG_fprintf(stderr, "Interactive: pick, drag to %dx%d.\n", ev->x, ev->y);
      glDrawSelection(inter, ev->x, ev->y);
      inter->xPrev = ev->x;
      inter->yPrev = ev->y;
    }
  else if (ev->buttonType == TOOL_BUTTON_TYPE_PRESS)
    {
      /* We create and store a nodeInfo data. */
      DBG_fprintf(stderr, "Interactive: pick, press at %dx%d.\n", ev->x, ev->y);
      inter->xOrig = ev->x;
      inter->yOrig = ev->y;
      inter->xPrev = ev->x;
      inter->yPrev = ev->y;
    }
  else if (ev->buttonType == TOOL_BUTTON_TYPE_RELEASE)
    {
      g_return_val_if_fail(inter->nodeList, TRUE);

      /* Reset current selection. */
      if (inter->idRegion)
	g_array_unref(inter->idRegion);
      inter->idRegion = (GArray*)0;
      inter->idSelected = -1;

      DBG_fprintf(stderr, "Interactive: pick, release from %dx%d.\n", inter->xOrig, inter->yOrig);

      /* Set new selection from click. */
      pick = PICK_NONE;
      /* If no drag action, we select the node and compute the pick mesure. */
      if ((inter->xOrig == inter->xPrev &&
	   inter->yOrig == inter->yPrev) || pickOnly)
	{
	  nodeId = visu_gl_ext_nodes_getSelection(inter->nodeList, ev->x, ev->y);

	  error = PICK_ERROR_NO_SELECTION;

	  DBG_fprintf(stderr, "Visu Interactive: set selection (single %d %d).\n",
		      ev->shiftMod, ev->controlMod);
	  if (pickOnly && (ev->button == 1 || ev->button == 2))
	    {
	      DBG_fprintf(stderr, "Visu Interactive: set pick node %d.\n", nodeId);
	      inter->idSelected = nodeId;
	      pick = (nodeId >= 0)?PICK_SELECTED:PICK_NONE;
	    }
	  else if (!pickOnly && ev->button == 1 && !ev->controlMod)
	    {
	      if (nodeId >= 0)
		{
		  pick = PICK_SELECTED;
		  if (nodeId == inter->idRef1 || nodeId == inter->idRef2)
		    {
		      error = PICK_ERROR_SAME_REF;
		      pick = PICK_NONE;
		    }

		  if (pick != PICK_NONE)
		    {
		      inter->idSelected = nodeId;
		      if (inter->idRef1 >= 0)
			pick = (inter->idRef2 < 0)?PICK_DISTANCE:PICK_ANGLE;
		    }
		}
	      else
		pick = PICK_NONE;
	    }
	  else if (!pickOnly && ev->button == 1 && ev->controlMod)
	    pick = (nodeId < 0)?PICK_NONE:PICK_HIGHLIGHT;
	  else if (!pickOnly && ev->button == 2 && ev->shiftMod && !ev->controlMod)
	    {
	      DBG_fprintf(stderr, "Visu Interactive: set ref1 info for node %d.\n",
			  nodeId);
	      pick = PICK_REFERENCE_1;
	      /* The error case. */
	      if (nodeId >= 0 && inter->idRef2 == nodeId)
		{
		  error = PICK_ERROR_SAME_REF;
		  pick = PICK_NONE;
		}
	      else if (nodeId < 0 && inter->idRef2 >= 0)
		{
		  error = PICK_ERROR_REF2;
		  pick = PICK_NONE;
		}

	      if (pick != PICK_NONE)
		{
		  inter->idRef1 = nodeId;
		  pick = (nodeId < 0)?PICK_UNREFERENCE_1:PICK_REFERENCE_1;
		}
	    }
	  else if (!pickOnly && ev->button == 2 && !ev->shiftMod && ev->controlMod)
	    {
	      DBG_fprintf(stderr, "Visu Interactive: set ref2 info for node %d.\n",
			  nodeId);
	      pick = PICK_REFERENCE_2;
	      /* The error case. */
	      if (nodeId >= 0 && inter->idRef1 < 0)
		{
		  error = PICK_ERROR_REF1;
		  pick = PICK_NONE;
		}
	      else if (nodeId >= 0 && inter->idRef1 == nodeId)
		{
		  error = PICK_ERROR_SAME_REF;
		  pick = PICK_NONE;
		}

	      if (pick != PICK_NONE)
		{
		  inter->idRef2 = nodeId;
		  pick = (nodeId < 0)?PICK_UNREFERENCE_2:PICK_REFERENCE_2;
		}
	    }
	  else if (!pickOnly && ev->button == 2 && !ev->shiftMod && !ev->controlMod)
	    pick = (nodeId < 0)?PICK_NONE:PICK_INFORMATION;
	  else
	    return FALSE;

	  DBG_fprintf(stderr, " | OK.\n");
	  if (pick != PICK_NONE)
	    {
              nodeArray = VISU_NODE_ARRAY(inter->dataObj);
	      nodes[0] = visu_node_array_getFromId(nodeArray, nodeId);
	      nodes[1] = visu_node_array_getFromId
		(nodeArray, (pick != PICK_REFERENCE_1)?inter->idRef1:nodeId);
	      nodes[2] = visu_node_array_getFromId
		(nodeArray, (pick != PICK_REFERENCE_2)?inter->idRef2:nodeId);
              DBG_fprintf(stderr, "Interactive: emit node-selection with nodes"
                          " %p, %p, %p.\n", (gpointer)nodes[0],
                          (gpointer)nodes[1], (gpointer)nodes[2]);
              switch (pick)
                {
                case (PICK_SELECTED):
                case (PICK_HIGHLIGHT):
                case (PICK_INFORMATION):
                  if (nodes[0])
                    g_signal_emit(G_OBJECT(inter),
                                  interactive_signals[INTERACTIVE_PICK_NODE_SIGNAL],
                                  0, pick, inter->dataObj, nodes[0], nodes[1], nodes[2]);
                  break;
                case (PICK_REFERENCE_1):
                case (PICK_UNREFERENCE_1):
                  g_signal_emit(G_OBJECT(inter),
                                interactive_signals[INTERACTIVE_PICK_NODE_SIGNAL],
                                0 , pick, inter->dataObj, nodes[0], nodes[1], nodes[2]);
                  break;
                case (PICK_REFERENCE_2):
                case (PICK_UNREFERENCE_2):
                  if (nodes[1])
                    g_signal_emit(G_OBJECT(inter),
                                  interactive_signals[INTERACTIVE_PICK_NODE_SIGNAL],
                                  0 , pick, inter->dataObj, nodes[0], nodes[1], nodes[2]);
                  break;
                case (PICK_DISTANCE):
                  if (nodes[0] && nodes[1])
                    g_signal_emit(G_OBJECT(inter),
                                  interactive_signals[INTERACTIVE_PICK_NODE_SIGNAL],
                                  0 , pick, inter->dataObj, nodes[0], nodes[1], nodes[2]);
                  break;
                case (PICK_ANGLE):
                  if (nodes[0] && nodes[1] && nodes[2])
                    g_signal_emit(G_OBJECT(inter),
                                  interactive_signals[INTERACTIVE_PICK_NODE_SIGNAL],
                                  0 , pick, inter->dataObj, nodes[0], nodes[1], nodes[2]);
                  break;
                default:
                  break;
                }
	    }
	  else if (pick == PICK_NONE && error != PICK_ERROR_NONE)
	    g_signal_emit(G_OBJECT(inter),
			  interactive_signals[INTERACTIVE_PICK_ERROR_SIGNAL],
			  0 , error, NULL);
	}
      /* If drag action, we compute all the nodes in the rectangle
	 and we erase it. */
      else
	{
	  /* We get the list of selected nodes. */
	  glDrawSelection(inter, -1, -1);
	  /* Get all nodes in the region. */
	  inter->idRegion = visu_gl_ext_nodes_getSelectionByRegion(inter->nodeList,
                                                                   inter->xOrig, inter->yOrig,
                                                                   ev->x, ev->y);
	  DBG_fprintf(stderr, "Interactive: set selection (drag).\n");
	  /* Copy the region list. */
	  if (inter->idRegion)
            g_signal_emit(G_OBJECT(inter),
                          interactive_signals[INTERACTIVE_PICK_REGION_SIGNAL],
                          0 , inter->idRegion, NULL);
	  else
	    g_signal_emit(G_OBJECT(inter),
			  interactive_signals[INTERACTIVE_PICK_ERROR_SIGNAL],
			  0 , PICK_ERROR_NO_SELECTION, NULL);
	}
    }
  else if (ev->specialKey == Key_Menu)
    {
      g_return_val_if_fail(inter->nodeList, TRUE);

      /* Set new selection from click. */
      pick = PICK_NONE;
      /* We select the node or none. */
      nodeId = visu_gl_ext_nodes_getSelection(inter->nodeList, ev->x, ev->y);
      if (nodeId >= 0)
        {
          nodeArray = VISU_NODE_ARRAY(inter->dataObj);
          nodes[0] = visu_node_array_getFromId(nodeArray, nodeId);
        }
      else
        nodes[0] = (VisuNode*)0;
      g_signal_emit(G_OBJECT(inter),
                    interactive_signals[INTERACTIVE_PICK_MENU_SIGNAL],
                    0, ev->root_x, ev->root_y, nodes[0], NULL);
    }
  return FALSE;
}

static gboolean move(VisuInteractive *inter, VisuGlView *view, ToolSimplifiedEvents *ev)
{
  int dx, dy, nodeId;
  float ratio, z, xyz[3];
  VisuNode *node;
  float delta[3], delta0[3], centre[3];
  guint i;

  g_return_val_if_fail(ev && inter, TRUE);
  if (ev->button == 3)
    {
      if (ev->buttonType == TOOL_BUTTON_TYPE_PRESS)
	return TRUE;
      else
	return FALSE;
    }
  if (ev->button != 1 && ev->button != 2 && ev->button != 4 && ev->button != 5 &&
      ev->specialKey != Key_Arrow_Left && ev->specialKey != Key_Arrow_Right &&
      ev->specialKey != Key_Arrow_Up && ev->specialKey != Key_Arrow_Down)
    return FALSE;
  DBG_fprintf(stderr, "Visu Interactive: event (%d - %d, %d %d).\n",
              ev->button, ev->specialKey, ev->shiftMod, ev->controlMod);

  if ((ev->motion == 1 || ev->button == 4 || ev->button == 5 ||
       ev->specialKey == Key_Arrow_Left || ev->specialKey == Key_Arrow_Right ||
       ev->specialKey == Key_Arrow_Up || ev->specialKey == Key_Arrow_Down) &&
      inter->movingNodes)
    {
      DBG_fprintf(stderr, "Visu Interactive: drag action (%dx%d).\n", ev->x, ev->y);
      if (ev->specialKey == Key_Arrow_Left)
        {
          ev->x = inter->xPrev - 1;
          ev->y = inter->yPrev;
          ev->button = 1;
        }
      else if (ev->specialKey == Key_Arrow_Right)
        {
          ev->x = inter->xPrev + 1;
          ev->y = inter->yPrev;
          ev->button = 1;
        }
      else if (ev->specialKey == Key_Arrow_Up)
        {
          ev->x = inter->xPrev;
          ev->y = inter->yPrev - 1;
          ev->button = 1;
        }
      else if (ev->specialKey == Key_Arrow_Down)
        {
          ev->x = inter->xPrev;
          ev->y = inter->yPrev + 1;
          ev->button = 1;
        }
      dx =   ev->x - inter->xPrev;
      dy = -(ev->y - inter->yPrev);
      DBG_fprintf(stderr, " | dx x dy : %d x %d\n", dx, dy);

      g_return_val_if_fail(inter->nodeList, TRUE);

      /* Get the camera orientation. */
      if (!ev->shiftMod && !ev->controlMod)
	{
	  if (ev->button == 1)
	    {
	      visu_box_getCentre(visu_boxed_getBox(VISU_BOXED(view)), centre);
	      glMatrixMode(GL_MODELVIEW); 
	      glPushMatrix();
	      glTranslated(-centre[0], -centre[1], -centre[2]);
              z = 0.f;
              for (i = 0; i < inter->movingNodes->len; i++)
		{
                  node = visu_node_array_getFromId
                    (VISU_NODE_ARRAY(inter->dataObj),
                     g_array_index(inter->movingNodes, guint, i));
		  visu_data_getNodePosition(inter->dataObj, node, xyz);
		  z += visu_gl_view_getZCoordinate(view, xyz);
		}
              z /= (float)inter->movingNodes->len;
	      /* Now, z contains the prev real space coord z of the
                 selected nodes (averaged). */

	      visu_gl_view_getRealCoordinates(view, delta,  (float)ev->x, (float)ev->y, z);
	      visu_gl_view_getRealCoordinates(view, delta0, (float)inter->xPrev,
                                              (float)inter->yPrev, z);
	      glPopMatrix();
	      /* Now, delta contains the new real space coord. */

	      delta[0] -= delta0[0];
	      delta[1] -= delta0[1];
	      delta[2] -= delta0[2];
	    }
	  else if (ev->button > 1)
	    {
	      if (ev->button == 4)
		dy = -5;
	      else if (ev->button == 5)
		dy = +5;
	      ratio = visu_gl_window_getFileUnitPerPixel(&view->window) * dy;
	      delta[0] = inter->movingAxe[0] * ratio;
	      delta[1] = inter->movingAxe[1] * ratio;
	      delta[2] = inter->movingAxe[2] * ratio;
	    }
	}
      else if (ev->button == 1)
	{
	  delta[0] = 0.f;
	  delta[1] = 0.f;
	  delta[2] = 0.f;
	  ratio = visu_gl_window_getFileUnitPerPixel(&view->window);
	  if (ABS(dy) > ABS(dx))
	    ratio *= ((dy > 0)?1.f:-1.f);
	  else
	    ratio *= ((dx > 0)?1.f:-1.f);
	  if (ev->shiftMod && !ev->controlMod)
	    delta[0] = ratio * sqrt(dx * dx + dy * dy);
	  else if (ev->controlMod && !ev->shiftMod)
	    delta[1] = ratio * sqrt(dx * dx + dy * dy);
	  else if (ev->controlMod && ev->shiftMod)
	    delta[2] = ratio * sqrt(dx * dx + dy * dy);
	}
      else
	return FALSE;

      DBG_fprintf(stderr, "Visu Interactive: drag a list of %d nodes of %gx%gx%g.\n",
		  inter->movingNodes->len,
		  delta[0], delta[1], delta[2]);
      visu_node_array_shiftNodes(VISU_NODE_ARRAY(inter->dataObj), inter->movingNodes, delta);

      /* Update stored position for drag info. */
      inter->xPrev = ev->x;
      inter->yPrev = ev->y;

      DBG_fprintf(stderr, "Visu Interactive: emit the 'move' signal.\n");
      g_signal_emit(G_OBJECT(inter),
		    interactive_signals[INTERACTIVE_MOVE_SIGNAL],
		    0 , delta, NULL);
    }
  else if ((ev->button == 1 || ev->button == 2) &&
	   ev->buttonType == TOOL_BUTTON_TYPE_PRESS)
    {
      /* Store the position to find the drag values. */
      inter->xOrig = inter->xPrev = ev->x;
      inter->yOrig = inter->yPrev = ev->y;

      /* Empty previously picked movingNodes. */
      if (inter->movingPicked)
        {
	  g_array_unref(inter->movingNodes);
	  inter->movingNodes = (GArray*)0;
        }

      /* Case no list set yet. */
      if (inter->movingNodes == (GArray*)0)
	{
          g_return_val_if_fail(inter->nodeList, FALSE);
          
          nodeId = visu_gl_ext_nodes_getSelection(inter->nodeList, ev->x, ev->y);
	  if (nodeId < 0)
	    return FALSE;

	  inter->movingNodes = g_array_new(FALSE, FALSE, sizeof(guint));
          g_array_append_val(inter->movingNodes, nodeId);
	  inter->movingPicked = TRUE;
	}

      DBG_fprintf(stderr, "Visu Interactive: emit the 'start-move' signal.\n");
      g_signal_emit(G_OBJECT(inter),
		    interactive_signals[INTERACTIVE_START_MOVE_SIGNAL],
		    0, inter->movingNodes, NULL);
    }
  else if ((ev->button == 1 || ev->button == 2) &&
           ev->buttonType == TOOL_BUTTON_TYPE_RELEASE &&
           inter->movingNodes)
    {
  /*     DBG_fprintf(stderr, "Visu Interactive: stop dragging a list of %d nodes.\n", */
  /*       	  inter->movingNodes->len); */
  /*     node = (VisuNode*)0; */
      DBG_fprintf(stderr, "Visu Interactive: emit the 'stop-move' signal.\n");
      g_signal_emit(G_OBJECT(inter),
		    interactive_signals[INTERACTIVE_STOP_MOVE_SIGNAL],
		    0, delta, NULL);
    }

  return FALSE;
}
/**
 * visu_interactive_setMovingNodes:
 * @inter: a #VisuInteractive object.
 * @nodeIds: (element-type guint) (allow-none): a list of node ids.
 *
 * Defines the nodes that should be moved if @inter is a move
 * action session. The list is actually copied.
 */
void visu_interactive_setMovingNodes(VisuInteractive *inter, GArray *nodeIds)
{
  g_return_if_fail(VISU_IS_INTERACTIVE(inter) && inter->id == interactive_move);

  /* Empty the node list. */
  if (inter->movingNodes)
    g_array_unref(inter->movingNodes);
  inter->movingNodes = (GArray*)0;
  if (nodeIds && nodeIds->len > 0)
    {
      inter->movingNodes = nodeIds;
      DBG_fprintf(stderr, "Visu Interactive: set the list of %d nodes to move.\n",
                  nodeIds->len);
      g_array_ref(nodeIds);
    }
  inter->movingPicked = FALSE;
}
/**
 * visu_interactive_setMovingAxe:
 * @inter: a #VisuInteractive object.
 * @axe: a direction.
 *
 * Defines the axe that can be used to move along if @inter is a move
 * action session.
 */
void visu_interactive_setMovingAxe(VisuInteractive *inter, float axe[3])
{
  float norm;

  norm = 1.f / sqrt(axe[0] * axe[0] + axe[1] * axe[1] + axe[2] * axe[2]);
  inter->movingAxe[0] = axe[0] * norm;
  inter->movingAxe[1] = axe[1] * norm;
  inter->movingAxe[2] = axe[2] * norm;
}
static void getDeltaScreen(gfloat delta[3], VisuGlView *view,
                           gint x, gint y, gint xOrig, gint yOrig)
{
  float z, centre[3], delta0[3];

  visu_box_getCentre(visu_boxed_getBox(VISU_BOXED(view)), centre);
  glMatrixMode(GL_MODELVIEW); 
  glPushMatrix();
  glTranslated(-centre[0], -centre[1], -centre[2]);
  z = visu_gl_view_getZCoordinate(view, centre);
  visu_gl_view_getRealCoordinates(view, delta,  (float)x, (float)y, z);
  visu_gl_view_getRealCoordinates(view, delta0, (float)xOrig, (float)yOrig, z);
  glPopMatrix();
  /* Now, delta contains the new real space coord. */

  delta[0] -= delta0[0];
  delta[1] -= delta0[1];
  delta[2] -= delta0[2];
}
static void getDeltaAlong(gfloat delta[3], VisuGlView *view, gfloat movingAxe[3])
{
  int dy;
  float ratio;

  dy = 5;
  ratio = visu_gl_window_getFileUnitPerPixel(&view->window) * dy;
  delta[0] = movingAxe[0] * ratio;
  delta[1] = movingAxe[1] * ratio;
  delta[2] = movingAxe[2] * ratio;
}
static void getDeltaAxis(gfloat delta[3], VisuGlView *view, ToolXyzDir dir, gint dx, gint dy)
{
  float ratio;

  delta[0] = 0.f;
  delta[1] = 0.f;
  delta[2] = 0.f;
  ratio = visu_gl_window_getFileUnitPerPixel(&view->window);
  DBG_fprintf(stderr, " | dx x dy : %d x %d\n", dx, dy);
  if (ABS(dy) > ABS(dx))
    ratio *= ((dy > 0)?1.f:-1.f);
  else
    ratio *= ((dx > 0)?1.f:-1.f);
  delta[dir] = ratio * sqrt(dx * dx + dy * dy);
}
static void getDelta(VisuInteractive *inter, VisuGlView *view,
                     ToolSimplifiedEvents *ev, gfloat delta[3])
{
  int dx, dy;

  /* Get the real space shift. */
  if (ev->button == 1)
    {
      dx =   ev->x - inter->xOrig;
      dy = -(ev->y - inter->yOrig);
      if (!ev->shiftMod && !ev->controlMod)
        getDeltaScreen(delta, view, ev->x, ev->y, inter->xOrig, inter->yOrig);
      else if (ev->shiftMod && !ev->controlMod)
        getDeltaAxis(delta, view, TOOL_XYZ_X, dx, dy);
      else if (ev->controlMod && !ev->shiftMod)
        getDeltaAxis(delta, view, TOOL_XYZ_Y, dx, dy);
      else if (ev->controlMod && ev->shiftMod)
        getDeltaAxis(delta, view, TOOL_XYZ_Z, dx, dy);
    }
  else
    {
      getDeltaAlong(delta, view, inter->movingAxe);
      if (ev->button == 4)
        {
          delta[0] *= -1.f;
          delta[1] *= -1.f;
          delta[2] *= -1.f;
        }
    }
}
static gboolean drag(VisuInteractive *inter, VisuGlView *view, ToolSimplifiedEvents *ev)
{
  float delta[3];

  g_return_val_if_fail(ev && inter, TRUE);
  if (ev->button == 3)
    {
      if (ev->buttonType == TOOL_BUTTON_TYPE_PRESS)
	return TRUE;
      else
	return FALSE;
    }
  if (ev->button != 1 && ev->button != 4 && ev->button != 5 &&
      ev->specialKey != Key_Arrow_Left && ev->specialKey != Key_Arrow_Right &&
      ev->specialKey != Key_Arrow_Up && ev->specialKey != Key_Arrow_Down)
    return FALSE;
  DBG_fprintf(stderr, "Visu Interactive: event (%d - %d, %d %d).\n",
              ev->button, ev->specialKey, ev->shiftMod, ev->controlMod);

  if ((ev->motion == 1 || ev->button == 4 || ev->button == 5) ||
      ev->specialKey == Key_Arrow_Left || ev->specialKey == Key_Arrow_Right ||
      ev->specialKey == Key_Arrow_Up || ev->specialKey == Key_Arrow_Down)
    {
      if (ev->specialKey == Key_Arrow_Left)
        {
          ev->x = inter->xPrev - 1;
          ev->y = inter->yPrev;
          ev->button = 1;
        }
      else if (ev->specialKey == Key_Arrow_Right)
        {
          ev->x = inter->xPrev + 1;
          ev->y = inter->yPrev;
          ev->button = 1;
        }
      else if (ev->specialKey == Key_Arrow_Up)
        {
          ev->x = inter->xPrev;
          ev->y = inter->yPrev - 1;
          ev->button = 1;
        }
      else if (ev->specialKey == Key_Arrow_Down)
        {
          ev->x = inter->xPrev;
          ev->y = inter->yPrev + 1;
          ev->button = 1;
        }
      DBG_fprintf(stderr, "Visu Interactive: drag action at (%dx%d).\n", ev->x, ev->y);

      getDelta(inter, view, ev, delta);
      DBG_fprintf(stderr, "Visu Interactive: drag a mouse of %gx%gx%g.\n",
		  delta[0], delta[1], delta[2]);

      /* Update stored position for drag info. */
      inter->xPrev = ev->x;
      inter->yPrev = ev->y;

      DBG_fprintf(stderr, "Visu Interactive: emit the 'move' signal.\n");
      g_signal_emit(G_OBJECT(inter),
		    interactive_signals[INTERACTIVE_MOVE_SIGNAL],
		    0 , delta, NULL);
    }
  else if (ev->button == 1 && ev->buttonType == TOOL_BUTTON_TYPE_PRESS)
    {
      g_return_val_if_fail(inter->nodeList, TRUE);

      /* Store the position to find the drag values. */
      inter->xOrig = inter->xPrev = ev->x;
      inter->yOrig = inter->yPrev = ev->y;

      /* DBG_fprintf(stderr, "Visu Interactive: emit the 'start-move' signal.\n"); */
      /* g_signal_emit(G_OBJECT(inter), */
      /*   	    interactive_signals[INTERACTIVE_START_MOVE_SIGNAL], */
      /*   	    0, inter->movingNodes, NULL); */
    }
  else if (ev->button == 1 && ev->buttonType == TOOL_BUTTON_TYPE_RELEASE)
    {
      getDelta(inter, view, ev, delta);
      DBG_fprintf(stderr, "Visu Interactive: stop dragging mouse of %gx%gx%g.\n",
		  delta[0], delta[1], delta[2]);
      g_signal_emit(G_OBJECT(inter),
		    interactive_signals[INTERACTIVE_STOP_MOVE_SIGNAL],
		    0 , delta, NULL);
    }

  return FALSE;
}
/**
 * visu_interactive_highlight:
 * @inter: a #VisuInteractive object.
 * @nodeId: a node id.
 *
 * This routine simulates an highlight action on @nodeId. This triggers the signal
 * #VisuInteractive::node-selection, or #VisuInteractive::selection-error
 * if @nodeId is invalid.
 *
 * Since: 3.7 
 **/
void visu_interactive_highlight(VisuInteractive *inter, guint nodeId)
{
  VisuNode *nodes;

  nodes = visu_node_array_getFromId(VISU_NODE_ARRAY(inter->dataObj), nodeId);
  if (nodes)
    g_signal_emit(G_OBJECT(inter),
                  interactive_signals[INTERACTIVE_PICK_NODE_SIGNAL],
                  0 , PICK_HIGHLIGHT, inter->dataObj, nodes, (VisuNode*)0, (VisuNode*)0);
  else
    g_signal_emit(G_OBJECT(inter),
		  interactive_signals[INTERACTIVE_PICK_ERROR_SIGNAL],
		  0 , PICK_ERROR_NO_SELECTION, NULL);
}

/**
 * visu_interactive_setMessage:
 * @inter: a #VisuInteractive object.
 * @message: some text.
 *
 * Set a describing @message to be shown when @inter is used.
 *
 * Since: 3.8
 **/
void visu_interactive_setMessage(VisuInteractive *inter, const gchar *message)
{
  g_return_if_fail(VISU_IS_INTERACTIVE(inter));

  g_free(inter->message);
  inter->message = g_strdup(message);
}

/**
 * visu_interactive_getMessage:
 * @inter: a #VisuInteractive object.
 *
 * Retrieves the message associated to @inter.
 *
 * Since: 3.8
 *
 * Returns: a message.
 **/
const gchar* visu_interactive_getMessage(VisuInteractive *inter)
{
  g_return_val_if_fail(VISU_IS_INTERACTIVE(inter), (const gchar*)0);
  return inter->message;
}

static gboolean mark(VisuInteractive *inter, ToolSimplifiedEvents *ev)
{
  int nodeId;

  g_return_val_if_fail(ev && inter, TRUE);
  if (ev->button == 3 && ev->buttonType == TOOL_BUTTON_TYPE_PRESS)
    return TRUE;
  if (ev->buttonType == TOOL_BUTTON_TYPE_RELEASE)
    return FALSE;

  g_return_val_if_fail(inter->nodeList, TRUE);

  nodeId = visu_gl_ext_nodes_getSelection(inter->nodeList, ev->x, ev->y);
  visu_interactive_highlight(inter, (guint)nodeId);
  return FALSE;
}

static void onReadCamera(VisuConfigFile *obj _U_, VisuConfigFileEntry *entry _U_, VisuInteractiveClass *klass)
{
  VisuGlCamera camera;

  memset(&camera, 0, sizeof(VisuGlCamera));
  visu_gl_camera_setThetaPhiOmega(&camera, cameraData[0], cameraData[1], cameraData[2],
                                VISU_GL_CAMERA_THETA | VISU_GL_CAMERA_PHI | VISU_GL_CAMERA_OMEGA);
  visu_gl_camera_setXsYs(&camera, cameraData[3], cameraData[4], VISU_GL_CAMERA_XS | VISU_GL_CAMERA_YS);
  visu_gl_camera_setGross(&camera, cameraData[5]);
  visu_gl_camera_setPersp(&camera, cameraData[6]);
  _pushSavedCamera(klass, &camera);
}
static void exportParameters(GString *data, VisuData *dataObj _U_)
{
  VisuGlCamera *camera;
  GList *tmp;

  if (!local_class)
    visu_interactive_get_type();

  g_string_append_printf(data, "# %s\n", DESC_PARAMETER_OBSERVE_METHOD);
  g_string_append_printf(data, "%s[gtk]: %d\n\n", FLAG_PARAMETER_OBSERVE_METHOD,
			 local_class->preferedObserveMethod);
  if (local_class->savedCameras)
    g_string_append_printf(data, "# %s\n", DESC_PARAMETER_CAMERA_SETTINGS);
  for (tmp = local_class->savedCameras; tmp; tmp = g_list_next(tmp))
    {
      camera = (VisuGlCamera*)tmp->data;
      g_string_append_printf(data, "%s[gtk]: %7.5g %7.5g %7.5g   %4.3g %4.3g   %g   %g\n",
                             FLAG_PARAMETER_CAMERA_SETTINGS,
                             camera->theta, camera->phi, camera->omega,
                             camera->xs, camera->ys, camera->gross, camera->d_red);
    }
  if (local_class->savedCameras)
    g_string_append(data, "\n");
}
void visu_interactive_class_setPreferedObserveMethod(VisuInteractiveMethod method)
{
  g_return_if_fail(method == interactive_constrained ||
		   method == interactive_walker);

  if (!local_class)
    visu_interactive_get_type();

  local_class->preferedObserveMethod = method;
}
VisuInteractiveMethod visu_interactive_class_getPreferedObserveMethod()
{
  if (!local_class)
    visu_interactive_get_type();

  return local_class->preferedObserveMethod;
}

/* From an array of nodes ids, we specify their 2D coordinates */
void visu_interactive_class_getNodes2DCoordinates(VisuData *dataObj, guint *nodeIds,
    	guint nNodes, GLfloat *coordinates2D, guint *size)
{
  int i,j;
  guint k;
  VisuNode *node;
  float xyz[3], centre[3];
  GLfloat *coord;
  GLint nValues;

  visu_box_getCentre(visu_boxed_getBox(VISU_BOXED(dataObj)), centre);
  coord = g_malloc(sizeof(GLfloat) * nNodes * (1 + 2));
  glFeedbackBuffer(nNodes * (2 + 1), GL_2D, coord);
  glRenderMode(GL_FEEDBACK);
  glPushMatrix();
  glTranslated(-centre[0], -centre[1], -centre[2]);

  /* Get nodes from ids and store their 2D coordinates */
  glBegin(GL_POINTS);
  for(k = 0; k < nNodes; k++){
    node = visu_node_array_getFromId(VISU_NODE_ARRAY(dataObj), nodeIds[k]);
    if(node != NULL) 
    {
      visu_data_getNodePosition(dataObj, node, xyz);
      glVertex3fv(xyz);
    }
  }
  glEnd();
  glPopMatrix();
  nValues = glRenderMode(GL_RENDER);
  i = 0;
  j = 0;
  /* Keep only the coordinates */
  while (i < nValues)
  {
    if (coord[i] == GL_POINT_TOKEN)
    {
      coordinates2D[j] = coord[i + 1];
      coordinates2D[j+1] = coord[i + 2];
      i += 3;
      j += 2;
    }
    else
      i++;
  }
  *size = j;
}
