source: josm/trunk/src/org/openstreetmap/josm/actions/UnGlueAction.java@ 3747

Last change on this file since 3747 was 3538, checked in by bastiK, 14 years ago

fixed #5452 - Tools-->UnGlue Ways is counterintuitive

  • Property svn:eol-style set to native
File size: 14.6 KB
RevLine 
[858]1// License: GPL. Copyright 2007 by Immanuel Scholz and others
2package org.openstreetmap.josm.actions;
3
[2410]4import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
[858]5import static org.openstreetmap.josm.tools.I18n.tr;
[2842]6import static org.openstreetmap.josm.tools.I18n.trn;
[858]7
8import java.awt.event.ActionEvent;
9import java.awt.event.KeyEvent;
10import java.util.ArrayList;
11import java.util.Collection;
[1429]12import java.util.Collections;
[858]13import java.util.HashSet;
14import java.util.LinkedList;
15import java.util.List;
16
17import javax.swing.JOptionPane;
[1429]18import javax.swing.JPanel;
[858]19
20import org.openstreetmap.josm.Main;
21import org.openstreetmap.josm.command.AddCommand;
22import org.openstreetmap.josm.command.ChangeCommand;
23import org.openstreetmap.josm.command.Command;
24import org.openstreetmap.josm.command.SequenceCommand;
25import org.openstreetmap.josm.data.osm.Node;
26import org.openstreetmap.josm.data.osm.OsmPrimitive;
27import org.openstreetmap.josm.data.osm.Relation;
28import org.openstreetmap.josm.data.osm.RelationMember;
29import org.openstreetmap.josm.data.osm.Way;
[1429]30import org.openstreetmap.josm.gui.MapView;
[1084]31import org.openstreetmap.josm.tools.Shortcut;
[858]32
33/**
[1024]34 * Duplicate nodes that are used by multiple ways.
[858]35 *
36 * Resulting nodes are identical, up to their position.
37 *
38 * This is the opposite of the MergeNodesAction.
[1677]39 *
[1429]40 * If a single node is selected, it will copy that node and remove all tags from the old one
[858]41 */
42
[1820]43public class UnGlueAction extends JosmAction {
[858]44
[1169]45 private Node selectedNode;
46 private Way selectedWay;
47 private ArrayList<Node> selectedNodes;
[858]48
[1169]49 /**
50 * Create a new UnGlueAction.
51 */
52 public UnGlueAction() {
53 super(tr("UnGlue Ways"), "unglueways", tr("Duplicate nodes that are used by multiple ways."),
[1814]54 Shortcut.registerShortcut("tools:unglue", tr("Tool: {0}", tr("UnGlue Ways")), KeyEvent.VK_G, Shortcut.GROUP_EDIT), true);
[2323]55 putValue("help", ht("/Action/UnGlue"));
[1169]56 }
[858]57
[1169]58 /**
59 * Called when the action is executed.
60 *
61 * This method does some checking on the selection and calls the matching unGlueWay method.
62 */
63 public void actionPerformed(ActionEvent e) {
[858]64
[1814]65 Collection<OsmPrimitive> selection = getCurrentDataSet().getSelected();
[1677]66
[1429]67 String errMsg = null;
[1169]68 if (checkSelection(selection)) {
69 int count = 0;
[2412]70 for (Way w : OsmPrimitive.getFilteredList(selectedNode.getReferrers(), Way.class)) {
[2120]71 if (!w.isUsable() || w.getNodesCount() < 1) {
[1814]72 continue;
73 }
[1169]74 count++;
75 }
76 if (count < 2) {
[1429]77 // If there aren't enough ways, maybe the user wanted to unglue the nodes
78 // (= copy tags to a new node)
[2381]79 if (checkForUnglueNode(selection)) {
[1429]80 unglueNode(e);
[1814]81 } else {
[1429]82 errMsg = tr("This node is not glued to anything else.");
[1814]83 }
[1169]84 } else {
85 // and then do the work.
86 unglueWays();
87 }
88 } else if (checkSelection2(selection)) {
89 ArrayList<Node> tmpNodes = new ArrayList<Node>();
90 for (Node n : selectedNodes) {
91 int count = 0;
[2412]92 for (Way w : OsmPrimitive.getFilteredList(n.getReferrers(), Way.class)) {
[2748]93 if (!w.isUsable()) {
[1814]94 continue;
[2748]95 }
[1169]96 count++;
97 }
98 if (count >= 2) {
99 tmpNodes.add(n);
100 }
101 }
102 if (tmpNodes.size() < 1) {
103 if (selection.size() > 1) {
[1429]104 errMsg = tr("None of these nodes are glued to anything else.");
[1169]105 } else {
[2842]106 errMsg = tr("None of this way''s nodes are glued to anything else.");
[1169]107 }
108 } else {
109 // and then do the work.
110 selectedNodes = tmpNodes;
111 unglueWays2();
112 }
113 } else {
[1429]114 errMsg =
[1169]115 tr("The current selection cannot be used for unglueing.")+"\n"+
116 "\n"+
117 tr("Select either:")+"\n"+
[1429]118 tr("* One tagged node, or")+"\n"+
[1169]119 tr("* One node that is used by more than one way, or")+"\n"+
120 tr("* One node that is used by more than one way and one of those ways, or")+"\n"+
121 tr("* One way that has one or more nodes that are used by more than one way, or")+"\n"+
122 tr("* One way and one or more of its nodes that are used by more than one way.")+"\n"+
123 "\n"+
124 tr("Note: If a way is selected, this way will get fresh copies of the unglued\n"+
[1814]125 "nodes and the new nodes will be selected. Otherwise, all ways will get their\n"+
126 "own copy and all nodes will be selected.");
[1169]127 }
[1677]128
[1814]129 if(errMsg != null) {
[2017]130 JOptionPane.showMessageDialog(
[1857]131 Main.parent,
132 errMsg,
133 tr("Error"),
134 JOptionPane.ERROR_MESSAGE);
[1814]135 }
[1677]136
[1169]137 selectedNode = null;
138 selectedWay = null;
139 selectedNodes = null;
140 }
[1677]141
[1429]142 /**
143 * Assumes there is one tagged Node stored in selectedNode that it will try to unglue
144 * (= copy node and remove all tags from the old one. Relations will not be removed)
145 */
146 private void unglueNode(ActionEvent e) {
147 LinkedList<Command> cmds = new LinkedList<Command>();
[858]148
[1429]149 Node c = new Node(selectedNode);
[1924]150 c.removeAll();
[2264]151 getCurrentDataSet().clearSelection(c);
[1429]152 cmds.add(new ChangeCommand(selectedNode, c));
[1677]153
[2410]154 Node n = new Node(selectedNode, true);
[1677]155
[1429]156 // If this wasn't called from menu, place it where the cursor is/was
157 if(e.getSource() instanceof JPanel) {
158 MapView mv = Main.map.mapView;
[1722]159 n.setCoor(mv.getLatLon(mv.lastMEvent.getX(), mv.lastMEvent.getY()));
[1429]160 }
[1677]161
[1429]162 cmds.add(new AddCommand(n));
[1677]163
[1429]164 fixRelations(selectedNode, cmds, Collections.singletonList(n));
[1677]165
[1429]166 Main.main.undoRedo.add(new SequenceCommand(tr("Unglued Node"), cmds));
[1814]167 getCurrentDataSet().setSelected(n);
[1429]168 Main.map.mapView.repaint();
169 }
[1677]170
[1169]171 /**
[1429]172 * Checks if selection is suitable for ungluing. This is the case when there's a single,
173 * tagged node selected that's part of at least one way (ungluing an unconnected node does
174 * not make sense. Due to the call order in actionPerformed, this is only called when the
[1677]175 * node is only part of one or less ways.
176 *
[1429]177 * @param The selection to check against
178 * @return Selection is suitable
179 */
180 private boolean checkForUnglueNode(Collection<? extends OsmPrimitive> selection) {
[2381]181 if (selection.size() != 1)
[1429]182 return false;
183 OsmPrimitive n = (OsmPrimitive) selection.toArray()[0];
[2381]184 if (!(n instanceof Node))
[1429]185 return false;
[2412]186 if (OsmPrimitive.getFilteredList(n.getReferrers(), Way.class).isEmpty())
[1429]187 return false;
[1677]188
[2381]189 selectedNode = (Node) n;
190 return selectedNode.isTagged();
[1429]191 }
192
193 /**
[1169]194 * Checks if the selection consists of something we can work with.
[3538]195 * Checks only if the number and type of items selected looks good.
[1169]196 *
197 * If this method returns "true", selectedNode and selectedWay will
198 * be set.
199 *
200 * Returns true if either one node is selected or one node and one
201 * way are selected and the node is part of the way.
202 *
203 * The way will be put into the object variable "selectedWay", the
204 * node into "selectedNode".
205 */
206 private boolean checkSelection(Collection<? extends OsmPrimitive> selection) {
[1023]207
[1169]208 int size = selection.size();
209 if (size < 1 || size > 2)
210 return false;
[1023]211
[1169]212 selectedNode = null;
213 selectedWay = null;
[1023]214
[1169]215 for (OsmPrimitive p : selection) {
216 if (p instanceof Node) {
217 selectedNode = (Node) p;
218 if (size == 1 || selectedWay != null)
[1912]219 return size == 1 || selectedWay.containsNode(selectedNode);
[1169]220 } else if (p instanceof Way) {
221 selectedWay = (Way) p;
222 if (size == 2 && selectedNode != null)
[1912]223 return selectedWay.containsNode(selectedNode);
[1169]224 }
225 }
[1023]226
[1169]227 return false;
228 }
[858]229
[1169]230 /**
231 * Checks if the selection consists of something we can work with.
[3538]232 * Checks only if the number and type of items selected looks good.
[1169]233 *
234 * Returns true if one way and any number of nodes that are part of
235 * that way are selected. Note: "any" can be none, then all nodes of
236 * the way are used.
237 *
238 * The way will be put into the object variable "selectedWay", the
239 * nodes into "selectedNodes".
240 */
241 private boolean checkSelection2(Collection<? extends OsmPrimitive> selection) {
242 if (selection.size() < 1)
243 return false;
[1024]244
[1169]245 selectedWay = null;
246 for (OsmPrimitive p : selection) {
247 if (p instanceof Way) {
[1814]248 if (selectedWay != null)
[1169]249 return false;
250 selectedWay = (Way) p;
251 }
252 }
[1814]253 if (selectedWay == null)
[1169]254 return false;
[1024]255
[1169]256 selectedNodes = new ArrayList<Node>();
257 for (OsmPrimitive p : selection) {
258 if (p instanceof Node) {
259 Node n = (Node) p;
[1912]260 if (!selectedWay.containsNode(n))
[1169]261 return false;
262 selectedNodes.add(n);
263 }
264 }
[1024]265
[1169]266 if (selectedNodes.size() < 1) {
[1862]267 selectedNodes.addAll(selectedWay.getNodes());
[1169]268 }
[1024]269
[1169]270 return true;
271 }
[1024]272
[1169]273 /**
274 * dupe the given node of the given way
275 *
[3241]276 * assume that OrginalNode is in the way
277 *
[1169]278 * -> the new node will be put into the parameter newNodes.
279 * -> the add-node command will be put into the parameter cmds.
280 * -> the changed way will be returned and must be put into cmds by the caller!
281 */
282 private Way modifyWay(Node originalNode, Way w, List<Command> cmds, List<Node> newNodes) {
[3241]283 // clone the node for the way
284 Node newNode = new Node(originalNode, true /* clear OSM ID */);
285 newNodes.add(newNode);
286 cmds.add(new AddCommand(newNode));
287
[1169]288 ArrayList<Node> nn = new ArrayList<Node>();
[1862]289 for (Node pushNode : w.getNodes()) {
[1169]290 if (originalNode == pushNode) {
[3241]291 pushNode = newNode;
[1169]292 }
293 nn.add(pushNode);
294 }
295 Way newWay = new Way(w);
[1862]296 newWay.setNodes(nn);
[858]297
[1169]298 return newWay;
299 }
[1023]300
[1169]301 /**
302 * put all newNodes into the same relation(s) that originalNode is in
303 */
304 private void fixRelations(Node originalNode, List<Command> cmds, List<Node> newNodes) {
305 // modify all relations containing the node
306 Relation newRel = null;
307 HashSet<String> rolesToReAdd = null;
[2412]308 for (Relation r : OsmPrimitive.getFilteredList(originalNode.getReferrers(), Relation.class)) {
[2748]309 if (r.isDeleted()) {
[1814]310 continue;
[2748]311 }
[1169]312 newRel = null;
313 rolesToReAdd = null;
[1925]314 for (RelationMember rm : r.getMembers()) {
[1938]315 if (rm.isNode()) {
316 if (rm.getMember() == originalNode) {
[1169]317 if (newRel == null) {
318 newRel = new Relation(r);
319 rolesToReAdd = new HashSet<String>();
320 }
[1930]321 rolesToReAdd.add(rm.getRole());
[1169]322 }
323 }
324 }
325 if (newRel != null) {
326 for (Node n : newNodes) {
327 for (String role : rolesToReAdd) {
[2070]328 newRel.addMember(new RelationMember(role, n));
[1169]329 }
330 }
331 cmds.add(new ChangeCommand(r, newRel));
332 }
333 }
334 }
[858]335
[1169]336 /**
337 * dupe a single node into as many nodes as there are ways using it, OR
338 *
339 * dupe a single node once, and put the copy on the selected way
340 */
341 private void unglueWays() {
342 LinkedList<Command> cmds = new LinkedList<Command>();
[3538]343 LinkedList<Node> newNodes = new LinkedList<Node>();
[1024]344
[1169]345 if (selectedWay == null) {
[3538]346 Way wayWithSelectedNode = null;
347 LinkedList<Way> parentWays = new LinkedList<Way>();
348 for (OsmPrimitive osm : selectedNode.getReferrers()) {
349 if (osm.isUsable() && osm instanceof Way) {
350 Way w = (Way) osm;
351 if (wayWithSelectedNode == null && !w.isFirstLastNode(selectedNode)) {
352 wayWithSelectedNode = w;
353 } else {
354 parentWays.add(w);
355 }
[1814]356 }
[1169]357 }
[3538]358 if (wayWithSelectedNode == null) {
359 wayWithSelectedNode = parentWays.removeFirst();
360 }
361 for (Way w : parentWays) {
362 cmds.add(new ChangeCommand(w, modifyWay(selectedNode, w, cmds, newNodes)));
363 }
[1169]364 } else {
365 cmds.add(new ChangeCommand(selectedWay, modifyWay(selectedNode, selectedWay, cmds, newNodes)));
366 }
[1024]367
[1169]368 fixRelations(selectedNode, cmds, newNodes);
[1024]369
[1169]370 Main.main.undoRedo.add(new SequenceCommand(tr("Dupe into {0} nodes", newNodes.size()+1), cmds));
[3538]371 // select one of the new nodes
372 getCurrentDataSet().setSelected(newNodes.getFirst());
[1169]373 }
[858]374
[1169]375 /**
376 * dupe all nodes that are selected, and put the copies on the selected way
377 *
378 */
379 private void unglueWays2() {
380 LinkedList<Command> cmds = new LinkedList<Command>();
381 List<Node> allNewNodes = new LinkedList<Node>();
382 Way tmpWay = selectedWay;
[1024]383
[1169]384 for (Node n : selectedNodes) {
385 List<Node> newNodes = new LinkedList<Node>();
386 tmpWay = modifyWay(n, tmpWay, cmds, newNodes);
387 fixRelations(n, cmds, newNodes);
388 allNewNodes.addAll(newNodes);
389 }
390 cmds.add(new ChangeCommand(selectedWay, tmpWay)); // only one changeCommand for a way, else garbage will happen
[1024]391
[2842]392 Main.main.undoRedo.add(new SequenceCommand(
393 trn("Dupe {0} node into {1} nodes", "Dupe {0} nodes into {1} nodes", selectedNodes.size(), selectedNodes.size(), selectedNodes.size()+allNewNodes.size()), cmds));
[1814]394 getCurrentDataSet().setSelected(allNewNodes);
[1169]395 }
[1820]396
397 @Override
398 protected void updateEnabledState() {
[2256]399 if (getCurrentDataSet() == null) {
400 setEnabled(false);
401 } else {
402 updateEnabledState(getCurrentDataSet().getSelected());
403 }
[1820]404 }
[2256]405
406 @Override
407 protected void updateEnabledState(Collection<? extends OsmPrimitive> selection) {
408 setEnabled(selection != null && !selection.isEmpty());
409 }
[858]410}
Note: See TracBrowser for help on using the repository browser.