/**
 *  File ge_obj.c
 *
 *  Manage menus for Q_Line, Q_Arc, Q_Box, Q_String, Q_TimeStamp
 *               and Q_Compound
 *  For ellipses, x1, x2, y1 and y2 are the box coord in objs,
 *                the meaning is different only in the popup
 *
 *   Copyright (c) 2010 P. Vincent.    See GNU GPL ../LICENCE
 *  Replace  Grace strwin.c
 *   Home page: http://plasma-gate.weizmann.ac.il/Grace/
 *   Copyright (c) 1991-1995 Paul J Turner, Portland, OR
 *   Copyright (c) 1996-2003 Grace Development Team
 */

#define _GE_OBJ_

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

#include "globals.h"

#include "defines.h"
#include "utils.h"
#include "noxprotos.h"
#include "t1fonts.h"
#include "graphs.h"

#include "gw_pannel.h"
#include "gw_choice.h"
#include "gw_list.h"

#include "gg_gutil.h"
#include "gg_gtkinc.h"
#include "gg_protos.h"
#include "gg_events.h"

#include "ng_objects.h"
#include "wu.h"
#include "nn_tree.h"
#include "gg_icons_xfig.h"

#include "ge_tree.h"
#include "ge_protos.h"

#define TEMPLATE 0
#define CURRENT  1

/* From ge.c */
extern Ge_tree *get_getree (void);

extern void gg_cmd_prt (char *buf);  // provisoire

extern GdkColor  bleuciel ,wheat;


/* Follow me variables */
static QDobject oa;

/************************************************************/
static int block_instant_update = FALSE;

/************************************************************/
/*  Widgets for QDobject struct elements (see defines.h 482)  */
/************************************************************/

typedef struct {
  GtkWidget *titre_item ,*subt_item;
  GtkWidget *vb;
  GtkWidget *w_gno     ,*w_hidden   ,*w_loctyp;
  GtkWidget *w_layer;
  spin2Struct xy1 ,xy2;
  GtkWidget *w_rot;
  spin2Struct angles;
  GtkWidget *w_color;
  GtkWidget *w_lines;
  GtkWidget *w_linew;
  GtkWidget *w_fillcolor;
  GtkWidget *w_fillpattern;
  GtkWidget *w_font;
  GtkWidget *w_just;
  GtkWidget *w_charsize;
  GtkWidget *w_arrow_end;
  GtkWidget *w_arrow_length   ,*w_arrow_type;
  GtkWidget *w_arrow_dL_ff    ,*w_arrow_lL_ff;
  GtkWidget *arc_frame;
  GtkWidget *arrow_frame;
  GtkWidget *geom_frame;
  GtkWidget *w_label_opt;
  spin2Struct offset;
  GtkWidget *w_fmt;
  GtkWidget *w_s;     	        /* added to give string in template */
  GtkWidget *w_layers_collapse; /* added to homogeneize compound layers */
  GtkWidget *w_closed;          /* for polylines */
  GtkWidget *w_smooth;          /* for polylines */
  GtkWidget *xspline_s;         /* for polylines */
  GtkWidget *xspline_p;         /* for polylines */
  int menu_finalized;
} ge_obj_struct;

/* For timestamp only */
static GtkWidget *hint_item ,*date_item ,*two_digits_years_item ,*wrap_year_item;

/* We use two realizations for  Qtype contextual menus
 *  (except for timestamp which is unique)
 *  Into each function, the variable n allows to choose the realization:
 *  n=0: template and n=1: cur_obj_typ
 */
static ge_obj_struct ui[2][Q_Last_objs];
#define U ui[n][typ]

enum WobjEnum {
  W_ALL          /* 0:  all types  */
  ,NOSTAMP       /* 1:  all types except timestamp */
  ,XY2	 	 /* 2:  x2 & y2_item */
  ,STRING	 /* 3:  String: font ,just ,w_charsize  */
  ,ROT 	 	 /* 4:  rot.w */
  ,LINE	 	 /* 5:  Line width & lines.w  */
  ,FILL	 	 /* 6:  fill_color & pattern_item  */
  ,ARROW	 /* 7:  w_arrow_end(s)  */
  ,STAMP	 /* 8:  timestamp  */
  ,ARC   	 /* 9:  Q_Arc */
  ,COLOR         /* 10:  */
  ,CLOSED        /* 11: for polylines */
  ,LAST_W_ROW
};

/* A grid to select widgets with respect to typ
 *   Columns must be synchronized with Qtype enum in defines.h
 *   Rows       "      "      "     "  WobjEnum above
 *   Q_Colorbar is special and does not use wg
 */
static int wg[LAST_W_ROW][8] ={
  /*  0    1    2    3     4    5    6  7
   *                	                T
   *  C         P   	             C  i
   *  o         o   	             o  m
   *  m         l   	   S         l  e
   *  p         y   	   t         o  S
   *  o    L    l   	   r         r  t
   *  u    i    i     A    i    B    b  a
   *  n    n    n     r    n    o    a  m
   *  d    e    e     c    g    x    r  p      W     */
  {   1  , 1  , 1  ,  1  , 1  , 1  , 0, 1 } /* 0 = W_ALL   :  all types  */
  ,{  1  , 1  , 1  ,  1  , 1  , 1  , 0, 0 } /* 1 = NOSTAMP :  all types except timestamp */
  ,{  0  , 1  , 1  ,  1  , 0  , 1  , 0, 0 } /* 2 = XY2     :  x2 & y2_item */
  ,{  0  , 0  , 0  ,  0  , 1  , 0  , 0, 1 } /* 3 = STRING  :  String: font ,just ,w_charsize  */
  ,{  0  , 0  , 0  ,  0  , 1  , 0  , 0, 1 } /* 4 = ROT     :  rot.w */
  ,{  0  , 1  , 1  ,  1  , 1  , 1  , 0, 1 } /* 5 = LINE    :  Line width & lines.w  */
  ,{  0  , 0  , 0  ,  1  , 1  , 1  , 0, 1 } /* 6 = FILL    :  fill_color & pattern_item  */
  ,{  0  , 1  , 1  ,  1  , 0  , 0  , 0, 0 } /* 7 = ARROW   :  w_arrow_end(s)  */
  ,{  0  , 0  , 0  ,  0  , 0  , 0  , 0, 1 } /* 8 = STAMP   :  timestamp  */
  ,{  0  , 0  , 0  ,  1  , 0  , 0  , 0, 0 } /* 9 = ARC     :  Q_Arc */
  ,{  0  , 1  , 1  ,  1  , 1  , 1  , 0, 1 } /* 10 = COLOR */
  ,{  0  , 0  , 1  ,  0  , 0  , 0  , 0, 0 } /* 11 = CLOSED : is polyline closed ? */
};
#define W(w)  if (wg[(w)][typ])

/* MUST accord with Qtype struct in defines.h */
static char *frame_pos_string[Q_Last_objs] = {"Compound shift"
					      ,"Line ends"
					      ,"Polyline points"
					      ,"Arc geometry"
					      ,"String anchor point"
					      ,"Box corners"
					      ,"Timestamp anchor"
};

/******* Callbacks *******/
static void ge_obj_change_loctyp_CB   (GtkWidget *w ,gpointer p);
static void ge_obj_change_gno_CB      (GtkWidget *w ,gpointer p);
static void ge_obj_subtyp_CB          (GtkWidget *w ,gpointer p);
static void ge_obj_collapse_layers_CB (GtkWidget *w ,gpointer p);

static gulong idgnoCB[2][Q_Last_objs];

static int rpane_obj_num = -2; /* The index of the current rpane objs[] */
static int rpane_n   = TEMPLATE;

void ge_obj_reset_rpane_obj_num (void)
{
  rpane_obj_num = -2;
}


/******** For Q_Polyline only ********/
typedef struct {
  GtkWidget *box;
  GtkWidget *x ,*y;
} W_Pline;

static gint poly_xy_height = 150;
static GtkWidget *w_poly_scroll ,*w_poly_box;
static W_Pline *w_pline;
static int cur_max_xy_lines = 0;


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

/*
 * Create/destroy a line to display a  polyline point
 */
