/** File gu_obj_utils.c
 *
 *  Functions that manage objs
 *  Used with and without NOGUI
 *
 *  Declarations in ng_objects.h
 *
 *  Replace part of objutils.c
 *
 *  Rules for functions names
 *  -------------------------
 *  aobjs_*           : all objects
 *  aobj_*            : any object
 *  objs_*            : all objects in array objs
 *  objs_t_*          : all objects in array objs
 *  obj_*             : one object in objs , accessed via its index in array
 *  obj_tid_*         : one object in objs , accessed via (typ,id)
 *
 *  Graph array g[] is allocated in realloc_graphs and
 *  the linked objs[] element is created in this function
 *
 * A FAIRE: unifier  default_t_objs et objs[i] ou i est associé
 *          a un temmplate
 */

#include <config.h>
#include <math.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "defines.h"
#include "globals.h"

#include "device.h"
#include "graphs.h"
#include "utils.h"
#include "protos.h"
#include "plotone.h"
#include "ssdata.h"

#include "zc.h"

#include "ng_objects.h"
#include "nn_tree.h"
#include "nn_cmd.h"

/* the maximum distance away from a point
 * you may be when picking it  (smaller than for objects) */
#define NG_MAXPICKDISTPOINTS 0.015

/*  g[] is defined in graphs.c 58 */
extern graph *g;

extern void ppp (char *s);
extern void bbb (char *s);

/* the maximum distance away from an object
 * you may be when picking it
 * (in viewport  coordinates) */
#define NG_MAXPICKDIST 0.015

static int maxobjs = 0;                       /* nb of QDobject(s) in iobs */
static QDobject default_obj;                  /* primary template for all default_t_objs */
static int default_obj_uninitialized = TRUE;

static QDobject default_t_objs[Q_Last];       /* templates for all Qtype(s) of  QDobject struct */

static int max_t_objs[Q_Last];                /* Nb of objs for each typ */
static int next_t_objs_id[Q_Last];            /* Last id used for each typ (some intermediate me be not used)*/

static void ng_create_project (void);

/***************** I N T E R N A L    F U N C T I O N S **************/
/**
 * Copy the master template into obj
 */
static void obj_get_default (QDobject *obj)
{
  memcpy (obj ,&default_obj ,sizeof (QDobject));
  memcpy (&(obj->arrow) ,&(default_obj.arrow) ,sizeof (Arrow));
}

/**
 *  obj_next : find next slot in objs,
 *  if no available, create OBJECT_BUFNUM more elements
 *  initialized to default_obj
 */
int obj_next (void)
{
  int i, maxold;
  for (i = 0; i < maxobjs; i++) {
    if (!obj_is_active (i)) {
      obj_get_default (&objs[i]);
      objs[i].self   = i;
      return (i);
    }
  }
  maxold = maxobjs;
  objs = xrealloc (objs, (maxobjs + OBJECT_BUFNUM) * sizeof(QDobject));
  if (objs == NULL) {
    errmsg ("obj_next: ERROR in reallocating array objs");
    return -1;
  }
  maxobjs += OBJECT_BUFNUM;
  for (i = maxold; i < maxobjs; i++) {
    obj_get_default (&objs[i]);
  }
  objs[maxold].self   = maxold;
  return maxold;
}

static void obj_apply_shift (QDobject *po ,VPoint vp ,int back)
{
  int j;
  double x ,y;
  if (back) {
    x = -vp.x;
    y = -vp.y;
  } else {
    x = vp.x;
    y = vp.y;
  }
  if (po->loctyp == COORD_WORLD) {
    WPoint wp;
    VVector vv;
    vv.x = x;
    vv.y = y;
    wp = Vshift2Wshift (vv);
    x = wp.x;
    y = wp.y;
  }
  po->x1 += x;
  po->y1 += y;
  po->x2 += x;
  po->y2 += y;
  if (po->typ == Q_Polyline) {
    for (j = 0; j < po->nxy; j++) {
      po->x[j] += x;
      po->y[j] += y;
    }
  } 
}




/*********** I N Q U I R Y    F U N C T I O N S   ***************/

/**
 *  objs_max : return the number of elements in array objs
 */
int objs_max (void)
{
  return maxobjs;
}

/**
 *  obj_t_count : count the number of t_objs of a given type
 */
int obj_t_count (Qtype typ)
{
  int i ,j = 0;

  for (i = 0; i < maxobjs; i++) {
    if (objs[i].typ == typ) j++;
  }
  return j;
}

int obj_is_active   (int i)
{
  if (i >= 0 && i < maxobjs) {
    return objs[i].active;
  } else {
    return FALSE;
  }
}

/**
 *  number_of_active_world_objs : in a given gno
 */
int number_of_active_world_objs (int gno)
{
  int i;
  int retval = 0;
  for (i = iTS+1; i < maxobjs; i++) {
    if ((objs[i].active               ) &&
	(objs[i].father_id == gno     ) &&
	(objs[i].loctyp    == COORD_WORLD)
	) retval++;
  }
  return retval;
}

/**
 * obj_tid_is_valid :  looks if (typ ,id) already exists
 */
int obj_tid_is_valid (Qtype typ, int id)
{
  int i;

  for (i = 0; i < maxobjs; i++) {
    if (objs[i].typ == typ && objs[i].id == id) return TRUE;
  }
  return FALSE;
}

/**
 * get_world_objs_minmax is useful to update bb for compound
 */
void get_world_objs_minmax  (int gno
			     ,double *xmin, double *xmax
			     ,double *ymin, double *ymax
			     ,int *first)
{
  int i ,j;
  double *x ,*y;
  for (i = iTS+1; i < maxobjs; i++) {
    if ((objs[i].active           ) &&
	(objs[i].father_id == gno ) &&
	(objs[i].loctyp    == COORD_WORLD)
	) {
      *xmin = (objs[i].x1 < *xmin) ? objs[i].x1 : *xmin;
      *xmax = (objs[i].x1 > *xmax) ? objs[i].x1 : *xmax;
      *ymin = (objs[i].y1 < *ymin) ? objs[i].y1 : *ymin;
      *ymax = (objs[i].y1 > *ymax) ? objs[i].y1 : *ymax;
      if (objs[i].typ != Q_String) {
	*xmin = (objs[i].x2 < *xmin) ? objs[i].x2 : *xmin;
	*xmax = (objs[i].x2 > *xmax) ? objs[i].x2 : *xmax;
	*ymin = (objs[i].y2 < *ymin) ? objs[i].y2 : *ymin;
	*ymax = (objs[i].y2 > *ymax) ? objs[i].y2 : *ymax;
	if (objs[i].typ == Q_Polyline) {
	  x = objs[i].x;
	  y = objs[i].y;
	  for (j = 0; j < objs[i].nxy; j++) {
	    *xmin = (x[j] < *xmin) ? x[j] : *xmin;
	    *xmax = (x[j] > *xmax) ? x[j] : *xmax;
	    *ymin = (y[j] < *ymin) ? y[j] : *ymin;
	    *ymax = (y[j] > *ymax) ? y[j] : *ymax;
	  }
	}
      }
      *first = FALSE;
    }
  }
}

/**
 * For Q_Line, Q_String, Q_Box, Q_TimeStamp ,Q_Compound
 * the father_id is not needed, as a unique id by typ is assumed
 */
int obj_geom_get_num (Qtype typ ,int id)
{
  int i;

  if (obj_t_is_geometric (typ)) {
    for (i = 0; i < maxobjs; i++) {
      if ( objs[i].active             &&
	   objs[i].typ       == typ &&
	   objs[i].id        == id
	   ) return i;
    }
  }
  if (id >= 0) {
    if (typ > -2 && typ < Q_Last) {
      fprintf (stderr ,"obj_geom_get_num: Warning %s %d not found\n" ,Qtype_name[typ] ,id);
    } else {
      fprintf (stderr ,"obj_geom_get_num: ATTENTION  n'a pas trouve typ=%d id=%d \n" ,typ ,id);
    }
  }
  return (-1);
}

/**
 *  obj_tid_get_num : returns the index in objs[]  if not found ,return -1
 */
int obj_tid_get_num (Qtype typ ,int id ,Qtype father_typ ,int father_id)
{
  int i;
  for (i = 0; i < maxobjs; i++) {
    if ( objs[i].active           	  &&
	 objs[i].typ        == typ 	  &&
	 objs[i].id         == id  	  &&
	 objs[i].father_typ == father_typ &&
	 objs[i].father_id  == father_id
	 ) return i;
  }
  return (-1);
}

int obj_tid_get_graph_num (int gno)
{
  return obj_tid_get_num (Q_Graph ,gno ,Q_Project ,-1);
}

int obj_tid_get_lbox_num (int gno)
{
  return obj_tid_get_num (Q_LegendBox ,-1 ,Q_Graph ,gno);
}

int obj_tid_get_set_num (int gno ,int setno)
{
  return obj_tid_get_num (Q_Set ,setno ,Q_Graph ,gno);
}

/**
 * Remplacer ???
 */
void *obj_tid_get_pointer (Qtype typ ,int id ,int father_id)
{
  int i;

  if (typ == Q_Graph     && father_id >= 0)  return &g[father_id];
  if (typ == Q_LegendBox && father_id >= 0)  return &(g[father_id].l);

  if (obj_t_is_geometric (typ) && id == -1) return &default_t_objs[typ];

  for (i = 0; i < maxobjs; i++) {
    if (typ 	  == objs[i].typ &&
	id  	  == objs[i].id  &&
	father_id == objs[i].father_id   )
      {
	return &(objs[i]);
      }
  }
  return NULL;
}


