Use internal SNAPSHOT couplings again
[trakem2.git] / TrakEM2_ / src / main / java / ini / trakem2 / display / LayerSet.java
blob296cbd9da52bdcd5aff07cfe63f3724a2ccb1193
1 /**
3 TrakEM2 plugin for ImageJ(C).
4 Copyright (C) 2005-2009 Albert Cardona and Rodney Douglas.
6 This program is free software; you can redistribute it and/or
7 modify it under the terms of the GNU General Public License
8 as published by the Free Software Foundation (http://www.gnu.org/licenses/gpl.txt )
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19 You may contact Albert Cardona at acardona at ini.phys.ethz.ch
20 Institute of Neuroinformatics, University of Zurich / ETH, Switzerland.
21 **/
23 package ini.trakem2.display;
26 import ij.ImagePlus;
27 import ij.ImageStack;
28 import ij.gui.GenericDialog;
29 import ij.measure.Calibration;
30 import ini.trakem2.ControlWindow;
31 import ini.trakem2.Project;
32 import ini.trakem2.imaging.LayerStack;
33 import ini.trakem2.parallel.Process;
34 import ini.trakem2.parallel.TaskFactory;
35 import ini.trakem2.persistence.DBObject;
36 import ini.trakem2.persistence.XMLOptions;
37 import ini.trakem2.tree.LayerThing;
38 import ini.trakem2.tree.ProjectThing;
39 import ini.trakem2.tree.TemplateThing;
40 import ini.trakem2.tree.Thing;
41 import ini.trakem2.utils.IJError;
42 import ini.trakem2.utils.ProjectToolbar;
43 import ini.trakem2.utils.Utils;
45 import java.awt.AlphaComposite;
46 import java.awt.Color;
47 import java.awt.Composite;
48 import java.awt.Graphics2D;
49 import java.awt.Image;
50 import java.awt.Rectangle;
51 import java.awt.event.KeyEvent;
52 import java.awt.event.MouseEvent;
53 import java.awt.geom.AffineTransform;
54 import java.awt.geom.Area;
55 import java.io.InputStream;
56 import java.util.ArrayList;
57 import java.util.Collection;
58 import java.util.Collections;
59 import java.util.HashMap;
60 import java.util.HashSet;
61 import java.util.Iterator;
62 import java.util.List;
63 import java.util.Map;
64 import java.util.Set;
65 import java.util.TreeMap;
66 import java.util.TreeSet;
68 import javax.xml.parsers.SAXParser;
69 import javax.xml.parsers.SAXParserFactory;
71 import org.xml.sax.Attributes;
72 import org.xml.sax.InputSource;
73 import org.xml.sax.helpers.DefaultHandler;
76 /** A LayerSet is a container for a list of Layer.
77 * 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.
79 public final class LayerSet extends Displayable implements Bucketable { // Displayable is already extending DBObject
81 // the anchors for resizing
82 static public final int NORTH = 0;
83 static public final int NORTHEAST = 1;
84 static public final int EAST = 2;
85 static public final int SOUTHEAST = 3;
86 static public final int SOUTH = 4;
87 static public final int SOUTHWEST = 5;
88 static public final int WEST = 6;
89 static public final int NORTHWEST = 7;
90 static public final int CENTER = 8;
92 // the possible rotations
93 static public final int R90 = 9;
94 static public final int R270 = 10;
95 // the possible flips
96 static public final int FLIP_HORIZONTAL = 11;
97 static public final int FLIP_VERTICAL = 12;
99 // positions in the stack
100 static public final int TOP = 13;
101 static public final int UP = 14;
102 static public final int DOWN = 15;
103 static public final int BOTTOM = 16;
105 static public final String[] snapshot_modes = new String[]{"Full","Outlines","Disabled"};
107 /** 0, 1, 2 -- corresponding to snapshot_modes entries above. */
108 private int snapshots_mode = 0;
110 static public final String[] ANCHORS = new String[]{"north", "north east", "east", "southeast", "south", "south west", "west", "north west", "center"};
111 static public final String[] ROTATIONS = new String[]{"90 right", "90 left", "Flip horizontally", "Flip vertically"};
113 private float layer_width = 5000, // the Displayable.width is for the representation, not for the dimensions of the LayerSet!
114 layer_height = 5000;
115 private double rot_x;
116 private double rot_y;
117 private double rot_z; // should be equivalent to the Displayable.rot
118 private final ArrayList<Layer> al_layers = new ArrayList<Layer>();
120 /** A map of Long vs Layer, that is lock-free for reading, but locks for modifying it,
121 * by synchronizing onto IDLAYERS_WRITE_LOCK. */
122 private HashMap<Long,Layer> idlayers = new HashMap<Long,Layer>();
123 private final Object IDLAYERS_WRITE_LOCK = new Object();
125 private final HashMap<Layer,Integer> layerindices = new HashMap<Layer,Integer>();
126 /** The layer in which this LayerSet lives. If null, this is the root LayerSet. */
127 private Layer parent = null;
128 /** A LayerSet can contain Displayables that are show in every single Layer, such as Pipe objects. */
129 private final ArrayList<ZDisplayable> al_zdispl = new ArrayList<ZDisplayable>();
131 /** For creating snapshots. */
132 private boolean snapshots_quality = true;
134 /** The maximum size of either width or height when virtualizing pixel access to the layers.*/
135 private int max_dimension = 1024;
136 private boolean virtualization_enabled = false;
138 protected boolean color_cues = true;
139 protected boolean area_color_cues = true;
140 protected boolean use_color_cue_colors = true;
141 protected boolean paint_arrows = true;
142 protected boolean paint_tags = true;
143 protected boolean paint_edge_confidence_boxes = true;
144 protected int n_layers_color_cue = 0; // -1 means all
145 protected boolean prepaint = false;
146 protected int preload_ahead = 0;
148 private Calibration calibration = new Calibration(); // default values
150 /** Dummy. */
151 protected LayerSet(Project project, long id) {
152 super(project, id, null, false, null, 20, 20);
155 /** Create a new LayerSet with a 0,0,0 rotation vector and default 20,20 px Displayable width,height. */
156 public LayerSet(Project project, String title, double x, double y, Layer parent, float layer_width, float layer_height) {
157 super(project, title, x, y);
158 rot_x = rot_y = rot_z = 0.0D;
159 this.width = 20;
160 this.height = 20; // for the label that paints into the parent Layer
161 this.parent = parent;
162 this.layer_width = layer_width;
163 this.layer_height = layer_height;
164 addToDatabase();
167 /** Reconstruct from the database. */
168 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) {
169 super(project, id, title, locked, at, width, height);
170 this.rot_x = rot_x;
171 this.rot_y = rot_y;
172 this.rot_z = rot_z;
173 this.layer_width = layer_width;
174 this.layer_height= layer_height;
175 this.snapshots_mode = snapshots_mode;
176 // the parent will be set by the LayerThing.setup() calling Layer.addSilently()
177 // the al_layers will be filled idem.
180 /** Reconstruct from an XML entry. */
181 public LayerSet(final Project project, final long id, final HashMap<String,String> ht_attributes, final HashMap<Displayable,String> ht_links) {
182 super(project, id, ht_attributes, ht_links);
183 String data;
184 if (null != (data = ht_attributes.get("layer_width"))) this.layer_width = Float.parseFloat(data);
185 else xmlError("layer_width", this.layer_width);
186 if (null != (data = ht_attributes.get("layer_height"))) this.layer_height = Float.parseFloat(data);
187 else xmlError("layer_height", this.layer_height);
188 if (null != (data = ht_attributes.get("rot_x"))) this.rot_x = Double.parseDouble(data);
189 else xmlError("rot_x", this.rot_x);
190 if (null != (data = ht_attributes.get("rot_y"))) this.rot_y = Double.parseDouble(data);
191 else xmlError("rot_y", this.rot_y);
192 if (null != (data = ht_attributes.get("rot_z"))) this.rot_y = Double.parseDouble(data);
193 else xmlError("rot_z", this.rot_z);
194 if (null != (data = ht_attributes.get("snapshots_quality"))) snapshots_quality = Boolean.valueOf(data.trim().toLowerCase());
195 if (null != (data = ht_attributes.get("snapshots_mode"))) {
196 final String smode = data.trim();
197 for (int i=0; i<snapshot_modes.length; i++) {
198 if (smode.equals(snapshot_modes[i])) {
199 snapshots_mode = i;
200 break;
204 if (null != (data = ht_attributes.get("color_cues"))) color_cues = Boolean.valueOf(data.trim().toLowerCase());
205 if (null != (data = ht_attributes.get("area_color_cues"))) area_color_cues = Boolean.valueOf(data.trim().toLowerCase());
206 if (null != (data = ht_attributes.get("n_layers_color_cue"))) {
207 n_layers_color_cue = Integer.parseInt(data.trim().toLowerCase());
208 if (n_layers_color_cue < -1) n_layers_color_cue = -1;
210 if (null != (data = ht_attributes.get("avoid_color_cue_colors"))) {
211 // If there's any error in the parsing, default to true for use_color_cue_colors:
212 use_color_cue_colors = !Boolean.valueOf(data.trim().toLowerCase());
214 if (null != (data = ht_attributes.get("paint_arrows"))) paint_arrows = Boolean.valueOf(data.trim().toLowerCase());
215 if (null != (data = ht_attributes.get("paint_tags"))) paint_tags = Boolean.valueOf(data.trim().toLowerCase());
216 if (null != (data = ht_attributes.get("paint_edge_confidence_boxes"))) paint_edge_confidence_boxes = Boolean.valueOf(data.trim().toLowerCase());
217 if (null != (data = ht_attributes.get("prepaint"))) prepaint = Boolean.valueOf(data.trim().toLowerCase());
218 if (null != (data = ht_attributes.get("preload_ahead"))) preload_ahead = Integer.parseInt(data);
221 /** For reconstruction purposes: set the active layer to the ZDisplayable objects. Recurses through LayerSets in the children layers. */
222 public void setup() {
223 final Layer la0 = al_layers.get(0);
224 for (ZDisplayable zd : al_zdispl) zd.setLayer(la0); // just any Layer
225 for (Layer layer : al_layers) {
226 for (final Displayable d : layer.getDisplayables()) {
227 if (d.getClass() == LayerSet.class) {
228 ((LayerSet)d).setup();
234 /** Create a new LayerSet in the middle of the parent Layer. */
235 public LayerSet create(Layer parent_layer) {
236 if (null == parent_layer) return null;
237 GenericDialog gd = ControlWindow.makeGenericDialog("New Layer Set");
238 gd.addMessage("In pixels:");
239 gd.addNumericField("width: ", this.layer_width, 3);
240 gd.addNumericField("height: ", this.layer_height, 3);
241 gd.showDialog();
242 if (gd.wasCanceled()) return null;
243 try {
244 float width = (float)gd.getNextNumber();
245 float height = (float)gd.getNextNumber();
246 if (Double.isNaN(width) || Double.isNaN(height)) return null;
247 if (0 == width || 0 == height) {
248 Utils.showMessage("Cannot accept zero width or height for LayerSet dimensions.");
249 return null;
251 // make a new LayerSet with x,y in the middle of the parent_layer
252 return new LayerSet(project, "Layer Set", parent_layer.getParent().getLayerWidth() / 2, parent_layer.getParent().getLayerHeight() / 2, parent_layer, width/2, height/2);
253 } catch (Exception e) { Utils.log("LayerSet.create: " + e); }
254 return null;
257 /** Add a new Layer silently, ordering by z as well.*/
258 public void addSilently(final Layer layer) {
259 if (null == layer || al_layers.contains(layer)) return;
260 try {
261 synchronized (IDLAYERS_WRITE_LOCK) {
262 // Like put, but replacing the map instance
263 final HashMap<Long,Layer> m = new HashMap<Long,Layer>(idlayers);
264 m.put(layer.getId(), layer);
265 idlayers = m;
267 synchronized (layerindices) { layerindices.clear(); }
268 double z = layer.getZ();
269 int i = 0;
270 for (final Layer la : al_layers) {
271 if (! (la.getZ() < z) ) {
272 al_layers.add(i, layer);
273 layer.setParentSilently(this);
274 return;
276 i++;
278 // else, add at the end
279 al_layers.add(layer);
280 layer.setParentSilently(this);
281 } catch (Exception e) {
282 Utils.log("LayerSet.addSilently: Not a Layer, not adding DBObject id=" + layer.getId());
283 return;
287 /** Add a new Layer, inserted according to its Z. */
288 public void add(final Layer layer) {
289 if (layer.getProject() != this.project)
290 throw new IllegalArgumentException("LayerSet rejected a Layer: belongs to a different project.");
292 if (null != idlayers.get(layer.getId())) return;
294 final double z = layer.getZ();
295 final int n = al_layers.size();
296 int i = 0;
297 for (; i<n; i++) {
298 Layer l = al_layers.get(i);
299 if (l.getZ() < z) continue;
300 break;
302 if (i < n) {
303 al_layers.add(i, layer);
304 } else {
305 al_layers.add(layer);
307 layer.setParent(this);
308 synchronized (IDLAYERS_WRITE_LOCK) {
309 // Like put, but replacing the map instance
310 final HashMap<Long,Layer> m = new HashMap<Long,Layer>(idlayers);
311 m.put(layer.getId(), layer);
312 idlayers = m;
314 synchronized (layerindices) { layerindices.clear(); }
315 Display.updateLayerScroller(this);
316 //debug();
319 public void printDebugInfo() {
320 Utils.log("LayerSet debug:");
321 for (int i=0; i<al_layers.size(); i++)
322 Utils.log(i + " : " + al_layers.get(i).getZ());
325 public Layer getParent() {
326 return parent;
329 /** 'update' in database or not. */
330 public void setLayer(Layer layer, boolean update) {
331 super.setLayer(layer, update);
332 if (null != layer) this.parent = layer; // repeated pointer, eliminate 'parent' !
335 public void setParent(Layer layer) {
336 if (null == layer || layer == parent) return;
337 this.parent = layer;
338 updateInDatabase("parent_id");
341 public void mousePressed(MouseEvent me, int x_p, int y_p, Rectangle srcRect, double mag) {
342 if (ProjectToolbar.SELECT != ProjectToolbar.getToolId()) return;
343 Display.setActive(me, this);
344 if (2 == me.getClickCount() && al_layers.size() > 0) {
345 new Display(project, al_layers.get(0));
349 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) {
350 if (ProjectToolbar.SELECT != ProjectToolbar.getToolId()) return;
351 super.translate(x_d - x_d_old, y_d - y_d_old);
352 Display.repaint(layer, this, 0);
355 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) {
356 // nothing
359 public void keyPressed(KeyEvent ke) {
360 Utils.log("LayerSet.keyPressed: not yet implemented.");
361 // TODO
364 public String toString() {
365 return this.title;
368 public void paint(Graphics2D g, Rectangle srcRect, double magnification, boolean active, int channels, Layer active_layer) {
369 //arrange transparency
370 Composite original_composite = null;
371 if (alpha != 1.0f) {
372 original_composite = g.getComposite();
373 g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha));
376 // Translate graphics context accordingly
377 AffineTransform gt = g.getTransform();
378 AffineTransform aff = new AffineTransform(this.at);
379 aff.preConcatenate(gt);
380 g.setTransform(aff);
382 //set color
383 g.setColor(this.color);
384 // fill a background box
385 g.fillRect(0, 0, (int)(this.width), (int)(this.height));
386 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
387 int x = (int)(this.width/5);
388 int y = (int)(this.height/5);
389 int width = (int)(this.width/5);
390 int height = (int)(this.height/5 * 3);
392 g.fillRect(x, y, width, height);
394 x = (int)(this.width/5 * 2);
395 y = (int)(this.height/5 * 3);
396 width = (int)(this.width/5 * 2);
397 height = (int)(this.height/5);
399 g.fillRect(x, y, width, height);
401 //Transparency: fix composite back to original.
402 if (alpha != 1.0f) {
403 g.setComposite(original_composite);
405 g.setTransform(gt);
408 public float getLayerWidth() { return layer_width; }
409 public float getLayerHeight() { return layer_height; }
410 public double getRotX() { return rot_x; }
411 public double getRotY() { return rot_y; }
412 public double getRotZ() { return rot_z; }
414 /** The number of Layers in this LayerSet. */
415 public int size() {
416 return al_layers.size();
419 public void setRotVector(double rot_x, double rot_y, double rot_z) {
420 if (Double.isNaN(rot_x) || Double.isNaN(rot_y) || Double.isNaN(rot_z)) {
421 Utils.showMessage("LayerSet: Rotation vector contains NaNs. Not updating.");
422 return;
423 } else if (rot_x == this.rot_x && rot_y == this.rot_y && rot_z == this.rot_z) {
424 return;
426 this.rot_x = rot_x;
427 this.rot_y = rot_y;
428 this.rot_z = rot_z;
429 updateInDatabase("rot");
432 /** Used by the Loader after loading blindly a lot of Patches. Will crop the canvas to the minimum size possible. */
433 public boolean setMinimumDimensions() {
434 // find current x,y,width,height that crops the canvas without cropping away any Displayable
435 double x = Double.NaN;
436 double y = Double.NaN;
437 double xe = 0; // lower right corner (x end)
438 double ye = 0;
439 double tx = 0;
440 double ty = 0;
441 double txe = 0;
442 double tye = 0;
443 // collect all Displayable and ZDisplayable objects
444 final ArrayList<Displayable> al = new ArrayList<Displayable>();
445 for (int i=al_layers.size() -1; i>-1; i--) {
446 al.addAll(al_layers.get(i).getDisplayables());
448 al.addAll(al_zdispl);
450 // find minimum bounding box
451 Rectangle b = new Rectangle();
452 for (final Displayable d : al) {
453 b = d.getBoundingBox(b); // considers rotation
454 tx = b.x;//d.getX();
455 ty = b.y;//d.getY();
456 // set first coordinates
457 if (Double.isNaN(x) || Double.isNaN(y)) { // Double.NaN == x fails!
458 x = tx;
459 y = ty;
461 txe = tx + b.width;//d.getWidth();
462 tye = ty + b.height;//d.getHeight();
463 if (tx < x) x = tx;
464 if (ty < y) y = ty;
465 if (txe > xe) xe = txe;
466 if (tye > ye) ye = tye;
468 // if none, then stop
469 if (Double.isNaN(x) || Double.isNaN(y)) {
470 Utils.showMessage("No displayable objects, don't know how to resize the canvas and Layerset.");
471 return false;
474 double w = xe - x;
475 double h = ye - y;
476 if (w <= 0 || h <= 0) {
477 Utils.log("LayerSet.setMinimumDimensions: zero width or height, NOT resizing.");
478 return false;
481 // Record previous state
482 if (prepareStep(this)) {
483 addEditStep(new LayerSet.DoResizeLayerSet(this));
486 // translate
487 if (0 != x || 0 != y) {
488 project.getLoader().startLargeUpdate();
489 try {
490 final AffineTransform at2 = new AffineTransform();
491 at2.translate(-x, -y);
492 //Utils.log2("translating all displayables by " + x + "," + y);
493 for (final Displayable d : al) {
494 //((Displayable)it.next()).translate(-x, -y, false); // drag regardless of getting off current LayerSet bounds
495 // optimized to avoid creating so many AffineTransform instances:
496 //Utils.log2("BEFORE: " + d.getBoundingBox());
497 d.getAffineTransform().preConcatenate(at2);
498 //Utils.log2("AFTER: " + d.getBoundingBox());
499 d.updateInDatabase("transform");
501 project.getLoader().commitLargeUpdate();
502 } catch (Exception e) {
503 IJError.print(e);
504 project.getLoader().rollback();
505 return false;
509 //Utils.log("x,y xe,ye : " + x + "," + y + " " + xe + "," + ye);
510 // finally, accept:
511 if (w != layer_width || h != layer_height) {
512 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
513 this.layer_height = (float)Math.ceil(h);
514 updateInDatabase("layer_dimensions");
515 recreateBuckets(true);
516 // and notify the Displays, if any
517 Display.update(this);
518 Display.pack(this);
521 // Record current state:
522 addEditStep(new LayerSet.DoResizeLayerSet(this));
524 return true;
527 /** Enlarge the 2D universe so that all Displayable in the collection fit in it;
528 * that is, that no Displayable has a negative x,y position or lays beyond bounds.*/
529 synchronized public void enlargeToFit(final Collection<? extends Displayable> ds) {
530 Rectangle r = null;
531 for (Displayable d : ds) {
532 if (null == r) r = d.getBoundingBox();
533 else r.add(d.getBoundingBox());
535 if (null == r) return; //empty collection
536 r.add(get2DBounds());
537 setDimensions(r.x, r.y, r.width, r.height);
540 /** 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. */
541 synchronized public boolean enlargeToFit(final Displayable d, final int anchor) {
542 final Rectangle r = new Rectangle(0, 0, (int)Math.ceil(layer_width), (int)Math.ceil(layer_height));
543 final Rectangle b = d.getBoundingBox(null);
544 // check if necessary
545 if (r.contains(b)) return false;
546 // else, enlarge to fit it
547 r.add(b);
548 return setDimensions(r.width, r.height, anchor);
551 /** May leave objects beyond the visible window. */
552 public void setDimensions(float x, float y, float layer_width, float layer_height) {
553 // Record previous state
554 if (prepareStep(this)) {
555 addEditStep(new LayerSet.DoResizeLayerSet(this));
558 this.layer_width = layer_width;
559 this.layer_height = layer_height;
560 final AffineTransform affine = new AffineTransform();
561 affine.translate(-x, -y);
562 for (ZDisplayable zd : al_zdispl) {
563 zd.getAffineTransform().preConcatenate(affine);
564 zd.updateInDatabase("transform");
566 for (Layer la : al_layers) la.apply(Displayable.class, affine);
568 recreateBuckets(true);
570 Display.update(this);
572 // Record new state
573 addEditStep(new LayerSet.DoResizeLayerSet(this));
576 /** Returns false if any Displayables are being partially or totally cropped away. */
577 public boolean setDimensions(float layer_width, float layer_height, int anchor) {
578 // check preconditions
579 if (Double.isNaN(layer_width) || Double.isNaN(layer_height)) { Utils.log("LayerSet.setDimensions: NaNs! Not adjusting."); return false; }
580 if (layer_width <=0 || layer_height <= 0) { Utils.showMessage("LayerSet: can't accept zero or a minus for layer width or height"); return false; }
581 if (anchor < NORTH || anchor > CENTER) { Utils.log("LayerSet: wrong anchor, not resizing."); return false; }
583 // Record previous state
584 if (prepareStep(this)) {
585 addEditStep(new LayerSet.DoResizeLayerSet(this));
588 // new coordinates:
589 double new_x = 0;// the x,y of the old 0,0
590 double new_y = 0;
591 switch (anchor) {
592 case NORTH:
593 case SOUTH:
594 case CENTER:
595 new_x = (layer_width - this.layer_width) / 2; // (this.layer_width - layer_width) / 2;
596 break;
597 case NORTHWEST:
598 case WEST:
599 case SOUTHWEST:
600 new_x = 0;
601 break;
602 case NORTHEAST:
603 case EAST:
604 case SOUTHEAST:
605 new_x = layer_width - this.layer_width; // (this.layer_width - layer_width);
606 break;
608 switch (anchor) {
609 case WEST:
610 case EAST:
611 case CENTER:
612 new_y = (layer_height - this.layer_height) / 2;
613 break;
614 case NORTHWEST:
615 case NORTH:
616 case NORTHEAST:
617 new_y = 0;
618 break;
619 case SOUTHWEST:
620 case SOUTH:
621 case SOUTHEAST:
622 new_y = (layer_height - this.layer_height);
623 break;
627 Utils.log("anchor: " + anchor);
628 Utils.log("LayerSet: existing w,h = " + this.layer_width + "," + this.layer_height);
629 Utils.log("LayerSet: new w,h = " + layer_width + "," + layer_height);
632 // collect all Displayable and ZDisplayable objects
633 ArrayList<Displayable> al = new ArrayList<Displayable>();
634 for (int i=al_layers.size() -1; i>-1; i--) {
635 al.addAll(al_layers.get(i).getDisplayables());
637 al.addAll(al_zdispl);
639 // check that no displayables are being cropped away
640 if (layer_width < this.layer_width || layer_height < this.layer_height) {
641 for (final Displayable d : al) {
642 Rectangle b = d.getBoundingBox(null);
643 double dw = b.getWidth();
644 double dh = b.getHeight();
645 // respect 10% margins
646 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) {
647 // cropping!
648 Utils.showMessage("Cropping " + d + "\nLayerSet: not resizing.");
649 return false;
653 this.layer_width = layer_width;
654 this.layer_height = layer_height;
655 //Utils.log("LayerSet.setDimensions: new_x,y: " + new_x + "," + new_y);
656 // translate all displayables
657 if (0 != new_x || 0 != new_y) {
658 for (final Displayable d : al) {
659 Rectangle b = d.getBoundingBox(null);
660 //Utils.log("d x,y = " + b.x + ", " + b.y);
661 d.setLocation(b.x + new_x, b.y + new_y);
665 updateInDatabase("layer_dimensions");
666 recreateBuckets(true);
667 // and notify the Display
668 Display.update(this);
669 Display.pack(this);
671 // Record new state
672 addEditStep(new LayerSet.DoResizeLayerSet(this));
674 return true;
677 protected boolean remove2(boolean check) {
678 if (check) {
679 if (!Utils.check("Really delete " + this.toString() + (null != al_layers && al_layers.size() > 0 ? " and all its children?" : ""))) return false;
681 LayerThing lt = project.findLayerThing(this);
682 if (null == lt) return false;
683 return project.getLayerTree().remove(check, lt, null); // will end up calling remove(boolean) on this object
686 public boolean remove(boolean check) {
687 if (check) {
688 if (!Utils.check("Really delete " + this.toString() + (null != al_layers && al_layers.size() > 0 ? " and all its children?" : ""))) return false;
690 // delete all layers
691 while (0 != al_layers.size()) {
692 if (!al_layers.get(0).remove(false)) {
693 Utils.showMessage("LayerSet id= " + id + " : Deletion incomplete, check database.");
694 return false;
697 // delete the ZDisplayables
698 for (final ZDisplayable zd : al_zdispl) {
699 zd.remove(false); // will call back the LayerSet.remove(ZDisplayable)
701 // remove the self
702 if (null != parent) parent.remove(this);
703 removeFromDatabase();
704 return true;
707 /** Remove a child. Does not destroy it or delete it from the database. */
708 public void remove(final Layer layer) {
709 if (null == layer || null == idlayers.get(layer.getId())) return;
710 al_layers.remove(layer);
711 synchronized (IDLAYERS_WRITE_LOCK) {
712 // Like remove, but replacing the map instance
713 final HashMap<Long,Layer> m = new HashMap<Long,Layer>(idlayers);
714 m.remove(layer.getId());
715 idlayers = m;
717 synchronized (layerindices) { layerindices.clear(); }
718 for (final ZDisplayable zd : new ArrayList<ZDisplayable>(al_zdispl)) zd.layerRemoved(layer); // may call back and add/remove ZDisplayable objects
719 Display.updateLayerScroller(this);
720 Display.updateTitle(this);
721 removeFromOffscreens(layer);
724 public Layer next(final Layer layer) {
725 final int i = indexOf(layer);
726 if (-1 == i) {
727 Utils.log("LayerSet.next: no such Layer " + layer);
728 return layer;
730 if (al_layers.size() -1 == i) return layer;
731 else return al_layers.get(i+1);
734 public Layer previous(final Layer layer) {
735 final int i = indexOf(layer);
736 if (-1 == i) {
737 Utils.log("LayerSet.previous: no such Layer " + layer);
738 return layer;
740 if (0 == i) return layer;
741 else return al_layers.get(i-1);
744 public Layer nextNonEmpty(Layer layer) {
745 Layer next = layer;
746 Layer given = layer;
747 do {
748 layer = next;
749 next = next(layer);
750 if (!next.isEmpty()) return next;
751 } while (next != layer);
752 return given;
754 public Layer previousNonEmpty(Layer layer) {
755 Layer previous = layer;
756 Layer given = layer;
757 do {
758 layer = previous;
759 previous = previous(layer);
760 if (!previous.isEmpty()) return previous;
761 } while (previous != layer);
762 return given;
765 public int getLayerIndex(final long id) {
766 final Layer layer = getLayer(id);
767 if (null == layer) return -1;
768 return indexOf(layer);
771 /** Find a layer by index, or null if none. */
772 public Layer getLayer(final int i) {
773 if (i >=0 && i < al_layers.size()) return al_layers.get(i);
774 return null;
777 /** Find a layer with the given id, or null if none. */
778 public Layer getLayer(final long id) {
779 return idlayers.get(id);
782 /** Same as getLayer(long) but without box/unbox. */
783 public Layer getLayer(final Long id) {
784 return idlayers.get(id);
787 /** Returns the first layer found with the given Z coordinate, rounded to seventh decimal precision, or null if none found. */
788 public Layer getLayer(final double z) {
789 double error = 0.0000001; // TODO adjust to an optimal
790 for (Layer layer : al_layers) {
791 if (error > Math.abs(layer.getZ() - z)) { // floating-point arithmetic is still not a solved problem!
792 return layer;
795 return null;
798 public Layer getNearestLayer(final double z) {
799 double min_dist = Double.MAX_VALUE;
800 Layer closest = null;
801 for (Layer layer : al_layers) {
802 double dist = Math.abs(layer.getZ() - z);
803 if (dist < min_dist) {
804 min_dist = dist;
805 closest = layer;
808 return closest;
811 /** 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. */
812 public Layer getLayer(double z, double thickness, boolean create) {
813 Iterator<Layer> it = al_layers.iterator();
814 Layer layer = null;
815 double error = 0.0000001; // TODO adjust to an optimal
816 while (it.hasNext()) {
817 Layer l = it.next();
818 if (error > Math.abs(l.getZ() - z) && error > Math.abs(l.getThickness() - thickness)) { // floating point is still not a solved problem.
819 //Utils.log("LayerSet.getLayer: found layer with z=" + l.getZ());
820 layer = l;
823 if (create && null == layer && !Double.isNaN(z) && !Double.isNaN(thickness)) {
824 //Utils.log("LayerSet.getLayer: creating new Layer with z=" + z);
825 layer = new Layer(project, z, thickness, this);
826 add(layer);
827 project.getLayerTree().addLayer(this, layer);
829 return layer;
832 /** Useful for typeless scripts so that a ZDisplayable can be added; the
833 * overloaded method add(Layer) and add(ZDisplayable) is not distinguishable otherwise. */
834 public void addZDisplayable(final ZDisplayable zdispl) {
835 add(zdispl);
838 /** Add a Displayable to be painted in all Layers, such as a Pipe. Also updates open displays of the fact. */
839 public void add(final ZDisplayable zdispl) {
840 if (null == zdispl || -1 != al_zdispl.indexOf(zdispl)) {
841 Utils.log2("LayerSet: not adding zdispl");
842 return;
844 if (zdispl.getProject() != this.project)
845 throw new IllegalArgumentException("LayerSet rejected a ZDisplayable: belongs to a different project.");
847 al_zdispl.add(zdispl); // at the top
849 zdispl.setLayerSet(this);
850 // 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.
851 zdispl.setLayer(al_layers.get(0));
852 zdispl.updateInDatabase("layer_set_id"); // TODO: update stack index? It should!
854 // insert into bucket
856 if (null != root) {
857 // add as last, then update
858 root.put(al_zdispl.size()-1, zdispl, zdispl.getBoundingBox(null));
859 // Updating takes too long, just don't do it
860 //root.update(this, zdispl, 0, al_zdispl.size()-1);
863 addToBuckets(zdispl, al_zdispl.size()-1);
865 Display.add(this, zdispl);
868 public void addAll(final Collection<? extends ZDisplayable> coll) {
869 if (null == coll || 0 == coll.size()) return;
870 for (final ZDisplayable zd : coll) {
871 al_zdispl.add(zd);
872 zd.setLayerSet(this);
873 zd.setLayer(al_layers.get(0));
874 zd.updateInDatabase("layer_set_id");
876 recreateBuckets(false); // only ZDisplayable
877 Display.addAll(this, coll);
880 /** Used for reconstruction purposes, avoids repainting or updating. */
881 public void addSilently(final ZDisplayable zdispl) {
882 if (null == zdispl || -1 != al_zdispl.indexOf(zdispl)) return;
883 try {
884 zdispl.setLayer(0 == al_layers.size() ? null : al_layers.get(0));
885 zdispl.setLayerSet(this, false);
886 //Utils.log2("setLayerSet to ZDipl id=" + zdispl.getId());
887 al_zdispl.add(zdispl);
888 } catch (Exception e) {
889 Utils.log("LayerSet.addSilently: not adding ZDisplayable with id=" + zdispl.getId());
890 IJError.print(e);
891 return;
895 /** Remove a child. Does not destroy the child nor remove it from the database, only from the LayerSet and the Display. */
896 public boolean remove(final ZDisplayable zdispl) {
897 if (null == zdispl || null == al_zdispl) return false;
898 final int old_stack_index = al_zdispl.indexOf(zdispl);
899 if (-1 == old_stack_index) {
900 Utils.log2("LayerSet.remove: Not found: " + zdispl);
901 return false;
903 al_zdispl.remove(old_stack_index);
904 // remove from Bucket AFTER modifying stack index, so it gets reindexed properly
905 removeFromBuckets(zdispl, old_stack_index);
906 removeFromOffscreens(zdispl);
907 Display.remove(zdispl);
908 return true;
911 /** Remove a child. Does not destroy the child nor remove it from the database, only from the LayerSet and the Display.
912 * Returns false if at least one failed to be removed. */
913 public boolean removeAll(final Set<ZDisplayable> zds) {
914 if (null == zds || null == al_zdispl) return false;
915 // Ensure list is iterated only once: don't ask for index every time!
916 int count = 0;
917 for (final Iterator<ZDisplayable> it = al_zdispl.iterator(); it.hasNext(); ) {
918 final ZDisplayable zd = it.next();
919 if (zds.contains(zd)) {
920 it.remove();
921 removeFromOffscreens(zd);
922 Display.remove(zd);
923 count++;
924 if (zds.size() == count) break;
927 removeFromBuckets(zds);
928 Display.updateVisibleTabs(this.project);
929 return true;
932 public boolean contains(final Layer layer) {
933 if (null == layer) return false;
934 return -1 != indexOf(layer);
937 public boolean contains(final Displayable zdispl) {
938 if (null == zdispl) return false;
939 return -1 != al_zdispl.indexOf(zdispl);
942 /** Returns a copy of the layer list. */
943 public ArrayList<Layer> getLayers() {
944 return new ArrayList<Layer>(al_layers); // for integrity and safety, return a copy.
947 /** Returns a sublist of layers from first to last, both inclusive. If last is larger than first, the order is reversed. */
948 public List<Layer> getLayers(final int first, final int last) {
949 final List<Layer> las = al_layers.subList(Math.min(first, last), Math.max(first, last) +1);
950 if (first > last) {
951 final List<Layer> las2 = new ArrayList<Layer>(las);
952 Collections.reverse(las2); // would otherwise reverse the original list! A monumental error.
953 return las2;
955 return new ArrayList<Layer>(las); // editable, thread-safe (a copy)
958 /** Returns the layer range from first to last, both included. If last.getZ() &lt; first.getZ(), the order is reversed. */
959 public List<Layer> getLayers(final Layer first, final Layer last) {
960 return getLayers(indexOf(first), indexOf(last));
963 /** Returns the list of layers to paint by considering the range of n_layers_color_cue around the active layer index. */
964 public List<Layer> getColorCueLayerRange(final Layer active_layer) {
965 if (n_layers_color_cue < 0) {
966 return new ArrayList<Layer>(al_layers); // a copy of all
967 } else if (0 == n_layers_color_cue) {
968 final ArrayList<Layer> list = new ArrayList<Layer>();
969 list.add(active_layer);
970 return list;
972 // Else:
973 final int i = indexOf(active_layer);
974 if (-1 == i) {
975 Utils.log("An error ocurred: could not find an index for layer " + active_layer);
976 final ArrayList<Layer> a = new ArrayList<Layer>(); a.add(active_layer); return a;
978 int first = i - n_layers_color_cue;
979 int last = i + n_layers_color_cue;
980 if (first < 0) first = 0;
981 int size = al_layers.size();
982 if (last >= size) last = size -1;
983 return getLayers(first, last);
986 public boolean isDeletable() {
987 return false;
990 /** Overiding. The alpha is used to show whether the LayerSet object is selected or not. */
991 public void setAlpha(float alpha) { return; }
993 /** Move the given Displayable to the next layer if possible. */
994 public void moveDown(final Layer layer, final Displayable d) {
995 final int i = indexOf(layer);
996 if (al_layers.size() -1 == i || -1 == i) return;
997 layer.remove(d);
998 (al_layers.get(i +1)).add(d);
1000 /** Move the given Displayable to the previous layer if possible. */
1001 public void moveUp(final Layer layer, final Displayable d) {
1002 final int i = indexOf(layer);
1003 if (0 == i || -1 == i) return;
1004 layer.remove(d);
1005 al_layers.get(i -1).add(d);
1008 /** Move all Displayable objects in the HashSet to the given target layer. */
1009 public void move(final Set<Displayable> hs_d, final Layer source, final Layer target) {
1010 if (0 == hs_d.size() || null == source || null == target || source == target) return;
1011 Display.setRepaint(false); // disable repaints
1012 for (final Displayable d : hs_d) {
1013 if (d instanceof ZDisplayable) continue; // ignore
1014 if (source == d.getLayer()) {
1015 source.remove(d);
1016 target.add(d, false, false); // these contortions to avoid repeated DB traffic
1017 d.updateInDatabase("layer_id");
1018 Display.add(target, d, false); // don't activate
1021 Display.setRepaint(true); // enable repaints
1022 source.updateInDatabase("stack_index");
1023 target.updateInDatabase("stack_index");
1024 Display.repaint(source); // update graphics: true
1025 Display.repaint(target);
1028 /** Returns the hash set of objects whose visibility has changed. */
1029 public HashSet<Displayable> setVisible(String type, final boolean visible, final boolean repaint) {
1030 type = type.toLowerCase();
1031 final HashSet<Displayable> hs = new HashSet<Displayable>();
1032 try {
1033 project.getLoader().startLargeUpdate();
1034 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")) {
1035 for (ZDisplayable zd : al_zdispl) {
1036 if (visible != zd.isVisible() && zd.getClass().getName().toLowerCase().endsWith(type)) { // endsWith, because DLabel is called as Label
1037 zd.setVisible(visible, false); // don't repaint
1038 hs.add(zd);
1041 } else {
1042 for (Layer layer : al_layers) {
1043 hs.addAll(layer.setVisible(type, visible, false)); // don't repaint
1046 } catch (Exception e) {
1047 IJError.print(e);
1048 } finally {
1049 project.getLoader().commitLargeUpdate();
1051 if (repaint) {
1052 Display.repaint(this); // this could be optimized to repaint only the accumulated box
1054 return hs;
1056 /** Hide all except those whose type is in 'type' list, whose visibility flag is left unchanged. Returns the list of displayables made hidden. */
1057 public HashSet<Displayable> hideExcept(ArrayList<Class<?>> type, boolean repaint) {
1058 final HashSet<Displayable> hs = new HashSet<Displayable>();
1059 for (ZDisplayable zd : al_zdispl) {
1060 if (!type.contains(zd.getClass()) && zd.isVisible()) {
1061 zd.setVisible(false, repaint);
1062 hs.add(zd);
1065 for (Layer la : al_layers) hs.addAll(la.hideExcept(type, repaint));
1066 return hs;
1068 /** Returns the collection of Displayable whose visibility state has changed. */
1069 public Collection<Displayable> setAllVisible(final boolean repaint) {
1070 final Collection<Displayable> col = new ArrayList<Displayable>();
1071 for (final ZDisplayable zd : al_zdispl) {
1072 if (!zd.isVisible()) {
1073 zd.setVisible(true, repaint);
1074 col.add(zd);
1077 for (Layer la : al_layers) col.addAll(la.setAllVisible(repaint));
1078 return col;
1081 /** Returns true if any of the ZDisplayable objects are of the given class. */
1082 public boolean contains(final Class<?> c) {
1083 for (final ZDisplayable zd : al_zdispl) {
1084 if (zd.getClass() == c) return true;
1086 return false;
1088 /** Check in all layers. */
1089 public boolean containsDisplayable(final Class<?> c) {
1090 for (final Layer layer : al_layers) {
1091 if (layer.contains(c)) return true;
1093 return false;
1096 /** Returns the distance from the first layer's Z to the last layer's Z. */
1097 public double getDepth() {
1098 if (null == al_layers || al_layers.isEmpty()) return 0;
1099 return al_layers.get(al_layers.size() -1).getZ() - al_layers.get(0).getZ();
1102 /** Return all the Displayable objects from all the layers of this LayerSet. Does not include the ZDisplayables. */
1103 public ArrayList<Displayable> getDisplayables() {
1104 final ArrayList<Displayable> al = new ArrayList<Displayable>();
1105 for (Layer layer : al_layers) {
1106 al.addAll(layer.getDisplayables());
1108 return al;
1110 /** Return all the Displayable objects from all the layers of this LayerSet of the given class. Does not include the ZDisplayables. */
1111 public ArrayList<Displayable> getDisplayables(Class<?> c) {
1112 final ArrayList<Displayable> al = new ArrayList<Displayable>();
1113 for (Layer layer : al_layers) {
1114 al.addAll(layer.getDisplayables(c));
1116 return al;
1118 /** 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. */
1119 public ArrayList<Displayable> getDisplayables(final Class<?> c, final Area aroi, final boolean visible_only) {
1120 final ArrayList<Displayable> al = new ArrayList<Displayable>();
1121 for (Layer layer : al_layers) {
1122 al.addAll(layer.getDisplayables(c, aroi, visible_only));
1124 return al;
1127 /** From zero to size-1. */
1128 public int indexOf(final Layer layer) {
1129 synchronized (layerindices) {
1130 Integer i = layerindices.get(layer);
1131 if (null == i) {
1132 // Recreate
1133 layerindices.clear();
1134 int k = 0;
1135 for (final Layer la : al_layers) {
1136 layerindices.put(la, k);
1137 k++;
1139 i = layerindices.get(layer);
1140 if (null == i) {
1141 Utils.log("ERROR: could not find an index for layer " + layer);
1142 return -1;
1145 return i.intValue();
1149 private static java.lang.reflect.Field sbvalue = null;
1150 static {
1151 try {
1152 sbvalue = StringBuilder.class.getSuperclass().getDeclaredField("value");
1153 sbvalue.setAccessible(true);
1154 } catch (Exception e) {
1155 IJError.print(e);
1159 public void exportXML(final java.io.Writer writer, final String indent, final XMLOptions options) throws Exception {
1160 final StringBuilder sb_body = new StringBuilder(512);
1161 sb_body.append(indent).append("<t2_layer_set\n");
1162 final String in = indent + "\t";
1163 super.exportXML(sb_body, in, options);
1164 sb_body.append(in).append("layer_width=\"").append(layer_width).append("\"\n")
1165 .append(in).append("layer_height=\"").append(layer_height).append("\"\n")
1166 .append(in).append("rot_x=\"").append(rot_x).append("\"\n")
1167 .append(in).append("rot_y=\"").append(rot_y).append("\"\n")
1168 .append(in).append("rot_z=\"").append(rot_z).append("\"\n")
1169 .append(in).append("snapshots_quality=\"").append(snapshots_quality).append("\"\n")
1170 .append(in).append("snapshots_mode=\"").append(snapshot_modes[snapshots_mode]).append("\"\n")
1171 .append(in).append("color_cues=\"").append(color_cues).append("\"\n")
1172 .append(in).append("area_color_cues=\"").append(area_color_cues).append("\"\n")
1173 .append(in).append("avoid_color_cue_colors=\"").append(!use_color_cue_colors).append("\"\n")
1174 .append(in).append("n_layers_color_cue=\"").append(n_layers_color_cue).append("\"\n")
1175 .append(in).append("paint_arrows=\"").append(paint_arrows).append("\"\n")
1176 .append(in).append("paint_tags=\"").append(paint_tags).append("\"\n")
1177 .append(in).append("paint_edge_confidence_boxes=\"").append(paint_edge_confidence_boxes).append("\"\n")
1178 .append(in).append("prepaint=\"").append(prepaint).append("\"\n")
1179 .append(in).append("preload_ahead=\"").append(preload_ahead).append("\"\n")
1180 // TODO: alpha! But it's not necessary.
1182 sb_body.append(indent).append(">\n");
1183 if (null != calibration) {
1184 sb_body.append(in).append("<t2_calibration\n")
1185 .append(in).append("\tpixelWidth=\"").append(calibration.pixelWidth).append("\"\n")
1186 .append(in).append("\tpixelHeight=\"").append(calibration.pixelHeight).append("\"\n")
1187 .append(in).append("\tpixelDepth=\"").append(calibration.pixelDepth).append("\"\n")
1188 .append(in).append("\txOrigin=\"").append(calibration.xOrigin).append("\"\n")
1189 .append(in).append("\tyOrigin=\"").append(calibration.yOrigin).append("\"\n")
1190 .append(in).append("\tzOrigin=\"").append(calibration.zOrigin).append("\"\n")
1191 .append(in).append("\tinfo=\"").append(calibration.info).append("\"\n")
1192 .append(in).append("\tvalueUnit=\"").append(calibration.getValueUnit()).append("\"\n")
1193 .append(in).append("\ttimeUnit=\"").append(calibration.getTimeUnit()).append("\"\n")
1194 .append(in).append("\tunit=\"").append(calibration.getUnit()).append("\"\n")
1195 .append(in).append("/>\n")
1198 if (null == sbvalue) {
1199 writer.write(sb_body.toString());
1200 } else {
1201 writer.write((char[])sbvalue.get(sb_body), 0, sb_body.length()); // avoid making a copy of the array
1203 // Count objects
1204 int done = 0;
1205 int total = 0;
1206 total += al_zdispl.size();
1207 for (final Layer la : al_layers) {
1208 total += la.getDisplayableList().size();
1210 // export ZDisplayable objects
1211 if (null != al_zdispl) {
1212 for (final ZDisplayable zd : al_zdispl) {
1213 sb_body.setLength(0);
1214 zd.exportXML(sb_body, in, options);
1215 if (null == sbvalue) {
1216 writer.write(sb_body.toString()); // each separately, for they can be huge
1217 } else {
1218 writer.write((char[])sbvalue.get(sb_body), 0, sb_body.length()); // avoid making a copy of the array
1221 done += al_zdispl.size();
1222 Utils.showProgress(done / (double)total);
1224 // export Layer and contained Displayable objects
1225 if (null != al_layers) {
1226 //Utils.log("LayerSet " + id + " is saving " + al_layers.size() + " layers.");
1227 for (final Layer la : al_layers) {
1228 sb_body.setLength(0);
1229 la.exportXML(sb_body, in, options);
1230 if (null == sbvalue) {
1231 writer.write(sb_body.toString());
1232 } else {
1233 writer.write((char[])sbvalue.get(sb_body), 0, sb_body.length()); // avoid making a copy of the array
1235 done += la.getDisplayableList().size();
1236 Utils.showProgress(done / (double)total);
1239 sb_body.setLength(0);
1240 if (sb_body.length() > 0) {
1241 super.restXML(sb_body, in, options);
1242 if (null == sbvalue) {
1243 writer.write(sb_body.toString());
1244 } else {
1245 writer.write((char[])sbvalue.get(sb_body), 0, sb_body.length()); // avoid making a copy of the array
1248 writer.write(indent + "</t2_layer_set>\n");
1251 /** Includes the !ELEMENT */
1252 static public void exportDTD(final StringBuilder sb_header, final HashSet<String> hs, final String indent) {
1253 final String type = "t2_layer_set";
1254 if (!hs.contains(type)) {
1255 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");
1256 Displayable.exportDTD(type, sb_header, hs, indent);
1257 sb_header.append(indent).append(TAG_ATTR1).append(type).append(" layer_width").append(TAG_ATTR2)
1258 .append(indent).append(TAG_ATTR1).append(type).append(" layer_height").append(TAG_ATTR2)
1259 .append(indent).append(TAG_ATTR1).append(type).append(" rot_x").append(TAG_ATTR2)
1260 .append(indent).append(TAG_ATTR1).append(type).append(" rot_y").append(TAG_ATTR2)
1261 .append(indent).append(TAG_ATTR1).append(type).append(" rot_z").append(TAG_ATTR2)
1262 .append(indent).append(TAG_ATTR1).append(type).append(" snapshots_quality").append(TAG_ATTR2)
1263 .append(indent).append(TAG_ATTR1).append(type).append(" color_cues").append(TAG_ATTR2)
1264 .append(indent).append(TAG_ATTR1).append(type).append(" area_color_cues").append(TAG_ATTR2)
1265 .append(indent).append(TAG_ATTR1).append(type).append(" avoid_color_cue_colors").append(TAG_ATTR2)
1266 .append(indent).append(TAG_ATTR1).append(type).append(" n_layers_color_cue").append(TAG_ATTR2)
1267 .append(indent).append(TAG_ATTR1).append(type).append(" paint_arrows").append(TAG_ATTR2)
1268 .append(indent).append(TAG_ATTR1).append(type).append(" paint_tags").append(TAG_ATTR2)
1269 .append(indent).append(TAG_ATTR1).append(type).append(" paint_edge_confidence_boxes").append(TAG_ATTR2)
1270 .append(indent).append(TAG_ATTR1).append(type).append(" preload_ahead").append(TAG_ATTR2)
1272 sb_header.append(indent).append("<!ELEMENT t2_calibration EMPTY>\n")
1273 .append(indent).append(TAG_ATTR1).append("t2_calibration pixelWidth").append(TAG_ATTR2)
1274 .append(indent).append(TAG_ATTR1).append("t2_calibration pixelHeight").append(TAG_ATTR2)
1275 .append(indent).append(TAG_ATTR1).append("t2_calibration pixelDepth").append(TAG_ATTR2)
1276 .append(indent).append(TAG_ATTR1).append("t2_calibration xOrigin").append(TAG_ATTR2)
1277 .append(indent).append(TAG_ATTR1).append("t2_calibration yOrigin").append(TAG_ATTR2)
1278 .append(indent).append(TAG_ATTR1).append("t2_calibration zOrigin").append(TAG_ATTR2)
1279 .append(indent).append(TAG_ATTR1).append("t2_calibration info").append(TAG_ATTR2)
1280 .append(indent).append(TAG_ATTR1).append("t2_calibration valueUnit").append(TAG_ATTR2)
1281 .append(indent).append(TAG_ATTR1).append("t2_calibration timeUnit").append(TAG_ATTR2)
1282 .append(indent).append(TAG_ATTR1).append("t2_calibration unit").append(TAG_ATTR2)
1287 public void setSnapshotsMode(final int mode) {
1288 if (mode == snapshots_mode) return;
1289 this.snapshots_mode = mode;
1290 Display.repaintSnapshots(this);
1291 updateInDatabase("snapshots_mode");
1294 public int getSnapshotsMode() {
1295 return this.snapshots_mode;
1298 @Override
1299 public void destroy() {
1300 for (Iterator<Layer> it = al_layers.iterator(); it.hasNext(); ) {
1301 Layer layer = it.next();
1302 layer.destroy();
1304 for (final ZDisplayable zd : al_zdispl) {
1305 zd.destroy();
1307 this.al_layers.clear();
1308 this.al_zdispl.clear();
1309 synchronized (IDLAYERS_WRITE_LOCK) { this.idlayers = new HashMap<Long,Layer>(); } // like .clear()
1310 synchronized (layerindices) { this.layerindices.clear(); }
1311 this.offscreens.clear();
1314 /** Used by the Layer.setZ method. */
1315 protected void reposition(final Layer layer) {
1316 if (null == layer || !idlayers.containsKey(layer.getId())) return;
1317 al_layers.remove(layer);
1318 addSilently(layer);
1321 /** Get up to 'n' layers before and after the given layers. */
1322 public ArrayList<Layer> getNeighborLayers(final Layer layer, final int n) {
1323 final int i_layer = indexOf(layer);
1324 final ArrayList<Layer> al = new ArrayList<Layer>();
1325 if (-1 == i_layer) return al;
1326 int start = i_layer - n;
1327 if (start < 0) start = 0;
1328 int end = i_layer + n;
1329 if (end > al_layers.size()) end = al_layers.size();
1330 for (int i=start; i<i_layer; i++) al.add(al_layers.get(i));
1331 for (int i=i_layer+1; i<= i_layer + n || i < end; i++) al.add(al_layers.get(i));
1332 return al;
1335 public boolean isTop(ZDisplayable zd) {
1336 if (null != zd && al_zdispl.size() > 0 && al_zdispl.indexOf(zd) == al_zdispl.size() -1) return true;
1337 return false;
1340 public boolean isBottom(ZDisplayable zd) {
1341 if (null != zd && al_zdispl.size() > 0 && al_zdispl.indexOf(zd) == 0) return true;
1342 return false;
1345 /** Hub method: ZDisplayable or into the Displayable's Layer. */
1346 protected boolean isTop(Displayable d) {
1347 if (d instanceof ZDisplayable) return isTop((ZDisplayable)d);
1348 else return d.getLayer().isTop(d);
1350 /** Hub method: ZDisplayable or into the Displayable's Layer. */
1351 protected boolean isBottom(Displayable d) {
1352 if (d instanceof ZDisplayable) return isBottom((ZDisplayable)d);
1353 else return d.getLayer().isBottom(d);
1356 /** 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
1357 synchronized protected void move(final int place, final Displayable d) {
1358 if (d instanceof ZDisplayable) {
1359 int i = al_zdispl.indexOf(d);
1360 if (-1 == i) {
1361 Utils.log("LayerSet.move: object does not belong here");
1362 return;
1364 int size = al_zdispl.size();
1365 if (1 == size) return;
1366 switch(place) {
1367 case LayerSet.TOP:
1368 // To the end of the list:
1369 al_zdispl.add(al_zdispl.remove(i));
1370 // OLD // if (null != root) root.update(this, d, i, al_zdispl.size()-1);
1371 updateRangeInBuckets(d, i, al_zdispl.size()-1);
1372 break;
1373 case LayerSet.UP:
1374 // +1 in the list
1375 if (size -1 == i) return;
1376 al_zdispl.add(i+1, al_zdispl.remove(i));
1377 //if (null != root) root.update(this, d, i, i+1);
1378 updateRangeInBuckets(d, i, i+1);
1379 break;
1380 case LayerSet.DOWN:
1381 // -1 in the list
1382 if (0 == i) return;
1383 al_zdispl.add(i-1, al_zdispl.remove(i)); //swap
1384 //if (null != root) root.update(this, d, i-1, i);
1385 updateRangeInBuckets(d, i-1, i);
1386 break;
1387 case LayerSet.BOTTOM:
1388 // to first position in the list
1389 al_zdispl.add(0, al_zdispl.remove(i));
1390 //if (null != root) root.update(this, d, 0, i);
1391 updateRangeInBuckets(d, 0, i);
1392 break;
1394 updateInDatabase("stack_index");
1395 Display.updatePanelIndex(d.getLayer(), d);
1396 } else {
1397 switch (place) {
1398 case LayerSet.TOP: d.getLayer().moveTop(d); break;
1399 case LayerSet.UP: d.getLayer().moveUp(d); break;
1400 case LayerSet.DOWN: d.getLayer().moveDown(d); break;
1401 case LayerSet.BOTTOM: d.getLayer().moveBottom(d); break;
1406 /** Returns the reverse index of ZDisplayable zd, which is the actual index as seen in the screen. */
1407 public int indexOf(final ZDisplayable zd) {
1408 int k = al_zdispl.indexOf(zd);
1409 if (-1 == k) return -1;
1410 return al_zdispl.size() - k -1;
1413 public boolean isEmptyAt(final Layer layer) {
1414 for (final ZDisplayable zd : al_zdispl) {
1415 if (zd.paintsAt(layer)) return false;
1417 return true;
1420 public Displayable clone(final Project pr, final boolean copy_id) {
1421 final Rectangle roi = new Rectangle(0, 0, (int)Math.ceil(getLayerWidth()), (int)Math.ceil(getLayerHeight()));
1422 final LayerSet copy = (LayerSet) clone(pr, al_layers.get(0), al_layers.get(al_layers.size()-1), roi, false, copy_id, false);
1423 try {
1424 LayerSet.cloneInto(this, al_layers.get(0), al_layers.get(al_layers.size()-1), pr, copy, roi, copy_id);
1425 } catch (Exception e) {
1426 IJError.print(e);
1427 return null;
1429 return copy;
1432 /** Clone the contents of this LayerSet, from first to last given layers, and cropping for the given rectangle;
1433 * does NOT copy the ZDisplayable, which may be copied using the LayerSet.cloneInto method. */
1434 public Displayable clone(Project pr, Layer first, Layer last, Rectangle roi, boolean add_to_tree, boolean copy_id, boolean ignore_hidden_patches) {
1435 // obtain a LayerSet
1436 final long nid = copy_id ? this.id : pr.getLoader().getNextId();
1437 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());
1438 copy.setCalibration(getCalibrationCopy());
1439 copy.snapshots_quality = this.snapshots_quality;
1440 // copy objects that intersect the roi, from within the given range of layers
1441 final java.util.List<Layer> range = new ArrayList<Layer>(al_layers).subList(indexOf(first), indexOf(last) +1);
1442 Utils.log2("range.size() : " + range.size());
1443 for (Layer layer : range) {
1444 Layer layercopy = layer.clone(pr, copy, roi, copy_id, ignore_hidden_patches);
1445 copy.addSilently(layercopy);
1446 if (add_to_tree) pr.getLayerTree().addLayer(copy, layercopy);
1448 return copy;
1451 static public void cloneInto(final LayerSet src, Layer src_first, Layer src_last,
1452 final Project pr, final LayerSet copy, Rectangle roi, boolean copy_id)
1453 throws Exception {
1454 // copy ZDisplayable objects if they intersect the roi, and translate them properly
1455 final AffineTransform trans = new AffineTransform();
1456 trans.translate(-roi.x, -roi.y);
1457 final List<Layer> range = copy.getLayers();
1458 List<Layer> src_range = null;
1459 if (0 == range.size()) throw new Exception("Cannot cloneInto for a range of zero layers!");
1460 for (final ZDisplayable zd : src.find(range.get(0), range.get(range.size()-1), new Area(roi))) {
1461 if (src.project != pr && zd instanceof Tree<?>) {
1462 // Special in-cloning + crop + out-cloning for Tree instances, since they hold Layer pointers
1463 // 1. Clone within same project, with ALL layers present
1464 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.
1465 // 2. Crop to the desired range, using the range from the original project
1466 if (null == src_range) src_range = new ArrayList<Layer>(src.al_layers).subList(src.indexOf(src_first), src.indexOf(src_last) +1);
1467 src_zd_copy.crop(src_range);
1468 // 3. Clone the cropped Tree
1469 Tree<?> tcopy = (Tree<?>)src_zd_copy.clone(pr, copy_id);
1470 tcopy.getAffineTransform().preConcatenate(trans);
1471 copy.addSilently(tcopy);
1472 tcopy.calculateBoundingBox(null); // for all layers. Will update buckets.
1473 continue;
1475 // Else, normally:
1476 ZDisplayable zdcopy = (ZDisplayable)zd.clone(pr, copy_id);
1477 zdcopy.getAffineTransform().preConcatenate(trans);
1478 copy.addSilently(zdcopy); // must be added before attempting to crop it, because crop needs a LayerSet ref.
1479 if (zdcopy.crop(range)) {
1480 if (zdcopy.isDeletable()) {
1481 pr.remove(zdcopy);
1482 Utils.log("Skipping empty " + zdcopy);
1483 } else {
1484 zdcopy.calculateBoundingBox(null); // null means update buckets for all layers
1486 } else {
1487 Utils.log("Could not crop " + zd);
1490 // fix links:
1491 copy.linkPatchesR();
1494 /** 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). */
1495 public LayerStack createLayerStack(Class<?> clazz, int type, int c_alphas) {
1496 return new LayerStack(this,
1497 getVirtualizationScale(),
1498 type,
1499 clazz,
1500 c_alphas);
1503 public int getPixelsMaxDimension() { return max_dimension; }
1504 /** From 0.000... to 1. */
1505 public double getVirtualizationScale() {
1506 double scale = max_dimension / Math.max(layer_width, layer_height);
1507 return scale > 1 ? 1 : scale;
1509 public void setPixelsMaxDimension(final int d) {
1510 if (d > 2) {
1511 if (d != max_dimension) {
1512 max_dimension = d;
1513 Polyline.flushTraceCache(project); // depends on the scale value
1514 Utils.log("3D Viewer NOT updated:\n close it and recreate meshes for any objects you had in it.");
1516 } else Utils.log("Can't set virtualization max pixels dimension to smaller than 2!");
1519 public void setPixelsVirtualizationEnabled(boolean b) { this.virtualization_enabled = b; }
1520 public boolean isPixelsVirtualizationEnabled() { return virtualization_enabled; }
1523 /** Returns a new Rectangle of 0, 0, layer_width, layer_height. */
1524 public Rectangle get2DBounds() {
1525 return new Rectangle(0, 0, (int)Math.ceil(layer_width), (int)Math.ceil(layer_height));
1528 /** Set the calibration to a clone of the given calibration. */
1529 public void setCalibration(Calibration cal) {
1530 if (null == cal) return;
1531 this.calibration = (Calibration)cal.clone();
1534 public Calibration getCalibration() {
1535 return this.calibration;
1538 public Calibration getCalibrationCopy() {
1539 return calibration.copy();
1542 public boolean isCalibrated() {
1543 Calibration identity = new Calibration();
1544 if (identity.equals(this.calibration)) return false;
1545 return true;
1548 /** Restore calibration from the given XML attributes table.*/
1549 public void restoreCalibration(HashMap<String,String> ht_attributes) {
1550 for (final Map.Entry<String,String> entry : ht_attributes.entrySet()) {
1551 final String key = (String)entry.getKey();
1552 final String value = (String)entry.getValue();
1553 // remove the prefix 't2_'
1554 key.substring(3).toLowerCase(); // case-resistant
1555 try {
1556 if (key.equals("pixelwidth")) {
1557 calibration.pixelWidth = Double.parseDouble(value);
1558 } else if (key.equals("pixelheight")) {
1559 calibration.pixelHeight = Double.parseDouble(value);
1560 } else if (key.equals("pixeldepth")) {
1561 calibration.pixelDepth = Double.parseDouble(value);
1562 } else if (key.equals("xorigin")) {
1563 calibration.xOrigin = Double.parseDouble(value);
1564 } else if (key.equals("yorigin")) {
1565 calibration.yOrigin = Double.parseDouble(value);
1566 } else if (key.equals("zorigin")) {
1567 calibration.zOrigin = Double.parseDouble(value);
1568 } else if (key.equals("info")) {
1569 calibration.info = value;
1570 } else if (key.equals("valueunit")) {
1571 calibration.setValueUnit(value);
1572 } else if (key.equals("timeunit")) {
1573 calibration.setTimeUnit(value);
1574 } else if (key.equals("unit")) {
1575 calibration.setUnit(value);
1577 } catch (Exception e) {
1578 Utils.log2("LayerSet.restoreCalibration, key/value failed:" + key + "=\"" + value +"\"");
1579 IJError.print(e);
1582 //Utils.log2("Restored LayerSet calibration: " + calibration);
1585 /** For creating snapshots, using a very slow but much better scaling algorithm (the Image.SCALE_AREA_AVERAGING method). */
1586 public boolean snapshotsQuality() {
1587 return snapshots_quality;
1590 public void setSnapshotsQuality(boolean b) {
1591 this.snapshots_quality = b;
1592 updateInDatabase("snapshots_quality");
1593 // TODO this is obsolete
1596 /** Find, in this LayerSet and contained layers and their nested LayerSets if any, all Displayable instances of Class c. Includes the ZDisplayables. */
1597 public ArrayList<Displayable> get(final Class<?> c) {
1598 return get(new ArrayList<Displayable>(), c);
1601 /** 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. */
1602 public ArrayList<Displayable> get(ArrayList<Displayable> all, final Class<?> c) {
1603 if (null == all) all = new ArrayList<Displayable>();
1604 // check whether to include all the ZDisplayable objects
1605 if (Displayable.class == c || ZDisplayable.class == c) all.addAll(al_zdispl);
1606 else {
1607 for (final ZDisplayable zd : al_zdispl) {
1608 if (zd.getClass() == c) all.add(zd);
1611 for (final Layer layer : al_layers) {
1612 all.addAll(layer.getDisplayables(c));
1613 for (final Displayable ls : layer.getDisplayables(LayerSet.class)) {
1614 ((LayerSet)ls).get(all, c);
1617 return all;
1620 /** Returns the region defined by the rectangle as an image in the type and format specified.
1621 * The type is either ImagePlus.GRAY8 or ImagePlus.COLOR_RGB.
1622 * 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.
1624 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) {
1625 // check preconditions
1626 if (first < 0 || first > last || last >= al_layers.size()) {
1627 Utils.log("Invalid first and/or last layers.");
1628 return null;
1630 // Ensure some memory is free
1631 project.getLoader().releaseToFit(r.width, r.height, type, 1.1f);
1632 if (Layer.IMAGEPLUS == format) {
1633 ImageStack stack = new ImageStack((int)(r.width*scale), (int)(r.height*scale));
1634 for (int i=first; i<=last; i++) {
1635 Layer la = al_layers.get(i);
1636 Utils.log2("c is " + c);
1637 ImagePlus imp = project.getLoader().getFlatImage(la, r, scale, c_alphas, type, c, null, true);
1638 if (null != imp) try {
1639 //if (0 == stack.getSize()) stack.setColorModel(imp.getProcessor().getColorModel());
1640 stack.addSlice(imp.getTitle(), imp.getProcessor()); //.getPixels());
1641 } catch (IllegalArgumentException iae) {
1642 IJError.print(iae);
1643 } else Utils.log("LayerSet.grab: Ignoring layer " + la);
1645 if (0 == stack.getSize()) {
1646 Utils.log("LayerSet.grab: could not make slices.");
1647 return null;
1649 return new ImagePlus("Stack " + first + "-" + last, stack);
1650 } else if (Layer.IMAGE == format) {
1651 final Image[] image = new Image[last - first + 1];
1652 for (int i=first, j=0; i<=last; i++, j++) {
1653 image[j] = project.getLoader().getFlatAWTImage(al_layers.get(i), r, scale, c_alphas, type, c, null, true, Color.black);
1655 return image;
1657 return null;
1661 /** Searches in all layers. Ignores the ZDisplaybles. */
1662 public Displayable findDisplayable(final long id) {
1663 for (Layer la : al_layers) {
1664 for (Displayable d : la.getDisplayables()) {
1665 if (d.getId() == id) return d;
1668 return null;
1671 /** Searches in all ZDisplayables and in all layers, recursively into nested LayerSets. */
1672 public DBObject findById(final long id) {
1673 if (this.id == id) return this;
1674 for (ZDisplayable zd : al_zdispl) {
1675 if (zd.getId() == id) return zd;
1677 for (Layer la : al_layers) {
1678 DBObject dbo = la.findById(id);
1679 if (null != dbo) return dbo;
1681 return null;
1684 // private to the package
1685 void linkPatchesR() {
1686 for (Layer la : al_layers) la.linkPatchesR();
1687 for (ZDisplayable zd : al_zdispl) zd.linkPatches();
1690 /** Recursive into nested LayerSet objects.*/
1691 public void updateLayerTree() {
1692 for (Layer la : al_layers) {
1693 la.updateLayerTree();
1697 /** Find the ZDisplayable objects that intersect with the 3D roi defined by the first and last layers, and the area -all in world coordinates. */
1698 public ArrayList<ZDisplayable> find(final Layer first, final Layer last, final Area area) {
1699 final ArrayList<ZDisplayable> al = new ArrayList<ZDisplayable>();
1700 for (ZDisplayable zd : al_zdispl) {
1701 if (zd.intersects(area, first.getZ(), last.getZ())) {
1702 al.add(zd);
1705 return al;
1708 /** A Bucket for the ZDisplayable parts that show in every Layer. */
1709 protected final class LayerBucket {
1710 protected final Bucket root;
1711 protected final HashMap<Displayable,HashSet<Bucket>> db_map = new HashMap<Displayable,HashSet<Bucket>>();
1713 LayerBucket(final Layer la) {
1714 this.root = new Bucket(0, 0, (int)(0.00005 + getLayerWidth()), (int)(0.00005 + getLayerHeight()), Bucket.getBucketSide(LayerSet.this, la));
1715 this.root.populate(LayerSet.this, la, this.db_map);
1719 /** For fast search. */
1720 protected HashMap<Layer,LayerBucket> lbucks = new HashMap<Layer,LayerBucket>();
1722 final private void addToBuckets(final Displayable zd, final int i) {
1723 synchronized (lbucks) {
1724 if (lbucks.isEmpty()) return;
1725 for (final Long lid : zd.getLayerIds()) {
1726 final Layer la = getLayer(lid); // map lookup
1727 final LayerBucket lb = lbucks.get(la);
1728 if (null == lb) {
1729 nbmsg(la);
1730 continue;
1732 lb.root.put(i, zd, la, lb.db_map);
1736 /** Recreate the buckets of every layer in which the {@link Displayable} has data. */
1737 final private void removeFromBuckets(final Displayable zd, final int old_stack_index) {
1738 synchronized (lbucks) {
1739 if (lbucks.isEmpty()) return;
1740 for (final Long lid : zd.getLayerIds()) {
1741 final Layer la = getLayer(lid);
1742 final LayerBucket lb = lbucks.get(la);
1743 if (null == lb) {
1744 nbmsg(la);
1745 continue;
1747 recreateBuckets(getLayer(lid), false);
1752 /** Recreate the buckets for all layers involved. */
1753 final private void removeFromBuckets(final Collection<ZDisplayable> zds) {
1754 synchronized (lbucks) {
1755 if (lbucks.isEmpty()) return;
1756 final Set<Layer> touched = new HashSet<Layer>();
1757 for (final ZDisplayable zd : zds) {
1758 touched.addAll(zd.getLayersWithData());
1760 for (final Layer la : touched) {
1761 final LayerBucket lb = lbucks.remove(la);
1762 if (null == lb) {
1763 nbmsg(la);
1764 continue;
1766 lbucks.put(la, new LayerBucket(la));
1770 /** Used ONLY by move up/down/top/bottom. */
1771 final private void updateRangeInBuckets(final Displayable zd, final int i, final int j) {
1772 synchronized (lbucks) {
1773 if (lbucks.isEmpty()) return;
1774 for (final Long lid : zd.getLayerIds()) {
1775 final Layer la = getLayer(lid);
1776 final LayerBucket lb = lbucks.get(la);
1777 if (null == lb) {
1778 nbmsg(la);
1779 continue;
1781 for (final Bucket bu : lb.db_map.get(zd)) {
1782 bu.updateRange(this, zd, i, j);
1788 /** Returns a copy of the list of ZDisplayable objects. */
1789 public ArrayList<ZDisplayable> getZDisplayables() { return new ArrayList<ZDisplayable>(al_zdispl); }
1791 /** Returns the real list of displayables, not a copy. If you modify this list, Thor may ground you with His lightning. */
1792 public ArrayList<ZDisplayable> getDisplayableList() {
1793 return al_zdispl;
1796 public HashMap<Displayable, HashSet<Bucket>> getBucketMap(final Layer la) {
1797 synchronized (lbucks) {
1798 if (lbucks.isEmpty()) return null;
1799 final LayerBucket lb = lbucks.get(la);
1800 if (null == lb) {
1801 nbmsg(la);
1802 return null;
1804 return lb.db_map;
1808 public void updateBucket(final Displayable d, final Layer layer) {
1809 synchronized (lbucks) {
1810 final LayerBucket lb = lbucks.get(layer);
1811 if (null != lb) lb.root.updatePosition(d, layer, lb.db_map);
1815 /** Recreate the ZDisplayable buckets, and also the Layer Displayable buckets if desired. */
1816 public void recreateBuckets(final boolean layer_buckets) {
1817 recreateBuckets(al_layers, layer_buckets);
1820 /** Recreate the ZDisplayable buckets for {@param layer}, and also the {@link Layer} {@link Displayable} buckets if desired.
1821 * @param layer The {@link Layer} to recreate {@link ZDisplayable} buckets for.
1822 * @param layer_buckets Whether to also recreate the {@link Layer}-specific buckets for images and text labels.
1824 public void recreateBuckets(final Layer layer, final boolean layer_buckets) {
1825 LayerBucket lb = new LayerBucket(layer);
1826 synchronized (lbucks) {
1827 lbucks.put(layer, lb);
1829 if (layer_buckets && null != layer.root) layer.recreateBuckets();
1832 /** Regenerate the quad-tree bucket system for the ZDisplayable instances that have data at each of the given layers,
1833 * and optionally regenerate the buckets as well for the 2D Displayable instances of that layer as well. */
1834 public void recreateBuckets(final Collection<Layer> layers, final boolean layer_buckets) {
1835 final HashMap<Layer,LayerBucket> m = new HashMap<Layer,LayerBucket>();
1836 try {
1837 Process.progressive(layers, new TaskFactory<Layer,Object>() {
1838 @Override
1839 public Object process(final Layer layer) {
1840 LayerBucket lb = new LayerBucket(layer);
1841 synchronized (m) {
1842 m.put(layer, lb);
1844 if (layer_buckets && null != layer.root) layer.recreateBuckets();
1845 return null;
1847 }, Process.NUM_PROCESSORS -1); // works even when there is only 1 core, since it checks and fixes the '0' processors request
1848 } catch (Exception e) {
1849 IJError.print(e);
1851 synchronized (lbucks) {
1852 lbucks.clear();
1853 lbucks.putAll(m);
1857 /** Checks only buckets for ZDisplayable, not any related to any layer. */
1858 public void checkBuckets() {
1859 synchronized (lbucks) {
1860 if (!lbucks.isEmpty()) return;
1862 recreateBuckets(false);
1865 /** Returns the minimal 2D bounding box for Displayables of class @param c in all layers. */
1866 public Rectangle getMinimalBoundingBox(final Class<?> c) {
1867 Rectangle r = null;
1868 for (final Layer la : al_layers) {
1869 if (null == r) r = la.getMinimalBoundingBox(c);
1870 else {
1871 Rectangle box = la.getMinimalBoundingBox(c); // may be null if Layer is empty
1872 if (null != box) r.add(box);
1875 return r;
1878 /** Time vs DoStep. Not all steps may be specific for a single Displayable. */
1879 final private TreeMap<Long,DoStep> edit_history = new TreeMap<Long,DoStep>();
1881 /** The step representing the current diff state. */
1882 private long current_edit_time = 0;
1883 private DoStep current_edit_step = null;
1885 /** Displayable vs its own set of time vs DoStep, for quick access, for those edits that are specific of a Displayable.
1886 * It's necessary to set a ground, starting point for any Displayable whose data will be edited. */
1887 final private Map<Displayable,TreeMap<Long,DoStep>> dedits = new HashMap<Displayable,TreeMap<Long,DoStep>>();
1889 /** Time vs DoStep; as steps are removed from the end of edit_history, they are put here. */
1890 final private TreeMap<Long,DoStep> redo = new TreeMap<Long,DoStep>();
1892 /** Whether an initial step should be added or not. */
1893 final boolean prepareStep(final Object ob) {
1894 synchronized (edit_history) {
1895 if (0 == edit_history.size() || redo.size() > 0) return true;
1896 // Check if the last added entry contains the exact same elements and data
1897 DoStep step = edit_history.get(edit_history.lastKey());
1898 boolean b = step.isIdenticalTo(ob);
1899 //Utils.log2(b + " == prepareStep for " + ob);
1900 // If identical, don't prepare one!
1901 return !b;
1905 /** If last step is not a DoEdit "data" step for d, then call addDataEditStep(d). */
1906 boolean addPreDataEditStep(final Displayable d) {
1907 if ( null == current_edit_step
1908 || (current_edit_step.getD() != d || !((DoEdit)current_edit_step).containsKey("data"))) {
1909 //Utils.log2("Adding pre-data edit step");
1910 //return addDataEditStep(d);
1911 return addEditStep(new Displayable.DoEdit(d).init(d, new String[]{"data"}));
1913 return false;
1916 /** A new undo step for the "data" field of Displayable d. */
1917 boolean addDataEditStep(final Displayable d) {
1918 //Utils.log2("Adding data edit step");
1919 // Adds "data", which contains width,height,affinetransform,links, and the data (points, areas, etc.)
1920 return addDataEditStep(d, new String[]{"data"});
1922 /** A new undo step for any desired fields of Displayable d. */
1923 boolean addDataEditStep(final Displayable d, final String[] fields) {
1924 return addEditStep(new Displayable.DoEdit(d).init(d, fields));
1927 /** A new undo step for the "data" field of all Displayable in the set. */
1928 public boolean addDataEditStep(final Set<? extends Displayable> ds) {
1929 return addDataEditStep(ds, new String[]{"data"});
1932 boolean addDataEditStep(final Set<? extends Displayable> ds, final String[] fields) {
1933 final Displayable.DoEdits edits = new Displayable.DoEdits(ds);
1934 edits.init(fields);
1935 return addEditStep(edits);
1938 /** Add an undo step for the transformations of all Displayable in the layer. */
1939 public void addTransformStep(final Layer layer) {
1940 addTransformStep(layer.getDisplayables());
1942 public void addTransformStep(final List<Layer> layers) {
1943 final ArrayList<Displayable> all = new ArrayList<Displayable>();
1944 for (final Layer la : layers) all.addAll(la.getDisplayables());
1945 addTransformStep(all);
1947 /** Add an undo step for the transformations of all Displayable in hs. */
1948 public void addTransformStep(final Collection<? extends Displayable> col) {
1949 //Utils.log2("Added transform step for col");
1950 addEditStep(new Displayable.DoTransforms().addAll(col));
1952 /** Add an undo step for the transformations of all Displayable in col, with data as well (for Patch, data includes the CoordinateTransform). */
1953 public DoEdits addTransformStepWithData(final Collection<? extends Displayable> col) {
1954 if (col.isEmpty()) return null;
1955 final Set<? extends Displayable> hs = col instanceof Set<?> ? (Set<? extends Displayable>)col : new HashSet<Displayable>(col);
1956 final DoEdits step = new Displayable.DoEdits(hs).init(new String[]{"data", "at", "width", "height"});
1957 addEditStep(step);
1958 return step;
1960 /** Includes all ZDisplayable that paint at any of the given layers. */
1961 public Collection<Displayable> addTransformStepWithDataForAll(final Collection<Layer> layers) {
1962 if (layers.isEmpty()) return Collections.emptyList();
1963 final Set<Displayable> hs = new HashSet<Displayable>();
1964 for (final Layer layer : layers) hs.addAll(layer.getDisplayables());
1965 for (final ZDisplayable zd : al_zdispl) {
1966 for (final Layer layer : layers) {
1967 if (zd.paintsAt(layer)) {
1968 hs.add((Displayable)zd);
1969 break;
1973 addTransformStepWithData(hs);
1974 return hs;
1976 /** Add an undo step for the transformations of all Displayable in all layers. */
1977 public void addTransformStep() {
1978 //Utils.log2("Added transform step for all");
1979 Displayable.DoTransforms dt = new Displayable.DoTransforms();
1980 for (final Layer la : al_layers) {
1981 dt.addAll(la.getDisplayables());
1983 addEditStep(dt);
1986 /** Add a step to undo the addition or deletion of one or more objects in this project and LayerSet. */
1987 public DoChangeTrees addChangeTreesStep() {
1988 DoChangeTrees step = new LayerSet.DoChangeTrees(this);
1989 if (prepareStep(step)) {
1990 Utils.log2("Added change trees step.");
1991 addEditStep(step);
1993 return step;
1995 /** Add a step to undo the addition or deletion of one or more objects in this project and LayerSet,
1996 * along with an arbitrary set of steps that may alter, for example the data. */
1997 public DoChangeTrees addChangeTreesStep(final Set<DoStep> dependents) {
1998 DoChangeTrees step = addChangeTreesStep();
1999 step.addDependents(dependents);
2000 addEditStep(step);
2001 return step;
2003 /** For the Displayable contained in a Layer: their number, and their stack order. */
2004 public void addLayerContentStep(final Layer la) {
2005 DoStep step = new Layer.DoContentChange(la);
2006 if (prepareStep(step)) {
2007 Utils.log2("Added layer content step.");
2008 addEditStep(step);
2011 /** For the Z and thickness of a layer. */
2012 public void addLayerEditedStep(final Layer layer) {
2013 addEditStep(new Layer.DoEditLayer(layer));
2015 /** For the Z and thickness of a list of layers. */
2016 public void addLayerEditedStep(final List<Layer> al) {
2017 addEditStep(new Layer.DoEditLayers(al));
2020 public void addUndoStep(final DoStep step) {
2021 addEditStep(step);
2024 boolean addEditStep(final DoStep step) {
2025 if (null == step || step.isEmpty()) {
2026 Utils.log2("Warning: can't add empty step " + step);
2027 return false;
2030 synchronized (edit_history) {
2031 // Check if it's identical to current step
2032 if (step.isIdenticalTo(current_edit_step)) {
2033 //Utils.log2("Skipping identical undo step of class " + step.getClass() + ": " + step);
2034 return false;
2037 // Store current in undo queue
2038 if (null != current_edit_step) {
2039 edit_history.put(current_edit_time, current_edit_step);
2040 // Store for speedy access, if its Displayable-specific:
2041 final Displayable d = current_edit_step.getD();
2042 if (null != d) {
2043 TreeMap<Long,DoStep> edits = dedits.get(d);
2044 if (null == edits) {
2045 edits = new TreeMap<Long,DoStep>();
2046 dedits.put(d, edits);
2048 edits.put(current_edit_time, current_edit_step);
2051 // prune if too large
2052 while (edit_history.size() > project.getProperty("n_undo_steps", 32)) {
2053 long t = edit_history.firstKey();
2054 DoStep st = edit_history.remove(t);
2055 if (null != st.getD()) {
2056 TreeMap<Long,DoStep> m = dedits.get(st.getD());
2057 m.remove(t);
2058 if (0 == m.size()) dedits.remove(st.getD());
2063 // Set step as current
2064 current_edit_time = System.currentTimeMillis();
2065 current_edit_step = step;
2067 // Bye bye redo! Can't branch.
2068 redo.clear();
2071 return true;
2074 public boolean canUndo() {
2075 return edit_history.size() > 0;
2077 public boolean canRedo() {
2078 return redo.size() > 0 || null != current_edit_step;
2081 /** Undoes one step of the ongoing transformation history, otherwise of the overall LayerSet history. */
2082 public boolean undoOneStep() {
2083 synchronized (edit_history) {
2084 if (0 == edit_history.size()) {
2085 Utils.logAll("Empty undo history!");
2086 return false;
2089 //Utils.log2("Undoing one step");
2091 // Add current (if any) to redo queue
2092 if (null != current_edit_step) {
2093 redo.put(current_edit_time, current_edit_step);
2096 // Remove last step from undo queue, and set it as current
2097 current_edit_time = edit_history.lastKey();
2098 current_edit_step = edit_history.remove(current_edit_time);
2100 // Remove as well from dedits
2101 if (null != current_edit_step.getD()) {
2102 dedits.get(current_edit_step.getD()).remove(current_edit_time);
2105 if (!current_edit_step.apply(DoStep.UNDO)) {
2106 Utils.log("Undo: could not apply step!");
2107 return false;
2110 Utils.log("Undoing " + current_edit_step.getClass().getSimpleName());
2112 Display.updateVisibleTabs(project);
2114 return true;
2117 protected boolean removeLastUndoStep() {
2118 synchronized (edit_history) {
2119 if (edit_history.isEmpty()) return false;
2120 final long time = edit_history.lastKey();
2121 final DoStep step = edit_history.remove(time);
2122 if (null != step.getD()) dedits.get(step.getD()).remove(time);
2123 // shift current
2124 if (step == current_edit_step) {
2125 current_edit_time = edit_history.lastKey();
2126 current_edit_step = edit_history.get(current_edit_time);
2129 return true;
2132 /** Redoes one step of the ongoing transformation history, otherwise of the overall LayerSet history. */
2133 public boolean redoOneStep() {
2134 synchronized (edit_history) {
2135 if (0 == redo.size()) {
2136 Utils.logAll("Empty redo history!");
2137 if (null != current_edit_step) {
2138 return current_edit_step.apply(DoStep.REDO);
2140 return false;
2143 //Utils.log2("Redoing one step");
2145 // Add current (if any) to undo queue
2146 if (null != current_edit_step) {
2147 edit_history.put(current_edit_time, current_edit_step);
2148 if (null != current_edit_step.getD()) {
2149 dedits.get(current_edit_step.getD()).put(current_edit_time, current_edit_step);
2153 // Remove one step from undo queue and set it as current
2154 current_edit_time = redo.firstKey();
2155 current_edit_step = redo.remove(current_edit_time);
2157 if (!current_edit_step.apply(DoStep.REDO)) {
2158 Utils.log("Undo: could not apply step!");
2159 return false;
2162 Utils.log("Redoing " + current_edit_step.getClass().getSimpleName());
2164 Display.updateVisibleTabs(project);
2166 return true;
2169 static public void applyTransforms(final Map<Displayable,AffineTransform> m) {
2170 for (final Map.Entry<Displayable,AffineTransform> e : m.entrySet()) {
2171 e.getKey().setAffineTransform(e.getValue()); // updates buckets
2175 static {
2176 // Undo background tasks: should be done in background threads,
2177 // but on attempting to undo/redo, the undo/redo should wait
2178 // until all tasks are done. For example, updating mipmaps when
2179 // undoing/redoing min/max or CoordinateTransform.
2180 // This could be done with futures: spawn and do in the
2181 // background, but on redo/undo, call for the Future return
2182 // value, which will block until there is one to return.
2183 // Since blocking would block the EventDispatchThread, just refuse to undo/redo and notify the user.
2185 // TODO
2188 /** Keeps the width,height of a LayerSet and the AffineTransform of every Displayable in it. */
2189 static private class DoResizeLayerSet implements DoStep {
2191 final LayerSet ls;
2192 final HashMap<Displayable,AffineTransform> affines;
2193 final float width, height;
2195 DoResizeLayerSet(final LayerSet ls) {
2196 this.ls = ls;
2197 this.width = ls.layer_width;
2198 this.height = ls.layer_height;
2199 this.affines = new HashMap<Displayable,AffineTransform>();
2201 final ArrayList<Displayable> col = ls.getDisplayables(); // it's a new list
2202 col.addAll(ls.getZDisplayables());
2203 for (final Displayable d : col) {
2204 this.affines.put(d, d.getAffineTransformCopy());
2207 public boolean isIdenticalTo(final Object ob) {
2208 if (!(ob instanceof LayerSet)) return false;
2209 final LayerSet layerset = (LayerSet) ob;
2210 if (layerset.layer_width != this.width || layerset.height != this.height || layerset != this.ls) return false;
2211 final ArrayList<Displayable> col = ls.getDisplayables();
2212 col.addAll(ls.getZDisplayables());
2213 for (final Displayable d : col) {
2214 final AffineTransform aff = this.affines.get(d);
2215 if (null == aff) return false;
2216 if (!aff.equals(d.getAffineTransform())) return false;
2218 return true;
2221 public boolean apply(int action) {
2222 ls.layer_width = width;
2223 ls.layer_height = height;
2224 for (final Map.Entry<Displayable,AffineTransform> e : affines.entrySet()) {
2225 e.getKey().getAffineTransform().setTransform(e.getValue());
2228 final boolean dobuckets;
2229 synchronized (ls.lbucks) {
2230 dobuckets = ls.lbucks.isEmpty();
2232 if (dobuckets) ls.recreateBuckets(true);
2234 Display.updateSelection();
2235 Display.update(ls); //so it's not left out painted beyond borders
2236 return true;
2238 public boolean isEmpty() { return false; }
2239 public Displayable getD() { return null; }
2242 /** Records the state of the LayerSet.al_layers, each Layer.al_displayables and all the trees and unique types of Project. */
2243 static protected class DoChangeTrees implements DoStep {
2244 final LayerSet ls;
2245 final HashMap<Thing,Boolean> ttree_exp, ptree_exp, ltree_exp;
2246 final Thing troot, proot, lroot;
2247 final ArrayList<Layer> all_layers;
2248 final HashMap<Layer,ArrayList<Displayable>> all_displ;
2249 final ArrayList<ZDisplayable> all_zdispl;
2250 final HashMap<Displayable,Set<Displayable>> links;
2251 final HashMap<Long,Layer> idlayers;
2252 final HashMap<Layer,Integer> layerindices;
2254 HashSet<DoStep> dependents = null;
2256 // TODO: does not consider recursive LayerSets!
2257 public DoChangeTrees(final LayerSet ls) {
2258 this.ls = ls;
2259 final Project p = ls.getProject();
2261 this.ttree_exp = new HashMap<Thing,Boolean>();
2262 this.troot = p.getTemplateTree().duplicate(this.ttree_exp);
2263 this.ptree_exp = new HashMap<Thing,Boolean>();
2264 this.proot = p.getProjectTree().duplicate(this.ptree_exp);
2265 this.ltree_exp = new HashMap<Thing,Boolean>();
2266 this.lroot = p.getLayerTree().duplicate(this.ltree_exp);
2268 this.all_layers = ls.getLayers(); // a copy of the list, but each object is the running instance
2269 this.all_zdispl = ls.getZDisplayables(); // idem
2270 this.idlayers = new HashMap<Long,Layer>(ls.idlayers);
2271 synchronized (ls.layerindices) {
2272 this.layerindices = new HashMap<Layer,Integer>(ls.layerindices);
2275 this.links = new HashMap<Displayable,Set<Displayable>>();
2276 for (final ZDisplayable zd : this.all_zdispl) {
2277 this.links.put(zd, zd.hs_linked); // LayerSet is a Displayable
2280 this.all_displ = new HashMap<Layer,ArrayList<Displayable>>();
2281 for (final Layer layer : all_layers) {
2282 final ArrayList<Displayable> al = layer.getDisplayables(); // a copy
2283 this.all_displ.put(layer, al);
2284 for (final Displayable d : al) {
2285 this.links.put(d, null == d.hs_linked ? null : new HashSet<Displayable>(d.hs_linked));
2289 public Displayable getD() { return null; }
2290 public boolean isEmpty() { return false; }
2291 public boolean isIdenticalTo(final Object ob) {
2292 // TODO
2293 return false;
2295 public boolean apply(int action) {
2296 // Replace all trees
2297 final Project p = ls.getProject();
2298 p.resetRootTemplateThing((TemplateThing)this.troot, ttree_exp);
2299 p.resetRootProjectThing((ProjectThing)this.proot, ptree_exp);
2300 p.resetRootLayerThing((LayerThing)this.lroot, ltree_exp);
2302 // Replace all layers
2303 ls.al_layers.clear();
2304 ls.al_layers.addAll(this.all_layers);
2305 synchronized (ls.IDLAYERS_WRITE_LOCK) {
2306 ls.idlayers = new HashMap<Long,Layer>(this.idlayers);
2308 synchronized (ls.layerindices) {
2309 ls.layerindices.clear();
2310 ls.layerindices.putAll(this.layerindices);
2313 // Replace all Displayable in each Layer
2314 for (final Map.Entry<Layer,ArrayList<Displayable>> e : all_displ.entrySet()) {
2315 // Acquire pointer to the actual instance list in each Layer
2316 final ArrayList<Displayable> al = e.getKey().getDisplayableList(); // the real one!
2317 // Create a list to contain those Displayable present in old list but not in list to use now
2318 final HashSet<Displayable> diff = new HashSet<Displayable>(al); // create with all Displayable of old list
2319 diff.removeAll(e.getValue()); // remove all Displayable present in list to use now, to leave the diff or remainder only
2320 // Clear current list
2321 al.clear();
2322 // Insert all to the current list
2323 al.addAll(e.getValue());
2324 // Add to remove-on-shutdown queue all those Patch no longer in the list to use now:
2325 for (final Displayable d : diff) {
2326 if (d.getClass() == Patch.class) {
2327 d.getProject().getLoader().tagForMipmapRemoval((Patch)d, true);
2330 // Remove from queue all those Patch in the list to use now:
2331 for (final Displayable d : al) {
2332 if (d.getClass() == Patch.class) {
2333 d.getProject().getLoader().tagForMipmapRemoval((Patch)d, false);
2338 // Replace all ZDisplayable
2339 ls.al_zdispl.clear();
2340 ls.al_zdispl.addAll(this.all_zdispl);
2342 // Replace all links
2343 for (final Map.Entry<Displayable,Set<Displayable>> e : this.links.entrySet()) {
2344 final Set<Displayable> hs = e.getKey().hs_linked;
2345 if (null != hs) {
2346 final Set<Displayable> hs2 = e.getValue();
2347 if (null == hs2) e.getKey().hs_linked = null;
2348 else {
2349 hs.clear();
2350 hs.addAll(hs2);
2355 // Invoke dependents
2356 if (null != dependents) for (DoStep step : dependents) step.apply(action);
2358 ls.recreateBuckets(true);
2360 Display.clearSelection(ls.project);
2361 Display.update(ls, false);
2363 return true;
2366 synchronized public void addDependents(Set<DoStep> dep) {
2367 if (null == this.dependents) this.dependents = new HashSet<DoStep>();
2368 this.dependents.addAll(dep);
2372 /** To undo moving up/down/top/bottom. */
2373 public DoStep createUndoMoveStep(final Displayable d) {
2374 return d instanceof ZDisplayable ?
2375 new LayerSet.DoMoveZDisplayable(this)
2376 : new Layer.DoMoveDisplayable(d.getLayer());
2379 /** To undo moving up/down/top/bottom. */
2380 public void addUndoMoveStep(final Displayable d) {
2381 addUndoStep(createUndoMoveStep(d));
2384 static protected class DoMoveZDisplayable implements DoStep {
2385 final ArrayList<ZDisplayable> al_zdispl;
2386 final LayerSet ls;
2387 HashSet<DoStep> dependents = null;
2388 DoMoveZDisplayable(final LayerSet ls) {
2389 this.ls = ls;
2390 this.al_zdispl = new ArrayList<ZDisplayable>(ls.al_zdispl);
2392 @Override
2393 public boolean apply(int action) {
2394 // Replace all ZDisplayable
2395 ls.al_zdispl.clear();
2396 ls.al_zdispl.addAll(this.al_zdispl);
2397 Display.update(ls, false);
2398 return true;
2400 @Override
2401 public boolean isEmpty() {
2402 return false;
2404 @Override
2405 public Displayable getD() {
2406 return null;
2408 @Override
2409 public boolean isIdenticalTo(Object ob) {
2410 if (!(ob instanceof DoMoveZDisplayable)) return false;
2411 final DoMoveZDisplayable dmz = (DoMoveZDisplayable)ob;
2412 if (dmz.ls != this.ls) return false;
2413 if (dmz.al_zdispl.size() != this.al_zdispl.size()) return false;
2414 for (int i=0; i<this.al_zdispl.size(); ++i) {
2415 if (dmz.al_zdispl.get(i) != this.al_zdispl.get(i)) return false;
2417 return true;
2423 private Overlay overlay = null;
2425 /** Return the current Overlay or a new one if none yet. */
2426 synchronized public Overlay getOverlay() {
2427 if (null == overlay) overlay = new Overlay();
2428 return overlay;
2430 // Used by DisplayCanvas to paint
2431 Overlay getOverlay2() {
2432 return overlay;
2434 /** Set to null to remove the Overlay.
2435 * @return the previous Overlay, if any. */
2436 synchronized public Overlay setOverlay(final Overlay o) {
2437 Overlay old = this.overlay;
2438 this.overlay = o;
2439 return old;
2442 private final HashMap<DisplayCanvas.ScreenshotProperties,DisplayCanvas.Screenshot> offscreens = new HashMap<DisplayCanvas.ScreenshotProperties,DisplayCanvas.Screenshot>();
2443 private final HashMap<Layer,HashSet<DisplayCanvas.Screenshot>> offscreens2 = new HashMap<Layer,HashSet<DisplayCanvas.Screenshot>>();
2445 final DisplayCanvas.Screenshot getScreenshot(final DisplayCanvas.ScreenshotProperties props) {
2446 synchronized (offscreens) {
2447 return offscreens.get(props);
2451 final private void putO2(final Layer la, final DisplayCanvas.Screenshot sc) {
2452 HashSet<DisplayCanvas.Screenshot> hs = offscreens2.get(la);
2453 if (null == hs) {
2454 hs = new HashSet<DisplayCanvas.Screenshot>();
2455 offscreens2.put(la, hs);
2457 hs.add(sc);
2459 final private void removeO2(final DisplayCanvas.Screenshot sc) {
2460 HashSet<DisplayCanvas.Screenshot> hs = offscreens2.get(sc.layer);
2461 if (null == hs) return;
2462 hs.remove(sc);
2465 final void storeScreenshot(DisplayCanvas.Screenshot s) {
2466 synchronized(offscreens) {
2467 offscreens.put(s.props, s);
2468 putO2(s.layer, s);
2471 final void clearScreenshots() {
2472 synchronized (offscreens) {
2473 for (final DisplayCanvas.Screenshot s : offscreens.values()) {
2474 s.flush();
2476 offscreens.clear();
2477 offscreens2.clear();
2480 final void trimScreenshots() {
2481 synchronized(offscreens) {
2482 if (offscreens.size() > 1000) {
2483 TreeMap<Long,DisplayCanvas.Screenshot> m = new TreeMap<Long,DisplayCanvas.Screenshot>();
2484 for (final DisplayCanvas.Screenshot s : offscreens.values()) {
2485 m.put(s.born, s);
2487 offscreens.clear();
2488 offscreens2.clear();
2489 ArrayList<Long> t = new ArrayList<Long>(m.keySet());
2490 for (final DisplayCanvas.Screenshot sc : m.subMap(m.firstKey(), t.get(t.size()/2)).values()) {
2491 offscreens.put(sc.props, sc);
2492 putO2(sc.layer, sc);
2494 // not flushing: they will get thrown out eventually
2498 final void removeFromOffscreens(final DisplayCanvas.Screenshot sc) {
2499 synchronized (offscreens) {
2500 offscreens.remove(sc.props);
2501 removeO2(sc);
2504 public final void removeFromOffscreens(final Layer la) {
2505 synchronized (offscreens) {
2506 final HashSet<DisplayCanvas.Screenshot> hs = offscreens2.remove(la);
2507 if (null != hs) {
2508 for (final DisplayCanvas.Screenshot sc : hs) {
2509 offscreens.remove(sc.props);
2514 final void removeFromOffscreens(final ZDisplayable zd) {
2515 synchronized (offscreens) {
2516 // Throw away any cached that intersect the zd
2517 final Rectangle box = zd.getBoundingBox();
2518 for (final Iterator<DisplayCanvas.Screenshot> it = offscreens.values().iterator(); it.hasNext(); ) {
2519 final DisplayCanvas.Screenshot sc = it.next();
2520 if (box.intersects(sc.props.srcRect)) {
2521 it.remove();
2522 final HashSet<DisplayCanvas.Screenshot> hs = offscreens2.get(sc.layer);
2523 if (null != hs) hs.remove(sc.props);
2529 final boolean containsScreenshot(final DisplayCanvas.Screenshot sc) {
2530 synchronized (offscreens) {
2531 return offscreens.containsKey(sc.props);
2535 /** Find all java.awt.geom.Area in layer that intersect with box, if visible.
2536 * Areas are returned as they are, with coords local to the Displayable they come from.
2537 * Modifying the Area instances will modify the actual data in the AreaContainer Displayable. */
2538 protected Map<Displayable,List<Area>> findAreas(final Layer layer, final Rectangle box, final boolean visible) {
2539 final Map<Displayable,List<Area>> m = new HashMap<Displayable,List<Area>>();
2540 for (final Displayable zd : findZDisplayables(layer, box, visible)) {
2541 if (!(zd instanceof AreaContainer)) continue;
2542 List<Area> a = ((AreaContainer)zd).getAreas(layer, box);
2543 if (null == a) continue;
2544 m.put(zd, a);
2546 return m;
2549 /** A set of unique tags, retrievable by their own identity. */
2550 protected final Map<Integer,HashMap<String,Tag>> tags = new HashMap<Integer,HashMap<String,Tag>>();
2553 final Tag TODO = new Tag("TODO", KeyEvent.VK_T),
2554 UNCERTAIN_END = new Tag("Uncertain end", KeyEvent.VK_U);
2555 final HashMap<String,Tag> m1 = new HashMap<String,Tag>(),
2556 m2 = new HashMap<String,Tag>();
2557 m1.put(TODO.toString(), TODO);
2558 m2.put(UNCERTAIN_END.toString(), UNCERTAIN_END);
2559 tags.put(KeyEvent.VK_T, m1);
2560 tags.put(KeyEvent.VK_U, m2);
2563 /** Returns an existing immutable Tag instance if already there, or stores a new one and returns it. */
2564 public Tag putTag(final String tag, final int keyCode) {
2565 if (null == tag) return null;
2566 synchronized (tags) {
2567 HashMap<String,Tag> ts = tags.get(keyCode);
2568 if (null == ts) {
2569 ts = new HashMap<String,Tag>();
2570 tags.put(keyCode, ts);
2572 final Tag t = new Tag(tag, keyCode);
2573 final Tag existing = ts.get(t);
2574 if (null == existing) {
2575 ts.put(t.toString(), t);
2576 return t;
2577 } else {
2578 return existing;
2583 /** If there aren't any tags for keyCode, returns an empty TreeSet. */
2584 @SuppressWarnings("unchecked")
2585 public TreeSet<Tag> getTags(final int keyCode) {
2586 synchronized (tags) {
2587 final HashMap<String,Tag> ts = tags.get(keyCode);
2588 return new TreeSet<Tag>(null == ts ? Collections.EMPTY_SET :
2589 KeyEvent.VK_R == keyCode ? filterReviewTags(ts.values()) :
2590 ts.values());
2594 private final Collection<Tag> filterReviewTags(final Collection<Tag> ts) {
2595 final ArrayList<Tag> a = new ArrayList<Tag>();
2596 for (final Tag tag : ts) {
2597 if ('#' == tag.toString().charAt(0)) continue;
2598 else a.add(tag);
2600 return a;
2603 protected Tag askForNewTag(final int keyCode) {
2604 GenericDialog gd = new GenericDialog("Define new tag");
2605 gd.addMessage("Define new tag for key: " + ((char)keyCode));
2606 TreeSet<Tag> ts = getTags(keyCode);
2607 gd.addStringField("New tag:", "", 40);
2608 if (null != ts && ts.size() > 0) {
2609 String[] names = new String[ts.size()];
2610 int next = 0;
2611 for (Tag t : ts) names[next++] = t.toString();
2612 gd.addChoice("Existing tags for " + ((char)keyCode) + ":", names, names[0]);
2614 gd.showDialog();
2615 if (gd.wasCanceled()) return null;
2616 String tag = gd.getNextString().trim();
2617 if (0 == tag.length()) {
2618 Utils.logAll("Invalid tag " + tag);
2619 return null;
2621 return putTag(tag, keyCode);
2624 /** Returns false if the dialog was canceled or there wasn't any tag to remove. */
2625 protected boolean askToRemoveTag(final int keyCode) {
2626 TreeSet<Tag> ts = getTags(keyCode);
2627 if (null == ts || ts.isEmpty()) return false;
2628 String[] tags = new String[ts.size()];
2629 int next = 0;
2630 for (Tag t : ts) tags[next++] = t.toString();
2631 GenericDialog gd = new GenericDialog("Remove tag");
2632 gd.addMessage("Remove a tag for key: " + ((char)keyCode));
2633 gd.addChoice("Remove:", tags, tags[0]);
2634 gd.showDialog();
2635 if (gd.wasCanceled()) return false;
2636 String tag = gd.getNextChoice();
2637 removeTag(tag, keyCode);
2638 return true;
2641 /** Removes the tag from the list of possible tags, and then from wherever it has been assigned. The @param tag is duck-typed. */
2642 public void removeTag(final String tag, final int keyCode) {
2643 removeTag(new Tag(tag, keyCode));
2645 public void removeTag(final Tag t) {
2646 synchronized (tags) {
2647 HashMap<String,Tag> ts = tags.get(t.getKeyCode());
2648 if (null == ts) return;
2649 ts.remove(t.toString());
2651 for (final Displayable d : getDisplayables()) {
2652 d.removeTag(t);
2654 for (final ZDisplayable zd : al_zdispl) {
2655 zd.removeTag(t);
2657 Display.repaint(this);
2660 public void removeAllTags() {
2661 // the easy and unperformant way ... I have better things to do
2662 for (final HashMap<String,Tag> m : tags.values()) {
2663 for (final Tag t : m.values()) {
2664 removeTag(t);
2669 public String exportTags() {
2670 StringBuilder sb = new StringBuilder("<tags>\n");
2671 for (final Map.Entry<Integer,HashMap<String,Tag>> e : tags.entrySet()) {
2672 final char key = (char)e.getKey().intValue();
2673 for (final Tag t : e.getValue().values()) {
2674 sb.append(" <tag key=\"").append(key).append("\" val=\"").append(t.toString()).append("\" />\n");
2677 return sb.append("</tags>").toString();
2680 public void importTags(String path, boolean replace) {
2681 HashMap<Integer,HashMap<String,Tag>> backup = new HashMap<Integer,HashMap<String,Tag>>(this.tags); // copy!
2682 InputStream istream = null;
2683 try {
2684 if (replace) removeAllTags();
2685 SAXParserFactory f = SAXParserFactory.newInstance();
2686 f.setValidating(false);
2687 SAXParser parser = f.newSAXParser();
2688 istream = Utils.createStream(path);
2689 parser.parse(new InputSource(istream), new TagsParser());
2690 } catch (Throwable t) {
2691 IJError.print(t);
2692 // restore:
2693 this.tags.clear();
2694 this.tags.putAll(backup);
2695 // no undo for all potentially removed tags ...
2696 } finally {
2697 try {
2698 if (null != istream) istream.close();
2699 } catch (Exception e) {}
2703 private class TagsParser extends DefaultHandler {
2704 public void startElement(String uri, String localName, String qName, Attributes attributes) {
2705 if (!"tag".equals(qName.toLowerCase())) return;
2706 final HashMap<String,String> m = new HashMap<String,String>();
2707 for (int i=attributes.getLength() -1; i>-1; i--) {
2708 m.put(attributes.getQName(i).toLowerCase(), attributes.getValue(i));
2710 final String key = m.get("key"),
2711 content = m.get("val");
2712 if (null == key || key.length() > 1 || Character.isDigit(key.charAt(0)) || null == content) {
2713 Utils.log("Ignoring invalid tag with key '" + key + "' and value '" + content + "'");
2714 return;
2716 putTag(content, (int)key.charAt(0));
2721 // ==== GET ZDisplayable objects ====
2723 // ESSENTIALLY, a filter operation on the al_zdispl list.
2725 public ArrayList<ZDisplayable> getZDisplayables(final Class<?> c) {
2726 return getZDisplayables(c, false);
2729 /** Returns a list of ZDisplayable of class c only.
2730 * If @param instance_of, use c.isInstance(...) instead of class equality. */
2731 public ArrayList<ZDisplayable> getZDisplayables(final Class<?> c, final boolean instance_of) {
2732 final ArrayList<ZDisplayable> al = new ArrayList<ZDisplayable>();
2733 if (null == c) return al;
2734 if (Displayable.class == c || ZDisplayable.class == c) {
2735 al.addAll(al_zdispl);
2736 return al;
2738 if (instance_of) {
2739 for (final ZDisplayable zd : al_zdispl) {
2740 if (c.isInstance(zd)) al.add(zd);
2742 } else {
2743 for (final ZDisplayable zd : al_zdispl) {
2744 if (zd.getClass() == c) al.add(zd);
2747 return al;
2750 // FILTER operations but also by an Area in a given Layer:
2752 /** Use method findZDisplayables(...) instead. */
2753 @Deprecated
2754 public ArrayList<ZDisplayable> getZDisplayables(final Class<?> c, final Layer layer, final Area aroi, final boolean visible_only) {
2755 return getZDisplayables(c, layer, aroi, visible_only, false);
2758 /** Use method findZDisplayables(...) instead. */
2759 @SuppressWarnings({ "unchecked", "rawtypes" })
2760 @Deprecated
2761 public ArrayList<ZDisplayable> getZDisplayables(final Class<?> c, final Layer layer, final Area aroi, final boolean visible_only, final boolean instance_of) {
2762 if (!ZDisplayable.class.isAssignableFrom(c)) return new ArrayList<ZDisplayable>();
2763 return new ArrayList<ZDisplayable>((Collection<ZDisplayable>)(Collection)findZDisplayables(c, layer, aroi, visible_only, instance_of));
2767 // ============== FIND Displayable or ZDisplayable onjects ============
2769 /** 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. */
2770 public ArrayList<Displayable> find(final Class<?> c, final Layer layer, final Area aroi, final boolean visible_only) {
2771 return find(c, layer, aroi, visible_only, false);
2774 /** 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. */
2775 public ArrayList<Displayable> find(final Class<?> c, final Layer layer, final Area aroi, final boolean visible_only, final boolean instance_of) {
2776 final ArrayList<Displayable> al = new ArrayList<Displayable>();
2777 if (!ZDisplayable.class.isAssignableFrom(c)) {
2778 al.addAll(layer.getDisplayables(c, aroi, visible_only, instance_of));
2780 al.addAll(findZDisplayables(c, layer, aroi, visible_only, instance_of));
2781 return al;
2784 public ArrayList<Displayable> find(final Class<?> c, final Layer layer, final int x, final int y, final boolean visible_only) {
2785 final ArrayList<Displayable> al = new ArrayList<Displayable>();
2786 if (!ZDisplayable.class.isAssignableFrom(c)) {
2787 al.addAll(layer.find(c, x, y, visible_only));
2789 al.addAll(findZDisplayables(c, layer, x, y, visible_only));
2790 return al;
2794 // ======== FIND ZDisplayable only =======
2796 /** Find ZDisplayable objects that contain the point x,y in the given layer. */
2797 public Collection<Displayable> findZDisplayables(final Layer layer, final int x, final int y, final boolean visible_only) {
2798 final LayerBucket lb;
2799 synchronized (lbucks) {
2800 lb = lbucks.get(layer);
2802 if (null != lb) return lb.root.find(x, y, layer, visible_only);
2803 else nbmsg(layer);
2805 final ArrayList<Displayable> al = new ArrayList<Displayable>();
2806 for (final ZDisplayable zd : al_zdispl) {
2807 if (zd.contains(layer, x, y)) al.add(zd);
2809 return al;
2812 /** Find ZDisplayable objects of Class c that contain the point x,y in the given layer. */
2813 public Collection<Displayable> findZDisplayables(final Class<?> c, final Layer layer, final int x, final int y, final boolean visible_only) {
2814 return findZDisplayables(c, layer, x, y, visible_only, false);
2816 /** Find ZDisplayable objects of Class c that contain the point x,y in the given layer. */
2817 public Collection<Displayable> findZDisplayables(final Class<?> c, final Layer layer, final int x, final int y, final boolean visible_only, final boolean instance_of) {
2818 final LayerBucket lb;
2819 synchronized (lbucks) {
2820 lb = lbucks.get(layer);
2822 if (null != lb) return lb.root.find(c, x, y, layer, visible_only, instance_of);
2823 else nbmsg(layer);
2825 final ArrayList<Displayable> al = new ArrayList<Displayable>();
2826 for (final ZDisplayable zd : al_zdispl) {
2827 if (zd.getClass() == c && zd.contains(layer, x, y)) al.add(zd);
2829 return al;
2832 public Collection<Displayable> findZDisplayables(final Class<?> c, final Layer layer, final Rectangle r, final boolean visible_only) {
2833 return findZDisplayables(c, layer, r, visible_only, false);
2836 /** Find ZDisplayable objects of the given class that intersect the given rectangle in the given layer. */
2837 public Collection<Displayable> findZDisplayables(final Class<?> c, final Layer layer, final Rectangle r, final boolean visible_only, final boolean instance_of) {
2838 final LayerBucket lb;
2839 synchronized (lbucks) {
2840 lb = lbucks.get(layer);
2842 if (null != lb) return lb.root.find(c, r, layer, visible_only, instance_of);
2843 else nbmsg(layer);
2845 final ArrayList<Displayable> al = new ArrayList<Displayable>();
2846 for (final ZDisplayable zd : al_zdispl) {
2847 if (instance_of && !c.isInstance(zd)) continue;
2848 else if (zd.getClass() != c) continue;
2849 if (zd.getBounds(null, layer).intersects(r)) al.add(zd);
2851 return al;
2853 /** Find ZDisplayable objects of the given class that intersect the given area in the given layer.
2854 * If @param instance_of is true, use c.isAssignableFrom instead of class equality. */
2855 public Collection<Displayable> findZDisplayables(final Class<?> c, final Layer layer, final Area aroi, final boolean visible_only, final boolean instance_of) {
2856 final LayerBucket lb;
2857 synchronized (lbucks) {
2858 lb = lbucks.get(layer);
2860 if (null != lb) return lb.root.find(c, aroi, layer, visible_only, instance_of);
2861 else nbmsg(layer);
2863 final ArrayList<Displayable> al = new ArrayList<Displayable>();
2864 for (final ZDisplayable zd : al_zdispl) {
2865 if (visible_only && !zd.isVisible()) continue;
2866 if (instance_of) {
2867 if (!c.isAssignableFrom(zd.getClass())) continue;
2868 } else if (zd.getClass() != c) continue;
2869 if (zd.intersects(layer, aroi)) al.add(zd);
2871 return al;
2873 /** Find ZDisplayable objects that intersect the given rectangle in the given layer. */
2874 public Collection<Displayable> findZDisplayables(final Layer layer, final Rectangle r, final boolean visible_only) {
2875 final LayerBucket lb;
2876 synchronized (lbucks) {
2877 lb = lbucks.get(layer);
2879 if (null != lb) return lb.root.find(r, layer, visible_only);
2880 else nbmsg(layer);
2882 final ArrayList<Displayable> al = new ArrayList<Displayable>();
2883 for (final ZDisplayable zd : al_zdispl) {
2884 if (visible_only && !zd.isVisible()) continue;
2885 if (zd.getBounds(null, layer).intersects(r)) al.add(zd);
2887 return al;
2890 /** Find ZDisplayable objects that intersect the given rectangle in the given layer.
2891 * May return false positives but never false negatives. */
2892 public Collection<Displayable> roughlyFindZDisplayables(final Layer layer, final Rectangle r, final boolean visible_only) {
2893 final LayerBucket lb;
2894 synchronized (lbucks) {
2895 lb = lbucks.get(layer);
2897 if (null != lb) return lb.root.roughlyFind(r, layer, visible_only);
2898 else nbmsg(layer);
2900 // Else, linear:
2901 final ArrayList<Displayable> al = new ArrayList<Displayable>();
2902 for (final ZDisplayable zd : al_zdispl) {
2903 if (visible_only && !zd.isVisible()) continue;
2904 if (zd.getBounds(null, layer).intersects(r)) al.add(zd);
2906 return al;
2909 private static final void nbmsg(final Layer la) {
2910 Utils.log2("No buckets for layer " + la);
2913 /** Get all Displayable or ZDisplayable of the given class.
2914 * Classes are tested by equality, except for ZDisplayable.class.
2915 * Will also consider Displayable.class and subclasses in
2916 * a similar fashion, by calling Layer.getAll(c). */
2917 @SuppressWarnings("unchecked")
2918 public<T extends Displayable> List<T> getAll(final Class<T> c) {
2919 final ArrayList<T> al = new ArrayList<T>();
2920 if (null == c) return al;
2921 if (ZDisplayable.class == c) {
2922 al.addAll((Collection<T>)al_zdispl);
2923 } else if (ZDisplayable.class.isAssignableFrom(c)) {
2924 for (final ZDisplayable d : al_zdispl) {
2925 if (d.getClass() == c) al.add((T)d);
2927 } else if (Displayable.class == c) {
2928 for (final Layer la : al_layers) {
2929 al.addAll((Collection<T>)la.getDisplayables());
2931 al.addAll((Collection<T>)al_zdispl);
2932 } else if (Displayable.class.isAssignableFrom(c)) {
2933 for (final Layer la : al_layers) {
2934 al.addAll(la.getAll(c));
2937 return al;