001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.data.preferences;
003
004import java.awt.BasicStroke;
005import java.util.Collections;
006import java.util.List;
007import java.util.regex.Pattern;
008import java.util.stream.Collectors;
009
010import org.openstreetmap.josm.tools.Logging;
011
012/**
013 * A property that stores a {@link BasicStroke}.
014 * @author Michael Zangl
015 * @since 10874
016 */
017public class StrokeProperty extends AbstractToStringProperty<BasicStroke> {
018
019    /**
020     * Create a new stroke property from a string.
021     * @param key The key to use
022     * @param defaultValue The default stroke as string
023     */
024    public StrokeProperty(String key, String defaultValue) {
025        super(key, getFromString(defaultValue));
026    }
027
028    /**
029     * Create a new stroke property from a stroke object.
030     * @param key The key
031     * @param defaultStroke The default stroke.
032     */
033    public StrokeProperty(String key, BasicStroke defaultStroke) {
034        super(key, defaultStroke);
035    }
036
037    @Override
038    protected BasicStroke fromString(String string) {
039        return getFromString(string);
040    }
041
042    @Override
043    protected String toString(BasicStroke t) {
044        StringBuilder string = new StringBuilder();
045        string.append(t.getLineWidth());
046
047        float[] dashes = t.getDashArray();
048        if (dashes != null) {
049            for (float d : dashes) {
050                string.append(' ').append(d);
051            }
052        }
053
054        return string.toString();
055    }
056
057    /**
058     * Return s new BasicStroke object with given thickness and style
059     * @param code = 3.5 -&gt; thickness=3.5px; 3.5 10 5 -&gt; thickness=3.5px, dashed: 10px filled + 5px empty
060     * @return stroke for drawing
061     */
062    public static BasicStroke getFromString(String code) {
063        Pattern floatPattern = Pattern.compile("(\\.\\d+|\\d+(\\.\\d+)?)");
064
065        List<Double> captures = Pattern.compile("[^\\d.]+").splitAsStream(code)
066                .filter(s -> floatPattern.matcher(s).matches())
067                .map(Double::valueOf).collect(Collectors.toList());
068
069        double w = 1;
070        List<Double> dashes = Collections.emptyList();
071        if (!captures.isEmpty()) {
072            w = captures.get(0);
073            dashes = captures.subList(1, captures.size());
074        }
075
076        if (!dashes.isEmpty()) {
077            double sumAbs = dashes.stream().mapToDouble(Math::abs).sum();
078
079            if (sumAbs < 1e-1) {
080                Logging.error("Error in stroke dash format (all zeros): " + code);
081                dashes = Collections.emptyList();
082            }
083        }
084
085        int cap;
086        int join;
087        if (w > 1) {
088            // thick stroke
089            cap = BasicStroke.CAP_ROUND;
090            join = BasicStroke.JOIN_ROUND;
091        } else {
092            // thin stroke
093            cap = BasicStroke.CAP_BUTT;
094            join = BasicStroke.JOIN_MITER;
095        }
096
097        return new BasicStroke((float) w, cap, join, 10.0f, toDashArray(dashes), 0.0f);
098    }
099
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}