/*----------------------------------------------------------------------*/
/* undo.c 								*/
/*									*/
/* The comprehensive "undo" and "redo" command handler			*/
/*									*/
/* Copyright (c) 2004  Tim Edwards, Open Circuit Design, Inc., and	*/
/* MultiGiG, Inc.							*/
/*----------------------------------------------------------------------*/

/*----------------------------------------------------------------------*/
/*      written by Tim Edwards, 1/29/04    				*/
/*----------------------------------------------------------------------*/

#define MODE_UNDO (u_char)0
#define MODE_REDO (u_char)1

#define MAX_UNDO_EVENTS 100

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

#include <X11/Intrinsic.h>
#include <X11/StringDefs.h>

/*----------------------------------------------------------------------*/
/* Local includes							*/
/*----------------------------------------------------------------------*/

#ifdef TCL_WRAPPER
#include <tk.h>
#endif

#include "xcircuit.h"

/*----------------------------------------------------------------------*/
/* Function prototype declarations                                      */
/*----------------------------------------------------------------------*/
#include "prototypes.h"

/*----------------------------------------------------------------------*/
/* Local structure definitions for holding undo information		*/
/*----------------------------------------------------------------------*/

typedef struct {
   float angle1;
   float angle2;
   short radius;
   short yaxis;
} arcinfo;

typedef struct {
   short number;
   genericptr *element;
   short *idx;
} uselection;

typedef struct {
   genericptr element;		/* element being edited */
   union {
      stringpart *string;	/* original contents of string, for label */
      pointlist points;		/* original polygon */
      arcinfo *arcspecs;	/* original arc values */
   } save;
} editelement;

/*----------------------------------------------------------------------*/
/* Externally declared variables					*/
/*----------------------------------------------------------------------*/

extern Globaldata xobjs;
extern Clientdata areastruct;
extern short eventmode;

/*----------------------------------------------------------------------*/
/* remember_selection ---						*/
/*									*/
/*   Copy a selection list into a "uselection" record.  The uselection	*/
/*   record maintains the order of the elements as well as pointers to	*/
/*   each element, so the original element ordering can be recovered.	*/
/*----------------------------------------------------------------------*/

uselection *remember_selection(objinstptr topinst, short *slist, int number)
{
   int i, idx;
   uselection *newlist;

   newlist = (uselection *)malloc(sizeof(uselection));
   if (number > 0) {
      newlist->element = (genericptr *)malloc(number * sizeof(genericptr));
      newlist->idx = (short *)malloc(number * sizeof(short));
   }
   else {
      newlist->element = NULL;
      newlist->idx = NULL;
   }
   newlist->number = number;
   for (i = 0; i < number; i++) {
      idx = *(slist + i);
      *(newlist->element + i) = *(topinst->thisobject->plist + idx);
      *(newlist->idx + i) = idx;
   }
   return newlist; 
}

/*----------------------------------------------------------------------*/
/* Create a selection list in areastruct from the saved uselection	*/
/* record.								*/
/*----------------------------------------------------------------------*/

short *regen_selection(objinstptr thisinst, uselection *srec)
{
   int i, j, k;
   genericptr egen;
   objectptr thisobj = thisinst->thisobject;
   Boolean reorder = False;
   short *slist;

   slist = (short *)malloc(srec->number * sizeof(short));

   k = 0;
   for (i = 0; i < srec->number; i++) {

      /* Use the element address, not the selection order. */
      egen = *(srec->element + i);
      if (egen == *(thisobj->plist + *(srec->idx + i)))
	 j = *(srec->idx + i);
      else {
	 reorder = True;
         for (j = 0; j < thisobj->parts; j++) {
	    if (egen == *(thisobj->plist + j))
	       break;
	 }
      }
      if (j < thisobj->parts) {
	 *(slist + k) = j;
	 k++;
      }
      else
	 Fprintf(stderr, "Error: element 0x%x in select list but not object\n",
		egen);
   } 

   /* If the selection order is different from the order in the object,	*/
   /* then rearrange the object's list to match the selection order.	*/

   if (reorder) {
      /* (to be done) */
   }

   if (k == 0) {
      free(slist);
      return NULL;
   }
   else
      return slist;
}

/*----------------------------------------------------------------------*/
/* free_editelement ---							*/
/*									*/
/*   Free memory allocated to an undo record edit element structure.	*/
/*----------------------------------------------------------------------*/

void free_editelement(editelement *erec)
{
   switch (erec->element->type) {
      case LABEL:
	 freelabel(erec->save.string);
         break;
      case POLYGON: case SPLINE:
	 free(erec->save.points);
	 break;
      case ARC:
	 free(erec->save.arcspecs);
	 break;
   }
   free(erec);
}

