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.
23 package ini
.trakem2
.display
;
26 import ij
.gui
.GenericDialog
;
27 import ij
.measure
.Calibration
;
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
;
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;
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
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
;
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
;
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
);
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
])) {
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);
208 if (gd
.wasCanceled()) return null;
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.");
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
); }
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;
227 double z
= layer
.getZ();
229 for (Layer la
: al_layers
) {
230 if (! (la
.getZ() < z
) ) {
231 al_layers
.add(i
, layer
);
232 layer
.setParentSilently(this);
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());
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();
253 Layer l
= (Layer
)al_layers
.get(i
);
254 if (l
.getZ() < z
) continue;
258 al_layers
.add(i
, layer
);
260 al_layers
.add(layer
);
262 layer
.setParent(this);
263 Display
.updateLayerScroller(this);
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() {
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;
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
) {
307 public void keyPressed(KeyEvent ke
) {
308 Utils
.log("LayerSet.keyPressed: not yet implemented.");
312 public String
toString() {
316 public void paint(Graphics2D g
, double magnification
, boolean active
, int channels
, Layer active_layer
) {
317 //arrange transparency
318 Composite original_composite
= null;
320 original_composite
= g
.getComposite();
321 g
.setComposite(AlphaComposite
.getInstance(AlphaComposite
.SRC_OVER
, alpha
));
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.
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
; }
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.");
363 } else if (rot_x
== this.rot_x
&& rot_y
== this.rot_y
&& rot_z
== this.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)
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
397 // set first coordinates
398 if (Double
.isNaN(x
) || Double
.isNaN(y
)) { // Double.NaN == x fails!
402 txe
= tx
+ b
.width
;//d.getWidth();
403 tye
= ty
+ b
.height
;//d.getHeight();
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.");
417 if (w
<= 0 || h
<= 0) {
418 Utils
.log("LayerSet.setMinimumDimensions: zero width or height, NOT resizing.");
422 // Record previous state
423 if (prepareStep(this)) {
424 addEditStep(new LayerSet
.DoResizeLayerSet(this));
428 if (0 != x
|| 0 != y
) {
429 project
.getLoader().startLargeUpdate();
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
) {
446 project
.getLoader().rollback();
451 //Utils.log("x,y xe,ye : " + x + "," + y + " " + xe + "," + ye);
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);
463 // Record current state:
464 addEditStep(new LayerSet
.DoResizeLayerSet(this));
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
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
);
497 recreateBuckets(true);
499 Display
.update(this);
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));
518 double new_x
= 0;// the x,y of the old 0,0
524 new_x
= (layer_width
- this.layer_width
) / 2; // (this.layer_width - layer_width) / 2;
534 new_x
= layer_width
- this.layer_width
; // (this.layer_width - layer_width);
541 new_y
= (layer_height
- this.layer_height
) / 2;
551 new_y
= (layer_height
- this.layer_height
);
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
) {
578 Utils
.showMessage("Cropping " + d
+ "\nLayerSet: not resizing.");
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);
603 addEditStep(new LayerSet
.DoResizeLayerSet(this));
608 protected boolean remove2(boolean 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
) {
619 if (!Utils
.check("Really delete " + this.toString() + (null != al_layers
&& al_layers
.size() > 0 ?
" and all its children?" : ""))) return false;
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.");
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)
634 if (null != parent
) parent
.remove(this);
635 removeFromDatabase();
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
);
650 Utils
.log("LayerSet.next: no such Layer " + 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
);
660 Utils
.log("LayerSet.previous: no such Layer " + layer
);
663 if (0 == i
) return layer
;
664 else return (Layer
)al_layers
.get(i
-1);
667 public Layer
nextNonEmpty(Layer layer
) {
673 if (!next
.isEmpty()) return next
;
674 } while (next
!= layer
);
677 public Layer
previousNonEmpty(Layer layer
) {
678 Layer previous
= layer
;
682 previous
= previous(layer
);
683 if (!previous
.isEmpty()) return previous
;
684 } while (previous
!= layer
);
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
;
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
);
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
;
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!
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
) {
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();
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());
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);
749 project
.getLayerTree().addLayer(this, 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");
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
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");
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;
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());
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
);
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
);
832 for (ZDisplayable zd
: al_zdispl
) {
833 if (c
.isInstance(zd
)) al
.add(zd
);
836 for (ZDisplayable zd
: al_zdispl
) {
837 if (zd
.getClass() == c
) al
.add(zd
);
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();
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() {
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;
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;
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()) {
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
);
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
);
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
>();
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
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
) {
951 project
.getLoader().commitLargeUpdate();
954 Display
.repaint(this); // this could be optimized to repaint only the accumulated box
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
);
967 for (Layer la
: al_layers
) hs
.addAll(la
.hideExcept(type
, repaint
));
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;
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;
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());
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
));
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
));
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();
1127 for (Iterator it
= al_zdispl
.iterator(); it
.hasNext(); ) {
1128 ZDisplayable zd
= (ZDisplayable
)it
.next();
1131 this.al_layers
.clear();
1132 this.al_zdispl
.clear();
1133 if (null != align
) {
1139 public boolean isAligning() {
1140 return null != align
;
1143 public void cancelAlign() {
1144 if (null != align
) {
1145 align
.cancel(); // will repaint
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() {
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
);
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
));
1187 public boolean isTop(ZDisplayable zd
) {
1188 if (null != zd
&& al_zdispl
.size() > 0 && al_zdispl
.indexOf(zd
) == al_zdispl
.size() -1) return true;
1192 public boolean isBottom(ZDisplayable zd
) {
1193 if (null != zd
&& al_zdispl
.size() > 0 && al_zdispl
.indexOf(zd
) == 0) return true;
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
);
1213 Utils
.log("LayerSet.move: object does not belong here");
1216 int size
= al_zdispl
.size();
1217 if (1 == size
) return;
1220 al_zdispl
.add(al_zdispl
.remove(i
));
1223 if (size
-1 == i
) return;
1224 al_zdispl
.add(i
+1, al_zdispl
.remove(i
));
1228 al_zdispl
.add(i
, al_zdispl
.remove(i
-1)); //swap
1230 case LayerSet
.BOTTOM
:
1231 al_zdispl
.add(0, al_zdispl
.remove(i
));
1234 updateInDatabase("stack_index");
1235 Display
.updatePanelIndex(d
.getLayer(), d
);
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;
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
);
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(),
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
) {
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;
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
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
+"\"");
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
);
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();
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.");
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.");
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
) {
1444 } else Utils
.log("LayerSet.grab: Ignoring layer " + la
);
1446 if (0 == stack
.getSize()) {
1447 Utils
.log("LayerSet.grab: could not make slices.");
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
);
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
;
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
;
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())) {
1509 /** For fast search. */
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() {
1521 public HashMap
<Displayable
, ArrayList
<Bucket
>> getBucketMap() {
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
);
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
) {
1548 for (final Layer la
: al_layers
) {
1549 if (null == r
) r
= la
.getMinimalBoundingBox(c
);
1551 Rectangle box
= la
.getMinimalBoundingBox(c
); // may be null if Layer is empty
1552 if (null != box
) r
.add(box
);
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!
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"}));
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
);
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());
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.");
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.");
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
);
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
);
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();
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());
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.
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.");
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!");
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
);
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!");
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
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.
1799 /** Keeps the width,height of a LayerSet and the AffineTransform of every Displayable in it. */
1800 static private class DoResizeLayerSet
implements DoStep
{
1803 final HashMap
<Displayable
,AffineTransform
> affines
;
1804 final double width
, height
;
1806 DoResizeLayerSet(final LayerSet 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;
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
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
{
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
) {
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
) {
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
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
;
1938 final Set
<Displayable
> hs2
= e
.getValue();
1939 if (null == hs2
) e
.getKey().hs_linked
= null;
1947 ls
.recreateBuckets(true);