/**
 *  File gg_events.c
 *  Transcripted from Motif to GTK+ by P. Vincent to
 *  replace events.c
 *
 *  Shortcuts :
 *      Gnome use <Ctrl><Alt> shortcuts, so I change
 *      into      <Shift><Ctrl> to draw boxes, ellipses...
 *
 *  Added nonlinera_extended by Nicola Ferralis
 */

#include <cmath.h>
#include <string.h>

#include <gdk/gdkkeysyms.h>
#include <gtk/gtk.h>

#include "defines.h"
#include "globals.h"
#include "utils.h"
#include "graphutils.h"
#include "noxprotos.h"

#include "gg_gtkdrv.h"
#include "gg_protos.h"
#include "ge_protos.h"
#include "ng_objects.h"
#include "nn_tree.h"
#include "nn_cmd.h"
#include "zc.h"

#include "gg_events.h"


// Pour deboguer
void bbb (char *s)
{
  ppp (s);
  if (cur_obj_num >= 0) {
    printf ("\t loctyp=%d\n" ,objs[cur_obj_num].loctyp);
    printf ("\t bb=( %5f , %5f , %5f , %5f )\n"
	    ,objs[cur_obj_num].bb.xv1
	    ,objs[cur_obj_num].bb.xv2
	    ,objs[cur_obj_num].bb.yv1
	    ,objs[cur_obj_num].bb.yv2 );
  }
}

static gboolean Shift_pressed = FALSE;
static gboolean Ctl_pressed   = FALSE;

static CanvasActionFlag a1 = DO_NOTHING;

extern int regiontype;

typedef struct {
  VPoint vp;
  int x ,y;
} Anchor;

static Anchor anchor[3];   /* used for points in a set or polyline */
static int  is_polyline;           /* T if current obj is a polyline */

static WPoint wp ,wp_new;

static VPoint  anchor_vp = {0.0, 0.0};
static int     anchor_x = 0;
static int     anchor_y = 0;

static VPoint  vp;           /* pointer coordinates in viewport coord. */
static int     x, y;         /* pointer coordinates in device coord. */

static VVector shift;
static view    bb;           /* bounding box */
static int     pmask;        /* properties mask */
static int     cg;           /* current graph */

static  QDobject tmp_obj;      /* for Q_Polyline */

static int    track_setno ,track_obj;
static int    track_loc;       /* the number of the point to be tracked */
static int    track_move_dir;
static int    track_end;
int    cursortype = 0;
/* Compound */
static GList *clist = NULL;

static void gg_select_line12 (int xs ,int ys ,int erase ,int a1move);

/** for region, area and perimeter computation
 */
#define MAX_POLY_POINTS 200
static int region_pts = 0;
static int iax[MAX_POLY_POINTS];
static int iay[MAX_POLY_POINTS];
static WPoint region_wps[MAX_POLY_POINTS];

static void ev_clean_up (void);


static gboolean all_obj = TRUE;

/**
 *  First sequence to prepare to mouse  action
 *  $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
 */

/* Must conform to CanvasActionFlag enum in gg_events.h */
static SetActionType sacts [] = {
  /*   act           cursor  message1                               message2  */
  { DO_NOTHING    	,-1 ,NULL                                      ,NULL}
  ,{ZOOM_1ST      	, 0 ,"Pick first corner for zoom"              ,NULL}
  ,{ZOOM_2ND      	,-1 ,"Pick second corner for zoom"             ,NULL}
  ,{ZOOMX_1ST     	, 0 ,"Pick first point for zoom along X-axis"  ,NULL}
  ,{ZOOMX_2ND     	,-1 ,"Pick second point for zoom along X-axis" ,NULL}
  ,{ZOOMY_1ST     	, 0 ,"Pick first point for zoom along Y-axis"  ,NULL}
  ,{ZOOMY_2ND     	,-1 ,"Pick second point for zoom along Y-axis" ,NULL}
  ,{VIEW_1ST      	, 0 ,"Pick first corner of viewport"  	       ,NULL}
  ,{VIEW_2ND      	,-1 ,"Pick second corner of viewport" 	       ,NULL}
  ,{EDIT_OBJ      	, 4 ,"Pick object to edit"            	       ,NULL}
  ,{DEL_OBJ       	, 3 ,"Pick object to delete"          	       ,NULL}
  ,{MOVE_OBJ_1ST  	, 1 ,"Pick"                               ,"to move"}
  ,{COPY_OBJ_1ST  	, 1 ,"Pick"                               ,"to copy"}
  ,{MAKE_OBJ_1ST  	, 0 ,"Pick first point"                  ," to make"}
  ,{SCALE_OBJ_1ST      	, 1 ,"Pick handle of object to scale"  	       ,NULL}
  ,{MOVE_OBJ_2ND  	, 4 ,"Place"                                ,"moved"}
  ,{COPY_OBJ_2ND  	, 4 ,"Place"                               ,"copied"}
  ,{MAKE_OBJ_2ND  	, 0 ,"Place 2nd point "                  ," to make"}
  ,{MAKE_POLYLINE  	, 0 ,"Place new polyline point "         ," to make"}
  ,{SCALE_OBJ_2ND      	, 1 ,"Pick final location of handle"  	       ,NULL}
  ,{AUTO_NEAREST  	, 0 ,"Click to autoscale on nearest set"       ,NULL}
  ,{DEL_POINT     	, 3 ,"DELETE a point in a set of a polyline"   ,NULL}
  ,{MOVE_POINT1ST 	, 5 ,"Pick point to move" 	       	       ,NULL}
  ,{MOVX_POINT1ST 	, 5 ,"Pick point to move along Ox"     	       ,NULL}
  ,{MOVY_POINT1ST 	, 5 ,"Pick point to move along Oy"     	       ,NULL}
  ,{MOVE_POINT2ND 	,-1 ,"Place final location"	       	       ,NULL}
  ,{ADD_POINT_1ST     	, 5 ,"Pick the point to break/add"             ,NULL}
  ,{ADD_POINT_2ND     	,-1 ,"Place the new point"                     ,NULL}
  ,{SEL_POINT     	, 0 ,"Pick reference point"      	       ,NULL}
  ,{DEF_REGION1ST 	, 0 ,"Pick first point for region"  	       ,NULL}
  ,{DEF_REGION2ND 	,-1 ,"Pick second point for region" 	       ,NULL}
  ,{DEF_REGION    	, 0 ,"Define region"                	       ,NULL}
  ,{GLUE_OBJS_1ST    	, 0 ,"Pick handle to start gluing a compound " ,NULL}
  ,{GLUE_OBJS_2ND    	, 0 ,"Tag with mouse1 ,glue with mouse2"       ,NULL}
  ,{BREAK_COMPOUND    	, 0 ,"select compound to break"                ,NULL}
  ,{PEAK_POS      	, 0 ,"Click on the approximate position of the maximum of the peak"    ,NULL}
  ,{PEAK_POS1     	, 0 ,"Click on the approximate position of the maximum of the peak #1" ,NULL}
  ,{PEAK_POS2     	, 0 ,"Click on the approximate position of the maximum of the peak #2" ,NULL}
  ,{PEAK_POS1B    	, 0 ,"Click on the approximate position of the maximum of the peak #1" ,NULL}
  ,{PEAK_POS2B    	, 0 ,"Click on the approximate position of the maximum of the peak #2" ,NULL}
  ,{PEAK_POS3B    	, 0 ,"Click on the approximate position of the maximum of the peak #3" ,NULL}
  ,{PLACE_LABEL    	, 0 ,"Click on contour line to place label"    ,NULL}
  ,{KILL_LABEL    	, 3 ,"Click on contour line label to DELETE"   ,NULL}
  ,{COMP_AREA     	, 0 ,"Compute area"                 	       ,NULL}
  ,{COMP_PERIMETER	, 0 ,"Compute perimeter"            	       ,NULL}
  ,{DISLINE1ST    	, 0 ,"Pick start of line for distance computation" ,NULL}
  ,{DISLINE2ND    	, 0 ,"Pick ending point"}
};

