/*----------------------------------------------------------------------*/ /* 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 #include #include #include #ifndef XC_WIN32 #include #include #endif /*----------------------------------------------------------------------*/ /* Local includes */ /*----------------------------------------------------------------------*/ #ifdef TCL_WRAPPER #include #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 /*----------------------------------------------------------------------*/