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}