/*
 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
 *
 * This is a plug-in for GIMP.
 *
 * Generates images containing vector type drawings.
 *
 * Copyright (C) 1997 Andy Thomas  alt@picnic.demon.co.uk
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

#include "config.h"

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

#include <libgimp/gimp.h>
#include <libgimp/gimpui.h>

#include "gfig.h"
#include "gfig-dialog.h"
#include "gfig-style.h"
#include "gfig-arc.h"
#include "gfig-bezier.h"
#include "gfig-circle.h"
#include "gfig-dobject.h"
#include "gfig-ellipse.h"
#include "gfig-line.h"
#include "gfig-poly.h"
#include "gfig-rectangle.h"
#include "gfig-spiral.h"
#include "gfig-star.h"

#include "libgimp/stdplugins-intl.h"

static GfigObject *operation_obj = NULL;
static GdkPoint   *move_all_pnt; /* Point moving all from */


static void draw_one_obj         (GfigObject *obj,
                                  cairo_t    *cr);
static void do_move_obj          (GfigObject *obj,
                                  GdkPoint   *to_pnt);
static void do_move_all_obj      (GdkPoint   *to_pnt);
static void do_move_obj_pnt      (GfigObject *obj,
                                  GdkPoint   *to_pnt);
static void remove_obj_from_list (GFigObj    *obj,
                                  GfigObject *del_obj);
static gint scan_obj_points      (DobjPoints *opnt,
                                  GdkPoint   *pnt);

void
d_save_object (GfigObject *obj,
               GString    *string)
{
  do_save_obj (obj, string);

  switch (obj->type)
    {
    case BEZIER:
    case POLY:
    case SPIRAL:
    case STAR:
      g_string_append_printf (string, "<EXTRA>\n");
      g_string_append_printf (string, "%d\n</EXTRA>\n", obj->type_data);
      break;
    default:
      break;
    }
}

static DobjType
gfig_read_object_type (gchar *desc)
{
  gchar    *ptr = desc;
  DobjType  type;

  if (*ptr != '<')
    return OBJ_TYPE_NONE;

  ptr++;

  for (type = LINE; type < NUM_OBJ_TYPES; type++)
    {
      if (ptr == strstr (ptr, dobj_class[type].name))
        return type;
    }

  return OBJ_TYPE_NONE;
}

GfigObject *
d_load_object (gchar *desc,
               FILE  *fp)
{
  GfigObject *new_obj = NULL;
  gint        xpnt;
  gint        ypnt;
  gchar       buf[MAX_LOAD_LINE];
  DobjType    type;

  type = gfig_read_object_type (desc);
  if (type == OBJ_TYPE_NONE)
    {
      g_message ("Error loading object: type not recognized.");
      return NULL;
    }

  while (get_line (buf, MAX_LOAD_LINE, fp, 0))
    {
      if (sscanf (buf, "%d %d", &xpnt, &ypnt) != 2)
        {
          /* Read <EXTRA> block if there is one */
          if (!strcmp ("<EXTRA>", buf))
            {
              if ( !new_obj)
                {
                  g_message ("Error while loading object (no points)");
                  return NULL;
                }

              get_line (buf, MAX_LOAD_LINE, fp, 0);

              if (sscanf (buf, "%d", &new_obj->type_data) != 1)
                {
                  g_message ("Error while loading object (no type data)");
                  g_free (new_obj);
                  return NULL;
                }

              get_line (buf, MAX_LOAD_LINE, fp, 0);
              if (strcmp ("</EXTRA>", buf))
                {
                  g_message ("Syntax error while loading object");
                  g_free (new_obj);
                  return NULL;
                }
              /* Go around and read the last line */
              continue;
            }
          else
            return new_obj;
        }

      if (!new_obj)
        new_obj = d_new_object (type, xpnt, ypnt);
      else
        d_pnt_add_line (new_obj, xpnt, ypnt, -1);
    }

  return new_obj;
}

GfigObject *
d_new_object (DobjType    type,
              gint        x,
              gint        y)
{
  GfigObject *nobj = g_new0 (GfigObject, 1);

  nobj->type = type;
  nobj->class = &dobj_class[type];
  nobj->points = new_dobjpoint (x, y);

  nobj->type_data = 0;

  if (type == BEZIER)
    {
      nobj->type_data = 4;
    }
  else if (type == POLY)
    {
      nobj->type_data = 3;  /* default to 3 sides */
    }
  else if (type == SPIRAL)
    {
      nobj->type_data = 4;  /* default to 4 turns */
    }
  else if (type == STAR)
    {
      nobj->type_data = 3;  /* default to 3 sides 6 points */
    }

  return nobj;
}

