Changeset 18246 in josm


Ignore:
Timestamp:
2021-10-04T16:05:23+02:00 (5 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
Files:
17 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.