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

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

fixed #7711 - Extract altitude from photos and GPX track

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