/*   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 "dataFile.h"

#include <string.h>

#include <visu_tools.h>
#include <visu_configFile.h>
#include <openGLFunctions/objectList.h>
#include <coreTools/toolColor.h>
#include <coreTools/toolShade.h>
#include <coreTools/toolArray.h>

#include "visu_commandLine.h"

/**
 * SECTION:dataFile
 * @short_description: Adds a possibility to colorize nodes depending
 * on data read in an input file.
 *
 * <para>With this module, it is possible to colorize nodes depending
 * on data read in an input file. An input file can be associated to a
 * #VisuData object using visu_colorization_new(). Doing this, the
 * rendering is changed and nodes are colorized following a scheme
 * describe later. To turn off colorization without removing the data
 * file (for temporary turn off for instance), use
 * visu_data_colorizer_setActive().</para>
 * <para>The input file must have the same numbers of uncommented
 * lines as there are nodes in the #VisuData associated with. If less
 * data is given, missing data are treaded as min values data. The
 * input data file can has as much column as desired. The colorization
 * is based on a linear color transformation. This transformation is
 * applied on color channel in RGB mode or in HSV mode. Resulting
 * color is given by : [resulting color vect] = [vectB] + [input
 * data][vectA], where [input data] are input data scaled to [0;1]. It
 * is possible to choose which column multiplies which color
 * channel.</para>
 * <para>It is implementing #VisuNodeMasker interface, thus allowing
 * to hide nodes depending on colorization values.</para>
 */

struct _VisuColorizationPrivate
{
  gboolean dispose_has_run;

  /* Storage for data. */
  gulong file_sig;
  VisuBox *box;
  gulong sig_box;
  float boxSize[3];
  VisuNodeValuesFarray *model;
  gulong sig_readMinMax, sig_nElements;
  GArray *manualMinMax; /* Min and max values for manual scale, for each column. */

  /* Representation. */
  int colUsed[3]; /* Columns to be used for each channel. */
  int scaleUsed; /* Column to be used for radius scaling. */
  VisuColorizationInputScaleId scaleType; /* Scheme to scale : manual or auto. */
  gboolean applyToAll; /* Colourisation is applied only on values in range when FALSE. */

  /* Hiding function and data. */
  VisuNodeMaskerFunc maskerFunc;
  gpointer maskerData;
  GDestroyNotify maskerDestroy;
};

enum
  {
    PROP_0,
    BOX_PROP,
    N_COLS_PROP,
    SINGLE_COL_PROP,
    SINGLE_MM_PROP,
    APPLY_ALL_PROP,
    FILE_PROP,
    NORM_PROP,
    COL_R_PROP,
    COL_G_PROP,
    COL_B_PROP,
    COL_SIZE_PROP,
    READ_MM_PROP,
    MANUAL_MM_PROP,
    N_PROP
  };
static GParamSpec *properties[N_PROP];

static void visu_colorization_dispose     (GObject* obj);
static void visu_colorization_finalize    (GObject* obj);
static void visu_colorization_get_property(GObject* obj, guint property_id,
                                           GValue *value, GParamSpec *pspec);
static void visu_colorization_set_property(GObject* obj, guint property_id,
                                           const GValue *value, GParamSpec *pspec);
static void visu_node_masker_interface_init(VisuNodeMaskerInterface *iface);
static gboolean _setMaskFunc(VisuNodeMasker *self, VisuNodeMaskerFunc func,
                             gpointer data, GDestroyNotify destroy);
static gboolean _maskApply(const VisuNodeMasker *self, VisuNodeArray *array);

/* Default values */
#define DATAFILE_SCALE_TYPE_DEFAULT VISU_COLORIZATION_NORMALIZE
#define DATAFILE_MIN_NORM_DEFAULT    0.
#define DATAFILE_MAX_NORM_DEFAULT   +1.

#define RESOURCE_RANGE_NAME "colorization_restrictInRange"
#define RESOURCE_RANGE_DESC "Apply colourisation only if in range."

/* Internal variables. */
static gboolean restrictInRange = FALSE;

/* local methods. */
static void _setNodeModel(VisuColorization *dt, VisuNodeValues *model);
static gboolean _toValues(const VisuDataColorizerShaded *self, gfloat values[3],
                          const VisuData *visuData, const VisuNode* node);
static gfloat _scale(const VisuDataColorizer *self,
                     const VisuData *visuData, const VisuNode *node);
static void exportResources(GString *data, VisuData *dataObj);
static void onEntryRange(VisuConfigFile *obj, VisuConfigFileEntry *entry, gpointer data);
static void onBoxSize(VisuColorization *dt, float extens, VisuBox *box);
static void onReadMinMax(VisuColorization *dt);
static void onNColumns(VisuColorization *dt);
static void onModelNotified(VisuColorization *dt);
static void onShadeNotified(VisuColorization *dt);

G_DEFINE_TYPE_WITH_CODE(VisuColorization, visu_colorization,
                        VISU_TYPE_DATA_COLORIZER_SHADED,
                        G_ADD_PRIVATE(VisuColorization)
                        G_IMPLEMENT_INTERFACE(VISU_TYPE_NODE_MASKER,
                                              visu_node_masker_interface_init))

