Ticket #13412: patch-draw-action.patch

File patch-draw-action.patch, 46.4 KB (added by michael2402, 6 years ago)
  • src/org/openstreetmap/josm/actions/PreferenceToggleAction.java

    diff --git a/src/org/openstreetmap/josm/actions/PreferenceToggleAction.java b/src/org/openstreetmap/josm/actions/PreferenceToggleAction.java
    index dec48ff..2afa61f 100644
    a b  
    22package org.openstreetmap.josm.actions;
    33
    44import java.awt.event.ActionEvent;
     5
    56import javax.swing.JCheckBoxMenuItem;
     7
    68import org.openstreetmap.josm.Main;
    79import org.openstreetmap.josm.data.Preferences;
    810import org.openstreetmap.josm.data.Preferences.PreferenceChangedListener;
     11import org.openstreetmap.josm.data.preferences.BooleanProperty;
    912
    1013public class PreferenceToggleAction extends JosmAction implements PreferenceChangedListener {
    1114
    1215    private final JCheckBoxMenuItem checkbox;
    13     private final String prefKey;
    14     private final boolean prefDefault;
     16    private final BooleanProperty pref;
    1517
    1618    public PreferenceToggleAction(String name, String tooltip, String prefKey, boolean prefDefault) {
    1719        super(name, null, tooltip, null, false);
    1820        putValue("toolbar", "toggle-" + prefKey);
    19         this.prefKey = prefKey;
    20         this.prefDefault = prefDefault;
    21         this.checkbox = new JCheckBoxMenuItem(this);
    22         this.checkbox.setSelected(Main.pref.getBoolean(prefKey, prefDefault));
    23         Main.pref.addPreferenceChangeListener(this);
     21        this.pref = new BooleanProperty(prefKey, prefDefault);
     22
     23        checkbox = new JCheckBoxMenuItem(this);
     24        checkbox.setSelected(pref.get());
     25        Main.pref.addWeakKeyPreferenceChangeListener(prefKey, this);
    2426    }
    2527
    2628    @Override
    2729    public void actionPerformed(ActionEvent e) {
    28         Main.pref.put(prefKey, checkbox.isSelected());
     30        pref.put(checkbox.isSelected());
    2931    }
    3032
     33    /**
     34     * Get the checkbox that can be used for this action. It can only be used at one place.
     35     * @return The checkbox.
     36     */
    3137    public JCheckBoxMenuItem getCheckbox() {
    3238        return checkbox;
    3339    }
    3440
    3541    @Override
    3642    public void preferenceChanged(Preferences.PreferenceChangeEvent e) {
    37         if (prefKey.equals(e.getKey())) {
    38             checkbox.setSelected(Main.pref.getBoolean(prefKey, prefDefault));
    39         }
     43        checkbox.setSelected(pref.get());
    4044    }
    4145}
  • src/org/openstreetmap/josm/actions/mapmode/DrawAction.java

    diff --git a/src/org/openstreetmap/josm/actions/mapmode/DrawAction.java b/src/org/openstreetmap/josm/actions/mapmode/DrawAction.java
    index 58c0b34..35feecd 100644
    a b import java.awt.Color; 
    1111import java.awt.Cursor;
    1212import java.awt.Graphics2D;
    1313import java.awt.Point;
    14 import java.awt.Stroke;
    1514import java.awt.event.ActionEvent;
    1615import java.awt.event.KeyEvent;
    1716import java.awt.event.MouseEvent;
    import java.util.ArrayList; 
    2019import java.util.Arrays;
    2120import java.util.Collection;
    2221import java.util.Collections;
     22import java.util.Comparator;
    2323import java.util.HashMap;
    2424import java.util.HashSet;
    2525import java.util.Iterator;
    import java.util.LinkedList; 
    2727import java.util.List;
    2828import java.util.Map;
    2929import java.util.Set;
     30import java.util.stream.DoubleStream;
    3031
    3132import javax.swing.AbstractAction;
    3233import javax.swing.JCheckBoxMenuItem;
    import org.openstreetmap.josm.data.osm.WaySegment; 
    5253import org.openstreetmap.josm.data.osm.visitor.paint.ArrowPaintHelper;
    5354import org.openstreetmap.josm.data.osm.visitor.paint.MapPath2D;
    5455import org.openstreetmap.josm.data.osm.visitor.paint.PaintColors;
     56import org.openstreetmap.josm.data.preferences.AbstractToStringProperty;
     57import org.openstreetmap.josm.data.preferences.BooleanProperty;
     58import org.openstreetmap.josm.data.preferences.CachingProperty;
    5559import org.openstreetmap.josm.data.preferences.ColorProperty;
     60import org.openstreetmap.josm.data.preferences.DoubleProperty;
     61import org.openstreetmap.josm.data.preferences.StrokeProperty;
    5662import org.openstreetmap.josm.gui.MainMenu;
    5763import org.openstreetmap.josm.gui.MapFrame;
    5864import org.openstreetmap.josm.gui.MapView;
     65import org.openstreetmap.josm.gui.MapViewState;
    5966import org.openstreetmap.josm.gui.MapViewState.MapViewPoint;
    6067import org.openstreetmap.josm.gui.NavigatableComponent;
    6168import org.openstreetmap.josm.gui.layer.Layer;
    6269import org.openstreetmap.josm.gui.layer.MapViewPaintable;
    6370import org.openstreetmap.josm.gui.layer.OsmDataLayer;
    64 import org.openstreetmap.josm.gui.util.GuiHelper;
    6571import org.openstreetmap.josm.gui.util.KeyPressReleaseListener;
    6672import org.openstreetmap.josm.gui.util.ModifierListener;
    6773import org.openstreetmap.josm.gui.widgets.PopupMenuLauncher;
    public class DrawAction extends MapMode implements MapViewPaintable, SelectionCh 
    8086
    8187    private static final ArrowPaintHelper START_WAY_INDICATOR = new ArrowPaintHelper(Math.toRadians(90), 8);
    8288
     89    private static final CachingProperty<Boolean> USE_REPEATED_SHORTCUT
     90            = new BooleanProperty("draw.anglesnap.toggleOnRepeatedA", true).cached();
     91    private static final CachingProperty<BasicStroke> RUBBER_LINE_STROKE
     92            = new StrokeProperty("draw.stroke.helper-line", "3").cached();
     93
     94    private static final CachingProperty<BasicStroke> HIGHLIGHT_STROKE
     95            = new StrokeProperty("draw.anglesnap.stroke.highlight", "10").cached();
     96    private static final CachingProperty<BasicStroke> HELPER_STROKE
     97            = new StrokeProperty("draw.anglesnap.stroke.helper", "1 4").cached();
     98
     99    private static final CachingProperty<Double> SNAP_ANGLE_TOLERANCE
     100            = new DoubleProperty("draw.anglesnap.tolerance", 5.0).cached();
     101    private static final CachingProperty<Boolean> DRAW_CONSTRUCTION_GEOMETRY
     102            = new BooleanProperty("draw.anglesnap.drawConstructionGeometry", true).cached();
     103    private static final CachingProperty<Boolean> SHOW_PROJECTED_POINT
     104            = new BooleanProperty("draw.anglesnap.drawProjectedPoint", true).cached();
     105    private static final CachingProperty<Boolean> SNAP_TO_PROJECTIONS
     106            = new BooleanProperty("draw.anglesnap.projectionsnap", true).cached();
     107
     108    private static final CachingProperty<Boolean> SHOW_ANGLE
     109            = new BooleanProperty("draw.anglesnap.showAngle", true).cached();
     110
     111    private static final CachingProperty<Color> SNAP_HELPER_COLOR
     112            = new ColorProperty(marktr("draw angle snap"), Color.ORANGE).cached();
     113
     114    private static final CachingProperty<Color> HIGHLIGHT_COLOR
     115            = new ColorProperty(marktr("draw angle snap highlight"), ORANGE_TRANSPARENT).cached();
     116
     117    private static final AbstractToStringProperty<Color> RUBBER_LINE_COLOR
     118            = PaintColors.SELECTED.getProperty().getChildColor(marktr("helper line"));
     119
     120    private static final CachingProperty<Boolean> DRAW_HELPER_LINE
     121            = new BooleanProperty("draw.helper-line", true).cached();
     122    private static final CachingProperty<Boolean> DRAW_TARGET_HIGHLIGHT
     123            = new BooleanProperty("draw.target-highlight", true).cached();
     124    private static final CachingProperty<Double> SNAP_TO_INTERSECTION_THRESHOLD
     125            = new DoubleProperty("edit.snap-intersection-threshold", 10).cached();
     126
    83127    private final Cursor cursorJoinNode;
    84128    private final Cursor cursorJoinWay;
    85129
    public class DrawAction extends MapMode implements MapViewPaintable, SelectionCh 
    96140    // but haven’t been so far. The idea is to compare old and new and only
    97141    // repaint if there are changes.
    98142    private transient Set<OsmPrimitive> newHighlights = new HashSet<>();
    99     private boolean drawHelperLine;
    100143    private boolean wayIsFinished;
    101     private boolean drawTargetHighlight;
    102144    private Point mousePos;
    103145    private Point oldMousePos;
    104     private Color rubberLineColor;
    105146
    106147    private transient Node currentBaseNode;
    107148    private transient Node previousNode;
    public class DrawAction extends MapMode implements MapViewPaintable, SelectionCh 
    116157
    117158    private final SnapChangeAction snapChangeAction;
    118159    private final JCheckBoxMenuItem snapCheckboxMenuItem;
    119     private boolean useRepeatedShortcut;
    120     private transient Stroke rubberLineStroke;
    121160    private static final BasicStroke BASIC_STROKE = new BasicStroke(1);
    122161
    123     private static int snapToIntersectionThreshold;
     162    private Point rightClickPressPos;
    124163
    125164    /**
    126165     * Constructs a new {@code DrawAction}.
    public class DrawAction extends MapMode implements MapViewPaintable, SelectionCh 
    142181        cursorJoinNode = ImageProvider.getCursor("crosshair", "joinnode");
    143182        cursorJoinWay = ImageProvider.getCursor("crosshair", "joinway");
    144183
    145         readPreferences();
    146184        snapHelper.init();
    147         readPreferences();
    148185    }
    149186
    150187    private JCheckBoxMenuItem addMenuItem() {
    public class DrawAction extends MapMode implements MapViewPaintable, SelectionCh 
    165202    private boolean redrawIfRequired() {
    166203        updateStatusLine();
    167204        // repaint required if the helper line is active.
    168         boolean needsRepaint = drawHelperLine && !wayIsFinished;
    169         if (drawTargetHighlight) {
     205        boolean needsRepaint = DRAW_HELPER_LINE.get() && !wayIsFinished;
     206        if (DRAW_TARGET_HIGHLIGHT.get()) {
    170207            // move newHighlights to oldHighlights; only update changed primitives
    171208            for (OsmPrimitive x : newHighlights) {
    172209                if (oldHighlights.contains(x)) {
    public class DrawAction extends MapMode implements MapViewPaintable, SelectionCh 
    184221        // required in order to print correct help text
    185222        oldHighlights = newHighlights;
    186223
    187         if (!needsRepaint && !drawTargetHighlight)
     224        if (!needsRepaint && !DRAW_TARGET_HIGHLIGHT.get())
    188225            return false;
    189226
    190227        // update selection to reflect which way being modified
    public class DrawAction extends MapMode implements MapViewPaintable, SelectionCh 
    245282    }
    246283
    247284    @Override
    248     protected void readPreferences() {
    249         rubberLineColor = new ColorProperty(marktr("helper line"), (Color) null).get();
    250         if (rubberLineColor == null)
    251             rubberLineColor = PaintColors.SELECTED.get();
    252 
    253         rubberLineStroke = GuiHelper.getCustomizedStroke(Main.pref.get("draw.stroke.helper-line", "3"));
    254         drawHelperLine = Main.pref.getBoolean("draw.helper-line", true);
    255         drawTargetHighlight = Main.pref.getBoolean("draw.target-highlight", true);
    256         snapToIntersectionThreshold = Main.pref.getInteger("edit.snap-intersection-threshold", 10);
    257     }
    258 
    259     @Override
    260285    public void exitMode() {
    261286        super.exitMode();
    262287        Main.map.mapView.removeMouseListener(this);
    public class DrawAction extends MapMode implements MapViewPaintable, SelectionCh 
    297322
    298323    @Override
    299324    public void doKeyPressed(KeyEvent e) {
    300         if (!snappingShortcut.isEvent(e) && !(useRepeatedShortcut && getShortcut().isEvent(e)))
     325        if (!snappingShortcut.isEvent(e) && !(USE_REPEATED_SHORTCUT.get() && getShortcut().isEvent(e)))
    301326            return;
    302327        snapHelper.setFixedMode();
    303328        computeHelperLine();
    public class DrawAction extends MapMode implements MapViewPaintable, SelectionCh 
    306331
    307332    @Override
    308333    public void doKeyReleased(KeyEvent e) {
    309         if (!snappingShortcut.isEvent(e) && !(useRepeatedShortcut && getShortcut().isEvent(e)))
     334        if (!snappingShortcut.isEvent(e) && !(USE_REPEATED_SHORTCUT.get() && getShortcut().isEvent(e)))
    310335            return;
    311336        if (ignoreNextKeyRelease) {
    312337            ignoreNextKeyRelease = false;
    public class DrawAction extends MapMode implements MapViewPaintable, SelectionCh 
    352377        removeHighlighting();
    353378    }
    354379
    355     private Point rightClickPressPos;
    356 
    357380    @Override
    358381    public void mousePressed(MouseEvent e) {
    359382        if (e.getButton() == MouseEvent.BUTTON3) {
    public class DrawAction extends MapMode implements MapViewPaintable, SelectionCh 
    590613                newSelection.add(wayToSelect);
    591614            }
    592615        }
     616        if (!extendedWay && !newNode) {
     617            return; // We didn't do anything.
     618        }
     619
     620        String title = getTitle(newNode, n, newSelection, reuseWays, extendedWay);
    593621
     622        Command c = new SequenceCommand(title, cmds);
     623
     624        Main.main.undoRedo.add(c);
     625        if (!wayIsFinished) {
     626            lastUsedNode = n;
     627        }
     628
     629        ds.setSelected(newSelection);
     630
     631        // "viewport following" mode for tracing long features
     632        // from aerial imagery or GPS tracks.
     633        if (Main.map.mapView.viewportFollowing) {
     634            Main.map.mapView.smoothScrollTo(n.getEastNorth());
     635        }
     636        computeHelperLine();
     637        removeHighlighting();
     638    }
     639
     640    private String getTitle(boolean newNode, Node n, Collection<OsmPrimitive> newSelection, List<Way> reuseWays,
     641            boolean extendedWay) {
    594642        String title;
    595643        if (!extendedWay) {
    596             if (!newNode)
    597                 return; // We didn't do anything.
    598             else if (reuseWays.isEmpty()) {
     644            if (reuseWays.isEmpty()) {
    599645                title = tr("Add node");
    600646            } else {
    601647                title = tr("Add node into way");
    public class DrawAction extends MapMode implements MapViewPaintable, SelectionCh 
    612658        } else {
    613659            title = tr("Add node into way and connect");
    614660        }
    615 
    616         Command c = new SequenceCommand(title, cmds);
    617 
    618         Main.main.undoRedo.add(c);
    619         if (!wayIsFinished) {
    620             lastUsedNode = n;
    621         }
    622 
    623         ds.setSelected(newSelection);
    624 
    625         // "viewport following" mode for tracing long features
    626         // from aerial imagery or GPS tracks.
    627         if (n != null && Main.map.mapView.viewportFollowing) {
    628             Main.map.mapView.smoothScrollTo(n.getEastNorth());
    629         }
    630         computeHelperLine();
    631         removeHighlighting();
     661        return title;
    632662    }
    633663
    634664    private void insertNodeIntoAllNearbySegments(List<WaySegment> wss, Node n, Collection<OsmPrimitive> newSelection,
    public class DrawAction extends MapMode implements MapViewPaintable, SelectionCh 
    10321062            // only adjust to intersection if within snapToIntersectionThreshold pixel of mouse click; otherwise
    10331063            // fall through to default action.
    10341064            // (for semi-parallel lines, intersection might be miles away!)
    1035             if (Main.map.mapView.getPoint2D(n).distance(Main.map.mapView.getPoint2D(intersection)) < snapToIntersectionThreshold) {
     1065            if (Main.map.mapView.getPoint2D(n).distance(Main.map.mapView.getPoint2D(intersection)) < SNAP_TO_INTERSECTION_THRESHOLD.get()) {
    10361066                n.setEastNorth(intersection);
    10371067                return;
    10381068            }
    public class DrawAction extends MapMode implements MapViewPaintable, SelectionCh 
    11341164            return;
    11351165
    11361166        Graphics2D g2 = g;
    1137         snapHelper.drawIfNeeded(g2, mv);
    1138         if (!drawHelperLine || wayIsFinished || shift)
     1167        snapHelper.drawIfNeeded(g2, mv.getState());
     1168        if (!DRAW_HELPER_LINE.get() || wayIsFinished || shift)
    11391169            return;
    11401170
    1141         if (!snapHelper.isActive()) { // else use color and stoke from  snapHelper.draw
    1142             g2.setColor(rubberLineColor);
    1143             g2.setStroke(rubberLineStroke);
    1144         } else if (!snapHelper.drawConstructionGeometry)
    1145             return;
     1171        if (!snapHelper.isActive()) {
     1172            g2.setColor(RUBBER_LINE_COLOR.get());
     1173            g2.setStroke(RUBBER_LINE_STROKE.get());
     1174            paintConstructionGeometry(mv, g2);
     1175        } else if (DRAW_CONSTRUCTION_GEOMETRY.get()) {
     1176            // else use color and stoke from  snapHelper.draw
     1177            paintConstructionGeometry(mv, g2);
     1178        }
     1179    }
     1180
     1181    private void paintConstructionGeometry(MapView mv, Graphics2D g2) {
    11461182        MapPath2D b = new MapPath2D();
    11471183        MapViewPoint p1 = mv.getState().getPointFor(getCurrentBaseNode());
    11481184        MapViewPoint p2 = mv.getState().getPointFor(currentMouseEastNorth);
    public class DrawAction extends MapMode implements MapViewPaintable, SelectionCh 
    13041340    }
    13051341
    13061342    private class SnapHelper {
     1343        private static final String DRAW_ANGLESNAP_ANGLES = "draw.anglesnap.angles";
     1344
    13071345        private final class AnglePopupMenu extends JPopupMenu {
    13081346
    13091347            private final JCheckBoxMenuItem repeatedCb = new JCheckBoxMenuItem(
    public class DrawAction extends MapMode implements MapViewPaintable, SelectionCh 
    13111349                @Override
    13121350                public void actionPerformed(ActionEvent e) {
    13131351                    boolean sel = ((JCheckBoxMenuItem) e.getSource()).getState();
    1314                     Main.pref.put("draw.anglesnap.toggleOnRepeatedA", sel);
    1315                     init();
     1352                    USE_REPEATED_SHORTCUT.put(sel);
    13161353                }
    13171354            });
    13181355
    public class DrawAction extends MapMode implements MapViewPaintable, SelectionCh 
    13211358                @Override
    13221359                public void actionPerformed(ActionEvent e) {
    13231360                    boolean sel = ((JCheckBoxMenuItem) e.getSource()).getState();
    1324                     Main.pref.put("draw.anglesnap.drawConstructionGeometry", sel);
    1325                     Main.pref.put("draw.anglesnap.drawProjectedPoint", sel);
    1326                     Main.pref.put("draw.anglesnap.showAngle", sel);
    1327                     init();
     1361                    DRAW_CONSTRUCTION_GEOMETRY.put(sel);
     1362                    SHOW_PROJECTED_POINT.put(sel);
     1363                    SHOW_ANGLE.put(sel);
    13281364                    enableSnapping();
    13291365                }
    13301366            });
    public class DrawAction extends MapMode implements MapViewPaintable, SelectionCh 
    13341370                @Override
    13351371                public void actionPerformed(ActionEvent e) {
    13361372                    boolean sel = ((JCheckBoxMenuItem) e.getSource()).getState();
    1337                     Main.pref.put("draw.anglesnap.projectionsnap", sel);
    1338                     init();
     1373                    SNAP_TO_PROJECTIONS.put(sel);
    13391374                    enableSnapping();
    13401375                }
    13411376            });
    13421377
    13431378            private AnglePopupMenu() {
    1344                 helperCb.setState(Main.pref.getBoolean("draw.anglesnap.drawConstructionGeometry", true));
    1345                 projectionCb.setState(Main.pref.getBoolean("draw.anglesnap.projectionsnapgvff", true));
    1346                 repeatedCb.setState(Main.pref.getBoolean("draw.anglesnap.toggleOnRepeatedA", true));
     1379                helperCb.setState(DRAW_CONSTRUCTION_GEOMETRY.get());
     1380                projectionCb.setState(SNAP_TO_PROJECTIONS.get());
     1381                repeatedCb.setState(USE_REPEATED_SHORTCUT.get());
    13471382                add(repeatedCb);
    13481383                add(helperCb);
    13491384                add(projectionCb);
    public class DrawAction extends MapMode implements MapViewPaintable, SelectionCh 
    13841419        private boolean fixed; // snap angle is fixed
    13851420        private boolean absoluteFix; // snap angle is absolute
    13861421
    1387         private boolean drawConstructionGeometry;
    1388         private boolean showProjectedPoint;
    1389         private boolean showAngle;
    1390 
    1391         private boolean snapToProjections;
    1392 
    13931422        private EastNorth dir2;
    13941423        private EastNorth projected;
    13951424        private String labelText;
    public class DrawAction extends MapMode implements MapViewPaintable, SelectionCh 
    14011430        private EastNorth projectionSource; // point that we are projecting to the line
    14021431
    14031432        private double[] snapAngles;
    1404         private double snapAngleTolerance;
    14051433
    14061434        private double pe, pn; // (pe, pn) - direction of snapping line
    14071435        private double e0, n0; // (e0, n0) - origin of snapping line
    14081436
    14091437        private final String fixFmt = "%d "+tr("FIX");
    1410         private Color snapHelperColor;
    1411         private Color highlightColor;
    1412 
    1413         private Stroke normalStroke;
    1414         private Stroke helperStroke;
    1415         private Stroke highlightStroke;
    14161438
    14171439        private JCheckBoxMenuItem checkBox;
    14181440
    public class DrawAction extends MapMode implements MapViewPaintable, SelectionCh 
    14271449            }
    14281450        };
    14291451
     1452        /**
     1453         * Set the initial state
     1454         */
    14301455        public void init() {
    14311456            snapOn = false;
    14321457            checkBox.setState(snapOn);
    14331458            fixed = false;
    14341459            absoluteFix = false;
    14351460
    1436             Collection<String> angles = Main.pref.getCollection("draw.anglesnap.angles",
    1437                     Arrays.asList("0", "30", "45", "60", "90", "120", "135", "150", "180"));
    1438 
    1439             snapAngles = new double[2*angles.size()];
    1440             int i = 0;
    1441             for (String s: angles) {
    1442                 try {
    1443                     snapAngles[i] = Double.parseDouble(s); i++;
    1444                     snapAngles[i] = 360-Double.parseDouble(s); i++;
    1445                 } catch (NumberFormatException e) {
    1446                     Main.warn("Incorrect number in draw.anglesnap.angles preferences: "+s);
    1447                     snapAngles[i] = 0; i++;
    1448                     snapAngles[i] = 0; i++;
    1449                 }
    1450             }
    1451             snapAngleTolerance = Main.pref.getDouble("draw.anglesnap.tolerance", 5.0);
    1452             drawConstructionGeometry = Main.pref.getBoolean("draw.anglesnap.drawConstructionGeometry", true);
    1453             showProjectedPoint = Main.pref.getBoolean("draw.anglesnap.drawProjectedPoint", true);
    1454             snapToProjections = Main.pref.getBoolean("draw.anglesnap.projectionsnap", true);
    1455 
    1456             showAngle = Main.pref.getBoolean("draw.anglesnap.showAngle", true);
    1457             useRepeatedShortcut = Main.pref.getBoolean("draw.anglesnap.toggleOnRepeatedA", true);
     1461            computeSnapAngles();
     1462            Main.pref.addWeakKeyPreferenceChangeListener(DRAW_ANGLESNAP_ANGLES, e -> this.computeSnapAngles());
     1463        }
    14581464
    1459             normalStroke = rubberLineStroke;
    1460             snapHelperColor = new ColorProperty(marktr("draw angle snap"), Color.ORANGE).get();
     1465        private void computeSnapAngles() {
     1466            snapAngles = Main.pref.getCollection(DRAW_ANGLESNAP_ANGLES,
     1467                    Arrays.asList("0", "30", "45", "60", "90", "120", "135", "150", "180"))
     1468                    .stream()
     1469                    .mapToDouble(this::parseSnapAngle)
     1470                    .flatMap(s -> DoubleStream.of(s, 360-s))
     1471                    .toArray();
     1472        }
    14611473
    1462             highlightColor = new ColorProperty(marktr("draw angle snap highlight"), ORANGE_TRANSPARENT).get();
    1463             highlightStroke = GuiHelper.getCustomizedStroke(Main.pref.get("draw.anglesnap.stroke.highlight", "10"));
    1464             helperStroke = GuiHelper.getCustomizedStroke(Main.pref.get("draw.anglesnap.stroke.helper", "1 4"));
     1474        private double parseSnapAngle(String string) {
     1475            try {
     1476                return Double.parseDouble(string);
     1477            } catch (NumberFormatException e) {
     1478                Main.warn("Incorrect number in draw.anglesnap.angles preferences: {0}", string);
     1479                return 0;
     1480            }
    14651481        }
    14661482
     1483        /**
     1484         * Save the snap angles
     1485         * @param angles The angles
     1486         */
    14671487        public void saveAngles(String ... angles) {
    1468             Main.pref.putCollection("draw.anglesnap.angles", Arrays.asList(angles));
     1488            Main.pref.putCollection(DRAW_ANGLESNAP_ANGLES, Arrays.asList(angles));
    14691489        }
    14701490
    14711491        public void setMenuCheckBox(JCheckBoxMenuItem checkBox) {
    14721492            this.checkBox = checkBox;
    14731493        }
    14741494
    1475         public void drawIfNeeded(Graphics2D g2, MapView mv) {
     1495        /**
     1496         * Draw the snap hint line.
     1497         * @param g2
     1498         * @param mv
     1499         */
     1500        public void drawIfNeeded(Graphics2D g2, MapViewState mv) {
    14761501            if (!snapOn || !active)
    14771502                return;
    1478             MapViewPoint p1 = mv.getState().getPointFor(getCurrentBaseNode());
    1479             MapViewPoint p2 = mv.getState().getPointFor(dir2);
    1480             MapViewPoint p3 = mv.getState().getPointFor(projected);
    1481             if (drawConstructionGeometry) {
    1482                 g2.setColor(snapHelperColor);
    1483                 g2.setStroke(helperStroke);
     1503            MapViewPoint p1 = mv.getPointFor(getCurrentBaseNode());
     1504            MapViewPoint p2 = mv.getPointFor(dir2);
     1505            MapViewPoint p3 = mv.getPointFor(projected);
     1506            if (DRAW_CONSTRUCTION_GEOMETRY.get()) {
     1507                g2.setColor(SNAP_HELPER_COLOR.get());
     1508                g2.setStroke(HELPER_STROKE.get());
    14841509
    14851510                MapPath2D b = new MapPath2D();
    14861511                b.moveTo(p2);
    14871512                if (absoluteFix) {
    1488                     b.lineTo(2d*p1.getInViewX()-p2.getInViewX(), 2d*p1.getInViewY()-p2.getInViewY()); // bi-directional line
     1513                    b.lineTo(p2.interpolate(p1, 2)); // bi-directional line
    14891514                } else {
    14901515                    b.lineTo(p3);
    14911516                }
    14921517                g2.draw(b);
    14931518            }
    14941519            if (projectionSource != null) {
    1495                 g2.setColor(snapHelperColor);
    1496                 g2.setStroke(helperStroke);
     1520                g2.setColor(SNAP_HELPER_COLOR.get());
     1521                g2.setStroke(HELPER_STROKE.get());
    14971522                MapPath2D b = new MapPath2D();
    14981523                b.moveTo(p3);
    1499                 b.lineTo(mv.getState().getPointFor(projectionSource));
     1524                b.lineTo(mv.getPointFor(projectionSource));
    15001525                g2.draw(b);
    15011526            }
    15021527
    15031528            if (customBaseHeading >= 0) {
    1504                 g2.setColor(highlightColor);
    1505                 g2.setStroke(highlightStroke);
     1529                g2.setColor(HIGHLIGHT_COLOR.get());
     1530                g2.setStroke(HIGHLIGHT_STROKE.get());
    15061531                MapPath2D b = new MapPath2D();
    1507                 b.moveTo(mv.getState().getPointFor(segmentPoint1));
    1508                 b.lineTo(mv.getState().getPointFor(segmentPoint2));
     1532                b.moveTo(mv.getPointFor(segmentPoint1));
     1533                b.lineTo(mv.getPointFor(segmentPoint2));
    15091534                g2.draw(b);
    15101535            }
    15111536
    1512             g2.setColor(rubberLineColor);
    1513             g2.setStroke(normalStroke);
     1537            g2.setColor(RUBBER_LINE_COLOR.get());
     1538            g2.setStroke(RUBBER_LINE_STROKE.get());
    15141539            MapPath2D b = new MapPath2D();
    15151540            b.moveTo(p1);
    15161541            b.lineTo(p3);
    15171542            g2.draw(b);
    15181543
    15191544            g2.drawString(labelText, (int) p3.getInViewX()-5, (int) p3.getInViewY()+20);
    1520             if (showProjectedPoint) {
    1521                 g2.setStroke(normalStroke);
     1545            if (SHOW_PROJECTED_POINT.get()) {
     1546                g2.setStroke(RUBBER_LINE_STROKE.get());
    15221547                g2.drawOval((int) p3.getInViewX()-5, (int) p3.getInViewY()-5, 10, 10); // projected point
    15231548            }
    15241549
    1525             g2.setColor(snapHelperColor);
    1526             g2.setStroke(helperStroke);
     1550            g2.setColor(SNAP_HELPER_COLOR.get());
     1551            g2.setStroke(HELPER_STROKE.get());
    15271552        }
    15281553
    1529         /* If mouse position is close to line at 15-30-45-... angle, remembers this direction
     1554        /**
     1555         *  If mouse position is close to line at 15-30-45-... angle, remembers this direction
     1556         * @param currentEN Current position
     1557         * @param baseHeading The heading
     1558         * @param curHeading The current mouse heading
    15301559         */
    15311560        public void checkAngleSnapping(EastNorth currentEN, double baseHeading, double curHeading) {
    15321561            EastNorth p0 = getCurrentBaseNode().getEastNorth();
    public class DrawAction extends MapMode implements MapViewPaintable, SelectionCh 
    15501579                    active = true;
    15511580                } else {
    15521581                    nearestAngle = getNearestAngle(angle);
    1553                     if (getAngleDelta(nearestAngle, angle) < snapAngleTolerance) {
     1582                    if (getAngleDelta(nearestAngle, angle) < SNAP_ANGLE_TOLERANCE.get()) {
    15541583                        active = customBaseHeading >= 0 || Math.abs(nearestAngle - 180) > 1e-3;
    15551584                        // if angle is to previous segment, exclude 180 degrees
    15561585                        lastAngle = nearestAngle;
    public class DrawAction extends MapMode implements MapViewPaintable, SelectionCh 
    15961625        }
    15971626
    15981627        private void buildLabelText(double nearestAngle) {
    1599             if (showAngle) {
     1628            if (SHOW_ANGLE.get()) {
    16001629                if (fixed) {
    16011630                    if (absoluteFix) {
    16021631                        labelText = "=";
    public class DrawAction extends MapMode implements MapViewPaintable, SelectionCh 
    16191648            }
    16201649        }
    16211650
     1651        /**
     1652         * Gets a snap point close to p. Stores the result for display.
     1653         * @param p The point
     1654         * @return The snap point close to p.
     1655         */
    16221656        public EastNorth getSnapPoint(EastNorth p) {
    16231657            if (!active)
    16241658                return p;
    public class DrawAction extends MapMode implements MapViewPaintable, SelectionCh 
    16321666            } //  do not go backward!
    16331667
    16341668            projectionSource = null;
    1635             if (snapToProjections) {
     1669            if (SNAP_TO_PROJECTIONS.get()) {
    16361670                DataSet ds = getLayerManager().getEditDataSet();
    16371671                Collection<Way> selectedWays = ds.getSelectedWays();
    16381672                if (selectedWays.size() == 1) {
    public class DrawAction extends MapMode implements MapViewPaintable, SelectionCh 
    16671701            return projected;
    16681702        }
    16691703
     1704        /**
     1705         * Disables snapping
     1706         */
    16701707        public void noSnapNow() {
    16711708            active = false;
    16721709            dir2 = null;
    public class DrawAction extends MapMode implements MapViewPaintable, SelectionCh 
    16901727            customBaseHeading = hdg;
    16911728        }
    16921729
     1730        /**
     1731         * Enable snapping.
     1732         */
    16931733        private void enableSnapping() {
    16941734            snapOn = true;
    16951735            checkBox.setState(snapOn);
    public class DrawAction extends MapMode implements MapViewPaintable, SelectionCh 
    17261766        }
    17271767
    17281768        private double getNearestAngle(double angle) {
    1729             double delta, minDelta = 1e5, bestAngle = 0.0;
    1730             for (double snapAngle : snapAngles) {
    1731                 delta = getAngleDelta(angle, snapAngle);
    1732                 if (delta < minDelta) {
    1733                     minDelta = delta;
    1734                     bestAngle = snapAngle;
    1735                 }
    1736             }
     1769            double bestAngle = DoubleStream.of(snapAngles).boxed()
     1770                    .min(Comparator.comparing(snapAngle -> getAngleDelta(angle, snapAngle))).orElse(0.0);
     1771
    17371772            if (Math.abs(bestAngle-360) < 1e-3) {
    17381773                bestAngle = 0;
    17391774            }
  • src/org/openstreetmap/josm/actions/mapmode/ParallelWayAction.java

    diff --git a/src/org/openstreetmap/josm/actions/mapmode/ParallelWayAction.java b/src/org/openstreetmap/josm/actions/mapmode/ParallelWayAction.java
    index 68a0f6f..16f985f 100644
    a b import static org.openstreetmap.josm.gui.help.HelpUtil.ht; 
    55import static org.openstreetmap.josm.tools.I18n.marktr;
    66import static org.openstreetmap.josm.tools.I18n.tr;
    77
     8import java.awt.BasicStroke;
    89import java.awt.Color;
    910import java.awt.Cursor;
    1011import java.awt.Graphics2D;
    1112import java.awt.Point;
    12 import java.awt.Stroke;
    1313import java.awt.event.KeyEvent;
    1414import java.awt.event.MouseEvent;
    1515import java.util.Collection;
    import org.openstreetmap.josm.data.preferences.CachingProperty; 
    4040import org.openstreetmap.josm.data.preferences.ColorProperty;
    4141import org.openstreetmap.josm.data.preferences.DoubleProperty;
    4242import org.openstreetmap.josm.data.preferences.IntegerProperty;
    43 import org.openstreetmap.josm.data.preferences.StringProperty;
     43import org.openstreetmap.josm.data.preferences.StrokeProperty;
    4444import org.openstreetmap.josm.gui.MapFrame;
    4545import org.openstreetmap.josm.gui.MapView;
    4646import org.openstreetmap.josm.gui.Notification;
    4747import org.openstreetmap.josm.gui.layer.Layer;
    4848import org.openstreetmap.josm.gui.layer.MapViewPaintable;
    4949import org.openstreetmap.josm.gui.layer.OsmDataLayer;
    50 import org.openstreetmap.josm.gui.util.GuiHelper;
    5150import org.openstreetmap.josm.gui.util.ModifierListener;
    5251import org.openstreetmap.josm.tools.CheckParameterUtil;
    5352import org.openstreetmap.josm.tools.Geometry;
    import org.openstreetmap.josm.tools.Shortcut; 
    9392 */
    9493public class ParallelWayAction extends MapMode implements ModifierListener, MapViewPaintable {
    9594
    96     private static final StringProperty HELPER_LINE_STROKE = new StringProperty(prefKey("stroke.hepler-line"), "1");
    97     private static final StringProperty REF_LINE_STROKE = new StringProperty(prefKey("stroke.ref-line"), "1 2 2");
     95    private static final CachingProperty<BasicStroke> HELPER_LINE_STROKE = new StrokeProperty(prefKey("stroke.hepler-line"), "1").cached();
     96    private static final CachingProperty<BasicStroke> REF_LINE_STROKE    = new StrokeProperty(prefKey("stroke.ref-line"), "2 2 3").cached();
    9897
    9998    // @formatter:off
    10099    // CHECKSTYLE.OFF: SingleSpaceSeparator
    public class ParallelWayAction extends MapMode implements ModifierListener, MapV 
    146145    private EastNorth helperLineStart;
    147146    private EastNorth helperLineEnd;
    148147
    149     private transient Stroke helpLineStroke;
    150     private transient Stroke refLineStroke;
    151 
    152148    /**
    153149     * Constructs a new {@code ParallelWayAction}.
    154150     * @param mapFrame Map frame
    public class ParallelWayAction extends MapMode implements ModifierListener, MapV 
    174170        mv.addMouseMotionListener(this);
    175171        mv.addTemporaryLayer(this);
    176172
    177         helpLineStroke = GuiHelper.getCustomizedStroke(HELPER_LINE_STROKE.get());
    178         refLineStroke = GuiHelper.getCustomizedStroke(REF_LINE_STROKE.get());
    179 
    180173        //// Needed to update the mouse cursor if modifiers are changed when the mouse is motionless
    181174        Main.map.keyDetector.addModifierListener(this);
    182175        sourceWays = new LinkedHashSet<>(getLayerManager().getEditDataSet().getSelectedWays());
    public class ParallelWayAction extends MapMode implements ModifierListener, MapV 
    472465            }
    473466
    474467            // FIXME: should clip the line (gets insanely slow when zoomed in on a very long line
    475             g.setStroke(refLineStroke);
     468            g.setStroke(REF_LINE_STROKE.get());
    476469            g.setColor(mainColor);
    477470            MapPath2D line = new MapPath2D();
    478471            line.moveTo(mv.getState().getPointFor(referenceSegment.getFirstNode()));
    479472            line.lineTo(mv.getState().getPointFor(referenceSegment.getSecondNode()));
    480473            g.draw(line);
    481474
    482             g.setStroke(helpLineStroke);
     475            g.setStroke(HELPER_LINE_STROKE.get());
    483476            g.setColor(mainColor);
    484477            line = new MapPath2D();
    485478            line.moveTo(mv.getState().getPointFor(helperLineStart));
  • src/org/openstreetmap/josm/data/osm/visitor/paint/PaintColors.java

    diff --git a/src/org/openstreetmap/josm/data/osm/visitor/paint/PaintColors.java b/src/org/openstreetmap/josm/data/osm/visitor/paint/PaintColors.java
    index 8f277b4..c5654e7 100644
    a b public enum PaintColors { 
    3333
    3434    private final String name;
    3535    private final Color defaultColor;
     36    private final ColorProperty baseProperty;
    3637    private final CachingProperty<Color> property;
    3738
    3839    private static volatile Color backgroundColorCache;
    public enum PaintColors { 
    5556    }
    5657
    5758    PaintColors(String name, Color defaultColor) {
    58         property = new ColorProperty(name, defaultColor).cached();
     59        baseProperty = new ColorProperty(name, defaultColor);
     60        property = baseProperty.cached();
    5961        this.name = name;
    6062        this.defaultColor = defaultColor;
    6163    }
    public enum PaintColors { 
    9597            return backgroundColorCache;
    9698        }
    9799    }
     100
     101    /**
     102     * Get the color property
     103     * @return The property that is used to access the color.
     104     */
     105    public ColorProperty getProperty() {
     106        return baseProperty;
     107    }
    98108}
  • new file src/org/openstreetmap/josm/data/preferences/StrokeProperty.java

    diff --git a/src/org/openstreetmap/josm/data/preferences/StrokeProperty.java b/src/org/openstreetmap/josm/data/preferences/StrokeProperty.java
    new file mode 100644
    index 0000000..574c819
    - +  
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.data.preferences;
     3
     4import java.awt.BasicStroke;
     5import java.util.Collections;
     6import java.util.List;
     7import java.util.regex.Pattern;
     8import java.util.stream.Collectors;
     9
     10import org.openstreetmap.josm.Main;
     11
     12/**
     13 * A property that stores a {@link BasicStroke}
     14 * @author Michael Zangl
     15 * @since xxx
     16 */
     17public class StrokeProperty extends AbstractToStringProperty<BasicStroke> {
     18
     19    /**
     20     * Create a new stroke property from a string.
     21     * @param key The key to use
     22     * @param defaultValue The default stroke as string
     23     */
     24    public StrokeProperty(String key, String defaultValue) {
     25        super(key, getFromString(defaultValue));
     26    }
     27
     28    /**
     29     * Create a new stroke property from a stroke object.
     30     * @param key The key
     31     * @param defaultStroke The default stroke.
     32     */
     33    public StrokeProperty(String key, BasicStroke defaultStroke) {
     34        super(key, defaultStroke);
     35    }
     36
     37    @Override
     38    protected BasicStroke fromString(String string) {
     39        return getFromString(string);
     40    }
     41
     42    @Override
     43    protected String toString(BasicStroke t) {
     44        StringBuilder string = new StringBuilder();
     45        string.append(t.getLineWidth());
     46
     47        float[] dashes = t.getDashArray();
     48        if (dashes != null) {
     49            for (float d : dashes) {
     50                string.append(' ').append(d);
     51            }
     52        }
     53
     54        return string.toString();
     55    }
     56
     57    /**
     58     * Return s new BasicStroke object with given thickness and style
     59     * @param code = 3.5 -&gt; thickness=3.5px; 3.5 10 5 -&gt; thickness=3.5px, dashed: 10px filled + 5px empty
     60     * @return stroke for drawing
     61     */
     62    public static BasicStroke getFromString(String code) {
     63        Pattern floatPattern = Pattern.compile("(\\.\\d+|\\d+(\\.\\d+)?)");
     64
     65        List<Double> captures = Pattern.compile("[^\\d.]+").splitAsStream(code)
     66                .filter(s -> floatPattern.matcher(s).matches())
     67                .map(Double::valueOf).collect(Collectors.toList());
     68
     69        double w = 1;
     70        List<Double> dashes = Collections.emptyList();
     71        if (!captures.isEmpty()) {
     72            w = captures.get(0);
     73            dashes = captures.subList(1, captures.size());
     74        }
     75
     76        if (!dashes.isEmpty()) {
     77            double sumAbs = dashes.stream().mapToDouble(Math::abs).sum();
     78
     79            if (sumAbs < 1e-1) {
     80                Main.error("Error in stroke dash format (all zeros): " + code);
     81                dashes = Collections.emptyList();
     82            }
     83        }
     84
     85        int cap;
     86        int join;
     87        if (w > 1) {
     88            // thick stroke
     89            cap = BasicStroke.CAP_ROUND;
     90            join = BasicStroke.JOIN_ROUND;
     91        } else {
     92            // thin stroke
     93            cap = BasicStroke.CAP_BUTT;
     94            join = BasicStroke.JOIN_MITER;
     95        }
     96
     97        return new BasicStroke((float) w, cap, join, 10.0f, toDashArray(dashes), 0.0f);
     98    }
     99
     100    private static float[] toDashArray(List<Double> dashes) {
     101        if (dashes.isEmpty()) {
     102            return null;
     103        } else {
     104            float[] array = new float[dashes.size()];
     105            for (int i = 0; i < array.length; i++) {
     106                array[i] = (float) (double) dashes.get(i);
     107            }
     108            return array;
     109        }
     110    }
     111
     112}
  • src/org/openstreetmap/josm/gui/MapViewState.java

    diff --git a/src/org/openstreetmap/josm/gui/MapViewState.java b/src/org/openstreetmap/josm/gui/MapViewState.java
    index 97e6037..9969eb0 100644
    a b public final class MapViewState { 
    522522        public double distanceToInView(MapViewPoint p2) {
    523523            return Math.sqrt(distanceToInViewSq(p2));
    524524        }
     525
     526        /**
     527         * Do a linear interpolation to the other point
     528         * @param p1 The other point
     529         * @param i The interpolation factor. 0 is at the current point, 1 at the other point.
     530         * @return The new point
     531         */
     532        public MapViewPoint interpolate(MapViewPoint p1, int i) {
     533            return new MapViewViewPoint((1 - i) * getInViewX() + i * p1.getInViewX(), (1 - i) * getInViewY() + i * p1.getInViewY());
     534        }
    525535    }
    526536
    527537    private class MapViewViewPoint extends MapViewPoint {
  • src/org/openstreetmap/josm/gui/util/GuiHelper.java

    diff --git a/src/org/openstreetmap/josm/gui/util/GuiHelper.java b/src/org/openstreetmap/josm/gui/util/GuiHelper.java
    index d964701..9f59dc3 100644
    a b package org.openstreetmap.josm.gui.util; 
    33
    44import static org.openstreetmap.josm.tools.I18n.tr;
    55
    6 import java.awt.BasicStroke;
    76import java.awt.Color;
    87import java.awt.Component;
    98import java.awt.Container;
    import javax.swing.UIManager; 
    4948import javax.swing.plaf.FontUIResource;
    5049
    5150import org.openstreetmap.josm.Main;
     51import org.openstreetmap.josm.data.preferences.StrokeProperty;
    5252import org.openstreetmap.josm.gui.ExtendedDialog;
    5353import org.openstreetmap.josm.gui.widgets.HtmlPanel;
    5454import org.openstreetmap.josm.tools.CheckParameterUtil;
    public final class GuiHelper { 
    299299     * Return s new BasicStroke object with given thickness and style
    300300     * @param code = 3.5 -&gt; thickness=3.5px; 3.5 10 5 -&gt; thickness=3.5px, dashed: 10px filled + 5px empty
    301301     * @return stroke for drawing
     302     * @see StrokeProperty
    302303     */
    303304    public static Stroke getCustomizedStroke(String code) {
    304         String[] s = code.trim().split("[^\\.0-9]+");
    305 
    306         if (s.length == 0) return new BasicStroke();
    307         float w;
    308         try {
    309             w = Float.parseFloat(s[0]);
    310         } catch (NumberFormatException ex) {
    311             w = 1.0f;
    312         }
    313         if (s.length > 1) {
    314             float[] dash = new float[s.length-1];
    315             float sumAbs = 0;
    316             try {
    317                 for (int i = 0; i < s.length-1; i++) {
    318                    dash[i] = Float.parseFloat(s[i+1]);
    319                    sumAbs += Math.abs(dash[i]);
    320                 }
    321             } catch (NumberFormatException ex) {
    322                 Main.error("Error in stroke preference format: "+code);
    323                 dash = new float[]{5.0f};
    324             }
    325             if (sumAbs < 1e-1) {
    326                 Main.error("Error in stroke dash format (all zeros): "+code);
    327                 return new BasicStroke(w);
    328             }
    329             // dashed stroke
    330             return new BasicStroke(w, BasicStroke.CAP_BUTT,
    331                     BasicStroke.JOIN_MITER, 10.0f, dash, 0.0f);
    332         } else {
    333             if (w > 1) {
    334                 // thick stroke
    335                 return new BasicStroke(w, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND);
    336             } else {
    337                 // thin stroke
    338                 return new BasicStroke(w);
    339             }
    340         }
     305        return StrokeProperty.getFromString(code);
    341306    }
    342307
    343308    /**
  • new file test/unit/org/openstreetmap/josm/data/preferences/StrokePropertyTest.java

    diff --git a/test/unit/org/openstreetmap/josm/data/preferences/StrokePropertyTest.java b/test/unit/org/openstreetmap/josm/data/preferences/StrokePropertyTest.java
    new file mode 100644
    index 0000000..ba7e68b
    - +  
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.data.preferences;
     3
     4import static org.junit.Assert.assertArrayEquals;
     5import static org.junit.Assert.assertEquals;
     6
     7import java.awt.BasicStroke;
     8
     9import org.junit.Rule;
     10import org.junit.Test;
     11import org.openstreetmap.josm.Main;
     12import org.openstreetmap.josm.testutils.JOSMTestRules;
     13
     14import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
     15
     16/**
     17 * Test {@link StrokeProperty}
     18 * @author Michael Zangl
     19 * @since xxx
     20 */
     21public class StrokePropertyTest {
     22    /**
     23     * This is a preference test
     24     */
     25    @Rule
     26    @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD")
     27    public JOSMTestRules test = new JOSMTestRules().preferences();
     28
     29    /**
     30     * Test {@link StrokeProperty#get()}
     31     */
     32    @Test
     33    public void testGetValue() {
     34        StrokeProperty property = new StrokeProperty("x", "1");
     35
     36        Main.pref.put("x", "11");
     37        BasicStroke bs = property.get();
     38        assertWide(bs);
     39        assertEquals(11, bs.getLineWidth(), 1e-10);
     40        assertEquals(null, bs.getDashArray());
     41
     42        Main.pref.put("x", ".5");
     43        bs = property.get();
     44        assertThin(bs);
     45        assertEquals(.5, bs.getLineWidth(), 1e-10);
     46        assertEquals(null, bs.getDashArray());
     47
     48        Main.pref.put("x", "2 1");
     49        bs = property.get();
     50        assertWide(bs);
     51        assertEquals(2, bs.getLineWidth(), 1e-10);
     52        assertArrayEquals(new float[] {1}, bs.getDashArray(), 1e-10f);
     53
     54        Main.pref.put("x", "2 0.1 1 10");
     55        bs = property.get();
     56        assertWide(bs);
     57        assertEquals(2, bs.getLineWidth(), 1e-10);
     58        assertArrayEquals(new float[] {0.1f, 1, 10}, bs.getDashArray(), 1e-10f);
     59
     60        Main.pref.put("x", "x");
     61        bs = property.get();
     62        assertThin(bs);
     63        assertEquals(1, bs.getLineWidth(), 1e-10);
     64        assertEquals(null, bs.getDashArray());
     65
     66        // ignore dashes
     67        Main.pref.put("x", "11 0 0 0.0001");
     68        bs = property.get();
     69        assertWide(bs);
     70        assertEquals(11, bs.getLineWidth(), 1e-10);
     71        assertEquals(null, bs.getDashArray());
     72    }
     73
     74    /**
     75     * Test {@link StrokeProperty#put(BasicStroke)}
     76     */
     77    @Test
     78    public void testPutValue() {
     79        StrokeProperty property = new StrokeProperty("x", new BasicStroke(12));
     80        BasicStroke bs = property.get();
     81
     82        assertWide(bs);
     83        assertEquals(12, bs.getLineWidth(), 1e-10);
     84        assertEquals(null, bs.getDashArray());
     85
     86        property.put(new BasicStroke(2, 0, 0, 1, new float[] {0.1f, 1, 10}, 0));
     87        bs = property.get();
     88        assertWide(bs);
     89        assertEquals(2, bs.getLineWidth(), 1e-10);
     90        assertArrayEquals(new float[] {0.1f, 1, 10}, bs.getDashArray(), 1e-10f);
     91    }
     92
     93    private void assertThin(BasicStroke bs) {
     94        assertBase(bs);
     95        assertEquals(BasicStroke.CAP_BUTT, bs.getEndCap());
     96        assertEquals(BasicStroke.JOIN_MITER, bs.getLineJoin());
     97    }
     98
     99    private void assertWide(BasicStroke bs) {
     100        assertBase(bs);
     101        assertEquals(BasicStroke.CAP_ROUND, bs.getEndCap());
     102        assertEquals(BasicStroke.JOIN_ROUND, bs.getLineJoin());
     103    }
     104
     105    private void assertBase(BasicStroke bs) {
     106        assertEquals(10, bs.getMiterLimit(), 1e-10);
     107        assertEquals(0, bs.getDashPhase(), 1e-10);
     108    }
     109}