Custom Undo/Redo Solution in Java

Articles —> Custom Undo/Redo Solution in Java

Quite often I wish to implement undo and redo features in an application. A user changes the state of the program, and then wishes to reverse that state to the previous - be it to remove a mistake, make a simple comparison, or follow the progression of their actions. Many java swing components already provide basic undo/redo features, in particular the JTextComponent classes. However these are limited in their ability and quite often it is necessary to implement a custom undo/redo, for example to change a user action that draws to a JPanel, adds to a JTree, changes a data structure, or a combination of the above.

How can we create undo's and redo's readily adaptable for not just one, but all sorts of actions? What we wish is to be able to generalize this design, allowing it to be re-used regardless of the user action. Having an interface which defines the undo/redo actions allows any object to implement these actions, to create composites of these interfaces to delegate the action, and therefore allows decoupling of the implementation of the action from how undoes and redoes will be managed.

The java source code:


/**

 * Interface to implement a Changeable type of action - either undo or redo.

 * @author Greg COpe

 *

 */

public interface Changeable {



	/**

	 * Undoes an action

	 */

	public void undo();

	

	/**

	 * Redoes an action

	 */

	public void redo();

}

Simple enough. Any class that implements this interface is responsible for dealing with any underlying undo and redo actions.

Now can instances of the Changeable interface be managed in a way which corresponds to a simple queue'd undo redo function? Ideally, we'd like a user to be able to undo and redo any action in a queue: a user does something and can undo that action. After an undo, a user can redo said action. If a user performs an action after calling undo, the queue is branched and all possible previous redo are not available.

When I hear the word queue, I think linked list. Using a linked list, we can traverse across the list easily saving the current position in the list, and check if we've reached an end so an undo or redo cannot be performed. And what will the list contain? Changeables of course. The manager class below implements this design and will manage Changeables in a custom made linked list, allowing us to monitor when one can undo or redo (when the internal marker has reached an end to the list), and performing those actions appropriately.


import java.util.List;

import java.util.ArrayList;



import javax.swing.event.ChangeEvent;



/**

 * Manages a Queue of Changables to perform undo and/or redo operations. Clients can add implementations of the Changeable

 * class to this class, and it will manage undo/redo as a Queue.

 * 

 * @author Greg Cope

 *

 */

public class ChangeManager {

	

	//the current index node

	private Node currentIndex = null;

	//the parent node far left node.

	private Node parentNode = new Node();

	

	/**

	 * Creates a new ChangeManager object which is initially empty.

	 */

	public ChangeManager(){

		currentIndex = parentNode;

	}

	

	/**

	 * Creates a new ChangeManager which is a duplicate of the parameter in both contents and current index.

	 * @param manager

	 */

	public ChangeManager(ChangeManager manager){

		this();

		currentIndex = manager.currentIndex;

	}

	

	

	/**

	 * Clears all Changables contained in this manager.

	 */

	public void clear(){

		currentIndex = parentNode;

	}

	

	/**

	 * Adds a Changeable to manage.

	 * @param changeable 

	 */

	public void addChangeable(Changeable changeable){

		Node node = new Node(changeable);

		currentIndex.right = node;

		node.left = currentIndex;

		currentIndex = node;

	}

	

	/**

	 * Determines if an undo can be performed.

	 * @return

	 */

	public boolean canUndo(){

		return currentIndex != parentNode;

	}

	

	/**

	 * Determines if a redo can be performed.

	 * @return

	 */

	public boolean canRedo(){

		return currentIndex.right != null;

	}

	

	/**

	 * Undoes the Changeable at the current index. 

	 * @throws IllegalStateException if canUndo returns false. 

	 */

	public void undo(){

		//validate

		if ( !canUndo() ){

			throw new IllegalStateException("Cannot undo. Index is out of range.");

		}

		//undo

		currentIndex.changeable.undo();

		//set index

		moveLeft();

	}

	

	/**

	 * Moves the internal pointer of the backed linked list to the left.

	 * @throws IllegalStateException If the left index is null.

	 */

