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

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

see #15182 - deprecate Main.main.undoRedo. Replacement: gui.MainApplication.undoRedo

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