void ge_obj_polyline_xy_new_line (GtkWidget *parent ,int n)
{
  int i;
  char buf[8];
  GtkWidget *w_label;
  if (n > cur_max_xy_lines) {
    if (cur_max_xy_lines == 0) {
      w_pline = malloc (n * sizeof(W_Pline));
    } else {
      w_pline = xrealloc (w_pline ,n * sizeof(W_Pline));
    }
    for (i = cur_max_xy_lines; i < n; i++) {
      w_pline[i].box = gg_CreateHContainer (parent);
      sprintf (buf ,"%2d" ,i);
      w_label = gtk_label_new (buf);
      gtk_box_pack_start (GTK_BOX (w_pline[i].box) ,w_label , FALSE, TRUE, 1);
      w_pline[i].x = gtk_entry_new ();
      gtk_entry_set_width_chars (GTK_ENTRY (w_pline[i].x) ,12);
      gtk_box_pack_start (GTK_BOX (w_pline[i].box) ,w_pline[i].x , FALSE, TRUE, 1);
      w_pline[i].y = gtk_entry_new ();
      gtk_entry_set_width_chars (GTK_ENTRY (w_pline[i].y) ,12);
      gtk_box_pack_start (GTK_BOX (w_pline[i].box) ,w_pline[i].y , FALSE, TRUE, 1);
    }
  } else if (n < cur_max_xy_lines) {
    for (i = n ; i < cur_max_xy_lines; i++) {
      gtk_widget_destroy (w_pline[i].box);
    }
  }
  cur_max_xy_lines = n;
  gtk_widget_show_all (parent);
}

static GtkWidget *ge_obj_polyline_xy_new (GtkWidget *parent)
{
  GtkWidget *retval;
  retval = gtk_scrolled_window_new (NULL,NULL);
  gtk_scrolled_window_set_policy  (GTK_SCROLLED_WINDOW (retval)
				   ,GTK_POLICY_AUTOMATIC
				   ,GTK_POLICY_AUTOMATIC);
  gtk_widget_set_size_request (retval ,0 ,poly_xy_height);
  w_poly_box = gtk_vbox_new (FALSE ,0);
  ge_obj_polyline_xy_new_line (w_poly_box ,2);
  gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (retval) ,w_poly_box);
  gtk_widget_show_all (retval);
  gg_frame_attach (parent ,retval);
  return retval;
}

/*********************** M E N U   C R E A T I O N  ********************/

/**
 *  Create rpane contextual menus for
 *  Q_Line, Q_Box, Q_Arc, Q_String and Q_TimeStamp
 */
