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

Last change on this file since 6127 was 6127, checked in by bastiK, 11 years ago

applied #8895 - Upgrade metadata-extractor to v. 2.6.4 (patch by ebourg)

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