/*----------------------------------------------------------------------*/
/* free_selection ---							*/
/*									*/
/*   Free memory allocated to an undo record selection list.		*/
/*----------------------------------------------------------------------*/

void free_selection(uselection *selrec)
{
   if (selrec->number > 0) {
      free(selrec->element);
      free(selrec->idx);
   }
   free(selrec);
}

/*----------------------------------------------------------------------*/
/* get_original_string ---						*/
/*									*/
/* Find the original version of the given label.			*/
/*----------------------------------------------------------------------*/

stringpart *get_original_string(labelptr thislab)
{
   Undoptr chkrecord, thisrecord;
   editelement *erec;
   labelptr elab;

   thisrecord = xobjs.undostack;
   if (thisrecord == NULL) return NULL;

   for (chkrecord = thisrecord->next; chkrecord != NULL; chkrecord = chkrecord->next) {
      switch (chkrecord->type) {
	 case XCF_Edit:
	    erec = (editelement *)(chkrecord->undodata);
	    elab = (labelptr)(erec->element);
	    if (elab != thislab) return NULL;
	    return erec->save.string;
	    
	 default:
	    return NULL;
      }
   }
}

/*----------------------------------------------------------------------*/
/* select_previous ---							*/
/*									*/
/* Set the selection to what was previously selected in the undo list.	*/
/* Return 0 on success, -1 if no previous selection was found.		*/
/*----------------------------------------------------------------------*/

int select_previous(Undoptr thisrecord)
{
   Undoptr chkrecord;
   uselection *srec;

   unselect_all();
   for (chkrecord = thisrecord->next; chkrecord != NULL; chkrecord = chkrecord->next) {

      /* Selections may cross page changes, but only if in the same series */
      if ((chkrecord->thisinst != thisrecord->thisinst) &&
		(chkrecord->idx != thisrecord->idx))
	 break;

      switch (chkrecord->type) {
	 case XCF_Delete: case XCF_Pop: case XCF_Push:
	    /* Delete/Push/Pop records imply a canceled selection */
	    return 0;
	 case XCF_Copy:
	 case XCF_Select:
	    srec = (uselection *)chkrecord->undodata;
	    areastruct.selectlist = regen_selection(thisrecord->thisinst, srec);
	    areastruct.selects = (areastruct.selectlist) ? srec->number : 0;
	    return 0;
      }
   }
   return -1;
}

/*----------------------------------------------------------------------*/
/* This is similar to the above routine, but we just return a pointer	*/
/* to the index list in the uselection record.  This lets the undelete	*/
/* function restore the original ordering of parts that were deleted.	*/
/*----------------------------------------------------------------------*/

short *recover_selectlist(Undoptr thisrecord)
{
   Undoptr chkrecord;
   uselection *srec;

   for (chkrecord = thisrecord->next; chkrecord != NULL; chkrecord = chkrecord->next) {

      /* Selections may cross page changes, but only if in the same series */
      if ((chkrecord->thisinst != thisrecord->thisinst) &&
		(chkrecord->idx != thisrecord->idx))
	 break;

      switch (chkrecord->type) {
	 case XCF_Delete: case XCF_Pop: case XCF_Push:
	    /* Delete/Push/Pop records imply a canceled selection */
	    return NULL;
	 case XCF_Copy:
	    /* Copy is the same as Select, but the copied objects are	*/
	    /* always at the end of the element list.  Returning NULL	*/
	    /* is the same as declaring that elements should be		*/
	    /* appended to the end of the object's element list.	*/
	    return NULL;
	 case XCF_Select:
	    srec = (uselection *)chkrecord->undodata;
	    return srec->idx;
      }
   }
   return NULL;
}

/*----------------------------------------------------------------------*/
/* Put element "thiselem" back into object "thisobj".  This is only	*/
/* used in the case where the elements were previously at the end of	*/
/* the object's element list.						*/
/*----------------------------------------------------------------------*/

void undelete_one_element(objinstptr thisinst, genericptr thiselem)
{
   objectptr thisobj = thisinst->thisobject;

   PLIST_INCR(thisobj);
   *(thisobj->plist + thisobj->parts) = thiselem;
   thisobj->parts++;
}

/*----------------------------------------------------------------------*/
/* register_for_undo ---						*/
/*									*/
/*   Register an event with the undo handler.  This creates a record	*/
/*   based on the type, which is one of the XCF_* bindings (see		*/
/*   xcircuit.h for the list).  This is a variable-argument routine,	*/
/*   with the arguments dependent on the command.			*/
/*									*/
/*   thisinst is the instance of the object in which the event occurred	*/
/*   and is registered for every event type.				*/
/*									*/
/*   "mode" is UNDO_MORE if one or more undo records are expected to 	*/
/*   follow in a series, or UNDO_DONE if this completes a series, or is	*/
/*   as single undo event.						*/
/*----------------------------------------------------------------------*/