GtkWidget *ge_obj_create_menu (GtkWidget *parent ,Qtype typ ,int n)
{
  QDobject *po;
  GtkWidget *vbox ,*vbox1 ,*rc ,*rc2 ,*bt;
  static GtkWidget *hbox = NULL;
  char buf[64];
  int gno;

  if (typ == Q_Colorbar) return NULL;


  if (n == TEMPLATE) {
    if (typ == Q_TimeStamp || typ == Q_Compound) {
      printf ("ge_obj_create_menu: %s with n==0 internal ERROR\n" ,Qtype_name[typ]);
      return NULL;
    }
  }
  block_instant_update = TRUE;            /* to avoid circular dependency */
  gg_frame_connect (ge_obj_apply_CB);     /* the default callback */
  vbox = gg_CreateVContainer (parent);
  rc = gg_CreateHContainer (vbox);
  if (n == TEMPLATE) {
    sprintf (buf ,"%s template" ,Qtype_name[typ]);
  } else {
    sprintf (buf ,"%s" ,Qtype_name[typ]);
  }
  U.titre_item = gg_label_new (rc ,buf);
  U.menu_finalized 	 = FALSE;
  W (ARC) {
    gg_frame_connect (NULL);              /* special callback ge_obj_subtyp_CB */
    U.subt_item = gg_combo_new (rc ,"" ,8
					,"0: Arc"                         /* 0 */
					,"1: Ellipse by bounding box"     /* 1 */
					,"2: Ellipse by radii"            /* 2 */
					,"3: Circle by bounding box"      /* 3 */
					,"4: Circle by radius"            /* 4 */
					,"5: Arc of circle by radius"     /* 5 */
					,"6: Arc of ellipse centered"     /* 6 */
					);
    gg_frame_connect (ge_obj_apply_CB);   /* restore default callback */
  }
  gint d = gg_get_depth ();
  U.vb = gg_CreateVContainer (vbox);
  W (NOSTAMP) {
    hbox        = gg_CreateHContainer  (U.vb);
    gg_frame_connect (NULL);       /* special callbacks for w_gno  and w_loctyp */
    U.w_gno     = gg_spin_new  (hbox ,"Attach to graph:" ,-1.0 ,0.0 ,1.0);
    U.w_loctyp  = gg_combo_new (U.vb ,"Position in:" ,3
				       ,"Viewport coordinates"   /* COORD_VIEW  */
				       ,"World coordinates");    /* COORD_WORLD */
    gg_frame_connect (ge_obj_apply_CB);
  }
  W (ARC) {
    U.arc_frame = gg_frame (U.vb ,"Arc angles in degrees" ,&wheat ,0 ,1);
    gg_spin2d (U.arc_frame ,&(U.angles) ,0.0 ,360.0 ,1.0);
  }

  rc =  gg_frame (U.vb ,"Appearance" ,&wheat ,0 ,1);
  rc2 = gg_CreateHContainer (rc);
  W (COLOR)   U.w_color    = gg_color_new     (rc2 ,"Color");
  W (STRING)  U.w_font     = gg_font_new      (rc  ,"Font:");
  W (STRING)  U.w_just     = gg_just_new      (rc  ,"Justification:");
  W (STRING)  U.w_charsize = gg_charsize_new  (rc  ,"Font size");
  W (ROT)     U.w_rot      = gg_angle_new     (rc  ,"Rotation");
  W (LINE)    U.w_linew    = gg_spin_new      (rc2 ,"Line width:" ,0.0 ,MAX_LINEWIDTH ,0.5);
  W (LINE)    U.w_lines    = gg_lines_new     (rc  ,"Line style:");
  W (FILL) {
    rc2 = gg_CreateHContainer (rc);
    U.w_fillcolor   = gg_color_new     (rc2 ,"Fill color");
    U.w_fillpattern = gg_pattern_new   (rc2 ,"pattern");
  }
  W (ARROW) {
    U.arrow_frame      = gg_frame     (rc ,"Arrow(s)" ,&wheat ,0 ,1);
    U.w_arrow_end      = gg_combo_new (U.arrow_frame ,"Place at:" ,5
					       ,"None" ,"Start" ,"End" ,"Both ends");
    hbox 	       = gg_CreateHContainer  (U.arrow_frame);
    U.w_arrow_type     = gg_combo_new (hbox ,"Type:" ,4 ,"Line" ,"Filled" ,"Opaque");
    U.w_arrow_length   = gg_spin_new  (hbox ,"Length"          ,-10.0 ,10.0 ,0.5);
    hbox 	       = gg_CreateHContainer  (U.arrow_frame);
    U.w_arrow_dL_ff    = gg_spin_new  (hbox ,"form factors d/L" ,0.0   ,10.0 ,0.1);
    U.w_arrow_lL_ff    = gg_spin_new  (hbox ,"l/L"              ,-1.0  ,1.0  ,0.1);
  }
  W (STAMP) {
    rc 			  = gg_frame 	 (vbox ,"Dates defaults" ,&bleuciel ,0 ,1);
    hint_item 		  = gg_combo_new (rc ,"Date hint" ,5
						  ,"ISO"             /* FMT_iso      */
						  ,"European"        /* FMT_european */
						  ,"US"              /* FMT_us       */
						  ,"None");          /* FMT_nohint   */
    date_item 		  = gg_entry_new   (rc ,"Reference date:" ,20);
    rc2 		  = gg_CreateHContainer  (rc);
    two_digits_years_item = gg_check_new   (rc2 ,"Two-digit year span");
    wrap_year_item        = gg_entry_new   (rc2 ,"Wrap year:" ,4);
  }
  /* It is practical to type the string before to place and create it */
  if (n == TEMPLATE) {
    W (STRING) U.w_s = gg_string_new (vbox ,"String:" ,30);
  }
  W (CLOSED) {
    rc = gg_CreateHContainer (vbox);
    U.w_closed  = gg_check_new (rc ,"Closed");
    U.w_smooth  = gg_check_new (rc ,"Smooth");
    U.xspline_s = gg_spin_new (rc  ,"s" ,-1.0 ,1.0 ,0.1);
    U.xspline_p = gg_spin_new (rc  ,"p" ,0.002 ,0.5 ,0.002);
  }
  if (typ == Q_Line) {
    rc = gg_frame (vbox ,"Label" ,&bleuciel ,2 ,4);
    U.w_label_opt = gg_combo_new     (rc ,"Def" ,4 ,"None" ,"Length" ,"String");
    U.w_rot       = gg_angle_new     (rc ,"Rotation");
    U.w_font      = gg_font_new      (rc ,"Font:");
    U.w_just      = gg_just_new      (rc ,"Justification:");
    U.w_fmt       = gg_entry_new     (rc ,"Format:" ,15);
    U.w_charsize  = gg_charsize_new  (rc ,"Font size");
    rc2 	  = gg_hbox_n_new    (rc ,2);
    gg_spin2d (rc2 ,&(U.offset) ,-0.05 ,0.05 ,0.002);
  }

  /* --------------- Bottom widgets  ------------------ */
  vbox1 = gtk_vbox_new (FALSE, 3);
  gtk_box_pack_end (GTK_BOX (vbox) ,vbox1 , FALSE, TRUE, 1);
  /* Action buttons */
  if (n == TEMPLATE) {                              /* template */
    gno = gg_get_int (U.w_gno);
    if (gno == -1) {
      cur_parent_typ = typ;
      cur_parent_id = -1;
    } else {
      cur_parent_typ = Q_Graph;
      cur_parent_id  = gno;
    }
    gg_buttonA (vbox1 ,"Create & place with mouse" ,gg_make_obj_CB ,typ);
    W (W_ALL) U.w_layer = gg_layer_new (vbox1 ,"Layer");
  } else {
    /* Localization of the object */
    U.geom_frame = gg_frame (vbox1 ,frame_pos_string[typ] ,&wheat ,0 ,1);
    W (W_ALL) {
      if (typ == Q_Polyline) {
	w_poly_scroll = ge_obj_polyline_xy_new (U.geom_frame);
      } else {
	W (XY2) gg_spin2e (U.geom_frame ,&(U.xy2) ,-10.0 ,10.0 ,0.01);
	gg_spin2e         (U.geom_frame ,&(U.xy1) ,-10.0 ,10.0 ,0.01);
      }
      hbox = gtk_hbox_new (FALSE, 3);
      gtk_box_pack_start (GTK_BOX (vbox1) ,hbox , FALSE, TRUE, 1);
      if (typ != Q_Compound) U.w_layer = gg_layer_new (hbox ,"Layer");
      if (typ == Q_Polyline) {
	bt = gg_buttonB (hbox ,22 ,d ,movepx_bits  ,gg_act_on_cur_obj_CB ,MOVX_POINT1ST ,"Click a point to move it along Ox");
	bt = gg_buttonB (hbox ,22 ,d ,movepy_bits  ,gg_act_on_cur_obj_CB ,MOVY_POINT1ST ,"Click a point to move it along Oy");
	bt = gg_buttonB (hbox ,22 ,d ,movept_bits  ,gg_act_on_cur_obj_CB ,MOVE_POINT1ST ,"Click a point to move it");
	bt = gg_buttonB (hbox ,22 ,d ,addpt_bits   ,gg_act_on_cur_obj_CB ,ADD_POINT_1ST ,"Click a point to add one after it");
      }
    }
    hbox = gtk_hbox_new (FALSE, 3);
    gtk_box_pack_end (GTK_BOX (vbox1) ,hbox , FALSE, TRUE, 1);
    U.w_hidden  = gg_check_new (hbox ,"Hide");
    bt 	      	= gg_buttonB (hbox ,22 ,d ,move_bits    ,gg_act_on_cur_obj_CB ,MOVE_OBJ_1ST  ,"Move object");
    W (NOSTAMP) {
      bt      	 = gg_buttonB (hbox ,22 ,d ,copy_bits   ,gg_act_on_cur_obj_CB ,COPY_OBJ_1ST  ,"Copy object");
      W (XY2) bt = gg_buttonB (hbox ,22 ,d ,scale_bits  ,gg_act_on_cur_obj_CB ,SCALE_OBJ_1ST ,"Scale object");
      bt      	 = gg_buttonB (hbox ,22 ,d ,delete_bits ,gg_act_on_cur_obj_CB ,DEL_OBJ       ,"Delete object");
      if (typ == Q_Polyline) {
 	bt = gg_buttonB (hbox ,22 ,d ,deletept_bits   ,gg_act_on_cur_obj_CB ,DEL_POINT ,"Click a point to kill it");
      }
    }
  }
  if (typ == Q_Compound && n == CURRENT) {
    rc =  gg_frame (vbox1 ,"Collapse  elements in one layer" ,&bleuciel ,1 ,0);
    U.w_layer 		= gg_layer_new  (rc ,"Layer");
    U.w_layers_collapse = gg_button_new (rc ,"Collapse");
    g_signal_connect (U.w_layers_collapse ,"clicked" ,G_CALLBACK (ge_obj_collapse_layers_CB) ,NULL);
  }
  W (NOSTAMP) {
    g_signal_connect (U.w_loctyp ,"changed"       ,G_CALLBACK (ge_obj_change_loctyp_CB) ,GINT_TO_POINTER (n));
    idgnoCB[n][typ] = 
      g_signal_connect (U.w_gno  ,"value-changed" ,G_CALLBACK (ge_obj_change_gno_CB) 	,GINT_TO_POINTER (n));
    W (ARC) {
      g_signal_connect (U.subt_item ,"changed" ,G_CALLBACK (ge_obj_subtyp_CB) ,GINT_TO_POINTER (n));
    }
  }
  if (typ == Q_Compound && hbox != NULL)  {
    bt = gg_buttonB (hbox ,22 ,d ,break_bits ,gg_act_on_cur_obj_CB ,BREAK_COMPOUND ,"Break compound");
  } else {
    po = obj_t_get_p_to_template (typ);
    if (po != NULL)   ge_obj_update (po ,n);
  }
  U.menu_finalized = TRUE;
  gg_frame_connect (NULL);
  block_instant_update = FALSE;
  return vbox;
}

/*********************** U P D A T E ********************/

/**
 *  ge_obj_update_xy is called  by ge_obj_update
 */