void
gfig_init_object_classes (void)
{
  d_arc_object_class_init ();
  d_line_object_class_init ();
  d_rectangle_object_class_init ();
  d_circle_object_class_init ();
  d_ellipse_object_class_init ();
  d_poly_object_class_init ();
  d_spiral_object_class_init ();
  d_star_object_class_init ();
  d_bezier_object_class_init ();
}

/* Delete a list of points */
void
d_delete_dobjpoints (DobjPoints * pnts)
{
  DobjPoints *next;
  DobjPoints *pnt2del = pnts;

  while (pnt2del)
    {
      next = pnt2del->next;
      g_free (pnt2del);
      pnt2del = next;
    }
}

DobjPoints *
new_dobjpoint (gint x, gint y)
{
  DobjPoints *npnt = g_new0 (DobjPoints, 1);

  npnt->pnt.x = x;
  npnt->pnt.y = y;

  return npnt;
}

DobjPoints *
d_copy_dobjpoints (DobjPoints *pnts)
{
  DobjPoints *ret = NULL;
  DobjPoints *head = NULL;
  DobjPoints *newpnt;
  DobjPoints *pnt2copy;

  for (pnt2copy = pnts; pnt2copy; pnt2copy = pnt2copy->next)
    {
      newpnt = new_dobjpoint (pnt2copy->pnt.x, pnt2copy->pnt.y);

      if (!ret)
        {
          head = ret = newpnt;
        }
      else
        {
          head->next = newpnt;
          head = newpnt;
        }
    }

  return ret;
}

static DobjPoints *
get_diffs (GfigObject *obj,
           gint       *xdiff,
           gint       *ydiff,
           GdkPoint   *to_pnt)
{
  DobjPoints *spnt;

  g_assert (obj != NULL);

  for (spnt = obj->points; spnt; spnt = spnt->next)
    {
      if (spnt->found_me)
        {
          *xdiff = spnt->pnt.x - to_pnt->x;
          *ydiff = spnt->pnt.y - to_pnt->y;
          return spnt;
        }
    }
  return NULL;
}

static gboolean
inside_sqr (GdkPoint *cpnt,
            GdkPoint *testpnt)
{
  /* Return TRUE if testpnt is near cpnt */
  gint x  = cpnt->x;
  gint y  = cpnt->y;
  gint tx = testpnt->x;
  gint ty = testpnt->y;

  return (abs (x - tx) <= SQ_SIZE && abs (y - ty) < SQ_SIZE);
}

static gboolean
scan_obj_points (DobjPoints *opnt,
                 GdkPoint   *pnt)
{
  while (opnt)
    {
      if (inside_sqr (&opnt->pnt, pnt))
        {
          opnt->found_me = TRUE;
          return TRUE;
        }
      opnt->found_me = FALSE;
      opnt = opnt->next;
    }
  return FALSE;
}

static GfigObject *
get_nearest_objs (GFigObj  *obj,
                  GdkPoint *pnt)
{
  /* Nearest object to given point or NULL */
  GList      *all;
  GfigObject *test_obj;
  gint        count = 0;

  if (!obj)
    return NULL;

  for (all = obj->obj_list; all; all = g_list_next (all))
    {
      test_obj = all->data;

      if (count == obj_show_single || obj_show_single == -1)
        if (scan_obj_points (test_obj->points, pnt))
          {
            return test_obj;
          }
      count++;
    }
  return NULL;
}