void register_for_undo(u_int type, u_char mode, objinstptr thisinst, ...)
{
   va_list args;
   int usize, drawmode, npage, opage, *idata, snum, deltax, deltay, dir;
   short *slist;
   objectptr delobj;
   objinstptr newinst;
   Undoptr newrecord;
   uselection *srec;
   editelement *erec;
   genericptr egen, newgen;
   XPoint *fpoint;

   /* Do not register new events while running undo/redo actions! */
   if (eventmode == UNDO_MODE) return;
 
   /* This action invalidates everything in the "redo" stack, so flush it */
   flush_redo_stack();

   /* Create the new record and push it onto the stack */
   newrecord = (Undoptr)malloc(sizeof(Undostack));
   newrecord->next = xobjs.undostack;
   newrecord->last = NULL;
   newrecord->type = type;
   newrecord->thisinst =  thisinst;
   newrecord->undodata = (char *)NULL;
   newrecord->idata = 0;

   if (xobjs.undostack) {
      xobjs.undostack->last = newrecord;
      if (xobjs.undostack->idx < 0) {
	 xobjs.undostack->idx = -xobjs.undostack->idx;
	 newrecord->idx = xobjs.undostack->idx;
      }
      else
	 newrecord->idx = xobjs.undostack->idx + 1;
   }
   else
      newrecord->idx = 1;

   if (mode == UNDO_MORE)
      newrecord->idx = -newrecord->idx;
   
   xobjs.undostack = newrecord;

   va_start(args, thisinst);

   switch(type) {
      case XCF_Delete:
	 /* 2 args:							*/
	 /*    delobj = pointer to object containing deleted entries	*/
	 /*    drawmode = true if elements should be erased		*/ 
	 delobj = va_arg(args, objectptr);
	 drawmode = va_arg(args, int);
	 newrecord->undodata = (char *)delobj;
	 newrecord->idata = drawmode;
	 break;

      case XCF_Select_Save:
	 /* 1 arg:							*/
	 /*    newobj = pointer to instance of new object		*/
	 newinst = va_arg(args, objinstptr);
	 newrecord->undodata = (char *)newinst;
	 break;

      case XCF_Page:
	 /* 2 args:							*/
	 /*	opage = original page (int)				*/
	 /*	npage = new page (int)					*/
	 opage = va_arg(args, int);
	 npage = va_arg(args, int);
	 idata = (int *)malloc(sizeof(int));
	 *idata = npage;
	 newrecord->undodata = (char *)idata;
	 newrecord->idata = opage;
	 break;

      case XCF_Pop:
	 /* No args; instance to pop is the instance passed. 		*/
	 break;

      case XCF_Push:
	 /* 1 arg:							*/
	 /*	newinst = object instance to push			*/
	 newinst = va_arg(args, objinstptr);
	 newrecord->undodata = (char *)newinst;
	 break;

      case XCF_Copy:
      case XCF_Select:
      case XCF_Library_Pop:
	 /* 2 args:							*/
	 /*	slist = current selection list (short *)		*/
	 /*	snum  = number of selections (int)			*/
	 slist = va_arg(args, short *);
	 snum = va_arg(args, int);
	 srec = remember_selection(thisinst, slist, snum);
	 newrecord->undodata = (char *)srec;
	 /* Fprintf(stdout, "Undo: registered selection or copy action\n"); */
	 break;

      case XCF_Box: case XCF_Arc: case XCF_Wire: case XCF_Text:
      case XCF_Pin_Label: case XCF_Pin_Global: case XCF_Info_Label:
      case XCF_Spline: case XCF_Dot: case XCF_Graphic:
	 /* 1 arg:							*/
	 /*	egen = element just created (genericptr)		*/
	 egen = va_arg(args, genericptr);
	 newrecord->undodata = (char *)egen;
	 break;

      case XCF_Edit:
	 /* 1 arg:							*/
	 /*	egen = element to be edited (genericptr)		*/
	 egen = va_arg(args, genericptr);

	 /* Create a copy of the element to save */
	 erec = (editelement *)malloc(sizeof(editelement));
	 erec->element = egen;
	 switch(egen->type) {
	    case LABEL:
	       erec->save.string =
			stringcopyall(((labelptr)egen)->string,
			areastruct.topinstance);
	       newrecord->idata = ((labelptr)egen)->justify;
	       break;
	    case POLYGON:
	       newrecord->idata = ((polyptr)egen)->number;
	       erec->save.points = copypoints(((polyptr)egen)->points,
			newrecord->idata);
	       break;
	    case SPLINE:
	       erec->save.points =
			copypoints((pointlist)((splineptr)egen)->ctrl, 4);
	       break;
	    case ARC:
	       erec->save.arcspecs = (arcinfo *)malloc(sizeof(arcinfo));
	       erec->save.arcspecs->angle1 = ((arcptr)egen)->angle1;
	       erec->save.arcspecs->angle2 = ((arcptr)egen)->angle2;
	       erec->save.arcspecs->radius = ((arcptr)egen)->radius;
	       erec->save.arcspecs->yaxis = ((arcptr)egen)->yaxis;
	       break;
	 }
	 newrecord->undodata = (char *)erec;
	 break;

      case XCF_Flip_X: case XCF_Flip_Y:
	 /* 1 arg:							*/
	 /*	fpoint = point of flip (XPoint *)			*/
	 fpoint = va_arg(args, XPoint *);
	 newrecord->undodata = (char *)malloc(sizeof(XPoint));
	 ((XPoint *)newrecord->undodata)->x = fpoint->x;
	 ((XPoint *)newrecord->undodata)->y = fpoint->y;
	 break;

      case XCF_Rotate:
	 /* 2 args:							*/
	 /*	fpoint = point of flip (XPoint *)			*/
	 /*	dir = direction and amound of rotation (int)		*/
	 fpoint = va_arg(args, XPoint *);
	 dir = va_arg(args, int);
	 newrecord->undodata = (char *)malloc(sizeof(XPoint));
	 ((XPoint *)newrecord->undodata)->x = fpoint->x;
	 ((XPoint *)newrecord->undodata)->y = fpoint->y;
	 newrecord->idata = dir;
	 break;

      case XCF_Move:
	 /* 2 args:							*/
	 /*	deltax = change in x position (int)			*/
	 /*	deltay = change in y position (int)			*/
	 deltax = va_arg(args, int);
	 deltay = va_arg(args, int);
	 newrecord->undodata = (char *)malloc(sizeof(XPoint));
	 ((XPoint *)newrecord->undodata)->x = deltax;
	 ((XPoint *)newrecord->undodata)->y = deltay;
	 /* Fprintf(stdout, "Undo: registered move of delta (%d, %d)\n",
			deltax, deltay); */
	 break;
   }

   va_end(args);
}