void ge_obj_update_xy (QDobject *po ,int n)
{
  Qtype typ = po->typ;
  double xc ,yc ,rx ,ry;
  int i;
  char buf[32];
  SpinScaling sp;

  if (typ == Q_Colorbar) {
    ge_colorbar_update (po->self);
    return;
  }

  if (U.titre_item == NULL) return;

  W (ARC) {                                    /* Q_Arc */
    if (n == CURRENT) {
      if (po->pmask & MA_CENTERED) {
        xc = 0.5 * (po->x1 + po->x2);
        yc = 0.5 * (po->y1 + po->y2);
        gg_spin2d_set   (&(U.xy1) ,"Centre xc=" ,xc ,"yc=" ,yc);
	rx = 0.5 * fabs(po->x1 - po->x2);
	if (po->pmask & MA_X_EQ_Y) {
	  /* circle */
	  gg_spin2d_set (&(U.xy2) ,"Radius rx="  ,rx ,NULL ,0);
	} else {
	  /* ellipse */
	  ry = 0.5 * fabs(po->y1 - po->y2);
	  gg_spin2d_set   (&(U.xy2) ,"Radii rx="  ,rx ,"ry=" ,ry);
	}
      } else {
	gg_spin2d_set (&(U.xy1) ,"X1=" ,po->x1 ,"Y1=" ,po->y1);
	gg_spin2d_set (&(U.xy2) ,"X2=" ,po->x2 ,"Y2=" ,po->y2);
      }
    }
  } else {
    W (W_ALL) {
      if (typ == Q_Polyline) {
	if (po->x != NULL) {
	  ge_obj_polyline_xy_new_line (w_poly_box ,po->nxy);
	  for (i = 0; i < cur_max_xy_lines; i++) {
	    gg_setstr_d (w_pline[i].x ,"%.9g" ,po->x[i] ,buf);
	    gg_setstr_d (w_pline[i].y ,"%.9g" ,po->y[i] ,buf);
	  }
	}
      } else {
	obj_calc_spinbox_scaling       (po->self ,&sp);
	gg_spin2d_set                  (&(U.xy1) ,"X1=" ,po->x1 ,"Y1=" ,po->y1);
	gtk_spin_button_set_range      (GTK_SPIN_BUTTON (U.xy1.spin1) ,sp.min_x  ,sp.max_x);
	gtk_spin_button_set_range      (GTK_SPIN_BUTTON (U.xy1.spin2) ,sp.min_y  ,sp.max_y);
	gtk_spin_button_set_increments (GTK_SPIN_BUTTON (U.xy1.spin1) ,sp.step_x ,10.0*sp.step_x);
	gtk_spin_button_set_increments (GTK_SPIN_BUTTON (U.xy1.spin2) ,sp.step_y ,10.0*sp.step_y);
	W (XY2) {
	  gg_spin2d_set (&(U.xy2) ,"X2=" ,po->x2 ,"Y2=" ,po->y2);
	  gtk_spin_button_set_range      (GTK_SPIN_BUTTON (U.xy2.spin1) ,sp.min_x  ,sp.max_x);
	  gtk_spin_button_set_range      (GTK_SPIN_BUTTON (U.xy2.spin2) ,sp.min_y  ,sp.max_y);
	  gtk_spin_button_set_increments (GTK_SPIN_BUTTON (U.xy2.spin1) ,sp.step_x ,10.0*sp.step_x);
	  gtk_spin_button_set_increments (GTK_SPIN_BUTTON (U.xy2.spin2) ,sp.step_y ,10.0*sp.step_y);
	}
      }
    }
  }
}
/**
 *  Update the rpane and the handles
 */
void ge_obj_update (QDobject *po ,int n)
{
  char buf[132];
  Qtype typ = po->typ;
  gdouble mingr;
  int pmask ,subarc ,extent ,gno ,just;

  if (typ < 0 || typ >= Q_Last) return;
  obj_make_current (po->self);
  if (typ == Q_Colorbar) {
    if (n == CURRENT) {
      ge_colorbar_update (po->self);
      ge_obj_update_xy (po ,n);
    }
    return;
  }
  if (U.titre_item == NULL) return;

  block_instant_update = TRUE;

  if (n == TEMPLATE) {
    sprintf (buf ,"%s template" ,Qtype_name[typ]);
  } else {
    if (typ == Q_TimeStamp) {
      sprintf (buf ,"Timestamp");
      gtk_label_set_text (GTK_LABEL (U.titre_item) ,buf);
    } else {
#ifdef DEBUG
      sprintf (buf ,"%s %d (num=%d)" ,Qtype_name[typ] ,cur_obj_id ,cur_obj_num);
#else
      sprintf (buf ,"%s %d" ,Qtype_name[typ] ,cur_obj_id);
#endif
      gtk_label_set_text (GTK_LABEL (U.titre_item) ,buf);
    }
  }
  W (ARC) {
    pmask = po->pmask & MARC;
    subarc = arc_subtyp (pmask);
    gg_set_int (U.subt_item ,subarc);
    if (pmask & MA_IS_CLOSED) {
      po->astart    = 0;
      po->aend      = 360;
      po->arrow_end = 0;
    }
    extent = po->aend - po->astart;
    gg_spin2d_set (&(U.angles) ,"Start" ,po->astart ,"Extent" ,extent);
    gtk_widget_set_sensitive (U.arc_frame ,(pmask & MA_IS_CLOSED) == 0);
  }
  W (COLOR) gg_set_int    (U.w_color       ,po->color  );
  W (W_ALL) gg_set_int    (U.w_layer       ,po->layer     );
  W (NOSTAMP) {
    gno = obj_get_typed_ancestor_id (po->self ,Q_Graph);
    g_signal_handler_block   (U.w_gno     ,idgnoCB[n][typ]);
    gg_set_int    	     (U.w_loctyp  ,po->loctyp    );
    mingr = (po->loctyp == COORD_WORLD) ? 0.0 : -1.0;
    gtk_spin_button_set_range (GTK_SPIN_BUTTON (U.w_gno)
			       ,mingr
			       ,(gdouble)(number_of_graphs () - 1) );
    gg_set_int (U.w_gno ,gno);
    gtk_widget_set_sensitive (U.w_gno    ,po->father_typ != Q_Compound);
    gtk_widget_set_sensitive (U.w_loctyp ,(po->typ != Q_Compound) && (po->father_typ == Q_Graph));
    g_signal_handler_unblock (U.w_gno ,idgnoCB[n][typ]);
  }
  W (STRING) {
    gg_set_int    (U.w_font        ,po->font );
    gg_set_dble   (U.w_charsize    ,po->charsize);
    just = set_just (po->just);
    gg_set_int   (U.w_just         ,just);
  }
  W (ROT) {
    gg_set_int    (U.w_rot         ,po->rot);
  }
  W (LINE) {
    gg_set_int    (U.w_lines        ,po->lines  );
    gg_set_dble   (U.w_linew        ,po->linew  );
  }
  if (typ == Q_Line) {
    gg_set_int    (U.w_label_opt   ,po->label_opt);
    gg_set_int    (U.w_rot         ,po->rot);
    gg_set_int    (U.w_font        ,po->font );
    gg_set_dble   (U.w_charsize    ,po->charsize);
    just = set_just (po->just);
    gg_set_int   (U.w_just         ,just);
    if (po->frmt == NULL) po->frmt = copy_string (NULL ,"%g");
    gg_setstr    (U.w_fmt          ,po->frmt);
    gg_spin2d_set (&(U.offset) ,"Offset: perp=" ,po->perp ,"para=" ,po->para);
  }
  W (CLOSED) {
    gg_set_int    (U.w_closed       ,(po->pmask & MA_IS_CLOSED) ? TRUE : FALSE);
    gg_set_int    (U.w_smooth       ,(po->pmask & MA_SMOOTH)    ? TRUE : FALSE);
    gg_set_dble   (U.xspline_s      ,po->xspline_s);
    gg_set_dble   (U.xspline_p      ,po->xspline_p);
  }
  W (FILL) {
    gg_set_int    (U.w_fillcolor    ,po->fillcolor);
    gg_set_int    (U.w_fillpattern  ,po->fillpattern);
  }
  W (ARROW) {
    gg_set_int    (U.w_arrow_end    ,po->arrow_end);
    gg_set_int    (U.w_arrow_type   ,po->arrow.type);
    gg_set_dble   (U.w_arrow_length ,po->arrow.length);
    gg_set_dble   (U.w_arrow_dL_ff  ,po->arrow.dL_ff);
    gg_set_dble   (U.w_arrow_lL_ff  ,po->arrow.lL_ff);
  }
  W (STAMP) {
    int y, m, d, h, mm, sec;
    char date_string[64], wrap_year_string[64];
    gg_set_int (hint_item   ,get_date_hint());
    jul_to_cal_and_time (0.0, ROUND_SECOND, &y, &m, &d, &h, &mm, &sec);
    sprintf    (date_string, "%d-%02d-%02d %02d:%02d:%02d", y, m, d, h, mm, sec);
    gg_setstr  (date_item        ,date_string);
    gg_set_int (two_digits_years_item   ,two_digits_years_allowed());
    sprintf    (wrap_year_string, "%04d", get_wrap_year());
    gg_setstr  (wrap_year_item, wrap_year_string);
    gtk_widget_set_sensitive (wrap_year_item ,two_digits_years_allowed() ? TRUE:FALSE);
  }
  if (n == CURRENT) {
    gtk_widget_set_sensitive (U.vb         ,po->hidden == FALSE);
    gtk_widget_set_sensitive (U.geom_frame ,po->hidden == FALSE);
    gg_set_int  (U.w_hidden ,po->hidden );
    ge_obj_update_xy (po ,n);
  }
  block_instant_update = FALSE;
  rpane_obj_num = po->self;
  rpane_n   = n;
}

void ge_obj_update_rpane (void)
{
  if (rpane_n == CURRENT && rpane_obj_num >= 0) {
    ge_obj_update (&(objs[rpane_obj_num]) ,CURRENT);
  }
}

