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

Last change on this file since 13405 was 13206, checked in by Don-vip, 6 years ago

enable PMD rule OptimizableToArrayCall

  • Property svn:eol-style set to native
File size: 12.9 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.Collection;
14import java.util.Collections;
15import java.util.Iterator;
16import java.util.List;
17import java.util.concurrent.atomic.AtomicInteger;
18
19import javax.swing.DefaultListCellRenderer;
20import javax.swing.JLabel;
21import javax.swing.JList;
22import javax.swing.JOptionPane;
23import javax.swing.JPanel;
24import javax.swing.ListSelectionModel;
25
26import org.openstreetmap.josm.Main;
27import org.openstreetmap.josm.command.SplitWayCommand;
28import org.openstreetmap.josm.data.osm.DefaultNameFormatter;
29import org.openstreetmap.josm.data.osm.Node;
30import org.openstreetmap.josm.data.osm.OsmPrimitive;
31import org.openstreetmap.josm.data.osm.PrimitiveId;
32import org.openstreetmap.josm.data.osm.Relation;
33import org.openstreetmap.josm.data.osm.Way;
34import org.openstreetmap.josm.data.osm.WaySegment;
35import org.openstreetmap.josm.gui.ExtendedDialog;
36import org.openstreetmap.josm.gui.MainApplication;
37import org.openstreetmap.josm.gui.MapFrame;
38import org.openstreetmap.josm.gui.Notification;
39import org.openstreetmap.josm.tools.GBC;
40import org.openstreetmap.josm.tools.Shortcut;
41
42/**
43 * Splits a way into multiple ways (all identical except for their node list).
44 *
45 * Ways are just split at the selected nodes. The nodes remain in their
46 * original order. Selected nodes at the end of a way are ignored.
47 */
48public class SplitWayAction extends JosmAction {
49
50 /**
51 * Create a new SplitWayAction.
52 */
53 public SplitWayAction() {
54 super(tr("Split Way"), "splitway", tr("Split a way at the selected node."),
55 Shortcut.registerShortcut("tools:splitway", tr("Tool: {0}", tr("Split Way")), KeyEvent.VK_P, Shortcut.DIRECT), true);
56 putValue("help", ht("/Action/SplitWay"));
57 }
58
59 /**
60 * Called when the action is executed.
61 *
62 * This method performs an expensive check whether the selection clearly defines one
63 * of the split actions outlined above, and if yes, calls the splitWay method.
64 */
65 @Override
66 public void actionPerformed(ActionEvent e) {
67
68 if (SegmentToKeepSelectionDialog.DISPLAY_COUNT.get() > 0) {
69 new Notification(tr("Cannot split since another split operation is already in progress"))
70 .setIcon(JOptionPane.WARNING_MESSAGE).show();
71 return;
72 }
73
74 Collection<OsmPrimitive> selection = getLayerManager().getEditDataSet().getSelected();
75
76 List<Node> selectedNodes = OsmPrimitive.getFilteredList(selection, Node.class);
77 List<Way> selectedWays = OsmPrimitive.getFilteredList(selection, Way.class);
78 List<Way> applicableWays = getApplicableWays(selectedWays, selectedNodes);
79
80 if (applicableWays == null) {
81 new Notification(
82 tr("The current selection cannot be used for splitting - no node is selected."))
83 .setIcon(JOptionPane.WARNING_MESSAGE)
84 .show();
85 return;
86 } else if (applicableWays.isEmpty()) {
87 new Notification(
88 tr("The selected nodes do not share the same way."))
89 .setIcon(JOptionPane.WARNING_MESSAGE)
90 .show();
91 return;
92 }
93
94 // If several ways have been found, remove ways that doesn't have selected
95 // node in the middle
96 if (applicableWays.size() > 1) {
97 for (Iterator<Way> it = applicableWays.iterator(); it.hasNext();) {
98 Way w = it.next();
99 for (Node n : selectedNodes) {
100 if (!w.isInnerNode(n)) {
101 it.remove();
102 break;
103 }
104 }
105 }
106 }
107
108 if (applicableWays.isEmpty()) {
109 new Notification(
110 trn("The selected node is not in the middle of any way.",
111 "The selected nodes are not in the middle of any way.",
112 selectedNodes.size()))
113 .setIcon(JOptionPane.WARNING_MESSAGE)
114 .show();
115 return;
116 } else if (applicableWays.size() > 1) {
117 new Notification(
118 trn("There is more than one way using the node you selected. Please select the way also.",
119 "There is more than one way using the nodes you selected. Please select the way also.",
120 selectedNodes.size()))
121 .setIcon(JOptionPane.WARNING_MESSAGE)
122 .show();
123 return;
124 }
125
126 // Finally, applicableWays contains only one perfect way
127 final Way selectedWay = applicableWays.get(0);
128 final List<List<Node>> wayChunks = SplitWayCommand.buildSplitChunks(selectedWay, selectedNodes);
129 if (wayChunks != null) {
130 List<Relation> selectedRelations = OsmPrimitive.getFilteredList(selection, Relation.class);
131 final List<OsmPrimitive> sel = new ArrayList<>(selectedWays.size() + selectedRelations.size());
132 sel.addAll(selectedWays);
133 sel.addAll(selectedRelations);
134
135 final List<Way> newWays = SplitWayCommand.createNewWaysFromChunks(selectedWay, wayChunks);
136 final Way wayToKeep = SplitWayCommand.Strategy.keepLongestChunk().determineWayToKeep(newWays);
137
138 if (ExpertToggleAction.isExpert() && !selectedWay.isNew()) {
139 final ExtendedDialog dialog = new SegmentToKeepSelectionDialog(selectedWay, newWays, wayToKeep, sel);
140 dialog.toggleEnable("way.split.segment-selection-dialog");
141 if (!dialog.toggleCheckState()) {
142 dialog.setModal(false);
143 dialog.showDialog();
144 return; // splitting is performed in SegmentToKeepSelectionDialog.buttonAction()
145 }
146 }
147 if (wayToKeep != null) {
148 doSplitWay(selectedWay, wayToKeep, newWays, sel);
149 }
150 }
151 }
152
153 /**
154 * A dialog to query which way segment should reuse the history of the way to split.
155 */
156 static class SegmentToKeepSelectionDialog extends ExtendedDialog {
157 static final AtomicInteger DISPLAY_COUNT = new AtomicInteger();
158 final transient Way selectedWay;
159 final transient List<Way> newWays;
160 final JList<Way> list;
161 final transient List<OsmPrimitive> selection;
162 final transient Way wayToKeep;
163
164 SegmentToKeepSelectionDialog(Way selectedWay, List<Way> newWays, Way wayToKeep, List<OsmPrimitive> selection) {
165 super(Main.parent, tr("Which way segment should reuse the history of {0}?", selectedWay.getId()),
166 new String[]{tr("Ok"), tr("Cancel")}, true);
167
168 this.selectedWay = selectedWay;
169 this.newWays = newWays;
170 this.selection = selection;
171 this.wayToKeep = wayToKeep;
172 this.list = new JList<>(newWays.toArray(new Way[0]));
173 configureList();
174
175 setButtonIcons("ok", "cancel");
176 final JPanel pane = new JPanel(new GridBagLayout());
177 pane.add(new JLabel(getTitle()), GBC.eol().fill(GBC.HORIZONTAL));
178 pane.add(list, GBC.eop().fill(GBC.HORIZONTAL));
179 setContent(pane);
180 setDefaultCloseOperation(HIDE_ON_CLOSE);
181 }
182
183 private void configureList() {
184 list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
185 list.addListSelectionListener(e -> {
186 final Way selected = list.getSelectedValue();
187 if (selected != null && MainApplication.isDisplayingMapView() && selected.getNodesCount() > 1) {
188 final Collection<WaySegment> segments = new ArrayList<>(selected.getNodesCount() - 1);
189 final Iterator<Node> it = selected.getNodes().iterator();
190 Node previousNode = it.next();
191 while (it.hasNext()) {
192 final Node node = it.next();
193 segments.add(WaySegment.forNodePair(selectedWay, previousNode, node));
194 previousNode = node;
195 }
196 setHighlightedWaySegments(segments);
197 }
198 });
199 list.setCellRenderer(new SegmentListCellRenderer());
200 }
201
202 protected void setHighlightedWaySegments(Collection<WaySegment> segments) {
203 selectedWay.getDataSet().setHighlightedWaySegments(segments);
204 MainApplication.getMap().mapView.repaint();
205 }
206
207 @Override
208 public void setVisible(boolean visible) {
209 super.setVisible(visible);
210 if (visible) {
211 DISPLAY_COUNT.incrementAndGet();
212 list.setSelectedValue(wayToKeep, true);
213 } else {
214 setHighlightedWaySegments(Collections.<WaySegment>emptyList());
215 DISPLAY_COUNT.decrementAndGet();
216 }
217 }
218
219 @Override
220 protected void buttonAction(int buttonIndex, ActionEvent evt) {
221 super.buttonAction(buttonIndex, evt);
222 toggleSaveState(); // necessary since #showDialog() does not handle it due to the non-modal dialog
223 if (getValue() == 1) {
224 doSplitWay(selectedWay, list.getSelectedValue(), newWays, selection);
225 }
226 }
227 }
228
229 static class SegmentListCellRenderer extends DefaultListCellRenderer {
230 @Override
231 public Component getListCellRendererComponent(JList<?> list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
232 final Component c = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
233 final String name = DefaultNameFormatter.getInstance().format((Way) value);
234 // get rid of id from DefaultNameFormatter.decorateNameWithId()
235 final String nameWithoutId = name
236 .replace(tr(" [id: {0}]", ((Way) value).getId()), "")
237 .replace(tr(" [id: {0}]", ((Way) value).getUniqueId()), "");
238 ((JLabel) c).setText(tr("Segment {0}: {1}", index + 1, nameWithoutId));
239 return c;
240 }
241 }
242
243 /**
244 * Determine which ways to split.
245 * @param selectedWays List of user selected ways.
246 * @param selectedNodes List of user selected nodes.
247 * @return List of ways to split
248 */
249 static List<Way> getApplicableWays(List<Way> selectedWays, List<Node> selectedNodes) {
250 if (selectedNodes.isEmpty())
251 return null;
252
253 // Special case - one of the selected ways touches (not cross) way that we want to split
254 if (selectedNodes.size() == 1) {
255 Node n = selectedNodes.get(0);
256 List<Way> referredWays = n.getParentWays();
257 Way inTheMiddle = null;
258 for (Way w: referredWays) {
259 // Need to look at all nodes see #11184 for a case where node n is
260 // firstNode, lastNode and also in the middle
261 if (selectedWays.contains(w) && w.isInnerNode(n)) {
262 if (inTheMiddle == null) {
263 inTheMiddle = w;
264 } else {
265 inTheMiddle = null;
266 break;
267 }
268 }
269 }
270 if (inTheMiddle != null)
271 return Collections.singletonList(inTheMiddle);
272 }
273
274 // List of ways shared by all nodes
275 return UnJoinNodeWayAction.getApplicableWays(selectedWays, selectedNodes);
276 }
277
278 static void doSplitWay(Way way, Way wayToKeep, List<Way> newWays, List<OsmPrimitive> newSelection) {
279 final MapFrame map = MainApplication.getMap();
280 final boolean isMapModeDraw = map != null && map.mapMode == map.mapModeDraw;
281 final SplitWayCommand result = SplitWayCommand.doSplitWay(way, wayToKeep, newWays, !isMapModeDraw ? newSelection : null);
282 MainApplication.undoRedo.add(result);
283 List<? extends PrimitiveId> newSel = result.getNewSelection();
284 if (newSel != null && !newSel.isEmpty()) {
285 MainApplication.getLayerManager().getEditDataSet().setSelected(newSel);
286 }
287 }
288
289 @Override
290 protected void updateEnabledState() {
291 updateEnabledStateOnCurrentSelection();
292 }
293
294 @Override
295 protected void updateEnabledState(Collection<? extends OsmPrimitive> selection) {
296 if (selection == null) {
297 setEnabled(false);
298 return;
299 }
300 for (OsmPrimitive primitive: selection) {
301 if (primitive instanceof Node) {
302 setEnabled(true); // Selection still can be wrong, but let SplitWayAction process and tell user what's wrong
303 return;
304 }
305 }
306 setEnabled(false);
307 }
308}
Note: See TracBrowser for help on using the repository browser.