Changeset 18613 in josm for trunk/src


Ignore:
Timestamp:
2022-12-12T22:38:24+01:00 (17 months ago)
Author:
taylor.smock
Message:

Fix #21605: Add tabs to ImageViewerDialog for use with different image layers

This allows users to have multiple geotagged image layers, and
quickly switch between them.

This is a complete rework of the functionality introduced in r18591 after user feedback.

Changes:

  • Tabs are now scrollable
  • Tabs now have a close button
  • Removes the functions where plugins could send a layer in (plugins should use IGeoImageLayer instead)
  • Tabs are sorted (by layer order)
  • GeoImageLayers will use a darker red for selected but not viewed images
  • Tabs are only opened when the user clicks on a geoimage from a different layer
  • GpxMarkers now implement IQuadBucketType. This was to speed up the containsImage method in MarkerLayer.
Location:
trunk/src/org/openstreetmap/josm/gui/layer
Files:
1 added
7 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/org/openstreetmap/josm/gui/layer/geoimage/GeoImageLayer.java

    r18592 r18613  
    3434import javax.swing.Action;
    3535import javax.swing.Icon;
     36import javax.swing.ImageIcon;
    3637
    3738import org.openstreetmap.josm.actions.AutoScaleAction;
     
    4849import org.openstreetmap.josm.data.gpx.GpxImageEntry;
    4950import org.openstreetmap.josm.data.gpx.GpxTrack;
     51import org.openstreetmap.josm.data.imagery.street_level.IImageEntry;
    5052import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
     53import org.openstreetmap.josm.data.preferences.NamedColorProperty;
    5154import org.openstreetmap.josm.gui.MainApplication;
    5255import org.openstreetmap.josm.gui.MapFrame;
     
    6366import org.openstreetmap.josm.gui.layer.Layer;
    6467import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeListener;
     68import org.openstreetmap.josm.gui.util.GuiHelper;
    6569import org.openstreetmap.josm.gui.util.imagery.Vector3D;
    6670import org.openstreetmap.josm.tools.ImageProvider;
     71import org.openstreetmap.josm.tools.ListenerList;
    6772import org.openstreetmap.josm.tools.Utils;
    6873
     
    7277 */
    7378public class GeoImageLayer extends AbstractModifiableLayer implements
    74         JumpToMarkerLayer, NavigatableComponent.ZoomChangeListener, ImageDataUpdateListener {
     79        JumpToMarkerLayer, NavigatableComponent.ZoomChangeListener, ImageDataUpdateListener,
     80        IGeoImageLayer {
    7581
    7682    private static final List<Action> menuAdditions = new LinkedList<>();
     
    7985
    8086    private final ImageData data;
     87    private final ListenerList<IGeoImageLayer.ImageChangeListener> imageChangeListeners = ListenerList.create();
    8188    GpxData gpxData;
    8289    GpxLayer gpxFauxLayer;
     
    8794    private final Icon icon = ImageProvider.get("dialogs/geoimage/photo-marker");
    8895    private final Icon selectedIcon = ImageProvider.get("dialogs/geoimage/photo-marker-selected");
     96    private final Icon selectedIconNotImageViewer = generateSelectedIconNotImageViewer(this.selectedIcon);
     97
     98    private static Icon generateSelectedIconNotImageViewer(Icon selectedIcon) {
     99        Color color = new NamedColorProperty("geoimage.selected.not.image.viewer", new Color(50, 0, 0)).get();
     100        BufferedImage bi = new BufferedImage(selectedIcon.getIconWidth(), selectedIcon.getIconHeight(), BufferedImage.TYPE_INT_ARGB);
     101        Graphics2D g2d = bi.createGraphics();
     102        selectedIcon.paintIcon(null, g2d, 0, 0);
     103        g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, 0.5f));
     104        g2d.setColor(color);
     105        g2d.fillRect(0, 0, selectedIcon.getIconWidth(), selectedIcon.getIconHeight());
     106        g2d.dispose();
     107        return new ImageIcon(bi);
     108    }
    89109
    90110    boolean useThumbs;
     
    173193        this.data.addImageDataUpdateListener(this);
    174194        this.data.setLayer(this);
     195        synchronized (ImageViewerDialog.class) {
     196            if (!ImageViewerDialog.hasInstance()) {
     197                GuiHelper.runInEDTAndWait(ImageViewerDialog::createInstance);
     198            }
     199        }
     200        if (getInvalidGeoImages().size() == data.size()) {
     201            ImageViewerDialog.getInstance().displayImages(Collections.singletonList(this.data.getFirstImage()));
     202        }
    175203    }
    176204
     
    233261                } else {
    234262                    data.setSelectedImage(img);
    235                     ImageViewerDialog.getInstance().displayImages(GeoImageLayer.this, Collections.singletonList(img));
    236                 }
     263                    ImageViewerDialog.getInstance().displayImages(Collections.singletonList(img));
     264                }
     265                GeoImageLayer.this.invalidate(); // Needed to update which image is being shown in the image viewer in the mapview
    237266            }
    238267        }
     
    249278
    250279    @Override
     280    public void clearSelection() {
     281        this.getImageData().clearSelectedImage();
     282    }
     283
     284    @Override
     285    public boolean containsImage(IImageEntry<?> imageEntry) {
     286        if (imageEntry instanceof ImageEntry) {
     287            return this.data.getImages().contains(imageEntry);
     288        }
     289        return false;
     290    }
     291
     292    @Override
    251293    public Icon getIcon() {
    252294        return ImageProvider.get("dialogs/geoimage", ImageProvider.ImageSizes.LAYER);
     295    }
     296
     297    @Override
     298    public List<ImageEntry> getSelection() {
     299        return this.getImageData().getSelectedImages();
     300    }
     301
     302    @Override
     303    public List<IImageEntry<?>> getInvalidGeoImages() {
     304        return this.getImageData().getImages().stream().filter(entry -> entry.getPos() == null || entry.getExifCoor() == null
     305              || !entry.getExifCoor().isValid() || !entry.getPos().isValid()).collect(toList());
     306    }
     307
     308    @Override
     309    public void addImageChangeListener(ImageChangeListener listener) {
     310        this.imageChangeListeners.addListener(listener);
     311    }
     312
     313    @Override
     314    public void removeImageChangeListener(ImageChangeListener listener) {
     315        this.imageChangeListeners.removeListener(listener);
    253316    }
    254317
     
    452515        }
    453516
     517        final IImageEntry<?> currentImage = ImageViewerDialog.getCurrentImage();
    454518        for (ImageEntry e: data.getSelectedImages()) {
    455519            if (e != null && e.getPos() != null) {
     
    466530                    g.setColor(new Color(128, 0, 0, 122));
    467531                    g.fillRect(p.x - imgDim.width / 2, p.y - imgDim.height / 2, imgDim.width, imgDim.height);
     532                } else if (e.equals(currentImage)) {
     533                    selectedIcon.paintIcon(mv, g,
     534                            p.x - imgDim.width / 2,
     535                            p.y - imgDim.height / 2);
    468536                } else {
    469                     selectedIcon.paintIcon(mv, g,
     537                    selectedIconNotImageViewer.paintIcon(mv, g,
    470538                            p.x - imgDim.width / 2,
    471539                            p.y - imgDim.height / 2);
     
    885953    public void selectedImageChanged(ImageData data) {
    886954        showCurrentPhoto();
     955        this.imageChangeListeners.fireEvent(e -> e.imageChanged(this, null, data.getSelectedImages()));
    887956    }
    888957
  • trunk/src/org/openstreetmap/josm/gui/layer/geoimage/ImageEntry.java

    r18592 r18613  
    148148            this.dataSet.setSelectedImage((ImageEntry) entry);
    149149        }
    150         imageViewerDialog.displayImages(this.dataSet.getLayer(), Collections.singletonList(entry));
     150        imageViewerDialog.displayImages(Collections.singletonList(entry));
    151151    }
    152152
  • trunk/src/org/openstreetmap/josm/gui/layer/geoimage/ImageViewerDialog.java

    r18605 r18613  
    1515import java.awt.event.KeyEvent;
    1616import java.awt.event.WindowEvent;
     17import java.beans.PropertyChangeEvent;
     18import java.beans.PropertyChangeListener;
    1719import java.io.IOException;
    1820import java.io.Serializable;
     
    2426import java.util.Collections;
    2527import java.util.Comparator;
    26 import java.util.HashMap;
    2728import java.util.List;
    28 import java.util.Map;
    2929import java.util.Objects;
    3030import java.util.Optional;
     
    3232import java.util.function.UnaryOperator;
    3333import java.util.stream.Collectors;
     34import java.util.stream.IntStream;
     35import java.util.stream.Stream;
    3436
    3537import javax.swing.AbstractAction;
     38import javax.swing.AbstractButton;
     39import javax.swing.BorderFactory;
    3640import javax.swing.Box;
    3741import javax.swing.JButton;
     
    3943import javax.swing.JOptionPane;
    4044import javax.swing.JPanel;
     45import javax.swing.JTabbedPane;
    4146import javax.swing.JToggleButton;
    4247import javax.swing.SwingConstants;
    4348import javax.swing.SwingUtilities;
    4449
     50import org.openstreetmap.josm.actions.ExpertToggleAction;
    4551import org.openstreetmap.josm.actions.JosmAction;
    4652import org.openstreetmap.josm.data.ImageData;
    47 import org.openstreetmap.josm.data.ImageData.ImageDataUpdateListener;
    4853import org.openstreetmap.josm.data.imagery.street_level.IImageEntry;
    4954import org.openstreetmap.josm.gui.ExtendedDialog;
     
    6570import org.openstreetmap.josm.gui.util.GuiHelper;
    6671import org.openstreetmap.josm.gui.util.imagery.Vector3D;
     72import org.openstreetmap.josm.gui.widgets.HideableTabbedPane;
     73import org.openstreetmap.josm.spi.preferences.Config;
    6774import org.openstreetmap.josm.tools.ImageProvider;
    6875import org.openstreetmap.josm.tools.Logging;
     
    7481 * Dialog to view and manipulate geo-tagged images from a {@link GeoImageLayer}.
    7582 */
    76 public final class ImageViewerDialog extends ToggleDialog implements LayerChangeListener, ActiveLayerChangeListener, ImageDataUpdateListener {
     83public final class ImageViewerDialog extends ToggleDialog implements LayerChangeListener, ActiveLayerChangeListener {
    7784    private static final String GEOIMAGE_FILLER = marktr("Geoimage: {0}");
    7885    private static final String DIALOG_FOLDER = "dialogs";
     
    125132    }
    126133
     134    /**
     135     * Check if there is an instance for the {@link ImageViewerDialog}
     136     * @return {@code true} if there is a static singleton instance of {@link ImageViewerDialog}
     137     * @since 18613
     138     */
     139    public static boolean hasInstance() {
     140        return dialog != null;
     141    }
     142
     143    /**
     144     * Destroy the current dialog
     145     */
     146    private static void destroyInstance() {
     147        dialog = null;
     148    }
     149
    127150    private JButton btnLast;
    128151    private JButton btnNext;
     
    136159    private JToggleButton tbCentre;
    137160    /** The layer tab (used to select images when multiple layers provide images, makes for easy switching) */
    138     private JPanel layers;
     161    private final HideableTabbedPane layers = new HideableTabbedPane();
    139162
    140163    private ImageViewerDialog() {
     
    169192    private void build() {
    170193        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);
     194        content.add(this.layers, BorderLayout.CENTER);
    175195
    176196        Dimension buttonDim = new Dimension(26, 26);
     
    188208
    189209        tbCentre = new JToggleButton(imageCenterViewAction);
     210        tbCentre.setSelected(Config.getPref().getBoolean("geoimage.viewer.centre.on.image", false));
    190211        tbCentre.setPreferredSize(buttonDim);
    191212
     
    197218
    198219        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));
     220        addButtonGroup(buttons, this.btnFirst, this.btnPrevious, this.btnNext, this.btnLast);
     221        addButtonGroup(buttons, this.tbCentre, btnZoomBestFit);
     222        addButtonGroup(buttons, this.btnDelete, this.btnDeleteFromDisk);
     223        addButtonGroup(buttons, this.btnCopyPath, this.btnOpenExternal);
     224        addButtonGroup(buttons, createButton(visibilityAction, buttonDim));
    214225
    215226        JPanel bottomPane = new JPanel(new GridBagLayout());
     
    232243    }
    233244
    234     private void updateLayers() {
    235         if (this.tabbedEntries.size() <= 1) {
     245    /**
     246     * Add a button group to a panel
     247     * @param buttonPanel The panel holding the buttons
     248     * @param buttons The button group to add
     249     */
     250    private static void addButtonGroup(JPanel buttonPanel, AbstractButton... buttons) {
     251        if (buttonPanel.getComponentCount() != 0) {
     252            buttonPanel.add(Box.createRigidArea(new Dimension(7, 0)));
     253        }
     254
     255        for (AbstractButton jButton : buttons) {
     256            buttonPanel.add(jButton);
     257        }
     258    }
     259
     260    /**
     261     * Update the tabs for the different image layers
     262     * @param changed {@code true} if the tabs changed
     263     */
     264    private void updateLayers(boolean changed) {
     265        MainLayerManager layerManager = MainApplication.getLayerManager();
     266        List<IGeoImageLayer> geoImageLayers = layerManager.getLayers().stream()
     267                .filter(IGeoImageLayer.class::isInstance).map(IGeoImageLayer.class::cast).collect(Collectors.toList());
     268        if (geoImageLayers.isEmpty()) {
    236269            this.layers.setVisible(false);
    237             this.layers.removeAll();
    238270        } else {
    239271            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();
     272            if (changed) {
     273                addButtonsForImageLayers();
     274            }
     275            MoveImgDisplayPanel<?> selected = (MoveImgDisplayPanel<?>) this.layers.getSelectedComponent();
     276            if ((this.imgDisplay.getParent() == null || this.imgDisplay.getParent().getParent() == null)
     277                && selected != null && selected.layer.containsImage(this.currentEntry)) {
     278                selected.setVisible(selected.isVisible());
     279            } else if (selected != null && !selected.layer.containsImage(this.currentEntry)) {
     280                this.getImageTabs().filter(m -> m.layer.containsImage(this.currentEntry)).mapToInt(this.layers::indexOfComponent).findFirst()
     281                        .ifPresent(this.layers::setSelectedIndex);
     282            }
    250283            this.layers.invalidate();
    251284        }
     285        this.layers.getParent().invalidate();
    252286        this.revalidate();
    253287    }
     
    257291     */
    258292    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));
    269         }
    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));
    276         }
    277         layerButtons.forEach(this.layers::add);
    278     }
    279 
    280     /**
    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
    286      */
    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;
     293        List<MoveImgDisplayPanel<?>> alreadyAdded = this.getImageTabs().collect(Collectors.toList());
     294        // Avoid the setVisible call recursively calling this method and adding duplicates
     295        alreadyAdded.forEach(m -> m.finishedAddingButtons = false);
     296        List<Layer> availableLayers = MainApplication.getLayerManager().getLayers();
     297        List<IGeoImageLayer> geoImageLayers = availableLayers.stream()
     298                .sorted(Comparator.comparingInt(entry -> /*reverse*/-availableLayers.indexOf(entry)))
     299                .filter(IGeoImageLayer.class::isInstance).map(IGeoImageLayer.class::cast).collect(Collectors.toList());
     300        List<IGeoImageLayer> tabLayers = geoImageLayers.stream()
     301                .filter(l -> alreadyAdded.stream().anyMatch(m -> Objects.equals(l, m.layer)) || l.containsImage(this.currentEntry))
     302                .collect(Collectors.toList());
     303        for (IGeoImageLayer layer : tabLayers) {
     304            final MoveImgDisplayPanel<?> panel = alreadyAdded.stream()
     305                    .filter(m -> Objects.equals(m.layer, layer)).findFirst()
     306                    .orElseGet(() -> new MoveImgDisplayPanel<>(this.imgDisplay, (Layer & IGeoImageLayer) layer));
     307            int componentIndex = this.layers.indexOfComponent(panel);
     308            if (componentIndex == geoImageLayers.indexOf(layer)) {
     309                this.layers.setTitleAt(componentIndex, panel.getLabel(availableLayers));
     310            } else {
     311                this.removeImageTab((Layer) layer);
     312                this.layers.insertTab(panel.getLabel(availableLayers), null, panel, null, tabLayers.indexOf(layer));
     313                int idx = this.layers.indexOfComponent(panel);
     314                CloseableTab closeableTab = new CloseableTab(this.layers, l -> {
     315                    Component source = (Component) l.getSource();
     316                    do {
     317                        int index = layers.indexOfTabComponent(source);
     318                        if (index >= 0) {
     319                            getImageTabs().forEach(m -> m.finishedAddingButtons = false);
     320                            removeImageTab(((MoveImgDisplayPanel<?>) layers.getComponentAt(index)).layer);
     321                            getImageTabs().forEach(m -> m.finishedAddingButtons = true);
     322                            getImageTabs().forEach(m -> m.setVisible(m.isVisible()));
     323                            return;
     324                        }
     325                        source = source.getParent();
     326                    } while (source != null);
     327                });
     328                this.layers.setTabComponentAt(idx, closeableTab);
     329            }
     330            if (layer.containsImage(this.currentEntry)) {
     331                this.layers.setSelectedComponent(panel);
     332            }
     333        }
     334        this.getImageTabs().map(p -> p.layer).filter(layer -> !availableLayers.contains(layer))
     335                // We have to collect to a list prior to removal -- if we don't, then the stream may get a layer at index 0,
     336                // remove that layer, and then get a layer at index 1, which was previously at index 2.
     337                .collect(Collectors.toList()).forEach(this::removeImageTab);
     338
     339        // This is need to avoid the first button becoming visible, and then recalling this method.
     340        this.getImageTabs().forEach(m -> m.finishedAddingButtons = true);
     341        // After that, trigger the visibility set code
     342        this.getImageTabs().forEach(m -> m.setVisible(m.isVisible()));
     343    }
     344
     345    /**
     346     * Remove a tab for a layer from the {@link #layers} tab pane
     347     * @param layer The layer to remove
     348     */
     349    private void removeImageTab(Layer layer) {
     350        // This must be reversed to avoid removing the wrong tab
     351        for (int i = this.layers.getTabCount() - 1; i >= 0; i--) {
     352            Component component = this.layers.getComponentAt(i);
     353            if (component instanceof MoveImgDisplayPanel) {
     354                MoveImgDisplayPanel<?> moveImgDisplayPanel = (MoveImgDisplayPanel<?>) component;
     355                if (Objects.equals(layer, moveImgDisplayPanel.layer)) {
     356                    this.layers.removeTabAt(i);
     357                    this.layers.remove(moveImgDisplayPanel);
     358                }
     359            }
     360        }
     361    }
     362
     363    /**
     364     * Get the {@link MoveImgDisplayPanel} objects in {@link #layers}.
     365     * @return The individual panels
     366     */
     367    private Stream<MoveImgDisplayPanel<?>> getImageTabs() {
     368        return IntStream.range(0, this.layers.getTabCount())
     369                .mapToObj(this.layers::getComponentAt)
     370                .filter(MoveImgDisplayPanel.class::isInstance)
     371                .map(m -> (MoveImgDisplayPanel<?>) m);
    292372    }
    293373
     
    310390        cancelLoadingImage();
    311391        super.destroy();
    312         dialog = null;
     392        destroyInstance();
    313393    }
    314394
     
    434514    }
    435515
    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 
    455516    private class ImageFirstAction extends ImageRememberAction {
    456517        ImageFirstAction() {
     
    479540            final JToggleButton button = (JToggleButton) e.getSource();
    480541            centerView = button.isEnabled() && button.isSelected();
     542            Config.getPref().putBoolean("geoimage.viewer.centre.on.image", centerView);
    481543            if (centerView && currentEntry != null && currentEntry.getPos() != null) {
    482544                MainApplication.getMap().mapView.zoomTo(currentEntry.getPos());
     
    620682
    621683    /**
     684     * A tab title renderer for {@link HideableTabbedPane} that allows us to close tabs.
     685     */
     686    private static class CloseableTab extends JPanel implements PropertyChangeListener {
     687        private final JLabel title;
     688        private final JButton close;
     689
     690        /**
     691         * Create a new {@link CloseableTab}.
     692         * @param parent The parent to add property change listeners to. It should be a {@link HideableTabbedPane} in most cases.
     693         * @param closeAction The action to run to close the tab. You probably want to call {@link JTabbedPane#removeTabAt(int)}
     694         *                    at the very least.
     695         */
     696        CloseableTab(Component parent, ActionListener closeAction) {
     697            this.title = new JLabel();
     698            this.add(this.title);
     699            close = new JButton(ImageProvider.get("misc", "close"));
     700            close.setBorder(BorderFactory.createEmptyBorder());
     701            this.add(close);
     702            close.addActionListener(closeAction);
     703            close.addActionListener(l -> parent.removePropertyChangeListener("indexForTitle", this));
     704            parent.addPropertyChangeListener("indexForTitle", this);
     705        }
     706
     707        @Override
     708        public void propertyChange(PropertyChangeEvent evt) {
     709            if (evt.getSource() instanceof JTabbedPane) {
     710                JTabbedPane source = (JTabbedPane) evt.getSource();
     711                if (this.getParent() == null) {
     712                    source.removePropertyChangeListener(evt.getPropertyName(), this);
     713                }
     714                if ("indexForTitle".equals(evt.getPropertyName())) {
     715                    int idx = source.indexOfTabComponent(this);
     716                    if (idx >= 0) {
     717                        this.title.setText(source.getTitleAt(idx));
     718                    }
     719                }
     720                // Used to hack around UI staying visible. This assumes that the parent component is a HideableTabbedPane.
     721                this.title.setVisible(source.getTabCount() != 1);
     722                this.close.setVisible(source.getTabCount() != 1);
     723            }
     724        }
     725    }
     726
     727    /**
     728     * A JPanel whose entire purpose is to display an image by (a) moving the imgDisplay around and (b) setting the imgDisplay as a child
     729     * for this panel.
     730     */
     731    private static class MoveImgDisplayPanel<T extends Layer & IGeoImageLayer> extends JPanel {
     732        private final T layer;
     733        private final ImageDisplay imgDisplay;
     734
     735        /**
     736         * The purpose of this field is to avoid having the same tab added to the dialog multiple times. This is only a problem when the dialog
     737         * has multiple tabs on initialization (like from a session).
     738         */
     739        boolean finishedAddingButtons;
     740        MoveImgDisplayPanel(ImageDisplay imgDisplay, T layer) {
     741            super(new BorderLayout());
     742            this.layer = layer;
     743            this.imgDisplay = imgDisplay;
     744        }
     745
     746        @Override
     747        public void setVisible(boolean visible) {
     748            super.setVisible(visible);
     749            JTabbedPane layers = ImageViewerDialog.getInstance().layers;
     750            int index = layers.indexOfComponent(this);
     751            if (visible && this.finishedAddingButtons) {
     752                if (!this.layer.getSelection().isEmpty() && !this.layer.getSelection().contains(ImageViewerDialog.getCurrentImage())) {
     753                    ImageViewerDialog.getInstance().displayImages(this.layer.getSelection());
     754                    this.layer.invalidate(); // This will force the geoimage layers to update properly.
     755                }
     756                if (this.imgDisplay.getParent() != this) {
     757                    this.add(this.imgDisplay, BorderLayout.CENTER);
     758                    this.imgDisplay.invalidate();
     759                    this.revalidate();
     760                }
     761                if (index >= 0) {
     762                    layers.setTitleAt(index, "* " + getLabel(MainApplication.getLayerManager().getLayers()));
     763                }
     764            } else if (index >= 0) {
     765                layers.setTitleAt(index, getLabel(MainApplication.getLayerManager().getLayers()));
     766            }
     767        }
     768
     769        /**
     770         * Get the label for this panel
     771         * @param availableLayers The layers to use to get the index
     772         * @return The label for this layer
     773         */
     774        String getLabel(List<Layer> availableLayers) {
     775            final int index = availableLayers.size() - availableLayers.indexOf(layer);
     776            return (ExpertToggleAction.isExpert() ? "[" + index + "] " : "") + layer.getLabel();
     777        }
     778    }
     779
     780    /**
    622781     * Enables (or disables) the "Previous" button.
    623782     * @param value {@code true} to enable the button, {@code false} otherwise
     
    652811    }
    653812
    654     /** Used for tabbed panes */
    655     private final transient Map<Layer, List<IImageEntry<?>>> tabbedEntries = new HashMap<>();
    656813    private transient IImageEntry<? extends IImageEntry<?>> currentEntry;
    657814
     
    680837     * @since 18246
    681838     */
    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) {
     839    public void displayImages(List<? extends IImageEntry<?>> entries) {
    693840        boolean imageChanged;
    694841        IImageEntry<?> entry = entries != null && entries.size() == 1 ? entries.get(0) : null;
     
    711858        }
    712859
    713         if (entries == null || entries.isEmpty() || entries.stream().allMatch(Objects::isNull)) {
    714             this.tabbedEntries.remove(layer);
     860
     861        final boolean updateRequired;
     862        final List<IGeoImageLayer> imageLayers = MainApplication.getLayerManager().getLayers().stream()
     863                    .filter(IGeoImageLayer.class::isInstance).map(IGeoImageLayer.class::cast).collect(Collectors.toList());
     864        if (!Config.getPref().getBoolean("geoimage.viewer.show.tabs", true)) {
     865            updateRequired = true;
     866            // Clear the selected images in other geoimage layers
     867            this.getImageTabs().map(m -> m.layer).filter(IGeoImageLayer.class::isInstance).map(IGeoImageLayer.class::cast)
     868                    .filter(l -> !Objects.equals(entries, l.getSelection()))
     869                    .forEach(IGeoImageLayer::clearSelection);
    715870        } else {
    716             this.tabbedEntries.put(layer, entries);
    717         }
    718         this.updateLayers();
     871            updateRequired = imageLayers.stream().anyMatch(l -> this.getImageTabs().map(m -> m.layer).noneMatch(l::equals));
     872        }
     873        this.updateLayers(updateRequired);
    719874        if (entry != null) {
    720875            this.updateButtonsNonNullEntry(entry, imageChanged);
    721         } else if (this.tabbedEntries.isEmpty()) {
     876        } else if (imageLayers.isEmpty()) {
    722877            this.updateButtonsNullEntry(entries);
    723878            return;
    724879        } 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) {
     880            IGeoImageLayer layer = this.getImageTabs().map(m -> m.layer).filter(l -> l.getSelection().size() == 1).findFirst().orElse(null);
     881            if (layer == null) {
    728882                this.updateButtonsNullEntry(entries);
    729883            } else {
    730                 this.displayImages(realEntry.getKey(), realEntry.getValue());
     884                this.displayImages(layer.getSelection());
    731885            }
    732886            return;
     
    745899     * @param entries {@code true} if multiple images are selected
    746900     */
    747     private void updateButtonsNullEntry(List<IImageEntry<?>> entries) {
     901    private void updateButtonsNullEntry(List<? extends IImageEntry<?>> entries) {
    748902        boolean hasMultipleImages = entries != null && entries.size() > 1;
    749903        // if this method is called to reinitialize dialog content with a blank image,
    750904        // do not actually show the dialog again with a blank image if currently hidden (fix #10672)
    751         setTitle(tr("Geotagged Images"));
     905        this.updateTitle();
    752906        imgDisplay.setImage(null);
    753907        imgDisplay.setOsdText("");
     
    788942        btnOpenExternal.setEnabled(true);
    789943
    790         setTitle(tr("Geotagged Images") + (!entry.getDisplayName().isEmpty() ? " - " + entry.getDisplayName() : ""));
     944        this.updateTitle();
    791945        StringBuilder osd = new StringBuilder(entry.getDisplayName());
    792946        if (entry.getElevation() != null) {
     
    819973    }
    820974
    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);
    831     }
    832 
    833     private static boolean isLastImageSelected(List<IImageEntry<?>> data) {
     975    private void updateTitle() {
     976        final IImageEntry<?> entry;
     977        synchronized (this) {
     978            entry = this.currentEntry;
     979        }
     980        String baseTitle = Optional.ofNullable(this.layers.getSelectedComponent())
     981                .filter(MoveImgDisplayPanel.class::isInstance).map(MoveImgDisplayPanel.class::cast)
     982                .map(m -> m.layer).map(Layer::getLabel).orElse(tr("Geotagged Images"));
     983        if (entry == null) {
     984            this.setTitle(baseTitle);
     985        } else {
     986            this.setTitle(baseTitle + (!entry.getDisplayName().isEmpty() ? " - " + entry.getDisplayName() : ""));
     987        }
     988    }
     989
     990    private static boolean isLastImageSelected(List<? extends IImageEntry<?>> data) {
    834991        return data.stream().anyMatch(image -> data.contains(image.getLastImage()));
    835992    }
    836993
    837     private static boolean isFirstImageSelected(List<IImageEntry<?>> data) {
     994    private static boolean isFirstImageSelected(List<? extends IImageEntry<?>> data) {
    838995        return data.stream().anyMatch(image -> data.contains(image.getFirstImage()));
    839996    }
     
    8581015            btnCollapse.setVisible(!isDocked);
    8591016        }
    860         this.updateLayers();
     1017        this.updateLayers(true);
    8611018    }
    8621019
     
    9051062    @Override
    9061063    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);
    913         }
    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());
     1064        if (e.getRemovedLayer() instanceof IGeoImageLayer && ((IGeoImageLayer) e.getRemovedLayer()).containsImage(this.currentEntry)) {
     1065            displayImages(null);
     1066        }
     1067        this.updateLayers(true);
    9161068    }
    9171069
    9181070    @Override
    9191071    public void layerOrderChanged(LayerOrderChangeEvent e) {
    920         // ignored
     1072        this.updateLayers(true);
    9211073    }
    9221074
     
    9421094
    9431095    private void registerOnLayer(Layer layer) {
    944         if (layer instanceof GeoImageLayer) {
    945             ((GeoImageLayer) layer).getImageData().addImageDataUpdateListener(this);
     1096        if (layer instanceof IGeoImageLayer) {
     1097            layer.addPropertyChangeListener(l -> {
     1098                final List<?> currentTabLayers = this.getImageTabs().map(m -> m.layer).collect(Collectors.toList());
     1099                if (Layer.NAME_PROP.equals(l.getPropertyName()) && currentTabLayers.contains(layer)) {
     1100                    this.updateLayers(true);
     1101                        if (((IGeoImageLayer) layer).containsImage(this.currentEntry)) {
     1102                            this.updateTitle();
     1103                        }
     1104                } // Use Layer.VISIBLE_PROP here if we decide to do something when layer visibility changes
     1105            });
    9461106        }
    9471107    }
     
    9521112            imageData.setSelectedImage(imageData.getFirstImage());
    9531113        }
     1114        if (newLayer instanceof IGeoImageLayer) {
     1115            this.updateLayers(true);
     1116        }
    9541117    }
    9551118
     
    9601123        }
    9611124    }
    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     }
    9751125}
  • trunk/src/org/openstreetmap/josm/gui/layer/geoimage/RemoteEntry.java

    r18592 r18613  
    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;
     
    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;
     
    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
     
    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);
     
    7471        this.nextImage = nextImage;
    7572        this.lastImage = lastImage;
    76         this.layer = layer;
    7773    }
    7874
     
    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
  • trunk/src/org/openstreetmap/josm/gui/layer/markerlayer/ImageMarker.java

    r18593 r18613  
    55
    66import java.awt.event.ActionEvent;
     7import java.net.URI;
    78import java.net.URISyntaxException;
    89import java.net.URL;
     
    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;
     
    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
     
    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        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}
  • trunk/src/org/openstreetmap/josm/gui/layer/markerlayer/Marker.java

    r18287 r18613  
    2121import javax.swing.ImageIcon;
    2222
     23import org.openstreetmap.josm.data.IQuadBucketType;
    2324import org.openstreetmap.josm.data.Preferences;
    2425import org.openstreetmap.josm.data.coor.CachedLatLon;
     
    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;
     
    7678 * @author Frederik Ramm
    7779 */
    78 public class Marker implements TemplateEngineDataProvider, ILatLon, Destroyable {
     80public class Marker implements TemplateEngineDataProvider, ILatLon, Destroyable, IQuadBucketType {
    7981
    8082    /**
     
    448450        return "draw.rawgps." + getTextTemplateKey();
    449451    }
     452
     453    @Override
     454    public BBox getBBox() {
     455        return new BBox(this);
     456    }
    450457}
  • trunk/src/org/openstreetmap/josm/gui/layer/markerlayer/MarkerLayer.java

    r18287 r18613  
    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
     
    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;
     
    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;
     
    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;
     
    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    /**
     
    8998    final int markerSize = new IntegerProperty("draw.rawgps.markers.size", 4).get();
    9099    final BasicStroke markerStroke = new StrokeProperty("draw.rawgps.markers.stroke", "1").get();
     100
     101    private final ListenerList<IGeoImageLayer.ImageChangeListener> imageChangeListenerListenerList = ListenerList.create();
    91102
    92103    /**
     
    400411        }
    401412        MainApplication.getMap().mapView.zoomTo(currentMarker);
     413    }
     414
     415    /**
     416     * Set the current marker
     417     * @param newMarker The marker to set
     418     */
     419    void setCurrentMarker(Marker newMarker) {
     420        this.currentMarker = newMarker;
    402421    }
    403422
     
    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
     
    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
     
    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}
Note: See TracChangeset for help on using the changeset viewer.