/*********************** A P P L Y ********************/

static void ge_obj_apply_xy (QDobject *po ,Qtype typ ,int n)
{
  double x1 ,y1 ,rx ,ry;
  int i;
  x1 = y1 = 0.0;
  W (W_ALL) {
    if (typ != Q_Polyline) {
      x1 = gg_get_dble (U.xy1.spin1);
      y1 = gg_get_dble (U.xy1.spin2);
    }
  }
  W (ARC) {
    if (n == CURRENT) {
      if (po->pmask & MA_CENTERED) {
	rx = gg_get_dble (U.xy2.spin1);
	if (po->pmask & MA_X_EQ_Y) {
	  ry = rx;
	} else {
	  ry = gg_get_dble (U.xy2.spin2);
	}
	po->x1 = x1 - rx;
	po->y1 = y1 - ry;
	po->x2 = x1 + rx;
	po->y2 = y1 + ry;
      } else {
	po->x1 = x1;
	po->y1 = y1;
	po->x2 = gg_get_dble (U.xy2.spin1);
	po->y2 = gg_get_dble (U.xy2.spin2);
      }
    }
  } else {
    W (W_ALL) {
      if (typ == Q_Polyline) {
	if (po->x != NULL) {
	  for (i = 0; i < cur_max_xy_lines; i++) {
	    po->x[i] = gg_get_dble (w_pline[i].x);
	    po->y[i] = gg_get_dble (w_pline[i].y);
	  }
	}
      } else {
	po->x1 = x1;
	po->y1 = y1;
	W (XY2) {
	  po->x2 = gg_get_dble (U.xy2.spin1);
	  po->y2 = gg_get_dble (U.xy2.spin2);
	}
      }
    }
  }
}


/**
 *  Store the values and redraw the canvas
 *   The use of 2nd arg n is #define'd at the beginning of the file
 *   n=0: template and n=1: cur_obj_typ
 *   If i>=0,  it is the index array in objs[], else it is not used
 *   typ is transmitted via cur_obj_typ
 */
void ge_obj_apply (int i ,int n)
{
  char buf[64];
  QDobject *po;
  Qtype typ = cur_obj_typ;
  int jus;

  if (typ < 0 || typ >= Q_Last) return;
  if (typ == Q_TimeStamp && n == TEMPLATE) {
    printf ("ge_obj_apply: Q_TimeStamp with n==0 internal ERROR\n");
    return;
  }
  if (typ == Q_Colorbar) {
    ge_colorbar_apply (0 ,i);
    rpane_obj_num  = i;
    return;
  }

  if (n == TEMPLATE) {
    po = obj_t_get_p_to_template (typ);
    obj_copy (&oa ,po);
  } else {
    sprintf (buf ,"%s %d num=%d"
	     ,Qtype_name[typ] ,cur_obj_id ,cur_obj_num);
    gtk_label_set_text (GTK_LABEL (U.titre_item) ,buf);
    po = &objs[i];
    obj_copy (&oa ,po);
    po->hidden         = gg_get_int    (U.w_hidden        );
    gtk_widget_set_sensitive (U.vb         ,po->hidden == FALSE);
    gtk_widget_set_sensitive (U.geom_frame ,po->hidden == FALSE);
    ge_hidden_update (typ ,po->id ,po->father_typ ,po->father_id ,po->hidden);
  }
  W (COLOR)   po->color = gg_get_int   (U.w_color);
  W (NOSTAMP) {
    if (po->father_typ != Q_Compound) {
      po->loctyp      = gg_get_int (U.w_loctyp);
      po->father_id   = gg_get_int (U.w_gno);
    }
  }
  W (W_ALL) po->layer = gg_get_int (U.w_layer);
  if(n == CURRENT)  ge_obj_apply_xy (po ,typ ,n);
  W (STRING) {
    po->font          = gg_get_int  (U.w_font);
    po->charsize      = gg_get_dble (U.w_charsize);
    jus               = gg_get_int  (U.w_just);
    po->just          = get_just    (jus);
  }
  W (ROT)   po->rot          = gg_get_int  (U.w_rot);
  W (LINE)  po->lines        = gg_get_int  (U.w_lines);
  W (LINE)  po->linew        = gg_get_dble (U.w_linew);
  W (FILL)  po->fillcolor    = gg_get_int  (U.w_fillcolor);
  W (FILL)  po->fillpattern  = gg_get_int  (U.w_fillpattern);
  W (ARROW) {
    po->arrow_end    = gg_get_int  (U.w_arrow_end);
    po->arrow.type   = gg_get_int  (U.w_arrow_type);
    po->arrow.length = gg_get_dble (U.w_arrow_length);
    po->arrow.dL_ff  = gg_get_dble (U.w_arrow_dL_ff);
    po->arrow.lL_ff  = gg_get_dble (U.w_arrow_lL_ff);
  }
  W (CLOSED) {
    po->pmask 	  = gg_get_int 	 (U.w_closed) ? (po->pmask | MA_IS_CLOSED) : (po->pmask & ~MA_IS_CLOSED);
    po->pmask 	  = gg_get_int 	 (U.w_smooth) ? (po->pmask | MA_SMOOTH   ) : (po->pmask & ~MA_SMOOTH);
    po->xspline_s =  gg_get_dble (U.xspline_s);
    po->xspline_p =  gg_get_dble (U.xspline_p);
  }
  if (typ == Q_Line) {
    po->label_opt     = gg_get_int  (U.w_label_opt);
    po->frmt          = copy_string (po->frmt ,gg_getstr (U.w_fmt));
    po->rot           = gg_get_int  (U.w_rot);
    po->font          = gg_get_int  (U.w_font);
    po->charsize      = gg_get_dble (U.w_charsize);
    jus               = gg_get_int  (U.w_just);
    po->just          = get_just    (jus);
    po->perp          = gg_get_dble (U.offset.spin1);
    po->para          = gg_get_dble (U.offset.spin2);
  }
  W (STAMP) {
    double jul;
    set_date_hint (gg_get_int (hint_item));
    if (parse_date_or_number (gg_getstr(date_item), TRUE, &jul) == RETURN_SUCCESS) {
      set_ref_date (jul);
    } else {
      errmsg("Invalid date");
    }
    allow_two_digits_years (gg_get_int (two_digits_years_item));
    set_wrap_year (atoi (gg_getstr (wrap_year_item)));
  }
  W (ARC) {
    po->astart = gg_get_int (U.angles.spin1);
    po->aend   = gg_get_int (U.angles.spin2) + po->astart;
  }
  ge_tree_need_update  (!ge_select_current ());
  gg_update_layers_hbox ();
  set_dirtystate ();
  gg_drawgraph   ();

  /* undo/redo and follow_me_on */
  nn_cmd_obj (&oa ,po ,FALSE ,TRUE);

  rpane_obj_num = po->self;
  rpane_n = n;
}


/*********************** C A L L  B A C K S  ********************/


void ge_obj_block_instant_update (int sw)
{
  block_instant_update = sw;
}

/**
 *
 */
void ge_obj_apply_CB (GtkWidget *w ,gpointer p)
{
  int n;
  Qtype typ;
  if (block_instant_update) return;
  n = ge_get_rpane_iu ();
  if (n == TEMPLATE)        return;
  typ = cur_obj_typ;
  if (instant_update && U.menu_finalized &&
      obj_tid_is_valid (cur_obj_typ ,cur_obj_id)) {
    ge_obj_apply (cur_obj_num ,n);
  }
}

/**
 * Toggle between COORD_VIEW and COORD_WORLD
 */