static void visu_colorization_class_init(VisuColorizationClass *klass)
{
  VisuConfigFileEntry *resourceEntry;

  resourceEntry = visu_config_file_addBooleanEntry(VISU_CONFIG_FILE_RESOURCE,
                                                   RESOURCE_RANGE_NAME,
                                                   RESOURCE_RANGE_DESC,
                                                   &restrictInRange, FALSE);
  visu_config_file_entry_setVersion(resourceEntry, 3.7f);
  visu_config_file_addExportFunction(VISU_CONFIG_FILE_RESOURCE, exportResources);

  /* Connect the overloading methods. */
  G_OBJECT_CLASS(klass)->dispose  = visu_colorization_dispose;
  G_OBJECT_CLASS(klass)->finalize = visu_colorization_finalize;
  G_OBJECT_CLASS(klass)->set_property = visu_colorization_set_property;
  G_OBJECT_CLASS(klass)->get_property = visu_colorization_get_property;
  VISU_DATA_COLORIZER_CLASS(klass)->scale = _scale;
  VISU_DATA_COLORIZER_SHADED_CLASS(klass)->toValues = _toValues;

  /**
   * VisuColorization::box:
   *
   * The box from which the boundaries are used to colorize using the coordinates.
   *
   * Since: 3.8
   */
  properties[BOX_PROP] = g_param_spec_object("box", "Box", "box",
                                             VISU_TYPE_BOX, G_PARAM_READWRITE |
                                             G_PARAM_STATIC_STRINGS);
  /**
   * VisuColorization::n-columns:
   *
   * Number of data stored per node.
   *
   * Since: 3.8
   */
  properties[N_COLS_PROP] = g_param_spec_uint("n-columns", "N columns", "number of columns",
                                              0, G_MAXUINT, 0, G_PARAM_READABLE |
                                              G_PARAM_STATIC_STRINGS);
  /**
   * VisuColorization::single-param:
   *
   * If colorization data is based on the variation of a single
   * column. If it's not the case, this parameter is set to #VISU_COLORIZATION_UNSET.
   *
   * Since: 3.8
   */
  properties[SINGLE_COL_PROP] = g_param_spec_int("single-param", "Single parameter",
                                                 "colorization data is single variable",
                                                 VISU_COLORIZATION_UNSET, G_MAXINT,
                                                 VISU_COLORIZATION_UNSET, G_PARAM_READWRITE |
                                                 G_PARAM_STATIC_STRINGS);
  /**
   * VisuColorization::single-range:
   *
   * If colorization data is based on the variation of a single
   * column, this stores its min and max values (either for automatic
   * normalisation or from manual settings).
   *
   * Since: 3.8
   */
  properties[SINGLE_MM_PROP] = g_param_spec_boxed("single-range", "Single range",
                                                  "applied min and max values",
                                                  TOOL_TYPE_MINMAX, G_PARAM_READABLE |
                                                  G_PARAM_STATIC_STRINGS);
  /**
   * VisuColorization::apply-all:
   *
   * Colourization is applied to all nodes, or only to those in range.
   *
   * Since: 3.8
   */
  properties[APPLY_ALL_PROP] = g_param_spec_boolean("apply-all", "Apply all",
                                                    "apply colorization on all nodes",
                                                    !restrictInRange,
                                                    G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
  /**
   * VisuColorization::source-file:
   *
   * Name of the source file the data come from.
   *
   * Since: 3.8
   */
  properties[FILE_PROP] = g_param_spec_string("source-file", "Source file",
                                              "Source file if any",
                                              (const gchar*)0,
                                              G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
  /**
   * VisuColorization::normalisation:
   *
   * Normalisation method for the input data.
   *
   * Since: 3.8
   */
  properties[NORM_PROP] = g_param_spec_uint("normalisation", "Normalisation",
                                            "input normalisation scheme",
                                            VISU_COLORIZATION_NORMALIZE,
                                            VISU_COLORIZATION_MINMAX,
                                            DATAFILE_SCALE_TYPE_DEFAULT,
                                            G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
  /**
   * VisuColorization::column-red:
   *
   * Column to be used to set the red channel values.
   *
   * Since: 3.8
   */
  properties[COL_R_PROP] = g_param_spec_int("column-red", "Column red value",
                                            "column the red channel is read from",
                                            VISU_COLORIZATION_UNSET, G_MAXINT, 
                                            VISU_COLORIZATION_UNSET,
                                            G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
  /**
   * VisuColorization::column-green:
   *
   * Column to be used to set the green channel values.
   *
   * Since: 3.8
   */
  properties[COL_G_PROP] = g_param_spec_int("column-green", "Column green value",
                                            "column the green channel is read from",
                                            VISU_COLORIZATION_UNSET, G_MAXINT, 
                                            VISU_COLORIZATION_UNSET,
                                            G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
  /**
   * VisuColorization::column-blue:
   *
   * Column to be used to set the green channel values.
   *
   * Since: 3.8
   */
  properties[COL_B_PROP] = g_param_spec_int("column-blue", "Column blue value",
                                            "column the blue channel is read from",
                                            VISU_COLORIZATION_UNSET, G_MAXINT, 
                                            VISU_COLORIZATION_UNSET,
                                            G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
  /**
   * VisuColorization::column-size:
   *
   * Column to be used to set the node scaling factor.
   *
   * Since: 3.8
   */
  properties[COL_SIZE_PROP] = g_param_spec_int("column-size", "Column size value",
                                               "column the size factor is read from",
                                               VISU_COLORIZATION_UNSET, G_MAXINT, 
                                               VISU_COLORIZATION_UNSET,
                                               G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
  /**
   * VisuColorization::data-min-max:
   *
   * Min / max values of the stored data.
   *
   * Since: 3.8
   */
  properties[READ_MM_PROP] = g_param_spec_boxed("data-min-max", "Data min/max",
                                                "min / max values of data",
                                                G_TYPE_ARRAY,
                                                G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
  /**
   * VisuColorization::range-min-max:
   *
   * Min / max range as used to normalise data. Default are the data
   * min / max themselves.
   *
   * Since: 3.8
   */
  properties[MANUAL_MM_PROP] = g_param_spec_boxed("range-min-max", "Range min/max",
                                                  "min / max range to normalise data",
                                                  G_TYPE_ARRAY,
                                                  G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);

  g_object_class_install_properties(G_OBJECT_CLASS(klass), N_PROP, properties);
}
static void visu_node_masker_interface_init(VisuNodeMaskerInterface *iface)
{
  iface->set_mask_func = _setMaskFunc;
  iface->apply = _maskApply;
}

static void visu_colorization_init(VisuColorization *obj)
{
  float init[2] = {DATAFILE_MIN_NORM_DEFAULT, G_MAXFLOAT};

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

  obj->priv->box             = (VisuBox*)0;
  obj->priv->manualMinMax    = g_array_sized_new(FALSE, FALSE, sizeof(float) * 2, 3);
  g_array_insert_vals(obj->priv->manualMinMax, 0, init, 1);
  g_array_insert_vals(obj->priv->manualMinMax, 1, init, 1);
  g_array_insert_vals(obj->priv->manualMinMax, 2, init, 1);
  obj->priv->scaleType       = DATAFILE_SCALE_TYPE_DEFAULT;
  obj->priv->colUsed[0]      = VISU_COLORIZATION_UNSET;
  obj->priv->colUsed[1]      = VISU_COLORIZATION_UNSET;
  obj->priv->colUsed[2]      = VISU_COLORIZATION_UNSET;
  obj->priv->scaleUsed       = VISU_COLORIZATION_UNSET;
  obj->priv->applyToAll      = !restrictInRange;
  obj->priv->maskerFunc      = (VisuNodeMaskerFunc)0;
  obj->priv->maskerData      = 0;
  obj->priv->maskerDestroy   = (GDestroyNotify)0;
  obj->priv->model           = (VisuNodeValuesFarray*)0;

  g_signal_connect_object(VISU_CONFIG_FILE_RESOURCE, "parsed::" RESOURCE_RANGE_NAME,
                          G_CALLBACK(onEntryRange), obj, G_CONNECT_AFTER);

  g_signal_connect(obj, "notify::model", G_CALLBACK(onModelNotified), (gpointer)0);
  g_signal_connect(obj, "notify::shade", G_CALLBACK(onShadeNotified), (gpointer)0);
}

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

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

  ext = VISU_COLORIZATION(obj);
  if (ext->priv->dispose_has_run)
    return;
  ext->priv->dispose_has_run = TRUE;

  g_object_freeze_notify(obj);
  visu_colorization_setBox(VISU_COLORIZATION(obj), (VisuBox*)0);
  _setNodeModel(VISU_COLORIZATION(obj), (VisuNodeValues*)0);

  if (ext->priv->maskerData && ext->priv->maskerDestroy)
    ext->priv->maskerDestroy(ext->priv->maskerData);

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

  g_return_if_fail(obj);

  DBG_fprintf(stderr, "Visu Colorization: finalize object %p.\n", (gpointer)obj);
  ext = VISU_COLORIZATION(obj)->priv;

  g_array_unref(ext->manualMinMax);

  /* Chain up to the parent class */
  DBG_fprintf(stderr, "Visu Colorization: chain to parent.\n");
  G_OBJECT_CLASS(visu_colorization_parent_class)->finalize(obj);
  DBG_fprintf(stderr, "Visu Colorization: freeing ... OK.\n");
}
static void visu_colorization_get_property(GObject* obj, guint property_id,
                                           GValue *value, GParamSpec *pspec)
{
  VisuColorizationPrivate *self = VISU_COLORIZATION(obj)->priv;
  VisuNodeValues *model;
  int id;
  float mm[2];

  DBG_fprintf(stderr, "Visu Colorization: get property '%s' -> ",
	      g_param_spec_get_name(pspec));
  switch (property_id)
    {
    case BOX_PROP:
      g_value_set_object(value, self->box);
      DBG_fprintf(stderr, "%p.\n", (gpointer)self->box);
      break;
    case N_COLS_PROP:
      g_value_set_uint(value, visu_colorization_getNColumns(VISU_COLORIZATION(obj)));
      DBG_fprintf(stderr, "%d.\n", g_value_get_uint(value));
      break;
    case SINGLE_COL_PROP:
      visu_colorization_getSingleColumnId(VISU_COLORIZATION(obj), &id);
      g_value_set_int(value, id);
      DBG_fprintf(stderr, "%d.\n", id);
      break;
    case SINGLE_MM_PROP:
      g_value_set_boxed(value, (gconstpointer)0);
      if (visu_colorization_getSingleColumnId(VISU_COLORIZATION(obj), &id))
        {
          if (self->scaleType == VISU_COLORIZATION_NORMALIZE && id < 0)
            {
              mm[0] = 0.f;
              mm[1] = -1.f;
              if (id == VISU_COLORIZATION_FROM_X)
                mm[1] = self->boxSize[TOOL_XYZ_X];
              else if (id == VISU_COLORIZATION_FROM_Y)
                mm[1] = self->boxSize[TOOL_XYZ_Y];
              else if (id == VISU_COLORIZATION_FROM_Z)
                mm[1] = self->boxSize[TOOL_XYZ_Z];
              g_value_set_boxed(value, mm);
            }
          else if (self->scaleType == VISU_COLORIZATION_NORMALIZE)
            {
              model = visu_sourceable_getNodeModel(VISU_SOURCEABLE(obj));
              if (model)
                {
                  visu_node_values_farray_getColumnMinMax(VISU_NODE_VALUES_FARRAY(model), mm, id);
                  g_value_set_boxed(value, mm);
                }
            }
          else
            g_value_set_boxed(value, &g_array_index(self->manualMinMax, float, 2 * (id + 3)));
        }
      DBG_fprintf(stderr, "%d.\n", id);
      break;
    case APPLY_ALL_PROP:
      g_value_set_boolean(value, self->applyToAll);
      DBG_fprintf(stderr, "%d.\n", self->applyToAll);
      break;
    case FILE_PROP:
      model = visu_sourceable_getNodeModel(VISU_SOURCEABLE(obj));
      if (model)
        g_object_get_property(G_OBJECT(model), "source-file", value);
      else
        g_value_set_static_string(value, (const gchar*)0);
      DBG_fprintf(stderr, "'%s'.\n", g_value_get_string(value));
      break;
    case NORM_PROP:
      g_value_set_uint(value, self->scaleType);
      DBG_fprintf(stderr, "%d.\n", self->scaleType);
      break;
    case COL_R_PROP:
      g_value_set_int(value, self->colUsed[0]);
      DBG_fprintf(stderr, "%d.\n", self->colUsed[0]);
      break;
    case COL_G_PROP:
      g_value_set_int(value, self->colUsed[1]);
      DBG_fprintf(stderr, "%d.\n", self->colUsed[1]);
      break;
    case COL_B_PROP:
      g_value_set_int(value, self->colUsed[2]);
      DBG_fprintf(stderr, "%d.\n", self->colUsed[2]);
      break;
    case COL_SIZE_PROP:
      g_value_set_int(value, self->scaleUsed);
      DBG_fprintf(stderr, "%d.\n", self->scaleUsed);
      break;
    case READ_MM_PROP:
      model = visu_sourceable_getNodeModel(VISU_SOURCEABLE(obj));
      if (VISU_IS_NODE_VALUES_FARRAY(model))
        g_object_get_property(G_OBJECT(model), "data-min-max", value);
      else
        g_value_set_boxed(value, (gconstpointer)0);
      DBG_fprintf(stderr, "%p.\n", (gpointer)g_value_get_boxed(value));
      break;
    case MANUAL_MM_PROP:
      g_value_set_boxed(value, self->manualMinMax);
      DBG_fprintf(stderr, "%p.\n", (gpointer)self->manualMinMax);
      break;
    default:
      /* We don't have any other property... */
      G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, property_id, pspec);
      break;
    }
}
static void visu_colorization_set_property(GObject* obj, guint property_id,
                                           const GValue *value, GParamSpec *pspec)
{
  VisuColorization *self = VISU_COLORIZATION(obj);
  GArray *arr;
  const ToolShade *shade;
  int vals[3];
  const float *vectA, *vectB;

  DBG_fprintf(stderr, "Visu Colorization: set property '%s' -> ",
	      g_param_spec_get_name(pspec));
  switch (property_id)
    {
    case BOX_PROP:
      DBG_fprintf(stderr, "%p.\n", g_value_get_object(value));
      visu_colorization_setBox(self, VISU_BOX(g_value_get_object(value)));
      break;
    case APPLY_ALL_PROP:
      visu_colorization_setRestrictInRange(self, !g_value_get_boolean(value));
      DBG_fprintf(stderr, "%d.\n", self->priv->applyToAll);
      break;
    case SINGLE_COL_PROP:
      DBG_fprintf(stderr, "%d.\n", g_value_get_int(value));
      if (g_value_get_int(value) == VISU_COLORIZATION_UNSET)
        break;
      if (g_value_get_int(value) < visu_colorization_getNColumns(self))
        vals[0] = vals[1] = vals[2] = g_value_get_int(value);
      else
        vals[0] = vals[1] = vals[2] = VISU_COLORIZATION_UNSET;
      shade = visu_data_colorizer_shaded_getShade(VISU_DATA_COLORIZER_SHADED(obj));
      if (tool_shade_getMode(shade) == TOOL_SHADE_MODE_LINEAR)
        {
          tool_shade_getLinearCoeff(shade, &vectA, &vectB);
          vals[0] = (vectA[0] == 0.f && self->priv->colUsed[0] != VISU_COLORIZATION_UNSET) ? self->priv->colUsed[0] : vals[0];
          vals[1] = (vectA[1] == 0.f && self->priv->colUsed[1] != VISU_COLORIZATION_UNSET) ? self->priv->colUsed[1] : vals[1];
          vals[2] = (vectA[2] == 0.f && self->priv->colUsed[2] != VISU_COLORIZATION_UNSET) ? self->priv->colUsed[2] : vals[2];
        }
      visu_colorization_setColUsedArr(self, vals);
      break;
    case NORM_PROP:
      visu_colorization_setScaleType(self, g_value_get_uint(value));
      DBG_fprintf(stderr, "%d.\n", self->priv->scaleType);
      break;
    case COL_R_PROP:
      visu_colorization_setColUsed(self, g_value_get_int(value), 0);
      DBG_fprintf(stderr, "%d.\n", self->priv->colUsed[0]);
      break;
    case COL_G_PROP:
      visu_colorization_setColUsed(self, g_value_get_int(value), 1);
      DBG_fprintf(stderr, "%d.\n", self->priv->colUsed[1]);
      break;
    case COL_B_PROP:
      visu_colorization_setColUsed(self, g_value_get_int(value), 2);
      DBG_fprintf(stderr, "%d.\n", self->priv->colUsed[2]);
      break;
    case COL_SIZE_PROP:
      visu_colorization_setScalingUsed(self, g_value_get_int(value));
      DBG_fprintf(stderr, "%d.\n", self->priv->scaleUsed);
      break;
    case MANUAL_MM_PROP:
      arr = (GArray*)g_value_dup_boxed(value);
      g_array_unref(self->priv->manualMinMax);
      self->priv->manualMinMax = arr;
      DBG_fprintf(stderr, "%p.\n", (gpointer)self->priv->manualMinMax);
      if (self->priv->scaleType == VISU_COLORIZATION_MINMAX)
        {
          g_object_notify_by_pspec(obj, properties[SINGLE_MM_PROP]);
          visu_data_colorizer_setDirty(VISU_DATA_COLORIZER(self));
        }
      break;
    default:
      /* We don't have any other property... */
      G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, property_id, pspec);
      break;
    }
}

/**
 * visu_colorization_new:
 *
 * Create a new object to store colorisation data.
 *
 * Since: 3.7
 *
 * Returns: (transfer full): a newly created #VisuColorization.
 **/
VisuColorization* visu_colorization_new()
{
  VisuColorization *dataFile;

  DBG_fprintf(stderr, "Visu Colorization: creating new object.\n");
  dataFile = VISU_COLORIZATION(g_object_new(VISU_TYPE_COLORIZATION, NULL));

  return dataFile;
}

/**
 * visu_colorization_setBox:
 * @dt: a #VisuColorization object.
 * @box: (allow-none): a #VisuBox object.
 *
 * Change the associated @box for @dt. This box is used for the
 * coordinate colourisation.
 *
 * Since: 3.8
 *
 * Returns: TRUE if value has been changed.
 **/
gboolean visu_colorization_setBox(VisuColorization *dt, VisuBox *box)
{
  g_return_val_if_fail(VISU_IS_COLORIZATION(dt), FALSE);

  if (dt->priv->box == box)
    return FALSE;

  if (dt->priv->box)
    {
      g_signal_handler_disconnect(G_OBJECT(dt->priv->box), dt->priv->sig_box);
      g_object_unref(dt->priv->box);
    }
  if (box)
    {
      g_object_ref(box);
      dt->priv->sig_box = g_signal_connect_swapped(G_OBJECT(box), "SizeChanged",
                                                   G_CALLBACK(onBoxSize), (gpointer)dt);
      onBoxSize(dt, 0.f, box);
    }
  dt->priv->box = box;

  return TRUE;
}

/**
 * visu_colorization_setNodeModel:
 * @dt: a #VisuColorization object.
 * @values: (allow-none): a #VisuNodeValuesFarray object.
 *
 * Change the #VisuNodeValuesFarray this @dt is associated to.
 *
 * Since: 3.8
 **/
void visu_colorization_setNodeModel(VisuColorization *dt, VisuNodeValuesFarray *values)
{
  visu_sourceable_setNodeModel(VISU_SOURCEABLE(dt), VISU_NODE_VALUES(values));
}

static void _setNodeModel(VisuColorization *dt, VisuNodeValues *model)
{
  guint i, nbColumns;
  float minmax[2];
  
  g_return_if_fail(VISU_IS_NODE_VALUES_FARRAY(model) || !model);

  if (dt->priv->model)
    {
      g_signal_handler_disconnect(G_OBJECT(dt->priv->model), dt->priv->sig_readMinMax);
      g_signal_handler_disconnect(G_OBJECT(dt->priv->model), dt->priv->sig_nElements);
      g_object_unref(dt->priv->model);
    }
  dt->priv->model = VISU_NODE_VALUES_FARRAY(model);
  if (model)
    {
      g_object_ref(model);
      dt->priv->sig_readMinMax = g_signal_connect_swapped
        (G_OBJECT(model), "notify::data-min-max", G_CALLBACK(onReadMinMax), dt);
      dt->priv->sig_nElements = g_signal_connect_swapped
        (G_OBJECT(model), "notify::n-elements", G_CALLBACK(onNColumns), dt);
    }
  onReadMinMax(dt);
  onNColumns(dt);

  nbColumns = model ? visu_node_values_getDimension(model) : 0;
  /* Copy the read minMax into the manual minMax. */
  for (i = dt->priv->manualMinMax->len - 3; i < nbColumns; i++)
    {
      visu_node_values_farray_getColumnMinMax(VISU_NODE_VALUES_FARRAY(model), minmax, i);
      g_array_append_vals(dt->priv->manualMinMax, minmax, 1);
    }
  g_array_set_size(dt->priv->manualMinMax, nbColumns + 3);
  g_object_notify_by_pspec(G_OBJECT(dt), properties[MANUAL_MM_PROP]);

  /* Select a default column if not any already. */
  if (dt->priv->colUsed[0] == VISU_COLORIZATION_UNSET &&
      dt->priv->colUsed[1] == VISU_COLORIZATION_UNSET &&
      dt->priv->colUsed[2] == VISU_COLORIZATION_UNSET &&
      nbColumns)
    g_object_set(dt, "single-param", 0, NULL);

  g_object_notify_by_pspec(G_OBJECT(dt), properties[SINGLE_MM_PROP]);
}
static void onModelNotified(VisuColorization *dt)
{
  VisuNodeValues *values;

  g_object_get(dt, "model", &values, NULL);
  _setNodeModel(dt, values);
  if (values)
    g_object_unref(values);

  g_object_notify_by_pspec(G_OBJECT(dt), properties[FILE_PROP]);

}

/**
 * visu_colorization_getFile:
 * @dt: (allow-none): a #VisuColorization object.
 *
 * If the given @dt has an input data file already loaded, it returns its name.
 *
 * Returns: the name of the input data file if set.
 */
const gchar* visu_colorization_getFile(const VisuColorization *dt)
{
  if (dt && visu_sourceable_getNodeModel(VISU_SOURCEABLE(dt)))
    return visu_node_values_farray_getFile(VISU_NODE_VALUES_FARRAY(visu_sourceable_getNodeModel(VISU_SOURCEABLE(dt))));
  else
    return (const gchar*)0;
}
/**
 * visu_colorization_setRestrictInRange:
 * @dt: a #VisuColorization object with some data file information.
 * @status: a boolean.
 *
 * The colourisation can be applied on all nodes or on nodes within
 * range. See visu_colorization_getRestrictInRange() and
 * visu_colorization_setMin() and visu_colorization_setMax().
 *
 * Since: 3.7
 *
 * Returns: TRUE if the status is changed indeed.
 */
gboolean visu_colorization_setRestrictInRange(VisuColorization *dt, gboolean status)
{
  g_return_val_if_fail(VISU_IS_COLORIZATION(dt), FALSE);

  if (dt->priv->applyToAll != status)
    return FALSE;

  dt->priv->applyToAll = !status;
  g_object_notify_by_pspec(G_OBJECT(dt), properties[APPLY_ALL_PROP]);

  visu_data_colorizer_setDirty(VISU_DATA_COLORIZER(dt));

  return TRUE;
}
/**
 * visu_colorization_getRestrictInRange:
 * @dt: a #VisuColorization object with some data file information.
 *
 * The colourisation can be applied on all nodes or on nodes within
 * range. See visu_colorization_setRestrictInRange().
 *
 * Since: 3.7
 *
 * Returns: TRUE if colourisation is done only if values are in range.
 */
gboolean visu_colorization_getRestrictInRange(const VisuColorization *dt)
{
  g_return_val_if_fail(VISU_IS_COLORIZATION(dt), TRUE);
  return !dt->priv->applyToAll;
}
/**
 * visu_colorization_setScaleType:
 * @dt: a #VisuColorization object ;
 * @scale: an integer.
 *
 * This method is used to change the scale method used on input data.
 * See #VisuColorizationInputScaleId for further informations. This method raises
 * a error if no input file has already been associated to the give @visuData.
 *
 * Returns: TRUE if VisuNodeArray::RenderingChanged should be emitted.
 */
gboolean visu_colorization_setScaleType(VisuColorization *dt, VisuColorizationInputScaleId scale)
{
  g_return_val_if_fail(VISU_IS_COLORIZATION(dt), FALSE);

  DBG_fprintf(stderr, "Visu Colorization: set the scale type to %d (previuosly %d).\n",
	      scale, dt->priv->scaleType);
  if (scale == dt->priv->scaleType)
    return FALSE;
  dt->priv->scaleType = scale;
  g_object_notify_by_pspec(G_OBJECT(dt), properties[NORM_PROP]);
  g_object_notify_by_pspec(G_OBJECT(dt), properties[SINGLE_MM_PROP]);

  visu_data_colorizer_setDirty(VISU_DATA_COLORIZER(dt));

  return TRUE;
}
/**
 * visu_colorization_getScaleType:
 * @dt: (allow-none): a #VisuColorization object.
 *
 * Retrieve the scaling method of input data associated to the given @dt.
 *
 * Returns: the scaling method if @dt is not NULL
 *          or the default value if not.
 */
VisuColorizationInputScaleId visu_colorization_getScaleType(const VisuColorization *dt)
{
  if (!dt || !VISU_IS_COLORIZATION(dt))
    return DATAFILE_SCALE_TYPE_DEFAULT;
  else
    return dt->priv->scaleType;
}
static gboolean _setManualMinMax(VisuColorization *dt,
                                 float val, int column, guint minmax)
{
  float *minMax;

  g_return_val_if_fail(VISU_IS_COLORIZATION(dt), FALSE);
  g_return_val_if_fail(column >= VISU_COLORIZATION_FROM_X &&
                       (guint)(column + 3) < dt->priv->manualMinMax->len, FALSE);

  minMax = &g_array_index(dt->priv->manualMinMax, float, 2 * (column + 3));
  DBG_fprintf(stderr, "Visu Colorization: set the min/max (%d) value"
              " of column %d to %f (previuosly %f).\n",
	      minmax, column, val, minMax[minmax]);
  if (minMax[minmax] == val)
    return FALSE;
  minMax[minmax] = val;
  g_object_notify_by_pspec(G_OBJECT(dt), properties[MANUAL_MM_PROP]);
  if (dt->priv->scaleType == VISU_COLORIZATION_MINMAX)
    g_object_notify_by_pspec(G_OBJECT(dt), properties[SINGLE_MM_PROP]);

  if (dt->priv->scaleType == VISU_COLORIZATION_MINMAX)
    visu_data_colorizer_setDirty(VISU_DATA_COLORIZER(dt));

  return TRUE;
}
/**
 * visu_colorization_setMin:
 * @dt: a #VisuColorization object ;
 * @min: a floating point value.
 * @column: a column id.
 *
 * When the scaling method is #VISU_COLORIZATION_MINMAX (see #VisuColorizationInputScaleId)
 * min and max value for convert input data are user defined. Use this method
 * to choose the minimum bound. This method raises
 * a error if no input file has already been associated to the give @visuData.
 *
 * Returns: TRUE if VisuNodeArray::RenderingChanged should be emitted.
 */
gboolean visu_colorization_setMin(VisuColorization *dt, float min, int column)
{
  return _setManualMinMax(dt, min, column, 0);
}
/**
 * visu_colorization_setMax:
 * @dt: a #VisuColorization object ;
 * @max: a floating point value.
 * @column: a column id.
 *
 * When the scaling method is #VISU_COLORIZATION_MINMAX (see #VisuColorizationInputScaleId)
 * min and max value for convert input data are user defined. Use this method
 * to choose the maximum bound. This method raises
 * a error if no input file has already been associated to the give @visuData.
 *
 * Returns: TRUE if VisuNodeArray::RenderingChanged should be emitted.
 **/
gboolean visu_colorization_setMax(VisuColorization *dt, float max, int column)
{
  return _setManualMinMax(dt, max, column, 1);
}
static float _getManualMinMax(const VisuColorization *dt, int column, int minmax)
{
  float *minMax;

  if (!dt || !VISU_IS_COLORIZATION(dt))
    return (minmax == 0)?DATAFILE_MIN_NORM_DEFAULT:DATAFILE_MAX_NORM_DEFAULT;

  g_return_val_if_fail((guint)(column + 3) < dt->priv->manualMinMax->len, 0.f);
  minMax = &g_array_index(dt->priv->manualMinMax, float, 2 * (column + 3));
  return minMax[minmax];
}
/**
 * visu_colorization_getMin:
 * @dt: (allow-none): a #VisuData object.
 * @column: a column id.
 *
 * Retrieve the minimum value used when scaling is user defined.
 *
 * Returns: the minimum bound if @dt is not NULL
 *          or the default value if not.
 */
float visu_colorization_getMin(const VisuColorization *dt, int column)
{
  return _getManualMinMax(dt, column, 0);
}
/**
 * visu_colorization_getMax:
 * @dt: (allow-none): a #VisuData object.
 * @column: a column id.
 *
 * Retrieve the maximum value used when scaling is user defined.
 *
 * Returns: the maximum bound if @dt is not NULL
 *          or the default value if not.
 */
float visu_colorization_getMax(const VisuColorization *dt, int column)
{
  return _getManualMinMax(dt, column, 1);
}
/**
 * visu_colorization_getNColumns:
 * @dt: a #VisuColorization object.
 *
 * This method is used to retrieve the number of columns of data read in
 * the loaded file.
 *
 * Returns: this number of columns, or -1 if none.
 */
int visu_colorization_getNColumns(const VisuColorization *dt)
{
  VisuNodeValues *model;

  if (!dt || !VISU_IS_COLORIZATION(dt))
    return -1;

  model = visu_sourceable_getNodeModel(VISU_SOURCEABLE(dt));

  if (!model)
    return 0;
  else
    return visu_node_values_getDimension(model);
}
/**
 * visu_colorization_getSingleColumnId:
 * @dt: (allow-none): a #VisuColorization object.
 * @id: (out): a location to store a column id.
 *
 * The colourisation can be applied from values coming from several
 * columns. But, if only one column is used, this routine will give it
 * in @id.
 *
 * Returns: FALSE if several columns are used, or TRUE if a single
 * column is used for the colourisation.
 */
gboolean visu_colorization_getSingleColumnId(const VisuColorization *dt, gint *id)
{
  const ToolShade *shade;
  const float *vectA, *vectB;
  gint col;

  if (!dt || !VISU_IS_COLORIZATION(dt))
    return FALSE;

  shade = visu_data_colorizer_shaded_getShade(VISU_DATA_COLORIZER_SHADED(dt));
  if (shade && tool_shade_getMode(shade) == TOOL_SHADE_MODE_LINEAR)
    tool_shade_getLinearCoeff(shade, &vectA, &vectB);
  else
    vectA = (float*)0;
  
  col = VISU_COLORIZATION_UNSET;
  if ((dt->priv->colUsed[0] == VISU_COLORIZATION_UNSET ||
       dt->priv->colUsed[1] == VISU_COLORIZATION_UNSET ||
       dt->priv->colUsed[0] == dt->priv->colUsed[1] ||
       (vectA && (vectA[0] == 0.f || vectA[1] == 0.f))) &&
      (dt->priv->colUsed[1] == VISU_COLORIZATION_UNSET ||
       dt->priv->colUsed[2] == VISU_COLORIZATION_UNSET ||
       dt->priv->colUsed[1] == dt->priv->colUsed[2] ||
       (vectA && (vectA[1] == 0.f || vectA[2] == 0.f))) &&
      (dt->priv->colUsed[2] == VISU_COLORIZATION_UNSET ||
       dt->priv->colUsed[0] == VISU_COLORIZATION_UNSET ||
       dt->priv->colUsed[2] == dt->priv->colUsed[0] ||
       (vectA && (vectA[0] == 0.f || vectA[2] == 0.f))))
    {
      if (dt->priv->colUsed[0] != VISU_COLORIZATION_UNSET && (!vectA || vectA[0] != 0.f))
        col = dt->priv->colUsed[0];
      else if (dt->priv->colUsed[1] != VISU_COLORIZATION_UNSET && (!vectA || vectA[1] != 0.f))
        col = dt->priv->colUsed[1];
      else
        col = dt->priv->colUsed[2];
    }
  if (id)
    *id = col;
  DBG_fprintf(stderr, "Visu Colorization: retrieve single column %d.\n", col);
  return (col != VISU_COLORIZATION_UNSET);
}
static gboolean _setCol(VisuColorization *dt, int val, int pos)
{
  DBG_fprintf(stderr, "Visu Colorization: channel %d uses column %d (previuosly %d).\n",
	      pos, val, dt->priv->colUsed[pos]);
  g_return_val_if_fail(val < (int)visu_colorization_getNColumns(dt) &&
                       val >= VISU_COLORIZATION_UNSET, FALSE);

  if (dt->priv->colUsed[pos] == val)
    return FALSE;
  dt->priv->colUsed[pos] = val;
  return TRUE;
}
/**
 * visu_colorization_setColUsedArr:
 * @dt: a #VisuColorization object.
 * @vals: (array fixed-size=3): the new columns to be used per
 * channel.
 *
 * Setup all three channels at once, see visu_colorization_setColUsed().
 *
 * Since: 3.8
 *
 * Returns: TRUE if any changes.
 **/
gboolean visu_colorization_setColUsedArr(VisuColorization *dt, const int vals[3])
{
  gboolean changed;

  g_return_val_if_fail(VISU_IS_COLORIZATION(dt), FALSE);

  changed = FALSE;
  if (_setCol(dt, vals[0], 0))
    {
      changed = TRUE;
      g_object_notify_by_pspec(G_OBJECT(dt), properties[COL_R_PROP]);
    }
  if (_setCol(dt, vals[1], 1))
    {
      changed = TRUE;
      g_object_notify_by_pspec(G_OBJECT(dt), properties[COL_G_PROP]);
    }
  if (_setCol(dt, vals[2], 2))
    {
      changed = TRUE;
      g_object_notify_by_pspec(G_OBJECT(dt), properties[COL_B_PROP]);
    }
  if (!changed)
    return FALSE;

  g_object_notify_by_pspec(G_OBJECT(dt), properties[SINGLE_COL_PROP]);
  g_object_notify_by_pspec(G_OBJECT(dt), properties[SINGLE_MM_PROP]);

  visu_data_colorizer_setDirty(VISU_DATA_COLORIZER(dt));

  return TRUE;
}
/**
 * visu_colorization_setColUsed:
 * @dt: a #VisuColorization object ;
 * @val: a column id a special value ;
 * @pos: an integer in [0;2].
 *
 * Choose if the loaded value should change the given channel of the colour.
 *
 * Returns: TRUE if VisuNodeArray::RenderingChanged should be emitted.
 */
gboolean visu_colorization_setColUsed(VisuColorization *dt, int val, int pos)
{
  g_return_val_if_fail(pos >= 0 && pos < 3, FALSE);
  g_return_val_if_fail(VISU_IS_COLORIZATION(dt), FALSE);

  if (!_setCol(dt, val, pos))
    return FALSE;

  g_object_notify_by_pspec(G_OBJECT(dt), properties[COL_R_PROP + pos]);
  g_object_notify_by_pspec(G_OBJECT(dt), properties[SINGLE_COL_PROP]);
  g_object_notify_by_pspec(G_OBJECT(dt), properties[SINGLE_MM_PROP]);

  visu_data_colorizer_setDirty(VISU_DATA_COLORIZER(dt));

  return TRUE;
}
/**
 * visu_colorization_getColUsed:
 * @dt: (allow-none): a #VisuData object.
 *
 * This method is used to retrieve the vector used to adapt or not the colour
 * to the value of the loaded data.
 *
 * Returns: (transfer none) (array fixed-size=3): a three value array,
 * own by V_Sim. It should not be freed.
 */
const int* visu_colorization_getColUsed(const VisuColorization *dt)
{
  if (!dt || !VISU_IS_COLORIZATION(dt))
    return (const int*)0;
  else
    return dt->priv->colUsed;
}
/**
 * visu_colorization_setScalingUsed:
 * @dt: a #VisuColorization object hosting the data values ;
 * @val: a column id.
 *
 * Give the column id to used to take the scaling values from. Set -1
 * if no scaling used. The scaling is used to change the size of each
 * node, using an homothetic factor.
 *
 * Returns: TRUE if the status changed.
 */
gboolean visu_colorization_setScalingUsed(VisuColorization *dt, int val)
{
  g_return_val_if_fail(VISU_IS_COLORIZATION(dt), FALSE);

  DBG_fprintf(stderr, "Visu Colorization: scaling uses column %d (previuosly %d).\n",
	      val, dt->priv->scaleUsed);
  g_return_val_if_fail((val < (int)visu_colorization_getNColumns(dt) &&
			val >= 0) || val == VISU_COLORIZATION_UNSET, FALSE);

  if (dt->priv->scaleUsed == val)
    return FALSE;
  dt->priv->scaleUsed = val;
  g_object_notify_by_pspec(G_OBJECT(dt), properties[COL_SIZE_PROP]);

  visu_data_colorizer_setDirty(VISU_DATA_COLORIZER(dt));

  return TRUE;
}
/**
 * visu_colorization_getScalingUsed:
 * @dt: (allow-none): a #VisuColorization object hosting the data values.
 *
 * Retrieve if a column is used as entry to scale the nodes.
 *
 * Returns: -1 if no scaling is used.
 */
int visu_colorization_getScalingUsed(const VisuColorization *dt)
{
  if (!dt || !VISU_IS_COLORIZATION(dt))
    return VISU_COLORIZATION_UNSET;
  else
    return dt->priv->scaleUsed;
}
static void onShadeNotified(VisuColorization *dt)
{
  g_object_notify_by_pspec(G_OBJECT(dt), properties[SINGLE_COL_PROP]);
  g_object_notify_by_pspec(G_OBJECT(dt), properties[SINGLE_MM_PROP]);
}
static gboolean _setMaskFunc(VisuNodeMasker *self, VisuNodeMaskerFunc func,
                             gpointer data, GDestroyNotify destroy)
{
  VisuColorization *dt;

  g_return_val_if_fail(VISU_IS_COLORIZATION(self), FALSE);

  dt = VISU_COLORIZATION(self);

  /* Free previous association. */
  if (dt->priv->maskerData && dt->priv->maskerDestroy)
    dt->priv->maskerDestroy(dt->priv->maskerData);

  DBG_fprintf(stderr, "Visu Colorization: set masker function with data %p.\n", data);
  dt->priv->maskerFunc = func;
  dt->priv->maskerData = data;
  dt->priv->maskerDestroy = destroy;

  return TRUE;
}
static gboolean _maskApply(const VisuNodeMasker *self, VisuNodeArray *array)
{
  VisuColorization *dt;
  gboolean redraw;
  VisuNodeValuesIter iter;
  VisuNodeValues *model;

  g_return_val_if_fail(VISU_IS_COLORIZATION(self), FALSE);

  dt = VISU_COLORIZATION(self);

  model = visu_sourceable_getNodeModel(VISU_SOURCEABLE(self));
  if (!visu_data_colorizer_getActive(VISU_DATA_COLORIZER(self)) ||
      !model || dt->priv->maskerFunc == (VisuNodeMaskerFunc)0)
    return FALSE;

  g_return_val_if_fail(visu_node_values_fromArray(model, array), FALSE);

  redraw = FALSE;
  for (visu_node_values_iter_new(&iter, ITER_NODES_VISIBLE, model);
       iter.iter.node; visu_node_values_iter_next(&iter))
    {
      if (dt->priv->maskerFunc(self, &iter, dt->priv->maskerData))
        redraw = visu_node_setVisibility(iter.iter.node, FALSE) || redraw;
    }

  return redraw;
}

static void onBoxSize(VisuColorization *dt, float extens _U_, VisuBox *box)
{
  float top[3] = {1.f, 1.f, 1.f};

  visu_box_convertBoxCoordinatestoXYZ(box, dt->priv->boxSize, top);
  if (g_array_index(dt->priv->manualMinMax, float, 1) == G_MAXFLOAT)
    g_array_index(dt->priv->manualMinMax, float, 1) = dt->priv->boxSize[TOOL_XYZ_X];
  if (g_array_index(dt->priv->manualMinMax, float, 3) == G_MAXFLOAT)
    g_array_index(dt->priv->manualMinMax, float, 3) = dt->priv->boxSize[TOOL_XYZ_Y];
  if (g_array_index(dt->priv->manualMinMax, float, 5) == G_MAXFLOAT)
    g_array_index(dt->priv->manualMinMax, float, 5) = dt->priv->boxSize[TOOL_XYZ_Z];
}

static void onReadMinMax(VisuColorization *dt)
{
  g_object_notify_by_pspec(G_OBJECT(dt), properties[READ_MM_PROP]);
  if (dt->priv->scaleType == VISU_COLORIZATION_NORMALIZE)
    g_object_notify_by_pspec(G_OBJECT(dt), properties[SINGLE_MM_PROP]);
}

static void onNColumns(VisuColorization *dt)
{
  guint nbColumns;

  nbColumns = visu_colorization_getNColumns(dt);

  g_object_notify_by_pspec(G_OBJECT(dt), properties[N_COLS_PROP]);

  if (dt->priv->colUsed[0] >= (int)nbColumns)
    {
      dt->priv->colUsed[0] = (nbColumns) ? 0 : VISU_COLORIZATION_UNSET;
      g_object_notify_by_pspec(G_OBJECT(dt), properties[COL_R_PROP]);
    }
  if (dt->priv->colUsed[1] >= (int)nbColumns)
    {
      dt->priv->colUsed[1] = (nbColumns) ? 0 : VISU_COLORIZATION_UNSET;
      g_object_notify_by_pspec(G_OBJECT(dt), properties[COL_G_PROP]);
    }
  if (dt->priv->colUsed[2] >= (int)nbColumns)
    {
      dt->priv->colUsed[2] = (nbColumns) ? 0 : VISU_COLORIZATION_UNSET;
      g_object_notify_by_pspec(G_OBJECT(dt), properties[COL_B_PROP]);
    }
  if (dt->priv->scaleUsed >= (int)nbColumns)
    {
      dt->priv->scaleUsed = (nbColumns) ? 0 : VISU_COLORIZATION_UNSET;
      g_object_notify_by_pspec(G_OBJECT(dt), properties[COL_SIZE_PROP]);
    }
}

/*******************/
/* Drawing methods */
/*******************/
/* Normalization methods */
static float valuesFromData(const VisuColorization *dt,
                            guint column, const float *vals, gboolean *out)
{
  float res, minmax[2], *manual;
  VisuNodeValues *model;

  g_return_val_if_fail(vals, 0.f);
  switch (dt->priv->scaleType)
    {
    case VISU_COLORIZATION_NORMALIZE:
      model = visu_sourceable_getNodeModel(VISU_SOURCEABLE(dt));
      visu_node_values_farray_getColumnMinMax(VISU_NODE_VALUES_FARRAY(model),
                                              minmax, column);
      break;
    case VISU_COLORIZATION_MINMAX:
      manual = &g_array_index(dt->priv->manualMinMax, float, 2 * (column + 3));
      minmax[0] = manual[0];
      minmax[1] = manual[1];
      break;
    default:
      minmax[0] = minmax[1] = 0.f;
    }
  res = ( (vals[column] - minmax[0]) / (minmax[1] - minmax[0]) );
  DBG_fprintf(stderr, "Visu VisuColorization: normalise %f -> %f.\n",
              vals[column], res);
  if (out)
    *out = (res < 0. || res > 1.);
  return CLAMP(res, 0., 1.);
}
static float valuesFromCoord(VisuColorizationPrivate *dt,
                             guint dir, const float xred[3], gboolean *out)
{
  float res = 0.f, *minmax;

  switch (dt->scaleType)
    {
    case VISU_COLORIZATION_NORMALIZE:
      res = xred[dir];
      break;
    case VISU_COLORIZATION_MINMAX:
      minmax = &g_array_index(dt->manualMinMax, float, 2 * dir);
      res = ( (xred[dir] * dt->boxSize[dir] - minmax[0]) / (minmax[1] - minmax[0]) );
      break;
    }
  DBG_fprintf(stderr, "Visu VisuColorization: normalise %f -> %f.\n",
              xred[dir] * dt->boxSize[dir], res);
  if (out)
    *out = (res < 0. || res > 1.);
  return CLAMP(res, 0., 1.);
}

/*******************/
/* Color functions */
/*******************/
static gboolean _toValues(const VisuDataColorizerShaded *self, gfloat values[3],
                          const VisuData *visuData, const VisuNode* node)
{
  float red[3], coord[3];
  int i;
  const float *storedValues;
  gboolean useCoord, useData, status, out;
  VisuColorization *dt = VISU_COLORIZATION(self);
  const VisuNodeValues* model;

  g_return_val_if_fail(VISU_IS_COLORIZATION(dt), FALSE);
  g_return_val_if_fail(visuData && node, FALSE);

  useCoord = (dt->priv->colUsed[0] == VISU_COLORIZATION_FROM_X ||
	      dt->priv->colUsed[0] == VISU_COLORIZATION_FROM_Y ||
	      dt->priv->colUsed[0] == VISU_COLORIZATION_FROM_Z ||
	      dt->priv->colUsed[1] == VISU_COLORIZATION_FROM_X ||
	      dt->priv->colUsed[1] == VISU_COLORIZATION_FROM_Y ||
	      dt->priv->colUsed[1] == VISU_COLORIZATION_FROM_Z ||
	      dt->priv->colUsed[2] == VISU_COLORIZATION_FROM_X ||
	      dt->priv->colUsed[2] == VISU_COLORIZATION_FROM_Y ||
	      dt->priv->colUsed[2] == VISU_COLORIZATION_FROM_Z);
  useData = (dt->priv->colUsed[0] >= 0 ||
	     dt->priv->colUsed[1] >= 0 ||
	     dt->priv->colUsed[2] >= 0);

  storedValues = (const float*)0;
  if (useData)
    {
      model = visu_sourceable_getConstNodeModel(VISU_SOURCEABLE(self));
      if (!model)
        return FALSE;
      storedValues = visu_node_values_farray_getAt(VISU_NODE_VALUES_FARRAY(model), node);
      if (!storedValues)
        return FALSE;
    }
  if (useCoord)
    {
      visu_data_getNodePosition(visuData, node, coord);
      visu_box_convertXYZtoBoxCoordinates(dt->priv->box, red, coord);
    }

  status = TRUE;
  for (i = 0; i < 3; i++)
    {
      out = FALSE;
      if (dt->priv->colUsed[i] == VISU_COLORIZATION_UNSET)
        values[i] = 1.;
      else if (dt->priv->colUsed[i] == VISU_COLORIZATION_FROM_X)
        values[i] = valuesFromCoord(dt->priv, TOOL_XYZ_X, red, &out);
      else if (dt->priv->colUsed[i] == VISU_COLORIZATION_FROM_Y)
        values[i] = valuesFromCoord(dt->priv, TOOL_XYZ_Y, red, &out);
      else if (dt->priv->colUsed[i] == VISU_COLORIZATION_FROM_Z)
        values[i] = valuesFromCoord(dt->priv, TOOL_XYZ_Z, red, &out);
      else
        values[i] = valuesFromData(dt, dt->priv->colUsed[i], storedValues, &out);
      status = status && !out;
    }

  return dt->priv->applyToAll || status;
}
static gfloat _scale(const VisuDataColorizer *colorizer,
                     const VisuData *visuData _U_, const VisuNode *node)
{
  const float *storedValues;
  VisuColorization *dt = VISU_COLORIZATION(colorizer);
  const VisuNodeValues *model;

  if (dt->priv->scaleUsed == VISU_COLORIZATION_UNSET)
    return 1.f;

  model = visu_sourceable_getConstNodeModel(VISU_SOURCEABLE(colorizer));
  if (!model)
    return 1.f;
  storedValues = visu_node_values_farray_getAt(VISU_NODE_VALUES_FARRAY(model), node);
  return storedValues ? valuesFromData(dt, dt->priv->scaleUsed, storedValues, (gboolean*)0) : 1.f;
}

static void exportResources(GString *data, VisuData *dataObj _U_)
{
  visu_config_file_exportComment(data, RESOURCE_RANGE_DESC);
  visu_config_file_exportEntry(data, RESOURCE_RANGE_NAME, NULL, "%d", restrictInRange);

  visu_config_file_exportComment(data, "");
}
static void onEntryRange(VisuConfigFile *obj _U_, VisuConfigFileEntry *entry _U_, gpointer data)
{
  VisuColorization *dt = (VisuColorization*)data;

  dt->priv->applyToAll = !restrictInRange;
}

/**
 * visu_colorization_new_fromCLI:
 * @dataObj: a #VisuData object.
 * @error: (allow-none): an error location.
 *
 * Use all command line options related to colorization to create a
 * new colorization object, storing its values in @dataObj.
 *
 * Since: 3.8
 *
 * Returns: (transfer full): a newly created #VisuColorization object.
 **/
VisuColorization* visu_colorization_new_fromCLI(VisuData *dataObj, GError **error)
{
  VisuColorization *dt;
  const gchar *colorFile;
  int *colUsed;
  guint i;
  ToolShade *shade;
  gboolean somethingIsLoaded, isFile;
  VisuNodeValues *vals;
  GArray *minMax;
  VisuColorRange *rg;

  dt = (VisuColorization*)0;
  colorFile = commandLineGet_colorizeSource(&isFile);
  colUsed = commandLineGet_colorizeColUsed();
  if (!colorFile && !colUsed)
    return dt;
  
  DBG_fprintf(stderr, "Visu basic : loading a new data file '%s'.\n", colorFile);
  if (colorFile && isFile)
    {
      VisuNodeValuesFarray *values;
      GError *error_;
          
      error_ = (GError*)0;
      values = visu_node_values_farray_new_fromFile(VISU_NODE_ARRAY(dataObj),
                                                    VISU_COLORIZATION_LABEL,
                                                    colorFile, &error_);
      if (!error_)
        {
          visu_data_removeNodeProperties
            (dataObj, visu_node_values_getLabel(VISU_NODE_VALUES(values)));
          visu_data_addNodeProperties(dataObj, VISU_NODE_VALUES(values));
          dt = visu_colorization_new();
          visu_sourceable_setSource(VISU_SOURCEABLE(dt),
                                    VISU_COLORIZATION_LABEL);
          visu_colorization_setNodeModel(dt, values);
        }
      else
        {
          g_propagate_error(error, error_);
          g_object_unref(values);
        }
    }
  else if (colorFile)
    {
      vals = visu_data_getNodeProperties(dataObj, colorFile);
      if (!vals)
        g_set_error(error, VISU_COMMAND_LINE_ERROR, ERROR_ARGUMENT,
                    _("Colorization from a non existing node property '%s'."),
                    colorFile);
      else if (!VISU_IS_NODE_VALUES_FARRAY(vals))
        g_set_error_literal(error, VISU_COMMAND_LINE_ERROR, ERROR_ARGUMENT,
                            _("Colorization from a node property that is not"
                              " a float array."));
      else
        {
          dt = visu_colorization_new();
          visu_sourceable_setSource(VISU_SOURCEABLE(dt), colorFile);
          visu_colorization_setNodeModel(dt, VISU_NODE_VALUES_FARRAY(vals));
        }
    }
  else
    {
      somethingIsLoaded = FALSE;
      for (i = 0; i < 3; i++)
        {
          somethingIsLoaded = somethingIsLoaded || (colUsed[i] <= 0);
          if (colUsed[i] > 0)
            g_set_error(error, VISU_COMMAND_LINE_ERROR, ERROR_ARGUMENT,
                        _("Assign a column data without specifying a data file."
                          " Use -c option or change the value %d."), colUsed[i]);
        }
      if (somethingIsLoaded)
        {
          dt = visu_colorization_new();
          visu_colorization_setBox(dt, visu_boxed_getBox(VISU_BOXED(dataObj)));
        }
    }

  if (dt)
    {
      minMax = commandLineGet_colorMinMax();
      if (minMax->len > 0)
        {
          visu_colorization_setScaleType(dt, VISU_COLORIZATION_MINMAX);
          for (i = 0; i < minMax->len; i++)
            {
              rg = &g_array_index(minMax, VisuColorRange, i);
              visu_colorization_setMin(dt, rg->min, rg->column - 1);
              visu_colorization_setMax(dt, rg->max, rg->column - 1);
            }
        }
      for (i = 0; i < 3; i++)
        visu_colorization_setColUsed(dt, (colUsed)?colUsed[i] - 1:0, i);
      shade = tool_pool_getById(tool_shade_getStorage(),
                                commandLineGet_presetColor());
      visu_data_colorizer_shaded_setShade(VISU_DATA_COLORIZER_SHADED(dt),
                                          (shade) ? shade : tool_pool_getById(tool_shade_getStorage(), 0));
      if (commandLineGet_scalingColumn() >= 0)
        visu_colorization_setScalingUsed(dt, commandLineGet_scalingColumn());
      visu_data_colorizer_setActive(VISU_DATA_COLORIZER(dt), TRUE);
    }
  return dt;
}