void
object_operation_start (GimpGfig *gfig,
                        GdkPoint *pnt,
                        gboolean  shift_down)
{
  GfigObject *new_obj;

  /* Find point in given object list */
  operation_obj = get_nearest_objs (gfig_context->current_obj, pnt);

  /* Special case if shift down && move obj then moving all objs */

  if (shift_down && selvals.otype == MOVE_OBJ)
    {
      move_all_pnt = g_new (GdkPoint, 1);
      *move_all_pnt = *pnt; /* Structure copy */
      setup_undo (gfig);
      return;
    }

  if (!operation_obj)
    return;/* None to work on */

  gfig_context->selected_obj = operation_obj;

  setup_undo (gfig);

  switch (selvals.otype)
    {
    case MOVE_OBJ:
      if (operation_obj->type == BEZIER)
        {
          tmp_bezier = operation_obj;
        }
      break;

    case MOVE_POINT:
      if (operation_obj->type == BEZIER)
        {
          tmp_bezier = operation_obj;
        }
      /* If shift is down the break into sep lines */
      if ((operation_obj->type == POLY
          || operation_obj->type == STAR)
         && shift_down)
        {
          switch (operation_obj->type)
            {
            case POLY:
              d_poly2lines (operation_obj);
              break;
            case STAR:
              d_star2lines (operation_obj);
              break;
            default:
              break;
            }
          /* Re calc which object point we are looking at */
          scan_obj_points (operation_obj->points, pnt);
          gtk_widget_queue_draw (gfig_context->preview);
        }
      break;

    case COPY_OBJ:
      /* Copy the "operation object" */
      /* Then bung us into "copy/move" mode */

      new_obj = (GfigObject*) operation_obj->class->copyfunc (operation_obj);
      if (new_obj)
        {
          gfig_style_copy (&new_obj->style, &operation_obj->style, "Object");
          scan_obj_points (new_obj->points, pnt);
          add_to_all_obj (gfig, gfig_context->current_obj, new_obj);
          operation_obj = new_obj;
          selvals.otype = MOVE_COPY_OBJ;
          gtk_widget_queue_draw (gfig_context->preview);
        }
      break;

    case DEL_OBJ:
      remove_obj_from_list (gfig_context->current_obj, operation_obj);
      break;

    case SELECT_OBJ:
      /* don't need to do anything */
      break;

    case MOVE_COPY_OBJ: /* Never when button down */
    default:
      g_warning ("Internal error selvals.otype object operation start");
      break;
    }

}

void
object_operation_end (GdkPoint *pnt,
                      gboolean  shift_down)
{
  if (selvals.otype != DEL_OBJ && operation_obj &&
      operation_obj->type == BEZIER)
    {
      tmp_bezier = NULL; /* use as switch */
    }

  if (operation_obj && selvals.otype != DEL_OBJ)
    gfig_style_set_context_from_style (&operation_obj->style);

  operation_obj = NULL;

  if (move_all_pnt)
    {
      g_free (move_all_pnt);
      move_all_pnt = NULL;
    }

  /* Special case - if copying mode MUST be copy when button up received */
  if (selvals.otype == MOVE_COPY_OBJ)
    selvals.otype = COPY_OBJ;
}

/* Move object around */
void
object_operation (GdkPoint *to_pnt,
                  gboolean  shift_down)
{
  /* Must do different things depending on object type */
  /* but must have object to operate on! */

  /* Special case - if shift own and move_obj then move ALL objects */
  if (move_all_pnt && shift_down && selvals.otype == MOVE_OBJ)
    {
      do_move_all_obj (to_pnt);
      return;
    }

  if (!operation_obj)
    return;

  switch (selvals.otype)
    {
    case MOVE_OBJ:
    case MOVE_COPY_OBJ:
      switch (operation_obj->type)
        {
        case LINE:
        case RECTANGLE:
        case CIRCLE:
        case ELLIPSE:
        case POLY:
        case ARC:
        case STAR:
        case SPIRAL:
        case BEZIER:
          do_move_obj (operation_obj, to_pnt);
          break;
        default:
          /* Internal error */
          g_warning ("Internal error in operation_obj->type");
          break;
        }
      break;
    case MOVE_POINT:
      switch (operation_obj->type)
        {
        case LINE:
        case RECTANGLE:
        case CIRCLE:
        case ELLIPSE:
        case POLY:
        case ARC:
        case STAR:
        case SPIRAL:
        case BEZIER:
          do_move_obj_pnt (operation_obj, to_pnt);
          break;
        default:
          /* Internal error */
          g_warning ("Internal error in operation_obj->type");
          break;
        }
      break;
    case DEL_OBJ:
    case SELECT_OBJ:
      break;
    case COPY_OBJ: /* Should have been changed to MOVE_COPY_OBJ */
    default:
      g_warning ("Internal error selvals.otype");
      break;
    }
}

