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

Last change on this file since 14134 was 14134, checked in by Don-vip, 2 months ago

see #15229 - deprecate Main*.undoRedo - make UndoRedoHandler a singleton

  • Property svn:eol-style set to native
File size: 27.8 KB
Line 
1// License: GPL. For details, see LICENSE file.
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.GridBagLayout;
9import java.awt.event.ActionEvent;
10import java.awt.event.KeyEvent;
11import java.util.ArrayList;
12import java.util.Collection;
13import java.util.Collections;
14import java.util.HashMap;
15import java.util.HashSet;
16import java.util.LinkedList;
17import java.util.List;
18import java.util.Map;
19import java.util.Set;
20
21import javax.swing.AbstractButton;
22import javax.swing.ButtonGroup;
23import javax.swing.JLabel;
24import javax.swing.JOptionPane;
25import javax.swing.JPanel;
26import javax.swing.JToggleButton;
27
28import org.openstreetmap.josm.Main;
29import org.openstreetmap.josm.command.AddCommand;
30import org.openstreetmap.josm.command.ChangeCommand;
31import org.openstreetmap.josm.command.ChangeNodesCommand;
32import org.openstreetmap.josm.command.Command;
33import org.openstreetmap.josm.command.SequenceCommand;
34import org.openstreetmap.josm.data.UndoRedoHandler;
35import org.openstreetmap.josm.data.osm.DefaultNameFormatter;
36import org.openstreetmap.josm.data.osm.Node;
37import org.openstreetmap.josm.data.osm.OsmPrimitive;
38import org.openstreetmap.josm.data.osm.Relation;
39import org.openstreetmap.josm.data.osm.RelationMember;
40import org.openstreetmap.josm.data.osm.Way;
41import org.openstreetmap.josm.gui.ExtendedDialog;
42import org.openstreetmap.josm.gui.MainApplication;
43import org.openstreetmap.josm.gui.MapView;
44import org.openstreetmap.josm.gui.Notification;
45import org.openstreetmap.josm.tools.GBC;
46import org.openstreetmap.josm.tools.ImageProvider;
47import org.openstreetmap.josm.tools.Logging;
48import org.openstreetmap.josm.tools.Shortcut;
49import org.openstreetmap.josm.tools.UserCancelException;
50import org.openstreetmap.josm.tools.Utils;
51
52/**
53 * Duplicate nodes that are used by multiple ways.
54 *
55 * Resulting nodes are identical, up to their position.
56 *
57 * This is the opposite of the MergeNodesAction.
58 *
59 * If a single node is selected, it will copy that node and remove all tags from the old one
60 */
61public class UnGlueAction extends JosmAction {
62
63    private transient Node selectedNode;
64    private transient Way selectedWay;
65    private transient Set<Node> selectedNodes;
66
67    /**
68     * Create a new UnGlueAction.
69     */
70    public UnGlueAction() {
71        super(tr("UnGlue Ways"), "unglueways", tr("Duplicate nodes that are used by multiple ways."),
72                Shortcut.registerShortcut("tools:unglue", tr("Tool: {0}", tr("UnGlue Ways")), KeyEvent.VK_G, Shortcut.DIRECT), true);
73        putValue("help", ht("/Action/UnGlue"));
74    }
75
76    /**
77     * Called when the action is executed.
78     *
79     * This method does some checking on the selection and calls the matching unGlueWay method.
80     */
81    @Override
82    public void actionPerformed(ActionEvent e) {
83        try {
84            unglue(e);
85        } catch (UserCancelException ignore) {
86            Logging.trace(ignore);
87        } finally {
88            cleanup();
89        }
90    }
91
92    protected void unglue(ActionEvent e) throws UserCancelException {
93
94        Collection<OsmPrimitive> selection = getLayerManager().getEditDataSet().getSelected();
95
96        String errMsg = null;
97        int errorTime = Notification.TIME_DEFAULT;
98        if (checkSelectionOneNodeAtMostOneWay(selection)) {
99            checkAndConfirmOutlyingUnglue();
100            int count = 0;
101            for (Way w : selectedNode.getParentWays()) {
102                if (!w.isUsable() || w.getNodesCount() < 1) {
103                    continue;
104                }
105                count++;
106            }
107            if (count < 2) {
108                boolean selfCrossing = false;
109                if (count == 1) {
110                    // First try unglue self-crossing way
111                    selfCrossing = unglueSelfCrossingWay();
112                }
113                // If there aren't enough ways, maybe the user wanted to unglue the nodes
114                // (= copy tags to a new node)
115                if (!selfCrossing)
116                    if (checkForUnglueNode(selection)) {
117                        unglueOneNodeAtMostOneWay(e);
118                    } else {
119                        errorTime = Notification.TIME_SHORT;
120                        errMsg = tr("This node is not glued to anything else.");
121                    }
122            } else {
123                // and then do the work.
124                unglueWays();
125            }
126        } else if (checkSelectionOneWayAnyNodes(selection)) {
127            checkAndConfirmOutlyingUnglue();
128            Set<Node> tmpNodes = new HashSet<>();
129            for (Node n : selectedNodes) {
130                int count = 0;
131                for (Way w : n.getParentWays()) {
132                    if (!w.isUsable()) {
133                        continue;
134                    }
135                    count++;
136                }
137                if (count >= 2) {
138                    tmpNodes.add(n);
139                }
140            }
141            if (tmpNodes.isEmpty()) {
142                if (selection.size() > 1) {
143                    errMsg = tr("None of these nodes are glued to anything else.");
144                } else {
145                    errMsg = tr("None of this way''s nodes are glued to anything else.");
146                }
147            } else {
148                // and then do the work.
149                selectedNodes = tmpNodes;
150                unglueOneWayAnyNodes();
151            }
152        } else {
153            errorTime = Notification.TIME_VERY_LONG;
154            errMsg =
155                tr("The current selection cannot be used for unglueing.")+'\n'+
156                '\n'+
157                tr("Select either:")+'\n'+
158                tr("* One tagged node, or")+'\n'+
159                tr("* One node that is used by more than one way, or")+'\n'+
160                tr("* One node that is used by more than one way and one of those ways, or")+'\n'+
161                tr("* One way that has one or more nodes that are used by more than one way, or")+'\n'+
162                tr("* One way and one or more of its nodes that are used by more than one way.")+'\n'+
163                '\n'+
164                tr("Note: If a way is selected, this way will get fresh copies of the unglued\n"+
165                        "nodes and the new nodes will be selected. Otherwise, all ways will get their\n"+
166                "own copy and all nodes will be selected.");
167        }
168
169        if (errMsg != null) {
170            new Notification(
171                    errMsg)
172                    .setIcon(JOptionPane.ERROR_MESSAGE)
173                    .setDuration(errorTime)
174                    .show();
175        }
176    }
177
178    private void cleanup() {
179        selectedNode = null;
180        selectedWay = null;
181        selectedNodes = null;
182    }
183
184    /**
185     * Provides toggle buttons to allow the user choose the existing node, the new nodes, or all of them.
186     */
187    private static class ExistingBothNewChoice {
188        final AbstractButton oldNode = new JToggleButton(tr("Existing node"), ImageProvider.get("dialogs/conflict/tagkeeptheir"));
189        final AbstractButton bothNodes = new JToggleButton(tr("Both nodes"), ImageProvider.get("dialogs/conflict/tagundecide"));
190        final AbstractButton newNode = new JToggleButton(tr("New node"), ImageProvider.get("dialogs/conflict/tagkeepmine"));
191
192        ExistingBothNewChoice(final boolean preselectNew) {
193            final ButtonGroup tagsGroup = new ButtonGroup();
194            tagsGroup.add(oldNode);
195            tagsGroup.add(bothNodes);
196            tagsGroup.add(newNode);
197            tagsGroup.setSelected((preselectNew ? newNode : oldNode).getModel(), true);
198        }
199    }
200
201    /**
202     * A dialog allowing the user decide whether the tags/memberships of the existing node should afterwards be at
203     * the existing node, the new nodes, or all of them.
204     */
205    static final class PropertiesMembershipDialog extends ExtendedDialog {
206
207        final transient ExistingBothNewChoice tags;
208        final transient ExistingBothNewChoice memberships;
209
210        private PropertiesMembershipDialog(boolean preselectNew, boolean queryTags, boolean queryMemberships) {
211            super(Main.parent, tr("Tags/Memberships"), tr("Unglue"), tr("Cancel"));
212            setButtonIcons("unglueways", "cancel");
213
214            final JPanel content = new JPanel(new GridBagLayout());
215
216            if (queryTags) {
217                content.add(new JLabel(tr("Where should the tags of the node be put?")), GBC.std(1, 1).span(3).insets(0, 20, 0, 0));
218                tags = new ExistingBothNewChoice(preselectNew);
219                content.add(tags.oldNode, GBC.std(1, 2));
220                content.add(tags.bothNodes, GBC.std(2, 2));
221                content.add(tags.newNode, GBC.std(3, 2));
222            } else {
223                tags = null;
224            }
225
226            if (queryMemberships) {
227                content.add(new JLabel(tr("Where should the memberships of this node be put?")), GBC.std(1, 3).span(3).insets(0, 20, 0, 0));
228                memberships = new ExistingBothNewChoice(preselectNew);
229                content.add(memberships.oldNode, GBC.std(1, 4));
230                content.add(memberships.bothNodes, GBC.std(2, 4));
231                content.add(memberships.newNode, GBC.std(3, 4));
232            } else {
233                memberships = null;
234            }
235
236            setContent(content);
237            setResizable(false);
238        }
239
240        static PropertiesMembershipDialog showIfNecessary(Collection<Node> selectedNodes, boolean preselectNew) throws UserCancelException {
241            final boolean tagged = isTagged(selectedNodes);
242            final boolean usedInRelations = isUsedInRelations(selectedNodes);
243            if (tagged || usedInRelations) {
244                final PropertiesMembershipDialog dialog = new PropertiesMembershipDialog(preselectNew, tagged, usedInRelations);
245                dialog.showDialog();
246                if (dialog.getValue() != 1) {
247                    throw new UserCancelException();
248                }
249                return dialog;
250            }
251            return null;
252        }
253
254        private static boolean isTagged(final Collection<Node> existingNodes) {
255            return existingNodes.stream().anyMatch(Node::hasKeys);
256        }
257
258        private static boolean isUsedInRelations(final Collection<Node> existingNodes) {
259            return existingNodes.stream().anyMatch(
260                    selectedNode -> selectedNode.getReferrers().stream().anyMatch(Relation.class::isInstance));
261        }
262
263        void update(final Node existingNode, final List<Node> newNodes, final Collection<Command> cmds) {
264            updateMemberships(existingNode, newNodes, cmds);
265            updateProperties(existingNode, newNodes, cmds);
266        }
267
268        private void updateProperties(final Node existingNode, final Iterable<Node> newNodes, final Collection<Command> cmds) {
269            if (tags != null && tags.newNode.isSelected()) {
270                final Node newSelectedNode = new Node(existingNode);
271                newSelectedNode.removeAll();
272                cmds.add(new ChangeCommand(existingNode, newSelectedNode));
273            } else if (tags != null && tags.oldNode.isSelected()) {
274                for (Node newNode : newNodes) {
275                    newNode.removeAll();
276                }
277            }
278        }
279
280        private void updateMemberships(final Node existingNode, final List<Node> newNodes, final Collection<Command> cmds) {
281            if (memberships != null && memberships.bothNodes.isSelected()) {
282                fixRelations(existingNode, cmds, newNodes, false);
283            } else if (memberships != null && memberships.newNode.isSelected()) {
284                fixRelations(existingNode, cmds, newNodes, true);
285            }
286        }
287    }
288
289    /**
290     * Assumes there is one tagged Node stored in selectedNode that it will try to unglue.
291     * (i.e. copy node and remove all tags from the old one. Relations will not be removed)
292     * @param e event that trigerred the action
293     */
294    private void unglueOneNodeAtMostOneWay(ActionEvent e) {
295        final PropertiesMembershipDialog dialog;
296        try {
297            dialog = PropertiesMembershipDialog.showIfNecessary(Collections.singleton(selectedNode), true);
298        } catch (UserCancelException ex) {
299            Logging.trace(ex);
300            return;
301        }
302
303        final Node n = new Node(selectedNode, true);
304
305        List<Command> cmds = new LinkedList<>();
306        cmds.add(new AddCommand(selectedNode.getDataSet(), n));
307        if (dialog != null) {
308            dialog.update(selectedNode, Collections.singletonList(n), cmds);
309        }
310
311        // If this wasn't called from menu, place it where the cursor is/was
312        MapView mv = MainApplication.getMap().mapView;
313        if (e.getSource() instanceof JPanel) {
314            n.setCoor(mv.getLatLon(mv.lastMEvent.getX(), mv.lastMEvent.getY()));
315        }
316
317        UndoRedoHandler.getInstance().add(new SequenceCommand(tr("Unglued Node"), cmds));
318        getLayerManager().getEditDataSet().setSelected(n);
319        mv.repaint();
320    }
321
322    /**
323     * Checks if selection is suitable for ungluing. This is the case when there's a single,
324     * tagged node selected that's part of at least one way (ungluing an unconnected node does
325     * not make sense. Due to the call order in actionPerformed, this is only called when the
326     * node is only part of one or less ways.
327     *
328     * @param selection The selection to check against
329     * @return {@code true} if selection is suitable
330     */
331    private boolean checkForUnglueNode(Collection<? extends OsmPrimitive> selection) {
332        if (selection.size() != 1)
333            return false;
334        OsmPrimitive n = (OsmPrimitive) selection.toArray()[0];
335        if (!(n instanceof Node))
336            return false;
337        if (((Node) n).getParentWays().isEmpty())
338            return false;
339
340        selectedNode = (Node) n;
341        return selectedNode.isTagged();
342    }
343
344    /**
345     * Checks if the selection consists of something we can work with.
346     * Checks only if the number and type of items selected looks good.
347     *
348     * If this method returns "true", selectedNode and selectedWay will be set.
349     *
350     * Returns true if either one node is selected or one node and one
351     * way are selected and the node is part of the way.
352     *
353     * The way will be put into the object variable "selectedWay", the node into "selectedNode".
354     * @param selection selected primitives
355     * @return true if either one node is selected or one node and one way are selected and the node is part of the way
356     */
357    private boolean checkSelectionOneNodeAtMostOneWay(Collection<? extends OsmPrimitive> selection) {
358
359        int size = selection.size();
360        if (size < 1 || size > 2)
361            return false;
362
363        selectedNode = null;
364        selectedWay = null;
365
366        for (OsmPrimitive p : selection) {
367            if (p instanceof Node) {
368                selectedNode = (Node) p;
369                if (size == 1 || selectedWay != null)
370                    return size == 1 || selectedWay.containsNode(selectedNode);
371            } else if (p instanceof Way) {
372                selectedWay = (Way) p;
373                if (size == 2 && selectedNode != null)
374                    return selectedWay.containsNode(selectedNode);
375            }
376        }
377
378        return false;
379    }
380
381    /**
382     * Checks if the selection consists of something we can work with.
383     * Checks only if the number and type of items selected looks good.
384     *
385     * Returns true if one way and any number of nodes that are part of that way are selected.
386     * Note: "any" can be none, then all nodes of the way are used.
387     *
388     * The way will be put into the object variable "selectedWay", the nodes into "selectedNodes".
389     * @param selection selected primitives
390     * @return true if one way and any number of nodes that are part of that way are selected
391     */
392    private boolean checkSelectionOneWayAnyNodes(Collection<? extends OsmPrimitive> selection) {
393        if (selection.isEmpty())
394            return false;
395
396        selectedWay = null;
397        for (OsmPrimitive p : selection) {
398            if (p instanceof Way) {
399                if (selectedWay != null)
400                    return false;
401                selectedWay = (Way) p;
402            }
403        }
404        if (selectedWay == null)
405            return false;
406
407        selectedNodes = new HashSet<>();
408        for (OsmPrimitive p : selection) {
409            if (p instanceof Node) {
410                Node n = (Node) p;
411                if (!selectedWay.containsNode(n))
412                    return false;
413                selectedNodes.add(n);
414            }
415        }
416
417        if (selectedNodes.isEmpty()) {
418            selectedNodes.addAll(selectedWay.getNodes());
419        }
420
421        return true;
422    }
423
424    /**
425     * dupe the given node of the given way
426     *
427     * assume that originalNode is in the way
428     * <ul>
429     * <li>the new node will be put into the parameter newNodes.</li>
430     * <li>the add-node command will be put into the parameter cmds.</li>
431     * <li>the changed way will be returned and must be put into cmds by the caller!</li>
432     * </ul>
433     * @param originalNode original node to duplicate
434     * @param w parent way
435     * @param cmds List of commands that will contain the new "add node" command
436     * @param newNodes List of nodes that will contain the new node
437     * @return new way The modified way. Change command mus be handled by the caller
438     */
439    private static Way modifyWay(Node originalNode, Way w, List<Command> cmds, List<Node> newNodes) {
440        // clone the node for the way
441        Node newNode = new Node(originalNode, true /* clear OSM ID */);
442        newNodes.add(newNode);
443        cmds.add(new AddCommand(originalNode.getDataSet(), newNode));
444
445        List<Node> nn = new ArrayList<>();
446        for (Node pushNode : w.getNodes()) {
447            if (originalNode == pushNode) {
448                pushNode = newNode;
449            }
450            nn.add(pushNode);
451        }
452        Way newWay = new Way(w);
453        newWay.setNodes(nn);
454
455        return newWay;
456    }
457
458    /**
459     * put all newNodes into the same relation(s) that originalNode is in
460     * @param originalNode original node to duplicate
461     * @param cmds List of commands that will contain the new "change relation" commands
462     * @param newNodes List of nodes that contain the new node
463     * @param removeOldMember whether the membership of the "old node" should be removed
464     */
465    private static void fixRelations(Node originalNode, Collection<Command> cmds, List<Node> newNodes, boolean removeOldMember) {
466        // modify all relations containing the node
467        for (Relation r : OsmPrimitive.getFilteredList(originalNode.getReferrers(), Relation.class)) {
468            if (r.isDeleted()) {
469                continue;
470            }
471            Relation newRel = null;
472            Map<String, Integer> rolesToReAdd = null; // <role name, index>
473            int i = 0;
474            for (RelationMember rm : r.getMembers()) {
475                if (rm.isNode() && rm.getMember() == originalNode) {
476                    if (newRel == null) {
477                        newRel = new Relation(r);
478                        rolesToReAdd = new HashMap<>();
479                    }
480                    if (rolesToReAdd != null) {
481                        rolesToReAdd.put(rm.getRole(), i);
482                    }
483                }
484                i++;
485            }
486            if (newRel != null) {
487                if (rolesToReAdd != null) {
488                    for (Map.Entry<String, Integer> role : rolesToReAdd.entrySet()) {
489                        for (Node n : newNodes) {
490                            newRel.addMember(role.getValue() + 1, new RelationMember(role.getKey(), n));
491                        }
492                        if (removeOldMember) {
493                            newRel.removeMember(role.getValue());
494                        }
495                    }
496                }
497                cmds.add(new ChangeCommand(r, newRel));
498            }
499        }
500    }
501
502    /**
503     * dupe a single node into as many nodes as there are ways using it, OR
504     *
505     * dupe a single node once, and put the copy on the selected way
506     */
507    private void unglueWays() {
508        final PropertiesMembershipDialog dialog;
509        try {
510            dialog = PropertiesMembershipDialog.showIfNecessary(Collections.singleton(selectedNode), false);
511        } catch (UserCancelException e) {
512            Logging.trace(e);
513            return;
514        }
515
516        List<Command> cmds = new LinkedList<>();
517        List<Node> newNodes = new LinkedList<>();
518        if (selectedWay == null) {
519            Way wayWithSelectedNode = null;
520            LinkedList<Way> parentWays = new LinkedList<>();
521            for (OsmPrimitive osm : selectedNode.getReferrers()) {
522                if (osm.isUsable() && osm instanceof Way) {
523                    Way w = (Way) osm;
524                    if (wayWithSelectedNode == null && !w.isFirstLastNode(selectedNode)) {
525                        wayWithSelectedNode = w;
526                    } else {
527                        parentWays.add(w);
528                    }
529                }
530            }
531            if (wayWithSelectedNode == null) {
532                parentWays.removeFirst();
533            }
534            for (Way w : parentWays) {
535                cmds.add(new ChangeCommand(w, modifyWay(selectedNode, w, cmds, newNodes)));
536            }
537            notifyWayPartOfRelation(parentWays);
538        } else {
539            cmds.add(new ChangeCommand(selectedWay, modifyWay(selectedNode, selectedWay, cmds, newNodes)));
540            notifyWayPartOfRelation(Collections.singleton(selectedWay));
541        }
542
543        if (dialog != null) {
544            dialog.update(selectedNode, newNodes, cmds);
545        }
546
547        execCommands(cmds, newNodes);
548    }
549
550    /**
551     * Add commands to undo-redo system.
552     * @param cmds Commands to execute
553     * @param newNodes New created nodes by this set of command
554     */
555    private void execCommands(List<Command> cmds, List<Node> newNodes) {
556        UndoRedoHandler.getInstance().add(new SequenceCommand(/* for correct i18n of plural forms - see #9110 */
557                trn("Dupe into {0} node", "Dupe into {0} nodes", newNodes.size() + 1L, newNodes.size() + 1L), cmds));
558        // select one of the new nodes
559        getLayerManager().getEditDataSet().setSelected(newNodes.get(0));
560    }
561
562    /**
563     * Duplicates a node used several times by the same way. See #9896.
564     * @return true if action is OK false if there is nothing to do
565     */
566    private boolean unglueSelfCrossingWay() {
567        // According to previous check, only one valid way through that node
568        Way way = null;
569        for (Way w: selectedNode.getParentWays()) {
570            if (w.isUsable() && w.getNodesCount() >= 1) {
571                way = w;
572            }
573        }
574        if (way == null) {
575            return false;
576        }
577        List<Command> cmds = new LinkedList<>();
578        List<Node> oldNodes = way.getNodes();
579        List<Node> newNodes = new ArrayList<>(oldNodes.size());
580        List<Node> addNodes = new ArrayList<>();
581        boolean seen = false;
582        for (Node n: oldNodes) {
583            if (n == selectedNode) {
584                if (seen) {
585                    Node newNode = new Node(n, true /* clear OSM ID */);
586                    cmds.add(new AddCommand(selectedNode.getDataSet(), newNode));
587                    newNodes.add(newNode);
588                    addNodes.add(newNode);
589                } else {
590                    newNodes.add(n);
591                    seen = true;
592                }
593            } else {
594                newNodes.add(n);
595            }
596        }
597        if (addNodes.isEmpty()) {
598            // selectedNode doesn't need unglue
599            return false;
600        }
601        cmds.add(new ChangeNodesCommand(way, newNodes));
602        notifyWayPartOfRelation(Collections.singleton(way));
603        try {
604            final PropertiesMembershipDialog dialog = PropertiesMembershipDialog.showIfNecessary(Collections.singleton(selectedNode), false);
605            if (dialog != null) {
606                dialog.update(selectedNode, addNodes, cmds);
607            }
608            execCommands(cmds, addNodes);
609            return true;
610        } catch (UserCancelException ignore) {
611            Logging.trace(ignore);
612        }
613        return false;
614    }
615
616    /**
617     * dupe all nodes that are selected, and put the copies on the selected way
618     *
619     */
620    private void unglueOneWayAnyNodes() {
621        Way tmpWay = selectedWay;
622
623        final PropertiesMembershipDialog dialog;
624        try {
625            dialog = PropertiesMembershipDialog.showIfNecessary(selectedNodes, false);
626        } catch (UserCancelException e) {
627            Logging.trace(e);
628            return;
629        }
630
631        List<Command> cmds = new LinkedList<>();
632        List<Node> allNewNodes = new LinkedList<>();
633        for (Node n : selectedNodes) {
634            List<Node> newNodes = new LinkedList<>();
635            tmpWay = modifyWay(n, tmpWay, cmds, newNodes);
636            if (dialog != null) {
637                dialog.update(n, newNodes, cmds);
638            }
639            allNewNodes.addAll(newNodes);
640        }
641        cmds.add(new ChangeCommand(selectedWay, tmpWay)); // only one changeCommand for a way, else garbage will happen
642        notifyWayPartOfRelation(Collections.singleton(selectedWay));
643
644        UndoRedoHandler.getInstance().add(new SequenceCommand(
645                trn("Dupe {0} node into {1} nodes", "Dupe {0} nodes into {1} nodes",
646                        selectedNodes.size(), selectedNodes.size(), selectedNodes.size()+allNewNodes.size()), cmds));
647        getLayerManager().getEditDataSet().setSelected(allNewNodes);
648    }
649
650    @Override
651    protected void updateEnabledState() {
652        updateEnabledStateOnCurrentSelection();
653    }
654
655    @Override
656    protected void updateEnabledState(Collection<? extends OsmPrimitive> selection) {
657        updateEnabledStateOnModifiableSelection(selection);
658    }
659
660    protected void checkAndConfirmOutlyingUnglue() throws UserCancelException {
661        List<OsmPrimitive> primitives = new ArrayList<>(2 + (selectedNodes == null ? 0 : selectedNodes.size()));
662        if (selectedNodes != null)
663            primitives.addAll(selectedNodes);
664        if (selectedNode != null)
665            primitives.add(selectedNode);
666        final boolean ok = checkAndConfirmOutlyingOperation("unglue",
667                tr("Unglue confirmation"),
668                tr("You are about to unglue nodes outside of the area you have downloaded."
669                        + "<br>"
670                        + "This can cause problems because other objects (that you do not see) might use them."
671                        + "<br>"
672                        + "Do you really want to unglue?"),
673                tr("You are about to unglue incomplete objects."
674                        + "<br>"
675                        + "This will cause problems because you don''t see the real object."
676                        + "<br>" + "Do you really want to unglue?"),
677                primitives, null);
678        if (!ok) {
679            throw new UserCancelException();
680        }
681    }
682
683    protected void notifyWayPartOfRelation(final Iterable<Way> ways) {
684        final Set<String> affectedRelations = new HashSet<>();
685        for (Way way : ways) {
686            for (OsmPrimitive ref : way.getReferrers()) {
687                if (ref instanceof Relation && ref.isUsable()) {
688                    affectedRelations.add(ref.getDisplayName(DefaultNameFormatter.getInstance()));
689                }
690            }
691        }
692        if (affectedRelations.isEmpty()) {
693            return;
694        }
695
696        final String msg1 = trn("Unglueing affected {0} relation: {1}", "Unglueing affected {0} relations: {1}",
697                affectedRelations.size(), affectedRelations.size(), Utils.joinAsHtmlUnorderedList(affectedRelations));
698        final String msg2 = trn("Ensure that the relation has not been broken!", "Ensure that the relations have not been broken!",
699                affectedRelations.size());
700        new Notification("<html>" + msg1 + msg2).setIcon(JOptionPane.WARNING_MESSAGE).show();
701    }
702}
Note: See TracBrowser for help on using the repository browser.