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

Last change on this file since 2543 was 2512, checked in by stoecker, 14 years ago

i18n updated, fixed files to reduce problems when applying patches, fix #4017

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