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

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

Major performance improvements in multipolygons rendering

  • Property svn:eol-style set to native
File size: 29.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.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.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.MetadataException;
67import com.drew.metadata.exif.ExifDirectory;
68import com.drew.metadata.exif.GpsDirectory;
69
70public class GeoImageLayer extends Layer implements PropertyChangeListener {
71
72 List<ImageEntry> data;
73 GpxLayer gpxLayer;
74
75 private Icon icon = ImageProvider.get("dialogs/geoimage/photo-marker");
76 private Icon selectedIcon = ImageProvider.get("dialogs/geoimage/photo-marker-selected");
77
78 private int currentPhoto = -1;
79
80 boolean useThumbs = false;
81 ThumbsLoader thumbsloader;
82 boolean thumbsLoaded = false;
83 private BufferedImage offscreenBuffer;
84 boolean updateOffscreenBuffer = true;
85
86 /** Loads a set of images, while displaying a dialog that indicates what the plugin is currently doing.
87 * In facts, this object is instantiated with a list of files. These files may be JPEG files or
88 * directories. In case of directories, they are scanned to find all the images they contain.
89 * Then all the images that have be found are loaded as ImageEntry instances.
90 */
91 private static final class Loader extends PleaseWaitRunnable {
92
93 private boolean canceled = false;
94 private GeoImageLayer layer;
95 private Collection<File> selection;
96 private HashSet<String> loadedDirectories = new HashSet<String>();
97 private LinkedHashSet<String> errorMessages;
98 private GpxLayer gpxLayer;
99
100 protected void rememberError(String message) {
101 this.errorMessages.add(message);
102 }
103
104 public Loader(Collection<File> selection, GpxLayer gpxLayer) {
105 super(tr("Extracting GPS locations from EXIF"));
106 this.selection = selection;
107 this.gpxLayer = gpxLayer;
108 errorMessages = new LinkedHashSet<String>();
109 }
110
111 @Override protected void realRun() throws IOException {
112
113 progressMonitor.subTask(tr("Starting directory scan"));
114 Collection<File> files = new ArrayList<File>();
115 try {
116 addRecursiveFiles(files, selection);
117 } catch(NullPointerException npe) {
118 rememberError(tr("One of the selected files was null"));
119 }
120
121 if (canceled)
122 return;
123 progressMonitor.subTask(tr("Read photos..."));
124 progressMonitor.setTicksCount(files.size());
125
126 progressMonitor.subTask(tr("Read photos..."));
127 progressMonitor.setTicksCount(files.size());
128
129 // read the image files
130 List<ImageEntry> data = new ArrayList<ImageEntry>(files.size());
131
132 for (File f : files) {
133
134 if (canceled) {
135 break;
136 }
137
138 progressMonitor.subTask(tr("Reading {0}...", f.getName()));
139 progressMonitor.worked(1);
140
141 ImageEntry e = new ImageEntry();
142
143 // Changed to silently cope with no time info in exif. One case
144 // of person having time that couldn't be parsed, but valid GPS info
145
146 try {
147 e.setExifTime(ExifReader.readTime(f));
148 } catch (ParseException e1) {
149 e.setExifTime(null);
150 }
151 e.setFile(f);
152 extractExif(e);
153 data.add(e);
154 }
155 layer = new GeoImageLayer(data, gpxLayer);
156 files.clear();
157 }
158
159 private void addRecursiveFiles(Collection<File> files, Collection<File> sel) {
160 boolean nullFile = false;
161
162 for (File f : sel) {
163
164 if(canceled) {
165 break;
166 }
167
168 if (f == null) {
169 nullFile = true;
170
171 } else if (f.isDirectory()) {
172 String canonical = null;
173 try {
174 canonical = f.getCanonicalPath();
175 } catch (IOException e) {
176 e.printStackTrace();
177 rememberError(tr("Unable to get canonical path for directory {0}\n",
178 f.getAbsolutePath()));
179 }
180
181 if (canonical == null || loadedDirectories.contains(canonical)) {
182 continue;
183 } else {
184 loadedDirectories.add(canonical);
185 }
186
187 Collection<File> children = Arrays.asList(f.listFiles(JpegFileFilter.getInstance()));
188 if (children != null) {
189 progressMonitor.subTask(tr("Scanning directory {0}", f.getPath()));
190 try {
191 addRecursiveFiles(files, children);
192 } catch(NullPointerException npe) {
193 npe.printStackTrace();
194 rememberError(tr("Found null file in directory {0}\n", f.getPath()));
195 }
196 } else {
197 rememberError(tr("Error while getting files from directory {0}\n", f.getPath()));
198 }
199
200 } else {
201 files.add(f);
202 }
203 }
204
205 if (nullFile)
206 throw new NullPointerException();
207 }
208
209 protected String formatErrorMessages() {
210 StringBuilder sb = new StringBuilder();
211 sb.append("<html>");
212 if (errorMessages.size() == 1) {
213 sb.append(errorMessages.iterator().next());
214 } else {
215 sb.append("<ul>");
216 for (String msg: errorMessages) {
217 sb.append("<li>").append(msg).append("</li>");
218 }
219 sb.append("/ul>");
220 }
221 sb.append("</html>");
222 return sb.toString();
223 }
224
225 @Override protected void finish() {
226 if (!errorMessages.isEmpty()) {
227 JOptionPane.showMessageDialog(
228 Main.parent,
229 formatErrorMessages(),
230 tr("Error"),
231 JOptionPane.ERROR_MESSAGE
232 );
233 }
234 if (layer != null) {
235 Main.main.addLayer(layer);
236 layer.hook_up_mouse_events(); // Main.map.mapView should exist
237 // now. Can add mouse listener
238 Main.map.mapView.addPropertyChangeListener(layer);
239 if (Main.map.getToggleDialog(ImageViewerDialog.class) == null) {
240 ImageViewerDialog.newInstance();
241 Main.map.addToggleDialog(ImageViewerDialog.getInstance());
242 }
243
244 if (! canceled && layer.data.size() > 0) {
245 boolean noGeotagFound = true;
246 for (ImageEntry e : layer.data) {
247 if (e.getPos() != null) {
248 noGeotagFound = false;
249 }
250 }
251 if (noGeotagFound) {
252 new CorrelateGpxWithImages(layer).actionPerformed(null);
253 }
254 }
255 }
256 }
257
258 @Override protected void cancel() {
259 canceled = true;
260 }
261 }
262
263 public static void create(Collection<File> files, GpxLayer gpxLayer) {
264 Loader loader = new Loader(files, gpxLayer);
265 Main.worker.execute(loader);
266 }
267
268 private GeoImageLayer(final List<ImageEntry> data, GpxLayer gpxLayer) {
269
270 super(tr("Geotagged Images"));
271
272 Collections.sort(data);
273 this.data = data;
274 this.gpxLayer = gpxLayer;
275 }
276
277 @Override
278 public Icon getIcon() {
279 return ImageProvider.get("dialogs/geoimage");
280 }
281
282 private static List<Action> menuAdditions = new LinkedList<Action>();
283 public static void registerMenuAddition(Action addition) {
284 menuAdditions.add(addition);
285 }
286
287 @Override
288 public Action[] getMenuEntries() {
289
290 List<Action> entries = new ArrayList<Action>();
291 entries.add(LayerListDialog.getInstance().createShowHideLayerAction());
292 entries.add(LayerListDialog.getInstance().createDeleteLayerAction());
293 entries.add(new RenameLayerAction(null, this));
294 entries.add(SeparatorLayerAction.INSTANCE);
295 entries.add(new CorrelateGpxWithImages(this));
296 if (!menuAdditions.isEmpty()) {
297 entries.add(SeparatorLayerAction.INSTANCE);
298 entries.addAll(menuAdditions);
299 }
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 // longitude
534
535 Rational[] components = dirGps.getRationalArray(GpsDirectory.TAG_GPS_LONGITUDE);
536
537 deg = components[0].doubleValue();
538 min = components[1].doubleValue();
539 sec = components[2].doubleValue();
540
541 if (Double.isNaN(deg) && Double.isNaN(min) && Double.isNaN(sec))
542 throw new IllegalArgumentException();
543
544 lon = (Double.isNaN(deg) ? 0 : deg + (Double.isNaN(min) ? 0 : (min / 60)) + (Double.isNaN(sec) ? 0 : (sec / 3600)));
545
546 if (dirGps.getString(GpsDirectory.TAG_GPS_LONGITUDE_REF).charAt(0) == 'W') {
547 lon = -lon;
548 }
549
550 // latitude
551
552 components = dirGps.getRationalArray(GpsDirectory.TAG_GPS_LATITUDE);
553
554 deg = components[0].doubleValue();
555 min = components[1].doubleValue();
556 sec = components[2].doubleValue();
557
558 if (Double.isNaN(deg) && Double.isNaN(min) && Double.isNaN(sec))
559 throw new IllegalArgumentException();
560
561 lat = (Double.isNaN(deg) ? 0 : deg + (Double.isNaN(min) ? 0 : (min / 60)) + (Double.isNaN(sec) ? 0 : (sec / 3600)));
562
563 if (Double.isNaN(lat))
564 throw new IllegalArgumentException();
565
566 if (dirGps.getString(GpsDirectory.TAG_GPS_LATITUDE_REF).charAt(0) == 'S') {
567 lat = -lat;
568 }
569
570 // Store values
571
572 e.setExifCoor(new LatLon(lat, lon));
573 e.setPos(e.getExifCoor());
574
575 } catch (CompoundException p) {
576 // Try to read lon/lat as double value (Nonstandard, created by some cameras -> #5220)
577 try {
578 Double longitude = dirGps.getDouble(GpsDirectory.TAG_GPS_LONGITUDE);
579 Double latitude = dirGps.getDouble(GpsDirectory.TAG_GPS_LATITUDE);
580 if (longitude == null || latitude == null)
581 throw new CompoundException("");
582
583 // Store values
584
585 e.setExifCoor(new LatLon(latitude, longitude));
586 e.setPos(e.getExifCoor());
587 } catch (CompoundException ex) {
588 e.setExifCoor(null);
589 e.setPos(null);
590 }
591 } catch (Exception ex) { // (other exceptions, e.g. #5271)
592 System.err.println("Error when reading EXIF from file: "+ex);
593 e.setExifCoor(null);
594 e.setPos(null);
595 }
596
597 // compass direction value
598
599 Rational direction = null;
600
601 try {
602 direction = dirGps.getRational(GpsDirectory.TAG_GPS_IMG_DIRECTION);
603 if (direction != null) {
604 e.setExifImgDir(direction.doubleValue());
605 }
606 } catch (Exception ex) { // (CompoundException and other exceptions, e.g. #5271)
607 // Do nothing
608 }
609 }
610
611 public void showNextPhoto() {
612 if (data != null && data.size() > 0) {
613 currentPhoto++;
614 if (currentPhoto >= data.size()) {
615 currentPhoto = data.size() - 1;
616 }
617 ImageViewerDialog.showImage(this, data.get(currentPhoto));
618 } else {
619 currentPhoto = -1;
620 }
621 Main.map.repaint();
622 }
623
624 public void showPreviousPhoto() {
625 if (data != null && data.size() > 0) {
626 currentPhoto--;
627 if (currentPhoto < 0) {
628 currentPhoto = 0;
629 }
630 ImageViewerDialog.showImage(this, data.get(currentPhoto));
631 } else {
632 currentPhoto = -1;
633 }
634 Main.map.repaint();
635 }
636
637 public void checkPreviousNextButtons() {
638 ImageViewerDialog.setNextEnabled(currentPhoto < data.size() - 1);
639 ImageViewerDialog.setPreviousEnabled(currentPhoto > 0);
640 }
641
642 public void removeCurrentPhoto() {
643 if (data != null && data.size() > 0 && currentPhoto >= 0 && currentPhoto < data.size()) {
644 data.remove(currentPhoto);
645 if (currentPhoto >= data.size()) {
646 currentPhoto = data.size() - 1;
647 }
648 if (currentPhoto >= 0) {
649 ImageViewerDialog.showImage(this, data.get(currentPhoto));
650 } else {
651 ImageViewerDialog.showImage(this, null);
652 }
653 updateOffscreenBuffer = true;
654 Main.map.repaint();
655 }
656 }
657
658 public void removeCurrentPhotoFromDisk() {
659 ImageEntry toDelete = null;
660 if (data != null && data.size() > 0 && currentPhoto >= 0 && currentPhoto < data.size()) {
661 toDelete = data.get(currentPhoto);
662
663 int result = new ExtendedDialog(
664 Main.parent,
665 tr("Delete image file from disk"),
666 new String[] {tr("Cancel"), tr("Delete")})
667 .setButtonIcons(new String[] {"cancel.png", "dialogs/delete.png"})
668 .setContent(new JLabel(tr("<html><h3>Delete the file {0} from disk?<p>The image file will be permanently lost!</h3></html>"
669 ,toDelete.getFile().getName()), ImageProvider.get("dialogs/geoimage/deletefromdisk"),SwingConstants.LEFT))
670 .toggleEnable("geoimage.deleteimagefromdisk")
671 .setCancelButton(1)
672 .setDefaultButton(2)
673 .showDialog()
674 .getValue();
675
676 if(result == 2)
677 {
678 data.remove(currentPhoto);
679 if (currentPhoto >= data.size()) {
680 currentPhoto = data.size() - 1;
681 }
682 if (currentPhoto >= 0) {
683 ImageViewerDialog.showImage(this, data.get(currentPhoto));
684 } else {
685 ImageViewerDialog.showImage(this, null);
686 }
687
688 if (toDelete.getFile().delete()) {
689 System.out.println("File "+toDelete.getFile().toString()+" deleted. ");
690 } else {
691 JOptionPane.showMessageDialog(
692 Main.parent,
693 tr("Image file could not be deleted."),
694 tr("Error"),
695 JOptionPane.ERROR_MESSAGE
696 );
697 }
698
699 updateOffscreenBuffer = true;
700 Main.map.repaint();
701 }
702 }
703 }
704
705 private MouseAdapter mouseAdapter = null;
706 private MapModeChangeListener mapModeListener = null;
707
708 private void hook_up_mouse_events() {
709 mouseAdapter = new MouseAdapter() {
710 @Override public void mousePressed(MouseEvent e) {
711
712 if (e.getButton() != MouseEvent.BUTTON1)
713 return;
714 if (isVisible()) {
715 Main.map.mapView.repaint();
716 }
717 }
718
719 @Override public void mouseReleased(MouseEvent ev) {
720 if (ev.getButton() != MouseEvent.BUTTON1)
721 return;
722 if (!isVisible())
723 return;
724
725 for (int i = data.size() - 1; i >= 0; --i) {
726 ImageEntry e = data.get(i);
727 if (e.getPos() == null) {
728 continue;
729 }
730 Point p = Main.map.mapView.getPoint(e.getPos());
731 Rectangle r;
732 if (e.thumbnail != null) {
733 Dimension d = scaledDimension(e.thumbnail);
734 r = new Rectangle(p.x - d.width / 2, p.y - d.height / 2, d.width, d.height);
735 } else {
736 r = new Rectangle(p.x - icon.getIconWidth() / 2,
737 p.y - icon.getIconHeight() / 2,
738 icon.getIconWidth(),
739 icon.getIconHeight());
740 }
741 if (r.contains(ev.getPoint())) {
742 currentPhoto = i;
743 ImageViewerDialog.showImage(GeoImageLayer.this, e);
744 Main.map.repaint();
745 break;
746 }
747 }
748 }
749 };
750
751 mapModeListener = new MapModeChangeListener() {
752 public void mapModeChange(MapMode oldMapMode, MapMode newMapMode) {
753 if (newMapMode instanceof org.openstreetmap.josm.actions.mapmode.SelectAction) {
754 Main.map.mapView.addMouseListener(mouseAdapter);
755 } else {
756 Main.map.mapView.removeMouseListener(mouseAdapter);
757 }
758 }
759 };
760
761 MapFrame.addMapModeChangeListener(mapModeListener);
762 mapModeListener.mapModeChange(null, Main.map.mapMode);
763
764 MapView.addLayerChangeListener(new LayerChangeListener() {
765 public void activeLayerChange(Layer oldLayer, Layer newLayer) {
766 if (newLayer == GeoImageLayer.this) {
767 // only in select mode it is possible to click the images
768 Main.map.selectSelectTool(false);
769 }
770 }
771
772 public void layerAdded(Layer newLayer) {
773 }
774
775 public void layerRemoved(Layer oldLayer) {
776 if (oldLayer == GeoImageLayer.this) {
777 if (thumbsloader != null) {
778 thumbsloader.stop = true;
779 }
780 Main.map.mapView.removeMouseListener(mouseAdapter);
781 MapFrame.removeMapModeChangeListener(mapModeListener);
782 currentPhoto = -1;
783 data.clear();
784 data = null;
785 // stop listening to layer change events
786 MapView.removeLayerChangeListener(this);
787 }
788 }
789 });
790 }
791
792 public void propertyChange(PropertyChangeEvent evt) {
793 if (NavigatableComponent.PROPNAME_CENTER.equals(evt.getPropertyName()) || NavigatableComponent.PROPNAME_SCALE.equals(evt.getPropertyName())) {
794 updateOffscreenBuffer = true;
795 }
796 }
797
798 public void loadThumbs() {
799 if (useThumbs && !thumbsLoaded) {
800 thumbsLoaded = true;
801 thumbsloader = new ThumbsLoader(this);
802 Thread t = new Thread(thumbsloader);
803 t.setPriority(Thread.MIN_PRIORITY);
804 t.start();
805 }
806 }
807
808 public void updateBufferAndRepaint() {
809 updateOffscreenBuffer = true;
810 Main.map.mapView.repaint();
811 }
812
813 public List<ImageEntry> getImages() {
814 List<ImageEntry> copy = new ArrayList<ImageEntry>();
815 for (ImageEntry ie : data) {
816 copy.add(ie.clone());
817 }
818 return copy;
819 }
820}
Note: See TracBrowser for help on using the repository browser.