/* $Id: DefaultUndoManager.java 18900 2010-12-07 22:38:44Z bobtarling $
*****************************************************************************
* Copyright (c) 2009 Contributors - see below
* 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:
* tfmorris
*****************************************************************************
*
* Some portions of this file was previously release using the BSD License:
*/
// Copyright (c) 1996-2006 The Regents of the University of California. All
// Rights Reserved. Permission to use, copy, modify, and distribute this
// software and its documentation without fee, and without a written
// agreement is hereby granted, provided that the above copyright notice
// and this paragraph appear in all copies. This software program and
// documentation are copyrighted by The Regents of the University of
// California. The software program and documentation are supplied "AS
// IS", without any accompanying services from The Regents. The Regents
// does not warrant that the operation of the program will be
// uninterrupted or error-free. The end-user understands that the program
// was developed for research purposes and is advised not to rely
// exclusively on the program for any reason. IN NO EVENT SHALL THE
// UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT,
// SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS,
// ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
// THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
// SUCH DAMAGE. THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY
// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
// PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
// CALIFORNIA HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT,
// UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
package org.argouml.kernel;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Stack;
import org.apache.log4j.Logger;
import org.argouml.i18n.Translator;
/**
* Manages a stacks of Commands to undo and redo. This DefaultUndoManager is
* only temporarily a singleton until changes are made to GEF.
*
* @author Bob Tarling
*/
class DefaultUndoManager implements UndoManager {
private static final Logger LOG =
Logger.getLogger(DefaultUndoManager.class);
/**
* The number of undoable commands to store. When set to zero undo is
* disabled entirely.
*/
private int undoMax = 0;
private ArrayList<PropertyChangeListener> listeners =
new ArrayList<PropertyChangeListener>();
/**
* Set when a new user interaction begins
*/
private boolean newInteraction = true;
/**
* The project to which this undo manager relates
*/
private final Project project;
/**
* A description of the user interaction taking place.
* Often this is the label of an Action.
*/
private String newInteractionLabel;
private UndoStack undoStack = new UndoStack();
private RedoStack redoStack = new RedoStack();
/**
* @deprecated in 0.32 alpha by Bob Tarling use DefaultUndoManager(Project)
*/
@Deprecated
private static final UndoManager INSTANCE = new DefaultUndoManager();
/**
* @deprecated in 0.32 alpha by Bob Tarling use DefaultUndoManager(Project)
*/
@Deprecated
private DefaultUndoManager() {
super();
project = null;
}
DefaultUndoManager(Project project) {
super();
this.project = project;
}
/**
* @deprecated in 0.32 alpha by Bob Tarling use DefaultUndoManager(Project)
*/
@Deprecated
public static UndoManager getInstance() {
return INSTANCE;
}
public synchronized Object execute(Command command) {
addCommand(command);
return command.execute();
}
public synchronized void addCommand(Command command) {
// TODO: Once the default constructor is deleted we only set dirty flag
if (project != null) {
project.setDirty(true);
} else {
ProjectManager.getManager().setSaveEnabled(true);
}
if (undoMax == 0) {
return;
}
if (!command.isUndoable()) {
undoStack.clear();
newInteraction = true;
}
// Flag the command as to whether it is first in a chain
final Interaction macroCommand;
if (newInteraction || undoStack.isEmpty()) {
redoStack.clear();
newInteraction = false;
if (undoStack.size() > undoMax) {
undoStack.remove(0);
}
macroCommand = new Interaction(newInteractionLabel);
undoStack.push(macroCommand);
} else {
macroCommand = undoStack.peek();
}
macroCommand.addCommand(command);
}
public void setUndoMax(int max) {
undoMax = max;
}
public synchronized void undo() {
final Interaction command = undoStack.pop();
command.undo();
if (!command.isRedoable()) {
redoStack.clear();
}
redoStack.push(command);
}
public synchronized void redo() {
final Interaction command = redoStack.pop();
command.execute();
undoStack.push(command);
}
public synchronized void startInteraction(String label) {
LOG.debug("Starting interaction " + label);
this.newInteractionLabel = label;
newInteraction = true;
}
public void addPropertyChangeListener(PropertyChangeListener listener) {
listeners.add(listener);
}
public void removePropertyChangeListener(PropertyChangeListener listener) {
listeners.remove(listener);
}
private void fire(final String property, final Object value) {
for (PropertyChangeListener listener : listeners) {
listener.propertyChange(
new PropertyChangeEvent(this, property, "", value));
}
}
/**
* An Interact is a Command the contains a list of sub-commands. It
* represents a single user interaction and contains all the commands
* executed as part of that interaction.
*
* @author Bob
*/
class Interaction extends AbstractCommand {
private List<Command> commands = new ArrayList<Command>();
private String label;
Interaction(String lbl) {
label = lbl;
}
public void undo() {
final ListIterator<Command> it =
commands.listIterator(commands.size());
while (it.hasPrevious()) {
it.previous().undo();
}
}
public Object execute() {
final Iterator<Command> it = commands.iterator();
while (it.hasNext()) {
it.next().execute();
}
return null;
}
public boolean isUndoable() {
final Iterator<Command> it = commands.iterator();
while (it.hasNext()) {
final Command command = it.next();
if (!command.isUndoable()) {
return false;
}
}
return true;
}
public boolean isRedoable() {
final Iterator<Command> it = commands.iterator();
while (it.hasNext()) {
final Command command = it.next();
if (!command.isRedoable()) {
return false;
}
}
return true;
}
private void addCommand(Command command) {
commands.add(command);
}
// TODO: i18n
private String getUndoLabel() {
if (isUndoable()) {
return "Undo " + label;
} else {
return "Can't Undo " + label;
}
}
// TODO: i18n
private String getRedoLabel() {
if (isRedoable()) {
return "Redo " + label;
} else {
return "Can't Redo " + label;
}
}
List<Command> getCommands() {
return new ArrayList<Command> (commands);
}
}
private abstract class InteractionStack extends Stack<Interaction> {
private String labelProperty;
private String addedProperty;
private String removedProperty;
private String sizeProperty;
public InteractionStack(
String labelProp,
String addedProp,
String removedProp,
String sizeProp) {
labelProperty = labelProp;
addedProperty = addedProp;
removedProperty = removedProp;
sizeProperty = sizeProp;
}
public Interaction push(Interaction item) {
super.push(item);
fireLabel();
fire(addedProperty, item);
fire(sizeProperty, size());
return item;
}
public Interaction pop() {
Interaction item = super.pop();
fireLabel();
fire(removedProperty, item);
fire(sizeProperty, size());
return item;
}
private void fireLabel() {
fire(labelProperty, getLabel());
}
protected abstract String getLabel();
}
private class UndoStack extends InteractionStack {
public UndoStack() {
super(
"undoLabel",
"undoAdded",
"undoRemoved",
"undoSize");
}
public Interaction push(Interaction item) {
super.push(item);
if (item.isUndoable()) {
fire("undoable", true);
}
return item;
}
public Interaction pop() {
Interaction item = super.pop();
if (size() == 0 || !peek().isUndoable()) {
fire("undoable", false);
}
return item;
}
public void clear() {
super.clear();
fire("undoSize", size());
fire("undoable", false);
}
protected String getLabel() {
if (empty()) {
return Translator.localize("action.undo");
} else {
return peek().getUndoLabel();
}
}
}
private class RedoStack extends InteractionStack {
public RedoStack() {
super(
"redoLabel",
"redoAdded",
"redoRemoved",
"redoSize");
}
public Interaction push(Interaction item) {
super.push(item);
if (item.isRedoable()) {
fire("redoable", true);
}
return item;
}
public Interaction pop() {
Interaction item = super.pop();
if (size() == 0 || !peek().isRedoable()) {
fire("redoable", false);
}
return item;
}
public void clear() {
super.clear();
fire("redoSize", size());
fire("redoable", false);
}
protected String getLabel() {
if (empty()) {
return Translator.localize("action.redo");
} else {
return peek().getRedoLabel();
}
}
}
}