static void
update_pnts (GfigObject *obj,
             gint        xdiff,
             gint        ydiff)
{
  DobjPoints *spnt;

  g_assert (obj != NULL);

  /* Update all pnts */
  for (spnt = obj->points; spnt; spnt = spnt->next)
    {
      spnt->pnt.x -= xdiff;
      spnt->pnt.y -= ydiff;
    }
}

static void
remove_obj_from_list (GFigObj    *obj,
                      GfigObject *del_obj)
{
  /* Nearest object to given point or NULL */

  g_assert (del_obj != NULL);

  if (g_list_find (obj->obj_list, del_obj))
    {
      obj->obj_list = g_list_remove (obj->obj_list, del_obj);

      free_one_obj (del_obj);

      if (obj->obj_list)
        gfig_context->selected_obj = obj->obj_list->data;
      else
        gfig_context->selected_obj = NULL;

      if (obj_show_single != -1)
        {
          /* We've just deleted the only visible one */
          draw_grid_clear ();
          obj_show_single = -1; /* Show entry again */
        }

      gtk_widget_queue_draw (gfig_context->preview);
    }
  else
    g_warning (_("Hey, where has the object gone?"));
}

static void
do_move_all_obj (GdkPoint *to_pnt)
{
  /* Move all objects in one go */
  /* Undraw/then draw in new pos */
  gint xdiff = move_all_pnt->x - to_pnt->x;
  gint ydiff = move_all_pnt->y - to_pnt->y;

  if (xdiff || ydiff)
    {
      GList *all;

      for (all = gfig_context->current_obj->obj_list; all; all = all->next)
        {
          GfigObject *obj = all->data;

          update_pnts (obj, xdiff, ydiff);
        }

      *move_all_pnt = *to_pnt;

      gtk_widget_queue_draw (gfig_context->preview);
    }
}

void
do_save_obj (GfigObject *obj,
             GString    *string)
{
  DobjPoints *spnt;

  for (spnt = obj->points; spnt; spnt = spnt->next)
    {
      g_string_append_printf (string, "%d %d\n", spnt->pnt.x, spnt->pnt.y);
    }
}

static void
do_move_obj (GfigObject *obj,
             GdkPoint   *to_pnt)
{
  /* Move the whole line - undraw the line to start with */
  /* Then draw in new pos */
  gint xdiff = 0;
  gint ydiff = 0;

  get_diffs (obj, &xdiff, &ydiff, to_pnt);

  if (xdiff || ydiff)
    {
      update_pnts (obj, xdiff, ydiff);

      gtk_widget_queue_draw (gfig_context->preview);
    }
}

static void
do_move_obj_pnt (GfigObject *obj,
                 GdkPoint   *to_pnt)
{
  /* Move the whole line - undraw the line to start with */
  /* Then draw in new pos */
  DobjPoints *spnt;
  gint xdiff = 0;
  gint ydiff = 0;

  spnt = get_diffs (obj, &xdiff, &ydiff, to_pnt);

  if ((!xdiff && !ydiff) || !spnt)
    return;

  spnt->pnt.x = spnt->pnt.x - xdiff;
  spnt->pnt.y = spnt->pnt.y - ydiff;

  /* Draw in new pos */
  gtk_widget_queue_draw (gfig_context->preview);
}

/* copy objs */
GList *
copy_all_objs (GList *objs)
{
  GList *new_all_objs = NULL;

  while (objs)
    {
      GfigObject *object = objs->data;
      GfigObject *new_object = (GfigObject *) object->class->copyfunc (object);
      gfig_style_copy (&new_object->style, &object->style, "Object");

      new_all_objs = g_list_prepend (new_all_objs, new_object);

      objs = objs->next;
    }

  new_all_objs = g_list_reverse (new_all_objs);

  return new_all_objs;
}

/* Screen refresh */
static void
draw_one_obj (GfigObject *obj,
              cairo_t    *cr)
{
  obj->class->drawfunc (obj, cr);
}

void
draw_objects (GList    *objs,
              gboolean  show_single,
              cairo_t  *cr)
{
  /* Show_single - only one object to draw Unless shift
   * is down in which case show all.
   */

  gint count = 0;

  while (objs)
    {
      if (!show_single || count == obj_show_single || obj_show_single == -1)
        draw_one_obj (objs->data, cr);

      objs = g_list_next (objs);
      count++;
    }
}

