UndoManager.java

/*
 * @(#)UndoManager.java
 *
 * Project:		JHotdraw - a GUI framework for technical drawings
 *				http://www.jhotdraw.org
 *				http://jhotdraw.sourceforge.net
 * Copyright:	© by the original author(s) and all contributors
 * License:		Lesser GNU Public License (LGPL)
 *				http://www.opensource.org/licenses/lgpl-license.html
 */

package CH.ifa.draw.util;

import CH.ifa.draw.framework.*;
import java.util.*;

/**
 * This class manages all the undoable commands. It keeps track of all 
 * the modifications done through user interactions.
 *
 * @version <$CURRENT_VERSION$>
 */
public class UndoManager {
	/**
	 * Maximum default buffer size for undo and redo stack
	 */
	public static final int DEFAULT_BUFFER_SIZE = 20;

	/**
	 * Collection of undo activities
	 */
	private Vector redoStack;
		
	/**
	 * Collection of undo activities
	 */
	private Vector undoStack;
	private int maxStackCapacity;
	
	public UndoManager() {
		this(DEFAULT_BUFFER_SIZE);
	}

	public UndoManager(int newUndoStackSize) {
		maxStackCapacity = newUndoStackSize;
		undoStack = new Vector(maxStackCapacity);
		redoStack = new Vector(maxStackCapacity);
	}

	public void pushUndo(Undoable undoActivity) {
		if (undoActivity.isUndoable()) {
			// If buffersize exceeds, remove the oldest command
			if (getUndoSize() >= maxStackCapacity) {
				undoStack.removeElementAt(0);
			}
		
			undoStack.addElement(undoActivity);
		}
		else {
			// a not undoable activity clears the stack because
			// the last activity does not correspond with the
			// last undo activity
			undoStack = new Vector(maxStackCapacity);
		}
	}

	public void pushRedo(Undoable redoActivity) {
		if (redoActivity.isRedoable()) {
			// If buffersize exceeds, remove the oldest command
			if (getRedoSize() >= maxStackCapacity) {
				redoStack.removeElementAt(0);
			}
		
			// add redo activity only if it is not already the last
			// one in the buffer
			if ((getRedoSize() == 0) || (peekRedo() != redoActivity)) {
				redoStack.addElement(redoActivity);
			}
		}
		else {
			// a not undoable activity clears the tack because
			// the last activity does not correspond with the
			// last undo activity
			redoStack = new Vector(maxStackCapacity);
		}
	}

	public boolean isUndoable() {
		if (getUndoSize() > 0) {
			return ((Undoable)undoStack.lastElement()).isUndoable();
		}
		else {
			return false;
		}
	}
	
	public boolean isRedoable() {
		if (getRedoSize() > 0) {
			return ((Undoable)redoStack.lastElement()).isRedoable();
		}
		else {
			return false;
		}
	}

	protected Undoable peekUndo() {
		if (getUndoSize() > 0) {
			return (Undoable) undoStack.lastElement();
		}
		else {
			return null;
		}
	}

	protected Undoable peekRedo() {
		if (getRedoSize() > 0) {
			return (Undoable) redoStack.lastElement();
		}
		else {
			return null;
		}
	}

	/**
	 * Returns the current size of undo buffer.
	 */
	public int getUndoSize() {
		return undoStack.size();
	}

	/**
	 * Returns the current size of redo buffer.
	 */
	public int getRedoSize() {
		return redoStack.size();
	}

	/**
	 * Throw NoSuchElementException if there is none
	 */
	public Undoable popUndo() {
		// Get the last element - throw NoSuchElementException if there is none
		Undoable lastUndoable = peekUndo();

		// Remove it from undo collection
		undoStack.removeElementAt(getUndoSize() - 1);
		
		return lastUndoable;
	}

	/**
	 * Throw NoSuchElementException if there is none
	 */
	public Undoable popRedo() {
		// Get the last element - throw NoSuchElementException if there is none
		Undoable lastUndoable = peekRedo();

		// Remove it from undo collection
		redoStack.removeElementAt(getRedoSize() - 1);

		return lastUndoable;
	}

	public void clearUndos() {
		clearStack(undoStack);
	}

	public void clearRedos() {
		clearStack(redoStack);
	}
	
	protected void clearStack(Vector clearStack) {
		clearStack.removeAllElements();
	}
}