From cbd7ff310ebecfd4e83fde2bc6fbee31faada2b2 Mon Sep 17 00:00:00 2001 From: Albert Cardona Date: Sat, 17 Jan 2009 00:23:44 -0500 Subject: [PATCH] Adding new undo system for transformations, still needs work. --- ini/trakem2/display/Display.java | 4 - ini/trakem2/display/DisplayCanvas.java | 29 ++++++- ini/trakem2/display/LayerSet.java | 150 ++++++++++++--------------------- ini/trakem2/display/Selection.java | 7 +- ini/trakem2/utils/History.java | 55 +++++++++--- 5 files changed, 122 insertions(+), 123 deletions(-) diff --git a/ini/trakem2/display/Display.java b/ini/trakem2/display/Display.java index c1c30732..824b5ec9 100644 --- a/ini/trakem2/display/Display.java +++ b/ini/trakem2/display/Display.java @@ -2677,10 +2677,6 @@ public final class Display extends DBObject implements ActionListener, ImageList } } else if (command.equals("Undo transforms")) { if (canvas.isTransforming()) return; - if (!layer.getParent().canRedo()) { - // catch the current - layer.getParent().appendCurrent(selection.getTransformationsCopy()); - } layer.getParent().undoOneStep(); } else if (command.equals("Redo transforms")) { if (canvas.isTransforming()) return; diff --git a/ini/trakem2/display/DisplayCanvas.java b/ini/trakem2/display/DisplayCanvas.java index a237c065..44feaf6e 100644 --- a/ini/trakem2/display/DisplayCanvas.java +++ b/ini/trakem2/display/DisplayCanvas.java @@ -75,6 +75,12 @@ public final class DisplayCanvas extends ImageCanvas implements KeyListener/*, F private boolean input_disabled = false; private boolean input_disabled2 = false; + private static final int NONE = 0; + private static final int MOUSE = 1; + private static final int KEY_MOVE = 2; + private static final int Z_KEY = 4; // the undo system + private int last_action = NONE; + /** Store a copy of whatever data as each Class may define it, one such data object per class. * Private to the package. */ static private Hashtable copy_buffer = new Hashtable(); @@ -665,7 +671,8 @@ public final class DisplayCanvas extends ImageCanvas implements KeyListener/*, F // store for undo: if (!selection.isEmpty() && ProjectToolbar.SELECT == tool && null == display.getLayer().getParent().getAlign() && !selection.isTransforming()) { - display.getLayer().getParent().addUndoStep(selection.getTransformationsCopy()); + // if there is one step, don't add it. + if (!display.getLayer().getParent().canUndo()) display.getLayer().getParent().addUndoStep(selection.getTransformationsCopy()); } if (ProjectToolbar.ALIGN == tool) { @@ -853,6 +860,7 @@ public final class DisplayCanvas extends ImageCanvas implements KeyListener/*, F } public void mouseReleased(MouseEvent me) { + last_action = MOUSE; boolean dragging2 = dragging; dragging = false; if (popup) { @@ -925,10 +933,15 @@ public final class DisplayCanvas extends ImageCanvas implements KeyListener/*, F flags &= ~InputEvent.BUTTON2_MASK; // make sure button 2 bit is not set flags &= ~InputEvent.BUTTON3_MASK; // make sure button 3 bit is not set + /* if (!dragging2) { // no real change display.getLayer().getParent().discardLastUndo(); } + */ + if (dragging2) { + display.getLayer().getParent().addUndoStep(display.getSelection().getTransformationsCopy()); + } int x_r = srcRect.x + (int)(me.getX() / magnification); int y_r = srcRect.y + (int)(me.getY() / magnification); @@ -1801,13 +1814,17 @@ public final class DisplayCanvas extends ImageCanvas implements KeyListener/*, F case KeyEvent.VK_Z: int mod = ke.getModifiers(); if (0 == (mod ^ Event.SHIFT_MASK)) { - if (!layer.getParent().canRedo()) { + // If it's the last step and the last action was not Z_KEY undo action, then store current: + if (!layer.getParent().canRedo() && Z_KEY != last_action) { // catch the current - display.getLayer().getParent().appendCurrent(display.getSelection().getTransformationsCopy()); + display.getLayer().getParent().addUndoStep(display.getSelection().getTransformationsCopy()); + Utils.log2("Storing current at key pressed."); } + last_action = Z_KEY; display.getLayer().getParent().undoOneStep(); ke.consume(); } else if (0 == (mod ^ Event.ALT_MASK)) { + last_action = Z_KEY; display.getLayer().getParent().redoOneStep(); ke.consume(); } @@ -1957,6 +1974,12 @@ public final class DisplayCanvas extends ImageCanvas implements KeyListener/*, F display.getSelection().deleteAll(); } break; + case KeyEvent.VK_UP: + case KeyEvent.VK_DOWN: + case KeyEvent.VK_LEFT: + case KeyEvent.VK_RIGHT: + last_action = KEY_MOVE; + // bleed to active: default: // forward event to active if (null != active) { diff --git a/ini/trakem2/display/LayerSet.java b/ini/trakem2/display/LayerSet.java index 31fd60cb..b431dea9 100644 --- a/ini/trakem2/display/LayerSet.java +++ b/ini/trakem2/display/LayerSet.java @@ -33,6 +33,7 @@ import ini.trakem2.Project; import ini.trakem2.persistence.DBObject; import ini.trakem2.utils.ProjectToolbar; import ini.trakem2.utils.Utils; +import ini.trakem2.utils.History; import ini.trakem2.utils.IJError; import ini.trakem2.imaging.LayerStack; import ini.trakem2.tree.LayerThing; @@ -49,6 +50,7 @@ import java.awt.Rectangle; import java.awt.geom.AffineTransform; import java.awt.geom.Area; import java.util.ArrayList; +import java.util.List; import java.util.HashMap; import java.util.Iterator; import java.util.HashSet; @@ -108,14 +110,29 @@ public final class LayerSet extends Displayable implements Bucketable { // Displ private boolean snapshots_quality = true; /** Store HashMaps of displayable/transformation pairs for undo. */ - private LinkedList undo_queue = new LinkedList(); - /** Store HashMaps of displayable/transformation pairs for redo, as they are popped out of the undo_queue list. This list will be cleared the moment a new action is stored in the undo_queue.*/ - //private LinkedList redo_queue = new LinkedList(); - /** The index of the current set of Transformations in the undo/redo queues. */ - private int current = 0; - /** A flag to indicate that the user is undoing/redoing without adding new undo steps. Gets reset to false when a new undo step is added. */ - private boolean cycle_flag = false; - private int MAX_UNDO_STEPS = 40; // should be editable, or rather, adaptable: count not the max steps but the max amount of memory used, computed by counting the number of AffineTransforms stored and the size of a single AffineTransform + private History history = new History(30); + + private class TransformationStep implements History.Step { + final HashMap ht; + TransformationStep(final HashMap ht) { + this.ht = ht; + } + public List remove(final long id) { + final List al = new ArrayList(); + for (Iterator it = ht.keySet().iterator(); it.hasNext(); ) { + final Displayable d = it.next(); + if (d.getId() == id) { + it.remove(); + } + al.add(d); + } + return al; + } + public boolean isEmpty() { + return ht.isEmpty(); + } + } + /** Tool to manually register using landmarks across two layers. Uses the toolbar's 'Align tool'. */ private Align align = null; @@ -435,11 +452,11 @@ public final class LayerSet extends Displayable implements Bucketable { // Displ d.updateInDatabase("transform"); } // translate all undo steps as well TODO need a better undo system, to call 'undo resize layerset', a system of undo actions or something - for (Iterator it = undo_queue.iterator(); it.hasNext(); ) { - HashMap ht = (HashMap)it.next(); - for (Iterator hi = ht.values().iterator(); hi.hasNext(); ) { - AffineTransform at = (AffineTransform)hi.next(); - at.preConcatenate(at2); + for (final History.Step step : history.getAll()) { + if (step instanceof TransformationStep) { + for (final AffineTransform at : ((TransformationStep)step).ht.values()) { + at.preConcatenate(at2); + } } } project.getLoader().commitLargeUpdate(); @@ -1112,92 +1129,43 @@ public final class LayerSet extends Displayable implements Bucketable { // Displ } /** The @param ht should be a hastable of Displayable keys and Transform values, such as those obtained from selection.getTransformationsCopy() . By adding a new undo step, the redo steps are cleared. */ - public void addUndoStep(HashMap ht) { + public void addUndoStep(final HashMap ht) { if (ht.isEmpty()) return; - if (undo_queue.size() == MAX_UNDO_STEPS) { - undo_queue.removeFirst(); - current--; - } - //Utils.log("addUndoStep A: current: " + current + " total: " + undo_queue.size()); - // clear undo_queue beyond current - while (undo_queue.size() > current) { - if (0 == undo_queue.size()) { - Utils.log2("attempted to remove from empty list: current is " + current); - break; - } - undo_queue.removeLast(); - } - // reset - cycle_flag = false; - undo_queue.add(ht); - current = undo_queue.size(); // current is not stored, is beyond bounds. What was just stored was the previous to current - //Utils.log("current: " + current + " total: " + undo_queue.size()); - /* - // discard redo steps - redo_queue.clear(); */ + Utils.log2("Add undo step"); + history.add(new TransformationStep(ht)); } /** Create an undo step involving all Displayable objects in the set. */ public void addUndoStep(final Set set) { - final HashMap ht = new HashMap(); + final HashMap ht = new HashMap(); for (final Displayable d : set) { ht.put(d, d.getAffineTransformCopy()); } addUndoStep(ht); } - /** Usable only when undoing the last step, to catch the current step (which is not in the undo queue).*/ - void appendCurrent(HashMap ht) { - if (ht.isEmpty() || undo_queue.size() != current || cycle_flag) return; - Utils.log2("appendCurrent: undo queue size: " + undo_queue.size() + " and current: " + current); - undo_queue.add(ht); - // current doesn't change, but now it exists in the undo_queue - } - public boolean canUndo() { - return current > 0; //0 != undo_queue.size(); + return history.canUndo(); } public boolean canRedo() { - return current < undo_queue.size(); //0 != redo_queue.size(); + return history.canRedo(); } + public void undoOneStep() { - if (current < 1 || 0 == undo_queue.size()) return; - if (cycle_flag && undo_queue.size() == current) current--; // compensate - current--; - if (current < 0) current = 0; // patching ... - HashMap step = (HashMap)undo_queue.get(current); - applyStep(step); - cycle_flag = true; - Utils.log2("undoing to current=" + current); - /* - HashMap last = (HashMap)undo_queue.removeLast(); - if (null != current) redo_queue.add(current); - current = last; - applyStep(last); - */ - //Utils.log2("undoing " + step); + History.Step step = history.undoOneStep(); + if (step instanceof TransformationStep) { + TransformationStep ts = (TransformationStep)step; + applyStep(ts.ht); + } + Utils.log2("Undoing: index is " + history.index()); } public void redoOneStep() { - //if (/*0 == redo_queue.size()*/ 0 == undo_queue.size() || current == undo_queue.size() -2) return; - current += 1; - if (current >= undo_queue.size()) { - Utils.log2("prevented redoing to current=" + current); - current = undo_queue.size(); - return; + History.Step step = history.redoOneStep(); + if (step instanceof TransformationStep) { + TransformationStep ts = (TransformationStep)step; + applyStep(ts.ht); } - HashMap step = (HashMap)undo_queue.get(current); - applyStep(step); - /* - HashMap next = (HashMap)redo_queue.removeLast(); - if (null != current) undo_queue.add(current); - current = next; - applyStep(next); - if (0 == redo_queue.size()) { - current = null; // reset - } - */ - //Utils.log2("redoing " + step); - Utils.log2("redoing to current=" + current); + Utils.log2("Redoing: index is " + history.index()); } private void applyStep(HashMap ht) { // apply: @@ -1227,24 +1195,13 @@ public final class LayerSet extends Displayable implements Bucketable { // Displ /** Find the given Displayable in the undo/redo queues and clear it. This functionality is used when an object is removed, for which there is no undo. */ public void removeFromUndo(final Displayable d) { - // from the undo_queue - for (Iterator it = undo_queue.iterator(); it.hasNext(); ) { - HashMap ht = (HashMap)it.next(); - for (Iterator itd = ht.keySet().iterator(); itd.hasNext(); ) { - if (d == itd.next()) { - itd.remove(); - break; // the inner loop only - } - } - } + history.remove(d.getId()); } /** Used when there has been no real transformation (for example, a mouse click and release, but no drag. */ void discardLastUndo() { - if (0 == undo_queue.size()) return; - //Utils.log2("discarding last undo!"); - undo_queue.removeLast(); - current--; + Utils.log2("discard last undo"); + history.removeLast(); } public void destroy() { @@ -1258,10 +1215,7 @@ public final class LayerSet extends Displayable implements Bucketable { // Displ } this.al_layers.clear(); this.al_zdispl.clear(); - this.undo_queue.clear(); - this.undo_queue = null; - //this.redo_queue.clear(); - //this.redo_queue = null; + this.history.clear(); if (null != align) { align.destroy(); align = null; diff --git a/ini/trakem2/display/Selection.java b/ini/trakem2/display/Selection.java index 3256b3f6..288f3c5f 100644 --- a/ini/trakem2/display/Selection.java +++ b/ini/trakem2/display/Selection.java @@ -1183,10 +1183,9 @@ public class Selection { } /** Returns a hash table with all selected Displayables as keys, and a copy of their affine transform as value. This is useful to easily create undo steps. */ - protected HashMap getTransformationsCopy() { - HashMap ht_copy = new HashMap(hs.size()); - for (Iterator it = hs.iterator(); it.hasNext(); ) { - Displayable d = (Displayable)it.next(); + protected HashMap getTransformationsCopy() { + final HashMap ht_copy = new HashMap(hs.size()); + for (final Displayable d : hs) { ht_copy.put(d, d.getAffineTransformCopy()); } return ht_copy; diff --git a/ini/trakem2/utils/History.java b/ini/trakem2/utils/History.java index 5c1d0aab..572856ac 100644 --- a/ini/trakem2/utils/History.java +++ b/ini/trakem2/utils/History.java @@ -7,6 +7,8 @@ import java.util.Iterator; /** 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 { @@ -24,6 +26,7 @@ public class History { /** 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 add(final Step step) { + Utils.printCaller(this, 3); ++index; if (list.size() == index) { list.add(step); @@ -37,8 +40,9 @@ public class History { /** Returns null if there aren't any more steps to undo. */ synchronized public Step undoOneStep() { - if (index < 1) return null; - return list.get(--index); + if (index < 0) return null; + // Return the current Step at index, then decrease index. + return list.get(index--); } /** Returns null if there aren't any more steps to redo. */ @@ -47,20 +51,23 @@ public class History { return list.get(++index); } - /** Remove all steps from the list that contain the given id, and return them. */ - synchronized public List remove(final long id) { - final List al = new ArrayList(); - for (Iterator it = list.iterator(); it.hasNext(); ) { - Step step = it.next(); - if (id == step.getId()) { - index--; - it.remove(); - al.add(step); - } + /** 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; } + /** Remove last step from the list, and return it, if any. */ + synchronized public Step removeLast() { + if (0 == list.size()) return null; + if (list.size() == (index + 1)) --index; + return list.remove(list.size()-1); + } + /** Resize to maximum the given size, removing from the beginning. Returns all removed elements, or an empty list if none. */ synchronized public List resize(final int size) { final List al = new ArrayList(); @@ -80,11 +87,31 @@ public class History { return al; } + /** Returns a list with all undo steps. */ + synchronized public List getAll() { + return new ArrayList(list); + } + synchronized public int size() { return list.size(); } - public interface Step { - public long getId(); + synchronized public int index() { + return index; + } + + synchronized public boolean canUndo() { + return index > -1; + } + + synchronized public boolean canRedo() { + return index < (list.size() -1); + } + + public interface Step { + /** Remove objects in this step that have the given id, + * and return a list of them. */ + public List remove(final long id); + public boolean isEmpty(); } } -- 2.11.4.GIT