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

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

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

#include <openGLFunctions/objectList.h>

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

/**
 * VisuGlExtMapsClass:
 * @parent: the parent class;
 * @added: a method that is called when a #VisuMap is added to a
 * #VisuGlExtMaps object.
 * @removed: a method that is called when a #VisuMap is removed from a
 * #VisuGlExtMaps object.
 *
 * A short way to identify #_VisuGlExtMapsClass structure.
 *
 * Since: 3.7
 */
/**
 * VisuGlExtMaps:
 *
 * An opaque structure.
 *
 * Since: 3.7
 */
/**
 * VisuGlExtMapsPrivate:
 *
 * Private fields for #VisuGlExtMaps objects.
 *
 * Since: 3.7
 */
struct _VisuGlExtMapsPrivate
{
  gboolean dispose_has_run;

  GList *maps;

  /* Objects for rendering. */
  VisuGlView *view;
  gulong gross_signal, detail_signal;
};

typedef struct _MapHandleStruct
{
  VisuGlExtMaps *maps;
  VisuMap *map;
  gulong chg_signal;
  gboolean isBuilt;
  float prec;
  ToolShade *shade;
  ToolColor *color;
  gboolean alpha;
  /* The OpenGL list for this map. */
  int glListId;
} _MapHandle;

enum
  {
    ADDED_SIGNAL,
    REMOVED_SIGNAL,
    N_SIGNALS
  };
static guint _signals[N_SIGNALS] = { 0 };

static void visu_gl_ext_maps_finalize(GObject* obj);
static void visu_gl_ext_maps_dispose(GObject* obj);
static void visu_gl_ext_maps_rebuild(VisuGlExt *ext);
static void visu_gl_ext_maps_draw(VisuGlExt *maps);
static gboolean visu_gl_ext_maps_setGlView(VisuGlExt *maps, VisuGlView *view);
static gboolean _add(VisuGlExtMaps *maps, VisuMap *map,
                     float prec, ToolShade *shade,
                     const ToolColor *color, gboolean alpha);

/* Local callbacks */
static void onViewChange(VisuGlView *view, gpointer data);
static void onMapChange(VisuMap *map, gpointer data);

/* Local routines. */
static void _freeMapHandle(gpointer obj)
{
  _MapHandle *mhd;

  mhd = (_MapHandle*)obj;
  if (VISU_GL_EXT_MAPS_GET_CLASS(mhd->maps)->removed)
    VISU_GL_EXT_MAPS_GET_CLASS(mhd->maps)->removed(mhd->maps, mhd->map);
  DBG_fprintf(stderr, "Extension Maps: emiting %p map removed.\n", (gpointer)mhd->map);
  g_signal_emit(G_OBJECT(mhd->maps), _signals[REMOVED_SIGNAL], 0, mhd->map);
  DBG_fprintf(stderr, "Extension Maps: emission done.\n");
  g_signal_handler_disconnect(G_OBJECT(mhd->map), mhd->chg_signal);
  g_object_unref(G_OBJECT(mhd->map));
  tool_shade_free(mhd->shade);
  g_free(mhd->color);
  glDeleteLists(mhd->glListId, 1);
#if GLIB_MINOR_VERSION > 9
  g_slice_free1(sizeof(_MapHandle), obj);
#else
  g_free(obj);
#endif
}
static gpointer _newMapHandle(VisuGlExtMaps *maps, VisuMap *map,
                              float prec, ToolShade *shade, const ToolColor *color, gboolean alpha)
{
  _MapHandle *mhd;

  g_object_ref(G_OBJECT(map));
#if GLIB_MINOR_VERSION > 9
  mhd = g_slice_alloc(sizeof(_MapHandle));
#else
  mhd = g_malloc(sizeof(_MapHandle));
#endif
  DBG_fprintf(stderr, "Extension Maps: add listeners on map %p.\n", (gpointer)map);
  mhd->maps = maps;
  mhd->map = map;
  mhd->chg_signal = g_signal_connect(G_OBJECT(map), "changed",
                                     G_CALLBACK(onMapChange), maps);
  mhd->isBuilt = FALSE;
  mhd->prec = prec;
  mhd->shade = tool_shade_copy(shade);
  mhd->color = (color) ? g_boxed_copy(TOOL_TYPE_COLOR, color) : (ToolColor*)0;
  mhd->alpha = alpha;
  mhd->glListId  = visu_gl_objectlist_new(1);
  if (maps->priv->view)
    visu_map_setLevel(mhd->map,
                      visu_gl_view_getPrecision(maps->priv->view),
                      maps->priv->view->camera.gross,
                      visu_gl_camera_getRefLength(&maps->priv->view->camera, (ToolUnits*)0));
  if (VISU_GL_EXT_MAPS_GET_CLASS(maps)->added)
    VISU_GL_EXT_MAPS_GET_CLASS(maps)->added(maps, map);
  return (gpointer)mhd;
}
static gint _cmpMapHandle(gconstpointer a, gconstpointer b)
{
  _MapHandle *mhd_a = (_MapHandle*)a;
  
  if (mhd_a->map == b)
    return 0;
  return 1;
}
#define _getMap(H) ((_MapHandle*)H)->map

