1 /*
2  * tkUndo.c --
3  *
4  *	This module provides the implementation of an undo stack.
5  *
6  * Copyright (c) 2002 by Ludwig Callewaert.
7  *
8  * See the file "license.terms" for information on usage and redistribution
9  * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
10  *
11  * RCS: @(#) $Id: tkUndo.c,v 1.1 2002/06/21 23:09:55 hobbs Exp $
12  */
13 
14 #include "tkUndo.h"
15 #include "tkVMacro.h"
16 /*
17  * TkUndoPushStack
18  *    Push elem on the stack identified by stack.
19  *
20  * Results:
21  *    None
22  *
23  * Side effects:
24  *    None.
25  */
26 
TkUndoPushStack(stack,elem)27 void TkUndoPushStack ( stack, elem )
28     TkUndoAtom ** stack;
29     TkUndoAtom *  elem;
30 {
31     elem->next = *stack;
32     *stack = elem;
33 }
34 
35 /*
36  * TkUndoPopStack --
37  *    Remove and return the top element from the stack identified by
38  *      stack.
39  *
40  * Results:
41  *    None
42  *
43  * Side effects:
44  *    None.
45  */
46 
TkUndoPopStack(stack)47 TkUndoAtom * TkUndoPopStack ( stack )
48     TkUndoAtom ** stack ;
49 {
50     TkUndoAtom * elem = NULL;
51     if (*stack != NULL ) {
52         elem   = *stack;
53         *stack = elem->next;
54     }
55     return elem;
56 }
57 
58 /*
59  * TkUndoInsertSeparator --
60  *    insert a separator on the stack, indicating a border for
61  *      an undo/redo chunk.
62  *
63  * Results:
64  *    None
65  *
66  * Side effects:
67  *    None.
68  */
69 
TkUndoInsertSeparator(stack)70 int TkUndoInsertSeparator ( stack )
71     TkUndoAtom ** stack;
72 {
73     TkUndoAtom * separator;
74 
75     if ( *stack != NULL && (*stack)->type != TK_UNDO_SEPARATOR ) {
76         separator = (TkUndoAtom *) ckalloc(sizeof(TkUndoAtom));
77         separator->type = TK_UNDO_SEPARATOR;
78         TkUndoPushStack(stack,separator);
79         return 1;
80     } else {
81         return 0;
82     }
83 }
84 
85 /*
86  * TkUndoClearStack --
87  *    Clear an entire undo or redo stack and destroy all elements in it.
88  *
89  * Results:
90  *    None
91  *
92  * Side effects:
93  *    None.
94  */
95 
TkUndoClearStack(stack)96 void TkUndoClearStack ( stack )
97     TkUndoAtom ** stack;      /* An Undo or Redo stack */
98 {
99     TkUndoAtom * elem;
100 
101     while ( (elem = TkUndoPopStack(stack)) ) {
102         if ( elem->type != TK_UNDO_SEPARATOR ) {
103             Tcl_DecrRefCount(elem->apply);
104             Tcl_DecrRefCount(elem->revert);
105         }
106         ckfree((char *)elem);
107     }
108     *stack = NULL;
109 }
110 
111 /*
112  * TkUndoPushAction
113  *    Push a new elem on the stack identified by stack.
114  *    action and revert are given through Tcl_DStrings
115  *
116  * Results:
117  *    None
118  *
119  * Side effects:
120  *    None.
121  */
122 
TkUndoPushAction(stack,actionScript,revertScript)123 void TkUndoPushAction ( stack, actionScript, revertScript )
124     TkUndoRedoStack * stack;      /* An Undo or Redo stack */
125     Tcl_DString * actionScript; /* The script to get the action (redo) */
126     Tcl_DString * revertScript; /* The script to revert the action (undo) */
127 {
128     TkUndoAtom * atom;
129 
130     atom = (TkUndoAtom *) ckalloc(sizeof(TkUndoAtom));
131     atom->type = TK_UNDO_ACTION;
132 
133     atom->apply = Tcl_NewStringObj(Tcl_DStringValue(actionScript),Tcl_DStringLength(actionScript));
134     Tcl_IncrRefCount(atom->apply);
135 
136     atom->revert = Tcl_NewStringObj(Tcl_DStringValue(revertScript),Tcl_DStringLength(revertScript));
137     Tcl_IncrRefCount(atom->revert);
138 
139     TkUndoPushStack(&(stack->undoStack), atom);
140     TkUndoClearStack(&(stack->redoStack));
141 }
142 
143 
144 /*
145  * TkUndoInitStack
146  *    Initialize a new undo/redo stack
147  *
148  * Results:
149  *    un Undo/Redo stack pointer
150  *
151  * Side effects:
152  *    None.
153  */
154 
TkUndoInitStack(interp,maxdepth)155 TkUndoRedoStack * TkUndoInitStack ( interp, maxdepth )
156     Tcl_Interp * interp;          /* The interpreter */
157     int          maxdepth;        /* The maximum stack depth */
158 {
159     TkUndoRedoStack * stack;      /* An Undo/Redo stack */
160     stack = (TkUndoRedoStack *) ckalloc(sizeof(TkUndoRedoStack));
161     stack->undoStack = NULL;
162     stack->redoStack = NULL;
163     stack->interp    = interp;
164     stack->maxdepth  = maxdepth;
165     stack->depth     = 0;
166     return stack;
167 }
168 
169 
170 /*
171  * TkUndoInitStack
172  *    Initialize a new undo/redo stack
173  *
174  * Results:
175  *    un Undo/Redo stack pointer
176  *
177  * Side effects:
178  *    None.
179  */
180 
TkUndoSetDepth(stack,maxdepth)181 void TkUndoSetDepth ( stack, maxdepth )
182     TkUndoRedoStack * stack;           /* An Undo/Redo stack */
183     int               maxdepth;        /* The maximum stack depth */
184 {
185     TkUndoAtom * elem;
186     TkUndoAtom * prevelem;
187     int sepNumber = 0;
188 
189     stack->maxdepth = maxdepth;
190 
191     if ((stack->maxdepth > 0) && (stack->depth > stack->maxdepth)) {
192         /* Maximum stack depth exceeded. We have to remove the last compound
193            elements on the stack */
194         elem = stack->undoStack;
195         prevelem = NULL;
196         while ( sepNumber <= stack->maxdepth ) {
197             if (elem != NULL && (elem->type == TK_UNDO_SEPARATOR) ) {
198                 sepNumber++;
199             }
200             prevelem = elem;
201             elem = elem->next;
202         }
203         prevelem->next = NULL;
204         while ( elem ) {
205            prevelem = elem;
206            elem = elem->next;
207            ckfree((char *) elem);
208         }
209         stack->depth = stack->maxdepth;
210     }
211 }
212 
213 
214 /*
215  * TkUndoClearStacks
216  *    Clear both the undo and redo stack
217  *
218  * Results:
219  *    None
220  *
221  * Side effects:
222  *    None.
223  */
224 
TkUndoClearStacks(stack)225 void TkUndoClearStacks ( stack )
226     TkUndoRedoStack * stack;      /* An Undo/Redo stack */
227 {
228     TkUndoClearStack(&(stack->undoStack));
229     TkUndoClearStack(&(stack->redoStack));
230     stack->depth = 0;
231 }
232 
233 
234 /*
235  * TkUndoFreeStack
236  *    Clear both the undo and redo stack
237  *    also free the memory allocated to the u/r stack pointer
238  *
239  * Results:
240  *    None
241  *
242  * Side effects:
243  *    None.
244  */
245 
TkUndoFreeStack(stack)246 void TkUndoFreeStack ( stack )
247     TkUndoRedoStack * stack;      /* An Undo/Redo stack */
248 {
249    TkUndoClearStacks(stack);
250 /*   ckfree((TkUndoRedoStack *) stack); */
251    ckfree((char *) stack);
252 }
253 
254 
255 /*
256  * TkUndoInsertUndoSeparator --
257  *    insert a separator on the undo stack, indicating a border for
258  *      an undo/redo chunk.
259  *
260  * Results:
261  *    None
262  *
263  * Side effects:
264  *    None.
265  */
266 
TkUndoInsertUndoSeparator(stack)267 void TkUndoInsertUndoSeparator ( stack )
268     TkUndoRedoStack * stack;
269 {
270 /*    TkUndoAtom * elem;
271     TkUndoAtom * prevelem;
272     int sepNumber = 0;
273 */
274 
275     if ( TkUndoInsertSeparator(&(stack->undoStack)) ) {
276         ++(stack->depth);
277         TkUndoSetDepth(stack,stack->maxdepth);
278 /*        if ((stack->maxdepth > 0) && (stack->depth > stack->maxdepth)) {
279             elem = stack->undoStack;
280             prevelem = NULL;
281             while ( sepNumber < stack->depth ) {
282                 if (elem != NULL && (elem->type == TK_UNDO_SEPARATOR) ) {
283                     sepNumber++;
284                 }
285                 prevelem = elem;
286                 elem = elem->next;
287             }
288             prevelem->next = NULL;
289             while ( elem ) {
290                prevelem = elem;
291                elem = elem->next;
292                ckfree((char *) elem);
293             }
294             stack->depth;
295         } */
296     }
297 }
298 
299 
300 /*
301  * TkUndoRevert --
302  *    Undo a compound action on the stack.
303  *
304  * Results:
305  *    A TCL status code
306  *
307  * Side effects:
308  *    None.
309  */
310 
TkUndoRevert(stack)311 int TkUndoRevert ( stack )
312     TkUndoRedoStack * stack;
313 {
314     TkUndoAtom * elem;
315 
316     /* insert a separator on the undo and the redo stack */
317 
318     TkUndoInsertUndoSeparator(stack);
319     TkUndoInsertSeparator(&(stack->redoStack));
320 
321     /* Pop and skip the first separator if there is one*/
322 
323     elem = TkUndoPopStack(&(stack->undoStack));
324 
325     if ( elem == NULL ) {
326         return TCL_ERROR;
327     }
328 
329     if ( ( elem != NULL ) && ( elem->type == TK_UNDO_SEPARATOR ) ) {
330         ckfree((char *) elem);
331         elem = TkUndoPopStack(&(stack->undoStack));
332     }
333 
334     while ( elem && (elem->type != TK_UNDO_SEPARATOR) ) {
335         Tcl_EvalObjEx(stack->interp,elem->revert,TCL_EVAL_GLOBAL);
336 
337         TkUndoPushStack(&(stack->redoStack),elem);
338         elem = TkUndoPopStack(&(stack->undoStack));
339     }
340 
341     /* insert a separator on the redo stack */
342 
343     TkUndoInsertSeparator(&(stack->redoStack));
344 
345     --(stack->depth);
346 
347     return TCL_OK;
348 }
349 
350 
351 /*
352  * TkUndoApply --
353  *    Redo a compound action on the stack.
354  *
355  * Results:
356  *    A TCL status code
357  *
358  * Side effects:
359  *    None.
360  */
361 
TkUndoApply(stack)362 int TkUndoApply ( stack )
363     TkUndoRedoStack * stack;
364 {
365     TkUndoAtom *elem;
366 
367     /* insert a separator on the undo stack */
368 
369     TkUndoInsertSeparator(&(stack->undoStack));
370 
371     /* Pop and skip the first separator if there is one*/
372 
373     elem = TkUndoPopStack(&(stack->redoStack));
374 
375     if ( elem == NULL ) {
376        return TCL_ERROR;
377     }
378 
379     if ( ( elem != NULL ) && ( elem->type == TK_UNDO_SEPARATOR ) ) {
380         ckfree((char *) elem);
381         elem = TkUndoPopStack(&(stack->redoStack));
382     }
383 
384     while ( elem && (elem->type != TK_UNDO_SEPARATOR) ) {
385         Tcl_EvalObjEx(stack->interp,elem->apply,TCL_EVAL_GLOBAL);
386 
387         TkUndoPushStack(&(stack->undoStack), elem);
388         elem = TkUndoPopStack(&(stack->redoStack));
389     }
390 
391     /* insert a separator on the undo stack */
392 
393     TkUndoInsertSeparator(&(stack->undoStack));
394 
395     ++(stack->depth);
396 
397     return TCL_OK;
398 }
399 
400 
401