static void ge_obj_change_loctyp_CB (GtkWidget *w ,gpointer p)
{
  int n = GPOINTER_TO_INT (p);
  Qtype typ = cur_obj_typ;
  QDobject *po;
  int gno ,newloc ,mingraphnum;
  if (n == TEMPLATE) return;
  po = obj_tid_get_pointer (typ ,cur_obj_id ,cur_parent_id);
  if (po == NULL) {
    errmsg("ge_obj_vw_CB internal error. see console message");
    printf ("ge_obj_vw_CB po == NULL pour n=%d typ=%d cur_id=%d "
	    , n ,typ ,cur_obj_id);
    printf (" parent=%d \n" ,cur_parent_id);
    return;
  }
  newloc = gg_get_int (w);
  if (obj_change_loctyp (po ,newloc) == RETURN_SUCCESS) {
    block_instant_update = TRUE;
    gno = gg_get_int (U.w_gno);
    select_graph (gno);
    mingraphnum = (newloc == COORD_WORLD) ? 0 : -1;
    gtk_spin_button_set_range (GTK_SPIN_BUTTON (U.w_gno)
			       ,(gdouble)mingraphnum
			       ,(gdouble)(number_of_graphs ()-1) );
    ge_obj_update_xy (po ,n);
    block_instant_update = FALSE;
  }
}

/**
 *  Called when U.w_gno is changed
 *  gno==-1 <=> project templates
 */
static void ge_obj_change_gno_CB (GtkWidget *w ,gpointer p)
{
  int igno;
  int n        = GPOINTER_TO_INT (p);
  Qtype typ    = cur_obj_typ;
  QDobject *po = &objs[cur_obj_num];
  int      gno = gg_get_int (U.w_gno);
  /* Sanity check */
  if (n == TEMPLATE) {
    errmsg ("Template attach cannot be changed; please select a drawable object");
    return;
  }
  /* Ok, we proceed */
  if (gno == -1) {       /* append to template or project  */
    igno = 0;
  } else {
    igno = obj_tid_get_graph_num (gno);
  }
  if (obj_append_child (po->self ,igno) == RETURN_SUCCESS) {
    gtk_widget_set_sensitive (U.w_loctyp ,gno >= 0);
    ge_update_explorer ();
    ge_expand_graph (gno);
    /*	gg_drawgraph ();  canvas image is unchanged */
  }
}



/**
 * Callback called when the default subtyp is changed
 */
static void ge_obj_subtyp_CB (GtkWidget *w ,gpointer p)
{
  QDobject *po;
  int n = GPOINTER_TO_INT (p);
  Qtype typ;
  int subarc = gg_get_int (w);
  int pmask ,old_pmask ,extent;
  gboolean ferme;
  double dx ,dy;
  int num = ge_selection_get_num ();
  if (num < 0) {
    errmsg ("Please select ONE item in TreeView");
    ge_unselect_all ();
    return;
  }
  obj_make_current (num);
  typ = cur_obj_typ;

  clear_all_handles ();
  po = &objs[cur_obj_num];

  old_pmask = po->pmask;
  pmask = arc_pmask (subarc);
  po->pmask = pmask;
  ferme = pmask & MA_IS_CLOSED;
  if (ferme) {
    po->astart    = 0;
    po->aend      = 360;
    po->arrow_end = 0;
  }
  extent = po->aend - po->astart;
  gg_spin2d_set (&(U.angles) ,"Start" ,po->astart ,"Extent" ,extent);
  gtk_widget_set_sensitive (U.arc_frame ,!ferme);
  if (ferme) {
    gtk_widget_hide (U.arrow_frame);
  } else {
    gtk_widget_show (U.arrow_frame);
  }

  if (pmask != old_pmask) {
    if (pmask & MA_X_EQ_Y) {
      dx = po->x2 - po->x1;
      dy = po->y2 - po->y1;
      if (dx*dy > 0.0) {
	po->y2 = po->y1 + dx;
      } else {
	po->y2 = po->y1 -dx;
      }
    }
    ge_obj_update_xy (po ,n);
    set_dirtystate ();
  }
  gg_drawgraph ();
  if (n != 0) draw_curobj_handles ();
}


static void ge_obj_collapse_layers_CB   (GtkWidget *w ,gpointer p)
{
  int layer = gg_get_int (ui[CURRENT][Q_Compound].w_layer);
  obj_collapse_layers    (objs[cur_obj_num].child ,layer);
  gg_update_layers_hbox  ();
  objs[cur_obj_num].layer = layer;
  set_dirtystate         ();
  gg_drawgraph           ();
}

/***************************** For string creation **************************/
/*
 * Remark: the string is not copied into the default array
 */
void ge_obj_copy_string_from_template (int i)
{
  if (obj_is_active (i) && ui != NULL) {
    objs[i].s = copy_string (objs[i].s ,gg_getstr (ui[TEMPLATE][Q_String].w_s) );
  }
}


/***************************** Button 3 menu **************************/

static GtkWidget *u_objs_dialog   = NULL;

static GtkWidget *ge_objs_u_new  (Qtype typ);

static void ge_objs_u_AAC_CB   (GtkWidget *widget ,gint reponse);

/**
 *  Create obj_menu3.
 *  Normally called with CB=ge_obj_menu3_CB
 */
ObjMenu3Struct *ge_CreateObjPopupEntries (ObjMenu3Struct *p ,GtkWidget *list
					  ,void (*CB) (GtkWidget *obj_list ,gpointer p))
{
  GtkWidget *menu;
  if (p  == NULL) {
    p = xmalloc (sizeof (ObjMenu3Struct));
    menu = gtk_menu_new ();
    p->popup 	 = menu;
    p->hide  	 = gg_CreateMenuButton (menu ,"Hide"          ,'H'  ,list ,CB ,MenuHideCB);
    p->show  	 = gg_CreateMenuButton (menu ,"Show"          ,'S'  ,list ,CB ,MenuShowCB);
    p->duplicate = gg_CreateMenuButton (menu ,"Duplicate"     ,'D'  ,list ,CB ,MenuDuplicateCB);
    p->kill 	 = gg_CreateMenuButton (menu ,"Kill" 	      ,'K'  ,list ,CB ,MenuKillCB);
    gg_CreateMenuSeparator (menu);
    p->update 	 = gg_CreateMenuButton (menu ,"Update"        ,' '  ,list ,CB ,MenuObjsUpdate);
    p->font_tool = gg_CreateMenuButton (menu ,"Use font tool" ,' '  ,list ,CB ,MenuUseFontTool);
    gg_CreateMenuSeparator (menu);
    p->glue      = gg_CreateMenuButton (menu ,"Create a new compound and glue" ,' '  ,list ,CB ,MenuObjsGlue);
    p->break_compound = gg_CreateMenuButton (menu ,"Break compound"    	       ,' '  ,list ,CB ,MenuObjsBreak);
    p->prune     = gg_CreateMenuButton 	    (menu ,"Prune"         	       ,' '  ,list ,CB ,MenuObjsPrune);
    p->insert    = gg_CreateMenuButton 	    (menu ,"Insert in a compound"      ,' '  ,list ,CB ,MenuObjsInsert);
  }
  return (p);
}

/**
 *  Called by ge_tree_CB when Button3 is pressed and
 *  geometric selected objects are of same type (colorbar excluded)
 */
void ge_obj_popup_menu3 (ObjMenu3Struct *p ,int nb ,Qtype typ ,int id)
{
  int nots = id >= 0 && typ != Q_TimeStamp;
  gtk_widget_set_sensitive (p->hide        ,id >= 0);
  gtk_widget_set_sensitive (p->show        ,id >= 0);
  gtk_widget_set_sensitive (p->duplicate   ,nots);
  gtk_widget_set_sensitive (p->kill        ,nots);
  gtk_widget_set_sensitive (p->update      ,nots);
  gtk_widget_set_sensitive (p->font_tool   ,nots && typ != Q_Compound);
  gtk_widget_set_sensitive (p->glue        ,nots);
  gtk_widget_set_sensitive (p->break_compound ,nots && typ == Q_Compound);
  gtk_widget_set_sensitive (p->prune       ,nb == 1 && obj_is_geom_prunable (cur_obj_num));
  gtk_widget_set_sensitive (p->insert      ,nb == 1 && nots && cur_parent_id < 0);
  gtk_menu_popup (GTK_MENU(p->popup) ,NULL ,NULL ,NULL ,NULL ,3 ,gtk_get_current_event_time () );
}

/**
 *  Called by ge_tree_CB when Button3 is pressed and
 *  types are different
 */
