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

	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 "mapset.h"

/**
 * SECTION:mapset
 * @short_description: Defines methods to draw maps that share a same #VisuScalarField.
 *
 * <para>Maps are coloured representation of a #VisuScalarField on a #VisuPlane.</para>
 */

/**
 * VisuGlExtMapSetClass:
 * @parent: the parent class;
 *
 * A short way to identify #_VisuGlExtMapSetClass structure.
 *
 * Since: 3.8
 */
/**
 * VisuGlExtMapSet:
 *
 * An opaque structure.
 *
 * Since: 3.8
 */
/**
 * VisuGlExtMapSetPrivate:
 *
 * Private fields for #VisuGlExtMapSet objects.
 *
 * Since: 3.8
 */
struct _VisuGlExtMapSetPrivate
{
  gboolean dispose_has_run;

  VisuScalarField *field;
  gulong sig_chg;

  GHashTable *maps;

  /* General values. */
  float prec;
  gboolean alpha;
  guint nLines;
  gboolean useManualRange;
  float manualMinMax[2];
  float drawnMinMax[2];
  ToolMatrixScalingFlag scale;
  ToolShade *shade;
  ToolColor *color;

  VisuGlExtShade *extLegend;
};

struct _mapData
{
  VisuMap *map;
  gulong changed_sig;
  VisuPlane *plane;
  gboolean planeStatus;
};

enum
  {
    PROP_0,
    FIELD_PROP,
    COLOR_PROP,
    SHADE_PROP,
    PRECISION_PROP,
    ALPHA_PROP,
    N_LINES_PROP,
    SCALE_PROP,
    USE_MANUAL_MM_PROP,
    MANUAL_MM_PROP,
    MANUAL_MIN_PROP,
    MANUAL_MAX_PROP,
    N_PROP
  };
static GParamSpec *_properties[N_PROP];

static void visu_gl_ext_map_set_finalize(GObject* obj);
static void visu_gl_ext_map_set_dispose(GObject* obj);
static void visu_gl_ext_map_set_get_property(GObject* obj, guint property_id,
                                          GValue *value, GParamSpec *pspec);
static void visu_gl_ext_map_set_set_property(GObject* obj, guint property_id,
                                             const GValue *value, GParamSpec *pspec);
static gboolean visu_gl_ext_map_set_add(VisuGlExtMaps *maps, VisuMap *map,
                                        float prec, ToolShade *shade,
                                        const ToolColor *color, gboolean alpha);
static void visu_gl_ext_map_set_added(VisuGlExtMaps *maps, VisuMap *map);
static void visu_gl_ext_map_set_removed(VisuGlExtMaps *maps, VisuMap *map);

/* Local callbacks */
static void onMapChange(VisuMap *map, gpointer data);
static void onFieldChanged(VisuScalarField *field, gpointer data);

/* Local routines. */
static struct _mapData* _newMapData(VisuGlExtMapSet *maps, VisuMap *map)
{
  struct _mapData *data = g_malloc(sizeof(struct _mapData));

  data->map = map;
  data->changed_sig = g_signal_connect(G_OBJECT(map), "changed",
                                       G_CALLBACK(onMapChange), (gpointer)maps);
  data->plane = (VisuPlane*)0;
  return data;
}
static void _freeMapData(struct _mapData *data)
{
  DBG_fprintf(stderr, "Extension MapSet: freeing handle on map %p.\n",
              (gpointer)data->map);
  g_signal_handler_disconnect(G_OBJECT(data->map), data->changed_sig);
  if (data->plane)
    {
      visu_plane_setRendered(data->plane, data->planeStatus);
      g_object_unref(data->plane);
    }
}
static gboolean _setLeg(GBinding *bind _U_, const GValue *from,
                        GValue *to, gpointer data)
{
  g_value_set_boolean(to, g_value_get_boolean(from) &&
                      g_hash_table_size(VISU_GL_EXT_MAP_SET(data)->priv->maps) > 0);
  return TRUE;
}

G_DEFINE_TYPE_WITH_CODE(VisuGlExtMapSet, visu_gl_ext_map_set, VISU_TYPE_GL_EXT_MAPS,
                        G_ADD_PRIVATE(VisuGlExtMapSet))

