/*   EXTRAITS DE LA LICENCE
	Copyright CEA, contributeurs : Damien
	CALISTE, laboratoire L_Sim, (2015)
  
	Adresse 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 : Damien
	CALISTE, laboratoire L_Sim, (2015)

	E-mail address:
	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 "ui_planetree.h"

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

#include <extraGtkFunctions/gtk_colorComboBoxWidget.h>
#include <extraGtkFunctions/gtk_numericalEntryWidget.h>
#include <extraGtkFunctions/gtk_orientationChooser.h>
#include <support.h>

/**
 * SECTION:ui_planetree
 * @short_description: Defines a #GtkListStore specialised to store #VisuPlanes.
 *
 * <para>It provides a Gtk wrapper around #VisuPlaneSet object to
 * store #VisuPlanes.</para>
 * <para>It also can creates #GtkTreeView and control widgets to
 * display itself.</para>
 */


struct _VisuUiPlaneListPrivate
{
  gboolean dispose_has_run;

  VisuPlaneSet *planes;
  gulong add_sig, remove_sig, box_sig;
  VisuBox *box;
  gulong size_sig;

  GtkWidget *hbox;
  GtkTreeView *view;
  VisuPlane *selection;

  GtkBox *controls;
  GtkBox *boxHidingMode;
  VisuUiOrientationChooser *orientationChooser;
  VisuUiNumericalEntry *entryNVect[3];
  GBinding *bindVect[3];
  GtkSpinButton *spinDistance;
  GBinding *bindDist;
  VisuUiColorCombobox *comboColor;
  GBinding *bindColor;
};

enum
  {
    ALIGN_SIGNAL,
    NB_SIGNAL
  };
static guint _signals[NB_SIGNAL] = { 0 };

enum
  {
    PROP_0,
    SELECTION_PROP,
    N_PROP
  };
static GParamSpec *properties[N_PROP];

static void visu_ui_plane_list_dispose     (GObject* obj);
static void visu_ui_plane_list_finalize    (GObject* obj);
static void visu_ui_plane_list_get_property(GObject* obj, guint property_id,
                                            GValue *value, GParamSpec *pspec);
static void visu_ui_plane_list_set_property(GObject* obj, guint property_id,
                                            const GValue *value, GParamSpec *pspec);

G_DEFINE_TYPE_WITH_CODE(VisuUiPlaneList, visu_ui_plane_list, GTK_TYPE_LIST_STORE,
                        G_ADD_PRIVATE(VisuUiPlaneList))

/* Local callbacks. */
static void onBox(VisuBoxed *boxed, VisuBox *box, gpointer data);
static void onSize(VisuBox *box, float extens, gpointer data);
static void onSelectionChanged(GtkTreeSelection *list, gpointer data);
static void onSetAdd(VisuPlaneSet *set, VisuPlane *plane, gpointer data);
static void onSetRemove(VisuPlaneSet *set, VisuPlane *plane, gpointer data);
static void onDrawnToggled(GtkCellRendererToggle *cell_renderer,
                           gchar *path, gpointer user_data);
static void onHideToggled(GtkCellRendererToggle *cell_renderer,
                          gchar *path, gpointer user_data);
static void onSideToggled(GtkCellRendererToggle *cell_renderer,
                          gchar *path, gpointer user_data);

/* Local routines. */
static void _bindControls(VisuUiPlaneList *list, VisuPlane *plane);
static gboolean _getPlane(VisuUiPlaneList *list, VisuPlane *plane, GtkTreeIter *iter);
static void _addPlane(VisuUiPlaneList *list, VisuPlane *plane, GtkTreeIter *iter);
static void _setBox(VisuUiPlaneList *list, VisuBoxed *boxed);
static void _adjustBoxSpan(VisuUiPlaneList *list);
static void _resetModel(VisuUiPlaneList *list);

static void visu_ui_plane_list_class_init(VisuUiPlaneListClass *klass)
{
  DBG_fprintf(stderr, "Visu UiPlaneList: creating the class of the object.\n");
  /* DBG_fprintf(stderr, "                - adding new signals ;\n"); */

  /* Connect the overloading methods. */
  G_OBJECT_CLASS(klass)->dispose  = visu_ui_plane_list_dispose;
  G_OBJECT_CLASS(klass)->finalize = visu_ui_plane_list_finalize;
  G_OBJECT_CLASS(klass)->set_property = visu_ui_plane_list_set_property;
  G_OBJECT_CLASS(klass)->get_property = visu_ui_plane_list_get_property;

  /**
   * VisuUiPlaneList::align:
   * @list: the object emitting the signal.
   * @plane: the requested #VisuPlane object for alignment.
   *
   * This signal is emitted each time the user request to align with a
   * plane.
   *
   * Since: 3.8
   */
  _signals[ALIGN_SIGNAL] =
    g_signal_new("align", G_TYPE_FROM_CLASS(klass),
                 G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS,
                 0, NULL, NULL, g_cclosure_marshal_VOID__OBJECT,
                 G_TYPE_NONE, 1, VISU_TYPE_PLANE, NULL);

  /**
   * VisuUiPlaneList::selection:
   *
   * Current selection.
   *
   * Since: 3.8
   */
  properties[SELECTION_PROP] = g_param_spec_object
    ("selection", "Selection", "current selection", VISU_TYPE_PLANE, G_PARAM_READWRITE);
  g_object_class_install_property(G_OBJECT_CLASS(klass), SELECTION_PROP, properties[SELECTION_PROP]);
}

