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

Last change on this file since 3411 was 3411, checked in by bastiK, 14 years ago

fixed #5275 - Displayed co-ordinate precision adjustment

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