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

Last change on this file since 10626 was 10601, checked in by Don-vip, 8 years ago

see #11390 - sonar - squid:S1604 - Java 8: Anonymous inner classes containing only one method should become lambdas

  • Property svn:eol-style set to native
File size: 27.7 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 : OsmPrimitive.getFilteredList(selectedNode.getReferrers(), Way.class)) {
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 : OsmPrimitive.getFilteredList(n.getReferrers(), Way.class)) {
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"), new String[]{tr("Unglue"), tr("Cancel")});
209 setButtonIcons(new String[]{"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(Iterable<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 Iterable<Node> existingNodes) {
252 return Utils.exists(existingNodes, selectedNode -> selectedNode.hasKeys());
253 }
254
255 private static boolean isUsedInRelations(final Iterable<Node> existingNodes) {
256 return Utils.exists(existingNodes, selectedNode -> Utils.exists(selectedNode.getReferrers(), OsmPrimitive.relationPredicate));
257 }
258
259 void update(final Node existingNode, final List<Node> newNodes, final Collection<Command> cmds) {
260 updateMemberships(existingNode, newNodes, cmds);
261 updateProperties(existingNode, newNodes, cmds);
262 }
263
264 private void updateProperties(final Node existingNode, final Iterable<Node> newNodes, final Collection<Command> cmds) {
265 if (tags != null && tags.newNode.isSelected()) {
266 final Node newSelectedNode = new Node(existingNode);
267 newSelectedNode.removeAll();
268 cmds.add(new ChangeCommand(existingNode, newSelectedNode));
269 } else if (tags != null && tags.oldNode.isSelected()) {
270 for (Node newNode : newNodes) {
271 newNode.removeAll();
272 }
273 }
274 }
275
276 private void updateMemberships(final Node existingNode, final List<Node> newNodes, final Collection<Command> cmds) {
277 if (memberships != null && memberships.bothNodes.isSelected()) {
278 fixRelations(existingNode, cmds, newNodes, false);
279 } else if (memberships != null && memberships.newNode.isSelected()) {
280 fixRelations(existingNode, cmds, newNodes, true);
281 }
282 }
283 }
284
285 /**
286 * Assumes there is one tagged Node stored in selectedNode that it will try to unglue.
287 * (i.e. copy node and remove all tags from the old one. Relations will not be removed)
288 * @param e event that trigerred the action
289 */
290 private void unglueOneNodeAtMostOneWay(ActionEvent e) {
291 final PropertiesMembershipDialog dialog;
292 try {
293 dialog = PropertiesMembershipDialog.showIfNecessary(Collections.singleton(selectedNode), true);
294 } catch (UserCancelException ex) {
295 Main.trace(ex);
296 return;
297 }
298
299 final Node n = new Node(selectedNode, true);
300
301 List<Command> cmds = new LinkedList<>();
302 cmds.add(new AddCommand(n));
303 if (dialog != null) {
304 dialog.update(selectedNode, Collections.singletonList(n), cmds);
305 }
306
307 // If this wasn't called from menu, place it where the cursor is/was
308 if (e.getSource() instanceof JPanel) {
309 MapView mv = Main.map.mapView;
310 n.setCoor(mv.getLatLon(mv.lastMEvent.getX(), mv.lastMEvent.getY()));
311 }
312
313 Main.main.undoRedo.add(new SequenceCommand(tr("Unglued Node"), cmds));
314 getLayerManager().getEditDataSet().setSelected(n);
315 Main.map.mapView.repaint();
316 }
317
318 /**
319 * Checks if selection is suitable for ungluing. This is the case when there's a single,
320 * tagged node selected that's part of at least one way (ungluing an unconnected node does
321 * not make sense. Due to the call order in actionPerformed, this is only called when the
322 * node is only part of one or less ways.
323 *
324 * @param selection The selection to check against
325 * @return {@code true} if selection is suitable
326 */
327 private boolean checkForUnglueNode(Collection<? extends OsmPrimitive> selection) {
328 if (selection.size() != 1)
329 return false;
330 OsmPrimitive n = (OsmPrimitive) selection.toArray()[0];
331 if (!(n instanceof Node))
332 return false;
333 if (OsmPrimitive.getFilteredList(n.getReferrers(), Way.class).isEmpty())
334 return false;
335
336 selectedNode = (Node) n;
337 return selectedNode.isTagged();
338 }
339
340 /**
341 * Checks if the selection consists of something we can work with.
342 * Checks only if the number and type of items selected looks good.
343 *
344 * If this method returns "true", selectedNode and selectedWay will be set.
345 *
346 * Returns true if either one node is selected or one node and one
347 * way are selected and the node is part of the way.
348 *
349 * The way will be put into the object variable "selectedWay", the node into "selectedNode".
350 * @param selection selected primitives
351 * @return true if either one node is selected or one node and one way are selected and the node is part of the way
352 */
353 private boolean checkSelectionOneNodeAtMostOneWay(Collection<? extends OsmPrimitive> selection) {
354
355 int size = selection.size();
356 if (size < 1 || size > 2)
357 return false;
358
359 selectedNode = null;
360 selectedWay = null;
361
362 for (OsmPrimitive p : selection) {
363 if (p instanceof Node) {
364 selectedNode = (Node) p;
365 if (size == 1 || selectedWay != null)
366 return size == 1 || selectedWay.containsNode(selectedNode);
367 } else if (p instanceof Way) {
368 selectedWay = (Way) p;
369 if (size == 2 && selectedNode != null)
370 return selectedWay.containsNode(selectedNode);
371 }
372 }
373
374 return false;
375 }
376
377 /**
378 * Checks if the selection consists of something we can work with.
379 * Checks only if the number and type of items selected looks good.
380 *
381 * Returns true if one way and any number of nodes that are part of that way are selected.
382 * Note: "any" can be none, then all nodes of the way are used.
383 *
384 * The way will be put into the object variable "selectedWay", the nodes into "selectedNodes".
385 * @param selection selected primitives
386 * @return true if one way and any number of nodes that are part of that way are selected
387 */
388 private boolean checkSelectionOneWayAnyNodes(Collection<? extends OsmPrimitive> selection) {
389 if (selection.isEmpty())
390 return false;
391
392 selectedWay = null;
393 for (OsmPrimitive p : selection) {
394 if (p instanceof Way) {
395 if (selectedWay != null)
396 return false;
397 selectedWay = (Way) p;
398 }
399 }
400 if (selectedWay == null)
401 return false;
402
403 selectedNodes = new HashSet<>();
404 for (OsmPrimitive p : selection) {
405 if (p instanceof Node) {
406 Node n = (Node) p;
407 if (!selectedWay.containsNode(n))
408 return false;
409 selectedNodes.add(n);
410 }
411 }
412
413 if (selectedNodes.isEmpty()) {
414 selectedNodes.addAll(selectedWay.getNodes());
415 }
416
417 return true;
418 }
419
420 /**
421 * dupe the given node of the given way
422 *
423 * assume that originalNode is in the way
424 * <ul>
425 * <li>the new node will be put into the parameter newNodes.</li>
426 * <li>the add-node command will be put into the parameter cmds.</li>
427 * <li>the changed way will be returned and must be put into cmds by the caller!</li>
428 * </ul>
429 * @param originalNode original node to duplicate
430 * @param w parent way
431 * @param cmds List of commands that will contain the new "add node" command
432 * @param newNodes List of nodes that will contain the new node
433 * @return new way The modified way. Change command mus be handled by the caller
434 */
435 private static Way modifyWay(Node originalNode, Way w, List<Command> cmds, List<Node> newNodes) {
436 // clone the node for the way
437 Node newNode = new Node(originalNode, true /* clear OSM ID */);
438 newNodes.add(newNode);
439 cmds.add(new AddCommand(newNode));
440
441 List<Node> nn = new ArrayList<>();
442 for (Node pushNode : w.getNodes()) {
443 if (originalNode == pushNode) {
444 pushNode = newNode;
445 }
446 nn.add(pushNode);
447 }
448 Way newWay = new Way(w);
449 newWay.setNodes(nn);
450
451 return newWay;
452 }
453
454 /**
455 * put all newNodes into the same relation(s) that originalNode is in
456 * @param originalNode original node to duplicate
457 * @param cmds List of commands that will contain the new "change relation" commands
458 * @param newNodes List of nodes that contain the new node
459 * @param removeOldMember whether the membership of the "old node" should be removed
460 */
461 private static void fixRelations(Node originalNode, Collection<Command> cmds, List<Node> newNodes, boolean removeOldMember) {
462 // modify all relations containing the node
463 for (Relation r : OsmPrimitive.getFilteredList(originalNode.getReferrers(), Relation.class)) {
464 if (r.isDeleted()) {
465 continue;
466 }
467 Relation newRel = null;
468 Map<String, Integer> rolesToReAdd = null; // <role name, index>
469 int i = 0;
470 for (RelationMember rm : r.getMembers()) {
471 if (rm.isNode() && rm.getMember() == originalNode) {
472 if (newRel == null) {
473 newRel = new Relation(r);
474 rolesToReAdd = new HashMap<>();
475 }
476 if (rolesToReAdd != null) {
477 rolesToReAdd.put(rm.getRole(), i);
478 }
479 }
480 i++;
481 }
482 if (newRel != null) {
483 if (rolesToReAdd != null) {
484 for (Map.Entry<String, Integer> role : rolesToReAdd.entrySet()) {
485 for (Node n : newNodes) {
486 newRel.addMember(role.getValue() + 1, new RelationMember(role.getKey(), n));
487 }
488 if (removeOldMember) {
489 newRel.removeMember(role.getValue());
490 }
491 }
492 }
493 cmds.add(new ChangeCommand(r, newRel));
494 }
495 }
496 }
497
498 /**
499 * dupe a single node into as many nodes as there are ways using it, OR
500 *
501 * dupe a single node once, and put the copy on the selected way
502 */
503 private void unglueWays() {
504 final PropertiesMembershipDialog dialog;
505 try {
506 dialog = PropertiesMembershipDialog.showIfNecessary(Collections.singleton(selectedNode), false);
507 } catch (UserCancelException e) {
508 Main.trace(e);
509 return;
510 }
511
512 List<Command> cmds = new LinkedList<>();
513 List<Node> newNodes = new LinkedList<>();
514 if (selectedWay == null) {
515 Way wayWithSelectedNode = null;
516 LinkedList<Way> parentWays = new LinkedList<>();
517 for (OsmPrimitive osm : selectedNode.getReferrers()) {
518 if (osm.isUsable() && osm instanceof Way) {
519 Way w = (Way) osm;
520 if (wayWithSelectedNode == null && !w.isFirstLastNode(selectedNode)) {
521 wayWithSelectedNode = w;
522 } else {
523 parentWays.add(w);
524 }
525 }
526 }
527 if (wayWithSelectedNode == null) {
528 parentWays.removeFirst();
529 }
530 for (Way w : parentWays) {
531 cmds.add(new ChangeCommand(w, modifyWay(selectedNode, w, cmds, newNodes)));
532 }
533 notifyWayPartOfRelation(parentWays);
534 } else {
535 cmds.add(new ChangeCommand(selectedWay, modifyWay(selectedNode, selectedWay, cmds, newNodes)));
536 notifyWayPartOfRelation(Collections.singleton(selectedWay));
537 }
538
539 if (dialog != null) {
540 dialog.update(selectedNode, newNodes, cmds);
541 }
542
543 execCommands(cmds, newNodes);
544 }
545
546 /**
547 * Add commands to undo-redo system.
548 * @param cmds Commands to execute
549 * @param newNodes New created nodes by this set of command
550 */
551 private void execCommands(List<Command> cmds, List<Node> newNodes) {
552 Main.main.undoRedo.add(new SequenceCommand(/* for correct i18n of plural forms - see #9110 */
553 trn("Dupe into {0} node", "Dupe into {0} nodes", newNodes.size() + 1L, newNodes.size() + 1L), cmds));
554 // select one of the new nodes
555 getLayerManager().getEditDataSet().setSelected(newNodes.get(0));
556 }
557
558 /**
559 * Duplicates a node used several times by the same way. See #9896.
560 * @return true if action is OK false if there is nothing to do
561 */
562 private boolean unglueSelfCrossingWay() {
563 // According to previous check, only one valid way through that node
564 Way way = null;
565 for (Way w: OsmPrimitive.getFilteredList(selectedNode.getReferrers(), Way.class)) {
566 if (w.isUsable() && w.getNodesCount() >= 1) {
567 way = w;
568 }
569 }
570 if (way == null) {
571 return false;
572 }
573 List<Command> cmds = new LinkedList<>();
574 List<Node> oldNodes = way.getNodes();
575 List<Node> newNodes = new ArrayList<>(oldNodes.size());
576 List<Node> addNodes = new ArrayList<>();
577 boolean seen = false;
578 for (Node n: oldNodes) {
579 if (n == selectedNode) {
580 if (seen) {
581 Node newNode = new Node(n, true /* clear OSM ID */);
582 newNodes.add(newNode);
583 cmds.add(new AddCommand(newNode));
584 newNodes.add(newNode);
585 addNodes.add(newNode);
586 } else {
587 newNodes.add(n);
588 seen = true;
589 }
590 } else {
591 newNodes.add(n);
592 }
593 }
594 if (addNodes.isEmpty()) {
595 // selectedNode doesn't need unglue
596 return false;
597 }
598 cmds.add(new ChangeNodesCommand(way, newNodes));
599 notifyWayPartOfRelation(Collections.singleton(way));
600 try {
601 final PropertiesMembershipDialog dialog = PropertiesMembershipDialog.showIfNecessary(Collections.singleton(selectedNode), false);
602 if (dialog != null) {
603 dialog.update(selectedNode, addNodes, cmds);
604 }
605 execCommands(cmds, addNodes);
606 return true;
607 } catch (UserCancelException ignore) {
608 Main.trace(ignore);
609 }
610 return false;
611 }
612
613 /**
614 * dupe all nodes that are selected, and put the copies on the selected way
615 *
616 */
617 private void unglueOneWayAnyNodes() {
618 Way tmpWay = selectedWay;
619
620 final PropertiesMembershipDialog dialog;
621 try {
622 dialog = PropertiesMembershipDialog.showIfNecessary(selectedNodes, false);
623 } catch (UserCancelException e) {
624 Main.trace(e);
625 return;
626 }
627
628 List<Command> cmds = new LinkedList<>();
629 List<Node> allNewNodes = new LinkedList<>();
630 for (Node n : selectedNodes) {
631 List<Node> newNodes = new LinkedList<>();
632 tmpWay = modifyWay(n, tmpWay, cmds, newNodes);
633 if (dialog != null) {
634 dialog.update(n, newNodes, cmds);
635 }
636 allNewNodes.addAll(newNodes);
637 }
638 cmds.add(new ChangeCommand(selectedWay, tmpWay)); // only one changeCommand for a way, else garbage will happen
639 notifyWayPartOfRelation(Collections.singleton(selectedWay));
640
641 Main.main.undoRedo.add(new SequenceCommand(
642 trn("Dupe {0} node into {1} nodes", "Dupe {0} nodes into {1} nodes",
643 selectedNodes.size(), selectedNodes.size(), selectedNodes.size()+allNewNodes.size()), cmds));
644 getLayerManager().getEditDataSet().setSelected(allNewNodes);
645 }
646
647 @Override
648 protected void updateEnabledState() {
649 updateEnabledStateOnCurrentSelection();
650 }
651
652 @Override
653 protected void updateEnabledState(Collection<? extends OsmPrimitive> selection) {
654 setEnabled(selection != null && !selection.isEmpty());
655 }
656
657 protected void checkAndConfirmOutlyingUnglue() throws UserCancelException {
658 List<OsmPrimitive> primitives = new ArrayList<>(2 + (selectedNodes == null ? 0 : selectedNodes.size()));
659 if (selectedNodes != null)
660 primitives.addAll(selectedNodes);
661 if (selectedNode != null)
662 primitives.add(selectedNode);
663 final boolean ok = Command.checkAndConfirmOutlyingOperation("unglue",
664 tr("Unglue confirmation"),
665 tr("You are about to unglue nodes outside of the area you have downloaded."
666 + "<br>"
667 + "This can cause problems because other objects (that you do not see) might use them."
668 + "<br>"
669 + "Do you really want to unglue?"),
670 tr("You are about to unglue incomplete objects."
671 + "<br>"
672 + "This will cause problems because you don''t see the real object."
673 + "<br>" + "Do you really want to unglue?"),
674 primitives, null);
675 if (!ok) {
676 throw new UserCancelException();
677 }
678 }
679
680 protected void notifyWayPartOfRelation(final Iterable<Way> ways) {
681 final Set<String> affectedRelations = new HashSet<>();
682 for (Way way : ways) {
683 for (OsmPrimitive ref : way.getReferrers()) {
684 if (ref instanceof Relation && ref.isUsable()) {
685 affectedRelations.add(ref.getDisplayName(DefaultNameFormatter.getInstance()));
686 }
687 }
688 }
689 if (affectedRelations.isEmpty()) {
690 return;
691 }
692
693 final String msg1 = trn("Unglueing affected {0} relation: {1}", "Unglueing affected {0} relations: {1}",
694 affectedRelations.size(), affectedRelations.size(), Utils.joinAsHtmlUnorderedList(affectedRelations));
695 final String msg2 = trn("Ensure that the relation has not been broken!", "Ensure that the relations have not been broken!",
696 affectedRelations.size());
697 new Notification("<html>" + msg1 + msg2).setIcon(JOptionPane.WARNING_MESSAGE).show();
698 }
699}
Note: See TracBrowser for help on using the repository browser.