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

Last change on this file since 2017 was 2017, checked in by Gubaer, 15 years ago

removed OptionPaneUtil
cleanup of deprecated Layer API
cleanup of deprecated APIs in OsmPrimitive and Way
cleanup of imports

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.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.deleted || w.incomplete || 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.deleted || 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.id = 0;
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.id = 0;
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.deleted || 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.members.clear();
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 //if (rm.member != selectedNode) {
343 newRel.members.add(rm);
344 //}
345 }
346 for (Node n : newNodes) {
347 for (String role : rolesToReAdd) {
348 newRel.members.add(new RelationMember(role, n));
349 }
350 }
351 cmds.add(new ChangeCommand(r, newRel));
352 }
353 }
354 }
355
356
357 /**
358 * dupe a single node into as many nodes as there are ways using it, OR
359 *
360 * dupe a single node once, and put the copy on the selected way
361 */
362 private void unglueWays() {
363 LinkedList<Command> cmds = new LinkedList<Command>();
364 List<Node> newNodes = new LinkedList<Node>();
365
366 if (selectedWay == null) {
367 boolean firstway = true;
368 // modify all ways containing the nodes
369 for (Way w : getCurrentDataSet().ways) {
370 if (w.deleted || w.incomplete || w.getNodesCount() < 1) {
371 continue;
372 }
373 if (!w.containsNode(selectedNode)) {
374 continue;
375 }
376 if (!firstway) {
377 cmds.add(new ChangeCommand(w, modifyWay(selectedNode, w, cmds, newNodes)));
378 }
379 firstway = false;
380 }
381 } else {
382 cmds.add(new ChangeCommand(selectedWay, modifyWay(selectedNode, selectedWay, cmds, newNodes)));
383 }
384
385 fixRelations(selectedNode, cmds, newNodes);
386
387 Main.main.undoRedo.add(new SequenceCommand(tr("Dupe into {0} nodes", newNodes.size()+1), cmds));
388 if (selectedWay == null) { // if a node has been selected, new selection is ALL nodes
389 newNodes.add(selectedNode);
390 } // if a node and a way has been selected, new selection is only the new node that was added to the selected way
391 getCurrentDataSet().setSelected(newNodes);
392 }
393
394 /**
395 * dupe all nodes that are selected, and put the copies on the selected way
396 *
397 */
398 private void unglueWays2() {
399 LinkedList<Command> cmds = new LinkedList<Command>();
400 List<Node> allNewNodes = new LinkedList<Node>();
401 Way tmpWay = selectedWay;
402
403 for (Node n : selectedNodes) {
404 List<Node> newNodes = new LinkedList<Node>();
405 tmpWay = modifyWay(n, tmpWay, cmds, newNodes);
406 fixRelations(n, cmds, newNodes);
407 allNewNodes.addAll(newNodes);
408 }
409 cmds.add(new ChangeCommand(selectedWay, tmpWay)); // only one changeCommand for a way, else garbage will happen
410
411 Main.main.undoRedo.add(new SequenceCommand(tr("Dupe {0} nodes into {1} nodes", selectedNodes.size(), selectedNodes.size()+allNewNodes.size()), cmds));
412 getCurrentDataSet().setSelected(allNewNodes);
413 }
414
415 @Override
416 protected void updateEnabledState() {
417 setEnabled(getCurrentDataSet() != null && !getCurrentDataSet().getSelected().isEmpty());
418 }
419}
Note: See TracBrowser for help on using the repository browser.