Index: /trunk/src/org/openstreetmap/josm/actions/PreferenceToggleAction.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/actions/PreferenceToggleAction.java	(revision 10873)
+++ /trunk/src/org/openstreetmap/josm/actions/PreferenceToggleAction.java	(revision 10874)
@@ -7,26 +7,29 @@
 import org.openstreetmap.josm.data.Preferences;
 import org.openstreetmap.josm.data.Preferences.PreferenceChangedListener;
+import org.openstreetmap.josm.data.preferences.BooleanProperty;
 
 public class PreferenceToggleAction extends JosmAction implements PreferenceChangedListener {
 
     private final JCheckBoxMenuItem checkbox;
-    private final String prefKey;
-    private final boolean prefDefault;
+    private final BooleanProperty pref;
 
     public PreferenceToggleAction(String name, String tooltip, String prefKey, boolean prefDefault) {
         super(name, null, tooltip, null, false);
         putValue("toolbar", "toggle-" + prefKey);
-        this.prefKey = prefKey;
-        this.prefDefault = prefDefault;
-        this.checkbox = new JCheckBoxMenuItem(this);
-        this.checkbox.setSelected(Main.pref.getBoolean(prefKey, prefDefault));
-        Main.pref.addPreferenceChangeListener(this);
+        this.pref = new BooleanProperty(prefKey, prefDefault);
+        checkbox = new JCheckBoxMenuItem(this);
+        checkbox.setSelected(pref.get());
+        Main.pref.addWeakKeyPreferenceChangeListener(prefKey, this);
     }
 
     @Override
     public void actionPerformed(ActionEvent e) {
-        Main.pref.put(prefKey, checkbox.isSelected());
+        pref.put(checkbox.isSelected());
     }
 
+    /**
+     * Get the checkbox that can be used for this action. It can only be used at one place.
+     * @return The checkbox.
+     */
     public JCheckBoxMenuItem getCheckbox() {
         return checkbox;
@@ -35,7 +38,5 @@
     @Override
     public void preferenceChanged(Preferences.PreferenceChangeEvent e) {
-        if (prefKey.equals(e.getKey())) {
-            checkbox.setSelected(Main.pref.getBoolean(prefKey, prefDefault));
-        }
+        checkbox.setSelected(pref.get());
     }
 }
Index: /trunk/src/org/openstreetmap/josm/actions/mapmode/DrawAction.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/actions/mapmode/DrawAction.java	(revision 10873)
+++ /trunk/src/org/openstreetmap/josm/actions/mapmode/DrawAction.java	(revision 10874)
@@ -12,5 +12,4 @@
 import java.awt.Graphics2D;
 import java.awt.Point;
-import java.awt.Stroke;
 import java.awt.event.ActionEvent;
 import java.awt.event.KeyEvent;
@@ -21,4 +20,5 @@
 import java.util.Collection;
 import java.util.Collections;
+import java.util.Comparator;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -28,4 +28,5 @@
 import java.util.Map;
 import java.util.Set;
+import java.util.stream.DoubleStream;
 
 import javax.swing.AbstractAction;
@@ -53,8 +54,14 @@
 import org.openstreetmap.josm.data.osm.visitor.paint.MapPath2D;
 import org.openstreetmap.josm.data.osm.visitor.paint.PaintColors;
+import org.openstreetmap.josm.data.preferences.AbstractToStringProperty;
+import org.openstreetmap.josm.data.preferences.BooleanProperty;
+import org.openstreetmap.josm.data.preferences.CachingProperty;
 import org.openstreetmap.josm.data.preferences.ColorProperty;
+import org.openstreetmap.josm.data.preferences.DoubleProperty;
+import org.openstreetmap.josm.data.preferences.StrokeProperty;
 import org.openstreetmap.josm.gui.MainMenu;
 import org.openstreetmap.josm.gui.MapFrame;
 import org.openstreetmap.josm.gui.MapView;
+import org.openstreetmap.josm.gui.MapViewState;
 import org.openstreetmap.josm.gui.MapViewState.MapViewPoint;
 import org.openstreetmap.josm.gui.NavigatableComponent;
@@ -62,5 +69,4 @@
 import org.openstreetmap.josm.gui.layer.MapViewPaintable;
 import org.openstreetmap.josm.gui.layer.OsmDataLayer;
-import org.openstreetmap.josm.gui.util.GuiHelper;
 import org.openstreetmap.josm.gui.util.KeyPressReleaseListener;
 import org.openstreetmap.josm.gui.util.ModifierListener;
@@ -80,4 +86,42 @@
 
     private static final ArrowPaintHelper START_WAY_INDICATOR = new ArrowPaintHelper(Math.toRadians(90), 8);
+
+    private static final CachingProperty<Boolean> USE_REPEATED_SHORTCUT
+            = new BooleanProperty("draw.anglesnap.toggleOnRepeatedA", true).cached();
+    private static final CachingProperty<BasicStroke> RUBBER_LINE_STROKE
+            = new StrokeProperty("draw.stroke.helper-line", "3").cached();
+
+    private static final CachingProperty<BasicStroke> HIGHLIGHT_STROKE
+            = new StrokeProperty("draw.anglesnap.stroke.highlight", "10").cached();
+    private static final CachingProperty<BasicStroke> HELPER_STROKE
+            = new StrokeProperty("draw.anglesnap.stroke.helper", "1 4").cached();
+
+    private static final CachingProperty<Double> SNAP_ANGLE_TOLERANCE
+            = new DoubleProperty("draw.anglesnap.tolerance", 5.0).cached();
+    private static final CachingProperty<Boolean> DRAW_CONSTRUCTION_GEOMETRY
+            = new BooleanProperty("draw.anglesnap.drawConstructionGeometry", true).cached();
+    private static final CachingProperty<Boolean> SHOW_PROJECTED_POINT
+            = new BooleanProperty("draw.anglesnap.drawProjectedPoint", true).cached();
+    private static final CachingProperty<Boolean> SNAP_TO_PROJECTIONS
+            = new BooleanProperty("draw.anglesnap.projectionsnap", true).cached();
+
+    private static final CachingProperty<Boolean> SHOW_ANGLE
+            = new BooleanProperty("draw.anglesnap.showAngle", true).cached();
+
+    private static final CachingProperty<Color> SNAP_HELPER_COLOR
+            = new ColorProperty(marktr("draw angle snap"), Color.ORANGE).cached();
+
+    private static final CachingProperty<Color> HIGHLIGHT_COLOR
+            = new ColorProperty(marktr("draw angle snap highlight"), ORANGE_TRANSPARENT).cached();
+
+    private static final AbstractToStringProperty<Color> RUBBER_LINE_COLOR
+            = PaintColors.SELECTED.getProperty().getChildColor(marktr("helper line"));
+
+    private static final CachingProperty<Boolean> DRAW_HELPER_LINE
+            = new BooleanProperty("draw.helper-line", true).cached();
+    private static final CachingProperty<Boolean> DRAW_TARGET_HIGHLIGHT
+            = new BooleanProperty("draw.target-highlight", true).cached();
+    private static final CachingProperty<Double> SNAP_TO_INTERSECTION_THRESHOLD
+            = new DoubleProperty("edit.snap-intersection-threshold", 10).cached();
 
     private final Cursor cursorJoinNode;
@@ -97,10 +141,7 @@
     // repaint if there are changes.
     private transient Set<OsmPrimitive> newHighlights = new HashSet<>();
-    private boolean drawHelperLine;
     private boolean wayIsFinished;
-    private boolean drawTargetHighlight;
     private Point mousePos;
     private Point oldMousePos;
-    private Color rubberLineColor;
 
     private transient Node currentBaseNode;
@@ -117,9 +158,7 @@
     private final SnapChangeAction snapChangeAction;
     private final JCheckBoxMenuItem snapCheckboxMenuItem;
-    private boolean useRepeatedShortcut;
-    private transient Stroke rubberLineStroke;
     private static final BasicStroke BASIC_STROKE = new BasicStroke(1);
 
-    private static int snapToIntersectionThreshold;
+    private Point rightClickPressPos;
 
     /**
@@ -143,7 +182,5 @@
         cursorJoinWay = ImageProvider.getCursor("crosshair", "joinway");
 
-        readPreferences();
         snapHelper.init();
-        readPreferences();
     }
 
@@ -166,6 +203,6 @@
         updateStatusLine();
         // repaint required if the helper line is active.
-        boolean needsRepaint = drawHelperLine && !wayIsFinished;
-        if (drawTargetHighlight) {
+        boolean needsRepaint = DRAW_HELPER_LINE.get() && !wayIsFinished;
+        if (DRAW_TARGET_HIGHLIGHT.get()) {
             // move newHighlights to oldHighlights; only update changed primitives
             for (OsmPrimitive x : newHighlights) {
@@ -185,5 +222,5 @@
         oldHighlights = newHighlights;
 
-        if (!needsRepaint && !drawTargetHighlight)
+        if (!needsRepaint && !DRAW_TARGET_HIGHLIGHT.get())
             return false;
 
@@ -246,16 +283,4 @@
 
     @Override
-    protected void readPreferences() {
-        rubberLineColor = new ColorProperty(marktr("helper line"), (Color) null).get();
-        if (rubberLineColor == null)
-            rubberLineColor = PaintColors.SELECTED.get();
-
-        rubberLineStroke = GuiHelper.getCustomizedStroke(Main.pref.get("draw.stroke.helper-line", "3"));
-        drawHelperLine = Main.pref.getBoolean("draw.helper-line", true);
-        drawTargetHighlight = Main.pref.getBoolean("draw.target-highlight", true);
-        snapToIntersectionThreshold = Main.pref.getInteger("edit.snap-intersection-threshold", 10);
-    }
-
-    @Override
     public void exitMode() {
         super.exitMode();
@@ -298,5 +323,5 @@
     @Override
     public void doKeyPressed(KeyEvent e) {
-        if (!snappingShortcut.isEvent(e) && !(useRepeatedShortcut && getShortcut().isEvent(e)))
+        if (!snappingShortcut.isEvent(e) && !(USE_REPEATED_SHORTCUT.get() && getShortcut().isEvent(e)))
             return;
         snapHelper.setFixedMode();
@@ -307,5 +332,5 @@
     @Override
     public void doKeyReleased(KeyEvent e) {
-        if (!snappingShortcut.isEvent(e) && !(useRepeatedShortcut && getShortcut().isEvent(e)))
+        if (!snappingShortcut.isEvent(e) && !(USE_REPEATED_SHORTCUT.get() && getShortcut().isEvent(e)))
             return;
         if (ignoreNextKeyRelease) {
@@ -352,6 +377,4 @@
         removeHighlighting();
     }
-
-    private Point rightClickPressPos;
 
     @Override
@@ -591,10 +614,33 @@
             }
         }
-
+        if (!extendedWay && !newNode) {
+            return; // We didn't do anything.
+        }
+
+        String title = getTitle(newNode, n, newSelection, reuseWays, extendedWay);
+
+        Command c = new SequenceCommand(title, cmds);
+
+        Main.main.undoRedo.add(c);
+        if (!wayIsFinished) {
+            lastUsedNode = n;
+        }
+
+        ds.setSelected(newSelection);
+
+        // "viewport following" mode for tracing long features
+        // from aerial imagery or GPS tracks.
+        if (Main.map.mapView.viewportFollowing) {
+            Main.map.mapView.smoothScrollTo(n.getEastNorth());
+        }
+        computeHelperLine();
+        removeHighlighting();
+    }
+
+    private String getTitle(boolean newNode, Node n, Collection<OsmPrimitive> newSelection, List<Way> reuseWays,
+            boolean extendedWay) {
         String title;
         if (!extendedWay) {
-            if (!newNode)
-                return; // We didn't do anything.
-            else if (reuseWays.isEmpty()) {
+            if (reuseWays.isEmpty()) {
                 title = tr("Add node");
             } else {
@@ -613,21 +659,5 @@
             title = tr("Add node into way and connect");
         }
-
-        Command c = new SequenceCommand(title, cmds);
-
-        Main.main.undoRedo.add(c);
-        if (!wayIsFinished) {
-            lastUsedNode = n;
-        }
-
-        ds.setSelected(newSelection);
-
-        // "viewport following" mode for tracing long features
-        // from aerial imagery or GPS tracks.
-        if (n != null && Main.map.mapView.viewportFollowing) {
-            Main.map.mapView.smoothScrollTo(n.getEastNorth());
-        }
-        computeHelperLine();
-        removeHighlighting();
+        return title;
     }
 
@@ -1033,5 +1063,5 @@
             // fall through to default action.
             // (for semi-parallel lines, intersection might be miles away!)
-            if (Main.map.mapView.getPoint2D(n).distance(Main.map.mapView.getPoint2D(intersection)) < snapToIntersectionThreshold) {
+            if (Main.map.mapView.getPoint2D(n).distance(Main.map.mapView.getPoint2D(intersection)) < SNAP_TO_INTERSECTION_THRESHOLD.get()) {
                 n.setEastNorth(intersection);
                 return;
@@ -1135,13 +1165,19 @@
 
         Graphics2D g2 = g;
-        snapHelper.drawIfNeeded(g2, mv);
-        if (!drawHelperLine || wayIsFinished || shift)
-            return;
-
-        if (!snapHelper.isActive()) { // else use color and stoke from  snapHelper.draw
-            g2.setColor(rubberLineColor);
-            g2.setStroke(rubberLineStroke);
-        } else if (!snapHelper.drawConstructionGeometry)
-            return;
+        snapHelper.drawIfNeeded(g2, mv.getState());
+        if (!DRAW_HELPER_LINE.get() || wayIsFinished || shift)
+            return;
+
+        if (!snapHelper.isActive()) {
+            g2.setColor(RUBBER_LINE_COLOR.get());
+            g2.setStroke(RUBBER_LINE_STROKE.get());
+            paintConstructionGeometry(mv, g2);
+        } else if (DRAW_CONSTRUCTION_GEOMETRY.get()) {
+            // else use color and stoke from  snapHelper.draw
+            paintConstructionGeometry(mv, g2);
+        }
+    }
+
+    private void paintConstructionGeometry(MapView mv, Graphics2D g2) {
         MapPath2D b = new MapPath2D();
         MapViewPoint p1 = mv.getState().getPointFor(getCurrentBaseNode());
@@ -1305,4 +1341,6 @@
 
     private class SnapHelper {
+        private static final String DRAW_ANGLESNAP_ANGLES = "draw.anglesnap.angles";
+
         private final class AnglePopupMenu extends JPopupMenu {
 
@@ -1312,6 +1350,5 @@
                 public void actionPerformed(ActionEvent e) {
                     boolean sel = ((JCheckBoxMenuItem) e.getSource()).getState();
-                    Main.pref.put("draw.anglesnap.toggleOnRepeatedA", sel);
-                    init();
+                    USE_REPEATED_SHORTCUT.put(sel);
                 }
             });
@@ -1322,8 +1359,7 @@
                 public void actionPerformed(ActionEvent e) {
                     boolean sel = ((JCheckBoxMenuItem) e.getSource()).getState();
-                    Main.pref.put("draw.anglesnap.drawConstructionGeometry", sel);
-                    Main.pref.put("draw.anglesnap.drawProjectedPoint", sel);
-                    Main.pref.put("draw.anglesnap.showAngle", sel);
-                    init();
+                    DRAW_CONSTRUCTION_GEOMETRY.put(sel);
+                    SHOW_PROJECTED_POINT.put(sel);
+                    SHOW_ANGLE.put(sel);
                     enableSnapping();
                 }
@@ -1335,6 +1371,5 @@
                 public void actionPerformed(ActionEvent e) {
                     boolean sel = ((JCheckBoxMenuItem) e.getSource()).getState();
-                    Main.pref.put("draw.anglesnap.projectionsnap", sel);
-                    init();
+                    SNAP_TO_PROJECTIONS.put(sel);
                     enableSnapping();
                 }
@@ -1342,7 +1377,7 @@
 
             private AnglePopupMenu() {
-                helperCb.setState(Main.pref.getBoolean("draw.anglesnap.drawConstructionGeometry", true));
-                projectionCb.setState(Main.pref.getBoolean("draw.anglesnap.projectionsnapgvff", true));
-                repeatedCb.setState(Main.pref.getBoolean("draw.anglesnap.toggleOnRepeatedA", true));
+                helperCb.setState(DRAW_CONSTRUCTION_GEOMETRY.get());
+                projectionCb.setState(SNAP_TO_PROJECTIONS.get());
+                repeatedCb.setState(USE_REPEATED_SHORTCUT.get());
                 add(repeatedCb);
                 add(helperCb);
@@ -1385,10 +1420,4 @@
         private boolean absoluteFix; // snap angle is absolute
 
-        private boolean drawConstructionGeometry;
-        private boolean showProjectedPoint;
-        private boolean showAngle;
-
-        private boolean snapToProjections;
-
         private EastNorth dir2;
         private EastNorth projected;
@@ -1402,5 +1431,4 @@
 
         private double[] snapAngles;
-        private double snapAngleTolerance;
 
         private double pe, pn; // (pe, pn) - direction of snapping line
@@ -1408,10 +1436,4 @@
 
         private final String fixFmt = "%d "+tr("FIX");
-        private Color snapHelperColor;
-        private Color highlightColor;
-
-        private Stroke normalStroke;
-        private Stroke helperStroke;
-        private Stroke highlightStroke;
 
         private JCheckBoxMenuItem checkBox;
@@ -1428,4 +1450,7 @@
         };
 
+        /**
+         * Set the initial state
+         */
         public void init() {
             snapOn = false;
@@ -1434,37 +1459,32 @@
             absoluteFix = false;
 
-            Collection<String> angles = Main.pref.getCollection("draw.anglesnap.angles",
-                    Arrays.asList("0", "30", "45", "60", "90", "120", "135", "150", "180"));
-
-            snapAngles = new double[2*angles.size()];
-            int i = 0;
-            for (String s: angles) {
-                try {
-                    snapAngles[i] = Double.parseDouble(s); i++;
-                    snapAngles[i] = 360-Double.parseDouble(s); i++;
-                } catch (NumberFormatException e) {
-                    Main.warn("Incorrect number in draw.anglesnap.angles preferences: "+s);
-                    snapAngles[i] = 0; i++;
-                    snapAngles[i] = 0; i++;
-                }
-            }
-            snapAngleTolerance = Main.pref.getDouble("draw.anglesnap.tolerance", 5.0);
-            drawConstructionGeometry = Main.pref.getBoolean("draw.anglesnap.drawConstructionGeometry", true);
-            showProjectedPoint = Main.pref.getBoolean("draw.anglesnap.drawProjectedPoint", true);
-            snapToProjections = Main.pref.getBoolean("draw.anglesnap.projectionsnap", true);
-
-            showAngle = Main.pref.getBoolean("draw.anglesnap.showAngle", true);
-            useRepeatedShortcut = Main.pref.getBoolean("draw.anglesnap.toggleOnRepeatedA", true);
-
-            normalStroke = rubberLineStroke;
-            snapHelperColor = new ColorProperty(marktr("draw angle snap"), Color.ORANGE).get();
-
-            highlightColor = new ColorProperty(marktr("draw angle snap highlight"), ORANGE_TRANSPARENT).get();
-            highlightStroke = GuiHelper.getCustomizedStroke(Main.pref.get("draw.anglesnap.stroke.highlight", "10"));
-            helperStroke = GuiHelper.getCustomizedStroke(Main.pref.get("draw.anglesnap.stroke.helper", "1 4"));
-        }
-
+            computeSnapAngles();
+            Main.pref.addWeakKeyPreferenceChangeListener(DRAW_ANGLESNAP_ANGLES, e -> this.computeSnapAngles());
+        }
+
+        private void computeSnapAngles() {
+            snapAngles = Main.pref.getCollection(DRAW_ANGLESNAP_ANGLES,
+                    Arrays.asList("0", "30", "45", "60", "90", "120", "135", "150", "180"))
+                    .stream()
+                    .mapToDouble(this::parseSnapAngle)
+                    .flatMap(s -> DoubleStream.of(s, 360-s))
+                    .toArray();
+        }
+
+        private double parseSnapAngle(String string) {
+            try {
+                return Double.parseDouble(string);
+            } catch (NumberFormatException e) {
+                Main.warn("Incorrect number in draw.anglesnap.angles preferences: {0}", string);
+                return 0;
+            }
+        }
+
+        /**
+         * Save the snap angles
+         * @param angles The angles
+         */
         public void saveAngles(String ... angles) {
-            Main.pref.putCollection("draw.anglesnap.angles", Arrays.asList(angles));
+            Main.pref.putCollection(DRAW_ANGLESNAP_ANGLES, Arrays.asList(angles));
         }
 