static void visu_ui_plane_list_init(VisuUiPlaneList *list)
{
  GType types[VISU_UI_PLANE_LIST_N_COLUMNS] = {VISU_TYPE_PLANE, G_TYPE_ULONG, G_TYPE_INT};

  DBG_fprintf(stderr, "Visu UiPlaneList: initializing a new object (%p).\n",
	      (gpointer)list);
  list->priv = visu_ui_plane_list_get_instance_private(list);
  list->priv->dispose_has_run = FALSE;

  gtk_list_store_set_column_types(GTK_LIST_STORE(list), VISU_UI_PLANE_LIST_N_COLUMNS, types);

  list->priv->planes = (VisuPlaneSet*)0;
  list->priv->box = (VisuBox*)0;

  list->priv->hbox = (GtkWidget*)0;
  list->priv->view = (GtkTreeView*)0;

  list->priv->controls = (GtkBox*)0;
  list->priv->boxHidingMode = (GtkBox*)0;
  list->priv->orientationChooser = (VisuUiOrientationChooser*)0;

  list->priv->bindVect[0] = (GBinding*)0;
  list->priv->bindVect[1] = (GBinding*)0;
  list->priv->bindVect[2] = (GBinding*)0;
  list->priv->bindDist = (GBinding*)0;
  list->priv->bindColor = (GBinding*)0;
}

/* This method can be called several times.
   It should unref all of its reference to
   GObjects. */
static void visu_ui_plane_list_dispose(GObject* obj)
{
  VisuUiPlaneList *list;

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

  list = VISU_UI_PLANE_LIST(obj);
  if (list->priv->dispose_has_run)
    return;
  list->priv->dispose_has_run = TRUE;

  _resetModel(list);

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

  g_return_if_fail(obj);

  DBG_fprintf(stderr, "Visu UiPlaneList: finalize object %p.\n", (gpointer)obj);
  list = VISU_UI_PLANE_LIST(obj)->priv;

  if (list->hbox)
    g_object_unref(list->hbox);
  if (list->controls)
      g_object_unref(list->controls);
  if (list->orientationChooser)
      g_object_unref(list->orientationChooser);

  /* Chain up to the parent class */
  DBG_fprintf(stderr, "Visu UiPlaneList: chain to parent.\n");
  G_OBJECT_CLASS(visu_ui_plane_list_parent_class)->finalize(obj);
  DBG_fprintf(stderr, "Visu UiPlaneList: freeing ... OK.\n");
}
static void visu_ui_plane_list_get_property(GObject* obj, guint property_id,
                                            GValue *value, GParamSpec *pspec)
{
  DBG_fprintf(stderr, "Visu UiPlaneList: get property '%s' -> ",
	      g_param_spec_get_name(pspec));
  switch (property_id)
    {
    case SELECTION_PROP:
      g_value_set_object(value, VISU_UI_PLANE_LIST(obj)->priv->selection);
      DBG_fprintf(stderr, "%p.\n", g_value_get_object(value));
      break;
    default:
      /* We don't have any other property... */
      G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, property_id, pspec);
      break;
    }
}
static void visu_ui_plane_list_set_property(GObject* obj, guint property_id,
                                            const GValue *value, GParamSpec *pspec)
{
  VisuUiPlaneList *list = VISU_UI_PLANE_LIST(obj);
  GtkTreeIter iter;

  DBG_fprintf(stderr, "Visu UiPlaneList: set property '%s' -> ",
	      g_param_spec_get_name(pspec));
  switch (property_id)
    {
    case SELECTION_PROP:
      if (list->priv->view && _getPlane(list, VISU_PLANE(g_value_get_object(value)), &iter))
        gtk_tree_selection_select_iter(gtk_tree_view_get_selection(list->priv->view), &iter);
      break;
    default:
      /* We don't have any other property... */
      G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, property_id, pspec);
      break;
    }
}

/**
 * visu_ui_plane_list_new:
 *
 * Create a new #GtkListStore to store planes.
 *
 * Since: 3.8
 *
 * Returns: a newly created object.
 **/
VisuUiPlaneList* visu_ui_plane_list_new()
{
  VisuUiPlaneList *list;

  list = VISU_UI_PLANE_LIST(g_object_new(VISU_TYPE_UI_PLANE_LIST, NULL));
  return list;
}

/**
 * visu_ui_plane_list_getModel:
 * @list: a #VisuUiPlaneList object.
 *
 * Retrieve the #VisuPlaneSet object that @list is built upon.
 *
 * Since: 3.8
 *
 * Returns: (transfer none): a #VisuPlaneSet object.
 **/
VisuPlaneSet* visu_ui_plane_list_getModel(const VisuUiPlaneList *list)
{
  g_return_val_if_fail(VISU_IS_UI_PLANE_LIST(list), (VisuPlaneSet*)0);

  return list->priv->planes;
}
static void _setBox(VisuUiPlaneList *list, VisuBoxed *boxed)
{
  DBG_fprintf(stderr, "VisuUi Planes: set box from boxed %p.\n", (gpointer)boxed);
  if (list->priv->box)
    {
      g_signal_handler_disconnect(G_OBJECT(list->priv->box), list->priv->size_sig);
      g_object_unref(G_OBJECT(list->priv->box));
    }
  list->priv->box = (boxed) ? visu_boxed_getBox(boxed) : (VisuBox*)0;
  if (list->priv->box)
    {
      g_object_ref(G_OBJECT(list->priv->box));
      list->priv->size_sig = g_signal_connect(G_OBJECT(list->priv->box), "SizeChanged",
                                              G_CALLBACK(onSize), (gpointer)list);
    }
}
static void _resetModel(VisuUiPlaneList *list)
{
  if (list->priv->planes)
    {
      DBG_fprintf(stderr, "Visu UiPlaneList: disconnecting old plane set %p.\n",
                  (gpointer)list->priv->planes);
      g_signal_handler_disconnect(G_OBJECT(list->priv->planes), list->priv->add_sig);
      g_signal_handler_disconnect(G_OBJECT(list->priv->planes), list->priv->remove_sig);
      g_signal_handler_disconnect(G_OBJECT(list->priv->planes), list->priv->box_sig);
      g_object_unref(G_OBJECT(list->priv->planes));
      DBG_fprintf(stderr, "Visu UiPlaneList: done.");
    }
  _setBox(list, (VisuBoxed*)0);
  list->priv->planes = (VisuPlaneSet*)0;
}
/**
 * visu_ui_plane_list_setModel:
 * @list: a #VisuUiPlaneList object.
 * @set: (allow-none): a #VisuPlaneSet object.
 *
 * Bind the @set object to @list.
 *
 * Returns: TRUE if @set is changed
 **/
