Ticket #21469: 21469.patch

File 21469.patch, 20.5 KB (added by taylor.smock, 3 years ago)

Remember last image selected for first/last buttons

  • 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..008bdd32fc 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            this.setEnabled(this.getSupplier().apply(ImageViewerDialog.this.currentEntry) != null);
     271        }
    231272    }
    232273
    233     private class ImagePreviousAction extends JosmAction {
     274    private class ImageNextAction extends ImageAction {
     275        ImageNextAction() {
     276            super(null, new ImageProvider(DIALOG_FOLDER, "next"), tr("Next"), Shortcut.registerShortcut(
     277                    "geoimage:next", tr(GEOIMAGE_FILLER, tr("Show next Image")), KeyEvent.VK_PAGE_DOWN, Shortcut.DIRECT),
     278                  false, null, false, IImageEntry::getNextImage);
     279        }
     280    }
     281
     282    private class ImagePreviousAction extends ImageAction {
    234283        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);
     284            super(null, new ImageProvider(DIALOG_FOLDER, "previous"), tr("Previous"), Shortcut.registerShortcut(
     285                    "geoimage:previous", tr(GEOIMAGE_FILLER, tr("Show previous Image")), KeyEvent.VK_PAGE_UP, Shortcut.DIRECT),
     286                  false, null, false, IImageEntry::getPreviousImage);
     287        }
     288    }
     289
     290    /** This class exists to remember the last entry, and go back if clicked again when it would not otherwise be enabled */
     291    private abstract class ImageRememberAction extends ImageAction {
     292        private final ImageProvider defaultIcon;
     293        transient IImageEntry<?> last;
     294        ImageRememberAction(String name, ImageProvider icon, String tooltip, Shortcut shortcut,
     295                boolean registerInToolbar, String toolbarId, boolean installAdaptors, SerializableUnaryOperator<IImageEntry<?>> supplier) {
     296            super(name, icon, tooltip, shortcut, registerInToolbar, toolbarId, installAdaptors, supplier);
     297            this.defaultIcon = icon;
     298        }
     299
     300        public void updateIcon() {
     301            if (this.last != null) {
     302                new ImageProvider(DIALOG_FOLDER, "history").getResource().attachImageIcon(this, true);
     303            } else {
     304                this.defaultIcon.getResource().attachImageIcon(this, true);
     305            }
    238306        }
    239307
    240308        @Override
    241         public void actionPerformed(ActionEvent e) {
    242             if (ImageViewerDialog.this.currentEntry != null) {
    243                 ImageViewerDialog.this.currentEntry.selectPreviousImage(ImageViewerDialog.this);
     309        public void actionPerformed(ActionEvent event) {
     310            final IImageEntry<?> current = ImageViewerDialog.this.currentEntry;
     311            final IImageEntry<?> expected = this.supplier.apply(current);
     312            if (current != null) {
     313                IImageEntry<?> nextEntry = this.getSupplier().apply(current);
     314                current.selectImage(ImageViewerDialog.this, nextEntry);
     315            }
     316            this.resetRememberActions();
     317            if (!Objects.equals(current, expected)) {
     318                this.last = current;
     319            } else {
     320                this.last = null;
    244321            }
     322            this.updateEnabledState();
    245323        }
    246     }
    247324
    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);
     325        @Override
     326        protected void updateEnabledState() {
     327            final IImageEntry<?> current = ImageViewerDialog.this.currentEntry;
     328            final IImageEntry<?> nextEntry = this.getSupplier().apply(current);
     329            if (this.last == null && nextEntry != null && nextEntry.equals(current)) {
     330                this.setEnabled(false);
     331            } else {
     332                super.updateEnabledState();
     333            }
     334            this.updateIcon();
    253335        }
    254336
    255337        @Override
    256         public void actionPerformed(ActionEvent e) {
    257             if (ImageViewerDialog.this.currentEntry != null) {
    258                 ImageViewerDialog.this.currentEntry.selectFirstImage(ImageViewerDialog.this);
     338        SerializableUnaryOperator<IImageEntry<?>> getSupplier() {
     339            if (this.last != null) {
     340                return entry -> this.last;
    259341            }
     342            return super.getSupplier();
    260343        }
    261344    }
    262345
    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);
     346    private class ImageFirstAction extends ImageRememberAction {
     347        ImageFirstAction() {
     348            super(null, new ImageProvider(DIALOG_FOLDER, "first"), tr("First"), Shortcut.registerShortcut(
     349                    "geoimage:first", tr(GEOIMAGE_FILLER, tr("Show first Image")), KeyEvent.VK_HOME, Shortcut.DIRECT),
     350                  false, null, false, IImageEntry::getFirstImage);
    268351        }
     352    }
    269353
    270         @Override
    271         public void actionPerformed(ActionEvent e) {
    272             if (ImageViewerDialog.this.currentEntry != null) {
    273                 ImageViewerDialog.this.currentEntry.selectLastImage(ImageViewerDialog.this);
    274             }
     354    private class ImageLastAction extends ImageRememberAction {
     355        ImageLastAction() {
     356            super(null, new ImageProvider(DIALOG_FOLDER, "last"), tr("Last"), Shortcut.registerShortcut(
     357                    "geoimage:last", tr(GEOIMAGE_FILLER, tr("Show last Image")), KeyEvent.VK_END, Shortcut.DIRECT),
     358                  false, null, false, IImageEntry::getLastImage);
    275359        }
    276360    }
    277361
    public final class ImageViewerDialog extends ToggleDialog implements LayerChange  
    293377
    294378    private class ImageZoomAction extends JosmAction {
    295379        ImageZoomAction() {
    296             super(null, new ImageProvider("dialogs", "zoom-best-fit"), tr("Zoom best fit and 1:1"), null,
     380            super(null, new ImageProvider(DIALOG_FOLDER, "zoom-best-fit"), tr("Zoom best fit and 1:1"), null,
    297381                  false, null, false);
    298382        }
    299383
    public final class ImageViewerDialog extends ToggleDialog implements LayerChange  
    305389
    306390    private class ImageRemoveAction extends JosmAction {
    307391        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),
     392            super(null, new ImageProvider(DIALOG_FOLDER, "delete"), tr("Remove photo from layer"), Shortcut.registerShortcut(
     393                    "geoimage:deleteimagefromlayer", tr(GEOIMAGE_FILLER, tr("Remove photo from layer")), KeyEvent.VK_DELETE, Shortcut.SHIFT),
    310394                  false, null, false);
    311395        }
    312396
    public final class ImageViewerDialog extends ToggleDialog implements LayerChange  
    323407
    324408    private class ImageRemoveFromDiskAction extends JosmAction {
    325409        ImageRemoveFromDiskAction() {
    326             super(null, new ImageProvider("dialogs", "geoimage/deletefromdisk"), tr("Delete image file from disk"),
     410            super(null, new ImageProvider(DIALOG_FOLDER, "geoimage/deletefromdisk"), tr("Delete image file from disk"),
    327411                    Shortcut.registerShortcut("geoimage:deletefilefromdisk",
    328                             tr("Geoimage: {0}", tr("Delete image file from disk")), KeyEvent.VK_DELETE, Shortcut.CTRL_SHIFT),
     412                            tr(GEOIMAGE_FILLER, tr("Delete image file from disk")), KeyEvent.VK_DELETE, Shortcut.CTRL_SHIFT),
    329413                    false, null, false);
    330414        }
    331415
    public final class ImageViewerDialog extends ToggleDialog implements LayerChange  
    383467    private class ImageCopyPathAction extends JosmAction {
    384468        ImageCopyPathAction() {
    385469            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),
     470                    "geoimage:copypath", tr(GEOIMAGE_FILLER, tr("Copy image path")), KeyEvent.VK_C, Shortcut.ALT_CTRL_SHIFT),
    387471                  false, null, false);
    388472        }
    389473
    public final class ImageViewerDialog extends ToggleDialog implements LayerChange  
    397481
    398482    private class ImageCollapseAction extends JosmAction {
    399483        ImageCollapseAction() {
    400             super(null, new ImageProvider("dialogs", "collapse"), tr("Move dialog to the side pane"), null,
     484            super(null, new ImageProvider(DIALOG_FOLDER, "collapse"), tr("Move dialog to the side pane"), null,
    401485                  false, null, false);
    402486        }
    403487
    public final class ImageViewerDialog extends ToggleDialog implements LayerChange  
    413497     * @param value {@code true} to enable the button, {@code false} otherwise
    414498     */
    415499    public void setPreviousEnabled(boolean value) {
    416         btnFirst.setEnabled(value);
     500        this.imageFirstAction.updateEnabledState();
     501        this.btnFirst.setEnabled(value || this.imageFirstAction.isEnabled());
    417502        btnPrevious.setEnabled(value);
    418503    }
    419504
    public final class ImageViewerDialog extends ToggleDialog implements LayerChange  
    423508     */
    424509    public void setNextEnabled(boolean value) {
    425510        btnNext.setEnabled(value);
    426         btnLast.setEnabled(value);
     511        this.imageLastAction.updateEnabledState();
     512        this.btnLast.setEnabled(value || this.imageLastAction.isEnabled());
    427513    }
    428514
    429515    /**
    public final class ImageViewerDialog extends ToggleDialog implements LayerChange  
    439525        return wasEnabled;
    440526    }
    441527
    442     private transient IImageEntry<?> currentEntry;
     528    private transient IImageEntry<? extends IImageEntry<?>> currentEntry;
    443529
    444530    /**
    445531     * Displays a single image for the given layer.
    public final class ImageViewerDialog extends ToggleDialog implements LayerChange  
    479565            }
    480566
    481567            currentEntry = entry;
     568
     569            for (ImageAction action : Arrays.asList(this.imageFirstAction, this.imagePreviousAction,
     570                    this.imageNextAction, this.imageLastAction)) {
     571                action.updateEnabledState();
     572            }
    482573        }
    483574
    484575        if (entry != null) {
    public final class ImageViewerDialog extends ToggleDialog implements LayerChange  
    527618     * @param imageChanged {@code true} if it is not the same image as the previous image.
    528619     */
    529620    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 
    536621        if (imageChanged) {
    537622            cancelLoadingImage();
    538623            // Set only if the image is new to preserve zoom and position if the same image is redisplayed
    539624            // (e.g. to update the OSD).
    540625            imgLoadingFuture = imgDisplay.setImage(entry);
    541626        }
     627
     628        // Update buttons after setting the new entry
     629        setNextEnabled(entry.getNextImage() != null);
     630        setPreviousEnabled(entry.getPreviousImage() != null);
     631        btnDelete.setEnabled(entry.isRemoveSupported());
     632        btnDeleteFromDisk.setEnabled(entry.isDeleteSupported() && entry.isRemoveSupported());
     633        btnCopyPath.setEnabled(true);
     634
    542635        setTitle(tr("Geotagged Images") + (!entry.getDisplayName().isEmpty() ? " - " + entry.getDisplayName() : ""));
    543636        StringBuilder osd = new StringBuilder(entry.getDisplayName());
    544637        if (entry.getElevation() != null) {