package ini.trakem2.utils; import java.util.ArrayList; import java.util.List; /** A class to represent a generic undo/redo history. * Keeps a list of objects and the current index. * When adding, and the index not being at the last slot, the list is cleared from that point onward. * * All added objects must implement the History.Step interface. */ public class History { int index = -1; int max_size = -1; List<Step<?>> list = new ArrayList<Step<?>>(); /** New unlimited history list. */ public History() {} public History(final int max_size) { this.max_size = max_size; } /** Append a new step. If max_size is set, resizes the list if larger than max_size, * and returns all removed elements. Otherwise returns an empty list. */ synchronized public List<Step<?>> add(final Step<?> step) { //Utils.log2("adding one step: index= " + index); if (-1 == index) { if (list.size() > 0) list.clear(); } else { // Crop list: from start to index, inclusive list = list.subList(0, index+1); } // TODO above some steps may not be returned! // Check if step is identical to last step in queue if (list.size() > 0 && list.get(list.size()-1).isIdentical(step)) { //Utils.log2("History: skipping adding and identical undo step"); return new ArrayList<Step<?>>(); } ++index; list.add(step); //Utils.log2("Added step: index=" + index + " list.size=" + list.size()); if (-1 != max_size) return resize(max_size); return new ArrayList<Step<?>>(); } /** Appends a step at the end of the list, without modifying the current index. * If max_size is set, resizes the list if larger than max_size. */ synchronized public List<Step<?>> append(final Step<?> step) { if (list.size() > 0) { if (list.get(list.size()-1).isIdentical(step)) { //Utils.log2("History: skipping appending an identical undo step."); return new ArrayList<Step<?>>(); } } list.add(step); if (-1 != max_size) return resize(max_size); return new ArrayList<Step<?>>(); } synchronized public Step<?> getCurrent() { if (-1 == index) return null; return list.get(index); } /** Returns null if there aren't any more steps to undo. */ synchronized public Step<?> undoOneStep() { if (index < 0) return null; // Return the current Step at index, then decrease index. if (index > 0) index--; // cannot go beyond index 0, the first step return list.get(index); } /** Returns null if there aren't any more steps to redo. */ synchronized public Step<?> redoOneStep() { if (list.size() == (index +1)) return null; return list.get(++index); } /** Empty all elements from each Step in the list that match the given id, and return them. */ synchronized public List<?> remove(final long id) { final List al = new ArrayList(); for (final Step<?> step : list) { List<?> rm = step.remove(id); if (null != rm) al.addAll(rm); } return al; } /** Resize to maximum the given size, removing from the beginning. Returns all removed elements, or an empty list if none. */ synchronized public List<Step<?>> resize(final int size) { final List<Step<?>> al = new ArrayList<Step<?>>(); if (list.size() < size) return al; // else: // fix index final int cut = list.size() - size; if (index < cut) index = 0; else index -= cut; // cut list al.addAll(list.subList(0, cut)); list = list.subList(cut, list.size()); return al; } /** Remove all steps from the list and return them. */ synchronized public List<Step<?>> clear() { final ArrayList<Step<?>> al = new ArrayList<Step<?>>(); al.addAll(list); list.clear(); return al; } /** Returns a list with all undo steps. */ synchronized public List<Step<?>> getAll() { return new ArrayList<Step<?>>(list); } synchronized public Step<?> get(final int i) { if (i < 0 || i >= list.size()) return null; return list.get(i); } /** Cut the list after the index, leaving from 0 to index, inclusive, inside. * Returns removed steps. */ synchronized public List<Step<?>> clip() { final ArrayList<Step<?>> al = new ArrayList<Step<?>>(); if (indexAtEnd()) return al; al.addAll(list.subList(index+1, list.size())); list = list.subList(0, index+1); return al; } synchronized public int size() { return list.size(); } synchronized public int index() { return index; } synchronized public boolean indexAtStart() { return 0 == index; } synchronized public boolean indexAtEnd() { return (index + 1) == list.size(); } synchronized public boolean canUndo() { return index > -1; } synchronized public boolean canRedo() { return index < (list.size() -1); } public interface Step<T> { /** Remove objects in this step that have the given id, * and return a list of them. */ public List<T> remove(final long id); public boolean isEmpty(); public boolean isIdentical(final Step<?> step); } }