gboolean visu_ui_plane_list_setModel(VisuUiPlaneList *list, VisuPlaneSet *set)
{
  VisuPlaneSetIter iter;

  g_return_val_if_fail(VISU_IS_UI_PLANE_LIST(list), FALSE);

  if (list->priv->planes == set)
    return FALSE;

  DBG_fprintf(stderr, "Visu UiPlaneList: resetting model.\n");
  gtk_list_store_clear(GTK_LIST_STORE(list));
  DBG_fprintf(stderr, "Visu UiPlaneList: done.\n");

  _resetModel(list);
  list->priv->planes = set;

  /* Populate the listview with existing planes in the model. */
  if (set)
    {
      g_object_ref(set);
      visu_plane_set_iter_new(set, &iter);
      for (visu_plane_set_iter_next(&iter); iter.plane; visu_plane_set_iter_next(&iter))
        _addPlane(list, iter.plane, (GtkTreeIter*)0);

      list->priv->add_sig = g_signal_connect(G_OBJECT(set), "added",
                                             G_CALLBACK(onSetAdd), (gpointer)list);
      list->priv->remove_sig = g_signal_connect(G_OBJECT(set), "removed",
                                                G_CALLBACK(onSetRemove), (gpointer)list);
      list->priv->box_sig = g_signal_connect(G_OBJECT(set), "setBox",
                                             G_CALLBACK(onBox), (gpointer)list);
      _setBox(list, VISU_BOXED(set));
      _adjustBoxSpan(list);
    }

  return TRUE;
}

static gboolean _getPlane(VisuUiPlaneList *list, VisuPlane *plane, GtkTreeIter *iter)
{
  gboolean valid;
  VisuPlane *pl;
  GtkTreeModel *model = GTK_TREE_MODEL(list);
  
  pl = (VisuPlane*)0;
  for (valid = gtk_tree_model_get_iter_first(model, iter);
       valid;
       valid = gtk_tree_model_iter_next(model, iter))
    {
      gtk_tree_model_get(model, iter, VISU_UI_PLANE_LIST_POINTER, &pl, -1);
      g_object_unref(pl);
      if (pl == plane)
        break;
      else
        pl = (VisuPlane*)0;
    }
  return (pl != (VisuPlane*)0);
}
static void onBox(VisuBoxed *boxed, VisuBox *box _U_, gpointer data)
{
  _setBox(VISU_UI_PLANE_LIST(data), boxed);
  _adjustBoxSpan(VISU_UI_PLANE_LIST(data));
}
static void onPlaneNotify(GObject *obj, GParamSpec *pspec, gpointer data)
{
  GtkTreeIter iter;
  GtkTreePath *path;

  if (_getPlane(VISU_UI_PLANE_LIST(data), VISU_PLANE(obj), &iter))
    {
      if (!strcmp(g_param_spec_get_name(pspec), "hidding-side") &&
          visu_plane_getHiddenState(VISU_PLANE(obj)) != VISU_PLANE_SIDE_NONE)
        gtk_list_store_set(GTK_LIST_STORE(data), &iter,
                           VISU_UI_PLANE_LIST_MODE, visu_plane_getHiddenState(VISU_PLANE(obj)), -1);
      else
        {
          path = gtk_tree_model_get_path(GTK_TREE_MODEL(data), &iter);
          g_signal_emit_by_name(G_OBJECT(data), "row-changed", path, &iter, NULL);
          gtk_tree_path_free(path);
        }
    }
}
static void _addPlane(VisuUiPlaneList *list, VisuPlane *plane, GtkTreeIter *iter)
{
  GtkTreeIter it;
  gulong sig;
  gint mode;
  
  /* String used to labelled planes, dist. means 'distance' and
     norm. means 'normal' (50 chars max). */
  sig = g_signal_connect_object(G_OBJECT(plane), "notify",
                                G_CALLBACK(onPlaneNotify), (gpointer)list, 0);
  mode = visu_plane_getHiddenState(plane);
  gtk_list_store_append(GTK_LIST_STORE(list), &it);
  gtk_list_store_set(GTK_LIST_STORE(list), &it,
		     VISU_UI_PLANE_LIST_POINTER, (gpointer)plane,
                     VISU_UI_PLANE_LIST_NOTIFY, sig,
                     VISU_UI_PLANE_LIST_MODE, (mode != VISU_PLANE_SIDE_NONE) ? mode : VISU_PLANE_SIDE_PLUS,
		     -1);

  if (list->priv->boxHidingMode)
    gtk_widget_set_sensitive(GTK_WIDGET(list->priv->boxHidingMode),
                             (gtk_tree_model_iter_n_children(GTK_TREE_MODEL(list),
                                                             (GtkTreeIter*)0) > 1));

  if (iter)
    *iter = it;
  DBG_fprintf(stderr, "Ui PlaneTree: %p has %d ref counts.\n", (gpointer)plane,
              G_OBJECT(plane)->ref_count);

}
static void onSetAdd(VisuPlaneSet *set _U_, VisuPlane *plane, gpointer data)
{
  GtkTreeIter iter;
  VisuUiPlaneList *list = VISU_UI_PLANE_LIST(data);

  _addPlane(list, plane, &iter);

  if (list->priv->view)
    gtk_tree_selection_select_iter
      (gtk_tree_view_get_selection(list->priv->view), &iter);
}

