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

Last change on this file since 14140 was 14134, checked in by Don-vip, 6 years 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.