fixed improper generic parameter use in Tree.duplicateAs > Map, necessary
[trakem2.git] / TrakEM2_ / src / main / java / ini / trakem2 / display / Layer.java
blob112e0988b1b020f6e4fbb90b1470dde4110063da
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)
688 * intersect the given Displayable (which is itself included if present in this very Layer). */
689 synchronized public <T extends Displayable> Collection<T> getIntersecting(final Displayable d, final Class<T> target) {
690 if (null != root) {
691 final Area area = new Area(d.getPerimeter());
692 if (root.isBetter(area.getBounds(), this)) {
693 return (Collection<T>) root.find(target, area, this, false, true);
696 final ArrayList<T> al = new ArrayList<T>();
697 for (int i = al_displayables.size() -1; i>-1; i--) {
698 final Object ob = al_displayables.get(i);
699 if (target.isAssignableFrom(ob.getClass())) continue;
700 final Displayable da = (Displayable)ob;
701 if (d.intersects(da)) {
702 al.add((T)da);
705 return al;
708 /** Returns -1 if not found. */
709 public final int indexOf(final Displayable d) {
710 return al_displayables.indexOf(d);
713 /** Within its own class only.
714 * '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). */
715 public void moveUp(final Displayable d) {
716 final int i = al_displayables.indexOf(d);
717 if (null == d || -1 == i || al_displayables.size() -1 == i) return;
718 if (al_displayables.get(i+1).getClass() == d.getClass()) {
719 //swap
720 al_displayables.remove(d);
721 al_displayables.add(i+1, d);
722 } else return;
723 updateInDatabase("stack_index");
724 Display.updatePanelIndex(d.getLayer(), d);
725 if (null != root) root.updateRange(this, d, i, i+1);
728 /** Within its own class only. */
729 public void moveDown(final Displayable d) {
730 final int i = al_displayables.indexOf(d);
731 if (null == d || -1 == i || 0 == i) return;
732 if (al_displayables.get(i-1).getClass() == d.getClass()) {
733 //swap
734 final Displayable o = al_displayables.remove(i-1);
735 al_displayables.add(i, o);
736 } else return;
737 updateInDatabase("stack_index");
738 Display.updatePanelIndex(d.getLayer(), d);
739 if (null != root) root.updateRange(this, d, i-1, i);
742 /** Within its own class only. */
743 public void moveTop(final Displayable d) { // yes I could have made several lists and make my life easier. Whatever
744 final int i = al_displayables.indexOf(d);
745 final int size = al_displayables.size();
746 if (null == d || -1 == i || size -1 == i) return;
747 final Class<?> c = d.getClass();
748 boolean done = false;
749 int j = i + 1;
750 for (; j<size; j++) {
751 if (al_displayables.get(j).getClass() == c) continue;
752 else {
753 al_displayables.remove(d);
754 al_displayables.add(--j, d); // j-1
755 done = true;
756 break;
759 // solves case of no other class present
760 if (!done) {
761 //add at the end
762 al_displayables.remove(d);
763 al_displayables.add(d);
764 j = size-1;
766 updateInDatabase("stack_index");
767 Display.updatePanelIndex(d.getLayer(), d);
768 if (null != root) root.updateRange(this, d, i, j);
771 /** Within its own class only. */
772 public void moveBottom(final Displayable d) {
773 final int i = al_displayables.indexOf(d);
774 if (null == d || -1 == i || 0 == i) return;
775 final Class<?> c = d.getClass();
776 boolean done = false;
777 int j = i - 1;
778 for (; j > -1; j--) {
779 if (al_displayables.get(j).getClass() == c) continue;
780 else {
781 al_displayables.remove(d);
782 al_displayables.add(++j, d); // j+1
783 done = true;
784 break;
787 // solve case of no other class present
788 if (!done) {
789 al_displayables.remove(d);
790 al_displayables.add(0, d);
791 j = 0;
793 updateInDatabase("stack_index");
794 Display.updatePanelIndex(d.getLayer(), d);
795 if (null != root) root.updateRange(this, d, j, i);
798 /** Within its own class only. */
799 public boolean isTop(final Displayable d) {
800 final int i = al_displayables.indexOf(d);
801 final int size = al_displayables.size();
802 if (size -1 == i) return true;
803 if (al_displayables.get(i+1).getClass() == d.getClass()) return false;
804 return true;
805 } // these two methods will throw an Exception if the Displayable is not found (-1 == i) (the null.getClass() *should* throw it)
806 /** Within its own class only. */
807 public boolean isBottom(final Displayable d) {
808 final int i = al_displayables.indexOf(d);
809 if (0 == i) return true;
810 if (al_displayables.get(i-1).getClass() == d.getClass()) return false;
811 return true;
814 /** 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.*/
815 public int relativeIndexOf(final Displayable d) {
816 final int k = al_displayables.indexOf(d);
817 if (-1 == k) return -1;
818 final Class<?> c = d.getClass();
819 final int size = al_displayables.size();
820 int i = k+1;
821 for (; i<size; i++) {
822 if (al_displayables.get(i).getClass() == c) continue;
823 else {
824 return i - k -1;
827 if (i == size) {
828 return i - k - 1;
830 Utils.log2("relativeIndexOf: return 0");
831 return 0;
834 /** Note: Not recursive into embedded LayerSet objects. Returns the hash set of objects whose visibility has changed. */
835 public HashSet<Displayable> setVisible(String type, final boolean visible, final boolean repaint) {
836 type = type.toLowerCase();
837 if (type.equals("image")) type = "patch";
838 final HashSet<Displayable> hs = new HashSet<Displayable>();
839 for (final Displayable d : al_displayables) {
840 if (visible != d.isVisible() && d.getClass().getName().toLowerCase().endsWith(type)) {
841 d.setVisible(visible, false); // don't repaint
842 hs.add(d);
845 if (repaint) {
846 Display.repaint(this);
848 Display.updateCheckboxes(hs, DisplayablePanel.VISIBILITY_STATE, visible);
849 return hs;
851 /** Returns the collection of Displayable whose visibility state has changed. */
852 public Collection<Displayable> setAllVisible(final boolean repaint) {
853 final Collection<Displayable> col = new ArrayList<Displayable>();
854 for (final Displayable d : al_displayables) {
855 if (!d.isVisible()) {
856 d.setVisible(true, repaint);
857 col.add(d);
860 return col;
863 /** Hide all except those whose type is in 'type' list, whose visibility flag is left unchanged. Returns the list of displayables made hidden. */
864 public HashSet<Displayable> hideExcept(final ArrayList<Class<?>> type, final boolean repaint) {
865 final HashSet<Displayable> hs = new HashSet<Displayable>();
866 for (final Displayable d : al_displayables) {
867 if (!type.contains(d.getClass()) && d.isVisible()) {
868 d.setVisible(false, repaint);
869 hs.add(d);
872 return hs;
875 @Override
876 public void exportXML(final StringBuilder sb_body, final String indent, final XMLOptions options) {
877 final String in = indent + "\t";
878 // 1 - open tag
879 sb_body.append(indent).append("<t2_layer oid=\"").append(id).append("\"\n")
880 .append(in).append(" thickness=\"").append(thickness).append("\"\n")
881 .append(in).append(" z=\"").append(z).append("\"\n")
883 // TODO this search is linear!
884 final LayerThing lt = project.findLayerThing(this);
885 String title;
886 if (null == lt) title = null;
887 else title = lt.getTitle();
888 if (null == title) title = "";
889 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.
890 sb_body.append(indent).append(">\n");
891 // 2 - export children
892 if (null != al_displayables) {
893 for (final Displayable d : al_displayables) {
894 d.exportXML(sb_body, in, options);
897 // 3 - close tag
898 sb_body.append(indent).append("</t2_layer>\n");
901 /** Includes all Displayable objects in the list of possible children. */
902 static public void exportDTD(final StringBuilder sb_header, final HashSet<String> hs, final String indent) {
903 final String type = "t2_layer";
904 if (hs.contains(type)) return;
905 hs.add(type);
906 sb_header.append(indent).append("<!ELEMENT t2_layer (t2_patch,t2_label,t2_layer_set,t2_profile)>\n")
907 .append(indent).append(Displayable.TAG_ATTR1).append(type).append(" oid").append(Displayable.TAG_ATTR2)
908 .append(indent).append(Displayable.TAG_ATTR1).append(type).append(" thickness").append(Displayable.TAG_ATTR2)
909 .append(indent).append(Displayable.TAG_ATTR1).append(type).append(" z").append(Displayable.TAG_ATTR2)
913 protected String getLayerThingTitle() {
914 final LayerThing lt = project.findLayerThing(this);
915 if (null == lt || null == lt.getTitle() || 0 == lt.getTitle().trim().length()) return "";
916 return lt.getTitle();
919 @Override
920 public String getTitle() {
921 final LayerThing lt = project.findLayerThing(this);
922 if (null == lt || null == lt.getTitle() || 0 == lt.getTitle().trim().length()) return this.toString();
923 return lt.getTitle();
926 public void destroy() {
927 for (final Displayable d : al_displayables) {
928 d.destroy();
932 /** Returns null if no Displayable objects of class c exist. */
933 public Rectangle getMinimalBoundingBox(final Class<?> c) {
934 return getMinimalBoundingBox(c, true);
937 /** Returns null if no Displayable objects of class c exist (or are visible if {@param visible_only} is true). */
938 public Rectangle getMinimalBoundingBox(final Class<?> c, final boolean visible_only) {
939 Rectangle box = null;
940 Rectangle tmp = new Rectangle();
941 for (final Displayable d : getDisplayables(c, visible_only)) {
942 tmp = d.getBoundingBox(tmp);
943 if (null == box) {
944 box = (Rectangle)tmp.clone();
945 continue;
947 box.add(tmp);
949 return box;
952 /** Returns an Area in world coordinates that represents the inside of all Patches. */
953 public Area getPatchArea(final boolean visible_only) {
954 final Area area = new Area(); // with width,height zero
955 for (final Patch p: getAll(Patch.class)) {
956 if (visible_only && p.isVisible()) {
957 area.add(p.getArea());
960 return area;
963 /** Preconcatenate the given AffineTransform to all Displayable objects of class c, without respecting their links. */
964 public void apply(final Class<?> c, final AffineTransform at) {
965 final boolean all = Displayable.class == c;
966 for (final Displayable d : al_displayables) {
967 if (all || d.getClass() == c) {
968 d.at.preConcatenate(at);
971 recreateBuckets();
974 /** 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. */
975 public Layer clone(final Project pr, final LayerSet ls, final Rectangle roi, final boolean copy_id, final boolean ignore_hidden_patches) {
976 final long nid = copy_id ? this.id : pr.getLoader().getNextId();
977 final Layer copy = new Layer(pr, nid, z, thickness);
978 copy.parent = ls;
979 for (final Displayable d : find(roi)) {
980 if (ignore_hidden_patches && !d.isVisible() && d.getClass() == Patch.class) continue;
981 copy.addSilently(d.clone(pr, copy_id));
983 final AffineTransform transform = new AffineTransform();
984 transform.translate(-roi.x, -roi.y);
985 copy.apply(Displayable.class, transform);
986 return copy;
989 static public final int IMAGEPROCESSOR = 0;
990 static public final int PIXELARRAY = 1;
991 static public final int IMAGE = 2;
992 static public final int IMAGEPLUS = 3;
994 /** Returns the region defined by the rectangle as an image in the type and format specified.
995 * The type is either ImagePlus.GRAY8 or ImagePlus.COLOR_RGB.
996 * The format is either Layer.IMAGEPROCESSOR, Layer.IMAGEPLUS, Layer.PIXELARRAY or Layer.IMAGE.
998 public Object grab(final Rectangle r, final double scale, final Class<?> c, final int c_alphas, final int format, final int type) {
999 //Ensure some memory is free
1000 project.getLoader().releaseToFit(r.width, r.height, type, 1.1f);
1001 if (IMAGE == format) {
1002 return project.getLoader().getFlatAWTImage(this, r, scale, c_alphas, type, c, null, true, Color.black);
1003 } else {
1004 final ImagePlus imp = project.getLoader().getFlatImage(this, r, scale, c_alphas, type, c, null, true);
1005 switch (format) {
1006 case IMAGEPLUS:
1007 return imp;
1008 case IMAGEPROCESSOR:
1009 return imp.getProcessor();
1010 case PIXELARRAY:
1011 return imp.getProcessor().getPixels();
1014 return null;
1017 public DBObject findById(final long id) {
1018 if (this.id == id) return this;
1019 for (final Displayable d : al_displayables) {
1020 if (d.getId() == id) return d;
1022 return null;
1025 // private to the package
1026 void linkPatchesR() {
1027 for (final Displayable d : al_displayables) {
1028 if (d.getClass() == LayerSet.class) ((LayerSet)d).linkPatchesR();
1029 d.linkPatches(); // Patch.class does nothing
1033 /** Recursive into nested LayerSet objects.*/
1034 public void updateLayerTree() {
1035 project.getLayerTree().addLayer(parent, this);
1036 for (final Displayable d : getDisplayables(LayerSet.class)) {
1037 ((LayerSet)d).updateLayerTree();
1041 /** Don't use this for fast pixel grabbing; this is intended for the dropper tool and status bar reporting by mouse motion. */
1042 public int[] getPixel(final int x, final int y, final double mag) {
1043 // find Patch under cursor
1044 final Collection<Displayable> under = find(Patch.class, x, y);
1045 if (null == under || under.isEmpty()) return new int[3]; // zeros
1046 final Patch pa = (Patch)under.iterator().next();// get(0) // the top one, since they are ordered like html divs
1047 // TODO: edit here when adding layer mipmaps
1048 return pa.getPixel(x, y, mag);
1051 synchronized public void recreateBuckets() {
1052 this.root = new Bucket(0, 0, (int)(0.00005 + getLayerWidth()), (int)(0.00005 + getLayerHeight()), Bucket.getBucketSide(this, this));
1053 this.db_map = new HashMap<Displayable,HashSet<Bucket>>();
1054 this.root.populate(this, this, db_map);
1055 //root.debug();
1058 /** Update buckets of a position change for the given Displayable. */
1059 @Override
1060 public void updateBucket(final Displayable d, final Layer layer) { // ignore layer
1061 if (null != root) root.updatePosition(d, this, db_map);
1064 public void checkBuckets() {
1065 if (use_buckets && (null == root || null == db_map)) recreateBuckets();
1068 private boolean use_buckets = true;
1070 public void setBucketsEnabled(final boolean b) {
1071 this.use_buckets = b;
1072 if (!use_buckets) this.root = null;
1075 static class DoEditLayer implements DoStep {
1076 final double z, thickness;
1077 final Layer la;
1078 DoEditLayer(final Layer layer) {
1079 this.la = layer;
1080 this.z = layer.z;
1081 this.thickness = layer.thickness;
1083 @Override
1084 public Displayable getD() { return null; }
1085 @Override
1086 public boolean isEmpty() { return false; }
1087 @Override
1088 public boolean isIdenticalTo(final Object ob) {
1089 if (!(ob instanceof Layer)) return false;
1090 final Layer layer = (Layer) ob;
1091 return this.la.id == layer.id && this.z == layer.z && this.thickness == layer.thickness;
1093 @Override
1094 public boolean apply(final int action) {
1095 la.z = this.z;
1096 la.thickness = this.thickness;
1097 la.getProject().getLayerTree().updateUILater();
1098 Display.update(la.getParent());
1099 return true;
1103 static class DoEditLayers implements DoStep {
1104 final ArrayList<DoEditLayer> all = new ArrayList<DoEditLayer>();
1105 DoEditLayers(final List<Layer> all) {
1106 for (final Layer la : all) {
1107 this.all.add(new DoEditLayer(la));
1110 @Override
1111 public Displayable getD() { return null; }
1112 @Override
1113 public boolean isEmpty() { return all.isEmpty(); }
1114 @Override
1115 public boolean isIdenticalTo(final Object ob) {
1116 if (!(ob instanceof DoEditLayers)) return false;
1117 final DoEditLayers other = (DoEditLayers) ob;
1118 if (all.size() != other.all.size()) return false;
1119 // Order matters:
1120 final Iterator<DoEditLayer> it1 = all.iterator();
1121 final Iterator<DoEditLayer> it2 = other.all.iterator();
1122 for (; it1.hasNext() && it2.hasNext(); ) {
1123 if (!it1.next().isIdenticalTo(it2.next())) return false;
1125 return true;
1127 @Override
1128 public boolean apply(final int action) {
1129 boolean failed = false;
1130 for (final DoEditLayer one : all) {
1131 if (!one.apply(action)) {
1132 failed = true;
1135 return !failed;
1139 /** Add an object that is Layer bound, such as Profile, Patch, DLabel and LayerSet. */
1140 static class DoContentChange implements DoStep {
1141 final Layer la;
1142 final ArrayList<Displayable> al;
1143 DoContentChange(final Layer la) {
1144 this.la = la;
1145 this.al = la.getDisplayables(); // a copy
1147 @Override
1148 public Displayable getD() { return null; }
1149 @Override
1150 public boolean isEmpty() { return false; }
1151 /** Check that the Displayable objects of this layer are the same and in the same quantity and order. */
1152 @Override
1153 public boolean isIdenticalTo(final Object ob) {
1154 if (!(ob instanceof DoContentChange)) return false;
1155 final DoContentChange dad = (DoContentChange) ob;
1156 if (la != dad.la || al.size() != dad.al.size()) return false;
1157 // Order matters:
1158 final Iterator<Displayable> it1 = al.iterator();
1159 final Iterator<Displayable> it2 = dad.al.iterator();
1160 for (; it1.hasNext() && it2.hasNext(); ) {
1161 if (it1.next() != it2.next()) return false;
1163 return true;
1165 @Override
1166 public boolean apply(final int action) {
1167 // find the subset in la.al_displayables that is not in this.al
1168 final HashSet<Displayable> sub1 = new HashSet<Displayable>(la.al_displayables);
1169 sub1.removeAll(this.al);
1170 // find the subset in this.al that is not in la.al_displayables
1171 final HashSet<Displayable> sub2 = new HashSet<Displayable>(this.al);
1172 sub2.removeAll(la.al_displayables);
1174 HashSet<Displayable> subA=null, subB=null;
1176 if (action == DoStep.UNDO) {
1177 subA = sub1;
1178 subB = sub2;
1179 } else if (action == DoStep.REDO) {
1180 subA = sub2;
1181 subB = sub1;
1183 if (null != subA && null != subB) {
1184 // Mark Patch for mipmap file removal
1185 for (final Displayable d: subA) {
1186 if (d.getClass() == Patch.class) {
1187 d.getProject().getLoader().queueForMipmapRemoval((Patch)d, true);
1190 // ... or unmark:
1191 for (final Displayable d: subB) {
1192 if (d.getClass() == Patch.class) {
1193 d.getProject().getLoader().queueForMipmapRemoval((Patch)d, false);
1198 la.al_displayables.clear();
1199 la.al_displayables.addAll(this.al);
1200 la.recreateBuckets();
1201 Display.updateVisibleTabs();
1202 Display.clearSelection();
1203 Display.update(la);
1204 return true;
1208 static protected class DoMoveDisplayable implements DoStep {
1209 final ArrayList<Displayable> al_displayables;
1210 final Layer layer;
1211 HashSet<DoStep> dependents = null;
1212 DoMoveDisplayable(final Layer layer) {
1213 this.layer = layer;
1214 this.al_displayables = new ArrayList<Displayable>(layer.al_displayables);
1216 @Override
1217 public boolean apply(final int action) {
1218 // Replace all ZDisplayable
1219 layer.al_displayables.clear();
1220 layer.al_displayables.addAll(this.al_displayables);
1221 Display.update(layer);
1222 return true;
1224 @Override
1225 public boolean isEmpty() {
1226 return false;
1228 @Override
1229 public Displayable getD() {
1230 return null;
1232 @Override
1233 public boolean isIdenticalTo(final Object ob) {
1234 if (!(ob instanceof DoMoveDisplayable)) return false;
1235 final DoMoveDisplayable dm = (DoMoveDisplayable)ob;
1236 if (dm.layer != this.layer) return false;
1237 if (dm.al_displayables.size() != this.al_displayables.size()) return false;
1238 for (int i=0; i<this.al_displayables.size(); ++i) {
1239 if (dm.al_displayables.get(i) != this.al_displayables.get(i)) return false;
1241 return true;
1247 private Overlay overlay = null;
1249 /** Return the current Overlay or a new one if none yet. */
1250 synchronized public Overlay getOverlay() {
1251 if (null == overlay) overlay = new Overlay();
1252 return overlay;
1254 // Used by DisplayCanvas to paint
1255 Overlay getOverlay2() {
1256 return overlay;
1258 /** Set to null to remove the Overlay.
1259 * @return the previous Overlay, if any. */
1260 synchronized public Overlay setOverlay(final Overlay o) {
1261 final Overlay old = this.overlay;
1262 this.overlay = o;
1263 return old;
1266 @Override
1267 public int compareTo(final Layer layer) {
1268 final double diff = this.z - layer.z;
1269 if (diff < 0) return -1;
1270 if (diff > 0) return 1;
1271 return 0;
1274 /** Transfer the world coordinate specified by {@param world_x},{@param world_y}
1275 * in pixels, to the local coordinate of the {@link Patch} immediately present under it.
1276 * @return null if no {@link Patch} is under the coordinate, else the {@link Coordinate} with the x, y, {@link Layer} and the {@link Patch}.
1277 * @throws NoninvertibleModelException
1278 * @throws NoninvertibleTransformException
1280 public Coordinate<Patch> toPatchCoordinate(final double world_x, final double world_y) throws NoninvertibleTransformException, NoninvertibleModelException {
1281 final Collection<Displayable> ps = find(Patch.class, world_x, world_y, true, false);
1282 Patch patch = null;
1283 if (ps.isEmpty()) {
1284 // No Patch under the point. Find the nearest Patch instead
1285 final Collection<Patch> patches = getAll(Patch.class);
1286 if (patches.isEmpty()) return null;
1287 double minSqDist = Double.MAX_VALUE;
1288 for (final Patch p : patches) {
1289 // Check if any of the 4 corners of the bounding box are beyond minSqDist
1290 final Rectangle b = p.getBoundingBox();
1291 final double d1 = Math.pow(b.x - world_x, 2) + Math.pow(b.y - world_y, 2),
1292 d2 = Math.pow(b.x + b.width - world_x, 2) + Math.pow(b.y - world_y, 2),
1293 d3 = Math.pow(b.x - world_x, 2) + Math.pow(b.y + b.height - world_y, 2),
1294 d4 = Math.pow(b.x + b.width - world_x, 2) + Math.pow(b.y + b.height - world_y, 2),
1295 d = Math.min(d1, Math.min(d2, Math.min(d3, d4)));
1296 if (d < minSqDist) {
1297 patch = p;
1298 minSqDist = d;
1300 // If the Patch has a CoordinateTransform, find the closest perimeter point
1301 if (p.hasCoordinateTransform()) {
1302 for (final Polygon pol : M.getPolygons(p.getArea())) { // Area in world coordinates
1303 for (int i=0; i<pol.npoints; ++i) {
1304 final double sqDist = Math.pow(pol.xpoints[0] - world_x, 2) + Math.pow(pol.ypoints[1] - world_y, 2);
1305 if (sqDist < minSqDist) {
1306 minSqDist = sqDist;
1307 patch = p;
1313 } else {
1314 patch = (Patch) ps.iterator().next();
1317 final double[] point = patch.toPixelCoordinate(world_x, world_y);
1318 return new Coordinate<Patch>(point[0], point[1], patch.getLayer(), patch);