/*----------------------------------------------------------------------*/
/* undo_one_action ---							*/
/*	Play undo record back one in the stack.				*/
/*----------------------------------------------------------------------*/

short undo_one_action()
{
   Undoptr thisrecord, chkrecord;
   objectptr thisobj;
   objinstptr thisinst;
   uselection *srec;
   editelement *erec;
   short *slist;
   XPoint *delta;
   int i, snum, npage, opage;
   genericptr egen;

   /* Undo the recorded action and shift the undo record pointer.	*/

   thisrecord = xobjs.undostack;
   if (thisrecord == NULL) {
      Fprintf(stderr, "Nothing to undo!\n");
      return;
   }

   xobjs.undostack = thisrecord->next;
   xobjs.redostack = thisrecord;

   /* Setting eventmode to UNDO_MODE prevents register_for_undo() from 	*/
   /* being called again while executing the event.			*/

   eventmode = UNDO_MODE;

   /* type-dependent part */

   switch(thisrecord->type) {
      case XCF_Delete:
	 unselect_all();
	 thisobj = (objectptr)thisrecord->undodata;
	 areastruct.selects = thisobj->parts;
	 areastruct.selectlist = xc_undelete(thisrecord->thisinst,
			thisobj, (short)thisrecord->idata,
			recover_selectlist(thisrecord));
	 srec = remember_selection(thisrecord->thisinst, areastruct.selectlist,
			areastruct.selects);
	 thisrecord->undodata = (char *)srec;
	 draw_all_selected();
	 break;

      /* To be finished:  Needs to remove the object & instance from	*/
      /* the library and library page.  Should keep the empty object	*/
      /* around so we have the name.					*/

      case XCF_Select_Save:
	 unselect_all();
	 thisinst = (objinstptr)thisrecord->undodata;
	 thisobj = thisinst->thisobject;

	 /* Remove the instance */
	 i = thisrecord->thisinst->thisobject->parts - 1;
	 if ((genericptr)thisinst != *(thisrecord->thisinst->thisobject->plist + i)) {
	    Fprintf(stderr, "Error: No such element!\n");
	    thisrecord->undodata = NULL;
	    break;
	 }
	 else
	    delete_one_element(thisrecord->thisinst, egen);

	 /* Put back all the parts */
	 areastruct.selects = thisobj->parts;
	 areastruct.selectlist = xc_undelete(thisrecord->thisinst,
			thisobj, (short)thisrecord->idata,
			recover_selectlist(thisrecord));
	 srec = remember_selection(thisrecord->thisinst, areastruct.selectlist,
			areastruct.selects);
	 thisrecord->undodata = (char *)srec;
	 draw_all_selected();
	 break;

      case XCF_Push:
	 popobject(areastruct.area, 0, NULL);
	 break;

      case XCF_Pop:
	 pushobject((objinstptr)thisrecord->thisinst);
	 break;

      case XCF_Page:
	 newpage(thisrecord->idata);
	 break;

      case XCF_Select:

	 /* If there was a previous selection in the undo list,	*/
	 /* revert to it.					*/
	 
	 select_previous(thisrecord);
	 draw_all_selected();
	 break;

      case XCF_Box: case XCF_Arc: case XCF_Wire: case XCF_Text:
		case XCF_Pin_Label: case XCF_Pin_Global: case XCF_Info_Label:
		case XCF_Spline: case XCF_Dot: case XCF_Graphic:
	 egen = (genericptr)thisrecord->undodata;
	 i = thisrecord->thisinst->thisobject->parts - 1;
	 if (egen != *(thisrecord->thisinst->thisobject->plist + i)) {
	    Fprintf(stderr, "Error: No such element!\n");
	    thisrecord->undodata = NULL;
	 }
	 else {
	    delete_one_element(thisrecord->thisinst, egen);
	    drawarea(areastruct.area, NULL, NULL);
	 }
	 break;

      case XCF_Edit:
	 erec = (editelement *)thisrecord->undodata;
	 switch (erec->element->type) {
	    case LABEL: {
	       stringpart *tmpstr;
	       int tmpjust;
	       labelptr elab = (labelptr)(erec->element);
	       undrawtext(elab);
	       tmpstr = elab->string;
	       tmpjust = (int)elab->justify;
	       elab->string = stringcopyback(erec->save.string,
				thisrecord->thisinst);
	       elab->justify = (short)thisrecord->idata;
	       erec->save.string = tmpstr;
	       thisrecord->idata = tmpjust;
	       resolveparams(thisrecord->thisinst);
	       redrawtext(elab);
	    } break;
	    case POLYGON: {
	       pointlist tmppts;
	       int tmpnum;
	       polyptr epoly = (polyptr)(erec->element);
	       tmppts = epoly->points;
	       tmpnum = epoly->number;
	       epoly->points = erec->save.points;
	       epoly->number = thisrecord->idata;
	       erec->save.points = tmppts;
	       thisrecord->idata = tmpnum;
	       drawarea(areastruct.area, NULL, NULL);
	    } break;
	    case SPLINE: {
	       pointlist tmppts;
	       splineptr espline = (splineptr)(erec->element);
	       tmppts = copypoints((pointlist)espline->ctrl, 4);
	       for (i = 0; i < 4; i++)
		   espline->ctrl[i] = *(erec->save.points + i);
	       free(erec->save.points);
	       erec->save.points = tmppts;
	       calcspline(espline);
	       drawarea(areastruct.area, NULL, NULL);
	    } break;
	    case ARC: {
	       arcinfo tmpinfo;
	       arcptr earc = (arcptr)(erec->element);
	       tmpinfo.angle1 = earc->angle1; 
	       tmpinfo.angle2 = earc->angle2; 
	       tmpinfo.radius = earc->radius; 
	       tmpinfo.yaxis = earc->yaxis; 
	       earc->angle1 = erec->save.arcspecs->angle1;
	       earc->angle2 = erec->save.arcspecs->angle2;
	       earc->radius = erec->save.arcspecs->radius;
	       earc->yaxis = erec->save.arcspecs->yaxis;
	       *(erec->save.arcspecs) = tmpinfo;
	       calcarc(earc);
	       drawarea(areastruct.area, NULL, NULL);
	    }
	 }
	 break;

      case XCF_Library_Pop:
	 srec = (uselection *)thisrecord->undodata;
	 slist = regen_selection(thisrecord->thisinst, srec);
	 thisobj = delete_element(thisrecord->thisinst, slist,
			srec->number, DRAW);
	 free(slist);
	 thisrecord->undodata = (char *)thisobj;
	 break;

      case XCF_Copy:
	 srec = (uselection *)thisrecord->undodata;
	 slist = regen_selection(thisrecord->thisinst, srec);
	 thisobj = delete_element(thisrecord->thisinst, slist,
			srec->number, DRAW);
	 free(slist);
	 thisrecord->undodata = (char *)thisobj;

	 /* Revert selection to previously selected */
	 select_previous(thisrecord);
	 drawarea(areastruct.area, NULL, NULL);
	 break;

      case XCF_Flip_X:
	 areastruct.save = *((XPoint *)thisrecord->undodata);
	 elementflip();
	 break;

      case XCF_Flip_Y:
	 areastruct.save = *((XPoint *)thisrecord->undodata);
	 elementvflip();
	 break;

      case XCF_Rotate:
	 areastruct.save = *((XPoint *)thisrecord->undodata);
	 elementrotate(-thisrecord->idata);
	 break;

      case XCF_Move:
	 delta = (XPoint *)thisrecord->undodata;
	 placeselects(-(delta->x), -(delta->y), NULL);
	 drawarea(areastruct.area, NULL, NULL);
	 break;

      default:
	 Fprintf(stderr, "Undo not implemented for this action!\n");
	 break;
   }

   eventmode = NORMAL_MODE;	/* Set on a per-event-type basis? */

   /* Diagnostic, to check if all multiple-event undo series are resolved */
   if (thisrecord->idx < 0) {
      Fprintf(stderr, "Warning:  Unfinished undo series in stack!\n");
      thisrecord->idx = -thisrecord->idx;
   }
   return thisrecord->idx;
}