static void visu_gl_ext_map_set_class_init(VisuGlExtMapSetClass *klass)
{
  DBG_fprintf(stderr, "Extension MapSet: creating the class of the object.\n");
  /* DBG_fprintf(stderr, "                - adding new signals ;\n"); */

  /* Connect the overloading methods. */
  G_OBJECT_CLASS(klass)->dispose  = visu_gl_ext_map_set_dispose;
  G_OBJECT_CLASS(klass)->finalize = visu_gl_ext_map_set_finalize;
  G_OBJECT_CLASS(klass)->set_property = visu_gl_ext_map_set_set_property;
  G_OBJECT_CLASS(klass)->get_property = visu_gl_ext_map_set_get_property;
  VISU_GL_EXT_MAPS_CLASS(klass)->add = visu_gl_ext_map_set_add;
  VISU_GL_EXT_MAPS_CLASS(klass)->added = visu_gl_ext_map_set_added;
  VISU_GL_EXT_MAPS_CLASS(klass)->removed = visu_gl_ext_map_set_removed;

  /**
   * VisuGlExtMapSet::field:
   *
   * Store the field maps are drawn from.
   *
   * Since: 3.8
   */
  _properties[FIELD_PROP] = g_param_spec_object("field", "Field",
                                                "field storing 3D data",
                                                VISU_TYPE_SCALAR_FIELD,
                                                G_PARAM_READWRITE);
  g_object_class_install_property(G_OBJECT_CLASS(klass), FIELD_PROP,
				  _properties[FIELD_PROP]);
  /**
   * VisuGlExtMapSet::line-color:
   *
   * Store the colour used to draw isolines.
   *
   * Since: 3.8
   */
  _properties[COLOR_PROP] = g_param_spec_boxed("line-color", "Line color",
                                               "colour used to draw isolines",
                                               TOOL_TYPE_COLOR, G_PARAM_READWRITE);
  g_object_class_install_property(G_OBJECT_CLASS(klass), COLOR_PROP,
				  _properties[COLOR_PROP]);
  /**
   * VisuGlExtMapSet::shade:
   *
   * Store the shade used to colourise the map.
   *
   * Since: 3.8
   */
  _properties[SHADE_PROP] = g_param_spec_boxed("shade", "Shade",
                                               "shade used to colourise the map",
                                               TOOL_TYPE_SHADE, G_PARAM_READWRITE);
  g_object_class_install_property(G_OBJECT_CLASS(klass), SHADE_PROP,
				  _properties[SHADE_PROP]);
  /**
   * VisuGlExtMapSet::precision:
   *
   * Store the adaptatbility level used to render mapSet.
   *
   * Since: 3.8
   */
  _properties[PRECISION_PROP] = g_param_spec_float("precision", "Precision",
                                                   "map degree of adaptability",
                                                   10.f, 200.f, 100.f, G_PARAM_READWRITE);
  g_object_class_install_property(G_OBJECT_CLASS(klass), PRECISION_PROP,
				  _properties[PRECISION_PROP]);
  /**
   * VisuGlExtMapSet::transparent:
   *
   * Define if field value is transfered to alpha channel also.
   *
   * Since: 3.8
   */
  _properties[ALPHA_PROP] = g_param_spec_boolean("transparent", "Transparent",
                                                 "use alpha channel according to field values",
                                                 FALSE, G_PARAM_READWRITE);
  g_object_class_install_property(G_OBJECT_CLASS(klass), ALPHA_PROP,
				  _properties[ALPHA_PROP]);
  /**
   * VisuGlExtMapSet::n-lines:
   *
   * Define how many lines are drawn in full range.
   *
   * Since: 3.8
   */
  _properties[N_LINES_PROP] = g_param_spec_uint("n-lines", "N lines",
                                                "number of lines is the full range",
                                                0, G_MAXUINT, 0, G_PARAM_READWRITE);
  g_object_class_install_property(G_OBJECT_CLASS(klass), N_LINES_PROP,
				  _properties[N_LINES_PROP]);
  /**
   * VisuGlExtMapSet::scale:
   *
   * Define how to scale input values into [0;1].
   *
   * Since: 3.8
   */
  _properties[SCALE_PROP] = g_param_spec_uint("scale", "Scale",
                                              "scaling method",
                                              0, TOOL_MATRIX_SCALING_N_VALUES - 1,
                                              TOOL_MATRIX_SCALING_LINEAR,
                                              G_PARAM_READWRITE);
  g_object_class_install_property(G_OBJECT_CLASS(klass), SCALE_PROP,
				  _properties[SCALE_PROP]);
  /**
   * VisuGlExtMapSet::use-manual-range:
   *
   * True, when mapSet are scaled according to a manual range.
   *
   * Since: 3.8
   */
  _properties[USE_MANUAL_MM_PROP] = g_param_spec_boolean("use-manual-range",
                                                         "Use manual range",
                                                         "use manual range",
                                                         FALSE, G_PARAM_READWRITE);
  g_object_class_install_property(G_OBJECT_CLASS(klass), USE_MANUAL_MM_PROP,
				  _properties[USE_MANUAL_MM_PROP]);
  /**
   * VisuGlExtMapSet::manual-range:
   *
   * Min / max range as used to normalise data.
   *
   * Since: 3.8
   */
  _properties[MANUAL_MM_PROP] = g_param_spec_boxed("manual-range", "Manual range",
                                                   "manual range values",
                                                   G_TYPE_ARRAY,
                                                   G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
  g_object_class_install_property
    (G_OBJECT_CLASS(klass), MANUAL_MM_PROP, _properties[MANUAL_MM_PROP]);
  /**
   * VisuGlExtMapSet::manual-range-min:
   *
   * Min range as used to normalise data.
   *
   * Since: 3.8
   */
  _properties[MANUAL_MIN_PROP] = g_param_spec_float("manual-range-min",
                                                    "Manual range minimum",
                                                    "manual range minimum value",
                                                    -G_MAXFLOAT, G_MAXFLOAT, -G_MAXFLOAT,
                                                    G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
  g_object_class_install_property
    (G_OBJECT_CLASS(klass), MANUAL_MIN_PROP, _properties[MANUAL_MIN_PROP]);
  /**
   * VisuGlExtMapSet::manual-range-max:
   *
   * Max range as used to normalise data.
   *
   * Since: 3.8
   */
  _properties[MANUAL_MAX_PROP] = g_param_spec_float("manual-range-max",
                                                    "Manual range maximum",
                                                    "manual range maximum value",
                                                    -G_MAXFLOAT, G_MAXFLOAT, -G_MAXFLOAT,
                                                    G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
  g_object_class_install_property
    (G_OBJECT_CLASS(klass), MANUAL_MAX_PROP, _properties[MANUAL_MAX_PROP]);
}

static void visu_gl_ext_map_set_init(VisuGlExtMapSet *obj)
{
  DBG_fprintf(stderr, "Extension MapSet: initializing a new object (%p).\n",
	      (gpointer)obj);
  
  obj->priv = visu_gl_ext_map_set_get_instance_private(obj);
  obj->priv->dispose_has_run = FALSE;

  /* Private data. */
  obj->priv->field           = (VisuScalarField*)0;
  obj->priv->maps            = g_hash_table_new_full(g_direct_hash, g_direct_equal,
                                                     NULL, (GDestroyNotify)_freeMapData);
  obj->priv->color           = (ToolColor*)0;
  obj->priv->shade           = (ToolShade*)0;
  obj->priv->prec            = 100.f;
  obj->priv->alpha           = FALSE;
  obj->priv->nLines          = 0;
  obj->priv->useManualRange  = FALSE;
  obj->priv->drawnMinMax[0]  =  G_MAXFLOAT;
  obj->priv->drawnMinMax[1]  = -G_MAXFLOAT;
  obj->priv->manualMinMax[0] = -G_MAXFLOAT;
  obj->priv->manualMinMax[1] =  G_MAXFLOAT;
  obj->priv->scale           = TOOL_MATRIX_SCALING_LINEAR;
  obj->priv->extLegend       = visu_gl_ext_shade_new("Map legend");
  g_object_bind_property_full(G_OBJECT(obj), "active",
                              G_OBJECT(obj->priv->extLegend), "active",
                              G_BINDING_SYNC_CREATE, _setLeg, NULL, obj, NULL);
  visu_gl_ext_frame_setScale(VISU_GL_EXT_FRAME(obj->priv->extLegend),
                             visu_map_getLegendScale());
  visu_gl_ext_frame_setPosition(VISU_GL_EXT_FRAME(obj->priv->extLegend),
                                visu_map_getLegendPosition(TOOL_XYZ_X),
                                visu_map_getLegendPosition(TOOL_XYZ_Y));
}
static void visu_gl_ext_map_set_dispose(GObject* obj)
{
  VisuGlExtMapSet *mapSet = VISU_GL_EXT_MAP_SET(obj);

  DBG_fprintf(stderr, "Extension MapSet: dispose object %p.\n", (gpointer)obj);
  if (mapSet->priv->dispose_has_run)
    return;

  mapSet->priv->dispose_has_run = TRUE;
  g_object_unref(mapSet->priv->extLegend);
  visu_gl_ext_map_set_setField(mapSet, (VisuScalarField*)0);
  g_hash_table_remove_all(mapSet->priv->maps);

  /* Chain up to the parent class */
  G_OBJECT_CLASS(visu_gl_ext_map_set_parent_class)->dispose(obj);
}
static void visu_gl_ext_map_set_finalize(GObject* obj)
{
  g_return_if_fail(obj);

  DBG_fprintf(stderr, "Extension MapSet: finalize object %p.\n", (gpointer)obj);
  g_hash_table_destroy(VISU_GL_EXT_MAP_SET(obj)->priv->maps);

  /* Chain up to the parent class */
  DBG_fprintf(stderr, "Extension MapSet: chain to parent.\n");
  G_OBJECT_CLASS(visu_gl_ext_map_set_parent_class)->finalize(obj);
  DBG_fprintf(stderr, "Extension MapSet: freeing ... OK.\n");
}
static void visu_gl_ext_map_set_get_property(GObject* obj, guint property_id,
                                         GValue *value, GParamSpec *pspec)
{
  GArray *arr;
  VisuGlExtMapSet *self = VISU_GL_EXT_MAP_SET(obj);

  DBG_fprintf(stderr, "Extension MapSet: get property '%s' -> ",
	      g_param_spec_get_name(pspec));
  switch (property_id)
    {
    case FIELD_PROP:
      g_value_set_object(value, self->priv->field);
      DBG_fprintf(stderr, "%p.\n", (gpointer)self->priv->field);
      break;
    case SHADE_PROP:
      g_value_set_boxed(value, self->priv->shade);
      DBG_fprintf(stderr, "%p.\n", (gpointer)self->priv->shade);
      break;
    case COLOR_PROP:
      g_value_set_boxed(value, self->priv->color);
      DBG_fprintf(stderr, "%p.\n", (gpointer)self->priv->color);
      break;
    case PRECISION_PROP:
      g_value_set_float(value, self->priv->prec);
      DBG_fprintf(stderr, "%g.\n", self->priv->prec);
      break;
    case ALPHA_PROP:
      g_value_set_boolean(value, self->priv->alpha);
      DBG_fprintf(stderr, "%d.\n", self->priv->alpha);
      break;
    case N_LINES_PROP:
      g_value_set_uint(value, self->priv->nLines);
      DBG_fprintf(stderr, "%u.\n", self->priv->nLines);
      break;
    case SCALE_PROP:
      g_value_set_uint(value, self->priv->scale);
      DBG_fprintf(stderr, "%u.\n", self->priv->scale);
      break;
    case USE_MANUAL_MM_PROP:
      g_value_set_boolean(value, self->priv->useManualRange);
      DBG_fprintf(stderr, "%d.\n", self->priv->useManualRange);
      break;
    case MANUAL_MM_PROP:
      arr = g_array_sized_new(FALSE, FALSE, sizeof(float) * 2, 2);
      g_array_append_vals(arr, self->priv->manualMinMax, 2);
      g_value_take_boxed(value, self->priv->manualMinMax);
      DBG_fprintf(stderr, "%p.\n", (gpointer)arr);
      break;
    case MANUAL_MIN_PROP:
      g_value_set_float(value, self->priv->manualMinMax[0]);
      DBG_fprintf(stderr, "%g.\n", self->priv->manualMinMax[0]);
      break;
    case MANUAL_MAX_PROP:
      g_value_set_float(value, self->priv->manualMinMax[1]);
      DBG_fprintf(stderr, "%g.\n", self->priv->manualMinMax[1]);
      break;
    default:
      /* We don't have any other property... */
      G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, property_id, pspec);
      break;
    }
}
static void visu_gl_ext_map_set_set_property(GObject* obj, guint property_id,
                                          const GValue *value, GParamSpec *pspec)
{
  GArray *arr;
  float mm[2];
  VisuGlExtMapSet *self = VISU_GL_EXT_MAP_SET(obj);

  DBG_fprintf(stderr, "Extension MapSet: set property '%s' -> ",
	      g_param_spec_get_name(pspec));
  switch (property_id)
    {
    case FIELD_PROP:
      DBG_fprintf(stderr, "%p.\n", g_value_get_object(value));
      visu_gl_ext_map_set_setField(self, VISU_SCALAR_FIELD(g_value_get_object(value)));
      break;
    case COLOR_PROP:
      DBG_fprintf(stderr, "%p.\n", g_value_get_boxed(value));
      visu_gl_ext_map_set_setLineColor(self, (ToolColor*)g_value_get_boxed(value));
      break;
    case SHADE_PROP:
      DBG_fprintf(stderr, "%p.\n", g_value_get_boxed(value));
      visu_gl_ext_map_set_setShade(self, (ToolShade*)g_value_get_boxed(value));
      break;
    case PRECISION_PROP:
      DBG_fprintf(stderr, "%g.\n", g_value_get_float(value));
      visu_gl_ext_map_set_setPrecision(self, g_value_get_float(value));
      break;
    case ALPHA_PROP:
      DBG_fprintf(stderr, "%d.\n", g_value_get_boolean(value));
      visu_gl_ext_map_set_setTransparent(self, g_value_get_boolean(value));
      break;
    case N_LINES_PROP:
      DBG_fprintf(stderr, "%u.\n", g_value_get_uint(value));
      visu_gl_ext_map_set_setLines(self, g_value_get_uint(value));
      break;
    case SCALE_PROP:
      DBG_fprintf(stderr, "%u.\n", g_value_get_uint(value));
      visu_gl_ext_map_set_setScaling(self, g_value_get_uint(value));
      break;
    case USE_MANUAL_MM_PROP:
      DBG_fprintf(stderr, "%d.\n", g_value_get_boolean(value));
      visu_gl_ext_map_set_setScalingRange(self,
                                       g_value_get_boolean(value) ?
                                       self->priv->manualMinMax : (const float*)0);
      break;
    case MANUAL_MM_PROP:
      arr = (GArray*)g_value_get_boxed(value);
      g_return_if_fail(arr && arr->len == 2);
      if (self->priv->useManualRange)
        visu_gl_ext_map_set_setScalingRange(self, (float*)arr->data);
      else
        {
          self->priv->manualMinMax[0] = g_array_index(arr, float, 0);
          self->priv->manualMinMax[1] = g_array_index(arr, float, 1);
        }
      break;
    case MANUAL_MIN_PROP:
      if (self->priv->useManualRange)
        {
          mm[0] = g_value_get_float(value);
          mm[1] = self->priv->manualMinMax[1];
          visu_gl_ext_map_set_setScalingRange(self, mm);
        }
      else
        self->priv->manualMinMax[0] = g_value_get_float(value);
      break;
    case MANUAL_MAX_PROP:
      if (self->priv->useManualRange)
        {
          mm[0] = self->priv->manualMinMax[0];
          mm[1] = g_value_get_float(value);
          visu_gl_ext_map_set_setScalingRange(self, mm);
        }
      else
        self->priv->manualMinMax[1] = g_value_get_float(value);
      break;
    default:
      /* We don't have any other property... */
      G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, property_id, pspec);
      break;
    }
}

/**
 * visu_gl_ext_map_set_new:
 * @name: (allow-none): the name to give to the extension.
 *
 * Creates a new #VisuGlExt to draw mapSet.
 *
 * Since: 3.7
 *
 * Returns: a pointer to the #VisuGlExt it created or
 * NULL otherwise.
 */
VisuGlExtMapSet* visu_gl_ext_map_set_new(const gchar *name)
{
  char *name_ = "MapSet";
  char *description = _("Drawing extension for mapSet.");
  VisuGlExt *extensionMapSet;

  DBG_fprintf(stderr,"Extension MapSet: new object.\n");
  
  extensionMapSet = VISU_GL_EXT(g_object_new(VISU_TYPE_GL_EXT_MAP_SET,
                                           "name", (name)?name:name_, "label", _(name),
                                           "description", description, "nGlObj", 1,
                                           "priority", VISU_GL_EXT_PRIORITY_NORMAL - 1,
                                           "saveState", TRUE, NULL));

  return VISU_GL_EXT_MAP_SET(extensionMapSet);
}
/**
 * visu_gl_ext_map_set_getLegend:
 * @mapSet: a #VisuGlExtMapSet object.
 *
 * Retrieve the associated #VisuGlExtShade object used to draw the legend.
 *
 * Since: 3.8
 *
 * Returns: (transfer none): the associated #VisuGlExtShade legend.
 **/
VisuGlExtShade* visu_gl_ext_map_set_getLegend(VisuGlExtMapSet *mapSet)
{
  g_return_val_if_fail(VISU_IS_GL_EXT_MAP_SET(mapSet), (VisuGlExtShade*)0);

  return mapSet->priv->extLegend;
}
/**
 * visu_gl_ext_map_set_setField:
 * @mapSet: a #VisuGlExtMapSet object.
 * @field: (transfer none): a #VisuScalarField object.
 *
 * Associate @field to the @mapSet.
 *
 * Since: 3.8
 **/
void visu_gl_ext_map_set_setField(VisuGlExtMapSet *mapSet, VisuScalarField *field)
{
  VisuGlExtMapsIter iter;

  g_return_if_fail(VISU_IS_GL_EXT_MAP_SET(mapSet));

  if (mapSet->priv->field)
    {
      g_signal_handler_disconnect(G_OBJECT(mapSet->priv->field), mapSet->priv->sig_chg);
      g_object_unref(G_OBJECT(mapSet->priv->field));
    }
  mapSet->priv->field = field;
  if (field)
    {
      g_object_ref(G_OBJECT(field));
      mapSet->priv->sig_chg = g_signal_connect(G_OBJECT(field), "changed",
                                               G_CALLBACK(onFieldChanged), mapSet);
    }
  g_object_notify_by_pspec(G_OBJECT(mapSet), _properties[FIELD_PROP]);

  /* Update stored maps. */
  for (visu_gl_ext_maps_iter_new(VISU_GL_EXT_MAPS(mapSet), &iter);
       iter.valid; visu_gl_ext_maps_iter_next(&iter))
    visu_map_setField(iter.map, field);
  DBG_fprintf(stderr, "Extension MapSet: setting field %p.\n", (gpointer)field);
  visu_gl_ext_setActive(VISU_GL_EXT(mapSet->priv->extLegend),
                        visu_gl_ext_getActive(VISU_GL_EXT(mapSet)) &&
                        g_hash_table_size(mapSet->priv->maps) > 0 &&
                        mapSet->priv->field &&
                        !visu_scalar_field_isEmpty(mapSet->priv->field));
}

static void _legend(VisuGlExtMapSet *mapSet)
{
  float *marks, minmax[2], inputMM[2] = {G_MAXFLOAT, -G_MAXFLOAT}, factor;
  guint i;
  double dmm[2];
  VisuGlExtMapsIter iter;

  if (!visu_gl_ext_getActive(VISU_GL_EXT(mapSet->priv->extLegend)))
    return;

  if (mapSet->priv->useManualRange)
    {
      inputMM[0] = mapSet->priv->manualMinMax[0];
      inputMM[1] = mapSet->priv->manualMinMax[1];
      minmax[0] = 0.;
      minmax[1] = 1.;
    }
  else
    {
      for (visu_gl_ext_maps_iter_new(VISU_GL_EXT_MAPS(mapSet), &iter);
           iter.valid; visu_gl_ext_maps_iter_next(&iter))
        {
          visu_scalar_field_getMinMax(visu_map_getField(iter.map), dmm);
          tool_minmax_fromDbl(inputMM, dmm);
        }
      minmax[0] = mapSet->priv->drawnMinMax[0];
      minmax[1] = mapSet->priv->drawnMinMax[1];
    }
  visu_gl_ext_shade_setMinMax(mapSet->priv->extLegend, inputMM[0], inputMM[1]);

  /* Update the shade legend. */
  marks = g_malloc(sizeof(float) * (2 + mapSet->priv->nLines));
  factor = (minmax[1] - minmax[0]) / (float)(mapSet->priv->nLines + 1);
  for (i = 0; i < mapSet->priv->nLines; i++)
    marks[1 + i] = factor * (float)(i + 1) + minmax[0];
  marks[0]                        = mapSet->priv->drawnMinMax[0];
  marks[1 + mapSet->priv->nLines] = mapSet->priv->drawnMinMax[1];

  visu_gl_ext_shade_setMarks(mapSet->priv->extLegend, marks, 2 + mapSet->priv->nLines);

  g_free(marks);
}
static void _setLines(VisuGlExtMapSet *mapSet)
{
  float minmax[2];
  VisuGlExtMapsIter iter;

  if (mapSet->priv->useManualRange)
    {
      minmax[0] = 0.;
      minmax[1] = 1.;
    }
  else
    {
      minmax[0] = mapSet->priv->drawnMinMax[0];
      minmax[1] = mapSet->priv->drawnMinMax[1];
    }
  for (visu_gl_ext_maps_iter_new(VISU_GL_EXT_MAPS(mapSet), &iter);
       iter.valid; visu_gl_ext_maps_iter_next(&iter))
    visu_map_setLines(iter.map, mapSet->priv->nLines, minmax);
  _legend(mapSet);
}
static gboolean _drawnMinMax(VisuGlExtMapSet *mapSet)
{
  float drawnMinMax[2], oldMinMax[2], minMax[2];
  VisuGlExtMapsIter iter;

  oldMinMax[0] = mapSet->priv->drawnMinMax[0];
  oldMinMax[1] = mapSet->priv->drawnMinMax[1];

  /* We update the drawnMinMax array. */
  drawnMinMax[0] = G_MAXFLOAT;
  drawnMinMax[1] = -G_MAXFLOAT;
  for (visu_gl_ext_maps_iter_new(VISU_GL_EXT_MAPS(mapSet), &iter);
       iter.valid; visu_gl_ext_maps_iter_next(&iter))
    if (visu_map_getScaledMinMax(iter.map, minMax))
      {
        drawnMinMax[0] = MIN(drawnMinMax[0], minMax[0]);
        drawnMinMax[1] = MAX(drawnMinMax[1], minMax[1]);
      }
    else
      return FALSE; /* No global drawn min max available yet. */
  DBG_fprintf(stderr, "Extension MapSet: global scaled min/max: %g/%g (%g/%g).\n",
              drawnMinMax[0], drawnMinMax[1],
              oldMinMax[0], oldMinMax[1]);

  if (oldMinMax[0] == drawnMinMax[0] && oldMinMax[1] == drawnMinMax[1])
    return FALSE;
  
  mapSet->priv->drawnMinMax[0] = drawnMinMax[0];
  mapSet->priv->drawnMinMax[1] = drawnMinMax[1];
  _setLines(mapSet);

  return TRUE;
}
/**
 * visu_gl_ext_map_set_addFromPlane:
 * @mapSet: a #VisuGlExtMapSet object.
 * @plane: (transfer none): a #VisuPlane object.
 *
 * Add a new map to the list of drawn mapSet. If @color is %NULL, then
 * iso-lines will be drawn in inverse color.
 *
 * Since: 3.8
 *
 * Returns: (transfer none): the corresponding #VisuMap.
 **/
VisuMap* visu_gl_ext_map_set_addFromPlane(VisuGlExtMapSet *mapSet,
                                          VisuPlane *plane)
{
  VisuMap *map;
  float full[2] = {0.f, 1.f};

  g_return_val_if_fail(VISU_IS_GL_EXT_MAP_SET(mapSet) && plane, (VisuMap*)0);

  map = visu_map_new_fromPlane(plane);
  visu_map_setField(map, mapSet->priv->field);
  visu_map_setScaling(map, mapSet->priv->scale);
  visu_map_setScalingRange(map, (mapSet->priv->useManualRange) ? mapSet->priv->manualMinMax : (float*)0);
  visu_map_setLines(map, mapSet->priv->nLines, (mapSet->priv->useManualRange) ? full : mapSet->priv->drawnMinMax);

  if (!visu_gl_ext_maps_add(VISU_GL_EXT_MAPS(mapSet), map,
                            mapSet->priv->prec, mapSet->priv->shade,
                            mapSet->priv->color, mapSet->priv->alpha))
    {
      g_object_unref(map);
      return (VisuMap*)0;
    }

  visu_gl_ext_map_set_setPlane(mapSet, map, plane);
  g_object_unref(map);

  return map;
}
/**
 * visu_gl_ext_map_set_setPlane:
 * @mapSet: a #VisuGlExtMapSet object.
 * @map: a #VisuMap object.
 * @plane: a #VisuPlane object.
 *
 * Change the plane where @map is projected on to @plane.
 *
 * Since: 3.8
 **/
void visu_gl_ext_map_set_setPlane(VisuGlExtMapSet *mapSet, VisuMap *map, VisuPlane *plane)
{
  struct _mapData *data;

  g_return_if_fail(VISU_IS_GL_EXT_MAP_SET(mapSet));

  data = (struct _mapData*)g_hash_table_lookup(mapSet->priv->maps, map);
  g_return_if_fail(data);

  if (data->plane == plane)
    return;

  if (data->plane)
    {
      visu_plane_setRendered(data->plane, data->planeStatus);
      g_object_unref(data->plane);
    }

  g_object_ref(plane);
  data->plane = plane;
  data->planeStatus = visu_plane_getRendered(plane);
  visu_plane_setRendered(plane, FALSE);

  visu_map_setPlane(map, plane);
}
/**
 * visu_gl_ext_map_set_getPlane:
 * @mapSet: a #VisuGlExtMapSet object.
 * @map: a #VisuMap object.
 *
 * Retrieve the #VisuPlane @map was build from (if any).
 *
 * Since: 3.8
 *
 * Returns: (transfer none): the attached #VisuPlane if any.
 **/
VisuPlane* visu_gl_ext_map_set_getPlane(VisuGlExtMapSet *mapSet, VisuMap *map)
{
  struct _mapData *data;

  g_return_val_if_fail(VISU_IS_GL_EXT_MAP_SET(mapSet), (VisuPlane*)0);

  DBG_fprintf(stderr, "Extension MapSet: looking for map %p.\n", (gpointer)map);
  data = (struct _mapData*)g_hash_table_lookup(mapSet->priv->maps, map);
  g_return_val_if_fail(data, (VisuPlane*)0);
  return data->plane;
}

/**
 * visu_gl_ext_map_set_setPrecision:
 * @mapSet: a #VisuGlExtMapSet object.
 * @prec: a floating point value (default is 100).
 *
 * Changes the adaptative mesh of @map. At a value of 200, there is no
 * adaptivity and all triangles are rendered. At a level of 100, a
 * variation of less than 3% on neighbouring triangles make them merged.
 *
 * Since: 3.8
 *
 * Returns: TRUE if @prec of @map is changed.
 **/
gboolean visu_gl_ext_map_set_setPrecision(VisuGlExtMapSet *mapSet, float prec)
{
  g_return_val_if_fail(VISU_IS_GL_EXT_MAP_SET(mapSet), FALSE);
  if (mapSet->priv->prec != prec)
    {
      mapSet->priv->prec = prec;
      g_object_notify_by_pspec(G_OBJECT(mapSet), _properties[PRECISION_PROP]);
    }
  return visu_gl_ext_maps_setPrecision(VISU_GL_EXT_MAPS(mapSet), (VisuMap*)0, prec);
}
/**
 * visu_gl_ext_map_set_setShade:
 * @mapSet: a #VisuGlExtMapSet object.
 * @shade: (allow-none) (transfer full): a #ToolShade object.
 *
 * Changes the #ToolShade used to render data variation on the @map.
 *
 * Since: 3.8
 *
 * Returns: TRUE if @shade of @map is changed.
 **/
gboolean visu_gl_ext_map_set_setShade(VisuGlExtMapSet *mapSet, ToolShade *shade)
{
  gboolean diff;

  if (!tool_shade_compare(mapSet->priv->shade, shade))
    {
      if (mapSet->priv->shade)
        g_boxed_free(TOOL_TYPE_SHADE, mapSet->priv->shade);
      mapSet->priv->shade = (shade) ? g_boxed_copy(TOOL_TYPE_SHADE, shade) : (ToolShade*)0;
      diff = TRUE;
      g_object_notify_by_pspec(G_OBJECT(mapSet), _properties[SHADE_PROP]);
    }
  visu_gl_ext_maps_setShade(VISU_GL_EXT_MAPS(mapSet), (VisuMap*)0, shade);
  visu_gl_ext_shade_setShade(mapSet->priv->extLegend, shade);
  return diff;
}
/**
 * visu_gl_ext_map_set_setLineColor:
 * @mapSet: a #VisuGlExtMapSet object.
 * @color: (allow-none) (transfer full): a #ToolColor object.
 *
 * Changes the rendered isoline color of @map to @color. If @color is
 * %NULL, then the isolines will be color inversed to the #ToolShade
 * of @map (see visu_gl_ext_map_set_setShade()).
 *
 * Since: 3.8
 *
 * Returns: TRUE if @color of @map is changed.
 **/
gboolean visu_gl_ext_map_set_setLineColor(VisuGlExtMapSet *mapSet,
                                          const ToolColor *color)
{
  gboolean diff;

  if (!tool_color_equal(mapSet->priv->color, color))
    {
      if (mapSet->priv->color)
        g_boxed_free(TOOL_TYPE_COLOR, mapSet->priv->color);
      mapSet->priv->color = (color) ? g_boxed_copy(TOOL_TYPE_COLOR, color) : (ToolColor*)0;
      diff = TRUE;
      g_object_notify_by_pspec(G_OBJECT(mapSet), _properties[COLOR_PROP]);
    }
  visu_gl_ext_maps_setLineColor(VISU_GL_EXT_MAPS(mapSet), (VisuMap*)0, color);
  return diff;
}
/**
 * visu_gl_ext_map_set_setTransparent:
 * @mapSet: a #VisuGlExtMapSet object.
 * @alpha: a boolean.
 *
 * Sets if @map is rendered with transparency or not. If @alpha is
 * %TRUE, the lower the rendered value is, the more transparent the
 * colour will be.
 *
 * Since: 3.8
 *
 * Returns: TRUE if transparency of @map is changed.
 **/
gboolean visu_gl_ext_map_set_setTransparent(VisuGlExtMapSet *mapSet, gboolean alpha)
{
  g_return_val_if_fail(VISU_IS_GL_EXT_MAP_SET(mapSet), FALSE);
  if (mapSet->priv->alpha != alpha)
    {
      mapSet->priv->alpha = alpha;
      g_object_notify_by_pspec(G_OBJECT(mapSet), _properties[ALPHA_PROP]);
    }
  return visu_gl_ext_maps_setTransparent(VISU_GL_EXT_MAPS(mapSet), (VisuMap*)0, alpha);
}
/**
 * visu_gl_ext_map_set_setLines:
 * @mapSet: a #VisuGlExtMapSet object.
 * @nLines: a number.
 *
 * Set the number of iso-lines that are computed for each #VisuMap in
 * @mapSet. Contrary to visu_map_setLines(), this routine set the same
 * number of lines for every #VisuMap and use the same bounds for all.
 *
 * Since: 3.8
 *
 * Returns: TRUE if the number of lines of @mapSet is changed.
 **/
gboolean visu_gl_ext_map_set_setLines(VisuGlExtMapSet *mapSet, guint nLines)
{
  g_return_val_if_fail(VISU_IS_GL_EXT_MAP_SET(mapSet), FALSE);

  if (mapSet->priv->nLines == nLines)
    return FALSE;

  mapSet->priv->nLines = nLines;

  g_object_notify_by_pspec(G_OBJECT(mapSet), _properties[N_LINES_PROP]);

  _setLines(mapSet);

  return TRUE;
}
/**
 * visu_gl_ext_map_set_setScaling:
 * @mapSet: a #VisuGlExtMapSet object.
 * @scale: a #ToolMatrixScalingFlag scaling method.
 *
 * Set globally the scaling method for all #VisuMap in @mapSet.
 *
 * Since: 3.8
 *
 * Returns: TRUE if scaling function is changed.
 **/
gboolean visu_gl_ext_map_set_setScaling(VisuGlExtMapSet *mapSet,
                                        ToolMatrixScalingFlag scale)
{
  VisuGlExtMapsIter iter;

  g_return_val_if_fail(VISU_IS_GL_EXT_MAP_SET(mapSet), FALSE);

  if (mapSet->priv->scale == scale)
    return FALSE;

  mapSet->priv->scale = scale;

  g_object_notify_by_pspec(G_OBJECT(mapSet), _properties[SCALE_PROP]);

  visu_gl_ext_shade_setScaling(mapSet->priv->extLegend, scale);
  for (visu_gl_ext_maps_iter_new(VISU_GL_EXT_MAPS(mapSet), &iter);
       iter.valid; visu_gl_ext_maps_iter_next(&iter))
    visu_map_setScaling(iter.map, scale);

  return TRUE;
}
/**
 * visu_gl_ext_map_set_setScalingRange:
 * @mapSet: a #VisuGlExtMapSet object.
 * @minMax: (array fixed-size=2) (allow-none): a range.
 *
 * Set globally the scaling range for all #VisuMap in @mapSet.
 *
 * Since: 3.8
 *
 * Returns: TRUE if the range is changed of @mapSet is changed.
 **/
gboolean visu_gl_ext_map_set_setScalingRange(VisuGlExtMapSet *mapSet, const float minMax[2])
{
  VisuGlExtMapsIter iter;

  g_return_val_if_fail(VISU_IS_GL_EXT_MAP_SET(mapSet), FALSE);

  DBG_fprintf(stderr, "Extension MapSet: set scaling range to %p (%d).\n",
              (gpointer)minMax, mapSet->priv->useManualRange);
  if ((mapSet->priv->useManualRange && minMax &&
       mapSet->priv->manualMinMax[0] == minMax[0] &&
       mapSet->priv->manualMinMax[1] == minMax[1]) ||
      (!mapSet->priv->useManualRange && !minMax))
    return FALSE;

  mapSet->priv->useManualRange = (minMax != (const float*)0);
  g_object_notify_by_pspec(G_OBJECT(mapSet), _properties[USE_MANUAL_MM_PROP]);
  if (minMax)
    {
      mapSet->priv->manualMinMax[0] = minMax[0];
      g_object_notify_by_pspec(G_OBJECT(mapSet), _properties[MANUAL_MIN_PROP]);
      mapSet->priv->manualMinMax[1] = minMax[1];
      g_object_notify_by_pspec(G_OBJECT(mapSet), _properties[MANUAL_MAX_PROP]);
      g_object_notify_by_pspec(G_OBJECT(mapSet), _properties[MANUAL_MM_PROP]);
    }

  for (visu_gl_ext_maps_iter_new(VISU_GL_EXT_MAPS(mapSet), &iter);
       iter.valid; visu_gl_ext_maps_iter_next(&iter))
    visu_map_setScalingRange(iter.map, minMax);

  /* We update the legend min and max. */
  _legend(mapSet);

  return TRUE;
}
/**
 * visu_gl_ext_map_set_getPrecision:
 * @mapSet: a #VisuGlExtMapSet object.
 *
 * Return the rendering adaptability of @map, or the general
 * adaptability value if @map is %NULL.
 *
 * Since: 3.8
 *
 * Returns: the precision value.
 **/
float visu_gl_ext_map_set_getPrecision(const VisuGlExtMapSet *mapSet)
{
  g_return_val_if_fail(VISU_IS_GL_EXT_MAP_SET(mapSet), 100.f);

  return mapSet->priv->prec;
}
/**
 * visu_gl_ext_map_set_getTransparent:
 * @mapSet: a #VisuGlExtMapSet object.
 *
 * Return if field values are also used for alpha channel for @map, or the general
 * setting if @map is %NULL.
 *
 * Since: 3.8
 *
 * Returns: if map is rendered with transparency or not.
 **/
gboolean visu_gl_ext_map_set_getTransparent(const VisuGlExtMapSet *mapSet)
{
  g_return_val_if_fail(VISU_IS_GL_EXT_MAP_SET(mapSet), FALSE);

  return mapSet->priv->alpha;
}

/* Callbacks. */
static gboolean visu_gl_ext_map_set_add(VisuGlExtMaps *maps, VisuMap *map,
                                        float prec, ToolShade *shade,
                                        const ToolColor *color, gboolean alpha)
{
  VisuGlExtMapSet *self = VISU_GL_EXT_MAP_SET(maps);
  g_return_val_if_fail(VISU_IS_GL_EXT_MAP_SET(maps), FALSE);
  g_return_val_if_fail(!visu_map_getField(map) ||
                       visu_map_getField(map) == self->priv->field, FALSE);

  g_hash_table_insert(self->priv->maps, map,
                      _newMapData(VISU_GL_EXT_MAP_SET(maps), map));
  if (!VISU_GL_EXT_MAPS_CLASS(visu_gl_ext_map_set_parent_class)->add(maps, map, prec,
                                                                     shade, color, alpha))
    {
      g_hash_table_remove(self->priv->maps, map);
      return FALSE;
    }

  return TRUE;
}
static void visu_gl_ext_map_set_added(VisuGlExtMaps *maps, VisuMap *map _U_)
{
  VisuGlExtMapSet *self = VISU_GL_EXT_MAP_SET(maps);

  if (visu_gl_ext_getActive(VISU_GL_EXT(maps)) && self->priv->field &&
      !visu_scalar_field_isEmpty(self->priv->field))
    visu_gl_ext_setActive(VISU_GL_EXT(self->priv->extLegend), TRUE);
}
static void visu_gl_ext_map_set_removed(VisuGlExtMaps *maps, VisuMap *map)
{
  VisuGlExtMapSet *self = VISU_GL_EXT_MAP_SET(maps);

  DBG_fprintf(stderr, "Extension MapSet: removing map %p.\n", (gpointer)map);
  g_hash_table_remove(self->priv->maps, map);
  if (!g_hash_table_size(self->priv->maps))
    visu_gl_ext_setActive(VISU_GL_EXT(self->priv->extLegend), FALSE);
}
static void onMapChange(VisuMap *map _U_, gpointer data)
{
  DBG_fprintf(stderr, "Extension MapSet: caught 'changed' signal for map %p.\n",
              (gpointer)map);
  _drawnMinMax(VISU_GL_EXT_MAP_SET(data));
}
static void onFieldChanged(VisuScalarField *field _U_, gpointer data)
{
  VisuGlExtMapSet *self = VISU_GL_EXT_MAP_SET(data);

  visu_gl_ext_setActive(VISU_GL_EXT(self->priv->extLegend),
                        visu_gl_ext_getActive(VISU_GL_EXT(self)) &&
                        g_hash_table_size(self->priv->maps) > 0 &&
                        !visu_scalar_field_isEmpty(self->priv->field));

  self->priv->drawnMinMax[0] = G_MAXFLOAT;
  self->priv->drawnMinMax[1] = -G_MAXFLOAT;
}

/**
 * visu_gl_ext_map_set_export:
 * @mapSet: a #VisuGlExtMapSet object.
 * @map: a given #VisuMap object.
 * @filename: a filename.
 * @format: a format.
 * @error: an error location.
 *
 * Runs visu_map_export() on @map with @mapSet shade and colour.
 *
 * Since: 3.8
 *
 * Returns: TRUE if exportation is successful.
 **/
gboolean visu_gl_ext_map_set_export(VisuGlExtMapSet *mapSet, VisuMap *map,
                                    const gchar *filename, VisuMapExportFormat format,
                                    GError **error)
{
  return visu_map_export(map, mapSet->priv->shade, (mapSet->priv->color) ? mapSet->priv->color->rgba : (float*)0, mapSet->priv->prec, filename, format, error);
}
