[858] | 1 | // License: GPL. Copyright 2007 by Immanuel Scholz and others
|
---|
| 2 | package org.openstreetmap.josm.actions;
|
---|
| 3 |
|
---|
[2410] | 4 | import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
|
---|
[858] | 5 | import static org.openstreetmap.josm.tools.I18n.tr;
|
---|
| 6 |
|
---|
| 7 | import java.awt.event.ActionEvent;
|
---|
| 8 | import java.awt.event.KeyEvent;
|
---|
| 9 | import java.util.ArrayList;
|
---|
| 10 | import java.util.Collection;
|
---|
[1429] | 11 | import java.util.Collections;
|
---|
[858] | 12 | import java.util.HashSet;
|
---|
| 13 | import java.util.LinkedList;
|
---|
| 14 | import java.util.List;
|
---|
| 15 |
|
---|
| 16 | import javax.swing.JOptionPane;
|
---|
[1429] | 17 | import javax.swing.JPanel;
|
---|
[858] | 18 |
|
---|
| 19 | import org.openstreetmap.josm.Main;
|
---|
| 20 | import org.openstreetmap.josm.command.AddCommand;
|
---|
| 21 | import org.openstreetmap.josm.command.ChangeCommand;
|
---|
| 22 | import org.openstreetmap.josm.command.Command;
|
---|
| 23 | import org.openstreetmap.josm.command.SequenceCommand;
|
---|
| 24 | import org.openstreetmap.josm.data.osm.Node;
|
---|
| 25 | import org.openstreetmap.josm.data.osm.OsmPrimitive;
|
---|
| 26 | import org.openstreetmap.josm.data.osm.Relation;
|
---|
| 27 | import org.openstreetmap.josm.data.osm.RelationMember;
|
---|
| 28 | import org.openstreetmap.josm.data.osm.Way;
|
---|
[1429] | 29 | import org.openstreetmap.josm.gui.MapView;
|
---|
[1084] | 30 | import 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] | 42 | public 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 | }
|
---|