G_DEFINE_TYPE_WITH_CODE(VisuGlExtMaps, visu_gl_ext_maps, VISU_TYPE_GL_EXT,
                        G_ADD_PRIVATE(VisuGlExtMaps))

static void visu_gl_ext_maps_class_init(VisuGlExtMapsClass *klass)
{
  DBG_fprintf(stderr, "Extension Maps: 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_maps_dispose;
  G_OBJECT_CLASS(klass)->finalize = visu_gl_ext_maps_finalize;
  VISU_GL_EXT_CLASS(klass)->rebuild = visu_gl_ext_maps_rebuild;
  VISU_GL_EXT_CLASS(klass)->draw = visu_gl_ext_maps_draw;
  VISU_GL_EXT_CLASS(klass)->setGlView = visu_gl_ext_maps_setGlView;
  VISU_GL_EXT_MAPS_CLASS(klass)->add = _add;

  /**
   * VisuGlExtMaps::added:
   * @maps: the object emitting the signal.
   * @map: the added #VisuMap.
   *
   * This signal is emitted when @map is added to @maps.
   *
   * Since: 3.8
   */
  _signals[ADDED_SIGNAL] =
    g_signal_new("added", G_TYPE_FROM_CLASS(klass),
                 G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS,
                 G_STRUCT_OFFSET(VisuGlExtMapsClass, added), NULL, NULL,
                 g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, VISU_TYPE_MAP);
  /**
   * VisuGlExtMaps::removed:
   * @maps: the object emitting the signal.
   * @map: the removed #VisuMap.
   *
   * This signal is emitted when @map is removed to @maps.
   *
   * Since: 3.8
   */
  _signals[REMOVED_SIGNAL] =
    g_signal_new("removed", G_TYPE_FROM_CLASS(klass),
                 G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS,
                 G_STRUCT_OFFSET(VisuGlExtMapsClass, removed), NULL, NULL,
                 g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, VISU_TYPE_MAP);
}

static void visu_gl_ext_maps_init(VisuGlExtMaps *obj)
{
  DBG_fprintf(stderr, "Extension Maps: initializing a new object (%p).\n",
	      (gpointer)obj);
  
  obj->priv = visu_gl_ext_maps_get_instance_private(obj);
  obj->priv->dispose_has_run = FALSE;

  /* Private data. */
  obj->priv->maps            = (GList*)0;
  obj->priv->view            = (VisuGlView*)0;
  obj->priv->gross_signal    = 0;
  obj->priv->detail_signal   = 0;
}
static void visu_gl_ext_maps_dispose(GObject* obj)
{
  VisuGlExtMaps *maps;
  GList *lst;

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

  maps = VISU_GL_EXT_MAPS(obj);
  if (maps->priv->dispose_has_run)
    return;
  maps->priv->dispose_has_run = TRUE;

  /* Disconnect signals. */
  visu_gl_ext_maps_setGlView(VISU_GL_EXT(obj), (VisuGlView*)0);
  DBG_fprintf(stderr, "Extension MapSet: clearing a list of %d maps.\n",
              g_list_length(maps->priv->maps));
  for (lst = maps->priv->maps; lst; lst = g_list_next(lst))
    _freeMapHandle(lst->data);

  /* Chain up to the parent class */
  G_OBJECT_CLASS(visu_gl_ext_maps_parent_class)->dispose(obj);
}
static void visu_gl_ext_maps_finalize(GObject* obj)
{
  VisuGlExtMaps *maps = VISU_GL_EXT_MAPS(obj);

  g_return_if_fail(obj);

  DBG_fprintf(stderr, "Extension Maps: finalize object %p.\n", (gpointer)obj);
  /* Free privs elements. */
  if (maps->priv)
    g_list_free(maps->priv->maps);

  /* Chain up to the parent class */
  DBG_fprintf(stderr, "Extension Maps: chain to parent.\n");
  G_OBJECT_CLASS(visu_gl_ext_maps_parent_class)->finalize(obj);
  DBG_fprintf(stderr, "Extension Maps: freeing ... OK.\n");
}

/**
 * visu_gl_ext_maps_new:
 * @name: (allow-none): the name to give to the extension (default is #VISU_GL_EXT_MAPS_ID).
 *
 * Creates a new #VisuGlExt to draw maps.
 *
 * Since: 3.7
 *
 * Returns: a pointer to the #VisuGlExt it created or
 * NULL otherwise.
 */
VisuGlExtMaps* visu_gl_ext_maps_new(const gchar *name)
{
  char *name_ = VISU_GL_EXT_MAPS_ID;
  char *description = _("Drawing extension for maps.");
  VisuGlExt *extensionMaps;

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

  return VISU_GL_EXT_MAPS(extensionMaps);
}

static void _setZoomLevel(VisuGlExtMaps *maps)
{
  GList *lst;
  _MapHandle *mhd;

  /* Adjust zoom level for all maps. */
  for (lst = maps->priv->maps; lst; lst = g_list_next(lst))
    {
      mhd = (_MapHandle*)lst->data;
      visu_map_setLevel(mhd->map,
                        visu_gl_view_getPrecision(maps->priv->view),
                        maps->priv->view->camera.gross,
                        visu_gl_camera_getRefLength(&maps->priv->view->camera, (ToolUnits*)0));
      mhd->isBuilt = FALSE;
    }

  visu_gl_ext_setDirty(VISU_GL_EXT(maps), TRUE);
}
static gboolean visu_gl_ext_maps_setGlView(VisuGlExt *maps, VisuGlView *view)
{
  VisuGlExtMapsPrivate *priv = VISU_GL_EXT_MAPS(maps)->priv;

  /* No change to be done. */
  if (view == priv->view)
    return FALSE;

  if (priv->view)
    {
      g_signal_handler_disconnect(G_OBJECT(priv->view), priv->detail_signal);
      g_object_unref(priv->view);
    }
  priv->view = view;
  if (view)
    {
      g_object_ref(view);
      priv->detail_signal =
        g_signal_connect(G_OBJECT(view), "DetailLevelChanged",
                         G_CALLBACK(onViewChange), (gpointer)maps);
      _setZoomLevel(VISU_GL_EXT_MAPS(maps));
    }
  else
    {
      /* priv->gross_signal     = 0; */
      priv->detail_signal     = 0;
    }

  return TRUE;
}

/**
 * visu_gl_ext_maps_add:
 * @maps: a #VisuGlExtMaps object.
 * @map: (transfer full): a #VisuMaps object.
 * @prec: rendering adaptivity level (default is 100).
 * @shade: (transfer full): a #ToolShade object.
 * @color: (transfer full) (allow-none): a #ToolColor object.
 * @alpha: a boolean.
 *
 * Add a new map to the list of drawn maps. If @color is %NULL, then
 * iso-lines will be drawn in inverse color. If @alpha is TRUE, the
 * map will be rendered with alpha blending when values go to zero.
 *
 * Since: 3.7
 *
 * Returns: FALSE if @surf was already reguistered.
 **/
gboolean visu_gl_ext_maps_add(VisuGlExtMaps *maps, VisuMap *map,
                              float prec, ToolShade *shade,
                              const ToolColor *color, gboolean alpha)
{
  VisuGlExtMapsClass *klass = VISU_GL_EXT_MAPS_GET_CLASS(maps);
  g_return_val_if_fail(klass && klass->add, FALSE);

  return klass->add(maps, map, prec, shade, color, alpha);
}
static gboolean _add(VisuGlExtMaps *maps, VisuMap *map,
                     float prec, ToolShade *shade,
                     const ToolColor *color, gboolean alpha)
{
  GList *lst;

  g_return_val_if_fail(VISU_IS_GL_EXT_MAPS(maps), FALSE);

  lst = g_list_find_custom(maps->priv->maps, map, _cmpMapHandle);
  if (lst)
    return FALSE;

  maps->priv->maps = g_list_prepend(maps->priv->maps,
                                    _newMapHandle(maps, map, prec, shade, color, alpha));
  DBG_fprintf(stderr, "Extension MapSet: adding map %p (%d).\n",
              (gpointer)map, g_list_length(maps->priv->maps));
  g_signal_emit(G_OBJECT(maps), _signals[ADDED_SIGNAL], 0, map);

  visu_gl_ext_setDirty(VISU_GL_EXT(maps), TRUE);
  return TRUE;
}
/**
 * visu_gl_ext_maps_remove:
 * @maps: a #VisuGlExtMaps object.
 * @map: a #VisuMaps object.
 *
 * Removes @map from the list of drawn maps.
 *
 * Since: 3.7
 *
 * Returns: TRUE if @map was part of drawn maps.
 **/
gboolean visu_gl_ext_maps_remove(VisuGlExtMaps *maps, VisuMap *map)
{
  GList *lst;

  g_return_val_if_fail(VISU_IS_GL_EXT_MAPS(maps), FALSE);
  
  lst = g_list_find_custom(maps->priv->maps, map, _cmpMapHandle);
  if (!lst)
    return FALSE;

  maps->priv->maps = g_list_remove_link(maps->priv->maps, lst);
  _freeMapHandle(lst->data);
  g_list_free(lst);
  DBG_fprintf(stderr, "Extension MapSet: removing map %p (%d).\n",
              (gpointer)map, g_list_length(maps->priv->maps));

  visu_gl_ext_setDirty(VISU_GL_EXT(maps), TRUE);
  return TRUE;
}
/**
 * visu_gl_ext_maps_removeAll:
 * @maps: a #VisuGlExtMaps object.
 *
 * Removes all the #VisuMap stored in @maps.
 *
 * Since: 3.8
 **/
void visu_gl_ext_maps_removeAll(VisuGlExtMaps *maps)
{
  g_return_if_fail(VISU_IS_GL_EXT_MAPS(maps));
  
  g_list_free_full(maps->priv->maps, _freeMapHandle);
  maps->priv->maps = (GList*)0;
  DBG_fprintf(stderr, "Extension MapSet: removing all.\n");
  visu_gl_ext_setDirty(VISU_GL_EXT(maps), TRUE);
}

static gboolean _getMapIter(VisuGlExtMaps *maps, VisuMap *map, GList *iter)
{
  GList *lst;

  if (map)
    {
      lst = g_list_find_custom(maps->priv->maps, map, _cmpMapHandle);
      if (!lst)
        return FALSE;
      iter->data = lst->data;
      iter->next = (GList*)0;
    }
  else
    {
      if (!maps->priv->maps)
        return FALSE;
      *iter = *maps->priv->maps;
    }
  return TRUE;
}
/**
 * visu_gl_ext_maps_setPrecision:
 * @maps: a #VisuGlExtMaps object.
 * @map: a #VisuMap 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.7
 *
 * Returns: TRUE if @prec of @map is changed.
 **/
gboolean visu_gl_ext_maps_setPrecision(VisuGlExtMaps *maps, VisuMap *map, float prec)
{
  GList *lst, iter;
  _MapHandle *mhd;
  gboolean diff;

  g_return_val_if_fail(VISU_IS_GL_EXT_MAPS(maps), FALSE);

  if (!_getMapIter(maps, map, &iter))
    return FALSE;
  
  diff = FALSE;
  for (lst = &iter; lst; lst = g_list_next(lst))
    {
      mhd = (_MapHandle*)lst->data;
      if (mhd->prec != prec)
        {
          mhd->prec = prec;
          mhd->isBuilt = FALSE;
          diff = TRUE;
        }
    }
  if (diff)
    visu_gl_ext_setDirty(VISU_GL_EXT(maps), TRUE);
  return diff;
}
/**
 * visu_gl_ext_maps_setShade:
 * @maps: a #VisuGlExtMaps object.
 * @map: a #VisuMap object.
 * @shade: (allow-none) (transfer full): a #ToolShade object.
 *
 * Changes the #ToolShade used to render data variation on the @map.
 *
 * Since: 3.7
 *
 * Returns: TRUE if @shade of @map is changed.
 **/
gboolean visu_gl_ext_maps_setShade(VisuGlExtMaps *maps, VisuMap *map, ToolShade *shade)
{
  GList *lst, iter;
  _MapHandle *mhd;
  gboolean diff;

  g_return_val_if_fail(VISU_IS_GL_EXT_MAPS(maps), FALSE);

  DBG_fprintf(stderr, "Extension Maps: change shade (for maps %p).\n", (gpointer)map);
  if (!_getMapIter(maps, map, &iter))
    return FALSE;
  
  diff = FALSE;
  for (lst = &iter; lst; lst = g_list_next(lst))
    {
      mhd = (_MapHandle*)lst->data;
      DBG_fprintf(stderr, " | map %p, update %d.\n", (gpointer)mhd->map,
                  !tool_shade_compare(mhd->shade, shade));
      if (!tool_shade_compare(mhd->shade, shade))
        {
          tool_shade_free(mhd->shade);
          mhd->shade = tool_shade_copy(shade);
          mhd->isBuilt = FALSE;
          diff = TRUE;
        }
    }
  if (diff)
    visu_gl_ext_setDirty(VISU_GL_EXT(maps), TRUE);
  return diff;
}
/**
 * visu_gl_ext_maps_setLineColor:
 * @maps: a #VisuGlExtMaps object.
 * @map: a #VisuMap 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_maps_setShade()).
 *
 * Since: 3.7
 *
 * Returns: TRUE if @color of @map is changed.
 **/
gboolean visu_gl_ext_maps_setLineColor(VisuGlExtMaps *maps, VisuMap *map,
                                       const ToolColor *color)
{
  GList *lst, iter;
  _MapHandle *mhd;
  gboolean diff;

  g_return_val_if_fail(VISU_IS_GL_EXT_MAPS(maps), FALSE);

  if (!_getMapIter(maps, map, &iter))
    return FALSE;
  
  diff = FALSE;
  for (lst = &iter; lst; lst = g_list_next(lst))
    {
      mhd = (_MapHandle*)lst->data;
      if (!tool_color_equal(mhd->color, color))
        {
          g_free(mhd->color);
          mhd->color = (color) ? g_boxed_copy(TOOL_TYPE_COLOR, color) : (ToolColor*)0;
          mhd->isBuilt = FALSE;
          diff = TRUE;
        }
    }
  if (diff)
    visu_gl_ext_setDirty(VISU_GL_EXT(maps), TRUE);
  return diff;
}
/**
 * visu_gl_ext_maps_setTransparent:
 * @maps: a #VisuGlExtMaps object.
 * @map: a #VisuMap 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.7
 *
 * Returns: TRUE if transparency of @map is changed.
 **/
gboolean visu_gl_ext_maps_setTransparent(VisuGlExtMaps *maps, VisuMap *map, gboolean alpha)
{
  GList *lst, iter;
  _MapHandle *mhd;
  gboolean diff;

  g_return_val_if_fail(VISU_IS_GL_EXT_MAPS(maps), FALSE);

  if (!_getMapIter(maps, map, &iter))
    return FALSE;
  
  diff = FALSE;
  for (lst = &iter; lst; lst = g_list_next(lst))
    {
      mhd = (_MapHandle*)lst->data;
      if (mhd->alpha != alpha)
        {
          mhd->alpha = alpha;
          mhd->isBuilt = FALSE;
          diff = TRUE;
        }
    }
  if (diff)
    visu_gl_ext_setDirty(VISU_GL_EXT(maps), TRUE);
  return diff;
}

static void visu_gl_ext_maps_draw(VisuGlExt *maps)
{
  _MapHandle *mhd;
  GList *lst;
  #if DEBUG == 1
  GTimer *timer;
  gulong fractionTimer;
  #endif
  VisuGlExtMapsPrivate *priv = VISU_GL_EXT_MAPS(maps)->priv;

  DBG_fprintf(stderr, "Extension Maps: rebuilding map list.\n");
  glDeleteLists(visu_gl_ext_getGlList(maps), 1);
  visu_gl_ext_setDirty(maps, FALSE);

  if (!priv->view || !priv->maps)
    return;

#if DEBUG == 1
  timer = g_timer_new();
  g_timer_start(timer);
#endif

  /* Rebuild maps if required. */
  for (lst = priv->maps; lst; lst = g_list_next(lst))
    {
      mhd = (_MapHandle*)lst->data;
      if (!mhd->isBuilt)
        {
          glNewList(mhd->glListId, GL_COMPILE);
          visu_map_draw(_getMap(lst->data), mhd->prec, mhd->shade,
                        (mhd->color) ? mhd->color->rgba:(float*)0, mhd->alpha);
          glEndList();
          mhd->isBuilt = TRUE;
        }
    }

  /* Call the map one by one. */
  visu_gl_ext_startDrawing(maps);
  for (lst = priv->maps; lst; lst = g_list_next(lst))
    glCallList(((_MapHandle*)lst->data)->glListId);
  visu_gl_ext_completeDrawing(maps);

#if DEBUG == 1
  g_timer_stop(timer);
  fprintf(stderr, "Extension Maps: draw map(s) in %g micro-s.\n",
	  g_timer_elapsed(timer, &fractionTimer)/1e-6);
  g_timer_destroy(timer);
#endif
}

/* Callbacks. */
static void visu_gl_ext_maps_rebuild(VisuGlExt *ext)
{
  VisuGlExtMaps *maps = VISU_GL_EXT_MAPS(ext);
  GList *lst;

  for (lst = maps->priv->maps; lst; lst = g_list_next(lst))
    ((_MapHandle*)lst->data)->isBuilt = FALSE;
  visu_gl_ext_maps_draw(ext);
}
static void onViewChange(VisuGlView *view _U_, gpointer data)
{
  _setZoomLevel(VISU_GL_EXT_MAPS(data));
}
static void onMapChange(VisuMap *map, gpointer data)
{
  GList *lst;

  for (lst = VISU_GL_EXT_MAPS(data)->priv->maps; lst; lst = g_list_next(lst))
    if (((_MapHandle*)lst->data)->map == map)
      {
        DBG_fprintf(stderr, "Extension Maps: map %p is dirty.\n", (gpointer)map);
        ((_MapHandle*)lst->data)->isBuilt = FALSE;
        break;
      }
  DBG_fprintf(stderr, "Extension Maps: map changed, becoming dirty.\n");
  visu_gl_ext_setDirty(VISU_GL_EXT(data), TRUE);
}

/**
 * visu_gl_ext_maps_iter_new:
 * @maps: a #VisuGlExtMaps object.
 * @iter: (out caller-allocates): a location to #VisuGlExtMapsIter.
 *
 * Generate a new iterator to run over #VisuMap objects stored in @maps.
 *
 * Since: 3.8
 **/
void visu_gl_ext_maps_iter_new(VisuGlExtMaps *maps, VisuGlExtMapsIter *iter)
{
  g_return_if_fail(VISU_IS_GL_EXT_MAPS(maps) && iter);

  iter->maps = maps;
  iter->next = maps->priv->maps;
  visu_gl_ext_maps_iter_next(iter);
}

/**
 * visu_gl_ext_maps_iter_next:
 * @iter: a #VisuGlExtMapsIter iterator.
 *
 * Go to the next #VisuMap in @iter.
 *
 * Since: 3.8
 **/
void visu_gl_ext_maps_iter_next(VisuGlExtMapsIter *iter)
{
  g_return_if_fail(iter);

  iter->valid = (iter->next != (GList*)0);
  iter->map = (iter->valid) ? _getMap(iter->next->data) : (VisuMap*)0;
  iter->next = g_list_next(iter->next);
}
