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

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

sonar - squid:S3878 - Arrays should not be created for varargs parameters

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