/**
 *  Returns TRUE  if vp is inside the bounding box bb.
 *       if all_obj==TRUE, scan all objs
 *                 else search only the current obj
 *       legendboxes, sets, timestamp can be excluded
 *       from the search
 *                 father_id and bb in args an make object current id and obj
 *                 gno and setno if a set (if not returns gno=-1)
 *  else if vp is be inside current object (cur_obj_num ,cur_obj_typ ,cur_obj_id)
 */
int  find_obj (VPoint vp ,int all_obj
	       ,int with_legendbox
	       ,int with_sets
	       ,int with_timestamp
	       ,int with_colorbar
	       ,view *bb ,int *gno ,int *setno)
{
  Qtype ti;
  int i ,father;
  legend l;
  QDobject lobj;
  int loc; // provisoire

  int i1 = with_timestamp ? iTS : iTS+1;
  *gno = -1;
  if (all_obj) {
    /* The loop on types is used to make a hierarchical find */
    for (ti = 0; ti < Q_Last_objs; ti++) {

      for (i = i1; i < maxobjs; i++) {
	if (objs[i].active && objs[i].typ == ti) {
	  if (is_vpoint_inside (objs[i].bb ,vp ,NG_MAXPICKDIST)) {
	    /* exclude colorbar if needed */
	    if (objs[i].typ == Q_Colorbar && !with_colorbar) continue;
	    /* exclude objects in compound(s) */
	    father = nn_get_father (i);
	    if (objs[father].typ == Q_Compound) continue;
	    obj_tid_make_current (ti ,objs[i].id
				  ,objs[i].father_typ ,objs[i].father_id);
	    cur_obj_num = i;
	    *bb         = objs[i].bb;
	    if (objs[i].loctyp == COORD_WORLD) {
	      select_graph (objs[i].father_id);
	    }
	    return TRUE;
	  }
	}
      }
    }
    for (i = 0; i < number_of_graphs (); i++) {
      if (with_legendbox) {
	get_graph_legend (i, &l ,&lobj);
	if (l.active && is_vpoint_inside(l.bb, vp, NG_MAXPICKDIST)) {
	  obj_tid_make_current (Q_LegendBox ,-1 ,Q_Graph ,i);
	  *bb         = l.bb;
	  return TRUE;
	}
      }
    }
    if (with_sets) {
      for (i = 0; i < number_of_graphs (); i++) {
	select_graph (i);
	*setno = -1;
	if (find_point (i ,&vp ,setno ,&loc) == RETURN_SUCCESS) {
	  *gno = i;
	  obj_tid_make_current (Q_Set ,*setno ,Q_Graph ,i);
	  return TRUE;
	}
      }
    }
  } else {
    /* search only for current obj */
    if (with_legendbox == TRUE && cur_obj_typ == Q_LegendBox) {
      get_graph_legend (cur_parent_id ,&l ,&lobj);
      *bb = l.bb;
      if (l.active && is_vpoint_inside(l.bb, vp, NG_MAXPICKDIST)) {
	get_graph_legend (cur_obj_id ,&l ,&lobj);
	return TRUE;
      }
    } else {
      i = cur_obj_num;
      if (objs[i].active) {
	if (is_vpoint_inside (objs[i].bb ,vp ,NG_MAXPICKDIST)) {
	  *bb = objs[i].bb;
	  return TRUE;
	}
      }
    }
  }
  return FALSE;
}

/**
 * find_polyline : locate a polyline if clicked near xy[]
 */
int find_polyline (VPoint *vp ,int all_obj ,int id ,int *npoint ,int *num)
{
  int i ,j ,start ,stop;
  VPoint vp0 ,vpshift ,vpj;
  if (all_obj) {
    start = 1;
    stop  = maxobjs;
  } else {
    start = obj_geom_get_num (Q_Polyline ,id);
    stop = start + 1;
  }
  if (start > 0) {
    for (i = start; i < stop; i++) {
      obj_get_shift (i ,&vpshift);
      vp0.x = vp->x - vpshift.x;
      vp0.y = vp->y - vpshift.y;
      for (j = 0; j < objs[i].nxy; j++) {
	if (obj_polyline_get_vp (i ,j ,&vpj)) {
	  if (dist_vp_xy (vp0 ,vpj.x ,vpj.y) < NG_MAXPICKDIST) {
	    vp->x = objs[i].x[j] + vpshift.x;
	    vp->y = objs[i].y[j] + vpshift.y;
	    *npoint = j;
	    *num = i;
	    return RETURN_SUCCESS;
	  }
	}
      }
    }
  }
  return RETURN_FAILURE;
}

/**
 * find_point : locate a point and the set the point is in
 *              if called with gno invalid, try all graphs.
 */
int find_point (int gno, VPoint *vp, int *setno, int *loc)
{
  int i, start, stop, j, found;
  double *xtmp, *ytmp;
  WPoint wptmp;
  VPoint vptmp;
  double dist;

  if (is_valid_gno (gno) != TRUE) return RETURN_FAILURE;

  if (is_valid_setno (gno, *setno)) {
    start = *setno;
    stop = *setno;
  } else {
    start = 0;
    stop = number_of_sets (gno) - 1;
  }
  found = FALSE;
  for (i = start; i <= stop; i++) {
    if (is_set_hidden (gno, i) == FALSE) {
      xtmp = getx (gno, i);
      ytmp = gety (gno, i);
      for (j = 0; j < getsetlength (gno, i); j++) {
	wptmp.x = xtmp[j];
	wptmp.y = ytmp[j];
	vptmp = Wpoint2Vpoint (wptmp);

	dist = MAX2 (fabs(vp->x - vptmp.x) , fabs(vp->y - vptmp.y));
	if (dist < NG_MAXPICKDISTPOINTS) {
	  found = TRUE;
	  *setno = i;
	  *loc = j;
	  vp->x = vptmp.x;
	  vp->y = vptmp.y;
	  return RETURN_SUCCESS;
	}
      }
    }
  }
  return RETURN_FAILURE;
}

int arc_subtyp (int pmask)
{
  switch (pmask) {
  case MARC_ArcCentered:    return 6;
  case MARC_ArcOfCircle:    return 5;
  case MARC_CircleByRadius: return 4;
  case MARC_Circle:         return 3;
  case MARC_EllipseByRadii: return 2;
  case MARC_Ellipse:        return 1;
  default:                  return 0;
  }
}

int arc_pmask (int subtyp)
{
  switch (subtyp) {
  case 1: return MARC_Ellipse        ;
  case 2: return MARC_EllipseByRadii ;
  case 3: return MARC_Circle         ;
  case 4: return MARC_CircleByRadius ;
  case 5: return MARC_ArcOfCircle    ;
  case 6: return MARC_ArcCentered    ;
  }
  return MA_NONE;
}

int template_pmask_get (Qtype typ)
{
  return default_t_objs[typ].pmask;
}

/**
 * obj_t_get_p_to_template : gets a pointer to the template
 */
QDobject *obj_t_get_p_to_template (Qtype typ)
{
  if (typ > 0 && typ < Q_TimeStamp) {
    return &default_t_objs[typ];
  } else {
    return NULL;
  }
}

int obj_t_get_template_num (Qtype typ)
{
  return default_t_objs[typ].self;
}

int obj_t_is_geometric (Qtype typ)
{
  return (typ == Q_Compound  ||
	  typ == Q_Line      ||
	  typ == Q_Polyline  ||
	  typ == Q_Arc       ||
	  typ == Q_String    ||
	  typ == Q_Box       ||
	  typ == Q_Colorbar  ||
	  typ == Q_TimeStamp
	  );
}

int obj_is_geom_prunable (int i)
{
  int   typ_ok, father_typ_ok;
  if (obj_is_active (i)) {
    Qtype typ = objs[i].typ;
    typ_ok = typ == Q_Compound ||
      typ == Q_Line        ||
      typ == Q_Polyline    ||
      typ == Q_Arc         ||
      typ == Q_String      ||
      typ == Q_Box;
    father_typ_ok = (objs[i].father_typ == Q_Compound) || (objs[i].father_typ == Q_Graph);
    return (typ_ok && father_typ_ok && objs[i].id >= 0);
  }
  return FALSE;
}


/**
 * Run backward and returns the id of ancestor with type "typ"
 *                          or -1 if not found
 */
int obj_get_typed_ancestor_id (int i ,Qtype typ)
{
  if (obj_is_active (i)) {
    if (objs[i].id < 0) return -1; /* a template */
    int e = objs[i].elder;
    while (e >= 0) {
      if (objs[e].typ == typ) return objs[e].id;
      e = nn_get_father(e);
    }
  }
  return -1;
}


int obj_get_plotarr (int i ,QDobject **ppo ,int *pgno ,int *psetno ,plotarr *p)
{
  if (i > 0 && i < maxobjs) {
    *ppo    = &objs[i];
    *pgno   = obj_get_typed_ancestor_id (i ,Q_Graph);
    *psetno = objs[i].father_id;
    if (is_valid_setno (*pgno ,*psetno)) {
      if (get_graph_plotarr (*pgno ,*psetno ,p) == RETURN_SUCCESS) {
	return RETURN_SUCCESS;
      }
    }
  }
  return RETURN_FAILURE;
}

