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, 15 months 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.