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

Last change on this file since 2702 was 2589, checked in by stoecker, 14 years ago

small code cleanup

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