Ticket #21605: 21605.4.patch

File 21605.4.patch, 48.8 KB (added by taylor.smock, 3 years ago)

Fix #16056, #21605, #22536. Fix title name issue. Remove most of the code for storing the layer in the image viewer. Most likely will break plugin compatibility (probably just Mapillary)

  • src/org/openstreetmap/josm/gui/layer/geoimage/GeoImageLayer.java

    IDEA additional info:
    Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
    <+>UTF-8
    diff --git a/src/org/openstreetmap/josm/gui/layer/geoimage/GeoImageLayer.java b/src/org/openstreetmap/josm/gui/layer/geoimage/GeoImageLayer.java
    a b  
    4747import org.openstreetmap.josm.data.gpx.GpxData;
    4848import org.openstreetmap.josm.data.gpx.GpxImageEntry;
    4949import org.openstreetmap.josm.data.gpx.GpxTrack;
     50import org.openstreetmap.josm.data.imagery.street_level.IImageEntry;
    5051import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
    5152import org.openstreetmap.josm.gui.MainApplication;
    5253import org.openstreetmap.josm.gui.MapFrame;
     
    6263import org.openstreetmap.josm.gui.layer.JumpToMarkerActions.JumpToPreviousMarker;
    6364import org.openstreetmap.josm.gui.layer.Layer;
    6465import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeListener;
     66import org.openstreetmap.josm.gui.util.GuiHelper;
    6567import org.openstreetmap.josm.gui.util.imagery.Vector3D;
    6668import org.openstreetmap.josm.tools.ImageProvider;
     69import org.openstreetmap.josm.tools.ListenerList;
    6770import org.openstreetmap.josm.tools.Utils;
    6871
    6972/**
     
    7174 * @since 99
    7275 */
    7376public class GeoImageLayer extends AbstractModifiableLayer implements
    74         JumpToMarkerLayer, NavigatableComponent.ZoomChangeListener, ImageDataUpdateListener {
     77        JumpToMarkerLayer, NavigatableComponent.ZoomChangeListener, ImageDataUpdateListener,
     78        IGeoImageLayer {
    7579
    7680    private static final List<Action> menuAdditions = new LinkedList<>();
    7781
    7882    private static volatile List<MapMode> supportedMapModes;
    7983
    8084    private final ImageData data;
     85    private final ListenerList<IGeoImageLayer.ImageChangeListener> imageChangeListeners = ListenerList.create();
    8186    GpxData gpxData;
    8287    GpxLayer gpxFauxLayer;
    8388    GpxData gpxFauxData;
     
    172177        this.useThumbs = useThumbs;
    173178        this.data.addImageDataUpdateListener(this);
    174179        this.data.setLayer(this);
     180        if (!ImageViewerDialog.hasInstance()) {
     181            this.data.setSelectedImage(this.data.getFirstImage());
     182            // This must be called *after* this layer is added to the layer manager.
     183            // But it must also be run in the EDT. By adding this to the worker queue
     184            // and then running the actual code in the EDT, we ensure that the layer
     185            // will be added to the layer manager regardless of whether or not this
     186            // was instantiated in the worker thread or the EDT thread.
     187            MainApplication.worker.submit(() -> GuiHelper.runInEDT(() ->
     188                    ImageViewerDialog.getInstance().displayImages(Collections.singletonList(this.data.getSelectedImage()))));
     189        }
    175190    }
    176191
    177192    private final class ImageMouseListener extends MouseAdapter {
     
    232247                    }
    233248                } else {
    234249                    data.setSelectedImage(img);
    235                     ImageViewerDialog.getInstance().displayImages(GeoImageLayer.this, Collections.singletonList(img));
     250                    ImageViewerDialog.getInstance().displayImages(Collections.singletonList(img));
    236251                }
    237252            }
    238253        }
     
    247262        MainApplication.worker.execute(new ImagesLoader(files, gpxLayer));
    248263    }
    249264
     265    @Override
     266    public void clearSelection() {
     267        this.getImageData().clearSelectedImage();
     268    }
     269
     270    @Override
     271    public boolean containsImage(IImageEntry<?> imageEntry) {
     272        if (imageEntry instanceof ImageEntry) {
     273            return this.data.getImages().contains(imageEntry);
     274        }
     275        return false;
     276    }
     277
    250278    @Override
    251279    public Icon getIcon() {
    252280        return ImageProvider.get("dialogs/geoimage", ImageProvider.ImageSizes.LAYER);
    253281    }
    254282
     283    @Override
     284    public List<ImageEntry> getSelection() {
     285        return this.getImageData().getSelectedImages();
     286    }
     287
     288    @Override
     289    public List<IImageEntry<?>> getInvalidGeoImages() {
     290        return this.getImageData().getImages().stream().filter(entry -> entry.getPos() == null || entry.getExifCoor() == null
     291              || !entry.getExifCoor().isValid() || !entry.getPos().isValid()).collect(toList());
     292    }
     293
     294    @Override
     295    public void addImageChangeListener(ImageChangeListener listener) {
     296        this.imageChangeListeners.addListener(listener);
     297    }
     298
     299    @Override
     300    public void removeImageChangeListener(ImageChangeListener listener) {
     301        this.imageChangeListeners.removeListener(listener);
     302    }
     303
    255304    /**
    256305     * Register actions on the layer
    257306     * @param addition the action to be added
     
    884933    @Override
    885934    public void selectedImageChanged(ImageData data) {
    886935        showCurrentPhoto();
     936        this.imageChangeListeners.fireEvent(e -> e.imageChanged(this, null, data.getSelectedImages()));
    887937    }
    888938
    889939    @Override
  • new file src/org/openstreetmap/josm/gui/layer/geoimage/IGeoImageLayer.java

    IDEA additional info:
    Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
    <+>UTF-8
    diff --git a/src/org/openstreetmap/josm/gui/layer/geoimage/IGeoImageLayer.java b/src/org/openstreetmap/josm/gui/layer/geoimage/IGeoImageLayer.java
    new file mode 100644
    - +  
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.gui.layer.geoimage;
     3
     4import java.util.Collections;
     5import java.util.List;
     6
     7import org.openstreetmap.josm.data.imagery.street_level.IImageEntry;
     8
     9/**
     10 * An interface for layers which want to show images
     11 * @since xxx
     12 */
     13public interface IGeoImageLayer {
     14    /**
     15     * Clear the selection of the layer
     16     */
     17    void clearSelection();
     18
     19    /**
     20     * Get the current selection
     21     * @return The currently selected images
     22     */
     23    List<? extends IImageEntry<?>> getSelection();
     24
     25    /**
     26     * Get the invalid geo images for this layer (specifically, those that <i>cannot</i> be displayed on the map)
     27     * @return The list of invalid geo images
     28     */
     29    default List<IImageEntry<?>> getInvalidGeoImages() {
     30        return Collections.emptyList();
     31    }
     32
     33    /**
     34     * Check if the layer contains the specified image
     35     * @param imageEntry The entry to look for
     36     * @return {@code true} if this layer contains the image
     37     */
     38    boolean containsImage(IImageEntry<?> imageEntry);
     39
     40    /**
     41     * Add a listener for when images change
     42     * @param listener The listener to call
     43     */
     44    void addImageChangeListener(ImageChangeListener listener);
     45
     46    /**
     47     * Remove a listener for when images change
     48     * @param listener The listener to remove
     49     */
     50    void removeImageChangeListener(ImageChangeListener listener);
     51
     52    /**
     53     * Listen for image changes
     54     */
     55    interface ImageChangeListener {
     56        /**
     57         * Called when the selected image(s) change
     58         * @param source The source of the change
     59         * @param oldImages The previously selected image(s)
     60         * @param newImages The newly selected image(s)
     61         */
     62        void imageChanged(IGeoImageLayer source, List<? extends IImageEntry<?>> oldImages, List<? extends IImageEntry<?>> newImages);
     63    }
     64}
  • src/org/openstreetmap/josm/gui/layer/geoimage/ImageEntry.java

    IDEA additional info:
    Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
    <+>UTF-8
    diff --git a/src/org/openstreetmap/josm/gui/layer/geoimage/ImageEntry.java b/src/org/openstreetmap/josm/gui/layer/geoimage/ImageEntry.java
    a b  
    147147        if (entry instanceof ImageEntry) {
    148148            this.dataSet.setSelectedImage((ImageEntry) entry);
    149149        }
    150         imageViewerDialog.displayImages(this.dataSet.getLayer(), Collections.singletonList(entry));
     150        imageViewerDialog.displayImages(Collections.singletonList(entry));
    151151    }
    152152
    153153    @Override
  • src/org/openstreetmap/josm/gui/layer/geoimage/ImageViewerDialog.java

    IDEA additional info:
    Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
    <+>UTF-8
    diff --git a/src/org/openstreetmap/josm/gui/layer/geoimage/ImageViewerDialog.java b/src/org/openstreetmap/josm/gui/layer/geoimage/ImageViewerDialog.java
    a b  
    1111import java.awt.GridBagConstraints;
    1212import java.awt.GridBagLayout;
    1313import java.awt.event.ActionEvent;
    14 import java.awt.event.ActionListener;
    1514import java.awt.event.KeyEvent;
    1615import java.awt.event.WindowEvent;
    1716import java.io.IOException;
     
    2322import java.util.Arrays;
    2423import java.util.Collections;
    2524import java.util.Comparator;
    26 import java.util.HashMap;
    2725import java.util.List;
    28 import java.util.Map;
    2926import java.util.Objects;
    3027import java.util.Optional;
    3128import java.util.concurrent.Future;
    3229import java.util.function.UnaryOperator;
    3330import java.util.stream.Collectors;
     31import java.util.stream.IntStream;
     32import java.util.stream.Stream;
    3433
    3534import javax.swing.AbstractAction;
     35import javax.swing.AbstractButton;
    3636import javax.swing.Box;
    3737import javax.swing.JButton;
    3838import javax.swing.JLabel;
     
    4242import javax.swing.SwingConstants;
    4343import javax.swing.SwingUtilities;
    4444
     45import org.openstreetmap.josm.actions.ExpertToggleAction;
    4546import org.openstreetmap.josm.actions.JosmAction;
    4647import org.openstreetmap.josm.data.ImageData;
    47 import org.openstreetmap.josm.data.ImageData.ImageDataUpdateListener;
    4848import org.openstreetmap.josm.data.imagery.street_level.IImageEntry;
    4949import org.openstreetmap.josm.gui.ExtendedDialog;
    5050import org.openstreetmap.josm.gui.MainApplication;
     
    6464import org.openstreetmap.josm.gui.layer.imagery.ImageryFilterSettings;
    6565import org.openstreetmap.josm.gui.util.GuiHelper;
    6666import org.openstreetmap.josm.gui.util.imagery.Vector3D;
     67import org.openstreetmap.josm.gui.widgets.HideableTabbedPane;
     68import org.openstreetmap.josm.spi.preferences.Config;
    6769import org.openstreetmap.josm.tools.ImageProvider;
    6870import org.openstreetmap.josm.tools.Logging;
    6971import org.openstreetmap.josm.tools.PlatformManager;
     
    7375/**
    7476 * Dialog to view and manipulate geo-tagged images from a {@link GeoImageLayer}.
    7577 */
    76 public final class ImageViewerDialog extends ToggleDialog implements LayerChangeListener, ActiveLayerChangeListener, ImageDataUpdateListener {
     78public final class ImageViewerDialog extends ToggleDialog implements LayerChangeListener, ActiveLayerChangeListener {
    7779    private static final String GEOIMAGE_FILLER = marktr("Geoimage: {0}");
    7880    private static final String DIALOG_FOLDER = "dialogs";
    7981
     
    124126        return dialog;
    125127    }
    126128
     129    /**
     130     * Check if there is an instance for the {@link ImageViewerDialog}
     131     * @return {@code true} if there is a static singleton instance of {@link ImageViewerDialog}
     132     * @since xxx
     133     */
     134    public static boolean hasInstance() {
     135        return dialog != null;
     136    }
     137
     138    /**
     139     * Destroy the current dialog
     140     */
     141    private static void destroyInstance() {
     142        dialog = null;
     143    }
     144
    127145    private JButton btnLast;
    128146    private JButton btnNext;
    129147    private JButton btnPrevious;
     
    135153    private JButton btnDeleteFromDisk;
    136154    private JToggleButton tbCentre;
    137155    /** The layer tab (used to select images when multiple layers provide images, makes for easy switching) */
    138     private JPanel layers;
     156    private HideableTabbedPane layers;
    139157
    140158    private ImageViewerDialog() {
    141159        super(tr("Geotagged Images"), "geoimage", tr("Display geotagged images"), Shortcut.registerShortcut("tools:geotagged",
     
    168186
    169187    private void build() {
    170188        JPanel content = new JPanel(new BorderLayout());
    171         this.layers = new JPanel(new GridBagLayout());
    172         content.add(layers, BorderLayout.NORTH);
    173 
    174         content.add(imgDisplay, BorderLayout.CENTER);
     189        this.layers = new HideableTabbedPane();
     190        content.add(layers, BorderLayout.CENTER);
    175191
    176192        Dimension buttonDim = new Dimension(26, 26);
    177193
     
    187203        btnLast = createNavigationButton(imageLastAction, buttonDim);
    188204
    189205        tbCentre = new JToggleButton(imageCenterViewAction);
     206        tbCentre.setSelected(Config.getPref().getBoolean("geoimage.viewer.centre.on.image", false));
    190207        tbCentre.setPreferredSize(buttonDim);
    191208
    192209        JButton btnZoomBestFit = new JButton(imageZoomAction);
     
    196213        btnCollapse.setAlignmentY(Component.TOP_ALIGNMENT);
    197214
    198215        JPanel buttons = new JPanel();
    199         buttons.add(btnFirst);
    200         buttons.add(btnPrevious);
    201         buttons.add(btnNext);
    202         buttons.add(btnLast);
    203         buttons.add(Box.createRigidArea(new Dimension(7, 0)));
    204         buttons.add(tbCentre);
    205         buttons.add(btnZoomBestFit);
    206         buttons.add(Box.createRigidArea(new Dimension(7, 0)));
    207         buttons.add(btnDelete);
    208         buttons.add(btnDeleteFromDisk);
    209         buttons.add(Box.createRigidArea(new Dimension(7, 0)));
    210         buttons.add(btnCopyPath);
    211         buttons.add(btnOpenExternal);
    212         buttons.add(Box.createRigidArea(new Dimension(7, 0)));
    213         buttons.add(createButton(visibilityAction, buttonDim));
     216        addButtonGroup(buttons, this.btnFirst, this.btnPrevious, this.btnNext, this.btnLast);
     217        addButtonGroup(buttons, this.tbCentre, btnZoomBestFit);
     218        addButtonGroup(buttons, this.btnDelete, this.btnDeleteFromDisk);
     219        addButtonGroup(buttons, this.btnCopyPath, this.btnOpenExternal);
     220        addButtonGroup(buttons, createButton(visibilityAction, buttonDim));
    214221
    215222        JPanel bottomPane = new JPanel(new GridBagLayout());
    216223        GridBagConstraints gc = new GridBagConstraints();
     
    231238        createLayout(content, false, null);
    232239    }
    233240
    234     private void updateLayers() {
    235         if (this.tabbedEntries.size() <= 1) {
     241    /**
     242     * Add a button group to a panel
     243     * @param buttonPanel The panel holding the buttons
     244     * @param buttons The button group to add
     245     */
     246    private static void addButtonGroup(JPanel buttonPanel, AbstractButton... buttons) {
     247        if (buttonPanel.getComponentCount() != 0) {
     248            buttonPanel.add(Box.createRigidArea(new Dimension(7, 0)));
     249        }
     250
     251        for (AbstractButton jButton : buttons) {
     252            buttonPanel.add(jButton);
     253        }
     254    }
     255
     256    /**
     257     * Update the tabs for the different image layers
     258     * @param changed {@code true} if the tabs changed
     259     */
     260    private void updateLayers(boolean changed) {
     261        MainLayerManager layerManager = MainApplication.getLayerManager();
     262        List<IGeoImageLayer> geoImageLayers = layerManager.getLayers().stream()
     263                .filter(IGeoImageLayer.class::isInstance).map(IGeoImageLayer.class::cast).collect(Collectors.toList());
     264        if (geoImageLayers.isEmpty()) {
    236265            this.layers.setVisible(false);
    237             this.layers.removeAll();
    238266        } else {
    239267            this.layers.setVisible(true);
    240             // Remove all old components
    241             this.layers.removeAll();
    242             MainLayerManager layerManager = MainApplication.getLayerManager();
    243             List<Layer> invalidLayers = this.tabbedEntries.keySet().stream().filter(layer -> !layerManager.containsLayer(layer))
    244                     .collect(Collectors.toList());
    245             // `null` is for anything using the old methods, without telling us what layer it comes from.
    246             invalidLayers.remove(null);
    247             // We need to do multiple calls to avoid ConcurrentModificationExceptions
    248             invalidLayers.forEach(this.tabbedEntries::remove);
    249             addButtonsForImageLayers();
     268            if (changed) {
     269                addButtonsForImageLayers();
     270            }
     271            MoveImgDisplayPanel<?> selected = (MoveImgDisplayPanel<?>) this.layers.getSelectedComponent();
     272            if ((this.imgDisplay.getParent() == null || this.imgDisplay.getParent().getParent() == null)
     273                && selected != null && selected.layer.containsImage(this.currentEntry)) {
     274                selected.setVisible(selected.isVisible());
     275            } else if (selected != null && !selected.layer.containsImage(this.currentEntry)) {
     276                this.getImageTabs().filter(m -> m.layer.containsImage(this.currentEntry)).mapToInt(this.layers::indexOfComponent).findFirst()
     277                        .ifPresent(this.layers::setSelectedIndex);
     278            }
    250279            this.layers.invalidate();
    251280        }
     281        this.layers.getParent().invalidate();
    252282        this.revalidate();
    253283    }
    254284
     
    256286     * Add the buttons for image layers
    257287     */
    258288    private void addButtonsForImageLayers() {
    259         final IImageEntry<?> current;
    260         synchronized (this) {
    261             current = this.currentEntry;
    262         }
    263         List<JButton> layerButtons = new ArrayList<>(this.tabbedEntries.size());
    264         if (this.tabbedEntries.containsKey(null)) {
    265             List<IImageEntry<?>> nullEntries = this.tabbedEntries.get(null);
    266             JButton layerButton = createImageLayerButton(null, nullEntries);
    267             layerButtons.add(layerButton);
    268             layerButton.setEnabled(!nullEntries.contains(current));
     289        List<MoveImgDisplayPanel<?>> alreadyAdded = this.getImageTabs().collect(Collectors.toList());
     290        List<Layer> availableLayers = MainApplication.getLayerManager().getLayers();
     291        for (IGeoImageLayer layer : availableLayers.stream()
     292                .sorted(Comparator.comparingInt(entry -> /*reverse*/-availableLayers.indexOf(entry)))
     293                .filter(IGeoImageLayer.class::isInstance).map(IGeoImageLayer.class::cast).collect(Collectors.toList())) {
     294            final int index = availableLayers.size() - availableLayers.indexOf((Layer) layer);
     295            final String label = (ExpertToggleAction.isExpert() ? "[" + index + "] " : "") + ((Layer) layer).getLabel();
     296            final Optional<MoveImgDisplayPanel<?>> originalPanel = alreadyAdded.stream()
     297                    .filter(m -> Objects.equals(m.layer, layer)).findFirst();
     298            if (originalPanel.isPresent()) {
     299                int componentIndex = this.layers.indexOfComponent(originalPanel.get());
     300                this.layers.setTitleAt(componentIndex, label);
     301            } else {
     302                this.layers.addTab(label, new MoveImgDisplayPanel<>(this.imgDisplay, (Layer & IGeoImageLayer) layer));
     303            }
    269304        }
    270         for (Map.Entry<Layer, List<IImageEntry<?>>> entry :
    271                 this.tabbedEntries.entrySet().stream().filter(entry -> entry.getKey() != null)
    272                         .sorted(Comparator.comparing(entry -> entry.getKey().getName())).collect(Collectors.toList())) {
    273             JButton layerButton = createImageLayerButton(entry.getKey(), entry.getValue());
    274             layerButtons.add(layerButton);
    275             layerButton.setEnabled(!entry.getValue().contains(current));
     305        this.getImageTabs().map(p -> p.layer).filter(layer -> !availableLayers.contains(layer))
     306                // We have to collect to a list prior to removal -- if we don't, then the stream may get a layer at index 0, remove that layer,
     307                // and then get a layer at index 1, which was previously at index 2.
     308                .collect(Collectors.toList()).forEach(this::removeImageTab);
     309    }
     310
     311    /**
     312     * Remove a tab for a layer from the {@link #layers} tab pane
     313     * @param layer The layer to remove
     314     */
     315    private void removeImageTab(Layer layer) {
     316        // This must be reversed to avoid removing the wrong tab
     317        for (int i = this.layers.getTabCount() - 1; i >= 0; i--) {
     318            Component component = this.layers.getComponentAt(i);
     319            if (component instanceof MoveImgDisplayPanel) {
     320                MoveImgDisplayPanel<?> moveImgDisplayPanel = (MoveImgDisplayPanel<?>) component;
     321                if (Objects.equals(layer, moveImgDisplayPanel.layer)) {
     322                    this.layers.removeTabAt(i);
     323                    this.layers.remove(moveImgDisplayPanel);
     324                }
     325            }
    276326        }
    277         layerButtons.forEach(this.layers::add);
    278327    }
    279328
    280329    /**
    281      * Create a button for a specific layer and its entries
    282      *
    283      * @param layer     The layer to switch to
    284      * @param entries   The entries to display
    285      * @return The button to use to switch to the specified layer
     330     * Get the {@link MoveImgDisplayPanel} objects in {@link #layers}.
     331     * @return The individual panels
    286332     */
    287     private static JButton createImageLayerButton(Layer layer, List<IImageEntry<?>> entries) {
    288         final JButton layerButton = new JButton();
    289         layerButton.addActionListener(new ImageActionListener(layer, entries));
    290         layerButton.setText(layer != null ? layer.getLabel() : tr("Default"));
    291         return layerButton;
     333    private Stream<MoveImgDisplayPanel<?>> getImageTabs() {
     334        return IntStream.range(0, this.layers.getTabCount())
     335                .mapToObj(this.layers::getComponentAt)
     336                .filter(MoveImgDisplayPanel.class::isInstance)
     337                .map(m -> (MoveImgDisplayPanel<?>) m);
    292338    }
    293339
    294340    @Override
     
    309355        imageZoomAction.destroy();
    310356        cancelLoadingImage();
    311357        super.destroy();
    312         dialog = null;
     358        destroyInstance();
    313359    }
    314360
    315361    /**
     
    433479        }
    434480    }
    435481
    436     /**
    437      * A listener that is called to change the viewing layer
    438      */
    439     private static class ImageActionListener implements ActionListener {
    440 
    441         private final Layer layer;
    442         private final List<IImageEntry<?>> entries;
    443 
    444         ImageActionListener(Layer layer, List<IImageEntry<?>> entries) {
    445             this.layer = layer;
    446             this.entries = entries;
    447         }
    448 
    449         @Override
    450         public void actionPerformed(ActionEvent e) {
    451             ImageViewerDialog.getInstance().displayImages(this.layer, this.entries);
    452         }
    453     }
    454 
    455482    private class ImageFirstAction extends ImageRememberAction {
    456483        ImageFirstAction() {
    457484            super(null, new ImageProvider(DIALOG_FOLDER, "first"), tr("First"), Shortcut.registerShortcut(
     
    478505        public void actionPerformed(ActionEvent e) {
    479506            final JToggleButton button = (JToggleButton) e.getSource();
    480507            centerView = button.isEnabled() && button.isSelected();
     508            Config.getPref().putBoolean("geoimage.viewer.centre.on.image", centerView);
    481509            if (centerView && currentEntry != null && currentEntry.getPos() != null) {
    482510                MainApplication.getMap().mapView.zoomTo(currentEntry.getPos());
    483511            }
     
    618646        }
    619647    }
    620648
     649    /**
     650     * A JPanel whose entire purpose is to display an image by (a) moving the imgDisplay arround and (b) setting the imgDisplay as a child
     651     * for this panel.
     652     */
     653    private static class MoveImgDisplayPanel<T extends Layer & IGeoImageLayer> extends JPanel {
     654        private final T layer;
     655        private final ImageDisplay imgDisplay;
     656        MoveImgDisplayPanel(ImageDisplay imgDisplay, T layer) {
     657            super(new BorderLayout());
     658            this.layer = layer;
     659            this.imgDisplay = imgDisplay;
     660        }
     661
     662        @Override
     663        public void setVisible(boolean visible) {
     664            super.setVisible(visible);
     665            if (visible) {
     666                if (!this.layer.getSelection().isEmpty() && !this.layer.getSelection().contains(ImageViewerDialog.getCurrentImage())) {
     667                    ImageViewerDialog.getInstance().displayImages(this.layer.getSelection());
     668                }
     669                if (this.imgDisplay.getParent() != this) {
     670                    this.add(this.imgDisplay, BorderLayout.CENTER);
     671                    this.imgDisplay.invalidate();
     672                    this.revalidate();
     673                }
     674            }
     675        }
     676    }
     677
    621678    /**
    622679     * Enables (or disables) the "Previous" button.
    623680     * @param value {@code true} to enable the button, {@code false} otherwise
     
    651708        return wasEnabled;
    652709    }
    653710
    654     /** Used for tabbed panes */
    655     private final transient Map<Layer, List<IImageEntry<?>>> tabbedEntries = new HashMap<>();
    656711    private transient IImageEntry<? extends IImageEntry<?>> currentEntry;
    657712
    658713    /**
     
    679734     * @param entries image entries
    680735     * @since 18246
    681736     */
    682     public void displayImages(List<IImageEntry<?>> entries) {
    683         this.displayImages((Layer) null, entries);
    684     }
    685 
    686     /**
    687      * Displays images for the given layer.
    688      * @param layer The layer to use for the tab ui
    689      * @param entries image entries
    690      * @since 18591
    691      */
    692     public void displayImages(Layer layer, List<IImageEntry<?>> entries) {
     737    public void displayImages(List<? extends IImageEntry<?>> entries) {
    693738        boolean imageChanged;
    694739        IImageEntry<?> entry = entries != null && entries.size() == 1 ? entries.get(0) : null;
    695740
     
    710755            }
    711756        }
    712757
    713         if (entries == null || entries.isEmpty() || entries.stream().allMatch(Objects::isNull)) {
    714             this.tabbedEntries.remove(layer);
     758
     759        final boolean updateRequired;
     760        final List<IGeoImageLayer> imageLayers = MainApplication.getLayerManager().getLayers().stream()
     761                    .filter(IGeoImageLayer.class::isInstance).map(IGeoImageLayer.class::cast).collect(Collectors.toList());
     762        if (!Config.getPref().getBoolean("geoimage.viewer.show.tabs", true)) {
     763            updateRequired = true;
     764            // Clear the selected images in other geoimage layers
     765            this.getImageTabs().map(m -> m.layer).filter(IGeoImageLayer.class::isInstance).map(IGeoImageLayer.class::cast)
     766                    .filter(l -> !Objects.equals(entries, l.getSelection()))
     767                    .forEach(IGeoImageLayer::clearSelection);
    715768        } else {
    716             this.tabbedEntries.put(layer, entries);
     769            updateRequired = imageLayers.stream().anyMatch(l -> this.getImageTabs().map(m -> m.layer).noneMatch(l::equals));
    717770        }
    718         this.updateLayers();
     771        this.updateLayers(updateRequired);
    719772        if (entry != null) {
    720773            this.updateButtonsNonNullEntry(entry, imageChanged);
    721         } else if (this.tabbedEntries.isEmpty()) {
     774        } else if (imageLayers.isEmpty()) {
    722775            this.updateButtonsNullEntry(entries);
    723776            return;
    724777        } else {
    725             Map.Entry<Layer, List<IImageEntry<?>>> realEntry =
    726                     this.tabbedEntries.entrySet().stream().filter(mapEntry -> mapEntry.getValue().size() == 1).findFirst().orElse(null);
    727             if (realEntry == null) {
     778            IGeoImageLayer layer = imageLayers.stream().filter(l -> l.getSelection().size() == 1).findFirst().orElse(null);
     779            if (layer == null) {
    728780                this.updateButtonsNullEntry(entries);
    729781            } else {
    730                 this.displayImages(realEntry.getKey(), realEntry.getValue());
     782                this.displayImages(layer.getSelection());
    731783            }
    732784            return;
    733785        }
     
    744796     * Update buttons for null entry
    745797     * @param entries {@code true} if multiple images are selected
    746798     */
    747     private void updateButtonsNullEntry(List<IImageEntry<?>> entries) {
     799    private void updateButtonsNullEntry(List<? extends IImageEntry<?>> entries) {
    748800        boolean hasMultipleImages = entries != null && entries.size() > 1;
    749801        // if this method is called to reinitialize dialog content with a blank image,
    750802        // do not actually show the dialog again with a blank image if currently hidden (fix #10672)
    751         setTitle(tr("Geotagged Images"));
     803        this.updateTitle();
    752804        imgDisplay.setImage(null);
    753805        imgDisplay.setOsdText("");
    754806        setNextEnabled(false);
     
    787839        btnCopyPath.setEnabled(true);
    788840        btnOpenExternal.setEnabled(true);
    789841
    790         setTitle(tr("Geotagged Images") + (!entry.getDisplayName().isEmpty() ? " - " + entry.getDisplayName() : ""));
     842        this.updateTitle();
    791843        StringBuilder osd = new StringBuilder(entry.getDisplayName());
    792844        if (entry.getElevation() != null) {
    793845            osd.append(tr("\nAltitude: {0} m", Math.round(entry.getElevation())));
     
    818870        imgDisplay.setOsdText(osd.toString());
    819871    }
    820872
    821     /**
    822      * Displays images for the given layer.
    823      * @param ignoredData the image data (unused, may be {@code null})
    824      * @param entries image entries
    825      * @since 18246 (signature)
    826      * @deprecated Use {@link #displayImages(List)} (The data param is no longer used)
    827      */
    828     @Deprecated
    829     public void displayImages(ImageData ignoredData, List<IImageEntry<?>> entries) {
    830         this.displayImages(entries);
     873    private void updateTitle() {
     874        final IImageEntry<?> entry;
     875        synchronized (this) {
     876            entry = this.currentEntry;
     877        }
     878        String baseTitle = Optional.of(this.layers.getSelectedComponent())
     879                .filter(MoveImgDisplayPanel.class::isInstance).map(MoveImgDisplayPanel.class::cast)
     880                .map(m -> m.layer).map(Layer::getLabel).orElse(tr("Geotagged Images"));
     881        if (entry == null) {
     882            this.setTitle(baseTitle);
     883        } else {
     884            this.setTitle(baseTitle + (!entry.getDisplayName().isEmpty() ? " - " + entry.getDisplayName() : ""));
     885        }
    831886    }
    832887
    833     private static boolean isLastImageSelected(List<IImageEntry<?>> data) {
     888    private static boolean isLastImageSelected(List<? extends IImageEntry<?>> data) {
    834889        return data.stream().anyMatch(image -> data.contains(image.getLastImage()));
    835890    }
    836891
    837     private static boolean isFirstImageSelected(List<IImageEntry<?>> data) {
     892    private static boolean isFirstImageSelected(List<? extends IImageEntry<?>> data) {
    838893        return data.stream().anyMatch(image -> data.contains(image.getFirstImage()));
    839894    }
    840895
     
    857912        if (btnCollapse != null) {
    858913            btnCollapse.setVisible(!isDocked);
    859914        }
    860         this.updateLayers();
     915        this.updateLayers(true);
    861916    }
    862917
    863918    /**
     
    904959
    905960    @Override
    906961    public void layerRemoving(LayerRemoveEvent e) {
    907         if (e.getRemovedLayer() instanceof GeoImageLayer && this.currentEntry instanceof ImageEntry) {
    908             ImageData removedData = ((GeoImageLayer) e.getRemovedLayer()).getImageData();
    909             if (removedData == ((ImageEntry) this.currentEntry).getDataSet()) {
    910                 displayImages(e.getRemovedLayer(), null);
    911             }
    912             removedData.removeImageDataUpdateListener(this);
     962        if (e.getRemovedLayer() instanceof IGeoImageLayer && ((IGeoImageLayer) e.getRemovedLayer()).containsImage(this.currentEntry)) {
     963            displayImages(null);
    913964        }
    914         // Unfortunately, there will be no way to remove the default null layer. This will be fixed as plugins update.
    915         this.tabbedEntries.remove(e.getRemovedLayer());
     965        this.updateLayers(true);
    916966    }
    917967
    918968    @Override
    919969    public void layerOrderChanged(LayerOrderChangeEvent e) {
    920         // ignored
     970        this.updateLayers(true);
    921971    }
    922972
    923973    @Override
     
    941991    }
    942992
    943993    private void registerOnLayer(Layer layer) {
    944         if (layer instanceof GeoImageLayer) {
    945             ((GeoImageLayer) layer).getImageData().addImageDataUpdateListener(this);
     994        if (layer instanceof IGeoImageLayer) {
     995            layer.addPropertyChangeListener(l -> {
     996                final List<?> currentTabLayers = this.getImageTabs().map(m -> m.layer).collect(Collectors.toList());
     997                if (Layer.NAME_PROP.equals(l.getPropertyName()) && currentTabLayers.contains(layer)) {
     998                    this.updateLayers(true);
     999                        if (((IGeoImageLayer) layer).containsImage(this.currentEntry)) {
     1000                            this.updateTitle();
     1001                        }
     1002                } else if (Layer.VISIBLE_PROP.equals(l.getPropertyName()) && currentTabLayers.contains(layer)) {
     1003                    this.getImageTabs().filter(m -> Objects.equals(m.layer, layer)).mapToInt(this.layers::indexOfComponent)
     1004                            .filter(i -> i >= 0).forEach(i -> this.layers.setEnabledAt(i, layer.isVisible()));
     1005                }
     1006            });
    9461007        }
    9471008    }
    9481009
     
    9591020            imgLoadingFuture = null;
    9601021        }
    9611022    }
    962 
    963     @Override
    964     public void selectedImageChanged(ImageData data) {
    965         if (this.currentEntry != data.getSelectedImage() && this.currentEntry instanceof ImageEntry &&
    966                 !data.getSelectedImages().contains(this.currentEntry)) {
    967             displayImages(data.getLayer(), new ArrayList<>(data.getSelectedImages()));
    968         }
    969     }
    970 
    971     @Override
    972     public void imageDataUpdated(ImageData data) {
    973         displayImages(data.getLayer(), new ArrayList<>(data.getSelectedImages()));
    974     }
    9751023}
  • src/org/openstreetmap/josm/gui/layer/geoimage/RemoteEntry.java

    IDEA additional info:
    Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
    <+>UTF-8
    diff --git a/src/org/openstreetmap/josm/gui/layer/geoimage/RemoteEntry.java b/src/org/openstreetmap/josm/gui/layer/geoimage/RemoteEntry.java
    a b  
    1919import org.openstreetmap.josm.data.coor.ILatLon;
    2020import org.openstreetmap.josm.data.imagery.street_level.IImageEntry;
    2121import org.openstreetmap.josm.data.imagery.street_level.Projections;
    22 import org.openstreetmap.josm.gui.layer.Layer;
    2322import org.openstreetmap.josm.tools.HttpClient;
    2423import org.openstreetmap.josm.tools.JosmRuntimeException;
    2524
     
    3332    private final Supplier<RemoteEntry> nextImage;
    3433    private final Supplier<RemoteEntry> previousImage;
    3534    private final Supplier<RemoteEntry> lastImage;
    36     private final Layer layer;
    3735    private int width;
    3836    private int height;
    3937    private ILatLon pos;
     
    5452
    5553    /**
    5654     * Create a new remote entry
    57      * @param layer The originating layer, used for tabs in the image viewer
    5855     * @param uri The URI to use
    5956     * @param firstImage first image supplier
    6057     * @param nextImage next image supplier
    6158     * @param lastImage last image supplier
    6259     * @param previousImage previous image supplier
    6360     */
    64     public RemoteEntry(Layer layer, URI uri, Supplier<RemoteEntry> firstImage, Supplier<RemoteEntry> previousImage,
     61    public RemoteEntry(URI uri, Supplier<RemoteEntry> firstImage, Supplier<RemoteEntry> previousImage,
    6562                       Supplier<RemoteEntry> nextImage, Supplier<RemoteEntry> lastImage) {
    6663        Objects.requireNonNull(uri);
    6764        Objects.requireNonNull(firstImage);
     
    7370        this.previousImage = previousImage;
    7471        this.nextImage = nextImage;
    7572        this.lastImage = lastImage;
    76         this.layer = layer;
    7773    }
    7874
    7975    @Override
     
    319315
    320316    @Override
    321317    public void selectImage(ImageViewerDialog imageViewerDialog, IImageEntry<?> entry) {
    322         imageViewerDialog.displayImages(this.layer, Collections.singletonList(entry));
     318        imageViewerDialog.displayImages(Collections.singletonList(entry));
    323319    }
    324320
    325321    @Override
  • src/org/openstreetmap/josm/gui/layer/markerlayer/ImageMarker.java

    IDEA additional info:
    Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
    <+>UTF-8
    diff --git a/src/org/openstreetmap/josm/gui/layer/markerlayer/ImageMarker.java b/src/org/openstreetmap/josm/gui/layer/markerlayer/ImageMarker.java
    a b  
    44import static org.openstreetmap.josm.tools.I18n.tr;
    55
    66import java.awt.event.ActionEvent;
     7import java.net.URI;
    78import java.net.URISyntaxException;
    89import java.net.URL;
    910import java.time.Instant;
     
    1617import org.openstreetmap.josm.data.gpx.GpxConstants;
    1718import org.openstreetmap.josm.data.gpx.GpxLink;
    1819import org.openstreetmap.josm.data.gpx.WayPoint;
     20import org.openstreetmap.josm.data.imagery.street_level.IImageEntry;
    1921import org.openstreetmap.josm.gui.Notification;
    2022import org.openstreetmap.josm.gui.layer.geoimage.ImageViewerDialog;
    2123import org.openstreetmap.josm.gui.layer.geoimage.RemoteEntry;
     
    4042
    4143    @Override
    4244    public void actionPerformed(ActionEvent ev) {
    43         ImageViewerDialog.getInstance().displayImages(this.parentLayer, Collections.singletonList(getRemoteEntry()));
     45        this.parentLayer.setCurrentMarker(this);
     46        ImageViewerDialog.getInstance().displayImages(Collections.singletonList(getRemoteEntry()));
    4447    }
    4548
    46     private RemoteEntry getRemoteEntry() {
     49    RemoteEntry getRemoteEntry() {
    4750        try {
    48             final RemoteEntry remoteEntry = new RemoteEntry(this.parentLayer, imageUrl.toURI(), getFirstImage(), getPreviousImage(),
     51            final RemoteEntry remoteEntry = new MarkerRemoteEntry(imageUrl.toURI(), getFirstImage(), getPreviousImage(),
    4952                    getNextImage(), getLastImage());
    5053            // First, extract EXIF data
    5154            remoteEntry.extractExif();
     
    128131        wpt.put(GpxConstants.META_LINKS, Collections.singleton(link));
    129132        return wpt;
    130133    }
     134
     135    private class MarkerRemoteEntry extends RemoteEntry {
     136        /**
     137         * Create a new remote entry
     138         *
     139         * @param uri           The URI to use
     140         * @param firstImage    first image supplier
     141         * @param previousImage previous image supplier
     142         * @param nextImage     next image supplier
     143         * @param lastImage     last image supplier
     144         */
     145        public MarkerRemoteEntry(URI uri, Supplier<RemoteEntry> firstImage, Supplier<RemoteEntry> previousImage,
     146                                 Supplier<RemoteEntry> nextImage, Supplier<RemoteEntry> lastImage) {
     147            super(uri, firstImage, previousImage, nextImage, lastImage);
     148        }
     149
     150        @Override
     151        public void selectImage(ImageViewerDialog imageViewerDialog, IImageEntry<?> entry) {
     152            ImageMarker.this.parentLayer.setCurrentMarker(ImageMarker.this);
     153            super.selectImage(imageViewerDialog, entry);
     154        }
     155    }
    131156}
  • src/org/openstreetmap/josm/gui/layer/markerlayer/Marker.java

    IDEA additional info:
    Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
    <+>UTF-8
    diff --git a/src/org/openstreetmap/josm/gui/layer/markerlayer/Marker.java b/src/org/openstreetmap/josm/gui/layer/markerlayer/Marker.java
    a b  
    2020
    2121import javax.swing.ImageIcon;
    2222
     23import org.openstreetmap.josm.data.IQuadBucketType;
    2324import org.openstreetmap.josm.data.Preferences;
    2425import org.openstreetmap.josm.data.coor.CachedLatLon;
    2526import org.openstreetmap.josm.data.coor.EastNorth;
     
    2728import org.openstreetmap.josm.data.coor.LatLon;
    2829import org.openstreetmap.josm.data.gpx.GpxConstants;
    2930import org.openstreetmap.josm.data.gpx.WayPoint;
     31import org.openstreetmap.josm.data.osm.BBox;
    3032import org.openstreetmap.josm.data.osm.search.SearchCompiler.Match;
    3133import org.openstreetmap.josm.gui.MapView;
    3234import org.openstreetmap.josm.gui.layer.GpxLayer;
     
    7577 *
    7678 * @author Frederik Ramm
    7779 */
    78 public class Marker implements TemplateEngineDataProvider, ILatLon, Destroyable {
     80public class Marker implements TemplateEngineDataProvider, ILatLon, Destroyable, IQuadBucketType {
    7981
    8082    /**
    8183     * Plugins can add their Marker creation stuff at the bottom or top of this list
     
    447449    private String getPreferenceKey() {
    448450        return "draw.rawgps." + getTextTemplateKey();
    449451    }
     452
     453    @Override
     454    public BBox getBBox() {
     455        return new BBox(this);
     456    }
    450457}
  • src/org/openstreetmap/josm/gui/layer/markerlayer/MarkerLayer.java

    IDEA additional info:
    Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
    <+>UTF-8
    diff --git a/src/org/openstreetmap/josm/gui/layer/markerlayer/MarkerLayer.java b/src/org/openstreetmap/josm/gui/layer/markerlayer/MarkerLayer.java
    a b  
    1919import java.net.URISyntaxException;
    2020import java.util.ArrayList;
    2121import java.util.Collection;
     22import java.util.Collections;
    2223import java.util.Comparator;
    2324import java.util.HashMap;
    2425import java.util.List;
     26import java.util.ListIterator;
    2527import java.util.Map;
     28import java.util.Objects;
    2629import java.util.Optional;
    2730
    2831import javax.swing.AbstractAction;
     
    4144import org.openstreetmap.josm.data.gpx.GpxLink;
    4245import org.openstreetmap.josm.data.gpx.IGpxLayerPrefs;
    4346import org.openstreetmap.josm.data.gpx.WayPoint;
     47import org.openstreetmap.josm.data.imagery.street_level.IImageEntry;
     48import org.openstreetmap.josm.data.osm.BBox;
     49import org.openstreetmap.josm.data.osm.QuadBuckets;
    4450import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
    4551import org.openstreetmap.josm.data.preferences.IntegerProperty;
    4652import org.openstreetmap.josm.data.preferences.NamedColorProperty;
     
    5561import org.openstreetmap.josm.gui.layer.JumpToMarkerActions.JumpToNextMarker;
    5662import org.openstreetmap.josm.gui.layer.JumpToMarkerActions.JumpToPreviousMarker;
    5763import org.openstreetmap.josm.gui.layer.Layer;
     64import org.openstreetmap.josm.gui.layer.geoimage.IGeoImageLayer;
     65import org.openstreetmap.josm.gui.layer.geoimage.RemoteEntry;
    5866import org.openstreetmap.josm.gui.layer.gpx.ConvertFromMarkerLayerAction;
    5967import org.openstreetmap.josm.gui.preferences.display.GPXSettingsPanel;
    6068import org.openstreetmap.josm.io.audio.AudioPlayer;
    6169import org.openstreetmap.josm.spi.preferences.Config;
    6270import org.openstreetmap.josm.tools.ColorHelper;
    6371import org.openstreetmap.josm.tools.ImageProvider;
     72import org.openstreetmap.josm.tools.ListenerList;
    6473import org.openstreetmap.josm.tools.Logging;
    6574import org.openstreetmap.josm.tools.Utils;
    6675
     
    7584 *
    7685 * The data is read only.
    7786 */
    78 public class MarkerLayer extends Layer implements JumpToMarkerLayer {
     87public class MarkerLayer extends Layer implements JumpToMarkerLayer, IGeoImageLayer {
    7988
    8089    /**
    8190     * A list of markers.
     
    8998    final int markerSize = new IntegerProperty("draw.rawgps.markers.size", 4).get();
    9099    final BasicStroke markerStroke = new StrokeProperty("draw.rawgps.markers.stroke", "1").get();
    91100
     101    private final ListenerList<IGeoImageLayer.ImageChangeListener> imageChangeListenerListenerList = ListenerList.create();
     102
    92103    /**
    93104     * The default color that is used for drawing markers.
    94105     */
     
    401412        MainApplication.getMap().mapView.zoomTo(currentMarker);
    402413    }
    403414
     415    /**
     416     * Set the current marker
     417     * @param newMarker The marker to set
     418     */
     419    void setCurrentMarker(Marker newMarker) {
     420        this.currentMarker = newMarker;
     421    }
     422
    404423    public static void playAudio() {
    405424        playAdjacentMarker(null, true);
    406425    }
     
    495514        this.realcolor = Optional.ofNullable(color).orElse(DEFAULT_COLOR_PROPERTY.get());
    496515    }
    497516
     517    @Override
     518    public void clearSelection() {
     519        this.currentMarker = null;
     520    }
     521
     522    @Override
     523    public List<? extends IImageEntry<?>> getSelection() {
     524        if (this.currentMarker instanceof ImageMarker) {
     525            return Collections.singletonList(((ImageMarker) this.currentMarker).getRemoteEntry());
     526        }
     527        return Collections.emptyList();
     528    }
     529
     530    @Override
     531    public boolean containsImage(IImageEntry<?> imageEntry) {
     532        if (imageEntry instanceof RemoteEntry) {
     533            RemoteEntry entry = (RemoteEntry) imageEntry;
     534            if (entry.getPos() != null && entry.getPos().isLatLonKnown()) {
     535                List<Marker> markers = this.data.search(new BBox(entry.getPos()));
     536                return checkIfListContainsEntry(markers, entry);
     537            } else if (entry.getExifCoor() != null && entry.getExifCoor().isLatLonKnown()) {
     538                List<Marker> markers = this.data.search(new BBox(entry.getExifCoor()));
     539                return checkIfListContainsEntry(markers, entry);
     540            } else {
     541                return checkIfListContainsEntry(this.data, entry);
     542            }
     543        }
     544        return false;
     545    }
     546
     547    /**
     548     * Check if a list contains an entry
     549     * @param markerList The list to look through
     550     * @param imageEntry The image entry to check
     551     * @return {@code true} if the entry is in the list
     552     */
     553    private static boolean checkIfListContainsEntry(List<Marker> markerList, RemoteEntry imageEntry) {
     554        for (Marker marker : markerList) {
     555            if (marker instanceof ImageMarker) {
     556                ImageMarker imageMarker = (ImageMarker) marker;
     557                try {
     558                    if (Objects.equals(imageMarker.imageUrl.toURI(), imageEntry.getImageURI())) {
     559                        return true;
     560                    }
     561                } catch (URISyntaxException e) {
     562                    Logging.trace(e);
     563                }
     564            }
     565        }
     566        return false;
     567    }
     568
     569    @Override
     570    public void addImageChangeListener(ImageChangeListener listener) {
     571        this.imageChangeListenerListenerList.addListener(listener);
     572    }
     573
     574    @Override
     575    public void removeImageChangeListener(ImageChangeListener listener) {
     576        this.imageChangeListenerListenerList.removeListener(listener);
     577    }
     578
    498579    private final class MarkerMouseAdapter extends MouseAdapter {
    499580        @Override
    500581        public void mousePressed(MouseEvent e) {
     
    627708     * the data of a MarkerLayer
    628709     * @since 18287
    629710     */
    630     public class MarkerData extends ArrayList<Marker> implements IGpxLayerPrefs {
     711    public class MarkerData extends QuadBuckets<Marker> implements List<Marker>, IGpxLayerPrefs {
    631712
    632713        private Map<String, String> ownLayerPrefs;
     714        private final List<Marker> markerList = new ArrayList<>();
    633715
    634716        @Override
    635717        public Map<String, String> getLayerPrefs() {
     
    658740                fromLayer.data.setModified(value);
    659741            }
    660742        }
     743
     744        @Override
     745        public boolean addAll(int index, Collection<? extends Marker> c) {
     746            c.forEach(this::add);
     747            return this.markerList.addAll(index, c);
     748        }
     749
     750        @Override
     751        public boolean addAll(Collection<? extends Marker> objects) {
     752            return this.markerList.addAll(objects) && super.addAll(objects);
     753        }
     754
     755        @Override
     756        public Marker get(int index) {
     757            return this.markerList.get(index);
     758        }
     759
     760        @Override
     761        public Marker set(int index, Marker element) {
     762            Marker original = this.markerList.set(index, element);
     763            this.remove(original);
     764            return original;
     765        }
     766
     767        @Override
     768        public void add(int index, Marker element) {
     769            this.add(element);
     770            this.markerList.add(index, element);
     771        }
     772
     773        @Override
     774        public Marker remove(int index) {
     775            Marker toRemove = this.markerList.remove(index);
     776            this.remove(toRemove);
     777            return toRemove;
     778        }
     779
     780        @Override
     781        public int indexOf(Object o) {
     782            return this.markerList.indexOf(o);
     783        }
     784
     785        @Override
     786        public int lastIndexOf(Object o) {
     787            return this.markerList.lastIndexOf(o);
     788        }
     789
     790        @Override
     791        public ListIterator<Marker> listIterator() {
     792            return this.markerList.listIterator();
     793        }
     794
     795        @Override
     796        public ListIterator<Marker> listIterator(int index) {
     797            return this.markerList.listIterator(index);
     798        }
     799
     800        @Override
     801        public List<Marker> subList(int fromIndex, int toIndex) {
     802            return this.markerList.subList(fromIndex, toIndex);
     803        }
     804
     805        @Override
     806        public boolean retainAll(Collection<?> objects) {
     807            return this.markerList.retainAll(objects) && super.retainAll(objects);
     808        }
     809
     810        @Override
     811        public boolean contains(Object o) {
     812            return this.markerList.contains(o) && super.contains(o);
     813        }
    661814    }
    662815}