/**
TrakEM2 plugin for ImageJ(C).
Copyright (C) 2005-2009 Albert Cardona and Rodney Douglas.
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation (http://www.gnu.org/licenses/gpl.txt )
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
You may contact Albert Cardona at acardona at ini.phys.ethz.ch
Institute of Neuroinformatics, University of Zurich / ETH, Switzerland.
**/
package ini.trakem2.display;
import ij.ImagePlus;
import ij.ImageStack;
import ij.gui.GenericDialog;
import ij.measure.Calibration;
import ini.trakem2.ControlWindow;
import ini.trakem2.Project;
import ini.trakem2.imaging.LayerStack;
import ini.trakem2.parallel.Process;
import ini.trakem2.parallel.TaskFactory;
import ini.trakem2.persistence.DBObject;
import ini.trakem2.persistence.XMLOptions;
import ini.trakem2.tree.LayerThing;
import ini.trakem2.tree.ProjectThing;
import ini.trakem2.tree.TemplateThing;
import ini.trakem2.tree.Thing;
import ini.trakem2.utils.IJError;
import ini.trakem2.utils.ProjectToolbar;
import ini.trakem2.utils.Utils;
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Composite;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.awt.geom.AffineTransform;
import java.awt.geom.Area;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.helpers.DefaultHandler;
/** A LayerSet is a container for a list of Layer.
* LayerSet methods are NOT synchronized. It is your reponsibility to synchronize access to a LayerSet instance methods. Failure to do so may result in corrupted internal data structures and overall misbehavior.
*/
public final class LayerSet extends Displayable implements Bucketable { // Displayable is already extending DBObject
// the anchors for resizing
static public final int NORTH = 0;
static public final int NORTHEAST = 1;
static public final int EAST = 2;
static public final int SOUTHEAST = 3;
static public final int SOUTH = 4;
static public final int SOUTHWEST = 5;
static public final int WEST = 6;
static public final int NORTHWEST = 7;
static public final int CENTER = 8;
// the possible rotations
static public final int R90 = 9;
static public final int R270 = 10;
// the possible flips
static public final int FLIP_HORIZONTAL = 11;
static public final int FLIP_VERTICAL = 12;
// positions in the stack
static public final int TOP = 13;
static public final int UP = 14;
static public final int DOWN = 15;
static public final int BOTTOM = 16;
static public final String[] snapshot_modes = new String[]{"Full","Outlines","Disabled"};
/** 0, 1, 2 -- corresponding to snapshot_modes entries above. */
private int snapshots_mode = 0;
static public final String[] ANCHORS = new String[]{"north", "north east", "east", "southeast", "south", "south west", "west", "north west", "center"};
static public final String[] ROTATIONS = new String[]{"90 right", "90 left", "Flip horizontally", "Flip vertically"};
private float layer_width = 5000, // the Displayable.width is for the representation, not for the dimensions of the LayerSet!
layer_height = 5000;
private double rot_x;
private double rot_y;
private double rot_z; // should be equivalent to the Displayable.rot
private final ArrayList<Layer> al_layers = new ArrayList<Layer>();
/** A map of Long vs Layer, that is lock-free for reading, but locks for modifying it,
* by synchronizing onto IDLAYERS_WRITE_LOCK. */
private HashMap<Long,Layer> idlayers = new HashMap<Long,Layer>();
private final Object IDLAYERS_WRITE_LOCK = new Object();
private final HashMap<Layer,Integer> layerindices = new HashMap<Layer,Integer>();
/** The layer in which this LayerSet lives. If null, this is the root LayerSet. */
private Layer parent = null;
/** A LayerSet can contain Displayables that are show in every single Layer, such as Pipe objects. */
private final ArrayList<ZDisplayable> al_zdispl = new ArrayList<ZDisplayable>();
/** For creating snapshots. */
private boolean snapshots_quality = true;
/** The maximum size of either width or height when virtualizing pixel access to the layers.*/
private int max_dimension = 1024;
private boolean virtualization_enabled = false;
protected boolean color_cues = true;
protected boolean area_color_cues = true;
protected boolean use_color_cue_colors = true;
protected boolean paint_arrows = true;
protected boolean paint_tags = true;
protected boolean paint_edge_confidence_boxes = true;
protected int n_layers_color_cue = 0; // -1 means all
protected boolean prepaint = false;
protected int preload_ahead = 0;
private Calibration calibration = new Calibration(); // default values
/** Dummy. */
protected LayerSet(Project project, long id) {
super(project, id, null, false, null, 20, 20);
}
/** Create a new LayerSet with a 0,0,0 rotation vector and default 20,20 px Displayable width,height. */
public LayerSet(Project project, String title, double x, double y, Layer parent, float layer_width, float layer_height) {
super(project, title, x, y);
rot_x = rot_y = rot_z = 0.0D;
this.width = 20;
this.height = 20; // for the label that paints into the parent Layer
this.parent = parent;
this.layer_width = layer_width;
this.layer_height = layer_height;
addToDatabase();
}
/** Reconstruct from the database. */
public LayerSet(Project project, long id, String title, float width, float height, double rot_x, double rot_y, double rot_z, float layer_width, float layer_height, boolean locked, int snapshots_mode, AffineTransform at) {
super(project, id, title, locked, at, width, height);
this.rot_x = rot_x;
this.rot_y = rot_y;
this.rot_z = rot_z;
this.layer_width = layer_width;
this.layer_height= layer_height;
this.snapshots_mode = snapshots_mode;
// the parent will be set by the LayerThing.setup() calling Layer.addSilently()
// the al_layers will be filled idem.
}
/** Reconstruct from an XML entry. */
public LayerSet(final Project project, final long id, final HashMap<String,String> ht_attributes, final HashMap<Displayable,String> ht_links) {
super(project, id, ht_attributes, ht_links);
String data;
if (null != (data = ht_attributes.get("layer_width"))) this.layer_width = Float.parseFloat(data);
else xmlError("layer_width", this.layer_width);
if (null != (data = ht_attributes.get("layer_height"))) this.layer_height = Float.parseFloat(data);
else xmlError("layer_height", this.layer_height);
if (null != (data = ht_attributes.get("rot_x"))) this.rot_x = Double.parseDouble(data);
else xmlError("rot_x", this.rot_x);
if (null != (data = ht_attributes.get("rot_y"))) this.rot_y = Double.parseDouble(data);
else xmlError("rot_y", this.rot_y);
if (null != (data = ht_attributes.get("rot_z"))) this.rot_y = Double.parseDouble(data);
else xmlError("rot_z", this.rot_z);
if (null != (data = ht_attributes.get("snapshots_quality"))) snapshots_quality = Boolean.valueOf(data.trim().toLowerCase());
if (null != (data = ht_attributes.get("snapshots_mode"))) {
final String smode = data.trim();
for (int i=0; i<snapshot_modes.length; i++) {
if (smode.equals(snapshot_modes[i])) {
snapshots_mode = i;
break;
}
}
}
if (null != (data = ht_attributes.get("color_cues"))) color_cues = Boolean.valueOf(data.trim().toLowerCase());
if (null != (data = ht_attributes.get("area_color_cues"))) area_color_cues = Boolean.valueOf(data.trim().toLowerCase());
if (null != (data = ht_attributes.get("n_layers_color_cue"))) {
n_layers_color_cue = Integer.parseInt(data.trim().toLowerCase());
if (n_layers_color_cue < -1) n_layers_color_cue = -1;
}
if (null != (data = ht_attributes.get("avoid_color_cue_colors"))) {
// If there's any error in the parsing, default to true for use_color_cue_colors:
use_color_cue_colors = !Boolean.valueOf(data.trim().toLowerCase());
}
if (null != (data = ht_attributes.get("paint_arrows"))) paint_arrows = Boolean.valueOf(data.trim().toLowerCase());
if (null != (data = ht_attributes.get("paint_tags"))) paint_tags = Boolean.valueOf(data.trim().toLowerCase());
if (null != (data = ht_attributes.get("paint_edge_confidence_boxes"))) paint_edge_confidence_boxes = Boolean.valueOf(data.trim().toLowerCase());
if (null != (data = ht_attributes.get("prepaint"))) prepaint = Boolean.valueOf(data.trim().toLowerCase());
if (null != (data = ht_attributes.get("preload_ahead"))) preload_ahead = Integer.parseInt(data);
}
/** For reconstruction purposes: set the active layer to the ZDisplayable objects. Recurses through LayerSets in the children layers. */
public void setup() {
final Layer la0 = al_layers.get(0);
for (ZDisplayable zd : al_zdispl) zd.setLayer(la0); // just any Layer
for (Layer layer : al_layers) {
for (final Displayable d : layer.getDisplayables()) {
if (d.getClass() == LayerSet.class) {
((LayerSet)d).setup();
}
}
}
}
/** Create a new LayerSet in the middle of the parent Layer. */
public LayerSet create(Layer parent_layer) {
if (null == parent_layer) return null;
GenericDialog gd = ControlWindow.makeGenericDialog("New Layer Set");
gd.addMessage("In pixels:");
gd.addNumericField("width: ", this.layer_width, 3);
gd.addNumericField("height: ", this.layer_height, 3);
gd.showDialog();
if (gd.wasCanceled()) return null;
try {
float width = (float)gd.getNextNumber();
float height = (float)gd.getNextNumber();
if (Double.isNaN(width) || Double.isNaN(height)) return null;
if (0 == width || 0 == height) {
Utils.showMessage("Cannot accept zero width or height for LayerSet dimensions.");
return null;
}
// make a new LayerSet with x,y in the middle of the parent_layer
return new LayerSet(project, "Layer Set", parent_layer.getParent().getLayerWidth() / 2, parent_layer.getParent().getLayerHeight() / 2, parent_layer, width/2, height/2);
} catch (Exception e) { Utils.log("LayerSet.create: " + e); }
return null;
}
/** Add a new Layer silently, ordering by z as well.*/
public void addSilently(final Layer layer) {
if (null == layer || al_layers.contains(layer)) return;
try {
synchronized (IDLAYERS_WRITE_LOCK) {
// Like put, but replacing the map instance
final HashMap<Long,Layer> m = new HashMap<Long,Layer>(idlayers);
m.put(layer.getId(), layer);
idlayers = m;
}
synchronized (layerindices) { layerindices.clear(); }
double z = layer.getZ();
int i = 0;
for (final Layer la : al_layers) {
if (! (la.getZ() < z) ) {
al_layers.add(i, layer);
layer.setParentSilently(this);
return;
}
i++;
}
// else, add at the end
al_layers.add(layer);
layer.setParentSilently(this);
} catch (Exception e) {
Utils.log("LayerSet.addSilently: Not a Layer, not adding DBObject id=" + layer.getId());
return;
}
}
/** Add a new Layer, inserted according to its Z. */
public void add(final Layer layer) {
if (layer.getProject() != this.project)
throw new IllegalArgumentException("LayerSet rejected a Layer: belongs to a different project.");
if (null != idlayers.get(layer.getId())) return;
final double z = layer.getZ();
final int n = al_layers.size();
int i = 0;
for (; i<n; i++) {
Layer l = al_layers.get(i);
if (l.getZ() < z) continue;
break;
}
if (i < n) {
al_layers.add(i, layer);
} else {
al_layers.add(layer);
}
layer.setParent(this);
synchronized (IDLAYERS_WRITE_LOCK) {
// Like put, but replacing the map instance
final HashMap<Long,Layer> m = new HashMap<Long,Layer>(idlayers);
m.put(layer.getId(), layer);
idlayers = m;
}
synchronized (layerindices) { layerindices.clear(); }
Display.updateLayerScroller(this);
//debug();
}
public void printDebugInfo() {
Utils.log("LayerSet debug:");
for (int i=0; i<al_layers.size(); i++)
Utils.log(i + " : " + al_layers.get(i).getZ());
}
public Layer getParent() {
return parent;
}
/** 'update' in database or not. */
public void setLayer(Layer layer, boolean update) {
super.setLayer(layer, update);
if (null != layer) this.parent = layer; // repeated pointer, eliminate 'parent' !
}
public void setParent(Layer layer) {
if (null == layer || layer == parent) return;
this.parent = layer;
updateInDatabase("parent_id");
}
public void mousePressed(MouseEvent me, int x_p, int y_p, Rectangle srcRect, double mag) {
if (ProjectToolbar.SELECT != ProjectToolbar.getToolId()) return;
Display.setActive(me, this);
if (2 == me.getClickCount() && al_layers.size() > 0) {
new Display(project, al_layers.get(0));
}
}
public void mouseDragged(MouseEvent me, int x_p, int y_p, int x_d, int y_d, int x_d_old, int y_d_old, Rectangle srcRect, double mag) {
if (ProjectToolbar.SELECT != ProjectToolbar.getToolId()) return;
super.translate(x_d - x_d_old, y_d - y_d_old);
Display.repaint(layer, this, 0);
}
public void mouseReleased(MouseEvent me, int x_p, int y_p, int x_d, int y_d, int x_r, int y_r, Rectangle srcRect, double mag) {
// nothing
}
public void keyPressed(KeyEvent ke) {
Utils.log("LayerSet.keyPressed: not yet implemented.");
// TODO
}
public String toString() {
return this.title;
}
public void paint(Graphics2D g, Rectangle srcRect, double magnification, boolean active, int channels, Layer active_layer) {
//arrange transparency
Composite original_composite = null;
if (alpha != 1.0f) {
original_composite = g.getComposite();
g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha));
}
// Translate graphics context accordingly
AffineTransform gt = g.getTransform();
AffineTransform aff = new AffineTransform(this.at);
aff.preConcatenate(gt);
g.setTransform(aff);
//set color
g.setColor(this.color);
// fill a background box
g.fillRect(0, 0, (int)(this.width), (int)(this.height));
g.setColor(new Color(255 - color.getRed(), 255 - color.getGreen(), 255 - color.getBlue()).brighter()); // the "opposite", but brighter, so it won't fail to generate contrast if the color is 127 in all channels
int x = (int)(this.width/5);
int y = (int)(this.height/5);
int width = (int)(this.width/5);
int height = (int)(this.height/5 * 3);
g.fillRect(x, y, width, height);
x = (int)(this.width/5 * 2);
y = (int)(this.height/5 * 3);
width = (int)(this.width/5 * 2);
height = (int)(this.height/5);
g.fillRect(x, y, width, height);
//Transparency: fix composite back to original.
if (alpha != 1.0f) {
g.setComposite(original_composite);
}
g.setTransform(gt);
}
public float getLayerWidth() { return layer_width; }
public float getLayerHeight() { return layer_height; }
public double getRotX() { return rot_x; }
public double getRotY() { return rot_y; }
public double getRotZ() { return rot_z; }
/** The number of Layers in this LayerSet. */
public int size() {
return al_layers.size();
}
public void setRotVector(double rot_x, double rot_y, double rot_z) {
if (Double.isNaN(rot_x) || Double.isNaN(rot_y) || Double.isNaN(rot_z)) {
Utils.showMessage("LayerSet: Rotation vector contains NaNs. Not updating.");
return;
} else if (rot_x == this.rot_x && rot_y == this.rot_y && rot_z == this.rot_z) {
return;
}
this.rot_x = rot_x;
this.rot_y = rot_y;
this.rot_z = rot_z;
updateInDatabase("rot");
}
/** Used by the Loader after loading blindly a lot of Patches. Will crop the canvas to the minimum size possible. */
public boolean setMinimumDimensions() {
// find current x,y,width,height that crops the canvas without cropping away any Displayable
double x = Double.NaN;
double y = Double.NaN;
double xe = 0; // lower right corner (x end)
double ye = 0;
double tx = 0;
double ty = 0;
double txe = 0;
double tye = 0;
// collect all Displayable and ZDisplayable objects
final ArrayList<Displayable> al = new ArrayList<Displayable>();
for (int i=al_layers.size() -1; i>-1; i--) {
al.addAll(al_layers.get(i).getDisplayables());
}
al.addAll(al_zdispl);
// find minimum bounding box
Rectangle b = new Rectangle();
for (final Displayable d : al) {
b = d.getBoundingBox(b); // considers rotation
tx = b.x;//d.getX();
ty = b.y;//d.getY();
// set first coordinates
if (Double.isNaN(x) || Double.isNaN(y)) { // Double.NaN == x fails!
x = tx;
y = ty;
}
txe = tx + b.width;//d.getWidth();
tye = ty + b.height;//d.getHeight();
if (tx < x) x = tx;
if (ty < y) y = ty;
if (txe > xe) xe = txe;
if (tye > ye) ye = tye;
}
// if none, then stop
if (Double.isNaN(x) || Double.isNaN(y)) {
Utils.showMessage("No displayable objects, don't know how to resize the canvas and Layerset.");
return false;
}
double w = xe - x;
double h = ye - y;
if (w <= 0 || h <= 0) {
Utils.log("LayerSet.setMinimumDimensions: zero width or height, NOT resizing.");
return false;
}
// Record previous state
if (prepareStep(this)) {
addEditStep(new LayerSet.DoResizeLayerSet(this));
}
// translate
if (0 != x || 0 != y) {
project.getLoader().startLargeUpdate();
try {
final AffineTransform at2 = new AffineTransform();
at2.translate(-x, -y);
//Utils.log2("translating all displayables by " + x + "," + y);
for (final Displayable d : al) {
//((Displayable)it.next()).translate(-x, -y, false); // drag regardless of getting off current LayerSet bounds
// optimized to avoid creating so many AffineTransform instances:
//Utils.log2("BEFORE: " + d.getBoundingBox());
d.getAffineTransform().preConcatenate(at2);
//Utils.log2("AFTER: " + d.getBoundingBox());
d.updateInDatabase("transform");
}
project.getLoader().commitLargeUpdate();
} catch (Exception e) {
IJError.print(e);
project.getLoader().rollback();
return false;
}
}
//Utils.log("x,y xe,ye : " + x + "," + y + " " + xe + "," + ye);
// finally, accept:
if (w != layer_width || h != layer_height) {
this.layer_width = (float)Math.ceil(w); // stupid int to double conversions ... why floating point math is a non-solved problem? It is for SBCL
this.layer_height = (float)Math.ceil(h);
updateInDatabase("layer_dimensions");
recreateBuckets(true);
// and notify the Displays, if any
Display.update(this);
Display.pack(this);
}
// Record current state:
addEditStep(new LayerSet.DoResizeLayerSet(this));
return true;
}
/** Enlarge the 2D universe so that all Displayable in the collection fit in it;
* that is, that no Displayable has a negative x,y position or lays beyond bounds.*/
synchronized public void enlargeToFit(final Collection<? extends Displayable> ds) {
Rectangle r = null;
for (Displayable d : ds) {
if (null == r) r = d.getBoundingBox();
else r.add(d.getBoundingBox());
}
if (null == r) return; //empty collection
r.add(get2DBounds());
setDimensions(r.x, r.y, r.width, r.height);
}
/** Enlarges the display in the given direction; the anchor is the point to keep still, and can be any of LayerSet.NORTHWEST (top-left), etc. */
synchronized public boolean enlargeToFit(final Displayable d, final int anchor) {
final Rectangle r = new Rectangle(0, 0, (int)Math.ceil(layer_width), (int)Math.ceil(layer_height));
final Rectangle b = d.getBoundingBox(null);
// check if necessary
if (r.contains(b)) return false;
// else, enlarge to fit it
r.add(b);
return setDimensions(r.width, r.height, anchor);
}
/** May leave objects beyond the visible window. */
public void setDimensions(float x, float y, float layer_width, float layer_height) {
// Record previous state
if (prepareStep(this)) {
addEditStep(new LayerSet.DoResizeLayerSet(this));
}
this.layer_width = layer_width;
this.layer_height = layer_height;
final AffineTransform affine = new AffineTransform();
affine.translate(-x, -y);
for (ZDisplayable zd : al_zdispl) {
zd.getAffineTransform().preConcatenate(affine);
zd.updateInDatabase("transform");
}
for (Layer la : al_layers) la.apply(Displayable.class, affine);
recreateBuckets(true);
Display.update(this);
// Record new state
addEditStep(new LayerSet.DoResizeLayerSet(this));
}
/** Returns false if any Displayables are being partially or totally cropped away. */
public boolean setDimensions(float layer_width, float layer_height, int anchor) {
// check preconditions
if (Double.isNaN(layer_width) || Double.isNaN(layer_height)) { Utils.log("LayerSet.setDimensions: NaNs! Not adjusting."); return false; }
if (layer_width <=0 || layer_height <= 0) { Utils.showMessage("LayerSet: can't accept zero or a minus for layer width or height"); return false; }
if (anchor < NORTH || anchor > CENTER) { Utils.log("LayerSet: wrong anchor, not resizing."); return false; }
// Record previous state
if (prepareStep(this)) {
addEditStep(new LayerSet.DoResizeLayerSet(this));
}
// new coordinates:
double new_x = 0;// the x,y of the old 0,0
double new_y = 0;
switch (anchor) {
case NORTH:
case SOUTH:
case CENTER:
new_x = (layer_width - this.layer_width) / 2; // (this.layer_width - layer_width) / 2;
break;
case NORTHWEST:
case WEST:
case SOUTHWEST:
new_x = 0;
break;
case NORTHEAST:
case EAST:
case SOUTHEAST:
new_x = layer_width - this.layer_width; // (this.layer_width - layer_width);
break;
}
switch (anchor) {
case WEST:
case EAST:
case CENTER:
new_y = (layer_height - this.layer_height) / 2;
break;
case NORTHWEST:
case NORTH:
case NORTHEAST:
new_y = 0;
break;
case SOUTHWEST:
case SOUTH:
case SOUTHEAST:
new_y = (layer_height - this.layer_height);
break;
}
/*
Utils.log("anchor: " + anchor);
Utils.log("LayerSet: existing w,h = " + this.layer_width + "," + this.layer_height);
Utils.log("LayerSet: new w,h = " + layer_width + "," + layer_height);
*/
// collect all Displayable and ZDisplayable objects
ArrayList<Displayable> al = new ArrayList<Displayable>();
for (int i=al_layers.size() -1; i>-1; i--) {
al.addAll(al_layers.get(i).getDisplayables());
}
al.addAll(al_zdispl);
// check that no displayables are being cropped away
if (layer_width < this.layer_width || layer_height < this.layer_height) {
for (final Displayable d : al) {
Rectangle b = d.getBoundingBox(null);
double dw = b.getWidth();
double dh = b.getHeight();
// respect 10% margins
if (b.x + dw + new_x < 0.1 * dw || b.x + 0.9 * dw + new_x > layer_width || b.y + dh + new_y < 0.1 * dh || b.y + 0.9 * dh + new_y > layer_height) {
// cropping!
Utils.showMessage("Cropping " + d + "\nLayerSet: not resizing.");
return false;
}
}
}
this.layer_width = layer_width;
this.layer_height = layer_height;
//Utils.log("LayerSet.setDimensions: new_x,y: " + new_x + "," + new_y);
// translate all displayables
if (0 != new_x || 0 != new_y) {
for (final Displayable d : al) {
Rectangle b = d.getBoundingBox(null);
//Utils.log("d x,y = " + b.x + ", " + b.y);
d.setLocation(b.x + new_x, b.y + new_y);
}
}
updateInDatabase("layer_dimensions");
recreateBuckets(true);
// and notify the Display
Display.update(this);
Display.pack(this);
// Record new state
addEditStep(new LayerSet.DoResizeLayerSet(this));
return true;
}
protected boolean remove2(boolean check) {
if (check) {
if (!Utils.check("Really delete " + this.toString() + (null != al_layers && al_layers.size() > 0 ? " and all its children?" : ""))) return false;
}
LayerThing lt = project.findLayerThing(this);
if (null == lt) return false;
return project.getLayerTree().remove(check, lt, null); // will end up calling remove(boolean) on this object
}
public boolean remove(boolean check) {
if (check) {
if (!Utils.check("Really delete " + this.toString() + (null != al_layers && al_layers.size() > 0 ? " and all its children?" : ""))) return false;
}
// delete all layers
while (0 != al_layers.size()) {
if (!al_layers.get(0).remove(false)) {
Utils.showMessage("LayerSet id= " + id + " : Deletion incomplete, check database.");
return false;
}
}
// delete the ZDisplayables
for (final ZDisplayable zd : al_zdispl) {
zd.remove(false); // will call back the LayerSet.remove(ZDisplayable)
}
// remove the self
if (null != parent) parent.remove(this);
removeFromDatabase();
return true;
}
/** Remove a child. Does not destroy it or delete it from the database. */
public void remove(final Layer layer) {
if (null == layer || null == idlayers.get(layer.getId())) return;
al_layers.remove(layer);
synchronized (IDLAYERS_WRITE_LOCK) {
// Like remove, but replacing the map instance
final HashMap<Long,Layer> m = new HashMap<Long,Layer>(idlayers);
m.remove(layer.getId());
idlayers = m;
}
synchronized (layerindices) { layerindices.clear(); }
for (final ZDisplayable zd : new ArrayList<ZDisplayable>(al_zdispl)) zd.layerRemoved(layer); // may call back and add/remove ZDisplayable objects
Display.updateLayerScroller(this);
Display.updateTitle(this);
removeFromOffscreens(layer);
}
public Layer next(final Layer layer) {
final int i = indexOf(layer);
if (-1 == i) {
Utils.log("LayerSet.next: no such Layer " + layer);
return layer;
}
if (al_layers.size() -1 == i) return layer;
else return al_layers.get(i+1);
}
public Layer previous(final Layer layer) {
final int i = indexOf(layer);
if (-1 == i) {
Utils.log("LayerSet.previous: no such Layer " + layer);
return layer;
}
if (0 == i) return layer;
else return al_layers.get(i-1);
}
public Layer nextNonEmpty(Layer layer) {
Layer next = layer;
Layer given = layer;
do {
layer = next;
next = next(layer);
if (!next.isEmpty()) return next;
} while (next != layer);
return given;
}
public Layer previousNonEmpty(Layer layer) {
Layer previous = layer;
Layer given = layer;
do {
layer = previous;
previous = previous(layer);
if (!previous.isEmpty()) return previous;
} while (previous != layer);
return given;
}
public int getLayerIndex(final long id) {
final Layer layer = getLayer(id);
if (null == layer) return -1;
return indexOf(layer);
}
/** Find a layer by index, or null if none. */
public Layer getLayer(final int i) {
if (i >=0 && i < al_layers.size()) return al_layers.get(i);
return null;
}
/** Find a layer with the given id, or null if none. */
public Layer getLayer(final long id) {
return idlayers.get(id);
}
/** Same as getLayer(long) but without box/unbox. */
public Layer getLayer(final Long id) {
return idlayers.get(id);
}
/** Returns the first layer found with the given Z coordinate, rounded to seventh decimal precision, or null if none found. */
public Layer getLayer(final double z) {
double error = 0.0000001; // TODO adjust to an optimal
for (Layer layer : al_layers) {
if (error > Math.abs(layer.getZ() - z)) { // floating-point arithmetic is still not a solved problem!
return layer;
}
}
return null;
}
public Layer getNearestLayer(final double z) {
double min_dist = Double.MAX_VALUE;
Layer closest = null;
for (Layer layer : al_layers) {
double dist = Math.abs(layer.getZ() - z);
if (dist < min_dist) {
min_dist = dist;
closest = layer;
}
}
return closest;
}
/** Returns null if none has the given z and thickness. If 'create' is true and no layer is found, a new one with the given Z is created and added to the LayerTree. */
public Layer getLayer(double z, double thickness, boolean create) {
Iterator<Layer> it = al_layers.iterator();
Layer layer = null;
double error = 0.0000001; // TODO adjust to an optimal
while (it.hasNext()) {
Layer l = it.next();
if (error > Math.abs(l.getZ() - z) && error > Math.abs(l.getThickness() - thickness)) { // floating point is still not a solved problem.
//Utils.log("LayerSet.getLayer: found layer with z=" + l.getZ());
layer = l;
}
}
if (create && null == layer && !Double.isNaN(z) && !Double.isNaN(thickness)) {
//Utils.log("LayerSet.getLayer: creating new Layer with z=" + z);
layer = new Layer(project, z, thickness, this);
add(layer);
project.getLayerTree().addLayer(this, layer);
}
return layer;
}
/** Useful for typeless scripts so that a ZDisplayable can be added; the
* overloaded method add(Layer) and add(ZDisplayable) is not distinguishable otherwise. */
public void addZDisplayable(final ZDisplayable zdispl) {
add(zdispl);
}
/** Add a Displayable to be painted in all Layers, such as a Pipe. Also updates open displays of the fact. */
public void add(final ZDisplayable zdispl) {
if (null == zdispl || -1 != al_zdispl.indexOf(zdispl)) {
Utils.log2("LayerSet: not adding zdispl");
return;
}
if (zdispl.getProject() != this.project)
throw new IllegalArgumentException("LayerSet rejected a ZDisplayable: belongs to a different project.");
al_zdispl.add(zdispl); // at the top
zdispl.setLayerSet(this);
// The line below can fail (and in the addSilently as well) if one can add zdispl objects while no Layer has been created. But the ProjectThing.createChild prevents this situation.
zdispl.setLayer(al_layers.get(0));
zdispl.updateInDatabase("layer_set_id"); // TODO: update stack index? It should!
// insert into bucket
/*
if (null != root) {
// add as last, then update
root.put(al_zdispl.size()-1, zdispl, zdispl.getBoundingBox(null));
// Updating takes too long, just don't do it
//root.update(this, zdispl, 0, al_zdispl.size()-1);
}
*/
addToBuckets(zdispl, al_zdispl.size()-1);
Display.add(this, zdispl);
}
public void addAll(final Collection<? extends ZDisplayable> coll) {
if (null == coll || 0 == coll.size()) return;
for (final ZDisplayable zd : coll) {
al_zdispl.add(zd);
zd.setLayerSet(this);
zd.setLayer(al_layers.get(0));
zd.updateInDatabase("layer_set_id");
}
recreateBuckets(false); // only ZDisplayable
Display.addAll(this, coll);
}
/** Used for reconstruction purposes, avoids repainting or updating. */
public void addSilently(final ZDisplayable zdispl) {
if (null == zdispl || -1 != al_zdispl.indexOf(zdispl)) return;
try {
zdispl.setLayer(0 == al_layers.size() ? null : al_layers.get(0));
zdispl.setLayerSet(this, false);
//Utils.log2("setLayerSet to ZDipl id=" + zdispl.getId());
al_zdispl.add(zdispl);
} catch (Exception e) {
Utils.log("LayerSet.addSilently: not adding ZDisplayable with id=" + zdispl.getId());
IJError.print(e);
return;
}
}
/** Remove a child. Does not destroy the child nor remove it from the database, only from the LayerSet and the Display. */
public boolean remove(final ZDisplayable zdispl) {
if (null == zdispl || null == al_zdispl) return false;
final int old_stack_index = al_zdispl.indexOf(zdispl);
if (-1 == old_stack_index) {
Utils.log2("LayerSet.remove: Not found: " + zdispl);
return false;
}
al_zdispl.remove(old_stack_index);
// remove from Bucket AFTER modifying stack index, so it gets reindexed properly
removeFromBuckets(zdispl, old_stack_index);
removeFromOffscreens(zdispl);
Display.remove(zdispl);
return true;
}
/** Remove a child. Does not destroy the child nor remove it from the database, only from the LayerSet and the Display.
* Returns false if at least one failed to be removed. */
public boolean removeAll(final Set<ZDisplayable> zds) {
if (null == zds || null == al_zdispl) return false;
// Ensure list is iterated only once: don't ask for index every time!
int count = 0;
for (final Iterator<ZDisplayable> it = al_zdispl.iterator(); it.hasNext(); ) {
final ZDisplayable zd = it.next();
if (zds.contains(zd)) {
it.remove();
removeFromOffscreens(zd);
Display.remove(zd);
count++;
if (zds.size() == count) break;
}
}
removeFromBuckets(zds);
Display.updateVisibleTabs(this.project);
return true;
}
public boolean contains(final Layer layer) {
if (null == layer) return false;
return -1 != indexOf(layer);
}
public boolean contains(final Displayable zdispl) {
if (null == zdispl) return false;
return -1 != al_zdispl.indexOf(zdispl);
}
/** Returns a copy of the layer list. */
public ArrayList<Layer> getLayers() {
return new ArrayList<Layer>(al_layers); // for integrity and safety, return a copy.
}
/** Returns a sublist of layers from first to last, both inclusive. If last is larger than first, the order is reversed. */
public List<Layer> getLayers(final int first, final int last) {
final List<Layer> las = al_layers.subList(Math.min(first, last), Math.max(first, last) +1);
if (first > last) {
final List<Layer> las2 = new ArrayList<Layer>(las);
Collections.reverse(las2); // would otherwise reverse the original list! A monumental error.
return las2;
}
return new ArrayList<Layer>(las); // editable, thread-safe (a copy)
}
/** Returns the layer range from first to last, both included. If last.getZ() < first.getZ(), the order is reversed. */
public List<Layer> getLayers(final Layer first, final Layer last) {
return getLayers(indexOf(first), indexOf(last));
}
/** Returns the list of layers to paint by considering the range of n_layers_color_cue around the active layer index. */
public List<Layer> getColorCueLayerRange(final Layer active_layer) {
if (n_layers_color_cue < 0) {
return new ArrayList<Layer>(al_layers); // a copy of all
} else if (0 == n_layers_color_cue) {
final ArrayList<Layer> list = new ArrayList<Layer>();
list.add(active_layer);
return list;
}
// Else:
final int i = indexOf(active_layer);
if (-1 == i) {
Utils.log("An error ocurred: could not find an index for layer " + active_layer);
final ArrayList<Layer> a = new ArrayList<Layer>(); a.add(active_layer); return a;
}
int first = i - n_layers_color_cue;
int last = i + n_layers_color_cue;
if (first < 0) first = 0;
int size = al_layers.size();
if (last >= size) last = size -1;
return getLayers(first, last);
}
public boolean isDeletable() {
return false;
}
/** Overiding. The alpha is used to show whether the LayerSet object is selected or not. */
public void setAlpha(float alpha) { return; }
/** Move the given Displayable to the next layer if possible. */
public void moveDown(final Layer layer, final Displayable d) {
final int i = indexOf(layer);
if (al_layers.size() -1 == i || -1 == i) return;
layer.remove(d);
(al_layers.get(i +1)).add(d);
}
/** Move the given Displayable to the previous layer if possible. */
public void moveUp(final Layer layer, final Displayable d) {
final int i = indexOf(layer);
if (0 == i || -1 == i) return;
layer.remove(d);
al_layers.get(i -1).add(d);
}
/** Move all Displayable objects in the HashSet to the given target layer. */
public void move(final Set<Displayable> hs_d, final Layer source, final Layer target) {
if (0 == hs_d.size() || null == source || null == target || source == target) return;
Display.setRepaint(false); // disable repaints
for (final Displayable d : hs_d) {
if (d instanceof ZDisplayable) continue; // ignore
if (source == d.getLayer()) {
source.remove(d);
target.add(d, false, false); // these contortions to avoid repeated DB traffic
d.updateInDatabase("layer_id");
Display.add(target, d, false); // don't activate
}
}
Display.setRepaint(true); // enable repaints
source.updateInDatabase("stack_index");
target.updateInDatabase("stack_index");
Display.repaint(source); // update graphics: true
Display.repaint(target);
}
/** Returns the hash set of objects whose visibility has changed. */
public HashSet<Displayable> setVisible(String type, final boolean visible, final boolean repaint) {
type = type.toLowerCase();
final HashSet<Displayable> hs = new HashSet<Displayable>();
try {
project.getLoader().startLargeUpdate();
if (type.equals("connector") || type.equals("treeline") || type.equals("areatree") || type.equals("pipe") || type.equals("ball") || type.equals("arealist") || type.equals("polyline") || type.equals("stack") || type.equals("dissector")) {
for (ZDisplayable zd : al_zdispl) {
if (visible != zd.isVisible() && zd.getClass().getName().toLowerCase().endsWith(type)) { // endsWith, because DLabel is called as Label
zd.setVisible(visible, false); // don't repaint
hs.add(zd);
}
}
} else {
for (Layer layer : al_layers) {
hs.addAll(layer.setVisible(type, visible, false)); // don't repaint
}
}
} catch (Exception e) {
IJError.print(e);
} finally {
project.getLoader().commitLargeUpdate();
}
if (repaint) {
Display.repaint(this); // this could be optimized to repaint only the accumulated box
}
return hs;
}
/** Hide all except those whose type is in 'type' list, whose visibility flag is left unchanged. Returns the list of displayables made hidden. */
public HashSet<Displayable> hideExcept(ArrayList<Class<?>> type, boolean repaint) {
final HashSet<Displayable> hs = new HashSet<Displayable>();
for (ZDisplayable zd : al_zdispl) {
if (!type.contains(zd.getClass()) && zd.isVisible()) {
zd.setVisible(false, repaint);
hs.add(zd);
}
}
for (Layer la : al_layers) hs.addAll(la.hideExcept(type, repaint));
return hs;
}
/** Returns the collection of Displayable whose visibility state has changed. */
public Collection<Displayable> setAllVisible(final boolean repaint) {
final Collection<Displayable> col = new ArrayList<Displayable>();
for (final ZDisplayable zd : al_zdispl) {
if (!zd.isVisible()) {
zd.setVisible(true, repaint);
col.add(zd);
}
}
for (Layer la : al_layers) col.addAll(la.setAllVisible(repaint));
return col;
}
/** Returns true if any of the ZDisplayable objects are of the given class. */
public boolean contains(final Class<?> c) {
for (final ZDisplayable zd : al_zdispl) {
if (zd.getClass() == c) return true;
}
return false;
}
/** Check in all layers. */
public boolean containsDisplayable(final Class<?> c) {
for (final Layer layer : al_layers) {
if (layer.contains(c)) return true;
}
return false;
}
/** Returns the distance from the first layer's Z to the last layer's Z. */
public double getDepth() {
if (null == al_layers || al_layers.isEmpty()) return 0;
return al_layers.get(al_layers.size() -1).getZ() - al_layers.get(0).getZ();
}
/** Return all the Displayable objects from all the layers of this LayerSet. Does not include the ZDisplayables. */
public ArrayList<Displayable> getDisplayables() {
final ArrayList<Displayable> al = new ArrayList<Displayable>();
for (Layer layer : al_layers) {
al.addAll(layer.getDisplayables());
}
return al;
}
/** Return all the Displayable objects from all the layers of this LayerSet of the given class. Does not include the ZDisplayables. */
public ArrayList<Displayable> getDisplayables(Class<?> c) {
final ArrayList<Displayable> al = new ArrayList<Displayable>();
for (Layer layer : al_layers) {
al.addAll(layer.getDisplayables(c));
}
return al;
}
/** Return all the Displayable objects from all the layers of this LayerSet of the given class that intersect the given area. Does not include the ZDisplayables. */
public ArrayList<Displayable> getDisplayables(final Class<?> c, final Area aroi, final boolean visible_only) {
final ArrayList<Displayable> al = new ArrayList<Displayable>();
for (Layer layer : al_layers) {
al.addAll(layer.getDisplayables(c, aroi, visible_only));
}
return al;
}
/** From zero to size-1. */
public int indexOf(final Layer layer) {
synchronized (layerindices) {
Integer i = layerindices.get(layer);
if (null == i) {
// Recreate
layerindices.clear();
int k = 0;
for (final Layer la : al_layers) {
layerindices.put(la, k);
k++;
}
i = layerindices.get(layer);
if (null == i) {
Utils.log("ERROR: could not find an index for layer " + layer);
return -1;
}
}
return i.intValue();
}
}
private static java.lang.reflect.Field sbvalue = null;
static {
try {
sbvalue = StringBuilder.class.getSuperclass().getDeclaredField("value");
sbvalue.setAccessible(true);
} catch (Exception e) {
IJError.print(e);
}
}
public void exportXML(final java.io.Writer writer, final String indent, final XMLOptions options) throws Exception {
final StringBuilder sb_body = new StringBuilder(512);
sb_body.append(indent).append("<t2_layer_set\n");
final String in = indent + "\t";
super.exportXML(sb_body, in, options);
sb_body.append(in).append("layer_width=\"").append(layer_width).append("\"\n")
.append(in).append("layer_height=\"").append(layer_height).append("\"\n")
.append(in).append("rot_x=\"").append(rot_x).append("\"\n")
.append(in).append("rot_y=\"").append(rot_y).append("\"\n")
.append(in).append("rot_z=\"").append(rot_z).append("\"\n")
.append(in).append("snapshots_quality=\"").append(snapshots_quality).append("\"\n")
.append(in).append("snapshots_mode=\"").append(snapshot_modes[snapshots_mode]).append("\"\n")
.append(in).append("color_cues=\"").append(color_cues).append("\"\n")
.append(in).append("area_color_cues=\"").append(area_color_cues).append("\"\n")
.append(in).append("avoid_color_cue_colors=\"").append(!use_color_cue_colors).append("\"\n")
.append(in).append("n_layers_color_cue=\"").append(n_layers_color_cue).append("\"\n")
.append(in).append("paint_arrows=\"").append(paint_arrows).append("\"\n")
.append(in).append("paint_tags=\"").append(paint_tags).append("\"\n")
.append(in).append("paint_edge_confidence_boxes=\"").append(paint_edge_confidence_boxes).append("\"\n")
.append(in).append("prepaint=\"").append(prepaint).append("\"\n")
.append(in).append("preload_ahead=\"").append(preload_ahead).append("\"\n")
// TODO: alpha! But it's not necessary.
;
sb_body.append(indent).append(">\n");
if (null != calibration) {
sb_body.append(in).append("<t2_calibration\n")
.append(in).append("\tpixelWidth=\"").append(calibration.pixelWidth).append("\"\n")
.append(in).append("\tpixelHeight=\"").append(calibration.pixelHeight).append("\"\n")
.append(in).append("\tpixelDepth=\"").append(calibration.pixelDepth).append("\"\n")
.append(in).append("\txOrigin=\"").append(calibration.xOrigin).append("\"\n")
.append(in).append("\tyOrigin=\"").append(calibration.yOrigin).append("\"\n")
.append(in).append("\tzOrigin=\"").append(calibration.zOrigin).append("\"\n")
.append(in).append("\tinfo=\"").append(calibration.info).append("\"\n")
.append(in).append("\tvalueUnit=\"").append(calibration.getValueUnit()).append("\"\n")
.append(in).append("\ttimeUnit=\"").append(calibration.getTimeUnit()).append("\"\n")
.append(in).append("\tunit=\"").append(calibration.getUnit()).append("\"\n")
.append(in).append("/>\n")
;
}
if (null == sbvalue) {
writer.write(sb_body.toString());
} else {
writer.write((char[])sbvalue.get(sb_body), 0, sb_body.length()); // avoid making a copy of the array
}
// Count objects
int done = 0;
int total = 0;
total += al_zdispl.size();
for (final Layer la : al_layers) {
total += la.getDisplayableList().size();
}
// export ZDisplayable objects
if (null != al_zdispl) {
for (final ZDisplayable zd : al_zdispl) {
sb_body.setLength(0);
zd.exportXML(sb_body, in, options);
if (null == sbvalue) {
writer.write(sb_body.toString()); // each separately, for they can be huge
} else {
writer.write((char[])sbvalue.get(sb_body), 0, sb_body.length()); // avoid making a copy of the array
}
}
done += al_zdispl.size();
Utils.showProgress(done / (double)total);
}
// export Layer and contained Displayable objects
if (null != al_layers) {
//Utils.log("LayerSet " + id + " is saving " + al_layers.size() + " layers.");
for (final Layer la : al_layers) {
sb_body.setLength(0);
la.exportXML(sb_body, in, options);
if (null == sbvalue) {
writer.write(sb_body.toString());
} else {
writer.write((char[])sbvalue.get(sb_body), 0, sb_body.length()); // avoid making a copy of the array
}
done += la.getDisplayableList().size();
Utils.showProgress(done / (double)total);
}
}
sb_body.setLength(0);
if (sb_body.length() > 0) {
super.restXML(sb_body, in, options);
if (null == sbvalue) {
writer.write(sb_body.toString());
} else {
writer.write((char[])sbvalue.get(sb_body), 0, sb_body.length()); // avoid making a copy of the array
}
}
writer.write(indent + "</t2_layer_set>\n");
}
/** Includes the !ELEMENT */
static public void exportDTD(final StringBuilder sb_header, final HashSet<String> hs, final String indent) {
final String type = "t2_layer_set";
if (!hs.contains(type)) {
sb_header.append(indent).append("<!ELEMENT t2_layer_set (").append(Displayable.commonDTDChildren()).append(",t2_layer,t2_pipe,t2_ball,t2_area_list,t2_calibration,t2_stack,t2_treeline)>\n");
Displayable.exportDTD(type, sb_header, hs, indent);
sb_header.append(indent).append(TAG_ATTR1).append(type).append(" layer_width").append(TAG_ATTR2)
.append(indent).append(TAG_ATTR1).append(type).append(" layer_height").append(TAG_ATTR2)
.append(indent).append(TAG_ATTR1).append(type).append(" rot_x").append(TAG_ATTR2)
.append(indent).append(TAG_ATTR1).append(type).append(" rot_y").append(TAG_ATTR2)
.append(indent).append(TAG_ATTR1).append(type).append(" rot_z").append(TAG_ATTR2)
.append(indent).append(TAG_ATTR1).append(type).append(" snapshots_quality").append(TAG_ATTR2)
.append(indent).append(TAG_ATTR1).append(type).append(" color_cues").append(TAG_ATTR2)
.append(indent).append(TAG_ATTR1).append(type).append(" area_color_cues").append(TAG_ATTR2)
.append(indent).append(TAG_ATTR1).append(type).append(" avoid_color_cue_colors").append(TAG_ATTR2)
.append(indent).append(TAG_ATTR1).append(type).append(" n_layers_color_cue").append(TAG_ATTR2)
.append(indent).append(TAG_ATTR1).append(type).append(" paint_arrows").append(TAG_ATTR2)
.append(indent).append(TAG_ATTR1).append(type).append(" paint_tags").append(TAG_ATTR2)
.append(indent).append(TAG_ATTR1).append(type).append(" paint_edge_confidence_boxes").append(TAG_ATTR2)
.append(indent).append(TAG_ATTR1).append(type).append(" preload_ahead").append(TAG_ATTR2)
;
sb_header.append(indent).append("<!ELEMENT t2_calibration EMPTY>\n")
.append(indent).append(TAG_ATTR1).append("t2_calibration pixelWidth").append(TAG_ATTR2)
.append(indent).append(TAG_ATTR1).append("t2_calibration pixelHeight").append(TAG_ATTR2)
.append(indent).append(TAG_ATTR1).append("t2_calibration pixelDepth").append(TAG_ATTR2)
.append(indent).append(TAG_ATTR1).append("t2_calibration xOrigin").append(TAG_ATTR2)
.append(indent).append(TAG_ATTR1).append("t2_calibration yOrigin").append(TAG_ATTR2)
.append(indent).append(TAG_ATTR1).append("t2_calibration zOrigin").append(TAG_ATTR2)
.append(indent).append(TAG_ATTR1).append("t2_calibration info").append(TAG_ATTR2)
.append(indent).append(TAG_ATTR1).append("t2_calibration valueUnit").append(TAG_ATTR2)
.append(indent).append(TAG_ATTR1).append("t2_calibration timeUnit").append(TAG_ATTR2)
.append(indent).append(TAG_ATTR1).append("t2_calibration unit").append(TAG_ATTR2)
;
}
}
public void setSnapshotsMode(final int mode) {
if (mode == snapshots_mode) return;
this.snapshots_mode = mode;
Display.repaintSnapshots(this);
updateInDatabase("snapshots_mode");
}
public int getSnapshotsMode() {
return this.snapshots_mode;
}
@Override
public void destroy() {
for (Iterator<Layer> it = al_layers.iterator(); it.hasNext(); ) {
Layer layer = it.next();
layer.destroy();
}
for (final ZDisplayable zd : al_zdispl) {
zd.destroy();
}
this.al_layers.clear();
this.al_zdispl.clear();
synchronized (IDLAYERS_WRITE_LOCK) { this.idlayers = new HashMap<Long,Layer>(); } // like .clear()
synchronized (layerindices) { this.layerindices.clear(); }
this.offscreens.clear();
}
/** Used by the Layer.setZ method. */
protected void reposition(final Layer layer) {
if (null == layer || !idlayers.containsKey(layer.getId())) return;
al_layers.remove(layer);
addSilently(layer);
}
/** Get up to 'n' layers before and after the given layers. */
public ArrayList<Layer> getNeighborLayers(final Layer layer, final int n) {
final int i_layer = indexOf(layer);
final ArrayList<Layer> al = new ArrayList<Layer>();
if (-1 == i_layer) return al;
int start = i_layer - n;
if (start < 0) start = 0;
int end = i_layer + n;
if (end > al_layers.size()) end = al_layers.size();
for (int i=start; i<i_layer; i++) al.add(al_layers.get(i));
for (int i=i_layer+1; i<= i_layer + n || i < end; i++) al.add(al_layers.get(i));
return al;
}
public boolean isTop(ZDisplayable zd) {
if (null != zd && al_zdispl.size() > 0 && al_zdispl.indexOf(zd) == al_zdispl.size() -1) return true;
return false;
}
public boolean isBottom(ZDisplayable zd) {
if (null != zd && al_zdispl.size() > 0 && al_zdispl.indexOf(zd) == 0) return true;
return false;
}
/** Hub method: ZDisplayable or into the Displayable's Layer. */
protected boolean isTop(Displayable d) {
if (d instanceof ZDisplayable) return isTop((ZDisplayable)d);
else return d.getLayer().isTop(d);
}
/** Hub method: ZDisplayable or into the Displayable's Layer. */
protected boolean isBottom(Displayable d) {
if (d instanceof ZDisplayable) return isBottom((ZDisplayable)d);
else return d.getLayer().isBottom(d);
}
/** Change z position in the layered stack, which defines the painting order. */ // the BOTTOM of the stack is the first element in the al_zdispl array
synchronized protected void move(final int place, final Displayable d) {
if (d instanceof ZDisplayable) {
int i = al_zdispl.indexOf(d);
if (-1 == i) {
Utils.log("LayerSet.move: object does not belong here");
return;
}
int size = al_zdispl.size();
if (1 == size) return;
switch(place) {
case LayerSet.TOP:
// To the end of the list:
al_zdispl.add(al_zdispl.remove(i));
// OLD // if (null != root) root.update(this, d, i, al_zdispl.size()-1);
updateRangeInBuckets(d, i, al_zdispl.size()-1);
break;
case LayerSet.UP:
// +1 in the list
if (size -1 == i) return;
al_zdispl.add(i+1, al_zdispl.remove(i));
//if (null != root) root.update(this, d, i, i+1);
updateRangeInBuckets(d, i, i+1);
break;
case LayerSet.DOWN:
// -1 in the list
if (0 == i) return;
al_zdispl.add(i-1, al_zdispl.remove(i)); //swap
//if (null != root) root.update(this, d, i-1, i);
updateRangeInBuckets(d, i-1, i);
break;
case LayerSet.BOTTOM:
// to first position in the list
al_zdispl.add(0, al_zdispl.remove(i));
//if (null != root) root.update(this, d, 0, i);
updateRangeInBuckets(d, 0, i);
break;
}
updateInDatabase("stack_index");
Display.updatePanelIndex(d.getLayer(), d);
} else {
switch (place) {
case LayerSet.TOP: d.getLayer().moveTop(d); break;
case LayerSet.UP: d.getLayer().moveUp(d); break;
case LayerSet.DOWN: d.getLayer().moveDown(d); break;
case LayerSet.BOTTOM: d.getLayer().moveBottom(d); break;
}
}
}
/** Returns the reverse index of ZDisplayable zd, which is the actual index as seen in the screen. */
public int indexOf(final ZDisplayable zd) {
int k = al_zdispl.indexOf(zd);
if (-1 == k) return -1;
return al_zdispl.size() - k -1;
}
public boolean isEmptyAt(final Layer layer) {
for (final ZDisplayable zd : al_zdispl) {
if (zd.paintsAt(layer)) return false;
}
return true;
}
public Displayable clone(final Project pr, final boolean copy_id) {
final Rectangle roi = new Rectangle(0, 0, (int)Math.ceil(getLayerWidth()), (int)Math.ceil(getLayerHeight()));
final LayerSet copy = (LayerSet) clone(pr, al_layers.get(0), al_layers.get(al_layers.size()-1), roi, false, copy_id, false);
try {
LayerSet.cloneInto(this, al_layers.get(0), al_layers.get(al_layers.size()-1), pr, copy, roi, copy_id);
} catch (Exception e) {
IJError.print(e);
return null;
}
return copy;
}
/** Clone the contents of this LayerSet, from first to last given layers, and cropping for the given rectangle;
* does NOT copy the ZDisplayable, which may be copied using the LayerSet.cloneInto method. */
public Displayable clone(Project pr, Layer first, Layer last, Rectangle roi, boolean add_to_tree, boolean copy_id, boolean ignore_hidden_patches) {
// obtain a LayerSet
final long nid = copy_id ? this.id : pr.getLoader().getNextId();
final LayerSet copy = new LayerSet(pr, nid, getTitle(), this.width, this.height, this.rot_x, this.rot_y, this.rot_z, roi.width, roi.height, this.locked, this.snapshots_mode, (AffineTransform)this.at.clone());
copy.setCalibration(getCalibrationCopy());
copy.snapshots_quality = this.snapshots_quality;
// copy objects that intersect the roi, from within the given range of layers
final java.util.List<Layer> range = new ArrayList<Layer>(al_layers).subList(indexOf(first), indexOf(last) +1);
Utils.log2("range.size() : " + range.size());
for (Layer layer : range) {
Layer layercopy = layer.clone(pr, copy, roi, copy_id, ignore_hidden_patches);
copy.addSilently(layercopy);
if (add_to_tree) pr.getLayerTree().addLayer(copy, layercopy);
}
return copy;
}
static public void cloneInto(final LayerSet src, Layer src_first, Layer src_last,
final Project pr, final LayerSet copy, Rectangle roi, boolean copy_id)
throws Exception {
// copy ZDisplayable objects if they intersect the roi, and translate them properly
final AffineTransform trans = new AffineTransform();
trans.translate(-roi.x, -roi.y);
final List<Layer> range = copy.getLayers();
List<Layer> src_range = null;
if (0 == range.size()) throw new Exception("Cannot cloneInto for a range of zero layers!");
for (final ZDisplayable zd : src.find(range.get(0), range.get(range.size()-1), new Area(roi))) {
if (src.project != pr && zd instanceof Tree<?>) {
// Special in-cloning + crop + out-cloning for Tree instances, since they hold Layer pointers
// 1. Clone within same project, with ALL layers present
ZDisplayable src_zd_copy = (ZDisplayable)zd.clone(src.project, true); // NOTICE I use src.project, not pr! And also reuse same id -- this is a throwaway.
// 2. Crop to the desired range, using the range from the original project
if (null == src_range) src_range = new ArrayList<Layer>(src.al_layers).subList(src.indexOf(src_first), src.indexOf(src_last) +1);
src_zd_copy.crop(src_range);
// 3. Clone the cropped Tree
Tree<?> tcopy = (Tree<?>)src_zd_copy.clone(pr, copy_id);
tcopy.getAffineTransform().preConcatenate(trans);
copy.addSilently(tcopy);
tcopy.calculateBoundingBox(null); // for all layers. Will update buckets.
continue;
}
// Else, normally:
ZDisplayable zdcopy = (ZDisplayable)zd.clone(pr, copy_id);
zdcopy.getAffineTransform().preConcatenate(trans);
copy.addSilently(zdcopy); // must be added before attempting to crop it, because crop needs a LayerSet ref.
if (zdcopy.crop(range)) {
if (zdcopy.isDeletable()) {
pr.remove(zdcopy);
Utils.log("Skipping empty " + zdcopy);
} else {
zdcopy.calculateBoundingBox(null); // null means update buckets for all layers
}
} else {
Utils.log("Could not crop " + zd);
}
}
// fix links:
copy.linkPatchesR();
}
/** Create a virtual layer stack that acts as a virtual ij.ImageStack, in RGB and set to a scale of max_dimension / Math.max(layer_width, layer_height). */
public LayerStack createLayerStack(Class<?> clazz, int type, int c_alphas) {
return new LayerStack(this,
getVirtualizationScale(),
type,
clazz,
c_alphas);
}
public int getPixelsMaxDimension() { return max_dimension; }
/** From 0.000... to 1. */
public double getVirtualizationScale() {
double scale = max_dimension / Math.max(layer_width, layer_height);
return scale > 1 ? 1 : scale;
}
public void setPixelsMaxDimension(final int d) {
if (d > 2) {
if (d != max_dimension) {
max_dimension = d;
Polyline.flushTraceCache(project); // depends on the scale value
Utils.log("3D Viewer NOT updated:\n close it and recreate meshes for any objects you had in it.");
}
} else Utils.log("Can't set virtualization max pixels dimension to smaller than 2!");
}
public void setPixelsVirtualizationEnabled(boolean b) { this.virtualization_enabled = b; }
public boolean isPixelsVirtualizationEnabled() { return virtualization_enabled; }
/** Returns a new Rectangle of 0, 0, layer_width, layer_height. */
public Rectangle get2DBounds() {
return new Rectangle(0, 0, (int)Math.ceil(layer_width), (int)Math.ceil(layer_height));
}
/** Set the calibration to a clone of the given calibration. */
public void setCalibration(Calibration cal) {
if (null == cal) return;
this.calibration = (Calibration)cal.clone();
}
public Calibration getCalibration() {
return this.calibration;
}
public Calibration getCalibrationCopy() {
return calibration.copy();
}
public boolean isCalibrated() {
Calibration identity = new Calibration();
if (identity.equals(this.calibration)) return false;
return true;
}
/** Restore calibration from the given XML attributes table.*/
public void restoreCalibration(HashMap<String,String> ht_attributes) {
for (final Map.Entry<String,String> entry : ht_attributes.entrySet()) {
final String key = (String)entry.getKey();
final String value = (String)entry.getValue();
// remove the prefix 't2_'
key.substring(3).toLowerCase(); // case-resistant
try {
if (key.equals("pixelwidth")) {
calibration.pixelWidth = Double.parseDouble(value);
} else if (key.equals("pixelheight")) {
calibration.pixelHeight = Double.parseDouble(value);
} else if (key.equals("pixeldepth")) {
calibration.pixelDepth = Double.parseDouble(value);
} else if (key.equals("xorigin")) {
calibration.xOrigin = Double.parseDouble(value);
} else if (key.equals("yorigin")) {
calibration.yOrigin = Double.parseDouble(value);
} else if (key.equals("zorigin")) {
calibration.zOrigin = Double.parseDouble(value);
} else if (key.equals("info")) {
calibration.info = value;
} else if (key.equals("valueunit")) {
calibration.setValueUnit(value);
} else if (key.equals("timeunit")) {
calibration.setTimeUnit(value);
} else if (key.equals("unit")) {
calibration.setUnit(value);
}
} catch (Exception e) {
Utils.log2("LayerSet.restoreCalibration, key/value failed:" + key + "=\"" + value +"\"");
IJError.print(e);
}
}
//Utils.log2("Restored LayerSet calibration: " + calibration);
}
/** For creating snapshots, using a very slow but much better scaling algorithm (the Image.SCALE_AREA_AVERAGING method). */
public boolean snapshotsQuality() {
return snapshots_quality;
}
public void setSnapshotsQuality(boolean b) {
this.snapshots_quality = b;
updateInDatabase("snapshots_quality");
// TODO this is obsolete
}
/** Find, in this LayerSet and contained layers and their nested LayerSets if any, all Displayable instances of Class c. Includes the ZDisplayables. */
public ArrayList<Displayable> get(final Class<?> c) {
return get(new ArrayList<Displayable>(), c);
}
/** Find, in this LayerSet and contained layers and their nested LayerSets if any, all Displayable instances of Class c, which are stored in the given ArrayList; returns the same ArrayList, or a new one if its null. Includes the ZDisplayables. */
public ArrayList<Displayable> get(ArrayList<Displayable> all, final Class<?> c) {
if (null == all) all = new ArrayList<Displayable>();
// check whether to include all the ZDisplayable objects
if (Displayable.class == c || ZDisplayable.class == c) all.addAll(al_zdispl);
else {
for (final ZDisplayable zd : al_zdispl) {
if (zd.getClass() == c) all.add(zd);
}
}
for (final Layer layer : al_layers) {
all.addAll(layer.getDisplayables(c));
for (final Displayable ls : layer.getDisplayables(LayerSet.class)) {
((LayerSet)ls).get(all, c);
}
}
return all;
}
/** Returns the region defined by the rectangle as an image in the type and format specified.
* The type is either ImagePlus.GRAY8 or ImagePlus.COLOR_RGB.
* The format is either Layer.IMAGE (an array) or Layer.ImagePlus (it returns an ImagePlus containing an ImageStack), from which any ImageProcessor or pixel arrays can be retrieved trivially.
*/
public Object grab(final int first, final int last, final Rectangle r, final double scale, final Class<?> c, final int c_alphas, final int format, final int type) {
// check preconditions
if (first < 0 || first > last || last >= al_layers.size()) {
Utils.log("Invalid first and/or last layers.");
return null;
}
// Ensure some memory is free
project.getLoader().releaseToFit(r.width, r.height, type, 1.1f);
if (Layer.IMAGEPLUS == format) {
ImageStack stack = new ImageStack((int)(r.width*scale), (int)(r.height*scale));
for (int i=first; i<=last; i++) {
Layer la = al_layers.get(i);
Utils.log2("c is " + c);
ImagePlus imp = project.getLoader().getFlatImage(la, r, scale, c_alphas, type, c, null, true);
if (null != imp) try {
//if (0 == stack.getSize()) stack.setColorModel(imp.getProcessor().getColorModel());
stack.addSlice(imp.getTitle(), imp.getProcessor()); //.getPixels());
} catch (IllegalArgumentException iae) {
IJError.print(iae);
} else Utils.log("LayerSet.grab: Ignoring layer " + la);
}
if (0 == stack.getSize()) {
Utils.log("LayerSet.grab: could not make slices.");
return null;
}
return new ImagePlus("Stack " + first + "-" + last, stack);
} else if (Layer.IMAGE == format) {
final Image[] image = new Image[last - first + 1];
for (int i=first, j=0; i<=last; i++, j++) {
image[j] = project.getLoader().getFlatAWTImage(al_layers.get(i), r, scale, c_alphas, type, c, null, true, Color.black);
}
return image;
}
return null;
}
/** Searches in all layers. Ignores the ZDisplaybles. */
public Displayable findDisplayable(final long id) {
for (Layer la : al_layers) {
for (Displayable d : la.getDisplayables()) {
if (d.getId() == id) return d;
}
}
return null;
}
/** Searches in all ZDisplayables and in all layers, recursively into nested LayerSets. */
public DBObject findById(final long id) {
if (this.id == id) return this;
for (ZDisplayable zd : al_zdispl) {
if (zd.getId() == id) return zd;
}
for (Layer la : al_layers) {
DBObject dbo = la.findById(id);
if (null != dbo) return dbo;
}
return null;
}
// private to the package
void linkPatchesR() {
for (Layer la : al_layers) la.linkPatchesR();
for (ZDisplayable zd : al_zdispl) zd.linkPatches();
}
/** Recursive into nested LayerSet objects.*/
public void updateLayerTree() {
for (Layer la : al_layers) {
la.updateLayerTree();
}
}
/** Find the ZDisplayable objects that intersect with the 3D roi defined by the first and last layers, and the area -all in world coordinates. */
public ArrayList<ZDisplayable> find(final Layer first, final Layer last, final Area area) {
final ArrayList<ZDisplayable> al = new ArrayList<ZDisplayable>();
for (ZDisplayable zd : al_zdispl) {
if (zd.intersects(area, first.getZ(), last.getZ())) {
al.add(zd);
}
}
return al;
}
/** A Bucket for the ZDisplayable parts that show in every Layer. */
protected final class LayerBucket {
protected final Bucket root;
protected final HashMap<Displayable,HashSet<Bucket>> db_map = new HashMap<Displayable,HashSet<Bucket>>();
LayerBucket(final Layer la) {
this.root = new Bucket(0, 0, (int)(0.00005 + getLayerWidth()), (int)(0.00005 + getLayerHeight()), Bucket.getBucketSide(LayerSet.this, la));
this.root.populate(LayerSet.this, la, this.db_map);
}
}
/** For fast search. */
protected HashMap<Layer,LayerBucket> lbucks = new HashMap<Layer,LayerBucket>();
final private void addToBuckets(final Displayable zd, final int i) {
synchronized (lbucks) {
if (lbucks.isEmpty()) return;
for (final Long lid : zd.getLayerIds()) {
final Layer la = getLayer(lid); // map lookup
final LayerBucket lb = lbucks.get(la);
if (null == lb) {
nbmsg(la);
continue;
}
lb.root.put(i, zd, la, lb.db_map);
}
}
}
/** Recreate the buckets of every layer in which the {@link Displayable} has data. */
final private void removeFromBuckets(final Displayable zd, final int old_stack_index) {
synchronized (lbucks) {
if (lbucks.isEmpty()) return;
for (final Long lid : zd.getLayerIds()) {
final Layer la = getLayer(lid);
final LayerBucket lb = lbucks.get(la);
if (null == lb) {
nbmsg(la);
continue;
}
recreateBuckets(getLayer(lid), false);
}
}
}
/** Recreate the buckets for all layers involved. */
final private void removeFromBuckets(final Collection<ZDisplayable> zds) {
synchronized (lbucks) {
if (lbucks.isEmpty()) return;
final Set<Layer> touched = new HashSet<Layer>();
for (final ZDisplayable zd : zds) {
touched.addAll(zd.getLayersWithData());
}
for (final Layer la : touched) {
final LayerBucket lb = lbucks.remove(la);
if (null == lb) {
nbmsg(la);
continue;
}
lbucks.put(la, new LayerBucket(la));
}
}
}
/** Used ONLY by move up/down/top/bottom. */
final private void updateRangeInBuckets(final Displayable zd, final int i, final int j) {
synchronized (lbucks) {
if (lbucks.isEmpty()) return;
for (final Long lid : zd.getLayerIds()) {
final Layer la = getLayer(lid);
final LayerBucket lb = lbucks.get(la);
if (null == lb) {
nbmsg(la);
continue;
}
for (final Bucket bu : lb.db_map.get(zd)) {
bu.updateRange(this, zd, i, j);
}
}
}
}
/** Returns a copy of the list of ZDisplayable objects. */
public ArrayList<ZDisplayable> getZDisplayables() { return new ArrayList<ZDisplayable>(al_zdispl); }
/** Returns the real list of displayables, not a copy. If you modify this list, Thor may ground you with His lightning. */
public ArrayList<ZDisplayable> getDisplayableList() {
return al_zdispl;
}
public HashMap<Displayable, HashSet<Bucket>> getBucketMap(final Layer la) {
synchronized (lbucks) {
if (lbucks.isEmpty()) return null;
final LayerBucket lb = lbucks.get(la);
if (null == lb) {
nbmsg(la);
return null;
}
return lb.db_map;
}
}
public void updateBucket(final Displayable d, final Layer layer) {
synchronized (lbucks) {
final LayerBucket lb = lbucks.get(layer);
if (null != lb) lb.root.updatePosition(d, layer, lb.db_map);
}
}
/** Recreate the ZDisplayable buckets, and also the Layer Displayable buckets if desired. */
public void recreateBuckets(final boolean layer_buckets) {
recreateBuckets(al_layers, layer_buckets);
}
/** Recreate the ZDisplayable buckets for {@code layer}, and also the {@link Layer} {@link Displayable} buckets if desired.
* @param layer The {@link Layer} to recreate {@link ZDisplayable} buckets for.
* @param layer_buckets Whether to also recreate the {@link Layer}-specific buckets for images and text labels.
*/
public void recreateBuckets(final Layer layer, final boolean layer_buckets) {
LayerBucket lb = new LayerBucket(layer);
synchronized (lbucks) {
lbucks.put(layer, lb);
}
if (layer_buckets && null != layer.root) layer.recreateBuckets();
}
/** Regenerate the quad-tree bucket system for the ZDisplayable instances that have data at each of the given layers,
* and optionally regenerate the buckets as well for the 2D Displayable instances of that layer as well. */
public void recreateBuckets(final Collection<Layer> layers, final boolean layer_buckets) {
final HashMap<Layer,LayerBucket> m = new HashMap<Layer,LayerBucket>();
try {
Process.progressive(layers, new TaskFactory<Layer,Object>() {
@Override
public Object process(final Layer layer) {
LayerBucket lb = new LayerBucket(layer);
synchronized (m) {
m.put(layer, lb);
}
if (layer_buckets && null != layer.root) layer.recreateBuckets();
return null;
}
}, Process.NUM_PROCESSORS -1); // works even when there is only 1 core, since it checks and fixes the '0' processors request
} catch (Exception e) {
IJError.print(e);
}
synchronized (lbucks) {
lbucks.clear();
lbucks.putAll(m);
}
}
/** Checks only buckets for ZDisplayable, not any related to any layer. */
public void checkBuckets() {
synchronized (lbucks) {
if (!lbucks.isEmpty()) return;
}
recreateBuckets(false);
}
/** Returns the minimal 2D bounding box for Displayables of class @param c in all layers. */
public Rectangle getMinimalBoundingBox(final Class<?> c) {
Rectangle r = null;
for (final Layer la : al_layers) {
if (null == r) r = la.getMinimalBoundingBox(c);
else {
Rectangle box = la.getMinimalBoundingBox(c); // may be null if Layer is empty
if (null != box) r.add(box);
}
}
return r;
}
/** Time vs DoStep. Not all steps may be specific for a single Displayable. */
final private TreeMap<Long,DoStep> edit_history = new TreeMap<Long,DoStep>();
/** The step representing the current diff state. */
private long current_edit_time = 0;
private DoStep current_edit_step = null;
/** Displayable vs its own set of time vs DoStep, for quick access, for those edits that are specific of a Displayable.
* It's necessary to set a ground, starting point for any Displayable whose data will be edited. */
final private Map<Displayable,TreeMap<Long,DoStep>> dedits = new HashMap<Displayable,TreeMap<Long,DoStep>>();
/** Time vs DoStep; as steps are removed from the end of edit_history, they are put here. */
final private TreeMap<Long,DoStep> redo = new TreeMap<Long,DoStep>();
/** Whether an initial step should be added or not. */
final boolean prepareStep(final Object ob) {
synchronized (edit_history) {
if (0 == edit_history.size() || redo.size() > 0) return true;
// Check if the last added entry contains the exact same elements and data
DoStep step = edit_history.get(edit_history.lastKey());
boolean b = step.isIdenticalTo(ob);
//Utils.log2(b + " == prepareStep for " + ob);
// If identical, don't prepare one!
return !b;
}
}
/** If last step is not a DoEdit "data" step for d, then call addDataEditStep(d). */
boolean addPreDataEditStep(final Displayable d) {
if ( null == current_edit_step
|| (current_edit_step.getD() != d || !((DoEdit)current_edit_step).containsKey("data"))) {
//Utils.log2("Adding pre-data edit step");
//return addDataEditStep(d);
return addEditStep(new Displayable.DoEdit(d).init(d, new String[]{"data"}));
}
return false;
}
/** A new undo step for the "data" field of Displayable d. */
boolean addDataEditStep(final Displayable d) {
//Utils.log2("Adding data edit step");
// Adds "data", which contains width,height,affinetransform,links, and the data (points, areas, etc.)
return addDataEditStep(d, new String[]{"data"});
}
/** A new undo step for any desired fields of Displayable d. */
boolean addDataEditStep(final Displayable d, final String[] fields) {
return addEditStep(new Displayable.DoEdit(d).init(d, fields));
}
/** A new undo step for the "data" field of all Displayable in the set. */
public boolean addDataEditStep(final Set<? extends Displayable> ds) {
return addDataEditStep(ds, new String[]{"data"});
}
boolean addDataEditStep(final Set<? extends Displayable> ds, final String[] fields) {
final Displayable.DoEdits edits = new Displayable.DoEdits(ds);
edits.init(fields);
return addEditStep(edits);
}
/** Add an undo step for the transformations of all Displayable in the layer. */
public void addTransformStep(final Layer layer) {
addTransformStep(layer.getDisplayables());
}
public void addTransformStep(final List<Layer> layers) {
final ArrayList<Displayable> all = new ArrayList<Displayable>();
for (final Layer la : layers) all.addAll(la.getDisplayables());
addTransformStep(all);
}
/** Add an undo step for the transformations of all Displayable in hs. */
public void addTransformStep(final Collection<? extends Displayable> col) {
//Utils.log2("Added transform step for col");
addEditStep(new Displayable.DoTransforms().addAll(col));
}
/** Add an undo step for the transformations of all Displayable in col, with data as well (for Patch, data includes the CoordinateTransform). */
public DoEdits addTransformStepWithData(final Collection<? extends Displayable> col) {
if (col.isEmpty()) return null;
final Set<? extends Displayable> hs = col instanceof Set<?> ? (Set<? extends Displayable>)col : new HashSet<Displayable>(col);
final DoEdits step = new Displayable.DoEdits(hs).init(new String[]{"data", "at", "width", "height"});
addEditStep(step);
return step;
}
/** Includes all ZDisplayable that paint at any of the given layers. */
public Collection<Displayable> addTransformStepWithDataForAll(final Collection<Layer> layers) {
if (layers.isEmpty()) return Collections.emptyList();
final Set<Displayable> hs = new HashSet<Displayable>();
for (final Layer layer : layers) hs.addAll(layer.getDisplayables());
for (final ZDisplayable zd : al_zdispl) {
for (final Layer layer : layers) {
if (zd.paintsAt(layer)) {
hs.add((Displayable)zd);
break;
}
}
}
addTransformStepWithData(hs);
return hs;
}
/** Add an undo step for the transformations of all Displayable in all layers. */
public void addTransformStep() {
//Utils.log2("Added transform step for all");
Displayable.DoTransforms dt = new Displayable.DoTransforms();
for (final Layer la : al_layers) {
dt.addAll(la.getDisplayables());
}
addEditStep(dt);
}
/** Add a step to undo the addition or deletion of one or more objects in this project and LayerSet. */
public DoChangeTrees addChangeTreesStep() {
DoChangeTrees step = new LayerSet.DoChangeTrees(this);
if (prepareStep(step)) {
Utils.log2("Added change trees step.");
addEditStep(step);
}
return step;
}
/** Add a step to undo the addition or deletion of one or more objects in this project and LayerSet,
* along with an arbitrary set of steps that may alter, for example the data. */
public DoChangeTrees addChangeTreesStep(final Set<DoStep> dependents) {
DoChangeTrees step = addChangeTreesStep();
step.addDependents(dependents);
addEditStep(step);
return 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<Layer> al) {
addEditStep(new Layer.DoEditLayers(al));
}
public void addUndoStep(final DoStep step) {
addEditStep(step);
}
boolean addEditStep(final DoStep step) {
if (null == step || step.isEmpty()) {
Utils.log2("Warning: can't add empty step " + step);
return false;
}
synchronized (edit_history) {
// Check if it's identical to current step
if (step.isIdenticalTo(current_edit_step)) {
//Utils.log2("Skipping identical undo step of class " + step.getClass() + ": " + step);
return false;
}
// Store current in undo queue
if (null != current_edit_step) {
edit_history.put(current_edit_time, current_edit_step);
// Store for speedy access, if its Displayable-specific:
final Displayable d = current_edit_step.getD();
if (null != d) {
TreeMap<Long,DoStep> edits = dedits.get(d);
if (null == edits) {
edits = new TreeMap<Long,DoStep>();
dedits.put(d, edits);
}
edits.put(current_edit_time, current_edit_step);
}
// prune if too large
while (edit_history.size() > project.getProperty("n_undo_steps", 32)) {
long t = edit_history.firstKey();
DoStep st = edit_history.remove(t);
if (null != st.getD()) {
TreeMap<Long,DoStep> m = dedits.get(st.getD());
m.remove(t);
if (0 == m.size()) dedits.remove(st.getD());
}
}
}
// Set step as current
current_edit_time = System.currentTimeMillis();
current_edit_step = step;
// Bye bye redo! Can't branch.
redo.clear();
}
return true;
}
public boolean canUndo() {
return edit_history.size() > 0;
}
public boolean canRedo() {
return redo.size() > 0 || null != current_edit_step;
}
/** Undoes one step of the ongoing transformation history, otherwise of the overall LayerSet history. */
public boolean undoOneStep() {
synchronized (edit_history) {
if (0 == edit_history.size()) {
Utils.logAll("Empty undo history!");
return false;
}
//Utils.log2("Undoing one step");
// Add current (if any) to redo queue
if (null != current_edit_step) {
redo.put(current_edit_time, current_edit_step);
}
// Remove last step from undo queue, and set it as current
current_edit_time = edit_history.lastKey();
current_edit_step = edit_history.remove(current_edit_time);
// Remove as well from dedits
if (null != current_edit_step.getD()) {
dedits.get(current_edit_step.getD()).remove(current_edit_time);
}
if (!current_edit_step.apply(DoStep.UNDO)) {
Utils.log("Undo: could not apply step!");
return false;
}
Utils.log("Undoing " + current_edit_step.getClass().getSimpleName());
Display.updateVisibleTabs(project);
}
return true;
}
protected boolean removeLastUndoStep() {
synchronized (edit_history) {
if (edit_history.isEmpty()) return false;
final long time = edit_history.lastKey();
final DoStep step = edit_history.remove(time);
if (null != step.getD()) dedits.get(step.getD()).remove(time);
// shift current
if (step == current_edit_step) {
current_edit_time = edit_history.lastKey();
current_edit_step = edit_history.get(current_edit_time);
}
}
return true;
}
/** Redoes one step of the ongoing transformation history, otherwise of the overall LayerSet history. */
public boolean redoOneStep() {
synchronized (edit_history) {
if (0 == redo.size()) {
Utils.logAll("Empty redo history!");
if (null != current_edit_step) {
return current_edit_step.apply(DoStep.REDO);
}
return false;
}
//Utils.log2("Redoing one step");
// Add current (if any) to undo queue
if (null != current_edit_step) {
edit_history.put(current_edit_time, current_edit_step);
if (null != current_edit_step.getD()) {
dedits.get(current_edit_step.getD()).put(current_edit_time, current_edit_step);
}
}
// Remove one step from undo queue and set it as current
current_edit_time = redo.firstKey();
current_edit_step = redo.remove(current_edit_time);
if (!current_edit_step.apply(DoStep.REDO)) {
Utils.log("Undo: could not apply step!");
return false;
}
Utils.log("Redoing " + current_edit_step.getClass().getSimpleName());
Display.updateVisibleTabs(project);
}
return true;
}
static public void applyTransforms(final Map<Displayable,AffineTransform> m) {
for (final Map.Entry<Displayable,AffineTransform> e : m.entrySet()) {
e.getKey().setAffineTransform(e.getValue()); // updates buckets
}
}
static {
// Undo background tasks: should be done in background threads,
// but on attempting to undo/redo, the undo/redo should wait
// until all tasks are done. For example, updating mipmaps when
// undoing/redoing min/max or CoordinateTransform.
// This could be done with futures: spawn and do in the
// background, but on redo/undo, call for the Future return
// value, which will block until there is one to return.
// Since blocking would block the EventDispatchThread, just refuse to undo/redo and notify the user.
//
// TODO
}
/** Keeps the width,height of a LayerSet and the AffineTransform of every Displayable in it. */
static private class DoResizeLayerSet implements DoStep {
final LayerSet ls;
final HashMap<Displayable,AffineTransform> affines;
final float width, height;
DoResizeLayerSet(final LayerSet ls) {
this.ls = ls;
this.width = ls.layer_width;
this.height = ls.layer_height;
this.affines = new HashMap<Displayable,AffineTransform>();
final ArrayList<Displayable> col = ls.getDisplayables(); // it's a new list
col.addAll(ls.getZDisplayables());
for (final Displayable d : col) {
this.affines.put(d, d.getAffineTransformCopy());
}
}
public boolean isIdenticalTo(final Object ob) {
if (!(ob instanceof LayerSet)) return false;
final LayerSet layerset = (LayerSet) ob;
if (layerset.layer_width != this.width || layerset.height != this.height || layerset != this.ls) return false;
final ArrayList<Displayable> col = ls.getDisplayables();
col.addAll(ls.getZDisplayables());
for (final Displayable d : col) {
final AffineTransform aff = this.affines.get(d);
if (null == aff) return false;
if (!aff.equals(d.getAffineTransform())) return false;
}
return true;
}
public boolean apply(int action) {
ls.layer_width = width;
ls.layer_height = height;
for (final Map.Entry<Displayable,AffineTransform> e : affines.entrySet()) {
e.getKey().getAffineTransform().setTransform(e.getValue());
}
final boolean dobuckets;
synchronized (ls.lbucks) {
dobuckets = ls.lbucks.isEmpty();
}
if (dobuckets) ls.recreateBuckets(true);
Display.updateSelection();
Display.update(ls); //so it's not left out painted beyond borders
return true;
}
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 protected class DoChangeTrees implements DoStep {
final LayerSet ls;
final HashMap<Thing,Boolean> ttree_exp, ptree_exp, ltree_exp;
final Thing troot, proot, lroot;
final ArrayList<Layer> all_layers;
final HashMap<Layer,ArrayList<Displayable>> all_displ;
final ArrayList<ZDisplayable> all_zdispl;
final HashMap<Displayable,Set<Displayable>> links;
final HashMap<Long,Layer> idlayers;
final HashMap<Layer,Integer> layerindices;
HashSet<DoStep> dependents = null;
// TODO: does not consider recursive LayerSets!
public DoChangeTrees(final LayerSet ls) {
this.ls = ls;
final Project p = ls.getProject();
this.ttree_exp = new HashMap<Thing,Boolean>();
this.troot = p.getTemplateTree().duplicate(this.ttree_exp);
this.ptree_exp = new HashMap<Thing,Boolean>();
this.proot = p.getProjectTree().duplicate(this.ptree_exp);
this.ltree_exp = new HashMap<Thing,Boolean>();
this.lroot = p.getLayerTree().duplicate(this.ltree_exp);
this.all_layers = ls.getLayers(); // a copy of the list, but each object is the running instance
this.all_zdispl = ls.getZDisplayables(); // idem
this.idlayers = new HashMap<Long,Layer>(ls.idlayers);
synchronized (ls.layerindices) {
this.layerindices = new HashMap<Layer,Integer>(ls.layerindices);
}
this.links = new HashMap<Displayable,Set<Displayable>>();
for (final ZDisplayable zd : this.all_zdispl) {
this.links.put(zd, zd.hs_linked); // LayerSet is a Displayable
}
this.all_displ = new HashMap<Layer,ArrayList<Displayable>>();
for (final Layer layer : all_layers) {
final ArrayList<Displayable> al = layer.getDisplayables(); // a copy
this.all_displ.put(layer, al);
for (final Displayable d : al) {
this.links.put(d, null == d.hs_linked ? null : new HashSet<Displayable>(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 trees
final Project p = ls.getProject();
p.resetRootTemplateThing((TemplateThing)this.troot, ttree_exp);
p.resetRootProjectThing((ProjectThing)this.proot, ptree_exp);
p.resetRootLayerThing((LayerThing)this.lroot, ltree_exp);
// Replace all layers
ls.al_layers.clear();
ls.al_layers.addAll(this.all_layers);
synchronized (ls.IDLAYERS_WRITE_LOCK) {
ls.idlayers = new HashMap<Long,Layer>(this.idlayers);
}
synchronized (ls.layerindices) {
ls.layerindices.clear();
ls.layerindices.putAll(this.layerindices);
}
// Replace all Displayable in each Layer
for (final Map.Entry<Layer,ArrayList<Displayable>> e : all_displ.entrySet()) {
// Acquire pointer to the actual instance list in each Layer
final ArrayList<Displayable> al = e.getKey().getDisplayableList(); // the real one!
// Create a list to contain those Displayable present in old list but not in list to use now
final HashSet<Displayable> diff = new HashSet<Displayable>(al); // create with all Displayable of old list
diff.removeAll(e.getValue()); // remove all Displayable present in list to use now, to leave the diff or remainder only
// Clear current list
al.clear();
// Insert all to the current list
al.addAll(e.getValue());
// Add to remove-on-shutdown queue all those Patch no longer in the list to use now:
for (final Displayable d : diff) {
if (d.getClass() == Patch.class) {
d.getProject().getLoader().tagForMipmapRemoval((Patch)d, true);
}
}
// Remove from queue all those Patch in the list to use now:
for (final Displayable d : al) {
if (d.getClass() == Patch.class) {
d.getProject().getLoader().tagForMipmapRemoval((Patch)d, false);
}
}
}
// Replace all ZDisplayable
ls.al_zdispl.clear();
ls.al_zdispl.addAll(this.all_zdispl);
// Replace all links
for (final Map.Entry<Displayable,Set<Displayable>> e : this.links.entrySet()) {
final Set<Displayable> hs = e.getKey().hs_linked;
if (null != hs) {
final Set<Displayable> hs2 = e.getValue();
if (null == hs2) e.getKey().hs_linked = null;
else {
hs.clear();
hs.addAll(hs2);
}
}
}
// Invoke dependents
if (null != dependents) for (DoStep step : dependents) step.apply(action);
ls.recreateBuckets(true);
Display.clearSelection(ls.project);
Display.update(ls, false);
return true;
}
synchronized public void addDependents(Set<DoStep> dep) {
if (null == this.dependents) this.dependents = new HashSet<DoStep>();
this.dependents.addAll(dep);
}
}
/** To undo moving up/down/top/bottom. */
public DoStep createUndoMoveStep(final Displayable d) {
return d instanceof ZDisplayable ?
new LayerSet.DoMoveZDisplayable(this)
: new Layer.DoMoveDisplayable(d.getLayer());
}
/** To undo moving up/down/top/bottom. */
public void addUndoMoveStep(final Displayable d) {
addUndoStep(createUndoMoveStep(d));
}
static protected class DoMoveZDisplayable implements DoStep {
final ArrayList<ZDisplayable> al_zdispl;
final LayerSet ls;
HashSet<DoStep> dependents = null;
DoMoveZDisplayable(final LayerSet ls) {
this.ls = ls;
this.al_zdispl = new ArrayList<ZDisplayable>(ls.al_zdispl);
}
@Override
public boolean apply(int action) {
// Replace all ZDisplayable
ls.al_zdispl.clear();
ls.al_zdispl.addAll(this.al_zdispl);
Display.update(ls, false);
return true;
}
@Override
public boolean isEmpty() {
return false;
}
@Override
public Displayable getD() {
return null;
}
@Override
public boolean isIdenticalTo(Object ob) {
if (!(ob instanceof DoMoveZDisplayable)) return false;
final DoMoveZDisplayable dmz = (DoMoveZDisplayable)ob;
if (dmz.ls != this.ls) return false;
if (dmz.al_zdispl.size() != this.al_zdispl.size()) return false;
for (int i=0; i<this.al_zdispl.size(); ++i) {
if (dmz.al_zdispl.get(i) != this.al_zdispl.get(i)) return false;
}
return true;
}
}
private Overlay overlay = null;
/** Return the current Overlay or a new one if none yet. */
synchronized public Overlay getOverlay() {
if (null == overlay) overlay = new Overlay();
return overlay;
}
// Used by DisplayCanvas to paint
Overlay getOverlay2() {
return overlay;
}
/** Set to null to remove the Overlay.
* @return the previous Overlay, if any. */
synchronized public Overlay setOverlay(final Overlay o) {
Overlay old = this.overlay;
this.overlay = o;
return old;
}
private final HashMap<DisplayCanvas.ScreenshotProperties,DisplayCanvas.Screenshot> offscreens = new HashMap<DisplayCanvas.ScreenshotProperties,DisplayCanvas.Screenshot>();
private final HashMap<Layer,HashSet<DisplayCanvas.Screenshot>> offscreens2 = new HashMap<Layer,HashSet<DisplayCanvas.Screenshot>>();
final DisplayCanvas.Screenshot getScreenshot(final DisplayCanvas.ScreenshotProperties props) {
synchronized (offscreens) {
return offscreens.get(props);
}
}
final private void putO2(final Layer la, final DisplayCanvas.Screenshot sc) {
HashSet<DisplayCanvas.Screenshot> hs = offscreens2.get(la);
if (null == hs) {
hs = new HashSet<DisplayCanvas.Screenshot>();
offscreens2.put(la, hs);
}
hs.add(sc);
}
final private void removeO2(final DisplayCanvas.Screenshot sc) {
HashSet<DisplayCanvas.Screenshot> hs = offscreens2.get(sc.layer);
if (null == hs) return;
hs.remove(sc);
}
final void storeScreenshot(DisplayCanvas.Screenshot s) {
synchronized(offscreens) {
offscreens.put(s.props, s);
putO2(s.layer, s);
}
}
final void clearScreenshots() {
synchronized (offscreens) {
for (final DisplayCanvas.Screenshot s : offscreens.values()) {
s.flush();
}
offscreens.clear();
offscreens2.clear();
}
}
final void trimScreenshots() {
synchronized(offscreens) {
if (offscreens.size() > 1000) {
TreeMap<Long,DisplayCanvas.Screenshot> m = new TreeMap<Long,DisplayCanvas.Screenshot>();
for (final DisplayCanvas.Screenshot s : offscreens.values()) {
m.put(s.born, s);
}
offscreens.clear();
offscreens2.clear();
ArrayList<Long> t = new ArrayList<Long>(m.keySet());
for (final DisplayCanvas.Screenshot sc : m.subMap(m.firstKey(), t.get(t.size()/2)).values()) {
offscreens.put(sc.props, sc);
putO2(sc.layer, sc);
}
// not flushing: they will get thrown out eventually
}
}
}
final void removeFromOffscreens(final DisplayCanvas.Screenshot sc) {
synchronized (offscreens) {
offscreens.remove(sc.props);
removeO2(sc);
}
}
public final void removeFromOffscreens(final Layer la) {
synchronized (offscreens) {
final HashSet<DisplayCanvas.Screenshot> hs = offscreens2.remove(la);
if (null != hs) {
for (final DisplayCanvas.Screenshot sc : hs) {
offscreens.remove(sc.props);
}
}
}
}
final void removeFromOffscreens(final ZDisplayable zd) {
synchronized (offscreens) {
// Throw away any cached that intersect the zd
final Rectangle box = zd.getBoundingBox();
for (final Iterator<DisplayCanvas.Screenshot> it = offscreens.values().iterator(); it.hasNext(); ) {
final DisplayCanvas.Screenshot sc = it.next();
if (box.intersects(sc.props.srcRect)) {
it.remove();
final HashSet<DisplayCanvas.Screenshot> hs = offscreens2.get(sc.layer);
if (null != hs) hs.remove(sc.props);
}
}
}
}
final boolean containsScreenshot(final DisplayCanvas.Screenshot sc) {
synchronized (offscreens) {
return offscreens.containsKey(sc.props);
}
}
/** Find all java.awt.geom.Area in layer that intersect with box, if visible.
* Areas are returned as they are, with coords local to the Displayable they come from.
* Modifying the Area instances will modify the actual data in the AreaContainer Displayable. */
protected Map<Displayable,List<Area>> findAreas(final Layer layer, final Rectangle box, final boolean visible) {
final Map<Displayable,List<Area>> m = new HashMap<Displayable,List<Area>>();
for (final Displayable zd : findZDisplayables(layer, box, visible)) {
if (!(zd instanceof AreaContainer)) continue;
List<Area> a = ((AreaContainer)zd).getAreas(layer, box);
if (null == a) continue;
m.put(zd, a);
}
return m;
}
/** A set of unique tags, retrievable by their own identity. */
protected final Map<Integer,HashMap<String,Tag>> tags = new HashMap<Integer,HashMap<String,Tag>>();
{
final Tag TODO = new Tag("TODO", KeyEvent.VK_T),
UNCERTAIN_END = new Tag("Uncertain end", KeyEvent.VK_U);
final HashMap<String,Tag> m1 = new HashMap<String,Tag>(),
m2 = new HashMap<String,Tag>();
m1.put(TODO.toString(), TODO);
m2.put(UNCERTAIN_END.toString(), UNCERTAIN_END);
tags.put(KeyEvent.VK_T, m1);
tags.put(KeyEvent.VK_U, m2);
}
/** Returns an existing immutable Tag instance if already there, or stores a new one and returns it. */
public Tag putTag(final String tag, final int keyCode) {
if (null == tag) return null;
synchronized (tags) {
HashMap<String,Tag> ts = tags.get(keyCode);
if (null == ts) {
ts = new HashMap<String,Tag>();
tags.put(keyCode, ts);
}
final Tag t = new Tag(tag, keyCode);
final Tag existing = ts.get(t);
if (null == existing) {
ts.put(t.toString(), t);
return t;
} else {
return existing;
}
}
}
/** If there aren't any tags for keyCode, returns an empty TreeSet. */
@SuppressWarnings("unchecked")
public TreeSet<Tag> getTags(final int keyCode) {
synchronized (tags) {
final HashMap<String,Tag> ts = tags.get(keyCode);
return new TreeSet<Tag>(null == ts ? Collections.EMPTY_SET :
KeyEvent.VK_R == keyCode ? filterReviewTags(ts.values()) :
ts.values());
}
}
private final Collection<Tag> filterReviewTags(final Collection<Tag> ts) {
final ArrayList<Tag> a = new ArrayList<Tag>();
for (final Tag tag : ts) {
if ('#' == tag.toString().charAt(0)) continue;
else a.add(tag);
}
return a;
}
protected Tag askForNewTag(final int keyCode) {
GenericDialog gd = new GenericDialog("Define new tag");
gd.addMessage("Define new tag for key: " + ((char)keyCode));
TreeSet<Tag> ts = getTags(keyCode);
gd.addStringField("New tag:", "", 40);
if (null != ts && ts.size() > 0) {
String[] names = new String[ts.size()];
int next = 0;
for (Tag t : ts) names[next++] = t.toString();
gd.addChoice("Existing tags for " + ((char)keyCode) + ":", names, names[0]);
}
gd.showDialog();
if (gd.wasCanceled()) return null;
String tag = gd.getNextString().trim();
if (0 == tag.length()) {
Utils.logAll("Invalid tag " + tag);
return null;
}
return putTag(tag, keyCode);
}
/** Returns false if the dialog was canceled or there wasn't any tag to remove. */
protected boolean askToRemoveTag(final int keyCode) {
TreeSet<Tag> ts = getTags(keyCode);
if (null == ts || ts.isEmpty()) return false;
String[] tags = new String[ts.size()];
int next = 0;
for (Tag t : ts) tags[next++] = t.toString();
GenericDialog gd = new GenericDialog("Remove tag");
gd.addMessage("Remove a tag for key: " + ((char)keyCode));
gd.addChoice("Remove:", tags, tags[0]);
gd.showDialog();
if (gd.wasCanceled()) return false;
String tag = gd.getNextChoice();
removeTag(tag, keyCode);
return true;
}
/** Removes the tag from the list of possible tags, and then from wherever it has been assigned. The @param tag is duck-typed. */
public void removeTag(final String tag, final int keyCode) {
removeTag(new Tag(tag, keyCode));
}
public void removeTag(final Tag t) {
synchronized (tags) {
HashMap<String,Tag> ts = tags.get(t.getKeyCode());
if (null == ts) return;
ts.remove(t.toString());
}
for (final Displayable d : getDisplayables()) {
d.removeTag(t);
}
for (final ZDisplayable zd : al_zdispl) {
zd.removeTag(t);
}
Display.repaint(this);
}
public void removeAllTags() {
// the easy and unperformant way ... I have better things to do
for (final HashMap<String,Tag> m : tags.values()) {
for (final Tag t : m.values()) {
removeTag(t);
}
}
}
public String exportTags() {
StringBuilder sb = new StringBuilder("<tags>\n");
for (final Map.Entry<Integer,HashMap<String,Tag>> e : tags.entrySet()) {
final char key = (char)e.getKey().intValue();
for (final Tag t : e.getValue().values()) {
sb.append(" <tag key=\"").append(key).append("\" val=\"").append(t.toString()).append("\" />\n");
}
}
return sb.append("</tags>").toString();
}
public void importTags(String path, boolean replace) {
HashMap<Integer,HashMap<String,Tag>> backup = new HashMap<Integer,HashMap<String,Tag>>(this.tags); // copy!
InputStream istream = null;
try {
if (replace) removeAllTags();
SAXParserFactory f = SAXParserFactory.newInstance();
f.setValidating(false);
SAXParser parser = f.newSAXParser();
istream = Utils.createStream(path);
parser.parse(new InputSource(istream), new TagsParser());
} catch (Throwable t) {
IJError.print(t);
// restore:
this.tags.clear();
this.tags.putAll(backup);
// no undo for all potentially removed tags ...
} finally {
try {
if (null != istream) istream.close();
} catch (Exception e) {}
}
}
private class TagsParser extends DefaultHandler {
public void startElement(String uri, String localName, String qName, Attributes attributes) {
if (!"tag".equals(qName.toLowerCase())) return;
final HashMap<String,String> m = new HashMap<String,String>();
for (int i=attributes.getLength() -1; i>-1; i--) {
m.put(attributes.getQName(i).toLowerCase(), attributes.getValue(i));
}
final String key = m.get("key"),
content = m.get("val");
if (null == key || key.length() > 1 || Character.isDigit(key.charAt(0)) || null == content) {
Utils.log("Ignoring invalid tag with key '" + key + "' and value '" + content + "'");
return;
}
putTag(content, (int)key.charAt(0));
}
}
// ==== GET ZDisplayable objects ====
//
// ESSENTIALLY, a filter operation on the al_zdispl list.
public ArrayList<ZDisplayable> getZDisplayables(final Class<?> c) {
return getZDisplayables(c, false);
}
/** Returns a list of ZDisplayable of class c only.
* If @param instance_of, use c.isInstance(...) instead of class equality. */
public ArrayList<ZDisplayable> getZDisplayables(final Class<?> c, final boolean instance_of) {
final ArrayList<ZDisplayable> al = new ArrayList<ZDisplayable>();
if (null == c) return al;
if (Displayable.class == c || ZDisplayable.class == c) {
al.addAll(al_zdispl);
return al;
}
if (instance_of) {
for (final ZDisplayable zd : al_zdispl) {
if (c.isInstance(zd)) al.add(zd);
}
} else {
for (final ZDisplayable zd : al_zdispl) {
if (zd.getClass() == c) al.add(zd);
}
}
return al;
}
// FILTER operations but also by an Area in a given Layer:
/** Use method findZDisplayables(...) instead. */
@Deprecated
public ArrayList<ZDisplayable> getZDisplayables(final Class<?> c, final Layer layer, final Area aroi, final boolean visible_only) {
return getZDisplayables(c, layer, aroi, visible_only, false);
}
/** Use method findZDisplayables(...) instead. */
@SuppressWarnings({ "unchecked", "rawtypes" })
@Deprecated
public ArrayList<ZDisplayable> getZDisplayables(final Class<?> c, final Layer layer, final Area aroi, final boolean visible_only, final boolean instance_of) {
if (!ZDisplayable.class.isAssignableFrom(c)) return new ArrayList<ZDisplayable>();
return new ArrayList<ZDisplayable>((Collection<ZDisplayable>)(Collection)findZDisplayables(c, layer, aroi, visible_only, instance_of));
}
// ============== FIND Displayable or ZDisplayable onjects ============
/** Find any Displayable or ZDisplayable objects of class C which intersect with the Area @param aroi. If @param visible_only, then only those that are not hidden. */
public ArrayList<Displayable> find(final Class<?> c, final Layer layer, final Area aroi, final boolean visible_only) {
return find(c, layer, aroi, visible_only, false);
}
/** Find any Displayable or ZDisplayable objects of class C which intersect with the Area @param aroi. If @param visible_only, then only those that are not hidden. If @param instance_of is true, then classes are not check by equality but by instanceof. */
public ArrayList<Displayable> find(final Class<?> c, final Layer layer, final Area aroi, final boolean visible_only, final boolean instance_of) {
final ArrayList<Displayable> al = new ArrayList<Displayable>();
if (!ZDisplayable.class.isAssignableFrom(c)) {
al.addAll(layer.getDisplayables(c, aroi, visible_only, instance_of));
}
al.addAll(findZDisplayables(c, layer, aroi, visible_only, instance_of));
return al;
}
public ArrayList<Displayable> find(final Class<?> c, final Layer layer, final int x, final int y, final boolean visible_only) {
final ArrayList<Displayable> al = new ArrayList<Displayable>();
if (!ZDisplayable.class.isAssignableFrom(c)) {
al.addAll(layer.find(c, x, y, visible_only));
}
al.addAll(findZDisplayables(c, layer, x, y, visible_only));
return al;
}
// ======== FIND ZDisplayable only =======
/** Find ZDisplayable objects that contain the point x,y in the given layer. */
public Collection<Displayable> findZDisplayables(final Layer layer, final int x, final int y, final boolean visible_only) {
final LayerBucket lb;
synchronized (lbucks) {
lb = lbucks.get(layer);
}
if (null != lb) return lb.root.find(x, y, layer, visible_only);
else nbmsg(layer);
final ArrayList<Displayable> al = new ArrayList<Displayable>();
for (final ZDisplayable zd : al_zdispl) {
if (zd.contains(layer, x, y)) al.add(zd);
}
return al;
}
/** Find ZDisplayable objects of Class c that contain the point x,y in the given layer. */
public Collection<Displayable> findZDisplayables(final Class<?> c, final Layer layer, final int x, final int y, final boolean visible_only) {
return findZDisplayables(c, layer, x, y, visible_only, false);
}
/** Find ZDisplayable objects of Class c that contain the point x,y in the given layer. */
public Collection<Displayable> findZDisplayables(final Class<?> c, final Layer layer, final int x, final int y, final boolean visible_only, final boolean instance_of) {
final LayerBucket lb;
synchronized (lbucks) {
lb = lbucks.get(layer);
}
if (null != lb) return lb.root.find(c, x, y, layer, visible_only, instance_of);
else nbmsg(layer);
final ArrayList<Displayable> al = new ArrayList<Displayable>();
for (final ZDisplayable zd : al_zdispl) {
if (zd.getClass() == c && zd.contains(layer, x, y)) al.add(zd);
}
return al;
}
public Collection<Displayable> findZDisplayables(final Class<?> c, final Layer layer, final Rectangle r, final boolean visible_only) {
return findZDisplayables(c, layer, r, visible_only, false);
}
/** Find ZDisplayable objects of the given class that intersect the given rectangle in the given layer. */
public Collection<Displayable> findZDisplayables(final Class<?> c, final Layer layer, final Rectangle r, final boolean visible_only, final boolean instance_of) {
final LayerBucket lb;
synchronized (lbucks) {
lb = lbucks.get(layer);
}
if (null != lb) return lb.root.find(c, r, layer, visible_only, instance_of);
else nbmsg(layer);
final ArrayList<Displayable> al = new ArrayList<Displayable>();
for (final ZDisplayable zd : al_zdispl) {
if (instance_of && !c.isInstance(zd)) continue;
else if (zd.getClass() != c) continue;
if (zd.getBounds(null, layer).intersects(r)) al.add(zd);
}
return al;
}
/** Find ZDisplayable objects of the given class that intersect the given area in the given layer.
* If @param instance_of is true, use c.isAssignableFrom instead of class equality. */
public Collection<Displayable> findZDisplayables(final Class<?> c, final Layer layer, final Area aroi, final boolean visible_only, final boolean instance_of) {
final LayerBucket lb;
synchronized (lbucks) {
lb = lbucks.get(layer);
}
if (null != lb) return lb.root.find(c, aroi, layer, visible_only, instance_of);
else nbmsg(layer);
final ArrayList<Displayable> al = new ArrayList<Displayable>();
for (final ZDisplayable zd : al_zdispl) {
if (visible_only && !zd.isVisible()) continue;
if (instance_of) {
if (!c.isAssignableFrom(zd.getClass())) continue;
} else if (zd.getClass() != c) continue;
if (zd.intersects(layer, aroi)) al.add(zd);
}
return al;
}
/** Find ZDisplayable objects that intersect the given rectangle in the given layer. */
public Collection<Displayable> findZDisplayables(final Layer layer, final Rectangle r, final boolean visible_only) {
final LayerBucket lb;
synchronized (lbucks) {
lb = lbucks.get(layer);
}
if (null != lb) return lb.root.find(r, layer, visible_only);
else nbmsg(layer);
final ArrayList<Displayable> al = new ArrayList<Displayable>();
for (final ZDisplayable zd : al_zdispl) {
if (visible_only && !zd.isVisible()) continue;
if (zd.getBounds(null, layer).intersects(r)) al.add(zd);
}
return al;
}
/** Find ZDisplayable objects that intersect the given rectangle in the given layer.
* May return false positives but never false negatives. */
public Collection<Displayable> roughlyFindZDisplayables(final Layer layer, final Rectangle r, final boolean visible_only) {
final LayerBucket lb;
synchronized (lbucks) {
lb = lbucks.get(layer);
}
if (null != lb) return lb.root.roughlyFind(r, layer, visible_only);
else nbmsg(layer);
// Else, linear:
final ArrayList<Displayable> al = new ArrayList<Displayable>();
for (final ZDisplayable zd : al_zdispl) {
if (visible_only && !zd.isVisible()) continue;
if (zd.getBounds(null, layer).intersects(r)) al.add(zd);
}
return al;
}
private static final void nbmsg(final Layer la) {
Utils.log2("No buckets for layer " + la);
}
/** Get all Displayable or ZDisplayable of the given class.
* Classes are tested by equality, except for ZDisplayable.class.
* Will also consider Displayable.class and subclasses in
* a similar fashion, by calling Layer.getAll(c). */
@SuppressWarnings("unchecked")
public<T extends Displayable> List<T> getAll(final Class<T> c) {
final ArrayList<T> al = new ArrayList<T>();
if (null == c) return al;
if (ZDisplayable.class == c) {
al.addAll((Collection<T>)al_zdispl);
} else if (ZDisplayable.class.isAssignableFrom(c)) {
for (final ZDisplayable d : al_zdispl) {
if (d.getClass() == c) al.add((T)d);
}
} else if (Displayable.class == c) {
for (final Layer la : al_layers) {
al.addAll((Collection<T>)la.getDisplayables());
}
al.addAll((Collection<T>)al_zdispl);
} else if (Displayable.class.isAssignableFrom(c)) {
for (final Layer la : al_layers) {
al.addAll(la.getAll(c));
}
}
return al;
}
}