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

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

fix #8039, fix #10456: final fixes for the read-only/locked layers:

  • rename "read-only" to "locked" (in XML and Java classes/interfaces)
  • add a new download policy (true/never) to allow private layers forbidding only to download data, but allowing everything else

This leads to:

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