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

Last change on this file since 9472 was 9470, checked in by simon04, 8 years ago

see #12304 - Do not show notification when no relation is affected

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