Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
tree: a93bc36fac
Fetching contributors…

Cannot retrieve contributors at this time

691 lines (614 sloc) 15.569 kb
/*
* 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.
*/
#include "tkInt.h"
#include "tkUndo.h"
static int EvaluateActionList(Tcl_Interp *interp,
TkUndoSubAtom *action);
/*
*----------------------------------------------------------------------
*
* TkUndoPushStack --
*
* Push elem on the stack identified by stack.
*
* Results:
* None.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
void
TkUndoPushStack(
TkUndoAtom **stack,
TkUndoAtom *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(
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(
TkUndoAtom **stack)
{
TkUndoAtom *separator;
if (*stack!=NULL && (*stack)->type!=TK_UNDO_SEPARATOR) {
separator = 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(
TkUndoAtom **stack) /* An Undo or Redo stack */
{
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(sub);
sub = next;
}
sub = elem->revert;
while (sub != NULL) {
TkUndoSubAtom *next = sub->next;
if (sub->action != NULL) {
Tcl_DecrRefCount(sub->action);
}
ckfree(sub);
sub = next;
}
}
ckfree(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(
TkUndoRedoStack *stack, /* An Undo or Redo stack */
TkUndoSubAtom *apply,
TkUndoSubAtom *revert)
{
TkUndoAtom *atom;
atom = ckalloc(sizeof(TkUndoAtom));
atom->type = TK_UNDO_ACTION;
atom->apply = apply;
atom->revert = revert;
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;
if (command == NULL && actionScript == NULL) {
Tcl_Panic("NULL command and actionScript in TkUndoMakeCmdSubAtom");
}
atom = 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 = 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.
*
* Results:
* An Undo/Redo stack pointer.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
TkUndoRedoStack *
TkUndoInitStack(
Tcl_Interp *interp, /* The interpreter */
int maxdepth) /* The maximum stack depth */
{
TkUndoRedoStack *stack; /* An Undo/Redo stack */
stack = ckalloc(sizeof(TkUndoRedoStack));
stack->undoStack = NULL;
stack->redoStack = NULL;
stack->interp = interp;
stack->maxdepth = maxdepth;
stack->depth = 0;
return stack;
}
/*
*----------------------------------------------------------------------
*
* TkUndoSetDepth --
*
* Set the maximum depth of stack.
*
* Results:
* None.
*
* Side effects:
* May delete elements from the stack if the new maximum depth is smaller
* than the number of elements previously in the stack.
*
*----------------------------------------------------------------------
*/
void
TkUndoSetDepth(
TkUndoRedoStack *stack, /* An Undo/Redo stack */
int maxdepth) /* The maximum stack depth */
{
stack->maxdepth = 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 != NULL) && (sepNumber <= stack->maxdepth)) {
if (elem->type == TK_UNDO_SEPARATOR) {
sepNumber++;
}
prevelem = elem;
elem = elem->next;
}
CLANG_ASSERT(prevelem);
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(sub);
sub = next;
}
sub = elem->revert;
while (sub != NULL) {
TkUndoSubAtom *next = sub->next;
if (sub->action != NULL) {
Tcl_DecrRefCount(sub->action);
}
ckfree(sub);
sub = next;
}
}
elem = elem->next;
ckfree(prevelem);
}
stack->depth = stack->maxdepth;
}
}
/*
*----------------------------------------------------------------------
*
* TkUndoClearStacks --
*
* Clear both the undo and redo stack.
*
* Results:
* None.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
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 and free the memory allocated to
* the u/r stack pointer.
*
* Results:
* None.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
void
TkUndoFreeStack(
TkUndoRedoStack *stack) /* An Undo/Redo stack */
{
TkUndoClearStacks(stack);
ckfree(stack);
}
/*
*----------------------------------------------------------------------
*
* TkUndoInsertUndoSeparator --
*
* Insert a separator on the undo stack, indicating a border for an
* undo/redo chunk.
*
* Results:
* None.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
void
TkUndoInsertUndoSeparator(
TkUndoRedoStack *stack)
{
if (TkUndoInsertSeparator(&stack->undoStack)) {
stack->depth++;
TkUndoSetDepth(stack, stack->maxdepth);
}
}
/*
*----------------------------------------------------------------------
*
* TkUndoRevert --
*
* Undo a compound action on the stack.
*
* Results:
* A Tcl status code
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
int
TkUndoRevert(
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->type == TK_UNDO_SEPARATOR) {
ckfree(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--;
return TCL_OK;
}
/*
*----------------------------------------------------------------------
*
* TkUndoApply --
*
* Redo a compound action on the stack.
*
* Results:
* A Tcl status code
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
int
TkUndoApply(
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->type == TK_UNDO_SEPARATOR) {
ckfree(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);
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:
*/
Jump to Line
Something went wrong with that request. Please try again.