Changeset 18591 in josm for trunk/src/org


Ignore:
Timestamp:
2022-11-09T20:26:24+01:00 (2 years 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.

Location:
trunk/src/org/openstreetmap/josm
Files:
4 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/org/openstreetmap/josm/data/ImageData.java

    r18444 r18591  
    1111import org.openstreetmap.josm.data.gpx.GpxImageEntry;
    1212import org.openstreetmap.josm.data.osm.QuadBuckets;
     13import org.openstreetmap.josm.gui.layer.Layer;
    1314import org.openstreetmap.josm.gui.layer.geoimage.ImageEntry;
    1415import org.openstreetmap.josm.tools.ListenerList;
     
    4243    private final ListenerList<ImageDataUpdateListener> listeners = ListenerList.create();
    4344    private final QuadBuckets<ImageEntry> geoImages = new QuadBuckets<>();
     45    private Layer layer;
    4446
    4547    /**
     
    327329
    328330    /**
    329      * Remove the image from the list and optionnally trigger update listener
     331     * Remove the image from the list and optionally trigger update listener
    330332     * @param img the {@link ImageEntry} to remove
    331333     * @param fireUpdateEvent if {@code true}, notifies listeners of image update
     
    377379
    378380    /**
     381     * Set the layer for use with {@link org.openstreetmap.josm.gui.layer.geoimage.ImageViewerDialog#displayImages(Layer, List)}
     382     * @param layer The layer to use for organization
     383     * @since 18591
     384     */
     385    public void setLayer(Layer layer) {
     386        this.layer = layer;
     387    }
     388
     389    /**
     390     * Get the layer that this data is associated with. May be {@code null}.
     391     * @return The layer this data is associated with.
     392     * @since 18591
     393     */
     394    public Layer getLayer() {
     395        return this.layer;
     396    }
     397
     398    /**
    379399     * Add a listener that listens to image data changes
    380400     * @param listener the {@link ImageDataUpdateListener}
  • trunk/src/org/openstreetmap/josm/gui/layer/geoimage/GeoImageLayer.java

    r18265 r18591  
    172172        this.useThumbs = useThumbs;
    173173        this.data.addImageDataUpdateListener(this);
     174        this.data.setLayer(this);
    174175    }
    175176
     
    232233                } else {
    233234                    data.setSelectedImage(img);
     235                    ImageViewerDialog.getInstance().displayImages(GeoImageLayer.this, Collections.singletonList(img));
    234236                }
    235237            }
     
    522524     */
    523525    public void showCurrentPhoto() {
    524         if (data.getSelectedImage() != null) {
    525             clearOtherCurrentPhotos();
    526         }
    527526        updateBufferAndRepaint();
    528527    }
     
    630629
    631630    /**
    632      * Clears the currentPhoto of the other GeoImageLayer's. Otherwise there could be multiple selected photos.
    633      */
    634     private void clearOtherCurrentPhotos() {
    635         for (GeoImageLayer layer:
    636                  MainApplication.getLayerManager().getLayersOfType(GeoImageLayer.class)) {
    637             if (layer != this) {
    638                 layer.getImageData().clearSelectedImage();
    639             }
    640         }
    641     }
    642 
    643     /**
    644631     * Registers a map mode for which the functionality of this layer should be available.
    645632     * @param mapMode Map mode to be registered
  • trunk/src/org/openstreetmap/josm/gui/layer/geoimage/ImageEntry.java

    r18421 r18591  
    154154    @Override
    155155    public void selectImage(ImageViewerDialog imageViewerDialog, IImageEntry<?> entry) {
    156         IImageEntry.super.selectImage(imageViewerDialog, entry);
    157156        if (entry instanceof ImageEntry) {
    158157            this.dataSet.setSelectedImage((ImageEntry) entry);
    159158        }
     159        imageViewerDialog.displayImages(this.dataSet.getLayer(), Collections.singletonList(entry));
    160160    }
    161161
     
    212212    }
    213213
    214     private ImageReadParam withSubsampling(ImageReader reader, Dimension target) {
     214    private static ImageReadParam withSubsampling(ImageReader reader, Dimension target) {
    215215        try {
    216216            ImageReadParam param = reader.getDefaultReadParam();
  • trunk/src/org/openstreetmap/josm/gui/layer/geoimage/ImageViewerDialog.java

    r18427 r18591  
    1212import java.awt.GridBagLayout;
    1313import java.awt.event.ActionEvent;
     14import java.awt.event.ActionListener;
    1415import java.awt.event.KeyEvent;
    1516import java.awt.event.WindowEvent;
     
    2223import java.util.Arrays;
    2324import java.util.Collections;
     25import java.util.Comparator;
     26import java.util.HashMap;
    2427import java.util.List;
     28import java.util.Map;
    2529import java.util.Objects;
    2630import java.util.Optional;
     
    3741import javax.swing.JToggleButton;
    3842import javax.swing.SwingConstants;
     43import javax.swing.SwingUtilities;
    3944
    4045import org.openstreetmap.josm.actions.JosmAction;
     
    5358import org.openstreetmap.josm.gui.layer.LayerManager.LayerOrderChangeEvent;
    5459import org.openstreetmap.josm.gui.layer.LayerManager.LayerRemoveEvent;
     60import org.openstreetmap.josm.gui.layer.MainLayerManager;
    5561import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeEvent;
    5662import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeListener;
    5763import org.openstreetmap.josm.gui.layer.imagery.ImageryFilterSettings;
     64import org.openstreetmap.josm.gui.util.GuiHelper;
    5865import org.openstreetmap.josm.gui.util.imagery.Vector3D;
    5966import org.openstreetmap.josm.tools.ImageProvider;
     
    121128    private JButton btnDeleteFromDisk;
    122129    private JToggleButton tbCentre;
     130    /** The layer tab (used to select images when multiple layers provide images, makes for easy switching) */
     131    private JPanel layers;
    123132
    124133    private ImageViewerDialog() {
     
    153162    private void build() {
    154163        JPanel content = new JPanel(new BorderLayout());
     164        this.layers = new JPanel(new GridBagLayout());
     165        content.add(layers, BorderLayout.NORTH);
    155166
    156167        content.add(imgDisplay, BorderLayout.CENTER);
     
    212223
    213224        createLayout(content, false, null);
     225    }
     226
     227    private void updateLayers() {
     228        if (this.tabbedEntries.size() <= 1) {
     229            this.layers.setVisible(false);
     230            this.layers.removeAll();
     231        } else {
     232            this.layers.setVisible(true);
     233            // Remove all old components
     234            this.layers.removeAll();
     235            MainLayerManager layerManager = MainApplication.getLayerManager();
     236            List<Layer> invalidLayers = this.tabbedEntries.keySet().stream().filter(layer -> !layerManager.containsLayer(layer))
     237                    .collect(Collectors.toList());
     238            // `null` is for anything using the old methods, without telling us what layer it comes from.
     239            invalidLayers.remove(null);
     240            // We need to do multiple calls to avoid ConcurrentModificationExceptions
     241            invalidLayers.forEach(this.tabbedEntries::remove);
     242            addButtonsForImageLayers();
     243        }
     244        this.revalidate();
     245    }
     246
     247    /**
     248     * Add the buttons for image layers
     249     */
     250    private void addButtonsForImageLayers() {
     251        final IImageEntry<?> current;
     252        synchronized (this) {
     253            current = this.currentEntry;
     254        }
     255        List<JButton> layerButtons = new ArrayList<>(this.tabbedEntries.size());
     256        if (this.tabbedEntries.containsKey(null)) {
     257            List<IImageEntry<?>> nullEntries = this.tabbedEntries.get(null);
     258            JButton layerButton = createImageLayerButton(null, nullEntries);
     259            layerButtons.add(layerButton);
     260            layerButton.setEnabled(!nullEntries.contains(current));
     261        }
     262        for (Map.Entry<Layer, List<IImageEntry<?>>> entry :
     263                this.tabbedEntries.entrySet().stream().filter(entry -> entry.getKey() != null)
     264                        .sorted(Comparator.comparing(entry -> entry.getKey().getName())).collect(Collectors.toList())) {
     265            JButton layerButton = createImageLayerButton(entry.getKey(), entry.getValue());
     266            layerButtons.add(layerButton);
     267            layerButton.setEnabled(!entry.getValue().contains(current));
     268        }
     269        layerButtons.forEach(this.layers::add);
     270    }
     271
     272    /**
     273     * Create a button for a specific layer and its entries
     274     *
     275     * @param layer     The layer to switch to
     276     * @param entries   The entries to display
     277     * @return The button to use to switch to the specified layer
     278     */
     279    private static JButton createImageLayerButton(Layer layer, List<IImageEntry<?>> entries) {
     280        final JButton layerButton = new JButton();
     281        layerButton.addActionListener(new ImageActionListener(layer, entries));
     282        layerButton.setText(layer != null ? layer.getLabel() : tr("Default"));
     283        return layerButton;
    214284    }
    215285
     
    307377        }
    308378
     379        /**
     380         * Update the icon for this action
     381         */
    309382        public void updateIcon() {
    310383            if (this.last != null) {
     
    350423            }
    351424            return super.getSupplier();
     425        }
     426    }
     427
     428    /**
     429     * A listener that is called to change the viewing layer
     430     */
     431    private static class ImageActionListener implements ActionListener {
     432
     433        private final Layer layer;
     434        private final List<IImageEntry<?>> entries;
     435
     436        ImageActionListener(Layer layer, List<IImageEntry<?>> entries) {
     437            this.layer = layer;
     438            this.entries = entries;
     439        }
     440
     441        @Override
     442        public void actionPerformed(ActionEvent e) {
     443            ImageViewerDialog.getInstance().displayImages(this.layer, this.entries);
    352444        }
    353445    }
     
    552644    }
    553645
     646    /** Used for tabbed panes */
     647    private final transient Map<Layer, List<IImageEntry<?>>> tabbedEntries = new HashMap<>();
    554648    private transient IImageEntry<? extends IImageEntry<?>> currentEntry;
    555649
     
    579673     */
    580674    public void displayImages(List<IImageEntry<?>> entries) {
     675        this.displayImages((Layer) null, entries);
     676    }
     677
     678    /**
     679     * Displays images for the given layer.
     680     * @param layer The layer to use for the tab ui
     681     * @param entries image entries
     682     * @since 18591
     683     */
     684    public void displayImages(Layer layer, List<IImageEntry<?>> entries) {
    581685        boolean imageChanged;
    582686        IImageEntry<?> entry = entries != null && entries.size() == 1 ? entries.get(0) : null;
     
    599703        }
    600704
     705        if (entries == null || entries.isEmpty() || entries.stream().allMatch(Objects::isNull)) {
     706            this.tabbedEntries.remove(layer);
     707        } else {
     708            this.tabbedEntries.put(layer, entries);
     709        }
     710        this.updateLayers();
    601711        if (entry != null) {
    602712            this.updateButtonsNonNullEntry(entry, imageChanged);
     713        } else if (this.tabbedEntries.isEmpty()) {
     714            this.updateButtonsNullEntry(entries);
     715            return;
    603716        } else {
    604             this.updateButtonsNullEntry(entries);
     717            Map.Entry<Layer, List<IImageEntry<?>>> realEntry =
     718                    this.tabbedEntries.entrySet().stream().filter(mapEntry -> mapEntry.getValue().size() == 1).findFirst().orElse(null);
     719            if (realEntry == null) {
     720                this.updateButtonsNullEntry(entries);
     721            } else {
     722                this.displayImages(realEntry.getKey(), realEntry.getValue());
     723            }
    605724            return;
    606725        }
     
    731850            btnCollapse.setVisible(!isDocked);
    732851        }
     852        this.updateLayers();
    733853    }
    734854
     
    798918    }
    799919
     920    /**
     921     * Reload the image. Call this if you load a low-resolution image first, and then get a high-resolution image, or
     922     * if you know that the image has changed on disk.
     923     * @since 18591
     924     */
     925    public void refresh() {
     926        if (SwingUtilities.isEventDispatchThread()) {
     927            this.updateButtonsNonNullEntry(currentEntry, true);
     928        } else {
     929            GuiHelper.runInEDT(this::refresh);
     930        }
     931    }
     932
    800933    private void registerOnLayer(Layer layer) {
    801934        if (layer instanceof GeoImageLayer) {
     
    820953    @Override
    821954    public void selectedImageChanged(ImageData data) {
    822         displayImages(new ArrayList<>(data.getSelectedImages()));
     955        if (this.currentEntry != data.getSelectedImage() && this.currentEntry instanceof ImageEntry &&
     956                !data.getSelectedImages().contains(this.currentEntry)) {
     957            displayImages(data.getLayer(), new ArrayList<>(data.getSelectedImages()));
     958        }
    823959    }
    824960
    825961    @Override
    826962    public void imageDataUpdated(ImageData data) {
    827         displayImages(new ArrayList<>(data.getSelectedImages()));
     963        displayImages(data.getLayer(), new ArrayList<>(data.getSelectedImages()));
    828964    }
    829965}
Note: See TracChangeset for help on using the changeset viewer.