/**
 *  Set the action_flag to the desired action
 *  (actions are defined in gg_events.h in enum CanvasActionFlag)
 *  and update the status message according to SetActionType.
 *  if act == DO_NOTHING, cleanup the results  from previous actions.
 */
void gg_set_action (CanvasActionFlag act)
{
  char buf[132];
  SetActionType a;

  if (act != sacts[act].act) {
    sprintf (buf ,"gg_set_action internal error act=%d != %d"
	     ,act ,sacts[act].act);
    errmsg (buf);
  }
  a = sacts[act];
  gg_set_cursor (a.cursor);

  if (act == DO_NOTHING) {
    ev_clean_up ();
  } else  if (a.message2 == NULL) {
    gg_status_message (a.message1);
  } else {
    if (all_obj) {
      sprintf (buf ,"%s object %s" ,a.message1  ,a.message2);
    } else {
      sprintf (buf ,"%s %s %d %s" ,a.message1 ,Qtype_name[cur_obj_typ] ,cur_obj_id ,a.message2);
    }
    gg_status_message (buf);
  }
  a1 = act;            /* the action flag is stored in a1 */
  switch (a1) {
  case EDIT_OBJ:
  case DEL_OBJ:
  case MOVE_OBJ_1ST:
  case COPY_OBJ_1ST:
    if (all_obj) {
      draw_branch_handles (0);
    } else {
      draw_curobj_handles ();
    }
    if (a1 == DEL_OBJ) clear_typed_handles (Q_Colorbar);
    break;
  case DEL_POINT:
  case MOVE_POINT1ST:
  case MOVX_POINT1ST:
  case MOVY_POINT1ST:
  case ADD_POINT_1ST:
    if (all_obj) {
      draw_typed_handles (Q_Polyline);
    } else {
      draw_curobj_handles ();
    }
    break;
  case SCALE_OBJ_1ST:
    if (all_obj) {
      draw_branch_handles (0);
      clear_typed_handles (Q_Compound);
      clear_typed_handles (Q_String);
      clear_typed_handles (Q_LegendBox);
    } else {
      draw_curobj_handles ();
    }
    break;
  case GLUE_OBJS_1ST:
    draw_branch_handles (0);
    break;
  case GLUE_OBJS_2ND:
    break;
  case BREAK_COMPOUND:
    if (all_obj) {
      draw_typed_handles (Q_Compound);
    } else {
      draw_curobj_handles ();
    }
    break;
  default:
    clear_all_handles ();
    break;
  }
}

/**
 *   Callback to call gg_set_action
 */
void gg_set_action_CB (GtkWidget *widget ,gpointer p)
{
  int act = GPOINTER_TO_INT (p);
  gg_set_action (DO_NOTHING);
  all_obj = TRUE;
  gg_set_action (act);
}

void gg_clear_and_set_action (CanvasActionFlag act)
{
  gg_set_action (DO_NOTHING);
  gg_set_action (act);
}

/**
 *
 */
void gg_act_on_cur_obj_CB (GtkWidget *widget ,gpointer p)
{
  int act = GPOINTER_TO_INT (p);
  gg_set_action (DO_NOTHING);
  all_obj = FALSE;
  gg_set_action (act);
}

void gg_make_obj_CB (GtkWidget *widget ,gpointer p)
{
  int i;
  cur_obj_typ = GPOINTER_TO_INT (p);
  gg_set_action (DO_NOTHING);
  all_obj = FALSE;
  i = obj_t_get_template_num (cur_obj_typ);
  ge_obj_apply (i ,0);
  gg_set_action (MAKE_OBJ_1ST);
}

static void ev_clean_up (void)
{
  int i ,x_new ,y_new;
  switch (a1) {
  case ZOOM_2ND:
  case ZOOMX_2ND:
  case ZOOMY_2ND:
  case VIEW_2ND:
    gg_select_region (anchor_x, anchor_y, x, y, 0);
    break;
  case MOVE_OBJ_2ND:
    gg_slide_region (bb, x - anchor_x, y - anchor_y, 0 ,1);
    break;
  case DEF_REGION2ND:
    gg_select_line (anchor_x, anchor_y, x, y, 0);
    break;
  case DEF_REGION:
    gg_select_line (anchor_x, anchor_y, x, y, 0);
    for (i = 0; i < region_pts - 1; i++) {
      gg_select_line (iax[i], iay[i], iax[i + 1], iay[i + 1], 0);
    }
    break;
  case MOVE_POINT2ND:
  case ADD_POINT_2ND:
    y_new = (track_move_dir == MOVE_POINT_X) ? anchor[1].y : y;
    x_new = (track_move_dir == MOVE_POINT_Y) ? anchor[1].x : x;
    gg_select_line12 (x_new ,y_new ,0 ,a1 == MOVE_POINT2ND);
    break;
  default:
    break;
  }
  gg_set_cursor (-1);
  gg_status_message (NULL);
}


/**
 *  Response to mouse actions
 *  $$$$$$$$$$$$$$$$$$$$$$$$$$
 *
 *  The function gg_myproc manage the canvas events according to
 *  the value of the action flag a1
 *
 *  The clicks on the Button1 on the mouse are managed according to
 *  the Button1_Pressed array (see gg_events.h typedef for details)
 *
 *  The current object status is stored in GLOBALs
 *      (cur_obj_typ , cur_obj_id , cur_obj_num)
 *
 *  For multiclicks actions anchor position is stored in file globals
 *      (anchor_vp ,anchor_x ,anchor_y),
 *  the  current position in (vp ,x ,y) and the bouding box in bb
 *
 */

static int ev_axis_clicked  (int gno, VPoint vp, int *axisno ,int pop);
static int ev_title_clicked (int gno, VPoint vp);

static int  ev_sel_region     (int p1);
static int  ev_newworld       (int axes);
static int  ev_view_2nd       (int p1);
static int  ev_del_obj        (int p1);
static int  ev_edit_obj       (int p1);
static int  ev_find_obj       (int p1);
static int  ev_find_handle    (int p1);
static int  ev_find_point     (int p1);
static int  ev_make_obj_1st   (int p1);
static int  ev_place_obj      (int p1);
static int  ev_place_polyline (int p1);
static int  ev_move_point2nd  (int dir);
static int  ev_add_point2nd   (int p1);
static void ev_move_graph     (int gno ,VVector shift);
static int  ev_place_locator  (int p1);
static int  ev_sel_line       (int p1);
static int  ev_def_region2nd  (int p1);
static int  ev_def_region     (int p1);
static int  ev_glue_objs_1st  (int p1);
static int  ev_glue_objs_2nd  (int p1);
static void ev_glue_objs_finalize (void);
static int  ev_break_compound (int p1);
static int  ev_nonlin_fit     (int p1);
static int  ev_place_label    (int p1);
static int  ev_kill_label     (int p1);

