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

Last change on this file since 12187 was 12031, checked in by Don-vip, 7 years ago

add Node.getParentWays()

  • 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"), 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(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.