001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.actions; 003 004import static org.openstreetmap.josm.gui.help.HelpUtil.ht; 005import static org.openstreetmap.josm.tools.I18n.tr; 006import static org.openstreetmap.josm.tools.I18n.trn; 007 008import java.awt.Component; 009import java.awt.GridBagLayout; 010import java.awt.event.ActionEvent; 011import java.awt.event.KeyEvent; 012import java.util.ArrayList; 013import java.util.Arrays; 014import java.util.Collection; 015import java.util.Collections; 016import java.util.HashSet; 017import java.util.Iterator; 018import java.util.LinkedList; 019import java.util.List; 020import java.util.Set; 021import java.util.concurrent.atomic.AtomicInteger; 022 023import javax.swing.DefaultListCellRenderer; 024import javax.swing.JLabel; 025import javax.swing.JList; 026import javax.swing.JOptionPane; 027import javax.swing.JPanel; 028import javax.swing.ListSelectionModel; 029 030import org.openstreetmap.josm.Main; 031import org.openstreetmap.josm.command.AddCommand; 032import org.openstreetmap.josm.command.ChangeCommand; 033import org.openstreetmap.josm.command.Command; 034import org.openstreetmap.josm.command.SequenceCommand; 035import org.openstreetmap.josm.data.osm.Node; 036import org.openstreetmap.josm.data.osm.OsmPrimitive; 037import org.openstreetmap.josm.data.osm.PrimitiveId; 038import org.openstreetmap.josm.data.osm.Relation; 039import org.openstreetmap.josm.data.osm.RelationMember; 040import org.openstreetmap.josm.data.osm.Way; 041import org.openstreetmap.josm.data.osm.WaySegment; 042import org.openstreetmap.josm.gui.DefaultNameFormatter; 043import org.openstreetmap.josm.gui.ExtendedDialog; 044import org.openstreetmap.josm.gui.Notification; 045import org.openstreetmap.josm.gui.layer.OsmDataLayer; 046import org.openstreetmap.josm.tools.CheckParameterUtil; 047import org.openstreetmap.josm.tools.GBC; 048import org.openstreetmap.josm.tools.Shortcut; 049 050/** 051 * Splits a way into multiple ways (all identical except for their node list). 052 * 053 * Ways are just split at the selected nodes. The nodes remain in their 054 * original order. Selected nodes at the end of a way are ignored. 055 */ 056public class SplitWayAction extends JosmAction { 057 058 /** 059 * Represents the result of a {@link SplitWayAction} 060 * @see SplitWayAction#splitWay 061 * @see SplitWayAction#split 062 */ 063 public static class SplitWayResult { 064 private final Command command; 065 private final List<? extends PrimitiveId> newSelection; 066 private final Way originalWay; 067 private final List<Way> newWays; 068 069 /** 070 * @param command The command to be performed to split the way (which is saved for later retrieval with {@link #getCommand}) 071 * @param newSelection The new list of selected primitives ids (which is saved for later retrieval with {@link #getNewSelection}) 072 * @param originalWay The original way being split (which is saved for later retrieval with {@link #getOriginalWay}) 073 * @param newWays The resulting new ways (which is saved for later retrieval with {@link #getOriginalWay}) 074 */ 075 public SplitWayResult(Command command, List<? extends PrimitiveId> newSelection, Way originalWay, List<Way> newWays) { 076 this.command = command; 077 this.newSelection = newSelection; 078 this.originalWay = originalWay; 079 this.newWays = newWays; 080 } 081 082 /** 083 * Replies the command to be performed to split the way 084 * @return The command to be performed to split the way 085 */ 086 public Command getCommand() { 087 return command; 088 } 089 090 /** 091 * Replies the new list of selected primitives ids 092 * @return The new list of selected primitives ids 093 */ 094 public List<? extends PrimitiveId> getNewSelection() { 095 return newSelection; 096 } 097 098 /** 099 * Replies the original way being split 100 * @return The original way being split 101 */ 102 public Way getOriginalWay() { 103 return originalWay; 104 } 105 106 /** 107 * Replies the resulting new ways 108 * @return The resulting new ways 109 */ 110 public List<Way> getNewWays() { 111 return newWays; 112 } 113 } 114 115 /** 116 * Create a new SplitWayAction. 117 */ 118 public SplitWayAction() { 119 super(tr("Split Way"), "splitway", tr("Split a way at the selected node."), 120 Shortcut.registerShortcut("tools:splitway", tr("Tool: {0}", tr("Split Way")), KeyEvent.VK_P, Shortcut.DIRECT), true); 121 putValue("help", ht("/Action/SplitWay")); 122 } 123 124 /** 125 * Called when the action is executed. 126 * 127 * This method performs an expensive check whether the selection clearly defines one 128 * of the split actions outlined above, and if yes, calls the splitWay method. 129 */ 130 @Override 131 public void actionPerformed(ActionEvent e) { 132 133 if (SegmentToKeepSelectionDialog.DISPLAY_COUNT.get() > 0) { 134 new Notification(tr("Cannot split since another split operation is already in progress")) 135 .setIcon(JOptionPane.WARNING_MESSAGE).show(); 136 return; 137 } 138 139 Collection<OsmPrimitive> selection = getLayerManager().getEditDataSet().getSelected(); 140 141 List<Node> selectedNodes = OsmPrimitive.getFilteredList(selection, Node.class); 142 List<Way> selectedWays = OsmPrimitive.getFilteredList(selection, Way.class); 143 List<Way> applicableWays = getApplicableWays(selectedWays, selectedNodes); 144 145 if (applicableWays == null) { 146 new Notification( 147 tr("The current selection cannot be used for splitting - no node is selected.")) 148 .setIcon(JOptionPane.WARNING_MESSAGE) 149 .show(); 150 return; 151 } else if (applicableWays.isEmpty()) { 152 new Notification( 153 tr("The selected nodes do not share the same way.")) 154 .setIcon(JOptionPane.WARNING_MESSAGE) 155 .show(); 156 return; 157 } 158 159 // If several ways have been found, remove ways that doesn't have selected 160 // node in the middle 161 if (applicableWays.size() > 1) { 162 for (Iterator<Way> it = applicableWays.iterator(); it.hasNext();) { 163 Way w = it.next(); 164 for (Node n : selectedNodes) { 165 if (!w.isInnerNode(n)) { 166 it.remove(); 167 break; 168 } 169 } 170 } 171 } 172 173 if (applicableWays.isEmpty()) { 174 new Notification( 175 trn("The selected node is not in the middle of any way.", 176 "The selected nodes are not in the middle of any way.", 177 selectedNodes.size())) 178 .setIcon(JOptionPane.WARNING_MESSAGE) 179 .show(); 180 return; 181 } else if (applicableWays.size() > 1) { 182 new Notification( 183 trn("There is more than one way using the node you selected. Please select the way also.", 184 "There is more than one way using the nodes you selected. Please select the way also.", 185 selectedNodes.size())) 186 .setIcon(JOptionPane.WARNING_MESSAGE) 187 .show(); 188 return; 189 } 190 191 // Finally, applicableWays contains only one perfect way 192 final Way selectedWay = applicableWays.get(0); 193 final List<List<Node>> wayChunks = buildSplitChunks(selectedWay, selectedNodes); 194 if (wayChunks != null) { 195 List<Relation> selectedRelations = OsmPrimitive.getFilteredList(selection, Relation.class); 196 final List<OsmPrimitive> sel = new ArrayList<>(selectedWays.size() + selectedRelations.size()); 197 sel.addAll(selectedWays); 198 sel.addAll(selectedRelations); 199 200 final List<Way> newWays = createNewWaysFromChunks(selectedWay, wayChunks); 201 final Way wayToKeep = Strategy.keepLongestChunk().determineWayToKeep(newWays); 202 203 if (ExpertToggleAction.isExpert() && !selectedWay.isNew()) { 204 final ExtendedDialog dialog = new SegmentToKeepSelectionDialog(selectedWay, newWays, wayToKeep, sel); 205 dialog.toggleEnable("way.split.segment-selection-dialog"); 206 if (!dialog.toggleCheckState()) { 207 dialog.setModal(false); 208 dialog.showDialog(); 209 return; // splitting is performed in SegmentToKeepSelectionDialog.buttonAction() 210 } 211 } 212 if (wayToKeep != null) { 213 final SplitWayResult result = doSplitWay(getLayerManager().getEditLayer(), selectedWay, wayToKeep, newWays, sel); 214 Main.main.undoRedo.add(result.getCommand()); 215 getLayerManager().getEditDataSet().setSelected(result.getNewSelection()); 216 } 217 } 218 } 219 220 /** 221 * A dialog to query which way segment should reuse the history of the way to split. 222 */ 223 static class SegmentToKeepSelectionDialog extends ExtendedDialog { 224 static final AtomicInteger DISPLAY_COUNT = new AtomicInteger(); 225 final transient Way selectedWay; 226 final transient List<Way> newWays; 227 final JList<Way> list; 228 final transient List<OsmPrimitive> selection; 229 final transient Way wayToKeep; 230 231 SegmentToKeepSelectionDialog(Way selectedWay, List<Way> newWays, Way wayToKeep, List<OsmPrimitive> selection) { 232 super(Main.parent, tr("Which way segment should reuse the history of {0}?", selectedWay.getId()), 233 new String[]{tr("Ok"), tr("Cancel")}, true); 234 235 this.selectedWay = selectedWay; 236 this.newWays = newWays; 237 this.selection = selection; 238 this.wayToKeep = wayToKeep; 239 this.list = new JList<>(newWays.toArray(new Way[newWays.size()])); 240 configureList(); 241 242 setButtonIcons(new String[]{"ok", "cancel"}); 243 final JPanel pane = new JPanel(new GridBagLayout()); 244 pane.add(new JLabel(getTitle()), GBC.eol().fill(GBC.HORIZONTAL)); 245 pane.add(list, GBC.eop().fill(GBC.HORIZONTAL)); 246 setContent(pane); 247 setDefaultCloseOperation(HIDE_ON_CLOSE); 248 } 249 250 private void configureList() { 251 list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); 252 list.addListSelectionListener(e -> { 253 final Way selected = list.getSelectedValue(); 254 if (Main.isDisplayingMapView() && selected != null && selected.getNodesCount() > 1) { 255 final Collection<WaySegment> segments = new ArrayList<>(selected.getNodesCount() - 1); 256 final Iterator<Node> it = selected.getNodes().iterator(); 257 Node previousNode = it.next(); 258 while (it.hasNext()) { 259 final Node node = it.next(); 260 segments.add(WaySegment.forNodePair(selectedWay, previousNode, node)); 261 previousNode = node; 262 } 263 setHighlightedWaySegments(segments); 264 } 265 }); 266 list.setCellRenderer(new SegmentListCellRenderer()); 267 } 268 269 protected void setHighlightedWaySegments(Collection<WaySegment> segments) { 270 selectedWay.getDataSet().setHighlightedWaySegments(segments); 271 Main.map.mapView.repaint(); 272 } 273 274 @Override 275 public void setVisible(boolean visible) { 276 super.setVisible(visible); 277 if (visible) { 278 DISPLAY_COUNT.incrementAndGet(); 279 list.setSelectedValue(wayToKeep, true); 280 } else { 281 setHighlightedWaySegments(Collections.<WaySegment>emptyList()); 282 DISPLAY_COUNT.decrementAndGet(); 283 } 284 } 285 286 @Override 287 protected void buttonAction(int buttonIndex, ActionEvent evt) { 288 super.buttonAction(buttonIndex, evt); 289 toggleSaveState(); // necessary since #showDialog() does not handle it due to the non-modal dialog 290 if (getValue() == 1) { 291 SplitWayResult result = doSplitWay(Main.getLayerManager().getEditLayer(), 292 selectedWay, list.getSelectedValue(), newWays, selection); 293 Main.main.undoRedo.add(result.getCommand()); 294 Main.getLayerManager().getEditDataSet().setSelected(result.getNewSelection()); 295 } 296 } 297 } 298 299 static class SegmentListCellRenderer extends DefaultListCellRenderer { 300 @Override 301 public Component getListCellRendererComponent(JList<?> list, Object value, int index, boolean isSelected, boolean cellHasFocus) { 302 final Component c = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); 303 final String name = DefaultNameFormatter.getInstance().format((Way) value); 304 // get rid of id from DefaultNameFormatter.decorateNameWithId() 305 final String nameWithoutId = name 306 .replace(tr(" [id: {0}]", ((Way) value).getId()), "") 307 .replace(tr(" [id: {0}]", ((Way) value).getUniqueId()), ""); 308 ((JLabel) c).setText(tr("Segment {0}: {1}", index + 1, nameWithoutId)); 309 return c; 310 } 311 } 312 313 /** 314 * Determines which way chunk should reuse the old id and its history 315 * 316 * @since 8954 317 * @since 10599 (functional interface) 318 */ 319 @FunctionalInterface 320 public interface Strategy { 321 322 /** 323 * Determines which way chunk should reuse the old id and its history. 324 * 325 * @param wayChunks the way chunks 326 * @return the way to keep 327 */ 328 Way determineWayToKeep(Iterable<Way> wayChunks); 329 330 /** 331 * Returns a strategy which selects the way chunk with the highest node count to keep. 332 * @return strategy which selects the way chunk with the highest node count to keep 333 */ 334 static Strategy keepLongestChunk() { 335 return wayChunks -> { 336 Way wayToKeep = null; 337 for (Way i : wayChunks) { 338 if (wayToKeep == null || i.getNodesCount() > wayToKeep.getNodesCount()) { 339 wayToKeep = i; 340 } 341 } 342 return wayToKeep; 343 }; 344 } 345 346 /** 347 * Returns a strategy which selects the first way chunk. 348 * @return strategy which selects the first way chunk 349 */ 350 static Strategy keepFirstChunk() { 351 return wayChunks -> wayChunks.iterator().next(); 352 } 353 } 354 355 /** 356 * Determine which ways to split. 357 * @param selectedWays List of user selected ways. 358 * @param selectedNodes List of user selected nodes. 359 * @return List of ways to split 360 */ 361 static List<Way> getApplicableWays(List<Way> selectedWays, List<Node> selectedNodes) { 362 if (selectedNodes.isEmpty()) 363 return null; 364 365 // Special case - one of the selected ways touches (not cross) way that we want to split 366 if (selectedNodes.size() == 1) { 367 Node n = selectedNodes.get(0); 368 List<Way> referredWays = 369 OsmPrimitive.getFilteredList(n.getReferrers(), Way.class); 370 Way inTheMiddle = null; 371 for (Way w: referredWays) { 372 // Need to look at all nodes see #11184 for a case where node n is 373 // firstNode, lastNode and also in the middle 374 if (selectedWays.contains(w) && w.isInnerNode(n)) { 375 if (inTheMiddle == null) { 376 inTheMiddle = w; 377 } else { 378 inTheMiddle = null; 379 break; 380 } 381 } 382 } 383 if (inTheMiddle != null) 384 return Collections.singletonList(inTheMiddle); 385 } 386 387 // List of ways shared by all nodes 388 return UnJoinNodeWayAction.getApplicableWays(selectedWays, selectedNodes); 389 } 390 391 /** 392 * Splits the nodes of {@code wayToSplit} into a list of node sequences 393 * which are separated at the nodes in {@code splitPoints}. 394 * 395 * This method displays warning messages if {@code wayToSplit} and/or 396 * {@code splitPoints} aren't consistent. 397 * 398 * Returns null, if building the split chunks fails. 399 * 400 * @param wayToSplit the way to split. Must not be null. 401 * @param splitPoints the nodes where the way is split. Must not be null. 402 * @return the list of chunks 403 */ 404 public static List<List<Node>> buildSplitChunks(Way wayToSplit, List<Node> splitPoints) { 405 CheckParameterUtil.ensureParameterNotNull(wayToSplit, "wayToSplit"); 406 CheckParameterUtil.ensureParameterNotNull(splitPoints, "splitPoints"); 407 408 Set<Node> nodeSet = new HashSet<>(splitPoints); 409 List<List<Node>> wayChunks = new LinkedList<>(); 410 List<Node> currentWayChunk = new ArrayList<>(); 411 wayChunks.add(currentWayChunk); 412 413 Iterator<Node> it = wayToSplit.getNodes().iterator(); 414 while (it.hasNext()) { 415 Node currentNode = it.next(); 416 boolean atEndOfWay = currentWayChunk.isEmpty() || !it.hasNext(); 417 currentWayChunk.add(currentNode); 418 if (nodeSet.contains(currentNode) && !atEndOfWay) { 419 currentWayChunk = new ArrayList<>(); 420 currentWayChunk.add(currentNode); 421 wayChunks.add(currentWayChunk); 422 } 423 } 424 425 // Handle circular ways specially. 426 // If you split at a circular way at two nodes, you just want to split 427 // it at these points, not also at the former endpoint. 428 // So if the last node is the same first node, join the last and the 429 // first way chunk. 430 List<Node> lastWayChunk = wayChunks.get(wayChunks.size() - 1); 431 if (wayChunks.size() >= 2 432 && wayChunks.get(0).get(0) == lastWayChunk.get(lastWayChunk.size() - 1) 433 && !nodeSet.contains(wayChunks.get(0).get(0))) { 434 if (wayChunks.size() == 2) { 435 new Notification( 436 tr("You must select two or more nodes to split a circular way.")) 437 .setIcon(JOptionPane.WARNING_MESSAGE) 438 .show(); 439 return null; 440 } 441 lastWayChunk.remove(lastWayChunk.size() - 1); 442 lastWayChunk.addAll(wayChunks.get(0)); 443 wayChunks.remove(wayChunks.size() - 1); 444 wayChunks.set(0, lastWayChunk); 445 } 446 447 if (wayChunks.size() < 2) { 448 if (wayChunks.get(0).get(0) == wayChunks.get(0).get(wayChunks.get(0).size() - 1)) { 449 new Notification( 450 tr("You must select two or more nodes to split a circular way.")) 451 .setIcon(JOptionPane.WARNING_MESSAGE) 452 .show(); 453 } else { 454 new Notification( 455 tr("The way cannot be split at the selected nodes. (Hint: Select nodes in the middle of the way.)")) 456 .setIcon(JOptionPane.WARNING_MESSAGE) 457 .show(); 458 } 459 return null; 460 } 461 return wayChunks; 462 } 463 464 /** 465 * Creates new way objects for the way chunks and transfers the keys from the original way. 466 * @param way the original way whose keys are transferred 467 * @param wayChunks the way chunks 468 * @return the new way objects 469 */ 470 protected static List<Way> createNewWaysFromChunks(Way way, Iterable<List<Node>> wayChunks) { 471 final List<Way> newWays = new ArrayList<>(); 472 for (List<Node> wayChunk : wayChunks) { 473 Way wayToAdd = new Way(); 474 wayToAdd.setKeys(way.getKeys()); 475 wayToAdd.setNodes(wayChunk); 476 newWays.add(wayToAdd); 477 } 478 return newWays; 479 } 480 481 /** 482 * Splits the way {@code way} into chunks of {@code wayChunks} and replies 483 * the result of this process in an instance of {@link SplitWayResult}. 484 * 485 * Note that changes are not applied to the data yet. You have to 486 * submit the command in {@link SplitWayResult#getCommand()} first, 487 * i.e. {@code Main.main.undoredo.add(result.getCommand())}. 488 * 489 * @param layer the layer which the way belongs to. Must not be null. 490 * @param way the way to split. Must not be null. 491 * @param wayChunks the list of way chunks into the way is split. Must not be null. 492 * @param selection The list of currently selected primitives 493 * @return the result from the split operation 494 */ 495 public static SplitWayResult splitWay(OsmDataLayer layer, Way way, List<List<Node>> wayChunks, 496 Collection<? extends OsmPrimitive> selection) { 497 return splitWay(layer, way, wayChunks, selection, Strategy.keepLongestChunk()); 498 } 499 500 /** 501 * Splits the way {@code way} into chunks of {@code wayChunks} and replies 502 * the result of this process in an instance of {@link SplitWayResult}. 503 * The {@link org.openstreetmap.josm.actions.SplitWayAction.Strategy} is used to determine which 504 * way chunk should reuse the old id and its history. 505 * 506 * Note that changes are not applied to the data yet. You have to 507 * submit the command in {@link SplitWayResult#getCommand()} first, 508 * i.e. {@code Main.main.undoredo.add(result.getCommand())}. 509 * 510 * @param layer the layer which the way belongs to. Must not be null. 511 * @param way the way to split. Must not be null. 512 * @param wayChunks the list of way chunks into the way is split. Must not be null. 513 * @param selection The list of currently selected primitives 514 * @param splitStrategy The strategy used to determine which way chunk should reuse the old id and its history 515 * @return the result from the split operation 516 * @since 8954 517 */ 518 public static SplitWayResult splitWay(OsmDataLayer layer, Way way, List<List<Node>> wayChunks, 519 Collection<? extends OsmPrimitive> selection, Strategy splitStrategy) { 520 // build a list of commands, and also a new selection list 521 final List<OsmPrimitive> newSelection = new ArrayList<>(selection.size() + wayChunks.size()); 522 newSelection.addAll(selection); 523 524 // Create all potential new ways 525 final List<Way> newWays = createNewWaysFromChunks(way, wayChunks); 526 527 // Determine which part reuses the existing way 528 final Way wayToKeep = splitStrategy.determineWayToKeep(newWays); 529 530 return wayToKeep != null ? doSplitWay(layer, way, wayToKeep, newWays, newSelection) : null; 531 } 532 533 static SplitWayResult doSplitWay(OsmDataLayer layer, Way way, Way wayToKeep, List<Way> newWays, 534 List<OsmPrimitive> newSelection) { 535 536 Collection<Command> commandList = new ArrayList<>(newWays.size()); 537 Collection<String> nowarnroles = Main.pref.getCollection("way.split.roles.nowarn", 538 Arrays.asList("outer", "inner", "forward", "backward", "north", "south", "east", "west")); 539 540 // Change the original way 541 final Way changedWay = new Way(way); 542 changedWay.setNodes(wayToKeep.getNodes()); 543 commandList.add(layer != null ? new ChangeCommand(layer, way, changedWay) : new ChangeCommand(way.getDataSet(), way, changedWay)); 544 if (!newSelection.contains(way)) { 545 newSelection.add(way); 546 } 547 final int indexOfWayToKeep = newWays.indexOf(wayToKeep); 548 newWays.remove(wayToKeep); 549 550 newSelection.addAll(newWays); 551 for (Way wayToAdd : newWays) { 552 commandList.add(layer != null ? new AddCommand(layer, wayToAdd) : new AddCommand(way.getDataSet(), wayToAdd)); 553 } 554 555 boolean warnmerole = false; 556 boolean warnme = false; 557 // now copy all relations to new way also 558 559 for (Relation r : OsmPrimitive.getFilteredList(way.getReferrers(), Relation.class)) { 560 if (!r.isUsable()) { 561 continue; 562 } 563 Relation c = null; 564 String type = r.get("type"); 565 if (type == null) { 566 type = ""; 567 } 568 569 int ic = 0; 570 int ir = 0; 571 List<RelationMember> relationMembers = r.getMembers(); 572 for (RelationMember rm: relationMembers) { 573 if (rm.isWay() && rm.getMember() == way) { 574 boolean insert = true; 575 if ("restriction".equals(type) || "destination_sign".equals(type)) { 576 /* this code assumes the restriction is correct. No real error checking done */ 577 String role = rm.getRole(); 578 if ("from".equals(role) || "to".equals(role)) { 579 OsmPrimitive via = findVia(r, type); 580 List<Node> nodes = new ArrayList<>(); 581 if (via != null) { 582 if (via instanceof Node) { 583 nodes.add((Node) via); 584 } else if (via instanceof Way) { 585 nodes.add(((Way) via).lastNode()); 586 nodes.add(((Way) via).firstNode()); 587 } 588 } 589 Way res = null; 590 for (Node n : nodes) { 591 if (changedWay.isFirstLastNode(n)) { 592 res = way; 593 } 594 } 595 if (res == null) { 596 for (Way wayToAdd : newWays) { 597 for (Node n : nodes) { 598 if (wayToAdd.isFirstLastNode(n)) { 599 res = wayToAdd; 600 } 601 } 602 } 603 if (res != null) { 604 if (c == null) { 605 c = new Relation(r); 606 } 607 c.addMember(new RelationMember(role, res)); 608 c.removeMembersFor(way); 609 insert = false; 610 } 611 } else { 612 insert = false; 613 } 614 } else if (!"via".equals(role)) { 615 warnme = true; 616 } 617 } else if (!("route".equals(type)) && !("multipolygon".equals(type))) { 618 warnme = true; 619 } 620 if (c == null) { 621 c = new Relation(r); 622 } 623 624 if (insert) { 625 if (rm.hasRole() && !nowarnroles.contains(rm.getRole())) { 626 warnmerole = true; 627 } 628 629 Boolean backwards = null; 630 int k = 1; 631 while (ir - k >= 0 || ir + k < relationMembers.size()) { 632 if ((ir - k >= 0) && relationMembers.get(ir - k).isWay()) { 633 Way w = relationMembers.get(ir - k).getWay(); 634 if ((w.lastNode() == way.firstNode()) || w.firstNode() == way.firstNode()) { 635 backwards = Boolean.FALSE; 636 } else if ((w.firstNode() == way.lastNode()) || w.lastNode() == way.lastNode()) { 637 backwards = Boolean.TRUE; 638 } 639 break; 640 } 641 if ((ir + k < relationMembers.size()) && relationMembers.get(ir + k).isWay()) { 642 Way w = relationMembers.get(ir + k).getWay(); 643 if ((w.lastNode() == way.firstNode()) || w.firstNode() == way.firstNode()) { 644 backwards = Boolean.TRUE; 645 } else if ((w.firstNode() == way.lastNode()) || w.lastNode() == way.lastNode()) { 646 backwards = Boolean.FALSE; 647 } 648 break; 649 } 650 k++; 651 } 652 653 int j = ic; 654 final List<Way> waysToAddBefore = newWays.subList(0, indexOfWayToKeep); 655 for (Way wayToAdd : waysToAddBefore) { 656 RelationMember em = new RelationMember(rm.getRole(), wayToAdd); 657 j++; 658 if (Boolean.TRUE.equals(backwards)) { 659 c.addMember(ic + 1, em); 660 } else { 661 c.addMember(j - 1, em); 662 } 663 } 664 final List<Way> waysToAddAfter = newWays.subList(indexOfWayToKeep, newWays.size()); 665 for (Way wayToAdd : waysToAddAfter) { 666 RelationMember em = new RelationMember(rm.getRole(), wayToAdd); 667 j++; 668 if (Boolean.TRUE.equals(backwards)) { 669 c.addMember(ic, em); 670 } else { 671 c.addMember(j, em); 672 } 673 } 674 ic = j; 675 } 676 } 677 ic++; 678 ir++; 679 } 680 681 if (c != null) { 682 commandList.add(layer != null ? new ChangeCommand(layer, r, c) : new ChangeCommand(r.getDataSet(), r, c)); 683 } 684 } 685 if (warnmerole) { 686 new Notification( 687 tr("A role based relation membership was copied to all new ways.<br>You should verify this and correct it when necessary.")) 688 .setIcon(JOptionPane.WARNING_MESSAGE) 689 .show(); 690 } else if (warnme) { 691 new Notification( 692 tr("A relation membership was copied to all new ways.<br>You should verify this and correct it when necessary.")) 693 .setIcon(JOptionPane.WARNING_MESSAGE) 694 .show(); 695 } 696 697 return new SplitWayResult( 698 new SequenceCommand( 699 /* for correct i18n of plural forms - see #9110 */ 700 trn("Split way {0} into {1} part", "Split way {0} into {1} parts", newWays.size() + 1, 701 way.getDisplayName(DefaultNameFormatter.getInstance()), newWays.size() + 1), 702 commandList 703 ), 704 newSelection, 705 way, 706 newWays 707 ); 708 } 709 710 static OsmPrimitive findVia(Relation r, String type) { 711 for (RelationMember rmv : r.getMembers()) { 712 if (("restriction".equals(type) && "via".equals(rmv.getRole())) 713 || ("destination_sign".equals(type) && rmv.hasRole("sign", "intersection"))) { 714 return rmv.getMember(); 715 } 716 } 717 return null; 718 } 719 720 /** 721 * Splits the way {@code way} at the nodes in {@code atNodes} and replies 722 * the result of this process in an instance of {@link SplitWayResult}. 723 * 724 * Note that changes are not applied to the data yet. You have to 725 * submit the command in {@link SplitWayResult#getCommand()} first, 726 * i.e. {@code Main.main.undoredo.add(result.getCommand())}. 727 * 728 * Replies null if the way couldn't be split at the given nodes. 729 * 730 * @param layer the layer which the way belongs to. Must not be null. 731 * @param way the way to split. Must not be null. 732 * @param atNodes the list of nodes where the way is split. Must not be null. 733 * @param selection The list of currently selected primitives 734 * @return the result from the split operation 735 */ 736 public static SplitWayResult split(OsmDataLayer layer, Way way, List<Node> atNodes, Collection<? extends OsmPrimitive> selection) { 737 List<List<Node>> chunks = buildSplitChunks(way, atNodes); 738 return chunks != null ? splitWay(layer, way, chunks, selection) : null; 739 } 740 741 @Override 742 protected void updateEnabledState() { 743 updateEnabledStateOnCurrentSelection(); 744 } 745 746 @Override 747 protected void updateEnabledState(Collection<? extends OsmPrimitive> selection) { 748 if (selection == null) { 749 setEnabled(false); 750 return; 751 } 752 for (OsmPrimitive primitive: selection) { 753 if (primitive instanceof Node) { 754 setEnabled(true); // Selection still can be wrong, but let SplitWayAction process and tell user what's wrong 755 return; 756 } 757 } 758 setEnabled(false); 759 } 760}