/* Must conform to CanvasActionFlag enum in gg_events.h */
BtActionType Button1_Pressed[] = {
  /*  act a1         next_act a2   order  fun              arg p1  */
    { DO_NOTHING     ,DO_NOTHING    ,12  ,NULL             ,0          }
    ,{ZOOM_1ST       ,ZOOM_2ND      ,21  ,ev_sel_region    ,ZOOM_2ND   }
    ,{ZOOM_2ND       ,DO_NOTHING    ,21  ,ev_newworld      ,ALL_AXES   }
    ,{ZOOMX_1ST      ,ZOOMX_2ND     ,21  ,ev_sel_region    ,ZOOMX_2ND  }
    ,{ZOOMX_2ND      ,DO_NOTHING    ,21  ,ev_newworld      ,ALL_X_AXES }
    ,{ZOOMY_1ST      ,ZOOMY_2ND     ,21  ,ev_sel_region    ,ZOOMY_2ND  }
    ,{ZOOMY_2ND      ,DO_NOTHING    ,21  ,ev_newworld      ,ALL_Y_AXES }
    ,{VIEW_1ST       ,VIEW_2ND      ,21  ,ev_sel_region    ,ALL_AXES   }
    ,{VIEW_2ND       ,DO_NOTHING    ,21  ,ev_view_2nd      ,ALL_AXES   }
    ,{EDIT_OBJ       ,EDIT_OBJ      ,12  ,ev_edit_obj      ,TRUE }
    ,{DEL_OBJ        ,DEL_OBJ       ,12  ,ev_del_obj       ,0 }
    ,{MOVE_OBJ_1ST   ,MOVE_OBJ_2ND  ,12  ,ev_find_obj      ,TRUE}
    ,{COPY_OBJ_1ST   ,COPY_OBJ_2ND  ,12  ,ev_find_obj      ,FALSE}
    ,{MAKE_OBJ_1ST   ,MAKE_OBJ_2ND  ,12  ,ev_make_obj_1st  ,FALSE}
    ,{SCALE_OBJ_1ST  ,SCALE_OBJ_2ND ,12  ,ev_find_handle   ,0 }
    ,{MOVE_OBJ_2ND   ,MOVE_OBJ_1ST  ,12  ,ev_place_obj     ,0 }
    ,{COPY_OBJ_2ND   ,COPY_OBJ_1ST  ,12  ,ev_place_obj     ,0 }
    ,{MAKE_OBJ_2ND   ,MAKE_OBJ_1ST  ,12  ,ev_place_obj     ,0 }
    ,{MAKE_POLYLINE  ,MAKE_POLYLINE ,12  ,ev_place_polyline ,0 }
    ,{SCALE_OBJ_2ND  ,SCALE_OBJ_1ST ,12  ,ev_place_obj     ,0 }
    ,{AUTO_NEAREST   ,DO_NOTHING    ,12  ,ev_find_point    ,0 }
    ,{DEL_POINT      ,DEL_POINT     ,12  ,ev_find_point    ,0 }
    ,{MOVE_POINT1ST  ,MOVE_POINT2ND ,12  ,ev_find_point    ,MOVE_POINT_XY }
    ,{MOVX_POINT1ST  ,MOVE_POINT2ND ,12  ,ev_find_point    ,MOVE_POINT_X }
    ,{MOVY_POINT1ST  ,MOVE_POINT2ND ,12  ,ev_find_point    ,MOVE_POINT_Y }
    ,{MOVE_POINT2ND  ,0             ,12  ,ev_move_point2nd ,0}
    ,{ADD_POINT_1ST  ,ADD_POINT_2ND ,12  ,ev_find_point    ,0 }
    ,{ADD_POINT_2ND  ,ADD_POINT_1ST ,12  ,ev_add_point2nd  ,0 }
    ,{SEL_POINT      ,DO_NOTHING    ,12  ,ev_place_locator ,0 }
    ,{DEF_REGION1ST  ,DEF_REGION2ND ,12  ,ev_sel_line      ,0 }
    ,{DEF_REGION2ND  ,DO_NOTHING    ,21  ,ev_def_region2nd ,0 }
    ,{DEF_REGION     ,DEF_REGION    ,12  ,ev_def_region    ,0 }
    ,{GLUE_OBJS_1ST  ,GLUE_OBJS_2ND ,12  ,ev_glue_objs_1st ,0 }
    ,{GLUE_OBJS_2ND  ,GLUE_OBJS_2ND ,12  ,ev_glue_objs_2nd ,0 }
    ,{BREAK_COMPOUND ,DO_NOTHING    ,12  ,ev_break_compound,0 }
    ,{PEAK_POS       ,DO_NOTHING    ,12  ,ev_nonlin_fit    ,0 }
    ,{PEAK_POS1      ,PEAK_POS2     ,12  ,ev_nonlin_fit    ,0 }
    ,{PEAK_POS2      ,DO_NOTHING    ,12  ,ev_nonlin_fit    ,0 }
    ,{PEAK_POS1B     ,PEAK_POS2B    ,12  ,ev_nonlin_fit    ,0 }
    ,{PEAK_POS2B     ,PEAK_POS3B    ,12  ,ev_nonlin_fit    ,0 }
    ,{PEAK_POS3B     ,DO_NOTHING    ,12  ,ev_nonlin_fit    ,0 }
    ,{PLACE_LABEL    ,PLACE_LABEL   ,12  ,ev_place_label   ,0 }
    ,{KILL_LABEL     ,KILL_LABEL    ,12  ,ev_kill_label    ,0 }
//    ,{COMP_AREA      ,DO_NOTHING    ,12  ,ev_to_be_done    ,0 }
//    ,{COMP_PERIMETER ,DO_NOTHING    ,12  ,ev_to_be_done    ,0 }
//    ,{DISLINE1ST     ,DO_NOTHING    ,12  ,ev_to_be_done    ,0 }
//    ,{DISLINE2ND     ,DO_NOTHING    ,12  ,ev_to_be_done    ,0 }
};


/**
 *  Response to  shortcuts
 *  $$$$$$$$$$$$$$$$$$$$$$
 */
ShortcutType Shortcut[] = {
  {  GDK_Escape ,0   ,MAKE_OBJ_1ST   ,Q_Undef   }
  ,{ GDK_B 	,TRUE ,MAKE_OBJ_1ST  ,Q_Box     }
  ,{ GDK_E 	,TRUE ,MAKE_OBJ_1ST  ,Q_Arc     }
  ,{ GDK_L 	,TRUE ,MAKE_OBJ_1ST  ,Q_Line    }
  ,{ GDK_T 	,TRUE ,MAKE_OBJ_1ST  ,Q_String  }
  ,{ GDK_v 	,TRUE ,VIEW_1ST      ,Q_Undef   }
};


/**
 *  Decode keyboard and mouse actions
 *  $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
 */

static int n_handle_found;  /* the No of handle returned by find_handle */

