/******************************************************************************* * Copyright (c) 2000, 2010, 2012 IBM Corporation, Gerhardt Informatics Kft. and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation * Gerhardt Informatics Kft. - GEFGWT port *******************************************************************************/ package org.eclipse.gef.commands; import java.util.ArrayList; import java.util.EventObject; import java.util.List; import java.util.Stack; /** * An implementation of a command stack. A stack manages the executing, undoing, * and redoing of {@link Command Commands}. Executed commands are pushed onto a * a stack for undoing later. Commands which are undone are pushed onto a redo * stack. Whenever a new command is executed, the Redo stack is flushed. * <P> * A CommandStack contains a dirty property. This property can be used to * determine when persisting changes is required. The stack is dirty whenever * the last executed or redone command is different than the command that was at * the top of the undo stack when {@link #markSaveLocation()} was last called. * Initially, the undo stack is empty, and not dirty. * * @author hudsonr */ public class CommandStack { /** * Constant indicating notification after a command has been executed (value * is 8). */ public static final int POST_EXECUTE = 8; /** * Constant indicating notification after a command has been redone (value * is 16). */ public static final int POST_REDO = 16; /** * Constant indicating notification after a command has been undone (value * is 32). */ public static final int POST_UNDO = 32; /** * A bit-mask indicating notification after a command has done something. * Currently this includes after a command has been undone, redone, or * executed. This will include new events should they be introduced in the * future. * <P> * Usage<BR/> * * <PRE> * if ((commandStackEvent.getDetail() & CommandStack.POST_MASK) != 0) { * // Do something, like: * stopBatchingChanges(); * } * </PRE> */ public static final int POST_MASK = new Integer(POST_EXECUTE | POST_UNDO | POST_REDO).intValue(); /** * Constant indicating notification prior to executing a command (value is * 1). */ public static final int PRE_EXECUTE = 1; /** * Constant indicating notification prior to redoing a command (value is 2). */ public static final int PRE_REDO = 2; /** * Constant indicating notification prior to undoing a command (value is 4). */ public static final int PRE_UNDO = 4; /** * A bit-mask indicating notification before a command makes a change. * Currently this includes before a command has been undone, redone, or * executed. This will include new events should they be introduced in the * future. * <P> * Usage<BR/> * * <PRE> * if ((commandStackEvent.getDetail() & CommandStack.PRE_MASK) != 0) { * // Do something, like: * startBatchingChanges(); * } * </PRE> * * @since 3.7 Had package visibility before. */ public static final int PRE_MASK = new Integer(PRE_EXECUTE | PRE_UNDO | PRE_REDO).intValue(); private List eventListeners = new ArrayList(); /** * The list of {@link CommandStackListener}s. * * @deprecated This field should not be referenced, use * {@link #notifyListeners()} */ protected List listeners = new ArrayList(); private Stack redoable = new Stack(); private int saveLocation = 0; private Stack undoable = new Stack(); private int undoLimit = 0; /** * Constructs a new command stack. By default, there is no undo limit, and * isDirty() will return <code>false</code>. */ public CommandStack() { } /** * Appends the listener to the list of command stack listeners. Multiple * adds result in multiple notifications. * * @since 3.1 * @param listener * the event listener */ public void addCommandStackEventListener(CommandStackEventListener listener) { eventListeners.add(listener); } /** * Appends the listener to the list of command stack listeners. Multiple * adds will result in multiple notifications. * * @param listener * the listener */ public void addCommandStackListener(CommandStackListener listener) { listeners.add(listener); } /** * @return <code>true</code> if it is appropriate to call {@link #redo()}. */ public boolean canRedo() { return !redoable.isEmpty(); } /** * @return <code>true</code> if {@link #undo()} can be called */ public boolean canUndo() { if (undoable.size() == 0) return false; return ((Command) undoable.lastElement()).canUndo(); } /** * This will <code>dispose()</code> all the commands in both the undo and * redo stack. Both stacks will be empty afterwards. */ public void dispose() { flushUndo(); flushRedo(); } /** * Executes the specified Command if possible. Prior to executing the * command, a CommandStackEvent for {@link #PRE_EXECUTE} will be fired to * event listeners. Similarly, after attempting to execute the command, an * event for {@link #POST_EXECUTE} will be fired. If the execution of the * command completely normally, stack listeners will receive * {@link CommandStackListener#commandStackChanged(EventObject) * stackChanged} notification. * <P> * If the command is <code>null</code> or cannot be executed, nothing * happens. * * @param command * the Command to execute * @see CommandStackEventListener */ public void execute(Command command) { if (command == null || !command.canExecute()) return; flushRedo(); notifyListeners(command, PRE_EXECUTE); try { command.execute(); if (getUndoLimit() > 0) { while (undoable.size() >= getUndoLimit()) { ((Command) undoable.remove(0)).dispose(); if (saveLocation > -1) saveLocation--; } } if (saveLocation > undoable.size()) saveLocation = -1; // The save point was somewhere in the redo // stack undoable.push(command); notifyListeners(); } finally { notifyListeners(command, POST_EXECUTE); } } /** * Flushes the entire stack and resets the save location to zero. This * method might be called when performing "revert to saved". */ public void flush() { flushRedo(); flushUndo(); saveLocation = 0; notifyListeners(); } private void flushRedo() { while (!redoable.isEmpty()) ((Command) redoable.pop()).dispose(); } private void flushUndo() { while (!undoable.isEmpty()) ((Command) undoable.pop()).dispose(); } /** * @return an array containing all commands in the order they were executed */ public Object[] getCommands() { List commands = new ArrayList(undoable); for (int i = redoable.size() - 1; i >= 0; i--) { commands.add(redoable.get(i)); } return commands.toArray(); } /** * Peeks at the top of the <i>redo</i> stack. This is useful for describing * to the User what will be redone. The returned <code>Command</code> has a * label describing it. * * @return the top of the <i>redo</i> stack, which may be <code>null</code> */ public Command getRedoCommand() { return redoable.isEmpty() ? null : (Command) redoable.peek(); } /** * Peeks at the top of the <i>undo</i> stack. This is useful for describing * to the User what will be undone. The returned <code>Command</code> has a * label describing it. * * @return the top of the <i>undo</i> stack, which may be <code>null</code> */ public Command getUndoCommand() { return undoable.isEmpty() ? null : (Command) undoable.peek(); } /** * Returns the undo limit. The undo limit is the maximum number of atomic * operations that the User can undo. <code>-1</code> is used to indicate no * limit. * * @return the undo limit */ public int getUndoLimit() { return undoLimit; } /** * Returns true if the stack is dirty. The stack is dirty whenever the last * executed or redone command is different than the command that was at the * top of the undo stack when {@link #markSaveLocation()} was last called. * * @return <code>true</code> if the stack is dirty */ public boolean isDirty() { return undoable.size() != saveLocation; } /** * Marks the last executed or redone Command as the point at which the * changes were saved. Calculation of {@link #isDirty()} will be based on * this checkpoint. */ public void markSaveLocation() { saveLocation = undoable.size(); notifyListeners(); } /** * Sends notification to all {@link CommandStackListener}s. * * @deprecated */ protected void notifyListeners() { EventObject event = new EventObject(this); for (int i = 0; i < listeners.size(); i++) ((CommandStackListener) listeners.get(i)) .commandStackChanged(event); } /** * Notifies command stack event listeners that the command stack has changed * to the specified state. * * @param command * the command * @param state * the current stack state * @since 3.2 */ protected void notifyListeners(Command command, int state) { CommandStackEvent event = new CommandStackEvent(this, command, state); for (int i = 0; i < eventListeners.size(); i++) ((CommandStackEventListener) eventListeners.get(i)) .stackChanged(event); } /** * Calls redo on the Command at the top of the <i>redo</i> stack, and pushes * that Command onto the <i>undo</i> stack. This method should only be * called when {@link #canUndo()} returns <code>true</code>. */ public void redo() { // Assert.isTrue(canRedo()) if (!canRedo()) return; Command command = (Command) redoable.pop(); notifyListeners(command, PRE_REDO); try { command.redo(); undoable.push(command); notifyListeners(); } finally { notifyListeners(command, POST_REDO); } } /** * Removes the first occurrence of the specified listener. * * @param listener * the listener */ public void removeCommandStackEventListener( CommandStackEventListener listener) { eventListeners.remove(listener); } /** * Removes the first occurrence of the specified listener. * * @param listener * the listener */ public void removeCommandStackListener(CommandStackListener listener) { listeners.remove(listener); } /** * Sets the undo limit. The undo limit is the maximum number of atomic * operations that the User can undo. <code>-1</code> is used to indicate no * limit. * * @param undoLimit * the undo limit */ public void setUndoLimit(int undoLimit) { this.undoLimit = undoLimit; } /** * Undoes the most recently executed (or redone) Command. The Command is * popped from the undo stack to and pushed onto the redo stack. This method * should only be called when {@link #canUndo()} returns <code>true</code>. */ public void undo() { // Assert.isTrue(canUndo()); Command command = (Command) undoable.pop(); notifyListeners(command, PRE_UNDO); try { command.undo(); redoable.push(command); notifyListeners(); } finally { notifyListeners(command, POST_UNDO); } } }