001//License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.actions;
003
004import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
005import static org.openstreetmap.josm.tools.I18n.tr;
006import static org.openstreetmap.josm.tools.I18n.trn;
007
008import java.awt.event.ActionEvent;
009import java.awt.event.KeyEvent;
010import java.util.ArrayList;
011import java.util.Collection;
012import java.util.Iterator;
013import java.util.List;
014
015import javax.swing.JOptionPane;
016
017import org.openstreetmap.josm.Main;
018import org.openstreetmap.josm.command.RemoveNodesCommand;
019import org.openstreetmap.josm.data.osm.Node;
020import org.openstreetmap.josm.data.osm.OsmPrimitive;
021import org.openstreetmap.josm.data.osm.Way;
022import org.openstreetmap.josm.tools.Shortcut;
023
024/**
025 * Disconnect nodes from a way they currently belong to.
026 * @since 6253
027 */
028public class UnJoinNodeWayAction extends JosmAction {
029    
030    /**
031     * Constructs a new {@code UnJoinNodeWayAction}.
032     */
033    public UnJoinNodeWayAction() {
034        super(tr("Disconnect Node from Way"), "unjoinnodeway",
035                tr("Disconnect nodes from a way they currently belong to"),
036                Shortcut.registerShortcut("tools:unjoinnodeway",
037                    tr("Tool: {0}", tr("Disconnect Node from Way")), KeyEvent.VK_J, Shortcut.ALT), true);
038        putValue("help", ht("/Action/UnJoinNodeWay"));
039    }
040
041    @Override
042    public void actionPerformed(ActionEvent e) {
043
044        Collection<OsmPrimitive> selection = getCurrentDataSet().getSelected();
045
046        List<Node> selectedNodes = OsmPrimitive.getFilteredList(selection, Node.class);
047        List<Way> selectedWays = OsmPrimitive.getFilteredList(selection, Way.class);
048        List<Way> applicableWays = getApplicableWays(selectedWays, selectedNodes);
049
050        if (applicableWays == null) {
051            JOptionPane.showMessageDialog(
052                    Main.parent,
053                    tr("Select at least one node to be disconnected."),
054                    tr("Warning"),
055                    JOptionPane.WARNING_MESSAGE);
056            return;
057        } else if (applicableWays.isEmpty()) {
058            JOptionPane.showMessageDialog(Main.parent,
059                    trn("Selected node cannot be disconnected from anything.",
060                        "Selected nodes cannot be disconnected from anything.",
061                        selectedNodes.size()),
062                    tr("Warning"),
063                    JOptionPane.WARNING_MESSAGE);
064            return;
065        } else if (applicableWays.size() > 1) {
066            JOptionPane.showMessageDialog(Main.parent,
067                    trn("There is more than one way using the node you selected. Please select the way also.",
068                        "There is more than one way using the nodes you selected. Please select the way also.",
069                        selectedNodes.size()),
070                    tr("Warning"),
071                    JOptionPane.WARNING_MESSAGE);
072            return;
073        } else if (applicableWays.get(0).getRealNodesCount() < selectedNodes.size() + 2) {
074            // there is only one affected way, but removing the selected nodes would only leave it
075            // with less than 2 nodes
076            JOptionPane.showMessageDialog(Main.parent,
077                    trn("The affected way would disappear after disconnecting the selected node.",
078                        "The affected way would disappear after disconnecting the selected nodes.",
079                        selectedNodes.size()),
080                    tr("Warning"),
081                    JOptionPane.WARNING_MESSAGE);
082            return;
083        }
084
085
086        // Finally, applicableWays contains only one perfect way
087        Way selectedWay = applicableWays.get(0);
088
089        // I'm sure there's a better way to handle this
090        Main.main.undoRedo.add(new RemoveNodesCommand(selectedWay, selectedNodes));
091        Main.map.repaint();
092    }
093
094    // Find ways to which the disconnect can be applied. This is the list of ways with more
095    // than two nodes which pass through all the given nodes, intersected with the selected ways (if any)
096    private List<Way> getApplicableWays(List<Way> selectedWays, List<Node> selectedNodes) {
097        if (selectedNodes.isEmpty())
098            return null;
099
100        // List of ways shared by all nodes
101        List<Way> result = new ArrayList<>(OsmPrimitive.getFilteredList(selectedNodes.get(0).getReferrers(), Way.class));
102        for (int i=1; i<selectedNodes.size(); i++) {
103            List<OsmPrimitive> ref = selectedNodes.get(i).getReferrers();
104            for (Iterator<Way> it = result.iterator(); it.hasNext(); ) {
105                if (!ref.contains(it.next())) {
106                    it.remove();
107                }
108            }
109        }
110
111        // Remove broken ways
112        for (Iterator<Way> it = result.iterator(); it.hasNext(); ) {
113            if (it.next().getNodesCount() <= 2) {
114                it.remove();
115            }
116        }
117
118        if (selectedWays.isEmpty())
119            return result;
120        else {
121            // Return only selected ways
122            for (Iterator<Way> it = result.iterator(); it.hasNext(); ) {
123                if (!selectedWays.contains(it.next())) {
124                    it.remove();
125                }
126            }
127            return result;
128        }
129    }
130
131    @Override
132    protected void updateEnabledState() {
133        if (getCurrentDataSet() == null) {
134            setEnabled(false);
135        } else {
136            updateEnabledState(getCurrentDataSet().getSelected());
137        }
138    }
139
140    @Override
141    protected void updateEnabledState(Collection<? extends OsmPrimitive> selection) {
142        setEnabled(selection != null && !selection.isEmpty());
143    }
144}