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

Last change on this file since 6083 was 5909, checked in by stoecker, 11 years ago

fix all remaining javadoc warnings

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