source: josm/trunk/src/org/openstreetmap/josm/gui/layer/geoimage/GeoImageLayer.java@ 4010

Last change on this file since 4010 was 4010, checked in by bastiK, 13 years ago

fixed #6162 - Error: NaN in greatCircleDistance for some images (patch by m.zdila)

  • Property svn:eol-style set to native
File size: 28.7 KB
Line 
1// License: GPL. See LICENSE file for details.
2// Copyright 2007 by Christian Gallioz (aka khris78)
3// Parts of code from Geotagged plugin (by Rob Neild)
4// and the core JOSM source code (by Immanuel Scholz and others)
5package org.openstreetmap.josm.gui.layer.geoimage;
6
7import static org.openstreetmap.josm.tools.I18n.tr;
8import static org.openstreetmap.josm.tools.I18n.trn;
9
10import java.awt.AlphaComposite;
11import java.awt.Color;
12import java.awt.Composite;
13import java.awt.Dimension;
14import java.awt.Graphics2D;
15import java.awt.Image;
16import java.awt.Point;
17import java.awt.Rectangle;
18import java.awt.event.MouseAdapter;
19import java.awt.event.MouseEvent;
20import java.awt.image.BufferedImage;
21import java.beans.PropertyChangeEvent;
22import java.beans.PropertyChangeListener;
23import java.io.File;
24import java.io.IOException;
25import java.text.ParseException;
26import java.util.ArrayList;
27import java.util.Arrays;
28import java.util.Collection;
29import java.util.Collections;
30import java.util.HashSet;
31import java.util.LinkedHashSet;
32import java.util.LinkedList;
33import java.util.List;
34
35import javax.swing.Action;
36import javax.swing.Icon;
37import javax.swing.JLabel;
38import javax.swing.JOptionPane;
39import javax.swing.SwingConstants;
40
41import org.openstreetmap.josm.Main;
42import org.openstreetmap.josm.actions.RenameLayerAction;
43import org.openstreetmap.josm.actions.mapmode.MapMode;
44import org.openstreetmap.josm.data.Bounds;
45import org.openstreetmap.josm.data.coor.LatLon;
46import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
47import org.openstreetmap.josm.gui.ExtendedDialog;
48import org.openstreetmap.josm.gui.MapFrame;
49import org.openstreetmap.josm.gui.MapFrame.MapModeChangeListener;
50import org.openstreetmap.josm.gui.MapView;
51import org.openstreetmap.josm.gui.MapView.LayerChangeListener;
52import org.openstreetmap.josm.gui.PleaseWaitRunnable;
53import org.openstreetmap.josm.gui.dialogs.LayerListDialog;
54import org.openstreetmap.josm.gui.dialogs.LayerListPopup;
55import org.openstreetmap.josm.gui.layer.GpxLayer;
56import org.openstreetmap.josm.gui.layer.Layer;
57import org.openstreetmap.josm.tools.ExifReader;
58import org.openstreetmap.josm.tools.ImageProvider;
59
60import com.drew.imaging.jpeg.JpegMetadataReader;
61import com.drew.lang.CompoundException;
62import com.drew.lang.Rational;
63import com.drew.metadata.Directory;
64import com.drew.metadata.Metadata;
65import com.drew.metadata.exif.GpsDirectory;
66
67public class GeoImageLayer extends Layer implements PropertyChangeListener {
68
69 List<ImageEntry> data;
70 GpxLayer gpxLayer;
71
72 private Icon icon = ImageProvider.get("dialogs/geoimage/photo-marker");
73 private Icon selectedIcon = ImageProvider.get("dialogs/geoimage/photo-marker-selected");
74
75 private int currentPhoto = -1;
76
77 boolean useThumbs = false;
78 ThumbsLoader thumbsloader;
79 boolean thumbsLoaded = false;
80 private BufferedImage offscreenBuffer;
81 boolean updateOffscreenBuffer = true;
82
83 /** Loads a set of images, while displaying a dialog that indicates what the plugin is currently doing.
84 * In facts, this object is instantiated with a list of files. These files may be JPEG files or
85 * directories. In case of directories, they are scanned to find all the images they contain.
86 * Then all the images that have be found are loaded as ImageEntry instances.
87 */
88 private static final class Loader extends PleaseWaitRunnable {
89
90 private boolean cancelled = false;
91 private GeoImageLayer layer;
92 private Collection<File> selection;
93 private HashSet<String> loadedDirectories = new HashSet<String>();
94 private LinkedHashSet<String> errorMessages;
95 private GpxLayer gpxLayer;
96
97 protected void rememberError(String message) {
98 this.errorMessages.add(message);
99 }
100
101 public Loader(Collection<File> selection, GpxLayer gpxLayer) {
102 super(tr("Extracting GPS locations from EXIF"));
103 this.selection = selection;
104 this.gpxLayer = gpxLayer;
105 errorMessages = new LinkedHashSet<String>();
106 }
107
108 @Override protected void realRun() throws IOException {
109
110 progressMonitor.subTask(tr("Starting directory scan"));
111 Collection<File> files = new ArrayList<File>();
112 try {
113 addRecursiveFiles(files, selection);
114 } catch(NullPointerException npe) {
115 rememberError(tr("One of the selected files was null"));
116 }
117
118 if (cancelled)
119 return;
120 progressMonitor.subTask(tr("Read photos..."));
121 progressMonitor.setTicksCount(files.size());
122
123 progressMonitor.subTask(tr("Read photos..."));
124 progressMonitor.setTicksCount(files.size());
125
126 // read the image files
127 List<ImageEntry> data = new ArrayList<ImageEntry>(files.size());
128
129 for (File f : files) {
130
131 if (cancelled) {
132 break;
133 }
134
135 progressMonitor.subTask(tr("Reading {0}...", f.getName()));
136 progressMonitor.worked(1);
137
138 ImageEntry e = new ImageEntry();
139
140 // Changed to silently cope with no time info in exif. One case
141 // of person having time that couldn't be parsed, but valid GPS info
142
143 try {
144 e.setExifTime(ExifReader.readTime(f));
145 } catch (ParseException e1) {
146 e.setExifTime(null);
147 }
148 e.setFile(f);
149 extractExif(e);
150 data.add(e);
151 }
152 layer = new GeoImageLayer(data, gpxLayer);
153 files.clear();
154 }
155
156 private void addRecursiveFiles(Collection<File> files, Collection<File> sel) {
157 boolean nullFile = false;
158
159 for (File f : sel) {
160
161 if(cancelled) {
162 break;
163 }
164
165 if (f == null) {
166 nullFile = true;
167
168 } else if (f.isDirectory()) {
169 String canonical = null;
170 try {
171 canonical = f.getCanonicalPath();
172 } catch (IOException e) {
173 e.printStackTrace();
174 rememberError(tr("Unable to get canonical path for directory {0}\n",
175 f.getAbsolutePath()));
176 }
177
178 if (canonical == null || loadedDirectories.contains(canonical)) {
179 continue;
180 } else {
181 loadedDirectories.add(canonical);
182 }
183
184 Collection<File> children = Arrays.asList(f.listFiles(JpegFileFilter.getInstance()));
185 if (children != null) {
186 progressMonitor.subTask(tr("Scanning directory {0}", f.getPath()));
187 try {
188 addRecursiveFiles(files, children);
189 } catch(NullPointerException npe) {
190 npe.printStackTrace();
191 rememberError(tr("Found null file in directory {0}\n", f.getPath()));
192 }
193 } else {
194 rememberError(tr("Error while getting files from directory {0}\n", f.getPath()));
195 }
196
197 } else {
198 files.add(f);
199 }
200 }
201
202 if (nullFile)
203 throw new NullPointerException();
204 }
205
206 protected String formatErrorMessages() {
207 StringBuilder sb = new StringBuilder();
208 sb.append("<html>");
209 if (errorMessages.size() == 1) {
210 sb.append(errorMessages.iterator().next());
211 } else {
212 sb.append("<ul>");
213 for (String msg: errorMessages) {
214 sb.append("<li>").append(msg).append("</li>");
215 }
216 sb.append("/ul>");
217 }
218 sb.append("</html>");
219 return sb.toString();
220 }
221
222 @Override protected void finish() {
223 if (!errorMessages.isEmpty()) {
224 JOptionPane.showMessageDialog(
225 Main.parent,
226 formatErrorMessages(),
227 tr("Error"),
228 JOptionPane.ERROR_MESSAGE
229 );
230 }
231 if (layer != null) {
232 Main.main.addLayer(layer);
233 layer.hook_up_mouse_events(); // Main.map.mapView should exist
234 // now. Can add mouse listener
235 Main.map.mapView.addPropertyChangeListener(layer);
236 if (Main.map.getToggleDialog(ImageViewerDialog.class) == null) {
237 ImageViewerDialog.newInstance();
238 Main.map.addToggleDialog(ImageViewerDialog.getInstance());
239 }
240
241 if (! cancelled && layer.data.size() > 0) {
242 boolean noGeotagFound = true;
243 for (ImageEntry e : layer.data) {
244 if (e.getPos() != null) {
245 noGeotagFound = false;
246 }
247 }
248 if (noGeotagFound) {
249 new CorrelateGpxWithImages(layer).actionPerformed(null);
250 }
251 }
252 }
253 }
254
255 @Override protected void cancel() {
256 cancelled = true;
257 }
258 }
259
260 public static void create(Collection<File> files, GpxLayer gpxLayer) {
261 Loader loader = new Loader(files, gpxLayer);
262 Main.worker.execute(loader);
263 }
264
265 private GeoImageLayer(final List<ImageEntry> data, GpxLayer gpxLayer) {
266
267 super(tr("Geotagged Images"));
268
269 Collections.sort(data);
270 this.data = data;
271 this.gpxLayer = gpxLayer;
272 }
273
274 @Override
275 public Icon getIcon() {
276 return ImageProvider.get("dialogs/geoimage");
277 }
278
279 private static List<Action> menuAdditions = new LinkedList<Action>();
280 public static void registerMenuAddition(Action addition) {
281 menuAdditions.add(addition);
282 }
283
284 @Override
285 public Action[] getMenuEntries() {
286
287 List<Action> entries = new ArrayList<Action>();
288 entries.add(LayerListDialog.getInstance().createShowHideLayerAction());
289 entries.add(LayerListDialog.getInstance().createDeleteLayerAction());
290 entries.add(new RenameLayerAction(null, this));
291 entries.add(SeparatorLayerAction.INSTANCE);
292 entries.add(new CorrelateGpxWithImages(this));
293 if (!menuAdditions.isEmpty()) {
294 entries.add(SeparatorLayerAction.INSTANCE);
295 entries.addAll(menuAdditions);
296 }
297 entries.add(SeparatorLayerAction.INSTANCE);
298 entries.add(new LayerListPopup.InfoAction(this));
299
300 return entries.toArray(new Action[0]);
301
302 }
303
304 private String infoText() {
305 int i = 0;
306 for (ImageEntry e : data)
307 if (e.getPos() != null) {
308 i++;
309 }
310 return trn("{0} image loaded.", "{0} images loaded.", data.size(), data.size())
311 + " " + trn("{0} was found to be GPS tagged.", "{0} were found to be GPS tagged.", i, i);
312 }
313
314 @Override public Object getInfoComponent() {
315 return infoText();
316 }
317
318 @Override
319 public String getToolTipText() {
320 return infoText();
321 }
322
323 @Override
324 public boolean isMergable(Layer other) {
325 return other instanceof GeoImageLayer;
326 }
327
328 @Override
329 public void mergeFrom(Layer from) {
330 GeoImageLayer l = (GeoImageLayer) from;
331
332 ImageEntry selected = null;
333 if (l.currentPhoto >= 0) {
334 selected = l.data.get(l.currentPhoto);
335 }
336
337 data.addAll(l.data);
338 Collections.sort(data);
339
340 // Supress the double photos.
341 if (data.size() > 1) {
342 ImageEntry cur;
343 ImageEntry prev = data.get(data.size() - 1);
344 for (int i = data.size() - 2; i >= 0; i--) {
345 cur = data.get(i);
346 if (cur.getFile().equals(prev.getFile())) {
347 data.remove(i);
348 } else {
349 prev = cur;
350 }
351 }
352 }
353
354 if (selected != null) {
355 for (int i = 0; i < data.size() ; i++) {
356 if (data.get(i) == selected) {
357 currentPhoto = i;
358 ImageViewerDialog.showImage(GeoImageLayer.this, data.get(i));
359 break;
360 }
361 }
362 }
363
364 setName(l.getName());
365 }
366
367 private Dimension scaledDimension(Image thumb) {
368 final double d = Main.map.mapView.getDist100Pixel();
369 final double size = 10 /*meter*/; /* size of the photo on the map */
370 double s = size * 100 /*px*/ / d;
371
372 final double sMin = ThumbsLoader.minSize;
373 final double sMax = ThumbsLoader.maxSize;
374
375 if (s < sMin) {
376 s = sMin;
377 }
378 if (s > sMax) {
379 s = sMax;
380 }
381 final double f = s / sMax; /* scale factor */
382
383 if (thumb == null)
384 return null;
385
386 return new Dimension(
387 (int) Math.round(f * thumb.getWidth(null)),
388 (int) Math.round(f * thumb.getHeight(null)));
389 }
390
391 @Override
392 public void paint(Graphics2D g, MapView mv, Bounds bounds) {
393 int width = Main.map.mapView.getWidth();
394 int height = Main.map.mapView.getHeight();
395 Rectangle clip = g.getClipBounds();
396 if (useThumbs) {
397 if (null == offscreenBuffer || offscreenBuffer.getWidth() != width // reuse the old buffer if possible
398 || offscreenBuffer.getHeight() != height) {
399 offscreenBuffer = new BufferedImage(width, height,
400 BufferedImage.TYPE_INT_ARGB);
401 updateOffscreenBuffer = true;
402 }
403
404 if (updateOffscreenBuffer) {
405 Graphics2D tempG = offscreenBuffer.createGraphics();
406 tempG.setColor(new Color(0,0,0,0));
407 Composite saveComp = tempG.getComposite();
408 tempG.setComposite(AlphaComposite.Clear); // remove the old images
409 tempG.fillRect(0, 0, width, height);
410 tempG.setComposite(saveComp);
411
412 for (ImageEntry e : data) {
413 if (e.getPos() == null) {
414 continue;
415 }
416 Point p = mv.getPoint(e.getPos());
417 if (e.thumbnail != null) {
418 Dimension d = scaledDimension(e.thumbnail);
419 Rectangle target = new Rectangle(p.x - d.width / 2, p.y - d.height / 2, d.width, d.height);
420 if (clip.intersects(target)) {
421 tempG.drawImage(e.thumbnail, target.x, target.y, target.width, target.height, null);
422 }
423 }
424 else { // thumbnail not loaded yet
425 icon.paintIcon(mv, tempG,
426 p.x - icon.getIconWidth() / 2,
427 p.y - icon.getIconHeight() / 2);
428 }
429 }
430 updateOffscreenBuffer = false;
431 }
432 g.drawImage(offscreenBuffer, 0, 0, null);
433 }
434 else {
435 for (ImageEntry e : data) {
436 if (e.getPos() == null) {
437 continue;
438 }
439 Point p = mv.getPoint(e.getPos());
440 icon.paintIcon(mv, g,
441 p.x - icon.getIconWidth() / 2,
442 p.y - icon.getIconHeight() / 2);
443 }
444 }
445
446 if (currentPhoto >= 0 && currentPhoto < data.size()) {
447 ImageEntry e = data.get(currentPhoto);
448
449 if (e.getPos() != null) {
450 Point p = mv.getPoint(e.getPos());
451
452 if (e.thumbnail != null) {
453 Dimension d = scaledDimension(e.thumbnail);
454 g.setColor(new Color(128, 0, 0, 122));
455 g.fillRect(p.x - d.width / 2, p.y - d.height / 2, d.width, d.height);
456 } else {
457 if (e.getExifImgDir() != null) {
458 double arrowlength = 25;
459 double arrowwidth = 18;
460
461 double dir = e.getExifImgDir();
462 // Rotate 90 degrees CCW
463 double headdir = ( dir < 90 ) ? dir + 270 : dir - 90;
464 double leftdir = ( headdir < 90 ) ? headdir + 270 : headdir - 90;
465 double rightdir = ( headdir > 270 ) ? headdir - 270 : headdir + 90;
466
467 double ptx = p.x + Math.cos(Math.toRadians(headdir)) * arrowlength;
468 double pty = p.y + Math.sin(Math.toRadians(headdir)) * arrowlength;
469
470 double ltx = p.x + Math.cos(Math.toRadians(leftdir)) * arrowwidth/2;
471 double lty = p.y + Math.sin(Math.toRadians(leftdir)) * arrowwidth/2;
472
473 double rtx = p.x + Math.cos(Math.toRadians(rightdir)) * arrowwidth/2;
474 double rty = p.y + Math.sin(Math.toRadians(rightdir)) * arrowwidth/2;
475
476 g.setColor(Color.white);
477 int[] xar = {(int) ltx, (int) ptx, (int) rtx, (int) ltx};
478 int[] yar = {(int) lty, (int) pty, (int) rty, (int) lty};
479 g.fillPolygon(xar, yar, 4);
480 }
481
482 selectedIcon.paintIcon(mv, g,
483 p.x - selectedIcon.getIconWidth() / 2,
484 p.y - selectedIcon.getIconHeight() / 2);
485
486 }
487 }
488 }
489 }
490
491 @Override
492 public void visitBoundingBox(BoundingXYVisitor v) {
493 for (ImageEntry e : data) {
494 v.visit(e.getPos());
495 }
496 }
497
498 /*
499 * Extract gps from image exif
500 *
501 * If successful, fills in the LatLon and EastNorth attributes of passed in
502 * image;
503 */
504
505 private static void extractExif(ImageEntry e) {
506
507 double deg;
508 double min, sec;
509 double lon, lat;
510 Metadata metadata = null;
511 Directory dir = null;
512
513 try {
514 metadata = JpegMetadataReader.readMetadata(e.getFile());
515 dir = metadata.getDirectory(GpsDirectory.class);
516 } catch (CompoundException p) {
517 e.setExifCoor(null);
518 e.setPos(null);
519 return;
520 }
521
522 try {
523 // longitude
524
525 Rational[] components = dir.getRationalArray(GpsDirectory.TAG_GPS_LONGITUDE);
526
527 deg = components[0].doubleValue();
528 min = components[1].doubleValue();
529 sec = components[2].doubleValue();
530
531 if (Double.isNaN(deg) && Double.isNaN(min) && Double.isNaN(sec))
532 throw new IllegalArgumentException();
533
534 lon = (Double.isNaN(deg) ? 0 : deg + (Double.isNaN(min) ? 0 : (min / 60)) + (Double.isNaN(sec) ? 0 : (sec / 3600)));
535
536 if (dir.getString(GpsDirectory.TAG_GPS_LONGITUDE_REF).charAt(0) == 'W') {
537 lon = -lon;
538 }
539
540 // latitude
541
542 components = dir.getRationalArray(GpsDirectory.TAG_GPS_LATITUDE);
543
544 deg = components[0].doubleValue();
545 min = components[1].doubleValue();
546 sec = components[2].doubleValue();
547
548 if (Double.isNaN(deg) && Double.isNaN(min) && Double.isNaN(sec))
549 throw new IllegalArgumentException();
550
551 lat = (Double.isNaN(deg) ? 0 : deg + (Double.isNaN(min) ? 0 : (min / 60)) + (Double.isNaN(sec) ? 0 : (sec / 3600)));
552
553 if (Double.isNaN(lat))
554 throw new IllegalArgumentException();
555
556 if (dir.getString(GpsDirectory.TAG_GPS_LATITUDE_REF).charAt(0) == 'S') {
557 lat = -lat;
558 }
559
560 // Store values
561
562 e.setExifCoor(new LatLon(lat, lon));
563 e.setPos(e.getExifCoor());
564
565 } catch (CompoundException p) {
566 // Try to read lon/lat as double value (Nonstandard, created by some cameras -> #5220)
567 try {
568 Double longitude = dir.getDouble(GpsDirectory.TAG_GPS_LONGITUDE);
569 Double latitude = dir.getDouble(GpsDirectory.TAG_GPS_LATITUDE);
570 if (longitude == null || latitude == null)
571 throw new CompoundException("");
572
573 // Store values
574
575 e.setExifCoor(new LatLon(latitude, longitude));
576 e.setPos(e.getExifCoor());
577 } catch (CompoundException ex) {
578 e.setExifCoor(null);
579 e.setPos(null);
580 }
581 } catch (Exception ex) { // (other exceptions, e.g. #5271)
582 System.err.println("Error when reading EXIF from file: "+ex);
583 e.setExifCoor(null);
584 e.setPos(null);
585 }
586
587 // compass direction value
588
589 Rational direction = null;
590
591 try {
592 direction = dir.getRational(GpsDirectory.TAG_GPS_IMG_DIRECTION);
593 if (direction != null) {
594 e.setExifImgDir(direction.doubleValue());
595 }
596 } catch (Exception ex) { // (CompoundException and other exceptions, e.g. #5271)
597 // Do nothing
598 }
599 }
600
601 public void showNextPhoto() {
602 if (data != null && data.size() > 0) {
603 currentPhoto++;
604 if (currentPhoto >= data.size()) {
605 currentPhoto = data.size() - 1;
606 }
607 ImageViewerDialog.showImage(this, data.get(currentPhoto));
608 } else {
609 currentPhoto = -1;
610 }
611 Main.map.repaint();
612 }
613
614 public void showPreviousPhoto() {
615 if (data != null && data.size() > 0) {
616 currentPhoto--;
617 if (currentPhoto < 0) {
618 currentPhoto = 0;
619 }
620 ImageViewerDialog.showImage(this, data.get(currentPhoto));
621 } else {
622 currentPhoto = -1;
623 }
624 Main.map.repaint();
625 }
626
627 public void checkPreviousNextButtons() {
628 ImageViewerDialog.setNextEnabled(currentPhoto < data.size() - 1);
629 ImageViewerDialog.setPreviousEnabled(currentPhoto > 0);
630 }
631
632 public void removeCurrentPhoto() {
633 if (data != null && data.size() > 0 && currentPhoto >= 0 && currentPhoto < data.size()) {
634 data.remove(currentPhoto);
635 if (currentPhoto >= data.size()) {
636 currentPhoto = data.size() - 1;
637 }
638 if (currentPhoto >= 0) {
639 ImageViewerDialog.showImage(this, data.get(currentPhoto));
640 } else {
641 ImageViewerDialog.showImage(this, null);
642 }
643 updateOffscreenBuffer = true;
644 Main.map.repaint();
645 }
646 }
647
648 public void removeCurrentPhotoFromDisk() {
649 ImageEntry toDelete = null;
650 if (data != null && data.size() > 0 && currentPhoto >= 0 && currentPhoto < data.size()) {
651 toDelete = data.get(currentPhoto);
652
653 int result = new ExtendedDialog(
654 Main.parent,
655 tr("Delete image file from disk"),
656 new String[] {tr("Cancel"), tr("Delete")})
657 .setButtonIcons(new String[] {"cancel.png", "dialogs/delete.png"})
658 .setContent(new JLabel(tr("<html><h3>Delete the file {0} from disk?<p>The image file will be permanently lost!</h3></html>"
659 ,toDelete.getFile().getName()), ImageProvider.get("dialogs/geoimage/deletefromdisk"),SwingConstants.LEFT))
660 .toggleEnable("geoimage.deleteimagefromdisk")
661 .setCancelButton(1)
662 .setDefaultButton(2)
663 .showDialog()
664 .getValue();
665
666 if(result == 2)
667 {
668 data.remove(currentPhoto);
669 if (currentPhoto >= data.size()) {
670 currentPhoto = data.size() - 1;
671 }
672 if (currentPhoto >= 0) {
673 ImageViewerDialog.showImage(this, data.get(currentPhoto));
674 } else {
675 ImageViewerDialog.showImage(this, null);
676 }
677
678 if (toDelete.getFile().delete()) {
679 System.out.println("File "+toDelete.getFile().toString()+" deleted. ");
680 } else {
681 JOptionPane.showMessageDialog(
682 Main.parent,
683 tr("Image file could not be deleted."),
684 tr("Error"),
685 JOptionPane.ERROR_MESSAGE
686 );
687 }
688
689 updateOffscreenBuffer = true;
690 Main.map.repaint();
691 }
692 }
693 }
694
695 private MouseAdapter mouseAdapter = null;
696 private MapModeChangeListener mapModeListener = null;
697
698 private void hook_up_mouse_events() {
699 mouseAdapter = new MouseAdapter() {
700 @Override public void mousePressed(MouseEvent e) {
701
702 if (e.getButton() != MouseEvent.BUTTON1)
703 return;
704 if (isVisible()) {
705 Main.map.mapView.repaint();
706 }
707 }
708
709 @Override public void mouseReleased(MouseEvent ev) {
710 if (ev.getButton() != MouseEvent.BUTTON1)
711 return;
712 if (!isVisible())
713 return;
714
715 for (int i = data.size() - 1; i >= 0; --i) {
716 ImageEntry e = data.get(i);
717 if (e.getPos() == null) {
718 continue;
719 }
720 Point p = Main.map.mapView.getPoint(e.getPos());
721 Rectangle r;
722 if (e.thumbnail != null) {
723 Dimension d = scaledDimension(e.thumbnail);
724 r = new Rectangle(p.x - d.width / 2, p.y - d.height / 2, d.width, d.height);
725 } else {
726 r = new Rectangle(p.x - icon.getIconWidth() / 2,
727 p.y - icon.getIconHeight() / 2,
728 icon.getIconWidth(),
729 icon.getIconHeight());
730 }
731 if (r.contains(ev.getPoint())) {
732 currentPhoto = i;
733 ImageViewerDialog.showImage(GeoImageLayer.this, e);
734 Main.map.repaint();
735 break;
736 }
737 }
738 }
739 };
740
741 mapModeListener = new MapModeChangeListener() {
742 public void mapModeChange(MapMode oldMapMode, MapMode newMapMode) {
743 if (newMapMode instanceof org.openstreetmap.josm.actions.mapmode.SelectAction) {
744 Main.map.mapView.addMouseListener(mouseAdapter);
745 } else {
746 Main.map.mapView.removeMouseListener(mouseAdapter);
747 }
748 }
749 };
750
751 MapFrame.addMapModeChangeListener(mapModeListener);
752 mapModeListener.mapModeChange(null, Main.map.mapMode);
753
754 MapView.addLayerChangeListener(new LayerChangeListener() {
755 public void activeLayerChange(Layer oldLayer, Layer newLayer) {
756 if (newLayer == GeoImageLayer.this) {
757 // only in select mode it is possible to click the images
758 Main.map.selectSelectTool(false);
759 }
760 }
761
762 public void layerAdded(Layer newLayer) {
763 }
764
765 public void layerRemoved(Layer oldLayer) {
766 if (oldLayer == GeoImageLayer.this) {
767 if (thumbsloader != null) {
768 thumbsloader.stop = true;
769 }
770 Main.map.mapView.removeMouseListener(mouseAdapter);
771 MapFrame.removeMapModeChangeListener(mapModeListener);
772 currentPhoto = -1;
773 data.clear();
774 data = null;
775 // stop listening to layer change events
776 MapView.removeLayerChangeListener(this);
777 }
778 }
779 });
780 }
781
782 public void propertyChange(PropertyChangeEvent evt) {
783 if ("center".equals(evt.getPropertyName()) || "scale".equals(evt.getPropertyName())) {
784 updateOffscreenBuffer = true;
785 }
786 }
787
788 public void loadThumbs() {
789 if (useThumbs && !thumbsLoaded) {
790 thumbsLoaded = true;
791 thumbsloader = new ThumbsLoader(this);
792 Thread t = new Thread(thumbsloader);
793 t.setPriority(Thread.MIN_PRIORITY);
794 t.start();
795 }
796 }
797
798 public void updateBufferAndRepaint() {
799 updateOffscreenBuffer = true;
800 Main.map.mapView.repaint();
801 }
802
803 public List<ImageEntry> getImages() {
804 List<ImageEntry> copy = new ArrayList<ImageEntry>();
805 for (ImageEntry ie : data) {
806 copy.add(ie.clone());
807 }
808 return copy;
809 }
810}
Note: See TracBrowser for help on using the repository browser.