/*----------------------------------------------------------------------*/
/* undo_action ---							*/
/*	Play undo record back to the completion of a series.		*/
/*----------------------------------------------------------------------*/

void undo_action()
{
   short idx = undo_one_action(False);
   while (xobjs.undostack && xobjs.undostack->idx == idx)
      undo_one_action(False);
}

/*----------------------------------------------------------------------*/
/* redo_one_action ---							*/
/*	Play undo record forward one in the stack.			*/
/*----------------------------------------------------------------------*/

short redo_one_action()
{
   Undoptr thisrecord;
   objectptr thisobj;
   genericptr egen;
   short *slist;
   XPoint *delta;
   uselection *srec;
   editelement *erec;
   int i, tpage, snum;

   /* Undo the recorded action and shift the undo record pointer.	*/

   thisrecord = xobjs.redostack;
   if (thisrecord == NULL) {
      Fprintf(stderr, "Nothing to redo!\n");
      return;
   }
   xobjs.undostack = thisrecord;
   xobjs.redostack = thisrecord->last;

   eventmode = UNDO_MODE;

   /* type-dependent part */

   switch(thisrecord->type) {
      case XCF_Delete:
	 srec = (uselection *)thisrecord->undodata;
	 slist = regen_selection(thisrecord->thisinst, srec);
	 thisobj = delete_element(thisrecord->thisinst, slist,
		srec->number, DRAW);
	 free(slist);
	 thisrecord->undodata = (char *)thisobj;
	 thisrecord->idata = (int)DRAW;
	 unselect_all();
	 drawarea(areastruct.area, NULL, NULL);
	 break;

      /* Unfinished! */
      case XCF_Select_Save:
	 srec = (uselection *)thisrecord->undodata;
	 slist = regen_selection(thisrecord->thisinst, srec);
	 break;

      case XCF_Page:
	 newpage(*((int *)thisrecord->undodata));
	 break;

      case XCF_Pop:
	 popobject(areastruct.area, 0, NULL);
	 break;

      case XCF_Push:
	 pushobject((objinstptr)thisrecord->undodata);
	 break;

      case XCF_Select:
	 unselect_all();
	 srec = (uselection *)thisrecord->undodata;
	 areastruct.selectlist = regen_selection(thisrecord->thisinst, srec);
	 areastruct.selects = (areastruct.selectlist) ? srec->number : 0;
	 draw_all_selected();
	 break;

      case XCF_Library_Pop:
	 thisobj = (objectptr)thisrecord->undodata;
	 if (thisobj != NULL) {
	    unselect_all();
	    snum = thisobj->parts;
	    slist = xc_undelete(thisrecord->thisinst, thisobj, DRAW, NULL);
	    thisrecord->undodata = (char *)remember_selection(thisrecord->thisinst,
			slist, snum);
	    free(slist);
	 }
	 break;

      case XCF_Copy:
	 thisobj = (objectptr)thisrecord->undodata;
	 if (thisobj != NULL) {
	    unselect_all();
	    areastruct.selects = thisobj->parts;
	    areastruct.selectlist = xc_undelete(thisrecord->thisinst, thisobj, DRAW,
			NULL);
	    thisrecord->undodata = (char *)remember_selection(thisrecord->thisinst,
			areastruct.selectlist, areastruct.selects);
	    draw_all_selected();
	 }
	 break;

      case XCF_Box: case XCF_Arc: case XCF_Wire: case XCF_Text:
      case XCF_Pin_Label: case XCF_Pin_Global: case XCF_Info_Label:
      case XCF_Spline: case XCF_Dot: case XCF_Graphic:
	 egen = (genericptr)thisrecord->undodata;
	 undelete_one_element(thisrecord->thisinst, egen);
	 drawarea(areastruct.area, NULL, NULL);
	 break;

      case XCF_Edit:
	 erec = (editelement *)thisrecord->undodata;
	 switch (erec->element->type) {
	    case LABEL: {
	       stringpart *tmpstr;
	       int tmpjust;
	       labelptr elab = (labelptr)(erec->element);
	       undrawtext(elab);
	       tmpstr = elab->string;
	       tmpjust = (int)elab->justify;
	       elab->string = stringcopyback(erec->save.string,
				thisrecord->thisinst);
	       elab->justify = (short)thisrecord->idata;
	       erec->save.string = tmpstr;
	       thisrecord->idata = tmpjust;
	       resolveparams(thisrecord->thisinst);
	       redrawtext(elab);
	    } break;
	    case POLYGON: {
	       pointlist tmppts;
	       int tmpnum;
	       polyptr epoly = (polyptr)(erec->element);
	       tmppts = epoly->points;
	       tmpnum = epoly->number;
	       epoly->points = erec->save.points;
	       epoly->number = thisrecord->idata;
	       erec->save.points = tmppts;
	       thisrecord->idata = tmpnum;
	       drawarea(areastruct.area, NULL, NULL);
	    } break;
	    case SPLINE: {
	       pointlist tmppts;
	       splineptr espline = (splineptr)(erec->element);
	       tmppts = copypoints((pointlist)espline->ctrl, 4);
	       for (i = 0; i < 4; i++)
		   espline->ctrl[i] = *(erec->save.points + i);
	       free(erec->save.points);
	       erec->save.points = tmppts;
	       calcspline(espline);
	       drawarea(areastruct.area, NULL, NULL);
	    } break;
	    case ARC: {
	       arcinfo tmpinfo;
	       arcptr earc = (arcptr)(erec->element);
	       tmpinfo.angle1 = earc->angle1; 
	       tmpinfo.angle2 = earc->angle2; 
	       tmpinfo.radius = earc->radius; 
	       tmpinfo.yaxis = earc->yaxis; 
	       earc->angle1 = erec->save.arcspecs->angle1;
	       earc->angle2 = erec->save.arcspecs->angle2;
	       earc->radius = erec->save.arcspecs->radius;
	       earc->yaxis = erec->save.arcspecs->yaxis;
	       *(erec->save.arcspecs) = tmpinfo;
	       calcarc(earc);
	       drawarea(areastruct.area, NULL, NULL);
	    }
	 }
	 break;

      case XCF_Flip_X:
	 areastruct.save = *((XPoint *)thisrecord->undodata);
	 elementflip();
	 break;

      case XCF_Flip_Y:
	 areastruct.save = *((XPoint *)thisrecord->undodata);
	 elementvflip();
	 break;

      case XCF_Rotate:
	 areastruct.save = *((XPoint *)thisrecord->undodata);
	 elementrotate(thisrecord->idata);
	 break;

      case XCF_Move:
	 delta = (XPoint *)thisrecord->undodata;
	 placeselects(delta->x, delta->y, NULL);
	 drawarea(areastruct.area, NULL, NULL);
	 break;

      default:
	 Fprintf(stderr, "Undo not implemented for this action!\n");
	 break;
   }

   eventmode = NORMAL_MODE;	/* Set on a per-event-type basis? */
   return thisrecord->idx;
}

