/* ********************************************************************** **
** Copyright notice **
** **
** (c) 2005-2009 RSSOwl Development Team **
** http://www.rssowl.org/ **
** **
** 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.rssowl.org/legal/epl-v10.html **
** **
** A copy is found in the file epl-v10.html and important notices to the **
** license from the team is found in the textfile LICENSE.txt distributed **
** in this package. **
** **
** This copyright notice MUST APPEAR in all copies of the file! **
** **
** Contributors: **
** RSSOwl Development Team - initial API and implementation **
** **
** ********************************************************************** */
package org.rssowl.ui.internal.undo;
import org.eclipse.core.runtime.Assert;
import org.eclipse.jface.util.SafeRunnable;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.custom.BusyIndicator;
import org.eclipse.swt.widgets.Display;
import org.rssowl.core.internal.InternalOwl;
import org.rssowl.core.util.LoggingSafeRunnable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* The {@link UndoStack} keeps a list of {@link IUndoOperation} and supports
* undo/redo of these. The stack has a maximum capacity as defined by
* <code>MAX_SIZE</code>.
*
* @author bpasero
*/
public class UndoStack {
private static final int MAX_SIZE = 20;
private static UndoStack singleton = new UndoStack();
private final List<IUndoOperation> fOperations = Collections.synchronizedList(new ArrayList<IUndoOperation>());
private int fCurrentIndex = 0;
private final List<IUndoRedoListener> fListeners = new ArrayList<IUndoRedoListener>();
private UndoStack() {}
/**
* @return the singleton instance of the {@link UndoStack}.
*/
public static UndoStack getInstance() {
if (singleton == null)
singleton = new UndoStack();
return singleton;
}
/**
* Clears all Operations from the Stack.
*/
public void clear() {
fOperations.clear();
}
/**
* @param listener the listener to be notified when Undo or Redo was
* performed, or when an operation was added to the stack.
*/
public void addListener(IUndoRedoListener listener) {
if (!fListeners.contains(listener))
fListeners.add(listener);
}
/**
* @param listener the listener to remove from the list of listeners.
*/
public void removeListener(IUndoRedoListener listener) {
fListeners.remove(listener);
}
/**
* Adds the given operation to the stack.
*
* @param operation the operation to add to the stack.
*/
public synchronized void addOperation(IUndoOperation operation) {
Assert.isNotNull(operation);
/* Handle case where User executed Undo-Operation */
if (fCurrentIndex < (fOperations.size() - 1)) {
/* Remove all following Undo-Operations */
List<IUndoOperation> toDelete = new ArrayList<IUndoOperation>();
for (int i = fCurrentIndex + 1; i < fOperations.size(); i++)
toDelete.add(fOperations.get(i));
fOperations.removeAll(toDelete);
}
/* Add operation and constrain size */
fOperations.add(operation);
if (fOperations.size() > MAX_SIZE) {
List<IUndoOperation> toDelete = new ArrayList<IUndoOperation>();
for (int i = 0; i < fOperations.size() - MAX_SIZE; i++)
toDelete.add(fOperations.get(i));
fOperations.removeAll(toDelete);
}
/* Set pointer to last element */
fCurrentIndex = fOperations.size() - 1;
/* Notify Listeners */
notifyOperationAdded();
}
/**
* @return Returns the name for the next undo-operation or a generic one if
* undo is not supported currently.
*/
public String getUndoName() {
if (!isUndoSupported())
return Messages.UndoStack_UNDO;
return NLS.bind(Messages.UndoStack_UNDO_N, fOperations.get(fCurrentIndex).getName());
}
/**
* @return Returns the name for the next redo-operation or a generic one if
* redo is not supported currently.
*/
public String getRedoName() {
if (!isRedoSupported())
return Messages.UndoStack_REDO;
return NLS.bind(Messages.UndoStack_REDO_N, fOperations.get(fCurrentIndex + 1).getName());
}
/**
* @return Returns <code>true</code> if undo is supported and
* <code>false</code> otherwise.
*/
public boolean isUndoSupported() {
return fCurrentIndex >= 0 && !fOperations.isEmpty();
}
/**
* @return Returns <code>true</code> if redo is supported and
* <code>false</code> otherwise.
*/
public boolean isRedoSupported() {
return fCurrentIndex < (fOperations.size() - 1);
}
/**
* Navigates backwards in the list of operations if possible and undos the
* operation.
*/
public synchronized void undo() {
if (!isUndoSupported())
return;
final IUndoOperation undoOperation = fOperations.get(fCurrentIndex);
Runnable undoRunnable = new Runnable() {
public void run() {
undoOperation.undo();
}
};
if (undoOperation.isLongRunning() && !InternalOwl.TESTING)
BusyIndicator.showWhile(Display.getDefault(), undoRunnable);
else
undoRunnable.run();
fCurrentIndex--;
notifyUndoPerformed();
}
/**
* Navigates forwards in the list of operations if possible and redos the
* operation.
*/
public synchronized void redo() {
if (!isRedoSupported())
return;
fCurrentIndex++;
final IUndoOperation redoOperation = fOperations.get(fCurrentIndex);
Runnable redoRunnable = new Runnable() {
public void run() {
redoOperation.redo();
}
};
if (redoOperation.isLongRunning() && !InternalOwl.TESTING)
BusyIndicator.showWhile(Display.getDefault(), redoRunnable);
else
redoRunnable.run();
notifyRedoPerformed();
}
private void notifyUndoPerformed() {
for (final IUndoRedoListener listener : fListeners) {
SafeRunnable.run(new LoggingSafeRunnable() {
public void run() throws Exception {
listener.undoPerformed();
}
});
}
}
private void notifyRedoPerformed() {
for (final IUndoRedoListener listener : fListeners) {
SafeRunnable.run(new LoggingSafeRunnable() {
public void run() throws Exception {
listener.redoPerformed();
}
});
}
}
private void notifyOperationAdded() {
for (final IUndoRedoListener listener : fListeners) {
SafeRunnable.run(new LoggingSafeRunnable() {
public void run() throws Exception {
listener.operationAdded();
}
});
}
}
}