static void onSetRemove(VisuPlaneSet *set _U_, VisuPlane *plane, gpointer data)
{
  GtkTreeIter iter;
  gboolean valid;
  gulong sig;
  VisuUiPlaneList *list = VISU_UI_PLANE_LIST(data);
  GtkTreeModel *model = GTK_TREE_MODEL(list);
  
  if (_getPlane(list, plane, &iter))
    {
      gtk_tree_model_get(model, &iter, VISU_UI_PLANE_LIST_NOTIFY, &sig, -1);
      g_signal_handler_disconnect(G_OBJECT(plane), sig);
      valid = TRUE;
      if (!gtk_list_store_remove(GTK_LIST_STORE(list), &iter))
        valid = gtk_tree_model_get_iter_first(model, &iter);
      if (valid && list->priv->view)
        gtk_tree_selection_select_iter
          (gtk_tree_view_get_selection(list->priv->view), &iter);

      if (list->priv->boxHidingMode && gtk_tree_model_iter_n_children(model, (GtkTreeIter*)0) < 2)
        gtk_widget_set_sensitive(GTK_WIDGET(list->priv->boxHidingMode), FALSE);
      DBG_fprintf(stderr, "Visu UiPlaneList: OK plane found and removed.\n");
    }
}

static void onSize(VisuBox *box _U_, float extens _U_, gpointer data)
{
  _adjustBoxSpan(VISU_UI_PLANE_LIST(data));
}

static void onAdd(GtkButton *button _U_, gpointer userData)
{
  VisuPlane* plane;
  float vect[3];
  const ToolColor *color;
  float dist;
  VisuUiPlaneList *list = VISU_UI_PLANE_LIST(userData);

  /* We create a new plane. */
  vect[0] = (float)visu_ui_numerical_entry_getValue(list->priv->entryNVect[0]);
  vect[1] = (float)visu_ui_numerical_entry_getValue(list->priv->entryNVect[1]);
  vect[2] = (float)visu_ui_numerical_entry_getValue(list->priv->entryNVect[2]);
  dist = (float)gtk_spin_button_get_value(list->priv->spinDistance);
  color = visu_ui_color_combobox_getSelection(list->priv->comboColor);
  plane = visu_plane_new((VisuBox*)0, vect, dist, color);
  
  /* We add it to the model. */ 
  visu_plane_set_add(list->priv->planes, plane);
  /* We count down the counter on plane since it has been added to the list. */
  g_object_unref(G_OBJECT(plane));
  DBG_fprintf(stderr, "Ui PlaneTree: plane added.\n");
}
static void onRemove(GtkButton *button _U_, gpointer data)
{
  VisuPlane *plane;
  VisuUiPlaneList *list = VISU_UI_PLANE_LIST(data);

  plane = visu_ui_plane_list_getSelection(list);
  if (!plane)
    return;

  visu_plane_set_remove(list->priv->planes, plane);
}
static void onAlign(GtkButton *button _U_, gpointer data)
{
  VisuPlane *plane;

  plane = visu_ui_plane_list_getSelection(VISU_UI_PLANE_LIST(data));
  if (!plane)
    return;
  g_signal_emit(G_OBJECT(data), _signals[ALIGN_SIGNAL], 0, plane);
}
static void _displayRender(GtkTreeViewColumn *tree_column _U_, GtkCellRenderer *cell,
                           GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer data _U_)
{
  VisuPlane *plane;

  gtk_tree_model_get(tree_model, iter, VISU_UI_PLANE_LIST_POINTER, &plane, -1);
  g_object_set(cell, "active", visu_plane_getRendered(plane), NULL);
  g_object_unref(plane);
}
static void _displayParam(GtkTreeViewColumn *tree_column _U_, GtkCellRenderer *cell,
                          GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer data _U_)
{
  VisuPlane *plane;
  gchar str[256];
  float vect[3];

  gtk_tree_model_get(tree_model, iter, VISU_UI_PLANE_LIST_POINTER, &plane, -1);

  visu_plane_getNVectUser(plane, vect);
  sprintf(str, _("<b>norm.</b>: (%3d;%3d;%3d)\n<b>distance</b>: %6.2f"),
	  (int)vect[0], (int)vect[1], (int)vect[2], visu_plane_getDistanceFromOrigin(plane));
  g_object_set(cell, "markup", str, NULL);
  g_object_unref(plane);
}
static void _displayMask(GtkTreeViewColumn *tree_column _U_, GtkCellRenderer *cell,
                         GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer data _U_)
{
  VisuPlane *plane;

  gtk_tree_model_get(tree_model, iter, VISU_UI_PLANE_LIST_POINTER, &plane, -1);
  g_object_set(cell, "active", (visu_plane_getHiddenState(plane) != VISU_PLANE_SIDE_NONE), NULL);
  g_object_unref(plane);
}
static void _displaySide(GtkTreeViewColumn *tree_column _U_, GtkCellRenderer *cell,
                         GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer data _U_)
{
  VisuPlane *plane;
  gint mode;

  gtk_tree_model_get(tree_model, iter, VISU_UI_PLANE_LIST_POINTER, &plane,
                     VISU_UI_PLANE_LIST_MODE, &mode, -1);
  if (visu_plane_getHiddenState(plane) != VISU_PLANE_SIDE_NONE)
    g_object_set(cell, "active", (visu_plane_getHiddenState(plane) == VISU_PLANE_SIDE_MINUS), NULL);
  else
    g_object_set(cell, "active", (mode == VISU_PLANE_SIDE_MINUS), NULL);
  g_object_unref(plane);
}
static void _displayColor(GtkTreeViewColumn *tree_column _U_, GtkCellRenderer *cell,
                           GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer data _U_)
{
  VisuPlane *plane;
  GdkPixbuf *pix;

  gtk_tree_model_get(tree_model, iter, VISU_UI_PLANE_LIST_POINTER, &plane, -1);
  pix = tool_color_get_stamp(visu_plane_getColor(plane), TRUE);
  g_object_set(cell, "pixbuf", pix, NULL);
  g_object_unref(pix);
  g_object_unref(plane);
}
/**
 * visu_ui_plane_list_getView:
 * @list: a #VisuUiPlaneList object.
 *
 * Retrieve a treeview and a side toolbar that displays the list
 * of planes of the model.
 *
 * Since: 3.8
 *
 * Returns: (transfer full): widgets are created if needed.
 **/
