From ce3a9aff51e5c9ab3ede9ece1c7167a7ac51adee Mon Sep 17 00:00:00 2001 From: Albert Cardona Date: Thu, 12 Feb 2009 16:33:03 +0100 Subject: [PATCH] The undo madness advances. Can undo add/delete images now! --- ini/trakem2/display/Display.java | 101 +++++++++++++++++++------- ini/trakem2/display/Displayable.java | 12 ++- ini/trakem2/display/DoStep.java | 6 +- ini/trakem2/display/Layer.java | 121 +++++++++++++++++++++++++++++++ ini/trakem2/display/LayerSet.java | 133 ++++++++++++++++++++++++++++++++-- ini/trakem2/display/Selection.java | 9 ++- ini/trakem2/persistence/FSLoader.java | 9 ++- ini/trakem2/persistence/Loader.java | 3 + ini/trakem2/tree/DNDTree.java | 56 ++++++++++++++ ini/trakem2/tree/LayerThing.java | 14 ++++ ini/trakem2/tree/LayerTree.java | 4 + ini/trakem2/tree/ProjectThing.java | 32 ++++++++ ini/trakem2/tree/TemplateThing.java | 13 ++++ ini/trakem2/tree/Thing.java | 2 + ini/trakem2/utils/Bureaucrat.java | 23 +++++- ini/trakem2/utils/DNDInsertImage.java | 23 +++++- 16 files changed, 515 insertions(+), 46 deletions(-) diff --git a/ini/trakem2/display/Display.java b/ini/trakem2/display/Display.java index 74f6e80c..6a089965 100644 --- a/ini/trakem2/display/Display.java +++ b/ini/trakem2/display/Display.java @@ -833,28 +833,7 @@ public final class Display extends DBObject implements ActionListener, ImageList ((ZDisplayable)it.next()).setLayer(layer); // the active layer } - // update only the visible tab - switch (tabs.getSelectedIndex()) { - case 0: - ht_panels.clear(); - updateTab(panel_patches, "Patches", layer.getDisplayables(Patch.class)); - break; - case 1: - ht_panels.clear(); - updateTab(panel_profiles, "Profiles", layer.getDisplayables(Profile.class)); - break; - case 2: - if (set_zdispl) { - ht_panels.clear(); - updateTab(panel_zdispl, "Z-space objects", layer.getParent().getZDisplayables()); - } - break; - // case 3: channel opacities - case 4: - ht_panels.clear(); - updateTab(panel_labels, "Labels", layer.getDisplayables(DLabel.class)); - break; - } + updateVisibleTab(set_zdispl); // see if a lot has to be reloaded, put the relevant ones at the end project.getLoader().prepare(layer); @@ -907,6 +886,39 @@ public final class Display extends DBObject implements ActionListener, ImageList setTempCurrentImage(); } + static public void updateVisibleTabs() { + for (final Display d : al_displays) { + d.updateVisibleTab(true); + } + } + + /** Recreate the tab that is being shown. */ + public void updateVisibleTab(boolean set_zdispl) { + // update only the visible tab + switch (tabs.getSelectedIndex()) { + case 0: + ht_panels.clear(); + updateTab(panel_patches, "Patches", layer.getDisplayables(Patch.class)); + break; + case 1: + ht_panels.clear(); + updateTab(panel_profiles, "Profiles", layer.getDisplayables(Profile.class)); + break; + case 2: + if (set_zdispl) { + ht_panels.clear(); + updateTab(panel_zdispl, "Z-space objects", layer.getParent().getZDisplayables()); + } + break; + // case 3: channel opacities + case 4: + ht_panels.clear(); + updateTab(panel_labels, "Labels", layer.getDisplayables(DLabel.class)); + break; + } + + } + private void setLayerLater(final Layer layer, final Displayable active) { if (null == layer) return; this.layer = layer; @@ -2723,15 +2735,35 @@ public final class Display extends DBObject implements ActionListener, ImageList } else if (command.equals("Import next image")) { importNextImage(); } else if (command.equals("Import stack...")) { - project.getLoader().importStack(layer, null, true); + Display.this.getLayerSet().addLayerContentStep(layer); + Bureaucrat burro = project.getLoader().importStack(layer, null, true); + burro.addPostTask(new Runnable() { public void run() { + Display.this.getLayerSet().addLayerContentStep(layer); + }}); } else if (command.equals("Import grid...")) { - project.getLoader().importGrid(layer); + Display.this.getLayerSet().addLayerContentStep(layer); + Bureaucrat burro = project.getLoader().importGrid(layer); + burro.addPostTask(new Runnable() { public void run() { + Display.this.getLayerSet().addLayerContentStep(layer); + }}); } else if (command.equals("Import sequence as grid...")) { - project.getLoader().importSequenceAsGrid(layer); + Display.this.getLayerSet().addLayerContentStep(layer); + Bureaucrat burro = project.getLoader().importSequenceAsGrid(layer); + burro.addPostTask(new Runnable() { public void run() { + Display.this.getLayerSet().addLayerContentStep(layer); + }}); } else if (command.equals("Import from text file...")) { - project.getLoader().importImages(layer); + Display.this.getLayerSet().addLayerContentStep(layer); + Bureaucrat burro = project.getLoader().importImages(layer); + burro.addPostTask(new Runnable() { public void run() { + Display.this.getLayerSet().addLayerContentStep(layer); + }}); } else if (command.equals("Import labels as arealists...")) { - project.getLoader().importLabelsAsAreaLists(layer, null, Double.MAX_VALUE, 0, 0.4f, false); + Display.this.getLayerSet().addChangeTreesStep(); + Bureaucrat burro = project.getLoader().importLabelsAsAreaLists(layer, null, Double.MAX_VALUE, 0, 0.4f, false); + burro.addPostTask(new Runnable() { public void run() { + Display.this.getLayerSet().addChangeTreesStep(); + }}); } else if (command.equals("Make flat image...")) { // if there's a ROI, just use that as cropping rectangle Rectangle srcRect = null; @@ -3184,8 +3216,13 @@ public final class Display extends DBObject implements ActionListener, ImageList Utils.showMessage("Could not open the image."); return; } + + Display.this.getLayerSet().addLayerContentStep(layer); + layer.add(p); // will add it to the proper Displays + Display.this.getLayerSet().addLayerContentStep(layer); + /// } catch (Exception e) { IJError.print(e); @@ -3211,8 +3248,13 @@ public final class Display extends DBObject implements ActionListener, ImageList finishedWorking(); return; } + + Display.this.getLayerSet().addLayerContentStep(layer); + layer.add(p); // will add it to the proper Displays + Display.this.getLayerSet().addLayerContentStep(layer); + } catch (Exception e) { IJError.print(e); } @@ -3514,6 +3556,11 @@ public final class Display extends DBObject implements ActionListener, ImageList if (d.layer == layer) d.selection.clear(); } } + static public void clearSelection() { + for (final Display d : al_displays) { + d.selection.clear(); + } + } private void setTempCurrentImage() { WindowManager.setTempCurrentImage(canvas.getFakeImagePlus()); diff --git a/ini/trakem2/display/Displayable.java b/ini/trakem2/display/Displayable.java index 7b588077..40678ec7 100644 --- a/ini/trakem2/display/Displayable.java +++ b/ini/trakem2/display/Displayable.java @@ -1646,7 +1646,7 @@ public abstract class Displayable extends DBObject { return ob; } /** Set the stored data to the stored Displayable. */ - public boolean apply() { + public boolean apply(int action) { final Class[] c = new Class[]{Displayable.class, d.getClass(), ZDisplayable.class}; for (final Map.Entry e : content.entrySet()) { String field = e.getKey(); @@ -1672,7 +1672,7 @@ public abstract class Displayable extends DBObject { boolean ok = true; if (null != dependents) { for (final DoStep step : dependents) { - if (!step.apply()) ok = false; + if (!step.apply(action)) ok = false; } } return ok; @@ -1684,21 +1684,27 @@ public abstract class Displayable extends DBObject { protected class DoTransforms implements DoStep { final private HashMap ht = new HashMap(); + final HashSet layers = new HashSet(); DoTransforms addAll(final Collection col) { for (final Displayable d : col) { ht.put(d, d.getAffineTransformCopy()); + layers.add(d.getLayer()); } return this; } public boolean isEmpty() { return null == ht || ht.isEmpty(); } - public boolean apply() { + public boolean apply(int action) { if (isEmpty()) return false; for (final Map.Entry e : ht.entrySet()) { e.getKey().at.setTransform(e.getValue()); } + for (final Layer layer : layers) { + layer.recreateBuckets(); + } + if (!layers.isEmpty()) layers.iterator().next().getParent().recreateBuckets(false); return true; } public Displayable getD() { return null; } diff --git a/ini/trakem2/display/DoStep.java b/ini/trakem2/display/DoStep.java index cceaf844..ff4064c0 100644 --- a/ini/trakem2/display/DoStep.java +++ b/ini/trakem2/display/DoStep.java @@ -1,8 +1,12 @@ package ini.trakem2.display; public interface DoStep { + + static public final int UNDO = 0; + static public final int REDO = 1; + /** Returns true on success. */ - public boolean apply(); + public boolean apply(int action); public boolean isEmpty(); diff --git a/ini/trakem2/display/Layer.java b/ini/trakem2/display/Layer.java index 220588cb..a1fde127 100644 --- a/ini/trakem2/display/Layer.java +++ b/ini/trakem2/display/Layer.java @@ -34,6 +34,7 @@ import ini.trakem2.utils.IJError; import ini.trakem2.utils.Utils; import java.util.ArrayList; +import java.util.List; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; @@ -865,4 +866,124 @@ public final class Layer extends DBObject implements Bucketable { this.use_buckets = b; if (!use_buckets) this.root = null; } + + static class DoEditLayer implements DoStep { + final double z, thickness; + final Layer la; + DoEditLayer(final Layer layer) { + this.la = layer; + this.z = layer.z; + this.thickness = layer.thickness; + } + public Displayable getD() { return null; } + public boolean isEmpty() { return false; } + public boolean isIdenticalTo(final Object ob) { + if (!(ob instanceof Layer)) return false; + final Layer layer = (Layer) ob; + return this.la.id == layer.id && this.z == layer.z && this.thickness == layer.thickness; + } + public boolean apply(int action) { + la.z = this.z; + la.thickness = this.thickness; + la.getProject().getLayerTree().updateUILater(); + Display.update(la.getParent()); + return true; + } + } + + static class DoEditLayers implements DoStep { + final ArrayList all = new ArrayList(); + DoEditLayers(final List all) { + for (final Layer la : all) { + this.all.add(new DoEditLayer(la)); + } + } + public Displayable getD() { return null; } + public boolean isEmpty() { return all.isEmpty(); } + public boolean isIdenticalTo(final Object ob) { + if (!(ob instanceof DoEditLayers)) return false; + final DoEditLayers other = (DoEditLayers) ob; + if (all.size() != other.all.size()) return false; + // Order matters: + final Iterator it1 = all.iterator(); + final Iterator it2 = other.all.iterator(); + for (; it1.hasNext() && it2.hasNext(); ) { + if (!it1.next().isIdenticalTo(it2.next())) return false; + } + return true; + } + public boolean apply(int action) { + for (final DoEditLayer one : all) { + if (!one.apply(action)) { + return false; + } + } + return true; + } + } + + /** Add an object that is Layer bound, such as Profile, Patch, DLabel and LayerSet. */ + static class DoContentChange implements DoStep { + final Layer la; + final ArrayList al; + DoContentChange(final Layer la) { + this.la = la; + this.al = la.getDisplayables(); // a copy + } + public Displayable getD() { return null; } + public boolean isEmpty() { return false; } + /** Check that the Displayable objects of this layer are the same and in the same quantity and order. */ + public boolean isIdenticalTo(Object ob) { + if (!(ob instanceof DoContentChange)) return false; + final DoContentChange dad = (DoContentChange) ob; + if (la != dad.la || al.size() != dad.al.size()) return false; + // Order matters: + final Iterator it1 = al.iterator(); + final Iterator it2 = dad.al.iterator(); + for (; it1.hasNext() && it2.hasNext(); ) { + if (it1.next() != it2.next()) return false; + } + return true; + } + public boolean apply(final int action) { + // find the subset in la.al_displayables that is not in this.al + final HashSet sub1 = new HashSet(la.al_displayables); + sub1.removeAll(this.al); + // find the subset in this.al that is not in la.al_displayables + final HashSet sub2 = new HashSet(this.al); + sub2.removeAll(la.al_displayables); + + HashSet subA=null, subB=null; + + if (action == DoStep.UNDO) { + subA = sub1; + subB = sub2; + } else if (action == DoStep.REDO) { + subA = sub2; + subB = sub1; + } + if (null != subA && null != subB) { + // Mark Patch for mipmap file removal + for (final Displayable d: subA) { + if (d.getClass() == Patch.class) { + d.getProject().getLoader().queueForMipmapRemoval((Patch)d, true); + } + } + // ... or unmark: + for (final Displayable d: subB) { + if (d.getClass() == Patch.class) { + d.getProject().getLoader().queueForMipmapRemoval((Patch)d, false); + } + } + } + + la.al_displayables.clear(); + la.al_displayables.addAll(this.al); + la.recreateBuckets(); + Display.updateVisibleTabs(); + Display.clearSelection(); + Display.update(la); + return true; + } + } } diff --git a/ini/trakem2/display/LayerSet.java b/ini/trakem2/display/LayerSet.java index a80542ea..7c860c98 100644 --- a/ini/trakem2/display/LayerSet.java +++ b/ini/trakem2/display/LayerSet.java @@ -36,6 +36,7 @@ import ini.trakem2.utils.Utils; import ini.trakem2.utils.IJError; import ini.trakem2.imaging.LayerStack; import ini.trakem2.tree.LayerThing; +import ini.trakem2.tree.Thing; import java.awt.AlphaComposite; import java.awt.Color; @@ -1588,9 +1589,16 @@ public final class LayerSet extends Displayable implements Bucketable { // Displ return false; } - // should check if it's identical to the last added one. synchronized (edit_history) { + // 0 - Check if it's identical to the last added one. + if (edit_history.size() > 0) { + final DoStep last = edit_history.get(edit_history.lastKey()); + if (last.getClass() == step.getClass() && last.isIdenticalTo(step)) { + return false; + } + } + final long time = System.currentTimeMillis(); // 1- Store for speedy access, if its Displayable-specific: @@ -1635,6 +1643,31 @@ public final class LayerSet extends Displayable implements Bucketable { // Displ addEditStep(dt); } + /** Add a step to undo the addition or deletion of one or more objects in this project and LayerSet. */ + public void addChangeTreesStep() { + DoStep step = new LayerSet.DoChangeTrees(this); + if (prepareStep(step)) { + Utils.log2("Added change trees step."); + addEditStep(step); + } + } + /** For the Displayable contained in a Layer: their number, and their stack order. */ + public void addLayerContentStep(final Layer la) { + DoStep step = new Layer.DoContentChange(la); + if (prepareStep(step)) { + Utils.log2("Added layer content step."); + addEditStep(step); + } + } + /** For the Z and thickness of a layer. */ + public void addLayerEditedStep(final Layer layer) { + addEditStep(new Layer.DoEditLayer(layer)); + } + /** For the Z and thickness of a list of layers. */ + public void addLayerEditedStep(final List al) { + addEditStep(new Layer.DoEditLayers(al)); + } + public boolean canUndo() { return edit_history.size() > 1; // the first step is the baseline } @@ -1663,11 +1696,11 @@ public final class LayerSet extends Displayable implements Bucketable { // Displ synchronized (edit_history) { if (1 == edit_history.size()) return false; - Utils.log2("Undoing one step"); + //Utils.log2("Undoing one step"); final DoStep step = pickLastStep(); - if (!step.apply()) { + if (!step.apply(DoStep.UNDO)) { Utils.log("Undo: could not apply step!"); return false; } @@ -1680,13 +1713,13 @@ public final class LayerSet extends Displayable implements Bucketable { // Displ synchronized (edit_history) { if (0 == redo.size()) return false; - Utils.log2("Redoing one step"); + //Utils.log2("Redoing one step"); // Remove from undo queue: final long time = redo.firstKey(); final DoStep step = redo.remove(time); - if (!step.apply()) { + if (!step.apply(DoStep.REDO)) { Utils.log("Undo: could not apply step!"); return false; } @@ -1752,7 +1785,7 @@ public final class LayerSet extends Displayable implements Bucketable { // Displ return true; } - public boolean apply() { + public boolean apply(int action) { ls.layer_width = width; ls.layer_height = height; for (final Map.Entry e : affines.entrySet()) { @@ -1766,4 +1799,92 @@ public final class LayerSet extends Displayable implements Bucketable { // Displ public boolean isEmpty() { return false; } public Displayable getD() { return null; } } + + /** Records the state of the LayerSet.al_layers, each Layer.al_displayables and all the trees and unique types of Project. */ + static private class DoChangeTrees implements DoStep { + final LayerSet ls; + final HashMap ttree_exp, ptree_exp, ltree_exp; + final Thing troot, proot, lroot; + final ArrayList all_layers; + final HashMap> all_displ; + final ArrayList all_zdispl; + final HashMap> links; + + // TODO: does not consider recursive LayerSets! + public DoChangeTrees(final LayerSet ls) { + this.ls = ls; + final Project p = ls.getProject(); + + this.ttree_exp = new HashMap(); + this.troot = p.getTemplateTree().duplicate(ttree_exp); + this.ptree_exp = new HashMap(); + this.proot = p.getProjectTree().duplicate(ptree_exp); + this.ltree_exp = new HashMap(); + this.lroot = p.getProjectTree().duplicate(ltree_exp); + + this.all_layers = ls.getLayers(); // a copy + this.all_zdispl = ls.getZDisplayables(); // a copy + + this.links = new HashMap>(); + for (final ZDisplayable zd : this.all_zdispl) { + this.links.put(zd, zd.hs_linked); // LayerSet is a Displayable + } + + this.all_displ = new HashMap>(); + for (final Layer layer : all_layers) { + final ArrayList al = layer.getDisplayables(); // a copy + this.all_displ.put(layer, al); + for (final Displayable d : al) { + this.links.put(d, new HashSet(d.hs_linked)); + } + } + } + public Displayable getD() { return null; } + public boolean isEmpty() { return false; } + public boolean isIdenticalTo(final Object ob) { + // TODO + return false; + } + public boolean apply(int action) { + // Replace all layers + ls.al_layers.clear(); + ls.al_layers.addAll(this.all_layers); + + // Replace all Displayable in each Layer + for (final Map.Entry> e : all_displ.entrySet()) { + final ArrayList al = e.getKey().getDisplayableList(); // the real one! + al.clear(); + al.addAll(e.getValue()); + } + + // Replace all ZDisplayable + ls.al_zdispl.clear(); + ls.al_zdispl.addAll(this.all_zdispl); + + // Replace all trees + final Project p = ls.getProject(); + p.getTemplateTree().set(this.troot, this.ttree_exp); + p.getProjectTree().set(this.proot, this.ptree_exp); + p.getLayerTree().set(this.lroot, this.ltree_exp); + + // Replace all links + for (final Map.Entry> e : this.links.entrySet()) { + final Set hs = e.getKey().hs_linked; + if (null != hs) { + final Set hs2 = e.getValue(); + if (null == hs2) e.getKey().hs_linked = null; + else { + hs.clear(); + hs.addAll(hs2); + } + } + } + + ls.recreateBuckets(true); + + Display.update(ls); + + return true; + } + } } diff --git a/ini/trakem2/display/Selection.java b/ini/trakem2/display/Selection.java index 0a0d6ec5..60eddbe4 100644 --- a/ini/trakem2/display/Selection.java +++ b/ini/trakem2/display/Selection.java @@ -714,10 +714,12 @@ public class Selection { // Remove starting with higher stack index numbers: Collections.reverse(al_d); + if (null != display) display.getLayerSet().addChangeTreesStep(); + // remove one by one, skip those that fail and log the error StringBuffer sb = new StringBuffer(); try { - display.getProject().getLoader().startLargeUpdate(); + if (null != display) display.getProject().getLoader().startLargeUpdate(); for (final Displayable d : al_d) { // Remove from the trees and from the Layer/LayerSet if (!d.remove2(false)) { @@ -728,13 +730,16 @@ public class Selection { } catch (Exception e) { IJError.print(e); } finally { - display.getProject().getLoader().commitLargeUpdate(); + if (null != display) display.getProject().getLoader().commitLargeUpdate(); } if (sb.length() > 0) { Utils.log("Could NOT delete:\n" + sb.toString()); } //Display.repaint(display.getLayer(), box, 0); Display.updateSelection(); // from all displays + + if (null != display) display.getLayerSet().addChangeTreesStep(); + return true; } diff --git a/ini/trakem2/persistence/FSLoader.java b/ini/trakem2/persistence/FSLoader.java index 8b2ae74e..c5ff15cc 100644 --- a/ini/trakem2/persistence/FSLoader.java +++ b/ini/trakem2/persistence/FSLoader.java @@ -97,7 +97,7 @@ public final class FSLoader extends Loader { /** Queue and execute Runnable tasks. */ static private Dispatcher dispatcher = new Dispatcher(); - private Vector touched_mipmaps = new Vector(); + private Set touched_mipmaps = Collections.synchronizedSet(new HashSet()); /** Used to open a project from an existing XML file. */ public FSLoader() { @@ -1450,7 +1450,12 @@ public final class FSLoader extends Loader { return (byte[])source.convertToByte(false).getPixels(); } - + /** Queue/unqueue for mipmap removal on shutdown without saving. */ + public void queueForMipmapRemoval(final Patch p, boolean yes) { + if (yes) touched_mipmaps.add(p); + else touched_mipmaps.remove(p); + } + /** Given an image and its source file name (without directory prepended), generate * a pyramid of images until reaching an image not smaller than 32x32 pixels.
* Such images are stored as jpeg 85% quality in a folder named trakem2.mipmaps.
diff --git a/ini/trakem2/persistence/Loader.java b/ini/trakem2/persistence/Loader.java index b5cf24ea..7a495287 100644 --- a/ini/trakem2/persistence/Loader.java +++ b/ini/trakem2/persistence/Loader.java @@ -4954,4 +4954,7 @@ abstract public class Loader { } return pix; } + + /** Does nothing unless overriden. */ + public void queueForMipmapRemoval(final Patch p, boolean yes) {} } diff --git a/ini/trakem2/tree/DNDTree.java b/ini/trakem2/tree/DNDTree.java index 12acd561..f715dc7a 100644 --- a/ini/trakem2/tree/DNDTree.java +++ b/ini/trakem2/tree/DNDTree.java @@ -462,6 +462,9 @@ public class DNDTree extends JTree implements TreeExpansionListener { public void setExpandedSilently(final Thing thing, final boolean b) { DefaultMutableTreeNode node = findNode(thing, this); if (null == node) return; + setExpandedSilently(node, b); + } + public void setExpandedSilently(final DefaultMutableTreeNode node, final boolean b) { try { java.lang.reflect.Field f = JTree.class.getDeclaredField("expandedState"); f.setAccessible(true); @@ -476,6 +479,10 @@ public class DNDTree extends JTree implements TreeExpansionListener { public boolean isExpanded(final Thing thing) { DefaultMutableTreeNode node = findNode(thing, this); if (null == node) return false; + return isExpanded(node); + } + + public boolean isExpanded(final DefaultMutableTreeNode node) { try { java.lang.reflect.Field f = JTree.class.getDeclaredField("expandedState"); f.setAccessible(true); @@ -576,4 +583,53 @@ public class DNDTree extends JTree implements TreeExpansionListener { this.updateUILater(); return true; } + + /** Shallow copy of the tree: returns a clone of the root node and cloned children, recursively, with all Thing cloned as well, but the Thing object is the same. */ + public Thing duplicate(final HashMap expanded_state) { + DefaultMutableTreeNode root_node = (DefaultMutableTreeNode) this.getModel().getRoot(); + Thing root = (Thing) root_node.getUserObject(); + // Descend both the root_copy tree and the root_node tree, and build shallow copies of Thing with same expanded state + return duplicate(root_node, expanded_state); + } + + /** Returns the copy of the node's Thing. */ + private Thing duplicate(final DefaultMutableTreeNode node, final HashMap expanded_state) { + Thing thing = (Thing) node.getUserObject(); + Thing copy = thing.shallowCopy(); + if (null != expanded_state) { + expanded_state.put(copy, isExpanded(node)); + } + final Enumeration e = node.children(); + while (e.hasMoreElements()) { + DefaultMutableTreeNode child = (DefaultMutableTreeNode) e.nextElement(); + copy.addChild(duplicate(child, expanded_state)); + } + return copy; + } + + /** For restoring purposes from an undo step. */ + public void set(final Thing root, final HashMap expanded_state) { + // rebuild all nodes, restore their expansion state. + DefaultMutableTreeNode root_node = (DefaultMutableTreeNode) this.getModel().getRoot(); + root_node.removeAllChildren(); + set(root_node, root, expanded_state); + updateUILater(); + } + + /** Recursive */ + private void set(final DefaultMutableTreeNode root, final Thing root_thing, final HashMap expanded_state) { + root.setUserObject(root_thing); + final ArrayList al_children = root_thing.getChildren(); + if (null != al_children) { + for (final Thing thing : al_children) { + DefaultMutableTreeNode child = new DefaultMutableTreeNode(thing); + root.add(child); + set(child, thing, expanded_state); + } + } + if (null != expanded_state) { + final Boolean b = expanded_state.get(root_thing); + if (null != b) setExpandedSilently(root, b.booleanValue()); + } + } } diff --git a/ini/trakem2/tree/LayerThing.java b/ini/trakem2/tree/LayerThing.java index 389844d5..a3496924 100644 --- a/ini/trakem2/tree/LayerThing.java +++ b/ini/trakem2/tree/LayerThing.java @@ -51,6 +51,20 @@ public final class LayerThing extends DBObject implements Thing { // TODO : attributes, at all? private String title = null; + /** A new copy with same template, same project, same id, same object and cloned table of same attributes, and same title, but no parent and no children. */ + public Thing shallowCopy() { + return new LayerThing(this); + } + + /** For shallow copying purposes. */ + private LayerThing(final LayerThing lt) { + super(lt.project, lt.id); + this.title = lt.title; + this.object = lt.object; + if (null != lt.ht_attributes) { + this.ht_attributes = (HashMap) lt.ht_attributes.clone(); + } + } public LayerThing(TemplateThing template, Project project, Object ob) throws Exception { // call super constructor diff --git a/ini/trakem2/tree/LayerTree.java b/ini/trakem2/tree/LayerTree.java index ce6ee694..11be491b 100644 --- a/ini/trakem2/tree/LayerTree.java +++ b/ini/trakem2/tree/LayerTree.java @@ -192,6 +192,9 @@ public final class LayerTree extends DNDTree implements MouseListener, ActionLis return; } } + final ArrayList al = new ArrayList(); + for (int i=0; i(al_children.size()); + for (final ProjectThing child : this.al_children) { + copy.al_children.add(child.shallowCopy(copy)); + } + } + return copy; + } + */ + + /** For shallow copying purposes. */ + private ProjectThing(final ProjectThing pt) { + super(pt.project, pt.id); + this.object = pt.object; + if (null != pt.ht_attributes) { + this.ht_attributes = (HashMap) pt.ht_attributes.clone(); + } + } + /** Create a new ProjectThing of the given type to contain the given Object. The object cannot be null. */ public ProjectThing(final TemplateThing template, Project project, Object ob) throws Exception { // call super constructor diff --git a/ini/trakem2/tree/TemplateThing.java b/ini/trakem2/tree/TemplateThing.java index 8330dcff..51d11dec 100644 --- a/ini/trakem2/tree/TemplateThing.java +++ b/ini/trakem2/tree/TemplateThing.java @@ -42,6 +42,19 @@ public final class TemplateThing extends DBObject implements Thing { /** The string or numeric value, if any, contained in the XML file between the opening and closing tags. */ private String value = null; + /** A new copy with same type, same project, same id, but no parent and no children. */ + public Thing shallowCopy() { + return new TemplateThing(this); + } + + private TemplateThing(final TemplateThing tt) { + super(tt.project, tt.id); + this.type = tt.type; + if (null != tt.ht_attributes) { + this.ht_attributes = (HashMap) tt.ht_attributes.clone(); + } + } + /** Create a new non-database-stored TemplateThing. */ public TemplateThing(String type) { super(null, -1); diff --git a/ini/trakem2/tree/Thing.java b/ini/trakem2/tree/Thing.java index 6a01afa1..bb50586d 100644 --- a/ini/trakem2/tree/Thing.java +++ b/ini/trakem2/tree/Thing.java @@ -56,4 +56,6 @@ public interface Thing { public boolean isExpanded(); public String getInfo(); + + public Thing shallowCopy(); } diff --git a/ini/trakem2/utils/Bureaucrat.java b/ini/trakem2/utils/Bureaucrat.java index 04b40c1e..12621ee2 100644 --- a/ini/trakem2/utils/Bureaucrat.java +++ b/ini/trakem2/utils/Bureaucrat.java @@ -22,11 +22,11 @@ Institute of Neuroinformatics, University of Zurich / ETH, Switzerland. package ini.trakem2.utils; -import ij.IJ; import ini.trakem2.ControlWindow; import ini.trakem2.Project; import ini.trakem2.persistence.Loader; import ini.trakem2.utils.Utils; +import java.util.ArrayList; /** Sets a Worker thread to work, and waits until it finishes, blocking all user interface input until then, except for zoom and pan, for all given projects. */ public class Bureaucrat extends Thread { @@ -35,6 +35,8 @@ public class Bureaucrat extends Thread { private long onset; private Project[] project; private boolean started = false; + /** A list of tasks to run when the Worker finishes of quits. */ + private final ArrayList post_tasks = new ArrayList(); /** Registers itself in the project loader job queue. */ private Bureaucrat(ThreadGroup tg, Worker worker, Project project) { @@ -131,6 +133,17 @@ public class Bureaucrat extends Thread { } ControlWindow.endWaitingCursor(); Utils.showStatus("Done " + worker.getTaskName(), !worker.onBackground()); + try { + for (final Runnable r : post_tasks) { + try { + r.run(); + } catch (Throwable t) { + IJError.print(t); + } + } + } catch(Throwable t) { + IJError.print(t); + } cleanup(); } /** Returns the task the worker is currently executing, which may change over time. */ @@ -174,4 +187,12 @@ public class Bureaucrat extends Thread { public Worker getWorker() { return worker; } + /** Add a task to run after the Worker has finished or quit. Does not accept more tasks once the Worker no longer runs. */ + public boolean addPostTask(final Runnable task) { + if (worker.hasQuitted()) return false; + synchronized (post_tasks) { + this.post_tasks.add(task); + } + return true; + } } diff --git a/ini/trakem2/utils/DNDInsertImage.java b/ini/trakem2/utils/DNDInsertImage.java index b81dab02..9f8e6602 100644 --- a/ini/trakem2/utils/DNDInsertImage.java +++ b/ini/trakem2/utils/DNDInsertImage.java @@ -116,6 +116,10 @@ public class DNDInsertImage implements DropTargetListener { private boolean importImageFile(File f, String path, Point point) throws Exception { if (f.exists()) { + + final Layer layer = display.getLayer(); + Bureaucrat burro = null; + if (f.isDirectory()) { // ask: GenericDialog gd = new GenericDialog("Import directory"); @@ -125,6 +129,9 @@ public class DNDInsertImage implements DropTargetListener { if (gd.wasCanceled()) { return true; // the user cancel it, so all is ok. } + + display.getLayerSet().addLayerContentStep(layer); + switch (gd.getNextChoiceIndex()) { case 0: // as stack // if importing image sequence as a stack: @@ -149,20 +156,28 @@ public class DNDInsertImage implements DropTargetListener { stack.addSlice(names[k]); } if (stack.getSize() > 0) { - display.getProject().getLoader().importStack(display.getLayer(), new ImagePlus("stack", stack), true, path); + burro = display.getProject().getLoader().importStack(layer, new ImagePlus("stack", stack), true, path); } break; case 1: // as grid - display.getProject().getLoader().importGrid(display.getLayer(), path); + burro = display.getProject().getLoader().importGrid(layer, path); break; case 2: // sequence as grid - display.getProject().getLoader().importSequenceAsGrid(display.getLayer(), path); + burro = display.getProject().getLoader().importSequenceAsGrid(layer, path); break; } } else { + layer.getParent().addLayerContentStep(layer); + // single image file (single image or a stack) - display.getProject().getLoader().importImage(display.getLayer(), point.x, point.y, path); + burro = display.getProject().getLoader().importImage(layer, point.x, point.y, path); } + + burro.addPostTask(new Runnable() { public void run() { + // The current state + layer.getParent().addLayerContentStep(layer); + }}); + return true; } else { Utils.log("File not found: " + path); -- 2.11.4.GIT