From a589db8cfa9cce3b2a0bf5acb153f9cdd7d1bb5d Mon Sep 17 00:00:00 2001 From: Taylor Smock Date: Mon, 10 Feb 2020 15:32:06 -0700 Subject: [PATCH] Add action for cycling through layers. Revert when JOSM-18638 is fixed. Signed-off-by: Taylor Smock --- .../plugins/mapwithai/MapWithAIPlugin.java | 4 + .../dialogs/layer/CycleLayerDownAction.java | 70 +++++++++++ .../gui/dialogs/layer/CycleLayerUpAction.java | 68 ++++++++++ .../dialogs/layer/CycleLayerActionTest.java | 116 ++++++++++++++++++ 4 files changed, 258 insertions(+) create mode 100644 src/main/java/org/openstreetmap/josm/plugins/mapwithai/gui/dialogs/layer/CycleLayerDownAction.java create mode 100644 src/main/java/org/openstreetmap/josm/plugins/mapwithai/gui/dialogs/layer/CycleLayerUpAction.java create mode 100644 test/unit/org/openstreetmap/josm/plugins/mapwithai/gui/dialogs/layer/CycleLayerActionTest.java diff --git a/src/main/java/org/openstreetmap/josm/plugins/mapwithai/MapWithAIPlugin.java b/src/main/java/org/openstreetmap/josm/plugins/mapwithai/MapWithAIPlugin.java index c86b63d..d1ad790 100644 --- a/src/main/java/org/openstreetmap/josm/plugins/mapwithai/MapWithAIPlugin.java +++ b/src/main/java/org/openstreetmap/josm/plugins/mapwithai/MapWithAIPlugin.java @@ -42,6 +42,8 @@ import org.openstreetmap.josm.plugins.mapwithai.data.validation.tests.StreetAddr import org.openstreetmap.josm.plugins.mapwithai.data.validation.tests.StreetAddressTest; import org.openstreetmap.josm.plugins.mapwithai.data.validation.tests.StubEndsTest; import org.openstreetmap.josm.plugins.mapwithai.frontend.MapWithAIDownloadReader; +import org.openstreetmap.josm.plugins.mapwithai.gui.dialogs.layer.CycleLayerDownAction; +import org.openstreetmap.josm.plugins.mapwithai.gui.dialogs.layer.CycleLayerUpAction; import org.openstreetmap.josm.spi.preferences.Config; import org.openstreetmap.josm.tools.Destroyable; import org.openstreetmap.josm.tools.Logging; @@ -105,6 +107,8 @@ public final class MapWithAIPlugin extends Plugin implements Destroyable { new MapWithAIRemoteControl(); // instantiate to get action into Remote Control Preferences destroyables = new ArrayList<>(); destroyables.add(new MapWithAIUploadHook(info)); + destroyables.add(new CycleLayerDownAction()); // TODO remove/put in if block when JOSM-18638 is fixed + destroyables.add(new CycleLayerUpAction()); // TODO see above mapFrameInitialized(null, MainApplication.getMap()); mapWithAIDownloadReader = new MapWithAIDownloadReader(); DownloadDialog.addDownloadSource(mapWithAIDownloadReader); diff --git a/src/main/java/org/openstreetmap/josm/plugins/mapwithai/gui/dialogs/layer/CycleLayerDownAction.java b/src/main/java/org/openstreetmap/josm/plugins/mapwithai/gui/dialogs/layer/CycleLayerDownAction.java new file mode 100644 index 0000000..7c621c1 --- /dev/null +++ b/src/main/java/org/openstreetmap/josm/plugins/mapwithai/gui/dialogs/layer/CycleLayerDownAction.java @@ -0,0 +1,70 @@ +// License: GPL. For details, see LICENSE file. +package org.openstreetmap.josm.plugins.mapwithai.gui.dialogs.layer; + +import static org.openstreetmap.josm.tools.I18n.tr; + +import java.awt.event.ActionEvent; +import java.awt.event.KeyEvent; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +import org.openstreetmap.josm.actions.JosmAction; +import org.openstreetmap.josm.gui.MainApplication; +import org.openstreetmap.josm.gui.layer.ImageryLayer; +import org.openstreetmap.josm.gui.layer.Layer; +import org.openstreetmap.josm.gui.layer.MainLayerManager; +import org.openstreetmap.josm.tools.ImageProvider; +import org.openstreetmap.josm.tools.Shortcut; + +/** + * Allow users to cycle between adjacent layers easily + * + * @author Taylor Smock + * @since xxx + */ +public class CycleLayerDownAction extends JosmAction { + private static final long serialVersionUID = -7094806029703064750L; + protected final static int KEYDOWN = KeyEvent.VK_CLOSE_BRACKET; + protected final static int MODIFIER = Shortcut.SHIFT; + private static Shortcut cycleDown = Shortcut.registerShortcut("core:cyclelayerdown", tr("Cycle layers down"), + KEYDOWN, MODIFIER); + + /** + * Create a CycleLayerDownAction that cycles through layers that are in the + * model + */ + public CycleLayerDownAction() { + super(tr("Cycle layers"), "dialogs/next", tr("Cycle through layers"), cycleDown, true, "cycle-layer", false); + new ImageProvider("dialogs", "next").getResource().attachImageIcon(this, true); + putValue(SHORT_DESCRIPTION, tr("Cycle through visible layers.")); + putValue(NAME, tr("Cycle layers")); + } + + @Override + public void actionPerformed(ActionEvent e) { + MainLayerManager manager = MainApplication.getLayerManager(); + List managerLayers = manager.getLayers().stream().filter(layer -> !(layer instanceof ImageryLayer)) + .collect(Collectors.toList()); + if (managerLayers.isEmpty()) { + return; + } + + List layers = new ArrayList<>(managerLayers); + Collections.reverse(layers); + int index = layers.indexOf(manager.getActiveLayer()); + int sublist = index < managerLayers.size() - 1 ? index + 1 : 0; + layers = layers.subList(sublist, layers.size()); + + Layer layer = layers.stream().filter(Layer::isVisible).filter(tlayer -> !(tlayer instanceof ImageryLayer)) + .findFirst().orElse(manager.getActiveLayer()); + + manager.setActiveLayer(layer); + } + + @Override + public void destroy() { + super.destroy(); + } +} diff --git a/src/main/java/org/openstreetmap/josm/plugins/mapwithai/gui/dialogs/layer/CycleLayerUpAction.java b/src/main/java/org/openstreetmap/josm/plugins/mapwithai/gui/dialogs/layer/CycleLayerUpAction.java new file mode 100644 index 0000000..c4cf55d --- /dev/null +++ b/src/main/java/org/openstreetmap/josm/plugins/mapwithai/gui/dialogs/layer/CycleLayerUpAction.java @@ -0,0 +1,68 @@ +// License: GPL. For details, see LICENSE file. +package org.openstreetmap.josm.plugins.mapwithai.gui.dialogs.layer; + +import static org.openstreetmap.josm.tools.I18n.tr; + +import java.awt.event.ActionEvent; +import java.awt.event.KeyEvent; +import java.util.List; +import java.util.stream.Collectors; + +import org.openstreetmap.josm.actions.JosmAction; +import org.openstreetmap.josm.gui.MainApplication; +import org.openstreetmap.josm.gui.layer.ImageryLayer; +import org.openstreetmap.josm.gui.layer.Layer; +import org.openstreetmap.josm.gui.layer.MainLayerManager; +import org.openstreetmap.josm.tools.ImageProvider; +import org.openstreetmap.josm.tools.Shortcut; + +/** + * Allow users to cycle between adjacent layers easily + * + * @author Taylor Smock + * @since xxx + */ +public class CycleLayerUpAction extends JosmAction { + private static final long serialVersionUID = -4041662823217465004L; + protected final static int KEYUP = KeyEvent.VK_OPEN_BRACKET; + protected final static int MODIFIER = Shortcut.SHIFT; + private static Shortcut cycleUp = Shortcut.registerShortcut("core:cyclelayerup", tr("Cycle layers up"), KEYUP, + MODIFIER); + + /** + * Create a CycleLayerDownAction that cycles through layers that are in the + * model + */ + public CycleLayerUpAction() { + super(tr("Cycle layer up"), "dialogs/next", tr("Cycle up through layers"), cycleUp, true, "cycle-layer", false); + new ImageProvider("dialogs", "next").getResource().attachImageIcon(this, true); + putValue(SHORT_DESCRIPTION, tr("Cycle through visible layers.")); + putValue(NAME, tr("Cycle layers")); + } + + @Override + public void actionPerformed(ActionEvent e) { + MainLayerManager manager = MainApplication.getLayerManager(); + List managerLayers = manager.getLayers().stream().filter(layer -> !(layer instanceof ImageryLayer)) + .collect(Collectors.toList()); + if (managerLayers.isEmpty()) { + return; + } + int index = managerLayers.indexOf(manager.getActiveLayer()); + int sublist = index < managerLayers.size() ? index + 1 : index; + if (index >= managerLayers.size() - 1) { + index = 0; + sublist = 0; + } + List layers = managerLayers.subList(sublist, managerLayers.size()); + Layer layer = layers.stream().filter(Layer::isVisible).filter(tlayer -> !(tlayer instanceof ImageryLayer)) + .findFirst().orElse(manager.getActiveLayer()); + + manager.setActiveLayer(layer); + } + + @Override + public void destroy() { + super.destroy(); + } +} \ No newline at end of file diff --git a/test/unit/org/openstreetmap/josm/plugins/mapwithai/gui/dialogs/layer/CycleLayerActionTest.java b/test/unit/org/openstreetmap/josm/plugins/mapwithai/gui/dialogs/layer/CycleLayerActionTest.java new file mode 100644 index 0000000..e995d53 --- /dev/null +++ b/test/unit/org/openstreetmap/josm/plugins/mapwithai/gui/dialogs/layer/CycleLayerActionTest.java @@ -0,0 +1,116 @@ +// License: GPL. For details, see LICENSE file. +package org.openstreetmap.josm.plugins.mapwithai.gui.dialogs.layer; + +import static org.junit.Assert.assertEquals; +import static org.openstreetmap.josm.tools.I18n.tr; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.openstreetmap.josm.data.imagery.ImageryInfo; +import org.openstreetmap.josm.data.imagery.ImageryLayerInfo; +import org.openstreetmap.josm.data.osm.DataSet; +import org.openstreetmap.josm.gui.MainApplication; +import org.openstreetmap.josm.gui.layer.ImageryLayer; +import org.openstreetmap.josm.gui.layer.MainLayerManager; +import org.openstreetmap.josm.gui.layer.OsmDataLayer; +import org.openstreetmap.josm.testutils.JOSMTestRules; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + +/** + * Test class for {@link CycleLayerDownAction} + * + * @author Taylor Smock + */ +public class CycleLayerActionTest { + /** Layers need a projection */ + @Rule + @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD") + public JOSMTestRules test = new JOSMTestRules().main().preferences().projection().fakeImagery(); + + CycleLayerDownAction cycleDown; + CycleLayerUpAction cycleUp; + MainLayerManager manager; + + /** + * Set up common items (make layers, etc.) + */ + @Before + public void setUp() { + cycleDown = new CycleLayerDownAction(); + cycleUp = new CycleLayerUpAction(); + manager = MainApplication.getLayerManager(); + for (int i = 0; i < 10; i++) { + manager.addLayer(new OsmDataLayer(new DataSet(), tr("Layer {0}", i), null)); + } + } + + /** + * Test going down from the bottom + */ + @Test + public void testDownBottom() { + manager.setActiveLayer(manager.getLayers().get(0)); + cycleDown.actionPerformed(null); + assertEquals(manager.getLayers().size() - 1, manager.getLayers().indexOf(manager.getActiveLayer())); + } + + /** + * Check going up from the top + */ + @Test + public void testUpTop() { + manager.setActiveLayer(manager.getLayers().get(manager.getLayers().size() - 1)); + cycleUp.actionPerformed(null); + assertEquals(0, manager.getLayers().indexOf(manager.getActiveLayer())); + } + + /** + * Check going down + */ + @Test + public void testDown() { + manager.setActiveLayer(manager.getLayers().get(3)); + cycleDown.actionPerformed(null); + assertEquals(2, manager.getLayers().indexOf(manager.getActiveLayer())); + } + + /** + * Check going up + */ + @Test + public void testUp() { + manager.setActiveLayer(manager.getLayers().get(3)); + cycleUp.actionPerformed(null); + assertEquals(4, manager.getLayers().indexOf(manager.getActiveLayer())); + } + + /** + * Test no layers + */ + @Test + public void testNoLayers() { + manager.getLayers().forEach(manager::removeLayer); + cycleUp.actionPerformed(null); + cycleDown.actionPerformed(null); + assertEquals(0, manager.getLayers().size()); + } + + /** + * Test with an aerial imagery layer + */ + @Test + public void testWithAerialImagery() { + final ImageryInfo magentaTilesInfo = ImageryLayerInfo.instance.getLayers().stream() + .filter(i -> i.getName().equals("Magenta Tiles")).findAny().get(); + ImageryLayer imageryLayer = ImageryLayer.create(magentaTilesInfo); + manager.addLayer(imageryLayer); + manager.moveLayer(imageryLayer, 5); + manager.setActiveLayer(manager.getLayers().get(4)); + cycleUp.actionPerformed(null); + assertEquals(6, manager.getLayers().indexOf(manager.getActiveLayer())); + cycleDown.actionPerformed(null); + assertEquals(4, manager.getLayers().indexOf(manager.getActiveLayer())); + } +}