void ge_obj_popup_menu3_mixed (ObjMenu3Struct *p)
{
  gtk_widget_set_sensitive (p->hide        ,TRUE);
  gtk_widget_set_sensitive (p->show        ,TRUE);
  gtk_widget_set_sensitive (p->duplicate   ,FALSE);
  gtk_widget_set_sensitive (p->kill        ,TRUE);
  gtk_widget_set_sensitive (p->update      ,FALSE);
  gtk_widget_set_sensitive (p->font_tool   ,FALSE);
  gtk_widget_set_sensitive (p->glue        ,TRUE);
  gtk_widget_set_sensitive (p->break_compound ,FALSE);
  gtk_widget_set_sensitive (p->prune       ,FALSE);
  gtk_widget_set_sensitive (p->insert      ,FALSE);
  gtk_menu_popup (GTK_MENU(p->popup) ,NULL ,NULL ,NULL ,NULL ,3 ,gtk_get_current_event_time () );
}

static void ge_obj_hide (int action ,int num)
{
  int hide = (action == MenuHideCB);
  objs[num].hidden = hide;
  ge_hidden_update (objs[num].typ ,objs[num].id ,objs[num].father_typ ,objs[num].father_id ,hide);
}

static int num_to_show = -1;    /* Trying to be smart in displaying TreeView */
static void ge_obj_kill (int action ,int num)
{
  char message[32];
  Qtype typ = objs[num].typ;
  if (objs[num].id < 0) {
    errmsg ("Cannot delete templates");
  } else {
    sprintf (message, "Kill %s %d?" ,Qtype_name[typ] ,objs[num].id);
    if (yesno (message) == TRUE) {
      num_to_show = (objs[num].brother > 0) ? objs[num].brother : objs[num].elder;
      nn_kill_branch (num);
      set_dirtystate  ();
    }
  }
}

static void ge_obj_duplicate (int action ,int num)
{
  int id;
  if (obj_is_active (num)) {
    int i = obj_duplicate (num ,&id);
    if (i < 0) {
      obj_errmsg (num ,"error in duplication of the object");
    } else {
      num_to_show = i;
    }
  }
}

/* first elem. of clist is the father of the compound to be created,
 *    then the list of the objs to put into */
static GList *clist = NULL;
static int    list_error = FALSE;

static void ge_obj_glue (int action ,int num)
{
  int father;
  if (clist == NULL) {
    obj_make_current (num);
    if (cur_parent_typ == Q_Graph) {
      father = obj_tid_get_graph_num (cur_parent_id); 
    } else {
      father = obj_t_get_template_num (cur_parent_typ);
    }
    clist = g_list_append (clist ,GINT_TO_POINTER (father));
  }
  clist = g_list_append (clist ,GINT_TO_POINTER (num));
  /* these objects can't be glued */
  list_error = list_error || 
    objs[num].id < 0      || 
    objs[num].father_typ == Q_Compound;
}

/* Compare to  ge_graph_menu3_CB */
/**
 *
 */
void ge_obj_menu3_CB (GtkWidget *w ,gpointer pact)
{
  Qtype typ;
  int   i ,id ,ret ,dest ,gno;
  double did;
  char message[64];
  gint              action = GPOINTER_TO_INT (pact);
  Ge_tree 	   *getree = get_getree ();
  GtkTreeSelection *select = gtk_tree_view_get_selection (GTK_TREE_VIEW (getree->view));
  int               nb     = gtk_tree_selection_count_selected_rows (select);
  switch (action) {
  case MenuUseFontTool:
    if (nb == 1) {
      gg_fontwin_create (NULL ,objs[cur_obj_num].s);
    }
    break;
  case MenuHideCB:
  case MenuShowCB:
    if (ge_selection_for_each (action ,ge_obj_hide)) {
      gg_drawgraph();
    }
    break;
  case MenuDuplicateCB:
    if (ge_selection_for_each (action ,ge_obj_duplicate)) {
      gg_drawgraph();
      ge_update_explorer ();
      if (obj_is_active (num_to_show)) {
	obj_make_current  (num_to_show);
	ge_expand_current ();
	ge_select_current ();
      }
    }
    break;
  case MenuKillCB:
    if (ge_selection_for_each (action ,ge_obj_kill)) {
      gg_drawgraph       ();
      ge_update_explorer ();
      if (obj_is_active (num_to_show)) {
	obj_make_current  (num_to_show);
	ge_expand_current ();
	ge_select_current ();
      }
    }
    break;
  case MenuObjsUpdate:
    nb = ge_selection_is_homogeneous (&typ);
    ge_objs_u_new (typ);
    break;
  case MenuObjsGlue:
    list_error = FALSE;
    g_list_free (clist);
    clist = NULL;
    if (ge_selection_for_each (action ,ge_obj_glue)) {
      if (list_error) {
	g_list_free (clist);
	clist = NULL;
	errmsg ("A selected object cannot be glued");
      } else {
	if (obj_glue (&clist) == RETURN_SUCCESS) {
	  ge_block_tree_CB   (TRUE);
	  ge_update_explorer ();
	  ge_block_tree_CB   (FALSE);
	  ge_expand_current  ();
	  ge_select_current  ();
	  set_dirtystate ();
	}
      }
    }
    break;
  case MenuObjsBreak:
    if (nb == 1 && cur_obj_typ == Q_Compound) {
      sprintf (message, "Break compound %d ?" ,cur_obj_id);
      if (yesno (message) == TRUE) {
	obj_break_compound (cur_obj_num);
	ge_update_explorer();
	/*	gg_drawgraph ();  canvas image is unchanged */
	set_dirtystate ();
      }
    } else {
      errmsg ("Only one compound must be selected");
    }
    break;
  case MenuObjsPrune:
    sprintf (message, "Prune %s %d ?" ,Qtype_name[cur_obj_typ] ,cur_obj_id);
    if (yesno (message) == TRUE) {
      i = cur_obj_num;
      if (obj_prune (i ,TRUE) == RETURN_SUCCESS) {
	ge_block_tree_CB   (TRUE);
	obj_make_current (i);
	ge_update_explorer ();
	ge_block_tree_CB   (FALSE);
	ge_expand_current  ();
	ge_select_current  ();
	set_dirtystate ();
      } else {
	errmsg ("MenuObjsPrune: object is not prunable");
      }
    }
    break;
  case MenuObjsInsert:
  encore:
    ret = gg_ask_expr ("id number" ,"Insert into compound" ,4 ,&did);
    if (ret == RETURN_FAILURE) {
      errmsg ("Error in evaluating expression");
      goto encore;
    } 
    if (ret == GTK_RESPONSE_CLOSE) break;
    id = (int) did;
    dest = obj_geom_get_num (Q_Compound ,id);
    if (dest >= 0) {
      i = cur_obj_num;
      gno = obj_get_typed_ancestor_id (dest ,Q_Graph);
      if (obj_append_child (i ,dest) == RETURN_SUCCESS) {
	ge_block_tree_CB   (TRUE);
	gg_switch_current_graph (gno);
	obj_make_current   (i);
	ge_update_explorer ();
	ge_block_tree_CB   (FALSE);
	ge_expand_current  ();
	ge_select_current  ();
	/*	gg_drawgraph ();  canvas image is unchanged */
	set_dirtystate ();
      }
    } else {
      sprintf (message, "Compound %d not found" ,id);
      errmsg (message);
      goto encore;
    }
    break;
  }
  ge_rpane_update ();
}




/************** COLLECTIVE and SELECTIVE UPDATE of OBJS *******************/

#define WU(uu) if (gg_get_int (uu))

#define U_OBJS_RESET 1001

static GtkWidget *u_color;
static GtkWidget *u_font;
static GtkWidget *u_just;
static GtkWidget *u_charsize;
static GtkWidget *u_rot;
static GtkWidget *u_linew;
static GtkWidget *u_lines;
static GtkWidget *u_fillcolor;
static GtkWidget *u_fillpattern;
static GtkWidget *u_arrow_end;
static GtkWidget *u_arrow_length   ,*u_arrow_type;
static GtkWidget *u_arrow_dL_ff    ,*u_arrow_lL_ff;
static GtkWidget *u_x1    	   ,*u_y1;
static GtkWidget *u_x2    	   ,*u_y2;
static GtkWidget *u_layer;

