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

Last change on this file since 1938 was 1938, checked in by jttt, 15 years ago

Replace some occurrences of RelationMember.member with getters

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