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 9a4cc2bb..bbf5f1b2 100644 --- a/src/main/java/org/openstreetmap/josm/plugins/mapwithai/MapWithAIPlugin.java +++ b/src/main/java/org/openstreetmap/josm/plugins/mapwithai/MapWithAIPlugin.java @@ -46,6 +46,7 @@ import org.openstreetmap.josm.plugins.mapwithai.gui.download.MapWithAIDownloadSourceType; import org.openstreetmap.josm.plugins.mapwithai.gui.preferences.MapWithAIPreferences; import org.openstreetmap.josm.plugins.mapwithai.tools.MapPaintUtils; +import org.openstreetmap.josm.plugins.mapwithai.tools.MapWithAICopyProhibit; import org.openstreetmap.josm.spi.preferences.Config; import org.openstreetmap.josm.tools.Destroyable; import org.openstreetmap.josm.tools.Logging; @@ -129,6 +130,8 @@ public MapWithAIPlugin(PluginInformation info) { mapFrameInitialized(null, MainApplication.getMap()); OSMDownloadSource.addDownloadType(new MapWithAIDownloadSourceType()); MainApplication.worker.execute(() -> UpdateProd.doProd(info.mainversion)); + + destroyables.add(new MapWithAICopyProhibit()); } @Override diff --git a/src/main/java/org/openstreetmap/josm/plugins/mapwithai/tools/MapWithAICopyProhibit.java b/src/main/java/org/openstreetmap/josm/plugins/mapwithai/tools/MapWithAICopyProhibit.java new file mode 100644 index 00000000..e7f5a388 --- /dev/null +++ b/src/main/java/org/openstreetmap/josm/plugins/mapwithai/tools/MapWithAICopyProhibit.java @@ -0,0 +1,68 @@ +// License: GPL. For details, see LICENSE file. +package org.openstreetmap.josm.plugins.mapwithai.tools; + +import org.openstreetmap.josm.data.osm.DataSet; +import org.openstreetmap.josm.gui.MainApplication; +import org.openstreetmap.josm.gui.Notification; +import org.openstreetmap.josm.gui.datatransfer.ClipboardUtils; +import org.openstreetmap.josm.gui.datatransfer.data.PrimitiveTransferData; +import org.openstreetmap.josm.gui.layer.MainLayerManager; +import org.openstreetmap.josm.gui.util.GuiHelper; +import org.openstreetmap.josm.plugins.mapwithai.backend.MapWithAILayer; +import org.openstreetmap.josm.tools.Destroyable; +import org.openstreetmap.josm.tools.Logging; + +import javax.swing.JOptionPane; +import java.awt.datatransfer.UnsupportedFlavorException; +import java.io.IOException; +import java.util.stream.Stream; + +import static org.openstreetmap.josm.gui.help.HelpUtil.ht; +import static org.openstreetmap.josm.tools.I18n.tr; + +/** + * Keep users from copying from the MapWithAI layer to the OSM layer + */ +public class MapWithAICopyProhibit implements MainLayerManager.ActiveLayerChangeListener, Destroyable { + /** + * Create a new listener to keep copy-paste from happening between the MapWithAI + * layer and the OSM layer + */ + public MapWithAICopyProhibit() { + MainApplication.getLayerManager().addActiveLayerChangeListener(this); + } + + @Override + public void activeOrEditLayerChanged(MainLayerManager.ActiveLayerChangeEvent e) { + if (e.getPreviousActiveLayer() instanceof MapWithAILayer && ClipboardUtils.getClipboardContent() != null + && Stream.of(ClipboardUtils.getClipboardContent().getTransferDataFlavors()) + .anyMatch(PrimitiveTransferData.DATA_FLAVOR::equals)) { + PrimitiveTransferData data; + try { + Object tData = ClipboardUtils.getClipboardContent().getTransferData(PrimitiveTransferData.DATA_FLAVOR); + if (tData instanceof PrimitiveTransferData) { + data = (PrimitiveTransferData) tData; + } else { + return; + } + } catch (UnsupportedFlavorException | IOException exception) { + Logging.error(exception); + return; + } + DataSet dataSet = ((MapWithAILayer) e.getPreviousActiveLayer()).getDataSet(); + if (data.getAll().stream().anyMatch(pdata -> dataSet.getPrimitiveById(pdata) != null)) { + ClipboardUtils.clear(); + Notification notification = new Notification(tr( + "Please use the `MapWithAI: Add Selected Data` command instead of copying and pasting from the MapWithAI Layer.")) + .setDuration(Notification.TIME_DEFAULT).setIcon(JOptionPane.INFORMATION_MESSAGE) + .setHelpTopic(ht("Plugin/MapWithAI#BasicUsage")); + GuiHelper.runInEDT(notification::show); + } + } + } + + @Override + public void destroy() { + MainApplication.getLayerManager().removeActiveLayerChangeListener(this); + } +} diff --git a/test/unit/org/openstreetmap/josm/plugins/mapwithai/tools/MapWithAICopyProhibitTest.java b/test/unit/org/openstreetmap/josm/plugins/mapwithai/tools/MapWithAICopyProhibitTest.java new file mode 100644 index 00000000..b5f8eb40 --- /dev/null +++ b/test/unit/org/openstreetmap/josm/plugins/mapwithai/tools/MapWithAICopyProhibitTest.java @@ -0,0 +1,95 @@ +// License: GPL. For details, see LICENSE file. +package org.openstreetmap.josm.plugins.mapwithai.tools; + +import mockit.Mock; +import mockit.MockUp; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.openstreetmap.josm.TestUtils; +import org.openstreetmap.josm.actions.CopyAction; +import org.openstreetmap.josm.actions.PasteAction; +import org.openstreetmap.josm.data.coor.LatLon; +import org.openstreetmap.josm.data.osm.DataSet; +import org.openstreetmap.josm.data.osm.Node; +import org.openstreetmap.josm.gui.MainApplication; +import org.openstreetmap.josm.gui.layer.MainLayerManager; +import org.openstreetmap.josm.gui.layer.OsmDataLayer; +import org.openstreetmap.josm.plugins.mapwithai.backend.MapWithAILayer; +import org.openstreetmap.josm.testutils.JOSMTestRules; +import org.openstreetmap.josm.testutils.mockers.WindowMocker; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +/** + * Test class for {@link MapWithAICopyProhibit} + * + * @author Taylor Smock + */ +class MapWithAICopyProhibitTest { + private static class BlacklistUtilsMock extends MockUp { + @Mock + public static boolean isBlacklisted() { + return false; + } + } + + // preferences for nodes, main for actions, projection for mapview + @RegisterExtension + JOSMTestRules josmTestRules = new JOSMTestRules().preferences().main().projection(); + + @Test + void testDestroyable() { + MapWithAICopyProhibit mapWithAICopyProhibit = new MapWithAICopyProhibit(); + assertDoesNotThrow( + () -> MainApplication.getLayerManager().removeActiveLayerChangeListener(mapWithAICopyProhibit)); + MainLayerManager layerManager = MainApplication.getLayerManager(); + IllegalArgumentException illegalArgumentException = assertThrows(IllegalArgumentException.class, + () -> layerManager.removeActiveLayerChangeListener(mapWithAICopyProhibit)); + assertEquals("Attempted to remove listener that was not in list: " + mapWithAICopyProhibit, + illegalArgumentException.getMessage()); + layerManager.addActiveLayerChangeListener(mapWithAICopyProhibit); + mapWithAICopyProhibit.destroy(); + illegalArgumentException = assertThrows(IllegalArgumentException.class, + () -> layerManager.removeActiveLayerChangeListener(mapWithAICopyProhibit)); + assertEquals("Attempted to remove listener that was not in list: " + mapWithAICopyProhibit, + illegalArgumentException.getMessage()); + } + + @Test + void testCopyProhibit() { + TestUtils.assumeWorkingJMockit(); + new WindowMocker(); + new BlacklistUtilsMock(); + + MapWithAICopyProhibit mapWithAICopyProhibit = new MapWithAICopyProhibit(); + MainLayerManager layerManager = MainApplication.getLayerManager(); + OsmDataLayer osmDataLayer = new OsmDataLayer(new DataSet(), "TEST", null); + MapWithAILayer mapWithAILayer = new MapWithAILayer(new DataSet(), "TEST", null); + layerManager.addLayer(osmDataLayer); + layerManager.addLayer(mapWithAILayer); + DataSet mapWithAIDataSet = mapWithAILayer.getDataSet(); + Node testNode = new Node(LatLon.ZERO); + mapWithAIDataSet.addPrimitive(testNode); + mapWithAIDataSet.setSelected(testNode); + layerManager.setActiveLayer(mapWithAILayer); + + CopyAction copyAction = new CopyAction(); + copyAction.actionPerformed(null); + PasteAction pasteAction = new PasteAction(); + + assertEquals(1, mapWithAIDataSet.allPrimitives().size()); + pasteAction.actionPerformed(null); + assertEquals(2, mapWithAIDataSet.allPrimitives().size()); + pasteAction.actionPerformed(null); + assertEquals(3, mapWithAIDataSet.allPrimitives().size()); + + layerManager.setActiveLayer(osmDataLayer); + assertEquals(0, osmDataLayer.getDataSet().allPrimitives().size()); + for (int i = 0; i < 10; i++) { + pasteAction.actionPerformed(null); + assertEquals(0, osmDataLayer.getDataSet().allPrimitives().size()); + } + } +}