void gg_myproc (GtkWidget *widget ,GdkEvent *event ,gpointer data)
{
  BtActionType a;
  static gint newg;
  static guint32 lastc = 0;
  static gboolean dbl_click;

  static int i;   /* for objects */

  int x_new ,y_new;
  int axisno;
  int pln;

  cg = get_cg();

  switch (event->type) {

  /*************** GDK_MOTION_NOTIFY ************/
  case GDK_MOTION_NOTIFY:
    x = (gint) event->motion.x;
    y = (gint) event->motion.y;
    if (cursortype != 0) gg_crosshair_motion (x, y);
    vp = gg_gtk2VPoint (x, y);
    gg_getpoints (&vp);
    if (focus_policy == FOCUS_FOLLOWS) {
      if ((newg = gg_next_graph_containing (-1, vp)) != cg) {
	gg_switch_current_graph (newg);
	cg = newg;
      }
    }
    switch (a1) {
    case DO_NOTHING:
      break;
    case MAKE_OBJ_2ND:
    case MAKE_POLYLINE:
      if (cur_obj_typ == Q_Arc) {
	/* to take into account pmask */
	int pmask = objs[cur_obj_num].pmask;
	gg_select_region_scale (anchor_x ,anchor_y ,x ,y ,1 ,pmask);
      } else {
	gg_select_region_or_line (cur_obj_typ ,anchor_x ,anchor_y ,x ,y ,1);
      }
      break;
    case VIEW_2ND:
    case ZOOM_2ND:
    case ZOOMX_2ND:
    case ZOOMY_2ND:
      gg_select_region (anchor_x ,anchor_y ,x ,y ,1);
      break;
    case SCALE_OBJ_2ND:
      gg_select_region_scale (anchor_x ,anchor_y ,x ,y ,1 ,pmask);
      break;
    case COPY_OBJ_2ND:
    case MOVE_OBJ_2ND:
      gg_slide_region (bb ,x - anchor_x ,y - anchor_y ,1 ,1);
      break;
    case DEF_REGION2ND:
      gg_select_line (anchor_x ,anchor_y ,x ,y ,1);
      break;
    case DEF_REGION:
      if (region_pts > 0) {
	gg_select_line (anchor_x ,anchor_y ,x ,y ,1);
      }
      break;
    case MOVE_POINT2ND:
    case ADD_POINT_2ND:
      y_new = (track_move_dir == MOVE_POINT_X) ? anchor[1].y : y;
      x_new = (track_move_dir == MOVE_POINT_Y) ? anchor[1].x : x;
      gg_select_line12 (x_new ,y_new ,1 ,a1 == MOVE_POINT2ND);
      break;
    default:
      return;
      break;
    }
    break;

    /*************** GDK_BUTTON_PRESS ************/
  case GDK_BUTTON_PRESS:
    x = (gint) event->button.x;
    y = (gint) event->button.y;
    vp = gg_gtk2VPoint (x ,y);
    gg_getpoints (&vp);
    switch (event->button.button) {

    /*************** Button1 ************/
    case Button1:
      /* first, determine if it's double click */
      dbl_click = (event->button.time - lastc  < CLICKINT) ? TRUE : FALSE;
      lastc = event->button.time;

      a = Button1_Pressed[a1];
      if (a1 == DO_NOTHING) {
	if (dbl_click && allow_dc) {
	  /* Double click */
	  if (gg_focus_clicked (cg ,vp ,&anchor_vp) == TRUE) {
	    gg_VPoint2dev (anchor_vp ,&anchor_x ,&anchor_y);
	    gg_set_action (VIEW_2ND);
	    gg_select_region (anchor_x ,anchor_y ,x ,y ,0);
	  } else if (ev_axis_clicked (cg ,vp ,&axisno ,1) == TRUE) {
	    ;
	  } else if (ev_title_clicked (cg, vp) == TRUE) {
	    ;
	  } else {
	    ev_edit_obj (TRUE);
	  }
	} else {
	  if (focus_policy == FOCUS_CLICK) {
	    if ((newg = gg_next_graph_containing (cg ,vp)) != cg) {
	      gg_switch_current_graph (newg);
	      obj_tid_make_current (Q_Graph ,newg ,Q_Project ,-1);
	      ge_unselect_all   ();
	      ge_select_current ();
	    }
	  }
	}
      } else {
	/* Simple click do action "a"  */
	if (a.order == 12) {
	  if (a.fun (a.p1) == TRUE) {
	    gg_set_action (a.a2);
	  }
	} else if  (a.order == 21) {
	  gg_set_action (a.a2);
	  a.fun (a.p1);
	}
      }
      break;     /* Button1 */

      /*************** Button2 ************/
    case Button2:
      switch (a1) {
      case GLUE_OBJS_2ND:
	ev_glue_objs_finalize ();
	ge_update_explorer ();
	gg_set_action (DO_NOTHING);
	break;
      case MAKE_POLYLINE:
	ev_place_polyline (0);
	pln = tmp_obj.nxy;
	cur_obj_num = obj_t_next (Q_Polyline ,&cur_obj_id ,cur_parent_typ ,cur_parent_id);
	objs[cur_obj_num].pmask = template_pmask_get (Q_Polyline);
	obj_set_xy (cur_obj_num ,tmp_obj.x ,tmp_obj.y ,pln);
	ge_update_explorer ();
	gg_drawgraph ();
	gg_set_action (DO_NOTHING);
	break;
      default:
	break;
      }
      break;     /* Button2 */

    /*************** Button3 ************/
    case Button3:
      switch (a1) {
      case DO_NOTHING:
	break;
      case DEF_REGION:
	/* end region definition */
	gg_select_line (x, y, iax[0], iay[0], 0);
	load_poly_region (nr, cg, region_pts, region_wps);
	gg_set_action (DO_NOTHING);
	gg_drawgraph ();
	break;
      case MOVE_OBJ_1ST:
	gg_set_action (DO_NOTHING);
	break;
      default:
	gg_set_action (DO_NOTHING);
	break;
      }
      break;     /* Button3 */
    }      /* switch (event->button.button) */


    /*************** GDK_KEY_PRESS ************/
  case GDK_KEY_PRESS:
    switch (event->key.keyval) {
    case GDK_Control_L:
      /*     case GDK_Control_R:
       *     it seems that my Dell AZERTY keyboard does not conform to
       *    /usr/include/gtk-2.0/gdk/gdkkeysyms.h
       */
    case 0xffe7:
      Ctl_pressed = TRUE;
      break;
    case GDK_Shift_L:
    case GDK_Shift_R:
      Shift_pressed = TRUE;
      break;

    default:
      break;
    }
    break;

    /*************** GDK_KEY_RELEASE  ************/
  case GDK_KEY_RELEASE:
    switch (event->key.keyval) {
    case GDK_Escape:
      gg_set_action (DO_NOTHING);
      break;
    case GDK_Control_L:
      /*     case GDK_Control_R:
       *     it seems that my Dell AZERTY keyboard does not conform to
       *    /usr/include/gtk-2.0/gdk/gdkkeysyms.h
       */
    case 0xffe7:         /* see above. Replace GDK_Control_R */
      Ctl_pressed = FALSE;
      break;
    default:
      if (Ctl_pressed) {
	for (i = 1; i <6; i++) {
	  if (event->key.keyval == Shortcut[i].key) {
	    gg_set_action (DO_NOTHING);
	    cur_obj_typ = Shortcut[i].typ;
	    gg_set_action (Shortcut[i].action);
	    break;
	  }
	}
      }
    break;
    }

    /*************** unused GDK enumeration ************/
  default:
    break;
  }
}

/**
 *  Functions to act the mouse and keyboard action
 *  $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
 */

static void ev_anchor_vp (int n ,VPoint vp ,int i)
{
  int xtmp ,ytmp;
  VPoint vpshift;
  obj_get_shift (i ,&vpshift);        /* shift is (0.,0.) if i<0 */
  anchor[n].vp.x = vp.x + vpshift.x;
  anchor[n].vp.y = vp.y + vpshift.y;
  gg_VPoint2dev (anchor[n].vp ,&xtmp ,&ytmp);
  anchor[n].x = xtmp;
  anchor[n].y = ytmp;
}