/**
 * For objs inside compound(s), return the shift introduced by the compound(s)
 * vp is always in view coordinates
 */
void obj_get_shift (int i ,VPoint *vp)
{
  WPoint wp;
  VPoint vp1;
  vp->x = 0.0;
  vp->y = 0.0;
  if (i < 0) return;
  while ((i = nn_get_father (i)) > 0) {
    if (objs[i].typ == Q_Compound) {
      if (objs[i].loctyp == COORD_WORLD) {
	wp.x = objs[i].x1;
	wp.y = objs[i].y1;
	vp1 = Wshift2Vshift (wp);
	vp->x += vp1.x;
	vp->y += vp1.y;
      } else {
	vp->x += objs[i].x1;
	vp->y += objs[i].y1;
      }
    }
  }
}

/**
 * Returns in "*vp" the viewport coordinates of
 * point j in polyline objs[i] (even if loctype==COORD_WORLD)
 * without the shift if part of compound(s)
 * if j<0 take 0, if j>=nxy, take last point
 */
int obj_polyline_get_vp (int i ,int j ,VPoint *vp)
{
  if (obj_is_active (i) == FALSE) return FALSE;
  if (objs[i].typ != Q_Polyline)  return FALSE;
  if (objs[i].x   == NULL)        return FALSE;
  if (objs[i].nxy == 0)           return FALSE;
  if (j < 0)            j = 0;
  if (j >= objs[i].nxy) j = objs[i].nxy - 1;
  if (objs[i].loctyp == COORD_WORLD) {
    world2view (objs[i].x[j] ,objs[i].y[j] ,&(vp->x) ,&(vp->y));
  } else {
    vp->x = objs[i].x[j];
    vp->y = objs[i].y[j];
  }
  return TRUE;
}

/*********** C R E A T E and K I L L   F U N C T I O N S   ***************/

/**
 *  Create also clipboard
 */
static void ng_create_project (void)
{
  int i = 0;
  Qtype typ;
  /* Set project */
  obj_set (i ,&default_t_objs[Q_Project]);
  /* Create timestamp */
  i = obj_next ();
  if (i != iTS) {
    errmsg ("ng_create_default_objs: internal error. Q_TimeStamp must be objs[iTS]");
    exit (0);
  }
  obj_set (iTS ,&default_t_objs[Q_TimeStamp]);
  nn_insert_child (0 ,iTS);
  /* add templates in the tree */
  for (typ = Q_Line; typ < Q_TimeStamp; typ++) {
    i = obj_next ();
    obj_get_template (typ ,&objs[i]);
    nn_append_child (0 ,i);
    objs[i].active = TRUE;
    default_t_objs[typ].self =i;
  }
}

/**
 * obj_t_next_set : creates a set in objs if it does not already exist in objs.
 * Assume axis exists.
 */
void obj_t_next_set (int gno ,int setno)
{
  if (obj_tid_get_set_num (gno ,setno) < 0) {
    int i = obj_next ();
    obj_set (i ,&default_t_objs[Q_Set]);
    int igno = obj_tid_get_graph_num (gno);
    int iaxis = objs[igno].child;
    nn_append_typed_brother (iaxis ,i);
    objs[i].id        = setno;
    objs[i].father_id = objs[igno].id;
  }
}

/**
 * obj_t_next_colorbar : add a Q_Colorbar child to setno
 */
int obj_t_next_colorbar (int gno ,int setno)
{
  view v;
  int i = obj_next ();
  obj_set (i ,&default_t_objs[Q_Colorbar]);
  objs[i].self = i;
  objs[i].id   = next_t_objs_id[Q_Colorbar]; 
  objs[i].father_id = setno;
  int nset = obj_tid_get_set_num (gno ,setno);
  nn_append_child (nset ,i);
  get_graph_viewport (gno ,&v);
  objs[i].x1 = v.xv2 + 0.05;
  objs[i].x2 = v.xv2 + 0.10;
  objs[i].y1 = v.yv1 + 0.02;
  objs[i].y2 = v.yv2 - 0.02;
  next_t_objs_id[Q_Colorbar]++;
  max_t_objs[Q_Colorbar]++;
  return i;
}

/**
 * obj_t_next_axis : add a Q_Axis child at objs[parent]
 */
static void obj_t_next_axis (int parent ,int axisno)
{
  int i = obj_next ();
  obj_set (i ,&default_t_objs[Q_Axis]);
  nn_append_typed_child (Q_Axis ,parent ,i);
  objs[i].id        = axisno;     /* X_AXIS , Y_AXIS , ZX_AXIS or ZY_AXIS */
  objs[i].father_id = objs[parent].id;
}

/**
 * obj_t_next_lbox : add a Q_LegendBox child at objs[parent]
 */
static void obj_t_next_lbox (int parent ,int gno)
{
  int i = obj_next ();
  obj_set (i ,&default_t_objs[Q_LegendBox]);
  objs[i].self      = i;
  objs[i].father_id = gno;
  nn_append_typed_child (Q_Axis ,parent ,i);
}
/**
 * obj_t_next_graph : add a Q_Graph with axis and legend box
 *                    called by realloc_graphs (used to create new graphs)
 */
void obj_t_next_graph (int gno)
{
  int i ,id = gno;
  i = obj_t_next (Q_Graph ,&id ,Q_Project ,-1);
  obj_t_next_axis (i ,X_AXIS);     /* create Q_Axis X_AXIS */
  obj_t_next_axis (i ,Y_AXIS);     /* create Q_Axis Y_AXIS */
  obj_t_next_axis (i ,ZX_AXIS);    /* create Q_Axis ZX_AXIS */
  obj_t_next_axis (i ,ZY_AXIS);    /* create Q_Axis ZY_AXIS */
  obj_t_next_lbox (i ,id);         /* create Q_LegendBox */
}

/**
 * Create a new orphaned compound with "first" as 1st element
 * Its father may be not yet created until a call to
 *  obj_links_update() insert it before "first"
 * but father_typ and father_id of "first" must be known
 * and will be used to 
 */
int obj_t_next_compound_orphaned (int *id ,int first)
{
  char buf[64];
  int father ,new ,*idn;
  if (obj_tid_is_valid (Q_Compound ,*id)) {
    snprintf (buf ,63 ,"Error: compound %d already exists\n" ,*id); 
    errmsg (buf);
    return -1;
  }
  father = nn_get_father (first);
  new    = obj_next ();
  obj_set (new ,&default_t_objs[Q_Compound]);
  objs[new].self       = new;
  idn = &next_t_objs_id[Q_Compound];
  if (*id < 0) *id = *idn;
  *idn = MAX2 (*idn+1 ,*id+1);
  objs[new].id         = *id;
  objs[new].father_typ = objs[first].father_typ;
  objs[new].father_id  = objs[first].father_id;
  objs[new].elder      = ORPHAN; 
  objs[new].loctyp     = objs[first].loctyp;
  nn_prune (first);
  nn_append_child (new ,first);
  max_t_objs[Q_Compound]++;
  set_dirtystate ();

  printf ("obj_t_next_compound_orphaned: new =%d id = %d father_typ = %d father_id = %d\n" 
	  ,new ,*id ,objs[new].father_typ ,objs[new].father_id);

  return new;
}


/**
 *  Create new compound with "first" as 1st element
 *  and "father" as father
 */
int obj_t_next_compound (int *id ,int father ,int first)
{
  int new ,father_id;
  Qtype father_typ;
  if (obj_is_active (father)) {
    father_typ = objs[father].typ;
    father_id  = objs[father].id;
    if (father_id < 0) {              /* if father is a template, append to project */
      father_typ = Q_Project;
      father = 0;
    }
    *id = -2;                                    /* create with a new id number */
    new = obj_t_next (Q_Compound ,id ,father_typ ,father_id); 
    nn_prune (new);
    nn_append_child (father ,new);
    objs[new].loctyp = objs[first].loctyp;
    nn_prune (first);
    nn_append_child (new ,first);
    set_dirtystate ();
  } else {
    new = obj_t_next_compound_orphaned (id ,first);
  }
  return new;
}

/**
 *  Create new compound with "first" as 1st element
 *  and ("father_typ" ,"father_id") as father
 */
int obj_tid_next_compound (int *id ,Qtype father_typ ,int father_id ,int first)
{
  int father ,new;
  new = obj_t_next (Q_Compound ,id ,father_typ ,father_id);
  objs[new].loctyp = objs[first].loctyp;
  nn_prune (first);
  nn_append_child (new ,first);
  nn_prune (new);
  if (father_id < 0) {  /* father is the project */
    nn_append_child (0 ,new);
    objs[new].father_typ = Q_Project;
    return new;
  } else {
    switch (father_typ) {
    case Q_Graph:
      father = obj_tid_get_graph_num (father_id);
      break;
    case Q_Compound:
      father = obj_geom_get_num (father_typ ,father_id);
      break;
    default:
      errmsg ("obj_tid_next_compound: internal error");
      return -1;
    }
    nn_append_child (father ,new);
    return new;
  }
}

/**
 *  obj_t_next :  creates a new element in objs , copy template
 *   insert into tree and returns the index into objs array
 *   if *id < -1, create with a new id number,
 *   else try to use *id to create the new object;
 *   if an object with same type and id already exist, returns an error
 *   else returns also the new id in 2nd arg, except for Q_Graph
 */
