source: osm/applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/actions/SplitObjectAction.java

Last change on this file was 36134, checked in by taylor.smock, 8 months ago

Use DeleteCommand.delete where possible and fix some potential NPEs from its usage

  • Property svn:eol-style set to native
File size: 27.9 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.plugins.utilsplugin2.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.event.ActionEvent;
9import java.awt.event.KeyEvent;
10import java.util.ArrayList;
11import java.util.Arrays;
12import java.util.Collection;
13import java.util.Collections;
14import java.util.HashMap;
15import java.util.HashSet;
16import java.util.List;
17import java.util.Map;
18import java.util.Set;
19import java.util.stream.Collectors;
20
21import javax.swing.JOptionPane;
22
23import org.openstreetmap.josm.actions.JosmAction;
24import org.openstreetmap.josm.command.AddCommand;
25import org.openstreetmap.josm.command.ChangeMembersCommand;
26import org.openstreetmap.josm.command.Command;
27import org.openstreetmap.josm.command.DeleteCommand;
28import org.openstreetmap.josm.command.SequenceCommand;
29import org.openstreetmap.josm.command.SplitWayCommand;
30import org.openstreetmap.josm.data.UndoRedoHandler;
31import org.openstreetmap.josm.data.osm.DataSet;
32import org.openstreetmap.josm.data.osm.MultipolygonBuilder;
33import org.openstreetmap.josm.data.osm.MultipolygonBuilder.JoinedPolygon;
34import org.openstreetmap.josm.data.osm.MultipolygonBuilder.JoinedPolygonCreationException;
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.data.osm.visitor.paint.relations.Multipolygon;
41import org.openstreetmap.josm.data.osm.visitor.paint.relations.Multipolygon.PolyData;
42import org.openstreetmap.josm.data.osm.visitor.paint.relations.Multipolygon.PolyData.Intersection;
43import org.openstreetmap.josm.data.validation.Severity;
44import org.openstreetmap.josm.data.validation.TestError;
45import org.openstreetmap.josm.data.validation.tests.MultipolygonTest;
46import org.openstreetmap.josm.gui.Notification;
47import org.openstreetmap.josm.spi.preferences.Config;
48import org.openstreetmap.josm.tools.CheckParameterUtil;
49import org.openstreetmap.josm.tools.Logging;
50import org.openstreetmap.josm.tools.Pair;
51import org.openstreetmap.josm.tools.Shortcut;
52
53/**
54 * Splits a closed way (polygon) into two closed ways or a multipolygon into two separate multipolygons.
55 * <p>
56 * The closed ways are just split at the selected nodes (which must be exactly two).
57 * The nodes remain in their original order.
58 * <p>
59 * This is similar to SplitWayAction with the addition that the split ways are closed
60 * immediately.
61 */
62public class SplitObjectAction extends JosmAction {
63 private static final String ALLOW_INV_MP_SPLIT_KEY = "utilsplugin2.split-object.allowInvalidMultipolygonSplit";
64
65 /**
66 * Create a new SplitObjectAction.
67 */
68 public SplitObjectAction() {
69 super(tr("Split Object"), "splitobject", tr("Split an object at the selected nodes."),
70 Shortcut.registerShortcut("tools:splitobject", tr("More tools: {0}", tr("Split Object")), KeyEvent.VK_X, Shortcut.ALT),
71 true);
72 putValue("help", ht("/Action/SplitObject"));
73 }
74
75 /**
76 * Called when the action is executed.
77 * <p>
78 * This method performs an expensive check whether the selection clearly defines one
79 * of the split actions outlined above, and if yes, calls the splitObject method.
80 */
81 @Override
82 public void actionPerformed(ActionEvent e) {
83 DataSet ds = getLayerManager().getEditDataSet();
84 if (!checkSelection(ds.getSelected())) {
85 showWarningNotification(tr("The current selection cannot be used for splitting."));
86 return;
87 }
88
89 List<Node> selectedNodes = new ArrayList<>(ds.getSelectedNodes());
90 List<Way> selectedWays = new ArrayList<>(ds.getSelectedWays());
91 List<Relation> selectedRelations = new ArrayList<>(ds.getSelectedRelations());
92
93 Way selectedWay = null;
94 Way splitWay = null;
95
96 if (selectedNodes.size() != 2) { // if not exactly 2 nodes are selected - try to find split way
97 selectedNodes.clear(); // empty selected nodes (see #8237)
98 for (Way selWay : selectedWays) { // we assume not more 2 ways in the list
99 if (selWay != null && // If one of selected ways is not closed we have it to get split points
100 selWay.isUsable() &&
101 selWay.getNodesCount() > 0 &&
102 !selWay.isClosed() &&
103 selWay.getKeys().isEmpty()) {
104 selectedNodes.add(selWay.firstNode());
105 selectedNodes.add(selWay.lastNode());
106 splitWay = selWay;
107 } else {
108 selectedWay = selWay; // use another way as selected way
109 }
110 }
111 } else if (selectedWays.size() == 1) {
112 selectedWay = selectedWays.get(0); // two nodes and a way is selected, so use this selected way
113 }
114
115 if (selectedRelations.size() > 1) {
116 showWarningNotification(tr("Only one multipolygon can be selected for splitting"));
117 return;
118 }
119
120 if ((selectedRelations.size() == 1) && selectedRelations.get(0).isMultipolygon()) {
121 Relation selectedMultipolygon = selectedRelations.get(0);
122 if (splitWay == null) {
123 showWarningNotification(tr("Splitting multipolygons requires a split way to be selected"));
124 return;
125 }
126
127 boolean allowInvalidMpSplit = Config.getPref().getBoolean(ALLOW_INV_MP_SPLIT_KEY, false);
128
129 splitMultipolygonAtWayChecked(selectedMultipolygon, splitWay, allowInvalidMpSplit);
130 return;
131 }
132
133 // If only nodes are selected, try to guess which way to split. This works if there
134 // is exactly one way that all nodes are part of.
135 if (selectedWay == null && !selectedNodes.isEmpty()) {
136 Map<Way, Integer> wayOccurenceCounter = new HashMap<>();
137 for (Node n : selectedNodes) {
138 for (Way w : n.getParentWays()) {
139 if (!w.isUsable()) {
140 continue;
141 }
142 // Only closed ways with at least four distinct nodes
143 // (i.e. five members since the first/last is listed twice)
144 // can be split into two objects
145 if (w.getNodesCount() < 5 || !w.isClosed()) {
146 continue;
147 }
148 for (Node wn : w.getNodes()) {
149 if (n.equals(wn)) {
150 Integer old = wayOccurenceCounter.get(w);
151 wayOccurenceCounter.put(w, (old == null) ? 1 : old + 1);
152 break;
153 }
154 }
155 }
156 }
157 if (wayOccurenceCounter.isEmpty()) {
158 showWarningNotification(
159 trn("The selected node is not in the middle of any way.",
160 "The selected nodes are not in the middle of any way.",
161 selectedNodes.size()));
162 return;
163 }
164
165 for (Map.Entry<Way, Integer> entry : wayOccurenceCounter.entrySet()) {
166 if (entry.getValue().equals(selectedNodes.size())) {
167 if (selectedWay != null) {
168 showWarningNotification(
169 trn("There is more than one way using the node you selected. Please select the way as well.",
170 "There is more than one way using the nodes you selected. Please select the way as well.",
171 selectedNodes.size())
172 );
173 return;
174 }
175 selectedWay = entry.getKey();
176 }
177 }
178
179 if (selectedWay == null) {
180 showWarningNotification(tr("The selected nodes do not share the same way."));
181 return;
182 }
183
184 // If a way and nodes are selected, verify that the nodes
185 // are part of the way and that the way is closed.
186 } else if (selectedWay != null && !selectedNodes.isEmpty()) {
187 if (!selectedWay.isClosed()) {
188 showWarningNotification(tr("The selected way is not closed."));
189 return;
190 }
191 HashSet<Node> nds = new HashSet<>(selectedNodes);
192 nds.removeAll(selectedWay.getNodes());
193 if (!nds.isEmpty()) {
194 showWarningNotification(
195 trn("The selected way does not contain the selected node.",
196 "The selected way does not contain all the selected nodes.",
197 selectedNodes.size()));
198 return;
199 }
200 } else if (selectedWay != null && selectedNodes.isEmpty()) {
201 showWarningNotification(
202 tr("The selected way is not a split way, please select split points or split way too."));
203 return;
204 }
205
206 // we're guaranteed to have two nodes
207 Node node1 = selectedNodes.get(0);
208 int nodeIndex1 = -1;
209 Node node2 = selectedNodes.get(1);
210 int nodeIndex2 = -1;
211 int i = 0;
212 for (Node wn : selectedWay.getNodes()) {
213 if (nodeIndex1 == -1 && wn.equals(node1)) {
214 nodeIndex1 = i;
215 } else if (nodeIndex2 == -1 && wn.equals(node2)) {
216 nodeIndex2 = i;
217 }
218 i++;
219 }
220 // both nodes aren't allowed to be consecutive
221 if ((splitWay == null || splitWay.getNodesCount() == 2)
222 && (nodeIndex1 == nodeIndex2 + 1 || nodeIndex2 == nodeIndex1 + 1 ||
223 // minus 2 because we've a circular way where
224 // the penultimate node is the last unique one
225 (nodeIndex1 == 0 && nodeIndex2 == selectedWay.getNodesCount() - 2)
226 || (nodeIndex2 == 0 && nodeIndex1 == selectedWay.getNodesCount() - 2))) {
227 showWarningNotification(
228 tr("The selected nodes can not be consecutive nodes in the object."));
229 return;
230 }
231 splitWayChecked(selectedNodes, selectedWay, splitWay);
232 }
233
234 private void splitWayChecked(List<Node> selectedNodes, Way selectedWay, Way splitWay) {
235 List<List<Node>> wayChunks = SplitWayCommand.buildSplitChunks(selectedWay, selectedNodes);
236 if (wayChunks != null) {
237 // close the chunks
238 // update the logic - if we have splitWay not null, we have to add points from it to both chunks (in the correct direction)
239 if (splitWay == null) {
240 for (List<Node> wayChunk : wayChunks) {
241 wayChunk.add(wayChunk.get(0));
242 }
243 } else {
244 for (List<Node> wayChunk : wayChunks) {
245 // check direction of the chunk and add splitWay nodes in the correct order
246 List<Node> way = splitWay.getNodes();
247 if (wayChunk.get(0).equals(splitWay.firstNode())) {
248 // add way to the end in the opposite direction.
249 way.remove(way.size()-1); // remove the last node
250 Collections.reverse(way);
251 } else {
252 // add way to the end in the given direction, remove the first node
253 way.remove(0);
254 }
255 wayChunk.addAll(way);
256 }
257 }
258 SplitWayCommand result = SplitWayCommand.splitWay(
259 selectedWay, wayChunks, Collections.emptyList());
260 if (splitWay != null) {
261 result.executeCommand();
262 Command delCmd = DeleteCommand.delete(Collections.singletonList(splitWay));
263 if (delCmd != null) {
264 delCmd.executeCommand();
265 UndoRedoHandler.getInstance().add(new SplitObjectCommand(Arrays.asList(result, delCmd)), false);
266 } else {
267 UndoRedoHandler.getInstance().add(new SplitObjectCommand(Collections.singletonList(result)), false);
268 }
269 } else {
270 UndoRedoHandler.getInstance().add(result);
271 }
272 getLayerManager().getEditDataSet().setSelected(result.getNewSelection());
273 }
274
275 }
276
277 /**
278 * Splits a multipolygon into two separate multipolygons along a way using {@link #splitMultipolygonAtWay}
279 * if the resulting multipolygons are valid.
280 * Inner polygon rings are automatically assigned to the appropriate multipolygon relation based on their location.
281 * Performs a complete check of the resulting multipolygons using {@link MultipolygonTest} and aborts + displays
282 * warning messages to the user if errors are encountered.
283 * @param mpRelation the multipolygon relation to split.
284 * @param splitWay the way along which the multipolygon should be split.
285 * Must start and end on the outer ways and must not intersect with or connect to any of the multipolygon inners.
286 * @param allowInvalidSplit allow multipolygon splits that result in invalid multipolygons.
287 * @return the new multipolygon relations after splitting + the executed commands
288 * (already executed and added to the {@link UndoRedoHandler}).
289 * Relation and command lists are empty if split did not succeed.
290 */
291 public static Pair<List<Relation>, List<Command>> splitMultipolygonAtWayChecked(
292 Relation mpRelation, Way splitWay, boolean allowInvalidSplit) {
293
294 CheckParameterUtil.ensureParameterNotNull(mpRelation, "mpRelation");
295 CheckParameterUtil.ensureParameterNotNull(splitWay, "splitWay");
296 CheckParameterUtil.ensureThat(mpRelation.isMultipolygon(), "mpRelation.isMultipolygon");
297
298 try {
299 Pair<List<Relation>, List<Command>> splitResult = splitMultipolygonAtWay(mpRelation, splitWay, allowInvalidSplit);
300 List<Relation> mpRelations = splitResult.a;
301 List<Command> commands = splitResult.b;
302
303 List<TestError> mpErrorsPostSplit = new ArrayList<>();
304 for (Relation mp : mpRelations) {
305 MultipolygonTest mpTestPostSplit = new MultipolygonTest();
306
307 mpTestPostSplit.visit(mp);
308
309 List<TestError> severeErrors = mpTestPostSplit.getErrors().stream()
310 .filter(e -> e.getSeverity().getLevel() <= Severity.ERROR.getLevel())
311 .collect(Collectors.toList());
312
313 mpErrorsPostSplit.addAll(severeErrors);
314 }
315
316 // Commands were already executed. Either undo them on error or add them to the UndoRedoHandler
317 if (!mpErrorsPostSplit.isEmpty()) {
318 if (!allowInvalidSplit) {
319 showWarningNotification(tr("Multipolygon split would create invalid multipolygons! Split was not performed."));
320 for (TestError testError : mpErrorsPostSplit) {
321 showWarningNotification(testError.getMessage());
322 }
323 for (int i = commands.size()-1; i >= 0; --i) {
324 commands.get(i).undoCommand();
325 }
326
327 return new Pair<>(new ArrayList<>(), new ArrayList<>());
328 } else {
329 showWarningNotification(tr("Multipolygon split created invalid multipolygons! Please review and fix these errors."));
330 for (TestError testError : mpErrorsPostSplit) {
331 showWarningNotification(testError.getMessage());
332 }
333 }
334 }
335 if (commands.size() > 1)
336 UndoRedoHandler.getInstance().add(new SplitObjectCommand(commands), false);
337 else
338 UndoRedoHandler.getInstance().add(commands.iterator().next(), false);
339
340 mpRelation.getDataSet().setSelected(mpRelations);
341 return splitResult;
342
343 } catch (IllegalArgumentException e) {
344 Logging.trace(e);
345 // Changes were already undone in splitMultipolygonAtWay
346 showWarningNotification(e.getMessage());
347 return new Pair<>(new ArrayList<>(), new ArrayList<>());
348 }
349 }
350
351 /**
352 * Splits a multipolygon into two separate multipolygons along a way.
353 * Inner polygon rings are automatically assigned to the appropriate multipolygon relation based on their location.
354 * @param mpRelation the multipolygon relation to split.
355 * @param splitWay the way along which the multipolygon should be split.
356 * Must start and end on the outer ways and must not intersect with or connect to any of the multipolygon inners.
357 * @param allowInvalidSplit allow multipolygon splits that result in invalid multipolygons.
358 * @return the new multipolygon relations after splitting + the commands required for the split
359 * (already executed, but not yet added to the {@link UndoRedoHandler}).
360 * @throws IllegalArgumentException if the multipolygon has errors and/or the splitWay is unsuitable for
361 * splitting the multipolygon (e.g. because it crosses inners and {@code allowInvalidSplit == false}).
362 */
363 public static Pair<List<Relation>, List<Command>> splitMultipolygonAtWay(Relation mpRelation,
364 Way splitWay,
365 boolean allowInvalidSplit) throws IllegalArgumentException {
366 CheckParameterUtil.ensureParameterNotNull(mpRelation, "mpRelation");
367 CheckParameterUtil.ensureParameterNotNull(splitWay, "splitWay");
368 CheckParameterUtil.ensureThat(mpRelation.isMultipolygon(), "mpRelation.isMultipolygon");
369
370 List<Command> commands = new ArrayList<>();
371 List<Relation> mpRelations = new ArrayList<>();
372 mpRelations.add(mpRelation);
373
374 Multipolygon mp = new Multipolygon(mpRelation);
375
376 if (mp.isIncomplete()) {
377 throw new IllegalArgumentException(tr("Cannot split incomplete multipolygon"));
378 }
379
380 /* Splitting multipolygons with multiple outer rings technically works, but assignment of parts is
381 * unpredictable and could lead to unwanted fragmentation. */
382 if (mp.getOuterPolygons().size() > 1) {
383 throw new IllegalArgumentException(tr("Cannot split multipolygon with multiple outer polygons"));
384 }
385
386 if (mpRelation.getMembers().stream().filter(RelationMember::isWay).anyMatch(w -> w.getWay() == splitWay)) {
387 throw new IllegalArgumentException(tr("Split ways must not be a member of the multipolygon"));
388 }
389
390 if (!mp.getOpenEnds().isEmpty()) {
391 throw new IllegalArgumentException(tr("Multipolygon has unclosed rings"));
392 }
393
394 List<Way> outerWaysUnsplit = mp.getOuterWays();
395
396 Node firstNode = splitWay.firstNode();
397 Node lastNode = splitWay.lastNode();
398
399 Set<Way> firstNodeWays = firstNode.getParentWays().stream().filter(outerWaysUnsplit::contains).collect(Collectors.toSet());
400 Set<Way> lastNodeWays = lastNode.getParentWays().stream().filter(outerWaysUnsplit::contains).collect(Collectors.toSet());
401
402 if (firstNodeWays.isEmpty() || lastNodeWays.isEmpty()) {
403 throw new IllegalArgumentException(tr("The split way does not start/end on the multipolygon outer ways"));
404 }
405
406 commands.addAll(splitMultipolygonWaysAtNodes(mpRelation, Arrays.asList(firstNode, lastNode)));
407
408 // Need to refresh the multipolygon members after splitting
409 mp = new Multipolygon(mpRelation);
410
411 List<JoinedPolygon> joinedOuter = null;
412 try {
413 joinedOuter = MultipolygonBuilder.joinWays(mp.getOuterWays());
414 } catch (JoinedPolygonCreationException e) {
415 for (int i = commands.size()-1; i >= 0; --i) {
416 commands.get(i).undoCommand();
417 }
418 throw new IllegalArgumentException(tr("Error in multipolygon: {0}", e.getMessage()), e);
419 }
420
421 // Find outer subring that should be moved to the new multipolygon
422 for (JoinedPolygon outerRing : joinedOuter) {
423 int firstIndex = -1;
424 int lastIndex = -1;
425
426 if (outerRing.nodes.containsAll(Arrays.asList(firstNode, lastNode))) {
427 for (int i = 0; i < outerRing.ways.size() && (firstIndex == -1 || lastIndex == -1); i++) {
428 Way w = outerRing.ways.get(i);
429 boolean reversed = outerRing.reversed.get(i);
430
431 Node cStartNode = reversed ? w.lastNode() : w.firstNode();
432 Node cEndNode = reversed ? w.firstNode() : w.lastNode();
433
434 if (cStartNode == firstNode) {
435 firstIndex = i;
436 }
437 if (cEndNode == lastNode) {
438 lastIndex = i;
439 }
440 }
441 }
442
443 if (firstIndex != -1 && lastIndex != -1) {
444 int startIt = -1;
445 int endIt = -1;
446
447 if (firstIndex <= lastIndex) {
448 startIt = firstIndex;
449 endIt = lastIndex + 1;
450 } else {
451 startIt = lastIndex + 1;
452 endIt = firstIndex;
453 }
454
455 /* Found outer subring for new multipolygon, now create new mp relation and move
456 * members + close old and new mp with split way */
457 List<Way> newOuterRingWays = outerRing.ways.subList(startIt, endIt);
458
459 RelationMember splitWayMember = new RelationMember("outer", splitWay);
460
461 List<RelationMember> mpMembers = mpRelation.getMembers();
462 List<RelationMember> newMpMembers = mpMembers.stream()
463 .filter(m -> m.isWay() && newOuterRingWays.contains(m.getWay()))
464 .collect(Collectors.toList());
465
466 mpMembers.removeAll(newMpMembers);
467 mpMembers.add(splitWayMember);
468
469 Relation newMpRelation = new Relation(mpRelation, true, false);
470 newMpMembers.add(splitWayMember);
471 newMpRelation.setMembers(newMpMembers);
472
473 Multipolygon newMp = new Multipolygon(newMpRelation);
474
475 // Check if inners need to be moved to new multipolygon
476 for (PolyData inner : mp.getInnerPolygons()) {
477 for (PolyData newOuter : newMp.getOuterPolygons()) {
478 Intersection intersection = newOuter.contains(inner.get());
479 switch (intersection) {
480 case INSIDE:
481 Collection<Long> innerWayIds = inner.getWayIds();
482 List<RelationMember> innerWayMembers = mpMembers.stream()
483 .filter(m -> m.isWay() && innerWayIds.contains(m.getWay().getUniqueId()))
484 .collect(Collectors.toList());
485
486 mpMembers.removeAll(innerWayMembers);
487 for (RelationMember innerWayMember : innerWayMembers) {
488 newMpRelation.addMember(innerWayMember);
489 }
490
491 break;
492 case CROSSING:
493 if (!allowInvalidSplit) {
494 for (int i = commands.size()-1; i >= 0; --i) {
495 commands.get(i).undoCommand();
496 }
497
498 throw new IllegalArgumentException(tr("Split way crosses inner polygon"));
499 }
500
501 break;
502 default:
503 break;
504 }
505 }
506 }
507
508 List<Command> mpCreationCommands = new ArrayList<>();
509 mpCreationCommands.add(new ChangeMembersCommand(mpRelation, mpMembers));
510 mpCreationCommands.add(new AddCommand(mpRelation.getDataSet(), newMpRelation));
511 mpCreationCommands.forEach(Command::executeCommand);
512 commands.addAll(mpCreationCommands);
513
514 mpRelations.add(newMpRelation);
515 }
516 }
517
518 return new Pair<>(mpRelations, commands);
519 }
520
521 /**
522 * Splits all ways of the multipolygon at the given nodes
523 * @param mpRelation the multipolygon relation whose ways should be split
524 * @param splitNodes the nodes at which the multipolygon ways should be split
525 * @return a list of (already executed) commands for the split ways
526 */
527 public static List<SplitWayCommand> splitMultipolygonWaysAtNodes(Relation mpRelation, Collection<Node> splitNodes) {
528 CheckParameterUtil.ensureParameterNotNull(mpRelation, "mpRelation");
529 CheckParameterUtil.ensureParameterNotNull(splitNodes, "splitNodes");
530
531 Set<Way> mpWays = mpRelation.getMembers().stream()
532 .filter(RelationMember::isWay)
533 .map(RelationMember::getWay)
534 .collect(Collectors.toSet());
535
536 List<SplitWayCommand> splitCmds = new ArrayList<>();
537 for (Way way : mpWays) {
538 List<Node> containedNodes = way.getNodes().stream()
539 .filter(n -> splitNodes.contains(n) &&
540 (way.isClosed() || (n != way.firstNode() && n != way.lastNode())))
541 .collect(Collectors.toList());
542
543 if (!containedNodes.isEmpty()) {
544 List<List<Node>> wayChunks = SplitWayCommand.buildSplitChunks(way, containedNodes);
545
546 if (wayChunks != null) {
547 SplitWayCommand result = SplitWayCommand.splitWay(
548 way, wayChunks, Collections.emptyList());
549 result.executeCommand(); // relation members are overwritten/broken if there are multiple unapplied splits
550 splitCmds.add(result);
551 }
552 }
553 }
554
555 return splitCmds;
556 }
557
558 /**
559 * Checks if the selection consists of something we can work with.
560 * Checks only if the number and type of items selected looks good;
561 * does not check whether the selected items are really a valid
562 * input for splitting (this would be too expensive to be carried
563 * out from the selectionChanged listener).
564 * @param selection the selection
565 * @return true if the selection is usable
566 */
567 private static boolean checkSelection(Collection<? extends OsmPrimitive> selection) {
568 int node = 0;
569 int ways = 0;
570 int multipolygons = 0;
571 for (OsmPrimitive p : selection) {
572 if (p instanceof Way) {
573 ways++;
574 } else if (p instanceof Node) {
575 node++;
576 } else if (p.isMultipolygon()) {
577 multipolygons++;
578 } else
579 return false;
580 }
581 return (node == 2 || ways == 1 || ways == 2) || //only 2 nodes selected. one split-way selected. split-way + way to split.
582 (multipolygons == 1 && ways == 1);
583 }
584
585 @Override
586 protected void updateEnabledState() {
587 updateEnabledStateOnCurrentSelection();
588 }
589
590 @Override
591 protected void updateEnabledState(Collection<? extends OsmPrimitive> selection) {
592 if (selection == null) {
593 setEnabled(false);
594 return;
595 }
596 setEnabled(checkSelection(selection));
597 }
598
599 private static void showWarningNotification(String msg) {
600 new Notification(msg)
601 .setIcon(JOptionPane.WARNING_MESSAGE).show();
602 }
603
604 private static class SplitObjectCommand extends SequenceCommand {
605 SplitObjectCommand(Collection<Command> sequenz) {
606 super(tr("Split Object"), sequenz, true);
607 setSequenceComplete(true);
608 }
609 }
610}
Note: See TracBrowser for help on using the repository browser.