source: josm/trunk/src/org/openstreetmap/josm/gui/layer/geoimage/ImageDisplay.java@ 2629

Last change on this file since 2629 was 2602, checked in by bastiK, 14 years ago

some minor things:

  • layerlist dialog: right click on an entry selects it
  • geoimage dialog: add button on the lower right, that moves the window to the side pane
  • geoimage dialog: show image file name in the title
  • chained calling for ExtendedDialog
  • Property svn:eol-style set to native
File size: 23.4 KB
Line 
1// License: GPL. See LICENSE file for details.
2// Copyright 2007 by Christian Gallioz (aka khris78)
3
4package org.openstreetmap.josm.gui.layer.geoimage;
5
6import static org.openstreetmap.josm.tools.I18n.tr;
7
8import java.awt.Color;
9import java.awt.Dimension;
10import java.awt.FontMetrics;
11import java.awt.Graphics;
12import java.awt.Image;
13import java.awt.MediaTracker;
14import java.awt.Point;
15import java.awt.Rectangle;
16import java.awt.Toolkit;
17import java.awt.event.MouseEvent;
18import java.awt.event.MouseListener;
19import java.awt.event.MouseMotionListener;
20import java.awt.event.MouseWheelEvent;
21import java.awt.event.MouseWheelListener;
22import java.awt.geom.Rectangle2D;
23import java.io.File;
24
25import javax.swing.JComponent;
26
27import org.openstreetmap.josm.Main;
28
29public class ImageDisplay extends JComponent {
30
31 /** The file that is currently displayed */
32 private File file = null;
33
34 /** The image currently displayed */
35 private Image image = null;
36 private Image image_c = null;
37
38 /** The image currently displayed */
39 private boolean errorLoading = false;
40 private boolean errorLoading_c = false;
41
42 /** The rectangle (in image coordinates) of the image that is visible. This rectangle is calculated
43 * each time the zoom is modified */
44 private Rectangle visibleRect = null;
45 private Rectangle visibleRect_c = null;
46
47 /** When a selection is done, the rectangle of the selection (in image coordinates) */
48 private Rectangle selectedRect = null;
49
50 /** The tracker to load the images */
51 private MediaTracker tracker = new MediaTracker(this);
52
53 private String osdText = null;
54
55 private static int DRAG_BUTTON = Main.pref.getBoolean("geoimage.agpifo-style-drag-and-zoom", false) ? 1 : 3;
56 private static int ZOOM_BUTTON = DRAG_BUTTON == 1 ? 3 : 1;
57
58 /** The thread that reads the images. */
59 private class LoadImageRunnable implements Runnable {
60
61 File file = null;
62
63 public LoadImageRunnable(File file) {
64 this.file = file;
65 }
66
67 public void run() {
68 Image img = Toolkit.getDefaultToolkit().createImage(file.getPath());
69 tracker.addImage(img, 1);
70
71 // Wait for the end of loading
72 while (! tracker.checkID(1, true)) {
73 if (this.file != ImageDisplay.this.file) {
74 // The file has changed
75 tracker.removeImage(img);
76 return;
77 }
78 try {
79 Thread.sleep(5);
80 } catch (InterruptedException e) {
81 }
82 }
83
84 boolean error = tracker.isErrorID(1);
85 if (img != null && (img.getWidth(null) == 0 || img.getHeight(null) == 0)) {
86 error = true;
87 }
88
89 synchronized(ImageDisplay.this) {
90 if (this.file != ImageDisplay.this.file) {
91 // The file has changed
92 tracker.removeImage(img);
93 return;
94 }
95 ImageDisplay.this.image = img;
96 visibleRect = new Rectangle(0, 0, img.getWidth(null), img.getHeight(null));
97 selectedRect = null;
98 errorLoading = error;
99 }
100 tracker.removeImage(img);
101 ImageDisplay.this.repaint();
102 }
103 }
104
105 private class ImgDisplayMouseListener implements MouseListener, MouseWheelListener, MouseMotionListener {
106
107 boolean mouseIsDragging = false;
108 long lastTimeForMousePoint = 0l;
109 Point mousePointInImg = null;
110
111 /** Zoom in and out, trying to preserve the point of the image that was under the mouse cursor
112 * at the same place */
113 public void mouseWheelMoved(MouseWheelEvent e) {
114 File file;
115 Image image;
116 Rectangle visibleRect;
117
118 synchronized (ImageDisplay.this) {
119 file = ImageDisplay.this.file;
120 image = ImageDisplay.this.image;
121 visibleRect = ImageDisplay.this.visibleRect;
122 }
123
124 mouseIsDragging = false;
125 selectedRect = null;
126
127 if (image == null) {
128 return;
129 }
130
131 // Calculate the mouse cursor position in image coordinates, so that we can center the zoom
132 // on that mouse position.
133 // To avoid issues when the user tries to zoom in on the image borders, this point is not calculated
134 // again if there was less than 1.5seconds since the last event.
135 if (e.getWhen() - lastTimeForMousePoint > 1500 || mousePointInImg == null) {
136 lastTimeForMousePoint = e.getWhen();
137 mousePointInImg = comp2imgCoord(visibleRect, e.getX(), e.getY());
138 }
139
140 // Applicate the zoom to the visible rectangle in image coordinates
141 if (e.getWheelRotation() > 0) {
142 visibleRect.width = visibleRect.width * 3 / 2;
143 visibleRect.height = visibleRect.height * 3 / 2;
144 } else {
145 visibleRect.width = visibleRect.width * 2 / 3;
146 visibleRect.height = visibleRect.height * 2 / 3;
147 }
148
149 // Check that the zoom doesn't exceed 2:1
150 if (visibleRect.width < getSize().width / 2) {
151 visibleRect.width = getSize().width / 2;
152 }
153 if (visibleRect.height < getSize().height / 2) {
154 visibleRect.height = getSize().height / 2;
155 }
156
157 // Set the same ratio for the visible rectangle and the display area
158 int hFact = visibleRect.height * getSize().width;
159 int wFact = visibleRect.width * getSize().height;
160 if (hFact > wFact) {
161 visibleRect.width = hFact / getSize().height;
162 } else {
163 visibleRect.height = wFact / getSize().width;
164 }
165
166 // The size of the visible rectangle is limited by the image size.
167 checkVisibleRectSize(image, visibleRect);
168
169 // Set the position of the visible rectangle, so that the mouse cursor doesn't move on the image.
170 Rectangle drawRect = calculateDrawImageRectangle(visibleRect);
171 visibleRect.x = mousePointInImg.x + ((drawRect.x - e.getX()) * visibleRect.width) / drawRect.width;
172 visibleRect.y = mousePointInImg.y + ((drawRect.y - e.getY()) * visibleRect.height) / drawRect.height;
173
174 // The position is also limited by the image size
175 checkVisibleRectPos(image, visibleRect);
176
177 synchronized(ImageDisplay.this) {
178 if (ImageDisplay.this.file == file) {
179 ImageDisplay.this.visibleRect = visibleRect;
180 }
181 }
182 ImageDisplay.this.repaint();
183 }
184
185 /** Center the display on the point that has been clicked */
186 public void mouseClicked(MouseEvent e) {
187 // Move the center to the clicked point.
188 File file;
189 Image image;
190 Rectangle visibleRect;
191
192 synchronized (ImageDisplay.this) {
193 file = ImageDisplay.this.file;
194 image = ImageDisplay.this.image;
195 visibleRect = ImageDisplay.this.visibleRect;
196 }
197
198 if (image == null) {
199 return;
200 }
201
202 if (e.getButton() != DRAG_BUTTON) {
203 return;
204 }
205
206 // Calculate the translation to set the clicked point the center of the view.
207 Point click = comp2imgCoord(visibleRect, e.getX(), e.getY());
208 Point center = getCenterImgCoord(visibleRect);
209
210 visibleRect.x += click.x - center.x;
211 visibleRect.y += click.y - center.y;
212
213 checkVisibleRectPos(image, visibleRect);
214
215 synchronized(ImageDisplay.this) {
216 if (ImageDisplay.this.file == file) {
217 ImageDisplay.this.visibleRect = visibleRect;
218 }
219 }
220 ImageDisplay.this.repaint();
221 }
222
223 /** Initialize the dragging, either with button 1 (simple dragging) or button 3 (selection of
224 * a picture part) */
225 public void mousePressed(MouseEvent e) {
226 if (image == null) {
227 mouseIsDragging = false;
228 selectedRect = null;
229 return;
230 }
231
232 File file;
233 Image image;
234 Rectangle visibleRect;
235
236 synchronized (ImageDisplay.this) {
237 file = ImageDisplay.this.file;
238 image = ImageDisplay.this.image;
239 visibleRect = ImageDisplay.this.visibleRect;
240 }
241
242 if (image == null) {
243 return;
244 }
245
246 if (e.getButton() == DRAG_BUTTON) {
247 mousePointInImg = comp2imgCoord(visibleRect, e.getX(), e.getY());
248 mouseIsDragging = true;
249 selectedRect = null;
250 } else if (e.getButton() == ZOOM_BUTTON) {
251 mousePointInImg = comp2imgCoord(visibleRect, e.getX(), e.getY());
252 checkPointInVisibleRect(mousePointInImg, visibleRect);
253 mouseIsDragging = false;
254 selectedRect = new Rectangle(mousePointInImg.x, mousePointInImg.y, 0, 0);
255 ImageDisplay.this.repaint();
256 } else {
257 mouseIsDragging = false;
258 selectedRect = null;
259 }
260 }
261
262 public void mouseDragged(MouseEvent e) {
263 if (! mouseIsDragging && selectedRect == null) {
264 return;
265 }
266
267 File file;
268 Image image;
269 Rectangle visibleRect;
270
271 synchronized (ImageDisplay.this) {
272 file = ImageDisplay.this.file;
273 image = ImageDisplay.this.image;
274 visibleRect = ImageDisplay.this.visibleRect;
275 }
276
277 if (image == null) {
278 mouseIsDragging = false;
279 selectedRect = null;
280 return;
281 }
282
283 if (mouseIsDragging) {
284 Point p = comp2imgCoord(visibleRect, e.getX(), e.getY());
285 visibleRect.x += mousePointInImg.x - p.x;
286 visibleRect.y += mousePointInImg.y - p.y;
287 checkVisibleRectPos(image, visibleRect);
288 synchronized(ImageDisplay.this) {
289 if (ImageDisplay.this.file == file) {
290 ImageDisplay.this.visibleRect = visibleRect;
291 }
292 }
293 ImageDisplay.this.repaint();
294
295 } else if (selectedRect != null) {
296 Point p = comp2imgCoord(visibleRect, e.getX(), e.getY());
297 checkPointInVisibleRect(p, visibleRect);
298 Rectangle rect = new Rectangle(
299 (p.x < mousePointInImg.x ? p.x : mousePointInImg.x),
300 (p.y < mousePointInImg.y ? p.y : mousePointInImg.y),
301 (p.x < mousePointInImg.x ? mousePointInImg.x - p.x : p.x - mousePointInImg.x),
302 (p.y < mousePointInImg.y ? mousePointInImg.y - p.y : p.y - mousePointInImg.y));
303 checkVisibleRectSize(image, rect);
304 checkVisibleRectPos(image, rect);
305 ImageDisplay.this.selectedRect = rect;
306 ImageDisplay.this.repaint();
307 }
308
309 }
310
311 public void mouseReleased(MouseEvent e) {
312 if (! mouseIsDragging && selectedRect == null) {
313 return;
314 }
315
316 File file;
317 Image image;
318 Rectangle visibleRect;
319
320 synchronized (ImageDisplay.this) {
321 file = ImageDisplay.this.file;
322 image = ImageDisplay.this.image;
323 visibleRect = ImageDisplay.this.visibleRect;
324 }
325
326 if (image == null) {
327 mouseIsDragging = false;
328 selectedRect = null;
329 return;
330 }
331
332 if (mouseIsDragging) {
333 mouseIsDragging = false;
334
335 } else if (selectedRect != null) {
336 int oldWidth = selectedRect.width;
337 int oldHeight = selectedRect.height;
338
339 // Check that the zoom doesn't exceed 2:1
340 if (selectedRect.width < getSize().width / 2) {
341 selectedRect.width = getSize().width / 2;
342 }
343 if (selectedRect.height < getSize().height / 2) {
344 selectedRect.height = getSize().height / 2;
345 }
346
347 // Set the same ratio for the visible rectangle and the display area
348 int hFact = selectedRect.height * getSize().width;
349 int wFact = selectedRect.width * getSize().height;
350 if (hFact > wFact) {
351 selectedRect.width = hFact / getSize().height;
352 } else {
353 selectedRect.height = wFact / getSize().width;
354 }
355
356 // Keep the center of the selection
357 if (selectedRect.width != oldWidth) {
358 selectedRect.x -= (selectedRect.width - oldWidth) / 2;
359 }
360 if (selectedRect.height != oldHeight) {
361 selectedRect.y -= (selectedRect.height - oldHeight) / 2;
362 }
363
364 checkVisibleRectSize(image, selectedRect);
365 checkVisibleRectPos(image, selectedRect);
366
367 synchronized (ImageDisplay.this) {
368 if (file == ImageDisplay.this.file) {
369 ImageDisplay.this.visibleRect = selectedRect;
370 }
371 }
372 selectedRect = null;
373 ImageDisplay.this.repaint();
374 }
375 }
376
377 public void mouseEntered(MouseEvent e) {
378 }
379
380 public void mouseExited(MouseEvent e) {
381 }
382
383 public void mouseMoved(MouseEvent e) {
384 }
385
386 private void checkPointInVisibleRect(Point p, Rectangle visibleRect) {
387 if (p.x < visibleRect.x) {
388 p.x = visibleRect.x;
389 }
390 if (p.x > visibleRect.x + visibleRect.width) {
391 p.x = visibleRect.x + visibleRect.width;
392 }
393 if (p.y < visibleRect.y) {
394 p.y = visibleRect.y;
395 }
396 if (p.y > visibleRect.y + visibleRect.height) {
397 p.y = visibleRect.y + visibleRect.height;
398 }
399 }
400 }
401
402 public ImageDisplay() {
403 ImgDisplayMouseListener mouseListener = new ImgDisplayMouseListener();
404 addMouseListener(mouseListener);
405 addMouseWheelListener(mouseListener);
406 addMouseMotionListener(mouseListener);
407 }
408
409 public void setImage(File file) {
410 synchronized(this) {
411 this.file = file;
412 image = null;
413 selectedRect = null;
414 errorLoading = false;
415 }
416 repaint();
417 if (file != null) {
418 new Thread(new LoadImageRunnable(file)).start();
419 }
420 }
421
422 public void setOsdText(String text) {
423 this.osdText = text;
424 }
425
426 public void paintComponent(Graphics g) {
427 Image image;
428 File file;
429 Rectangle visibleRect;
430 boolean errorLoading;
431
432 synchronized(this) {
433 image = this.image;
434 file = this.file;
435 visibleRect = this.visibleRect;
436 errorLoading = this.errorLoading;
437 }
438
439 if (file == null) {
440 g.setColor(Color.black);
441 String noImageStr = tr("No image");
442 Rectangle2D noImageSize = g.getFontMetrics(g.getFont()).getStringBounds(noImageStr, g);
443 Dimension size = getSize();
444 g.drawString(noImageStr,
445 (int) ((size.width - noImageSize.getWidth()) / 2),
446 (int) ((size.height - noImageSize.getHeight()) / 2));
447 } else if (image == null) {
448 g.setColor(Color.black);
449 String loadingStr;
450 if (! errorLoading) {;
451 loadingStr = tr("Loading {0}", file.getName());
452 } else {
453 loadingStr = tr("Error on file {0}", file.getName());
454 }
455 Rectangle2D noImageSize = g.getFontMetrics(g.getFont()).getStringBounds(loadingStr, g);
456 Dimension size = getSize();
457 g.drawString(loadingStr,
458 (int) ((size.width - noImageSize.getWidth()) / 2),
459 (int) ((size.height - noImageSize.getHeight()) / 2));
460 } else {
461 Rectangle target = calculateDrawImageRectangle(visibleRect);
462 g.drawImage(image,
463 target.x, target.y, target.x + target.width, target.y + target.height,
464 visibleRect.x, visibleRect.y, visibleRect.x + visibleRect.width, visibleRect.y + visibleRect.height,
465 null);
466 if (selectedRect != null) {
467 Point topLeft = img2compCoord(visibleRect, selectedRect.x, selectedRect.y);
468 Point bottomRight = img2compCoord(visibleRect,
469 selectedRect.x + selectedRect.width,
470 selectedRect.y + selectedRect.height);
471 g.setColor(new Color(128, 128, 128, 180));
472 g.fillRect(target.x, target.y, target.width, topLeft.y - target.y);
473 g.fillRect(target.x, target.y, topLeft.x - target.x, target.height);
474 g.fillRect(bottomRight.x, target.y, target.x + target.width - bottomRight.x, target.height);
475 g.fillRect(target.x, bottomRight.y, target.width, target.y + target.height - bottomRight.y);
476 g.setColor(Color.black);
477 g.drawRect(topLeft.x, topLeft.y, bottomRight.x - topLeft.x, bottomRight.y - topLeft.y);
478 }
479 if (errorLoading) {
480 String loadingStr = tr("Error on file {0}", file.getName());
481 Rectangle2D noImageSize = g.getFontMetrics(g.getFont()).getStringBounds(loadingStr, g);
482 Dimension size = getSize();
483 g.drawString(loadingStr,
484 (int) ((size.width - noImageSize.getWidth()) / 2),
485 (int) ((size.height - noImageSize.getHeight()) / 2));
486 }
487 if (osdText != null) {
488 FontMetrics metrics = g.getFontMetrics(g.getFont());
489 int ascent = metrics.getAscent();
490 Color bkground = new Color(255, 255, 255, 128);
491 int lastPos = 0;
492 int pos = osdText.indexOf("\n");
493 int x = 3;
494 int y = 3;
495 String line;
496 while (pos > 0) {
497 line = osdText.substring(lastPos, pos);
498 Rectangle2D lineSize = metrics.getStringBounds(line, g);
499 g.setColor(bkground);
500 g.fillRect(x, y, (int) lineSize.getWidth(), (int) lineSize.getHeight());
501 g.setColor(Color.black);
502 g.drawString(line, x, y + ascent);
503 y += (int) lineSize.getHeight();
504 lastPos = pos + 1;
505 pos = osdText.indexOf("\n", lastPos);
506 }
507
508 line = osdText.substring(lastPos);
509 Rectangle2D lineSize = g.getFontMetrics(g.getFont()).getStringBounds(line, g);
510 g.setColor(bkground);
511 g.fillRect(x, y, (int) lineSize.getWidth(), (int) lineSize.getHeight());
512 g.setColor(Color.black);
513 g.drawString(line, x, y + ascent);
514 }
515 }
516 }
517
518 private final Point img2compCoord(Rectangle visibleRect, int xImg, int yImg) {
519 Rectangle drawRect = calculateDrawImageRectangle(visibleRect);
520 return new Point(drawRect.x + ((xImg - visibleRect.x) * drawRect.width) / visibleRect.width,
521 drawRect.y + ((yImg - visibleRect.y) * drawRect.height) / visibleRect.height);
522 }
523
524 private final Point comp2imgCoord(Rectangle visibleRect, int xComp, int yComp) {
525 Rectangle drawRect = calculateDrawImageRectangle(visibleRect);
526 return new Point(visibleRect.x + ((xComp - drawRect.x) * visibleRect.width) / drawRect.width,
527 visibleRect.y + ((yComp - drawRect.y) * visibleRect.height) / drawRect.height);
528 }
529
530 private final Point getCenterImgCoord(Rectangle visibleRect) {
531 return new Point(visibleRect.x + visibleRect.width / 2,
532 visibleRect.y + visibleRect.height / 2);
533 }
534
535 private Rectangle calculateDrawImageRectangle(Rectangle visibleRect) {
536 return calculateDrawImageRectangle(visibleRect, new Rectangle(0, 0, getSize().width, getSize().height));
537 }
538
539 /**
540 * calculateDrawImageRectangle
541 *
542 * @param imgRect the part of the image that should be drawn (in image coordinates)
543 * @param compRect the part of the component where the image should be drawn (in component coordinates)
544 * @return the part of compRect with the same width/height ratio as the image
545 */
546 static Rectangle calculateDrawImageRectangle(Rectangle imgRect, Rectangle compRect) {
547 int x, y, w, h;
548 x = 0;
549 y = 0;
550 w = compRect.width;
551 h = compRect.height;
552
553 int wFact = w * imgRect.height;
554 int hFact = h * imgRect.width;
555 if (wFact != hFact) {
556 if (wFact > hFact) {
557 w = hFact / imgRect.height;
558 x = (compRect.width - w) / 2;
559 } else {
560 h = wFact / imgRect.width;
561 y = (compRect.height - h) / 2;
562 }
563 }
564 return new Rectangle(x + compRect.x, y + compRect.y, w, h);
565 }
566
567 public void zoomBestFitOrOne() {
568 File file;
569 Image image;
570 Rectangle visibleRect;
571
572 synchronized (this) {
573 file = ImageDisplay.this.file;
574 image = ImageDisplay.this.image;
575 visibleRect = ImageDisplay.this.visibleRect;
576 }
577
578 if (image == null) {
579 return;
580 }
581
582 if (visibleRect.width != image.getWidth(null) || visibleRect.height != image.getHeight(null)) {
583 // The display is not at best fit. => Zoom to best fit
584 visibleRect = new Rectangle(0, 0, image.getWidth(null), image.getHeight(null));
585
586 } else {
587 // The display is at best fit => zoom to 1:1
588 Point center = getCenterImgCoord(visibleRect);
589 visibleRect = new Rectangle(center.x - getWidth() / 2, center.y - getHeight() / 2,
590 getWidth(), getHeight());
591 checkVisibleRectPos(image, visibleRect);
592 }
593
594 synchronized(this) {
595 if (file == this.file) {
596 this.visibleRect = visibleRect;
597 }
598 }
599 repaint();
600 }
601
602 private final void checkVisibleRectPos(Image image, Rectangle visibleRect) {
603 if (visibleRect.x < 0) {
604 visibleRect.x = 0;
605 }
606 if (visibleRect.y < 0) {
607 visibleRect.y = 0;
608 }
609 if (visibleRect.x + visibleRect.width > image.getWidth(null)) {
610 visibleRect.x = image.getWidth(null) - visibleRect.width;
611 }
612 if (visibleRect.y + visibleRect.height > image.getHeight(null)) {
613 visibleRect.y = image.getHeight(null) - visibleRect.height;
614 }
615 }
616
617 private void checkVisibleRectSize(Image image, Rectangle visibleRect) {
618 if (visibleRect.width > image.getWidth(null)) {
619 visibleRect.width = image.getWidth(null);
620 }
621 if (visibleRect.height > image.getHeight(null)) {
622 visibleRect.height = image.getHeight(null);
623 }
624 }
625}
Note: See TracBrowser for help on using the repository browser.