From 87b6c43e098c34d7a3f12ce07557f991c5bfaff6 Mon Sep 17 00:00:00 2001
From: Giuseppe Bilotta <giuseppe.bilotta@gmail.com>
Date: Mon, 29 Jul 2013 08:29:30 +0200
Subject: [PATCH] Action to disconnect nodes from ways

A RemoveNodes command is also added to simplify undo management.
---
 images/unjoinnodeway.png                           | Bin 0 -> 385 bytes
 .../josm/actions/UnJoinNodeWayAction.java          | 138 +++++++++++++++++++++
 .../josm/command/RemoveNodesCommand.java           |  56 +++++++++
 src/org/openstreetmap/josm/gui/MainMenu.java       |   3 +
 4 files changed, 197 insertions(+)
 create mode 100644 images/unjoinnodeway.png
 create mode 100644 src/org/openstreetmap/josm/actions/UnJoinNodeWayAction.java
 create mode 100644 src/org/openstreetmap/josm/command/RemoveNodesCommand.java

diff --git a/images/unjoinnodeway.png b/images/unjoinnodeway.png
new file mode 100644
index 0000000000000000000000000000000000000000..4179e6902577640d66deab6f78cab43c8b0a4786
GIT binary patch
literal 385
zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjY)RhkE)4%caKYZ?lYt_f1s;*b
z3=G`DAk4@xYYs>cdx@v7EBjq`Ssnvp>D3WU3=E8Xo-U3d9>?Ea-s{H{D8Tx_-@z~{
zQbL-=Y@@jL+J&1w&#Y0FW4M3d$h{-1Nl7Lf4FegKvp1VZhPx;oS@AHWxc=Ns<Kp)p
zOv7|H9x+*?<D`-oHu;T})$8@iElW0NEdT1~zxOitX|8JvOqjPf<X$oGu;iWdKtc5M
zVwqjRvx1(<nF%};*nPvjr+7<Ym-UxjYj1rq@0fj6Wy+1*t1jt(rArDLPw$A#ea<8E
zkjq~qN!|FXb4;?uTG!(h9;|K)&mVNy^}pkJPlmx9mUFvVb{fSO&gW7Q=Ha<y?WuN<
zeM(XM>PdD13OpY_bvE99@ag*bi}z>$`QKZlmY48ZeUjL|S_|fFVharAyNW+4_gt>9
cd3i{>Jl~Te!YJi0Ft8XrUHx3vIVCg!02-y3CIA2c

literal 0
HcmV?d00001

diff --git a/src/org/openstreetmap/josm/actions/UnJoinNodeWayAction.java b/src/org/openstreetmap/josm/actions/UnJoinNodeWayAction.java
new file mode 100644
index 0000000..c1c3f14
--- /dev/null
+++ b/src/org/openstreetmap/josm/actions/UnJoinNodeWayAction.java
@@ -0,0 +1,138 @@
+//License: GPL. Copyright 2007 by Immanuel Scholz and others
+package org.openstreetmap.josm.actions;
+
+import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
+import static org.openstreetmap.josm.tools.I18n.tr;
+import static org.openstreetmap.josm.tools.I18n.trn;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.KeyEvent;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+import javax.swing.JOptionPane;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.command.RemoveNodesCommand;
+import org.openstreetmap.josm.command.Command;
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.Way;
+import org.openstreetmap.josm.tools.Shortcut;
+
+public class UnJoinNodeWayAction extends JosmAction {
+    public UnJoinNodeWayAction() {
+        super(tr("Disconnect Node from Way"), "unjoinnodeway",
+                tr("Disconnect nodes from a way they currently belong to"),
+                Shortcut.registerShortcut("tools:unjoinnodeway",
+                    tr("Tool: {0}", tr("Disconnect Node from Way")), KeyEvent.VK_J, Shortcut.ALT), true);
+        putValue("help", ht("/Action/UnJoinNodeWay"));
+    }
+
+    @Override
+    public void actionPerformed(ActionEvent e) {
+
+        Collection<OsmPrimitive> selection = getCurrentDataSet().getSelected();
+
+        List<Node> selectedNodes = OsmPrimitive.getFilteredList(selection, Node.class);
+        List<Way> selectedWays = OsmPrimitive.getFilteredList(selection, Way.class);
+        List<Way> applicableWays = getApplicableWays(selectedWays, selectedNodes);
+
+        if (applicableWays == null) {
+            JOptionPane.showMessageDialog(
+                    Main.parent,
+                    tr("Select at least one node to be disconnected."),
+                    tr("Warning"),
+                    JOptionPane.WARNING_MESSAGE);
+            return;
+        } else if (applicableWays.isEmpty()) {
+            JOptionPane.showMessageDialog(Main.parent,
+                    trn("Selected node cannot be disconnected from anything.",
+                        "Selected nodes cannot be disconnected from anything.",
+                        selectedNodes.size()),
+                    tr("Warning"),
+                    JOptionPane.WARNING_MESSAGE);
+            return;
+        } else if (applicableWays.size() > 1) {
+            JOptionPane.showMessageDialog(Main.parent,
+                    trn("There is more than one way using the node you selected. Please select the way also.",
+                        "There is more than one way using the nodes you selected. Please select the way also.",
+                        selectedNodes.size()),
+                    tr("Warning"),
+                    JOptionPane.WARNING_MESSAGE);
+            return;
+        } else if (applicableWays.get(0).getRealNodesCount() < selectedNodes.size() + 2) {
+            // there is only one affected way, but removing the selected nodes would only leave it
+            // with less than 2 nodes
+            JOptionPane.showMessageDialog(Main.parent,
+                    trn("The affected way would disappear after disconnecting the selected node.",
+                        "The affected way would disappear after disconnecting the selected nodes.",
+                        selectedNodes.size()),
+                    tr("Warning"),
+                    JOptionPane.WARNING_MESSAGE);
+            return;
+        }
+
+
+        // Finally, applicableWays contains only one perfect way
+        Way selectedWay = applicableWays.get(0);
+
+        // I'm sure there's a better way to handle this
+        Main.main.undoRedo.add(new RemoveNodesCommand(selectedWay, selectedNodes));
+        Main.map.repaint();
+    }
+
+    // Find ways to which the disconnect can be applied. This is the list of ways with more
+    // than two nodes which pass through all the given nodes, intersected with the selected ways (if any)
+    private List<Way> getApplicableWays(List<Way> selectedWays, List<Node> selectedNodes) {
+        if (selectedNodes.isEmpty())
+            return null;
+
+        // List of ways shared by all nodes
+        List<Way> result = new ArrayList<Way>(OsmPrimitive.getFilteredList(selectedNodes.get(0).getReferrers(), Way.class));
+        for (int i=1; i<selectedNodes.size(); i++) {
+            List<OsmPrimitive> ref = selectedNodes.get(i).getReferrers();
+            for (Iterator<Way> it = result.iterator(); it.hasNext(); ) {
+                if (!ref.contains(it.next())) {
+                    it.remove();
+                }
+            }
+        }
+
+        // Remove broken ways
+        for (Iterator<Way> it = result.iterator(); it.hasNext(); ) {
+            if (it.next().getNodesCount() <= 2) {
+                it.remove();
+            }
+        }
+
+        if (selectedWays.isEmpty())
+            return result;
+        else {
+            // Return only selected ways
+            for (Iterator<Way> it = result.iterator(); it.hasNext(); ) {
+                if (!selectedWays.contains(it.next())) {
+                    it.remove();
+                }
+            }
+            return result;
+        }
+    }
+
+    @Override
+    protected void updateEnabledState() {
+        if (getCurrentDataSet() == null) {
+            setEnabled(false);
+        } else {
+            updateEnabledState(getCurrentDataSet().getSelected());
+        }
+    }
+
+    @Override
+    protected void updateEnabledState(Collection<? extends OsmPrimitive> selection) {
+        setEnabled(selection != null && !selection.isEmpty());
+    }
+}
diff --git a/src/org/openstreetmap/josm/command/RemoveNodesCommand.java b/src/org/openstreetmap/josm/command/RemoveNodesCommand.java
new file mode 100644
index 0000000..5959a08
--- /dev/null
+++ b/src/org/openstreetmap/josm/command/RemoveNodesCommand.java
@@ -0,0 +1,56 @@
+// License: GPL. See LICENSE file for details.
+package org.openstreetmap.josm.command;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import javax.swing.Icon;
+
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.Way;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
+import org.openstreetmap.josm.gui.DefaultNameFormatter;
+import org.openstreetmap.josm.tools.ImageProvider;
+
+/**
+ * Command that removes a set of nodes from a way.
+ * The same can be done with ChangeNodesCommand, but this is more
+ * efficient. (Needed for the tool to disconnect nodes from ways.)
+ *
+ * @author Giuseppe Bilotta
+ */
+public class RemoveNodesCommand extends Command {
+
+    private final Way way;
+    private final HashSet<Node> rmNodes;
+
+    public RemoveNodesCommand(Way way, List<Node> rmNodes) {
+        super();
+        this.way = way;
+        this.rmNodes = new HashSet<Node>(rmNodes);
+    }
+
+    @Override public boolean executeCommand() {
+        super.executeCommand();
+        way.removeNodes(rmNodes);
+        way.setModified(true);
+        return true;
+    }
+
+    @Override public void fillModifiedData(Collection<OsmPrimitive> modified, Collection<OsmPrimitive> deleted, Collection<OsmPrimitive> added) {
+        modified.add(way);
+    }
+
+    @Override
+    public String getDescriptionText() {
+        return tr("Removed nodes from {0}", way.getDisplayName(DefaultNameFormatter.getInstance()));
+    }
+
+    @Override
+    public Icon getDescriptionIcon() {
+        return ImageProvider.get(OsmPrimitiveType.WAY);
+    }
+}
diff --git a/src/org/openstreetmap/josm/gui/MainMenu.java b/src/org/openstreetmap/josm/gui/MainMenu.java
index 7671796..ec867ba 100644
--- a/src/org/openstreetmap/josm/gui/MainMenu.java
+++ b/src/org/openstreetmap/josm/gui/MainMenu.java
@@ -84,6 +84,7 @@ import org.openstreetmap.josm.actions.SimplifyWayAction;
 import org.openstreetmap.josm.actions.SplitWayAction;
 import org.openstreetmap.josm.actions.ToggleGPXLinesAction;
 import org.openstreetmap.josm.actions.UnGlueAction;
+import org.openstreetmap.josm.actions.UnJoinNodeWayAction;
 import org.openstreetmap.josm.actions.UndoAction;
 import org.openstreetmap.josm.actions.UnselectAllAction;
 import org.openstreetmap.josm.actions.UpdateDataAction;
@@ -187,6 +188,7 @@ public class MainMenu extends JMenuBar {
     public final JosmAction createCircle = new CreateCircleAction();
     public final JosmAction mergeNodes = new MergeNodesAction();
     public final JosmAction joinNodeWay = new JoinNodeWayAction();
+    public final JosmAction unJoinNodeWay = new UnJoinNodeWayAction();
     public final JosmAction unglueNodes = new UnGlueAction();
     public final JosmAction joinAreas = new JoinAreasAction();
     public final JosmAction createMultipolygon = new CreateMultipolygonAction();
@@ -607,6 +609,7 @@ public class MainMenu extends JMenuBar {
         toolsMenu.addSeparator();
         add(toolsMenu, mergeNodes);
         add(toolsMenu, joinNodeWay);
+        add(toolsMenu, unJoinNodeWay);
         add(toolsMenu, unglueNodes);
         toolsMenu.addSeparator();
         add(toolsMenu, joinAreas);
-- 
1.8.3.432.g1b9a440