int obj_t_next (Qtype typ ,int *id ,Qtype father_typ ,int father_id)
{
  int start ,i;
  int oid = *id;
  /* Look if the element already exists */
  if (*id > -1) {
    i = obj_tid_get_num (typ ,*id ,father_typ ,father_id);
    if (i >= 0) {
      fprintf (stderr ,"obj_t_next: %s %d  parent: %s %d already exists num=%d\n"
	       ,Qtype_name[typ] ,*id ,Qtype_name[father_typ] ,father_id ,i);
      return i;
    }
  }
  /* If not, create it */
  i = obj_next ();
  if (i == 0) {
    ng_create_project ();
    if (typ == Q_Project) return -1;
    i = obj_next ();
  }
  /* Initialize new objs element */
  if (i > 0) {
    obj_set (i ,&default_t_objs[typ]);
    objs[i].self      = i;          /* needed because self=-1 after obj_set() */
    objs[i].active    = TRUE;
    if (*id < 0) *id = next_t_objs_id[typ];
    *id = MAX2 (*id ,oid);
    next_t_objs_id[typ] = *id + 1;
    objs[i].id        = *id;
    objs[i].father_id = father_id;
    switch (typ) {
    case Q_Line:     objs[i].pmask = MA_2_HANDLES ; break;
    case Q_Polyline: objs[i].pmask = MA_N_HANDLES ; break;
    case Q_Arc:      objs[i].pmask = MARC_Ellipse ; break;
    default:         objs[i].pmask = MA_NONE      ; break;
    }
    /* Manage tree links */
    switch (typ) {
    case Q_Graph:
      /* insert Q_Graph as brother of  Q_TimeStamp. Called by realloc_graphs. */
      nn_insert_typed_brother (Q_Graph ,iTS ,i);
      objs[i].id = oid;
      *id = oid;
      break;
    case Q_Line:
    case Q_Polyline:
    case Q_Arc:
    case Q_String:
    case Q_Box:
      /* globally numbered objs are attached to templates by default */
      start = default_t_objs[typ].self;
      nn_append_typed_child (typ ,start ,i);
      set_dirtystate ();
      break;
    case Q_Compound:
      nn_append_brother (iTS ,i);
      set_dirtystate ();
      break;
    default:
      errmsg ("obj_t_next internal error : illegal typ");
      return -1;
    }
    max_t_objs[typ]++;
  }
  return i;
}

/**
 *  obj_tid_kill : set objs[]  inactive and kill branch
 */
int obj_tid_kill (Qtype typ, int id ,Qtype father_typ ,int father_id)
{
  int i = obj_tid_get_num (typ ,id ,father_typ ,father_id);
  if (objs != NULL && i >= 0) {
    nn_kill_branch (i);
    set_dirtystate();
    return RETURN_SUCCESS;
  }
  return RETURN_FAILURE;
}
/**
 *  Set obj inactive
 */
int obj_geom_kill (Qtype typ, int id)
{
  int i = obj_geom_get_num (typ ,id);
  if (objs != NULL && i >= 0) {
    nn_kill_branch (i);
    set_dirtystate();
    return RETURN_SUCCESS;
  }
  return RETURN_FAILURE;
}

void obj_polyline_kill_point (int i ,int n)
{
  int j ,nxy;
  if (obj_is_active (i)) {
    nxy = objs[i].nxy;
    if (nxy > 2) {
      for (j = n; j < nxy-1; j++) {
	objs[i].x[j] = objs[i].x[j+1];
	objs[i].y[j] = objs[i].y[j+1];
      }
      objs[i].nxy--;
    }
  }
}
 
/**
 * Compute for objs[i] values to configure x/y spin boxes
 */
int obj_calc_spinbox_scaling (int i ,SpinScaling *sp)
{
  int graph_id;
  double x1 ,y1 ,x2 ,y2;
  double range_x ,range_y;
  world w;
  view v;
 
  if (obj_is_active (i)) {
    graph_id = obj_get_typed_ancestor_id (i ,Q_Graph);
    if (objs[i].loctyp ==  COORD_VIEW) {
      if (graph_id < 0) {  	/* attached to template */
	x1 = y1 = 0.0;
	get_page_viewport (&x2 ,&y2);
      } else {
       	if (get_graph_viewport (graph_id ,&v) == RETURN_FAILURE) {
       	  return RETURN_FAILURE;
       	}
	x1 = v.xv1;
	y1 = v.yv1;
	x2 = v.xv2;
	y2 = v.yv2;
	
      }
    } else {                /* COORD_WORLD */
      if (get_graph_world (graph_id ,&w) == RETURN_FAILURE) {
	return RETURN_FAILURE;
      }
      x1 = w.xg1;
      y1 = w.yg1;
      x2 = w.xg2;
      y2 = w.yg2;
    }
    range_x = fabs (x2 - x1);
    range_y = fabs (y2 - y1);
    sp->min_x  = x1 - range_x;
    sp->min_y  = y1 - range_y;
    sp->max_x  = x2 + range_x;
    sp->max_y  = y2 + range_y;
    sp->step_x = 0.01 * range_x;
    sp->step_y = 0.01 * range_y;
    return RETURN_SUCCESS;
  }
  return RETURN_FAILURE;
}

/*********** I N I T I A L I Z A T I O N    F U N C T I O N S   ***************/

/**
 *  Clear all handles and destroy array objs
 */
void objs_clear (void)
{
  int i;
  clear_all_handles ();
  for (i = 0; i < maxobjs; i++){
    /*   Q_Colorbar   managed else where */
    xfree (objs[i].x);
    xfree (objs[i].y);
    xfree (objs[i].s);
  }
  XCFREE (objs);
  for (i = 0; i < Q_Last;  i++) {
    max_t_objs[i]     = 0;
    next_t_objs_id[i] = 0;
  }
  maxobjs = 0;
}


void objs_t_clear (Qtype typ)
{
  int i;

  for (i = 1; i < maxobjs; i++) {
    if (objs[i].typ == typ) objs[i].active = FALSE;
  }
}


/**
 * objs_clear_graph
 */
void objs_clear_graph (int gno)
{
  int i = obj_tid_get_graph_num (gno);
  if (i >= 0) {
    nn_traverse_stop (FALSE);
    nn_traverse_child_first (objs[i].child ,NULL ,nn_kill_node);
    obj_t_next_axis (i ,X_AXIS);     /* recreate Q_Axis X_AXIS */
    obj_t_next_axis (i ,Y_AXIS);     /* recreate Q_Axis Y_AXIS */
    obj_t_next_axis (i ,ZX_AXIS);    /* recreate Q_Axis ZX_AXIS */
    obj_t_next_axis (i ,ZY_AXIS);    /* recreate Q_Axis ZY_AXIS */
    obj_t_next_lbox (i ,gno);        /* recreate Q_LegendBox */

  }
}


/************ M A N A G E  T E M P L A T E S and  D E F A U L T S  FUNCTIONS ***************/

/**
 *  Copy *obj into  default_t_objs[typ]
 */
void obj_set_template (QDobject *obj ,Qtype typ)
{
  memcpy (&default_t_objs[typ] ,obj ,sizeof (QDobject));
  set_default_arrow (typ ,&default_obj.arrow);
}

/**
 *  Copy default_t_objs[typ] into obj
 */
void obj_get_template (Qtype typ ,QDobject *obj)
{
  memcpy (obj ,&default_t_objs[typ] ,sizeof (QDobject));
  if (typ == Q_Line) {
    xfree (obj->frmt);
    obj->frmt = copy_string (NULL ,default_t_objs[typ].frmt);
  }
}

/**
 *   obj_default_init : initialize the templates
 */
