source: josm/trunk/src/org/openstreetmap/josm/actions/mapmode/ParallelWayAction.java@ 12464

Last change on this file since 12464 was 12108, checked in by michael2402, 7 years ago

ParallelWayAction: Use a separate temporary layer, invalidate it to update view.

  • Property svn:eol-style set to native
File size: 24.0 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.actions.mapmode;
3
4import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
5import static org.openstreetmap.josm.tools.I18n.marktr;
6import static org.openstreetmap.josm.tools.I18n.tr;
7
8import java.awt.BasicStroke;
9import java.awt.Color;
10import java.awt.Cursor;
11import java.awt.Graphics2D;
12import java.awt.Point;
13import java.awt.event.KeyEvent;
14import java.awt.event.MouseEvent;
15import java.util.Collection;
16import java.util.Collections;
17import java.util.EnumMap;
18import java.util.EnumSet;
19import java.util.LinkedHashSet;
20import java.util.Map;
21import java.util.Optional;
22import java.util.Set;
23import java.util.stream.Stream;
24
25import javax.swing.JOptionPane;
26
27import org.openstreetmap.josm.Main;
28import org.openstreetmap.josm.data.Bounds;
29import org.openstreetmap.josm.data.SystemOfMeasurement;
30import org.openstreetmap.josm.data.coor.EastNorth;
31import org.openstreetmap.josm.data.osm.Node;
32import org.openstreetmap.josm.data.osm.OsmPrimitive;
33import org.openstreetmap.josm.data.osm.Way;
34import org.openstreetmap.josm.data.osm.WaySegment;
35import org.openstreetmap.josm.data.preferences.AbstractToStringProperty;
36import org.openstreetmap.josm.data.preferences.BooleanProperty;
37import org.openstreetmap.josm.data.preferences.CachingProperty;
38import org.openstreetmap.josm.data.preferences.ColorProperty;
39import org.openstreetmap.josm.data.preferences.DoubleProperty;
40import org.openstreetmap.josm.data.preferences.IntegerProperty;
41import org.openstreetmap.josm.data.preferences.StrokeProperty;
42import org.openstreetmap.josm.gui.MapFrame;
43import org.openstreetmap.josm.gui.MapView;
44import org.openstreetmap.josm.gui.Notification;
45import org.openstreetmap.josm.gui.draw.MapViewPath;
46import org.openstreetmap.josm.gui.layer.AbstractMapViewPaintable;
47import org.openstreetmap.josm.gui.layer.Layer;
48import org.openstreetmap.josm.gui.layer.OsmDataLayer;
49import org.openstreetmap.josm.gui.util.ModifierListener;
50import org.openstreetmap.josm.tools.CheckParameterUtil;
51import org.openstreetmap.josm.tools.Geometry;
52import org.openstreetmap.josm.tools.ImageProvider;
53import org.openstreetmap.josm.tools.Shortcut;
54
55//// TODO: (list below)
56/* == Functionality ==
57 *
58 * 1. Use selected nodes as split points for the selected ways.
59 *
60 * The ways containing the selected nodes will be split and only the "inner"
61 * parts will be copied
62 *
63 * 2. Enter exact offset
64 *
65 * 3. Improve snapping
66 *
67 * 4. Visual cues could be better
68 *
69 * 5. (long term) Parallelize and adjust offsets of existing ways
70 *
71 * == Code quality ==
72 *
73 * a) The mode, flags, and modifiers might be updated more than necessary.
74 *
75 * Not a performance problem, but better if they where more centralized
76 *
77 * b) Extract generic MapMode services into a super class and/or utility class
78 *
79 * c) Maybe better to simply draw our own source way highlighting?
80 *
81 * Current code doesn't not take into account that ways might been highlighted
82 * by other than us. Don't think that situation should ever happen though.
83 */
84
85/**
86 * MapMode for making parallel ways.
87 *
88 * All calculations are done in projected coordinates.
89 *
90 * @author Ole Jørgen Brønner (olejorgenb)
91 */
92public class ParallelWayAction extends MapMode implements ModifierListener {
93
94 private static final CachingProperty<BasicStroke> HELPER_LINE_STROKE = new StrokeProperty(prefKey("stroke.hepler-line"), "1").cached();
95 private static final CachingProperty<BasicStroke> REF_LINE_STROKE = new StrokeProperty(prefKey("stroke.ref-line"), "2 2 3").cached();
96
97 // @formatter:off
98 // CHECKSTYLE.OFF: SingleSpaceSeparator
99 private static final CachingProperty<Double> SNAP_THRESHOLD = new DoubleProperty(prefKey("snap-threshold-percent"), 0.70).cached();
100 private static final CachingProperty<Boolean> SNAP_DEFAULT = new BooleanProperty(prefKey("snap-default"), true).cached();
101 private static final CachingProperty<Boolean> COPY_TAGS_DEFAULT = new BooleanProperty(prefKey("copy-tags-default"), true).cached();
102 private static final CachingProperty<Integer> INITIAL_MOVE_DELAY = new IntegerProperty(prefKey("initial-move-delay"), 200).cached();
103 private static final CachingProperty<Double> SNAP_DISTANCE_METRIC = new DoubleProperty(prefKey("snap-distance-metric"), 0.5).cached();
104 private static final CachingProperty<Double> SNAP_DISTANCE_IMPERIAL = new DoubleProperty(prefKey("snap-distance-imperial"), 1).cached();
105 private static final CachingProperty<Double> SNAP_DISTANCE_CHINESE = new DoubleProperty(prefKey("snap-distance-chinese"), 1).cached();
106 private static final CachingProperty<Double> SNAP_DISTANCE_NAUTICAL = new DoubleProperty(prefKey("snap-distance-nautical"), 0.1).cached();
107 private static final CachingProperty<Color> MAIN_COLOR = new ColorProperty(marktr("make parallel helper line"), Color.RED).cached();
108
109 private static final CachingProperty<Map<Modifier, Boolean>> SNAP_MODIFIER_COMBO
110 = new KeyboardModifiersProperty(prefKey("snap-modifier-combo"), "?sC").cached();
111 private static final CachingProperty<Map<Modifier, Boolean>> COPY_TAGS_MODIFIER_COMBO
112 = new KeyboardModifiersProperty(prefKey("copy-tags-modifier-combo"), "As?").cached();
113 private static final CachingProperty<Map<Modifier, Boolean>> ADD_TO_SELECTION_MODIFIER_COMBO
114 = new KeyboardModifiersProperty(prefKey("add-to-selection-modifier-combo"), "aSc").cached();
115 private static final CachingProperty<Map<Modifier, Boolean>> TOGGLE_SELECTED_MODIFIER_COMBO
116 = new KeyboardModifiersProperty(prefKey("toggle-selection-modifier-combo"), "asC").cached();
117 private static final CachingProperty<Map<Modifier, Boolean>> SET_SELECTED_MODIFIER_COMBO
118 = new KeyboardModifiersProperty(prefKey("set-selection-modifier-combo"), "asc").cached();
119 // CHECKSTYLE.ON: SingleSpaceSeparator
120 // @formatter:on
121
122 enum Mode {
123 DRAGGING, NORMAL
124 }
125
126 //// Preferences and flags
127 // See updateModeLocalPreferences for defaults
128 private Mode mode;
129 private boolean copyTags;
130
131 private boolean snap;
132
133 private final MapView mv;
134
135 // Mouse tracking state
136 private Point mousePressedPos;
137 private boolean mouseIsDown;
138 private long mousePressedTime;
139 private boolean mouseHasBeenDragged;
140
141 private transient WaySegment referenceSegment;
142 private transient ParallelWays pWays;
143 private transient Set<Way> sourceWays;
144 private EastNorth helperLineStart;
145 private EastNorth helperLineEnd;
146
147 private final ParallelWayLayer temporaryLayer = new ParallelWayLayer();
148
149 /**
150 * Constructs a new {@code ParallelWayAction}.
151 * @param mapFrame Map frame
152 */
153 public ParallelWayAction(MapFrame mapFrame) {
154 super(tr("Parallel"), "parallel", tr("Make parallel copies of ways"),
155 Shortcut.registerShortcut("mapmode:parallel", tr("Mode: {0}",
156 tr("Parallel")), KeyEvent.VK_P, Shortcut.SHIFT),
157 ImageProvider.getCursor("normal", "parallel"));
158 putValue("help", ht("/Action/Parallel"));
159 mv = mapFrame.mapView;
160 }
161
162 @Override
163 public void enterMode() {
164 // super.enterMode() updates the status line and cursor so we need our state to be set correctly
165 setMode(Mode.NORMAL);
166 pWays = null;
167
168 super.enterMode();
169
170 mv.addMouseListener(this);
171 mv.addMouseMotionListener(this);
172 mv.addTemporaryLayer(temporaryLayer);
173
174 //// Needed to update the mouse cursor if modifiers are changed when the mouse is motionless
175 Main.map.keyDetector.addModifierListener(this);
176 sourceWays = new LinkedHashSet<>(getLayerManager().getEditDataSet().getSelectedWays());
177 for (Way w : sourceWays) {
178 w.setHighlighted(true);
179 }
180 }
181
182 @Override
183 public void exitMode() {
184 super.exitMode();
185 mv.removeMouseListener(this);
186 mv.removeMouseMotionListener(this);
187 mv.removeTemporaryLayer(temporaryLayer);
188 Main.map.statusLine.setDist(-1);
189 Main.map.statusLine.repaint();
190 Main.map.keyDetector.removeModifierListener(this);
191 removeWayHighlighting(sourceWays);
192 pWays = null;
193 sourceWays = null;
194 referenceSegment = null;
195 }
196
197 @Override
198 public String getModeHelpText() {
199 // TODO: add more detailed feedback based on modifier state.
200 // TODO: dynamic messages based on preferences. (Could be problematic translation wise)
201 switch (mode) {
202 case NORMAL:
203 // CHECKSTYLE.OFF: LineLength
204 return tr("Select ways as in Select mode. Drag selected ways or a single way to create a parallel copy (Alt toggles tag preservation)");
205 // CHECKSTYLE.ON: LineLength
206 case DRAGGING:
207 return tr("Hold Ctrl to toggle snapping");
208 }
209 return ""; // impossible ..
210 }
211
212 @Override
213 public boolean layerIsSupported(Layer layer) {
214 return layer instanceof OsmDataLayer;
215 }
216
217 @Override
218 public void modifiersChanged(int modifiers) {
219 if (Main.map == null || mv == null || !mv.isActiveLayerDrawable())
220 return;
221
222 // Should only get InputEvents due to the mask in enterMode
223 if (updateModifiersState(modifiers)) {
224 updateStatusLine();
225 updateCursor();
226 }
227 }
228
229 private boolean updateModifiersState(int modifiers) {
230 boolean oldAlt = alt, oldShift = shift, oldCtrl = ctrl;
231 updateKeyModifiers(modifiers);
232 return oldAlt != alt || oldShift != shift || oldCtrl != ctrl;
233 }
234
235 private void updateCursor() {
236 Cursor newCursor = null;
237 switch (mode) {
238 case NORMAL:
239 if (matchesCurrentModifiers(SET_SELECTED_MODIFIER_COMBO)) {
240 newCursor = ImageProvider.getCursor("normal", "parallel");
241 } else if (matchesCurrentModifiers(ADD_TO_SELECTION_MODIFIER_COMBO)) {
242 newCursor = ImageProvider.getCursor("normal", "parallel_add");
243 } else if (matchesCurrentModifiers(TOGGLE_SELECTED_MODIFIER_COMBO)) {
244 newCursor = ImageProvider.getCursor("normal", "parallel_remove");
245 }
246 break;
247 case DRAGGING:
248 newCursor = Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR);
249 break;
250 default: throw new AssertionError();
251 }
252 if (newCursor != null) {
253 mv.setNewCursor(newCursor, this);
254 }
255 }
256
257 private void setMode(Mode mode) {
258 this.mode = mode;
259 updateCursor();
260 updateStatusLine();
261 }
262
263 private boolean sanityCheck() {
264 // @formatter:off
265 boolean areWeSane =
266 mv.isActiveLayerVisible() &&
267 mv.isActiveLayerDrawable() &&
268 ((Boolean) this.getValue("active"));
269 // @formatter:on
270 assert areWeSane; // mad == bad
271 return areWeSane;
272 }
273
274 @Override
275 public void mousePressed(MouseEvent e) {
276 requestFocusInMapView();
277 updateModifiersState(e.getModifiers());
278 // Other buttons are off limit, but we still get events.
279 if (e.getButton() != MouseEvent.BUTTON1)
280 return;
281
282 if (!sanityCheck())
283 return;
284
285 updateFlagsOnlyChangeableOnPress();
286 updateFlagsChangeableAlways();
287
288 // Since the created way is left selected, we need to unselect again here
289 if (pWays != null && pWays.getWays() != null) {
290 getLayerManager().getEditDataSet().clearSelection(pWays.getWays());
291 pWays = null;
292 }
293
294 mouseIsDown = true;
295 mousePressedPos = e.getPoint();
296 mousePressedTime = System.currentTimeMillis();
297
298 }
299
300 @Override
301 public void mouseReleased(MouseEvent e) {
302 updateModifiersState(e.getModifiers());
303 // Other buttons are off limit, but we still get events.
304 if (e.getButton() != MouseEvent.BUTTON1)
305 return;
306
307 if (!mouseHasBeenDragged) {
308 // use point from press or click event? (or are these always the same)
309 Way nearestWay = mv.getNearestWay(e.getPoint(), OsmPrimitive::isSelectable);
310 if (nearestWay == null) {
311 if (matchesCurrentModifiers(SET_SELECTED_MODIFIER_COMBO)) {
312 clearSourceWays();
313 }
314 resetMouseTrackingState();
315 return;
316 }
317 boolean isSelected = nearestWay.isSelected();
318 if (matchesCurrentModifiers(ADD_TO_SELECTION_MODIFIER_COMBO)) {
319 if (!isSelected) {
320 addSourceWay(nearestWay);
321 }
322 } else if (matchesCurrentModifiers(TOGGLE_SELECTED_MODIFIER_COMBO)) {
323 if (isSelected) {
324 removeSourceWay(nearestWay);
325 } else {
326 addSourceWay(nearestWay);
327 }
328 } else if (matchesCurrentModifiers(SET_SELECTED_MODIFIER_COMBO)) {
329 clearSourceWays();
330 addSourceWay(nearestWay);
331 } // else -> invalid modifier combination
332 } else if (mode == Mode.DRAGGING) {
333 clearSourceWays();
334 }
335
336 setMode(Mode.NORMAL);
337 resetMouseTrackingState();
338 temporaryLayer.invalidate();
339 }
340
341 private static void removeWayHighlighting(Collection<Way> ways) {
342 if (ways == null)
343 return;
344 for (Way w : ways) {
345 w.setHighlighted(false);
346 }
347 }
348
349 @Override
350 public void mouseDragged(MouseEvent e) {
351 // WTF.. the event passed here doesn't have button info?
352 // Since we get this event from other buttons too, we must check that
353 // _BUTTON1_ is down.
354 if (!mouseIsDown)
355 return;
356
357 boolean modifiersChanged = updateModifiersState(e.getModifiers());
358 updateFlagsChangeableAlways();
359
360 if (modifiersChanged) {
361 // Since this could be remotely slow, do it conditionally
362 updateStatusLine();
363 updateCursor();
364 }
365
366 if ((System.currentTimeMillis() - mousePressedTime) < INITIAL_MOVE_DELAY.get())
367 return;
368 // Assuming this event only is emitted when the mouse has moved
369 // Setting this after the check above means we tolerate clicks with some movement
370 mouseHasBeenDragged = true;
371
372 if (mode == Mode.NORMAL) {
373 // Should we ensure that the copyTags modifiers are still valid?
374
375 // Important to use mouse position from the press, since the drag
376 // event can come quite late
377 if (!isModifiersValidForDragMode())
378 return;
379 if (!initParallelWays(mousePressedPos, copyTags))
380 return;
381 setMode(Mode.DRAGGING);
382 }
383
384 // Calculate distance to the reference line
385 Point p = e.getPoint();
386 EastNorth enp = mv.getEastNorth((int) p.getX(), (int) p.getY());
387 EastNorth nearestPointOnRefLine = Geometry.closestPointToLine(referenceSegment.getFirstNode().getEastNorth(),
388 referenceSegment.getSecondNode().getEastNorth(), enp);
389
390 // Note: d is the distance in _projected units_
391 double d = enp.distance(nearestPointOnRefLine);
392 double realD = mv.getProjection().eastNorth2latlon(enp).greatCircleDistance(mv.getProjection().eastNorth2latlon(nearestPointOnRefLine));
393 double snappedRealD = realD;
394
395 boolean toTheRight = Geometry.angleIsClockwise(
396 referenceSegment.getFirstNode(), referenceSegment.getSecondNode(), new Node(enp));
397
398 if (snap) {
399 // TODO: Very simple snapping
400 // - Snap steps relative to the distance?
401 double snapDistance;
402 SystemOfMeasurement som = SystemOfMeasurement.getSystemOfMeasurement();
403 if (som.equals(SystemOfMeasurement.CHINESE)) {
404 snapDistance = SNAP_DISTANCE_CHINESE.get() * SystemOfMeasurement.CHINESE.aValue;
405 } else if (som.equals(SystemOfMeasurement.IMPERIAL)) {
406 snapDistance = SNAP_DISTANCE_IMPERIAL.get() * SystemOfMeasurement.IMPERIAL.aValue;
407 } else if (som.equals(SystemOfMeasurement.NAUTICAL_MILE)) {
408 snapDistance = SNAP_DISTANCE_NAUTICAL.get() * SystemOfMeasurement.NAUTICAL_MILE.aValue;
409 } else {
410 snapDistance = SNAP_DISTANCE_METRIC.get(); // Metric system by default
411 }
412 double closestWholeUnit;
413 double modulo = realD % snapDistance;
414 if (modulo < snapDistance/2.0) {
415 closestWholeUnit = realD - modulo;
416 } else {
417 closestWholeUnit = realD + (snapDistance-modulo);
418 }
419 if (Math.abs(closestWholeUnit - realD) < (SNAP_THRESHOLD.get() * snapDistance)) {
420 snappedRealD = closestWholeUnit;
421 } else {
422 snappedRealD = closestWholeUnit + Math.signum(realD - closestWholeUnit) * snapDistance;
423 }
424 }
425 d = snappedRealD * (d/realD); // convert back to projected distance. (probably ok on small scales)
426 helperLineStart = nearestPointOnRefLine;
427 helperLineEnd = enp;
428 if (toTheRight) {
429 d = -d;
430 }
431 pWays.changeOffset(d);
432
433 Main.map.statusLine.setDist(Math.abs(snappedRealD));
434 Main.map.statusLine.repaint();
435 temporaryLayer.invalidate();
436 }
437
438 private boolean matchesCurrentModifiers(CachingProperty<Map<Modifier, Boolean>> spec) {
439 return matchesCurrentModifiers(spec.get());
440 }
441
442 private boolean matchesCurrentModifiers(Map<Modifier, Boolean> spec) {
443 EnumSet<Modifier> modifiers = EnumSet.noneOf(Modifier.class);
444 if (ctrl) {
445 modifiers.add(Modifier.CTRL);
446 }
447 if (alt) {
448 modifiers.add(Modifier.ALT);
449 }
450 if (shift) {
451 modifiers.add(Modifier.SHIFT);
452 }
453 return spec.entrySet().stream().allMatch(entry -> modifiers.contains(entry.getKey()) == entry.getValue().booleanValue());
454 }
455
456 private boolean isModifiersValidForDragMode() {
457 return (!alt && !shift && !ctrl) || matchesCurrentModifiers(SNAP_MODIFIER_COMBO)
458 || matchesCurrentModifiers(COPY_TAGS_MODIFIER_COMBO);
459 }
460
461 private void updateFlagsOnlyChangeableOnPress() {
462 copyTags = COPY_TAGS_DEFAULT.get().booleanValue() != matchesCurrentModifiers(COPY_TAGS_MODIFIER_COMBO);
463 }
464
465 private void updateFlagsChangeableAlways() {
466 snap = SNAP_DEFAULT.get().booleanValue() != matchesCurrentModifiers(SNAP_MODIFIER_COMBO);
467 }
468
469 // We keep the source ways and the selection in sync so the user can see the source way's tags
470 private void addSourceWay(Way w) {
471 assert sourceWays != null;
472 getLayerManager().getEditDataSet().addSelected(w);
473 w.setHighlighted(true);
474 sourceWays.add(w);
475 }
476
477 private void removeSourceWay(Way w) {
478 assert sourceWays != null;
479 getLayerManager().getEditDataSet().clearSelection(w);
480 w.setHighlighted(false);
481 sourceWays.remove(w);
482 }
483
484 private void clearSourceWays() {
485 assert sourceWays != null;
486 getLayerManager().getEditDataSet().clearSelection(sourceWays);
487 for (Way w : sourceWays) {
488 w.setHighlighted(false);
489 }
490 sourceWays.clear();
491 }
492
493 private void resetMouseTrackingState() {
494 mouseIsDown = false;
495 mousePressedPos = null;
496 mouseHasBeenDragged = false;
497 }
498
499 // TODO: rename
500 private boolean initParallelWays(Point p, boolean copyTags) {
501 referenceSegment = mv.getNearestWaySegment(p, OsmPrimitive::isUsable, true);
502 if (referenceSegment == null)
503 return false;
504
505 if (!sourceWays.contains(referenceSegment.way)) {
506 clearSourceWays();
507 addSourceWay(referenceSegment.way);
508 }
509
510 try {
511 int referenceWayIndex = -1;
512 int i = 0;
513 for (Way w : sourceWays) {
514 if (w == referenceSegment.way) {
515 referenceWayIndex = i;
516 break;
517 }
518 i++;
519 }
520 pWays = new ParallelWays(sourceWays, copyTags, referenceWayIndex);
521 pWays.commit();
522 getLayerManager().getEditDataSet().setSelected(pWays.getWays());
523 return true;
524 } catch (IllegalArgumentException e) {
525 Main.debug(e);
526 new Notification(tr("ParallelWayAction\n" +
527 "The ways selected must form a simple branchless path"))
528 .setIcon(JOptionPane.INFORMATION_MESSAGE)
529 .show();
530 // The error dialog prevents us from getting the mouseReleased event
531 resetMouseTrackingState();
532 pWays = null;
533 return false;
534 }
535 }
536
537 private static String prefKey(String subKey) {
538 return "edit.make-parallel-way-action." + subKey;
539 }
540
541 /**
542 * A property that holds the keyboard modifiers.
543 * @author Michael Zangl
544 * @since 10869
545 */
546 private static class KeyboardModifiersProperty extends AbstractToStringProperty<Map<Modifier, Boolean>> {
547
548 KeyboardModifiersProperty(String key, String defaultValue) {
549 super(key, createFromString(defaultValue));
550 }
551
552 KeyboardModifiersProperty(String key, Map<Modifier, Boolean> defaultValue) {
553 super(key, defaultValue);
554 }
555
556 @Override
557 protected String toString(Map<Modifier, Boolean> t) {
558 StringBuilder sb = new StringBuilder();
559 for (Modifier mod : Modifier.values()) {
560 Boolean val = t.get(mod);
561 if (val == null) {
562 sb.append('?');
563 } else if (val) {
564 sb.append(Character.toUpperCase(mod.shortChar));
565 } else {
566 sb.append(mod.shortChar);
567 }
568 }
569 return sb.toString();
570 }
571
572 @Override
573 protected Map<Modifier, Boolean> fromString(String string) {
574 return createFromString(string);
575 }
576
577 private static Map<Modifier, Boolean> createFromString(String string) {
578 Map<Modifier, Boolean> ret = new EnumMap<>(Modifier.class);
579 for (char c : string.toCharArray()) {
580 if (c == '?') {
581 continue;
582 }
583 Optional<Modifier> mod = Modifier.findWithShortCode(c);
584 if (mod.isPresent()) {
585 ret.put(mod.get(), Character.isUpperCase(c));
586 } else {
587 Main.debug("Ignoring unknown modifier {0}", c);
588 }
589 }
590 return Collections.unmodifiableMap(ret);
591 }
592 }
593
594 enum Modifier {
595 CTRL('c'),
596 ALT('a'),
597 SHIFT('s');
598
599 private final char shortChar;
600
601 Modifier(char shortChar) {
602 this.shortChar = Character.toLowerCase(shortChar);
603 }
604
605 /**
606 * Find the modifier with the given short code
607 * @param charCode The short code
608 * @return The modifier
609 */
610 public static Optional<Modifier> findWithShortCode(int charCode) {
611 return Stream.of(values()).filter(m -> m.shortChar == Character.toLowerCase(charCode)).findAny();
612 }
613 }
614
615 private class ParallelWayLayer extends AbstractMapViewPaintable {
616 @Override
617 public void paint(Graphics2D g, MapView mv, Bounds bbox) {
618 if (mode == Mode.DRAGGING) {
619 CheckParameterUtil.ensureParameterNotNull(mv, "mv");
620
621 Color mainColor = MAIN_COLOR.get();
622 g.setStroke(REF_LINE_STROKE.get());
623 g.setColor(mainColor);
624 MapViewPath line = new MapViewPath(mv);
625 line.moveTo(referenceSegment.getFirstNode());
626 line.lineTo(referenceSegment.getSecondNode());
627 g.draw(line.computeClippedLine(g.getStroke()));
628
629 g.setStroke(HELPER_LINE_STROKE.get());
630 g.setColor(mainColor);
631 line = new MapViewPath(mv);
632 line.moveTo(helperLineStart);
633 line.lineTo(helperLineEnd);
634 g.draw(line.computeClippedLine(g.getStroke()));
635 }
636 }
637 }
638}
Note: See TracBrowser for help on using the repository browser.