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