/* * (c) Copyright 2010-2011 AgileBirds * * This file is part of OpenFlexo. * * OpenFlexo is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OpenFlexo is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OpenFlexo. If not, see <http://www.gnu.org/licenses/>. * */ package org.openflexo.foundation.action; import java.beans.PropertyChangeSupport; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Vector; import java.util.logging.Level; import java.util.logging.Logger; import org.openflexo.foundation.FlexoModelObject; import org.openflexo.toolbox.HasPropertyChangeSupport; public class UndoManager implements HasPropertyChangeSupport { private static final Logger logger = Logger.getLogger(UndoManager.class.getPackage().getName()); public static final String ACTION_HISTORY = "actionHistory"; public static final String ENABLED = "enabled"; private List<FlexoUndoableAction<?, ?, ?>> _actionHistory; private int _lastDoneIndex = -1; private PropertyChangeSupport propertyChangeSupport; private boolean enabled; private int undoLevel; public UndoManager() { this.propertyChangeSupport = new PropertyChangeSupport(this); _actionHistory = new LinkedList<FlexoUndoableAction<?, ?, ?>>(); } @Override public PropertyChangeSupport getPropertyChangeSupport() { return propertyChangeSupport; } @Override public String getDeletedProperty() { return null; } public void registerDoneAction(FlexoAction<?, ?, ?> action) { if (!enabled) { return; } if (action instanceof FlexoGUIAction) { // Ignore } else if (action instanceof FlexoUndoableAction) { _actionHistory.add((FlexoUndoableAction<?, ?, ?>) action); _lastDoneIndex = _actionHistory.size() - 1; getPropertyChangeSupport().firePropertyChange(ACTION_HISTORY, null, action); } else { // Cannot be undone // Reset action history resetActionHistory(); getPropertyChangeSupport().firePropertyChange(ACTION_HISTORY, null, action); } if (_actionHistory.size() > undoLevel) { if (logger.isLoggable(Level.FINE)) { logger.fine("Removing " + _actionHistory.get(0)); } _actionHistory.get(0).delete(); _actionHistory.remove(0); _lastDoneIndex--; } if (logger.isLoggable(Level.FINE)) { debug(); } } private void debug() { logger.info("History: "); int i = 0; for (FlexoAction<?, ?, ?> a : _actionHistory) { logger.info("" + i + " " + (_lastDoneIndex == i ? "*" : ">") + " : " + a); i++; } } private void resetActionHistory() { _actionHistory.clear(); _lastDoneIndex = -1; } public void undo() { if (isUndoActive()) { FlexoUndoableAction<?, ?, ?> action = getNextUndoAction(); action.undoAction(); _lastDoneIndex--; getPropertyChangeSupport().firePropertyChange(ACTION_HISTORY, null, action); if (logger.isLoggable(Level.FINE)) { debug(); } } } public void redo() { if (isRedoActive()) { FlexoUndoableAction<?, ?, ?> action = getNextRedoAction(); action.redoAction(); _lastDoneIndex++; getPropertyChangeSupport().firePropertyChange(ACTION_HISTORY, null, action); if (logger.isLoggable(Level.FINE)) { debug(); } } } public boolean isUndoActive() { return enabled && _actionHistory.size() > 0 && _lastDoneIndex > -1 && _lastDoneIndex < _actionHistory.size(); } public boolean isRedoActive() { return enabled && _actionHistory.size() > 0 && _lastDoneIndex < _actionHistory.size() - 1 && _lastDoneIndex >= -1; } public FlexoUndoableAction<?, ?, ?> getNextRedoAction() { if (isRedoActive()) { return _actionHistory.get(_lastDoneIndex + 1); } else { return null; } } public FlexoUndoableAction<?, ?, ?> getNextUndoAction() { if (isUndoActive()) { return _actionHistory.get(_lastDoneIndex); } else { return null; } } public void actionWillBePerformed(FlexoAction<?, ?, ?> action) { List<FlexoAction<?, ?, ?>> allActionsToTakeIntoAccount = new ArrayList<FlexoAction<?, ?, ?>>(); allActionsToTakeIntoAccount.add(action); for (int i = 0; i < _actionHistory.size(); i++) { FlexoAction<?, ?, ?> actionToTakeIntoAccount = _actionHistory.get(i); allActionsToTakeIntoAccount.add(actionToTakeIntoAccount); } action.getExecutionContext().saveExecutionContext(allActionsToTakeIntoAccount); } public <A extends FlexoAction<A, T1, T2>, T1 extends FlexoModelObject, T2 extends FlexoModelObject> void actionHasBeenPerformed( A action, boolean success) { if (success) { if (!action.isEmbedded()) { registerDoneAction(action); } } } public <A extends FlexoUndoableAction<A, T1, T2>, T1 extends FlexoModelObject, T2 extends FlexoModelObject> void actionWillBeUndone( A action) { } public <A extends FlexoUndoableAction<A, T1, T2>, T1 extends FlexoModelObject, T2 extends FlexoModelObject> void actionHasBeenUndone( A action, boolean success) { List<FlexoAction<?, ?, ?>> allActionsToNotify = new Vector<FlexoAction<?, ?, ?>>(); allActionsToNotify.add(action); int indexOfActionCurrentlyUndone = _actionHistory.indexOf(action); for (int i = indexOfActionCurrentlyUndone + 1; i < _actionHistory.size(); i++) { FlexoAction<?, ?, ?> actionToNotify = _actionHistory.get(i); allActionsToNotify.add(actionToNotify); } if (logger.isLoggable(Level.FINE)) { logger.fine("******* actionHasBeenUndone() for " + action + " notify actions: " + allActionsToNotify); } for (Map.Entry<String, FlexoModelObject> e : action.getExecutionContext().getObjectsCreatedWhileExecutingAction().entrySet()) { // Actions creating object are normally deleting those while undoing FlexoModelObject deletedObject = e.getValue(); for (FlexoAction<?, ?, ?> a : allActionsToNotify) { a.getExecutionContext().notifyExternalObjectDeletedByAction(deletedObject, action, e.getKey(), false); } } for (Map.Entry<String, FlexoModelObject> e : action.getExecutionContext().getObjectsDeletedWhileExecutingAction().entrySet()) { // Actions deleting object are normally recreating those while undoing FlexoModelObject createdObject = e.getValue(); for (FlexoAction<?, ?, ?> a : allActionsToNotify) { a.getExecutionContext().notifyExternalObjectCreatedByAction(createdObject, action, e.getKey(), false); } } } public <A extends FlexoUndoableAction<A, T1, T2>, T1 extends FlexoModelObject, T2 extends FlexoModelObject> void actionWillBeRedone( A action) { } public <A extends FlexoUndoableAction<A, T1, T2>, T1 extends FlexoModelObject, T2 extends FlexoModelObject> void actionHasBeenRedone( A action, boolean success) { List<FlexoAction<?, ?, ?>> allActionsToNotify = new Vector<FlexoAction<?, ?, ?>>(); allActionsToNotify.add(action); int indexOfActionCurrentlyUndone = _actionHistory.indexOf(action); for (int i = indexOfActionCurrentlyUndone + 1; i < _actionHistory.size(); i++) { FlexoAction<?, ?, ?> actionToNotify = _actionHistory.get(i); allActionsToNotify.add(actionToNotify); } for (Map.Entry<String, FlexoModelObject> e : action.getExecutionContext().getObjectsCreatedWhileExecutingAction().entrySet()) { // Actions creating object are normally recreated those while redoing FlexoModelObject createdObject = e.getValue(); for (FlexoAction<?, ?, ?> a : allActionsToNotify) { a.getExecutionContext().notifyExternalObjectCreatedByAction(createdObject, action, e.getKey(), true); } } for (Map.Entry<String, FlexoModelObject> e : action.getExecutionContext().getObjectsDeletedWhileExecutingAction().entrySet()) { // Actions deleting object are normally redeleted those while redoing FlexoModelObject deletedObject = e.getValue(); for (FlexoAction<?, ?, ?> a : allActionsToNotify) { a.getExecutionContext().notifyExternalObjectDeletedByAction(deletedObject, action, e.getKey(), true); } } } public boolean isEnabled() { return enabled; } public void setEnabled(boolean enabled) { this.enabled = enabled; getPropertyChangeSupport().firePropertyChange(ENABLED, !enabled, enabled); } public int getUndoLevel() { return undoLevel; } public void setUndoLevel(int undoLevel) { this.undoLevel = undoLevel; } }