Ticket #21605: 21605.6.patch
| File 21605.6.patch, 53.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; … … 64 67 import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeListener; 65 68 import org.openstreetmap.josm.gui.util.imagery.Vector3D; 66 69 import org.openstreetmap.josm.tools.ImageProvider; 70 import org.openstreetmap.josm.tools.ListenerList; 67 71 import org.openstreetmap.josm.tools.Utils; 68 72 69 73 /** … … 71 75 * @since 99 72 76 */ 73 77 public class GeoImageLayer extends AbstractModifiableLayer implements 74 JumpToMarkerLayer, NavigatableComponent.ZoomChangeListener, ImageDataUpdateListener { 78 JumpToMarkerLayer, NavigatableComponent.ZoomChangeListener, ImageDataUpdateListener, 79 IGeoImageLayer { 75 80 76 81 private static final List<Action> menuAdditions = new LinkedList<>(); 77 82 78 83 private static volatile List<MapMode> supportedMapModes; 79 84 80 85 private final ImageData data; 86 private final ListenerList<IGeoImageLayer.ImageChangeListener> imageChangeListeners = ListenerList.create(); 81 87 GpxData gpxData; 82 88 GpxLayer gpxFauxLayer; 83 89 GpxData gpxFauxData; … … 86 92 87 93 private final Icon icon = ImageProvider.get("dialogs/geoimage/photo-marker"); 88 94 private final Icon selectedIcon = ImageProvider.get("dialogs/geoimage/photo-marker-selected"); 95 private final Icon selectedIconNotImageViewer = generateSelectedIconNotImageViewer(this.selectedIcon); 96 97 private static Icon generateSelectedIconNotImageViewer(Icon selectedIcon) { 98 Color color = new NamedColorProperty("geoimage.selected.not.image.viewer", new Color(50, 0, 0)).get(); 99 BufferedImage bi = new BufferedImage(selectedIcon.getIconWidth(), selectedIcon.getIconHeight(), BufferedImage.TYPE_INT_ARGB); 100 Graphics2D g2d = bi.createGraphics(); 101 selectedIcon.paintIcon(null, g2d, 0, 0); 102 g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, 0.5f)); 103 g2d.setColor(color); 104 g2d.fillRect(0, 0, selectedIcon.getIconWidth(), selectedIcon.getIconHeight()); 105 g2d.dispose(); 106 return new ImageIcon(bi); 107 } 89 108 90 109 boolean useThumbs; 91 110 private final ExecutorService thumbsLoaderExecutor = … … 172 191 this.useThumbs = useThumbs; 173 192 this.data.addImageDataUpdateListener(this); 174 193 this.data.setLayer(this); 194 synchronized (ImageViewerDialog.class) { 195 if (!ImageViewerDialog.hasInstance()) { 196 ImageViewerDialog.createInstance(); 197 } 198 } 199 if (getInvalidGeoImages().size() == data.size()) { 200 ImageViewerDialog.getInstance().displayImages(Collections.singletonList(this.data.getFirstImage())); 201 } 175 202 } 176 203 177 204 private final class ImageMouseListener extends MouseAdapter { … … 232 259 } 233 260 } else { 234 261 data.setSelectedImage(img); 235 ImageViewerDialog.getInstance().displayImages( GeoImageLayer.this,Collections.singletonList(img));262 ImageViewerDialog.getInstance().displayImages(Collections.singletonList(img)); 236 263 } 264 GeoImageLayer.this.invalidate(); // Needed to update which image is being shown in the image viewer in the mapview 237 265 } 238 266 } 239 267 } … … 247 275 MainApplication.worker.execute(new ImagesLoader(files, gpxLayer)); 248 276 } 249 277 278 @Override 279 public void clearSelection() { 280 this.getImageData().clearSelectedImage(); 281 } 282 283 @Override 284 public boolean containsImage(IImageEntry<?> imageEntry) { 285 if (imageEntry instanceof ImageEntry) { 286 return this.data.getImages().contains(imageEntry); 287 } 288 return false; 289 } 290 250 291 @Override 251 292 public Icon getIcon() { 252 293 return ImageProvider.get("dialogs/geoimage", ImageProvider.ImageSizes.LAYER); 253 294 } 254 295 296 @Override 297 public List<ImageEntry> getSelection() { 298 return this.getImageData().getSelectedImages(); 299 } 300 301 @Override 302 public List<IImageEntry<?>> getInvalidGeoImages() { 303 return this.getImageData().getImages().stream().filter(entry -> entry.getPos() == null || entry.getExifCoor() == null 304 || !entry.getExifCoor().isValid() || !entry.getPos().isValid()).collect(toList()); 305 } 306 307 @Override 308 public void addImageChangeListener(ImageChangeListener listener) { 309 this.imageChangeListeners.addListener(listener); 310 } 311 312 @Override 313 public void removeImageChangeListener(ImageChangeListener listener) { 314 this.imageChangeListeners.removeListener(listener); 315 } 316 255 317 /** 256 318 * Register actions on the layer 257 319 * @param addition the action to be added … … 451 513 } 452 514 } 453 515 516 final IImageEntry<?> currentImage = ImageViewerDialog.getCurrentImage(); 454 517 for (ImageEntry e: data.getSelectedImages()) { 455 518 if (e != null && e.getPos() != null) { 456 519 Point p = mv.getPoint(e.getPos()); … … 465 528 if (useThumbs && e.hasThumbnail()) { 466 529 g.setColor(new Color(128, 0, 0, 122)); 467 530 g.fillRect(p.x - imgDim.width / 2, p.y - imgDim.height / 2, imgDim.width, imgDim.height); 531 } else if (e.equals(currentImage)) { 532 selectedIcon.paintIcon(mv, g, 533 p.x - imgDim.width / 2, 534 p.y - imgDim.height / 2); 468 535 } else { 469 selectedIcon .paintIcon(mv, g,536 selectedIconNotImageViewer.paintIcon(mv, g, 470 537 p.x - imgDim.width / 2, 471 538 p.y - imgDim.height / 2); 472 539 } … … 884 951 @Override 885 952 public void selectedImageChanged(ImageData data) { 886 953 showCurrentPhoto(); 954 this.imageChangeListeners.fireEvent(e -> e.imageChanged(this, null, data.getSelectedImages())); 887 955 } 888 956 889 957 @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; 39 39 import javax.swing.JOptionPane; 40 40 import javax.swing.JPanel; 41 import javax.swing.JTabbedPane; 41 42 import javax.swing.JToggleButton; 42 43 import javax.swing.SwingConstants; 43 44 import javax.swing.SwingUtilities; 44 45 46 import org.openstreetmap.josm.actions.ExpertToggleAction; 45 47 import org.openstreetmap.josm.actions.JosmAction; 46 48 import org.openstreetmap.josm.data.ImageData; 47 import org.openstreetmap.josm.data.ImageData.ImageDataUpdateListener;48 49 import org.openstreetmap.josm.data.imagery.street_level.IImageEntry; 49 50 import org.openstreetmap.josm.gui.ExtendedDialog; 50 51 import org.openstreetmap.josm.gui.MainApplication; … … 64 65 import org.openstreetmap.josm.gui.layer.imagery.ImageryFilterSettings; 65 66 import org.openstreetmap.josm.gui.util.GuiHelper; 66 67 import org.openstreetmap.josm.gui.util.imagery.Vector3D; 68 import org.openstreetmap.josm.gui.widgets.HideableTabbedPane; 69 import org.openstreetmap.josm.spi.preferences.Config; 67 70 import org.openstreetmap.josm.tools.ImageProvider; 68 71 import org.openstreetmap.josm.tools.Logging; 69 72 import org.openstreetmap.josm.tools.PlatformManager; … … 73 76 /** 74 77 * Dialog to view and manipulate geo-tagged images from a {@link GeoImageLayer}. 75 78 */ 76 public final class ImageViewerDialog extends ToggleDialog implements LayerChangeListener, ActiveLayerChangeListener , ImageDataUpdateListener{79 public final class ImageViewerDialog extends ToggleDialog implements LayerChangeListener, ActiveLayerChangeListener { 77 80 private static final String GEOIMAGE_FILLER = marktr("Geoimage: {0}"); 78 81 private static final String DIALOG_FOLDER = "dialogs"; 79 82 … … 124 127 return dialog; 125 128 } 126 129 130 /** 131 * Check if there is an instance for the {@link ImageViewerDialog} 132 * @return {@code true} if there is a static singleton instance of {@link ImageViewerDialog} 133 * @since xxx 134 */ 135 public static boolean hasInstance() { 136 return dialog != null; 137 } 138 139 /** 140 * Destroy the current dialog 141 */ 142 private static void destroyInstance() { 143 dialog = null; 144 } 145 127 146 private JButton btnLast; 128 147 private JButton btnNext; 129 148 private JButton btnPrevious; … … 135 154 private JButton btnDeleteFromDisk; 136 155 private JToggleButton tbCentre; 137 156 /** The layer tab (used to select images when multiple layers provide images, makes for easy switching) */ 138 private JPanel layers;157 private final HideableTabbedPane layers = new HideableTabbedPane(); 139 158 140 159 private ImageViewerDialog() { 141 160 super(tr("Geotagged Images"), "geoimage", tr("Display geotagged images"), Shortcut.registerShortcut("tools:geotagged", … … 168 187 169 188 private void build() { 170 189 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); 190 content.add(this.layers, BorderLayout.CENTER); 175 191 176 192 Dimension buttonDim = new Dimension(26, 26); 177 193 … … 187 203 btnLast = createNavigationButton(imageLastAction, buttonDim); 188 204 189 205 tbCentre = new JToggleButton(imageCenterViewAction); 206 tbCentre.setSelected(Config.getPref().getBoolean("geoimage.viewer.centre.on.image", false)); 190 207 tbCentre.setPreferredSize(buttonDim); 191 208 192 209 JButton btnZoomBestFit = new JButton(imageZoomAction); … … 196 213 btnCollapse.setAlignmentY(Component.TOP_ALIGNMENT); 197 214 198 215 JPanel buttons = new JPanel(); 199 buttons.add(btnFirst); 200 buttons.add(btnPrevious); 201 buttons.add(btnNext); 202 buttons.add(btnLast); 203 buttons.add(Box.createRigidArea(new Dimension(7, 0))); 204 buttons.add(tbCentre); 205 buttons.add(btnZoomBestFit); 206 buttons.add(Box.createRigidArea(new Dimension(7, 0))); 207 buttons.add(btnDelete); 208 buttons.add(btnDeleteFromDisk); 209 buttons.add(Box.createRigidArea(new Dimension(7, 0))); 210 buttons.add(btnCopyPath); 211 buttons.add(btnOpenExternal); 212 buttons.add(Box.createRigidArea(new Dimension(7, 0))); 213 buttons.add(createButton(visibilityAction, buttonDim)); 216 addButtonGroup(buttons, this.btnFirst, this.btnPrevious, this.btnNext, this.btnLast); 217 addButtonGroup(buttons, this.tbCentre, btnZoomBestFit); 218 addButtonGroup(buttons, this.btnDelete, this.btnDeleteFromDisk); 219 addButtonGroup(buttons, this.btnCopyPath, this.btnOpenExternal); 220 addButtonGroup(buttons, createButton(visibilityAction, buttonDim)); 214 221 215 222 JPanel bottomPane = new JPanel(new GridBagLayout()); 216 223 GridBagConstraints gc = new GridBagConstraints(); … … 231 238 createLayout(content, false, null); 232 239 } 233 240 234 private void updateLayers() { 235 if (this.tabbedEntries.size() <= 1) { 241 /** 242 * Add a button group to a panel 243 * @param buttonPanel The panel holding the buttons 244 * @param buttons The button group to add 245 */ 246 private static void addButtonGroup(JPanel buttonPanel, AbstractButton... buttons) { 247 if (buttonPanel.getComponentCount() != 0) { 248 buttonPanel.add(Box.createRigidArea(new Dimension(7, 0))); 249 } 250 251 for (AbstractButton jButton : buttons) { 252 buttonPanel.add(jButton); 253 } 254 } 255 256 /** 257 * Update the tabs for the different image layers 258 * @param changed {@code true} if the tabs changed 259 */ 260 private void updateLayers(boolean changed) { 261 MainLayerManager layerManager = MainApplication.getLayerManager(); 262 List<IGeoImageLayer> geoImageLayers = layerManager.getLayers().stream() 263 .filter(IGeoImageLayer.class::isInstance).map(IGeoImageLayer.class::cast).collect(Collectors.toList()); 264 if (geoImageLayers.isEmpty()) { 236 265 this.layers.setVisible(false); 237 this.layers.removeAll();238 266 } else { 239 267 this.layers.setVisible(true); 240 // Remove all old components 241 this.layers.removeAll(); 242 MainLayerManager layerManager = MainApplication.getLayerManager(); 243 List<Layer> invalidLayers = this.tabbedEntries.keySet().stream().filter(layer -> !layerManager.containsLayer(layer)) 244 .collect(Collectors.toList()); 245 // `null` is for anything using the old methods, without telling us what layer it comes from. 246 invalidLayers.remove(null); 247 // We need to do multiple calls to avoid ConcurrentModificationExceptions 248 invalidLayers.forEach(this.tabbedEntries::remove); 249 addButtonsForImageLayers(); 268 if (changed) { 269 addButtonsForImageLayers(); 270 } 271 MoveImgDisplayPanel<?> selected = (MoveImgDisplayPanel<?>) this.layers.getSelectedComponent(); 272 if ((this.imgDisplay.getParent() == null || this.imgDisplay.getParent().getParent() == null) 273 && selected != null && selected.layer.containsImage(this.currentEntry)) { 274 selected.setVisible(selected.isVisible()); 275 } else if (selected != null && !selected.layer.containsImage(this.currentEntry)) { 276 this.getImageTabs().filter(m -> m.layer.containsImage(this.currentEntry)).mapToInt(this.layers::indexOfComponent).findFirst() 277 .ifPresent(this.layers::setSelectedIndex); 278 } 250 279 this.layers.invalidate(); 251 280 } 281 this.layers.getParent().invalidate(); 252 282 this.revalidate(); 253 283 } 254 284 … … 256 286 * Add the buttons for image layers 257 287 */ 258 288 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)); 289 List<MoveImgDisplayPanel<?>> alreadyAdded = this.getImageTabs().collect(Collectors.toList()); 290 // Avoid the setVisible call recursively calling this method and adding duplicates 291 alreadyAdded.forEach(m -> m.finishedAddingButtons = false); 292 List<Layer> availableLayers = MainApplication.getLayerManager().getLayers(); 293 List<IGeoImageLayer> geoImageLayers = availableLayers.stream() 294 .sorted(Comparator.comparingInt(entry -> /*reverse*/-availableLayers.indexOf(entry))) 295 .filter(IGeoImageLayer.class::isInstance).map(IGeoImageLayer.class::cast).collect(Collectors.toList()); 296 for (IGeoImageLayer layer : geoImageLayers) { 297 final Optional<MoveImgDisplayPanel<?>> originalPanel = alreadyAdded.stream() 298 .filter(m -> Objects.equals(m.layer, layer)).findFirst(); 299 if (originalPanel.isPresent()) { 300 MoveImgDisplayPanel<?> panel = originalPanel.get(); 301 int componentIndex = this.layers.indexOfComponent(panel); 302 if (componentIndex == geoImageLayers.indexOf(layer)) { 303 this.layers.setTitleAt(componentIndex, panel.getLabel(availableLayers)); 304 } else { 305 boolean isSelected = this.layers.getSelectedComponent() == panel; 306 this.removeImageTab((Layer) layer); 307 this.layers.insertTab(panel.getLabel(availableLayers), null, panel, null, geoImageLayers.indexOf(layer)); 308 if (isSelected) { 309 this.layers.setSelectedComponent(panel); 310 } 311 } 312 } else { 313 MoveImgDisplayPanel<?> panel = new MoveImgDisplayPanel<>(this.imgDisplay, (Layer & IGeoImageLayer) layer); 314 this.layers.addTab(panel.getLabel(availableLayers), panel); 315 } 276 316 } 277 layerButtons.forEach(this.layers::add); 317 this.getImageTabs().map(p -> p.layer).filter(layer -> !availableLayers.contains(layer)) 318 // We have to collect to a list prior to removal -- if we don't, then the stream may get a layer at index 0, 319 // remove that layer, and then get a layer at index 1, which was previously at index 2. 320 .collect(Collectors.toList()).forEach(this::removeImageTab); 321 322 // This is need to avoid the first button becoming visible, and then recalling this method. 323 this.getImageTabs().forEach(m -> m.finishedAddingButtons = true); 324 // After that, trigger the visibility set code 325 this.getImageTabs().forEach(m -> m.setVisible(m.isVisible())); 278 326 } 279 327 280 328 /** 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 329 * Remove a tab for a layer from the {@link #layers} tab pane 330 * @param layer The layer to remove 286 331 */ 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; 332 private void removeImageTab(Layer layer) { 333 // This must be reversed to avoid removing the wrong tab 334 for (int i = this.layers.getTabCount() - 1; i >= 0; i--) { 335 Component component = this.layers.getComponentAt(i); 336 if (component instanceof MoveImgDisplayPanel) { 337 MoveImgDisplayPanel<?> moveImgDisplayPanel = (MoveImgDisplayPanel<?>) component; 338 if (Objects.equals(layer, moveImgDisplayPanel.layer)) { 339 this.layers.removeTabAt(i); 340 this.layers.remove(moveImgDisplayPanel); 341 } 342 } 343 } 344 } 345 346 /** 347 * Get the {@link MoveImgDisplayPanel} objects in {@link #layers}. 348 * @return The individual panels 349 */ 350 private Stream<MoveImgDisplayPanel<?>> getImageTabs() { 351 return IntStream.range(0, this.layers.getTabCount()) 352 .mapToObj(this.layers::getComponentAt) 353 .filter(MoveImgDisplayPanel.class::isInstance) 354 .map(m -> (MoveImgDisplayPanel<?>) m); 292 355 } 293 356 294 357 @Override … … 309 372 imageZoomAction.destroy(); 310 373 cancelLoadingImage(); 311 374 super.destroy(); 312 d ialog = null;375 destroyInstance(); 313 376 } 314 377 315 378 /** … … 433 496 } 434 497 } 435 498 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 499 private class ImageFirstAction extends ImageRememberAction { 456 500 ImageFirstAction() { 457 501 super(null, new ImageProvider(DIALOG_FOLDER, "first"), tr("First"), Shortcut.registerShortcut( … … 478 522 public void actionPerformed(ActionEvent e) { 479 523 final JToggleButton button = (JToggleButton) e.getSource(); 480 524 centerView = button.isEnabled() && button.isSelected(); 525 Config.getPref().putBoolean("geoimage.viewer.centre.on.image", centerView); 481 526 if (centerView && currentEntry != null && currentEntry.getPos() != null) { 482 527 MainApplication.getMap().mapView.zoomTo(currentEntry.getPos()); 483 528 } … … 618 663 } 619 664 } 620 665 666 /** 667 * A JPanel whose entire purpose is to display an image by (a) moving the imgDisplay arround and (b) setting the imgDisplay as a child 668 * for this panel. 669 */ 670 private static class MoveImgDisplayPanel<T extends Layer & IGeoImageLayer> extends JPanel { 671 private final T layer; 672 private final ImageDisplay imgDisplay; 673 674 /** 675 * 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 676 * has multiple tabs on initialization (like from a session). 677 */ 678 boolean finishedAddingButtons; 679 MoveImgDisplayPanel(ImageDisplay imgDisplay, T layer) { 680 super(new BorderLayout()); 681 this.layer = layer; 682 this.imgDisplay = imgDisplay; 683 } 684 685 @Override 686 public void setVisible(boolean visible) { 687 super.setVisible(visible); 688 JTabbedPane layers = ImageViewerDialog.getInstance().layers; 689 int index = layers.indexOfComponent(this); 690 if (visible && this.finishedAddingButtons) { 691 if (!this.layer.getSelection().isEmpty() && !this.layer.getSelection().contains(ImageViewerDialog.getCurrentImage())) { 692 ImageViewerDialog.getInstance().displayImages(this.layer.getSelection()); 693 this.layer.invalidate(); // This will force the geoimage layers to update properly. 694 } 695 if (this.imgDisplay.getParent() != this) { 696 this.add(this.imgDisplay, BorderLayout.CENTER); 697 this.imgDisplay.invalidate(); 698 this.revalidate(); 699 } 700 if (index >= 0) { 701 layers.setTitleAt(index, "* " + getLabel(MainApplication.getLayerManager().getLayers())); 702 } 703 } else if (index >= 0) { 704 layers.setTitleAt(index, getLabel(MainApplication.getLayerManager().getLayers())); 705 } 706 } 707 708 /** 709 * Get the label for this panel 710 * @param availableLayers The layers to use to get the index 711 * @return The label for this layer 712 */ 713 String getLabel(List<Layer> availableLayers) { 714 final int index = availableLayers.size() - availableLayers.indexOf(layer); 715 return (ExpertToggleAction.isExpert() ? "[" + index + "] " : "") + layer.getLabel(); 716 } 717 } 718 621 719 /** 622 720 * Enables (or disables) the "Previous" button. 623 721 * @param value {@code true} to enable the button, {@code false} otherwise … … 651 749 return wasEnabled; 652 750 } 653 751 654 /** Used for tabbed panes */655 private final transient Map<Layer, List<IImageEntry<?>>> tabbedEntries = new HashMap<>();656 752 private transient IImageEntry<? extends IImageEntry<?>> currentEntry; 657 753 658 754 /** … … 679 775 * @param entries image entries 680 776 * @since 18246 681 777 */ 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) { 778 public void displayImages(List<? extends IImageEntry<?>> entries) { 693 779 boolean imageChanged; 694 780 IImageEntry<?> entry = entries != null && entries.size() == 1 ? entries.get(0) : null; 695 781 … … 710 796 } 711 797 } 712 798 713 if (entries == null || entries.isEmpty() || entries.stream().allMatch(Objects::isNull)) { 714 this.tabbedEntries.remove(layer); 799 800 final boolean updateRequired; 801 final List<IGeoImageLayer> imageLayers = MainApplication.getLayerManager().getLayers().stream() 802 .filter(IGeoImageLayer.class::isInstance).map(IGeoImageLayer.class::cast).collect(Collectors.toList()); 803 if (!Config.getPref().getBoolean("geoimage.viewer.show.tabs", true)) { 804 updateRequired = true; 805 // Clear the selected images in other geoimage layers 806 this.getImageTabs().map(m -> m.layer).filter(IGeoImageLayer.class::isInstance).map(IGeoImageLayer.class::cast) 807 .filter(l -> !Objects.equals(entries, l.getSelection())) 808 .forEach(IGeoImageLayer::clearSelection); 715 809 } else { 716 this.tabbedEntries.put(layer, entries);810 updateRequired = imageLayers.stream().anyMatch(l -> this.getImageTabs().map(m -> m.layer).noneMatch(l::equals)); 717 811 } 718 this.updateLayers( );812 this.updateLayers(updateRequired); 719 813 if (entry != null) { 720 814 this.updateButtonsNonNullEntry(entry, imageChanged); 721 } else if ( this.tabbedEntries.isEmpty()) {815 } else if (imageLayers.isEmpty()) { 722 816 this.updateButtonsNullEntry(entries); 723 817 return; 724 818 } 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) { 819 IGeoImageLayer layer = imageLayers.stream().filter(l -> l.getSelection().size() == 1).findFirst().orElse(null); 820 if (layer == null) { 728 821 this.updateButtonsNullEntry(entries); 729 822 } else { 730 this.displayImages( realEntry.getKey(), realEntry.getValue());823 this.displayImages(layer.getSelection()); 731 824 } 732 825 return; 733 826 } … … 744 837 * Update buttons for null entry 745 838 * @param entries {@code true} if multiple images are selected 746 839 */ 747 private void updateButtonsNullEntry(List< IImageEntry<?>> entries) {840 private void updateButtonsNullEntry(List<? extends IImageEntry<?>> entries) { 748 841 boolean hasMultipleImages = entries != null && entries.size() > 1; 749 842 // if this method is called to reinitialize dialog content with a blank image, 750 843 // do not actually show the dialog again with a blank image if currently hidden (fix #10672) 751 setTitle(tr("Geotagged Images"));844 this.updateTitle(); 752 845 imgDisplay.setImage(null); 753 846 imgDisplay.setOsdText(""); 754 847 setNextEnabled(false); … … 787 880 btnCopyPath.setEnabled(true); 788 881 btnOpenExternal.setEnabled(true); 789 882 790 setTitle(tr("Geotagged Images") + (!entry.getDisplayName().isEmpty() ? " - " + entry.getDisplayName() : ""));883 this.updateTitle(); 791 884 StringBuilder osd = new StringBuilder(entry.getDisplayName()); 792 885 if (entry.getElevation() != null) { 793 886 osd.append(tr("\nAltitude: {0} m", Math.round(entry.getElevation()))); … … 818 911 imgDisplay.setOsdText(osd.toString()); 819 912 } 820 913 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); 914 private void updateTitle() { 915 final IImageEntry<?> entry; 916 synchronized (this) { 917 entry = this.currentEntry; 918 } 919 String baseTitle = Optional.of(this.layers.getSelectedComponent()) 920 .filter(MoveImgDisplayPanel.class::isInstance).map(MoveImgDisplayPanel.class::cast) 921 .map(m -> m.layer).map(Layer::getLabel).orElse(tr("Geotagged Images")); 922 if (entry == null) { 923 this.setTitle(baseTitle); 924 } else { 925 this.setTitle(baseTitle + (!entry.getDisplayName().isEmpty() ? " - " + entry.getDisplayName() : "")); 926 } 831 927 } 832 928 833 private static boolean isLastImageSelected(List< IImageEntry<?>> data) {929 private static boolean isLastImageSelected(List<? extends IImageEntry<?>> data) { 834 930 return data.stream().anyMatch(image -> data.contains(image.getLastImage())); 835 931 } 836 932 837 private static boolean isFirstImageSelected(List< IImageEntry<?>> data) {933 private static boolean isFirstImageSelected(List<? extends IImageEntry<?>> data) { 838 934 return data.stream().anyMatch(image -> data.contains(image.getFirstImage())); 839 935 } 840 936 … … 857 953 if (btnCollapse != null) { 858 954 btnCollapse.setVisible(!isDocked); 859 955 } 860 this.updateLayers( );956 this.updateLayers(true); 861 957 } 862 958 863 959 /** … … 904 1000 905 1001 @Override 906 1002 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); 1003 if (e.getRemovedLayer() instanceof IGeoImageLayer && ((IGeoImageLayer) e.getRemovedLayer()).containsImage(this.currentEntry)) { 1004 displayImages(null); 913 1005 } 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()); 1006 this.updateLayers(true); 916 1007 } 917 1008 918 1009 @Override 919 1010 public void layerOrderChanged(LayerOrderChangeEvent e) { 920 // ignored1011 this.updateLayers(true); 921 1012 } 922 1013 923 1014 @Override … … 941 1032 } 942 1033 943 1034 private void registerOnLayer(Layer layer) { 944 if (layer instanceof GeoImageLayer) { 945 ((GeoImageLayer) layer).getImageData().addImageDataUpdateListener(this); 1035 if (layer instanceof IGeoImageLayer) { 1036 layer.addPropertyChangeListener(l -> { 1037 final List<?> currentTabLayers = this.getImageTabs().map(m -> m.layer).collect(Collectors.toList()); 1038 if (Layer.NAME_PROP.equals(l.getPropertyName()) && currentTabLayers.contains(layer)) { 1039 this.updateLayers(true); 1040 if (((IGeoImageLayer) layer).containsImage(this.currentEntry)) { 1041 this.updateTitle(); 1042 } 1043 } // Use Layer.VISIBLE_PROP here if we decide to do something when layer visibility changes 1044 }); 946 1045 } 947 1046 } 948 1047 … … 959 1058 imgLoadingFuture = null; 960 1059 } 961 1060 } 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 1061 } -
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 }
