Use internal SNAPSHOT couplings again
[trakem2.git] / TrakEM2_ / src / main / java / ini / trakem2 / display / Bucket.java
blob2d705f7b0f69804c5aeccf12d33505747836b18b
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;
25 import ini.trakem2.utils.M;
26 import ini.trakem2.utils.Utils;
28 import java.awt.BasicStroke;
29 import java.awt.Color;
30 import java.awt.Graphics2D;
31 import java.awt.Rectangle;
32 import java.awt.Stroke;
33 import java.awt.geom.AffineTransform;
34 import java.awt.geom.Area;
35 import java.util.ArrayList;
36 import java.util.Arrays;
37 import java.util.Collection;
38 import java.util.HashMap;
39 import java.util.HashSet;
40 import java.util.Iterator;
41 import java.util.Map;
42 import java.util.TreeMap;
45 /**
46 * A Bucket is a subarea of the Layer area, which contains either other Buckets or a map of stack_index vs. Displayable instances. VERY IMPORTANT: either children is null, or map is null, but both cannot be null at the same time neither not null at the same time.
49 public class Bucket {
51 static public final int MIN_BUCKET_SIZE = 4096;
53 private int bucket_side;
55 /** The sorted map of stack_index and Displayable objects that are fully contained in this bucket or intersect at top and left, but not at right and bottom. That is, the lower-right corner of the Displayable is contained with the area of this bucket. */
56 private TreeMap<Integer,Displayable> map = null;
57 /** The set of sub-buckets contained here. */
58 private ArrayList<Bucket> children = null;
60 private final int x,y,w,h;
62 private boolean empty = true;
64 public Bucket(final int x, final int y, final int w, final int h, final int bucket_side) {
65 this.x = x;
66 this.y = y;
67 this.w = w;
68 this.h = h;
69 this.bucket_side = bucket_side;
70 Utils.showStatus(new StringBuilder("Creating bucket ").append(x).append(',').append(y).append(',').append(w).append(',').append(h).toString(), false);
71 //Utils.log2(this.toString());
74 public String toString() {
75 return "Bucket: " + x + ", " + y + ", " + w + ", " + h;
78 synchronized final void populate(final Bucketable container, final Layer layer, final HashMap<Displayable,HashSet<Bucket>> db_map) {
79 // Reset
80 if (null != this.map) this.map.clear();
81 this.children = null;
82 // Refill:
83 final HashMap<Integer,Displayable> list = new HashMap<Integer,Displayable>();
84 int i = 0;
85 // cache all bounding boxes
86 final HashMap<Displayable,Area> areas = new HashMap<Displayable,Area>();
87 for (final Displayable d : container.getDisplayableList()) {
88 list.put(i, d);
89 i++;
90 final Area a = d.getAreaForBucket(layer);
91 if (null != a) areas.put(d, a);
93 populate(container, db_map, w+w, h+h, w, h, list, areas);
96 /** Recursive initialization of buckets. This method is meant to be used as init, when root is null or is made new from scratch. Returns true if not empty. */
97 final private boolean populate(final Bucketable container, final HashMap<Displayable,HashSet<Bucket>> db_map, final int parent_w, final int parent_h, final int max_width, final int max_height, final HashMap<Integer,Displayable> parent_list, final HashMap<Displayable,Area> areas) {
98 if (this.w <= bucket_side || this.h <= bucket_side) {
99 // add displayables, sorted by index
100 map = new TreeMap<Integer,Displayable>();
101 for (final Map.Entry<Integer,Displayable> e : parent_list.entrySet()) {
102 final Displayable d = e.getValue();
103 final Area a = areas.get(d);
104 if (null == a) continue;
105 if (a.intersects(x, y, w, h)) {
106 map.put(e.getKey(), d);
107 putToBucketMap(d, db_map);
110 this.empty = map.isEmpty();
111 //Utils.log2(empty ? "EMPTY ": "FILLED " + this);
112 } else {
113 // create child buckets as subdivisions of this one
114 children = new ArrayList<Bucket>(2*2);
116 int side_w = (int)Math.pow(2, (int)Math.floor(Math.log(Math.max(w,h)) / Math.log(2)) - 1);
117 int side_h = side_w;
118 if (side_w > max_width) side_w = max_width;
119 if (side_h > max_height) side_h = max_height;
121 // create list of Displayables that will be added here, as extracted from the parent list
122 final HashMap<Integer,Displayable> local_list = new HashMap<Integer,Displayable>();
123 for (final Map.Entry<Integer,Displayable> e : parent_list.entrySet()) {
124 final Displayable d = e.getValue();
125 final Area a = areas.get(d);
126 if (null == a) continue;
127 if (a.intersects(x, y, w, h)) local_list.put(e.getKey(), d);
130 //Utils.log2(local_list.size() + " :: " + this.toString());
132 for (int x=0; x<parent_w; x += side_w) {
133 if (this.x + x >= max_width) continue;
134 int width = side_w;
135 if (this.x + x + side_w > max_width) width = max_width - this.x - x;
136 for (int y=0; y<parent_h; y += side_h) {
137 if (this.y + y >= max_height) continue;
138 int height = side_h;
139 if (this.y + y + side_h > max_height) height = max_height - this.y - y;
140 final Bucket bu = new Bucket(this.x + x, this.y + y, width, height, bucket_side);
141 if (bu.populate(container, db_map, width, height, max_width, max_height, local_list, areas)) {
142 this.empty = false;
144 children.add(bu);
150 int w = this.w / 2;
151 int h = this.h / 2;
152 for (int i=0; i<2; i++) {
153 for (int j=0; j<2; j++) {
154 Bucket bu = new Bucket(this.x + i * w, this.y + j * h, w, h);
155 if (bu.populate(container, db_map)) {
156 //Utils.log2("FILLEd " + this);
157 this.empty = false;
158 //} else {
159 // Utils.log2("EMPTY " + this);
161 children.add(bu);
166 return !this.empty;
169 private final boolean intersects(final Rectangle r) {
170 if (r.width <= 0 || r.height <= 0 || w <= 0 || h <= 0) {
171 return false;
173 final int rw = r.x + r.width;
174 final int rh = r.y + r.height;
175 final int tw = w + x;
176 final int th = h + y;
177 // overflow || intersect
178 return ((rw < r.x || rw > x) &&
179 (rh < r.y || rh > y) &&
180 (tw < x || tw > r.x) &&
181 (th < y || th > r.y));
184 private final boolean contains(final double px, final double py) {
185 return px >= x &&
186 py >= y &&
187 px <= x + w &&
188 py <= y + h;
191 /** Find All Displayable objects that intersect with the given srcRect and return them ordered by stack_index. Of @param visible_only is true, then hidden Displayable objects are ignored. */
192 synchronized final Collection<Displayable> find(final Rectangle srcRect, final Layer layer, final boolean visible_only) {
193 final TreeMap<Integer,Displayable> accum = new TreeMap<Integer,Displayable>();
194 find(accum, srcRect, layer, visible_only);
195 return accum.values(); // sorted by integer key
198 /** Recursive search, accumulates Displayable objects that intersect the srcRect and, if @param visible_only is true, then checks first if so. */
199 private void find(final TreeMap<Integer,Displayable> accum, final Rectangle srcRect, final Layer layer, final boolean visible_only) {
200 if (empty || !intersects(srcRect)) return;
201 if (null != children) {
202 for (final Bucket bu : children) {
203 bu.find(accum, srcRect, layer, visible_only);
205 } else {
206 final Area asrc = new Area(srcRect);
207 for (final Map.Entry<Integer,Displayable> entry : map.entrySet()) {
208 final Displayable d = entry.getValue();
209 if (visible_only && !d.isVisible()) continue;
210 final Area a = d.getAreaForBucket(layer);
211 if (null != a && M.intersects(asrc, a)) {
212 accum.put(entry.getKey(), d);
218 /** Find All Displayable objects that intersect with the given srcRect and return them ordered by stack_index. Of @param visible_only is true, then hidden Displayable objects are ignored.
220 * Fast and dirty, never returns a false negative but may return a false positive. */
221 synchronized final Collection<Displayable> roughlyFind(final Rectangle srcRect, final Layer layer, final boolean visible_only) {
222 final TreeMap<Integer,Displayable> accum = new TreeMap<Integer,Displayable>();
223 roughlyFind(accum, srcRect, layer, visible_only);
224 return accum.values(); // sorted by integer key
227 /** Recursive search, accumulates Displayable objects that intersect the srcRect and, if @param visible_only is true, then checks first if so. */
228 private void roughlyFind(final TreeMap<Integer,Displayable> accum, final Rectangle srcRect, final Layer layer, final boolean visible_only) {
229 if (empty || !intersects(srcRect)) return;
230 if (null != children) {
231 for (final Bucket bu : children) {
232 bu.roughlyFind(accum, srcRect, layer, visible_only);
234 } else {
235 //final Rectangle tmp = new Rectangle();
236 //final Area asrc = new Area(srcRect);
237 final Rectangle BOX = new Rectangle(x, y, w, h);
238 for (final Map.Entry<Integer,Displayable> entry : map.entrySet()) {
239 final Displayable d = entry.getValue();
240 if (visible_only && !d.isVisible()) continue;
241 /* // Too slow for a rough search as needed by DisplayCanvas.gatherDisplayables!
242 * // That method calls LayerSet.findZDisplayables(Layer, Rectangle, boolean) which calls here
243 final Area a = d.getAreaForBucket(layer);
244 if (null != a && M.intersects(asrc, a)) {
245 accum.put(entry.getKey(), d);
248 // Instead:
249 if (d.isRoughlyInside(layer, BOX)) {
250 accum.put(entry.getKey(), d);
256 /** Find All Displayable objects that intersect with the given srcRect and return them ordered by stack_index. Of @param visible_only is true, then hidden Displayable objects are ignored. */
257 synchronized final Collection<Displayable> find(final Class<?> c, final Rectangle srcRect, final Layer layer, final boolean visible_only, final boolean instance_of) {
258 final TreeMap<Integer,Displayable> accum = new TreeMap<Integer,Displayable>();
259 find(accum, c, srcRect, layer, visible_only, instance_of);
260 return accum.values(); // sorted by integer key
263 /** Recursive search, accumulates Displayable objects that intersect the srcRect and, if @param visible_only is true, then checks first if so. */
264 private void find(final TreeMap<Integer,Displayable> accum, final Class<?> c, final Rectangle srcRect, final Layer layer, final boolean visible_only, final boolean instance_of) {
265 if (empty || !intersects(srcRect)) return;
266 if (null != children) {
267 for (final Bucket bu : children) {
268 bu.find(accum, c, srcRect, layer, visible_only, instance_of);
270 } else {
271 final Area asrc = new Area(srcRect);
272 if (instance_of) {
273 for (final Map.Entry<Integer,Displayable> entry : map.entrySet()) {
274 final Displayable d = entry.getValue();
275 if (visible_only && !d.isVisible()) continue;
276 if (c.isAssignableFrom(d.getClass())) {
277 final Area a = d.getAreaForBucket(layer);
278 if (null != a && M.intersects(asrc, a)) {
279 accum.put(entry.getKey(), d);
283 } else {
284 for (final Map.Entry<Integer,Displayable> entry : map.entrySet()) {
285 final Displayable d = entry.getValue();
286 if (visible_only && !d.isVisible()) continue;
287 if (d.getClass() == c) {
288 final Area a = d.getAreaForBucket(layer);
289 if (null != a && M.intersects(asrc, a)) {
290 accum.put(entry.getKey(), d);
298 /** Find all Displayable objects that contain the given point at the given layer (here layer acts as the Z coordinate, then) and return them ordered by stack_index. If @param visible_only is trye, then hidden Displayable objects are ignored. */
299 synchronized final Collection<Displayable> find(final double px, final double py, final Layer layer, final boolean visible_only) {
300 final TreeMap<Integer,Displayable> accum = new TreeMap<Integer,Displayable>();
301 find(accum, px, py, layer, visible_only);
302 return accum.values(); // sorted by integer key
304 /** Recursive search, accumulates Displayable objects that contain the given point and, if @param visible_only is true, then checks first if so. */
305 private void find(final TreeMap<Integer,Displayable> accum, final double px, final double py, final Layer layer, final boolean visible_only) {
306 if (empty || !contains(px, py)) return;
307 if (null != children) {
308 for (final Bucket bu : children) {
309 bu.find(accum, px, py, layer, visible_only);
311 } else {
312 for (final Map.Entry<Integer,Displayable> entry : map.entrySet()) {
313 final Displayable d = entry.getValue();
314 if (visible_only && !d.isVisible()) continue;
315 if (d.contains(layer, px, py)) {
316 accum.put(entry.getKey(), d);
319 //Utils.log2("Bucket with " + map.size() + " contains click " + this.toString());
323 /** Find all Displayable objects that contain the given point at the given layer (here layer acts as the Z coordinate, then) and return them ordered by stack_index. If @param visible_only is trye, then hidden Displayable objects are ignored. */
324 synchronized final Collection<Displayable> find(final Class<?> c, final double px, final double py, final Layer layer, final boolean visible_only, final boolean instance_of) {
325 final TreeMap<Integer,Displayable> accum = new TreeMap<Integer,Displayable>();
326 find(accum, c, px, py, layer, visible_only, instance_of);
327 return accum.values(); // sorted by integer key
329 /** Recursive search, accumulates Displayable objects that contain the given point and, if @param visible_only is true, then checks first if so. */
330 private void find(final TreeMap<Integer,Displayable> accum, final Class<?> c, final double px, final double py, final Layer layer, final boolean visible_only, final boolean instance_of) {
331 if (empty || !contains(px, py)) return;
332 if (null != children) {
333 for (final Bucket bu : children) {
334 bu.find(accum, c, px, py, layer, visible_only, instance_of);
336 } else {
337 if (instance_of) {
338 for (final Map.Entry<Integer,Displayable> entry : map.entrySet()) {
339 final Displayable d = entry.getValue();
340 if (visible_only && !d.isVisible()) continue;
341 if (c.isAssignableFrom(d.getClass()) && d.contains(layer, px, py)) {
342 accum.put(entry.getKey(), d);
345 } else {
346 for (final Map.Entry<Integer,Displayable> entry : map.entrySet()) {
347 final Displayable d = entry.getValue();
348 if (visible_only && !d.isVisible()) continue;
349 if (d.getClass() == c && d.contains(layer, px, py)) {
350 accum.put(entry.getKey(), d);
357 /** Find all Displayable objects that intersect the given Area and return them ordered by stack_index. If @param visible_only is trye, then hidden Displayable objects are ignored. */
358 synchronized final Collection<Displayable> find(final Area area, final Layer layer, final boolean visible_only) {
359 final TreeMap<Integer,Displayable> accum = new TreeMap<Integer,Displayable>();
360 find(accum, area, layer, visible_only);
361 return accum.values(); // sorted by integer key
363 /** Recursive search, accumulates Displayable objects that contain the given point and, if @param visible_only is true, then checks first if so. */
364 private void find(final TreeMap<Integer,Displayable> accum, final Area area, final Layer layer, final boolean visible_only) {
365 if (empty || !intersects(area.getBounds())) return;
366 if (null != children) {
367 for (final Bucket bu : children) {
368 bu.find(accum, area, layer, visible_only);
370 } else {
371 for (final Map.Entry<Integer,Displayable> entry : map.entrySet()) {
372 final Displayable d = entry.getValue();
373 if (visible_only && !d.isVisible()) continue;
374 if (d.intersects(layer, area)) {
375 accum.put(entry.getKey(), d);
380 /** Find all Displayable objects that intersect the given Area and return them ordered by stack_index. If @param visible_only is trye, then hidden Displayable objects are ignored. */
381 synchronized final Collection<Displayable> find(final Class<?> c, final Area area, final Layer layer, final boolean visible_only, final boolean instance_of) {
382 final TreeMap<Integer,Displayable> accum = new TreeMap<Integer,Displayable>();
383 find(accum, c, area, layer, visible_only, instance_of);
384 return accum.values(); // sorted by integer key
386 /** Recursive search, accumulates Displayable objects that contain the given point and, if @param visible_only is true, then checks first if so. */
387 private void find(final TreeMap<Integer,Displayable> accum, final Class<?> c, final Area area, final Layer layer, final boolean visible_only, final boolean instance_of) {
388 if (empty || !intersects(area.getBounds())) return;
389 if (null != children) {
390 for (final Bucket bu : children) {
391 bu.find(accum, c, area, layer, visible_only, instance_of);
393 } else {
394 if (instance_of) {
395 for (final Map.Entry<Integer,Displayable> entry : map.entrySet()) {
396 final Displayable d = entry.getValue();
397 if (visible_only && !d.isVisible()) continue;
398 if (c.isAssignableFrom(d.getClass()) && d.intersects(layer, area)) {
399 accum.put(entry.getKey(), d);
402 } else {
403 for (final Map.Entry<Integer,Displayable> entry : map.entrySet()) {
404 final Displayable d = entry.getValue();
405 if (visible_only && !d.isVisible()) continue;
406 if (d.getClass() == c && d.intersects(layer, area)) {
407 accum.put(entry.getKey(), d);
414 /** Update a Displayable's stack index from old to new, or a range. */
415 synchronized final void updateRange(final Bucketable container, final Displayable d, final int old_i, final int new_i) {
416 // Build a map with the new indices
417 final HashMap<Displayable,Integer> stack_indices = new HashMap<Displayable,Integer>();
418 final ArrayList<? extends Displayable> dlist = container.getDisplayableList();
419 for (int i=old_i; i<=new_i; i++) {
420 stack_indices.put(dlist.get(i), i);
422 updateRange(container, old_i, new_i, stack_indices);
425 final Set<Displayable> removeRange(final Bucketable container, final int first, final int last) {
426 final HashSet<Displayable> hs = new HashSet<Displayable>();
427 removeRange(container, first, last, hs);
428 return hs;
431 /** Accumulate removed Displayable instances into the HashSet. */
433 final private void removeRange(final Bucketable container, final int first, final int last, final HashSet<Displayable> hs) {
434 if (null != children) {
435 for (Bucket bu : children) bu.removeRange(container, first, last, hs);
436 } else if (null != map) {
437 // remove entire range
438 for (int i=first; i<=last; i++) {
439 final Displayable d = map.remove(i);
440 if (null != d) hs.add(d);
446 final private void updateRange(final Bucketable container, final int first, final int last, final HashMap<Displayable,Integer> new_stack_indices) {
447 if (null != children) {
448 for (final Bucket bu : children) bu.updateRange(container, first, last, new_stack_indices);
449 } else if (null != map) {
450 // remove range
451 // (in two steps, to avoid overwriting existing entries)
452 final ArrayList<Displayable> a = new ArrayList<Displayable>(last - first + 1);
453 for (int i=first; i<=last; i++) {
454 final Displayable d = map.remove(i);
455 if (null != d) a.add(d);
457 // re-add range with new stack_index keys
458 for (final Displayable d : a) map.put(new_stack_indices.get(d), d);
462 /** Remove from wherever it is, then test if it's in that bucket, otherwise re-add. */
463 synchronized final void updatePosition(final Displayable d, final Layer layer, final HashMap<Displayable,HashSet<Bucket>> db_map) {
464 final HashSet<Bucket> hs = db_map.get(d);
465 final Area a = d.getAreaForBucket(layer);
466 final int stack_index = d.getBucketable().getDisplayableList().indexOf(d);
467 if (null != hs) {
468 for (final Iterator<Bucket> it = hs.iterator(); it.hasNext(); ) {
469 final Bucket bu = it.next();
470 if (null != a && a.intersects(bu.x, bu.y, bu.w, bu.h)) continue; // bu.intersects(box)) continue; // no change of bucket: lower-right corner still within the bucket
471 // else, remove
472 bu.map.remove(stack_index);
473 it.remove();
476 // insert wherever appropriate, if not there
477 if (null != a) this.put(stack_index, d, layer, a, db_map);
480 /** Add the given Displayable to all buckets that intercept its bounding box. */
481 synchronized final void put(final int stack_index, final Displayable d, final Layer layer, final HashMap<Displayable,HashSet<Bucket>> db_map) {
482 put(stack_index, d, layer, d.getAreaForBucket(layer), db_map);
484 synchronized final void put(final int stack_index, final Displayable d, final Layer layer, final Area a, final HashMap<Displayable,HashSet<Bucket>> db_map) {
485 if (null == a) return;
487 if (0 == box.width || 0 == box.height) {
488 // d doesn't contain any data: use whole 2D world
489 box.width = (int) layer.getLayerWidth();
490 box.height = (int) layer.getLayerHeight();
493 putIn(stack_index, d, a, db_map);
495 private final void putIn(final int stack_index, final Displayable d, final Area a, final HashMap<Displayable,HashSet<Bucket>> db_map) {
496 if (!a.intersects(x, y, w, h)) return;
497 // there will be at least one now
498 this.empty = false;
499 if (null != children) {
500 for (final Bucket bu : children) bu.putIn(stack_index, d, a, db_map);
501 } else if (null != map) {
502 map.put(stack_index, d);
503 putToBucketMap(d, db_map); // the db_map
508 private void debugMap(String title) {
509 if (null == map) return;
510 Utils.log2("@@@ " + title);
511 for (final Map.Entry<Integer,Displayable> e: map.entrySet()) {
512 Utils.log2("k,v : " + e.getKey() + " , " + e.getValue());
517 final private void putToBucketMap(final Displayable d, final HashMap<Displayable,HashSet<Bucket>> db_map) {
518 HashSet<Bucket> list = db_map.get(d);
519 if (null == list) {
520 list = new HashSet<Bucket>();
521 db_map.put(d, list);
522 list.add(this);
523 } else list.add(this);
526 final private void removeFromBucketMap(final Displayable d, final HashMap<Displayable,ArrayList<Bucket>> db_map) {
527 ArrayList<Bucket> list = db_map.get(d);
528 if (null == list) return;
529 list.remove(d);
530 if (0 == list.size()) db_map.remove(d);
534 /** Returns whether the stack index was successfully removed.
535 * Assumes that 'd' is in this Bucket at old_stack_index. */
536 final private boolean remove2(final Displayable d, final int old_stack_index, final HashMap<Displayable,Integer> new_stack_indices) {
537 boolean success = true;
538 if (null != children) {
539 this.empty = true;
540 for (final Bucket bu : children) {
541 if (!bu.remove2(d, old_stack_index, new_stack_indices)) {
542 this.empty = false;
543 success = false;
546 return success;
547 } else if (null != map) {
548 reindex(new_stack_indices);
550 return success;
553 /** Returns whether the stack index was successfully removed .*/
554 synchronized final boolean remove(final Displayable d, final int old_stack_index, final HashMap<Displayable,Integer> new_stack_indices) {
555 return remove2(d, old_stack_index, new_stack_indices);
558 /** Returns whether this bucket is empty of Displayable objects. */
559 final private boolean removeAll2(final Collection<Integer> old_stack_indices, final HashMap<Displayable,Integer> new_stack_indices) {
560 if (null != children) {
561 this.empty = true;
562 for (final Bucket bu : children) {
563 if (!bu.removeAll2(old_stack_indices, new_stack_indices)) this.empty = false;
565 } else if (null != map) {
566 reindex(new_stack_indices);
567 return map.isEmpty();
569 return true;
572 synchronized final void removeAll(final Collection<Integer> old_stack_indices, final HashMap<Displayable,Integer> new_stack_indices) {
573 removeAll2(old_stack_indices, new_stack_indices);
576 final void reindex(final HashMap<Displayable,Integer> new_stack_indices) {
577 if (null == new_stack_indices) return;
578 if (null != children) {
579 for (final Bucket bu : children) {
580 bu.reindex(new_stack_indices);
582 } else if (null != map) {
583 final HashSet<Displayable> hs = new HashSet<Displayable>(this.map.values());
584 this.map.clear();
585 for (final Displayable d : hs) {
586 final Integer i = new_stack_indices.get(d);
587 if (null == i) {
588 Utils.log2("WARNING: Bucket.reindex could not find an index for " + d);
589 continue;
591 this.map.put(i, d);
596 synchronized public void paint(Graphics2D g, Rectangle srcRect, double mag, Color color) {
597 if (null == map) {
598 for (final Bucket bu : children) bu.paint(g, srcRect, mag, color);
599 return;
601 //Utils.log("going to paint ... ");
602 //if (!intersects(srcRect)) return;
603 //Utils.log("painting : " + x + ", " + y + ", " + w + ", " + h);
604 final Graphics2D g2d = (Graphics2D)g;
605 final Stroke original_stroke = g2d.getStroke();
606 AffineTransform original = g2d.getTransform();
607 g2d.setTransform(new AffineTransform());
608 g2d.setStroke(new BasicStroke(2, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER));
609 g.setColor(color);
610 g.drawRect((int)((x - srcRect.x) * mag), (int)((y-srcRect.y)*mag), (int)(w*mag), (int)(h*mag));
611 g2d.setStroke(original_stroke);
612 g2d.drawString(Integer.toString(map.size()), (int)((x - srcRect.x + w/2) * mag), (int)((y - srcRect.y + h/2) * mag));
613 g2d.setTransform(original);
616 /** Determine whether the rectangle is smaller than the layer dimensions padded in by one bucket_side -- if not, makes little sense to use buckets, and it's better to do linear search without the TreeMap overhead. */
617 public final boolean isBetter(final Rectangle r, final Bucketable container) {
619 final boolean b = r.width * r.height < (layer.getLayerWidth() - bucket_side) * (layer.getLayerHeight() - bucket_side);
620 Utils.log2("isBetter: " + b);
621 if (b) {
622 Utils.log2("\t r is " + r.width + ", " + r.height);
623 Utils.log2("\t o is " + (int)(layer.getLayerWidth() - bucket_side) + ", " + (int)(layer.getLayerHeight() * bucket_side));
625 return b;
627 return r.width * r.height < (container.getLayerWidth() - bucket_side) * (container.getLayerHeight() - bucket_side);
630 private final ArrayList<Bucket>getChildren(final ArrayList<Bucket> bus) {
631 if (null != children) {
632 for (final Bucket bu : children) {
633 bu.getChildren(bus);
635 } else if (null != map) {
636 bus.add(this);
638 return bus;
641 public void debug() {
642 Utils.log2("total map buckets: " + getChildren(new ArrayList<Bucket>()).size());
645 static public int getBucketSide(final Bucketable container, final Layer la) {
646 if (null != container.getProject().getProperty("bucket_side")) {
647 final int size = (int)container.getProject().getProperty("bucket_side", Bucket.MIN_BUCKET_SIZE);
648 if (size < Bucket.MIN_BUCKET_SIZE) {
649 Utils.logAll("WARNING: bucket side (" + size + ") is smaller than the recommended minimum of " + Bucket.MIN_BUCKET_SIZE
650 + "\nYou may adjust the bucket side in the 'Project - Properties' popup menu.");
652 return size;
653 } else {
654 // estimate median
655 final ArrayList<? extends Displayable> col = container.getDisplayableList();
656 if (0 == col.size()) return Bucket.MIN_BUCKET_SIZE;
657 final int[] sizes = new int[col.size()];
658 int i = 0;
659 for (final Displayable d : col) {
660 Area a = d.getAreaForBucket(la);
661 if (null == a) continue;
662 Rectangle r = a.getBounds();
663 sizes[i++] = Math.max(r.width, r.height);
665 Arrays.sort(sizes);
666 int size = 2 * sizes[sizes.length/2];
667 return size > Bucket.MIN_BUCKET_SIZE ? size : Bucket.MIN_BUCKET_SIZE;