void obj_default_init (void)
{
  /* default QDobject common to all objects */
  view d_v;
  default_obj.typ     	   = Q_Undef;
  default_obj.id     	   = -1;          /* templates have always id=-1 */
  default_obj.father_typ   = Q_Project;
  default_obj.father_id    = -1;
  default_obj.self         = -1;
  default_obj.elder        = 0;
  default_obj.brother      = -1;
  default_obj.child        = -1;
  default_obj.active  	   = FALSE;
  default_obj.hidden  	   = FALSE;
  default_obj.pmask        = MA_NONE;
  default_obj.layer   	   = LAYER_DEFAULT;
  default_obj.loctyp  	   = COORD_VIEW;
  default_obj.nxy     	   = 0;
  default_obj.x     	   = NULL;
  default_obj.y     	   = NULL;
  default_obj.handle_on    = FALSE;
  set_default_view (&d_v);
  default_obj.x1      	   = d_v.xv1;
  default_obj.y1      	   = d_v.yv1;
  default_obj.x2      	   = d_v.xv2;
  default_obj.y2      	   = d_v.yv2;
  default_obj.rot     	   = 0;
  default_obj.astart   	   = 0;
  default_obj.aend   	   = 360;
  default_obj.color   	   = grdefaults.color;
  default_obj.lines   	   = grdefaults.lines;
  default_obj.linew   	   = grdefaults.linew;
  default_obj.fillcolor    = grdefaults.color;
  default_obj.fillpattern  = 0;  /*  grdefaults.pattern; => black */
  default_obj.font         = grdefaults.font;
  default_obj.just         = JUST_LEFT | JUST_BLINE;
  default_obj.charsize     = grdefaults.charsize;
  default_obj.s            = NULL;
  default_obj.frmt          = NULL;
  default_obj.label_opt    = 0;
  default_obj.perp         = 0.0;
  default_obj.para         = 0.0;
  default_obj.arrow_end    = 0;
  default_obj.arrow.type   = 0;
  default_obj.arrow.length = 1.0;
  default_obj.arrow.dL_ff  = 1.0;
  default_obj.arrow.lL_ff  = 1.0;
  default_obj.xspline_s    = -0.5;
  default_obj.xspline_p    = 0.002;
  default_obj_uninitialized = FALSE;

  /* now we adapt QDobject templates for each type */
  Qtype typ;
  for (typ = 0; typ <Q_Last; typ++) {
    max_t_objs[typ]     = 0;
    next_t_objs_id[typ] = 0;
    obj_get_default (&default_t_objs[typ]);
    default_t_objs[typ].typ = typ;
    switch (typ) {
    case Q_Project:
      default_t_objs[typ].father_typ = Q_Undef;
      default_t_objs[typ].self       = 0;
      default_t_objs[typ].elder      = -1;
      default_t_objs[typ].active     = TRUE;
      default_t_objs[typ].layer      = 0;
      break;
    case Q_TimeStamp:
      default_t_objs[typ].father_typ  = Q_Project;
      default_t_objs[typ].id          = 0;
      default_t_objs[typ].elder       = 0;
      default_t_objs[typ].active      = TRUE;
      default_t_objs[typ].x1          = 0.03;
      default_t_objs[typ].y1          = 0.03;
      default_t_objs[typ].lines       = 0;
      default_t_objs[typ].fillpattern = 0;
      break;
    case Q_Graph:
      default_t_objs[typ].father_typ = Q_Project;
      break;
    case Q_Set:
    case Q_Axis:
    case Q_LegendBox:
      default_t_objs[typ].father_typ = Q_Graph;
      default_t_objs[typ].active     = TRUE;
      default_t_objs[typ].x1         = 0.0;
      default_t_objs[typ].y1         = 0.0;
      default_t_objs[typ].x2         = 1.0;
      default_t_objs[typ].y2         = 1.0;
      break;
    case Q_Line:
      default_t_objs[typ].just       = JUST_CENTER | JUST_BOTTOM;
      default_t_objs[typ].frmt 	     = copy_string (NULL ,"%g");
      break;
    case Q_Polyline:
      default_t_objs[typ].pmask      = MA_N_HANDLES;
      break;
    case Q_Colorbar:
      default_t_objs[typ].father_typ = Q_Set;
      default_t_objs[typ].active     = TRUE;
      default_t_objs[typ].just       = ZC_COLORBAR_RIGHT;
      break;
    case Q_String:
      default_t_objs[typ].lines       = 0;
      default_t_objs[typ].fillpattern = 0;
      break;
    case Q_Compound:
      default_t_objs[typ].father_typ = Q_Project;
      default_t_objs[typ].active     = TRUE;
      default_t_objs[typ].x1         = 0.0;
      default_t_objs[typ].y1         = 0.0;
      default_t_objs[typ].x2         = 1.0;
      default_t_objs[typ].y2         = 1.0;
      default_t_objs[typ].bb.xv1     = 0.0;
      default_t_objs[typ].bb.yv1     = 0.0;
      default_t_objs[typ].bb.xv2     = 0.0;
      default_t_objs[typ].bb.yv2     = 0.0;
      break;
    default:
      /* works only if parent is a template */
      default_t_objs[typ].father_typ = typ;
      break;
    }
  }
  /* Reset also globals defined in global.h */
  obj_rot   	 = default_obj.rot;
  obj_color 	 = default_obj.color;
  obj_lines 	 = default_obj.lines;
  obj_linew 	 = default_obj.linew;
  obj_fillpat 	 = default_obj.fillpattern;
  obj_fillcolor  = default_obj.fillcolor;
  obj_loctyp     = default_obj.loctyp;
}

void set_default_arrow (Qtype typ ,Arrow *arrowp)
{
  arrowp->type 	 = default_t_objs[typ].arrow.type;
  arrowp->length = default_t_objs[typ].arrow.length;
  arrowp->dL_ff  = default_t_objs[typ].arrow.dL_ff;
  arrowp->lL_ff  = default_t_objs[typ].arrow.lL_ff;
}


/*********** S E T,  C O P Y   and   M O V E    F U N C T I O N S   ***************/

/**
 *   Creates "*new" and adds at "father" as a child a copy of "model"
 */
static void obj_dup_append (int model ,int father ,int *new)
{
  char buf[64];
  if (model < 0) return;
  *new = obj_next ();
  if (*new >= 0) {
    Qtype typ = objs[model].typ;
    obj_set (*new ,&objs[model]);
    objs[*new].self    = *new;
    objs[*new].elder   = father;
    objs[*new].child   = -1;
    objs[*new].brother = -1;
    objs[*new].id      = next_t_objs_id[typ];
    next_t_objs_id[typ]++;
    max_t_objs[typ]++;
    objs[*new].handle_on = FALSE;
    sprintf (buf ," (copy of %s %d)" ,Qtype_name[typ] ,objs[model].id);
    objs[*new].s = copy_string 	  (NULL ,objs[model].s);
    objs[*new].s = concat_strings (objs[*new].s  ,buf);
    nn_append_typed_child (typ ,father ,*new);
  }
}

/**
 *  traverse objs tree to create and add copy of elements in compounds
 *  See also _ge_traverse()
 */
static void obj_dup_traverse (int from ,int to ,int *new)
{
  int next;
  if (from < 0) return;
  obj_dup_append (from ,to ,new);
  next = objs[from].child;
  if (next > 0) {
    to = *new;
    obj_dup_traverse (next ,to ,new);
    *new = objs[to].elder;
    to = *new;
  }
  next = objs[from].brother;
  if (next > 0) obj_dup_traverse (next ,to ,new);
}

/**
 *   Duplicate objs[i] and its descendants,
 *    insert as brother of objs[i] in the tree
 *    and returns num of the replica
 *   For a simple copy of QDobject, see obj_copy()
 */
int obj_duplicate (int i ,int *id)
{
  int new;
  char buf[64];
  Qtype typ = objs[i].typ;
  int j = obj_next ();
  if (j >= 0) {
    obj_set (j ,&objs[i]);
    objs[j].self     = j;
    objs[j].elder    = ORPHAN;
    objs[j].child    = -1;
    objs[j].brother  = -1;
    objs[j].id 	     = *id = next_t_objs_id[typ];
    next_t_objs_id[typ]++;
    max_t_objs[typ]++;
    objs[j].handle_on = FALSE;
    nn_append_typed_brother (i ,j);
    sprintf (buf ," (copy of %s %d)" ,Qtype_name[typ] ,objs[i].id);
    objs[j].s = copy_string    (NULL ,objs[i].s);
    objs[j].s = concat_strings (objs[j].s  ,buf);
    switch (typ) {
    case Q_Compound:
      /* Must also duplicate all the objects contained */
      obj_dup_traverse (objs[i].child ,j ,&new);
      break;
    case Q_Polyline:
      obj_set_xy (j ,objs[i].x ,objs[i].y ,objs[i].nxy);
      break;
    default:
      break;
    }
    set_dirtystate ();
    return j;
  } else {
    return (-1);
  }
}


void obj_set_xy12 (int i ,VPoint vp1 ,VPoint vp2)
{
  if (objs[i].loctyp == COORD_WORLD) {
    view2world (vp1.x ,vp1.y ,&objs[i].x1 ,&objs[i].y1);
    view2world (vp2.x ,vp2.y ,&objs[i].x2 ,&objs[i].y2);
  } else {
    objs[i].x1 = vp1.x;
    objs[i].y1 = vp1.y;
    objs[i].x2 = vp2.x;
    objs[i].y2 = vp2.y;
  }
  set_dirtystate ();
}

/**
 * for Q_Polyline
 */

/**
 * Copy arrays x[] and y[] in objs[i]
 */
void obj_set_xy (int i ,double *x ,double *y ,int nxy)
{
  int len;
  if (obj_is_active (i) == FALSE) return;
  if (nxy <= 0) {
    XCFREE (objs[i].x);
    XCFREE (objs[i].y);
    return;
  }
  len = nxy * SIZEOF_DOUBLE;
  if (objs[i].x == NULL) {
    objs[i].x = xmalloc (len);
    objs[i].y = xmalloc (len);
  } else {
    objs[i].x = xrealloc (objs[i].x ,len);
    objs[i].y = xrealloc (objs[i].y ,len);
  }
  memcpy (objs[i].x ,x ,len);
  memcpy (objs[i].y ,y ,len);
  objs[i].nxy = nxy;
  /* synchronize x1, x2 ,y1 and y2 */
  objs[i].x1 = objs[i].x[0];
  objs[i].y1 = objs[i].y[0];
  objs[i].x2 = objs[i].x[nxy-1];
  objs[i].y2 = objs[i].y[nxy-1];
}

