/* * tkUndo.c -- * * This module provides the implementation of an undo stack. * * Copyright (c) 2002 by Ludwig Callewaert. * * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. * * RCS: @(#) $Id: tkUndo.c,v 1.3 2003/10/30 23:35:14 dkf Exp $ */ #include "tkUndo.h" static int UndoScriptsEvaluate _ANSI_ARGS_ ((Tcl_Interp *interp, Tcl_Obj *objPtr, TkUndoAtomType type)); /* *---------------------------------------------------------------------- * * TkUndoPushStack -- * Push elem on the stack identified by stack. * * Results: * None. * * Side effects: * None. * *---------------------------------------------------------------------- */ void TkUndoPushStack(stack, elem) TkUndoAtom **stack, *elem; { elem->next = *stack; *stack = elem; } /* *---------------------------------------------------------------------- * * TkUndoPopStack -- * Remove and return the top element from the stack identified by * stack. * * Results: * None. * * Side effects: * None. * *---------------------------------------------------------------------- */ TkUndoAtom * TkUndoPopStack(stack) TkUndoAtom **stack; { TkUndoAtom *elem = NULL; if (*stack != NULL) { elem = *stack; *stack = elem->next; } return elem; } /* *---------------------------------------------------------------------- * * TkUndoInsertSeparator -- * Insert a separator on the stack, indicating a border for * an undo/redo chunk. * * Results: * None. * * Side effects: * None. * *---------------------------------------------------------------------- */ int TkUndoInsertSeparator(stack) TkUndoAtom **stack; { TkUndoAtom *separator; if (*stack!=NULL && (*stack)->type!=TK_UNDO_SEPARATOR) { separator = (TkUndoAtom *) ckalloc(sizeof(TkUndoAtom)); separator->type = TK_UNDO_SEPARATOR; TkUndoPushStack(stack,separator); return 1; } return 0; } /* *---------------------------------------------------------------------- * * TkUndoClearStack -- * Clear an entire undo or redo stack and destroy all elements in it. * * Results: * None. * * Side effects: * None. * *---------------------------------------------------------------------- */ void TkUndoClearStack(stack) TkUndoAtom **stack; /* An Undo or Redo stack */ { TkUndoAtom *elem; while ((elem = TkUndoPopStack(stack)) != NULL) { if (elem->type != TK_UNDO_SEPARATOR) { Tcl_DecrRefCount(elem->apply); Tcl_DecrRefCount(elem->revert); } ckfree((char *)elem); } *stack = NULL; } /* *---------------------------------------------------------------------- * * TkUndoPushAction * Push a new elem on the stack identified by stack. * action and revert are given through Tcl_Obj's to which * we will retain a reference. (So they can be passed in * with a zero refCount if desired). * * Results: * None. * * Side effects: * None. * *---------------------------------------------------------------------- */ void TkUndoPushAction(stack, actionScript, revertScript, isList) TkUndoRedoStack *stack; /* An Undo or Redo stack */ Tcl_Obj *actionScript; /* The script to get the action (redo) */ Tcl_Obj *revertScript; /* The script to revert the action (undo) */ int isList; /* Are the given objects lists of scripts? */ { TkUndoAtom *atom; atom = (TkUndoAtom *) ckalloc(sizeof(TkUndoAtom)); if (isList) { atom->type = TK_UNDO_ACTION_LIST; } else { atom->type = TK_UNDO_ACTION; } atom->apply = actionScript; Tcl_IncrRefCount(atom->apply); atom->revert = revertScript; Tcl_IncrRefCount(atom->revert); TkUndoPushStack(&(stack->undoStack), atom); TkUndoClearStack(&(stack->redoStack)); } /* *---------------------------------------------------------------------- * * TkUndoInitStack * Initialize a new undo/redo stack. * * Results: * An Undo/Redo stack pointer. * * Side effects: * None. * *---------------------------------------------------------------------- */ TkUndoRedoStack * TkUndoInitStack(interp, maxdepth) Tcl_Interp *interp; /* The interpreter */ int maxdepth; /* The maximum stack depth */ { TkUndoRedoStack *stack; /* An Undo/Redo stack */ stack = (TkUndoRedoStack *) ckalloc(sizeof(TkUndoRedoStack)); stack->undoStack = NULL; stack->redoStack = NULL; stack->interp = interp; stack->maxdepth = maxdepth; stack->depth = 0; return stack; } /* *---------------------------------------------------------------------- * * TkUndoInitStack * Initialize a new undo/redo stack. * * Results: * An Undo/Redo stack pointer. * * Side effects: * None. * *---------------------------------------------------------------------- */ void TkUndoSetDepth(stack, maxdepth) TkUndoRedoStack *stack; /* An Undo/Redo stack */ int maxdepth; /* The maximum stack depth */ { TkUndoAtom *elem, *prevelem; int sepNumber = 0; stack->maxdepth = maxdepth; if (stack->maxdepth>0 && stack->depth>stack->maxdepth) { /* * Maximum stack depth exceeded. We have to remove the last * compound elements on the stack */ elem = stack->undoStack; prevelem = NULL; while (sepNumber <= stack->maxdepth) { if (elem!=NULL && elem->type==TK_UNDO_SEPARATOR) { sepNumber++; } prevelem = elem; elem = elem->next; } prevelem->next = NULL; while (elem != NULL) { prevelem = elem; elem = elem->next; ckfree((char *) elem); } stack->depth = stack->maxdepth; } } /* *---------------------------------------------------------------------- * * TkUndoClearStacks * Clear both the undo and redo stack. * * Results: * None. * * Side effects: * None. * *---------------------------------------------------------------------- */ void TkUndoClearStacks(stack) TkUndoRedoStack *stack; /* An Undo/Redo stack */ { TkUndoClearStack(&(stack->undoStack)); TkUndoClearStack(&(stack->redoStack)); stack->depth = 0; } /* *---------------------------------------------------------------------- * * TkUndoFreeStack * Clear both the undo and redo stack and free the memory * allocated to the u/r stack pointer. * * Results: * None. * * Side effects: * None. * *---------------------------------------------------------------------- */ void TkUndoFreeStack(stack) TkUndoRedoStack *stack; /* An Undo/Redo stack */ { TkUndoClearStacks(stack); /* ckfree((TkUndoRedoStack *) stack); */ ckfree((char *) stack); } /* *---------------------------------------------------------------------- * * TkUndoInsertUndoSeparator -- * Insert a separator on the undo stack, indicating a border for * an undo/redo chunk. * * Results: * None. * * Side effects: * None. * *---------------------------------------------------------------------- */ void TkUndoInsertUndoSeparator(stack) TkUndoRedoStack *stack; { /* * TkUndoAtom * elem; * TkUndoAtom * prevelem; * int sepNumber = 0; */ if (TkUndoInsertSeparator(&(stack->undoStack))) { stack->depth++; TkUndoSetDepth(stack, stack->maxdepth); #if 0 if (stack->maxdepth>0 && stack->depth>stack->maxdepth) { elem = stack->undoStack; prevelem = NULL; while (sepNumber < stack->depth) { if (elem!=NULL && elem->type==TK_UNDO_SEPARATOR) { sepNumber++; } prevelem = elem; elem = elem->next; } prevelem->next = NULL; while (elem) { prevelem = elem; elem = elem->next; ckfree((char *) elem); } stack->depth; } #endif } } /* *---------------------------------------------------------------------- * * TkUndoRevert -- * Undo a compound action on the stack. * * Results: * A Tcl status code * * Side effects: * None. * *---------------------------------------------------------------------- */ int TkUndoRevert(stack) TkUndoRedoStack *stack; { TkUndoAtom *elem; /* insert a separator on the undo and the redo stack */ TkUndoInsertUndoSeparator(stack); TkUndoInsertSeparator(&(stack->redoStack)); /* Pop and skip the first separator if there is one*/ elem = TkUndoPopStack(&(stack->undoStack)); if (elem == NULL) { return TCL_ERROR; } if (elem!=NULL && elem->type==TK_UNDO_SEPARATOR) { ckfree((char *) elem); elem = TkUndoPopStack(&(stack->undoStack)); } while (elem!=NULL && elem->type!=TK_UNDO_SEPARATOR) { UndoScriptsEvaluate(stack->interp,elem->revert,elem->type); TkUndoPushStack(&(stack->redoStack),elem); elem = TkUndoPopStack(&(stack->undoStack)); } /* insert a separator on the redo stack */ TkUndoInsertSeparator(&(stack->redoStack)); stack->depth--; return TCL_OK; } /* *---------------------------------------------------------------------- * * TkUndoApply -- * Redo a compound action on the stack. * * Results: * A Tcl status code * * Side effects: * None. * *---------------------------------------------------------------------- */ int TkUndoApply(stack) TkUndoRedoStack *stack; { TkUndoAtom *elem; /* insert a separator on the undo stack */ TkUndoInsertSeparator(&(stack->undoStack)); /* Pop and skip the first separator if there is one*/ elem = TkUndoPopStack(&(stack->redoStack)); if (elem == NULL) { return TCL_ERROR; } if (elem!=NULL && elem->type==TK_UNDO_SEPARATOR) { ckfree((char *) elem); elem = TkUndoPopStack(&(stack->redoStack)); } while (elem!=NULL && elem->type!=TK_UNDO_SEPARATOR) { UndoScriptsEvaluate(stack->interp,elem->apply,elem->type); TkUndoPushStack(&(stack->undoStack), elem); elem = TkUndoPopStack(&(stack->redoStack)); } /* insert a separator on the undo stack */ TkUndoInsertSeparator(&(stack->undoStack)); stack->depth++; return TCL_OK; } /* *---------------------------------------------------------------------- * * UndoScriptsEvaluate -- * Execute either a single script, or a set of scripts * * Results: * A Tcl status code * * Side effects: * None. * *---------------------------------------------------------------------- */ static int UndoScriptsEvaluate(interp, objPtr, type) Tcl_Interp *interp; Tcl_Obj *objPtr; TkUndoAtomType type; { if (type == TK_UNDO_ACTION_LIST) { int objc; Tcl_Obj **objv; int res, i; res = Tcl_ListObjGetElements(interp, objPtr, &objc, &objv); if (res != TCL_OK) { return res; } for (i=0 ; i