	private void moveLeft(){

		if ( currentIndex.left == null ){

			throw new IllegalStateException("Internal index set to null.");

		}

		currentIndex = currentIndex.left;

	}

	

	/**

	 * Moves the internal pointer of the backed linked list to the right.

	 * @throws IllegalStateException If the right index is null.

	 */

	private void moveRight(){

		if ( currentIndex.right == null ){

			throw new IllegalStateException("Internal index set to null.");

		}

		currentIndex = currentIndex.right;

	}

	

	

	/**

	 * Redoes the Changable at the current index.

	 * @throws IllegalStateException if canRedo returns false. 

	 */

	public void redo(){

		//validate

		if ( !canRedo() ){

			throw new IllegalStateException("Cannot redo. Index is out of range.");

		}

		//reset index

		moveRight();

		//redo

		currentIndex.changeable.redo();

	}

	

	/**

	 * Inner class to implement a doubly linked list for our queue of Changeables. 

	 * @author Greg Cope

	 *

	 */

	private class Node {

		private Node left = null;

		private Node right = null;

		

		private final Changeable changeable;

		

		public Node(Changeable c){

			changeable = c;

		}

		

		public Node(){

			changeable = null;

		}

	}

	

}

That's a bit of a mouthful, but the idea is that Changeables can be added to the manager, and all clients need to do is call check if an undo or redo can be performed, and then call undo and redo appropriately. When we call undo, we can redo that action. When we call redo, we can undo that action. How does this work in a real program? For a basic example, lets start with a code snippet that prints out some values to the command line via undo and redo actions:


pubic class Test{

	public static void main(String[] args){

		ChangeManager manager = new ChangeManager();

		manager.addChangeable(new CommandLineChanger("1") );

		manager.addChangeable(new CommandLineChanger("2") );

		manager.undo();

		manager.redo();

		manager.undo();

		manager.undo();

		manager.addChangeable(new CommandLineChanger("3") );

		manager.undo();

		

	}

	public static class CommandLineChanger implements Changeable{

		

		private final String val;

		public CommandLineChanger(String v){

			super();

			this.val = v;

		}

		

		public void undo(){

			System.out.println(val + " undone");

		}

		

		public void redo(){

			System.out.println(val + " redone");

		}

	}

}

Prints out:

2 undone

2 redone

2 undone

1 undone

3 undone

A simple example, but notice how the manager allows one to track any undo and redo actions regardless of what they actually are - using an interface completely decouples the underlying undo and redo actions from how they are actually managed.




Comments

  • Nikolas Nikolas   -   February, 1, 2011

    Excellent! very very useful...!

  • Raymond Buckley   -   October, 30, 2011

    Excellent tutorial. I integrated this into my level editor for my games very easily. It works perfectly. I will suggest this strategy to everyone I help!

  • one two   -   July, 18, 2012

    Thank you very much.

  • Kanishka Roman   -   April, 10, 2014

    Newbie Question.

    How do I add a button click event to: manager.addChangeable();.

    In my GUI I have a button to add an image and another button to delete it. I want to add redo / undo functionality to my actions. Please help.

  • Greg Cope   -   April, 10, 2014

    Kanishka, you need to implement the Changeable interface - this defines what happens when you wish to undo/redo. When changes occur (such as when a button is clicked as you describe), create an instance of this class and add it to the ChangeManager. Future calls to the ChangeManager's undo/redo will then result in the undo/redo changes to occur.

  • sepideh bayati   -   May, 19, 2016

    what is changeable? please write a complete code

  • Greg Cope   -   November, 30, -0001

    @sepideh, Changeable is an interface...it is up to the programmer to implement as it is context dependent. There is an example of implementation in the code above.

  • Prakhar Khandelwal   -   June, 14, 2017

    i am a beginner so can u just Elaborate the hint for image undo/redo... means can u just give some exaple related to it(image undo/redo) for just basic idea to create and implement that..

Back to Articles


© 2008-2017 Greg Cope