1 /*
2 * tkUndo.c --
3 *
4 * This module provides the implementation of an undo stack.
5 *
6 * Copyright © 2002 Ludwig Callewaert.
7 * Copyright © 2003-2004 Vincent Darley.
8 *
9 * See the file "license.terms" for information on usage and redistribution of
10 * this file, and for a DISCLAIMER OF ALL WARRANTIES.
11 */
12
13 #include "tkInt.h"
14 #include "tkUndo.h"
15
16 static int EvaluateActionList(Tcl_Interp *interp,
17 TkUndoSubAtom *action);
18
19 /*
20 *----------------------------------------------------------------------
21 *
22 * TkUndoPushStack --
23 *
24 * Push elem on the stack identified by stack.
25 *
26 * Results:
27 * None.
28 *
29 * Side effects:
30 * None.
31 *
32 *----------------------------------------------------------------------
33 */
34
35 void
TkUndoPushStack(TkUndoAtom ** stack,TkUndoAtom * elem)36 TkUndoPushStack(
37 TkUndoAtom **stack,
38 TkUndoAtom *elem)
39 {
40 elem->next = *stack;
41 *stack = elem;
42 }
43
44 /*
45 *----------------------------------------------------------------------
46 *
47 * TkUndoPopStack --
48 *
49 * Remove and return the top element from the stack identified by stack.
50 *
51 * Results:
52 * None.
53 *
54 * Side effects:
55 * None.
56 *
57 *----------------------------------------------------------------------
58 */
59
60 TkUndoAtom *
TkUndoPopStack(TkUndoAtom ** stack)61 TkUndoPopStack(
62 TkUndoAtom **stack)
63 {
64 TkUndoAtom *elem = NULL;
65
66 if (*stack != NULL) {
67 elem = *stack;
68 *stack = elem->next;
69 }
70 return elem;
71 }
72
73 /*
74 *----------------------------------------------------------------------
75 *
76 * TkUndoInsertSeparator --
77 *
78 * Insert a separator on the stack, indicating a border for an undo/redo
79 * chunk.
80 *
81 * Results:
82 * None.
83 *
84 * Side effects:
85 * None.
86 *
87 *----------------------------------------------------------------------
88 */
89
90 int
TkUndoInsertSeparator(TkUndoAtom ** stack)91 TkUndoInsertSeparator(
92 TkUndoAtom **stack)
93 {
94 TkUndoAtom *separator;
95
96 if (*stack!=NULL && (*stack)->type!=TK_UNDO_SEPARATOR) {
97 separator = (TkUndoAtom *)ckalloc(sizeof(TkUndoAtom));
98 separator->type = TK_UNDO_SEPARATOR;
99 TkUndoPushStack(stack,separator);
100 return 1;
101 }
102 return 0;
103 }
104
105 /*
106 *----------------------------------------------------------------------
107 *
108 * TkUndoClearStack --
109 *
110 * Clear an entire undo or redo stack and destroy all elements in it.
111 *
112 * Results:
113 * None.
114 *
115 * Side effects:
116 * None.
117 *
118 *----------------------------------------------------------------------
119 */
120
121 void
TkUndoClearStack(TkUndoAtom ** stack)122 TkUndoClearStack(
123 TkUndoAtom **stack) /* An Undo or Redo stack */
124 {
125 TkUndoAtom *elem;
126
127 while ((elem = TkUndoPopStack(stack)) != NULL) {
128 if (elem->type != TK_UNDO_SEPARATOR) {
129 TkUndoSubAtom *sub;
130
131 sub = elem->apply;
132 while (sub != NULL) {
133 TkUndoSubAtom *next = sub->next;
134
135 if (sub->action != NULL) {
136 Tcl_DecrRefCount(sub->action);
137 }
138 ckfree(sub);
139 sub = next;
140 }
141
142 sub = elem->revert;
143 while (sub != NULL) {
144 TkUndoSubAtom *next = sub->next;
145
146 if (sub->action != NULL) {
147 Tcl_DecrRefCount(sub->action);
148 }
149 ckfree(sub);
150 sub = next;
151 }
152 }
153 ckfree(elem);
154 }
155 *stack = NULL;
156 }
157
158 /*
159 *----------------------------------------------------------------------
160 *
161 * TkUndoPushAction --
162 *
163 * Push a new elem on the stack identified by stack. Action and revert
164 * are given through Tcl_Obj's to which we will retain a reference. (So
165 * they can be passed in with a zero refCount if desired).
166 *
167 * Results:
168 * None.
169 *
170 * Side effects:
171 * None.
172 *
173 *----------------------------------------------------------------------
174 */
175
176 void
TkUndoPushAction(TkUndoRedoStack * stack,TkUndoSubAtom * apply,TkUndoSubAtom * revert)177 TkUndoPushAction(
178 TkUndoRedoStack *stack, /* An Undo or Redo stack */
179 TkUndoSubAtom *apply,
180 TkUndoSubAtom *revert)
181 {
182 TkUndoAtom *atom;
183
184 atom = (TkUndoAtom *)ckalloc(sizeof(TkUndoAtom));
185 atom->type = TK_UNDO_ACTION;
186 atom->apply = apply;
187 atom->revert = revert;
188
189 TkUndoPushStack(&stack->undoStack, atom);
190 TkUndoClearStack(&stack->redoStack);
191 }
192
193 /*
194 *----------------------------------------------------------------------
195 *
196 * TkUndoMakeCmdSubAtom --
197 *
198 * Create a new undo/redo step which must later be place into an undo
199 * stack with TkUndoPushAction. This sub-atom, if evaluated, will take
200 * the given command (if non-NULL), find its full Tcl command string, and
201 * then evaluate that command with the list elements of 'actionScript'
202 * appended.
203 *
204 * If 'subAtomList' is non-NULL, the newly created sub-atom is added onto
205 * the end of the linked list of which 'subAtomList' is a part. This
206 * makes it easy to build up a sequence of actions which will be pushed
207 * in one step.
208 *
209 * Note: if the undo stack can persist for longer than the Tcl_Command
210 * provided, the stack will cause crashes when actions are evaluated. In
211 * this case the 'command' argument should not be used. This is the case
212 * with peer text widgets, for example.
213 *
214 * Results:
215 * The newly created subAtom is returned. It must be passed to
216 * TkUndoPushAction otherwise a memory leak will result.
217 *
218 * Side effects:
219 * A refCount is retained on 'actionScript'.
220 *
221 *----------------------------------------------------------------------
222 */
223
224 TkUndoSubAtom *
TkUndoMakeCmdSubAtom(Tcl_Command command,Tcl_Obj * actionScript,TkUndoSubAtom * subAtomList)225 TkUndoMakeCmdSubAtom(
226 Tcl_Command command, /* Tcl command token for actions, may be NULL
227 * if not needed. */
228 Tcl_Obj *actionScript, /* The script to append to the command to
229 * perform the action (may be NULL if the
230 * command is not-null). */
231 TkUndoSubAtom *subAtomList) /* Add to the end of this list of actions if
232 * non-NULL */
233 {
234 TkUndoSubAtom *atom;
235
236 if (command == NULL && actionScript == NULL) {
237 Tcl_Panic("NULL command and actionScript in TkUndoMakeCmdSubAtom");
238 }
239
240 atom = (TkUndoSubAtom *)ckalloc(sizeof(TkUndoSubAtom));
241 atom->command = command;
242 atom->funcPtr = NULL;
243 atom->clientData = NULL;
244 atom->next = NULL;
245 atom->action = actionScript;
246 if (atom->action != NULL) {
247 Tcl_IncrRefCount(atom->action);
248 }
249
250 if (subAtomList != NULL) {
251 while (subAtomList->next != NULL) {
252 subAtomList = subAtomList->next;
253 }
254 subAtomList->next = atom;
255 }
256 return atom;
257 }
258
259 /*
260 *----------------------------------------------------------------------
261 *
262 * TkUndoMakeSubAtom --
263 *
264 * Create a new undo/redo step which must later be place into an undo
265 * stack with TkUndoPushAction. This sub-atom, if evaluated, will take
266 * the given C-funcPtr (which must be non-NULL), and call it with three
267 * arguments: the undo stack's 'interp', the 'clientData' given and the
268 * 'actionScript'. The callback should return a standard Tcl return code
269 * (TCL_OK on success).
270 *
271 * If 'subAtomList' is non-NULL, the newly created sub-atom is added onto
272 * the end of the linked list of which 'subAtomList' is a part. This
273 * makes it easy to build up a sequence of actions which will be pushed
274 * in one step.
275 *
276 * Results:
277 * The newly created subAtom is returned. It must be passed to
278 * TkUndoPushAction otherwise a memory leak will result.
279 *
280 * Side effects:
281 * A refCount is retained on 'actionScript'.
282 *
283 *----------------------------------------------------------------------
284 */
285
286 TkUndoSubAtom *
TkUndoMakeSubAtom(TkUndoProc * funcPtr,ClientData clientData,Tcl_Obj * actionScript,TkUndoSubAtom * subAtomList)287 TkUndoMakeSubAtom(
288 TkUndoProc *funcPtr, /* Callback function to perform the
289 * undo/redo. */
290 ClientData clientData, /* Data to pass to the callback function. */
291 Tcl_Obj *actionScript, /* Additional Tcl data to pass to the callback
292 * function (may be NULL). */
293 TkUndoSubAtom *subAtomList) /* Add to the end of this list of actions if
294 * non-NULL */
295 {
296 TkUndoSubAtom *atom;
297
298 if (funcPtr == NULL) {
299 Tcl_Panic("NULL funcPtr in TkUndoMakeSubAtom");
300 }
301
302 atom = (TkUndoSubAtom *)ckalloc(sizeof(TkUndoSubAtom));
303 atom->command = NULL;
304 atom->funcPtr = funcPtr;
305 atom->clientData = clientData;
306 atom->next = NULL;
307 atom->action = actionScript;
308 if (atom->action != NULL) {
309 Tcl_IncrRefCount(atom->action);
310 }
311
312 if (subAtomList != NULL) {
313 while (subAtomList->next != NULL) {
314 subAtomList = subAtomList->next;
315 }
316 subAtomList->next = atom;
317 }
318 return atom;
319 }
320
321 /*
322 *----------------------------------------------------------------------
323 *
324 * TkUndoInitStack --
325 *
326 * Initialize a new undo/redo stack.
327 *
328 * Results:
329 * An Undo/Redo stack pointer.
330 *
331 * Side effects:
332 * None.
333 *
334 *----------------------------------------------------------------------
335 */
336
337 TkUndoRedoStack *
TkUndoInitStack(Tcl_Interp * interp,int maxdepth)338 TkUndoInitStack(
339 Tcl_Interp *interp, /* The interpreter */
340 int maxdepth) /* The maximum stack depth */
341 {
342 TkUndoRedoStack *stack; /* An Undo/Redo stack */
343
344 stack = (TkUndoRedoStack *)ckalloc(sizeof(TkUndoRedoStack));
345 stack->undoStack = NULL;
346 stack->redoStack = NULL;
347 stack->interp = interp;
348 stack->maxdepth = maxdepth;
349 stack->depth = 0;
350 return stack;
351 }
352
353 /*
354 *----------------------------------------------------------------------
355 *
356 * TkUndoSetMaxDepth --
357 *
358 * Set the maximum depth of stack.
359 *
360 * Results:
361 * None.
362 *
363 * Side effects:
364 * May delete elements from the stack if the new maximum depth is smaller
365 * than the number of elements previously in the stack.
366 *
367 *----------------------------------------------------------------------
368 */
369
370 void
TkUndoSetMaxDepth(TkUndoRedoStack * stack,int maxdepth)371 TkUndoSetMaxDepth(
372 TkUndoRedoStack *stack, /* An Undo/Redo stack */
373 int maxdepth) /* The maximum stack depth */
374 {
375 stack->maxdepth = maxdepth;
376
377 if (stack->maxdepth>0 && stack->depth>stack->maxdepth) {
378 TkUndoAtom *elem, *prevelem;
379 int sepNumber = 0;
380
381 /*
382 * Maximum stack depth exceeded. We have to remove the last compound
383 * elements on the stack.
384 */
385
386 elem = stack->undoStack;
387 prevelem = NULL;
388 while ((elem != NULL) && (sepNumber <= stack->maxdepth)) {
389 if (elem->type == TK_UNDO_SEPARATOR) {
390 sepNumber++;
391 }
392 prevelem = elem;
393 elem = elem->next;
394 }
395 CLANG_ASSERT(prevelem);
396 prevelem->next = NULL;
397 while (elem != NULL) {
398 prevelem = elem;
399 if (elem->type != TK_UNDO_SEPARATOR) {
400 TkUndoSubAtom *sub = elem->apply;
401 while (sub != NULL) {
402 TkUndoSubAtom *next = sub->next;
403
404 if (sub->action != NULL) {
405 Tcl_DecrRefCount(sub->action);
406 }
407 ckfree(sub);
408 sub = next;
409 }
410 sub = elem->revert;
411 while (sub != NULL) {
412 TkUndoSubAtom *next = sub->next;
413
414 if (sub->action != NULL) {
415 Tcl_DecrRefCount(sub->action);
416 }
417 ckfree(sub);
418 sub = next;
419 }
420 }
421 elem = elem->next;
422 ckfree(prevelem);
423 }
424 stack->depth = stack->maxdepth;
425 }
426 }
427
428 /*
429 *----------------------------------------------------------------------
430 *
431 * TkUndoClearStacks --
432 *
433 * Clear both the undo and redo stack.
434 *
435 * Results:
436 * None.
437 *
438 * Side effects:
439 * None.
440 *
441 *----------------------------------------------------------------------
442 */
443
444 void
TkUndoClearStacks(TkUndoRedoStack * stack)445 TkUndoClearStacks(
446 TkUndoRedoStack *stack) /* An Undo/Redo stack */
447 {
448 TkUndoClearStack(&stack->undoStack);
449 TkUndoClearStack(&stack->redoStack);
450 stack->depth = 0;
451 }
452
453 /*
454 *----------------------------------------------------------------------
455 *
456 * TkUndoFreeStack
457 *
458 * Clear both the undo and redo stack and free the memory allocated to
459 * the u/r stack pointer.
460 *
461 * Results:
462 * None.
463 *
464 * Side effects:
465 * None.
466 *
467 *----------------------------------------------------------------------
468 */
469
470 void
TkUndoFreeStack(TkUndoRedoStack * stack)471 TkUndoFreeStack(
472 TkUndoRedoStack *stack) /* An Undo/Redo stack */
473 {
474 TkUndoClearStacks(stack);
475 ckfree(stack);
476 }
477
478 /*
479 *----------------------------------------------------------------------
480 *
481 * TkUndoCanRedo --
482 *
483 * Returns true if redo is possible, i.e. if the redo stack is not empty.
484 *
485 * Results:
486 * A boolean.
487 *
488 * Side effects:
489 * None.
490 *
491 *----------------------------------------------------------------------
492 */
493
494 int
TkUndoCanRedo(TkUndoRedoStack * stack)495 TkUndoCanRedo(
496 TkUndoRedoStack *stack) /* An Undo/Redo stack */
497 {
498 return stack->redoStack != NULL;
499 }
500
501 /*
502 *----------------------------------------------------------------------
503 *
504 * TkUndoCanUndo --
505 *
506 * Returns true if undo is possible, i.e. if the undo stack is not empty.
507 *
508 * Results:
509 * A boolean.
510 *
511 * Side effects:
512 * None.
513 *
514 *----------------------------------------------------------------------
515 */
516
517 int
TkUndoCanUndo(TkUndoRedoStack * stack)518 TkUndoCanUndo(
519 TkUndoRedoStack *stack) /* An Undo/Redo stack */
520 {
521 return stack->undoStack != NULL;
522 }
523
524 /*
525 *----------------------------------------------------------------------
526 *
527 * TkUndoInsertUndoSeparator --
528 *
529 * Insert a separator on the undo stack, indicating a border for an
530 * undo/redo chunk.
531 *
532 * Results:
533 * None.
534 *
535 * Side effects:
536 * None.
537 *
538 *----------------------------------------------------------------------
539 */
540
541 void
TkUndoInsertUndoSeparator(TkUndoRedoStack * stack)542 TkUndoInsertUndoSeparator(
543 TkUndoRedoStack *stack)
544 {
545 if (TkUndoInsertSeparator(&stack->undoStack)) {
546 stack->depth++;
547 TkUndoSetMaxDepth(stack, stack->maxdepth);
548 }
549 }
550
551 /*
552 *----------------------------------------------------------------------
553 *
554 * TkUndoRevert --
555 *
556 * Undo a compound action on the stack.
557 *
558 * Results:
559 * A Tcl status code
560 *
561 * Side effects:
562 * None.
563 *
564 *----------------------------------------------------------------------
565 */
566
567 int
TkUndoRevert(TkUndoRedoStack * stack)568 TkUndoRevert(
569 TkUndoRedoStack *stack)
570 {
571 TkUndoAtom *elem;
572
573 /*
574 * Insert a separator on the undo and the redo stack.
575 */
576
577 TkUndoInsertUndoSeparator(stack);
578 TkUndoInsertSeparator(&stack->redoStack);
579
580 /*
581 * Pop and skip the first separator if there is one.
582 */
583
584 elem = TkUndoPopStack(&stack->undoStack);
585 if (elem == NULL) {
586 return TCL_ERROR;
587 }
588
589 if (elem->type == TK_UNDO_SEPARATOR) {
590 ckfree(elem);
591 elem = TkUndoPopStack(&stack->undoStack);
592 }
593
594 while (elem != NULL && elem->type != TK_UNDO_SEPARATOR) {
595 /*
596 * Note that we currently ignore errors thrown here.
597 */
598
599 EvaluateActionList(stack->interp, elem->revert);
600
601 TkUndoPushStack(&stack->redoStack, elem);
602 elem = TkUndoPopStack(&stack->undoStack);
603 }
604
605 /*
606 * Insert a separator on the redo stack.
607 */
608
609 TkUndoInsertSeparator(&stack->redoStack);
610 stack->depth--;
611 return TCL_OK;
612 }
613
614 /*
615 *----------------------------------------------------------------------
616 *
617 * TkUndoApply --
618 *
619 * Redo a compound action on the stack.
620 *
621 * Results:
622 * A Tcl status code
623 *
624 * Side effects:
625 * None.
626 *
627 *----------------------------------------------------------------------
628 */
629
630 int
TkUndoApply(TkUndoRedoStack * stack)631 TkUndoApply(
632 TkUndoRedoStack *stack)
633 {
634 TkUndoAtom *elem;
635
636 /*
637 * Insert a separator on the undo stack.
638 */
639
640 TkUndoInsertSeparator(&stack->undoStack);
641
642 /*
643 * Pop and skip the first separator if there is one.
644 */
645
646 elem = TkUndoPopStack(&stack->redoStack);
647 if (elem == NULL) {
648 return TCL_ERROR;
649 }
650
651 if (elem->type == TK_UNDO_SEPARATOR) {
652 ckfree(elem);
653 elem = TkUndoPopStack(&stack->redoStack);
654 }
655
656 while (elem != NULL && elem->type != TK_UNDO_SEPARATOR) {
657 /*
658 * Note that we currently ignore errors thrown here.
659 */
660
661 EvaluateActionList(stack->interp, elem->apply);
662
663 TkUndoPushStack(&stack->undoStack, elem);
664 elem = TkUndoPopStack(&stack->redoStack);
665 }
666
667 /*
668 * Insert a separator on the undo stack.
669 */
670
671 TkUndoInsertSeparator(&stack->undoStack);
672 stack->depth++;
673 return TCL_OK;
674 }
675
676 /*
677 *----------------------------------------------------------------------
678 *
679 * EvaluateActionList --
680 *
681 * Execute a linked list of undo/redo sub-atoms. If any sub-atom returns
682 * a non TCL_OK value, execution of subsequent sub-atoms is cancelled and
683 * the error returned immediately.
684 *
685 * Results:
686 * A Tcl status code
687 *
688 * Side effects:
689 * The undo/redo subAtoms can perform arbitrary actions.
690 *
691 *----------------------------------------------------------------------
692 */
693
694 static int
EvaluateActionList(Tcl_Interp * interp,TkUndoSubAtom * action)695 EvaluateActionList(
696 Tcl_Interp *interp, /* Interpreter to evaluate the action in. */
697 TkUndoSubAtom *action) /* Head of linked list of action steps to
698 * perform. */
699 {
700 int result = TCL_OK;
701
702 while (action != NULL) {
703 if (action->funcPtr != NULL) {
704 result = action->funcPtr(interp, action->clientData,
705 action->action);
706 } else if (action->command != NULL) {
707 Tcl_Obj *cmdNameObj, *evalObj;
708
709 cmdNameObj = Tcl_NewObj();
710 evalObj = Tcl_NewObj();
711 Tcl_IncrRefCount(evalObj);
712 Tcl_GetCommandFullName(interp, action->command, cmdNameObj);
713 Tcl_ListObjAppendElement(NULL, evalObj, cmdNameObj);
714 if (action->action != NULL) {
715 Tcl_ListObjAppendList(NULL, evalObj, action->action);
716 }
717 result = Tcl_EvalObjEx(interp, evalObj, TCL_EVAL_GLOBAL);
718 Tcl_DecrRefCount(evalObj);
719 } else {
720 result = Tcl_EvalObjEx(interp, action->action, TCL_EVAL_GLOBAL);
721 }
722 if (result != TCL_OK) {
723 return result;
724 }
725 action = action->next;
726 }
727 return result;
728 }
729
730 /*
731 * Local Variables:
732 * mode: c
733 * c-basic-offset: 4
734 * fill-column: 78
735 * End:
736 */
737