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

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

see #12943 - gsoc-core - fix most of deprecation warnings (static accesses must be fixed)

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