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