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

Last change on this file since 1084 was 1084, checked in by framm, 15 years ago
  • cosmetics: rename ShortCut to Shortcut, and shortCut to shortcut
File size: 10.3 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.HashSet;
11import java.util.LinkedList;
12import java.util.List;
13
14import javax.swing.JOptionPane;
15
16import org.openstreetmap.josm.Main;
17import org.openstreetmap.josm.command.AddCommand;
18import org.openstreetmap.josm.command.ChangeCommand;
19import org.openstreetmap.josm.command.Command;
20import org.openstreetmap.josm.command.SequenceCommand;
21import org.openstreetmap.josm.data.osm.Node;
22import org.openstreetmap.josm.data.osm.OsmPrimitive;
23import org.openstreetmap.josm.data.osm.Relation;
24import org.openstreetmap.josm.data.osm.RelationMember;
25import org.openstreetmap.josm.data.osm.Way;
26import org.openstreetmap.josm.tools.Shortcut;
27
28/**
29 * Duplicate nodes that are used by multiple ways.
30 *
31 * Resulting nodes are identical, up to their position.
32 *
33 * This is the opposite of the MergeNodesAction.
34 */
35
36public class UnGlueAction extends JosmAction { //implements SelectionChangedListener {
37
38 private Node selectedNode;
39 private Way selectedWay;
40 private ArrayList<Node> selectedNodes;
41
42 /**
43 * Create a new UnGlueAction.
44 */
45 public UnGlueAction() {
46 super(tr("UnGlue Ways"), "unglueways", tr("Duplicate nodes that are used by multiple ways."),
47 Shortcut.registerShortcut("tools:unglue", tr("Tool: {0}", tr("UnGlue Ways")), KeyEvent.VK_G, Shortcut.GROUP_EDIT), true);
48 //DataSet.selListeners.add(this);
49 }
50
51 /**
52 * Called when the action is executed.
53 *
54 * This method does some checking on the selection and calls the matching unGlueWay method.
55 */
56 public void actionPerformed(ActionEvent e) {
57
58 Collection<OsmPrimitive> selection = Main.ds.getSelected();
59
60 if (checkSelection(selection)) {
61 int count = 0;
62 for (Way w : Main.ds.ways) {
63 if (w.deleted || w.incomplete || w.nodes.size() < 1) continue;
64 if (!w.nodes.contains(selectedNode)) continue;
65 count++;
66 }
67 if (count < 2) {
68 JOptionPane.showMessageDialog(Main.parent, tr("This node is not glued to anything else."));
69 } else {
70 // and then do the work.
71 unglueWays();
72 }
73 } else if (checkSelection2(selection)) {
74 ArrayList<Node> tmpNodes = new ArrayList<Node>();
75 for (Node n : selectedNodes) {
76 int count = 0;
77 for (Way w : Main.ds.ways) {
78 if (w.deleted || w.incomplete || w.nodes.size() < 1) continue;
79 if (!w.nodes.contains(n)) continue;
80 count++;
81 }
82 if (count >= 2) {
83 tmpNodes.add(n);
84 }
85 }
86 if (tmpNodes.size() < 1) {
87 if (selection.size() > 1) {
88 JOptionPane.showMessageDialog(Main.parent, tr("None of these nodes is glued to anything else."));
89 } else {
90 JOptionPane.showMessageDialog(Main.parent, tr("None of this way's nodes is glued to anything else."));
91 }
92 } else {
93 // and then do the work.
94 selectedNodes = tmpNodes;
95 unglueWays2();
96 }
97 } else {
98 JOptionPane.showMessageDialog(Main.parent,
99 tr("The current selection cannot be used for unglueing.")+"\n"+
100 "\n"+
101 tr("Select either:")+"\n"+
102 tr("* One node that is used by more than one way, or")+"\n"+
103 tr("* One node that is used by more than one way and one of those ways, or")+"\n"+
104 tr("* One way that has one or more nodes that are used by more than one way, or")+"\n"+
105 tr("* One way and one or more of its nodes that are used by more than one way.")+"\n"+
106 "\n"+
107 tr("Note: If a way is selected, this way will get fresh copies of the unglued\n"+
108 "nodes and the new nodes will be selected. Otherwise, all ways will get their\n"+
109 "own copy and all nodes will be selected.")
110 );
111 }
112 selectedNode = null;
113 selectedWay = null;
114 selectedNodes = null;
115 }
116
117 /**
118 * Checks if the selection consists of something we can work with.
119 * Checks only if the number and type of items selected looks good;
120 * does not check whether the selected items are really a valid
121 * input for splitting (this would be too expensive to be carried
122 * out from the selectionChanged listener).
123 *
124 * If this method returns "true", selectedNode and selectedWay will
125 * be set.
126 *
127 * Returns true if either one node is selected or one node and one
128 * way are selected and the node is part of the way.
129 *
130 * The way will be put into the object variable "selectedWay", the
131 * node into "selectedNode".
132 */
133 private boolean checkSelection(Collection<? extends OsmPrimitive> selection) {
134
135 int size = selection.size();
136 if (size < 1 || size > 2)
137 return false;
138
139 selectedNode = null;
140 selectedWay = null;
141
142 for (OsmPrimitive p : selection) {
143 if (p instanceof Node) {
144 selectedNode = (Node) p;
145 if (size == 1 || selectedWay != null)
146 return size == 1 || selectedWay.nodes.contains(selectedNode);
147 } else if (p instanceof Way) {
148 selectedWay = (Way) p;
149 if (size == 2 && selectedNode != null)
150 return selectedWay.nodes.contains(selectedNode);
151 }
152 }
153
154 return false;
155 }
156
157 /**
158 * Checks if the selection consists of something we can work with.
159 * Checks only if the number and type of items selected looks good;
160 * does not check whether the selected items are really a valid
161 * input for splitting (this would be too expensive to be carried
162 * out from the selectionChanged listener).
163 *
164 * Returns true if one way and any number of nodes that are part of
165 * that way are selected. Note: "any" can be none, then all nodes of
166 * the way are used.
167 *
168 * The way will be put into the object variable "selectedWay", the
169 * nodes into "selectedNodes".
170 */
171 private boolean checkSelection2(Collection<? extends OsmPrimitive> selection) {
172 if (selection.size() < 1)
173 return false;
174
175 selectedWay = null;
176 for (OsmPrimitive p : selection) {
177 if (p instanceof Way) {
178 if (selectedWay != null) {
179 return false;
180 }
181 selectedWay = (Way) p;
182 }
183 }
184 if (selectedWay == null) {
185 return false;
186 }
187
188 selectedNodes = new ArrayList<Node>();
189 for (OsmPrimitive p : selection) {
190 if (p instanceof Node) {
191 Node n = (Node) p;
192 if (!selectedWay.nodes.contains(n)) {
193 return false;
194 }
195 selectedNodes.add(n);
196 }
197 }
198
199 if (selectedNodes.size() < 1) {
200 selectedNodes.addAll(selectedWay.nodes);
201 }
202
203 return true;
204 }
205
206 /**
207 * dupe the given node of the given way
208 *
209 * -> the new node will be put into the parameter newNodes.
210 * -> the add-node command will be put into the parameter cmds.
211 * -> the changed way will be returned and must be put into cmds by the caller!
212 */
213 private Way modifyWay(Node originalNode, Way w, List<Command> cmds, List<Node> newNodes) {
214 ArrayList<Node> nn = new ArrayList<Node>();
215 for (Node pushNode : w.nodes) {
216 if (originalNode == pushNode) {
217 // clone the node for all other ways
218 pushNode = new Node(pushNode);
219 pushNode.id = 0;
220 newNodes.add(pushNode);
221 cmds.add(new AddCommand(pushNode));
222 }
223 nn.add(pushNode);
224 }
225 Way newWay = new Way(w);
226 newWay.nodes.clear();
227 newWay.nodes.addAll(nn);
228
229 return newWay;
230 }
231
232 /**
233 * put all newNodes into the same relation(s) that originalNode is in
234 */
235 private void fixRelations(Node originalNode, List<Command> cmds, List<Node> newNodes) {
236 // modify all relations containing the node
237 Relation newRel = null;
238 HashSet<String> rolesToReAdd = null;
239 for (Relation r : Main.ds.relations) {
240 if (r.deleted || r.incomplete) continue;
241 newRel = null;
242 rolesToReAdd = null;
243 for (RelationMember rm : r.members) {
244 if (rm.member instanceof Node) {
245 if (rm.member == originalNode) {
246 if (newRel == null) {
247 newRel = new Relation(r);
248 newRel.members.clear();
249 rolesToReAdd = new HashSet<String>();
250 }
251 rolesToReAdd.add(rm.role);
252 }
253 }
254 }
255 if (newRel != null) {
256 for (RelationMember rm : r.members) {
257 //if (rm.member != selectedNode) {
258 newRel.members.add(rm);
259 //}
260 }
261 for (Node n : newNodes) {
262 for (String role : rolesToReAdd) {
263 newRel.members.add(new RelationMember(role, n));
264 }
265 }
266 cmds.add(new ChangeCommand(r, newRel));
267 }
268 }
269 }
270
271
272 /**
273 * dupe a single node into as many nodes as there are ways using it, OR
274 *
275 * dupe a single node once, and put the copy on the selected way
276 */
277 private void unglueWays() {
278 LinkedList<Command> cmds = new LinkedList<Command>();
279 List<Node> newNodes = new LinkedList<Node>();
280
281 if (selectedWay == null) {
282 boolean firstway = true;
283 // modify all ways containing the nodes
284 for (Way w : Main.ds.ways) {
285 if (w.deleted || w.incomplete || w.nodes.size() < 1) continue;
286 if (!w.nodes.contains(selectedNode)) continue;
287 if (!firstway) cmds.add(new ChangeCommand(w, modifyWay(selectedNode, w, cmds, newNodes)));
288 firstway = false;
289 }
290 } else {
291 cmds.add(new ChangeCommand(selectedWay, modifyWay(selectedNode, selectedWay, cmds, newNodes)));
292 }
293
294 fixRelations(selectedNode, cmds, newNodes);
295
296 Main.main.undoRedo.add(new SequenceCommand(tr("Dupe into {0} nodes", newNodes.size()+1), cmds));
297 if (selectedWay == null) { // if a node has been selected, new selection is ALL nodes
298 newNodes.add(selectedNode);
299 } // if a node and a way has been selected, new selection is only the new node that was added to the selected way
300 Main.ds.setSelected(newNodes);
301 }
302
303 /**
304 * dupe all nodes that are selected, and put the copies on the selected way
305 *
306 */
307 private void unglueWays2() {
308 LinkedList<Command> cmds = new LinkedList<Command>();
309 List<Node> allNewNodes = new LinkedList<Node>();
310 Way tmpWay = selectedWay;
311
312 for (Node n : selectedNodes) {
313 List<Node> newNodes = new LinkedList<Node>();
314 tmpWay = modifyWay(n, tmpWay, cmds, newNodes);
315 fixRelations(n, cmds, newNodes);
316 allNewNodes.addAll(newNodes);
317 }
318 cmds.add(new ChangeCommand(selectedWay, tmpWay)); // only one changeCommand for a way, else garbage will happen
319
320 Main.main.undoRedo.add(new SequenceCommand(tr("Dupe {0} nodes into {1} nodes", selectedNodes.size(), selectedNodes.size()+allNewNodes.size()), cmds));
321 Main.ds.setSelected(allNewNodes);
322 }
323
324// Disabled because we have such a nice help text that would not be shown otherwise.
325//
326// /**
327// * Enable the menu option if the selection looks like we could use it.
328// */
329// public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) {
330// setEnabled(checkSelection(newSelection) || checkSelection2(newSelection));
331// selectedNode = null;
332// selectedWay = null;
333// selectedNodes = null;
334// }
335}
Note: See TracBrowser for help on using the repository browser.