/**
 * Store as anchor "n" the  point "loc" defined
 * for Q_Set:      use cg and track_setno
 * for Q_Polyline: use objs[i].xy[]
 *
 */
static void ev_anchor3 (int n ,int loc ,int i)
{
  int nxy;
  VPoint vploc;
  WPoint wploc;
  if (obj_is_active (i) == FALSE) return;
  if (loc < 0) loc = (n == 0) ? 1 : 0;
  nxy = objs[i].nxy;
  if (objs[i].typ == Q_Polyline) {
    obj_polyline_get_vp (i ,loc ,&vploc);
    ev_anchor_vp (n ,vploc ,i);
  } else if (objs[i].typ == Q_Set) {
    nxy = getsetlength (cg ,track_setno);
    if (loc >= nxy) loc = nxy - 1;
    get_point (cg ,track_setno ,loc ,&wploc);
    vploc = Wpoint2Vpoint (wploc);
    ev_anchor_vp (n ,vploc ,-1);  /* -1: no shift for sets */
  }
  track_end = nxy - 1;
}

/**
 * Set anchor_vp, anchor_x and anchor_y
 */
static void ev_anchor_point (int curx ,int cury ,VPoint curvp)
{
  anchor_vp = curvp;
  anchor_x = curx;
  anchor_y = cury;
}


static int ev_sel_region (int action)
{
  ev_anchor_point (x, y, vp);
  gg_set_action (action);
  gg_select_region (anchor_x, anchor_y, x, y, 0);
  return TRUE;
}


static int ev_newworld (int axes)
{
  VPoint vp1 = anchor_vp;
  VPoint vp2 = vp;
  int cg     = get_cg ();
  world w ,wtmp;

  if (vp1.x == vp2.x && (axes == ALL_AXES || axes == ALL_X_AXES)) {
    errmsg ("Zoomed rectangle is zero along X ,zoom cancelled");
    return FALSE;
  }
  if (vp1.y == vp2.y && (axes == ALL_AXES || axes == ALL_Y_AXES)) {
    errmsg ("Zoomed rectangle is zero along Y ,zoom cancelled");
    return FALSE;
  }
  if ((axes == ALL_AXES || axes == ALL_X_AXES) &&
      get_graph_type (cg) == GRAPH_POLAR) {
    errmsg ("Only Y-zoom for polar graphs, zoom cancelled");
    return FALSE;
  }
  view2world (vp1.x ,vp1.y ,&w.xg1 ,&w.yg1);
  view2world (vp2.x ,vp2.y ,&w.xg2 ,&w.yg2);
  if (w.xg1 > w.xg2) {
    fswap (&w.xg1 ,&w.xg2);
  }
  if (w.yg1 > w.yg2) {
    fswap (&w.yg1 ,&w.yg2);
  }
  if (is_graph_active(cg)) {
    get_graph_world (cg ,&wtmp);
    switch (axes) {
    case ALL_AXES:
      wtmp.xg1 = w.xg1;
      wtmp.xg2 = w.xg2;
      wtmp.yg1 = w.yg1;
      wtmp.yg2 = w.yg2;
      break;
    case ALL_X_AXES:
      wtmp.xg1 = w.xg1;
      wtmp.xg2 = w.xg2;
      break;
    case ALL_Y_AXES:
      wtmp.yg1 = w.yg1;
      wtmp.yg2 = w.yg2;
      break;
    default:
      return FALSE;
      break;
    }
    set_graph_world (cg ,wtmp);
    autotick_axis (cg ,axes);
    gg_drawgraph ();
  }
  return TRUE;
}

static int ev_view_2nd (int dummy)
{
  view v ,old_v;
  get_graph_viewport (cg, &old_v);         /*  undo/redo */
  v.xv1 = MIN2(vp.x, anchor_vp.x);
  v.yv1 = MIN2(vp.y, anchor_vp.y);
  v.xv2 = MAX2(vp.x, anchor_vp.x);
  v.yv2 = MAX2(vp.y, anchor_vp.y);
  set_graph_viewport (cg, v);
  gg_drawgraph ();
  /*  undo/redo */
  nn_cmd_undo_start (TRUE ,cg);
  nn_cmd_prefix ("@   ");
  nn_cmd_view  (&old_v ,v ,"%s view %f, %f, %f, %f\n");
  nn_cmd_undo_end (TRUE ,cg);
  return TRUE;
}


static int ev_del_obj (int dummy)
{
  int gno ,setno ,id;
  if (find_obj (vp ,all_obj ,FALSE ,TRUE ,FALSE ,FALSE ,&bb ,&gno ,&setno)) {
    char message[32];
    id = (cur_obj_typ == Q_Set) ? setno : objs[cur_obj_num].id;
    sprintf (message, "Kill %s %d?" ,Qtype_name[cur_obj_typ] ,id);
    if (yesno (message) == TRUE) {

      if (cur_obj_typ == Q_Set) {
	killset (gno, setno);
	ge_kill_set (gno ,setno);
      }
      nn_kill_branch (cur_obj_num);
      set_dirtystate  ();
      gg_drawgraph ();
      ge_update_explorer();
      return TRUE;
    }
  }
  return FALSE;
}

static int ev_edit_obj (int p1)
{
  int gno ,setno;
  if (find_obj (vp ,all_obj ,p1 ,p1 ,p1 ,p1 ,&bb ,&gno ,&setno)) {
    int i = cur_obj_num;
    ge_explorer_popup  ();
    ge_block_tree_CB   (TRUE);
    ge_update_explorer ();
    ge_block_tree_CB   (FALSE);
    obj_make_current  (i);
    ge_expand_current  ();
    ge_select_current  ();
  }
  return TRUE;
}


static int ev_find_obj (int p1)
{
  int gno ,setno; // provisoire
  if (find_obj (vp ,all_obj ,p1 ,FALSE ,p1 ,p1 ,&bb ,&gno ,&setno)) {
    ev_anchor_point (x ,y ,vp);
    gg_slide_region (bb ,x - anchor_x ,y - anchor_y ,0 ,1);
    clear_all_handles ();
    return TRUE;
  } else if (a1 == MOVE_OBJ_1ST && gg_focus_clicked (cg ,vp ,&anchor_vp)) {
    if (get_graph_viewport (cg ,&bb) == RETURN_SUCCESS) {
      ev_anchor_point (x ,y ,vp);
      cur_obj_typ = Q_Graph;
      cur_obj_id  = cg;
      gg_slide_region (bb ,x - anchor_x ,y - anchor_y ,0 ,1);
      clear_all_handles ();
      return TRUE;
    }
  }
  return FALSE;
}

static int ev_find_handle (int dummy)
{
  gint newg;
  n_handle_found = find_handle (vp ,all_obj ,FALSE ,&anchor_vp ,&bb);
  if (n_handle_found > 0) {
    if ((newg = gg_next_graph_containing (cg ,vp)) != cg) {
      gg_switch_current_graph (newg);
    }
    pmask = objs[cur_obj_num].pmask;
    gg_VPoint2dev (anchor_vp ,&anchor_x ,&anchor_y);
    gg_select_region_scale (anchor_x ,anchor_y ,x ,y ,0 ,pmask);
    return TRUE;
  } else if (gg_focus_clicked (cg ,vp ,&anchor_vp)) {
    /* graphs are a special case */
    gg_VPoint2dev (anchor_vp ,&anchor_x ,&anchor_y);
    gg_set_action (VIEW_2ND);
    gg_select_region (anchor_x ,anchor_y ,x ,y ,0);
  }
  return FALSE;
}


