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

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

see #8039, see #10456 - support read-only data layers

  • Property svn:eol-style set to native
File size: 24.1 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.data.Bounds;
28import org.openstreetmap.josm.data.SystemOfMeasurement;
29import org.openstreetmap.josm.data.coor.EastNorth;
30import org.openstreetmap.josm.data.osm.Node;
31import org.openstreetmap.josm.data.osm.OsmPrimitive;
32import org.openstreetmap.josm.data.osm.Way;
33import org.openstreetmap.josm.data.osm.WaySegment;
34import org.openstreetmap.josm.data.preferences.AbstractToStringProperty;
35import org.openstreetmap.josm.data.preferences.BooleanProperty;
36import org.openstreetmap.josm.data.preferences.CachingProperty;
37import org.openstreetmap.josm.data.preferences.DoubleProperty;
38import org.openstreetmap.josm.data.preferences.IntegerProperty;
39import org.openstreetmap.josm.data.preferences.NamedColorProperty;
40import org.openstreetmap.josm.data.preferences.StrokeProperty;
41import org.openstreetmap.josm.gui.MainApplication;
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.util.ModifierExListener;
49import org.openstreetmap.josm.tools.CheckParameterUtil;
50import org.openstreetmap.josm.tools.Geometry;
51import org.openstreetmap.josm.tools.ImageProvider;
52import org.openstreetmap.josm.tools.Logging;
53import org.openstreetmap.josm.tools.Shortcut;
54
55/**
56 * MapMode for making parallel ways.
57 *
58 * All calculations are done in projected coordinates.
59 *
60 * TODO:
61 * == Functionality ==
62 *
63 * 1. Use selected nodes as split points for the selected ways.
64 *
65 * The ways containing the selected nodes will be split and only the "inner"
66 * parts will be copied
67 *
68 * 2. Enter exact offset
69 *
70 * 3. Improve snapping
71 *
72 * 4. Visual cues could be better
73 *
74 * 5. (long term) Parallelize and adjust offsets of existing ways
75 *
76 * == Code quality ==
77 *
78 * a) The mode, flags, and modifiers might be updated more than necessary.
79 *
80 * Not a performance problem, but better if they where more centralized
81 *
82 * b) Extract generic MapMode services into a super class and/or utility class
83 *
84 * c) Maybe better to simply draw our own source way highlighting?
85 *
86 * Current code doesn't not take into account that ways might been highlighted
87 * by other than us. Don't think that situation should ever happen though.
88 *
89 * @author Ole Jørgen Brønner (olejorgenb)
90 */
91public class ParallelWayAction extends MapMode implements ModifierExListener {
92
93 private static final CachingProperty<BasicStroke> HELPER_LINE_STROKE = new StrokeProperty(prefKey("stroke.hepler-line"), "1").cached();
94 private static final CachingProperty<BasicStroke> REF_LINE_STROKE = new StrokeProperty(prefKey("stroke.ref-line"), "2 2 3").cached();
95
96 // @formatter:off
97 // CHECKSTYLE.OFF: SingleSpaceSeparator
98 private static final CachingProperty<Double> SNAP_THRESHOLD = new DoubleProperty(prefKey("snap-threshold-percent"), 0.70).cached();
99 private static final CachingProperty<Boolean> SNAP_DEFAULT = new BooleanProperty(prefKey("snap-default"), true).cached();
100 private static final CachingProperty<Boolean> COPY_TAGS_DEFAULT = new BooleanProperty(prefKey("copy-tags-default"), true).cached();
101 private static final CachingProperty<Integer> INITIAL_MOVE_DELAY = new IntegerProperty(prefKey("initial-move-delay"), 200).cached();
102 private static final CachingProperty<Double> SNAP_DISTANCE_METRIC = new DoubleProperty(prefKey("snap-distance-metric"), 0.5).cached();
103 private static final CachingProperty<Double> SNAP_DISTANCE_IMPERIAL = new DoubleProperty(prefKey("snap-distance-imperial"), 1).cached();
104 private static final CachingProperty<Double> SNAP_DISTANCE_CHINESE = new DoubleProperty(prefKey("snap-distance-chinese"), 1).cached();
105 private static final CachingProperty<Double> SNAP_DISTANCE_NAUTICAL = new DoubleProperty(prefKey("snap-distance-nautical"), 0.1).cached();
106 private static final CachingProperty<Color> MAIN_COLOR = new NamedColorProperty(marktr("make parallel helper line"), Color.RED).cached();
107
108 private static final CachingProperty<Map<Modifier, Boolean>> SNAP_MODIFIER_COMBO
109 = new KeyboardModifiersProperty(prefKey("snap-modifier-combo"), "?sC").cached();
110 private static final CachingProperty<Map<Modifier, Boolean>> COPY_TAGS_MODIFIER_COMBO
111 = new KeyboardModifiersProperty(prefKey("copy-tags-modifier-combo"), "As?").cached();
112 private static final CachingProperty<Map<Modifier, Boolean>> ADD_TO_SELECTION_MODIFIER_COMBO
113 = new KeyboardModifiersProperty(prefKey("add-to-selection-modifier-combo"), "aSc").cached();
114 private static final CachingProperty<Map<Modifier, Boolean>> TOGGLE_SELECTED_MODIFIER_COMBO
115 = new KeyboardModifiersProperty(prefKey("toggle-selection-modifier-combo"), "asC").cached();
116 private static final CachingProperty<Map<Modifier, Boolean>> SET_SELECTED_MODIFIER_COMBO
117 = new KeyboardModifiersProperty(prefKey("set-selection-modifier-combo"), "asc").cached();
118 // CHECKSTYLE.ON: SingleSpaceSeparator
119 // @formatter:on
120
121 enum Mode {
122 DRAGGING, NORMAL
123 }
124
125 //// Preferences and flags
126 // See updateModeLocalPreferences for defaults
127 private Mode mode;
128 private boolean copyTags;
129
130 private boolean snap;
131
132 private final MapView mv;
133
134 // Mouse tracking state
135 private Point mousePressedPos;
136 private boolean mouseIsDown;
137 private long mousePressedTime;
138 private boolean mouseHasBeenDragged;
139
140 private transient WaySegment referenceSegment;
141 private transient ParallelWays pWays;
142 private transient Set<Way> sourceWays;
143 private EastNorth helperLineStart;
144 private EastNorth helperLineEnd;
145
146 private final ParallelWayLayer temporaryLayer = new ParallelWayLayer();
147
148 /**
149 * Constructs a new {@code ParallelWayAction}.
150 * @param mapFrame Map frame
151 */
152 public ParallelWayAction(MapFrame mapFrame) {
153 super(tr("Parallel"), "parallel", tr("Make parallel copies of ways"),
154 Shortcut.registerShortcut("mapmode:parallel", tr("Mode: {0}",
155 tr("Parallel")), KeyEvent.VK_P, Shortcut.SHIFT),
156 ImageProvider.getCursor("normal", "parallel"));
157 putValue("help", ht("/Action/Parallel"));
158 mv = mapFrame.mapView;
159 }
160
161 @Override
162 public void enterMode() {
163 // super.enterMode() updates the status line and cursor so we need our state to be set correctly
164 setMode(Mode.NORMAL);
165 pWays = null;
166
167 super.enterMode();
168
169 mv.addMouseListener(this);
170 mv.addMouseMotionListener(this);
171 mv.addTemporaryLayer(temporaryLayer);
172
173 // Needed to update the mouse cursor if modifiers are changed when the mouse is motionless
174 MainApplication.getMap().keyDetector.addModifierExListener(this);
175 sourceWays = new LinkedHashSet<>(getLayerManager().getEditDataSet().getSelectedWays());
176 for (Way w : sourceWays) {
177 w.setHighlighted(true);
178 }
179 }
180
181 @Override
182 public void exitMode() {
183 super.exitMode();
184 mv.removeMouseListener(this);
185 mv.removeMouseMotionListener(this);
186 mv.removeTemporaryLayer(temporaryLayer);
187 MapFrame map = MainApplication.getMap();
188 map.statusLine.setDist(-1);
189 map.statusLine.repaint();
190 map.keyDetector.removeModifierExListener(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 l) {
214 return isEditableDataLayer(l);
215 }
216
217 @Override
218 public void modifiersExChanged(int modifiers) {
219 if (MainApplication.getMap() == 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 updateKeyModifiersEx(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.getModifiersEx());
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.getModifiersEx());
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.getModifiersEx());
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 MapFrame map = MainApplication.getMap();
434 map.statusLine.setDist(Math.abs(snappedRealD));
435 map.statusLine.repaint();
436 temporaryLayer.invalidate();
437 }
438
439 private boolean matchesCurrentModifiers(CachingProperty<Map<Modifier, Boolean>> spec) {
440 return matchesCurrentModifiers(spec.get());
441 }
442
443 private boolean matchesCurrentModifiers(Map<Modifier, Boolean> spec) {
444 EnumSet<Modifier> modifiers = EnumSet.noneOf(Modifier.class);
445 if (ctrl) {
446 modifiers.add(Modifier.CTRL);
447 }
448 if (alt) {
449 modifiers.add(Modifier.ALT);
450 }
451 if (shift) {
452 modifiers.add(Modifier.SHIFT);
453 }
454 return spec.entrySet().stream().allMatch(entry -> modifiers.contains(entry.getKey()) == entry.getValue().booleanValue());
455 }
456
457 private boolean isModifiersValidForDragMode() {
458 return (!alt && !shift && !ctrl) || matchesCurrentModifiers(SNAP_MODIFIER_COMBO)
459 || matchesCurrentModifiers(COPY_TAGS_MODIFIER_COMBO);
460 }
461
462 private void updateFlagsOnlyChangeableOnPress() {
463 copyTags = COPY_TAGS_DEFAULT.get().booleanValue() != matchesCurrentModifiers(COPY_TAGS_MODIFIER_COMBO);
464 }
465
466 private void updateFlagsChangeableAlways() {
467 snap = SNAP_DEFAULT.get().booleanValue() != matchesCurrentModifiers(SNAP_MODIFIER_COMBO);
468 }
469
470 // We keep the source ways and the selection in sync so the user can see the source way's tags
471 private void addSourceWay(Way w) {
472 assert sourceWays != null;
473 getLayerManager().getEditDataSet().addSelected(w);
474 w.setHighlighted(true);
475 sourceWays.add(w);
476 }
477
478 private void removeSourceWay(Way w) {
479 assert sourceWays != null;
480 getLayerManager().getEditDataSet().clearSelection(w);
481 w.setHighlighted(false);
482 sourceWays.remove(w);
483 }
484
485 private void clearSourceWays() {
486 assert sourceWays != null;
487 getLayerManager().getEditDataSet().clearSelection(sourceWays);
488 for (Way w : sourceWays) {
489 w.setHighlighted(false);
490 }
491 sourceWays.clear();
492 }
493
494 private void resetMouseTrackingState() {
495 mouseIsDown = false;
496 mousePressedPos = null;
497 mouseHasBeenDragged = false;
498 }
499
500 // TODO: rename
501 private boolean initParallelWays(Point p, boolean copyTags) {
502 referenceSegment = mv.getNearestWaySegment(p, OsmPrimitive::isUsable, true);
503 if (referenceSegment == null)
504 return false;
505
506 sourceWays.removeIf(w -> w.isIncomplete() || w.getNodesCount() == 0);
507
508 if (!sourceWays.contains(referenceSegment.way)) {
509 clearSourceWays();
510 addSourceWay(referenceSegment.way);
511 }
512
513 try {
514 int referenceWayIndex = -1;
515 int i = 0;
516 for (Way w : sourceWays) {
517 if (w == referenceSegment.way) {
518 referenceWayIndex = i;
519 break;
520 }
521 i++;
522 }
523 pWays = new ParallelWays(sourceWays, copyTags, referenceWayIndex);
524 pWays.commit();
525 getLayerManager().getEditDataSet().setSelected(pWays.getWays());
526 return true;
527 } catch (IllegalArgumentException e) {
528 Logging.debug(e);
529 new Notification(tr("ParallelWayAction\n" +
530 "The ways selected must form a simple branchless path"))
531 .setIcon(JOptionPane.INFORMATION_MESSAGE)
532 .show();
533 // The error dialog prevents us from getting the mouseReleased event
534 resetMouseTrackingState();
535 pWays = null;
536 return false;
537 }
538 }
539
540 private static String prefKey(String subKey) {
541 return "edit.make-parallel-way-action." + subKey;
542 }
543
544 /**
545 * A property that holds the keyboard modifiers.
546 * @author Michael Zangl
547 * @since 10869
548 */
549 private static class KeyboardModifiersProperty extends AbstractToStringProperty<Map<Modifier, Boolean>> {
550
551 KeyboardModifiersProperty(String key, String defaultValue) {
552 super(key, createFromString(defaultValue));
553 }
554
555 KeyboardModifiersProperty(String key, Map<Modifier, Boolean> defaultValue) {
556 super(key, defaultValue);
557 }
558
559 @Override
560 protected String toString(Map<Modifier, Boolean> t) {
561 StringBuilder sb = new StringBuilder();
562 for (Modifier mod : Modifier.values()) {
563 Boolean val = t.get(mod);
564 if (val == null) {
565 sb.append('?');
566 } else if (val) {
567 sb.append(Character.toUpperCase(mod.shortChar));
568 } else {
569 sb.append(mod.shortChar);
570 }
571 }
572 return sb.toString();
573 }
574
575 @Override
576 protected Map<Modifier, Boolean> fromString(String string) {
577 return createFromString(string);
578 }
579
580 private static Map<Modifier, Boolean> createFromString(String string) {
581 Map<Modifier, Boolean> ret = new EnumMap<>(Modifier.class);
582 for (char c : string.toCharArray()) {
583 if (c == '?') {
584 continue;
585 }
586 Optional<Modifier> mod = Modifier.findWithShortCode(c);
587 if (mod.isPresent()) {
588 ret.put(mod.get(), Character.isUpperCase(c));
589 } else {
590 Logging.debug("Ignoring unknown modifier {0}", c);
591 }
592 }
593 return Collections.unmodifiableMap(ret);
594 }
595 }
596
597 enum Modifier {
598 CTRL('c'),
599 ALT('a'),
600 SHIFT('s');
601
602 private final char shortChar;
603
604 Modifier(char shortChar) {
605 this.shortChar = Character.toLowerCase(shortChar);
606 }
607
608 /**
609 * Find the modifier with the given short code
610 * @param charCode The short code
611 * @return The modifier
612 */
613 public static Optional<Modifier> findWithShortCode(int charCode) {
614 return Stream.of(values()).filter(m -> m.shortChar == Character.toLowerCase(charCode)).findAny();
615 }
616 }
617
618 private class ParallelWayLayer extends AbstractMapViewPaintable {
619 @Override
620 public void paint(Graphics2D g, MapView mv, Bounds bbox) {
621 if (mode == Mode.DRAGGING) {
622 CheckParameterUtil.ensureParameterNotNull(mv, "mv");
623
624 Color mainColor = MAIN_COLOR.get();
625 g.setStroke(REF_LINE_STROKE.get());
626 g.setColor(mainColor);
627 MapViewPath line = new MapViewPath(mv);
628 line.moveTo(referenceSegment.getFirstNode());
629 line.lineTo(referenceSegment.getSecondNode());
630 g.draw(line.computeClippedLine(g.getStroke()));
631
632 g.setStroke(HELPER_LINE_STROKE.get());
633 g.setColor(mainColor);
634 line = new MapViewPath(mv);
635 line.moveTo(helperLineStart);
636 line.lineTo(helperLineEnd);
637 g.draw(line.computeClippedLine(g.getStroke()));
638 }
639 }
640 }
641}
Note: See TracBrowser for help on using the repository browser.