static GtkWidget *ge_objs_u_new (Qtype typ)
{
  GtkWidget *vb ,*ta;
  if (u_objs_dialog == NULL) {
    u_objs_dialog = gg_CreateAACDialog ("Update geometric objectss" ,ge_objs_u_AAC_CB ,0 ,0 ,&vb);
    gtk_dialog_add_buttons (GTK_DIALOG (u_objs_dialog) ,"Reset" ,U_OBJS_RESET ,NULL);
    gg_label_new (vb ,"Choose items to update in the selected objs");
    ta 	      	    = gg_frame     (vb ,"Objects appearance" ,&wheat ,4 ,5);
    u_color         = gg_check_new (ta, "Color");
    u_font          = gg_check_new (ta, "Font");
    u_just     	    = gg_check_new (ta, "Justification");
    u_charsize      = gg_check_new (ta, "Font size");
    u_rot     	    = gg_check_new (ta, "Rotation");
    u_linew         = gg_check_new (ta, "Line width");
    u_lines         = gg_check_new (ta, "Line style");
    u_fillcolor     = gg_check_new (ta, "Fill color");
    u_fillpattern   = gg_check_new (ta, "Fill pattern");
    u_arrow_end     = gg_check_new (ta, "Arrow end");
    u_arrow_length  = gg_check_new (ta, "Arrow length");
    u_arrow_type    = gg_check_new (ta, "Arrow type");
    u_arrow_dL_ff   = gg_check_new (ta, "d/L");
    u_arrow_lL_ff   = gg_check_new (ta, "l/L");
    u_x1   	    = gg_check_new (ta, "X1");
    u_y1   	    = gg_check_new (ta, "Y1");
    u_x2   	    = gg_check_new (ta, "X2");
    u_y2   	    = gg_check_new (ta, "Y2");

    ta 	      	    = gg_frame     (vb ,"Object layer" ,&wheat ,4,1);
    u_layer         = gg_check_new (ta, "Layer");
  }
  gtk_widget_show_all (u_objs_dialog);
  ge_objs_u_update (typ);
  return u_objs_dialog;
}

void ge_objs_u_update (Qtype typ)
{
  if (u_objs_dialog != NULL) {
    gtk_widget_set_sensitive (u_color 	     ,wg[COLOR][typ]);
    gtk_widget_set_sensitive (u_font 	     ,wg[STRING][typ]);
    gtk_widget_set_sensitive (u_just 	     ,wg[STRING][typ]);
    gtk_widget_set_sensitive (u_charsize     ,wg[STRING][typ]);
    gtk_widget_set_sensitive (u_rot          ,wg[ROT][typ]);
    gtk_widget_set_sensitive (u_linew        ,wg[LINE][typ]);
    gtk_widget_set_sensitive (u_lines        ,wg[LINE][typ]);
    gtk_widget_set_sensitive (u_fillcolor    ,wg[FILL][typ]);
    gtk_widget_set_sensitive (u_fillpattern  ,wg[FILL][typ]);
    gtk_widget_set_sensitive (u_arrow_end    ,wg[ARROW][typ]);
    gtk_widget_set_sensitive (u_arrow_length ,wg[ARROW][typ]);
    gtk_widget_set_sensitive (u_arrow_type   ,wg[ARROW][typ]);
    gtk_widget_set_sensitive (u_arrow_dL_ff  ,wg[ARROW][typ]);
    gtk_widget_set_sensitive (u_arrow_lL_ff  ,wg[ARROW][typ]);
    gtk_widget_set_sensitive (u_layer        ,wg[W_ALL][typ]);
  }
}

static int ge_obj_u_apply (Qtype typ ,int n ,QDobject *po)
{
  W (COLOR) {
    WU (u_color)       po->color    = gg_get_int (U.w_color);
  }
  W (STRING) {
    WU (u_font)        po->font     = gg_get_int  (U.w_font);
    WU (u_just)	       po->just     = get_just    (gg_get_int (U.w_just));
    WU (u_charsize)    po->charsize = gg_get_dble (U.w_charsize);
  }
  W (ROT) {
    WU (u_rot) 	       po->rot      = gg_get_int (U.w_rot);
  }
  W (LINE) {
    WU (u_linew)       po->linew    = gg_get_dble (U.w_linew);
    WU (u_lines)       po->lines    = gg_get_int  (U.w_lines);
  }
  W (FILL) {
    WU (u_fillcolor)   po->fillcolor   = gg_get_int (U.w_fillcolor);
    WU (u_fillpattern) po->fillpattern = gg_get_int (U.w_fillpattern);
  }
  W (ARROW) {
    WU (u_arrow_end)   	po->arrow_end    = gg_get_int  (U.w_arrow_end);
    WU (u_arrow_length)	po->arrow.type   = gg_get_int  (U.w_arrow_type);
    WU (u_arrow_type)  	po->arrow.length = gg_get_dble (U.w_arrow_length);
    WU (u_arrow_dL_ff) 	po->arrow.dL_ff  = gg_get_dble (U.w_arrow_dL_ff);
    WU (u_arrow_lL_ff) 	po->arrow.lL_ff  = gg_get_dble (U.w_arrow_lL_ff);
  }
  W (W_ALL) {
    WU (u_layer)        po->layer        = gg_get_int  (U.w_layer);
  }
  /* This is broken, it does not take into account
   *  pmask or if loctyp is changed */
  WU (u_x1) po->x1 = gg_get_dble (U.xy1.spin1);
  WU (u_y1) po->y1 = gg_get_dble (U.xy1.spin2);
  WU (u_x2) po->x2 = gg_get_dble (U.xy2.spin1);
  WU (u_y2) po->y2 = gg_get_dble (U.xy2.spin2);

  return RETURN_SUCCESS;
}



static void ge_objs_u_AAC_CB (GtkWidget *widget ,gint reponse)
{
  static int loctyp;
  if (reponse == U_OBJS_RESET) {
    gg_set_int (u_color        ,FALSE);
    gg_set_int (u_font 	       ,FALSE);
    gg_set_int (u_just 	       ,FALSE);
    gg_set_int (u_charsize     ,FALSE);
    gg_set_int (u_rot 	       ,FALSE);
    gg_set_int (u_linew        ,FALSE);
    gg_set_int (u_lines        ,FALSE);
    gg_set_int (u_fillcolor    ,FALSE);
    gg_set_int (u_fillpattern  ,FALSE);
    gg_set_int (u_arrow_end    ,FALSE);
    gg_set_int (u_arrow_length ,FALSE);
    gg_set_int (u_arrow_type   ,FALSE);
    gg_set_int (u_arrow_dL_ff  ,FALSE);
    gg_set_int (u_arrow_lL_ff  ,FALSE);
    gg_set_int (u_x1  	       ,FALSE);
    gg_set_int (u_y1  	       ,FALSE);
    gg_set_int (u_x2  	       ,FALSE);
    gg_set_int (u_y2  	       ,FALSE);
    gg_set_int (u_layer	       ,FALSE);
  } else if (reponse == GTK_RESPONSE_ACCEPT || reponse == GTK_RESPONSE_APPLY) {
    int n = CURRENT;
    Qtype typ;
    gint id ,num;
    GtkTreeIter iter;
    GtkTreeSelection *select;
    GList *lpath = NULL;
    Ge_tree *getree;
    getree = get_getree ();
    int nb = ge_selection_is_homogeneous (&typ);
    if (nb < 1) {
      errmsg ("Selection must have one or several objects of same type only");
      return;
    }
    select = gtk_tree_view_get_selection (GTK_TREE_VIEW (getree->view));
    lpath  = gtk_tree_selection_get_selected_rows (select ,&(getree->model));
    while (lpath != NULL) {
      if (gtk_tree_model_get_iter (getree->model ,&iter ,lpath->data)) {
	gtk_tree_model_get (getree->model ,&iter
			    ,GE_TREE_ID   ,&id
			    ,-1);
	num = obj_geom_get_num (typ ,id);
	loctyp = objs[num].loctyp;
	ge_obj_u_apply (typ ,n ,&(objs[num]));
	lpath = g_list_next (lpath);
      }
    }
    gg_update_layers_hbox ();
    set_dirtystate ();
    gg_drawgraph  ();
  }
  if (reponse == GTK_RESPONSE_ACCEPT || reponse == GTK_RESPONSE_CLOSE) {
    gtk_widget_hide (u_objs_dialog);
  }
  wu_update_on (FALSE);
}