static int ev_find_point (int p1)
{
  int trouve = RETURN_FAILURE;
  is_polyline = (cur_obj_typ == Q_Polyline);
  if (is_polyline) {
    trouve = find_polyline (&vp ,all_obj ,cur_obj_id ,&track_loc ,&track_obj);
  } else {
    track_setno = all_obj ? -1 : cur_obj_id;
    if (find_point (cg ,&vp ,&track_setno ,&track_loc) == RETURN_SUCCESS) {
      gg_VPoint2dev (vp ,&x ,&y);
      if ( (all_obj == FALSE) && (track_setno != cur_obj_id) ) return FALSE;
      track_obj = obj_tid_get_set_num (cg ,track_setno);
      trouve = RETURN_SUCCESS;
    }
  }
  if (trouve == RETURN_SUCCESS) {
    switch (a1) {
    case AUTO_NEAREST:
      autoscale_byset (cg ,track_setno ,AUTOSCALE_XY);
      ge_axis_update (cg ,-1);
      break;
    case DEL_POINT:
      if (is_polyline) {
	obj_polyline_kill_point (track_obj ,track_loc);
	ge_obj_update_xy (&objs[track_obj] , /* CURRENT */ 1);
      } else if (all_obj || track_setno == cur_obj_id) {
	del_point (cg ,track_setno ,track_loc);
      }
      break;
    case MOVE_POINT1ST:
    case MOVX_POINT1ST:
    case MOVY_POINT1ST:
      if (all_obj || is_polyline || (track_setno == cur_obj_id)) {
	track_move_dir = p1;
	ev_anchor3 (0 ,track_loc-1 ,track_obj);
	ev_anchor3 (1 ,track_loc   ,track_obj);
	ev_anchor3 (2 ,track_loc+1 ,track_obj);
	gg_select_line12 (x ,y ,0 ,TRUE);
      }
      break;
    case ADD_POINT_1ST:
      if (all_obj || is_polyline || (track_setno == cur_obj_id)) {
	track_move_dir = p1;
	ev_anchor3 (0 ,track_loc   ,track_obj);
	ev_anchor3 (1 ,track_loc   ,track_obj);
	ev_anchor3 (2 ,track_loc+1 ,track_obj);
      }

      break;
    default:
      break;
    }
    gg_drawgraph ();
    return TRUE;
  }
  return FALSE;
}

static int ev_move_point2nd (int dummy)
{
  int next_action;
  VPoint vp_old ,vpshift ,vp_new;
  if (is_polyline) {
    obj_get_shift (track_obj ,&vpshift);
    obj_polyline_get_vp (track_obj ,track_loc ,&vp_old);
    switch (track_move_dir) {
    case MOVE_POINT_XY:
      next_action = MOVE_POINT1ST;
      break;
    case MOVE_POINT_X:
      vp.y = vp_old.y + vpshift.y;
      next_action = MOVX_POINT1ST;
      break;
    case MOVE_POINT_Y:
      vp.x = vp_old.x + vpshift.x;
      next_action = MOVY_POINT1ST;
      break;
    default:
      next_action = DO_NOTHING;
    }
    vp_new.x = vp.x - vpshift.x;
    vp_new.y = vp.y - vpshift.y;
    obj_polyline_set_vp (track_obj ,track_loc ,vp_new);

    ge_obj_update_xy (&objs[track_obj] , /* CURRENT */ 1);
  } else {
    get_point (cg ,track_setno  ,track_loc ,&wp);
    view2world(vp.x ,vp.y ,&wp_new.x ,&wp_new.y);
    switch (track_move_dir) {
    case MOVE_POINT_XY:
      wp = wp_new;
      next_action = MOVE_POINT1ST;
      break;
    case MOVE_POINT_X:
      wp.x = wp_new.x;
      next_action = MOVX_POINT1ST;
      break;
    case MOVE_POINT_Y:
      wp.y = wp_new.y;
      next_action = MOVY_POINT1ST;
      break;
    default:
      next_action = DO_NOTHING;
    }
    set_point (cg ,track_setno ,track_loc ,wp);
  }
  gg_drawgraph ();
  gg_set_action (next_action);
  return FALSE;
}

static int ev_add_point2nd (int dummy)
{
  Datapoint dpoint;
  if (is_polyline) {
    if (obj_polyline_insert_vpoint (&objs[track_obj] ,vp ,track_loc+1) == RETURN_SUCCESS) {
      ge_obj_update_xy (&objs[track_obj] , /* CURRENT */ 1);
      gg_drawgraph ();
    }
  } else {
    zero_datapoint (&dpoint);
    view2world (vp.x ,vp.y ,&wp_new.x ,&wp_new.y);
    dpoint.ex[0] = wp_new.x;
    dpoint.ex[1] = wp_new.y;
    if (add_point_at (cg ,track_setno ,track_loc+1 ,&dpoint) == RETURN_SUCCESS) {
      gg_drawgraph ();
    }
  }
  return TRUE;
}

static int ev_make_obj_1st (int dummy)
{
  ev_anchor_point (x ,y ,vp);
  /* Multiclicks object */
  if (cur_obj_typ == Q_Arc) {
    /* to take into account pmask */
    int pmask = objs[cur_obj_num].pmask;
    gg_select_region_scale (x ,y ,x ,y ,0 ,pmask);
  } else if (cur_obj_typ == Q_String) {
    /* Create the string now */
    cur_obj_id  = -2;
    cur_obj_num = obj_t_next (Q_String ,&cur_obj_id ,cur_parent_typ ,cur_parent_id);
    obj_make_current (cur_obj_num);
    obj_set_xy12 (cur_obj_num ,anchor_vp ,vp);
    ge_obj_copy_string_from_template (cur_obj_num);
    ge_update_explorer ();
    gg_drawgraph ();
    gg_set_action (MAKE_OBJ_1ST);
    return FALSE;
  } else {
    gg_select_region_or_line (cur_obj_typ ,anchor_x ,anchor_y ,x ,y ,0);
  }
  if (cur_obj_typ == Q_Polyline) {
    obj_get_template 	    (Q_Polyline ,&tmp_obj);
    obj_polyline_add_vpoint (&tmp_obj ,anchor_vp);
    gg_set_action 	    (MAKE_POLYLINE);
    return FALSE;
  } else {
    return TRUE;
  }
}

