877: it is legal for layerThing to be null
[trakem2.git] / TrakEM2_ / src / main / java / ini / trakem2 / display / Layer.java
blobfa50b4db709f55f5a6c2c081f7c53f51fb2611bb
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.gui.GenericDialog;
28 import ini.trakem2.ControlWindow;
29 import ini.trakem2.Project;
30 import ini.trakem2.persistence.DBObject;
31 import ini.trakem2.persistence.XMLOptions;
32 import ini.trakem2.tree.LayerThing;
33 import ini.trakem2.utils.IJError;
34 import ini.trakem2.utils.M;
35 import ini.trakem2.utils.Utils;
37 import java.awt.Color;
38 import java.awt.Polygon;
39 import java.awt.Rectangle;
40 import java.awt.geom.AffineTransform;
41 import java.awt.geom.Area;
42 import java.awt.geom.NoninvertibleTransformException;
43 import java.util.ArrayList;
44 import java.util.Collection;
45 import java.util.Comparator;
46 import java.util.HashMap;
47 import java.util.HashSet;
48 import java.util.Iterator;
49 import java.util.List;
50 import java.util.Set;
52 import mpicbg.models.NoninvertibleModelException;
54 public final class Layer extends DBObject implements Bucketable, Comparable<Layer> {
56 private final ArrayList<Displayable> al_displayables = new ArrayList<Displayable>();
57 /** For fast search. */
58 Bucket root = null;
59 private HashMap<Displayable,HashSet<Bucket>> db_map = null;
61 private double z = 0;
62 private double thickness = 0;
64 private LayerSet parent;
66 /** Compare layers by Z. */
67 static public final Comparator<Layer> COMPARATOR = new Comparator<Layer>() {
68 @Override
69 public final int compare(final Layer l1, final Layer l2) {
70 if (l1 == l2) return 0; // the same layer
71 if (l1.getZ() < l2.getZ()) return -1;
72 return 1; // even if same Z, prefer the second
74 @Override
75 public final boolean equals(final Object ob) { return this == ob; }
78 public Layer(final Project project, final double z, final double thickness, final LayerSet parent) {
79 super(project);
80 this.z = z;
81 this.thickness = thickness;
82 this.parent = parent;
83 addToDatabase();
86 /** Reconstruct from database*/
87 public Layer(final Project project, final long id, final double z, final double thickness) {
88 super(project, id);
89 this.z = z;
90 this.thickness = thickness;
91 this.parent = null;
94 /** Reconstruct from XML file. */
95 public Layer(final Project project, final long id, final HashMap<String,String> ht_attributes) {
96 super(project, id);
97 this.parent = null;
98 // parse data
99 String data;
100 if (null != (data = ht_attributes.get("z"))) this.z = Double.parseDouble(data);
101 else Displayable.xmlError(this, "z", this.z);
102 if (null != (data = ht_attributes.get("thickness"))) this.thickness = Double.parseDouble(data);
103 else Displayable.xmlError(this, "thickness", this.thickness);
106 /** Creates a new Layer asking for z and thickness, and adds it to the parent and returns it. Returns null if the dialog was canceled.*/
107 static public Layer create(final Project project, final LayerSet parent) {
108 if (null == parent) return null;
109 final GenericDialog gd = ControlWindow.makeGenericDialog("New Layer");
110 gd.addMessage("In pixels:"); // TODO set elsewhere the units!
111 gd.addNumericField("z coordinate: ", 0.0D, 3);
112 gd.addNumericField("thickness: ", 1.0D, 3);
113 gd.showDialog();
114 if (gd.wasCanceled()) return null;
115 try {
116 final double z = gd.getNextNumber();
117 final double thickness = gd.getNextNumber();
118 if (Double.isNaN(z) || Double.isNaN(thickness)) return null;
119 final Layer layer = new Layer(project, z, thickness, parent);
120 parent.add(layer);
121 parent.recreateBuckets(layer, true);
122 return layer;
123 } catch (final Exception e) {}
124 return null;
127 /** Pops up a dialog to choose the first Z coord, the thickness, the number of layers,
128 * and whether to skip the creation of any layers whose Z and thickness match
129 * that of existing layers.
130 * @return The newly created layers. */
131 static public List<Layer> createMany(final Project project, final LayerSet parent) {
132 if (null == parent) return null;
133 final GenericDialog gd = ControlWindow.makeGenericDialog("Many new layers");
134 gd.addNumericField("First Z coord: ", 0, 3);
135 gd.addNumericField("thickness: ", 1.0, 3);
136 gd.addNumericField("Number of layers: ", 1, 0);
137 gd.addCheckbox("Skip existing layers", true);
138 gd.showDialog();
139 if (gd.wasCanceled()) return null;
140 // start iteration to add layers
141 double z = gd.getNextNumber();
142 final double thickness = gd.getNextNumber();
143 final int n_layers = (int)gd.getNextNumber();
144 final boolean skip = gd.getNextBoolean();
145 if (thickness < 0) {
146 Utils.log("Can't create layers with negative thickness");
147 return null;
149 if (n_layers < 1) {
150 Utils.log("Invalid number of layers");
151 return null;
153 final List<Layer> layers = new ArrayList<Layer>(n_layers);
154 for (int i=0; i<n_layers; i++) {
155 Layer la = null;
156 if (skip) {
157 // Check if layer exists
158 la = parent.getLayer(z);
159 if (null == la) la = new Layer(project, z, thickness, parent);
160 else la = null;
161 } else la = new Layer(project, z, thickness, parent);
162 if (null != la) {
163 parent.addSilently(la);
164 layers.add(la);
166 z += thickness;
168 parent.recreateBuckets(layers, true); // all empty
169 // update the scroller of currently open Displays
170 Display.updateLayerScroller(parent);
171 return layers;
174 /** Returns a title such as 018__4-K4_2__z1.67 , which is [layer_set index]__[title]__[z coord] . The ordinal number starts at 1 and finishes at parent's length, inclusive. */
175 public String getPrintableTitle() {
176 final LayerThing lt = project.findLayerThing(this);
177 if (null == lt) return toString();
178 String title = lt.getTitle();
179 if (null == title) title = "";
180 else title = title.replace(' ', '_');
181 final StringBuilder sb = new StringBuilder().append(parent.indexOf(this) + 1);
182 final int s_size = Integer.toString(parent.size()).length();
183 while (sb.length() < s_size) {
184 sb.insert(0, '0');
186 sb.append('_').append('_').append(title).append('_').append('_').append('z').append(Utils.cutNumber(this.z, 3, true));
187 return sb.toString();
190 @Override
191 public String toString() {
192 if (null == parent) return new StringBuilder("z=").append(Utils.cutNumber(z, 4)).toString();
193 //return "z=" + Utils.cutNumber(z / parent.getCalibration().pixelDepth * z !!!?? I don't have the actual depth to correct with.
194 //return "z=" + Utils.cutNumber(z, 4);
196 final String unit = parent.getCalibration().getUnit();
197 if (unit.equals("pixel")) {
198 return "z=" + Utils.cutNumber(z, 4);
200 return "z=" + (z * parent.getCalibration().pixelWidth) + " " + unit
201 + " (" + Utils.cutNumber(z, 4) + " px)";
204 /** Add a displayable and update all Display instances showing this Layer. */
205 public void add(final Displayable displ) { add(displ, true); }
207 public void add(final Displayable displ, final boolean update_displays) {
208 add(displ, update_displays, true);
211 public void add(final Displayable displ, final boolean update_displays, final boolean update_db) {
212 if (null == displ || -1 != al_displayables.indexOf(displ)) return;
213 if (displ.getProject() != this.project)
214 throw new IllegalArgumentException("Layer rejected a Displayable: belongs to a different project.");
216 int i=-1, j=-1;
217 final Displayable[] d = new Displayable[al_displayables.size()];
218 al_displayables.toArray(d);
219 int stack_index = 0;
220 // what is it?
221 if (displ instanceof Patch) {
222 // find last Patch (which start at 0)
223 for (i=0; i<d.length; i++) {
224 if (d[i] instanceof Patch) { j = i;}
225 else break;
227 if (-1 != j) {
228 j++;
229 if (j >= d.length) {
230 al_displayables.add(displ); // at the end
231 stack_index = d.length;
232 } else {
233 al_displayables.add(j, displ);
234 stack_index = j;
236 } else {
237 // no patches
238 al_displayables.add(0, displ); // at the very beggining
239 stack_index = 0;
241 } else if (displ instanceof Profile) {
242 // find first LayerSet or if none, first DLabel, add before it
243 for (i=d.length-1; i>-1; i--) {
244 if (! (d[i] instanceof DLabel || d[i] instanceof LayerSet)) { j = i; break; }
246 if (-1 != j) {
247 j++;
248 if (j >= d.length) { al_displayables.add(displ); stack_index = d.length; }
249 else { al_displayables.add(j, displ); stack_index = j; }
250 } else {
251 // no labels or LayerSets
252 al_displayables.add(displ); // at the end
253 stack_index = d.length;
255 } else if (displ instanceof LayerSet) {
256 // find first DLabel, add before it
257 for (i=d.length-1; i>-1; i--) {
258 if (! (d[i] instanceof DLabel)) { j = i; break; }
260 if (-1 != j) {
261 j++; // add it after the non-label one, displacing the label one position
262 if (j >= d.length) { al_displayables.add(displ); stack_index = d.length; } // at the end
263 else { al_displayables.add(j, displ); stack_index = j; }
264 } else {
265 // no labels
266 al_displayables.add(displ); // at the end
267 stack_index = d.length;
269 } else {
270 // displ is a DLabel
271 al_displayables.add(displ); // at the end
272 stack_index = d.length;
275 if (update_db) {
276 updateInDatabase("stack_index"); // of the displayables ...
277 displ.setLayer(this);
278 } else {
279 displ.setLayer(this, false);
282 // insert into bucket
283 if (null != root) {
284 if (d.length == stack_index) {
285 // append at the end
286 root.put(stack_index, displ, this, db_map);
287 } else {
288 // add as last first, then update
289 root.put(d.length, displ, this, db_map);
290 // find and update the range of affected Displayable objects
291 root.updateRange(this, displ, stack_index, d.length); // first to last indices affected
295 if (update_displays) {
296 Display.add(this, displ);
300 @Override
301 public HashMap<Displayable, HashSet<Bucket>> getBucketMap(final Layer layer) { // ignore layer
302 return db_map;
305 /** Used for reconstruction purposes. Assumes the displ are given in the proper order! */
306 public void addSilently(final DBObject displ) { // why DBObject and not Displayable ?? TODO
307 if (null == displ || -1 != al_displayables.indexOf(displ)) return;
308 try {
309 ((Displayable)displ).setLayer(this, false);
310 al_displayables.add((Displayable)displ);
311 } catch (final Exception e) {
312 Utils.log("Layer.addSilently: Not a Displayable/LayerSet, not adding DBObject id=" + displ.getId());
313 return;
317 /** Will recreate the buckets; if you intend to remove many, use "removeAll" instead,
318 * so that the expensive operation of recreating the buckets is done only once. */
319 public synchronized boolean remove(final Displayable displ) {
320 if (null == displ || null == al_displayables) {
321 Utils.log2("Layer can't remove Displayable " + displ.getId());
322 return false;
324 final int old_stack_index = al_displayables.indexOf(displ);
325 if (-1 == old_stack_index) {
326 Utils.log2("Layer.remove: not found: " + displ);
327 return false;
329 al_displayables.remove(old_stack_index);
330 if (null != root) recreateBuckets();
331 parent.removeFromOffscreens(this);
332 Display.remove(this, displ);
333 return true;
336 /** Remove a set of children. Does not destroy the children nor remove them from the database, only from the Layer and the Display. */
337 public synchronized boolean removeAll(final Set<Displayable> ds) {
338 if (null == ds || null == al_displayables) return false;
339 // Ensure list is iterated only once: don't ask for index every time!
340 for (final Iterator<Displayable> it = al_displayables.iterator(); it.hasNext(); ) {
341 final Displayable d = it.next();
342 if (ds.contains(d)) {
343 it.remove();
344 parent.removeFromOffscreens(this);
345 Display.remove(this, d);
348 if (null != root) recreateBuckets();
349 Display.updateVisibleTabs(this.project);
350 return true;
353 /** Used for reconstruction purposes. */
354 public void setParentSilently(final LayerSet layer_set) {
355 if (layer_set == this.parent) return;
356 this.parent = layer_set;
357 //Utils.log("Layer " +id + ": I have as new parent the LayerSet " + layer_set.getId());
360 public void setParent(final LayerSet layer_set) { // can be null
361 if (layer_set == this.parent) return;
362 this.parent = layer_set;
363 updateInDatabase("layer_set_id");
366 public LayerSet getParent() {
367 return parent;
370 public double getZ() { return z; }
371 public double getThickness() { return thickness; }
373 public double getCalibratedZ() {
374 return z * parent.getCalibration().pixelWidth; // not pixelDepth ...
376 public double getCalibratedThickness() {
377 return thickness * parent.getCalibration().pixelWidth; // not pixelDepth ...
380 /** Remove this layer and all its contents from the project. */
381 @Override
382 public boolean remove(final boolean check) {
383 try {
384 if (check && !Utils.check("Really delete " + this.toString() + " and all its children?")) return false;
385 // destroy the Display objects that show this layer
386 Display.remove(this);
387 // proceed to remove all the children
388 final Displayable[] displ = new Displayable[al_displayables.size()]; // to avoid concurrent modifications
389 al_displayables.toArray(displ);
390 for (int i=0; i<displ.length; i++) {
391 if (!displ[i].remove2(false)) { // will call back Layer.remove(Displayable)
392 Utils.log("Could not delete " + displ[i]);
393 return false;
396 al_displayables.clear();
397 // remove from the parent
398 /*can't ever be null//if (null != parent) */
399 parent.remove(this);
400 Display.updateLayerScroller(parent);
401 removeFromDatabase();
402 } catch (final Exception e) { IJError.print(e); return false; }
403 return true;
406 public void setZ(final double z) {
407 if (Double.isNaN(z) || z == this.z) return;
408 this.z = z;
409 if (null != parent) {
410 parent.reposition(this);
411 // fix ordering in the trees (must be done after repositioning in the parent)
412 final LayerThing lt = project.findLayerThing(this);
413 if (null != lt) {
414 final LayerThing p = (LayerThing)lt.getParent();
415 if (null != p) {
416 p.removeChild(lt); // does not affect the database
417 p.addChild(lt); // idem
421 updateInDatabase("z");
424 public void setThickness(final double thickness) {
425 if (Double.isNaN(thickness) || thickness == this.thickness) return;
426 this.thickness = thickness;
427 updateInDatabase("thickness");
430 public boolean contains(final int x, final int y, int inset) {
431 if (inset < 0) inset = -inset;
432 return x >= inset && y >= inset && x <= parent.getLayerWidth() - inset && y <= parent.getLayerHeight() - inset;
435 public boolean contains(final Displayable displ) {
436 return -1 != al_displayables.indexOf(displ);
439 /** Returns true if any of the Displayable objects are of the given class. */
440 public boolean contains(final Class<?> c) {
441 for (final Object ob : al_displayables) {
442 if (ob.getClass() == c) return true;
444 return false;
447 /** Returns true if any of the Displayable objects are of the given class; if {@param visible_only} is true,
448 * will return true only if at least one of the matched objects is visible. */
449 public boolean contains(final Class<?> c, final boolean visible_only) {
450 for (final Displayable d : al_displayables) {
451 if (visible_only && !d.isVisible()) continue;
452 if (d.getClass() == c) return true;
454 return false;
457 /** Count instances of the given Class. */
458 public int count(final Class<?> c) {
459 int n = 0;
460 for (final Object ob : al_displayables) {
461 if (ob.getClass() == c) n++;
463 return n;
466 /** Checks if there are any Displayable or if any ZDisplayable paints in this layer. */
467 public boolean isEmpty() {
468 return 0 == al_displayables.size() && parent.isEmptyAt(this); // check for ZDisplayable painting here as well
471 /** Returns a copy of the list of Displayable objects.*/
472 synchronized public ArrayList<Displayable> getDisplayables() {
473 return new ArrayList<Displayable>(al_displayables);
476 /** Returns the real list of displayables, not a copy. If you modify this list, Thor may ground you with His lightning. */
477 @Override
478 public final ArrayList<Displayable> getDisplayableList() {
479 return al_displayables;
482 synchronized public int getNDisplayables() {
483 return al_displayables.size();
486 /** Returns a list of Displayable of class c only.*/
487 synchronized public<T extends Displayable> ArrayList<T> getAll(final Class<T> c) {
488 // So yes, it can be done to return a typed list of any kind: this WORKS:
489 final ArrayList<T> al = new ArrayList<T>();
490 if (null == c) return al;
491 if (Displayable.class == c) {
492 al.addAll((Collection<T>)al_displayables); // T is Displayable
493 return al;
495 for (final Displayable d : al_displayables) {
496 if (d.getClass() == c) al.add((T)d);
498 return al;
501 /** Returns a list of Displayable of class c only.*/
502 synchronized public ArrayList<Displayable> getDisplayables(final Class<?> c) {
503 final ArrayList<Displayable> al = new ArrayList<Displayable>();
504 if (null == c) return al;
505 if (Displayable.class == c) {
506 al.addAll(al_displayables);
507 return al;
509 for (final Displayable d : al_displayables) {
510 if (d.getClass() == c) al.add(d);
512 return al;
515 synchronized public ArrayList<Displayable> getDisplayables(final Class<?> c, final boolean visible_only, final boolean instance_of) {
516 final ArrayList<Displayable> al = new ArrayList<Displayable>();
517 if (null == c) return al;
518 if (instance_of) {
519 for (final Displayable d : al_displayables) {
520 if (visible_only && !d.isVisible()) continue;
521 if (c.isAssignableFrom(d.getClass())) al.add(d);
523 } else {
524 for (final Displayable d : al_displayables) {
525 if (visible_only && !d.isVisible()) continue;
526 if (d.getClass() == c) al.add(d);
529 return al;
533 /** Returns a list of all Displayable of class c that intersect the given rectangle. */
534 public Collection<Displayable> getDisplayables(final Class<?> c, final Rectangle roi) {
535 return getDisplayables(c, new Area(roi), true, false);
538 /** Returns a list of all Displayable of class c that intersect the given area. */
539 synchronized public Collection<Displayable> getDisplayables(final Class<?> c, final Area aroi, final boolean visible_only) {
540 return getDisplayables(c, aroi, visible_only, false);
543 /** Check class identity by instanceof instead of equality. */
544 synchronized public Collection<Displayable> getDisplayables(final Class<?> c, final Area aroi, final boolean visible_only, final boolean instance_of) {
545 if (null != root) return root.find(c, aroi, this, visible_only, instance_of);
546 // Else, the slow way
547 final ArrayList<Displayable> al = new ArrayList<Displayable>();
548 if (Displayable.class == c) {
549 for (final Displayable d : al_displayables) {
550 if (visible_only && !d.isVisible()) continue;
551 final Area area = d.getArea();
552 area.intersect(aroi);
553 final Rectangle b = area.getBounds();
554 if (!(0 == b.width || 0 == b.height)) al.add(d);
556 return al;
558 if (instance_of) {
559 for (final Displayable d : al_displayables) {
560 if (visible_only && !d.isVisible()) continue;
561 if (c.isAssignableFrom(d.getClass())) {
562 final Area area = d.getArea();
563 area.intersect(aroi);
564 final Rectangle b = area.getBounds();
565 if (!(0 == b.width || 0 == b.height)) al.add(d);
568 } else {
569 for (final Displayable d : al_displayables) {
570 if (visible_only && !d.isVisible()) continue;
571 if (d.getClass() == c) {
572 final Area area = d.getArea();
573 area.intersect(aroi);
574 final Rectangle b = area.getBounds();
575 if (!(0 == b.width || 0 == b.height)) al.add(d);
579 return al;
582 /** Check class identity with equality, so no superclasses or interfaces are possible. */
583 synchronized public ArrayList<Displayable> getDisplayables(final Class<?> c, final boolean visible_only) {
584 final ArrayList<Displayable> al = new ArrayList<Displayable>();
585 for (final Displayable d : al_displayables) {
586 if (d.getClass() == c) {
587 if (visible_only && !d.isVisible()) continue;
588 al.add(d);
591 return al;
594 public Displayable get(final long id) {
595 for (final Displayable d : al_displayables) {
596 if (d.getId() == id) return d;
598 return null;
601 @Override
602 public float getLayerWidth() {
603 return parent.getLayerWidth();
605 @Override
606 public float getLayerHeight() {
607 return parent.getLayerHeight();
610 public Collection<Displayable> find(final double x, final double y) {
611 return find(x, y, false);
614 /** Find the Displayable objects that contain the point. */
615 synchronized public Collection<Displayable> find(final double x, final double y, final boolean visible_only) {
616 if (null != root) return root.find(x, y, this, visible_only);
617 final ArrayList<Displayable> al = new ArrayList<Displayable>();
618 for (int i = al_displayables.size() -1; i>-1; i--) {
619 final Displayable d = (Displayable)al_displayables.get(i);
620 if (visible_only && !d.isVisible()) continue;
621 if (d.contains(x, y)) {
622 al.add(d);
625 return al;
628 public Collection<Displayable> find(final Class<?> c, final double x, final double y) {
629 return find(c, x, y, false, false);
632 /** Find the Displayable objects of Class c that contain the point, with class equality. */
633 synchronized public Collection<Displayable> find(final Class<?> c, final double x, final double y, final boolean visible_only) {
634 return find(c, x, y, visible_only, false);
636 /** Find the Displayable objects of Class c that contain the point, with instanceof if instance_of is true. */
637 synchronized public Collection<Displayable> find(final Class<?> c, final double x, final double y, final boolean visible_only, final boolean instance_of) {
638 if (null != root) return root.find(c, x, y, this, visible_only, instance_of);
639 if (Displayable.class == c) return find(x, y, visible_only); // search among all
640 final ArrayList<Displayable> al = new ArrayList<Displayable>();
641 for (int i = al_displayables.size() -1; i>-1; i--) {
642 final Displayable d = al_displayables.get(i);
643 if (visible_only && !d.isVisible()) continue;
644 if (d.getClass() == c && d.contains(x, y)) {
645 al.add(d);
648 return al;
651 public Collection<Displayable> find(final Rectangle r) {
652 return find(r, false);
655 /** Find the Displayable objects whose bounding box intersects with the given rectangle. */
656 synchronized public Collection<Displayable> find(final Rectangle r, final boolean visible_only) {
657 if (null != root && root.isBetter(r, this)) return root.find(r, this, visible_only);
658 final ArrayList<Displayable> al = new ArrayList<Displayable>();
659 for (final Displayable d : al_displayables) {
660 if (visible_only && !d.isVisible()) continue;
661 if (d.getBoundingBox().intersects(r)) {
662 al.add(d);
665 return al;
668 synchronized public Collection<Displayable> find(final Class<?> c, final Rectangle r, final boolean visible_only) {
669 return find(c, r, visible_only, false);
672 /** Find the Displayable objects whose bounding box intersects with the given rectangle. */
673 synchronized public Collection<Displayable> find(final Class<?> c, final Rectangle r, final boolean visible_only, final boolean instance_of) {
674 if (Displayable.class == c) return find(r, visible_only);
675 if (null != root && root.isBetter(r, this)) return root.find(c, r, this, visible_only, instance_of);
676 final ArrayList<Displayable> al = new ArrayList<Displayable>();
677 for (final Displayable d : al_displayables) {
678 if (visible_only && !d.isVisible()) continue;
679 if (d.getClass() != c) continue;
680 if (d.getBoundingBox().intersects(r)) {
681 al.add(d);
684 return al;
687 /** Find the Displayable objects of class 'target' whose perimeter (not just the bounding box) intersect the given Displayable (which is itself included if present in this very Layer). */
688 synchronized public Collection<Displayable> getIntersecting(final Displayable d, final Class<?> target) {
689 if (null != root) {
690 final Area area = new Area(d.getPerimeter());
691 if (root.isBetter(area.getBounds(), this)) return root.find(area, this, false);
693 final ArrayList<Displayable> al = new ArrayList<Displayable>();
694 for (int i = al_displayables.size() -1; i>-1; i--) {
695 final Object ob = al_displayables.get(i);
696 if (ob.getClass() != target) continue;
697 final Displayable da = (Displayable)ob;
698 if (d.intersects(da)) {
699 al.add(da);
702 // remove the calling one
703 if (al.contains(d)) al.remove(d);
704 return al;
707 /** Returns -1 if not found. */
708 public final int indexOf(final Displayable d) {
709 return al_displayables.indexOf(d);
712 /** Within its own class only.
713 * 'up' is at the last element of the ArrayList (since when painting, the first one gets painted first, and thus gets buried the most while the last paints last, on top). */
714 public void moveUp(final Displayable d) {
715 final int i = al_displayables.indexOf(d);
716 if (null == d || -1 == i || al_displayables.size() -1 == i) return;
717 if (al_displayables.get(i+1).getClass() == d.getClass()) {
718 //swap
719 al_displayables.remove(d);
720 al_displayables.add(i+1, d);
721 } else return;
722 updateInDatabase("stack_index");
723 Display.updatePanelIndex(d.getLayer(), d);
724 if (null != root) root.updateRange(this, d, i, i+1);
727 /** Within its own class only. */
728 public void moveDown(final Displayable d) {
729 final int i = al_displayables.indexOf(d);
730 if (null == d || -1 == i || 0 == i) return;
731 if (al_displayables.get(i-1).getClass() == d.getClass()) {
732 //swap
733 final Displayable o = al_displayables.remove(i-1);
734 al_displayables.add(i, o);
735 } else return;
736 updateInDatabase("stack_index");
737 Display.updatePanelIndex(d.getLayer(), d);
738 if (null != root) root.updateRange(this, d, i-1, i);
741 /** Within its own class only. */
742 public void moveTop(final Displayable d) { // yes I could have made several lists and make my life easier. Whatever
743 final int i = al_displayables.indexOf(d);
744 final int size = al_displayables.size();
745 if (null == d || -1 == i || size -1 == i) return;
746 final Class<?> c = d.getClass();
747 boolean done = false;
748 int j = i + 1;
749 for (; j<size; j++) {
750 if (al_displayables.get(j).getClass() == c) continue;
751 else {
752 al_displayables.remove(d);
753 al_displayables.add(--j, d); // j-1
754 done = true;
755 break;
758 // solves case of no other class present
759 if (!done) {
760 //add at the end
761 al_displayables.remove(d);
762 al_displayables.add(d);
763 j = size-1;
765 updateInDatabase("stack_index");
766 Display.updatePanelIndex(d.getLayer(), d);
767 if (null != root) root.updateRange(this, d, i, j);
770 /** Within its own class only. */
771 public void moveBottom(final Displayable d) {
772 final int i = al_displayables.indexOf(d);
773 if (null == d || -1 == i || 0 == i) return;
774 final Class<?> c = d.getClass();
775 boolean done = false;
776 int j = i - 1;
777 for (; j > -1; j--) {
778 if (al_displayables.get(j).getClass() == c) continue;
779 else {
780 al_displayables.remove(d);
781 al_displayables.add(++j, d); // j+1
782 done = true;
783 break;
786 // solve case of no other class present
787 if (!done) {
788 al_displayables.remove(d);
789 al_displayables.add(0, d);
790 j = 0;
792 updateInDatabase("stack_index");
793 Display.updatePanelIndex(d.getLayer(), d);
794 if (null != root) root.updateRange(this, d, j, i);
797 /** Within its own class only. */
798 public boolean isTop(final Displayable d) {
799 final int i = al_displayables.indexOf(d);
800 final int size = al_displayables.size();
801 if (size -1 == i) return true;
802 if (al_displayables.get(i+1).getClass() == d.getClass()) return false;
803 return true;
804 } // these two methods will throw an Exception if the Displayable is not found (-1 == i) (the null.getClass() *should* throw it)
805 /** Within its own class only. */
806 public boolean isBottom(final Displayable d) {
807 final int i = al_displayables.indexOf(d);
808 if (0 == i) return true;
809 if (al_displayables.get(i-1).getClass() == d.getClass()) return false;
810 return true;
813 /** Get the index of the given Displayable relative to the rest of its class. Beware that the order of the al_displayables is bottom at zero, top at last, but the relative index returned here is inverted: top at zero, bottom at last -to match the tabs' vertical orientation in a Display.*/
814 public int relativeIndexOf(final Displayable d) {
815 final int k = al_displayables.indexOf(d);
816 if (-1 == k) return -1;
817 final Class<?> c = d.getClass();
818 final int size = al_displayables.size();
819 int i = k+1;
820 for (; i<size; i++) {
821 if (al_displayables.get(i).getClass() == c) continue;
822 else {
823 return i - k -1;
826 if (i == size) {
827 return i - k - 1;
829 Utils.log2("relativeIndexOf: return 0");
830 return 0;
833 /** Note: Not recursive into embedded LayerSet objects. Returns the hash set of objects whose visibility has changed. */
834 public HashSet<Displayable> setVisible(String type, final boolean visible, final boolean repaint) {
835 type = type.toLowerCase();
836 if (type.equals("image")) type = "patch";
837 final HashSet<Displayable> hs = new HashSet<Displayable>();
838 for (final Displayable d : al_displayables) {
839 if (visible != d.isVisible() && d.getClass().getName().toLowerCase().endsWith(type)) {
840 d.setVisible(visible, false); // don't repaint
841 hs.add(d);
844 if (repaint) {
845 Display.repaint(this);
847 Display.updateCheckboxes(hs, DisplayablePanel.VISIBILITY_STATE, visible);
848 return hs;
850 /** Returns the collection of Displayable whose visibility state has changed. */
851 public Collection<Displayable> setAllVisible(final boolean repaint) {
852 final Collection<Displayable> col = new ArrayList<Displayable>();
853 for (final Displayable d : al_displayables) {
854 if (!d.isVisible()) {
855 d.setVisible(true, repaint);
856 col.add(d);
859 return col;
862 /** Hide all except those whose type is in 'type' list, whose visibility flag is left unchanged. Returns the list of displayables made hidden. */
863 public HashSet<Displayable> hideExcept(final ArrayList<Class<?>> type, final boolean repaint) {
864 final HashSet<Displayable> hs = new HashSet<Displayable>();
865 for (final Displayable d : al_displayables) {
866 if (!type.contains(d.getClass()) && d.isVisible()) {
867 d.setVisible(false, repaint);
868 hs.add(d);
871 return hs;
874 @Override
875 public void exportXML(final StringBuilder sb_body, final String indent, final XMLOptions options) {
876 final String in = indent + "\t";
877 // 1 - open tag
878 sb_body.append(indent).append("<t2_layer oid=\"").append(id).append("\"\n")
879 .append(in).append(" thickness=\"").append(thickness).append("\"\n")
880 .append(in).append(" z=\"").append(z).append("\"\n")
882 // TODO this search is linear!
883 final LayerThing lt = project.findLayerThing(this);
884 String title;
885 if (null == lt) title = null;
886 else title = lt.getTitle();
887 if (null == title) title = "";
888 sb_body.append(in).append(" title=\"").append(title).append("\"\n"); // TODO 'title' should be a property of the Layer, not the LayerThing. Also, the LayerThing should not exist: LayerSet and Layer should be directly presentable in a tree. They are not Things as in "objects of the sample", but rather, structural necessities such as Patch.
889 sb_body.append(indent).append(">\n");
890 // 2 - export children
891 if (null != al_displayables) {
892 for (final Displayable d : al_displayables) {
893 d.exportXML(sb_body, in, options);
896 // 3 - close tag
897 sb_body.append(indent).append("</t2_layer>\n");
900 /** Includes all Displayable objects in the list of possible children. */
901 static public void exportDTD(final StringBuilder sb_header, final HashSet<String> hs, final String indent) {
902 final String type = "t2_layer";
903 if (hs.contains(type)) return;
904 hs.add(type);
905 sb_header.append(indent).append("<!ELEMENT t2_layer (t2_patch,t2_label,t2_layer_set,t2_profile)>\n")
906 .append(indent).append(Displayable.TAG_ATTR1).append(type).append(" oid").append(Displayable.TAG_ATTR2)
907 .append(indent).append(Displayable.TAG_ATTR1).append(type).append(" thickness").append(Displayable.TAG_ATTR2)
908 .append(indent).append(Displayable.TAG_ATTR1).append(type).append(" z").append(Displayable.TAG_ATTR2)
912 protected String getLayerThingTitle() {
913 final LayerThing lt = project.findLayerThing(this);
914 if (null == lt || null == lt.getTitle() || 0 == lt.getTitle().trim().length()) return "";
915 return lt.getTitle();
918 @Override
919 public String getTitle() {
920 final LayerThing lt = project.findLayerThing(this);
921 if (null == lt || null == lt.getTitle() || 0 == lt.getTitle().trim().length()) return this.toString();
922 return lt.getTitle();
925 public void destroy() {
926 for (final Displayable d : al_displayables) {
927 d.destroy();
931 /** Returns null if no Displayable objects of class c exist. */
932 public Rectangle getMinimalBoundingBox(final Class<?> c) {
933 return getMinimalBoundingBox(c, true);
936 /** Returns null if no Displayable objects of class c exist (or are visible if {@param visible_only} is true). */
937 public Rectangle getMinimalBoundingBox(final Class<?> c, final boolean visible_only) {
938 Rectangle box = null;
939 Rectangle tmp = new Rectangle();
940 for (final Displayable d : getDisplayables(c, visible_only)) {
941 tmp = d.getBoundingBox(tmp);
942 if (null == box) {
943 box = (Rectangle)tmp.clone();
944 continue;
946 box.add(tmp);
948 return box;
951 /** Returns an Area in world coordinates that represents the inside of all Patches. */
952 public Area getPatchArea(final boolean visible_only) {
953 final Area area = new Area(); // with width,height zero
954 for (final Patch p: getAll(Patch.class)) {
955 if (visible_only && p.isVisible()) {
956 area.add(p.getArea());
959 return area;
962 /** Preconcatenate the given AffineTransform to all Displayable objects of class c, without respecting their links. */
963 public void apply(final Class<?> c, final AffineTransform at) {
964 final boolean all = Displayable.class == c;
965 for (final Displayable d : al_displayables) {
966 if (all || d.getClass() == c) {
967 d.at.preConcatenate(at);
970 recreateBuckets();
973 /** Make a copy of this layer into the given LayerSet, enclosing only Displayable objects within the roi, and translating them for that roi x,y. */
974 public Layer clone(final Project pr, final LayerSet ls, final Rectangle roi, final boolean copy_id, final boolean ignore_hidden_patches) {
975 final long nid = copy_id ? this.id : pr.getLoader().getNextId();
976 final Layer copy = new Layer(pr, nid, z, thickness);
977 copy.parent = ls;
978 for (final Displayable d : find(roi)) {
979 if (ignore_hidden_patches && !d.isVisible() && d.getClass() == Patch.class) continue;
980 copy.addSilently(d.clone(pr, copy_id));
982 final AffineTransform transform = new AffineTransform();
983 transform.translate(-roi.x, -roi.y);
984 copy.apply(Displayable.class, transform);
985 return copy;
988 static public final int IMAGEPROCESSOR = 0;
989 static public final int PIXELARRAY = 1;
990 static public final int IMAGE = 2;
991 static public final int IMAGEPLUS = 3;
993 /** Returns the region defined by the rectangle as an image in the type and format specified.
994 * The type is either ImagePlus.GRAY8 or ImagePlus.COLOR_RGB.
995 * The format is either Layer.IMAGEPROCESSOR, Layer.IMAGEPLUS, Layer.PIXELARRAY or Layer.IMAGE.
997 public Object grab(final Rectangle r, final double scale, final Class<?> c, final int c_alphas, final int format, final int type) {
998 //Ensure some memory is free
999 project.getLoader().releaseToFit(r.width, r.height, type, 1.1f);
1000 if (IMAGE == format) {
1001 return project.getLoader().getFlatAWTImage(this, r, scale, c_alphas, type, c, null, true, Color.black);
1002 } else {
1003 final ImagePlus imp = project.getLoader().getFlatImage(this, r, scale, c_alphas, type, c, null, true);
1004 switch (format) {
1005 case IMAGEPLUS:
1006 return imp;
1007 case IMAGEPROCESSOR:
1008 return imp.getProcessor();
1009 case PIXELARRAY:
1010 return imp.getProcessor().getPixels();
1013 return null;
1016 public DBObject findById(final long id) {
1017 if (this.id == id) return this;
1018 for (final Displayable d : al_displayables) {
1019 if (d.getId() == id) return d;
1021 return null;
1024 // private to the package
1025 void linkPatchesR() {
1026 for (final Displayable d : al_displayables) {
1027 if (d.getClass() == LayerSet.class) ((LayerSet)d).linkPatchesR();
1028 d.linkPatches(); // Patch.class does nothing
1032 /** Recursive into nested LayerSet objects.*/
1033 public void updateLayerTree() {
1034 project.getLayerTree().addLayer(parent, this);
1035 for (final Displayable d : getDisplayables(LayerSet.class)) {
1036 ((LayerSet)d).updateLayerTree();
1040 /** Don't use this for fast pixel grabbing; this is intended for the dropper tool and status bar reporting by mouse motion. */
1041 public int[] getPixel(final int x, final int y, final double mag) {
1042 // find Patch under cursor
1043 final Collection<Displayable> under = find(Patch.class, x, y);
1044 if (null == under || under.isEmpty()) return new int[3]; // zeros
1045 final Patch pa = (Patch)under.iterator().next();// get(0) // the top one, since they are ordered like html divs
1046 // TODO: edit here when adding layer mipmaps
1047 return pa.getPixel(x, y, mag);
1050 synchronized public void recreateBuckets() {
1051 this.root = new Bucket(0, 0, (int)(0.00005 + getLayerWidth()), (int)(0.00005 + getLayerHeight()), Bucket.getBucketSide(this, this));
1052 this.db_map = new HashMap<Displayable,HashSet<Bucket>>();
1053 this.root.populate(this, this, db_map);
1054 //root.debug();
1057 /** Update buckets of a position change for the given Displayable. */
1058 @Override
1059 public void updateBucket(final Displayable d, final Layer layer) { // ignore layer
1060 if (null != root) root.updatePosition(d, this, db_map);
1063 public void checkBuckets() {
1064 if (use_buckets && (null == root || null == db_map)) recreateBuckets();
1067 private boolean use_buckets = true;
1069 public void setBucketsEnabled(final boolean b) {
1070 this.use_buckets = b;
1071 if (!use_buckets) this.root = null;
1074 static class DoEditLayer implements DoStep {
1075 final double z, thickness;
1076 final Layer la;
1077 DoEditLayer(final Layer layer) {
1078 this.la = layer;
1079 this.z = layer.z;
1080 this.thickness = layer.thickness;
1082 @Override
1083 public Displayable getD() { return null; }
1084 @Override
1085 public boolean isEmpty() { return false; }
1086 @Override
1087 public boolean isIdenticalTo(final Object ob) {
1088 if (!(ob instanceof Layer)) return false;
1089 final Layer layer = (Layer) ob;
1090 return this.la.id == layer.id && this.z == layer.z && this.thickness == layer.thickness;
1092 @Override
1093 public boolean apply(final int action) {
1094 la.z = this.z;
1095 la.thickness = this.thickness;
1096 la.getProject().getLayerTree().updateUILater();
1097 Display.update(la.getParent());
1098 return true;
1102 static class DoEditLayers implements DoStep {
1103 final ArrayList<DoEditLayer> all = new ArrayList<DoEditLayer>();
1104 DoEditLayers(final List<Layer> all) {
1105 for (final Layer la : all) {
1106 this.all.add(new DoEditLayer(la));
1109 @Override
1110 public Displayable getD() { return null; }
1111 @Override
1112 public boolean isEmpty() { return all.isEmpty(); }
1113 @Override
1114 public boolean isIdenticalTo(final Object ob) {
1115 if (!(ob instanceof DoEditLayers)) return false;
1116 final DoEditLayers other = (DoEditLayers) ob;
1117 if (all.size() != other.all.size()) return false;
1118 // Order matters:
1119 final Iterator<DoEditLayer> it1 = all.iterator();
1120 final Iterator<DoEditLayer> it2 = other.all.iterator();
1121 for (; it1.hasNext() && it2.hasNext(); ) {
1122 if (!it1.next().isIdenticalTo(it2.next())) return false;
1124 return true;
1126 @Override
1127 public boolean apply(final int action) {
1128 boolean failed = false;
1129 for (final DoEditLayer one : all) {
1130 if (!one.apply(action)) {
1131 failed = true;
1134 return !failed;
1138 /** Add an object that is Layer bound, such as Profile, Patch, DLabel and LayerSet. */
1139 static class DoContentChange implements DoStep {
1140 final Layer la;
1141 final ArrayList<Displayable> al;
1142 DoContentChange(final Layer la) {
1143 this.la = la;
1144 this.al = la.getDisplayables(); // a copy
1146 @Override
1147 public Displayable getD() { return null; }
1148 @Override
1149 public boolean isEmpty() { return false; }
1150 /** Check that the Displayable objects of this layer are the same and in the same quantity and order. */
1151 @Override
1152 public boolean isIdenticalTo(final Object ob) {
1153 if (!(ob instanceof DoContentChange)) return false;
1154 final DoContentChange dad = (DoContentChange) ob;
1155 if (la != dad.la || al.size() != dad.al.size()) return false;
1156 // Order matters:
1157 final Iterator<Displayable> it1 = al.iterator();
1158 final Iterator<Displayable> it2 = dad.al.iterator();
1159 for (; it1.hasNext() && it2.hasNext(); ) {
1160 if (it1.next() != it2.next()) return false;
1162 return true;
1164 @Override
1165 public boolean apply(final int action) {
1166 // find the subset in la.al_displayables that is not in this.al
1167 final HashSet<Displayable> sub1 = new HashSet<Displayable>(la.al_displayables);
1168 sub1.removeAll(this.al);
1169 // find the subset in this.al that is not in la.al_displayables
1170 final HashSet<Displayable> sub2 = new HashSet<Displayable>(this.al);
1171 sub2.removeAll(la.al_displayables);
1173 HashSet<Displayable> subA=null, subB=null;
1175 if (action == DoStep.UNDO) {
1176 subA = sub1;
1177 subB = sub2;
1178 } else if (action == DoStep.REDO) {
1179 subA = sub2;
1180 subB = sub1;
1182 if (null != subA && null != subB) {
1183 // Mark Patch for mipmap file removal
1184 for (final Displayable d: subA) {
1185 if (d.getClass() == Patch.class) {
1186 d.getProject().getLoader().queueForMipmapRemoval((Patch)d, true);
1189 // ... or unmark:
1190 for (final Displayable d: subB) {
1191 if (d.getClass() == Patch.class) {
1192 d.getProject().getLoader().queueForMipmapRemoval((Patch)d, false);
1197 la.al_displayables.clear();
1198 la.al_displayables.addAll(this.al);
1199 la.recreateBuckets();
1200 Display.updateVisibleTabs();
1201 Display.clearSelection();
1202 Display.update(la);
1203 return true;
1207 static protected class DoMoveDisplayable implements DoStep {
1208 final ArrayList<Displayable> al_displayables;
1209 final Layer layer;
1210 HashSet<DoStep> dependents = null;
1211 DoMoveDisplayable(final Layer layer) {
1212 this.layer = layer;
1213 this.al_displayables = new ArrayList<Displayable>(layer.al_displayables);
1215 @Override
1216 public boolean apply(final int action) {
1217 // Replace all ZDisplayable
1218 layer.al_displayables.clear();
1219 layer.al_displayables.addAll(this.al_displayables);
1220 Display.update(layer);
1221 return true;
1223 @Override
1224 public boolean isEmpty() {
1225 return false;
1227 @Override
1228 public Displayable getD() {
1229 return null;
1231 @Override
1232 public boolean isIdenticalTo(final Object ob) {
1233 if (!(ob instanceof DoMoveDisplayable)) return false;
1234 final DoMoveDisplayable dm = (DoMoveDisplayable)ob;
1235 if (dm.layer != this.layer) return false;
1236 if (dm.al_displayables.size() != this.al_displayables.size()) return false;
1237 for (int i=0; i<this.al_displayables.size(); ++i) {
1238 if (dm.al_displayables.get(i) != this.al_displayables.get(i)) return false;
1240 return true;
1246 private Overlay overlay = null;
1248 /** Return the current Overlay or a new one if none yet. */
1249 synchronized public Overlay getOverlay() {
1250 if (null == overlay) overlay = new Overlay();
1251 return overlay;
1253 // Used by DisplayCanvas to paint
1254 Overlay getOverlay2() {
1255 return overlay;
1257 /** Set to null to remove the Overlay.
1258 * @return the previous Overlay, if any. */
1259 synchronized public Overlay setOverlay(final Overlay o) {
1260 final Overlay old = this.overlay;
1261 this.overlay = o;
1262 return old;
1265 @Override
1266 public int compareTo(final Layer layer) {
1267 final double diff = this.z - layer.z;
1268 if (diff < 0) return -1;
1269 if (diff > 0) return 1;
1270 return 0;
1273 /** Transfer the world coordinate specified by {@param world_x},{@param world_y}
1274 * in pixels, to the local coordinate of the {@link Patch} immediately present under it.
1275 * @return null if no {@link Patch} is under the coordinate, else the {@link Coordinate} with the x, y, {@link Layer} and the {@link Patch}.
1276 * @throws NoninvertibleModelException
1277 * @throws NoninvertibleTransformException
1279 public Coordinate<Patch> toPatchCoordinate(final double world_x, final double world_y) throws NoninvertibleTransformException, NoninvertibleModelException {
1280 final Collection<Displayable> ps = find(Patch.class, world_x, world_y, true, false);
1281 Patch patch = null;
1282 if (ps.isEmpty()) {
1283 // No Patch under the point. Find the nearest Patch instead
1284 final Collection<Patch> patches = getAll(Patch.class);
1285 if (patches.isEmpty()) return null;
1286 double minSqDist = Double.MAX_VALUE;
1287 for (final Patch p : patches) {
1288 // Check if any of the 4 corners of the bounding box are beyond minSqDist
1289 final Rectangle b = p.getBoundingBox();
1290 final double d1 = Math.pow(b.x - world_x, 2) + Math.pow(b.y - world_y, 2),
1291 d2 = Math.pow(b.x + b.width - world_x, 2) + Math.pow(b.y - world_y, 2),
1292 d3 = Math.pow(b.x - world_x, 2) + Math.pow(b.y + b.height - world_y, 2),
1293 d4 = Math.pow(b.x + b.width - world_x, 2) + Math.pow(b.y + b.height - world_y, 2),
1294 d = Math.min(d1, Math.min(d2, Math.min(d3, d4)));
1295 if (d < minSqDist) {
1296 patch = p;
1297 minSqDist = d;
1299 // If the Patch has a CoordinateTransform, find the closest perimeter point
1300 if (p.hasCoordinateTransform()) {
1301 for (final Polygon pol : M.getPolygons(p.getArea())) { // Area in world coordinates
1302 for (int i=0; i<pol.npoints; ++i) {
1303 final double sqDist = Math.pow(pol.xpoints[0] - world_x, 2) + Math.pow(pol.ypoints[1] - world_y, 2);
1304 if (sqDist < minSqDist) {
1305 minSqDist = sqDist;
1306 patch = p;
1312 } else {
1313 patch = (Patch) ps.iterator().next();
1316 final double[] point = patch.toPixelCoordinate(world_x, world_y);
1317 return new Coordinate<Patch>(point[0], point[1], patch.getLayer(), patch);