- Timestamp:
- 2021-10-04T16:05:23+02:00 (3 years ago)
- Location:
- trunk
- Files:
-
- 17 added
- 9 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/org/openstreetmap/josm/data/ImageData.java
r18063 r18246 133 133 134 134 /** 135 * Get the first image on the layer 136 * @return The first image 137 * @since 18246 138 */ 139 public ImageEntry getFirstImage() { 140 if (!this.data.isEmpty()) { 141 return this.data.get(0); 142 } 143 return null; 144 } 145 146 /** 135 147 * Select the first image of the sequence 136 */ 148 * @deprecated Use {@link #getFirstImage()} in conjunction with {@link #setSelectedImage} 149 */ 150 @Deprecated 137 151 public void selectFirstImage() { 138 152 if (!data.isEmpty()) { … … 142 156 143 157 /** 158 * Get the last image in the layer 159 * @return The last image 160 * @since 18246 161 */ 162 public ImageEntry getLastImage() { 163 if (!this.data.isEmpty()) { 164 return this.data.get(this.data.size() - 1); 165 } 166 return null; 167 } 168 169 /** 144 170 * Select the last image of the sequence 145 */ 171 * @deprecated Use {@link #getLastImage()} with {@link #setSelectedImage} 172 */ 173 @Deprecated 146 174 public void selectLastImage() { 147 175 setSelectedImageIndex(data.size() - 1); … … 167 195 168 196 /** 197 * Get the image next to the current image 198 * @return The next image 199 * @since 18246 200 */ 201 public ImageEntry getNextImage() { 202 if (this.hasNextImage()) { 203 return this.data.get(this.selectedImagesIndex.get(0) + 1); 204 } 205 return null; 206 } 207 208 /** 169 209 * Select the next image of the sequence 170 */ 210 * @deprecated Use {@link #getNextImage()} in conjunction with {@link #setSelectedImage} 211 */ 212 @Deprecated 171 213 public void selectNextImage() { 172 214 if (hasNextImage()) { … … 176 218 177 219 /** 220 * Get the image previous to the current image 221 * @return The previous image 222 * @since 18246 223 */ 224 public ImageEntry getPreviousImage() { 225 if (this.hasPreviousImage()) { 226 return this.data.get(Integer.max(0, selectedImagesIndex.get(0) - 1)); 227 } 228 return null; 229 } 230 231 /** 178 232 * Check if there is a previous image in the sequence 179 233 * @return {@code true} is there is a previous image, {@code false} otherwise … … 185 239 /** 186 240 * Select the previous image of the sequence 187 */ 241 * @deprecated Use {@link #getPreviousImage()} with {@link #setSelectedImage} 242 */ 243 @Deprecated 188 244 public void selectPreviousImage() { 189 245 if (data.isEmpty()) { -
trunk/src/org/openstreetmap/josm/data/gpx/GpxImageEntry.java
r18120 r18246 4 4 import static org.openstreetmap.josm.tools.I18n.tr; 5 5 6 import java.awt.Dimension; 7 import java.awt.image.BufferedImage; 6 8 import java.io.File; 7 9 import java.io.IOException; … … 10 12 import java.util.List; 11 13 import java.util.Locale; 14 import java.util.Map; 12 15 import java.util.Objects; 13 16 import java.util.function.Consumer; 17 import java.util.stream.Stream; 18 19 import javax.imageio.IIOParam; 14 20 15 21 import org.openstreetmap.josm.data.IQuadBucketType; 16 22 import org.openstreetmap.josm.data.coor.CachedLatLon; 17 23 import org.openstreetmap.josm.data.coor.LatLon; 24 import org.openstreetmap.josm.data.imagery.street_level.Projections; 18 25 import org.openstreetmap.josm.data.osm.BBox; 19 26 import org.openstreetmap.josm.tools.ExifReader; … … 34 41 import com.drew.metadata.iptc.IptcDirectory; 35 42 import com.drew.metadata.jpeg.JpegDirectory; 43 import com.drew.metadata.xmp.XmpDirectory; 36 44 37 45 /** … … 45 53 private Double exifImgDir; 46 54 private Instant exifTime; 55 private Projections cameraProjection = Projections.UNKNOWN; 47 56 /** 48 57 * Flag isNewGpsData indicates that the GPS data of the image is new or has changed. … … 478 487 elevation, exifCoor, exifGpsTime, exifImgDir, exifOrientation, exifTime, 479 488 iptcCaption, iptcHeadline, iptcKeywords, iptcObjectName, 480 file, gpsTime, pos, speed, tmp );489 file, gpsTime, pos, speed, tmp, cameraProjection); 481 490 } 482 491 … … 505 514 && Objects.equals(pos, other.pos) 506 515 && Objects.equals(speed, other.speed) 507 && Objects.equals(tmp, other.tmp); 516 && Objects.equals(tmp, other.tmp) 517 && cameraProjection == other.cameraProjection; 508 518 } 509 519 … … 754 764 ifNotNull(ExifReader.readObjectName(dirIptc), this::setIptcObjectName); 755 765 } 766 767 for (XmpDirectory xmpDirectory : metadata.getDirectoriesOfType(XmpDirectory.class)) { 768 Map<String, String> properties = xmpDirectory.getXmpProperties(); 769 final String projectionType = "GPano:ProjectionType"; 770 if (properties.containsKey(projectionType)) { 771 Stream.of(Projections.values()).filter(p -> p.name().equalsIgnoreCase(properties.get(projectionType))) 772 .findFirst().ifPresent(projection -> this.cameraProjection = projection); 773 break; 774 } 775 } 776 } 777 778 /** 779 * Reads the image represented by this entry in the given target dimension. 780 * @param target the desired dimension used for {@linkplain IIOParam#setSourceSubsampling subsampling} or {@code null} 781 * @return the read image, or {@code null} 782 * @throws IOException if any I/O error occurs 783 * @since 18246 784 */ 785 public BufferedImage read(Dimension target) throws IOException { 786 throw new UnsupportedOperationException("read not implemented for " + this.getClass().getSimpleName()); 756 787 } 757 788 … … 766 797 setter.accept(value); 767 798 } 799 } 800 801 /** 802 * Get the projection type for this entry 803 * @return The projection type 804 * @since 18246 805 */ 806 public Projections getProjectionType() { 807 return this.cameraProjection; 768 808 } 769 809 -
trunk/src/org/openstreetmap/josm/data/osm/FilterWorker.java
r17867 r18246 30 30 * @return true, if the filter state (normal / disabled / hidden) of any primitive has changed in the process 31 31 * @throws SearchParseError if the search expression in a filter cannot be parsed 32 * @since 12383, xxx(generics)32 * @since 12383, 17862 (generics) 33 33 */ 34 34 public static <T extends IPrimitive & IFilterablePrimitive> boolean executeFilters(Collection<T> all, Filter... filters) -
trunk/src/org/openstreetmap/josm/data/osm/QuadBuckets.java
r18208 r18246 23 23 * This class is (no longer) thread safe. 24 24 * @param <T> type of object extending {@link IQuadBucketType}. 25 * @since 2165 ({@link IPrimitive} only), xxxfor {@link IQuadBucketType}25 * @since 2165 ({@link IPrimitive} only), 17459 for {@link IQuadBucketType} 26 26 */ 27 27 public class QuadBuckets<T extends IQuadBucketType> implements Collection<T> { -
trunk/src/org/openstreetmap/josm/data/sources/SourceInfo.java
r18211 r18246 490 490 * @param clazz The class of the type of id 491 491 * @return sorted list of activated source IDs 492 * @since 13536, xxx(extracted)492 * @since 13536, 16545 (extracted) 493 493 */ 494 494 public static <W extends SourceInfo<?, ?, ?, ?>> Collection<String> getActiveIds(Class<W> clazz) { -
trunk/src/org/openstreetmap/josm/gui/layer/geoimage/GeoImageLayer.java
r18078 r18246 868 868 @Override 869 869 public void jumpToNextMarker() { 870 data.se lectNextImage();870 data.setSelectedImage(data.getNextImage()); 871 871 } 872 872 873 873 @Override 874 874 public void jumpToPreviousMarker() { 875 data.se lectPreviousImage();875 data.setSelectedImage(data.getPreviousImage()); 876 876 } 877 877 -
trunk/src/org/openstreetmap/josm/gui/layer/geoimage/ImageDisplay.java
r18217 r18246 13 13 import java.awt.Rectangle; 14 14 import java.awt.RenderingHints; 15 import java.awt.event.ComponentEvent; 16 import java.awt.event.MouseAdapter; 15 17 import java.awt.event.MouseEvent; 16 import java.awt.event.MouseListener;17 import java.awt.event.MouseMotionListener;18 18 import java.awt.event.MouseWheelEvent; 19 import java.awt.event.MouseWheelListener;20 19 import java.awt.geom.Rectangle2D; 21 20 import java.awt.image.BufferedImage; … … 27 26 import javax.swing.SwingUtilities; 28 27 28 import org.openstreetmap.josm.data.imagery.street_level.IImageEntry; 29 import org.openstreetmap.josm.data.imagery.street_level.Projections; 29 30 import org.openstreetmap.josm.data.preferences.BooleanProperty; 30 31 import org.openstreetmap.josm.data.preferences.DoubleProperty; 31 32 import org.openstreetmap.josm.data.preferences.IntegerProperty; 32 33 import org.openstreetmap.josm.gui.MainApplication; 34 import org.openstreetmap.josm.gui.layer.geoimage.viewers.projections.IImageViewer; 35 import org.openstreetmap.josm.gui.layer.geoimage.viewers.projections.ImageProjectionRegistry; 33 36 import org.openstreetmap.josm.gui.layer.imagery.ImageryFilterSettings; 34 37 import org.openstreetmap.josm.gui.layer.imagery.ImageryFilterSettings.FilterChangeListener; … … 39 42 import org.openstreetmap.josm.tools.Destroyable; 40 43 import org.openstreetmap.josm.tools.ImageProcessor; 44 import org.openstreetmap.josm.tools.JosmRuntimeException; 41 45 import org.openstreetmap.josm.tools.Logging; 42 46 import org.openstreetmap.josm.tools.Utils; … … 50 54 public class ImageDisplay extends JComponent implements Destroyable, PreferenceChangedListener, FilterChangeListener { 51 55 56 /** The current image viewer */ 57 private IImageViewer iImageViewer; 58 52 59 /** The file that is currently displayed */ 53 private I mageEntryentry;60 private IImageEntry<?> entry; 54 61 55 62 /** The previous file that is currently displayed. Cleared on paint. Only used to help improve UI error information. */ 56 private I mageEntryoldEntry;63 private IImageEntry<?> oldEntry; 57 64 58 65 /** The image currently displayed */ … … 262 269 protected class LoadImageRunnable implements Runnable { 263 270 264 private final I mageEntryentry;265 266 LoadImageRunnable(I mageEntryentry) {271 private final IImageEntry<?> entry; 272 273 LoadImageRunnable(IImageEntry<?> entry) { 267 274 this.entry = entry; 268 275 } … … 296 303 // This will clear the loading info box 297 304 ImageDisplay.this.oldEntry = ImageDisplay.this.entry; 298 visibleRect = new VisRect(0, 0, width, height);305 visibleRect = getIImageViewer(entry).getDefaultVisibleRectangle(ImageDisplay.this, image); 299 306 300 307 selectedRect = null; … … 308 315 } 309 316 310 private class ImgDisplayMouseListener implements MouseListener, MouseWheelListener, MouseMotionListener {317 private class ImgDisplayMouseListener extends MouseAdapter { 311 318 312 319 private MouseEvent lastMouseEvent; … … 331 338 332 339 private void mouseWheelMovedImpl(int x, int y, int rotation, boolean refreshMousePointInImg) { 333 ImageEntry entry; 334 Image image; 335 VisRect visibleRect; 340 IImageEntry<?> currentEntry; 341 IImageViewer imageViewer; 342 Image currentImage; 343 VisRect currentVisibleRect; 336 344 337 345 synchronized (ImageDisplay.this) { 338 entry = ImageDisplay.this.entry; 339 image = ImageDisplay.this.image; 340 visibleRect = ImageDisplay.this.visibleRect; 346 currentEntry = ImageDisplay.this.entry; 347 currentImage = ImageDisplay.this.image; 348 currentVisibleRect = ImageDisplay.this.visibleRect; 349 imageViewer = ImageDisplay.this.iImageViewer; 341 350 } 342 351 343 352 selectedRect = null; 344 353 345 if ( image == null)354 if (currentImage == null) 346 355 return; 347 356 348 357 // Calculate the mouse cursor position in image coordinates to center the zoom. 349 358 if (refreshMousePointInImg) 350 mousePointInImg = comp2imgCoord( visibleRect, x, y, getSize());359 mousePointInImg = comp2imgCoord(currentVisibleRect, x, y, getSize()); 351 360 352 361 // Apply the zoom to the visible rectangle in image coordinates 353 362 if (rotation > 0) { 354 visibleRect.width = (int) (visibleRect.width * ZOOM_STEP.get());355 visibleRect.height = (int) (visibleRect.height * ZOOM_STEP.get());363 currentVisibleRect.width = (int) (currentVisibleRect.width * ZOOM_STEP.get()); 364 currentVisibleRect.height = (int) (currentVisibleRect.height * ZOOM_STEP.get()); 356 365 } else { 357 visibleRect.width = (int) (visibleRect.width / ZOOM_STEP.get());358 visibleRect.height = (int) (visibleRect.height / ZOOM_STEP.get());366 currentVisibleRect.width = (int) (currentVisibleRect.width / ZOOM_STEP.get()); 367 currentVisibleRect.height = (int) (currentVisibleRect.height / ZOOM_STEP.get()); 359 368 } 360 369 361 370 // Check that the zoom doesn't exceed MAX_ZOOM:1 362 if (visibleRect.width < getSize().width / MAX_ZOOM.get()) { 363 visibleRect.width = (int) (getSize().width / MAX_ZOOM.get()); 364 } 365 if (visibleRect.height < getSize().height / MAX_ZOOM.get()) { 366 visibleRect.height = (int) (getSize().height / MAX_ZOOM.get()); 367 } 368 369 // Set the same ratio for the visible rectangle and the display area 370 int hFact = visibleRect.height * getSize().width; 371 int wFact = visibleRect.width * getSize().height; 372 if (hFact > wFact) { 373 visibleRect.width = hFact / getSize().height; 371 ensureMaxZoom(currentVisibleRect); 372 373 // The size of the visible rectangle is limited by the image size or the viewer implementation. 374 if (imageViewer != null) { 375 imageViewer.checkAndModifyVisibleRectSize(currentImage, currentVisibleRect); 374 376 } else { 375 visibleRect.height = wFact / getSize().width; 376 } 377 378 // The size of the visible rectangle is limited by the image size. 379 visibleRect.checkRectSize(); 377 currentVisibleRect.checkRectSize(); 378 } 380 379 381 380 // Set the position of the visible rectangle, so that the mouse cursor doesn't move on the image. 382 Rectangle drawRect = calculateDrawImageRectangle( visibleRect, getSize());383 visibleRect.x = mousePointInImg.x + ((drawRect.x - x) * visibleRect.width) / drawRect.width;384 visibleRect.y = mousePointInImg.y + ((drawRect.y - y) * visibleRect.height) / drawRect.height;381 Rectangle drawRect = calculateDrawImageRectangle(currentVisibleRect, getSize()); 382 currentVisibleRect.x = mousePointInImg.x + ((drawRect.x - x) * currentVisibleRect.width) / drawRect.width; 383 currentVisibleRect.y = mousePointInImg.y + ((drawRect.y - y) * currentVisibleRect.height) / drawRect.height; 385 384 386 385 // The position is also limited by the image size 387 visibleRect.checkRectPos();386 currentVisibleRect.checkRectPos(); 388 387 389 388 synchronized (ImageDisplay.this) { 390 if (ImageDisplay.this.entry == entry) {391 ImageDisplay.this.visibleRect = visibleRect;389 if (ImageDisplay.this.entry == currentEntry) { 390 ImageDisplay.this.visibleRect = currentVisibleRect; 392 391 } 393 392 } … … 417 416 public void mouseClicked(MouseEvent e) { 418 417 // Move the center to the clicked point. 419 I mageEntry entry;420 Image image;421 VisRect visibleRect;418 IImageEntry<?> currentEntry; 419 Image currentImage; 420 VisRect currentVisibleRect; 422 421 423 422 synchronized (ImageDisplay.this) { 424 entry = ImageDisplay.this.entry;425 image = ImageDisplay.this.image;426 visibleRect = ImageDisplay.this.visibleRect;427 } 428 429 if ( image == null)423 currentEntry = ImageDisplay.this.entry; 424 currentImage = ImageDisplay.this.image; 425 currentVisibleRect = ImageDisplay.this.visibleRect; 426 } 427 428 if (currentImage == null) 430 429 return; 431 430 … … 434 433 lastMouseEvent = null; 435 434 436 if (mouseIsZoomSelecting(e) && !isAtMaxZoom( visibleRect)) {435 if (mouseIsZoomSelecting(e) && !isAtMaxZoom(currentVisibleRect)) { 437 436 // zoom in if clicked with the zoom button 438 437 mouseWheelMovedImpl(e.getX(), e.getY(), -1, true); … … 447 446 448 447 // Calculate the translation to set the clicked point the center of the view. 449 Point click = comp2imgCoord( visibleRect, e.getX(), e.getY(), getSize());450 Point center = getCenterImgCoord( visibleRect);451 452 visibleRect.x += click.x - center.x;453 visibleRect.y += click.y - center.y;454 455 visibleRect.checkRectPos();448 Point click = comp2imgCoord(currentVisibleRect, e.getX(), e.getY(), getSize()); 449 Point center = getCenterImgCoord(currentVisibleRect); 450 451 currentVisibleRect.x += click.x - center.x; 452 currentVisibleRect.y += click.y - center.y; 453 454 currentVisibleRect.checkRectPos(); 456 455 457 456 synchronized (ImageDisplay.this) { 458 if (ImageDisplay.this.entry == entry) {459 ImageDisplay.this.visibleRect = visibleRect;457 if (ImageDisplay.this.entry == currentEntry) { 458 ImageDisplay.this.visibleRect = currentVisibleRect; 460 459 } 461 460 } … … 467 466 @Override 468 467 public void mousePressed(MouseEvent e) { 469 Image image;470 VisRect visibleRect;468 Image currentImage; 469 VisRect currentVisibleRect; 471 470 472 471 synchronized (ImageDisplay.this) { 473 image = ImageDisplay.this.image;474 visibleRect = ImageDisplay.this.visibleRect;475 } 476 477 if ( image == null)472 currentImage = ImageDisplay.this.image; 473 currentVisibleRect = ImageDisplay.this.visibleRect; 474 } 475 476 if (currentImage == null) 478 477 return; 479 478 … … 481 480 482 481 if (mouseIsDragging(e) || mouseIsZoomSelecting(e)) 483 mousePointInImg = comp2imgCoord( visibleRect, e.getX(), e.getY(), getSize());482 mousePointInImg = comp2imgCoord(currentVisibleRect, e.getX(), e.getY(), getSize()); 484 483 } 485 484 … … 489 488 return; 490 489 491 I mageEntry entry;492 Image image;493 VisRect visibleRect;490 IImageEntry<?> imageEntry; 491 Image currentImage; 492 VisRect currentVisibleRect; 494 493 495 494 synchronized (ImageDisplay.this) { 496 entry = ImageDisplay.this.entry;497 image = ImageDisplay.this.image;498 visibleRect = ImageDisplay.this.visibleRect;499 } 500 501 if ( image == null)495 imageEntry = ImageDisplay.this.entry; 496 currentImage = ImageDisplay.this.image; 497 currentVisibleRect = ImageDisplay.this.visibleRect; 498 } 499 500 if (currentImage == null) 502 501 return; 503 502 504 503 if (mouseIsDragging(e) && mousePointInImg != null) { 505 Point p = comp2imgCoord(visibleRect, e.getX(), e.getY(), getSize()); 506 visibleRect.isDragUpdate = true; 507 visibleRect.x += mousePointInImg.x - p.x; 508 visibleRect.y += mousePointInImg.y - p.y; 509 visibleRect.checkRectPos(); 504 Point p = comp2imgCoord(currentVisibleRect, e.getX(), e.getY(), getSize()); 505 getIImageViewer(entry).mouseDragged(this.mousePointInImg, p, currentVisibleRect); 506 currentVisibleRect.checkRectPos(); 510 507 synchronized (ImageDisplay.this) { 511 if (ImageDisplay.this.entry == entry) {512 ImageDisplay.this.visibleRect = visibleRect;508 if (ImageDisplay.this.entry == imageEntry) { 509 ImageDisplay.this.visibleRect = currentVisibleRect; 513 510 } 514 511 } 512 // We have to update the mousePointInImg for 360 image panning, as otherwise the panning 513 // never stops. 514 // This does not work well with the perspective viewer at this time (2021-08-26). 515 if (entry != null && Projections.EQUIRECTANGULAR == entry.getProjectionType()) { 516 this.mousePointInImg = p; 517 } 515 518 ImageDisplay.this.repaint(); 516 519 } 517 520 518 521 if (mouseIsZoomSelecting(e) && mousePointInImg != null) { 519 Point p = comp2imgCoord( visibleRect, e.getX(), e.getY(), getSize());520 visibleRect.checkPointInside(p);521 VisRect selectedRect = new VisRect(522 p.x < mousePointInImg.x ? p.x : mousePointInImg.x,523 p.y < mousePointInImg.y ? p.y : mousePointInImg.y,522 Point p = comp2imgCoord(currentVisibleRect, e.getX(), e.getY(), getSize()); 523 currentVisibleRect.checkPointInside(p); 524 VisRect selectedRectTemp = new VisRect( 525 Math.min(p.x, mousePointInImg.x), 526 Math.min(p.y, mousePointInImg.y), 524 527 p.x < mousePointInImg.x ? mousePointInImg.x - p.x : p.x - mousePointInImg.x, 525 528 p.y < mousePointInImg.y ? mousePointInImg.y - p.y : p.y - mousePointInImg.y, 526 visibleRect);527 selectedRect .checkRectSize();528 selectedRect .checkRectPos();529 ImageDisplay.this.selectedRect = selectedRect ;529 currentVisibleRect); 530 selectedRectTemp.checkRectSize(); 531 selectedRectTemp.checkRectPos(); 532 ImageDisplay.this.selectedRect = selectedRectTemp; 530 533 ImageDisplay.this.repaint(); 531 534 } 532 533 535 } 534 536 535 537 @Override 536 538 public void mouseReleased(MouseEvent e) { 537 I mageEntry entry;538 Image image;539 VisRect visibleRect;539 IImageEntry<?> currentEntry; 540 Image currentImage; 541 VisRect currentVisibleRect; 540 542 541 543 synchronized (ImageDisplay.this) { 542 entry = ImageDisplay.this.entry;543 image = ImageDisplay.this.image;544 visibleRect = ImageDisplay.this.visibleRect;545 } 546 547 if ( image == null)544 currentEntry = ImageDisplay.this.entry; 545 currentImage = ImageDisplay.this.image; 546 currentVisibleRect = ImageDisplay.this.visibleRect; 547 } 548 549 if (currentImage == null) 548 550 return; 549 551 550 552 if (mouseIsDragging(e)) { 551 visibleRect.isDragUpdate = false;553 currentVisibleRect.isDragUpdate = false; 552 554 } 553 555 … … 557 559 558 560 // Check that the zoom doesn't exceed MAX_ZOOM:1 559 if (selectedRect.width < getSize().width / MAX_ZOOM.get()) { 560 selectedRect.width = (int) (getSize().width / MAX_ZOOM.get()); 561 } 562 if (selectedRect.height < getSize().height / MAX_ZOOM.get()) { 563 selectedRect.height = (int) (getSize().height / MAX_ZOOM.get()); 564 } 565 566 // Set the same ratio for the visible rectangle and the display area 567 int hFact = selectedRect.height * getSize().width; 568 int wFact = selectedRect.width * getSize().height; 569 if (hFact > wFact) { 570 selectedRect.width = hFact / getSize().height; 571 } else { 572 selectedRect.height = wFact / getSize().width; 573 } 561 ensureMaxZoom(selectedRect); 574 562 575 563 // Keep the center of the selection … … 586 574 587 575 synchronized (ImageDisplay.this) { 588 if ( entry == ImageDisplay.this.entry) {576 if (currentEntry == ImageDisplay.this.entry) { 589 577 if (selectedRect == null) { 590 ImageDisplay.this.visibleRect = visibleRect;578 ImageDisplay.this.visibleRect = currentVisibleRect; 591 579 } else { 592 580 ImageDisplay.this.visibleRect.setBounds(selectedRect); … … 597 585 ImageDisplay.this.repaint(); 598 586 } 599 600 @Override601 public void mouseEntered(MouseEvent e) {602 // Do nothing603 }604 605 @Override606 public void mouseExited(MouseEvent e) {607 // Do nothing608 }609 610 @Override611 public void mouseMoved(MouseEvent e) {612 // Do nothing613 }614 587 } 615 588 … … 618 591 */ 619 592 public ImageDisplay() { 620 this(image -> image);593 this(imageObject -> imageObject); 621 594 } 622 595 … … 653 626 * @param entry new source image 654 627 * @return a {@link Future} representing pending completion of the image loading task 655 * @since 18 150656 */ 657 public Future<?> setImage(I mageEntryentry) {628 * @since 18246 (signature) 629 */ 630 public Future<?> setImage(IImageEntry<?> entry) { 658 631 LoadImageRunnable runnable = setImage0(entry); 659 632 return runnable != null ? MainApplication.worker.submit(runnable) : null; 660 633 } 661 634 662 protected LoadImageRunnable setImage0(I mageEntryentry) {635 protected LoadImageRunnable setImage0(IImageEntry<?> entry) { 663 636 synchronized (this) { 664 637 this.oldEntry = this.entry; … … 708 681 private void updateProcessedImage() { 709 682 processedImage = image == null ? null : imageProcessor.process(image); 710 GuiHelper.runInEDT( () -> repaint());683 GuiHelper.runInEDT(this::repaint); 711 684 } 712 685 … … 715 688 super.paintComponent(g); 716 689 717 ImageEntry entry; 718 ImageEntry oldEntry; 719 BufferedImage image; 720 VisRect visibleRect; 721 boolean errorLoading; 690 IImageEntry<?> currentEntry; 691 IImageEntry<?> currentOldEntry; 692 IImageViewer currentImageViewer; 693 BufferedImage currentImage; 694 VisRect currentVisibleRect; 695 boolean currentErrorLoading; 722 696 723 697 synchronized (this) { 724 image = this.processedImage;725 entry = this.entry;726 oldEntry = this.oldEntry;727 visibleRect = this.visibleRect;728 errorLoading = this.errorLoading;698 currentImage = this.processedImage; 699 currentEntry = this.entry; 700 currentOldEntry = this.oldEntry; 701 currentVisibleRect = this.visibleRect; 702 currentErrorLoading = this.errorLoading; 729 703 } 730 704 … … 735 709 Dimension size = getSize(); 736 710 // Draw the image first, then draw error information 737 if (image != null && (entry != null || oldEntry != null)) { 738 Rectangle r = new Rectangle(visibleRect); 739 Rectangle target = calculateDrawImageRectangle(visibleRect, size); 740 741 g.drawImage(image, 742 target.x, target.y, target.x + target.width, target.y + target.height, 743 r.x, r.y, r.x + r.width, r.y + r.height, null); 744 745 if (selectedRect != null) { 746 Point topLeft = img2compCoord(visibleRect, selectedRect.x, selectedRect.y, size); 747 Point bottomRight = img2compCoord(visibleRect, 748 selectedRect.x + selectedRect.width, 749 selectedRect.y + selectedRect.height, size); 750 g.setColor(new Color(128, 128, 128, 180)); 751 g.fillRect(target.x, target.y, target.width, topLeft.y - target.y); 752 g.fillRect(target.x, target.y, topLeft.x - target.x, target.height); 753 g.fillRect(bottomRight.x, target.y, target.x + target.width - bottomRight.x, target.height); 754 g.fillRect(target.x, bottomRight.y, target.width, target.y + target.height - bottomRight.y); 755 g.setColor(Color.black); 756 g.drawRect(topLeft.x, topLeft.y, bottomRight.x - topLeft.x, bottomRight.y - topLeft.y); 757 } 758 if (errorLoading && entry != null) { 759 String loadingStr = tr("Error on file {0}", entry.getDisplayName()); 711 if (currentImage != null && (currentEntry != null || currentOldEntry != null)) { 712 currentImageViewer = this.getIImageViewer(currentEntry); 713 Rectangle r = new Rectangle(currentVisibleRect); 714 Rectangle target = calculateDrawImageRectangle(currentVisibleRect, size); 715 716 currentImageViewer.paintImage(g, currentImage, target, r); 717 paintSelectedRect(g, target, currentVisibleRect, size); 718 if (currentErrorLoading && currentEntry != null) { 719 String loadingStr = tr("Error on file {0}", currentEntry.getDisplayName()); 760 720 Rectangle2D noImageSize = g.getFontMetrics(g.getFont()).getStringBounds(loadingStr, g); 761 g.drawString(loadingStr, 762 (int) ((size.width - noImageSize.getWidth()) / 2), 721 g.drawString(loadingStr, (int) ((size.width - noImageSize.getWidth()) / 2), 763 722 (int) ((size.height - noImageSize.getHeight()) / 2)); 764 723 } 765 if (osdText != null) { 766 FontMetrics metrics = g.getFontMetrics(g.getFont()); 767 int ascent = metrics.getAscent(); 768 Color bkground = new Color(255, 255, 255, 128); 769 int lastPos = 0; 770 int pos = osdText.indexOf('\n'); 771 int x = 3; 772 int y = 3; 773 String line; 774 while (pos > 0) { 775 line = osdText.substring(lastPos, pos); 776 Rectangle2D lineSize = metrics.getStringBounds(line, g); 777 g.setColor(bkground); 778 g.fillRect(x, y, (int) lineSize.getWidth(), (int) lineSize.getHeight()); 779 g.setColor(Color.black); 780 g.drawString(line, x, y + ascent); 781 y += (int) lineSize.getHeight(); 782 lastPos = pos + 1; 783 pos = osdText.indexOf('\n', lastPos); 784 } 785 786 line = osdText.substring(lastPos); 787 Rectangle2D lineSize = g.getFontMetrics(g.getFont()).getStringBounds(line, g); 788 g.setColor(bkground); 789 g.fillRect(x, y, (int) lineSize.getWidth(), (int) lineSize.getHeight()); 790 g.setColor(Color.black); 791 g.drawString(line, x, y + ascent); 792 } 793 } 724 paintOsdText(g); 725 } 726 paintErrorMessage(g, currentEntry, currentOldEntry, currentImage, currentErrorLoading, size); 727 } 728 729 /** 730 * Paint an error message 731 * @param g The graphics to paint on 732 * @param imageEntry The current image entry 733 * @param oldImageEntry The old image entry 734 * @param bufferedImage The image being painted 735 * @param currentErrorLoading If there was an error loading the image 736 * @param size The size of the component 737 */ 738 private void paintErrorMessage(Graphics g, IImageEntry<?> imageEntry, IImageEntry<?> oldImageEntry, 739 BufferedImage bufferedImage, boolean currentErrorLoading, Dimension size) { 794 740 final String errorMessage; 795 741 // If the new entry is null, then there is no image. 796 if ( entry == null) {742 if (imageEntry == null) { 797 743 if (emptyText == null) { 798 744 emptyText = tr("No image"); 799 745 } 800 746 errorMessage = emptyText; 801 } else if ( image == null || !Objects.equals(entry, oldEntry)) {747 } else if (bufferedImage == null || !Objects.equals(imageEntry, oldImageEntry)) { 802 748 // The image is not necessarily null when loading anymore. If the oldEntry is not the same as the new entry, 803 749 // we are probably still loading the image. (oldEntry gets set to entry when the image finishes loading). 804 if (! errorLoading) {805 errorMessage = tr("Loading {0}", entry.getDisplayName());750 if (!currentErrorLoading) { 751 errorMessage = tr("Loading {0}", imageEntry.getDisplayName()); 806 752 } else { 807 errorMessage = tr("Error on file {0}", entry.getDisplayName());753 errorMessage = tr("Error on file {0}", imageEntry.getDisplayName()); 808 754 } 809 755 } else { … … 831 777 } 832 778 779 /** 780 * Paint OSD text 781 * @param g The graphics to paint on 782 */ 783 private void paintOsdText(Graphics g) { 784 if (osdText != null) { 785 FontMetrics metrics = g.getFontMetrics(g.getFont()); 786 int ascent = metrics.getAscent(); 787 Color bkground = new Color(255, 255, 255, 128); 788 int lastPos = 0; 789 int pos = osdText.indexOf('\n'); 790 int x = 3; 791 int y = 3; 792 String line; 793 while (pos > 0) { 794 line = osdText.substring(lastPos, pos); 795 Rectangle2D lineSize = metrics.getStringBounds(line, g); 796 g.setColor(bkground); 797 g.fillRect(x, y, (int) lineSize.getWidth(), (int) lineSize.getHeight()); 798 g.setColor(Color.black); 799 g.drawString(line, x, y + ascent); 800 y += (int) lineSize.getHeight(); 801 lastPos = pos + 1; 802 pos = osdText.indexOf('\n', lastPos); 803 } 804 805 line = osdText.substring(lastPos); 806 Rectangle2D lineSize = g.getFontMetrics(g.getFont()).getStringBounds(line, g); 807 g.setColor(bkground); 808 g.fillRect(x, y, (int) lineSize.getWidth(), (int) lineSize.getHeight()); 809 g.setColor(Color.black); 810 g.drawString(line, x, y + ascent); 811 } 812 } 813 814 /** 815 * Paint the selected rectangle 816 * @param g The graphics to paint on 817 * @param target The target area (i.e., the selection) 818 * @param visibleRectTemp The current visible rect 819 * @param size The size of the component 820 */ 821 private void paintSelectedRect(Graphics g, Rectangle target, VisRect visibleRectTemp, Dimension size) { 822 if (selectedRect != null) { 823 Point topLeft = img2compCoord(visibleRectTemp, selectedRect.x, selectedRect.y, size); 824 Point bottomRight = img2compCoord(visibleRectTemp, 825 selectedRect.x + selectedRect.width, 826 selectedRect.y + selectedRect.height, size); 827 g.setColor(new Color(128, 128, 128, 180)); 828 g.fillRect(target.x, target.y, target.width, topLeft.y - target.y); 829 g.fillRect(target.x, target.y, topLeft.x - target.x, target.height); 830 g.fillRect(bottomRight.x, target.y, target.x + target.width - bottomRight.x, target.height); 831 g.fillRect(target.x, bottomRight.y, target.width, target.y + target.height - bottomRight.y); 832 g.setColor(Color.black); 833 g.drawRect(topLeft.x, topLeft.y, bottomRight.x - topLeft.x, bottomRight.y - topLeft.y); 834 } 835 } 836 833 837 static Point img2compCoord(VisRect visibleRect, int xImg, int yImg, Dimension compSize) { 834 838 Rectangle drawRect = calculateDrawImageRectangle(visibleRect, compSize); … … 854 858 } 855 859 860 /** 861 * calculateDrawImageRectangle 862 * 863 * @param visibleRect the part of the image that should be drawn (in image coordinates) 864 * @param compSize the part of the component where the image should be drawn (in component coordinates) 865 * @return the part of compRect with the same width/height ratio as the image 866 */ 856 867 static VisRect calculateDrawImageRectangle(VisRect visibleRect, Dimension compSize) { 857 868 return calculateDrawImageRectangle(visibleRect, new Rectangle(0, 0, compSize.width, compSize.height)); … … 907 918 */ 908 919 public void zoomBestFitOrOne() { 909 I mageEntry entry;910 Image image;911 VisRect visibleRect;920 IImageEntry<?> currentEntry; 921 Image currentImage; 922 VisRect currentVisibleRect; 912 923 913 924 synchronized (this) { 914 entry = this.entry;915 image = this.image;916 visibleRect = this.visibleRect;917 } 918 919 if ( image == null)925 currentEntry = this.entry; 926 currentImage = this.image; 927 currentVisibleRect = this.visibleRect; 928 } 929 930 if (currentImage == null) 920 931 return; 921 932 922 if ( visibleRect.width != image.getWidth(null) || visibleRect.height != image.getHeight(null)) {933 if (currentVisibleRect.width != currentImage.getWidth(null) || currentVisibleRect.height != currentImage.getHeight(null)) { 923 934 // The display is not at best fit. => Zoom to best fit 924 visibleRect.reset();935 currentVisibleRect.reset(); 925 936 } else { 926 937 // The display is at best fit => zoom to 1:1 927 Point center = getCenterImgCoord( visibleRect);928 visibleRect.setBounds(center.x - getWidth() / 2, center.y - getHeight() / 2,938 Point center = getCenterImgCoord(currentVisibleRect); 939 currentVisibleRect.setBounds(center.x - getWidth() / 2, center.y - getHeight() / 2, 929 940 getWidth(), getHeight()); 930 visibleRect.checkRectSize();931 visibleRect.checkRectPos();941 currentVisibleRect.checkRectSize(); 942 currentVisibleRect.checkRectPos(); 932 943 } 933 944 934 945 synchronized (this) { 935 if (this.entry == entry) {936 this.visibleRect = visibleRect;946 if (this.entry == currentEntry) { 947 this.visibleRect = currentVisibleRect; 937 948 } 938 949 } 939 950 repaint(); 940 951 } 952 953 /** 954 * Get the image viewer for an entry 955 * @param entry The entry to get the viewer for. May be {@code null}. 956 * @return The new image viewer, may be {@code null} 957 */ 958 private IImageViewer getIImageViewer(IImageEntry<?> entry) { 959 IImageViewer imageViewer; 960 IImageEntry<?> imageEntry; 961 synchronized (this) { 962 imageViewer = this.iImageViewer; 963 imageEntry = entry == null ? this.entry : entry; 964 } 965 if (imageEntry == null || (imageViewer != null && imageViewer.getSupportedProjections().contains(imageEntry.getProjectionType()))) { 966 return imageViewer; 967 } 968 try { 969 imageViewer = ImageProjectionRegistry.getViewer(imageEntry.getProjectionType()).getConstructor().newInstance(); 970 } catch (ReflectiveOperationException e) { 971 throw new JosmRuntimeException(e); 972 } 973 synchronized (this) { 974 if (imageEntry.equals(this.entry)) { 975 this.removeComponentListener(this.iImageViewer); 976 this.iImageViewer = imageViewer; 977 imageViewer.componentResized(new ComponentEvent(this, ComponentEvent.COMPONENT_RESIZED)); 978 this.addComponentListener(this.iImageViewer); 979 } 980 } 981 return imageViewer; 982 } 983 984 /** 985 * Ensure that a rectangle isn't zoomed in too much 986 * @param rectangle The rectangle to get (typically the visible area) 987 */ 988 private void ensureMaxZoom(final Rectangle rectangle) { 989 if (rectangle.width < getSize().width / MAX_ZOOM.get()) { 990 rectangle.width = (int) (getSize().width / MAX_ZOOM.get()); 991 } 992 if (rectangle.height < getSize().height / MAX_ZOOM.get()) { 993 rectangle.height = (int) (getSize().height / MAX_ZOOM.get()); 994 } 995 996 // Set the same ratio for the visible rectangle and the display area 997 int hFact = rectangle.height * getSize().width; 998 int wFact = rectangle.width * getSize().height; 999 if (hFact > wFact) { 1000 rectangle.width = hFact / getSize().height; 1001 } else { 1002 rectangle.height = wFact / getSize().width; 1003 } 1004 } 1005 1006 /** 1007 * Update the visible rectangle (ensure zoom does not exceed specified values). 1008 * Specifically only visible for {@link IImageViewer} implementations. 1009 * @since 18246 1010 */ 1011 public void updateVisibleRectangle() { 1012 final VisRect currentVisibleRect; 1013 final Image mouseImage; 1014 final IImageViewer iImageViewer; 1015 synchronized (this) { 1016 currentVisibleRect = this.visibleRect; 1017 mouseImage = this.image; 1018 iImageViewer = this.getIImageViewer(this.entry); 1019 } 1020 if (mouseImage != null && currentVisibleRect != null && iImageViewer != null) { 1021 final Image maxImageSize = iImageViewer.getMaxImageSize(this, mouseImage); 1022 final VisRect maxVisibleRect = new VisRect(0, 0, maxImageSize.getWidth(null), maxImageSize.getHeight(null)); 1023 maxVisibleRect.setRect(currentVisibleRect); 1024 ensureMaxZoom(maxVisibleRect); 1025 1026 maxVisibleRect.checkRectSize(); 1027 synchronized (this) { 1028 this.visibleRect = maxVisibleRect; 1029 } 1030 } 1031 } 941 1032 } -
trunk/src/org/openstreetmap/josm/gui/layer/geoimage/ImageEntry.java
r18108 r18246 16 16 import java.util.Collections; 17 17 import java.util.Objects; 18 19 18 import javax.imageio.IIOParam; 20 19 import javax.imageio.ImageReadParam; … … 23 22 import org.openstreetmap.josm.data.ImageData; 24 23 import org.openstreetmap.josm.data.gpx.GpxImageEntry; 24 import org.openstreetmap.josm.data.imagery.street_level.IImageEntry; 25 25 import org.openstreetmap.josm.tools.ExifReader; 26 26 import org.openstreetmap.josm.tools.ImageProvider; … … 31 31 * @since 2662 32 32 */ 33 public class ImageEntry extends GpxImageEntry {33 public class ImageEntry extends GpxImageEntry implements IImageEntry<ImageEntry> { 34 34 35 35 private Image thumbnail; … … 136 136 } 137 137 138 @Override 139 public ImageEntry getNextImage() { 140 return this.dataSet.getNextImage(); 141 } 142 143 @Override 144 public void selectNextImage(final ImageViewerDialog imageViewerDialog) { 145 IImageEntry.super.selectNextImage(imageViewerDialog); 146 this.dataSet.setSelectedImage(this.getNextImage()); 147 } 148 149 @Override 150 public ImageEntry getPreviousImage() { 151 return this.dataSet.getPreviousImage(); 152 } 153 154 @Override 155 public void selectPreviousImage(ImageViewerDialog imageViewerDialog) { 156 IImageEntry.super.selectPreviousImage(imageViewerDialog); 157 this.dataSet.setSelectedImage(this.getPreviousImage()); 158 } 159 160 @Override 161 public ImageEntry getFirstImage() { 162 return this.dataSet.getFirstImage(); 163 } 164 165 @Override 166 public void selectFirstImage(ImageViewerDialog imageViewerDialog) { 167 IImageEntry.super.selectFirstImage(imageViewerDialog); 168 this.dataSet.setSelectedImage(this.getFirstImage()); 169 } 170 171 @Override 172 public ImageEntry getLastImage() { 173 return this.dataSet.getLastImage(); 174 } 175 176 @Override 177 public void selectLastImage(ImageViewerDialog imageViewerDialog) { 178 IImageEntry.super.selectLastImage(imageViewerDialog); 179 this.dataSet.setSelectedImage(this.getLastImage()); 180 } 181 182 @Override 183 public boolean isRemoveSupported() { 184 return true; 185 } 186 187 @Override 188 public boolean remove() { 189 this.dataSet.removeImage(this, false); 190 return true; 191 } 192 138 193 /** 139 194 * Reads the image represented by this entry in the given target dimension. … … 142 197 * @throws IOException if any I/O error occurs 143 198 */ 199 @Override 144 200 public BufferedImage read(Dimension target) throws IOException { 145 201 URL imageUrl = getImageUrl(); -
trunk/src/org/openstreetmap/josm/gui/layer/geoimage/ImageViewerDialog.java
r18150 r18246 16 16 import java.time.format.DateTimeFormatter; 17 17 import java.time.format.FormatStyle; 18 import java.util.ArrayList; 18 19 import java.util.Collections; 19 20 import java.util.List; 20 21 import java.util.Optional; 21 22 import java.util.concurrent.Future; 22 23 import java.util.stream.Collectors; 23 24 import javax.swing.AbstractAction; 24 25 import javax.swing.Box; … … 33 34 import org.openstreetmap.josm.data.ImageData; 34 35 import org.openstreetmap.josm.data.ImageData.ImageDataUpdateListener; 36 import org.openstreetmap.josm.data.imagery.street_level.IImageEntry; 35 37 import org.openstreetmap.josm.gui.ExtendedDialog; 36 38 import org.openstreetmap.josm.gui.MainApplication; 37 39 import org.openstreetmap.josm.gui.datatransfer.ClipboardUtils; 38 import org.openstreetmap.josm.gui.dialogs.DialogsPanel .Action;40 import org.openstreetmap.josm.gui.dialogs.DialogsPanel; 39 41 import org.openstreetmap.josm.gui.dialogs.ToggleDialog; 40 42 import org.openstreetmap.josm.gui.dialogs.layer.LayerVisibilityAction; … … 50 52 import org.openstreetmap.josm.tools.Logging; 51 53 import org.openstreetmap.josm.tools.Shortcut; 52 import org.openstreetmap.josm.tools.Utils;53 54 import org.openstreetmap.josm.tools.date.DateUtils; 54 55 … … 221 222 @Override 222 223 public void actionPerformed(ActionEvent e) { 223 if ( currentData!= null) {224 currentData.selectNextImage();224 if (ImageViewerDialog.this.currentEntry != null) { 225 ImageViewerDialog.this.currentEntry.selectNextImage(ImageViewerDialog.this); 225 226 } 226 227 } … … 236 237 @Override 237 238 public void actionPerformed(ActionEvent e) { 238 if ( currentData!= null) {239 currentData.selectPreviousImage();239 if (ImageViewerDialog.this.currentEntry != null) { 240 ImageViewerDialog.this.currentEntry.selectPreviousImage(ImageViewerDialog.this); 240 241 } 241 242 } … … 251 252 @Override 252 253 public void actionPerformed(ActionEvent e) { 253 if ( currentData!= null) {254 currentData.selectFirstImage();254 if (ImageViewerDialog.this.currentEntry != null) { 255 ImageViewerDialog.this.currentEntry.selectFirstImage(ImageViewerDialog.this); 255 256 } 256 257 } … … 266 267 @Override 267 268 public void actionPerformed(ActionEvent e) { 268 if ( currentData!= null) {269 currentData.selectLastImage();269 if (ImageViewerDialog.this.currentEntry != null) { 270 ImageViewerDialog.this.currentEntry.selectLastImage(ImageViewerDialog.this); 270 271 } 271 272 } … … 309 310 @Override 310 311 public void actionPerformed(ActionEvent e) { 311 if (currentData != null) { 312 currentData.removeSelectedImages(); 312 if (ImageViewerDialog.this.currentEntry != null) { 313 IImageEntry<?> imageEntry = ImageViewerDialog.this.currentEntry; 314 if (imageEntry.isRemoveSupported()) { 315 imageEntry.remove(); 316 } 313 317 } 314 318 } … … 325 329 @Override 326 330 public void actionPerformed(ActionEvent e) { 327 if (currentData != null && currentData.getSelectedImage() != null) { 328 List<ImageEntry> toDelete = currentData.getSelectedImages(); 331 if (currentEntry != null) { 332 List<IImageEntry<?>> toDelete = currentEntry instanceof ImageEntry ? 333 new ArrayList<>(((ImageEntry) currentEntry).getDataSet().getSelectedImages()) 334 : Collections.singletonList(currentEntry); 329 335 int size = toDelete.size(); 330 336 … … 347 353 348 354 if (result == 2) { 349 for (ImageEntry delete : toDelete) { 350 if (Utils.deleteFile(delete.getFile())) { 351 currentData.removeImage(delete, false); 355 final List<ImageData> imageDataCollection = toDelete.stream().filter(ImageEntry.class::isInstance) 356 .map(ImageEntry.class::cast).map(ImageEntry::getDataSet).distinct().collect(Collectors.toList()); 357 for (IImageEntry<?> delete : toDelete) { 358 if (delete.isRemoveSupported() && delete.remove()) { 352 359 Logging.info("File {0} deleted.", delete.getFile()); 353 360 } else { … … 360 367 } 361 368 } 362 currentData.notifyImageUpdate(); 363 currentData.updateSelectedImage(); 369 imageDataCollection.forEach(data -> { 370 data.notifyImageUpdate(); 371 data.updateSelectedImage(); 372 }); 364 373 } 365 374 } … … 376 385 @Override 377 386 public void actionPerformed(ActionEvent e) { 378 if (current Data!= null) {379 ClipboardUtils.copyString(String.valueOf(current Data.getSelectedImage().getFile()));387 if (currentEntry != null) { 388 ClipboardUtils.copyString(String.valueOf(currentEntry.getFile())); 380 389 } 381 390 } … … 426 435 } 427 436 428 private transient ImageData currentData; 429 private transient ImageEntry currentEntry; 437 private transient IImageEntry<?> currentEntry; 430 438 431 439 /** 432 440 * Displays a single image for the given layer. 433 * @param data the image data441 * @param ignoredData the image data 434 442 * @param entry image entry 435 443 * @see #displayImages 436 444 */ 437 public void displayImage(ImageData data, ImageEntry entry) { 438 displayImages(data, Collections.singletonList(entry)); 445 public void displayImage(ImageData ignoredData, ImageEntry entry) { 446 displayImages(Collections.singletonList(entry)); 447 } 448 449 /** 450 * Displays a single image for the given layer. 451 * @param entry image entry 452 * @see #displayImages 453 */ 454 public void displayImage(IImageEntry<?> entry) { 455 this.displayImages(Collections.singletonList(entry)); 439 456 } 440 457 441 458 /** 442 459 * Displays images for the given layer. 443 * @param data the image data444 460 * @param entries image entries 445 * @since 1 5333446 */ 447 public void displayImages( ImageData data, List<ImageEntry> entries) {461 * @since 18246 462 */ 463 public void displayImages(List<IImageEntry<?>> entries) { 448 464 boolean imageChanged; 449 I mageEntryentry = entries != null && entries.size() == 1 ? entries.get(0) : null;465 IImageEntry<?> entry = entries != null && entries.size() == 1 ? entries.get(0) : null; 450 466 451 467 synchronized (this) { … … 458 474 } 459 475 460 currentData = data;461 476 currentEntry = entry; 462 477 } 463 478 464 479 if (entry != null) { 465 setNextEnabled(data.hasNextImage()); 466 setPreviousEnabled(data.hasPreviousImage()); 467 btnDelete.setEnabled(true); 468 btnDeleteFromDisk.setEnabled(entry.getFile() != null); 469 btnCopyPath.setEnabled(true); 470 471 if (imageChanged) { 472 cancelLoadingImage(); 473 // Set only if the image is new to preserve zoom and position if the same image is redisplayed 474 // (e.g. to update the OSD). 475 imgLoadingFuture = imgDisplay.setImage(entry); 476 } 477 setTitle(tr("Geotagged Images") + (!entry.getDisplayName().isEmpty() ? " - " + entry.getDisplayName() : "")); 478 StringBuilder osd = new StringBuilder(entry.getDisplayName()); 479 if (entry.getElevation() != null) { 480 osd.append(tr("\nAltitude: {0} m", Math.round(entry.getElevation()))); 481 } 482 if (entry.getSpeed() != null) { 483 osd.append(tr("\nSpeed: {0} km/h", Math.round(entry.getSpeed()))); 484 } 485 if (entry.getExifImgDir() != null) { 486 osd.append(tr("\nDirection {0}\u00b0", Math.round(entry.getExifImgDir()))); 487 } 488 489 DateTimeFormatter dtf = DateUtils.getDateTimeFormatter(FormatStyle.SHORT, FormatStyle.MEDIUM) 490 // Set timezone to UTC since UTC is assumed when parsing the EXIF timestamp, 491 // see see org.openstreetmap.josm.tools.ExifReader.readTime(com.drew.metadata.Metadata) 492 .withZone(ZoneOffset.UTC); 493 494 if (entry.hasExifTime()) { 495 osd.append(tr("\nEXIF time: {0}", dtf.format(entry.getExifInstant()))); 496 } 497 if (entry.hasGpsTime()) { 498 osd.append(tr("\nGPS time: {0}", dtf.format(entry.getGpsInstant()))); 499 } 500 Optional.ofNullable(entry.getIptcCaption()).map(s -> tr("\nCaption: {0}", s)).ifPresent(osd::append); 501 Optional.ofNullable(entry.getIptcHeadline()).map(s -> tr("\nHeadline: {0}", s)).ifPresent(osd::append); 502 Optional.ofNullable(entry.getIptcKeywords()).map(s -> tr("\nKeywords: {0}", s)).ifPresent(osd::append); 503 Optional.ofNullable(entry.getIptcObjectName()).map(s -> tr("\nObject name: {0}", s)).ifPresent(osd::append); 504 505 imgDisplay.setOsdText(osd.toString()); 480 this.updateButtonsNonNullEntry(entry, imageChanged); 506 481 } else { 507 boolean hasMultipleImages = entries != null && entries.size() > 1; 508 // if this method is called to reinitialize dialog content with a blank image, 509 // do not actually show the dialog again with a blank image if currently hidden (fix #10672) 510 setTitle(tr("Geotagged Images")); 511 imgDisplay.setImage(null); 512 imgDisplay.setOsdText(""); 513 setNextEnabled(false); 514 setPreviousEnabled(false); 515 btnDelete.setEnabled(hasMultipleImages); 516 btnDeleteFromDisk.setEnabled(hasMultipleImages); 517 btnCopyPath.setEnabled(false); 518 if (hasMultipleImages) { 519 imgDisplay.setEmptyText(tr("Multiple images selected")); 520 btnFirst.setEnabled(!isFirstImageSelected(data)); 521 btnLast.setEnabled(!isLastImageSelected(data)); 522 } 523 imgDisplay.setImage(null); 524 imgDisplay.setOsdText(""); 482 this.updateButtonsNullEntry(entries); 525 483 return; 526 484 } 527 485 if (!isDialogShowing()) { 528 setIsDocked(false); 486 setIsDocked(false); // always open a detached window when an image is clicked and dialog is closed 529 487 showDialog(); 530 } else { 531 if (isDocked && isCollapsed) { 532 expand(); 533 dialogsPanel.reconstruct(Action.COLLAPSED_TO_DEFAULT, this); 534 } 535 } 536 } 537 538 private static boolean isLastImageSelected(ImageData data) { 539 return data.isImageSelected(data.getImages().get(data.getImages().size() - 1)); 540 } 541 542 private static boolean isFirstImageSelected(ImageData data) { 543 return data.isImageSelected(data.getImages().get(0)); 488 } else if (isDocked && isCollapsed) { 489 expand(); 490 dialogsPanel.reconstruct(DialogsPanel.Action.COLLAPSED_TO_DEFAULT, this); 491 } 492 } 493 494 /** 495 * Update buttons for null entry 496 * @param entries {@code true} if multiple images are selected 497 */ 498 private void updateButtonsNullEntry(List<IImageEntry<?>> entries) { 499 boolean hasMultipleImages = entries != null && entries.size() > 1; 500 // if this method is called to reinitialize dialog content with a blank image, 501 // do not actually show the dialog again with a blank image if currently hidden (fix #10672) 502 setTitle(tr("Geotagged Images")); 503 imgDisplay.setImage(null); 504 imgDisplay.setOsdText(""); 505 setNextEnabled(false); 506 setPreviousEnabled(false); 507 btnDelete.setEnabled(hasMultipleImages); 508 btnDeleteFromDisk.setEnabled(hasMultipleImages); 509 btnCopyPath.setEnabled(false); 510 if (hasMultipleImages) { 511 imgDisplay.setEmptyText(tr("Multiple images selected")); 512 btnFirst.setEnabled(!isFirstImageSelected(entries)); 513 btnLast.setEnabled(!isLastImageSelected(entries)); 514 } 515 imgDisplay.setImage(null); 516 imgDisplay.setOsdText(""); 517 } 518 519 /** 520 * Update the image viewer buttons for the new entry 521 * @param entry The new entry 522 * @param imageChanged {@code true} if it is not the same image as the previous image. 523 */ 524 private void updateButtonsNonNullEntry(IImageEntry<?> entry, boolean imageChanged) { 525 setNextEnabled(entry.getNextImage() != null); 526 setPreviousEnabled(entry.getPreviousImage() != null); 527 btnDelete.setEnabled(true); 528 btnDeleteFromDisk.setEnabled(entry.getFile() != null); 529 btnCopyPath.setEnabled(true); 530 531 if (imageChanged) { 532 cancelLoadingImage(); 533 // Set only if the image is new to preserve zoom and position if the same image is redisplayed 534 // (e.g. to update the OSD). 535 imgLoadingFuture = imgDisplay.setImage(entry); 536 } 537 setTitle(tr("Geotagged Images") + (!entry.getDisplayName().isEmpty() ? " - " + entry.getDisplayName() : "")); 538 StringBuilder osd = new StringBuilder(entry.getDisplayName()); 539 if (entry.getElevation() != null) { 540 osd.append(tr("\nAltitude: {0} m", Math.round(entry.getElevation()))); 541 } 542 if (entry.getSpeed() != null) { 543 osd.append(tr("\nSpeed: {0} km/h", Math.round(entry.getSpeed()))); 544 } 545 if (entry.getExifImgDir() != null) { 546 osd.append(tr("\nDirection {0}\u00b0", Math.round(entry.getExifImgDir()))); 547 } 548 549 DateTimeFormatter dtf = DateUtils.getDateTimeFormatter(FormatStyle.SHORT, FormatStyle.MEDIUM) 550 // Set timezone to UTC since UTC is assumed when parsing the EXIF timestamp, 551 // see see org.openstreetmap.josm.tools.ExifReader.readTime(com.drew.metadata.Metadata) 552 .withZone(ZoneOffset.UTC); 553 554 if (entry.hasExifTime()) { 555 osd.append(tr("\nEXIF time: {0}", dtf.format(entry.getExifInstant()))); 556 } 557 if (entry.hasGpsTime()) { 558 osd.append(tr("\nGPS time: {0}", dtf.format(entry.getGpsInstant()))); 559 } 560 Optional.ofNullable(entry.getIptcCaption()).map(s -> tr("\nCaption: {0}", s)).ifPresent(osd::append); 561 Optional.ofNullable(entry.getIptcHeadline()).map(s -> tr("\nHeadline: {0}", s)).ifPresent(osd::append); 562 Optional.ofNullable(entry.getIptcKeywords()).map(s -> tr("\nKeywords: {0}", s)).ifPresent(osd::append); 563 Optional.ofNullable(entry.getIptcObjectName()).map(s -> tr("\nObject name: {0}", s)).ifPresent(osd::append); 564 565 imgDisplay.setOsdText(osd.toString()); 566 } 567 568 /** 569 * Displays images for the given layer. 570 * @param ignoredData the image data (unused, may be {@code null}) 571 * @param entries image entries 572 * @since 18246 (signature) 573 * @deprecated Use {@link #displayImages(List)} (The data param is no longer used) 574 */ 575 @Deprecated 576 public void displayImages(ImageData ignoredData, List<IImageEntry<?>> entries) { 577 this.displayImages(entries); 578 } 579 580 private static boolean isLastImageSelected(List<IImageEntry<?>> data) { 581 return data.stream().anyMatch(image -> data.contains(image.getLastImage())); 582 } 583 584 private static boolean isFirstImageSelected(List<IImageEntry<?>> data) { 585 return data.stream().anyMatch(image -> data.contains(image.getFirstImage())); 544 586 } 545 587 … … 576 618 * Returns the currently displayed image. 577 619 * @return Currently displayed image or {@code null} 578 * @since 6392579 */ 580 public static I mageEntrygetCurrentImage() {620 * @since 18246 (signature) 621 */ 622 public static IImageEntry<?> getCurrentImage() { 581 623 return getInstance().currentEntry; 582 624 } … … 599 641 @Override 600 642 public void layerRemoving(LayerRemoveEvent e) { 601 if (e.getRemovedLayer() instanceof GeoImageLayer ) {643 if (e.getRemovedLayer() instanceof GeoImageLayer && this.currentEntry instanceof ImageEntry) { 602 644 ImageData removedData = ((GeoImageLayer) e.getRemovedLayer()).getImageData(); 603 if (removedData == currentData) {604 displayImages(null , null);645 if (removedData == ((ImageEntry) this.currentEntry).getDataSet()) { 646 displayImages(null); 605 647 } 606 648 removedData.removeImageDataUpdateListener(this); … … 627 669 628 670 private void showLayer(Layer newLayer) { 629 if (currentData == null && newLayer instanceof GeoImageLayer) { 630 ((GeoImageLayer) newLayer).getImageData().selectFirstImage(); 671 if (this.currentEntry == null && newLayer instanceof GeoImageLayer) { 672 ImageData imageData = ((GeoImageLayer) newLayer).getImageData(); 673 imageData.setSelectedImage(imageData.getFirstImage()); 631 674 } 632 675 } … … 641 684 @Override 642 685 public void selectedImageChanged(ImageData data) { 643 displayImages( data, data.getSelectedImages());686 displayImages(new ArrayList<>(data.getSelectedImages())); 644 687 } 645 688 646 689 @Override 647 690 public void imageDataUpdated(ImageData data) { 648 displayImages( data, data.getSelectedImages());691 displayImages(new ArrayList<>(data.getSelectedImages())); 649 692 } 650 693 }
Note:
See TracChangeset
for help on using the changeset viewer.