GtkWidget* visu_ui_plane_list_getView(VisuUiPlaneList *list)
{
  GtkCellRenderer *renderer;
  GtkTreeViewColumn *column;
  GtkWidget *image, *wd;
  GtkToolItem *item;
#if GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION < 12
  GtkTooltips *tooltips;

  tooltips = gtk_tooltips_new ();
#endif

  g_return_val_if_fail(VISU_IS_UI_PLANE_LIST(list), (GtkWidget*)0);

  if (list->priv->hbox)
    {
      g_object_ref(list->priv->hbox);
      return list->priv->hbox;
    }

  list->priv->hbox = gtk_hbox_new(FALSE, 0);

  wd = gtk_scrolled_window_new(NULL, NULL);
  gtk_box_pack_start(GTK_BOX(list->priv->hbox), wd, TRUE, TRUE, 0);
  gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(wd),
                                 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
  gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(wd), GTK_SHADOW_IN);

  list->priv->view = GTK_TREE_VIEW(gtk_tree_view_new());
  gtk_container_add(GTK_CONTAINER(wd), GTK_WIDGET(list->priv->view));
  gtk_tree_view_set_headers_visible(list->priv->view, TRUE);

  /* Render the associated list */
  renderer = gtk_cell_renderer_toggle_new ();
  g_signal_connect(G_OBJECT(renderer), "toggled",
		   G_CALLBACK(onDrawnToggled), (gpointer)list);
  column = gtk_tree_view_column_new_with_attributes(_("Drawn"), renderer, NULL);
  gtk_tree_view_append_column(list->priv->view, column);
  gtk_tree_view_column_set_cell_data_func(column, renderer, _displayRender,
                                          (gpointer)0, (GDestroyNotify)0);

  renderer = gtk_cell_renderer_text_new ();
  column = gtk_tree_view_column_new_with_attributes(_("Parameters"), renderer, NULL);
  gtk_tree_view_column_set_expand(column, TRUE);
  gtk_tree_view_column_set_alignment(column, 0.5);
  gtk_tree_view_append_column (list->priv->view, column);
  gtk_tree_view_column_set_cell_data_func(column, renderer, _displayParam,
                                          (gpointer)0, (GDestroyNotify)0);

  renderer = gtk_cell_renderer_toggle_new ();
  g_signal_connect(G_OBJECT(renderer), "toggled",
		   G_CALLBACK(onHideToggled), (gpointer)list);
  column = gtk_tree_view_column_new_with_attributes (_("Mask"), renderer, NULL);
  image = create_pixmap((GtkWidget*)0, "stock-masking.png");
  gtk_tree_view_column_set_widget(column, image);
  gtk_tree_view_append_column (list->priv->view, column);
  gtk_tree_view_column_set_cell_data_func(column, renderer, _displayMask,
                                          (gpointer)0, (GDestroyNotify)0);

  renderer = gtk_cell_renderer_toggle_new ();
  g_signal_connect(G_OBJECT(renderer), "toggled",
		   G_CALLBACK(onSideToggled), (gpointer)list);
  column = gtk_tree_view_column_new_with_attributes (_("Invert"), renderer, NULL);
  gtk_tree_view_append_column (list->priv->view, column);
  gtk_tree_view_column_set_cell_data_func(column, renderer, _displaySide,
                                          (gpointer)0, (GDestroyNotify)0);

  renderer = gtk_cell_renderer_pixbuf_new ();
  column = gtk_tree_view_column_new_with_attributes (_("Color"), renderer, NULL);
  image = gtk_image_new_from_icon_name("applications-graphics",
                                       GTK_ICON_SIZE_SMALL_TOOLBAR);
  gtk_tree_view_column_set_widget(column, image);
  gtk_tree_view_append_column (list->priv->view, column);
  gtk_tree_view_column_set_cell_data_func(column, renderer, _displayColor,
                                          (gpointer)0, (GDestroyNotify)0);

  gtk_tree_selection_set_mode(gtk_tree_view_get_selection(list->priv->view),
			      GTK_SELECTION_SINGLE);
  g_signal_connect(gtk_tree_view_get_selection(list->priv->view),
		   "changed", G_CALLBACK(onSelectionChanged), (gpointer)list);

  gtk_tree_view_set_model(list->priv->view, GTK_TREE_MODEL(list));

  wd = gtk_toolbar_new();
  gtk_orientable_set_orientation(GTK_ORIENTABLE(wd), GTK_ORIENTATION_VERTICAL);
  gtk_toolbar_set_style(GTK_TOOLBAR(wd), GTK_TOOLBAR_ICONS);
  gtk_toolbar_set_icon_size(GTK_TOOLBAR(wd), GTK_ICON_SIZE_SMALL_TOOLBAR);
  gtk_box_pack_start(GTK_BOX(list->priv->hbox), wd, FALSE, FALSE, 0);

  item = gtk_tool_button_new(NULL, NULL);
  gtk_tool_button_set_icon_name(GTK_TOOL_BUTTON(item), "list-add");
  g_signal_connect(G_OBJECT(item), "clicked", G_CALLBACK(onAdd), (gpointer)list);
  gtk_toolbar_insert(GTK_TOOLBAR(wd), item, -1);
  item = gtk_tool_button_new(NULL, NULL);
  gtk_tool_button_set_icon_name(GTK_TOOL_BUTTON(item), "list-remove");
  g_signal_connect(G_OBJECT(item), "clicked", G_CALLBACK(onRemove), (gpointer)list);
  gtk_toolbar_insert(GTK_TOOLBAR(wd), item, -1);
  item = gtk_tool_button_new(create_pixmap((GtkWidget*)0, "stock_rotate_20.png"), _("align"));
  g_signal_connect(G_OBJECT(item), "clicked", G_CALLBACK(onAlign), (gpointer)list);
  gtk_toolbar_insert(GTK_TOOLBAR(wd), item, -1);
  gtk_widget_set_tooltip_text(GTK_WIDGET(item), _("Set the camera to look in the direction"
                                                  " normal to the selected plane."));


  g_object_ref(list->priv->hbox);
  return list->priv->hbox;
}

