source: josm/trunk/src/org/openstreetmap/josm/actions/SplitWayAction.java@ 8963

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

see #10730 - Way splitting: hide id of new ways for non English languages

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