int obj_change_loctyp (QDobject *po ,int newloc)
{
  int i;
  QDobject oa;
  if (po->loctyp == newloc) return RETURN_FAILURE;     /* nothing to be done */
  if (po->typ == Q_Compound) {
    errmsg ("Can't change loctyp of compounds: braak the compound first");
    return RETURN_FAILURE;
  }
  obj_copy (&oa ,po);                     /* undo/redo and follow_me_on */
  if (newloc == COORD_VIEW ) {
    world2view (po->x1 ,po->y1 ,&(po->x1) ,&(po->y1));
    world2view (po->x2 ,po->y2 ,&(po->x2) ,&(po->y2));
  } else {
    view2world (po->x1 ,po->y1 ,&(po->x1) ,&(po->y1));
    view2world (po->x2 ,po->y2 ,&(po->x2) ,&(po->y2));
  }
  if (po->typ == Q_Polyline) {
    for (i = 0; i < po->nxy; i++) {
      if (newloc == COORD_VIEW ) {
	world2view (po->x[i] ,po->y[i] ,&(po->x[i]) ,&(po->y[i]));
      } else {
	view2world (po->x[i] ,po->y[i] ,&(po->x[i]) ,&(po->y[i]));
      }
    }
  }
  po->loctyp = newloc;
  nn_cmd_obj (&oa ,po ,FALSE ,TRUE);    /* undo/redo and follow_me_on */
  return RETURN_SUCCESS;
}

/**
 *  Prune object and append to template node
 *  If in COORD_WORLD, it is converted in COORD_VIEW
 */
int obj_prune (int i ,int undo_on)
{
  int j ,itpl;
  VPoint vp;
  if (obj_is_geom_prunable (i) == FALSE)  return RETURN_FAILURE;
  QDobject oa;
  QDobject *po = &objs[i];
  if (undo_on) obj_copy (&oa ,po);
  obj_get_shift (i ,&vp);
  if (po->loctyp == COORD_WORLD) {
    world2view (po->x1 ,po->y1 ,&(po->x1) ,&(po->y1));
    world2view (po->x2 ,po->y2 ,&(po->x2) ,&(po->y2));
    if (po->typ == Q_Polyline) {
      for (j = 0; j < po->nxy; j++) {
	world2view (po->x[i] ,po->y[i] ,&(po->x[i]) ,&(po->y[i]));
      }
    }
  }
  nn_prune (i);
  if (cur_obj_typ == Q_Compound) {
    itpl = 0;                      /* append to project */
  } else {
    itpl = obj_t_get_template_num (cur_obj_typ);
  }
  nn_append_child (itpl ,i);
  po->loctyp = COORD_VIEW;
  obj_apply_shift (po ,vp ,FALSE);
  if (undo_on) nn_cmd_obj (&oa ,po ,FALSE ,TRUE);
  return RETURN_SUCCESS;
}

/**
 *  Append obj i as child of dest
 *  If needed, in an intermdiate step,
 *       first prune to template and convert loctyp to view
 */
int obj_append_child (int i ,int dest)
{
  int j ,gno ,newloctyp;
  VPoint vp;
  QDobject *po = &objs[i];
  if (po->typ == Q_Compound && po->loctyp != objs[dest].loctyp) {
    /* This needs to change loctyp in all the branch */
    errmsg ("obj_append_child: Can't move a compound into a dest. with a different loctyp");
    return RETURN_FAILURE;
  }
  /* Move obj into a template node if necessary */
  if (po->father_id != -1) {
    if (obj_prune (i ,FALSE) == RETURN_FAILURE) return RETURN_FAILURE;
  }
  /* if obj to move is into a compound, extract it out */
  obj_get_shift   (i ,&vp);
  obj_apply_shift (po ,vp ,TRUE);
  /* and determine newloctyp */
  switch (objs[dest].typ) {
  case Q_Graph:
    gno       = objs[dest].id;
    newloctyp = po->loctyp;
    break;
  case Q_Compound:
    gno       = obj_get_typed_ancestor_id (dest ,Q_Graph);
    newloctyp = objs[dest].loctyp;
    break;
  case Q_Line:
  case Q_Polyline:
  case Q_Arc:
  case Q_String:
  case Q_Box:
  case Q_Project:
    po->loctyp = COORD_VIEW;
    return RETURN_SUCCESS;
  default:
    return RETURN_FAILURE;
  }
  /* convert to coord. world if needed */
  if (newloctyp == COORD_WORLD) {
    select_graph (gno);
    view2world (po->x1 ,po->y1 ,&(po->x1) ,&(po->y1));
    view2world (po->x2 ,po->y2 ,&(po->x2) ,&(po->y2));
    if (po->typ == Q_Polyline) {
      for (j = 0; j < po->nxy; j++) {
	view2world (po->x[i] ,po->y[i] ,&(po->x[i]) ,&(po->y[i]));
      }
    }
  }
  po->loctyp = newloctyp;
  /* move links */
  nn_prune (i);
  nn_append_child (dest ,i);
  /* if i is into compound(s), substract shift */
  obj_get_shift (i ,&vp);
  obj_apply_shift (po ,vp ,TRUE);
  return RETURN_SUCCESS;
}


/**
 * Set objs[i].(x,y)[j] for both loctyp(es)
 *  Compare to obj_polyline_get_vp()
 */
int obj_polyline_set_vp (int i ,int j ,VPoint vp)
{
  double x ,y;
  if (obj_is_active (i) == FALSE) return FALSE;
  if (objs[i].typ != Q_Polyline)  return FALSE;
  if (objs[i].x   == NULL)        return FALSE;
  if (j < 0 || j >= objs[i].nxy)  return FALSE;
  if (objs[i].loctyp == COORD_WORLD) {
    view2world (vp.x ,vp.y ,&x ,&y);
    objs[i].x[j] = x;
    objs[i].y[j] = y;
  } else {
    objs[i].x[j] = vp.x;
    objs[i].y[j] = vp.y;
  }
  return TRUE;
}


/**
 * Add (x,y) to objs[i].(x,y)
 *  x1,y1,x2 and y2 must but managed apart
 */
void obj_polyline_shift (int i ,double x ,double y)
{
  int j;
  if (obj_is_active (i) == FALSE) return;
  for (j = 0; j < objs[i].nxy; j++) {
    objs[i].x[j] += x;
    objs[i].y[j] += y;
  }
}

/**
 * Move obj on the canvas
 */
void obj_move (int i, VVector shift)
{
  QDobject *po;
  double xs ,ys;
  WPoint wpshift;
  if (obj_is_active (i) == FALSE) return;
  po = &objs[i];
  if (po->loctyp == COORD_VIEW) {
    xs = shift.x;
    ys = shift.y;
  } else {
    wpshift = Vshift2Wshift (shift);
    xs = wpshift.x;
    ys = wpshift.y;
  }
  po->x1 += xs;
  po->y1 += ys;
  if (po->typ != Q_Compound) {
    po->x2 += xs;
    po->y2 += ys;
    if (po->typ == Q_Polyline) {
      obj_polyline_shift (i ,xs ,ys);
    }
  }
  set_dirtystate ();
}

void obj_scale (int i ,VPoint anchor_vp ,VPoint vp ,int n_handle_found)
{
  VPoint compound_shift ,vp1 ,vp2;
  WPoint wp1 ,wp2;
  /* For objs in a compounds, x1 and x2 are shifted by the compound ancestor(s) */
  obj_get_shift (i ,&compound_shift);
  anchor_vp.x -= compound_shift.x;
  anchor_vp.y -= compound_shift.y;
  vp.x 	      -= compound_shift.x;
  vp.y 	      -= compound_shift.y;
  if (objs[i].pmask & MA_2_HANDLES) {
    if (n_handle_found == 1) {
      vp1 = vp;
      vp2 = anchor_vp;
    } else {
      vp1 = anchor_vp;
      vp2 = vp;
    }
  } else {                    /* 4 handles */
    vp1 = anchor_vp;
    vp2 = vp;
  }
  if (objs[i].loctyp == COORD_WORLD) {
    int gno = objs[i].father_id;
    if (select_graph (gno) == RETURN_SUCCESS) {
      wp1 = Vpoint2Wpoint (vp1);
      wp2 = Vpoint2Wpoint (vp2);
      objs[i].x1 = wp1.x;
      objs[i].y1 = wp1.y;
      objs[i].x2 = wp2.x;
      objs[i].y2 = wp2.y;
    }
  } else {
    obj_set_xy12 (i ,vp1 ,vp2);
  }
}


/**
 *  Copy  objs[i] into *obj
 */
void obj_get (int i ,QDobject *obj)
{
  obj_copy (obj ,&objs[i]);
}

/**
 *  Copy *obj into objs[i]
 */
void obj_set (int i ,QDobject *obj)
{
  obj_copy (&objs[i] ,obj);
}

void obj_copy (QDobject *dest ,QDobject *src)
{
  if (src == dest) {
    ;
  } else if (src == NULL) {
    xfree (dest);
    dest = NULL;
  } else {
    memcpy (dest ,src ,sizeof (QDobject));
    dest->s = copy_string (NULL, src->s);
    memcpy (&(dest->arrow) ,&(src->arrow) ,sizeof (Arrow));
    if (src->typ == Q_Line) {
      dest->frmt = copy_string (NULL, src->frmt);
    } else if (src->typ == Q_Polyline && src->nxy > 0) {
      size_t len = src->nxy * sizeof (VPoint);
      dest->nxy  = src->nxy;
      dest->x 	 = malloc (len);
      dest->y 	 = malloc (len);
      memcpy (dest->x ,src->x ,len);
      memcpy (dest->y ,src->y ,len);
    }
  }
}

/**
 *  Add point at the end of a polyline
 *  If loctyp=COORD_WORLD, use the current graph
 */