/**
 * visu_ui_plane_list_getAt:
 * @list: a #VisuUiPlaneList object.
 * @i: an integer.
 *
 * Retrieve the #VisuPlane object, stored at the place @i in @list.
 *
 * Since: 3.8
 *
 * Returns: (transfer full): a #VisuPlane object or NULL.
 **/
VisuPlane* visu_ui_plane_list_getAt(VisuUiPlaneList *list, guint i)
{
  GtkTreeIter iter;
  VisuPlane *plane;

  if (!gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(list), &iter, NULL, i))
    return (VisuPlane*)0;

  gtk_tree_model_get(GTK_TREE_MODEL(list), &iter,
                     VISU_UI_PLANE_LIST_POINTER, &plane, -1);
  return plane;
}
/**
 * visu_ui_plane_list_getSelection:
 * @list: a #VisuUiPlaneList object.
 *
 * Retrieve the selected #VisuPlane, or NULL if no selection.
 *
 * Since: 3.8
 *
 * Returns: (transfer none): a #VisuPlane object or NULL.
 **/
VisuPlane* visu_ui_plane_list_getSelection(const VisuUiPlaneList *list)
{
  g_return_val_if_fail(VISU_IS_UI_PLANE_LIST(list), (VisuPlane*)0);

  return list->priv->selection;
}
static void onSelectionChanged(GtkTreeSelection *tree, gpointer data)
{
  VisuUiPlaneList *list = VISU_UI_PLANE_LIST(data);
  GtkTreeIter it;

  list->priv->selection = (VisuPlane*)0;
  if (gtk_tree_selection_get_selected(tree, NULL, &it))
    {
      gtk_tree_model_get(GTK_TREE_MODEL(data), &it,
                         VISU_UI_PLANE_LIST_POINTER, &list->priv->selection, -1);
      g_object_unref(G_OBJECT(list->priv->selection));
    }
  _bindControls(list, list->priv->selection);
  g_object_notify_by_pspec(G_OBJECT(data), properties[SELECTION_PROP]);
}

static void onDrawnToggled(GtkCellRendererToggle *cell_renderer,
                           gchar *path, gpointer user_data)
{
  gboolean validIter;
  GtkTreeIter iter;
  VisuPlane *plane;

  validIter = gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(user_data), &iter, path);
  g_return_if_fail(validIter);
  gtk_tree_model_get(GTK_TREE_MODEL(user_data), &iter, VISU_UI_PLANE_LIST_POINTER, &plane, -1);
  g_object_unref(G_OBJECT(plane));

  g_object_set(plane, "rendered", !gtk_cell_renderer_toggle_get_active(cell_renderer), NULL);
}
static void onHideToggled(GtkCellRendererToggle *cell_renderer _U_,
                          gchar *path, gpointer user_data)
{
  gboolean validIter;
  GtkTreeIter iter;
  VisuPlane *plane;
  gint mode;

  validIter = gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(user_data), &iter, path);
  g_return_if_fail(validIter);
  gtk_tree_model_get(GTK_TREE_MODEL(user_data), &iter,
                     VISU_UI_PLANE_LIST_POINTER, &plane,
                     VISU_UI_PLANE_LIST_MODE, &mode, -1);
  g_object_unref(G_OBJECT(plane));

  if (visu_plane_getHiddenState(plane) != VISU_PLANE_SIDE_NONE)
    visu_plane_setHiddenState(plane, VISU_PLANE_SIDE_NONE);
  else
    visu_plane_setHiddenState(plane, mode);
}
static void onSideToggled(GtkCellRendererToggle *cell_renderer _U_,
                          gchar *path, gpointer user_data)
{
  gboolean validIter;
  GtkTreeIter iter;
  VisuPlane *plane;
  gint mode;

  validIter = gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(user_data), &iter, path);
  g_return_if_fail(validIter);
  gtk_tree_model_get(GTK_TREE_MODEL(user_data), &iter,
                     VISU_UI_PLANE_LIST_POINTER, &plane,
                     VISU_UI_PLANE_LIST_MODE, &mode, -1);
  g_object_unref(G_OBJECT(plane));

  if (visu_plane_getHiddenState(plane) == VISU_PLANE_SIDE_MINUS)
    visu_plane_setHiddenState(plane, VISU_PLANE_SIDE_PLUS);
  else if (visu_plane_getHiddenState(plane) == VISU_PLANE_SIDE_PLUS)
    visu_plane_setHiddenState(plane, VISU_PLANE_SIDE_MINUS);
  else
    gtk_list_store_set(GTK_LIST_STORE(user_data), &iter, VISU_UI_PLANE_LIST_MODE, mode * -1, -1);
}

