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

Revision 4982, 15.9 KB checked in by stoecker, 3 months ago (diff)

see #7226 - patch by akks (fixed a bit) - fix shortcut deprecations

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