void
prepend_to_all_obj (GimpGfig *gfig,
                    GFigObj  *fobj,
                    GList    *nobj)
{
  setup_undo (gfig); /* Remember ME */

  fobj->obj_list = g_list_concat (fobj->obj_list, nobj);
}

static void
scale_obj_points (DobjPoints *opnt,
                  gdouble     scale_x,
                  gdouble     scale_y)
{
  while (opnt)
    {
      opnt->pnt.x = (gint) (opnt->pnt.x * scale_x);
      opnt->pnt.y = (gint) (opnt->pnt.y * scale_y);
      opnt = opnt->next;
    }
}

void
add_to_all_obj (GimpGfig   *gfig,
                GFigObj    *fobj,
                GfigObject *obj)
{
  GList *nobj = NULL;

  nobj = g_list_append (nobj, obj);

  if (need_to_scale)
    scale_obj_points (obj->points, scale_x_factor, scale_y_factor);

  prepend_to_all_obj (gfig, fobj, nobj);

  /* initialize style when we add the object */
  gfig_context->selected_obj = obj;
}

/* First button press -- start drawing object */
/*
 * object_start() creates a new object of the type specified in the
 * button panel.  It is activated by a button press, and causes
 * a small square to be drawn at the initial point.  The style of
 * the new object is set to values taken from the style control
 * widgets.
 */
void
object_start (GdkPoint *pnt,
              gboolean  shift_down)
{
  /* start for the current object */
  if (!selvals.scaletoimage)
    {
      need_to_scale = 1;
      selvals.scaletoimage = 1;
    }
  else
    {
      need_to_scale = 0;
    }

  switch (selvals.otype)
    {
    case LINE:
      /* Shift means we are still drawing */
      d_line_start (pnt, shift_down);
      break;
    case RECTANGLE:
      d_rectangle_start (pnt, shift_down);
      break;
    case CIRCLE:
      d_circle_start (pnt, shift_down);
      break;
    case ELLIPSE:
      d_ellipse_start (pnt, shift_down);
      break;
    case POLY:
      d_poly_start (pnt, shift_down);
      break;
    case ARC:
      d_arc_start (pnt, shift_down);
      break;
    case STAR:
      d_star_start (pnt, shift_down);
      break;
    case SPIRAL:
      d_spiral_start (pnt, shift_down);
      break;
    case BEZIER:
      d_bezier_start (pnt, shift_down);
      break;
    default:
      /* Internal error */
      break;
    }

  if (obj_creating)
    {
      if (gfig_context->debug_styles)
        g_printerr ("Creating object, setting style from context\n");
      gfig_style_set_style_from_context (&obj_creating->style);
    }

}

void
object_end (GimpGfig *gfig,
            GdkPoint *pnt,
            gboolean  shift_down)
{
  /* end for the current object */
  /* Add onto global object list */

  /* If shift is down may carry on drawing */
  switch (selvals.otype)
    {
    case LINE:
      d_line_end (gfig, pnt, shift_down);
      break;
    case RECTANGLE:
      d_rectangle_end (gfig, pnt, shift_down);
      break;
    case CIRCLE:
      d_circle_end (gfig, pnt, shift_down);
      break;
    case ELLIPSE:
      d_ellipse_end (gfig, pnt, shift_down);
      break;
    case POLY:
      d_poly_end (gfig, pnt, shift_down);
      break;
    case STAR:
      d_star_end (gfig, pnt, shift_down);
      break;
    case ARC:
      d_arc_end (gfig, pnt, shift_down);
      break;
    case SPIRAL:
      d_spiral_end (gfig, pnt, shift_down);
      break;
    case BEZIER:
      d_bezier_end (gfig, pnt, shift_down);
      break;
    default:
      /* Internal error */
      break;
    }

  if (need_to_scale)
    {
      need_to_scale = 0;
      selvals.scaletoimage = 0;
    }
}

/* Stuff for the generation/deletion of objects. */

/* Objects are easy one they are created - you just go down the object
 * list calling the draw function for each object but... when they
 * are been created we have to be a little more careful. When
 * the first point is placed on the canvas we create the object,
 * the mouse position then defines the next point that can move around.
 * careful how we draw this position.
 */