static int ev_place_obj (int dummy)
{
  int i;
  QDobject old_obj;
  graph *old_graph;
  shift.x = vp.x - anchor_vp.x;
  shift.y = vp.y - anchor_vp.y;
  if (cur_obj_typ == Q_LegendBox) { /* move only */
    legend old_l;
    get_graph_legend       (cur_parent_id ,&old_l ,&old_obj); /*  undo/redo */
    move_legend            (cur_parent_id ,shift);
    ge_legendbox_update_xy (cur_parent_id);
    nn_cmd_legendbox       (cur_parent_id ,&old_l ,&old_obj ,FALSE ,TRUE);    /*  undo/redo */
    gg_drawgraph ();
    return TRUE;
  } else {
    switch (a1) {
    case COPY_OBJ_2ND:
      i = cur_obj_num;
      cur_obj_num = obj_duplicate (i ,&cur_obj_id);
      obj_move (cur_obj_num ,shift);
      ge_update_explorer  ();
      ge_expand_current   ();
      ge_select_current   ();
      break;
    case MAKE_OBJ_2ND:
      cur_obj_id  = -2;
      cur_obj_num = obj_t_next (cur_obj_typ ,&cur_obj_id ,cur_parent_typ ,cur_parent_id);
      if (cur_obj_typ == Q_Arc) {
	/* to take into account pmask */
	i = obj_t_get_template_num (cur_obj_typ);
	int pmask = objs[i].pmask;
	gg_select_region_scale (anchor_x ,anchor_y ,x ,y ,1 ,pmask);
	objs[cur_obj_num].pmask = objs[i].pmask;
	gg_get_cur_square (pmask ,&anchor_vp ,&vp);
	obj_scale (cur_obj_num ,anchor_vp ,vp ,0);
      } else {
	gg_select_region_or_line (cur_obj_typ ,anchor_x ,anchor_y ,x ,y ,0);
	obj_set_xy12 (cur_obj_num ,anchor_vp ,vp);
      }
      ge_update_explorer ();
      ge_expand_current ();
      break;
    case MOVE_OBJ_2ND:
      if (cur_obj_typ == Q_Graph) {
	old_graph = copy_graph_params (cg);               /*  undo/redo */
	ev_move_graph (cg ,shift);
	nn_cmd_graph  (cg ,old_graph ,FALSE ,TRUE ,TRUE); /*  undo/redo */
	free_graph_params (old_graph);                    /*  undo/redo */
  	gg_drawgraph       ();
	return TRUE;
      } else {
	obj_get  (cur_obj_num ,&old_obj);                 /*  undo/redo */
	obj_move (cur_obj_num ,shift);
	nn_cmd_obj (&old_obj ,&objs[cur_obj_num] ,FALSE ,TRUE); /*  undo/redo */
      }
      break;
    case SCALE_OBJ_2ND:
      obj_get  (cur_obj_num ,&old_obj);                   /*  undo/redo */
      gg_get_cur_square (pmask ,&anchor_vp ,&vp);
      obj_scale (cur_obj_num ,anchor_vp ,vp ,n_handle_found);
      nn_cmd_obj (&old_obj ,&objs[cur_obj_num] ,FALSE ,TRUE);   /*  undo/redo */
    default:
      break;
    }
  }
  ge_obj_block_instant_update (TRUE);
  ge_obj_update_xy (&objs[cur_obj_num] ,1);
  ge_obj_block_instant_update (FALSE);
  gg_drawgraph ();
  return TRUE;
}


static int ev_place_polyline (int dummy)
{
  shift.x = vp.x - anchor_vp.x;
  shift.y = vp.y - anchor_vp.y;
  gg_select_region_or_line (Q_Polyline ,anchor_x ,anchor_y ,x ,y ,0);
  if (tmp_obj.nxy == 0) {
    obj_polyline_add_vpoint (&tmp_obj ,anchor_vp);
  }
  obj_polyline_add_vpoint (&tmp_obj ,vp);
  ev_anchor_point (x ,y ,vp);
  return TRUE;
}
/**
 *  Find the place and the value to put a label on a contour line
 *  The set is the one (and only one)  selected in the ge_tree
 */
static int  ev_place_label (int p1)
{
  int i = ge_selection_get_num ();
  if (i > 0 && objs[i].typ == Q_Set) {
#if defined (HAVE_CONTOURS)
    if (zc_label_place (objs[i].father_id ,objs[i].id ,vp ,TRUE)) {
      gg_drawgraph ();
    }
#endif  /* HAVE_CONTOURS */
  }
  return TRUE;
}


static int  ev_kill_label (int p1)
{
  int i = ge_selection_get_num ();
  if (i > 0 && objs[i].typ == Q_Set) {
#if defined (HAVE_CONTOURS)
    zc_label_kill (objs[i].id ,objs[i].father_id ,vp);
#endif  /* HAVE_CONTOURS */
  }
  return TRUE;
}


/**
 * ev_glue_objs_1st create a compound clist and put first tagged obj in
 */
static int ev_glue_objs_1st (int dummy)
{
  int i;
  if (find_handle (vp ,all_obj ,TRUE ,&anchor_vp ,&bb)) {
    g_list_free (clist);
    clist = NULL;
    /* parent is the first element in clist */
    clear_all_handles ();
    if (cur_parent_typ == Q_Graph) {
      i = obj_tid_get_graph_num (cur_parent_id);
      draw_branch_handles (objs[i].child);
    } else {
      i = obj_tid_get_num (Q_Line ,-1 ,Q_Project ,-1);
      draw_branch_handles (i);
    }
    clear_obj_handles (cur_obj_num);
    clist = g_list_append (clist ,GINT_TO_POINTER (i));
    /* add the tagged element */
    clist = g_list_append (clist ,GINT_TO_POINTER (cur_obj_num));
    return TRUE;
  }
  return FALSE;
}

/**
 * ev_glue_objs_2nd add an object to a compound clist
 * Handles management should ensure that only eligible objects
 *                    can be added.
 */
static int ev_glue_objs_2nd (int dummy)
{
  if (find_handle (vp ,all_obj ,TRUE ,&anchor_vp ,&bb)) {
    clist = g_list_append (clist ,GINT_TO_POINTER (cur_obj_num));
    clear_obj_handles (cur_obj_num);
    return TRUE;
  }
  return FALSE;
}

static void ev_glue_objs_finalize (void)
{
  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  ();
  }
}

static int ev_break_compound (int dummy)
{
  char message[32];
  if (find_handle (vp ,all_obj ,TRUE ,&anchor_vp ,&bb)) {
    if (cur_obj_typ == Q_Compound) {
      sprintf (message, "Break compound %d ?" ,cur_obj_id);
      if (yesno(message) == TRUE) {
	obj_break_compound (cur_obj_num);
	gg_drawgraph ();
	ge_update_explorer();
	return TRUE;
      }
    }
  }
  return FALSE;
}

static void ev_move_graph (int gno ,VVector shift)
{
  view v;
  get_graph_viewport (gno ,&v);
  v.xv1 += shift.x;
  v.yv1 += shift.y;
  v.xv2 += shift.x;
  v.yv2 += shift.y;
  set_graph_viewport (gno ,v);
}

static int ev_place_locator (int dummy)
{
  GLocator locator;
  if (get_graph_locator (cg ,&locator) == RETURN_SUCCESS) {
    view2world (vp.x ,vp.y ,&locator.dsx, &locator.dsy);
    locator.pointset = TRUE;
    set_graph_locator (cg, &locator);
    gg_update_locator_items (cg);
    gg_drawgraph ();
  }
  return TRUE;
}

static int ev_sel_line (int dummy)
{
  ev_anchor_point (x, y, vp);
  gg_select_line (anchor_x, anchor_y, x, y, 0);
  return TRUE;
}

static int ev_def_region2nd (int p1)
{
  gg_select_line (anchor_x, anchor_y, x, y, 0);
  activate_region (nr, regiontype, cg);
  view2world (anchor_vp.x, anchor_vp.y, &rg[nr].x1, &rg[nr].y1);
  view2world (vp.x, vp.y, &rg[nr].x2, &rg[nr].y2);
  gg_drawgraph ();
  return TRUE;
}


static int ev_def_region (int p1)
{
  ev_anchor_point (x, y, vp);
  iax[region_pts] = x;
  iay[region_pts] = y;
  view2world (vp.x, vp.y,
	      &region_wps[region_pts].x, &region_wps[region_pts].y);
  if (region_pts < MAX_POLY_POINTS) {
    region_pts++;
  } else {
    errmsg("Too many points in polygon!");
  }
  gg_select_line (anchor_x, anchor_y, x, y, 0);

  return TRUE;
}
/********************************************************/


