Ticket #21469: 21469.2.patch

File 21469.2.patch, 20.6 KB (added by taylor.smock, 3 years ago)

Fix NPE

  • src/org/openstreetmap/josm/data/imagery/street_level/IImageEntry.java

    diff --git a/src/org/openstreetmap/josm/data/imagery/street_level/IImageEntry.java b/src/org/openstreetmap/josm/data/imagery/street_level/IImageEntry.java
    index 1a23e094f8..f2ee9ab8b5 100644
    a b public interface IImageEntry<I extends IImageEntry<I>> {  
    2525     * @param imageViewerDialog The image viewer to update
    2626     */
    2727    default void selectNextImage(final ImageViewerDialog imageViewerDialog) {
    28         imageViewerDialog.displayImage(this.getNextImage());
     28        this.selectImage(imageViewerDialog, this.getNextImage());
    2929    }
    3030
    3131    /**
    public interface IImageEntry<I extends IImageEntry<I>> {  
    3939     * @param imageViewerDialog The image viewer to update
    4040     */
    4141    default void selectPreviousImage(final ImageViewerDialog imageViewerDialog) {
    42         imageViewerDialog.displayImage(this.getPreviousImage());
     42        this.selectImage(imageViewerDialog, this.getPreviousImage());
    4343    }
    4444
    4545    /**
    public interface IImageEntry<I extends IImageEntry<I>> {  
    5353     * @param imageViewerDialog The image viewer to update
    5454     */
    5555    default void selectFirstImage(final ImageViewerDialog imageViewerDialog) {
    56         imageViewerDialog.displayImage(this.getFirstImage());
     56        this.selectImage(imageViewerDialog, this.getFirstImage());
    5757    }
    5858
    5959    /**
    public interface IImageEntry<I extends IImageEntry<I>> {  
    6767     * @param imageViewerDialog The image viewer to update
    6868     */
    6969    default void selectLastImage(final ImageViewerDialog imageViewerDialog) {
    70         imageViewerDialog.displayImage(this.getLastImage());
     70        this.selectImage(imageViewerDialog, this.getLastImage());
     71    }
     72
     73    /**
     74     * Select a specific image
     75     * @param imageViewerDialog The image viewer to update
     76     * @param entry The image to select
     77     * @since xxx
     78     */
     79    default void selectImage(final ImageViewerDialog imageViewerDialog, final IImageEntry<?> entry) {
     80        imageViewerDialog.displayImage(entry);
    7181    }
    7282
    7383    /**
  • src/org/openstreetmap/josm/gui/layer/geoimage/ImageEntry.java

    diff --git a/src/org/openstreetmap/josm/gui/layer/geoimage/ImageEntry.java b/src/org/openstreetmap/josm/gui/layer/geoimage/ImageEntry.java
    index 7ea6371f62..0f227a37e6 100644
    a b public class ImageEntry extends GpxImageEntry implements IImageEntry<ImageEntry>  
    141141        return this.dataSet.getNextImage();
    142142    }
    143143
    144     @Override
    145     public void selectNextImage(final ImageViewerDialog imageViewerDialog) {
    146         IImageEntry.super.selectNextImage(imageViewerDialog);
    147         this.dataSet.setSelectedImage(this.getNextImage());
    148     }
    149 
    150144    @Override
    151145    public ImageEntry getPreviousImage() {
    152146        return this.dataSet.getPreviousImage();
    153147    }
    154148
    155     @Override
    156     public void selectPreviousImage(ImageViewerDialog imageViewerDialog) {
    157         IImageEntry.super.selectPreviousImage(imageViewerDialog);
    158         this.dataSet.setSelectedImage(this.getPreviousImage());
    159     }
    160 
    161149    @Override
    162150    public ImageEntry getFirstImage() {
    163151        return this.dataSet.getFirstImage();
    164152    }
    165153
    166154    @Override
    167     public void selectFirstImage(ImageViewerDialog imageViewerDialog) {
    168         IImageEntry.super.selectFirstImage(imageViewerDialog);
    169         this.dataSet.setSelectedImage(this.getFirstImage());
     155    public void selectImage(ImageViewerDialog imageViewerDialog, IImageEntry<?> entry) {
     156        IImageEntry.super.selectImage(imageViewerDialog, entry);
     157        if (entry instanceof ImageEntry) {
     158            this.dataSet.setSelectedImage((ImageEntry) entry);
     159        }
    170160    }
    171161
    172162    @Override
    public class ImageEntry extends GpxImageEntry implements IImageEntry<ImageEntry>  
    174164        return this.dataSet.getLastImage();
    175165    }
    176166
    177     @Override
    178     public void selectLastImage(ImageViewerDialog imageViewerDialog) {
    179         IImageEntry.super.selectLastImage(imageViewerDialog);
    180         this.dataSet.setSelectedImage(this.getLastImage());
    181     }
    182 
    183167    @Override
    184168    public boolean isRemoveSupported() {
    185169        return true;
  • src/org/openstreetmap/josm/gui/layer/geoimage/ImageViewerDialog.java

    diff --git a/src/org/openstreetmap/josm/gui/layer/geoimage/ImageViewerDialog.java b/src/org/openstreetmap/josm/gui/layer/geoimage/ImageViewerDialog.java
    index 209a549158..85f3750244 100644
    a b  
    11// License: GPL. For details, see LICENSE file.
    22package org.openstreetmap.josm.gui.layer.geoimage;
    33
     4import static org.openstreetmap.josm.tools.I18n.marktr;
    45import static org.openstreetmap.josm.tools.I18n.tr;
    56import static org.openstreetmap.josm.tools.I18n.trn;
    67
    import java.awt.GridBagLayout;  
    1213import java.awt.event.ActionEvent;
    1314import java.awt.event.KeyEvent;
    1415import java.awt.event.WindowEvent;
     16import java.io.Serializable;
    1517import java.time.ZoneOffset;
    1618import java.time.format.DateTimeFormatter;
    1719import java.time.format.FormatStyle;
    1820import java.util.ArrayList;
     21import java.util.Arrays;
    1922import java.util.Collections;
    2023import java.util.List;
     24import java.util.Objects;
    2125import java.util.Optional;
    2226import java.util.concurrent.Future;
     27import java.util.function.UnaryOperator;
    2328import java.util.stream.Collectors;
    2429
    2530import javax.swing.AbstractAction;
    import org.openstreetmap.josm.tools.date.DateUtils;  
    5964 * Dialog to view and manipulate geo-tagged images from a {@link GeoImageLayer}.
    6065 */
    6166public final class ImageViewerDialog extends ToggleDialog implements LayerChangeListener, ActiveLayerChangeListener, ImageDataUpdateListener {
     67    private static final String GEOIMAGE_FILLER = marktr("Geoimage: {0}");
     68    private static final String DIALOG_FOLDER = "dialogs";
    6269
    6370    private final ImageryFilterSettings imageryFilterSettings = new ImageryFilterSettings();
    6471
    public final class ImageViewerDialog extends ToggleDialog implements LayerChange  
    131138    private static JButton createNavigationButton(AbstractAction action, Dimension buttonDim) {
    132139        JButton btn = createButton(action, buttonDim);
    133140        btn.setEnabled(false);
     141        action.addPropertyChangeListener(l -> {
     142            if ("enabled".equals(l.getPropertyName())) {
     143                btn.setEnabled(action.isEnabled());
     144            }
     145        });
    134146        return btn;
    135147    }
    136148
    public final class ImageViewerDialog extends ToggleDialog implements LayerChange  
    215227        dialog = null;
    216228    }
    217229
    218     private class ImageNextAction extends JosmAction {
    219         ImageNextAction() {
    220             super(null, new ImageProvider("dialogs", "next"), tr("Next"), Shortcut.registerShortcut(
    221                     "geoimage:next", tr("Geoimage: {0}", tr("Show next Image")), KeyEvent.VK_PAGE_DOWN, Shortcut.DIRECT),
    222                   false, null, false);
     230    /**
     231     * This literally exists to silence sonarlint complaints.
     232     */
     233    @FunctionalInterface
     234    private interface SerializableUnaryOperator<I> extends UnaryOperator<I>, Serializable {
     235    }
     236
     237    private abstract class ImageAction extends JosmAction {
     238        final SerializableUnaryOperator<IImageEntry<?>> supplier;
     239        ImageAction(String name, ImageProvider icon, String tooltip, Shortcut shortcut,
     240                boolean registerInToolbar, String toolbarId, boolean installAdaptors,
     241                final SerializableUnaryOperator<IImageEntry<?>> supplier) {
     242            super(name, icon, tooltip, shortcut, registerInToolbar, toolbarId, installAdaptors);
     243            Objects.requireNonNull(supplier);
     244            this.supplier = supplier;
    223245        }
    224246
    225247        @Override
    226         public void actionPerformed(ActionEvent e) {
    227             if (ImageViewerDialog.this.currentEntry != null) {
    228                 ImageViewerDialog.this.currentEntry.selectNextImage(ImageViewerDialog.this);
     248        public void actionPerformed(ActionEvent event) {
     249            final IImageEntry<?> entry = ImageViewerDialog.this.currentEntry;
     250            if (entry != null) {
     251                IImageEntry<?> nextEntry = this.getSupplier().apply(entry);
     252                entry.selectImage(ImageViewerDialog.this, nextEntry);
     253            }
     254            this.resetRememberActions();
     255        }
     256
     257        void resetRememberActions() {
     258            for (ImageRememberAction action : Arrays.asList(ImageViewerDialog.this.imageLastAction, ImageViewerDialog.this.imageFirstAction)) {
     259                action.last = null;
     260                action.updateEnabledState();
    229261            }
    230262        }
     263
     264        SerializableUnaryOperator<IImageEntry<?>> getSupplier() {
     265            return this.supplier;
     266        }
     267
     268        @Override
     269        protected void updateEnabledState() {
     270            final IImageEntry<?> entry = ImageViewerDialog.this.currentEntry;
     271            this.setEnabled(entry != null && this.getSupplier().apply(entry) != null);
     272        }
    231273    }
    232274
    233     private class ImagePreviousAction extends JosmAction {
     275    private class ImageNextAction extends ImageAction {
     276        ImageNextAction() {
     277            super(null, new ImageProvider(DIALOG_FOLDER, "next"), tr("Next"), Shortcut.registerShortcut(
     278                    "geoimage:next", tr(GEOIMAGE_FILLER, tr("Show next Image")), KeyEvent.VK_PAGE_DOWN, Shortcut.DIRECT),
     279                  false, null, false, IImageEntry::getNextImage);
     280        }
     281    }
     282
     283    private class ImagePreviousAction extends ImageAction {
    234284        ImagePreviousAction() {
    235             super(null, new ImageProvider("dialogs", "previous"), tr("Previous"), Shortcut.registerShortcut(
    236                     "geoimage:previous", tr("Geoimage: {0}", tr("Show previous Image")), KeyEvent.VK_PAGE_UP, Shortcut.DIRECT),
    237                   false, null, false);
     285            super(null, new ImageProvider(DIALOG_FOLDER, "previous"), tr("Previous"), Shortcut.registerShortcut(
     286                    "geoimage:previous", tr(GEOIMAGE_FILLER, tr("Show previous Image")), KeyEvent.VK_PAGE_UP, Shortcut.DIRECT),
     287                  false, null, false, IImageEntry::getPreviousImage);
     288        }
     289    }
     290
     291    /** This class exists to remember the last entry, and go back if clicked again when it would not otherwise be enabled */
     292    private abstract class ImageRememberAction extends ImageAction {
     293        private final ImageProvider defaultIcon;
     294        transient IImageEntry<?> last;
     295        ImageRememberAction(String name, ImageProvider icon, String tooltip, Shortcut shortcut,
     296                boolean registerInToolbar, String toolbarId, boolean installAdaptors, SerializableUnaryOperator<IImageEntry<?>> supplier) {
     297            super(name, icon, tooltip, shortcut, registerInToolbar, toolbarId, installAdaptors, supplier);
     298            this.defaultIcon = icon;
     299        }
     300
     301        public void updateIcon() {
     302            if (this.last != null) {
     303                new ImageProvider(DIALOG_FOLDER, "history").getResource().attachImageIcon(this, true);
     304            } else {
     305                this.defaultIcon.getResource().attachImageIcon(this, true);
     306            }
    238307        }
    239308
    240309        @Override
    241         public void actionPerformed(ActionEvent e) {
    242             if (ImageViewerDialog.this.currentEntry != null) {
    243                 ImageViewerDialog.this.currentEntry.selectPreviousImage(ImageViewerDialog.this);
     310        public void actionPerformed(ActionEvent event) {
     311            final IImageEntry<?> current = ImageViewerDialog.this.currentEntry;
     312            final IImageEntry<?> expected = this.supplier.apply(current);
     313            if (current != null) {
     314                IImageEntry<?> nextEntry = this.getSupplier().apply(current);
     315                current.selectImage(ImageViewerDialog.this, nextEntry);
     316            }
     317            this.resetRememberActions();
     318            if (!Objects.equals(current, expected)) {
     319                this.last = current;
     320            } else {
     321                this.last = null;
    244322            }
     323            this.updateEnabledState();
    245324        }
    246     }
    247325
    248     private class ImageFirstAction extends JosmAction {
    249         ImageFirstAction() {
    250             super(null, new ImageProvider("dialogs", "first"), tr("First"), Shortcut.registerShortcut(
    251                     "geoimage:first", tr("Geoimage: {0}", tr("Show first Image")), KeyEvent.VK_HOME, Shortcut.DIRECT),
    252                   false, null, false);
     326        @Override
     327        protected void updateEnabledState() {
     328            final IImageEntry<?> current = ImageViewerDialog.this.currentEntry;
     329            final IImageEntry<?> nextEntry = current != null ? this.getSupplier().apply(current) : null;
     330            if (this.last == null && nextEntry != null && nextEntry.equals(current)) {
     331                this.setEnabled(false);
     332            } else {
     333                super.updateEnabledState();
     334            }
     335            this.updateIcon();
    253336        }
    254337
    255338        @Override
    256         public void actionPerformed(ActionEvent e) {
    257             if (ImageViewerDialog.this.currentEntry != null) {
    258                 ImageViewerDialog.this.currentEntry.selectFirstImage(ImageViewerDialog.this);
     339        SerializableUnaryOperator<IImageEntry<?>> getSupplier() {
     340            if (this.last != null) {
     341                return entry -> this.last;
    259342            }
     343            return super.getSupplier();
    260344        }
    261345    }
    262346
    263     private class ImageLastAction extends JosmAction {
    264         ImageLastAction() {
    265             super(null, new ImageProvider("dialogs", "last"), tr("Last"), Shortcut.registerShortcut(
    266                     "geoimage:last", tr("Geoimage: {0}", tr("Show last Image")), KeyEvent.VK_END, Shortcut.DIRECT),
    267                   false, null, false);
     347    private class ImageFirstAction extends ImageRememberAction {
     348        ImageFirstAction() {
     349            super(null, new ImageProvider(DIALOG_FOLDER, "first"), tr("First"), Shortcut.registerShortcut(
     350                    "geoimage:first", tr(GEOIMAGE_FILLER, tr("Show first Image")), KeyEvent.VK_HOME, Shortcut.DIRECT),
     351                  false, null, false, IImageEntry::getFirstImage);
    268352        }
     353    }
    269354
    270         @Override
    271         public void actionPerformed(ActionEvent e) {
    272             if (ImageViewerDialog.this.currentEntry != null) {
    273                 ImageViewerDialog.this.currentEntry.selectLastImage(ImageViewerDialog.this);
    274             }
     355    private class ImageLastAction extends ImageRememberAction {
     356        ImageLastAction() {
     357            super(null, new ImageProvider(DIALOG_FOLDER, "last"), tr("Last"), Shortcut.registerShortcut(
     358                    "geoimage:last", tr(GEOIMAGE_FILLER, tr("Show last Image")), KeyEvent.VK_END, Shortcut.DIRECT),
     359                  false, null, false, IImageEntry::getLastImage);
    275360        }
    276361    }
    277362
    public final class ImageViewerDialog extends ToggleDialog implements LayerChange  
    293378
    294379    private class ImageZoomAction extends JosmAction {
    295380        ImageZoomAction() {
    296             super(null, new ImageProvider("dialogs", "zoom-best-fit"), tr("Zoom best fit and 1:1"), null,
     381            super(null, new ImageProvider(DIALOG_FOLDER, "zoom-best-fit"), tr("Zoom best fit and 1:1"), null,
    297382                  false, null, false);
    298383        }
    299384
    public final class ImageViewerDialog extends ToggleDialog implements LayerChange  
    305390
    306391    private class ImageRemoveAction extends JosmAction {
    307392        ImageRemoveAction() {
    308             super(null, new ImageProvider("dialogs", "delete"), tr("Remove photo from layer"), Shortcut.registerShortcut(
    309                     "geoimage:deleteimagefromlayer", tr("Geoimage: {0}", tr("Remove photo from layer")), KeyEvent.VK_DELETE, Shortcut.SHIFT),
     393            super(null, new ImageProvider(DIALOG_FOLDER, "delete"), tr("Remove photo from layer"), Shortcut.registerShortcut(
     394                    "geoimage:deleteimagefromlayer", tr(GEOIMAGE_FILLER, tr("Remove photo from layer")), KeyEvent.VK_DELETE, Shortcut.SHIFT),
    310395                  false, null, false);
    311396        }
    312397
    public final class ImageViewerDialog extends ToggleDialog implements LayerChange  
    323408
    324409    private class ImageRemoveFromDiskAction extends JosmAction {
    325410        ImageRemoveFromDiskAction() {
    326             super(null, new ImageProvider("dialogs", "geoimage/deletefromdisk"), tr("Delete image file from disk"),
     411            super(null, new ImageProvider(DIALOG_FOLDER, "geoimage/deletefromdisk"), tr("Delete image file from disk"),
    327412                    Shortcut.registerShortcut("geoimage:deletefilefromdisk",
    328                             tr("Geoimage: {0}", tr("Delete image file from disk")), KeyEvent.VK_DELETE, Shortcut.CTRL_SHIFT),
     413                            tr(GEOIMAGE_FILLER, tr("Delete image file from disk")), KeyEvent.VK_DELETE, Shortcut.CTRL_SHIFT),
    329414                    false, null, false);
    330415        }
    331416
    public final class ImageViewerDialog extends ToggleDialog implements LayerChange  
    383468    private class ImageCopyPathAction extends JosmAction {
    384469        ImageCopyPathAction() {
    385470            super(null, new ImageProvider("copy"), tr("Copy image path"), Shortcut.registerShortcut(
    386                     "geoimage:copypath", tr("Geoimage: {0}", tr("Copy image path")), KeyEvent.VK_C, Shortcut.ALT_CTRL_SHIFT),
     471                    "geoimage:copypath", tr(GEOIMAGE_FILLER, tr("Copy image path")), KeyEvent.VK_C, Shortcut.ALT_CTRL_SHIFT),
    387472                  false, null, false);
    388473        }
    389474
    public final class ImageViewerDialog extends ToggleDialog implements LayerChange  
    397482
    398483    private class ImageCollapseAction extends JosmAction {
    399484        ImageCollapseAction() {
    400             super(null, new ImageProvider("dialogs", "collapse"), tr("Move dialog to the side pane"), null,
     485            super(null, new ImageProvider(DIALOG_FOLDER, "collapse"), tr("Move dialog to the side pane"), null,
    401486                  false, null, false);
    402487        }
    403488
    public final class ImageViewerDialog extends ToggleDialog implements LayerChange  
    413498     * @param value {@code true} to enable the button, {@code false} otherwise
    414499     */
    415500    public void setPreviousEnabled(boolean value) {
    416         btnFirst.setEnabled(value);
     501        this.imageFirstAction.updateEnabledState();
     502        this.btnFirst.setEnabled(value || this.imageFirstAction.isEnabled());
    417503        btnPrevious.setEnabled(value);
    418504    }
    419505
    public final class ImageViewerDialog extends ToggleDialog implements LayerChange  
    423509     */
    424510    public void setNextEnabled(boolean value) {
    425511        btnNext.setEnabled(value);
    426         btnLast.setEnabled(value);
     512        this.imageLastAction.updateEnabledState();
     513        this.btnLast.setEnabled(value || this.imageLastAction.isEnabled());
    427514    }
    428515
    429516    /**
    public final class ImageViewerDialog extends ToggleDialog implements LayerChange  
    439526        return wasEnabled;
    440527    }
    441528
    442     private transient IImageEntry<?> currentEntry;
     529    private transient IImageEntry<? extends IImageEntry<?>> currentEntry;
    443530
    444531    /**
    445532     * Displays a single image for the given layer.
    public final class ImageViewerDialog extends ToggleDialog implements LayerChange  
    479566            }
    480567
    481568            currentEntry = entry;
     569
     570            for (ImageAction action : Arrays.asList(this.imageFirstAction, this.imagePreviousAction,
     571                    this.imageNextAction, this.imageLastAction)) {
     572                action.updateEnabledState();
     573            }
    482574        }
    483575
    484576        if (entry != null) {
    public final class ImageViewerDialog extends ToggleDialog implements LayerChange  
    527619     * @param imageChanged {@code true} if it is not the same image as the previous image.
    528620     */
    529621    private void updateButtonsNonNullEntry(IImageEntry<?> entry, boolean imageChanged) {
    530         setNextEnabled(entry.getNextImage() != null);
    531         setPreviousEnabled(entry.getPreviousImage() != null);
    532         btnDelete.setEnabled(true);
    533         btnDeleteFromDisk.setEnabled(entry.getFile() != null);
    534         btnCopyPath.setEnabled(true);
    535 
    536622        if (imageChanged) {
    537623            cancelLoadingImage();
    538624            // Set only if the image is new to preserve zoom and position if the same image is redisplayed
    539625            // (e.g. to update the OSD).
    540626            imgLoadingFuture = imgDisplay.setImage(entry);
    541627        }
     628
     629        // Update buttons after setting the new entry
     630        setNextEnabled(entry.getNextImage() != null);
     631        setPreviousEnabled(entry.getPreviousImage() != null);
     632        btnDelete.setEnabled(entry.isRemoveSupported());
     633        btnDeleteFromDisk.setEnabled(entry.isDeleteSupported() && entry.isRemoveSupported());
     634        btnCopyPath.setEnabled(true);
     635
    542636        setTitle(tr("Geotagged Images") + (!entry.getDisplayName().isEmpty() ? " - " + entry.getDisplayName() : ""));
    543637        StringBuilder osd = new StringBuilder(entry.getDisplayName());
    544638        if (entry.getElevation() != null) {