/*----------------------------------------------------------------------*/
/* redo_action ---							*/
/*	Play undo record forward to the completion of a series.		*/
/*----------------------------------------------------------------------*/

void redo_action()
{
   short idx = redo_one_action();
   while (xobjs.redostack && xobjs.redostack->idx == idx)
      redo_one_action();
}

/*----------------------------------------------------------------------*/
/* flush_redo_stack ---							*/
/*	Free all memory allocated to the redo stack due to the		*/
/* 	insertion of a new undo record.					*/
/*----------------------------------------------------------------------*/

void flush_redo_stack()
{
   Undoptr thisrecord, nextrecord;

   if (xobjs.redostack == NULL) return;	/* no redo stack */

   thisrecord = xobjs.redostack;

   while (thisrecord != NULL) {
      nextrecord = thisrecord->last;
      free_redo_record(thisrecord);
      thisrecord = nextrecord;
   }
   xobjs.redostack = NULL;

   if (xobjs.undostack)
      xobjs.undostack->last = NULL;
}

/*----------------------------------------------------------------------*/
/* flush_undo_stack ---							*/
/*	Free all memory allocated to the undo and redo stacks.		*/
/*----------------------------------------------------------------------*/

void flush_undo_stack()
{
   Undoptr thisrecord, nextrecord;

   flush_redo_stack();

   thisrecord = xobjs.undostack;

   while (thisrecord != NULL) {
      nextrecord = thisrecord->next;
      free_undo_record(thisrecord);
      thisrecord = nextrecord;
   }
   xobjs.undostack = NULL;
}

