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

Last change on this file since 19050 was 19050, checked in by taylor.smock, 4 weeks ago

Revert most var changes from r19048, fix most new compile warnings and checkstyle issues

Also, document why various ErrorProne checks were originally disabled and fix
generic SonarLint issues.

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