Removed numerous debug msg from AreaList and added comment.
[trakem2.git] / ini / trakem2 / display / DisplayNavigator.java
blobcf40db46e2488cbd7b7c8127351eaf6d24fe40ff
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 java.awt.Canvas;
26 import javax.swing.JPanel;
27 import java.awt.Image;
28 import java.awt.Color;
29 import java.awt.Component;
30 import java.awt.Dimension;
31 import java.awt.Graphics;
32 import java.awt.image.BufferedImage;
33 import java.awt.image.VolatileImage;
34 import java.awt.Graphics2D;
35 import java.awt.BasicStroke;
36 import java.awt.Rectangle;
37 import java.awt.event.MouseListener;
38 import java.awt.event.MouseMotionListener;
39 import java.awt.event.MouseEvent;
40 import java.util.Iterator;
41 import java.util.ArrayList;
42 import java.util.HashSet;
43 import ini.trakem2.utils.*;
44 import java.awt.geom.AffineTransform;
45 import java.awt.GraphicsConfiguration;
47 public final class DisplayNavigator extends JPanel implements MouseListener, MouseMotionListener {
49 private Display display;
50 private Layer layer;
51 private HashSet hs_painted = new HashSet();
52 static private final int FIXED_WIDTH = 250;
53 private int height;
54 private BufferedImage image = null;
55 private boolean redraw_displayables = true;
56 private double scale;
57 private Rectangle srcRect;
58 private int x_p, y_p;
59 private int new_x_old=0, new_y_old=0;
61 private final Object updating_ob = new Object();
62 private boolean updating = false;
64 private VolatileImage volatileImage;
65 private boolean invalid_volatile = false;
67 DisplayNavigator(Display display, double layer_width, double layer_height) { // contorsions to avoid java bugs ( a.k.a. the 'this' is not functional until the object in question has finished initialization.
68 this.display = display;
69 this.layer = display.getLayer();
70 this.scale = FIXED_WIDTH / layer_width;
71 this.height = (int)(layer_height * scale);
72 //Utils.log("fixed_w, h: " + FIXED_WIDTH +","+ height + " layer_width,height: " + layer_width + "," + layer_height);
73 Dimension d = new Dimension(FIXED_WIDTH, height);
74 setPreferredSize(d);
75 setMinimumSize(d);
76 setMaximumSize(d);
77 addMouseMotionListener(this);
78 addMouseListener(this);
79 addKeyListener(display.getCanvas());
82 /** Fixes size if changed. Multithreaded. */
83 public void repaint() {
84 if (null == display || null == display.getCanvas() || null == display.getLayer() || display.getCanvas().isDragging()) return;
85 // fixing null at start up (because the JPanel becomes initialized and repainted before returning to my subclass constructor! Stupid java!)
86 if (null == display) return;
88 //check if layer has changed
89 if (this.layer != display.getLayer()) {
90 this.layer = display.getLayer();
91 this.hs_painted.clear();
94 scale = FIXED_WIDTH / display.getLayer().getLayerWidth();
95 int height = (int)(display.getLayer().getLayerHeight() * scale);
96 if (height != this.height) {
97 Dimension d = new Dimension(FIXED_WIDTH, height);
98 setPreferredSize(d);
99 setMinimumSize(d);
100 setMaximumSize(d); //this triple set *should* update the values in the super class JPanel
101 redraw_displayables = true;
102 invalid_volatile = true;
103 this.height = height;
105 //Utils.log2("w,h: " + FIXED_WIDTH + "," + height + ", scale: " + scale);
106 // magic cocktel:
107 //this.invalidate();
108 //this.validate(); // possible cause of infinite loops with infinite threads
109 RT.paint(null, redraw_displayables);
112 public void repaint(boolean update_graphics) {
113 redraw_displayables = update_graphics;
114 invalid_volatile = true;
115 repaint();
118 /** Only its bounding box. */ // TODO problems: when the object has been moved, it leaves a trail (no repainting of the old position). So this is for now only useful for the setVisible (where the object doesn't move)
119 public void repaint(Displayable d) {
120 if (display.getCanvas().isDragging()) return;
121 redraw_displayables = true;
122 invalid_volatile = true;
123 final Rectangle r = d.getBoundingBox(null);
124 r.x = (int)(r.x * scale);
125 r.y = (int)(r.y * scale);
126 r.width = (int)Math.ceil(r.width * scale);
127 r.height = (int)Math.ceil(r.height * scale);
128 RT.paint(r, redraw_displayables);
131 /** Overridden to multithread. TrakEM2 does not call this method directly ever. */
132 public void repaint(int x, int y, int width, int height) {
133 if (display.getCanvas().isDragging()) return;
134 RT.paint(new Rectangle(x, y, width, height), redraw_displayables);
137 /** Box is given in offscreen canvas coords. */
138 public void repaint(Rectangle box) {
139 if (null == box || display.getCanvas().isDragging()) return;
140 // bring box to the scale
141 Rectangle b = new Rectangle((int)(box.x * scale), (int)(box.y * scale), (int)Math.ceil(box.width * scale), (int)Math.ceil(box.height * scale));
142 RT.paint(b, redraw_displayables);
145 /* // saved as not overridden to make sure there are no infinite thread loops when calling super in buggy JVMs
146 public void repaint(long ms, int x, int y, int width, int height) {
147 RT.paint(new Rectangle(x, y, width, height));
151 public void update(Graphics g) {
152 paint(g);
156 private int snapshots_mode = 0;
158 private class RepaintProperties implements AbstractOffscreenThread.RepaintProperties {
159 final Rectangle clipRect;
160 final int snapshots_mode;
161 final Layer layer;
163 RepaintProperties(final Rectangle clipRect, final Layer layer, final int snapshots_mode) {
164 this.clipRect = clipRect;
165 this.layer = layer;
166 this.snapshots_mode = snapshots_mode;
170 private final class UpdateGraphicsThread extends AbstractOffscreenThread {
172 UpdateGraphicsThread() {
173 super("T2-Navigator-UpdateGraphics");
176 /** paint all snapshots, scaled, to an offscreen awt.Image */
177 public void paint() {
179 final BufferedImage target = new BufferedImage(FIXED_WIDTH, height, BufferedImage.TYPE_INT_ARGB);
181 final Layer layer;
182 final int snapshots_mode;
183 final Rectangle clipRect;
185 synchronized (this) {
186 DisplayNavigator.RepaintProperties rp = (DisplayNavigator.RepaintProperties) this.rp;
187 layer = rp.layer;
188 snapshots_mode = rp.snapshots_mode;
189 clipRect = rp.clipRect;
192 if (null != DisplayNavigator.this.image && 2 == snapshots_mode && DisplayNavigator.this.snapshots_mode == snapshots_mode) {
193 DisplayNavigator.this.redraw_displayables = false;
194 RT.paint(clipRect, false);
195 return;
196 } else {
197 DisplayNavigator.this.snapshots_mode = snapshots_mode;
200 try {
201 final Graphics2D g = target.createGraphics();
202 // paint background as black
203 g.setColor(Color.black);
204 g.fillRect(0, 0, DisplayNavigator.super.getWidth(), DisplayNavigator.super.getHeight());
206 // check if disabled
207 if (2 != snapshots_mode) {
208 // set a scaled stroke, or 0.4 if too small
209 if (scale >= 0.4D) g.setStroke(new BasicStroke((float)scale));
210 else g.setStroke(new BasicStroke(0.4f));
212 g.scale(scale, scale);
214 final ArrayList al = display.getLayer().getDisplayables();
215 final int size = al.size();
216 boolean zd_done = false;
217 for (int i=0; i<size; i++) {
218 final Displayable d = (Displayable)al.get(i);
219 //if (d.isOutOfRepaintingClip(clip, scale)) continue; // needed at least for the visibility
220 if (!d.isVisible()) continue; // TODO proper clipRect for this navigator image may be necessary (lots of changes needed in the lines above reltive to filling the black background, etc)
221 final Class c = d.getClass();
222 if (!zd_done && DLabel.class == c) {
223 zd_done = true;
224 // paint ZDisplayables before the labels (i.e. text labels on top)
225 final Iterator itz = display.getLayer().getParent().getZDisplayables().iterator();
226 while (itz.hasNext()) {
227 ZDisplayable zd = (ZDisplayable)itz.next();
228 if (!zd.isVisible()) continue;
229 zd.paintSnapshot(g, scale);
231 // paint the label too!
232 d.paint(g, scale, false, 1, DisplayNavigator.this.layer);
233 } else if (Patch.class == c) {
234 if (0 == snapshots_mode) {
235 // paint fully
236 final Patch p = (Patch)d;
237 final Image img = d.getProject().getLoader().getCachedClosestAboveImage(p, scale);
238 if (null != img) {
239 if (d.isVisible()) d.paint(g, scale, false, p.getChannelAlphas(), DisplayNavigator.this.layer);
240 hs_painted.add(d);
241 } else {
242 d.paintAsBox(g);
244 } else {
245 // paint as outlines
246 d.paintAsBox(g);
248 } else {
249 if (d.isVisible()) d.paint(g, scale, false, 1, DisplayNavigator.this.layer);
252 if (!zd_done) { // if no labels, ZDisplayables haven't been painted
253 zd_done = true;
254 // paint ZDisplayables before the labels
255 final Iterator itz = display.getLayer().getParent().getZDisplayables().iterator();
256 while (itz.hasNext()) {
257 ZDisplayable zd = (ZDisplayable)itz.next();
258 if (!zd.isVisible()) continue;
259 zd.paintSnapshot(g, scale);
263 // finally, when done, call repaint (like sending an event)
265 // block only while modifying the image pointer
266 synchronized (updating_ob) {
267 while (updating) {
268 try { updating_ob.wait(); } catch (InterruptedException ie) {}
270 updating = true;
272 height = DisplayNavigator.super.getHeight();
273 DisplayNavigator.this.image = target;
274 redraw_displayables = false;
276 updating = false;
277 updating_ob.notifyAll();
279 RT.paint(clipRect, false);
280 } catch (Exception e) {
281 IJError.print(e);
286 private void renderVolatileImage(final BufferedImage bufferedImage) {
287 do {
288 final int w = FIXED_WIDTH, h = this.height;
289 final GraphicsConfiguration gc = getGraphicsConfiguration();
290 if (invalid_volatile || volatileImage == null || volatileImage.getWidth() != w
291 || volatileImage.getHeight() != h
292 || volatileImage.validate(gc) == VolatileImage.IMAGE_INCOMPATIBLE) {
293 if (volatileImage != null) {
294 volatileImage.flush();
296 volatileImage = gc.createCompatibleVolatileImage(w, h);
297 volatileImage.setAccelerationPriority(1.0f);
298 invalid_volatile = false;
301 // Now paint the BufferedImage into the accelerated image
303 final Graphics2D g = volatileImage.createGraphics();
304 g.drawImage(bufferedImage, 0, 0, FIXED_WIDTH, this.height, null);
306 // paint red rectangle indicating srcRect
307 final Rectangle srcRect = display.getCanvas().getSrcRect();
308 g.setColor(Color.red);
309 g.setStroke(new BasicStroke(2.0f));
310 int gw = (int)(srcRect.width * scale) -2;
311 int gh = (int)(srcRect.height * scale) -2;
312 if (gw < 5) gw = 5;
313 if (gh < 5) gh = 5;
314 g.drawRect((int)(srcRect.x * scale) +1, (int)(srcRect.y * scale) +1, gw, gh);
316 } while (volatileImage.contentsLost());
319 private void render(final Graphics g) {
320 final Graphics2D g2d = (Graphics2D) g.create();
321 g2d.setRenderingHints(DisplayCanvas.rhints);
322 do {
323 if (invalid_volatile || null == volatileImage
324 || volatileImage.validate(getGraphicsConfiguration()) != VolatileImage.IMAGE_OK)
326 renderVolatileImage(image);
328 g2d.drawImage(volatileImage, 0, 0, null);
329 } while (volatileImage.contentsLost());
331 g2d.dispose();
334 public void paint(final Graphics g) {
335 final Graphics2D g2d = (Graphics2D)g;
336 synchronized (updating_ob) {
337 while (updating) { try { updating_ob.wait(); } catch (InterruptedException ie) {} }
338 updating = true;
340 if (null != image) {
341 g.drawImage(image, 0, 0, FIXED_WIDTH, this.height, null);
344 render(g);
346 updating = false;
347 updating_ob.notifyAll();
351 /** Handles repaint event requests and the generation of offscreen threads. */
352 private final AbstractRepaintThread RT = new AbstractRepaintThread(this, "T2-Navigator-Repainter", new UpdateGraphicsThread()) {
353 protected void handleUpdateGraphics(Component target, Rectangle clipRect) {
354 this.off.setProperties(new RepaintProperties(clipRect, layer, layer.getParent().getSnapshotsMode()));
358 private boolean drag = false;
360 public void mousePressed(final MouseEvent me) {
361 x_p = me.getX();
362 y_p = me.getY();
363 this.srcRect = (Rectangle)display.getCanvas().getSrcRect().clone();
364 // prevent dragging unless mouse is inside the red box
365 if (srcRect.contains((int)(x_p / scale), (int)(y_p / scale))) {
366 drag = true;
370 public void mouseDragged(final MouseEvent me) {
371 if (!drag) return;
372 // prevent action if the srcRect takes over the whole area
373 if (this.srcRect.width == display.getLayer().getLayerWidth() && this.srcRect.height == display.getLayer().getLayerHeight()) {
374 return;
376 int x_d = me.getX();
377 int y_d = me.getY();
378 // prevent dragging beyond screen
379 if (x_d > this.getWidth() || x_d < 0 || y_d > this.getHeight() || y_d < 0) {
380 return;
382 int new_x = srcRect.x + (int)((x_d - x_p) / scale);
383 int new_y = srcRect.y + (int)((y_d - y_p) / scale);
384 if (new_x < 0) new_x = 0;
385 if (new_y < 0) new_y = 0;
386 if (new_x + srcRect.width > (int)(this.getWidth() / scale)) new_x = (int)(this.getWidth() / scale - srcRect.width);
387 if (new_y + srcRect.height > (int)(this.getHeight() / scale)) new_y = (int)(this.getHeight() / scale - srcRect.height);
388 if (new_x_old == new_x && new_y_old == new_y) {
389 // avoid repaints
390 return;
392 new_x_old = new_x;
393 new_y_old = new_y;
394 DisplayCanvas canvas = display.getCanvas();
395 canvas.setSrcRect(new_x, new_y, this.srcRect.width, this.srcRect.height);
396 canvas.repaint(true);
397 invalid_volatile = true;
398 this.repaint();
401 public void mouseReleased(MouseEvent me) { drag = false; }
402 public void mouseEntered(MouseEvent me) {}
403 public void mouseExited (MouseEvent me) {}
404 public void mouseClicked(MouseEvent me) {}
405 public void mouseMoved(MouseEvent me) {}
407 /** Release resources. */
408 public void destroy() {
409 synchronized (updating_ob) {
410 while (updating) try { updating_ob.wait(); } catch (InterruptedException ie) {}
411 updating = true;
412 RT.quit();
413 updating = false;
414 updating_ob.notifyAll();
416 Thread.yield();
417 synchronized (updating_ob) {
418 while (updating) try { updating_ob.wait(); } catch (InterruptedException ie) {}
419 updating = true;
420 if (null != image) {
421 image.flush();
422 image = null;
424 updating = false;
425 updating_ob.notifyAll();
429 /** Returns true if the given Displayable has been painted as an image and false if as a box or not at all. */
430 public boolean isPainted(Displayable d) {
431 return hs_painted.contains(d);