/*----------------------------------------------------------------------*/
/* free_undo_data ---							*/
/*	Free memory allocated to the "undodata" part of the undo	*/
/*	record, based on the record type.				*/
/*									*/
/* "mode" specifies whether this is for an "undo" or a "redo" event.	*/
/*									*/
/*	Note that the action taken for a specific record may *NOT* be	*/
/*	the same for a record in the undo stack as it is for a record	*/
/*	in the redo stack, because the data types are changed when	*/
/*	moving from one record to the next.				*/
/*----------------------------------------------------------------------*/

void free_undo_data(Undoptr thisrecord, u_char mode)
{
   u_int type;
   objectptr uobj;
   uselection *srec;
   editelement *erec;

   type = thisrecord->type;
   switch (type) {
      case XCF_Delete:
	 if (mode == MODE_UNDO) {
	    uobj = (objectptr)thisrecord->undodata;
	    reset(uobj, DESTROY);
	 }
	 else { /* MODE_REDO */
	    srec = (uselection *)thisrecord->undodata;
	    free_selection(srec);
	 }
	 break;

      case XCF_Box: case XCF_Arc: case XCF_Wire: case XCF_Text:
      case XCF_Pin_Label: case XCF_Pin_Global: case XCF_Info_Label:
      case XCF_Spline: case XCF_Dot: case XCF_Graphic:
	 /* if MODE_UNDO, the element is on the page, so don't destroy it! */
	 if (mode == MODE_REDO)
	    free(thisrecord->undodata);
	 break;

      case XCF_Edit:
	 erec = (editelement *)thisrecord->undodata;
	 free_editelement(erec);
	 break;

      case XCF_Copy:
      case XCF_Library_Pop:
	 if (mode == MODE_UNDO) {
	    srec = (uselection *)thisrecord->undodata;
	    free_selection(srec);
	 }
	 else { /* MODE_REDO */
	    uobj = (objectptr)thisrecord->undodata;
	    reset(uobj, DESTROY);
	 }
	 break;

      case XCF_Push:
	 /* Do nothing --- undodata points to a valid element */
	 break;

      case XCF_Select:
	 srec = (uselection *)thisrecord->undodata;
	 free_selection(srec);
	 break;

      default:
	 if (thisrecord->undodata != NULL)
	    free(thisrecord->undodata);
	 break;
   }
   thisrecord->undodata = NULL;
}


