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

Last change on this file since 4241 was 4241, checked in by bastiK, 13 years ago

applied #5605 - Geotagged image viewer should rotate images according to EXIF orientation tag (patch by m.zdila, some modifications)

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