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