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.Main; 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 -> thickness=3.5px; 3.5 10 5 -> 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 Main.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}