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

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

Sonar/Findbugs - Avoid Throwing Null Pointer Exception

  • 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.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.metadata.Directory;
68import com.drew.metadata.Metadata;
69import com.drew.metadata.MetadataException;
70import com.drew.metadata.exif.ExifIFD0Directory;
71import com.drew.metadata.exif.GpsDirectory;
72
73/**
74 * Layer displaying geottaged pictures.
75 */
76public class GeoImageLayer extends Layer implements PropertyChangeListener, JumpToMarkerLayer {
77
78 List<ImageEntry> data;
79 GpxLayer gpxLayer;
80
81 private Icon icon = ImageProvider.get("dialogs/geoimage/photo-marker");
82 private Icon selectedIcon = ImageProvider.get("dialogs/geoimage/photo-marker-selected");
83
84 private int currentPhoto = -1;
85
86 boolean useThumbs = false;
87 ThumbsLoader thumbsloader;
88 boolean thumbsLoaded = false;
89 private BufferedImage offscreenBuffer;
90 boolean updateOffscreenBuffer = true;
91
92 /** Loads a set of images, while displaying a dialog that indicates what the plugin is currently doing.
93 * In facts, this object is instantiated with a list of files. These files may be JPEG files or
94 * directories. In case of directories, they are scanned to find all the images they contain.
95 * Then all the images that have be found are loaded as ImageEntry instances.
96 */
97 private static final class Loader extends PleaseWaitRunnable {
98
99 private boolean canceled = false;
100 private GeoImageLayer layer;
101 private Collection<File> selection;
102 private HashSet<String> loadedDirectories = new HashSet<String>();
103 private LinkedHashSet<String> errorMessages;
104 private GpxLayer gpxLayer;
105
106 protected void rememberError(String message) {
107 this.errorMessages.add(message);
108 }
109
110 public Loader(Collection<File> selection, GpxLayer gpxLayer) {
111 super(tr("Extracting GPS locations from EXIF"));
112 this.selection = selection;
113 this.gpxLayer = gpxLayer;
114 errorMessages = new LinkedHashSet<String>();
115 }
116
117 @Override protected void realRun() throws IOException {
118
119 progressMonitor.subTask(tr("Starting directory scan"));
120 Collection<File> files = new ArrayList<File>();
121 try {
122 addRecursiveFiles(files, selection);
123 } catch (IllegalStateException e) {
124 rememberError(e.getMessage());
125 }
126
127 if (canceled)
128 return;
129 progressMonitor.subTask(tr("Read photos..."));
130 progressMonitor.setTicksCount(files.size());
131
132 progressMonitor.subTask(tr("Read photos..."));
133 progressMonitor.setTicksCount(files.size());
134
135 // read the image files
136 List<ImageEntry> data = new ArrayList<ImageEntry>(files.size());
137
138 for (File f : files) {
139
140 if (canceled) {
141 break;
142 }
143
144 progressMonitor.subTask(tr("Reading {0}...", f.getName()));
145 progressMonitor.worked(1);
146
147 ImageEntry e = new ImageEntry();
148
149 // Changed to silently cope with no time info in exif. One case
150 // of person having time that couldn't be parsed, but valid GPS info
151
152 try {
153 e.setExifTime(ExifReader.readTime(f));
154 } catch (ParseException e1) {
155 e.setExifTime(null);
156 }
157 e.setFile(f);
158 extractExif(e);
159 data.add(e);
160 }
161 layer = new GeoImageLayer(data, gpxLayer);
162 files.clear();
163 }
164
165 private void addRecursiveFiles(Collection<File> files, Collection<File> sel) {
166 boolean nullFile = false;
167
168 for (File f : sel) {
169
170 if(canceled) {
171 break;
172 }
173
174 if (f == null) {
175 nullFile = true;
176
177 } else if (f.isDirectory()) {
178 String canonical = null;
179 try {
180 canonical = f.getCanonicalPath();
181 } catch (IOException e) {
182 e.printStackTrace();
183 rememberError(tr("Unable to get canonical path for directory {0}\n",
184 f.getAbsolutePath()));
185 }
186
187 if (canonical == null || loadedDirectories.contains(canonical)) {
188 continue;
189 } else {
190 loadedDirectories.add(canonical);
191 }
192
193 File[] children = f.listFiles(JpegFileFilter.getInstance());
194 if (children != null) {
195 progressMonitor.subTask(tr("Scanning directory {0}", f.getPath()));
196 try {
197 addRecursiveFiles(files, Arrays.asList(children));
198 } catch(NullPointerException npe) {
199 npe.printStackTrace();
200 rememberError(tr("Found null file in directory {0}\n", f.getPath()));
201 }
202 } else {
203 rememberError(tr("Error while getting files from directory {0}\n", f.getPath()));
204 }
205
206 } else {
207 files.add(f);
208 }
209 }
210
211 if (nullFile) {
212 throw new IllegalStateException(tr("One of the selected files was null"));
213 }
214 }
215
216 protected String formatErrorMessages() {
217 StringBuilder sb = new StringBuilder();
218 sb.append("<html>");
219 if (errorMessages.size() == 1) {
220 sb.append(errorMessages.iterator().next());
221 } else {
222 sb.append("<ul>");
223 for (String msg: errorMessages) {
224 sb.append("<li>").append(msg).append("</li>");
225 }
226 sb.append("/ul>");
227 }
228 sb.append("</html>");
229 return sb.toString();
230 }
231
232 @Override protected void finish() {
233 if (!errorMessages.isEmpty()) {
234 JOptionPane.showMessageDialog(
235 Main.parent,
236 formatErrorMessages(),
237 tr("Error"),
238 JOptionPane.ERROR_MESSAGE
239 );
240 }
241 if (layer != null) {
242 Main.main.addLayer(layer);
243
244 if (!canceled && !layer.data.isEmpty()) {
245 boolean noGeotagFound = true;
246 for (ImageEntry e : layer.data) {
247 if (e.getPos() != null) {
248 noGeotagFound = false;
249 }
250 }
251 if (noGeotagFound) {
252 new CorrelateGpxWithImages(layer).actionPerformed(null);
253 }
254 }
255 }
256 }
257
258 @Override protected void cancel() {
259 canceled = true;
260 }
261 }
262
263 public static void create(Collection<File> files, GpxLayer gpxLayer) {
264 Loader loader = new Loader(files, gpxLayer);
265 Main.worker.execute(loader);
266 }
267
268 /**
269 * Constructs a new {@code GeoImageLayer}.
270 * @param data The list of images to display
271 * @param gpxLayer The associated GPX layer
272 */
273 public GeoImageLayer(final List<ImageEntry> data, GpxLayer gpxLayer) {
274 super(tr("Geotagged Images"));
275
276 Collections.sort(data);
277 this.data = data;
278 this.gpxLayer = gpxLayer;
279 }
280
281 @Override
282 public Icon getIcon() {
283 return ImageProvider.get("dialogs/geoimage");
284 }
285
286 private static List<Action> menuAdditions = new LinkedList<Action>();
287 public static void registerMenuAddition(Action addition) {
288 menuAdditions.add(addition);
289 }
290
291 @Override
292 public Action[] getMenuEntries() {
293
294 List<Action> entries = new ArrayList<Action>();
295 entries.add(LayerListDialog.getInstance().createShowHideLayerAction());
296 entries.add(LayerListDialog.getInstance().createDeleteLayerAction());
297 entries.add(new RenameLayerAction(null, this));
298 entries.add(SeparatorLayerAction.INSTANCE);
299 entries.add(new CorrelateGpxWithImages(this));
300 if (!menuAdditions.isEmpty()) {
301 entries.add(SeparatorLayerAction.INSTANCE);
302 entries.addAll(menuAdditions);
303 }
304 entries.add(SeparatorLayerAction.INSTANCE);
305 entries.add(new JumpToNextMarker(this));
306 entries.add(new JumpToPreviousMarker(this));
307 entries.add(SeparatorLayerAction.INSTANCE);
308 entries.add(new LayerListPopup.InfoAction(this));
309
310 return entries.toArray(new Action[entries.size()]);
311
312 }
313
314 private String infoText() {
315 int i = 0;
316 for (ImageEntry e : data)
317 if (e.getPos() != null) {
318 i++;
319 }
320 return trn("{0} image loaded.", "{0} images loaded.", data.size(), data.size())
321 + " " + trn("{0} was found to be GPS tagged.", "{0} were found to be GPS tagged.", i, i);
322 }
323
324 @Override public Object getInfoComponent() {
325 return infoText();
326 }
327
328 @Override
329 public String getToolTipText() {
330 return infoText();
331 }
332
333 @Override
334 public boolean isMergable(Layer other) {
335 return other instanceof GeoImageLayer;
336 }
337
338 @Override
339 public void mergeFrom(Layer from) {
340 GeoImageLayer l = (GeoImageLayer) from;
341
342 ImageEntry selected = null;
343 if (l.currentPhoto >= 0) {
344 selected = l.data.get(l.currentPhoto);
345 }
346
347 data.addAll(l.data);
348 Collections.sort(data);
349
350 // Supress the double photos.
351 if (data.size() > 1) {
352 ImageEntry cur;
353 ImageEntry prev = data.get(data.size() - 1);
354 for (int i = data.size() - 2; i >= 0; i--) {
355 cur = data.get(i);
356 if (cur.getFile().equals(prev.getFile())) {
357 data.remove(i);
358 } else {
359 prev = cur;
360 }
361 }
362 }
363
364 if (selected != null) {
365 for (int i = 0; i < data.size() ; i++) {
366 if (data.get(i) == selected) {
367 currentPhoto = i;
368 ImageViewerDialog.showImage(GeoImageLayer.this, data.get(i));
369 break;
370 }
371 }
372 }
373
374 setName(l.getName());
375 }
376
377 private Dimension scaledDimension(Image thumb) {
378 final double d = Main.map.mapView.getDist100Pixel();
379 final double size = 10 /*meter*/; /* size of the photo on the map */
380 double s = size * 100 /*px*/ / d;
381
382 final double sMin = ThumbsLoader.minSize;
383 final double sMax = ThumbsLoader.maxSize;
384
385 if (s < sMin) {
386 s = sMin;
387 }
388 if (s > sMax) {
389 s = sMax;
390 }
391 final double f = s / sMax; /* scale factor */
392
393 if (thumb == null)
394 return null;
395
396 return new Dimension(
397 (int) Math.round(f * thumb.getWidth(null)),
398 (int) Math.round(f * thumb.getHeight(null)));
399 }
400
401 @Override
402 public void paint(Graphics2D g, MapView mv, Bounds bounds) {
403 int width = mv.getWidth();
404 int height = mv.getHeight();
405 Rectangle clip = g.getClipBounds();
406 if (useThumbs) {
407 if (null == offscreenBuffer || offscreenBuffer.getWidth() != width // reuse the old buffer if possible
408 || offscreenBuffer.getHeight() != height) {
409 offscreenBuffer = new BufferedImage(width, height,
410 BufferedImage.TYPE_INT_ARGB);
411 updateOffscreenBuffer = true;
412 }
413
414 if (updateOffscreenBuffer) {
415 Graphics2D tempG = offscreenBuffer.createGraphics();
416 tempG.setColor(new Color(0,0,0,0));
417 Composite saveComp = tempG.getComposite();
418 tempG.setComposite(AlphaComposite.Clear); // remove the old images
419 tempG.fillRect(0, 0, width, height);
420 tempG.setComposite(saveComp);
421
422 for (ImageEntry e : data) {
423 if (e.getPos() == null) {
424 continue;
425 }
426 Point p = mv.getPoint(e.getPos());
427 if (e.thumbnail != null) {
428 Dimension d = scaledDimension(e.thumbnail);
429 Rectangle target = new Rectangle(p.x - d.width / 2, p.y - d.height / 2, d.width, d.height);
430 if (clip.intersects(target)) {
431 tempG.drawImage(e.thumbnail, target.x, target.y, target.width, target.height, null);
432 }
433 }
434 else { // thumbnail not loaded yet
435 icon.paintIcon(mv, tempG,
436 p.x - icon.getIconWidth() / 2,
437 p.y - icon.getIconHeight() / 2);
438 }
439 }
440 updateOffscreenBuffer = false;
441 }
442 g.drawImage(offscreenBuffer, 0, 0, null);
443 }
444 else {
445 for (ImageEntry e : data) {
446 if (e.getPos() == null) {
447 continue;
448 }
449 Point p = mv.getPoint(e.getPos());
450 icon.paintIcon(mv, g,
451 p.x - icon.getIconWidth() / 2,
452 p.y - icon.getIconHeight() / 2);
453 }
454 }
455
456 if (currentPhoto >= 0 && currentPhoto < data.size()) {
457 ImageEntry e = data.get(currentPhoto);
458
459 if (e.getPos() != null) {
460 Point p = mv.getPoint(e.getPos());
461
462 if (e.thumbnail != null) {
463 Dimension d = scaledDimension(e.thumbnail);
464 g.setColor(new Color(128, 0, 0, 122));
465 g.fillRect(p.x - d.width / 2, p.y - d.height / 2, d.width, d.height);
466 } else {
467 if (e.getExifImgDir() != null) {
468 double arrowlength = 25;
469 double arrowwidth = 18;
470
471 double dir = e.getExifImgDir();
472 // Rotate 90 degrees CCW
473 double headdir = ( dir < 90 ) ? dir + 270 : dir - 90;
474 double leftdir = ( headdir < 90 ) ? headdir + 270 : headdir - 90;
475 double rightdir = ( headdir > 270 ) ? headdir - 270 : headdir + 90;
476
477 double ptx = p.x + Math.cos(Math.toRadians(headdir)) * arrowlength;
478 double pty = p.y + Math.sin(Math.toRadians(headdir)) * arrowlength;
479
480 double ltx = p.x + Math.cos(Math.toRadians(leftdir)) * arrowwidth/2;
481 double lty = p.y + Math.sin(Math.toRadians(leftdir)) * arrowwidth/2;
482
483 double rtx = p.x + Math.cos(Math.toRadians(rightdir)) * arrowwidth/2;
484 double rty = p.y + Math.sin(Math.toRadians(rightdir)) * arrowwidth/2;
485
486 g.setColor(Color.white);
487 int[] xar = {(int) ltx, (int) ptx, (int) rtx, (int) ltx};
488 int[] yar = {(int) lty, (int) pty, (int) rty, (int) lty};
489 g.fillPolygon(xar, yar, 4);
490 }
491
492 selectedIcon.paintIcon(mv, g,
493 p.x - selectedIcon.getIconWidth() / 2,
494 p.y - selectedIcon.getIconHeight() / 2);
495
496 }
497 }
498 }
499 }
500
501 @Override
502 public void visitBoundingBox(BoundingXYVisitor v) {
503 for (ImageEntry e : data) {
504 v.visit(e.getPos());
505 }
506 }
507
508 /**
509 * Extract GPS metadata from image EXIF
510 *
511 * If successful, fills in the LatLon and EastNorth attributes of passed in image
512 */
513 private static void extractExif(ImageEntry e) {
514
515 Metadata metadata;
516 Directory dirExif;
517 GpsDirectory dirGps;
518
519 try {
520 metadata = JpegMetadataReader.readMetadata(e.getFile());
521 dirExif = metadata.getDirectory(ExifIFD0Directory.class);
522 dirGps = metadata.getDirectory(GpsDirectory.class);
523 } catch (CompoundException p) {
524 e.setExifCoor(null);
525 e.setPos(null);
526 return;
527 } catch (IOException p) {
528 e.setExifCoor(null);
529 e.setPos(null);
530 return;
531 }
532
533 try {
534 int orientation = dirExif.getInt(ExifIFD0Directory.TAG_ORIENTATION);
535 e.setExifOrientation(orientation);
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.