Changeset 18246 in josm for trunk/src


Ignore:
Timestamp:
2021-10-04T16:05:23+02:00 (3 years ago)
Author:
Don-vip
Message:

see #16472 - add initial working 360 viewer, minimal tests, many tests disabled due to failures, but is usable (patch by taylor.smock)

Location:
trunk/src/org/openstreetmap/josm
Files:
13 added
9 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/org/openstreetmap/josm/data/ImageData.java

    r18063 r18246  
    133133
    134134    /**
     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    /**
    135147     * Select the first image of the sequence
    136      */
     148     * @deprecated Use {@link #getFirstImage()} in conjunction with {@link #setSelectedImage}
     149     */
     150    @Deprecated
    137151    public void selectFirstImage() {
    138152        if (!data.isEmpty()) {
     
    142156
    143157    /**
     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    /**
    144170     * Select the last image of the sequence
    145      */
     171     * @deprecated Use {@link #getLastImage()} with {@link #setSelectedImage}
     172     */
     173    @Deprecated
    146174    public void selectLastImage() {
    147175        setSelectedImageIndex(data.size() - 1);
     
    167195
    168196    /**
     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    /**
    169209     * Select the next image of the sequence
    170      */
     210     * @deprecated Use {@link #getNextImage()} in conjunction with {@link #setSelectedImage}
     211     */
     212    @Deprecated
    171213    public void selectNextImage() {
    172214        if (hasNextImage()) {
     
    176218
    177219    /**
     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    /**
    178232     *  Check if there is a previous image in the sequence
    179233     * @return {@code true} is there is a previous image, {@code false} otherwise
     
    185239    /**
    186240     * Select the previous image of the sequence
    187      */
     241     * @deprecated Use {@link #getPreviousImage()} with {@link #setSelectedImage}
     242     */
     243    @Deprecated
    188244    public void selectPreviousImage() {
    189245        if (data.isEmpty()) {
  • trunk/src/org/openstreetmap/josm/data/gpx/GpxImageEntry.java

    r18120 r18246  
    44import static org.openstreetmap.josm.tools.I18n.tr;
    55
     6import java.awt.Dimension;
     7import java.awt.image.BufferedImage;
    68import java.io.File;
    79import java.io.IOException;
     
    1012import java.util.List;
    1113import java.util.Locale;
     14import java.util.Map;
    1215import java.util.Objects;
    1316import java.util.function.Consumer;
     17import java.util.stream.Stream;
     18
     19import javax.imageio.IIOParam;
    1420
    1521import org.openstreetmap.josm.data.IQuadBucketType;
    1622import org.openstreetmap.josm.data.coor.CachedLatLon;
    1723import org.openstreetmap.josm.data.coor.LatLon;
     24import org.openstreetmap.josm.data.imagery.street_level.Projections;
    1825import org.openstreetmap.josm.data.osm.BBox;
    1926import org.openstreetmap.josm.tools.ExifReader;
     
    3441import com.drew.metadata.iptc.IptcDirectory;
    3542import com.drew.metadata.jpeg.JpegDirectory;
     43import com.drew.metadata.xmp.XmpDirectory;
    3644
    3745/**
     
    4553    private Double exifImgDir;
    4654    private Instant exifTime;
     55    private Projections cameraProjection = Projections.UNKNOWN;
    4756    /**
    4857     * Flag isNewGpsData indicates that the GPS data of the image is new or has changed.
     
    478487            elevation, exifCoor, exifGpsTime, exifImgDir, exifOrientation, exifTime,
    479488            iptcCaption, iptcHeadline, iptcKeywords, iptcObjectName,
    480             file, gpsTime, pos, speed, tmp);
     489            file, gpsTime, pos, speed, tmp, cameraProjection);
    481490    }
    482491
     
    505514            && Objects.equals(pos, other.pos)
    506515            && Objects.equals(speed, other.speed)
    507             && Objects.equals(tmp, other.tmp);
     516            && Objects.equals(tmp, other.tmp)
     517            && cameraProjection == other.cameraProjection;
    508518    }
    509519
     
    754764            ifNotNull(ExifReader.readObjectName(dirIptc), this::setIptcObjectName);
    755765        }
     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());
    756787    }
    757788
     
    766797            setter.accept(value);
    767798        }
     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;
    768808    }
    769809
  • trunk/src/org/openstreetmap/josm/data/osm/FilterWorker.java

    r17867 r18246  
    3030     * @return true, if the filter state (normal / disabled / hidden) of any primitive has changed in the process
    3131     * @throws SearchParseError if the search expression in a filter cannot be parsed
    32      * @since 12383, xxx (generics)
     32     * @since 12383, 17862 (generics)
    3333     */
    3434    public static <T extends IPrimitive & IFilterablePrimitive> boolean executeFilters(Collection<T> all, Filter... filters)
  • trunk/src/org/openstreetmap/josm/data/osm/QuadBuckets.java

    r18208 r18246  
    2323 * This class is (no longer) thread safe.
    2424 * @param <T> type of object extending {@link IQuadBucketType}.
    25  * @since 2165 ({@link IPrimitive} only), xxx for {@link IQuadBucketType}
     25 * @since 2165 ({@link IPrimitive} only), 17459 for {@link IQuadBucketType}
    2626 */
    2727public class QuadBuckets<T extends IQuadBucketType> implements Collection<T> {
  • trunk/src/org/openstreetmap/josm/data/sources/SourceInfo.java

    r18211 r18246  
    490490     * @param clazz The class of the type of id
    491491     * @return sorted list of activated source IDs
    492      * @since 13536, xxx (extracted)
     492     * @since 13536, 16545 (extracted)
    493493     */
    494494    public static <W extends SourceInfo<?, ?, ?, ?>> Collection<String> getActiveIds(Class<W> clazz) {
  • trunk/src/org/openstreetmap/josm/gui/layer/geoimage/GeoImageLayer.java

    r18078 r18246  
    868868    @Override
    869869    public void jumpToNextMarker() {
    870         data.selectNextImage();
     870        data.setSelectedImage(data.getNextImage());
    871871    }
    872872
    873873    @Override
    874874    public void jumpToPreviousMarker() {
    875         data.selectPreviousImage();
     875        data.setSelectedImage(data.getPreviousImage());
    876876    }
    877877
  • trunk/src/org/openstreetmap/josm/gui/layer/geoimage/ImageDisplay.java

    r18217 r18246  
    1313import java.awt.Rectangle;
    1414import java.awt.RenderingHints;
     15import java.awt.event.ComponentEvent;
     16import java.awt.event.MouseAdapter;
    1517import java.awt.event.MouseEvent;
    16 import java.awt.event.MouseListener;
    17 import java.awt.event.MouseMotionListener;
    1818import java.awt.event.MouseWheelEvent;
    19 import java.awt.event.MouseWheelListener;
    2019import java.awt.geom.Rectangle2D;
    2120import java.awt.image.BufferedImage;
     
    2726import javax.swing.SwingUtilities;
    2827
     28import org.openstreetmap.josm.data.imagery.street_level.IImageEntry;
     29import org.openstreetmap.josm.data.imagery.street_level.Projections;
    2930import org.openstreetmap.josm.data.preferences.BooleanProperty;
    3031import org.openstreetmap.josm.data.preferences.DoubleProperty;
    3132import org.openstreetmap.josm.data.preferences.IntegerProperty;
    3233import org.openstreetmap.josm.gui.MainApplication;
     34import org.openstreetmap.josm.gui.layer.geoimage.viewers.projections.IImageViewer;
     35import org.openstreetmap.josm.gui.layer.geoimage.viewers.projections.ImageProjectionRegistry;
    3336import org.openstreetmap.josm.gui.layer.imagery.ImageryFilterSettings;
    3437import org.openstreetmap.josm.gui.layer.imagery.ImageryFilterSettings.FilterChangeListener;
     
    3942import org.openstreetmap.josm.tools.Destroyable;
    4043import org.openstreetmap.josm.tools.ImageProcessor;
     44import org.openstreetmap.josm.tools.JosmRuntimeException;
    4145import org.openstreetmap.josm.tools.Logging;
    4246import org.openstreetmap.josm.tools.Utils;
     
    5054public class ImageDisplay extends JComponent implements Destroyable, PreferenceChangedListener, FilterChangeListener {
    5155
     56    /** The current image viewer */
     57    private IImageViewer iImageViewer;
     58
    5259    /** The file that is currently displayed */
    53     private ImageEntry entry;
     60    private IImageEntry<?> entry;
    5461
    5562    /** The previous file that is currently displayed. Cleared on paint. Only used to help improve UI error information. */
    56     private ImageEntry oldEntry;
     63    private IImageEntry<?> oldEntry;
    5764
    5865    /** The image currently displayed */
     
    262269    protected class LoadImageRunnable implements Runnable {
    263270
    264         private final ImageEntry entry;
    265 
    266         LoadImageRunnable(ImageEntry entry) {
     271        private final IImageEntry<?> entry;
     272
     273        LoadImageRunnable(IImageEntry<?> entry) {
    267274            this.entry = entry;
    268275        }
     
    296303                    // This will clear the loading info box
    297304                    ImageDisplay.this.oldEntry = ImageDisplay.this.entry;
    298                     visibleRect = new VisRect(0, 0, width, height);
     305                    visibleRect = getIImageViewer(entry).getDefaultVisibleRectangle(ImageDisplay.this, image);
    299306
    300307                    selectedRect = null;
     
    308315    }
    309316
    310     private class ImgDisplayMouseListener implements MouseListener, MouseWheelListener, MouseMotionListener {
     317    private class ImgDisplayMouseListener extends MouseAdapter {
    311318
    312319        private MouseEvent lastMouseEvent;
     
    331338
    332339        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;
    336344
    337345            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;
    341350            }
    342351
    343352            selectedRect = null;
    344353
    345             if (image == null)
     354            if (currentImage == null)
    346355                return;
    347356
    348357            // Calculate the mouse cursor position in image coordinates to center the zoom.
    349358            if (refreshMousePointInImg)
    350                 mousePointInImg = comp2imgCoord(visibleRect, x, y, getSize());
     359                mousePointInImg = comp2imgCoord(currentVisibleRect, x, y, getSize());
    351360
    352361            // Apply the zoom to the visible rectangle in image coordinates
    353362            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());
    356365            } 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());
    359368            }
    360369
    361370            // 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);
    374376            } 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            }
    380379
    381380            // 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;
    385384
    386385            // The position is also limited by the image size
    387             visibleRect.checkRectPos();
     386            currentVisibleRect.checkRectPos();
    388387
    389388            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;
    392391                }
    393392            }
     
    417416        public void mouseClicked(MouseEvent e) {
    418417            // Move the center to the clicked point.
    419             ImageEntry entry;
    420             Image image;
    421             VisRect visibleRect;
     418            IImageEntry<?> currentEntry;
     419            Image currentImage;
     420            VisRect currentVisibleRect;
    422421
    423422            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)
    430429                return;
    431430
     
    434433                lastMouseEvent = null;
    435434
    436                 if (mouseIsZoomSelecting(e) && !isAtMaxZoom(visibleRect)) {
     435                if (mouseIsZoomSelecting(e) && !isAtMaxZoom(currentVisibleRect)) {
    437436                    // zoom in if clicked with the zoom button
    438437                    mouseWheelMovedImpl(e.getX(), e.getY(), -1, true);
     
    447446
    448447            // 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();
    456455
    457456            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;
    460459                }
    461460            }
     
    467466        @Override
    468467        public void mousePressed(MouseEvent e) {
    469             Image image;
    470             VisRect visibleRect;
     468            Image currentImage;
     469            VisRect currentVisibleRect;
    471470
    472471            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)
    478477                return;
    479478
     
    481480
    482481            if (mouseIsDragging(e) || mouseIsZoomSelecting(e))
    483                 mousePointInImg = comp2imgCoord(visibleRect, e.getX(), e.getY(), getSize());
     482                mousePointInImg = comp2imgCoord(currentVisibleRect, e.getX(), e.getY(), getSize());
    484483        }
    485484
     
    489488                return;
    490489
    491             ImageEntry entry;
    492             Image image;
    493             VisRect visibleRect;
     490            IImageEntry<?> imageEntry;
     491            Image currentImage;
     492            VisRect currentVisibleRect;
    494493
    495494            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)
    502501                return;
    503502
    504503            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();
    510507                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;
    513510                    }
    514511                }
     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                }
    515518                ImageDisplay.this.repaint();
    516519            }
    517520
    518521            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),
    524527                        p.x < mousePointInImg.x ? mousePointInImg.x - p.x : p.x - mousePointInImg.x,
    525528                        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;
    530533                ImageDisplay.this.repaint();
    531534            }
    532 
    533535        }
    534536
    535537        @Override
    536538        public void mouseReleased(MouseEvent e) {
    537             ImageEntry entry;
    538             Image image;
    539             VisRect visibleRect;
     539            IImageEntry<?> currentEntry;
     540            Image currentImage;
     541            VisRect currentVisibleRect;
    540542
    541543            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)
    548550                return;
    549551
    550552            if (mouseIsDragging(e)) {
    551                 visibleRect.isDragUpdate = false;
     553                currentVisibleRect.isDragUpdate = false;
    552554            }
    553555
     
    557559
    558560                // 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);
    574562
    575563                // Keep the center of the selection
     
    586574
    587575            synchronized (ImageDisplay.this) {
    588                 if (entry == ImageDisplay.this.entry) {
     576                if (currentEntry == ImageDisplay.this.entry) {
    589577                    if (selectedRect == null) {
    590                         ImageDisplay.this.visibleRect = visibleRect;
     578                        ImageDisplay.this.visibleRect = currentVisibleRect;
    591579                    } else {
    592580                        ImageDisplay.this.visibleRect.setBounds(selectedRect);
     
    597585            ImageDisplay.this.repaint();
    598586        }
    599 
    600         @Override
    601         public void mouseEntered(MouseEvent e) {
    602             // Do nothing
    603         }
    604 
    605         @Override
    606         public void mouseExited(MouseEvent e) {
    607             // Do nothing
    608         }
    609 
    610         @Override
    611         public void mouseMoved(MouseEvent e) {
    612             // Do nothing
    613         }
    614587    }
    615588
     
    618591     */
    619592    public ImageDisplay() {
    620         this(image -> image);
     593        this(imageObject -> imageObject);
    621594    }
    622595
     
    653626     * @param entry new source image
    654627     * @return a {@link Future} representing pending completion of the image loading task
    655      * @since 18150
    656      */
    657     public Future<?> setImage(ImageEntry entry) {
     628     * @since 18246 (signature)
     629     */
     630    public Future<?> setImage(IImageEntry<?> entry) {
    658631        LoadImageRunnable runnable = setImage0(entry);
    659632        return runnable != null ? MainApplication.worker.submit(runnable) : null;
    660633    }
    661634
    662     protected LoadImageRunnable setImage0(ImageEntry entry) {
     635    protected LoadImageRunnable setImage0(IImageEntry<?> entry) {
    663636        synchronized (this) {
    664637            this.oldEntry = this.entry;
     
    708681    private void updateProcessedImage() {
    709682        processedImage = image == null ? null : imageProcessor.process(image);
    710         GuiHelper.runInEDT(() -> repaint());
     683        GuiHelper.runInEDT(this::repaint);
    711684    }
    712685
     
    715688        super.paintComponent(g);
    716689
    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;
    722696
    723697        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;
    729703        }
    730704
     
    735709        Dimension size = getSize();
    736710        // 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());
    760720                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),
    763722                        (int) ((size.height - noImageSize.getHeight()) / 2));
    764723            }
    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) {
    794740        final String errorMessage;
    795741        // If the new entry is null, then there is no image.
    796         if (entry == null) {
     742        if (imageEntry == null) {
    797743            if (emptyText == null) {
    798744                emptyText = tr("No image");
    799745            }
    800746            errorMessage = emptyText;
    801         } else if (image == null || !Objects.equals(entry, oldEntry)) {
     747        } else if (bufferedImage == null || !Objects.equals(imageEntry, oldImageEntry)) {
    802748            // The image is not necessarily null when loading anymore. If the oldEntry is not the same as the new entry,
    803749            // 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());
    806752            } else {
    807                 errorMessage = tr("Error on file {0}", entry.getDisplayName());
     753                errorMessage = tr("Error on file {0}", imageEntry.getDisplayName());
    808754            }
    809755        } else {
     
    831777    }
    832778
     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
    833837    static Point img2compCoord(VisRect visibleRect, int xImg, int yImg, Dimension compSize) {
    834838        Rectangle drawRect = calculateDrawImageRectangle(visibleRect, compSize);
     
    854858    }
    855859
     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     */
    856867    static VisRect calculateDrawImageRectangle(VisRect visibleRect, Dimension compSize) {
    857868        return calculateDrawImageRectangle(visibleRect, new Rectangle(0, 0, compSize.width, compSize.height));
     
    907918     */
    908919    public void zoomBestFitOrOne() {
    909         ImageEntry entry;
    910         Image image;
    911         VisRect visibleRect;
     920        IImageEntry<?> currentEntry;
     921        Image currentImage;
     922        VisRect currentVisibleRect;
    912923
    913924        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)
    920931            return;
    921932
    922         if (visibleRect.width != image.getWidth(null) || visibleRect.height != image.getHeight(null)) {
     933        if (currentVisibleRect.width != currentImage.getWidth(null) || currentVisibleRect.height != currentImage.getHeight(null)) {
    923934            // The display is not at best fit. => Zoom to best fit
    924             visibleRect.reset();
     935            currentVisibleRect.reset();
    925936        } else {
    926937            // 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,
    929940                    getWidth(), getHeight());
    930             visibleRect.checkRectSize();
    931             visibleRect.checkRectPos();
     941            currentVisibleRect.checkRectSize();
     942            currentVisibleRect.checkRectPos();
    932943        }
    933944
    934945        synchronized (this) {
    935             if (this.entry == entry) {
    936                 this.visibleRect = visibleRect;
     946            if (this.entry == currentEntry) {
     947                this.visibleRect = currentVisibleRect;
    937948            }
    938949        }
    939950        repaint();
    940951    }
     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    }
    9411032}
  • trunk/src/org/openstreetmap/josm/gui/layer/geoimage/ImageEntry.java

    r18108 r18246  
    1616import java.util.Collections;
    1717import java.util.Objects;
    18 
    1918import javax.imageio.IIOParam;
    2019import javax.imageio.ImageReadParam;
     
    2322import org.openstreetmap.josm.data.ImageData;
    2423import org.openstreetmap.josm.data.gpx.GpxImageEntry;
     24import org.openstreetmap.josm.data.imagery.street_level.IImageEntry;
    2525import org.openstreetmap.josm.tools.ExifReader;
    2626import org.openstreetmap.josm.tools.ImageProvider;
     
    3131 * @since 2662
    3232 */
    33 public class ImageEntry extends GpxImageEntry {
     33public class ImageEntry extends GpxImageEntry implements IImageEntry<ImageEntry> {
    3434
    3535    private Image thumbnail;
     
    136136    }
    137137
     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
    138193    /**
    139194     * Reads the image represented by this entry in the given target dimension.
     
    142197     * @throws IOException if any I/O error occurs
    143198     */
     199    @Override
    144200    public BufferedImage read(Dimension target) throws IOException {
    145201        URL imageUrl = getImageUrl();
  • trunk/src/org/openstreetmap/josm/gui/layer/geoimage/ImageViewerDialog.java

    r18150 r18246  
    1616import java.time.format.DateTimeFormatter;
    1717import java.time.format.FormatStyle;
     18import java.util.ArrayList;
    1819import java.util.Collections;
    1920import java.util.List;
    2021import java.util.Optional;
    2122import java.util.concurrent.Future;
    22 
     23import java.util.stream.Collectors;
    2324import javax.swing.AbstractAction;
    2425import javax.swing.Box;
     
    3334import org.openstreetmap.josm.data.ImageData;
    3435import org.openstreetmap.josm.data.ImageData.ImageDataUpdateListener;
     36import org.openstreetmap.josm.data.imagery.street_level.IImageEntry;
    3537import org.openstreetmap.josm.gui.ExtendedDialog;
    3638import org.openstreetmap.josm.gui.MainApplication;
    3739import org.openstreetmap.josm.gui.datatransfer.ClipboardUtils;
    38 import org.openstreetmap.josm.gui.dialogs.DialogsPanel.Action;
     40import org.openstreetmap.josm.gui.dialogs.DialogsPanel;
    3941import org.openstreetmap.josm.gui.dialogs.ToggleDialog;
    4042import org.openstreetmap.josm.gui.dialogs.layer.LayerVisibilityAction;
     
    5052import org.openstreetmap.josm.tools.Logging;
    5153import org.openstreetmap.josm.tools.Shortcut;
    52 import org.openstreetmap.josm.tools.Utils;
    5354import org.openstreetmap.josm.tools.date.DateUtils;
    5455
     
    221222        @Override
    222223        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);
    225226            }
    226227        }
     
    236237        @Override
    237238        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);
    240241            }
    241242        }
     
    251252        @Override
    252253        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);
    255256            }
    256257        }
     
    266267        @Override
    267268        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);
    270271            }
    271272        }
     
    309310        @Override
    310311        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                }
    313317            }
    314318        }
     
    325329        @Override
    326330        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);
    329335                int size = toDelete.size();
    330336
     
    347353
    348354                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()) {
    352359                            Logging.info("File {0} deleted.", delete.getFile());
    353360                        } else {
     
    360367                        }
    361368                    }
    362                     currentData.notifyImageUpdate();
    363                     currentData.updateSelectedImage();
     369                    imageDataCollection.forEach(data -> {
     370                        data.notifyImageUpdate();
     371                        data.updateSelectedImage();
     372                    });
    364373                }
    365374            }
     
    376385        @Override
    377386        public void actionPerformed(ActionEvent e) {
    378             if (currentData != null) {
    379                 ClipboardUtils.copyString(String.valueOf(currentData.getSelectedImage().getFile()));
     387            if (currentEntry != null) {
     388                ClipboardUtils.copyString(String.valueOf(currentEntry.getFile()));
    380389            }
    381390        }
     
    426435    }
    427436
    428     private transient ImageData currentData;
    429     private transient ImageEntry currentEntry;
     437    private transient IImageEntry<?> currentEntry;
    430438
    431439    /**
    432440     * Displays a single image for the given layer.
    433      * @param data the image data
     441     * @param ignoredData the image data
    434442     * @param entry image entry
    435443     * @see #displayImages
    436444     */
    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));
    439456    }
    440457
    441458    /**
    442459     * Displays images for the given layer.
    443      * @param data the image data
    444460     * @param entries image entries
    445      * @since 15333
    446      */
    447     public void displayImages(ImageData data, List<ImageEntry> entries) {
     461     * @since 18246
     462     */
     463    public void displayImages(List<IImageEntry<?>> entries) {
    448464        boolean imageChanged;
    449         ImageEntry entry = entries != null && entries.size() == 1 ? entries.get(0) : null;
     465        IImageEntry<?> entry = entries != null && entries.size() == 1 ? entries.get(0) : null;
    450466
    451467        synchronized (this) {
     
    458474            }
    459475
    460             currentData = data;
    461476            currentEntry = entry;
    462477        }
    463478
    464479        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);
    506481        } 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);
    525483            return;
    526484        }
    527485        if (!isDialogShowing()) {
    528             setIsDocked(false);     // always open a detached window when an image is clicked and dialog is closed
     486            setIsDocked(false); // always open a detached window when an image is clicked and dialog is closed
    529487            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()));
    544586    }
    545587
     
    576618     * Returns the currently displayed image.
    577619     * @return Currently displayed image or {@code null}
    578      * @since 6392
    579      */
    580     public static ImageEntry getCurrentImage() {
     620     * @since 18246 (signature)
     621     */
     622    public static IImageEntry<?> getCurrentImage() {
    581623        return getInstance().currentEntry;
    582624    }
     
    599641    @Override
    600642    public void layerRemoving(LayerRemoveEvent e) {
    601         if (e.getRemovedLayer() instanceof GeoImageLayer) {
     643        if (e.getRemovedLayer() instanceof GeoImageLayer && this.currentEntry instanceof ImageEntry) {
    602644            ImageData removedData = ((GeoImageLayer) e.getRemovedLayer()).getImageData();
    603             if (removedData == currentData) {
    604                 displayImages(null, null);
     645            if (removedData == ((ImageEntry) this.currentEntry).getDataSet()) {
     646                displayImages(null);
    605647            }
    606648            removedData.removeImageDataUpdateListener(this);
     
    627669
    628670    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());
    631674        }
    632675    }
     
    641684    @Override
    642685    public void selectedImageChanged(ImageData data) {
    643         displayImages(data, data.getSelectedImages());
     686        displayImages(new ArrayList<>(data.getSelectedImages()));
    644687    }
    645688
    646689    @Override
    647690    public void imageDataUpdated(ImageData data) {
    648         displayImages(data, data.getSelectedImages());
     691        displayImages(new ArrayList<>(data.getSelectedImages()));
    649692    }
    650693}
Note: See TracChangeset for help on using the changeset viewer.