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

Last change on this file since 2198 was 2120, checked in by stoecker, 15 years ago

see #3475 - patch by Petr Dlouhý - code rework for display filtering

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