Updated copyright dates.
[trakem2.git] / ini / trakem2 / display / LayerSet.java
blobd7900eb70e651f4313aa42893436388b6dd9dd11
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.gui.GenericDialog;
27 import ij.measure.Calibration;
28 import ij.ImagePlus;
29 import ij.ImageStack;
31 import ini.trakem2.ControlWindow;
32 import ini.trakem2.Project;
33 import ini.trakem2.persistence.DBObject;
34 import ini.trakem2.utils.ProjectToolbar;
35 import ini.trakem2.utils.Utils;
36 import ini.trakem2.utils.IJError;
37 import ini.trakem2.imaging.LayerStack;
38 import ini.trakem2.tree.LayerThing;
39 import ini.trakem2.tree.Thing;
41 import java.awt.AlphaComposite;
42 import java.awt.Color;
43 import java.awt.Composite;
44 import java.awt.event.KeyEvent;
45 import java.awt.event.MouseEvent;
46 import java.awt.Graphics;
47 import java.awt.Graphics2D;
48 import java.awt.Image;
49 import java.awt.Rectangle;
50 import java.awt.geom.AffineTransform;
51 import java.awt.geom.Area;
52 import java.util.ArrayList;
53 import java.util.List;
54 import java.util.HashMap;
55 import java.util.TreeMap;
56 import java.util.Iterator;
57 import java.util.HashSet;
58 import java.util.LinkedList;
59 import java.util.Map;
60 import java.util.Set;
61 import java.util.Collection;
64 /** A LayerSet represents an axis on which layers can be stacked up. Paints with 0.67 alpha transparency when not active. */
65 public final class LayerSet extends Displayable implements Bucketable { // Displayable is already extending DBObject
67 // the anchors for resizing
68 static public final int NORTH = 0;
69 static public final int NORTHEAST = 1;
70 static public final int EAST = 2;
71 static public final int SOUTHEAST = 3;
72 static public final int SOUTH = 4;
73 static public final int SOUTHWEST = 5;
74 static public final int WEST = 6;
75 static public final int NORTHWEST = 7;
76 static public final int CENTER = 8;
78 // the possible rotations
79 static public final int R90 = 9;
80 static public final int R270 = 10;
81 // the posible flips
82 static public final int FLIP_HORIZONTAL = 11;
83 static public final int FLIP_VERTICAL = 12;
85 // postions in the stack
86 static public final int TOP = 13;
87 static public final int UP = 14;
88 static public final int DOWN = 15;
89 static public final int BOTTOM = 16;
91 static public final String[] snapshot_modes = new String[]{"Full","Outlines","Disabled"};
93 /** 0, 1, 2 -- corresponding to snapshot_modes entries above. */
94 private int snapshots_mode = 0;
96 static public final String[] ANCHORS = new String[]{"north", "north east", "east", "southeast", "south", "south west", "west", "north west", "center"};
97 static public final String[] ROTATIONS = new String[]{"90 right", "90 left", "Flip horizontally", "Flip vertically"};
99 private double layer_width; // the Displayable.width is for the representation, not for the dimensions of the LayerSet!
100 private double layer_height;
101 private double rot_x;
102 private double rot_y;
103 private double rot_z; // should be equivalent to the Displayable.rot
104 private final ArrayList<Layer> al_layers = new ArrayList<Layer>();
105 /** The layer in which this LayerSet lives. If null, this is the root LayerSet. */
106 private Layer parent = null;
107 /** A LayerSet can contain Displayables that are show in every single Layer, such as Pipe objects. */
108 private final ArrayList<ZDisplayable> al_zdispl = new ArrayList<ZDisplayable>();
110 /** For creating snapshots. */
111 private boolean snapshots_quality = true;
113 /** Tool to manually register using landmarks across two layers. Uses the toolbar's 'Align tool'. */
114 private Align align = null;
116 /** The scaling applied to the Layers when painting them for presentation as a LayerStack. If -1, automatic mode (default) */
117 private double virtual_scale = -1;
118 /** The maximum size of either width or height when virtuzaling pixel access to the layers.*/
119 private int max_dimension = 1024;
120 private boolean virtualization_enabled = false;
122 private Calibration calibration = new Calibration(); // default values
124 /** Dummy. */
125 protected LayerSet(Project project, long id) {
126 super(project, id, null, false, null, 20, 20);
129 /** Create a new LayerSet with a 0,0,0 rotation vector and default 20,20 px Displayable width,height. */
130 public LayerSet(Project project, String title, double x, double y, Layer parent, double layer_width, double layer_height) {
131 super(project, title, x, y);
132 rot_x = rot_y = rot_z = 0.0D;
133 this.width = 20;
134 this.height = 20; // for the label that paints into the parent Layer
135 this.parent = parent;
136 this.layer_width = layer_width;
137 this.layer_height = layer_height;
138 addToDatabase();
141 /** Reconstruct from the database. */
142 public LayerSet(Project project, long id, String title, double width, double height, double rot_x, double rot_y, double rot_z, double layer_width, double layer_height, boolean locked, int shapshots_mode, AffineTransform at) {
143 super(project, id, title, locked, at, width, height);
144 this.rot_x = rot_x;
145 this.rot_y = rot_y;
146 this.rot_z = rot_z;
147 this.layer_width = layer_width;
148 this.layer_height= layer_height;
149 this.snapshots_mode = snapshots_mode;
150 // the parent will be set by the LayerThing.setup() calling Layer.addSilently()
151 // the al_layers will be filled idem.
154 /** Reconstruct from an XML entry. */
155 public LayerSet(Project project, long id, HashMap ht_attributes, HashMap ht_links) {
156 super(project, id, ht_attributes, ht_links);
157 for (Iterator it = ht_attributes.entrySet().iterator(); it.hasNext(); ) {
158 Map.Entry entry = (Map.Entry)it.next();
159 String key = (String)entry.getKey();
160 String data = (String)entry.getValue();
161 if (key.equals("layer_width")) {
162 this.layer_width = Double.parseDouble(data);
163 } else if (key.equals("layer_height")) {
164 this.layer_height = Double.parseDouble(data);
165 } else if (key.equals("rot_x")) {
166 this.rot_x = Double.parseDouble(data);
167 } else if (key.equals("rot_y")) {
168 this.rot_y = Double.parseDouble(data);
169 } else if (key.equals("rot_z")) {
170 this.rot_z = Double.parseDouble(data);
171 } else if (key.equals("snapshots_quality")) {
172 snapshots_quality = Boolean.valueOf(data.trim().toLowerCase());
173 } else if (key.equals("snapshots_mode")) {
174 String smode = data.trim();
175 for (int i=0; i<snapshot_modes.length; i++) {
176 if (smode.equals(snapshot_modes[i])) {
177 snapshots_mode = i;
178 break;
182 // the above would be trivial in Jython, and can be done by reflection! The problem would be in the parsing, that would need yet another if/else if/ sequence was any field to change or be added.
186 /** For reconstruction purposes: set the active layer to the ZDisplayable objects. Recurses through LayerSets in the children layers. */
187 public void setup() {
188 final Layer la0 = al_layers.get(0);
189 for (ZDisplayable zd : al_zdispl) zd.setLayer(la0); // just any Layer
190 for (Layer layer : al_layers) {
191 for (Iterator itl = layer.getDisplayables().iterator(); itl.hasNext(); ) {
192 Object ob = itl.next();
193 if (ob instanceof LayerSet) {
194 ((LayerSet)ob).setup();
200 /** Create a new LayerSet in the middle of the parent Layer. */
201 public LayerSet create(Layer parent_layer) {
202 if (null == parent_layer) return null;
203 GenericDialog gd = ControlWindow.makeGenericDialog("New Layer Set");
204 gd.addMessage("In pixels:");
205 gd.addNumericField("width: ", this.layer_width, 3);
206 gd.addNumericField("height: ", this.layer_height, 3);
207 gd.showDialog();
208 if (gd.wasCanceled()) return null;
209 try {
210 double width = gd.getNextNumber();
211 double height = gd.getNextNumber();
212 if (Double.isNaN(width) || Double.isNaN(height)) return null;
213 if (0 == width || 0 == height) {
214 Utils.showMessage("Cannot accept zero width or height for LayerSet dimensions.");
215 return null;
217 // make a new LayerSet with x,y in the middle of the parent_layer
218 return new LayerSet(project, "Layer Set", parent_layer.getParent().getLayerWidth() / 2, parent_layer.getParent().getLayerHeight() / 2, parent_layer, width/2, height/2);
219 } catch (Exception e) { Utils.log("LayerSet.create: " + e); }
220 return null;
223 /** Add a new Layer silently, ordering by z as well.*/
224 public void addSilently(final Layer layer) {
225 if (null == layer || al_layers.contains(layer)) return;
226 try {
227 double z = layer.getZ();
228 int i = 0;
229 for (Layer la : al_layers) {
230 if (! (la.getZ() < z) ) {
231 al_layers.add(i, layer);
232 layer.setParentSilently(this);
233 return;
235 i++;
237 // else, add at the end
238 al_layers.add(layer);
239 layer.setParentSilently(this);
240 } catch (Exception e) {
241 Utils.log("LayerSet.addSilently: Not a Layer, not adding DBObject id=" + layer.getId());
242 return;
246 /** Add a new Layer, inserted according to its Z. */
247 public void add(final Layer layer) {
248 if (-1 != al_layers.indexOf(layer)) return;
249 final double z = layer.getZ();
250 final int n = al_layers.size();
251 int i = 0;
252 for (; i<n; i++) {
253 Layer l = (Layer)al_layers.get(i);
254 if (l.getZ() < z) continue;
255 break;
257 if (i < n) {
258 al_layers.add(i, layer);
259 } else {
260 al_layers.add(layer);
262 layer.setParent(this);
263 Display.updateLayerScroller(this);
264 //debug();
267 private void debug() {
268 Utils.log("LayerSet debug:");
269 for (int i=0; i<al_layers.size(); i++)
270 Utils.log(i + " : " + ((Layer)al_layers.get(i)).getZ());
273 public Layer getParent() {
274 return parent;
277 /** 'update' in database or not. */
278 public void setLayer(Layer layer, boolean update) {
279 super.setLayer(layer, update);
280 if (null != layer) this.parent = layer; // repeated pointer, eliminate 'parent' !
283 public void setParent(Layer layer) {
284 if (null == layer || layer == parent) return;
285 this.parent = layer;
286 updateInDatabase("parent_id");
289 public void mousePressed(MouseEvent me, int x_p, int y_p, Rectangle srcRect, double mag) {
290 if (ProjectToolbar.SELECT != ProjectToolbar.getToolId()) return;
291 Display.setActive(me, this);
292 if (2 == me.getClickCount() && al_layers.size() > 0) {
293 new Display(project, al_layers.get(0));
297 public void mouseDragged(MouseEvent me, int x_p, int y_p, int x_d, int y_d, int x_d_old, int y_d_old, Rectangle srcRect, double mag) {
298 if (ProjectToolbar.SELECT != ProjectToolbar.getToolId()) return;
299 super.translate(x_d - x_d_old, y_d - y_d_old);
300 Display.repaint(layer, this, 0);
303 public void mouseReleased(MouseEvent me, int x_p, int y_p, int x_d, int y_d, int x_r, int y_r, Rectangle srcRect, double mag) {
304 // nothing
307 public void keyPressed(KeyEvent ke) {
308 Utils.log("LayerSet.keyPressed: not yet implemented.");
309 // TODO
312 public String toString() {
313 return this.title;
316 public void paint(Graphics2D g, double magnification, boolean active, int channels, Layer active_layer) {
317 //arrange transparency
318 Composite original_composite = null;
319 if (alpha != 1.0f) {
320 original_composite = g.getComposite();
321 g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha));
324 //set color
325 g.setColor(this.color);
326 // fill a background box
327 g.fillRect(0, 0, (int)(this.width), (int)(this.height));
328 g.setColor(new Color(255 - color.getRed(), 255 - color.getGreen(), 255 - color.getBlue()).brighter()); // the "opposite", but brighter, so it won't fail to generate contrast if the color is 127 in all channels
329 int x = (int)(this.width/5);
330 int y = (int)(this.height/5);
331 int width = (int)(this.width/5);
332 int height = (int)(this.height/5 * 3);
334 g.fillRect(x, y, width, height);
336 x = (int)(this.width/5 * 2);
337 y = (int)(this.height/5 * 3);
338 width = (int)(this.width/5 * 2);
339 height = (int)(this.height/5);
341 g.fillRect(x, y, width, height);
343 //Transparency: fix composite back to original.
344 if (alpha != 1.0f) {
345 g.setComposite(original_composite);
349 public double getLayerWidth() { return layer_width; }
350 public double getLayerHeight() { return layer_height; }
351 public double getRotX() { return rot_x; }
352 public double getRotY() { return rot_y; }
353 public double getRotZ() { return rot_z; }
355 public int size() {
356 return al_layers.size();
359 public void setRotVector(double rot_x, double rot_y, double rot_z) {
360 if (Double.isNaN(rot_x) || Double.isNaN(rot_y) || Double.isNaN(rot_z)) {
361 Utils.showMessage("LayerSet: Rotation vector contains NaNs. Not updating.");
362 return;
363 } else if (rot_x == this.rot_x && rot_y == this.rot_y && rot_z == this.rot_z) {
364 return;
366 this.rot_x = rot_x;
367 this.rot_y = rot_y;
368 this.rot_z = rot_z;
369 updateInDatabase("rot");
372 /** Used by the Loader after loading blindly a lot of Patches. Will crop the canvas to the minimum size possible. */
373 public boolean setMinimumDimensions() {
374 // find current x,y,width,height that crops the canvas without cropping away any Displayable
375 double x = Double.NaN;
376 double y = Double.NaN;
377 double xe = 0; // lower right corner (x end)
378 double ye = 0;
379 double tx = 0;
380 double ty = 0;
381 double txe = 0;
382 double tye = 0;
383 // collect all Displayable and ZDisplayable objects
384 final ArrayList al = new ArrayList();
385 for (int i=al_layers.size() -1; i>-1; i--) {
386 al.addAll(((Layer)al_layers.get(i)).getDisplayables());
388 al.addAll(al_zdispl);
390 // find minimum bounding box
391 Rectangle b = new Rectangle();
392 for (Iterator it = al.iterator(); it.hasNext(); ) {
393 Displayable d = (Displayable)it.next();
394 b = d.getBoundingBox(b); // considers rotation
395 tx = b.x;//d.getX();
396 ty = b.y;//d.getY();
397 // set first coordinates
398 if (Double.isNaN(x) || Double.isNaN(y)) { // Double.NaN == x fails!
399 x = tx;
400 y = ty;
402 txe = tx + b.width;//d.getWidth();
403 tye = ty + b.height;//d.getHeight();
404 if (tx < x) x = tx;
405 if (ty < y) y = ty;
406 if (txe > xe) xe = txe;
407 if (tye > ye) ye = tye;
409 // if none, then stop
410 if (Double.isNaN(x) || Double.isNaN(y)) {
411 Utils.showMessage("No displayable objects, don't know how to resize the canvas and Layerset.");
412 return false;
415 double w = xe - x;
416 double h = ye - y;
417 if (w <= 0 || h <= 0) {
418 Utils.log("LayerSet.setMinimumDimensions: zero width or height, NOT resizing.");
419 return false;
422 // Record previous state
423 if (prepareStep(this)) {
424 addEditStep(new LayerSet.DoResizeLayerSet(this));
427 // translate
428 if (0 != x || 0 != y) {
429 project.getLoader().startLargeUpdate();
430 try {
431 final AffineTransform at2 = new AffineTransform();
432 at2.translate(-x, -y);
433 //Utils.log2("translating all displayables by " + x + "," + y);
434 for (Iterator it = al.iterator(); it.hasNext(); ) {
435 //((Displayable)it.next()).translate(-x, -y, false); // drag regardless of getting off current LayerSet bounds
436 // optimized to avoid creating so many AffineTransform instances:
437 final Displayable d = (Displayable)it.next();
438 //Utils.log2("BEFORE: " + d.getBoundingBox());
439 d.getAffineTransform().preConcatenate(at2);
440 //Utils.log2("AFTER: " + d.getBoundingBox());
441 d.updateInDatabase("transform");
443 project.getLoader().commitLargeUpdate();
444 } catch (Exception e) {
445 IJError.print(e);
446 project.getLoader().rollback();
447 return false;
451 //Utils.log("x,y xe,ye : " + x + "," + y + " " + xe + "," + ye);
452 // finally, accept:
453 if (w != layer_width || h != layer_height) {
454 this.layer_width = Math.ceil(w); // stupid int to double conversions ... why floating point math is a non-solved problem? Well, it is for SBCL
455 this.layer_height = Math.ceil(h);
456 updateInDatabase("layer_dimensions");
457 if (null != root) recreateBuckets(true);
458 // and notify the Displays, if any
459 Display.update(this);
460 Display.pack(this);
463 // Record current state:
464 addEditStep(new LayerSet.DoResizeLayerSet(this));
466 return true;
469 /** Enlarges the display in the given direction; the anchor is the point to keep still, and can be any of LayerSet.NORTHWEST (top-left), etc. */
470 public boolean enlargeToFit(final Displayable d, final int anchor) {
471 final Rectangle r = new Rectangle(0, 0, (int)Math.ceil(layer_width), (int)Math.ceil(layer_height));
472 final Rectangle b = d.getBoundingBox(null);
473 // check if necessary
474 if (r.contains(b)) return false;
475 // else, enlarge to fit it
476 r.add(b);
477 return setDimensions(r.width, r.height, anchor);
480 /** May leave objects beyond the visible window. */
481 public void setDimensions(double x, double y, double layer_width, double layer_height) {
482 // Record previous state
483 if (prepareStep(this)) {
484 addEditStep(new LayerSet.DoResizeLayerSet(this));
487 this.layer_width = layer_width;
488 this.layer_height = layer_height;
489 final AffineTransform affine = new AffineTransform();
490 affine.translate(-x, -y);
491 for (ZDisplayable zd : al_zdispl) {
492 zd.getAffineTransform().preConcatenate(affine);
493 zd.updateInDatabase("transform");
495 for (Layer la : al_layers) la.apply(Displayable.class, affine);
496 if (null != root) {
497 recreateBuckets(true);
499 Display.update(this);
501 // Record new state
502 addEditStep(new LayerSet.DoResizeLayerSet(this));
505 /** Returns false if any Displayables are being partially or totally cropped away. */
506 public boolean setDimensions(double layer_width, double layer_height, int anchor) {
507 // check preconditions
508 if (Double.isNaN(layer_width) || Double.isNaN(layer_height)) { Utils.log("LayerSet.setDimensions: NaNs! Not adjusting."); return false; }
509 if (layer_width <=0 || layer_height <= 0) { Utils.showMessage("LayerSet: can't accept zero or a minus for layer width or height"); return false; }
510 if (anchor < NORTH || anchor > CENTER) { Utils.log("LayerSet: wrong anchor, not resizing."); return false; }
512 // Record previous state
513 if (prepareStep(this)) {
514 addEditStep(new LayerSet.DoResizeLayerSet(this));
517 // new coordinates:
518 double new_x = 0;// the x,y of the old 0,0
519 double new_y = 0;
520 switch (anchor) {
521 case NORTH:
522 case SOUTH:
523 case CENTER:
524 new_x = (layer_width - this.layer_width) / 2; // (this.layer_width - layer_width) / 2;
525 break;
526 case NORTHWEST:
527 case WEST:
528 case SOUTHWEST:
529 new_x = 0;
530 break;
531 case NORTHEAST:
532 case EAST:
533 case SOUTHEAST:
534 new_x = layer_width - this.layer_width; // (this.layer_width - layer_width);
535 break;
537 switch (anchor) {
538 case WEST:
539 case EAST:
540 case CENTER:
541 new_y = (layer_height - this.layer_height) / 2;
542 break;
543 case NORTHWEST:
544 case NORTH:
545 case NORTHEAST:
546 new_y = 0;
547 break;
548 case SOUTHWEST:
549 case SOUTH:
550 case SOUTHEAST:
551 new_y = (layer_height - this.layer_height);
552 break;
556 Utils.log("anchor: " + anchor);
557 Utils.log("LayerSet: existing w,h = " + this.layer_width + "," + this.layer_height);
558 Utils.log("LayerSet: new w,h = " + layer_width + "," + layer_height);
561 // collect all Displayable and ZDisplayable objects
562 ArrayList al = new ArrayList();
563 for (int i=al_layers.size() -1; i>-1; i--) {
564 al.addAll(((Layer)al_layers.get(i)).getDisplayables());
566 al.addAll(al_zdispl);
568 // check that no displayables are being cropped away
569 if (layer_width < this.layer_width || layer_height < this.layer_height) {
570 for (Iterator it = al.iterator(); it.hasNext(); ) {
571 Displayable d = (Displayable)it.next();
572 Rectangle b = d.getBoundingBox(null);
573 double dw = b.getWidth();
574 double dh = b.getHeight();
575 // respect 10% margins
576 if (b.x + dw + new_x < 0.1 * dw || b.x + 0.9 * dw + new_x > layer_width || b.y + dh + new_y < 0.1 * dh || b.y + 0.9 * dh + new_y > layer_height) {
577 // cropping!
578 Utils.showMessage("Cropping " + d + "\nLayerSet: not resizing.");
579 return false;
583 this.layer_width = layer_width;
584 this.layer_height = layer_height;
585 //Utils.log("LayerSet.setDimensions: new_x,y: " + new_x + "," + new_y);
586 // translate all displayables
587 if (0 != new_x || 0 != new_y) {
588 for (Iterator it = al.iterator(); it.hasNext(); ) {
589 Displayable d = (Displayable)it.next();
590 Rectangle b = d.getBoundingBox(null);
591 //Utils.log("d x,y = " + b.x + ", " + b.y);
592 d.setLocation(b.x + new_x, b.y + new_y);
596 updateInDatabase("layer_dimensions");
597 if (null != root) recreateBuckets(true);
598 // and notify the Display
599 Display.update(this);
600 Display.pack(this);
602 // Record new state
603 addEditStep(new LayerSet.DoResizeLayerSet(this));
605 return true;
608 protected boolean remove2(boolean check) {
609 if (check) {
610 if (!Utils.check("Really delete " + this.toString() + (null != al_layers && al_layers.size() > 0 ? " and all its children?" : ""))) return false;
612 LayerThing lt = project.findLayerThing(this);
613 if (null == lt) return false;
614 return project.getLayerTree().remove(check, lt, null); // will end up calling remove(boolean) on this object
617 public boolean remove(boolean check) {
618 if (check) {
619 if (!Utils.check("Really delete " + this.toString() + (null != al_layers && al_layers.size() > 0 ? " and all its children?" : ""))) return false;
621 // delete all layers
622 while (0 != al_layers.size()) {
623 if (!((DBObject)al_layers.get(0)).remove(false)) {
624 Utils.showMessage("LayerSet id= " + id + " : Deletion incomplete, check database.");
625 return false;
628 // delete the ZDisplayables
629 Iterator it = al_zdispl.iterator();
630 while (it.hasNext()) {
631 ((ZDisplayable)it.next()).remove(false); // will call back the LayerSet.remove(ZDisplayable)
633 // remove the self
634 if (null != parent) parent.remove(this);
635 removeFromDatabase();
636 return true;
639 /** Remove a child. Does not destroy it or delete it from the database. */
640 public void remove(Layer layer) {
641 if (null == layer || -1 == al_layers.indexOf(layer)) return;
642 al_layers.remove(layer);
643 Display.updateLayerScroller(this);
644 Display.updateTitle(this);
647 public Layer next(Layer layer) {
648 int i = al_layers.indexOf(layer);
649 if (-1 == i) {
650 Utils.log("LayerSet.next: no such Layer " + layer);
651 return layer;
653 if (al_layers.size() -1 == i) return layer;
654 else return (Layer)al_layers.get(i+1);
657 public Layer previous(Layer layer) {
658 int i = al_layers.indexOf(layer);
659 if (-1 == i) {
660 Utils.log("LayerSet.previous: no such Layer " + layer);
661 return layer;
663 if (0 == i) return layer;
664 else return (Layer)al_layers.get(i-1);
667 public Layer nextNonEmpty(Layer layer) {
668 Layer next = layer;
669 Layer given = layer;
670 do {
671 layer = next;
672 next = next(layer);
673 if (!next.isEmpty()) return next;
674 } while (next != layer);
675 return given;
677 public Layer previousNonEmpty(Layer layer) {
678 Layer previous = layer;
679 Layer given = layer;
680 do {
681 layer = previous;
682 previous = previous(layer);
683 if (!previous.isEmpty()) return previous;
684 } while (previous != layer);
685 return given;
688 public int getLayerIndex(final long id) {
689 for (int i=al_layers.size()-1; i>-1; i--) {
690 if (((Layer)al_layers.get(i)).getId() == id) return i;
692 return -1;
695 /** Find a layer by index, or null if none. */
696 public Layer getLayer(final int i) {
697 if (i >=0 && i < al_layers.size()) return (Layer)al_layers.get(i);
698 return null;
701 /** Find a layer with the given id, or null if none. */
702 public Layer getLayer(final long id) {
703 for (Layer layer : al_layers) {
704 if (layer.getId() == id) return layer;
706 return null;
709 /** Returns the first layer found with the given Z coordinate, rounded to seventh decimal precision, or null if none found. */
710 public Layer getLayer(final double z) {
711 double error = 0.0000001; // TODO adjust to an optimal
712 for (Layer layer : al_layers) {
713 if (error > Math.abs(layer.getZ() - z)) { // floating-point arithmetic is still not a solved problem!
714 return layer;
717 return null;
720 public Layer getNearestLayer(final double z) {
721 double min_dist = Double.MAX_VALUE;
722 Layer closest = null;
723 for (Layer layer : al_layers) {
724 double dist = Math.abs(layer.getZ() - z);
725 if (dist < min_dist) {
726 min_dist = dist;
727 closest = layer;
730 return closest;
733 /** Returns null if none has the given z and thickness. If 'create' is true and no layer is found, a new one with the given Z is created and added to the LayerTree. */
734 public Layer getLayer(double z, double thickness, boolean create) {
735 Iterator it = al_layers.iterator();
736 Layer layer = null;
737 double error = 0.0000001; // TODO adjust to an optimal
738 while (it.hasNext()) {
739 Layer l = (Layer)it.next();
740 if (error > Math.abs(l.getZ() - z) && error > Math.abs(l.getThickness() - thickness)) { // floating point is still not a solved problem.
741 //Utils.log("LayerSet.getLayer: found layer with z=" + l.getZ());
742 layer = l;
745 if (create && null == layer && !Double.isNaN(z) && !Double.isNaN(thickness)) {
746 //Utils.log("LayerSet.getLayer: creating new Layer with z=" + z);
747 layer = new Layer(project, z, thickness, this);
748 add(layer);
749 project.getLayerTree().addLayer(this, layer);
751 return layer;
754 /** Add a Displayable to be painted in all Layers, such as a Pipe. Also updates open displays of the fact. */
755 public void add(final ZDisplayable zdispl) {
756 if (null == zdispl || -1 != al_zdispl.indexOf(zdispl)) {
757 Utils.log2("LayerSet: not adding zdispl");
758 return;
760 al_zdispl.add(0, zdispl); // at the top
762 zdispl.setLayerSet(this);
763 // The line below can fail (and in the addSilently as well) if one can add zdispl objects while no Layer has been created. But the ProjectThing.createChild prevents this situation.
764 zdispl.setLayer(al_layers.get(0));
765 zdispl.updateInDatabase("layer_set_id"); // TODO: update stack index?
767 // insert into bucket
768 if (null != root) {
769 // add as last, then update
770 root.put(al_zdispl.size()-1, zdispl, zdispl.getBoundingBox(null));
771 root.update(this, zdispl, 0, al_zdispl.size()-1);
774 Display.add(this, zdispl);
777 public void addAll(final Collection<? extends ZDisplayable> coll) {
778 if (null == coll || 0 == coll.size()) return;
779 for (final ZDisplayable zd : coll) {
780 al_zdispl.add(0, zd);
781 zd.setLayerSet(this);
782 zd.setLayer(al_layers.get(0));
783 zd.updateInDatabase("layer_set_id");
784 if (null != root) {
785 // add as last, then update
786 root.put(al_zdispl.size()-1, zd, zd.getBoundingBox(null));
787 root.update(this, zd, 0, al_zdispl.size()-1);
790 Display.addAll(this, coll);
793 /** Used for reconstruction purposes, avoids repainting or updating. */
794 public void addSilently(final ZDisplayable zdispl) {
795 if (null == zdispl || -1 != al_zdispl.indexOf(zdispl)) return;
796 try {
797 zdispl.setLayer(0 == al_layers.size() ? null : al_layers.get(0));
798 zdispl.setLayerSet(this, false);
799 //Utils.log2("setLayerSet to ZDipl id=" + zdispl.getId());
800 al_zdispl.add(zdispl);
801 } catch (Exception e) {
802 Utils.log("LayerSet.addSilently: not adding ZDisplayable with id=" + zdispl.getId());
803 IJError.print(e);
804 return;
808 /** Remove a child. Does not destroy the child nor remove it from the database, only from the Display. */
809 public boolean remove(final ZDisplayable zdispl) {
810 if (null == zdispl || null == al_zdispl || -1 == al_zdispl.indexOf(zdispl)) return false;
811 // remove from Bucket before modifying stack index
812 if (null != root) Bucket.remove(zdispl, db_map);
813 // now remove proper, so stack_index hasn't changed yet
814 al_zdispl.remove(zdispl);
815 Display.remove(zdispl);
816 return true;
819 public ArrayList<ZDisplayable> getZDisplayables(final Class c) {
820 return getZDisplayables(c, false);
823 /** Returns a list of ZDisplayable of class c only.*/
824 public ArrayList<ZDisplayable> getZDisplayables(final Class c, final boolean instance_of) {
825 final ArrayList<ZDisplayable> al = new ArrayList<ZDisplayable>();
826 if (null == c) return al;
827 if (Displayable.class == c || ZDisplayable.class == c) {
828 al.addAll(al_zdispl);
829 return al;
831 if (instance_of) {
832 for (ZDisplayable zd : al_zdispl) {
833 if (c.isInstance(zd)) al.add(zd);
835 } else {
836 for (ZDisplayable zd : al_zdispl) {
837 if (zd.getClass() == c) al.add(zd);
840 return al;
843 public ArrayList<ZDisplayable> getZDisplayables(final Class c, final Layer layer, final Area aroi, final boolean visible_only) {
844 final ArrayList<ZDisplayable> al = getZDisplayables(c);
845 final double z = layer.getZ();
846 for (Iterator<ZDisplayable> it = al.iterator(); it.hasNext(); ) {
847 ZDisplayable zd = it.next();
848 if (visible_only && !zd.isVisible()) { it.remove(); continue; }
849 if (!zd.intersects(aroi, z, z)) it.remove();
851 return al;
854 public boolean contains(final Layer layer) {
855 if (null == layer) return false;
856 return -1 != al_layers.indexOf(layer);
859 public boolean contains(final Displayable zdispl) {
860 if (null == zdispl) return false;
861 return -1 != al_zdispl.indexOf(zdispl);
864 /** Returns a copy of the layer list. */
865 public ArrayList<Layer> getLayers() {
866 return (ArrayList<Layer>)al_layers.clone(); // for integrity and safety, return a copy.
869 public boolean isDeletable() {
870 return false;
873 /** Overiding. The alpha is used to show whether the LayerSet object is selected or not. */
874 public void setAlpha(float alpha) { return; }
876 /** Move the given Displayable to the next layer if possible. */
877 public void moveDown(Layer layer, Displayable d) {
878 int i = al_layers.indexOf(layer);
879 if (al_layers.size() -1 == i || -1 == i) return;
880 layer.remove(d);
881 ((Layer)(al_layers.get(i +1))).add(d);
883 /** Move the given Displayable to the previous layer if possible. */
884 public void moveUp(Layer layer, Displayable d) {
885 int i = al_layers.indexOf(layer);
886 if (0 == i || -1 == i) return;
887 layer.remove(d);
888 ((Layer)(al_layers.get(i -1))).add(d);
891 /** Move all Displayable objects in the HashSet to the given target layer. */
892 public void move(final HashSet hs_d, final Layer source, final Layer target) {
893 if (0 == hs_d.size() || null == source || null == target || source == target) return;
894 Display.setRepaint(false); // disable repaints
895 for (Iterator it = hs_d.iterator(); it.hasNext(); ) {
896 Displayable d = (Displayable)it.next();
897 if (source == d.getLayer()) {
898 source.remove(d);
899 target.add(d, false, false); // these contortions to avoid repeated DB traffic
900 d.updateInDatabase("layer_id");
901 Display.add(target, d, false); // don't activate
904 Display.setRepaint(true); // enable repaints
905 source.updateInDatabase("stack_index");
906 target.updateInDatabase("stack_index");
907 Display.repaint(source); // update graphics: true
908 Display.repaint(target);
911 /** Find ZDisplayable objects that contain the point x,y in the given layer. */
912 public Collection<Displayable> findZDisplayables(final Layer layer, final int x, final int y, final boolean visible_only) {
913 if (null != root) return root.find(x, y, layer, visible_only);
914 final ArrayList<Displayable> al = new ArrayList<Displayable>();
915 for (ZDisplayable zd : al_zdispl) {
916 if (zd.contains(layer, x, y)) al.add(zd);
918 return al;
920 public Collection<Displayable> findZDisplayables(final Layer layer, final Rectangle r, final boolean visible_only) {
921 if (null != root) return root.find(r, layer, visible_only);
922 final ArrayList<Displayable> al = new ArrayList<Displayable>();
923 for (ZDisplayable zd : al_zdispl) {
924 if (zd.getBounds(null, layer).intersects(r)) al.add(zd);
926 return al;
929 /** Returns the hash set of objects whose visibility has changed. */
930 public HashSet<Displayable> setVisible(String type, final boolean visible, final boolean repaint) {
931 type = type.toLowerCase();
932 final HashSet<Displayable> hs = new HashSet<Displayable>();
933 try {
934 project.getLoader().startLargeUpdate();
935 if (type.equals("pipe") || type.equals("ball") || type.equals("arealist") || type.equals("polyline")) {
936 for (ZDisplayable zd : al_zdispl) {
937 if (visible != zd.isVisible() && zd.getClass().getName().toLowerCase().endsWith(type)) { // endsWith, because DLabel is called as Label
938 zd.setVisible(visible, false); // don't repaint
939 hs.add(zd);
942 } else {
943 if (type.equals("image")) type = "patch";
944 for (Layer layer : al_layers) {
945 hs.addAll(layer.setVisible(type, visible, false)); // don't repaint
948 } catch (Exception e) {
949 IJError.print(e);
950 } finally {
951 project.getLoader().commitLargeUpdate();
953 if (repaint) {
954 Display.repaint(this); // this could be optimized to repaint only the accumulated box
956 return hs;
958 /** Hide all except those whose type is in 'type' list, whose visibility flag is left unchanged. Returns the list of displayables made hidden. */
959 public HashSet<Displayable> hideExcept(ArrayList<Class> type, boolean repaint) {
960 final HashSet<Displayable> hs = new HashSet<Displayable>();
961 for (ZDisplayable zd : al_zdispl) {
962 if (!type.contains(zd.getClass()) && zd.isVisible()) {
963 zd.setVisible(false, repaint);
964 hs.add(zd);
967 for (Layer la : al_layers) hs.addAll(la.hideExcept(type, repaint));
968 return hs;
970 public void setAllVisible(boolean repaint) {
971 for (ZDisplayable zd : al_zdispl) {
972 if (!zd.isVisible()) zd.setVisible(true, repaint);
974 for (Layer la : al_layers) la.setAllVisible(repaint);
977 /** Returns true if any of the ZDisplayable objects are of the given class. */
978 public boolean contains(final Class c) {
979 for (ZDisplayable zd : al_zdispl) {
980 if (zd.getClass() == c) return true;
982 return false;
984 /** Check in all layers. */
985 public boolean containsDisplayable(Class c) {
986 for (Iterator it = al_layers.iterator(); it.hasNext(); ) {
987 Layer la = (Layer)it.next();
988 if (la.contains(c)) return true;
990 return false;
993 /** Returns the distance from the first layer's Z to the last layer's Z. */
994 public double getDepth() {
995 if (null == al_layers || al_layers.isEmpty()) return 0;
996 return ((Layer)al_layers.get(al_layers.size() -1)).getZ() - ((Layer)al_layers.get(0)).getZ();
999 /** Return all the Displayable objects from all the layers of this LayerSet. Does not include the ZDisplayables. */
1000 public ArrayList<Displayable> getDisplayables() {
1001 final ArrayList<Displayable> al = new ArrayList<Displayable>();
1002 for (Layer layer : al_layers) {
1003 al.addAll(layer.getDisplayables());
1005 return al;
1007 /** Return all the Displayable objects from all the layers of this LayerSet of the given class. Does not include the ZDisplayables. */
1008 public ArrayList<Displayable> getDisplayables(Class c) {
1009 final ArrayList<Displayable> al = new ArrayList<Displayable>();
1010 for (Layer layer : al_layers) {
1011 al.addAll(layer.getDisplayables(c));
1013 return al;
1015 /** Return all the Displayable objects from all the layers of this LayerSet of the given class that intersect the given area. Does not include the ZDisplayables. */
1016 public ArrayList<Displayable> getDisplayables(final Class c, final Area aroi, final boolean visible_only) {
1017 final ArrayList<Displayable> al = new ArrayList<Displayable>();
1018 for (Layer layer : al_layers) {
1019 al.addAll(layer.getDisplayables(c, aroi, visible_only));
1021 return al;
1024 /** From zero to size-1. */
1025 public int indexOf(Layer layer) {
1026 return al_layers.indexOf(layer);
1029 public void exportXML(final java.io.Writer writer, final String indent, final Object any) throws Exception {
1030 final StringBuffer sb_body = new StringBuffer();
1031 sb_body.append(indent).append("<t2_layer_set\n");
1032 final String in = indent + "\t";
1033 super.exportXML(sb_body, in, any);
1034 sb_body.append(in).append("layer_width=\"").append(layer_width).append("\"\n")
1035 .append(in).append("layer_height=\"").append(layer_height).append("\"\n")
1036 .append(in).append("rot_x=\"").append(rot_x).append("\"\n")
1037 .append(in).append("rot_y=\"").append(rot_y).append("\"\n")
1038 .append(in).append("rot_z=\"").append(rot_z).append("\"\n")
1039 .append(in).append("snapshots_quality=\"").append(snapshots_quality).append("\"\n")
1040 .append(in).append("snapshots_mode=\"").append(snapshot_modes[snapshots_mode]).append("\"\n")
1041 // TODO: alpha! But it's not necessary.
1043 sb_body.append(indent).append(">\n");
1044 if (null != calibration) {
1045 sb_body.append(in).append("<t2_calibration\n")
1046 .append(in).append("\tpixelWidth=\"").append(calibration.pixelWidth).append("\"\n")
1047 .append(in).append("\tpixelHeight=\"").append(calibration.pixelHeight).append("\"\n")
1048 .append(in).append("\tpixelDepth=\"").append(calibration.pixelDepth).append("\"\n")
1049 .append(in).append("\txOrigin=\"").append(calibration.xOrigin).append("\"\n")
1050 .append(in).append("\tyOrigin=\"").append(calibration.yOrigin).append("\"\n")
1051 .append(in).append("\tzOrigin=\"").append(calibration.zOrigin).append("\"\n")
1052 .append(in).append("\tinfo=\"").append(calibration.info).append("\"\n")
1053 .append(in).append("\tvalueUnit=\"").append(calibration.getValueUnit()).append("\"\n")
1054 .append(in).append("\ttimeUnit=\"").append(calibration.getTimeUnit()).append("\"\n")
1055 .append(in).append("\tunit=\"").append(calibration.getUnit()).append("\"\n")
1056 .append(in).append("/>\n")
1059 writer.write(sb_body.toString());
1060 // export ZDisplayable objects
1061 if (null != al_zdispl) {
1062 for (Iterator it = al_zdispl.iterator(); it.hasNext(); ) {
1063 ZDisplayable zd = (ZDisplayable)it.next();
1064 sb_body.setLength(0);
1065 zd.exportXML(sb_body, in, any);
1066 writer.write(sb_body.toString()); // each separately, for they can be huge
1069 // export Layer and contained Displayable objects
1070 if (null != al_layers) {
1071 //Utils.log("LayerSet " + id + " is saving " + al_layers.size() + " layers.");
1072 for (Iterator it = al_layers.iterator(); it.hasNext(); ) {
1073 sb_body.setLength(0);
1074 ((Layer)it.next()).exportXML(sb_body, in, any);
1075 writer.write(sb_body.toString());
1078 super.restXML(sb_body, in, any);
1079 writer.write("</t2_layer_set>\n");
1082 /** Includes the !ELEMENT */
1083 static public void exportDTD(StringBuffer sb_header, HashSet hs, String indent) {
1084 String type = "t2_layer_set";
1085 if (!hs.contains(type)) {
1086 sb_header.append(indent).append("<!ELEMENT t2_layer_set (").append(Displayable.commonDTDChildren()).append(",t2_layer,t2_pipe,t2_ball,t2_area_list,t2_calibration)>\n");
1087 Displayable.exportDTD(type, sb_header, hs, indent);
1088 sb_header.append(indent).append(TAG_ATTR1).append(type).append(" layer_width").append(TAG_ATTR2)
1089 .append(indent).append(TAG_ATTR1).append(type).append(" layer_height").append(TAG_ATTR2)
1090 .append(indent).append(TAG_ATTR1).append(type).append(" rot_x").append(TAG_ATTR2)
1091 .append(indent).append(TAG_ATTR1).append(type).append(" rot_y").append(TAG_ATTR2)
1092 .append(indent).append(TAG_ATTR1).append(type).append(" rot_z").append(TAG_ATTR2)
1093 .append(indent).append(TAG_ATTR1).append(type).append(" snapshots_quality").append(TAG_ATTR2)
1094 .append(indent).append(TAG_ATTR1).append(type).append(" snapshots_mode").append(TAG_ATTR2)
1096 sb_header.append(indent).append("<!ELEMENT t2_calibration EMPTY>\n")
1097 .append(indent).append(TAG_ATTR1).append("t2_calibration pixelWidth").append(TAG_ATTR2)
1098 .append(indent).append(TAG_ATTR1).append("t2_calibration pixelHeight").append(TAG_ATTR2)
1099 .append(indent).append(TAG_ATTR1).append("t2_calibration pixelDepth").append(TAG_ATTR2)
1100 .append(indent).append(TAG_ATTR1).append("t2_calibration xOrigin").append(TAG_ATTR2)
1101 .append(indent).append(TAG_ATTR1).append("t2_calibration yOrigin").append(TAG_ATTR2)
1102 .append(indent).append(TAG_ATTR1).append("t2_calibration zOrigin").append(TAG_ATTR2)
1103 .append(indent).append(TAG_ATTR1).append("t2_calibration info").append(TAG_ATTR2)
1104 .append(indent).append(TAG_ATTR1).append("t2_calibration valueUnit").append(TAG_ATTR2)
1105 .append(indent).append(TAG_ATTR1).append("t2_calibration timeUnit").append(TAG_ATTR2)
1106 .append(indent).append(TAG_ATTR1).append("t2_calibration unit").append(TAG_ATTR2)
1111 public void setSnapshotsMode(final int mode) {
1112 if (mode == snapshots_mode) return;
1113 this.snapshots_mode = mode;
1114 Display.repaintSnapshots(this);
1115 updateInDatabase("snapshots_mode");
1118 public int getSnapshotsMode() {
1119 return this.snapshots_mode;
1122 public void destroy() {
1123 for (Iterator it = al_layers.iterator(); it.hasNext(); ) {
1124 Layer layer = (Layer)it.next();
1125 layer.destroy();
1127 for (Iterator it = al_zdispl.iterator(); it.hasNext(); ) {
1128 ZDisplayable zd = (ZDisplayable)it.next();
1129 zd.destroy();
1131 this.al_layers.clear();
1132 this.al_zdispl.clear();
1133 if (null != align) {
1134 align.destroy();
1135 align = null;
1139 public boolean isAligning() {
1140 return null != align;
1143 public void cancelAlign() {
1144 if (null != align) {
1145 align.cancel(); // will repaint
1146 align = null;
1150 public void applyAlign(final boolean post_register) {
1151 if (null != align) align.apply(post_register);
1154 public void applyAlign(final Layer la_start, final Layer la_end, final Selection selection) {
1155 if (null != align) align.apply(la_start, la_end, selection);
1158 public void startAlign(Display display) {
1159 align = new Align(display);
1162 public Align getAlign() {
1163 return align;
1166 /** Used by the Layer.setZ method. */
1167 protected void reposition(Layer layer) {
1168 if (null == layer || !al_layers.contains(layer)) return;
1169 al_layers.remove(layer);
1170 addSilently(layer);
1173 /** Get up to 'n' layers before and after the given layers. */
1174 public ArrayList getNeighborLayers(final Layer layer, final int n) {
1175 final int i_layer = al_layers.indexOf(layer);
1176 final ArrayList al = new ArrayList();
1177 if (-1 == i_layer) return al;
1178 int start = i_layer - n;
1179 if (start < 0) start = 0;
1180 int end = i_layer + n;
1181 if (end > al_layers.size()) end = al_layers.size();
1182 for (int i=start; i<i_layer; i++) al.add(al_layers.get(i));
1183 for (int i=i_layer+1; i<= i_layer + n || i < end; i++) al.add(al_layers.get(i));
1184 return al;
1187 public boolean isTop(ZDisplayable zd) {
1188 if (null != zd && al_zdispl.size() > 0 && al_zdispl.indexOf(zd) == al_zdispl.size() -1) return true;
1189 return false;
1192 public boolean isBottom(ZDisplayable zd) {
1193 if (null != zd && al_zdispl.size() > 0 && al_zdispl.indexOf(zd) == 0) return true;
1194 return false;
1197 /** Hub method: ZDisplayable or into the Displayable's Layer. */
1198 protected boolean isTop(Displayable d) {
1199 if (d instanceof ZDisplayable) return isTop((ZDisplayable)d);
1200 else return d.getLayer().isTop(d);
1202 /** Hub method: ZDisplayable or into the Displayable's Layer. */
1203 protected boolean isBottom(Displayable d) {
1204 if (d instanceof ZDisplayable) return isBottom((ZDisplayable)d);
1205 else return d.getLayer().isBottom(d);
1208 /** Change z position in the layered stack, which defines the painting order. */ // the BOTTOM of the stack is the first element in the al_zdispl array
1209 protected void move(final int place, final Displayable d) {
1210 if (d instanceof ZDisplayable) {
1211 int i = al_zdispl.indexOf(d);
1212 if (-1 == i) {
1213 Utils.log("LayerSet.move: object does not belong here");
1214 return;
1216 int size = al_zdispl.size();
1217 if (1 == size) return;
1218 switch(place) {
1219 case LayerSet.TOP:
1220 al_zdispl.add(al_zdispl.remove(i));
1221 break;
1222 case LayerSet.UP:
1223 if (size -1 == i) return;
1224 al_zdispl.add(i+1, al_zdispl.remove(i));
1225 break;
1226 case LayerSet.DOWN:
1227 if (0 == i) return;
1228 al_zdispl.add(i, al_zdispl.remove(i-1)); //swap
1229 break;
1230 case LayerSet.BOTTOM:
1231 al_zdispl.add(0, al_zdispl.remove(i));
1232 break;
1234 updateInDatabase("stack_index");
1235 Display.updatePanelIndex(d.getLayer(), d);
1236 } else {
1237 switch (place) {
1238 case LayerSet.TOP: d.getLayer().moveTop(d); break;
1239 case LayerSet.UP: d.getLayer().moveUp(d); break;
1240 case LayerSet.DOWN: d.getLayer().moveDown(d); break;
1241 case LayerSet.BOTTOM: d.getLayer().moveBottom(d); break;
1246 public int indexOf(final ZDisplayable zd) {
1247 int k = al_zdispl.indexOf(zd);
1248 if (-1 == k) return -1;
1249 return al_zdispl.size() - k -1;
1252 public boolean isEmptyAt(Layer la) {
1253 for (Iterator it = al_zdispl.iterator(); it.hasNext(); ) {
1254 if (((ZDisplayable)it.next()).paintsAt(la)) return false;
1256 return true;
1259 public Displayable clone(final Project pr, final boolean copy_id) {
1260 return clone(pr, (Layer)al_layers.get(0), (Layer)al_layers.get(al_layers.size()-1), new Rectangle(0, 0, (int)Math.ceil(getLayerWidth()), (int)Math.ceil(getLayerHeight())), false, copy_id);
1263 /** Clone the contents of this LayerSet, from first to last given layers, and cropping for the given rectangle. */
1264 public Displayable clone(Project pr, Layer first, Layer last, Rectangle roi, boolean add_to_tree, boolean copy_id) {
1265 // obtain a LayerSet
1266 final long nid = copy_id ? this.id : pr.getLoader().getNextId();
1267 final LayerSet copy = new LayerSet(pr, nid, getTitle(), this.width, this.height, this.rot_x, this.rot_y, this.rot_z, roi.width, roi.height, this.locked, this.snapshots_mode, (AffineTransform)this.at.clone());
1268 copy.setCalibration(getCalibrationCopy());
1269 copy.snapshots_quality = this.snapshots_quality;
1270 // copy objects that intersect the roi, from within the given range of layers
1271 final java.util.List<Layer> al = ((ArrayList<Layer>)al_layers.clone()).subList(indexOf(first), indexOf(last) +1);
1272 Utils.log2("al.size() : " + al.size());
1273 for (Layer layer : al) {
1274 Layer layercopy = layer.clone(pr, copy, roi, copy_id);
1275 copy.addSilently(layercopy);
1276 if (add_to_tree) pr.getLayerTree().addLayer(copy, layercopy);
1278 // copy ZDisplayable objects if they intersect the roi, and translate them properly
1279 final AffineTransform trans = new AffineTransform();
1280 trans.translate(-roi.x, -roi.y);
1281 for (ZDisplayable zd : find(first, last, new Area(roi))) {
1282 ZDisplayable zdcopy = (ZDisplayable)zd.clone(pr, copy_id);
1283 zdcopy.getAffineTransform().preConcatenate(trans);
1284 copy.addSilently(zdcopy);
1286 // fix links:
1287 copy.linkPatchesR();
1288 return (Displayable)copy;
1291 /** Create a virtual layer stack that acts as a virtual ij.ImageStack, in RGB and set to a scale of max_dimension / Math.max(layer_width, layer_height). */
1292 public LayerStack createLayerStack(Class clazz, int type, int c_alphas) {
1293 return new LayerStack(this,
1294 getVirtualizationScale(),
1295 type,
1296 clazz,
1297 c_alphas);
1300 public int getPixelsMaxDimension() { return max_dimension; }
1301 /** From 0.000... to 1. */
1302 public double getVirtualizationScale() {
1303 double scale = max_dimension / Math.max(layer_width, layer_height);
1304 return scale > 1 ? 1 : scale;
1306 public void setPixelsMaxDimension(int d) {
1307 if (d > 2 && d != max_dimension) {
1308 max_dimension = d;
1309 Polyline.flushTraceCache(project); // depends on the scale value
1310 } else Utils.log("Can't set virtualization max pixels dimension to smaller than 2!");
1313 public void setPixelsVirtualizationEnabled(boolean b) { this.virtualization_enabled = b; }
1314 public boolean isPixelsVirtualizationEnabled() { return virtualization_enabled; }
1317 /** Returns a new Rectangle of 0, 0, layer_width, layer_height. */
1318 public Rectangle get2DBounds() {
1319 return new Rectangle(0, 0, (int)Math.ceil(layer_width), (int)Math.ceil(layer_height));
1322 /** Set the calibration to a clone of the given calibration. */
1323 public void setCalibration(Calibration cal) {
1324 if (null == cal) return;
1325 this.calibration = (Calibration)cal.clone();
1328 public Calibration getCalibration() {
1329 return this.calibration;
1332 public Calibration getCalibrationCopy() {
1333 return calibration.copy();
1336 public boolean isCalibrated() {
1337 Calibration identity = new Calibration();
1338 if (identity.equals(this.calibration)) return false;
1339 return true;
1342 /** Restore calibration from the given XML attributes table.*/
1343 public void restoreCalibration(HashMap ht_attributes) {
1344 for (Iterator it = ht_attributes.entrySet().iterator(); it.hasNext(); ) {
1345 Map.Entry entry = (Map.Entry)it.next();
1346 String key = (String)entry.getKey();
1347 String value = (String)entry.getValue();
1348 // remove the prefix 't2_'
1349 key.substring(3).toLowerCase(); // case-resistant
1350 try {
1351 if (key.equals("pixelwidth")) {
1352 calibration.pixelWidth = Double.parseDouble(value);
1353 } else if (key.equals("pixelheight")) {
1354 calibration.pixelHeight = Double.parseDouble(value);
1355 } else if (key.equals("pixeldepth")) {
1356 calibration.pixelDepth = Double.parseDouble(value);
1357 } else if (key.equals("xorigin")) {
1358 calibration.xOrigin = Double.parseDouble(value);
1359 } else if (key.equals("yorigin")) {
1360 calibration.yOrigin = Double.parseDouble(value);
1361 } else if (key.equals("zorigin")) {
1362 calibration.zOrigin = Double.parseDouble(value);
1363 } else if (key.equals("info")) {
1364 calibration.info = value;
1365 } else if (key.equals("valueunit")) {
1366 calibration.setValueUnit(value);
1367 } else if (key.equals("timeunit")) {
1368 calibration.setTimeUnit(value);
1369 } else if (key.equals("unit")) {
1370 calibration.setUnit(value);
1372 } catch (Exception e) {
1373 Utils.log2("LayerSet.restoreCalibration, key/value failed:" + key + "=\"" + value +"\"");
1374 IJError.print(e);
1377 //Utils.log2("Restored LayerSet calibration: " + calibration);
1380 /** For creating snapshots, using a very slow but much better scaling algorithm (the Image.SCALE_AREA_AVERAGING method). */
1381 public boolean snapshotsQuality() {
1382 return snapshots_quality;
1385 public void setSnapshotsQuality(boolean b) {
1386 this.snapshots_quality = b;
1387 updateInDatabase("snapshots_quality");
1388 // TODO this is obsolete
1391 /** Find, in this LayerSet and contained layers and their nested LayerSets if any, all Displayable instances of Class c. Includes the ZDisplayables. */
1392 public ArrayList get(final Class c) {
1393 return get(new ArrayList(), c);
1396 /** Find, in this LayerSet and contained layers and their nested LayerSets if any, all Displayable instances of Class c, which are stored in the given ArrayList; returns the same ArrayList, or a new one if its null. Includes the ZDisplayables. */
1397 public ArrayList get(ArrayList all, final Class c) {
1398 if (null == all) all = new ArrayList();
1399 // check whether to include all the ZDisplayable objects
1400 if (Displayable.class == c || ZDisplayable.class == c) all.addAll(al_zdispl);
1401 else {
1402 for (Iterator it = al_zdispl.iterator(); it.hasNext(); ){
1403 Object ob = it.next();
1404 if (ob.getClass() == c) all.add(ob);
1407 for (Layer layer : al_layers) {
1408 all.addAll(layer.getDisplayables(c));
1409 ArrayList al_ls = layer.getDisplayables(LayerSet.class);
1410 for (Iterator i2 = al_ls.iterator(); i2.hasNext(); ) {
1411 LayerSet ls = (LayerSet)i2.next();
1412 ls.get(all, c);
1415 return all;
1418 /** Returns the region defined by the rectangle as an image in the type and format specified.
1419 * The type is either ImagePlus.GRAY8 or ImagePlus.COLOR_RGB.
1420 * The format is either Layer.IMAGE (an array) or Layer.ImagePlus (it returns an ImagePlus containing an ImageStack), from which any ImageProcessor or pixel arrays can be retrieved trivially.
1422 public Object grab(final int first, final int last, final Rectangle r, final double scale, final Class c, final int c_alphas, final int format, final int type) {
1423 // check preconditions
1424 if (first < 0 || first > last || last >= al_layers.size()) {
1425 Utils.log("Invalid first and/or last layers.");
1426 return null;
1428 // check that it will fit in memory
1429 if (!project.getLoader().releaseToFit(r.width, r.height, type, 1.1f)) {
1430 Utils.log("LayerSet.grab: Cannot fit an image stack of " + (long)(r.width*r.height*(ImagePlus.GRAY8==type?1:4)*1.1) + " bytes in memory.");
1431 return null;
1433 if (Layer.IMAGEPLUS == format) {
1434 ImageStack stack = new ImageStack((int)(r.width*scale), (int)(r.height*scale));
1435 for (int i=first; i<=last; i++) {
1436 Layer la = (Layer)al_layers.get(i);
1437 Utils.log2("c is " + c);
1438 ImagePlus imp = project.getLoader().getFlatImage(la, r, scale, c_alphas, type, c, null, true);
1439 if (null != imp) try {
1440 //if (0 == stack.getSize()) stack.setColorModel(imp.getProcessor().getColorModel());
1441 stack.addSlice(imp.getTitle(), imp.getProcessor()); //.getPixels());
1442 } catch (IllegalArgumentException iae) {
1443 IJError.print(iae);
1444 } else Utils.log("LayerSet.grab: Ignoring layer " + la);
1446 if (0 == stack.getSize()) {
1447 Utils.log("LayerSet.grab: could not make slices.");
1448 return null;
1450 return new ImagePlus("Stack " + first + "-" + last, stack);
1451 } else if (Layer.IMAGE == format) {
1452 final Image[] image = new Image[last - first + 1];
1453 for (int i=first, j=0; i<=last; i++, j++) {
1454 image[j] = project.getLoader().getFlatAWTImage((Layer)al_layers.get(i), r, scale, c_alphas, type, c, null, true, Color.black);
1456 return image;
1458 return null;
1462 /** Searches in all layers. Ignores the ZDisplaybles. */
1463 public Displayable findDisplayable(final long id) {
1464 for (Layer la : al_layers) {
1465 for (Displayable d : la.getDisplayables()) {
1466 if (d.getId() == id) return d;
1469 return null;
1472 /** Searches in all ZDisplayables and in all layers, recursively into nested LayerSets. */
1473 public DBObject findById(final long id) {
1474 if (this.id == id) return this;
1475 for (ZDisplayable zd : al_zdispl) {
1476 if (zd.getId() == id) return zd;
1478 for (Layer la : al_layers) {
1479 DBObject dbo = la.findById(id);
1480 if (null != dbo) return dbo;
1482 return null;
1485 // private to the package
1486 void linkPatchesR() {
1487 for (Layer la : al_layers) la.linkPatchesR();
1488 for (ZDisplayable zd : al_zdispl) zd.linkPatches();
1491 /** Recursive into nested LayerSet objects.*/
1492 public void updateLayerTree() {
1493 for (Layer la : al_layers) {
1494 la.updateLayerTree();
1498 /** Find the ZDisplayable objects that intersect with the 3D roi defined by the first and last layers, and the area -all in world coordinates. */
1499 public ArrayList<ZDisplayable> find(final Layer first, final Layer last, final Area area) {
1500 final ArrayList<ZDisplayable> al = new ArrayList<ZDisplayable>();
1501 for (ZDisplayable zd : al_zdispl) {
1502 if (zd.intersects(area, first.getZ(), last.getZ())) {
1503 al.add(zd);
1506 return al;
1509 /** For fast search. */
1510 Bucket root = null;
1511 private HashMap<Displayable,ArrayList<Bucket>> db_map = null;
1513 /** Returns a copy of the list of ZDisplayable objects. */
1514 public ArrayList<ZDisplayable> getZDisplayables() { return (ArrayList<ZDisplayable>)al_zdispl.clone(); }
1516 /** Returns the real list of displayables, not a copy. If you modify this list, Thor may ground you with His lightning. */
1517 public ArrayList<ZDisplayable> getDisplayableList() {
1518 return al_zdispl;
1521 public HashMap<Displayable, ArrayList<Bucket>> getBucketMap() {
1522 return db_map;
1525 public void updateBucket(final Displayable d) {
1526 if (null != root) root.updatePosition(d, db_map);
1529 public void recreateBuckets(final boolean layers) {
1530 this.root = new Bucket(0, 0, (int)(0.00005 + getLayerWidth()), (int)(0.00005 + getLayerHeight()), Bucket.getBucketSide(this));
1531 this.db_map = new HashMap<Displayable,ArrayList<Bucket>>();
1532 this.root.populate(this, db_map);
1533 if (layers) {
1534 for (final Layer la : al_layers) {
1535 // recreate only if there were any already
1536 if (null != la.root) la.recreateBuckets();
1541 /** Checks only buckets for ZDisplayable, not any related to any layer. */
1542 public void checkBuckets() {
1543 if (null == root || null == db_map) recreateBuckets(false);
1546 public Rectangle getMinimalBoundingBox(final Class c) {
1547 Rectangle r = null;
1548 for (final Layer la : al_layers) {
1549 if (null == r) r = la.getMinimalBoundingBox(c);
1550 else {
1551 Rectangle box = la.getMinimalBoundingBox(c); // may be null if Layer is empty
1552 if (null != box) r.add(box);
1555 return r;
1558 /** Time vs DoStep. Not all steps may be specific for a single Displayable. */
1559 final private TreeMap<Long,DoStep> edit_history = new TreeMap<Long,DoStep>();
1561 /** The step representing the current diff state. */
1562 private long current_edit_time = 0;
1563 private DoStep current_edit_step = null;
1565 /** Displayable vs its own set of time vs DoStep, for quick access, for those edits that are specific of a Displayable.
1566 * It's necessary to set a ground, starting point for any Displayable whose data will be edited. */
1567 final private Map<Displayable,TreeMap<Long,DoStep>> dedits = new HashMap<Displayable,TreeMap<Long,DoStep>>();
1569 /** Time vs DoStep; as steps are removed from the end of edit_history, they are put here. */
1570 final private TreeMap<Long,DoStep> redo = new TreeMap<Long,DoStep>();
1572 /** Whether an initial step should be added or not. */
1573 final boolean prepareStep(final Object ob) {
1574 synchronized (edit_history) {
1575 if (0 == edit_history.size() || redo.size() > 0) return true;
1576 // Check if the last added entry contains the exact same elements and data
1577 DoStep step = edit_history.get(edit_history.lastKey());
1578 boolean b = step.isIdenticalTo(ob);
1579 Utils.log2(b + " == prepareStep for " + ob);
1580 // If identical, don't prepare one!
1581 return !b;
1585 /** If last step is not a DoEdit "data" step for d, then call addDataEditStep(d). */
1586 boolean addPreDataEditStep(final Displayable d) {
1587 if ( null == current_edit_step
1588 || (current_edit_step.getD() != d || !((DoEdit)current_edit_step).containsKey("data"))) {
1589 //Utils.log2("Adding pre-data edit step");
1590 //return addDataEditStep(d);
1591 return addEditStep(new Displayable.DoEdit(d).init(d, new String[]{"data"}));
1593 return false;
1596 /** A new undo step for the "data" field of Displayable d. */
1597 boolean addDataEditStep(final Displayable d) {
1598 //Utils.log2("Adding data edit step");
1599 // Adds "data", which contains width,height,affinetransform,links, and the data (points, areas, etc.)
1600 return addEditStep(new Displayable.DoEdit(d).init(d, new String[]{"data"}));
1603 /** A new undo step for the "data" field of all Displayable in the set. */
1604 boolean addDataEditStep(final Set<Displayable> ds) {
1605 return addDataEditStep(ds, new String[]{"data"});
1608 boolean addDataEditStep(final Set<Displayable> ds, final String[] fields) {
1609 final Displayable.DoEdits edits = new Displayable.DoEdits(ds);
1610 edits.init(fields);
1611 return addEditStep(edits);
1614 /** Add an undo step for the transformations of all Displayable in the layer. */
1615 public void addTransformStep(final Layer layer) {
1616 addTransformStep(layer.getDisplayables());
1618 /** Add an undo step for the transformations of all Displayable in hs. */
1619 public void addTransformStep(final Collection<Displayable> col) {
1620 Utils.log2("Added transform step for col");
1621 addEditStep(new Displayable.DoTransforms().addAll(col));
1623 /** Add an undo step for the transformations of all Displayable in all layers. */
1624 public void addTransformStep() {
1625 Utils.log2("Added transform step for all");
1626 Displayable.DoTransforms dt = new Displayable.DoTransforms();
1627 for (final Layer la : al_layers) {
1628 dt.addAll(la.getDisplayables());
1630 addEditStep(dt);
1633 /** Add a step to undo the addition or deletion of one or more objects in this project and LayerSet. */
1634 public void addChangeTreesStep() {
1635 DoStep step = new LayerSet.DoChangeTrees(this);
1636 if (prepareStep(step)) {
1637 Utils.log2("Added change trees step.");
1638 addEditStep(step);
1641 /** For the Displayable contained in a Layer: their number, and their stack order. */
1642 public void addLayerContentStep(final Layer la) {
1643 DoStep step = new Layer.DoContentChange(la);
1644 if (prepareStep(step)) {
1645 Utils.log2("Added layer content step.");
1646 addEditStep(step);
1649 /** For the Z and thickness of a layer. */
1650 public void addLayerEditedStep(final Layer layer) {
1651 addEditStep(new Layer.DoEditLayer(layer));
1653 /** For the Z and thickness of a list of layers. */
1654 public void addLayerEditedStep(final List<Layer> al) {
1655 addEditStep(new Layer.DoEditLayers(al));
1658 boolean addEditStep(final DoStep step) {
1659 if (null == step || step.isEmpty()) {
1660 Utils.log2("Warning: can't add empty step " + step);
1661 return false;
1664 synchronized (edit_history) {
1665 // Check if it's identical to current step
1666 if (step.isIdenticalTo(current_edit_step)) {
1667 Utils.log2("Skipping identical undo step of class " + step.getClass() + ": " + step);
1668 return false;
1671 // Store current in undo queue
1672 if (null != current_edit_step) {
1673 edit_history.put(current_edit_time, current_edit_step);
1674 // Store for speedy access, if its Displayable-specific:
1675 final Displayable d = current_edit_step.getD();
1676 if (null != d) {
1677 TreeMap<Long,DoStep> edits = dedits.get(d);
1678 if (null == edits) {
1679 edits = new TreeMap<Long,DoStep>();
1680 dedits.put(d, edits);
1682 edits.put(current_edit_time, current_edit_step);
1685 // prune if too large
1686 while (edit_history.size() > project.getProperty("n_undo_steps", 32)) {
1687 long t = edit_history.firstKey();
1688 DoStep st = edit_history.remove(t);
1689 if (null != st.getD()) {
1690 TreeMap<Long,DoStep> m = dedits.get(st.getD());
1691 m.remove(t);
1692 if (0 == m.size()) dedits.remove(st.getD());
1697 // Set step as current
1698 current_edit_time = System.currentTimeMillis();
1699 current_edit_step = step;
1701 // Bye bye redo! Can't branch.
1702 redo.clear();
1705 return true;
1708 public boolean canUndo() {
1709 return edit_history.size() > 0;
1711 public boolean canRedo() {
1712 return redo.size() > 0 || null != current_edit_step;
1715 /** Undoes one step of the ongoing transformation history, otherwise of the overall LayerSet history. */
1716 public boolean undoOneStep() {
1717 synchronized (edit_history) {
1718 if (0 == edit_history.size()) {
1719 Utils.log2("Empty undo history.");
1720 return false;
1723 //Utils.log2("Undoing one step");
1725 // Add current (if any) to redo queue
1726 if (null != current_edit_step) {
1727 redo.put(current_edit_time, current_edit_step);
1730 // Remove last step from undo queue, and set it as current
1731 current_edit_time = edit_history.lastKey();
1732 current_edit_step = edit_history.remove(current_edit_time);
1734 // Remove as well from dedits
1735 if (null != current_edit_step.getD()) {
1736 dedits.get(current_edit_step.getD()).remove(current_edit_time);
1739 if (!current_edit_step.apply(DoStep.UNDO)) {
1740 Utils.log("Undo: could not apply step!");
1741 return false;
1744 return true;
1747 /** Redoes one step of the ongoing transformation history, otherwise of the overall LayerSet history. */
1748 public boolean redoOneStep() {
1749 synchronized (edit_history) {
1750 if (0 == redo.size()) {
1751 Utils.log2("Empty redo history.");
1752 if (null != current_edit_step) {
1753 return current_edit_step.apply(DoStep.REDO);
1755 return false;
1758 //Utils.log2("Redoing one step");
1760 // Add current (if any) to undo queue
1761 if (null != current_edit_step) {
1762 edit_history.put(current_edit_time, current_edit_step);
1763 if (null != current_edit_step.getD()) {
1764 dedits.get(current_edit_step.getD()).put(current_edit_time, current_edit_step);
1768 // Remove one step from undo queue and set it as current
1769 current_edit_time = redo.firstKey();
1770 current_edit_step = redo.remove(current_edit_time);
1772 if (!current_edit_step.apply(DoStep.REDO)) {
1773 Utils.log("Undo: could not apply step!");
1774 return false;
1777 return true;
1780 static public void applyTransforms(final Map<Displayable,AffineTransform> m) {
1781 for (final Map.Entry<Displayable,AffineTransform> e : m.entrySet()) {
1782 e.getKey().setAffineTransform(e.getValue()); // updates buckets
1786 static {
1787 // Undo background tasks: should be done in background threads,
1788 // but on attempting to undo/redo, the undo/redo should wait
1789 // until all tasks are done. For example, updating mipmaps when
1790 // undoing/redoing min/max or CoordinateTransform.
1791 // This could be done with futures: spawn and do in the
1792 // background, but on redo/undo, call for the Future return
1793 // value, which will block until there is one to return.
1794 // Since blocking would block the EventDispatchThread, just refuse to undo/redo and notify the user.
1796 // TODO
1799 /** Keeps the width,height of a LayerSet and the AffineTransform of every Displayable in it. */
1800 static private class DoResizeLayerSet implements DoStep {
1802 final LayerSet ls;
1803 final HashMap<Displayable,AffineTransform> affines;
1804 final double width, height;
1806 DoResizeLayerSet(final LayerSet ls) {
1807 this.ls = ls;
1808 this.width = ls.layer_width;
1809 this.height = ls.layer_height;
1810 this.affines = new HashMap<Displayable,AffineTransform>();
1812 final ArrayList<Displayable> col = ls.getDisplayables(); // it's a new list
1813 col.addAll(ls.getZDisplayables());
1814 for (final Displayable d : col) {
1815 this.affines.put(d, d.getAffineTransformCopy());
1818 public boolean isIdenticalTo(final Object ob) {
1819 if (!(ob instanceof LayerSet)) return false;
1820 final LayerSet layerset = (LayerSet) ob;
1821 if (layerset.layer_width != this.width || layerset.height != this.height || layerset != this.ls) return false;
1822 final ArrayList<Displayable> col = ls.getDisplayables();
1823 col.addAll(ls.getZDisplayables());
1824 for (final Displayable d : col) {
1825 final AffineTransform aff = this.affines.get(d);
1826 if (null == aff) return false;
1827 if (!aff.equals(d.getAffineTransform())) return false;
1829 return true;
1832 public boolean apply(int action) {
1833 ls.layer_width = width;
1834 ls.layer_height = height;
1835 for (final Map.Entry<Displayable,AffineTransform> e : affines.entrySet()) {
1836 e.getKey().getAffineTransform().setTransform(e.getValue());
1838 if (null != ls.root) ls.recreateBuckets(true);
1839 Display.updateSelection();
1840 Display.update(ls); //so it's not left out painted beyond borders
1841 return true;
1843 public boolean isEmpty() { return false; }
1844 public Displayable getD() { return null; }
1847 /** Records the state of the LayerSet.al_layers, each Layer.al_displayables and all the trees and unique types of Project. */
1848 static private class DoChangeTrees implements DoStep {
1849 final LayerSet ls;
1850 final HashMap<Thing,Boolean> ttree_exp, ptree_exp, ltree_exp;
1851 final Thing troot, proot, lroot;
1852 final ArrayList<Layer> all_layers;
1853 final HashMap<Layer,ArrayList<Displayable>> all_displ;
1854 final ArrayList<ZDisplayable> all_zdispl;
1855 final HashMap<Displayable,Set<Displayable>> links;
1857 // TODO: does not consider recursive LayerSets!
1858 public DoChangeTrees(final LayerSet ls) {
1859 this.ls = ls;
1860 final Project p = ls.getProject();
1862 this.ttree_exp = new HashMap<Thing,Boolean>();
1863 this.troot = p.getTemplateTree().duplicate(ttree_exp);
1864 this.ptree_exp = new HashMap<Thing,Boolean>();
1865 this.proot = p.getProjectTree().duplicate(ptree_exp);
1866 this.ltree_exp = new HashMap<Thing,Boolean>();
1867 this.lroot = p.getProjectTree().duplicate(ltree_exp);
1869 this.all_layers = ls.getLayers(); // a copy
1870 this.all_zdispl = ls.getZDisplayables(); // a copy
1872 this.links = new HashMap<Displayable,Set<Displayable>>();
1873 for (final ZDisplayable zd : this.all_zdispl) {
1874 this.links.put(zd, zd.hs_linked); // LayerSet is a Displayable
1877 this.all_displ = new HashMap<Layer,ArrayList<Displayable>>();
1878 for (final Layer layer : all_layers) {
1879 final ArrayList<Displayable> al = layer.getDisplayables(); // a copy
1880 this.all_displ.put(layer, al);
1881 for (final Displayable d : al) {
1882 this.links.put(d, null == d.hs_linked ? null : new HashSet<Displayable>(d.hs_linked));
1886 public Displayable getD() { return null; }
1887 public boolean isEmpty() { return false; }
1888 public boolean isIdenticalTo(final Object ob) {
1889 // TODO
1890 return false;
1892 public boolean apply(int action) {
1893 // Replace all layers
1894 ls.al_layers.clear();
1895 ls.al_layers.addAll(this.all_layers);
1897 final ArrayList<Displayable> patches = new ArrayList<Displayable>();
1899 // Replace all Displayable in each Layer
1900 for (final Map.Entry<Layer,ArrayList<Displayable>> e : all_displ.entrySet()) {
1901 // Acquire pointer to the actual instance list in each Layer
1902 final ArrayList<Displayable> al = e.getKey().getDisplayableList(); // the real one!
1903 // Create a list to contain those Displayable present in old list but not in list to use now
1904 final HashSet<Displayable> diff = new HashSet<Displayable>(al); // create with all Displayable of old list
1905 diff.removeAll(e.getValue()); // remove all Displayable present in list to use now, to leave the diff or remainder only
1906 // Clear current list
1907 al.clear();
1908 // Insert all to the current list
1909 al.addAll(e.getValue());
1910 // Add to remove-on-shutdown queue all those Patch no longer in the list to use now:
1911 for (final Displayable d : diff) {
1912 if (d.getClass() == Patch.class) {
1913 d.getProject().getLoader().tagForMipmapRemoval((Patch)d, true);
1916 // Remove from queue all those Patch in the list to use now:
1917 for (final Displayable d : al) {
1918 if (d.getClass() == Patch.class) {
1919 d.getProject().getLoader().tagForMipmapRemoval((Patch)d, false);
1924 // Replace all ZDisplayable
1925 ls.al_zdispl.clear();
1926 ls.al_zdispl.addAll(this.all_zdispl);
1928 // Replace all trees
1929 final Project p = ls.getProject();
1930 p.getTemplateTree().set(this.troot, this.ttree_exp);
1931 p.getProjectTree().set(this.proot, this.ptree_exp);
1932 p.getLayerTree().set(this.lroot, this.ltree_exp);
1934 // Replace all links
1935 for (final Map.Entry<Displayable,Set<Displayable>> e : this.links.entrySet()) {
1936 final Set<Displayable> hs = e.getKey().hs_linked;
1937 if (null != hs) {
1938 final Set<Displayable> hs2 = e.getValue();
1939 if (null == hs2) e.getKey().hs_linked = null;
1940 else {
1941 hs.clear();
1942 hs.addAll(hs2);
1947 ls.recreateBuckets(true);
1949 Display.update(ls);
1951 return true;