void obj_polyline_add_vpoint (QDobject *po ,VPoint vp)
{
  double xtmp ,ytmp;
  int n = po->nxy;
  if (n < 0) n = 0;
  po->x = xrealloc (po->x ,(n+1) * sizeof (VPoint));
  po->y = xrealloc (po->y ,(n+1) * sizeof (VPoint));
  if (po->loctyp == COORD_WORLD) {
    view2world (vp.x ,vp.y ,&xtmp ,&ytmp);
    po->x2 = po->x[n] = xtmp;
    po->y2 = po->y[n] = ytmp;
  } else {                 /* COORD_VIEW */
    po->x2 = po->x[n] = vp.x;
    po->y2 = po->y[n] = vp.y;
  }
  (po->nxy)++;
}

/**
 *  Insert "vp" into polyline "po" at position "n"
 */
int obj_polyline_insert_vpoint (QDobject *po ,VPoint vp ,int n)
{
  VPoint vpshift;
  int j;
  int nxy = po->nxy;
  if (n < 0 || n > nxy) {
    errmsg ("obj_polyline_insert_vpoint point nb out of range");
    return RETURN_FAILURE;
  }
  obj_get_shift (po->self ,&vpshift);
  vp.x -= vpshift.x;
  vp.y -= vpshift.y;
  if (n == nxy) {
    obj_polyline_add_vpoint (po ,vp);
  } else {
    po->x = xrealloc (po->x ,(nxy+1) * sizeof (VPoint));
    po->y = xrealloc (po->y ,(nxy+1) * sizeof (VPoint));
    for (j = nxy; j > n; j--) {
      po->x[j] = po->x[j-1];
      po->y[j] = po->y[j-1];
    }
    obj_polyline_set_vp (po->self ,n ,vp);
    (po->nxy)++;
  }
  return RETURN_SUCCESS;
}

/* **************** C O M P O U N D S **************** */

static int obj_t_can_be_glued (Qtype typ)
{
  return (typ == Q_Compound  ||
	  typ == Q_Line      ||
	  typ == Q_Polyline  ||
	  typ == Q_Arc       ||
	  typ == Q_String    ||
	  typ == Q_Box
	  );
}

/**
 * Agglomerate objects in clist into a new compound.
 * The first element in clist is the num of the father
 * to create the new compound into
 * On return, always free the list, nullify the pointer
 * and make the compound current
 */
int obj_glue (GList **clist)
{
  GList *list;
  int   i ,ic ,id ,father ,loctyp;
  if (*clist == NULL) return RETURN_FAILURE;  /* list is empty */
  list 	 = *clist = g_list_first (*clist);
  father = GPOINTER_TO_INT (list->data);
  list 	 = g_list_next (list);
  i 	 = GPOINTER_TO_INT (list->data);      /* the 1st elem. into the compound */
  /* check if first elem in clist (i.e. the father) is valid */
  if (objs[father].typ != Q_Graph     &&
      objs[father].typ != Q_Compound  &&
      objs[father].typ != Q_Project   &&
      objs[father].typ != objs[i].father_typ) {   /* a template */
    errmsg ("obj_glue: the father of the first object is not compatible with gluing a compound");
    goto erreur;
  }
  /* check the others elements */
  loctyp = objs[i].loctyp;
  while (list != NULL) {
    i = GPOINTER_TO_INT (list->data);       /* next elem. */
    if (objs[i].loctyp != loctyp) {
      errmsg ("obj_glue: selected objects have different loctyp");
      goto erreur;
    }
    if (obj_t_can_be_glued (objs[i].typ) == FALSE ) {
      errmsg ("obj_glue: a selected object can't be glued into a compound");
      goto erreur;
    }
    list = g_list_next (list);
  }
  /* finally build the compound */
  list   = g_list_first (*clist); 
  list   = g_list_next  (list);
  i 	 = GPOINTER_TO_INT (list->data);
  ic 	 = obj_t_next_compound (&id ,father ,i);
  obj_make_current (ic);
  obj_set_bbc (ic);
  while (list != NULL) {
    i = GPOINTER_TO_INT (list->data);
    nn_prune (i);
    nn_append_child (ic ,i);
    obj_update_bbc (i ,-1);
    list = g_list_next (list);
  }
  objs[ic].bb = obj_get_bbc ();
  /* and make it current */
  obj_make_current   (ic);  
  g_list_free (*clist);
  *clist = NULL;
  return RETURN_SUCCESS;
 erreur:
  g_list_free (*clist);
  *clist = NULL;
  return RETURN_FAILURE;
}


/**
 * Add immediatly objs[i] at compound with id "id_compound"
 */
void obj_glue_add (int id_compound ,int i)
{
  char buf[256];
  int ic = obj_geom_get_num (Q_Compound ,id_compound);
  if (ic > 0) {
    if (objs[ic].loctyp != objs[i].loctyp) {
      sprintf (buf ,"obj_glue_add: %s %d has different loctyp than compound %d"
	       ,Qtype_name[objs[i].typ] ,objs[i].id ,id_compound);
      errmsg (buf);
      return;
    }
    if (objs[i].elder != ORPHAN) nn_prune (i);
    nn_append_child  (ic ,i);
    obj_make_current (ic);
  }
}


/**
 * Breaks compound and returns num. of first element in compound
 */
int obj_break_compound (int i)
{
  int e ,b ,c ;                        /* compound links */
  int lc ;
  int bnew ,father_id;
  Qtype father_typ;
  double x1 ,y1;
  /* Manage links */
  e = objs[i].elder;
  b = objs[i].brother;
  c = objs[i].child;
  if (c < 0) return -1;             /* empty compound : do nothing */
  /* set c as child/brother of elder */
  if (i == objs[e].child) {
    objs[e].child = c;
  } else if (i == objs[e].brother) {
    objs[e].brother = c;
  } else {
    errmsg("obj_break_compound Internal error");
    return -1;
  }
  objs[c].elder = e;
  /* Manage father_typ, father_id */
  father_typ = objs[i].father_typ;
  father_id  = objs[i].father_id;
  /* Incorporate the shift of the compound in its children */
  x1   = objs[i].x1;
  y1   = objs[i].y1;
  bnew = c;
  while (bnew >= 0) {
    objs[bnew].father_typ = father_typ;
    objs[bnew].father_id  = father_id;
    objs[bnew].x1         += x1;
    objs[bnew].y1         += y1;
    if (objs[bnew].typ != Q_Compound) {
      objs[bnew].x2         += x1;
      objs[bnew].y2         += y1;
    }
    if (objs[bnew].typ == Q_Polyline) obj_polyline_shift (bnew ,x1 ,y1);
    bnew = objs[bnew].brother;
  }
  /* relink b */
  lc = nn_get_last_brother (c);
  if (lc > 0) {
    objs[lc].brother = b;
    if (b > 0) objs[b].elder = lc;
  }
  /* reset objs[i] to empty */
  objs[i].typ      = Q_Undef;
  objs[i].elder    = -1;
  objs[i].brother  = -1;
  objs[i].child    = -1;
  objs[i].active   =  0;
  objs[i].id       = -1;
  set_dirtystate ();
  return c;
}

/**
 *  gno=-1 means attach to template
 */
int obj_geom_attach_to_graph (Qtype typ ,int id ,int gno ,int delayed)  //10 A TERMINER
{
  int igno ,i;
  i = obj_geom_get_num (typ ,id);
  /* nothing to do ? */
  if (objs[i].father_typ == Q_Graph && objs[i].father_id == gno) return RETURN_FAILURE;
  /* no, make the change */
  if (gno == -1) {
    igno = obj_t_get_template_num (typ);
    nn_append_child (igno ,i);
    objs[i].father_typ = typ;
    objs[i].father_id  = -1;
  } else if (is_valid_gno (gno)) {
    if (objs[i].elder >= 0) nn_prune (i);
    igno = obj_tid_get_graph_num (gno);
    nn_append_typed_child (Q_LegendBox ,igno ,i);
    /* override nn_append_typed_child default              (is it necessary? )*/
    objs[i].father_typ = Q_Graph;
    objs[i].father_id  = gno;
  } else if (delayed) {
    /* gno is perhaps not yet created, thus we wait for
     *  running obj_links_update(); */
    objs[i].elder = -2;  /* marked to update link */
    objs[i].brother = -1;
    objs[i].father_typ = Q_Graph;
    objs[i].father_id  = gno;
  } else {
    errmsg("obj_geom_attach_to_graph: graph not valid");
    return RETURN_FAILURE;
  }
  return RETURN_SUCCESS;
}


/*********** B O U N D I N G   B O X E S   M A N A G E M E N T ************/
/**
 * Bounding boxes are computed only when the objects are plotted
 * thus, the bb of compounds can be updated only at the end of the plot:
 * this is done by obj_update_compounds_bb().
 */

static view bbc;  /* current bounding box to update */

view obj_get_bbc (void)
{
  return bbc;
}
void obj_set_bbc (int i)
{
  bbc = objs[i].bb;
}


void obj_update_bbc (int i ,int depth)
{
  if (i < 0) return;
  view *bb = &(objs[i].bb);
  if (is_valid_bbox (bbc)){
    bbc.xv1 = MIN2 (bb->xv1 ,bbc.xv1);
    bbc.yv1 = MIN2 (bb->yv1 ,bbc.yv1);
    bbc.xv2 = MAX2 (bb->xv2 ,bbc.xv2);
    bbc.yv2 = MAX2 (bb->yv2 ,bbc.yv2);
  } else {
    bbc = *bb;
  }
}