/**
 *  Define a (polygon) region
 *  Replace    do_select_region      events.c  921
 */
void gg_do_select_region (void)
{
    region_pts = 0;
    gg_set_action (DO_NOTHING);
    gg_set_action (DEF_REGION);
}



/**
 * Find the graph that contains  vp.
 * Used for setting the graph focus.
 */
int gg_next_graph_containing (int cg ,VPoint vp)
{
  int i ,j ,ng ,gno = -1;
  view v;

  ng = number_of_graphs ();
  if (is_valid_gno(cg) == FALSE) cg = -1;
  for (i = 0; i < ng ; i++) {
    j = (i + cg + 1) % ng;
    if (is_graph_hidden (j)        == FALSE &&
	get_graph_viewport (j ,&v) == RETURN_SUCCESS &&
	is_vpoint_inside (v ,vp ,MAXPICKDIST)   == TRUE) {
      gno = j;
      break;
    }
  }
  return gno;
}

static gboolean track_wipe_rect = FALSE;
static VPoint   track_old_vp;


/**
 *  Replace   track_point    events.c  769
 */
void gg_track_point (int gno, int setno, int *loc, int shift)
{
    int len;
    double *xtmp, *ytmp;
    WPoint wp;
    VPoint vp;
    world w;

    if ((len = getsetlength (gno, setno)) > 0) {
      if (track_wipe_rect) gg_draw_rect (track_old_vp);
      *loc += shift;
      if (*loc < 0) {
	*loc += len;
      } else {
	*loc = *loc % len;
      }
      xtmp = getx (gno, setno);
      ytmp = gety (gno, setno);
      wp.x = xtmp[*loc];
      wp.y = ytmp[*loc];

      get_graph_world (gno, &w);
      wp.x = MAX2(wp.x, w.xg1);
      wp.x = MIN2(wp.x, w.xg2);
      wp.y = MAX2(wp.y, w.yg1);
      wp.y = MIN2(wp.y, w.yg2);
      vp = Wpoint2Vpoint(wp);
      gg_draw_rect (vp);
      track_wipe_rect = shift != 0;
      track_old_vp = vp;
    }
}


/**
 *  Replace  (identical) focus_clicked   events.c  1000
 */
int gg_focus_clicked(int cg, VPoint vp, VPoint *avp)
{
    view v;

    if (is_graph_hidden(cg) == TRUE) {
        return FALSE;
    }
    if (get_graph_viewport(cg, &v) != RETURN_SUCCESS) {
        return FALSE;
    }

    if (fabs(vp.x - v.xv1) < MAXPICKDIST && fabs(vp.y - v.yv1) < MAXPICKDIST) {
        avp->x = v.xv2;
        avp->y = v.yv2;
        return TRUE;
    } else if (fabs(vp.x - v.xv1) < MAXPICKDIST && fabs(vp.y - v.yv2) < MAXPICKDIST) {
        avp->x = v.xv2;
        avp->y = v.yv1;
        return TRUE;
    } else if (fabs(vp.x - v.xv2) < MAXPICKDIST && fabs(vp.y - v.yv1) < MAXPICKDIST) {
        avp->x = v.xv1;
        avp->y = v.yv2;
        return TRUE;
    } else if (fabs(vp.x - v.xv2) < MAXPICKDIST && fabs(vp.y - v.yv2) < MAXPICKDIST) {
        avp->x = v.xv1;
        avp->y = v.yv1;
        return TRUE;
    } else {
        return FALSE;
    }
}


static void gg_select_line12 (int xs ,int ys ,int erase ,int a1move)
{
  if (track_loc == 0 && a1move) {
    gg_select_line (anchor[2].x  ,anchor[2].y  ,xs ,ys ,erase);
  } else if (track_loc == track_end && a1move)  {
    gg_select_line (anchor[0].x  ,anchor[0].y  ,xs ,ys ,erase);
  } else if (track_loc == track_end)  {
    gg_select_line (anchor[1].x  ,anchor[1].y  ,xs ,ys ,erase);
  } else  {
    gg_select_2lines (anchor[0].x  ,anchor[0].y  ,xs ,ys
		      ,anchor[2].x ,anchor[2].y  ,erase);
  }
}

static int ev_axis_clicked (int gno, VPoint vp, int *axisno ,int pop)
{
  view v;

  /* TODO: check for offsets, zero axes, polar graphs */
  if (is_graph_hidden(gno) == TRUE) {
    return FALSE;
  } else {
    get_graph_viewport(gno, &v);
    if (vp.x >= v.xv1 && vp.x <= v.xv2 &&
	(fabs(vp.y - v.yv1) < MAXPICKDIST ||
	 fabs(vp.y - v.yv2) < MAXPICKDIST)) {
      *axisno = X_AXIS;
      goto fin;
    } else if (vp.y >= v.yv1 && vp.y <= v.yv2 &&
	       (fabs(vp.x - v.xv1) < MAXPICKDIST ||
		fabs(vp.x - v.xv2) < MAXPICKDIST)) {
      *axisno = Y_AXIS;
      goto fin;
    } else {
      return FALSE;
    }
  fin:
    if (pop) {
      ge_explorer_popup ();
      obj_tid_make_current (Q_Axis ,*axisno ,Q_Graph ,cg);
      ge_expand_current ();
      ge_select_current ();
    }
    return TRUE;
  }
}

static int ev_title_clicked (int gno, VPoint vp)
{
  view v;

  /* a rude check; TODO: use right offsets */
  if (is_graph_hidden(gno) == TRUE) {
    return FALSE;
  } else {
    get_graph_viewport(gno, &v);
    if (vp.x >= v.xv1 && vp.x <= v.xv2 &&
	vp.y > v.yv2 && vp.y < v.yv2 + 0.1) {
      ge_explorer_popup ();
      ge_select (Q_Project ,-1 ,Q_Graph ,gno);
      return TRUE;
    } else {
      return FALSE;
    }
  }
}

static int  ev_nonlin_fit     (int p1)
{
  ev_anchor_point (x, y, vp);
  switch (a1) {
  case PEAK_POS:
    nonl_parms[1].value = Vpoint2Wpoint(vp).x;
    nonl_parms[3].value = Vpoint2Wpoint(vp).y;
    break;
  case PEAK_POS1:
    nonl_parms[1].value = Vpoint2Wpoint(vp).x;
    nonl_parms[3].value = Vpoint2Wpoint(vp).y;
    break;
  case PEAK_POS2:
    nonl_parms[4].value = Vpoint2Wpoint(vp).x;
    nonl_parms[6].value = Vpoint2Wpoint(vp).y;
    break;
  case PEAK_POS1B:
    nonl_parms[1].value = Vpoint2Wpoint(vp).x;
    nonl_parms[3].value = Vpoint2Wpoint(vp).y;
    break;
  case PEAK_POS2B:
    nonl_parms[4].value = Vpoint2Wpoint(vp).x;
    nonl_parms[6].value = Vpoint2Wpoint(vp).y;
    break;
  case PEAK_POS3B:
    nonl_parms[7].value = Vpoint2Wpoint(vp).x;
    nonl_parms[9].value = Vpoint2Wpoint(vp).y;
    break;
  default:
    return FALSE;
  }
  gg_nonl_update ();
  return TRUE;
}
