/*
* Autopsy Forensic Browser
*
* Copyright 2015 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.imagegallery.actions;
import java.util.Objects;
import java.util.Optional;
import javafx.beans.property.ReadOnlyIntegerProperty;
import javafx.beans.property.SimpleListProperty;
import javafx.collections.FXCollections;
import javax.annotation.Nonnull;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
/**
*
*/
@ThreadSafe
public class UndoRedoManager {
@GuardedBy("this")
private final ObservableStack<UndoableCommand> undoStack = new ObservableStack<>();
@GuardedBy("this")
private final ObservableStack<UndoableCommand> redoStack = new ObservableStack<>();
synchronized public int getRedosAvaialable() {
return redoStack.getSize();
}
synchronized public int getUndosAvailable() {
return redoStack.getSize();
}
synchronized public ReadOnlyIntegerProperty redosAvailableProporty() {
return redoStack.sizeProperty();
}
synchronized public ReadOnlyIntegerProperty undosAvailableProperty() {
return undoStack.sizeProperty();
}
synchronized public void clear() {
redoStack.clear();
undoStack.clear();
}
/**
* Flip the top redo command over to the undo stack, after applying it
*
* @return the redone command or null if there are no redos available
*/
synchronized Optional<UndoableCommand> redo() {
if (redoStack.isEmpty()) {
return Optional.empty();
} else {
UndoableCommand pop = redoStack.pop();
undoStack.push(pop);
pop.run();
return Optional.of(pop);
}
}
/**
* Flip the top undo command over to the redo stack, after undoing it
*
* @return the undone command or null if there there are no undos available.
*/
synchronized Optional<UndoableCommand> undo() {
if (undoStack.isEmpty()) {
return Optional.empty();
} else {
final UndoableCommand pop = undoStack.pop();
redoStack.push(pop);
pop.undo();
return Optional.of(pop);
}
}
/**
* push a new command onto the undo stack and clear the redo stack.
*
* Note: this method does not actually apply/execute the given command, only
* record it in the undo stack.
*
* @param command the command to add to the undo stack
*/
synchronized void addToUndo(@Nonnull UndoableCommand command) {
Objects.requireNonNull(command);
undoStack.push(command);
redoStack.clear();
}
/**
* A simple extension to SimpleListProperty to add a stack api
*
* TODO: this really should not extend SimpleListProperty but should
* delegate to an appropriate observable implementation while implementing
* the {@link Deque} interface
*/
private static class ObservableStack<T> extends SimpleListProperty<T> {
ObservableStack() {
super(FXCollections.<T>synchronizedObservableList(FXCollections.<T>observableArrayList()));
}
public void push(T item) {
synchronized (this) {
add(0, item);
}
}
public T pop() {
synchronized (this) {
if (isEmpty()) {
return null;
} else {
return remove(0);
}
}
}
public T peek() {
synchronized (this) {
if (isEmpty()) {
return null;
} else {
return get(0);
}
}
}
}
/**
* Encapulates an operation and its inverse.
*
*/
public static interface UndoableCommand extends Runnable {
/**
* Execute this command
*/
@Override
void run();
/**
* Undo this command
*/
void undo();
}
}