/**
 *  bb of compounds are correctly updated only when all the bb of the
 *  objects contained are previously updated
 */
void obj_update_compounds_bb (void)
{
  int i;
  for (i = 0; i < maxobjs; i++) {
    if (objs[i].typ == Q_Compound) {
      bbc = default_t_objs[Q_Compound].bb;
      nn_traverse_child_first (objs[i].child ,obj_update_bbc ,NULL);
      objs[i].bb = bbc;
    }
  }
}

/************ M A N A G E    obj_tid ***************/

/**
 *
 */
void obj_tid_make_current (Qtype typ, int id ,Qtype father_typ ,int father_id)
{
  cur_obj_typ 	 = typ;
  cur_obj_id  	 = id;
  cur_parent_typ = father_typ;
  cur_parent_id  = father_id;
  cur_obj_num    = obj_tid_get_num (typ ,id ,father_typ ,father_id);
}

/**
 *
 */
void obj_geom_make_current (Qtype typ, int id)
{
  cur_obj_typ = typ;
  cur_obj_id  = id;
  cur_obj_num = obj_geom_get_num (typ ,id);
  cur_parent_typ = objs[cur_obj_num].father_typ;
  cur_parent_id  = objs[cur_obj_num].father_id;
}

void obj_make_current (int i)
{
  if (i >= 0 && i < maxobjs) {
    cur_obj_typ    = objs[i].typ;
    cur_obj_id     = objs[i].id;
    cur_parent_typ = objs[i].father_typ;
    cur_parent_id  = objs[i].father_id;
    cur_obj_num    = i;
  }
}

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



/**
 *  obj_links_update updates orphaned objs created before their parent
 *  i.e. with elder==ORPHAN
 */
void obj_links_update (void)
{
  int i ,father;
  for (i = iTS + 1; i < maxobjs; i++) {
    if (obj_is_active (i) && objs[i].elder == ORPHAN) {
      if (objs[i].father_typ == Q_Graph) {
	father = obj_tid_get_graph_num (objs[i].father_id);
      } else {

	father = obj_tid_get_num (Q_Compound ,objs[i].id ,objs[i].father_typ ,objs[i].father_id);

      }
      printf ("obj_links_update : i = %d father num = %d typ = %d id = %d \n" ,i ,father ,objs[i].father_typ ,objs[i].father_id);
      nn_append_child (father ,i);
    }
  }
}


/* noxprotos.h */
void set_plotstr_string (QDobject *pstr, const char *buf)
{
  pstr->s = copy_string (pstr->s, buf);
}



/********************* L A Y E R S **********************/



static int layers[LAYER_MAX];

static int o_layer_min;
static int o_layer_max;

void layers_init (void)
{
  int i;
  for (i = 0; i < LAYER_MAX; i++) layers[i] = TRUE;
}

void layer_set (int layer ,int onoff)
{
  if (layer > 0 && layer < LAYER_MAX) layers[layer] = onoff;
}

int layer_is_active (int layer)
{
  return layers[layer];
}

int obj_tid_is_visible_on_layer (Qtype typ ,int id ,int father_typ ,int father_id ,int layer)
{
  int l;
  int num = obj_tid_get_num (typ ,id ,father_typ ,father_id);
  if (num < 0) return FALSE;
  l = objs[num].layer;
  if (l != layer) return FALSE;
  return layers[l];
}

int layer_not_used (int n)
{
  int i;
  int frame_layer ,title_layer ,stitle_layer;
  for (i = 0; i < maxobjs; i++) {
    if (objs[i].layer == n &&
	objs[i].typ   != Q_Graph &&
	objs[i].typ   != Q_Compound)
      return FALSE;
  }
  /* Frame ,title and subtitle of graphs are not in objs */
  for (i = 0; i < number_of_graphs (); i++) {
    if (get_graph_layers (i ,&frame_layer ,&title_layer ,&stitle_layer) == RETURN_SUCCESS) {
      if (n == frame_layer || n == title_layer || n == stitle_layer) return FALSE;
    }
  }
  return TRUE;
}

int layer_this_set_is_not_visible (int layer ,int gno ,int setno)
{
  int l;
  int num = obj_tid_get_set_num (gno ,setno);
  if (num < 0) return TRUE;
  l = objs[num].layer;
  if (l != layer) return TRUE;
  return !layers[l];
}


static void _obj_get_layer_minmax (int i ,int depth)
{
  if (i < 0) return;
  o_layer_min = MIN2 (objs[i].layer ,o_layer_min);
  o_layer_max = MAX2 (objs[i].layer ,o_layer_max);
}

int obj_get_child_layers_minmax (int start ,int *layer_min ,int *layer_max)
{
  if (start < 0) return RETURN_FAILURE;
  o_layer_min = LAYER_MAX;
  o_layer_max = -1;
  nn_traverse_child_first (start ,_obj_get_layer_minmax ,NULL);
  *layer_min = o_layer_min;
  *layer_max = o_layer_max;
  return RETURN_SUCCESS;
}



static void _obj_collapse_layers (int i ,int depth)
{
  if (i < 0) return;
  nn_cmd_layer_collapsed (i ,o_layer_min);
  objs[i].layer = o_layer_min;
}

void obj_collapse_layers (int start ,int layer)
{
  if (start < 0) return;
  o_layer_min = layer;
  nn_traverse_child_first (start ,_obj_collapse_layers ,NULL);
}

void set_legend_layer (int gno ,int layer)
{
  if (is_valid_gno (gno)) {
    int i = obj_tid_get_lbox_num (gno);
    if (i > 0) objs[i].layer = layer;
  }
}

int get_legend_layer (int gno)
{
  if (is_valid_gno (gno)) {
    int i = obj_tid_get_lbox_num (gno);
    if (i > 0) {
      return objs[i].layer;
    }
  }
  return LAYER_DEFAULT;
}

/************ C O M P A T I B I L I T Y   F U N C T I O N S  ***************/

/**
 * For XYVMAP vmap vectormap grace-5 uses errorbar
 * in a confusing manner: replaced by objs[] in GraceGTK
 */
void errbar_to_obj (int gno ,int setno ,Errbar *errbar ,int file_project_version)
{
  if (file_project_version > 0 && file_project_version < 70000) {
    int i = obj_tid_get_set_num (gno ,setno);
    if (i >= 0) {
      objs[i].color  	 = errbar->pen.color;
      objs[i].linew 	 = errbar->linew;
      objs[i].lines 	 = errbar->lines;
      objs[i].arrow_end  = 1;
      objs[i].arrow.type = 0;
    }
  }
}


/************ O U T P U T  ***************/

/**
 * Write out polyline data
 *    Compare to write_set()
 */
int obj_polyline_write (int id , FILE *cp)
{
  int i ,j;
  if (cp == NULL) return RETURN_FAILURE;
  i = obj_geom_get_num (Q_Polyline ,id);
  if (i >= 0) {
    /* Open/close contour */
    if (objs[i].pmask & MA_IS_CLOSED) {
      fprintf (cp ,"@ Polyline %d Close\n" ,id);
    } else {
      fprintf (cp ,"@ Polyline %d Open\n" ,id);
    }
    /* Spline interpolation */
    if (objs[i].pmask & MA_SMOOTH) {
      fprintf (cp ,"@ Polyline %d Spline ( %f , %f )\n" ,id ,objs[i].xspline_s ,objs[i].xspline_p);
    } else {
      fprintf (cp ,"@ Polyline %d Spline None\n" ,id);
    }
    /* Points data */
    fprintf (cp ,"@ Target Polyline %d\n" ,id);
    for (j = 0; j < objs[i].nxy; j++) {
      fprintf (cp ,"%f \t%f\n" ,objs[i].x[j] ,objs[i].y[j]);
    }
    fprintf (cp ,"&\n");
    return RETURN_SUCCESS;
  }
  return RETURN_FAILURE;
}


/**
 * Store points coordinates for the polyline cur_obj_num
 *  Does not care if "ssd[]" are VPoint or WPoint
 *  Compare to store_data()
 */
int obj_polyline_store (ss_data *ssd ,char *label)
{
  int i ,ncols ,nrows;

  double *x ,*y;
  if (ssd == NULL) return RETURN_FAILURE;
  ncols = ssd->ncols;
  nrows = ssd->nrows;
  if (ncols != 2) return RETURN_FAILURE;
  if (nrows <  2) return RETURN_FAILURE;
  if (ssd->formats[0] != FFORMAT_NUMBER ||
      ssd->formats[1] != FFORMAT_NUMBER ) {
    errmsg ("obj_polyline_store : Format error\n");
    return RETURN_FAILURE;
  }
  i = cur_obj_num;
  if (obj_is_active (i) && objs[i].typ == Q_Polyline) {
    x = (double *) ssd->data[0];
    y = (double *) ssd->data[1];
    obj_set_xy (i ,x ,y ,nrows);
    free_ss_data (ssd);
    return RETURN_SUCCESS;
  }
  return RETURN_FAILURE;
}


/************ U T I L I  T I E S  ***************/

void obj_errmsg (int i ,char *s)
{
  char buf[128];
  Qtype typ;
  if (obj_is_active (i)) {
    typ = objs[i].typ;
    snprintf (buf ,128 ," %s %d : %s" ,Qtype_name[typ] ,objs[i].id ,s);
  } else {
    snprintf (buf ,128 ," obj num %d inactive : %s" ,i ,s);
  }
  errmsg (buf);
}
