Ticket #21605: 21605.2.patch
File 21605.2.patch, 17.0 KB (added by , 2 years ago) |
---|
-
src/org/openstreetmap/josm/data/ImageData.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/src/org/openstreetmap/josm/data/ImageData.java b/src/org/openstreetmap/josm/data/ImageData.java
a b 10 10 import org.openstreetmap.josm.data.coor.LatLon; 11 11 import org.openstreetmap.josm.data.gpx.GpxImageEntry; 12 12 import org.openstreetmap.josm.data.osm.QuadBuckets; 13 import org.openstreetmap.josm.gui.layer.Layer; 13 14 import org.openstreetmap.josm.gui.layer.geoimage.ImageEntry; 14 15 import org.openstreetmap.josm.tools.ListenerList; 15 16 … … 41 42 42 43 private final ListenerList<ImageDataUpdateListener> listeners = ListenerList.create(); 43 44 private final QuadBuckets<ImageEntry> geoImages = new QuadBuckets<>(); 45 private Layer layer; 44 46 45 47 /** 46 48 * Construct a new image container without images … … 375 377 notifyImageUpdate(); 376 378 } 377 379 380 /** 381 * Set the layer for use with {@link org.openstreetmap.josm.gui.layer.geoimage.ImageViewerDialog#displayImages(Layer, List)} 382 * @param layer The layer to use for organization 383 * @since xxx 384 */ 385 public void setLayer(Layer layer) { 386 this.layer = layer; 387 } 388 389 /** 390 * Get the layer that this data is associated with. May be {@code null}. 391 * @return The layer this data is associated with. 392 * @since xxx 393 */ 394 public Layer getLayer() { 395 return this.layer; 396 } 397 378 398 /** 379 399 * Add a listener that listens to image data changes 380 400 * @param listener the {@link ImageDataUpdateListener} -
src/org/openstreetmap/josm/gui/layer/geoimage/GeoImageLayer.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/src/org/openstreetmap/josm/gui/layer/geoimage/GeoImageLayer.java b/src/org/openstreetmap/josm/gui/layer/geoimage/GeoImageLayer.java
a b 171 171 this.gpxData = gpxData; 172 172 this.useThumbs = useThumbs; 173 173 this.data.addImageDataUpdateListener(this); 174 this.data.setLayer(this); 174 175 } 175 176 176 177 private final class ImageMouseListener extends MouseAdapter { … … 231 232 } 232 233 } else { 233 234 data.setSelectedImage(img); 235 ImageViewerDialog.getInstance().displayImages(GeoImageLayer.this, Collections.singletonList(img)); 234 236 } 235 237 } 236 238 } … … 521 523 * Show current photo on map and in image viewer. 522 524 */ 523 525 public void showCurrentPhoto() { 524 if (data.getSelectedImage() != null) {525 clearOtherCurrentPhotos();526 }527 526 updateBufferAndRepaint(); 528 527 } 529 528 … … 628 627 } 629 628 } 630 629 631 /**632 * Clears the currentPhoto of the other GeoImageLayer's. Otherwise there could be multiple selected photos.633 */634 private void clearOtherCurrentPhotos() {635 for (GeoImageLayer layer:636 MainApplication.getLayerManager().getLayersOfType(GeoImageLayer.class)) {637 if (layer != this) {638 layer.getImageData().clearSelectedImage();639 }640 }641 }642 643 630 /** 644 631 * Registers a map mode for which the functionality of this layer should be available. 645 632 * @param mapMode Map mode to be registered -
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 8 8 import java.awt.BorderLayout; 9 9 import java.awt.Component; 10 10 import java.awt.Dimension; 11 import java.awt.FlowLayout; 11 12 import java.awt.GridBagConstraints; 12 13 import java.awt.GridBagLayout; 13 14 import java.awt.event.ActionEvent; 15 import java.awt.event.ActionListener; 14 16 import java.awt.event.KeyEvent; 15 17 import java.awt.event.WindowEvent; 18 import java.awt.image.BufferedImage; 16 19 import java.io.IOException; 17 20 import java.io.Serializable; 21 import java.io.UncheckedIOException; 18 22 import java.time.ZoneOffset; 19 23 import java.time.format.DateTimeFormatter; 20 24 import java.time.format.FormatStyle; 21 25 import java.util.ArrayList; 22 26 import java.util.Arrays; 23 27 import java.util.Collections; 28 import java.util.Comparator; 29 import java.util.HashMap; 24 30 import java.util.List; 31 import java.util.Map; 25 32 import java.util.Objects; 26 33 import java.util.Optional; 27 34 import java.util.concurrent.Future; 28 35 import java.util.function.UnaryOperator; 29 36 import java.util.stream.Collectors; 37 import java.util.stream.Stream; 30 38 31 39 import javax.swing.AbstractAction; 32 40 import javax.swing.Box; 41 import javax.swing.ImageIcon; 33 42 import javax.swing.JButton; 43 import javax.swing.JComponent; 34 44 import javax.swing.JLabel; 35 45 import javax.swing.JOptionPane; 36 46 import javax.swing.JPanel; 37 47 import javax.swing.JToggleButton; 38 48 import javax.swing.SwingConstants; 49 import javax.swing.SwingUtilities; 39 50 40 51 import org.openstreetmap.josm.actions.JosmAction; 41 52 import org.openstreetmap.josm.data.ImageData; … … 52 63 import org.openstreetmap.josm.gui.layer.LayerManager.LayerChangeListener; 53 64 import org.openstreetmap.josm.gui.layer.LayerManager.LayerOrderChangeEvent; 54 65 import org.openstreetmap.josm.gui.layer.LayerManager.LayerRemoveEvent; 66 import org.openstreetmap.josm.gui.layer.MainLayerManager; 55 67 import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeEvent; 56 68 import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeListener; 57 69 import org.openstreetmap.josm.gui.layer.imagery.ImageryFilterSettings; 70 import org.openstreetmap.josm.gui.util.GuiHelper; 58 71 import org.openstreetmap.josm.gui.util.imagery.Vector3D; 59 72 import org.openstreetmap.josm.tools.ImageProvider; 60 73 import org.openstreetmap.josm.tools.Logging; … … 68 81 public final class ImageViewerDialog extends ToggleDialog implements LayerChangeListener, ActiveLayerChangeListener, ImageDataUpdateListener { 69 82 private static final String GEOIMAGE_FILLER = marktr("Geoimage: {0}"); 70 83 private static final String DIALOG_FOLDER = "dialogs"; 84 private static final String JOSM_LAYER_COMPONENT = "JOSM LAYER"; 85 private static final String JOSM_LAYER_IMAGE_COMPONENT = "JOSM LAYER IImageEntry"; 86 private static final String JOSM_LAYER_IMAGE_COMPONENT_DONE = JOSM_LAYER_IMAGE_COMPONENT + ".done"; 71 87 72 88 private final ImageryFilterSettings imageryFilterSettings = new ImageryFilterSettings(); 73 89 … … 120 136 private JButton btnOpenExternal; 121 137 private JButton btnDeleteFromDisk; 122 138 private JToggleButton tbCentre; 139 /** The layer tab (used to select images when multiple layers provide images, makes for easy switching) */ 140 private JPanel layers; 123 141 124 142 private ImageViewerDialog() { 125 143 super(tr("Geotagged Images"), "geoimage", tr("Display geotagged images"), Shortcut.registerShortcut("tools:geotagged", … … 152 170 153 171 private void build() { 154 172 JPanel content = new JPanel(new BorderLayout()); 173 this.layers = new JPanel(new FlowLayout(FlowLayout.LEADING)); 174 content.add(layers, BorderLayout.NORTH); 155 175 156 176 content.add(imgDisplay, BorderLayout.CENTER); 157 177 … … 213 233 createLayout(content, false, null); 214 234 } 215 235 236 private void updateLayers() { 237 if (this.tabbedEntries.size() <= 1) { 238 this.layers.setVisible(false); 239 } else { 240 final IImageEntry<?> current; 241 synchronized (this) { 242 current = this.currentEntry; 243 } 244 this.layers.setVisible(true); 245 // Get the old components 246 Map<Layer, JButton> oldLayers = Stream.of(this.layers.getComponents()).filter(JButton.class::isInstance).map(JButton.class::cast) 247 .filter(component -> component.getClientProperty(JOSM_LAYER_COMPONENT) instanceof Layer 248 || component.getClientProperty(JOSM_LAYER_COMPONENT) == null) 249 .collect(Collectors.toMap(component -> (Layer) component.getClientProperty(JOSM_LAYER_COMPONENT), component -> component)); 250 // Remove all old components 251 this.layers.removeAll(); 252 List<JButton> layerButtons = new ArrayList<>(this.tabbedEntries.size()); 253 MainLayerManager layerManager = MainApplication.getLayerManager(); 254 List<Layer> invalidLayers = this.tabbedEntries.keySet().stream().filter(layer -> !layerManager.containsLayer(layer)) 255 .collect(Collectors.toList()); 256 // `null` is for anything using the old methods, without telling us what layer it comes from. 257 invalidLayers.remove(null); 258 if (this.tabbedEntries.containsKey(null)) { 259 List<IImageEntry<?>> nullEntries = this.tabbedEntries.get(null); 260 JButton layerButton = createImageLayerButton(oldLayers, null, nullEntries); 261 layerButtons.add(layerButton); 262 layerButton.setEnabled(!nullEntries.contains(current)); 263 } 264 // We need to do multiple calls to avoid ConcurrentModificationExceptions 265 invalidLayers.forEach(this.tabbedEntries::remove); 266 for (Map.Entry<Layer, List<IImageEntry<?>>> entry : 267 this.tabbedEntries.entrySet().stream().filter(entry -> entry.getKey() != null) 268 .sorted(Comparator.comparing(entry -> entry.getKey().getName())).collect(Collectors.toList())) { 269 JButton layerButton = createImageLayerButton(oldLayers, entry.getKey(), entry.getValue()); 270 layerButtons.add(layerButton); 271 layerButton.setEnabled(!entry.getValue().contains(current)); 272 } 273 layerButtons.forEach(this.layers::add); 274 int maxPreferredHeight = layerButtons.stream().mapToInt(JComponent::getHeight).max().orElse(Integer.MIN_VALUE); 275 if (maxPreferredHeight > 0) { 276 layerButtons.forEach(button -> button.setPreferredSize(new Dimension(button.getPreferredSize().width, maxPreferredHeight))); 277 } 278 this.layers.invalidate(); 279 } 280 } 281 282 /** 283 * Create a button for a specific layer and its entries 284 * 285 * @param oldLayers A map of old layers to {@link JButton}s. If a layer is in the map, there is at least one old {@link JButton}. 286 * @param layer The layer to switch to 287 * @param entries The entries to display 288 * @return The button to use to switch to the specified layer 289 */ 290 private JButton createImageLayerButton(Map<Layer, JButton> oldLayers, Layer layer, List<IImageEntry<?>> entries) { 291 final JButton layerButton = new JButton(tr(layer != null ? layer.getLabel() : "Default")); 292 layerButton.putClientProperty(JOSM_LAYER_COMPONENT, layer); 293 layerButton.addActionListener(new ImageActionListener(layer, entries)); 294 if (!this.isDocked && entries.size() == 1) { 295 IImageEntry<?> entry = entries.get(0); 296 JButton old = oldLayers.get(layer); 297 Object saved = Optional.ofNullable(old).map(button -> button.getClientProperty(JOSM_LAYER_IMAGE_COMPONENT)).orElse(null); 298 boolean done = Optional.ofNullable(old).map(button -> button.getClientProperty(JOSM_LAYER_IMAGE_COMPONENT_DONE)) 299 .map(Boolean.TRUE::equals).orElse(false); 300 // Avoid reloading images if at all possible 301 if (!Objects.equals(entry, saved) || !done) { 302 ImageProvider.ImageSizes size = ImageProvider.ImageSizes.LARGEICON; 303 layerButton.putClientProperty(JOSM_LAYER_IMAGE_COMPONENT, entry); 304 layerButton.setIcon(ImageProvider.getEmpty(size)); 305 layerButton.setPreferredSize(new Dimension(layerButton.getPreferredSize().width + 2, 306 size.getAdjustedHeight())); 307 MainApplication.worker.submit(() -> loadImage(layerButton, entry)); 308 } else { 309 layerButton.putClientProperty(JOSM_LAYER_IMAGE_COMPONENT, old.getClientProperty(JOSM_LAYER_IMAGE_COMPONENT)); 310 layerButton.putClientProperty(JOSM_LAYER_IMAGE_COMPONENT_DONE, old.getClientProperty(JOSM_LAYER_IMAGE_COMPONENT_DONE)); 311 layerButton.setIcon(old.getIcon()); 312 layerButton.setPreferredSize(old.getPreferredSize()); 313 } 314 } 315 return layerButton; 316 } 317 318 /** 319 * Load an image for a button 320 * @param layerButton The button to load the image into 321 * @param iImageEntry The image entry to load 322 */ 323 private static void loadImage(JButton layerButton, IImageEntry<?> iImageEntry) { 324 try { 325 BufferedImage image = iImageEntry.read(ImageProvider.ImageSizes.LARGEICON.getImageDimension()); 326 ImageIcon imageIcon = new ImageIcon(image); 327 GuiHelper.runInEDT(() -> layerButton.setIcon(imageIcon)); 328 GuiHelper.runInEDT(layerButton::invalidate); 329 layerButton.putClientProperty(JOSM_LAYER_IMAGE_COMPONENT_DONE, true); 330 } catch (IOException e) { 331 throw new UncheckedIOException(e); 332 } 333 } 334 216 335 @Override 217 336 public void destroy() { 218 337 MainApplication.getLayerManager().removeActiveLayerChangeListener(this); … … 352 471 } 353 472 } 354 473 474 /** 475 * A listener that is called to change the viewing layer 476 */ 477 private static class ImageActionListener implements ActionListener { 478 479 private final Layer layer; 480 private final List<IImageEntry<?>> entries; 481 482 ImageActionListener(Layer layer, List<IImageEntry<?>> entries) { 483 this.layer = layer; 484 this.entries = entries; 485 } 486 487 @Override 488 public void actionPerformed(ActionEvent e) { 489 ImageViewerDialog.getInstance().displayImages(this.layer, this.entries); 490 } 491 } 492 493 355 494 private class ImageFirstAction extends ImageRememberAction { 356 495 ImageFirstAction() { 357 496 super(null, new ImageProvider(DIALOG_FOLDER, "first"), tr("First"), Shortcut.registerShortcut( … … 551 690 return wasEnabled; 552 691 } 553 692 693 /** Used for tabbed panes */ 694 private final transient Map<Layer, List<IImageEntry<?>>> tabbedEntries = new HashMap<>(); 554 695 private transient IImageEntry<? extends IImageEntry<?>> currentEntry; 555 696 556 697 /** … … 578 719 * @since 18246 579 720 */ 580 721 public void displayImages(List<IImageEntry<?>> entries) { 722 this.displayImages((Layer) null, entries); 723 } 724 725 /** 726 * Displays images for the given layer. 727 * @param layer The layer to use for the tab ui 728 * @param entries image entries 729 * @since xxx 730 */ 731 public void displayImages(Layer layer, List<IImageEntry<?>> entries) { 581 732 boolean imageChanged; 582 733 IImageEntry<?> entry = entries != null && entries.size() == 1 ? entries.get(0) : null; 583 734 … … 598 749 } 599 750 } 600 751 752 if (entries == null || entries.isEmpty()) { 753 this.tabbedEntries.remove(layer); 754 } else { 755 this.tabbedEntries.put(layer, entries); 756 } 757 this.updateLayers(); 601 758 if (entry != null) { 602 759 this.updateButtonsNonNullEntry(entry, imageChanged); 603 760 } else { … … 730 887 if (btnCollapse != null) { 731 888 btnCollapse.setVisible(!isDocked); 732 889 } 890 this.updateLayers(); 733 891 } 734 892 735 893 /** … … 797 955 } 798 956 } 799 957 958 /** 959 * Reload the image. Call this if you load a low-resolution image first, and then get a high-resolution image, or 960 * if you know that the image has changed on disk. 961 * @since xxx 962 */ 963 public void refresh() { 964 if (SwingUtilities.isEventDispatchThread()) { 965 this.updateButtonsNonNullEntry(currentEntry, true); 966 } else { 967 GuiHelper.runInEDT(this::refresh); 968 } 969 } 970 800 971 private void registerOnLayer(Layer layer) { 801 972 if (layer instanceof GeoImageLayer) { 802 973 ((GeoImageLayer) layer).getImageData().addImageDataUpdateListener(this); … … 819 990 820 991 @Override 821 992 public void selectedImageChanged(ImageData data) { 822 displayImages( new ArrayList<>(data.getSelectedImages()));993 displayImages(data.getLayer(), new ArrayList<>(data.getSelectedImages())); 823 994 } 824 995 825 996 @Override 826 997 public void imageDataUpdated(ImageData data) { 827 displayImages( new ArrayList<>(data.getSelectedImages()));998 displayImages(data.getLayer(), new ArrayList<>(data.getSelectedImages())); 828 999 } 829 1000 }