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

Last change on this file since 6930 was 6830, checked in by Don-vip, 10 years ago

javadoc fixes for jdk8 compatibility

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