Ticket #21605: 21605.5.patch
| File 21605.5.patch, 52.0 KB (added by , 3 years ago) |
|---|
-
src/org/openstreetmap/josm/gui/layer/geoimage/GeoImageLayer.java
Subject: [PATCH] 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. --- 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 33 33 34 34 import javax.swing.Action; 35 35 import javax.swing.Icon; 36 import javax.swing.ImageIcon; 36 37 37 38 import org.openstreetmap.josm.actions.AutoScaleAction; 38 39 import org.openstreetmap.josm.actions.ExpertToggleAction; … … 47 48 import org.openstreetmap.josm.data.gpx.GpxData; 48 49 import org.openstreetmap.josm.data.gpx.GpxImageEntry; 49 50 import org.openstreetmap.josm.data.gpx.GpxTrack; 51 import org.openstreetmap.josm.data.imagery.street_level.IImageEntry; 50 52 import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor; 53 import org.openstreetmap.josm.data.preferences.NamedColorProperty; 51 54 import org.openstreetmap.josm.gui.MainApplication; 52 55 import org.openstreetmap.josm.gui.MapFrame; 53 56 import org.openstreetmap.josm.gui.MapFrame.MapModeChangeListener; … … 62 65 import org.openstreetmap.josm.gui.layer.JumpToMarkerActions.JumpToPreviousMarker; 63 66 import org.openstreetmap.josm.gui.layer.Layer; 64 67 import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeListener; 68 import org.openstreetmap.josm.gui.util.GuiHelper; 65 69 import org.openstreetmap.josm.gui.util.imagery.Vector3D; 66 70 import org.openstreetmap.josm.tools.ImageProvider; 71 import org.openstreetmap.josm.tools.ListenerList; 67 72 import org.openstreetmap.josm.tools.Utils; 68 73 69 74 /** … … 71 76 * @since 99 72 77 */ 73 78 public class GeoImageLayer extends AbstractModifiableLayer implements 74 JumpToMarkerLayer, NavigatableComponent.ZoomChangeListener, ImageDataUpdateListener { 79 JumpToMarkerLayer, NavigatableComponent.ZoomChangeListener, ImageDataUpdateListener, 80 IGeoImageLayer { 75 81 76 82 private static final List<Action> menuAdditions = new LinkedList<>(); 77 83 78 84 private static volatile List<MapMode> supportedMapModes; 79 85 80 86 private final ImageData data; 87 private final ListenerList<IGeoImageLayer.ImageChangeListener> imageChangeListeners = ListenerList.create(); 81 88 GpxData gpxData; 82 89 GpxLayer gpxFauxLayer; 83 90 GpxData gpxFauxData; … … 86 93 87 94 private final Icon icon = ImageProvider.get("dialogs/geoimage/photo-marker"); 88 95 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(255, 170, 255)).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 } 89 109 90 110 boolean useThumbs; 91 111 private final ExecutorService thumbsLoaderExecutor = … … 172 192 this.useThumbs = useThumbs; 173 193 this.data.addImageDataUpdateListener(this); 174 194 this.data.setLayer(this); 195 if (!ImageViewerDialog.hasInstance()) { 196 this.data.setSelectedImage(this.data.getFirstImage()); 197 // This must be called *after* this layer is added to the layer manager. 198 // But it must also be run in the EDT. By adding this to the worker queue 199 // and then running the actual code in the EDT, we ensure that the layer 200 // will be added to the layer manager regardless of whether or not this 201 // was instantiated in the worker thread or the EDT thread. 202 MainApplication.worker.submit(() -> GuiHelper.runInEDT(() -> 203 ImageViewerDialog.getInstance().displayImages(Collections.singletonList(this.data.getSelectedImage())))); 204 } 175 205 } 176 206 177 207 private final class ImageMouseListener extends MouseAdapter { … … 232 262 } 233 263 } else { 234 264 data.setSelectedImage(img); 235 ImageViewerDialog.getInstance().displayImages( GeoImageLayer.this,Collections.singletonList(img));265 ImageViewerDialog.getInstance().displayImages(Collections.singletonList(img)); 236 266 } 237 267 } 238 268 } … … 247 277 MainApplication.worker.execute(new ImagesLoader(files, gpxLayer)); 248 278 } 249 279 280 @Override 281 public void clearSelection() { 282 this.getImageData().clearSelectedImage(); 283 } 284 285 @Override 286 public boolean containsImage(IImageEntry<?> imageEntry) { 287 if (imageEntry instanceof ImageEntry) { 288 return this.data.getImages().contains(imageEntry); 289 } 290 return false; 291 } 292 250 293 @Override 251 294 public Icon getIcon() { 252 295 return ImageProvider.get("dialogs/geoimage", ImageProvider.ImageSizes.LAYER); 253 296 } 254 297 298 @Override 299 public List<ImageEntry> getSelection() { 300 return this.getImageData().getSelectedImages(); 301 } 302 303 @Override 304 public List<IImageEntry<?>> getInvalidGeoImages() { 305 return this.getImageData().getImages().stream().filter(entry -> entry.getPos() == null || entry.getExifCoor() == null 306 || !entry.getExifCoor().isValid() || !entry.getPos().isValid()).collect(toList()); 307 } 308 309 @Override 310 public void addImageChangeListener(ImageChangeListener listener) { 311 this.imageChangeListeners.addListener(listener); 312 } 313 314 @Override 315 public void removeImageChangeListener(ImageChangeListener listener) { 316 this.imageChangeListeners.removeListener(listener); 317 } 318 255 319 /** 256 320 * Register actions on the layer 257 321 * @param addition the action to be added … … 451 515 } 452 516 } 453 517 518 final IImageEntry<?> currentImage = ImageViewerDialog.getCurrentImage(); 454 519 for (ImageEntry e: data.getSelectedImages()) { 455 520 if (e != null && e.getPos() != null) { 456 521 Point p = mv.getPoint(e.getPos()); … … 465 530 if (useThumbs && e.hasThumbnail()) { 466 531 g.setColor(new Color(128, 0, 0, 122)); 467 532 g.fillRect(p.x - imgDim.width / 2, p.y - imgDim.height / 2, imgDim.width, imgDim.height); 533 } else if (e.equals(currentImage)) { 534 selectedIcon.paintIcon(mv, g, 535 p.x - imgDim.width / 2, 536 p.y - imgDim.height / 2); 468 537 } else { 469 selectedIcon .paintIcon(mv, g,538 selectedIconNotImageViewer.paintIcon(mv, g, 470 539 p.x - imgDim.width / 2, 471 540 p.y - imgDim.height / 2); 472 541 } … … 884 953 @Override 885 954 public void selectedImageChanged(ImageData data) { 886 955 showCurrentPhoto(); 956 this.imageChangeListeners.fireEvent(e -> e.imageChanged(this, null, data.getSelectedImages())); 887 957 } 888 958 889 959 @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. 2 package org.openstreetmap.josm.gui.layer.geoimage; 3 4 import java.util.Collections; 5 import java.util.List; 6 7 import org.openstreetmap.josm.data.imagery.street_level.IImageEntry; 8 9 /** 10 * An interface for layers which want to show images 11 * @since xxx 12 */ 13 public 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 147 147 if (entry instanceof ImageEntry) { 148 148 this.dataSet.setSelectedImage((ImageEntry) entry); 149 149 } 150 imageViewerDialog.displayImages( this.dataSet.getLayer(),Collections.singletonList(entry));150 imageViewerDialog.displayImages(Collections.singletonList(entry)); 151 151 } 152 152 153 153 @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 11 11 import java.awt.GridBagConstraints; 12 12 import java.awt.GridBagLayout; 13 13 import java.awt.event.ActionEvent; 14 import java.awt.event.ActionListener;15 14 import java.awt.event.KeyEvent; 16 15 import java.awt.event.WindowEvent; 17 16 import java.io.IOException; … … 23 22 import java.util.Arrays; 24 23 import java.util.Collections; 25 24 import java.util.Comparator; 26 import java.util.HashMap;27 25 import java.util.List; 28 import java.util.Map;29 26 import java.util.Objects; 30 27 import java.util.Optional; 31 28 import java.util.concurrent.Future; 32 29 import java.util.function.UnaryOperator; 33 30 import java.util.stream.Collectors; 31 import java.util.stream.IntStream; 32 import java.util.stream.Stream; 34 33 35 34 import javax.swing.AbstractAction; 35 import javax.swing.AbstractButton; 36 36 import javax.swing.Box; 37 37 import javax.swing.JButton; 38 38 import javax.swing.JLabel; … … 42 42 import javax.swing.SwingConstants; 43 43 import javax.swing.SwingUtilities; 44 44 45 import org.openstreetmap.josm.actions.ExpertToggleAction; 45 46 import org.openstreetmap.josm.actions.JosmAction; 46 47 import org.openstreetmap.josm.data.ImageData; 47 import org.openstreetmap.josm.data.ImageData.ImageDataUpdateListener;48 48 import org.openstreetmap.josm.data.imagery.street_level.IImageEntry; 49 49 import org.openstreetmap.josm.gui.ExtendedDialog; 50 50 import org.openstreetmap.josm.gui.MainApplication; … … 64 64 import org.openstreetmap.josm.gui.layer.imagery.ImageryFilterSettings; 65 65 import org.openstreetmap.josm.gui.util.GuiHelper; 66 66 import org.openstreetmap.josm.gui.util.imagery.Vector3D; 67 import org.openstreetmap.josm.gui.widgets.HideableTabbedPane; 68 import org.openstreetmap.josm.spi.preferences.Config; 67 69 import org.openstreetmap.josm.tools.ImageProvider; 68 70 import org.openstreetmap.josm.tools.Logging; 69 71 import org.openstreetmap.josm.tools.PlatformManager; … … 73 75 /** 74 76 * Dialog to view and manipulate geo-tagged images from a {@link GeoImageLayer}. 75 77 */ 76 public final class ImageViewerDialog extends ToggleDialog implements LayerChangeListener, ActiveLayerChangeListener , ImageDataUpdateListener{78 public final class ImageViewerDialog extends ToggleDialog implements LayerChangeListener, ActiveLayerChangeListener { 77 79 private static final String GEOIMAGE_FILLER = marktr("Geoimage: {0}"); 78 80 private static final String DIALOG_FOLDER = "dialogs"; 79 81 … … 124 126 return dialog; 125 127 } 126 128 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 127 145 private JButton btnLast; 128 146 private JButton btnNext; 129 147 private JButton btnPrevious; … … 135 153 private JButton btnDeleteFromDisk; 136 154 private JToggleButton tbCentre; 137 155 /** The layer tab (used to select images when multiple layers provide images, makes for easy switching) */ 138 private JPanel layers;156 private final HideableTabbedPane layers = new HideableTabbedPane(); 139 157 140 158 private ImageViewerDialog() { 141 159 super(tr("Geotagged Images"), "geoimage", tr("Display geotagged images"), Shortcut.registerShortcut("tools:geotagged", … … 168 186 169 187 private void build() { 170 188 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 content.add(this.layers, BorderLayout.CENTER); 175 190 176 191 Dimension buttonDim = new Dimension(26, 26); 177 192 … … 187 202 btnLast = createNavigationButton(imageLastAction, buttonDim); 188 203 189 204 tbCentre = new JToggleButton(imageCenterViewAction); 205 tbCentre.setSelected(Config.getPref().getBoolean("geoimage.viewer.centre.on.image", false)); 190 206 tbCentre.setPreferredSize(buttonDim); 191 207 192 208 JButton btnZoomBestFit = new JButton(imageZoomAction); … … 196 212 btnCollapse.setAlignmentY(Component.TOP_ALIGNMENT); 197 213 198 214 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)); 215 addButtonGroup(buttons, this.btnFirst, this.btnPrevious, this.btnNext, this.btnLast); 216 addButtonGroup(buttons, this.tbCentre, btnZoomBestFit); 217 addButtonGroup(buttons, this.btnDelete, this.btnDeleteFromDisk); 218 addButtonGroup(buttons, this.btnCopyPath, this.btnOpenExternal); 219 addButtonGroup(buttons, createButton(visibilityAction, buttonDim)); 214 220 215 221 JPanel bottomPane = new JPanel(new GridBagLayout()); 216 222 GridBagConstraints gc = new GridBagConstraints(); … … 231 237 createLayout(content, false, null); 232 238 } 233 239 234 private void updateLayers() { 235 if (this.tabbedEntries.size() <= 1) { 240 /** 241 * Add a button group to a panel 242 * @param buttonPanel The panel holding the buttons 243 * @param buttons The button group to add 244 */ 245 private static void addButtonGroup(JPanel buttonPanel, AbstractButton... buttons) { 246 if (buttonPanel.getComponentCount() != 0) { 247 buttonPanel.add(Box.createRigidArea(new Dimension(7, 0))); 248 } 249 250 for (AbstractButton jButton : buttons) { 251 buttonPanel.add(jButton); 252 } 253 } 254 255 /** 256 * Update the tabs for the different image layers 257 * @param changed {@code true} if the tabs changed 258 */ 259 private void updateLayers(boolean changed) { 260 MainLayerManager layerManager = MainApplication.getLayerManager(); 261 List<IGeoImageLayer> geoImageLayers = layerManager.getLayers().stream() 262 .filter(IGeoImageLayer.class::isInstance).map(IGeoImageLayer.class::cast).collect(Collectors.toList()); 263 if (geoImageLayers.isEmpty()) { 236 264 this.layers.setVisible(false); 237 this.layers.removeAll();238 265 } else { 239 266 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(); 267 if (changed) { 268 addButtonsForImageLayers(); 269 } 270 MoveImgDisplayPanel<?> selected = (MoveImgDisplayPanel<?>) this.layers.getSelectedComponent(); 271 if ((this.imgDisplay.getParent() == null || this.imgDisplay.getParent().getParent() == null) 272 && selected != null && selected.layer.containsImage(this.currentEntry)) { 273 selected.setVisible(selected.isVisible()); 274 } else if (selected != null && !selected.layer.containsImage(this.currentEntry)) { 275 this.getImageTabs().filter(m -> m.layer.containsImage(this.currentEntry)).mapToInt(this.layers::indexOfComponent).findFirst() 276 .ifPresent(this.layers::setSelectedIndex); 277 } 250 278 this.layers.invalidate(); 251 279 } 280 this.layers.getParent().invalidate(); 252 281 this.revalidate(); 253 282 } 254 283 … … 256 285 * Add the buttons for image layers 257 286 */ 258 287 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)); 288 List<MoveImgDisplayPanel<?>> alreadyAdded = this.getImageTabs().collect(Collectors.toList()); 289 List<Layer> availableLayers = MainApplication.getLayerManager().getLayers(); 290 for (IGeoImageLayer layer : availableLayers.stream() 291 .sorted(Comparator.comparingInt(entry -> /*reverse*/-availableLayers.indexOf(entry))) 292 .filter(IGeoImageLayer.class::isInstance).map(IGeoImageLayer.class::cast).collect(Collectors.toList())) { 293 final int index = availableLayers.size() - availableLayers.indexOf((Layer) layer); 294 final String label = (ExpertToggleAction.isExpert() ? "[" + index + "] " : "") + ((Layer) layer).getLabel(); 295 final Optional<MoveImgDisplayPanel<?>> originalPanel = alreadyAdded.stream() 296 .filter(m -> Objects.equals(m.layer, layer)).findFirst(); 297 if (originalPanel.isPresent()) { 298 int componentIndex = this.layers.indexOfComponent(originalPanel.get()); 299 this.layers.setTitleAt(componentIndex, label); 300 } else { 301 this.layers.addTab(label, new MoveImgDisplayPanel<>(this.imgDisplay, (Layer & IGeoImageLayer) layer)); 302 } 269 303 } 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)); 304 this.getImageTabs().map(p -> p.layer).filter(layer -> !availableLayers.contains(layer)) 305 // We have to collect to a list prior to removal -- if we don't, then the stream may get a layer at index 0, 306 // remove that layer, and then get a layer at index 1, which was previously at index 2. 307 .collect(Collectors.toList()).forEach(this::removeImageTab); 308 309 // This is need to avoid the first button becoming visible, and then recalling this method. 310 this.getImageTabs().forEach(m -> m.finishedAddingButtons = true); 311 // After that, trigger the visibility set code 312 this.getImageTabs().forEach(m -> m.setVisible(m.isVisible())); 313 } 314 315 /** 316 * Remove a tab for a layer from the {@link #layers} tab pane 317 * @param layer The layer to remove 318 */ 319 private void removeImageTab(Layer layer) { 320 // This must be reversed to avoid removing the wrong tab 321 for (int i = this.layers.getTabCount() - 1; i >= 0; i--) { 322 Component component = this.layers.getComponentAt(i); 323 if (component instanceof MoveImgDisplayPanel) { 324 MoveImgDisplayPanel<?> moveImgDisplayPanel = (MoveImgDisplayPanel<?>) component; 325 if (Objects.equals(layer, moveImgDisplayPanel.layer)) { 326 this.layers.removeTabAt(i); 327 this.layers.remove(moveImgDisplayPanel); 328 } 329 } 276 330 } 277 layerButtons.forEach(this.layers::add);278 331 } 279 332 280 333 /** 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 334 * Get the {@link MoveImgDisplayPanel} objects in {@link #layers}. 335 * @return The individual panels 286 336 */ 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;337 private Stream<MoveImgDisplayPanel<?>> getImageTabs() { 338 return IntStream.range(0, this.layers.getTabCount()) 339 .mapToObj(this.layers::getComponentAt) 340 .filter(MoveImgDisplayPanel.class::isInstance) 341 .map(m -> (MoveImgDisplayPanel<?>) m); 292 342 } 293 343 294 344 @Override … … 309 359 imageZoomAction.destroy(); 310 360 cancelLoadingImage(); 311 361 super.destroy(); 312 d ialog = null;362 destroyInstance(); 313 363 } 314 364 315 365 /** … … 433 483 } 434 484 } 435 485 436 /**437 * A listener that is called to change the viewing layer438 */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 @Override450 public void actionPerformed(ActionEvent e) {451 ImageViewerDialog.getInstance().displayImages(this.layer, this.entries);452 }453 }454 455 486 private class ImageFirstAction extends ImageRememberAction { 456 487 ImageFirstAction() { 457 488 super(null, new ImageProvider(DIALOG_FOLDER, "first"), tr("First"), Shortcut.registerShortcut( … … 478 509 public void actionPerformed(ActionEvent e) { 479 510 final JToggleButton button = (JToggleButton) e.getSource(); 480 511 centerView = button.isEnabled() && button.isSelected(); 512 Config.getPref().putBoolean("geoimage.viewer.centre.on.image", centerView); 481 513 if (centerView && currentEntry != null && currentEntry.getPos() != null) { 482 514 MainApplication.getMap().mapView.zoomTo(currentEntry.getPos()); 483 515 } … … 618 650 } 619 651 } 620 652 653 /** 654 * A JPanel whose entire purpose is to display an image by (a) moving the imgDisplay arround and (b) setting the imgDisplay as a child 655 * for this panel. 656 */ 657 private static class MoveImgDisplayPanel<T extends Layer & IGeoImageLayer> extends JPanel { 658 private final T layer; 659 private final ImageDisplay imgDisplay; 660 661 /** 662 * 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 663 * has multiple tabs on initialization (like from a session). 664 */ 665 boolean finishedAddingButtons; 666 MoveImgDisplayPanel(ImageDisplay imgDisplay, T layer) { 667 super(new BorderLayout()); 668 this.layer = layer; 669 this.imgDisplay = imgDisplay; 670 } 671 672 @Override 673 public void setVisible(boolean visible) { 674 super.setVisible(visible); 675 if (visible && this.finishedAddingButtons) { 676 if (!this.layer.getSelection().isEmpty() && !this.layer.getSelection().contains(ImageViewerDialog.getCurrentImage())) { 677 ImageViewerDialog.getInstance().displayImages(this.layer.getSelection()); 678 this.layer.invalidate(); 679 } 680 if (this.imgDisplay.getParent() != this) { 681 this.add(this.imgDisplay, BorderLayout.CENTER); 682 this.imgDisplay.invalidate(); 683 this.revalidate(); 684 } 685 } 686 } 687 } 688 621 689 /** 622 690 * Enables (or disables) the "Previous" button. 623 691 * @param value {@code true} to enable the button, {@code false} otherwise … … 651 719 return wasEnabled; 652 720 } 653 721 654 /** Used for tabbed panes */655 private final transient Map<Layer, List<IImageEntry<?>>> tabbedEntries = new HashMap<>();656 722 private transient IImageEntry<? extends IImageEntry<?>> currentEntry; 657 723 658 724 /** … … 679 745 * @param entries image entries 680 746 * @since 18246 681 747 */ 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) { 748 public void displayImages(List<? extends IImageEntry<?>> entries) { 693 749 boolean imageChanged; 694 750 IImageEntry<?> entry = entries != null && entries.size() == 1 ? entries.get(0) : null; 695 751 … … 710 766 } 711 767 } 712 768 713 if (entries == null || entries.isEmpty() || entries.stream().allMatch(Objects::isNull)) { 714 this.tabbedEntries.remove(layer); 769 770 final boolean updateRequired; 771 final List<IGeoImageLayer> imageLayers = MainApplication.getLayerManager().getLayers().stream() 772 .filter(IGeoImageLayer.class::isInstance).map(IGeoImageLayer.class::cast).collect(Collectors.toList()); 773 if (!Config.getPref().getBoolean("geoimage.viewer.show.tabs", true)) { 774 updateRequired = true; 775 // Clear the selected images in other geoimage layers 776 this.getImageTabs().map(m -> m.layer).filter(IGeoImageLayer.class::isInstance).map(IGeoImageLayer.class::cast) 777 .filter(l -> !Objects.equals(entries, l.getSelection())) 778 .forEach(IGeoImageLayer::clearSelection); 715 779 } else { 716 this.tabbedEntries.put(layer, entries);780 updateRequired = imageLayers.stream().anyMatch(l -> this.getImageTabs().map(m -> m.layer).noneMatch(l::equals)); 717 781 } 718 this.updateLayers( );782 this.updateLayers(updateRequired); 719 783 if (entry != null) { 720 784 this.updateButtonsNonNullEntry(entry, imageChanged); 721 } else if ( this.tabbedEntries.isEmpty()) {785 } else if (imageLayers.isEmpty()) { 722 786 this.updateButtonsNullEntry(entries); 723 787 return; 724 788 } 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) { 789 IGeoImageLayer layer = imageLayers.stream().filter(l -> l.getSelection().size() == 1).findFirst().orElse(null); 790 if (layer == null) { 728 791 this.updateButtonsNullEntry(entries); 729 792 } else { 730 this.displayImages( realEntry.getKey(), realEntry.getValue());793 this.displayImages(layer.getSelection()); 731 794 } 732 795 return; 733 796 } … … 744 807 * Update buttons for null entry 745 808 * @param entries {@code true} if multiple images are selected 746 809 */ 747 private void updateButtonsNullEntry(List< IImageEntry<?>> entries) {810 private void updateButtonsNullEntry(List<? extends IImageEntry<?>> entries) { 748 811 boolean hasMultipleImages = entries != null && entries.size() > 1; 749 812 // if this method is called to reinitialize dialog content with a blank image, 750 813 // do not actually show the dialog again with a blank image if currently hidden (fix #10672) 751 setTitle(tr("Geotagged Images"));814 this.updateTitle(); 752 815 imgDisplay.setImage(null); 753 816 imgDisplay.setOsdText(""); 754 817 setNextEnabled(false); … … 787 850 btnCopyPath.setEnabled(true); 788 851 btnOpenExternal.setEnabled(true); 789 852 790 setTitle(tr("Geotagged Images") + (!entry.getDisplayName().isEmpty() ? " - " + entry.getDisplayName() : ""));853 this.updateTitle(); 791 854 StringBuilder osd = new StringBuilder(entry.getDisplayName()); 792 855 if (entry.getElevation() != null) { 793 856 osd.append(tr("\nAltitude: {0} m", Math.round(entry.getElevation()))); … … 818 881 imgDisplay.setOsdText(osd.toString()); 819 882 } 820 883 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); 884 private void updateTitle() { 885 final IImageEntry<?> entry; 886 synchronized (this) { 887 entry = this.currentEntry; 888 } 889 String baseTitle = Optional.of(this.layers.getSelectedComponent()) 890 .filter(MoveImgDisplayPanel.class::isInstance).map(MoveImgDisplayPanel.class::cast) 891 .map(m -> m.layer).map(Layer::getLabel).orElse(tr("Geotagged Images")); 892 if (entry == null) { 893 this.setTitle(baseTitle); 894 } else { 895 this.setTitle(baseTitle + (!entry.getDisplayName().isEmpty() ? " - " + entry.getDisplayName() : "")); 896 } 831 897 } 832 898 833 private static boolean isLastImageSelected(List< IImageEntry<?>> data) {899 private static boolean isLastImageSelected(List<? extends IImageEntry<?>> data) { 834 900 return data.stream().anyMatch(image -> data.contains(image.getLastImage())); 835 901 } 836 902 837 private static boolean isFirstImageSelected(List< IImageEntry<?>> data) {903 private static boolean isFirstImageSelected(List<? extends IImageEntry<?>> data) { 838 904 return data.stream().anyMatch(image -> data.contains(image.getFirstImage())); 839 905 } 840 906 … … 857 923 if (btnCollapse != null) { 858 924 btnCollapse.setVisible(!isDocked); 859 925 } 860 this.updateLayers( );926 this.updateLayers(true); 861 927 } 862 928 863 929 /** … … 904 970 905 971 @Override 906 972 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); 973 if (e.getRemovedLayer() instanceof IGeoImageLayer && ((IGeoImageLayer) e.getRemovedLayer()).containsImage(this.currentEntry)) { 974 displayImages(null); 913 975 } 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()); 976 this.updateLayers(true); 916 977 } 917 978 918 979 @Override 919 980 public void layerOrderChanged(LayerOrderChangeEvent e) { 920 // ignored981 this.updateLayers(true); 921 982 } 922 983 923 984 @Override … … 941 1002 } 942 1003 943 1004 private void registerOnLayer(Layer layer) { 944 if (layer instanceof GeoImageLayer) { 945 ((GeoImageLayer) layer).getImageData().addImageDataUpdateListener(this); 1005 if (layer instanceof IGeoImageLayer) { 1006 layer.addPropertyChangeListener(l -> { 1007 final List<?> currentTabLayers = this.getImageTabs().map(m -> m.layer).collect(Collectors.toList()); 1008 if (Layer.NAME_PROP.equals(l.getPropertyName()) && currentTabLayers.contains(layer)) { 1009 this.updateLayers(true); 1010 if (((IGeoImageLayer) layer).containsImage(this.currentEntry)) { 1011 this.updateTitle(); 1012 } 1013 } else if (Layer.VISIBLE_PROP.equals(l.getPropertyName()) && currentTabLayers.contains(layer)) { 1014 this.getImageTabs().filter(m -> Objects.equals(m.layer, layer)).mapToInt(this.layers::indexOfComponent) 1015 .filter(i -> i >= 0).forEach(i -> this.layers.setEnabledAt(i, layer.isVisible())); 1016 } 1017 }); 946 1018 } 947 1019 } 948 1020 … … 959 1031 imgLoadingFuture = null; 960 1032 } 961 1033 } 962 963 @Override964 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 @Override972 public void imageDataUpdated(ImageData data) {973 displayImages(data.getLayer(), new ArrayList<>(data.getSelectedImages()));974 }975 1034 } -
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 19 19 import org.openstreetmap.josm.data.coor.ILatLon; 20 20 import org.openstreetmap.josm.data.imagery.street_level.IImageEntry; 21 21 import org.openstreetmap.josm.data.imagery.street_level.Projections; 22 import org.openstreetmap.josm.gui.layer.Layer;23 22 import org.openstreetmap.josm.tools.HttpClient; 24 23 import org.openstreetmap.josm.tools.JosmRuntimeException; 25 24 … … 33 32 private final Supplier<RemoteEntry> nextImage; 34 33 private final Supplier<RemoteEntry> previousImage; 35 34 private final Supplier<RemoteEntry> lastImage; 36 private final Layer layer;37 35 private int width; 38 36 private int height; 39 37 private ILatLon pos; … … 54 52 55 53 /** 56 54 * Create a new remote entry 57 * @param layer The originating layer, used for tabs in the image viewer58 55 * @param uri The URI to use 59 56 * @param firstImage first image supplier 60 57 * @param nextImage next image supplier 61 58 * @param lastImage last image supplier 62 59 * @param previousImage previous image supplier 63 60 */ 64 public RemoteEntry( Layer layer,URI uri, Supplier<RemoteEntry> firstImage, Supplier<RemoteEntry> previousImage,61 public RemoteEntry(URI uri, Supplier<RemoteEntry> firstImage, Supplier<RemoteEntry> previousImage, 65 62 Supplier<RemoteEntry> nextImage, Supplier<RemoteEntry> lastImage) { 66 63 Objects.requireNonNull(uri); 67 64 Objects.requireNonNull(firstImage); … … 73 70 this.previousImage = previousImage; 74 71 this.nextImage = nextImage; 75 72 this.lastImage = lastImage; 76 this.layer = layer;77 73 } 78 74 79 75 @Override … … 319 315 320 316 @Override 321 317 public void selectImage(ImageViewerDialog imageViewerDialog, IImageEntry<?> entry) { 322 imageViewerDialog.displayImages( this.layer,Collections.singletonList(entry));318 imageViewerDialog.displayImages(Collections.singletonList(entry)); 323 319 } 324 320 325 321 @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 4 4 import static org.openstreetmap.josm.tools.I18n.tr; 5 5 6 6 import java.awt.event.ActionEvent; 7 import java.net.URI; 7 8 import java.net.URISyntaxException; 8 9 import java.net.URL; 9 10 import java.time.Instant; … … 16 17 import org.openstreetmap.josm.data.gpx.GpxConstants; 17 18 import org.openstreetmap.josm.data.gpx.GpxLink; 18 19 import org.openstreetmap.josm.data.gpx.WayPoint; 20 import org.openstreetmap.josm.data.imagery.street_level.IImageEntry; 19 21 import org.openstreetmap.josm.gui.Notification; 20 22 import org.openstreetmap.josm.gui.layer.geoimage.ImageViewerDialog; 21 23 import org.openstreetmap.josm.gui.layer.geoimage.RemoteEntry; … … 40 42 41 43 @Override 42 44 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())); 44 47 } 45 48 46 privateRemoteEntry getRemoteEntry() {49 RemoteEntry getRemoteEntry() { 47 50 try { 48 final RemoteEntry remoteEntry = new RemoteEntry(this.parentLayer,imageUrl.toURI(), getFirstImage(), getPreviousImage(),51 final RemoteEntry remoteEntry = new MarkerRemoteEntry(imageUrl.toURI(), getFirstImage(), getPreviousImage(), 49 52 getNextImage(), getLastImage()); 50 53 // First, extract EXIF data 51 54 remoteEntry.extractExif(); … … 128 131 wpt.put(GpxConstants.META_LINKS, Collections.singleton(link)); 129 132 return wpt; 130 133 } 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 } 131 156 } -
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 20 20 21 21 import javax.swing.ImageIcon; 22 22 23 import org.openstreetmap.josm.data.IQuadBucketType; 23 24 import org.openstreetmap.josm.data.Preferences; 24 25 import org.openstreetmap.josm.data.coor.CachedLatLon; 25 26 import org.openstreetmap.josm.data.coor.EastNorth; … … 27 28 import org.openstreetmap.josm.data.coor.LatLon; 28 29 import org.openstreetmap.josm.data.gpx.GpxConstants; 29 30 import org.openstreetmap.josm.data.gpx.WayPoint; 31 import org.openstreetmap.josm.data.osm.BBox; 30 32 import org.openstreetmap.josm.data.osm.search.SearchCompiler.Match; 31 33 import org.openstreetmap.josm.gui.MapView; 32 34 import org.openstreetmap.josm.gui.layer.GpxLayer; … … 75 77 * 76 78 * @author Frederik Ramm 77 79 */ 78 public class Marker implements TemplateEngineDataProvider, ILatLon, Destroyable {80 public class Marker implements TemplateEngineDataProvider, ILatLon, Destroyable, IQuadBucketType { 79 81 80 82 /** 81 83 * Plugins can add their Marker creation stuff at the bottom or top of this list … … 447 449 private String getPreferenceKey() { 448 450 return "draw.rawgps." + getTextTemplateKey(); 449 451 } 452 453 @Override 454 public BBox getBBox() { 455 return new BBox(this); 456 } 450 457 } -
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 19 19 import java.net.URISyntaxException; 20 20 import java.util.ArrayList; 21 21 import java.util.Collection; 22 import java.util.Collections; 22 23 import java.util.Comparator; 23 24 import java.util.HashMap; 24 25 import java.util.List; 26 import java.util.ListIterator; 25 27 import java.util.Map; 28 import java.util.Objects; 26 29 import java.util.Optional; 27 30 28 31 import javax.swing.AbstractAction; … … 41 44 import org.openstreetmap.josm.data.gpx.GpxLink; 42 45 import org.openstreetmap.josm.data.gpx.IGpxLayerPrefs; 43 46 import org.openstreetmap.josm.data.gpx.WayPoint; 47 import org.openstreetmap.josm.data.imagery.street_level.IImageEntry; 48 import org.openstreetmap.josm.data.osm.BBox; 49 import org.openstreetmap.josm.data.osm.QuadBuckets; 44 50 import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor; 45 51 import org.openstreetmap.josm.data.preferences.IntegerProperty; 46 52 import org.openstreetmap.josm.data.preferences.NamedColorProperty; … … 55 61 import org.openstreetmap.josm.gui.layer.JumpToMarkerActions.JumpToNextMarker; 56 62 import org.openstreetmap.josm.gui.layer.JumpToMarkerActions.JumpToPreviousMarker; 57 63 import org.openstreetmap.josm.gui.layer.Layer; 64 import org.openstreetmap.josm.gui.layer.geoimage.IGeoImageLayer; 65 import org.openstreetmap.josm.gui.layer.geoimage.RemoteEntry; 58 66 import org.openstreetmap.josm.gui.layer.gpx.ConvertFromMarkerLayerAction; 59 67 import org.openstreetmap.josm.gui.preferences.display.GPXSettingsPanel; 60 68 import org.openstreetmap.josm.io.audio.AudioPlayer; 61 69 import org.openstreetmap.josm.spi.preferences.Config; 62 70 import org.openstreetmap.josm.tools.ColorHelper; 63 71 import org.openstreetmap.josm.tools.ImageProvider; 72 import org.openstreetmap.josm.tools.ListenerList; 64 73 import org.openstreetmap.josm.tools.Logging; 65 74 import org.openstreetmap.josm.tools.Utils; 66 75 … … 75 84 * 76 85 * The data is read only. 77 86 */ 78 public class MarkerLayer extends Layer implements JumpToMarkerLayer {87 public class MarkerLayer extends Layer implements JumpToMarkerLayer, IGeoImageLayer { 79 88 80 89 /** 81 90 * A list of markers. … … 89 98 final int markerSize = new IntegerProperty("draw.rawgps.markers.size", 4).get(); 90 99 final BasicStroke markerStroke = new StrokeProperty("draw.rawgps.markers.stroke", "1").get(); 91 100 101 private final ListenerList<IGeoImageLayer.ImageChangeListener> imageChangeListenerListenerList = ListenerList.create(); 102 92 103 /** 93 104 * The default color that is used for drawing markers. 94 105 */ … … 401 412 MainApplication.getMap().mapView.zoomTo(currentMarker); 402 413 } 403 414 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 404 423 public static void playAudio() { 405 424 playAdjacentMarker(null, true); 406 425 } … … 495 514 this.realcolor = Optional.ofNullable(color).orElse(DEFAULT_COLOR_PROPERTY.get()); 496 515 } 497 516 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 498 579 private final class MarkerMouseAdapter extends MouseAdapter { 499 580 @Override 500 581 public void mousePressed(MouseEvent e) { … … 627 708 * the data of a MarkerLayer 628 709 * @since 18287 629 710 */ 630 public class MarkerData extends ArrayList<Marker> implementsIGpxLayerPrefs {711 public class MarkerData extends QuadBuckets<Marker> implements List<Marker>, IGpxLayerPrefs { 631 712 632 713 private Map<String, String> ownLayerPrefs; 714 private final List<Marker> markerList = new ArrayList<>(); 633 715 634 716 @Override 635 717 public Map<String, String> getLayerPrefs() { … … 658 740 fromLayer.data.setModified(value); 659 741 } 660 742 } 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 } 661 814 } 662 815 }