@@ -1473,18 +1493,24 @@
         }
 
-        public void drawIfNeeded(Graphics2D g2, MapView mv) {
+        /**
+         * Draw the snap hint line.
+         * @param g2 graphics
+         * @param mv MapView state
+         * @since 10874
+         */
+        public void drawIfNeeded(Graphics2D g2, MapViewState mv) {
             if (!snapOn || !active)
                 return;
-            MapViewPoint p1 = mv.getState().getPointFor(getCurrentBaseNode());
-            MapViewPoint p2 = mv.getState().getPointFor(dir2);
-            MapViewPoint p3 = mv.getState().getPointFor(projected);
-            if (drawConstructionGeometry) {
-                g2.setColor(snapHelperColor);
-                g2.setStroke(helperStroke);
+            MapViewPoint p1 = mv.getPointFor(getCurrentBaseNode());
+            MapViewPoint p2 = mv.getPointFor(dir2);
+            MapViewPoint p3 = mv.getPointFor(projected);
+            if (DRAW_CONSTRUCTION_GEOMETRY.get()) {
+                g2.setColor(SNAP_HELPER_COLOR.get());
+                g2.setStroke(HELPER_STROKE.get());
 
                 MapPath2D b = new MapPath2D();
                 b.moveTo(p2);
                 if (absoluteFix) {
-                    b.lineTo(2d*p1.getInViewX()-p2.getInViewX(), 2d*p1.getInViewY()-p2.getInViewY()); // bi-directional line
+                    b.lineTo(p2.interpolate(p1, 2)); // bi-directional line
                 } else {
                     b.lineTo(p3);
@@ -1493,23 +1519,23 @@
             }
             if (projectionSource != null) {
-                g2.setColor(snapHelperColor);
-                g2.setStroke(helperStroke);
+                g2.setColor(SNAP_HELPER_COLOR.get());
+                g2.setStroke(HELPER_STROKE.get());
                 MapPath2D b = new MapPath2D();
                 b.moveTo(p3);
-                b.lineTo(mv.getState().getPointFor(projectionSource));
+                b.lineTo(mv.getPointFor(projectionSource));
                 g2.draw(b);
             }
 
             if (customBaseHeading >= 0) {
-                g2.setColor(highlightColor);
-                g2.setStroke(highlightStroke);
+                g2.setColor(HIGHLIGHT_COLOR.get());
+                g2.setStroke(HIGHLIGHT_STROKE.get());
                 MapPath2D b = new MapPath2D();
-                b.moveTo(mv.getState().getPointFor(segmentPoint1));
-                b.lineTo(mv.getState().getPointFor(segmentPoint2));
+                b.moveTo(mv.getPointFor(segmentPoint1));
+                b.lineTo(mv.getPointFor(segmentPoint2));
                 g2.draw(b);
             }
 
-            g2.setColor(rubberLineColor);
-            g2.setStroke(normalStroke);
+            g2.setColor(RUBBER_LINE_COLOR.get());
+            g2.setStroke(RUBBER_LINE_STROKE.get());
             MapPath2D b = new MapPath2D();
             b.moveTo(p1);
@@ -1518,14 +1544,18 @@
 
             g2.drawString(labelText, (int) p3.getInViewX()-5, (int) p3.getInViewY()+20);
-            if (showProjectedPoint) {
-                g2.setStroke(normalStroke);
+            if (SHOW_PROJECTED_POINT.get()) {
+                g2.setStroke(RUBBER_LINE_STROKE.get());
                 g2.drawOval((int) p3.getInViewX()-5, (int) p3.getInViewY()-5, 10, 10); // projected point
             }
 
-            g2.setColor(snapHelperColor);
-            g2.setStroke(helperStroke);
-        }
-
-        /* If mouse position is close to line at 15-30-45-... angle, remembers this direction
+            g2.setColor(SNAP_HELPER_COLOR.get());
+            g2.setStroke(HELPER_STROKE.get());
+        }
+
+        /**
+         *  If mouse position is close to line at 15-30-45-... angle, remembers this direction
+         * @param currentEN Current position
+         * @param baseHeading The heading
+         * @param curHeading The current mouse heading
          */
         public void checkAngleSnapping(EastNorth currentEN, double baseHeading, double curHeading) {
@@ -1551,5 +1581,5 @@
                 } else {
                     nearestAngle = getNearestAngle(angle);
-                    if (getAngleDelta(nearestAngle, angle) < snapAngleTolerance) {
+                    if (getAngleDelta(nearestAngle, angle) < SNAP_ANGLE_TOLERANCE.get()) {
                         active = customBaseHeading >= 0 || Math.abs(nearestAngle - 180) > 1e-3;
                         // if angle is to previous segment, exclude 180 degrees
@@ -1597,5 +1627,5 @@
 
         private void buildLabelText(double nearestAngle) {
-            if (showAngle) {
+            if (SHOW_ANGLE.get()) {
                 if (fixed) {
                     if (absoluteFix) {
@@ -1620,4 +1650,9 @@
         }
 
+        /**
+         * Gets a snap point close to p. Stores the result for display.
+         * @param p The point
+         * @return The snap point close to p.
+         */
         public EastNorth getSnapPoint(EastNorth p) {
             if (!active)
@@ -1633,5 +1668,5 @@
 
             projectionSource = null;
-            if (snapToProjections) {
+            if (SNAP_TO_PROJECTIONS.get()) {
                 DataSet ds = getLayerManager().getEditDataSet();
                 Collection<Way> selectedWays = ds.getSelectedWays();
@@ -1668,4 +1703,7 @@
         }
 
+        /**
+         * Disables snapping
+         */
         public void noSnapNow() {
             active = false;
@@ -1691,4 +1729,7 @@
         }
 
+        /**
+         * Enable snapping.
+         */
         private void enableSnapping() {
             snapOn = true;
@@ -1727,12 +1768,6 @@
 
         private double getNearestAngle(double angle) {
-            double delta, minDelta = 1e5, bestAngle = 0.0;
-            for (double snapAngle : snapAngles) {
-                delta = getAngleDelta(angle, snapAngle);
-                if (delta < minDelta) {
-                    minDelta = delta;
-                    bestAngle = snapAngle;
-                }
-            }
+            double bestAngle = DoubleStream.of(snapAngles).boxed()
+                    .min(Comparator.comparing(snapAngle -> getAngleDelta(angle, snapAngle))).orElse(0.0);
             if (Math.abs(bestAngle-360) < 1e-3) {
                 bestAngle = 0;
Index: /trunk/src/org/openstreetmap/josm/actions/mapmode/ParallelWayAction.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/actions/mapmode/ParallelWayAction.java	(revision 10873)
+++ /trunk/src/org/openstreetmap/josm/actions/mapmode/ParallelWayAction.java	(revision 10874)
@@ -6,9 +6,9 @@
 import static org.openstreetmap.josm.tools.I18n.tr;
 
+import java.awt.BasicStroke;
 import java.awt.Color;
 import java.awt.Cursor;
 import java.awt.Graphics2D;
 import java.awt.Point;
-import java.awt.Stroke;
 import java.awt.event.KeyEvent;
 import java.awt.event.MouseEvent;
@@ -41,5 +41,5 @@
 import org.openstreetmap.josm.data.preferences.DoubleProperty;
 import org.openstreetmap.josm.data.preferences.IntegerProperty;
-import org.openstreetmap.josm.data.preferences.StringProperty;
+import org.openstreetmap.josm.data.preferences.StrokeProperty;
 import org.openstreetmap.josm.gui.MapFrame;
 import org.openstreetmap.josm.gui.MapView;
@@ -48,5 +48,4 @@
 import org.openstreetmap.josm.gui.layer.MapViewPaintable;
 import org.openstreetmap.josm.gui.layer.OsmDataLayer;
-import org.openstreetmap.josm.gui.util.GuiHelper;
 import org.openstreetmap.josm.gui.util.ModifierListener;
 import org.openstreetmap.josm.tools.CheckParameterUtil;
@@ -94,6 +93,6 @@
 public class ParallelWayAction extends MapMode implements ModifierListener, MapViewPaintable {
 
-    private static final StringProperty HELPER_LINE_STROKE = new StringProperty(prefKey("stroke.hepler-line"), "1");
-    private static final StringProperty REF_LINE_STROKE = new StringProperty(prefKey("stroke.ref-line"), "1 2 2");
+    private static final CachingProperty<BasicStroke> HELPER_LINE_STROKE = new StrokeProperty(prefKey("stroke.hepler-line"), "1").cached();
+    private static final CachingProperty<BasicStroke> REF_LINE_STROKE = new StrokeProperty(prefKey("stroke.ref-line"), "2 2 3").cached();
 
     // @formatter:off
@@ -147,7 +146,4 @@
     private EastNorth helperLineEnd;
 
-    private transient Stroke helpLineStroke;
-    private transient Stroke refLineStroke;
-
     /**
      * Constructs a new {@code ParallelWayAction}.
@@ -174,7 +170,4 @@
         mv.addMouseMotionListener(this);
         mv.addTemporaryLayer(this);
-
-        helpLineStroke = GuiHelper.getCustomizedStroke(HELPER_LINE_STROKE.get());
-        refLineStroke = GuiHelper.getCustomizedStroke(REF_LINE_STROKE.get());
 
         //// Needed to update the mouse cursor if modifiers are changed when the mouse is motionless
@@ -473,5 +466,5 @@
 
             // FIXME: should clip the line (gets insanely slow when zoomed in on a very long line
-            g.setStroke(refLineStroke);
+            g.setStroke(REF_LINE_STROKE.get());
             g.setColor(mainColor);
             MapPath2D line = new MapPath2D();
@@ -480,5 +473,5 @@
             g.draw(line);
 
-            g.setStroke(helpLineStroke);
+            g.setStroke(HELPER_LINE_STROKE.get());
             g.setColor(mainColor);
             line = new MapPath2D();
Index: /trunk/src/org/openstreetmap/josm/data/osm/visitor/paint/PaintColors.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/data/osm/visitor/paint/PaintColors.java	(revision 10873)
+++ /trunk/src/org/openstreetmap/josm/data/osm/visitor/paint/PaintColors.java	(revision 10874)
@@ -34,4 +34,5 @@
     private final String name;
     private final Color defaultColor;
+    private final ColorProperty baseProperty;
     private final CachingProperty<Color> property;
 
@@ -56,5 +57,6 @@
 
     PaintColors(String name, Color defaultColor) {
-        property = new ColorProperty(name, defaultColor).cached();
+        baseProperty = new ColorProperty(name, defaultColor);
+        property = baseProperty.cached();
         this.name = name;
         this.defaultColor = defaultColor;
@@ -96,3 +98,12 @@
         }
     }
+
+    /**
+     * Get the color property
+     * @return The property that is used to access the color.
+     * @since 10874
+     */
+    public ColorProperty getProperty() {
+        return baseProperty;
+    }
 }
Index: /trunk/src/org/openstreetmap/josm/data/preferences/StrokeProperty.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/data/preferences/StrokeProperty.java	(revision 10874)
+++ /trunk/src/org/openstreetmap/josm/data/preferences/StrokeProperty.java	(revision 10874)
@@ -0,0 +1,111 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.data.preferences;
+
+import java.awt.BasicStroke;
+import java.util.Collections;
+import java.util.List;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+import org.openstreetmap.josm.Main;
+
+/**
+ * A property that stores a {@link BasicStroke}.
+ * @author Michael Zangl
+ * @since 10874
+ */
+public class StrokeProperty extends AbstractToStringProperty<BasicStroke> {
+
+    /**
+     * Create a new stroke property from a string.
+     * @param key The key to use
+     * @param defaultValue The default stroke as string
+     */
+    public StrokeProperty(String key, String defaultValue) {
+        super(key, getFromString(defaultValue));
+    }
+
+    /**
+     * Create a new stroke property from a stroke object.
+     * @param key The key
+     * @param defaultStroke The default stroke.
+     */
+    public StrokeProperty(String key, BasicStroke defaultStroke) {
+        super(key, defaultStroke);
+    }
+
+    @Override
+    protected BasicStroke fromString(String string) {
+        return getFromString(string);
+    }
+
+    @Override
+    protected String toString(BasicStroke t) {
+        StringBuilder string = new StringBuilder();
+        string.append(t.getLineWidth());
+
+        float[] dashes = t.getDashArray();
+        if (dashes != null) {
+            for (float d : dashes) {
+                string.append(' ').append(d);
+            }
+        }
+
+        return string.toString();
+    }
+
+    /**
+     * Return s new BasicStroke object with given thickness and style
+     * @param code = 3.5 -&gt; thickness=3.5px; 3.5 10 5 -&gt; thickness=3.5px, dashed: 10px filled + 5px empty
+     * @return stroke for drawing
+     */
+    public static BasicStroke getFromString(String code) {
+        Pattern floatPattern = Pattern.compile("(\\.\\d+|\\d+(\\.\\d+)?)");
+
+        List<Double> captures = Pattern.compile("[^\\d.]+").splitAsStream(code)
+                .filter(s -> floatPattern.matcher(s).matches())
+                .map(Double::valueOf).collect(Collectors.toList());
+
+        double w = 1;
+        List<Double> dashes = Collections.emptyList();
+        if (!captures.isEmpty()) {
+            w = captures.get(0);
+            dashes = captures.subList(1, captures.size());
+        }
+
+        if (!dashes.isEmpty()) {
+            double sumAbs = dashes.stream().mapToDouble(Math::abs).sum();
+
+            if (sumAbs < 1e-1) {
+                Main.error("Error in stroke dash format (all zeros): " + code);
+                dashes = Collections.emptyList();
+            }
+        }
+
+        int cap;
+        int join;
+        if (w > 1) {
+            // thick stroke
+            cap = BasicStroke.CAP_ROUND;
+            join = BasicStroke.JOIN_ROUND;
+        } else {
+            // thin stroke
+            cap = BasicStroke.CAP_BUTT;
+            join = BasicStroke.JOIN_MITER;
+        }
+
+        return new BasicStroke((float) w, cap, join, 10.0f, toDashArray(dashes), 0.0f);
+    }
+
+    private static float[] toDashArray(List<Double> dashes) {
+        if (dashes.isEmpty()) {
+            return null;
+        } else {
+            float[] array = new float[dashes.size()];
+            for (int i = 0; i < array.length; i++) {
+                array[i] = (float) (double) dashes.get(i);
+            }
+            return array;
+        }
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/gui/MapViewState.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/MapViewState.java	(revision 10873)
+++ /trunk/src/org/openstreetmap/josm/gui/MapViewState.java	(revision 10874)
@@ -523,4 +523,15 @@
             return Math.sqrt(distanceToInViewSq(p2));
         }
+
+        /**
+         * Do a linear interpolation to the other point
+         * @param p1 The other point
+         * @param i The interpolation factor. 0 is at the current point, 1 at the other point.
+         * @return The new point
+         * @since 10874
+         */
+        public MapViewPoint interpolate(MapViewPoint p1, int i) {
+            return new MapViewViewPoint((1 - i) * getInViewX() + i * p1.getInViewX(), (1 - i) * getInViewY() + i * p1.getInViewY());
+        }
     }
 
Index: /trunk/src/org/openstreetmap/josm/gui/util/GuiHelper.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/util/GuiHelper.java	(revision 10873)
+++ /trunk/src/org/openstreetmap/josm/gui/util/GuiHelper.java	(revision 10874)
@@ -4,5 +4,4 @@
 import static org.openstreetmap.josm.tools.I18n.tr;
 
-import java.awt.BasicStroke;
 import java.awt.Color;
 import java.awt.Component;
@@ -50,4 +49,5 @@
 
 import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.preferences.StrokeProperty;
 import org.openstreetmap.josm.gui.ExtendedDialog;
 import org.openstreetmap.josm.gui.widgets.HtmlPanel;
@@ -300,43 +300,8 @@
      * @param code = 3.5 -&gt; thickness=3.5px; 3.5 10 5 -&gt; thickness=3.5px, dashed: 10px filled + 5px empty
      * @return stroke for drawing
+     * @see StrokeProperty
      */
     public static Stroke getCustomizedStroke(String code) {
-        String[] s = code.trim().split("[^\\.0-9]+");
-
-        if (s.length == 0) return new BasicStroke();
-        float w;
-        try {
-            w = Float.parseFloat(s[0]);
-        } catch (NumberFormatException ex) {
-            w = 1.0f;
-        }
-        if (s.length > 1) {
-            float[] dash = new float[s.length-1];
-            float sumAbs = 0;
-            try {
-                for (int i = 0; i < s.length-1; i++) {
-                   dash[i] = Float.parseFloat(s[i+1]);
-                   sumAbs += Math.abs(dash[i]);
-                }
-            } catch (NumberFormatException ex) {
-                Main.error("Error in stroke preference format: "+code);
-                dash = new float[]{5.0f};
-            }
-            if (sumAbs < 1e-1) {
-                Main.error("Error in stroke dash format (all zeros): "+code);
-                return new BasicStroke(w);
-            }
-            // dashed stroke
-            return new BasicStroke(w, BasicStroke.CAP_BUTT,
-                    BasicStroke.JOIN_MITER, 10.0f, dash, 0.0f);
-        } else {
-            if (w > 1) {
-                // thick stroke
-                return new BasicStroke(w, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND);
-            } else {
-                // thin stroke
-                return new BasicStroke(w);
-            }
-        }
+        return StrokeProperty.getFromString(code);
     }
 
Index: /trunk/test/unit/org/openstreetmap/josm/data/preferences/StrokePropertyTest.java
===================================================================
--- /trunk/test/unit/org/openstreetmap/josm/data/preferences/StrokePropertyTest.java	(revision 10874)
+++ /trunk/test/unit/org/openstreetmap/josm/data/preferences/StrokePropertyTest.java	(revision 10874)
@@ -0,0 +1,108 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.data.preferences;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+
+import java.awt.BasicStroke;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.testutils.JOSMTestRules;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+
+/**
+ * Test {@link StrokeProperty}
+ * @author Michael Zangl
+ */
+public class StrokePropertyTest {
+    /**
+     * This is a preference test
+     */
+    @Rule
+    @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD")
+    public JOSMTestRules test = new JOSMTestRules().preferences();
+
+    /**
+     * Test {@link StrokeProperty#get()}
+     */
+    @Test
+    public void testGetValue() {
+        StrokeProperty property = new StrokeProperty("x", "1");
+
+        Main.pref.put("x", "11");
+        BasicStroke bs = property.get();
+        assertWide(bs);
+        assertEquals(11, bs.getLineWidth(), 1e-10);
+        assertEquals(null, bs.getDashArray());
+
+        Main.pref.put("x", ".5");
+        bs = property.get();
+        assertThin(bs);
+        assertEquals(.5, bs.getLineWidth(), 1e-10);
+        assertEquals(null, bs.getDashArray());
+
+        Main.pref.put("x", "2 1");
+        bs = property.get();
+        assertWide(bs);
+        assertEquals(2, bs.getLineWidth(), 1e-10);
+        assertArrayEquals(new float[] {1}, bs.getDashArray(), 1e-10f);
+
+        Main.pref.put("x", "2 0.1 1 10");
+        bs = property.get();
+        assertWide(bs);
+        assertEquals(2, bs.getLineWidth(), 1e-10);
+        assertArrayEquals(new float[] {0.1f, 1, 10}, bs.getDashArray(), 1e-10f);
+
+        Main.pref.put("x", "x");
+        bs = property.get();
+        assertThin(bs);
+        assertEquals(1, bs.getLineWidth(), 1e-10);
+        assertEquals(null, bs.getDashArray());
+
+        // ignore dashes
+        Main.pref.put("x", "11 0 0 0.0001");
+        bs = property.get();
+        assertWide(bs);
+        assertEquals(11, bs.getLineWidth(), 1e-10);
+        assertEquals(null, bs.getDashArray());
+    }
+
+    /**
+     * Test {@link StrokeProperty#put(BasicStroke)}
+     */
+    @Test
+    public void testPutValue() {
+        StrokeProperty property = new StrokeProperty("x", new BasicStroke(12));
+        BasicStroke bs = property.get();
+
+        assertWide(bs);
+        assertEquals(12, bs.getLineWidth(), 1e-10);
+        assertEquals(null, bs.getDashArray());
+
+        property.put(new BasicStroke(2, 0, 0, 1, new float[] {0.1f, 1, 10}, 0));
+        bs = property.get();
+        assertWide(bs);
+        assertEquals(2, bs.getLineWidth(), 1e-10);
+        assertArrayEquals(new float[] {0.1f, 1, 10}, bs.getDashArray(), 1e-10f);
+    }
+
+    private static void assertThin(BasicStroke bs) {
+        assertBase(bs);
+        assertEquals(BasicStroke.CAP_BUTT, bs.getEndCap());
+        assertEquals(BasicStroke.JOIN_MITER, bs.getLineJoin());
+    }
+
+    private static void assertWide(BasicStroke bs) {
+        assertBase(bs);
+        assertEquals(BasicStroke.CAP_ROUND, bs.getEndCap());
+        assertEquals(BasicStroke.JOIN_ROUND, bs.getLineJoin());
+    }
+
+    private static void assertBase(BasicStroke bs) {
+        assertEquals(10, bs.getMiterLimit(), 1e-10);
+        assertEquals(0, bs.getDashPhase(), 1e-10);
+    }
+}
