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

Last change on this file since 6296 was 6296, checked in by Don-vip, 11 years ago

Sonar/Findbugs - Avoid commented-out lines of code, javadoc

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