/*----------------------------------------------------------------------*/
/* free_undo_record ---							*/
/*	Free allocated memory for one record in the undo stack.		*/
/*----------------------------------------------------------------------*/

void free_undo_record(Undoptr thisrecord)
{
   Undoptr nextrecord, lastrecord;

   /* Reset the master list pointers */

   if (xobjs.undostack == thisrecord)
      xobjs.undostack = thisrecord->next;

   /* Relink the stack pointers */

   if (thisrecord->last)
      thisrecord->last->next = thisrecord->next;

   if (thisrecord->next)
      thisrecord->next->last = thisrecord->last;

   /* Free memory allocated to the record */

   free_undo_data(thisrecord, MODE_UNDO);
   free(thisrecord);
}

/*----------------------------------------------------------------------*/
/* free_redo_record ---							*/
/*	Free allocated memory for one record in the redo stack.		*/
/*----------------------------------------------------------------------*/

void free_redo_record(Undoptr thisrecord)
{
   Undoptr nextrecord, lastrecord;

   /* Reset the master list pointers */

   if (xobjs.redostack == thisrecord)
      xobjs.redostack = thisrecord->last;

   /* Relink the stack pointers */

   if (thisrecord->next)
      thisrecord->next->last = thisrecord->last;

   if (thisrecord->last)
      thisrecord->last->next = thisrecord->next;

   /* Free memory allocated to the record */

   free_undo_data(thisrecord, MODE_REDO);
   free(thisrecord);
}

/*----------------------------------------------------------------------*/
/* truncate_undo_stack ---						*/
/*	If the limit MAX_UNDO_EVENTS has been reached, discard the	*/
/*	last undo series on the stack (index = 1) and renumber the	*/
/*	others by decrementing.						*/
/*----------------------------------------------------------------------*/

void truncate_undo_stack()
{
   Undoptr thisrecord, nextrecord;

   thisrecord = xobjs.undostack;
   while (thisrecord != NULL) {
      nextrecord = thisrecord->next;
      if (thisrecord->idx > 1)
	 thisrecord->idx--;
      else
         free_undo_record(thisrecord);
      thisrecord = nextrecord;
   }
}

#ifndef TCL_WRAPPER

/* Calls from the Xt menus (see menus.h)				*/
/* These are wrappers for undo_action and redo_action			*/

void undo_call(xcWidget button, caddr_t clientdata, caddr_t calldata)
{
   undo_action();
}

void redo_call(xcWidget button, caddr_t clientdata, caddr_t calldata)
{
   redo_action();
}

#endif
/*----------------------------------------------------------------------*/
