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

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

Sonar/FindBugs - Loose coupling

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