1 | // License: GPL. For details, see LICENSE file.
|
---|
2 | package org.openstreetmap.josm.plugins.fastdraw;
|
---|
3 |
|
---|
4 | import static org.openstreetmap.josm.tools.I18n.tr;
|
---|
5 |
|
---|
6 | import java.awt.Color;
|
---|
7 | import java.awt.Cursor;
|
---|
8 | import java.awt.Graphics2D;
|
---|
9 | import java.awt.Point;
|
---|
10 | import java.awt.event.KeyEvent;
|
---|
11 | import java.awt.event.MouseEvent;
|
---|
12 | import java.util.ArrayList;
|
---|
13 | import java.util.Collection;
|
---|
14 | import java.util.Collections;
|
---|
15 | import java.util.Iterator;
|
---|
16 | import java.util.LinkedList;
|
---|
17 | import java.util.List;
|
---|
18 | import java.util.Map;
|
---|
19 |
|
---|
20 | import javax.swing.JOptionPane;
|
---|
21 |
|
---|
22 | import org.openstreetmap.josm.actions.mapmode.MapMode;
|
---|
23 | import org.openstreetmap.josm.command.AddCommand;
|
---|
24 | import org.openstreetmap.josm.command.ChangeCommand;
|
---|
25 | import org.openstreetmap.josm.command.Command;
|
---|
26 | import org.openstreetmap.josm.command.DeleteCommand;
|
---|
27 | import org.openstreetmap.josm.command.SequenceCommand;
|
---|
28 | import org.openstreetmap.josm.data.Bounds;
|
---|
29 | import org.openstreetmap.josm.data.UndoRedoHandler;
|
---|
30 | import org.openstreetmap.josm.data.coor.LatLon;
|
---|
31 | import org.openstreetmap.josm.data.osm.DataSet;
|
---|
32 | import org.openstreetmap.josm.data.osm.Node;
|
---|
33 | import org.openstreetmap.josm.data.osm.OsmPrimitive;
|
---|
34 | import org.openstreetmap.josm.data.osm.Way;
|
---|
35 | import org.openstreetmap.josm.gui.ConditionalOptionPaneUtil;
|
---|
36 | import org.openstreetmap.josm.gui.MainApplication;
|
---|
37 | import org.openstreetmap.josm.gui.MapFrame;
|
---|
38 | import org.openstreetmap.josm.gui.MapView;
|
---|
39 | import org.openstreetmap.josm.gui.datatransfer.OsmTransferHandler;
|
---|
40 | import org.openstreetmap.josm.gui.layer.Layer;
|
---|
41 | import org.openstreetmap.josm.gui.layer.MapViewPaintable;
|
---|
42 | import org.openstreetmap.josm.gui.util.KeyPressReleaseListener;
|
---|
43 | import org.openstreetmap.josm.gui.util.ModifierExListener;
|
---|
44 | import org.openstreetmap.josm.tools.ImageProvider;
|
---|
45 | import org.openstreetmap.josm.tools.Shortcut;
|
---|
46 | import org.openstreetmap.josm.tools.TextTagParser;
|
---|
47 |
|
---|
48 | /**
|
---|
49 | * Fast Drawing map mode.
|
---|
50 | */
|
---|
51 | class FastDrawingMode extends MapMode implements MapViewPaintable, KeyPressReleaseListener, ModifierExListener {
|
---|
52 | private static final long serialVersionUID = -3395918050593965929L;
|
---|
53 | // CHECKSTYLE.OFF: LineLength
|
---|
54 | private static final String SIMPLIFY_MODE_MESSAGE =
|
---|
55 | tr("Q=Options, Enter=save, Ctrl-Enter=save with tags, Up/Down=tune");
|
---|
56 | private static final String DRAWING_MODE_MESSAGE =
|
---|
57 | tr("Click or Click&drag to continue, Ctrl-Click to add fixed node, Shift-Click to delete, Enter to simplify or save, Ctrl-Shift-Click to start new line");
|
---|
58 | // CHECKSTYLE.ON: LineLength
|
---|
59 |
|
---|
60 | private FDSettings settings;
|
---|
61 |
|
---|
62 | private final DrawnPolyLine line;
|
---|
63 | private MapView mv;
|
---|
64 | private String statusText;
|
---|
65 | private boolean drawing;
|
---|
66 | private double eps;
|
---|
67 | private final Cursor cursorDraw;
|
---|
68 | private final Cursor cursorCtrl;
|
---|
69 | private final Cursor cursorShift;
|
---|
70 | private final Cursor cursorReady;
|
---|
71 | private final Cursor cursorDrawing;
|
---|
72 | private boolean nearSomeNode;
|
---|
73 | private LatLon highlightedFragmentStart;
|
---|
74 | private int nearestPointIndex;
|
---|
75 | private int dragNode = -1;
|
---|
76 | private List<Node> oldNodes;
|
---|
77 |
|
---|
78 | private boolean lineWasSaved;
|
---|
79 | private boolean deltaChanged;
|
---|
80 | private Way oldWay;
|
---|
81 |
|
---|
82 | FastDrawingMode() {
|
---|
83 | super(tr("FastDrawing"), "turbopen", tr("Fast drawing mode"),
|
---|
84 | Shortcut.registerShortcut("mapmode:fastdraw", tr("Mode: {0}", tr("Fast drawing mode")), KeyEvent.VK_F, Shortcut.SHIFT),
|
---|
85 | Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
|
---|
86 | line = new DrawnPolyLine();
|
---|
87 | cursorDraw = ImageProvider.getCursor("crosshair", null);
|
---|
88 | cursorCtrl = ImageProvider.getCursor("crosshair", "fixed");
|
---|
89 | cursorShift = ImageProvider.getCursor("crosshair", "new");
|
---|
90 | cursorReady = ImageProvider.getCursor("crosshair", "ready");
|
---|
91 | cursorDrawing = ImageProvider.getCursor("crosshair", "mode");
|
---|
92 | }
|
---|
93 |
|
---|
94 | // <editor-fold defaultstate="collapsed" desc="Event listeners">
|
---|
95 |
|
---|
96 | @Override
|
---|
97 | public void enterMode() {
|
---|
98 | this.updateEnabledState();
|
---|
99 | if (!isEnabled())
|
---|
100 | return;
|
---|
101 | super.enterMode();
|
---|
102 | lineWasSaved = false;
|
---|
103 | settings = new FDSettings();
|
---|
104 | settings.loadPrefs();
|
---|
105 | settings.savePrefs();
|
---|
106 |
|
---|
107 | MapFrame map = MainApplication.getMap();
|
---|
108 | eps = settings.startingEps;
|
---|
109 | mv = map.mapView;
|
---|
110 | line.setMv(mv);
|
---|
111 |
|
---|
112 | if (getLayerManager().getEditDataSet() == null) return;
|
---|
113 |
|
---|
114 | map.mapView.addMouseListener(this);
|
---|
115 | map.mapView.addMouseMotionListener(this);
|
---|
116 | map.mapView.addTemporaryLayer(this);
|
---|
117 |
|
---|
118 | map.keyDetector.addKeyListener(this);
|
---|
119 | map.keyDetector.addModifierExListener(this);
|
---|
120 | }
|
---|
121 |
|
---|
122 | @Override
|
---|
123 | public void exitMode() {
|
---|
124 | super.exitMode();
|
---|
125 | if (line.wasSimplified() && !lineWasSaved) saveAsWay(false);
|
---|
126 | MapFrame map = MainApplication.getMap();
|
---|
127 |
|
---|
128 | map.mapView.removeMouseListener(this);
|
---|
129 | map.mapView.removeMouseMotionListener(this);
|
---|
130 |
|
---|
131 | map.mapView.removeTemporaryLayer(this);
|
---|
132 |
|
---|
133 | map.keyDetector.removeKeyListener(this);
|
---|
134 | map.keyDetector.removeModifierExListener(this);
|
---|
135 |
|
---|
136 | settings.savePrefs();
|
---|
137 | map.mapView.setCursor(cursorDraw);
|
---|
138 | repaint();
|
---|
139 | }
|
---|
140 |
|
---|
141 | @Override
|
---|
142 | public boolean layerIsSupported(Layer l) {
|
---|
143 | return isEditableDataLayer(l);
|
---|
144 | }
|
---|
145 |
|
---|
146 | @Override
|
---|
147 | protected void updateEnabledState() {
|
---|
148 | setEnabled(getLayerManager().getEditLayer() != null);
|
---|
149 | }
|
---|
150 |
|
---|
151 | private final ArrayList<Point> fixedPoints = new ArrayList<>(3000); // temporary storage for paint
|
---|
152 |
|
---|
153 | ////////// Event listener methods
|
---|
154 | @Override
|
---|
155 | public void paint(Graphics2D g, MapView mv, Bounds bbox) {
|
---|
156 | LinkedList<LatLon> pts = line.getPoints();
|
---|
157 | if (pts.isEmpty()) return;
|
---|
158 |
|
---|
159 | if (line.wasSimplified()) {
|
---|
160 | // we are drawing simplified version, that exists
|
---|
161 | g.setStroke(settings.simplifiedStroke);
|
---|
162 | } else {
|
---|
163 | g.setStroke(settings.normalStroke);
|
---|
164 | }
|
---|
165 |
|
---|
166 | int bigDotSize = settings.bigDotSize;
|
---|
167 |
|
---|
168 | Point p1, p2;
|
---|
169 | LatLon pp1, pp2;
|
---|
170 | p1 = line.getPoint(pts.get(0));
|
---|
171 | g.setColor(settings.COLOR_FIXED.get());
|
---|
172 | g.fillOval(p1.x - bigDotSize/2, p1.y - bigDotSize/2, bigDotSize, bigDotSize);
|
---|
173 | Color lineColor, initLineColor;
|
---|
174 | initLineColor = line.wasSimplified() ? settings.COLOR_SIMPLIFIED.get() : settings.COLOR_NORMAL.get();
|
---|
175 | lineColor = initLineColor;
|
---|
176 | int rp, dp;
|
---|
177 | dp = line.wasSimplified() ? settings.bigDotSize : settings.dotSize; rp = dp/2;
|
---|
178 | if (pts.size() > 1) {
|
---|
179 | Iterator<LatLon> it1, it2;
|
---|
180 | it1 = pts.listIterator(0);
|
---|
181 | it2 = pts.listIterator(1);
|
---|
182 | fixedPoints.clear();
|
---|
183 | for (int i = 0; i < pts.size() - 1; i++) {
|
---|
184 | pp1 = it1.next();
|
---|
185 | p1 = line.getPoint(pp1);
|
---|
186 | pp2 = it2.next();
|
---|
187 | p2 = line.getPoint(pp2);
|
---|
188 | if (shift && highlightedFragmentStart == pp1 && nearestPointIndex < 0) {
|
---|
189 | lineColor = settings.COLOR_SELECTEDFRAGMENT.get();
|
---|
190 | }
|
---|
191 | if (!shift && line.isLastPoint(i)) {
|
---|
192 | lineColor = settings.COLOR_EDITEDFRAGMENT.get();
|
---|
193 | }
|
---|
194 | g.setColor(lineColor);
|
---|
195 | g.drawLine(p1.x, p1.y, p2.x, p2.y);
|
---|
196 | if (line.isFixed(pp2)) {
|
---|
197 | lineColor = initLineColor;
|
---|
198 | fixedPoints.add(p2);
|
---|
199 | } else {
|
---|
200 | g.fillRect(p2.x - rp, p2.y - rp, dp, dp);
|
---|
201 | }
|
---|
202 | if (!drawing && !line.wasSimplified() && nearestPointIndex == i+1) {
|
---|
203 | if (shift) {
|
---|
204 | // highlight node to delete
|
---|
205 | g.setStroke(settings.deleteStroke);
|
---|
206 | g.setColor(settings.COLOR_DELETE.get());
|
---|
207 | g.drawLine(p2.x - 5, p2.y - 5, p2.x + 5, p2.y + 5);
|
---|
208 | g.drawLine(p2.x - 5, p2.y + 5, p2.x + 5, p2.y - 5);
|
---|
209 | g.setStroke(settings.normalStroke);
|
---|
210 | } else if (ctrl) {
|
---|
211 | // highlight node to toggle fixation
|
---|
212 | g.setStroke(settings.deleteStroke);
|
---|
213 | g.setColor(line.isFixed(pp2) ? settings.COLOR_NORMAL.get() : settings.COLOR_FIXED.get());
|
---|
214 | g.fillOval(p2.x - bigDotSize/2-2, p2.y - bigDotSize/2-2, bigDotSize+4, bigDotSize+4);
|
---|
215 | g.setStroke(settings.normalStroke);
|
---|
216 | }
|
---|
217 | }
|
---|
218 | }
|
---|
219 | g.setColor(settings.COLOR_FIXED.get());
|
---|
220 | for (Point p: fixedPoints) {
|
---|
221 | g.fillOval(p.x - bigDotSize/2, p.y - bigDotSize/2, bigDotSize, bigDotSize);
|
---|
222 | }
|
---|
223 | }
|
---|
224 | if (settings.drawLastSegment && !drawing && dragNode < 0 && !shift &&
|
---|
225 | nearestPointIndex <= 0 && !line.wasSimplified()) {
|
---|
226 | // draw line to current point
|
---|
227 | g.setColor(lineColor);
|
---|
228 | Point lp = line.getLastPoint();
|
---|
229 | Point mp = mv.getMousePosition();
|
---|
230 | if (lp != null && mp != null) g.drawLine(lp.x, lp.y, mp.x, mp.y);
|
---|
231 | }
|
---|
232 | if (deltaChanged) {
|
---|
233 | g.setColor(lineColor);
|
---|
234 | Point lp = line.getLastPoint();
|
---|
235 | int r = (int) settings.minPixelsBetweenPoints;
|
---|
236 | if (lp != null) g.drawOval(lp.x-r, lp.y-r, 2*r, 2*r);
|
---|
237 | }
|
---|
238 | }
|
---|
239 |
|
---|
240 | @Override
|
---|
241 | public void mousePressed(MouseEvent e) {
|
---|
242 | if (!isEnabled()) return;
|
---|
243 | if (e.getButton() != MouseEvent.BUTTON1) return;
|
---|
244 | updateKeyModifiers(e);
|
---|
245 |
|
---|
246 | requestFocusInMapView();
|
---|
247 |
|
---|
248 | int idx = line.findClosestPoint(e.getPoint(), settings.maxDist);
|
---|
249 | if (idx == 0 && !line.isClosed()) {
|
---|
250 | line.closeLine();
|
---|
251 | // the way should become closed
|
---|
252 | drawing = false;
|
---|
253 | dragNode = 0;
|
---|
254 | updateCursor();
|
---|
255 | return;
|
---|
256 | }
|
---|
257 | autoCloseIfNeeded();
|
---|
258 |
|
---|
259 | if (ctrl && shift) {
|
---|
260 | newDrawing();
|
---|
261 | repaint();
|
---|
262 | return;
|
---|
263 | }
|
---|
264 | if (!ctrl && shift) {
|
---|
265 | if (idx >= 0) {
|
---|
266 | line.deleteNode(idx);
|
---|
267 | nearestPointIndex = -1;
|
---|
268 | } else
|
---|
269 | line.tryToDeleteSegment(e.getPoint());
|
---|
270 | return;
|
---|
271 | }
|
---|
272 | if (idx >= 0) {
|
---|
273 | if (ctrl) {
|
---|
274 | // toggle fixed point
|
---|
275 | line.toggleFixed(idx);
|
---|
276 | }
|
---|
277 | // node dragging
|
---|
278 | dragNode = idx;
|
---|
279 | return;
|
---|
280 | }
|
---|
281 | startDrawing(e.getPoint(), settings.fixedClick);
|
---|
282 | }
|
---|
283 |
|
---|
284 | private void startDrawing(Point point, boolean fixFlag) {
|
---|
285 | drawing = true;
|
---|
286 | if (line.wasSimplified()) {
|
---|
287 | // new line started after simplification
|
---|
288 | // we need to save old line
|
---|
289 | saveAsWay(false);
|
---|
290 | newDrawing();
|
---|
291 | }
|
---|
292 |
|
---|
293 | LatLon p = mv.getLatLon(point.x, point.y);
|
---|
294 | if (settings.snapNodes) { // find existing node near point and use it
|
---|
295 | Node nd1 = getNearestNode(point, settings.maxDist);
|
---|
296 | if (nd1 != null) {
|
---|
297 | // found node, make it fixed point of the line
|
---|
298 | p = nd1.getCoor();
|
---|
299 | line.fixPoint(p);
|
---|
300 | }
|
---|
301 | }
|
---|
302 |
|
---|
303 | line.addLast(p);
|
---|
304 | if (ctrl || fixFlag) line.fixPoint(p);
|
---|
305 |
|
---|
306 | setStatusLine(tr("Please move the mouse to draw new way"));
|
---|
307 | repaint();
|
---|
308 | }
|
---|
309 |
|
---|
310 | @Override
|
---|
311 | public void mouseReleased(MouseEvent e) {
|
---|
312 | if (e.getButton() != MouseEvent.BUTTON1) return;
|
---|
313 | stopDrawing();
|
---|
314 | }
|
---|
315 |
|
---|
316 | private void stopDrawing() {
|
---|
317 | if (!isEnabled()) return;
|
---|
318 | dragNode = -1;
|
---|
319 | drawing = false;
|
---|
320 | highlightedFragmentStart = null;
|
---|
321 | if (!line.isClosed()) setStatusLine(DRAWING_MODE_MESSAGE);
|
---|
322 | updateCursor();
|
---|
323 | repaint();
|
---|
324 | }
|
---|
325 |
|
---|
326 | @Override
|
---|
327 | public void mouseDragged(MouseEvent e) {
|
---|
328 | mouseMoved(e);
|
---|
329 | }
|
---|
330 |
|
---|
331 | @Override
|
---|
332 | public void mouseMoved(MouseEvent e) {
|
---|
333 | if (!isEnabled()) return;
|
---|
334 | updateKeyModifiers(e);
|
---|
335 | deltaChanged = false;
|
---|
336 | Node nd1 = getNearestNode(e.getPoint(), settings.maxDist);
|
---|
337 | boolean nearSomeNode2 = nd1 != null;
|
---|
338 | boolean needRepaint = false;
|
---|
339 | if (nearSomeNode != nearSomeNode2) {
|
---|
340 | nearSomeNode = nearSomeNode2;
|
---|
341 | updateCursor();
|
---|
342 | needRepaint = true;
|
---|
343 | }
|
---|
344 |
|
---|
345 | int nearestIdx2 = line.findClosestPoint(e.getPoint(), settings.maxDist);
|
---|
346 | if (nearestPointIndex != nearestIdx2) {
|
---|
347 | nearestPointIndex = nearestIdx2;
|
---|
348 | updateCursor();
|
---|
349 | needRepaint = true;
|
---|
350 | }
|
---|
351 | if (settings.drawLastSegment) {
|
---|
352 | needRepaint = true;
|
---|
353 | }
|
---|
354 |
|
---|
355 | if (!drawing) {
|
---|
356 | if (dragNode >= 0 && dragNode < line.getPointCount()) {
|
---|
357 | line.moveNode(dragNode, getLatLon(e));
|
---|
358 | repaint();
|
---|
359 | return;
|
---|
360 | }
|
---|
361 |
|
---|
362 | if (shift && nearestPointIndex == -1) {
|
---|
363 | // find line fragment to highlight
|
---|
364 | LatLon h2 = line.findBigSegment(e.getPoint());
|
---|
365 | if (highlightedFragmentStart != h2) {
|
---|
366 | highlightedFragmentStart = h2;
|
---|
367 | needRepaint = true;
|
---|
368 | }
|
---|
369 | }
|
---|
370 |
|
---|
371 | if (needRepaint) {
|
---|
372 | repaint();
|
---|
373 | }
|
---|
374 | return;
|
---|
375 | }
|
---|
376 | if (line.isClosed()) setStatusLine(SIMPLIFY_MODE_MESSAGE);
|
---|
377 |
|
---|
378 | // do not draw points close to existing points - we do not want self-intersections
|
---|
379 | if (nearestPointIndex >= 0) {
|
---|
380 | return;
|
---|
381 | }
|
---|
382 |
|
---|
383 | Point lastP = line.getLastPoint(); // last point of line fragment being edited
|
---|
384 |
|
---|
385 | // free mouse-drawing
|
---|
386 | if (nearSomeNode) {
|
---|
387 | if (nd1 != null && settings.snapNodes && lastP != null
|
---|
388 | && Math.hypot((double) e.getX() - lastP.x, (double) e.getY() - lastP.y) > 1e-2) {
|
---|
389 | line.addFixed(nd1.getCoor()); // snap to node coords
|
---|
390 | repaint();
|
---|
391 | return;
|
---|
392 | }
|
---|
393 | } else {
|
---|
394 | if (lastP != null && Math.hypot((double) e.getX() - lastP.x, (double) e.getY() - lastP.y) > settings.minPixelsBetweenPoints) {
|
---|
395 | line.addLast(getLatLon(e)); // add new point
|
---|
396 | repaint();
|
---|
397 | return;
|
---|
398 | }
|
---|
399 | }
|
---|
400 | autoCloseIfNeeded();
|
---|
401 | }
|
---|
402 |
|
---|
403 | @Override
|
---|
404 | public void doKeyPressed(KeyEvent e) {
|
---|
405 | if (getShortcut().isEvent(e)) { // repeated press
|
---|
406 | tryToLoadWay();
|
---|
407 | return;
|
---|
408 | }
|
---|
409 | switch(e.getKeyCode()) {
|
---|
410 | case KeyEvent.VK_BACK_SPACE:
|
---|
411 | if (line.wasSimplified()) {
|
---|
412 | // return to line editing
|
---|
413 | line.clearSimplifiedVersion();
|
---|
414 | repaint();
|
---|
415 | eps = settings.startingEps;
|
---|
416 | }
|
---|
417 | back();
|
---|
418 | break;
|
---|
419 | case KeyEvent.VK_ENTER:
|
---|
420 | e.consume();
|
---|
421 | // first Enter = simplify, second = save the way
|
---|
422 | if (!line.wasSimplified()) {
|
---|
423 | switch(settings.simplifyMode) {
|
---|
424 | case 0: //case 1:
|
---|
425 | eps = line.autoSimplify(settings.startingEps, settings.epsilonMult,
|
---|
426 | settings.pkmBlockSize, settings.maxPointsPerKm);
|
---|
427 | break;
|
---|
428 | case 1: //case 2: case 3:
|
---|
429 | line.simplify(eps);
|
---|
430 | break;
|
---|
431 | }
|
---|
432 | if (settings.simplifyMode == 2) {
|
---|
433 | // autosave
|
---|
434 | saveAsWay(true);
|
---|
435 | } else {
|
---|
436 | repaint();
|
---|
437 | showSimplifyHint();
|
---|
438 | }
|
---|
439 | } else {
|
---|
440 | saveAsWay(true);
|
---|
441 | }
|
---|
442 | break;
|
---|
443 | case KeyEvent.VK_DOWN:
|
---|
444 | if (ctrl || shift || alt) return;
|
---|
445 | // more details
|
---|
446 | e.consume();
|
---|
447 | if (line.wasSimplified()) changeEpsilon(settings.epsilonMult);
|
---|
448 | else changeDelta(1/1.1);
|
---|
449 | break;
|
---|
450 | case KeyEvent.VK_UP:
|
---|
451 | if (ctrl || shift || alt) return;
|
---|
452 | // fewer details
|
---|
453 | e.consume();
|
---|
454 | if (line.wasSimplified()) changeEpsilon(1/settings.epsilonMult);
|
---|
455 | else changeDelta(1.1);
|
---|
456 | break;
|
---|
457 | case KeyEvent.VK_ESCAPE:
|
---|
458 | e.consume();
|
---|
459 | Point lastPoint = line.getLastPoint();
|
---|
460 | if (!line.isClosed()) line.moveToTheEnd();
|
---|
461 | if (lastPoint == null || lastPoint.equals(line.getLastPoint())) {
|
---|
462 | if (line.getPoints().size() > 5) {
|
---|
463 | boolean answer = ConditionalOptionPaneUtil.showConfirmationDialog(
|
---|
464 | "delete_drawn_line", MainApplication.getMainFrame(),
|
---|
465 | tr("Are you sure you do not want to save the line containing {0} points?",
|
---|
466 | line.getPoints().size()), tr("Delete confirmation"),
|
---|
467 | JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, JOptionPane.YES_OPTION);
|
---|
468 | if (!answer) break;
|
---|
469 | }
|
---|
470 | newDrawing(); // stop drawing
|
---|
471 | MainApplication.getMap().selectSelectTool(false);
|
---|
472 | }
|
---|
473 | break;
|
---|
474 |
|
---|
475 | case KeyEvent.VK_I:
|
---|
476 | JOptionPane.showMessageDialog(MainApplication.getMainFrame(),
|
---|
477 | tr("{0} m - length of the line\n{1} nodes\n{2} points per km (maximum)\n{3} points per km (average)",
|
---|
478 | line.getLength(), line.getPoints().size(), line.getNodesPerKm(settings.pkmBlockSize),
|
---|
479 | line.getNodesPerKm(1_000_000)),
|
---|
480 | tr("Line information"), JOptionPane.INFORMATION_MESSAGE);
|
---|
481 | break;
|
---|
482 | case KeyEvent.VK_Q:
|
---|
483 | // fewer details
|
---|
484 | e.consume();
|
---|
485 | new FastDrawConfigDialog(settings).showDialog();
|
---|
486 | if (line.wasSimplified()) {
|
---|
487 | eps = line.autoSimplify(settings.startingEps, settings.epsilonMult, settings.pkmBlockSize, settings.maxPointsPerKm);
|
---|
488 | showSimplifyHint();
|
---|
489 | }
|
---|
490 | repaint();
|
---|
491 | break;
|
---|
492 | case KeyEvent.VK_SPACE:
|
---|
493 | e.consume();
|
---|
494 | if (!drawing) {
|
---|
495 | Point p = MainApplication.getMap().mapView.getMousePosition();
|
---|
496 | if (p != null) startDrawing(p, settings.fixedSpacebar);
|
---|
497 | }
|
---|
498 | break;
|
---|
499 | }
|
---|
500 | }
|
---|
501 |
|
---|
502 | @Override
|
---|
503 | public void doKeyReleased(KeyEvent keyEvent) {
|
---|
504 | if (keyEvent.getKeyCode() == KeyEvent.VK_SPACE) stopDrawing();
|
---|
505 | updateCursor();
|
---|
506 | }
|
---|
507 |
|
---|
508 | @Override
|
---|
509 | public void modifiersExChanged(int modifiers) {
|
---|
510 | updateKeyModifiersEx(modifiers);
|
---|
511 | updateCursor();
|
---|
512 | }
|
---|
513 |
|
---|
514 | @Override
|
---|
515 | protected void updateStatusLine() {
|
---|
516 | MainApplication.getMap().statusLine.setHelpText(statusText);
|
---|
517 | MainApplication.getMap().statusLine.repaint();
|
---|
518 | }
|
---|
519 | // </editor-fold>
|
---|
520 |
|
---|
521 | // <editor-fold defaultstate="collapsed" desc="Different action helper methods">
|
---|
522 | public void newDrawing() {
|
---|
523 | oldWay = null; oldNodes = null;
|
---|
524 | eps = settings.startingEps;
|
---|
525 | line.clear();
|
---|
526 | }
|
---|
527 |
|
---|
528 | private void saveAsWay(boolean autoExit) {
|
---|
529 | List<LatLon> pts = line.getPoints();
|
---|
530 | int n = pts.size();
|
---|
531 | if (n < 2) return; //do not save oversimplified lines
|
---|
532 | if (line.isClosed() && n == 2) return;
|
---|
533 | if (line.isClosed() && n == 3) pts.remove(2); // two-point way can not be closed
|
---|
534 |
|
---|
535 | DataSet ds = getLayerManager().getEditDataSet();
|
---|
536 | if (ds == null) return;
|
---|
537 | Collection<Command> cmds = new LinkedList<>();
|
---|
538 | int i = 0;
|
---|
539 |
|
---|
540 | Way w;
|
---|
541 | if (oldWay == null) {
|
---|
542 | w = new Way();
|
---|
543 | } else {
|
---|
544 | w = new Way(oldWay);
|
---|
545 | w.setNodes(new ArrayList<>()); // nodes will be created from scratch
|
---|
546 | }
|
---|
547 |
|
---|
548 | LatLon first = pts.get(0);
|
---|
549 | Node firstNode = null;
|
---|
550 |
|
---|
551 | for (LatLon p : pts) {
|
---|
552 | Node nd = MainApplication.getMap().mapView.getNearestNode(line.getPoint(p), OsmPrimitive::isSelectable);
|
---|
553 | // there may be a node with the same coords!
|
---|
554 |
|
---|
555 | if (nd != null && p.greatCircleDistance(nd) > 0.01) nd = null;
|
---|
556 | if (nd == null) {
|
---|
557 | if (i > 0 && p.equals(first)) {
|
---|
558 | nd = firstNode;
|
---|
559 | } else {
|
---|
560 | nd = new Node(p);
|
---|
561 | cmds.add(new AddCommand(ds, nd));
|
---|
562 | }
|
---|
563 | }
|
---|
564 | if (nd.isOutSideWorld()) {
|
---|
565 | JOptionPane.showMessageDialog(MainApplication.getMainFrame(),
|
---|
566 | tr("Cannot place node outside of the world."));
|
---|
567 | return;
|
---|
568 | }
|
---|
569 | if (i == 0) {
|
---|
570 | firstNode = nd;
|
---|
571 | }
|
---|
572 | w.addNode(nd);
|
---|
573 | i++;
|
---|
574 | }
|
---|
575 | if (ctrl) {
|
---|
576 | // paste tags - from ctrl-shift-v
|
---|
577 | new OsmTransferHandler().pasteTags(Collections.singleton(w));
|
---|
578 | }
|
---|
579 | if (!settings.autoTags.isEmpty()) {
|
---|
580 | Map<String, String> tags = TextTagParser.readTagsFromText(settings.autoTags);
|
---|
581 | for (Map.Entry<String, String> entry : tags.entrySet()) {
|
---|
582 | w.put(entry.getKey(), entry.getValue());
|
---|
583 | }
|
---|
584 | }
|
---|
585 | if (oldWay != null) {
|
---|
586 | List<Node> nodes = w.getNodes();
|
---|
587 | cmds.add(new ChangeCommand(oldWay, w));
|
---|
588 | for (Node nd: oldNodes) {
|
---|
589 | // node from old way but not in new way
|
---|
590 | if (!nodes.contains(nd)) {
|
---|
591 | List<OsmPrimitive> refs = nd.getReferrers();
|
---|
592 | // does someone need this node? if no-delete it.
|
---|
593 | if (refs.size() == 1 && !nd.isDeleted() && nd.isUsable() && !nd.isTagged()) cmds.add(new DeleteCommand(nd));
|
---|
594 | }
|
---|
595 | }
|
---|
596 | oldWay = null; // that is all with this command
|
---|
597 | } else cmds.add(new AddCommand(ds, w));
|
---|
598 | Command c = new SequenceCommand(tr("Draw the way by mouse"), cmds);
|
---|
599 | UndoRedoHandler.getInstance().add(c);
|
---|
600 | lineWasSaved = true;
|
---|
601 | newDrawing(); // stop drawing
|
---|
602 | if (autoExit) {
|
---|
603 | // Select this way and switch drawing mode off
|
---|
604 | ds.setSelected(w);
|
---|
605 | MainApplication.getMap().selectSelectTool(false);
|
---|
606 | }
|
---|
607 | }
|
---|
608 |
|
---|
609 | public void back() {
|
---|
610 | line.undo();
|
---|
611 | repaint();
|
---|
612 | }
|
---|
613 |
|
---|
614 | void changeEpsilon(double k) {
|
---|
615 | eps *= k;
|
---|
616 | line.simplify(eps);
|
---|
617 | /* I18N: Eps = Epsilon, the tolerance parameter */
|
---|
618 | showSimplifyHint();
|
---|
619 | repaint();
|
---|
620 | }
|
---|
621 |
|
---|
622 | void changeDelta(double k) {
|
---|
623 | settings.minPixelsBetweenPoints *= k;
|
---|
624 | deltaChanged = true;
|
---|
625 |
|
---|
626 | setStatusLine(tr("min distance={0} px ({1} m)", (int) settings.minPixelsBetweenPoints,
|
---|
627 | mv.getDist100Pixel()/100*settings.minPixelsBetweenPoints));
|
---|
628 | repaint();
|
---|
629 | }
|
---|
630 |
|
---|
631 | private void loadFromWay(Way w) {
|
---|
632 |
|
---|
633 | Object[] nodes = w.getNodes().toArray();
|
---|
634 | int n = nodes.length;
|
---|
635 | if (w.isClosed()) n--;
|
---|
636 | for (int i = 0; i < n; i++) {
|
---|
637 | Node nd = (Node) nodes[i];
|
---|
638 | List<OsmPrimitive> refs = nd.getReferrers();
|
---|
639 | if (refs.size() > 1 || nd.isTagged()) {
|
---|
640 | line.addFixed(nd.getCoor());
|
---|
641 | } else {
|
---|
642 | line.addLast(nd.getCoor());
|
---|
643 | }
|
---|
644 | }
|
---|
645 | if (w.isClosed()) line.closeLine();
|
---|
646 | oldNodes = w.getNodes();
|
---|
647 | oldWay = w;
|
---|
648 | }
|
---|
649 |
|
---|
650 | private void setStatusLine(String tr) {
|
---|
651 | statusText = tr;
|
---|
652 | updateStatusLine();
|
---|
653 | }
|
---|
654 |
|
---|
655 | private void showSimplifyHint() {
|
---|
656 | setStatusLine(tr("Eps={0}, {1} points, {2} p/km",
|
---|
657 | eps, line.getSimplePointsCount(), line.getNodesPerKm(settings.pkmBlockSize))+" "
|
---|
658 | + SIMPLIFY_MODE_MESSAGE);
|
---|
659 | }
|
---|
660 |
|
---|
661 | private void updateCursor() {
|
---|
662 | MapView mapView = MainApplication.getMap().mapView;
|
---|
663 | if (shift) mapView.setCursor(cursorShift); else
|
---|
664 | if (line.isClosed() || (nearestPointIndex == 0)) mapView.setCursor(cursorReady);
|
---|
665 | else if (ctrl) mapView.setCursor(cursorCtrl);
|
---|
666 | else if (nearSomeNode && settings.snapNodes) mapView.setCursor(cursorCtrl);
|
---|
667 | else if (drawing) mapView.setCursor(cursorDrawing);
|
---|
668 | else mapView.setCursor(cursorDraw);
|
---|
669 | }
|
---|
670 |
|
---|
671 | private static void repaint() {
|
---|
672 | MainApplication.getMap().mapView.repaint();
|
---|
673 | }
|
---|
674 |
|
---|
675 | private void tryToLoadWay() {
|
---|
676 | updateCursor();
|
---|
677 | Collection<Way> selectedWays = MainApplication.getLayerManager().getEditDataSet().getSelectedWays();
|
---|
678 | if (selectedWays != null // if there is a selection
|
---|
679 | && selectedWays.size() == 1 // and one way is selected
|
---|
680 | && line.getPoints().isEmpty()) /* and there is no already drawn line */ {
|
---|
681 | // we can start drawing new way starting from old one
|
---|
682 | Way w = selectedWays.iterator().next();
|
---|
683 |
|
---|
684 | if (w.isNew() || settings.allowEditExistingWays) loadFromWay(w);
|
---|
685 | }
|
---|
686 | }
|
---|
687 |
|
---|
688 | private void autoCloseIfNeeded() {
|
---|
689 | if (settings.drawClosed && line.getPointCount() > 1 && !line.isClosed()) {
|
---|
690 | line.closeLine();
|
---|
691 | }
|
---|
692 | }
|
---|
693 | // </editor-fold>
|
---|
694 |
|
---|
695 | // <editor-fold defaultstate="collapsed" desc="Helper functions">
|
---|
696 |
|
---|
697 | private Node getNearestNode(Point point, double maxDist) {
|
---|
698 | Node nd = MainApplication.getMap().mapView.getNearestNode(point, OsmPrimitive::isSelectable);
|
---|
699 | if (nd != null && line.getPoint(nd.getCoor()).distance(point) <= maxDist) return nd;
|
---|
700 | else return null;
|
---|
701 | }
|
---|
702 |
|
---|
703 | LatLon getLatLon(MouseEvent e) {
|
---|
704 | return mv.getLatLon(e.getX(), e.getY());
|
---|
705 | }
|
---|
706 | // </editor-fold>
|
---|
707 | }
|
---|