diff options
Diffstat (limited to 'generic/tkUndo.c')
-rw-r--r-- | generic/tkUndo.c | 725 |
1 files changed, 518 insertions, 207 deletions
diff --git a/generic/tkUndo.c b/generic/tkUndo.c index 81c8648..bf2ed7c 100644 --- a/generic/tkUndo.c +++ b/generic/tkUndo.c @@ -1,378 +1,689 @@ -/* +/* * tkUndo.c -- * * This module provides the implementation of an undo stack. * * Copyright (c) 2002 by Ludwig Callewaert. + * Copyright (c) 2003-2004 by Vincent Darley. * - * See the file "license.terms" for information on usage and redistribution - * of this file, and for a DISCLAIMER OF ALL WARRANTIES. + * See the file "license.terms" for information on usage and redistribution of + * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ +#include "tkInt.h" #include "tkUndo.h" +static int EvaluateActionList(Tcl_Interp *interp, + TkUndoSubAtom *action); /* - * TkUndoPushStack - * Push elem on the stack identified by stack. + *---------------------------------------------------------------------- + * + * TkUndoPushStack -- + * + * Push elem on the stack identified by stack. * * Results: - * None + * None. * * Side effects: - * None. + * None. + * + *---------------------------------------------------------------------- */ - -void TkUndoPushStack ( stack, elem ) - TkUndoAtom ** stack; - TkUndoAtom * elem; -{ + +void +TkUndoPushStack( + TkUndoAtom **stack, + TkUndoAtom *elem) +{ elem->next = *stack; *stack = elem; } /* + *---------------------------------------------------------------------- + * * TkUndoPopStack -- - * Remove and return the top element from the stack identified by - * stack. + * + * Remove and return the top element from the stack identified by stack. * * Results: - * None + * None. * * Side effects: - * None. + * None. + * + *---------------------------------------------------------------------- */ - -TkUndoAtom * TkUndoPopStack ( stack ) - TkUndoAtom ** stack ; -{ - TkUndoAtom * elem = NULL; - if (*stack != NULL ) { - elem = *stack; - *stack = elem->next; + +TkUndoAtom * +TkUndoPopStack( + 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. + * + * Insert a separator on the stack, indicating a border for an undo/redo + * chunk. * * Results: - * None + * None. * * Side effects: - * None. + * None. + * + *---------------------------------------------------------------------- */ - -int TkUndoInsertSeparator ( stack ) - TkUndoAtom ** stack; + +int +TkUndoInsertSeparator( + 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; - } else { - return 0; + 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. + * + * Clear an entire undo or redo stack and destroy all elements in it. * * Results: - * None + * None. * * Side effects: - * None. + * None. + * + *---------------------------------------------------------------------- */ -void TkUndoClearStack ( stack ) - TkUndoAtom ** stack; /* An Undo or Redo stack */ +void +TkUndoClearStack( + TkUndoAtom **stack) /* An Undo or Redo stack */ { - TkUndoAtom * elem; - - while ( (elem = TkUndoPopStack(stack)) ) { - if ( elem->type != TK_UNDO_SEPARATOR ) { - Tcl_DecrRefCount(elem->apply); - Tcl_DecrRefCount(elem->revert); - } - ckfree((char *)elem); + TkUndoAtom *elem; + + while ((elem = TkUndoPopStack(stack)) != NULL) { + if (elem->type != TK_UNDO_SEPARATOR) { + TkUndoSubAtom *sub; + + sub = elem->apply; + while (sub != NULL) { + TkUndoSubAtom *next = sub->next; + + if (sub->action != NULL) { + Tcl_DecrRefCount(sub->action); + } + ckfree((char *)sub); + sub = next; + } + + sub = elem->revert; + while (sub != NULL) { + TkUndoSubAtom *next = sub->next; + + if (sub->action != NULL) { + Tcl_DecrRefCount(sub->action); + } + ckfree((char *)sub); + sub = next; + } + } + ckfree((char *)elem); } *stack = NULL; } /* - * TkUndoPushAction - * Push a new elem on the stack identified by stack. - * action and revert are given through Tcl_DStrings + *---------------------------------------------------------------------- + * + * 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 + * None. * * Side effects: - * None. + * None. + * + *---------------------------------------------------------------------- */ - -void TkUndoPushAction ( stack, actionScript, revertScript ) - TkUndoRedoStack * stack; /* An Undo or Redo stack */ - Tcl_DString * actionScript; /* The script to get the action (redo) */ - Tcl_DString * revertScript; /* The script to revert the action (undo) */ -{ - TkUndoAtom * atom; + +void +TkUndoPushAction( + TkUndoRedoStack *stack, /* An Undo or Redo stack */ + TkUndoSubAtom *apply, + TkUndoSubAtom *revert) +{ + TkUndoAtom *atom; atom = (TkUndoAtom *) ckalloc(sizeof(TkUndoAtom)); atom->type = TK_UNDO_ACTION; + atom->apply = apply; + atom->revert = revert; - atom->apply = Tcl_NewStringObj(Tcl_DStringValue(actionScript),Tcl_DStringLength(actionScript)); - Tcl_IncrRefCount(atom->apply); + TkUndoPushStack(&stack->undoStack, atom); + TkUndoClearStack(&stack->redoStack); +} + +/* + *---------------------------------------------------------------------- + * + * TkUndoMakeCmdSubAtom -- + * + * Create a new undo/redo step which must later be place into an undo + * stack with TkUndoPushAction. This sub-atom, if evaluated, will take + * the given command (if non-NULL), find its full Tcl command string, and + * then evaluate that command with the list elements of 'actionScript' + * appended. + * + * If 'subAtomList' is non-NULL, the newly created sub-atom is added onto + * the end of the linked list of which 'subAtomList' is a part. This + * makes it easy to build up a sequence of actions which will be pushed + * in one step. + * + * Note: if the undo stack can persist for longer than the Tcl_Command + * provided, the stack will cause crashes when actions are evaluated. In + * this case the 'command' argument should not be used. This is the case + * with peer text widgets, for example. + * + * Results: + * The newly created subAtom is returned. It must be passed to + * TkUndoPushAction otherwise a memory leak will result. + * + * Side effects: + * A refCount is retained on 'actionScript'. + * + *---------------------------------------------------------------------- + */ + +TkUndoSubAtom * +TkUndoMakeCmdSubAtom( + Tcl_Command command, /* Tcl command token for actions, may be NULL + * if not needed. */ + Tcl_Obj *actionScript, /* The script to append to the command to + * perform the action (may be NULL if the + * command is not-null). */ + TkUndoSubAtom *subAtomList) /* Add to the end of this list of actions if + * non-NULL */ +{ + TkUndoSubAtom *atom; - atom->revert = Tcl_NewStringObj(Tcl_DStringValue(revertScript),Tcl_DStringLength(revertScript)); - Tcl_IncrRefCount(atom->revert); + if (command == NULL && actionScript == NULL) { + Tcl_Panic("NULL command and actionScript in TkUndoMakeCmdSubAtom"); + } - TkUndoPushStack(&(stack->undoStack), atom); - TkUndoClearStack(&(stack->redoStack)); + atom = (TkUndoSubAtom *) ckalloc(sizeof(TkUndoSubAtom)); + atom->command = command; + atom->funcPtr = NULL; + atom->clientData = NULL; + atom->next = NULL; + atom->action = actionScript; + if (atom->action != NULL) { + Tcl_IncrRefCount(atom->action); + } + + if (subAtomList != NULL) { + while (subAtomList->next != NULL) { + subAtomList = subAtomList->next; + } + subAtomList->next = atom; + } + return atom; } + +/* + *---------------------------------------------------------------------- + * + * TkUndoMakeSubAtom -- + * + * Create a new undo/redo step which must later be place into an undo + * stack with TkUndoPushAction. This sub-atom, if evaluated, will take + * the given C-funcPtr (which must be non-NULL), and call it with three + * arguments: the undo stack's 'interp', the 'clientData' given and the + * 'actionScript'. The callback should return a standard Tcl return code + * (TCL_OK on success). + * + * If 'subAtomList' is non-NULL, the newly created sub-atom is added onto + * the end of the linked list of which 'subAtomList' is a part. This + * makes it easy to build up a sequence of actions which will be pushed + * in one step. + * + * Results: + * The newly created subAtom is returned. It must be passed to + * TkUndoPushAction otherwise a memory leak will result. + * + * Side effects: + * A refCount is retained on 'actionScript'. + * + *---------------------------------------------------------------------- + */ + +TkUndoSubAtom * +TkUndoMakeSubAtom( + TkUndoProc *funcPtr, /* Callback function to perform the + * undo/redo. */ + ClientData clientData, /* Data to pass to the callback function. */ + Tcl_Obj *actionScript, /* Additional Tcl data to pass to the callback + * function (may be NULL). */ + TkUndoSubAtom *subAtomList) /* Add to the end of this list of actions if + * non-NULL */ +{ + TkUndoSubAtom *atom; + if (funcPtr == NULL) { + Tcl_Panic("NULL funcPtr in TkUndoMakeSubAtom"); + } + + atom = (TkUndoSubAtom *) ckalloc(sizeof(TkUndoSubAtom)); + atom->command = NULL; + atom->funcPtr = funcPtr; + atom->clientData = clientData; + atom->next = NULL; + atom->action = actionScript; + if (atom->action != NULL) { + Tcl_IncrRefCount(atom->action); + } + + if (subAtomList != NULL) { + while (subAtomList->next != NULL) { + subAtomList = subAtomList->next; + } + subAtomList->next = atom; + } + return atom; +} /* - * TkUndoInitStack - * Initialize a new undo/redo stack + *---------------------------------------------------------------------- + * + * TkUndoInitStack -- + * + * Initialize a new undo/redo stack. * * Results: - * un Undo/Redo stack pointer + * An Undo/Redo stack pointer. * * Side effects: - * None. + * None. + * + *---------------------------------------------------------------------- */ - -TkUndoRedoStack * TkUndoInitStack ( interp, maxdepth ) - Tcl_Interp * interp; /* The interpreter */ - int maxdepth; /* The maximum stack depth */ -{ - TkUndoRedoStack * stack; /* An Undo/Redo stack */ + +TkUndoRedoStack * +TkUndoInitStack( + 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; + stack->interp = interp; + stack->maxdepth = maxdepth; + stack->depth = 0; return stack; } - /* - * TkUndoInitStack - * Initialize a new undo/redo stack + *---------------------------------------------------------------------- + * + * TkUndoSetDepth -- + * + * Set the maximum depth of stack. * * Results: - * un Undo/Redo stack pointer + * None. * * Side effects: - * None. + * May delete elements from the stack if the new maximum depth is smaller + * than the number of elements previously in the stack. + * + *---------------------------------------------------------------------- */ - -void TkUndoSetDepth ( stack, maxdepth ) - TkUndoRedoStack * stack; /* An Undo/Redo stack */ - int maxdepth; /* The maximum stack depth */ + +void +TkUndoSetDepth( + TkUndoRedoStack *stack, /* An Undo/Redo stack */ + int maxdepth) /* The maximum stack depth */ { - TkUndoAtom * elem; - TkUndoAtom * prevelem; - int sepNumber = 0; - stack->maxdepth = maxdepth; - if ((stack->maxdepth > 0) && (stack->depth > stack->maxdepth)) { - /* + if (stack->maxdepth>0 && stack->depth>stack->maxdepth) { + TkUndoAtom *elem, *prevelem; + int sepNumber = 0; + + /* * Maximum stack depth exceeded. We have to remove the last compound * elements on the stack. */ - elem = stack->undoStack; - prevelem = NULL; - while (elem && (sepNumber <= stack->maxdepth)) { - if (elem->type == TK_UNDO_SEPARATOR) { - sepNumber++; - } - prevelem = elem; - elem = elem->next; - } - prevelem->next = NULL; - while ( elem ) { - prevelem = elem; - elem = elem->next; - ckfree((char *) prevelem); - } - stack->depth = stack->maxdepth; + elem = stack->undoStack; + prevelem = NULL; + while ((elem != NULL) && (sepNumber <= stack->maxdepth)) { + if (elem->type == TK_UNDO_SEPARATOR) { + sepNumber++; + } + prevelem = elem; + elem = elem->next; + } + prevelem->next = NULL; + while (elem != NULL) { + prevelem = elem; + if (elem->type != TK_UNDO_SEPARATOR) { + TkUndoSubAtom *sub = elem->apply; + while (sub != NULL) { + TkUndoSubAtom *next = sub->next; + + if (sub->action != NULL) { + Tcl_DecrRefCount(sub->action); + } + ckfree((char *)sub); + sub = next; + } + sub = elem->revert; + while (sub != NULL) { + TkUndoSubAtom *next = sub->next; + + if (sub->action != NULL) { + Tcl_DecrRefCount(sub->action); + } + ckfree((char *)sub); + sub = next; + } + } + elem = elem->next; + ckfree((char *) prevelem); + } + stack->depth = stack->maxdepth; } } - /* - * TkUndoClearStacks - * Clear both the undo and redo stack + *---------------------------------------------------------------------- + * + * TkUndoClearStacks -- + * + * Clear both the undo and redo stack. * * Results: - * None + * None. * * Side effects: - * None. + * None. + * + *---------------------------------------------------------------------- */ - -void TkUndoClearStacks ( stack ) - TkUndoRedoStack * stack; /* An Undo/Redo stack */ -{ - TkUndoClearStack(&(stack->undoStack)); - TkUndoClearStack(&(stack->redoStack)); + +void +TkUndoClearStacks( + TkUndoRedoStack *stack) /* An Undo/Redo stack */ +{ + TkUndoClearStack(&stack->undoStack); + TkUndoClearStack(&stack->redoStack); stack->depth = 0; } - /* + *---------------------------------------------------------------------- + * * TkUndoFreeStack - * Clear both the undo and redo stack - * also free the memory allocated to the u/r stack pointer + * + * Clear both the undo and redo stack and free the memory allocated to + * the u/r stack pointer. * * Results: - * None + * None. * * Side effects: - * None. + * None. + * + *---------------------------------------------------------------------- */ - -void TkUndoFreeStack ( stack ) - TkUndoRedoStack * stack; /* An Undo/Redo stack */ -{ - TkUndoClearStacks(stack); -/* ckfree((TkUndoRedoStack *) stack); */ - ckfree((char *) stack); -} +void +TkUndoFreeStack( + TkUndoRedoStack *stack) /* An Undo/Redo stack */ +{ + TkUndoClearStacks(stack); + ckfree((char *) stack); +} /* + *---------------------------------------------------------------------- + * * TkUndoInsertUndoSeparator -- - * insert a separator on the undo stack, indicating a border for - * an undo/redo chunk. + * + * Insert a separator on the undo stack, indicating a border for an + * undo/redo chunk. * * Results: - * None + * None. * * Side effects: - * None. + * None. + * + *---------------------------------------------------------------------- */ - -void TkUndoInsertUndoSeparator ( stack ) - TkUndoRedoStack * stack; + +void +TkUndoInsertUndoSeparator( + TkUndoRedoStack *stack) { - if ( TkUndoInsertSeparator(&(stack->undoStack)) ) { - ++(stack->depth); - TkUndoSetDepth(stack,stack->maxdepth); + if (TkUndoInsertSeparator(&stack->undoStack)) { + stack->depth++; + TkUndoSetDepth(stack, stack->maxdepth); } } - /* + *---------------------------------------------------------------------- + * * TkUndoRevert -- - * Undo a compound action on the stack. + * + * Undo a compound action on the stack. * * Results: - * A TCL status code + * A Tcl status code * * Side effects: - * None. + * None. + * + *---------------------------------------------------------------------- */ - -int TkUndoRevert ( stack ) - TkUndoRedoStack * stack; + +int +TkUndoRevert( + TkUndoRedoStack *stack) { - TkUndoAtom * elem; + TkUndoAtom *elem; - /* insert a separator on the undo and the redo stack */ + /* + * 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*/ + TkUndoInsertSeparator(&stack->redoStack); - elem = TkUndoPopStack(&(stack->undoStack)); + /* + * Pop and skip the first separator if there is one. + */ - if ( elem == NULL ) { - return TCL_ERROR; + 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)); + if (elem->type == TK_UNDO_SEPARATOR) { + ckfree((char *) elem); + elem = TkUndoPopStack(&stack->undoStack); } - - while ( elem && (elem->type != TK_UNDO_SEPARATOR) ) { - Tcl_EvalObjEx(stack->interp,elem->revert,TCL_EVAL_GLOBAL); - - TkUndoPushStack(&(stack->redoStack),elem); - elem = TkUndoPopStack(&(stack->undoStack)); + + while (elem != NULL && elem->type != TK_UNDO_SEPARATOR) { + /* + * Note that we currently ignore errors thrown here. + */ + + EvaluateActionList(stack->interp, elem->revert); + + TkUndoPushStack(&stack->redoStack, elem); + elem = TkUndoPopStack(&stack->undoStack); } - - /* insert a separator on the redo stack */ - - TkUndoInsertSeparator(&(stack->redoStack)); - - --(stack->depth); - + + /* + * Insert a separator on the redo stack. + */ + + TkUndoInsertSeparator(&stack->redoStack); + stack->depth--; return TCL_OK; } - /* + *---------------------------------------------------------------------- + * * TkUndoApply -- - * Redo a compound action on the stack. + * + * Redo a compound action on the stack. * * Results: - * A TCL status code + * A Tcl status code * * Side effects: - * None. + * None. + * + *---------------------------------------------------------------------- */ - -int TkUndoApply ( stack ) - TkUndoRedoStack * stack; + +int +TkUndoApply( + TkUndoRedoStack *stack) { TkUndoAtom *elem; - /* insert a separator on the undo stack */ + /* + * Insert a separator on the undo stack. + */ - TkUndoInsertSeparator(&(stack->undoStack)); + TkUndoInsertSeparator(&stack->undoStack); - /* Pop and skip the first separator if there is one*/ + /* + * Pop and skip the first separator if there is one. + */ - elem = TkUndoPopStack(&(stack->redoStack)); - - if ( elem == NULL ) { - return TCL_ERROR; + 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)); + if (elem->type == TK_UNDO_SEPARATOR) { + ckfree((char *) elem); + elem = TkUndoPopStack(&stack->redoStack); } - while ( elem && (elem->type != TK_UNDO_SEPARATOR) ) { - Tcl_EvalObjEx(stack->interp,elem->apply,TCL_EVAL_GLOBAL); - - TkUndoPushStack(&(stack->undoStack), elem); - elem = TkUndoPopStack(&(stack->redoStack)); + while (elem != NULL && elem->type != TK_UNDO_SEPARATOR) { + /* + * Note that we currently ignore errors thrown here. + */ + + EvaluateActionList(stack->interp, elem->apply); + + TkUndoPushStack(&stack->undoStack, elem); + elem = TkUndoPopStack(&stack->redoStack); } - /* insert a separator on the undo stack */ - - TkUndoInsertSeparator(&(stack->undoStack)); + /* + * Insert a separator on the undo stack. + */ - ++(stack->depth); - + TkUndoInsertSeparator(&stack->undoStack); + stack->depth++; return TCL_OK; } + +/* + *---------------------------------------------------------------------- + * + * EvaluateActionList -- + * + * Execute a linked list of undo/redo sub-atoms. If any sub-atom returns + * a non TCL_OK value, execution of subsequent sub-atoms is cancelled and + * the error returned immediately. + * + * Results: + * A Tcl status code + * + * Side effects: + * The undo/redo subAtoms can perform arbitrary actions. + * + *---------------------------------------------------------------------- + */ +static int +EvaluateActionList( + Tcl_Interp *interp, /* Interpreter to evaluate the action in. */ + TkUndoSubAtom *action) /* Head of linked list of action steps to + * perform. */ +{ + int result = TCL_OK; + + while (action != NULL) { + if (action->funcPtr != NULL) { + result = (*action->funcPtr)(interp, action->clientData, + action->action); + } else if (action->command != NULL) { + Tcl_Obj *cmdNameObj, *evalObj; + + cmdNameObj = Tcl_NewObj(); + evalObj = Tcl_NewObj(); + Tcl_IncrRefCount(evalObj); + Tcl_GetCommandFullName(interp, action->command, cmdNameObj); + Tcl_ListObjAppendElement(NULL, evalObj, cmdNameObj); + if (action->action != NULL) { + Tcl_ListObjAppendList(NULL, evalObj, action->action); + } + result = Tcl_EvalObjEx(interp, evalObj, TCL_EVAL_GLOBAL); + Tcl_DecrRefCount(evalObj); + } else { + result = Tcl_EvalObjEx(interp, action->action, TCL_EVAL_GLOBAL); + } + if (result != TCL_OK) { + return result; + } + action = action->next; + } + return result; +} + +/* + * Local Variables: + * mode: c + * c-basic-offset: 4 + * fill-column: 78 + * End: + */ |