static gboolean _getVect(GBinding *binding _U_, const GValue *from, GValue *to, gpointer data)
{
  float *vect;
  vect = (float*)g_value_get_boxed(from);
  g_value_set_double(to, vect[GPOINTER_TO_INT(data)]);
  return TRUE;
}
static gboolean _setVect(GBinding *binding, const GValue *from, GValue *to, gpointer data)
{
  float vect[3], oldValue;
  visu_plane_getNVectUser(VISU_PLANE(g_binding_get_source(binding)), vect);
  oldValue = vect[GPOINTER_TO_INT(data)];
  vect[GPOINTER_TO_INT(data)] = (float)g_value_get_double(from);
  if (vect[0] == 0.f && vect[1] == 0.f && vect[2] == 0.f)
    {
      visu_ui_numerical_entry_warnValue(VISU_UI_NUMERICAL_ENTRY(g_binding_get_target(binding)), oldValue);
      return FALSE;
    }
  g_value_set_boxed(to, vect);
  return TRUE;
}
static void _bindControls(VisuUiPlaneList *list, VisuPlane *plane)
{
#define BIND_VECT(I) {list->priv->bindVect[I] = g_object_bind_property_full \
                        (plane, "n-vector", list->priv->entryNVect[I], "value", \
                         G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE, _getVect, _setVect, \
                         GINT_TO_POINTER(I), (GDestroyNotify)0);}
  if (list->priv->bindVect[0])
    g_object_unref(list->priv->bindVect[0]);
  if (list->priv->bindVect[1])
    g_object_unref(list->priv->bindVect[1]);
  if (list->priv->bindVect[2])
    g_object_unref(list->priv->bindVect[2]);
  if (list->priv->bindDist)
    g_object_unref(list->priv->bindDist);
  if (list->priv->bindColor)
    g_object_unref(list->priv->bindColor);
  if (plane && list->priv->controls)
    {
      BIND_VECT(0);
      BIND_VECT(1);
      BIND_VECT(2);
      list->priv->bindDist = g_object_bind_property
        (plane, "distance", list->priv->spinDistance, "value",
         G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
      list->priv->bindColor = g_object_bind_property
        (plane, "color", list->priv->comboColor, "color",
         G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
    }
  else
    {
      list->priv->bindVect[0] = (GBinding*)0;
      list->priv->bindVect[1] = (GBinding*)0;
      list->priv->bindVect[2] = (GBinding*)0;
      list->priv->bindDist    = (GBinding*)0;
      list->priv->bindColor   = (GBinding*)0;
    }
}
static void onOrientationChanged(VisuUiOrientationChooser *orientationChooser,
				 gpointer data)
{
  float values[3];
  VisuUiPlaneList *list = VISU_UI_PLANE_LIST(data);

  visu_ui_orientation_chooser_getOrthoValues(orientationChooser, values);
  visu_ui_numerical_entry_setValue(list->priv->entryNVect[0], (double)values[0]);
  visu_ui_numerical_entry_setValue(list->priv->entryNVect[1], (double)values[1]);
  visu_ui_numerical_entry_setValue(list->priv->entryNVect[2], (double)values[2]);
}
static void onOrientation(GtkButton *button _U_, gpointer data)
{
  float values[3];
  VisuUiPlaneList *list = VISU_UI_PLANE_LIST(data);

  if (!list->priv->orientationChooser)
    {
      list->priv->orientationChooser = VISU_UI_ORIENTATION_CHOOSER
        (visu_ui_orientation_chooser_new(VISU_UI_ORIENTATION_NORMAL, TRUE,
                                         VISU_BOXED(list->priv->planes), NULL));
/*       gtk_window_set_modal(GTK_WINDOW(orientationChooser), TRUE); */
      values[0] = (float)visu_ui_numerical_entry_getValue(list->priv->entryNVect[0]);
      values[1] = (float)visu_ui_numerical_entry_getValue(list->priv->entryNVect[1]);
      values[2] = (float)visu_ui_numerical_entry_getValue(list->priv->entryNVect[2]);
      visu_ui_orientation_chooser_setOrthoValues(list->priv->orientationChooser, values);
      g_signal_connect(G_OBJECT(list->priv->orientationChooser), "values-changed",
		       G_CALLBACK(onOrientationChanged), data);
    }
  else
    gtk_window_present(GTK_WINDOW(list->priv->orientationChooser));
  
  gtk_widget_show(GTK_WIDGET(list->priv->orientationChooser));
  switch (gtk_dialog_run(GTK_DIALOG(list->priv->orientationChooser)))
    {
    case GTK_RESPONSE_ACCEPT:
      DBG_fprintf(stderr, "Visu UiPlaneList: accept changings on orientation.\n");
      break;
    default:
      DBG_fprintf(stderr, "Visu UiPlaneList: reset values on orientation.\n");
      visu_ui_numerical_entry_setValue(list->priv->entryNVect[0], (double)values[0]);
      visu_ui_numerical_entry_setValue(list->priv->entryNVect[1], (double)values[1]);
      visu_ui_numerical_entry_setValue(list->priv->entryNVect[2], (double)values[2]);
    }
  gtk_widget_destroy(GTK_WIDGET(list->priv->orientationChooser));
  list->priv->orientationChooser = (VisuUiOrientationChooser*)0;
}
static gboolean _setMode(GBinding *binding _U_, const GValue *from, GValue *to, gpointer data)
{
  if (!g_value_get_boolean(from))
    return FALSE;
  g_value_set_uint(to, GPOINTER_TO_INT(data));
  return TRUE;
}
static gboolean _getMode(GBinding *binding _U_, const GValue *from, GValue *to, gpointer data)
{
  if (g_value_get_uint(from) != (guint)GPOINTER_TO_INT(data))
    return FALSE;
  g_value_set_boolean(to, TRUE);
  return TRUE;
}
static void _buildHidingMode(VisuUiPlaneList *list)
{
  GtkWidget *hbox, *label, *wd;
  GSList *group;
#if GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION < 12
  GtkTooltips *tooltips;

  tooltips = gtk_tooltips_new ();
#endif
#define DEFINE_RADIO(K, G) {wd = gtk_radio_button_new(NULL);            \
    gtk_box_pack_start(GTK_BOX(list->priv->boxHidingMode), wd, FALSE, FALSE, 0); \
    gtk_radio_button_set_group(GTK_RADIO_BUTTON(wd), G);                \
    g_object_bind_property_full(list->priv->planes, "hidding-mode", wd, "active", \
                                G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE, \
                                _getMode, _setMode, GINT_TO_POINTER(K), (GDestroyNotify)0);}
#define SET_LABEL(I, T) {hbox = gtk_hbox_new(FALSE, 2);                 \
    gtk_container_add(GTK_CONTAINER(wd), hbox);                         \
    gtk_box_pack_start(GTK_BOX(hbox), create_pixmap(NULL, I), FALSE, FALSE, 0); \
    gtk_box_pack_start(GTK_BOX(hbox), gtk_label_new(T), FALSE, FALSE, 0);}

  list->priv->boxHidingMode = GTK_BOX(gtk_hbox_new(FALSE, 0));

  label = gtk_label_new(_("Hiding mode: "));
  gtk_box_pack_start(GTK_BOX(list->priv->boxHidingMode), label, FALSE, FALSE, 0);

  DEFINE_RADIO(VISU_PLANE_SET_HIDE_UNION, (GSList*)0);
  group = gtk_radio_button_get_group(GTK_RADIO_BUTTON(wd));
  gtk_widget_set_tooltip_text(wd, _("Hide all elements that are hidden by at least one plane."));
  SET_LABEL("stock-union.png", _("Union"));

  DEFINE_RADIO(VISU_PLANE_SET_HIDE_INTER, group);
  gtk_widget_set_tooltip_text(wd, _("Hide elements only if they are hidden by all planes."));
  SET_LABEL("stock-inter.png", _("Intersection"));

  gtk_widget_set_sensitive(GTK_WIDGET(list->priv->boxHidingMode),
                           (gtk_tree_model_iter_n_children(GTK_TREE_MODEL(list),
                                                           (GtkTreeIter*)0) > 1));
}
static void _adjustBoxSpan(VisuUiPlaneList *list)
{
  float span[2];
  float diag[3], ext[3], red[3] = {1.f, 1.f, 1.f};

  if (!list->priv->spinDistance)
    return;

  if (list->priv->box)
    {
      visu_box_convertBoxCoordinatestoXYZ(list->priv->box, diag, red);
      visu_box_getExtension(list->priv->box, ext);
      diag[0] *= diag[0];
      diag[1] *= diag[1];
      diag[2] *= diag[2];
      span[0] = -sqrt(ext[0] * ext[0] * diag[0] +
                      ext[1] * ext[1] * diag[1] +
                      ext[2] * ext[2] * diag[2]);
      span[1] = sqrt((ext[0] + 1.f) *
                     (ext[0] + 1.f) * diag[0] +
                     (ext[1] + 1.f) *
                     (ext[1] + 1.f) * diag[1] +
                     (ext[2] + 1.f) *
                     (ext[2] + 1.f) * diag[2]);
    }
  else
    {
      span[0] = -998.f;
      span[1] = 1000.f;
    }
  gtk_spin_button_set_range(list->priv->spinDistance, span[0], span[1]);
}
/**
 * visu_ui_plane_list_getControls:
 * @list: a #VisuUiPlaneList object.
 *
 * Retrieve the #GtkBox object with the #GtkWidget to control the
 * selected plane in the treeview of @list.
 *
 * Since: 3.8
 *
 * Returns: (transfer full): widgets are created if needed.
 **/
