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

Last change on this file since 5505 was 5505, checked in by bastiK, 12 years ago

add session support for geoimage layers (see #4029)

  • Property svn:eol-style set to native
File size: 30.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.Rational;
68import com.drew.metadata.Directory;
69import com.drew.metadata.Metadata;
70import com.drew.metadata.MetadataException;
71import com.drew.metadata.exif.ExifDirectory;
72import com.drew.metadata.exif.GpsDirectory;
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 HashSet<String> loadedDirectories = new HashSet<String>();
101 private LinkedHashSet<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(NullPointerException npe) {
122 rememberError(tr("One of the selected files was null"));
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 Collection<File> children = Arrays.asList(f.listFiles(JpegFileFilter.getInstance()));
192 if (children != null) {
193 progressMonitor.subTask(tr("Scanning directory {0}", f.getPath()));
194 try {
195 addRecursiveFiles(files, 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 NullPointerException();
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.size() > 0) {
242 boolean noGeotagFound = true;
243 for (ImageEntry e : layer.data) {
244 if (e.getPos() != null) {
245 noGeotagFound = false;
246 }
247 }
248 if (noGeotagFound) {
249 new CorrelateGpxWithImages(layer).actionPerformed(null);
250 }
251 }
252 }
253 }
254
255 @Override protected void cancel() {
256 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 public GeoImageLayer(final List<ImageEntry> data, GpxLayer gpxLayer) {
266
267 super(tr("Geotagged Images"));
268
269 Collections.sort(data);
270 this.data = data;
271 this.gpxLayer = gpxLayer;
272 }
273
274 @Override
275 public Icon getIcon() {
276 return ImageProvider.get("dialogs/geoimage");
277 }
278
279 private static List<Action> menuAdditions = new LinkedList<Action>();
280 public static void registerMenuAddition(Action addition) {
281 menuAdditions.add(addition);
282 }
283
284 @Override
285 public Action[] getMenuEntries() {
286
287 List<Action> entries = new ArrayList<Action>();
288 entries.add(LayerListDialog.getInstance().createShowHideLayerAction());
289 entries.add(LayerListDialog.getInstance().createDeleteLayerAction());
290 entries.add(new RenameLayerAction(null, this));
291 entries.add(SeparatorLayerAction.INSTANCE);
292 entries.add(new CorrelateGpxWithImages(this));
293 if (!menuAdditions.isEmpty()) {
294 entries.add(SeparatorLayerAction.INSTANCE);
295 entries.addAll(menuAdditions);
296 }
297 entries.add(SeparatorLayerAction.INSTANCE);
298 entries.add(new JumpToNextMarker(this));
299 entries.add(new JumpToPreviousMarker(this));
300 entries.add(SeparatorLayerAction.INSTANCE);
301 entries.add(new LayerListPopup.InfoAction(this));
302
303 return entries.toArray(new Action[0]);
304
305 }
306
307 private String infoText() {
308 int i = 0;
309 for (ImageEntry e : data)
310 if (e.getPos() != null) {
311 i++;
312 }
313 return trn("{0} image loaded.", "{0} images loaded.", data.size(), data.size())
314 + " " + trn("{0} was found to be GPS tagged.", "{0} were found to be GPS tagged.", i, i);
315 }
316
317 @Override public Object getInfoComponent() {
318 return infoText();
319 }
320
321 @Override
322 public String getToolTipText() {
323 return infoText();
324 }
325
326 @Override
327 public boolean isMergable(Layer other) {
328 return other instanceof GeoImageLayer;
329 }
330
331 @Override
332 public void mergeFrom(Layer from) {
333 GeoImageLayer l = (GeoImageLayer) from;
334
335 ImageEntry selected = null;
336 if (l.currentPhoto >= 0) {
337 selected = l.data.get(l.currentPhoto);
338 }
339
340 data.addAll(l.data);
341 Collections.sort(data);
342
343 // Supress the double photos.
344 if (data.size() > 1) {
345 ImageEntry cur;
346 ImageEntry prev = data.get(data.size() - 1);
347 for (int i = data.size() - 2; i >= 0; i--) {
348 cur = data.get(i);
349 if (cur.getFile().equals(prev.getFile())) {
350 data.remove(i);
351 } else {
352 prev = cur;
353 }
354 }
355 }
356
357 if (selected != null) {
358 for (int i = 0; i < data.size() ; i++) {
359 if (data.get(i) == selected) {
360 currentPhoto = i;
361 ImageViewerDialog.showImage(GeoImageLayer.this, data.get(i));
362 break;
363 }
364 }
365 }
366
367 setName(l.getName());
368 }
369
370 private Dimension scaledDimension(Image thumb) {
371 final double d = Main.map.mapView.getDist100Pixel();
372 final double size = 10 /*meter*/; /* size of the photo on the map */
373 double s = size * 100 /*px*/ / d;
374
375 final double sMin = ThumbsLoader.minSize;
376 final double sMax = ThumbsLoader.maxSize;
377
378 if (s < sMin) {
379 s = sMin;
380 }
381 if (s > sMax) {
382 s = sMax;
383 }
384 final double f = s / sMax; /* scale factor */
385
386 if (thumb == null)
387 return null;
388
389 return new Dimension(
390 (int) Math.round(f * thumb.getWidth(null)),
391 (int) Math.round(f * thumb.getHeight(null)));
392 }
393
394 @Override
395 public void paint(Graphics2D g, MapView mv, Bounds bounds) {
396 int width = Main.map.mapView.getWidth();
397 int height = Main.map.mapView.getHeight();
398 Rectangle clip = g.getClipBounds();
399 if (useThumbs) {
400 if (null == offscreenBuffer || offscreenBuffer.getWidth() != width // reuse the old buffer if possible
401 || offscreenBuffer.getHeight() != height) {
402 offscreenBuffer = new BufferedImage(width, height,
403 BufferedImage.TYPE_INT_ARGB);
404 updateOffscreenBuffer = true;
405 }
406
407 if (updateOffscreenBuffer) {
408 Graphics2D tempG = offscreenBuffer.createGraphics();
409 tempG.setColor(new Color(0,0,0,0));
410 Composite saveComp = tempG.getComposite();
411 tempG.setComposite(AlphaComposite.Clear); // remove the old images
412 tempG.fillRect(0, 0, width, height);
413 tempG.setComposite(saveComp);
414
415 for (ImageEntry e : data) {
416 if (e.getPos() == null) {
417 continue;
418 }
419 Point p = mv.getPoint(e.getPos());
420 if (e.thumbnail != null) {
421 Dimension d = scaledDimension(e.thumbnail);
422 Rectangle target = new Rectangle(p.x - d.width / 2, p.y - d.height / 2, d.width, d.height);
423 if (clip.intersects(target)) {
424 tempG.drawImage(e.thumbnail, target.x, target.y, target.width, target.height, null);
425 }
426 }
427 else { // thumbnail not loaded yet
428 icon.paintIcon(mv, tempG,
429 p.x - icon.getIconWidth() / 2,
430 p.y - icon.getIconHeight() / 2);
431 }
432 }
433 updateOffscreenBuffer = false;
434 }
435 g.drawImage(offscreenBuffer, 0, 0, null);
436 }
437 else {
438 for (ImageEntry e : data) {
439 if (e.getPos() == null) {
440 continue;
441 }
442 Point p = mv.getPoint(e.getPos());
443 icon.paintIcon(mv, g,
444 p.x - icon.getIconWidth() / 2,
445 p.y - icon.getIconHeight() / 2);
446 }
447 }
448
449 if (currentPhoto >= 0 && currentPhoto < data.size()) {
450 ImageEntry e = data.get(currentPhoto);
451
452 if (e.getPos() != null) {
453 Point p = mv.getPoint(e.getPos());
454
455 if (e.thumbnail != null) {
456 Dimension d = scaledDimension(e.thumbnail);
457 g.setColor(new Color(128, 0, 0, 122));
458 g.fillRect(p.x - d.width / 2, p.y - d.height / 2, d.width, d.height);
459 } else {
460 if (e.getExifImgDir() != null) {
461 double arrowlength = 25;
462 double arrowwidth = 18;
463
464 double dir = e.getExifImgDir();
465 // Rotate 90 degrees CCW
466 double headdir = ( dir < 90 ) ? dir + 270 : dir - 90;
467 double leftdir = ( headdir < 90 ) ? headdir + 270 : headdir - 90;
468 double rightdir = ( headdir > 270 ) ? headdir - 270 : headdir + 90;
469
470 double ptx = p.x + Math.cos(Math.toRadians(headdir)) * arrowlength;
471 double pty = p.y + Math.sin(Math.toRadians(headdir)) * arrowlength;
472
473 double ltx = p.x + Math.cos(Math.toRadians(leftdir)) * arrowwidth/2;
474 double lty = p.y + Math.sin(Math.toRadians(leftdir)) * arrowwidth/2;
475
476 double rtx = p.x + Math.cos(Math.toRadians(rightdir)) * arrowwidth/2;
477 double rty = p.y + Math.sin(Math.toRadians(rightdir)) * arrowwidth/2;
478
479 g.setColor(Color.white);
480 int[] xar = {(int) ltx, (int) ptx, (int) rtx, (int) ltx};
481 int[] yar = {(int) lty, (int) pty, (int) rty, (int) lty};
482 g.fillPolygon(xar, yar, 4);
483 }
484
485 selectedIcon.paintIcon(mv, g,
486 p.x - selectedIcon.getIconWidth() / 2,
487 p.y - selectedIcon.getIconHeight() / 2);
488
489 }
490 }
491 }
492 }
493
494 @Override
495 public void visitBoundingBox(BoundingXYVisitor v) {
496 for (ImageEntry e : data) {
497 v.visit(e.getPos());
498 }
499 }
500
501 /*
502 * Extract gps from image exif
503 *
504 * If successful, fills in the LatLon and EastNorth attributes of passed in
505 * image;
506 */
507
508 private static void extractExif(ImageEntry e) {
509
510 double deg;
511 double min, sec;
512 double lon, lat;
513 Metadata metadata = null;
514 Directory dirExif = null, dirGps = null;
515
516 try {
517 metadata = JpegMetadataReader.readMetadata(e.getFile());
518 dirExif = metadata.getDirectory(ExifDirectory.class);
519 dirGps = metadata.getDirectory(GpsDirectory.class);
520 } catch (CompoundException p) {
521 e.setExifCoor(null);
522 e.setPos(null);
523 return;
524 }
525
526 try {
527 int orientation = dirExif.getInt(ExifDirectory.TAG_ORIENTATION);
528 e.setExifOrientation(orientation);
529 } catch (MetadataException ex) {
530 }
531
532 try {
533 double ele=dirGps.getDouble(GpsDirectory.TAG_GPS_ALTITUDE);
534 int d = dirGps.getInt(GpsDirectory.TAG_GPS_ALTITUDE_REF);
535 if (d == 1) {
536 ele *= -1;
537 }
538 e.setElevation(ele);
539 } catch (MetadataException ex) {
540 }
541
542 try {
543 // longitude
544
545 Rational[] components = dirGps.getRationalArray(GpsDirectory.TAG_GPS_LONGITUDE);
546
547 deg = components[0].doubleValue();
548 min = components[1].doubleValue();
549 sec = components[2].doubleValue();
550
551 if (Double.isNaN(deg) && Double.isNaN(min) && Double.isNaN(sec))
552 throw new IllegalArgumentException();
553
554 lon = (Double.isNaN(deg) ? 0 : deg + (Double.isNaN(min) ? 0 : (min / 60)) + (Double.isNaN(sec) ? 0 : (sec / 3600)));
555
556 if (dirGps.getString(GpsDirectory.TAG_GPS_LONGITUDE_REF).charAt(0) == 'W') {
557 lon = -lon;
558 }
559
560 // latitude
561
562 components = dirGps.getRationalArray(GpsDirectory.TAG_GPS_LATITUDE);
563
564 deg = components[0].doubleValue();
565 min = components[1].doubleValue();
566 sec = components[2].doubleValue();
567
568 if (Double.isNaN(deg) && Double.isNaN(min) && Double.isNaN(sec))
569 throw new IllegalArgumentException();
570
571 lat = (Double.isNaN(deg) ? 0 : deg + (Double.isNaN(min) ? 0 : (min / 60)) + (Double.isNaN(sec) ? 0 : (sec / 3600)));
572
573 if (Double.isNaN(lat))
574 throw new IllegalArgumentException();
575
576 if (dirGps.getString(GpsDirectory.TAG_GPS_LATITUDE_REF).charAt(0) == 'S') {
577 lat = -lat;
578 }
579
580 // Store values
581
582 e.setExifCoor(new LatLon(lat, lon));
583 e.setPos(e.getExifCoor());
584
585 } catch (CompoundException p) {
586 // Try to read lon/lat as double value (Nonstandard, created by some cameras -> #5220)
587 try {
588 Double longitude = dirGps.getDouble(GpsDirectory.TAG_GPS_LONGITUDE);
589 Double latitude = dirGps.getDouble(GpsDirectory.TAG_GPS_LATITUDE);
590 if (longitude == null || latitude == null)
591 throw new CompoundException("");
592
593 // Store values
594
595 e.setExifCoor(new LatLon(latitude, longitude));
596 e.setPos(e.getExifCoor());
597 } catch (CompoundException ex) {
598 e.setExifCoor(null);
599 e.setPos(null);
600 }
601 } catch (Exception ex) { // (other exceptions, e.g. #5271)
602 System.err.println("Error reading EXIF from file: "+ex);
603 e.setExifCoor(null);
604 e.setPos(null);
605 }
606
607 // compass direction value
608
609 Rational direction = null;
610
611 try {
612 direction = dirGps.getRational(GpsDirectory.TAG_GPS_IMG_DIRECTION);
613 if (direction != null) {
614 e.setExifImgDir(direction.doubleValue());
615 }
616 } catch (Exception ex) { // (CompoundException and other exceptions, e.g. #5271)
617 // Do nothing
618 }
619 }
620
621 public void showNextPhoto() {
622 if (data != null && data.size() > 0) {
623 currentPhoto++;
624 if (currentPhoto >= data.size()) {
625 currentPhoto = data.size() - 1;
626 }
627 ImageViewerDialog.showImage(this, data.get(currentPhoto));
628 } else {
629 currentPhoto = -1;
630 }
631 Main.map.repaint();
632 }
633
634 public void showPreviousPhoto() {
635 if (data != null && data.size() > 0) {
636 currentPhoto--;
637 if (currentPhoto < 0) {
638 currentPhoto = 0;
639 }
640 ImageViewerDialog.showImage(this, data.get(currentPhoto));
641 } else {
642 currentPhoto = -1;
643 }
644 Main.map.repaint();
645 }
646
647 public void checkPreviousNextButtons() {
648 ImageViewerDialog.setNextEnabled(currentPhoto < data.size() - 1);
649 ImageViewerDialog.setPreviousEnabled(currentPhoto > 0);
650 }
651
652 public void removeCurrentPhoto() {
653 if (data != null && data.size() > 0 && currentPhoto >= 0 && currentPhoto < data.size()) {
654 data.remove(currentPhoto);
655 if (currentPhoto >= data.size()) {
656 currentPhoto = data.size() - 1;
657 }
658 if (currentPhoto >= 0) {
659 ImageViewerDialog.showImage(this, data.get(currentPhoto));
660 } else {
661 ImageViewerDialog.showImage(this, null);
662 }
663 updateOffscreenBuffer = true;
664 Main.map.repaint();
665 }
666 }
667
668 public void removeCurrentPhotoFromDisk() {
669 ImageEntry toDelete = null;
670 if (data != null && data.size() > 0 && currentPhoto >= 0 && currentPhoto < data.size()) {
671 toDelete = data.get(currentPhoto);
672
673 int result = new ExtendedDialog(
674 Main.parent,
675 tr("Delete image file from disk"),
676 new String[] {tr("Cancel"), tr("Delete")})
677 .setButtonIcons(new String[] {"cancel.png", "dialogs/delete.png"})
678 .setContent(new JLabel(tr("<html><h3>Delete the file {0} from disk?<p>The image file will be permanently lost!</h3></html>"
679 ,toDelete.getFile().getName()), ImageProvider.get("dialogs/geoimage/deletefromdisk"),SwingConstants.LEFT))
680 .toggleEnable("geoimage.deleteimagefromdisk")
681 .setCancelButton(1)
682 .setDefaultButton(2)
683 .showDialog()
684 .getValue();
685
686 if(result == 2)
687 {
688 data.remove(currentPhoto);
689 if (currentPhoto >= data.size()) {
690 currentPhoto = data.size() - 1;
691 }
692 if (currentPhoto >= 0) {
693 ImageViewerDialog.showImage(this, data.get(currentPhoto));
694 } else {
695 ImageViewerDialog.showImage(this, null);
696 }
697
698 if (toDelete.getFile().delete()) {
699 System.out.println("File "+toDelete.getFile().toString()+" deleted. ");
700 } else {
701 JOptionPane.showMessageDialog(
702 Main.parent,
703 tr("Image file could not be deleted."),
704 tr("Error"),
705 JOptionPane.ERROR_MESSAGE
706 );
707 }
708
709 updateOffscreenBuffer = true;
710 Main.map.repaint();
711 }
712 }
713 }
714
715 private MouseAdapter mouseAdapter = null;
716 private MapModeChangeListener mapModeListener = null;
717
718 @Override
719 public void hookUpMapView() {
720 mouseAdapter = new MouseAdapter() {
721 private final boolean isMapModeOk() {
722 return Main.map.mapMode == null || Main.map.mapMode instanceof SelectAction;
723 }
724 @Override public void mousePressed(MouseEvent e) {
725
726 if (e.getButton() != MouseEvent.BUTTON1)
727 return;
728 if (isVisible() && isMapModeOk()) {
729 Main.map.mapView.repaint();
730 }
731 }
732
733 @Override public void mouseReleased(MouseEvent ev) {
734 if (ev.getButton() != MouseEvent.BUTTON1)
735 return;
736 if (data == null || !isVisible() || !isMapModeOk())
737 return;
738
739 for (int i = data.size() - 1; i >= 0; --i) {
740 ImageEntry e = data.get(i);
741 if (e.getPos() == null) {
742 continue;
743 }
744 Point p = Main.map.mapView.getPoint(e.getPos());
745 Rectangle r;
746 if (e.thumbnail != null) {
747 Dimension d = scaledDimension(e.thumbnail);
748 r = new Rectangle(p.x - d.width / 2, p.y - d.height / 2, d.width, d.height);
749 } else {
750 r = new Rectangle(p.x - icon.getIconWidth() / 2,
751 p.y - icon.getIconHeight() / 2,
752 icon.getIconWidth(),
753 icon.getIconHeight());
754 }
755 if (r.contains(ev.getPoint())) {
756 currentPhoto = i;
757 ImageViewerDialog.showImage(GeoImageLayer.this, e);
758 Main.map.repaint();
759 break;
760 }
761 }
762 }
763 };
764
765 mapModeListener = new MapModeChangeListener() {
766 public void mapModeChange(MapMode oldMapMode, MapMode newMapMode) {
767 if (newMapMode == null || (newMapMode instanceof org.openstreetmap.josm.actions.mapmode.SelectAction)) {
768 Main.map.mapView.addMouseListener(mouseAdapter);
769 } else {
770 Main.map.mapView.removeMouseListener(mouseAdapter);
771 }
772 }
773 };
774
775 MapFrame.addMapModeChangeListener(mapModeListener);
776 mapModeListener.mapModeChange(null, Main.map.mapMode);
777
778 MapView.addLayerChangeListener(new LayerChangeListener() {
779 public void activeLayerChange(Layer oldLayer, Layer newLayer) {
780 if (newLayer == GeoImageLayer.this) {
781 // only in select mode it is possible to click the images
782 Main.map.selectSelectTool(false);
783 }
784 }
785
786 public void layerAdded(Layer newLayer) {
787 }
788
789 public void layerRemoved(Layer oldLayer) {
790 if (oldLayer == GeoImageLayer.this) {
791 if (thumbsloader != null) {
792 thumbsloader.stop = true;
793 }
794 Main.map.mapView.removeMouseListener(mouseAdapter);
795 MapFrame.removeMapModeChangeListener(mapModeListener);
796 currentPhoto = -1;
797 data.clear();
798 data = null;
799 // stop listening to layer change events
800 MapView.removeLayerChangeListener(this);
801 }
802 }
803 });
804
805 Main.map.mapView.addPropertyChangeListener(this);
806 if (Main.map.getToggleDialog(ImageViewerDialog.class) == null) {
807 ImageViewerDialog.newInstance();
808 Main.map.addToggleDialog(ImageViewerDialog.getInstance());
809 }
810 }
811
812 public void propertyChange(PropertyChangeEvent evt) {
813 if (NavigatableComponent.PROPNAME_CENTER.equals(evt.getPropertyName()) || NavigatableComponent.PROPNAME_SCALE.equals(evt.getPropertyName())) {
814 updateOffscreenBuffer = true;
815 }
816 }
817
818 public void loadThumbs() {
819 if (useThumbs && !thumbsLoaded) {
820 thumbsLoaded = true;
821 thumbsloader = new ThumbsLoader(this);
822 Thread t = new Thread(thumbsloader);
823 t.setPriority(Thread.MIN_PRIORITY);
824 t.start();
825 }
826 }
827
828 public void updateBufferAndRepaint() {
829 updateOffscreenBuffer = true;
830 Main.map.mapView.repaint();
831 }
832
833 public List<ImageEntry> getImages() {
834 List<ImageEntry> copy = new ArrayList<ImageEntry>();
835 for (ImageEntry ie : data) {
836 copy.add(ie.clone());
837 }
838 return copy;
839 }
840
841 public GpxLayer getGpxLayer() {
842 return gpxLayer;
843 }
844
845 public void jumpToNextMarker() {
846 showNextPhoto();
847 }
848
849 public void jumpToPreviousMarker() {
850 showPreviousPhoto();
851 }
852}
Note: See TracBrowser for help on using the repository browser.