void
free_one_obj (GfigObject *obj)
{
  d_delete_dobjpoints (obj->points);
  g_free (obj);
}

void
free_all_objs (GList *objs)
{
  g_list_free_full (objs, (GDestroyNotify) free_one_obj);
}

gchar *
get_line (gchar *buf,
          gint   s,
          FILE  *from,
          gint   init)
{
  gint slen;
  char *ret;

  if (init)
    line_no = 1;
  else
    line_no++;

  do
    {
      ret = fgets (buf, s, from);
    }
  while (!ferror (from) && buf[0] == '#');

  slen = strlen (buf);

  /* The last newline is a pain */
  if (slen > 0)
    buf[slen - 1] = '\0';

  /* Check and remove an '\r' too from Windows */
  if ((slen > 1) && (buf[slen - 2] == '\r'))
    buf[slen - 2] = '\0';

  if (ferror (from))
    {
      g_warning (_("Error reading file"));
      return NULL;
    }

#ifdef DEBUG
  printf ("Processing line '%s'\n", buf);
#endif /* DEBUG */

  return ret;
}

void
clear_undo (GimpGfig *gfig)
{
  int      lv;
  GAction *action;

  for (lv = undo_level; lv >= 0; lv--)
    {
      free_all_objs (undo_table[lv]);
      undo_table[lv] = NULL;
    }

  undo_level = -1;

  action = g_action_map_lookup_action (G_ACTION_MAP (gfig->app), "undo");
  g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
                               FALSE);
}

void
setup_undo (GimpGfig *gfig)
{
  GAction *action;

  /* Copy object list to undo buffer */
#if DEBUG
  printf ("setup undo level [%d]\n", undo_level);
#endif /*DEBUG*/

  if (!gfig_context->current_obj)
    {
      /* If no current_obj must be loading -> no undo */
      return;
    }

  if (undo_level >= selvals.maxundo - 1)
    {
      int loop;
      /* the little one in the bed said "roll over".. */
      if (undo_table[0])
        free_one_obj (undo_table[0]->data);
      for (loop = 0; loop < undo_level; loop++)
        {
          undo_table[loop] = undo_table[loop + 1];
        }
    }
  else
    {
      undo_level++;
    }
  undo_table[undo_level] =
    copy_all_objs (gfig_context->current_obj->obj_list);

  action = g_action_map_lookup_action (G_ACTION_MAP (gfig->app), "undo");
  g_simple_action_set_enabled (G_SIMPLE_ACTION (action), TRUE);

  gfig_context->current_obj->obj_status |= GFIG_MODIFIED;
}

void
new_obj_2edit (GimpGfig *gfig,
               GFigObj  *obj)
{
  GAction *action;
  GFigObj *old_current = gfig_context->current_obj;

  /* Clear undo levels */
  /* redraw the preview */
  /* Set up options as define in the selected object */

  clear_undo (gfig);

  /* Point at this one */
  gfig_context->current_obj = obj;

  /* Show all objects to start with */
  obj_show_single = -1;

  /* Change options */
  options_update (old_current);

  /* redraw with new */
  gtk_widget_queue_draw (gfig_context->preview);

  action = g_action_map_lookup_action (G_ACTION_MAP (gfig->app), "save");
  if (obj->obj_status & GFIG_READONLY)
    {
      g_message (_("Editing read-only object - "
                   "you will not be able to save it"));

      g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
                                   FALSE);
    }
  else
    {
      g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
                                   FALSE);
    }
}

/* Add a point to a line (given x, y)
 * pos = 0 = head
 * pos = -1 = tail
 * 0 < pos = nth position
 */

void
d_pnt_add_line (GfigObject *obj,
                gint        x,
                gint        y,
                gint        pos)
{
  DobjPoints *npnts = new_dobjpoint (x, y);

  g_assert (obj != NULL);

  if (!pos)
    {
      /* Add to head */
      npnts->next = obj->points;
      obj->points = npnts;
    }
  else
    {
      DobjPoints *pnt = obj->points;

      /* Go down chain until the end if pos */
      while (pos < 0 || pos-- > 0)
        {
          if (!(pnt->next) || !pos)
            {
              npnts->next = pnt->next;
              pnt->next = npnts;
              break;
            }
          else
            {
              pnt = pnt->next;
            }
        }
    }
}