GtkBox* visu_ui_plane_list_getControls(VisuUiPlaneList *list)
{
  GtkWidget *hbox, *label, *wd, *image;
  guint i;
  double span[2];
#define PARAM_LINE(T) {    hbox = gtk_hbox_new(FALSE, 2);               \
    gtk_box_pack_start(GTK_BOX(list->priv->controls), hbox, FALSE, FALSE, 0); \
    label = gtk_label_new(T);                                           \
    gtk_label_set_xalign(GTK_LABEL(label), 0.);                         \
    gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE, 0);}
  
  g_return_val_if_fail(VISU_IS_UI_PLANE_LIST(list), (GtkBox*)0);

  if (list->priv->controls)
    {
      g_object_ref(list->priv->controls);
      return list->priv->controls;
    }

  list->priv->controls = GTK_BOX(gtk_vbox_new(FALSE, 2));
  gtk_container_set_border_width(GTK_CONTAINER(list->priv->controls), 5);
  g_object_ref(list->priv->controls);

  /* Hiding Mode */
  _buildHidingMode(list);
  gtk_box_pack_start(GTK_BOX(list->priv->controls), GTK_WIDGET(list->priv->boxHidingMode), TRUE, TRUE, 2);

  /* VisuPlanes parameters */
  PARAM_LINE(_("Normal: "));
  for (i = 0; i < 3; i++)
    {
      list->priv->entryNVect[i] = VISU_UI_NUMERICAL_ENTRY(visu_ui_numerical_entry_new(1.));
      gtk_entry_set_width_chars(GTK_ENTRY(list->priv->entryNVect[i]), 5);
      gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(list->priv->entryNVect[i]), FALSE, FALSE, 0);
    }
  wd = gtk_button_new();
  gtk_box_pack_start(GTK_BOX(hbox), wd, FALSE, FALSE, 0);
  g_signal_connect(G_OBJECT(wd), "clicked", G_CALLBACK(onOrientation), (gpointer)list);
  image = create_pixmap((GtkWidget*)0, "axes-button.png");
  gtk_container_add(GTK_CONTAINER(wd), image);

  PARAM_LINE(_("Distance from origin: "));
  list->priv->spinDistance = GTK_SPIN_BUTTON(gtk_spin_button_new_with_range(-1, 1., 0.25));
  _adjustBoxSpan(list);
  gtk_spin_button_set_digits(list->priv->spinDistance, 2);
  gtk_spin_button_get_range(list->priv->spinDistance, span, span + 1);
  gtk_spin_button_set_value(list->priv->spinDistance, (span[0] + span[1]) / 2.);
  gtk_spin_button_set_numeric(list->priv->spinDistance, TRUE);
  gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(list->priv->spinDistance), FALSE, FALSE, 0);

  PARAM_LINE(_("Color: "));
  list->priv->comboColor = VISU_UI_COLOR_COMBOBOX(visu_ui_color_combobox_new(TRUE));
  gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(list->priv->comboColor), FALSE, FALSE, 0);

  g_object_ref(list->priv->controls);
  return list->priv->controls;
}
