diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatLaf.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatLaf.java index b5801a41..4781957a 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatLaf.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatLaf.java @@ -784,14 +784,17 @@ public abstract class FlatLaf *

* See: https://www.formdev.com/flatlaf/properties-files/ * - * @param key the key, which is used to determine the value type + * @param key the key, which is used to determine the value type if parameter {@code valueType} is {@code null} * @param value the value string + * @param valueType the expected value type, or {@code null} * @return the binary value * @throws IllegalArgumentException on syntax errors * @since TODO */ - public static Object parseDefaultsValue( String key, String value ) throws IllegalArgumentException { - return UIDefaultsLoader.parseValue( key, value ); + public static Object parseDefaultsValue( String key, String value, Class valueType ) + throws IllegalArgumentException + { + return UIDefaultsLoader.parseValue( key, value, valueType ); } private static void reSetLookAndFeel() { diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/IntelliJTheme.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/IntelliJTheme.java index 5a2fa716..554f7d2b 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/IntelliJTheme.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/IntelliJTheme.java @@ -338,7 +338,7 @@ public class IntelliJTheme // parse value try { - uiValue = UIDefaultsLoader.parseValue( key, valueStr ); + uiValue = UIDefaultsLoader.parseValue( key, valueStr, null ); } catch( RuntimeException ex ) { UIDefaultsLoader.logParseError( key, valueStr, ex, false ); return; // ignore invalid value diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/UIDefaultsLoader.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/UIDefaultsLoader.java index f71cf72c..9a1e9702 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/UIDefaultsLoader.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/UIDefaultsLoader.java @@ -33,8 +33,10 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Properties; import java.util.function.Function; +import javax.swing.Icon; import javax.swing.UIDefaults; import javax.swing.UIManager; +import javax.swing.border.Border; import javax.swing.UIDefaults.ActiveValue; import javax.swing.UIDefaults.LazyValue; import javax.swing.plaf.ColorUIResource; @@ -241,7 +243,7 @@ class UIDefaultsLoader String value = resolveValue( (String) e.getValue(), propertiesGetter ); try { - defaults.put( key, parseValue( key, value, null, resolver, addonClassLoaders ) ); + defaults.put( key, parseValue( key, value, null, null, resolver, addonClassLoaders ) ); } catch( RuntimeException ex ) { logParseError( key, value, ex, true ); } @@ -292,12 +294,13 @@ class UIDefaultsLoader SCALEDINTEGER, SCALEDFLOAT, SCALEDINSETS, SCALEDDIMENSION, INSTANCE, CLASS, GRAYFILTER, NULL, LAZY } private static ValueType[] tempResultValueType = new ValueType[1]; + private static Map, ValueType> javaValueTypes; - static Object parseValue( String key, String value ) { - return parseValue( key, value, null, v -> v, Collections.emptyList() ); + static Object parseValue( String key, String value, Class valueType ) { + return parseValue( key, value, valueType, null, v -> v, Collections.emptyList() ); } - static Object parseValue( String key, String value, ValueType[] resultValueType, + static Object parseValue( String key, String value, Class javaValueType, ValueType[] resultValueType, Function resolver, List addonClassLoaders ) { if( resultValueType == null ) @@ -305,71 +308,106 @@ class UIDefaultsLoader value = value.trim(); - // null, false, true - switch( value ) { - case "null": resultValueType[0] = ValueType.NULL; return null; - case "false": resultValueType[0] = ValueType.BOOLEAN; return false; - case "true": resultValueType[0] = ValueType.BOOLEAN; return true; - } - - // check for function "lazy" - // Syntax: lazy(uiKey) - if( value.startsWith( "lazy(" ) && value.endsWith( ")" ) ) { - resultValueType[0] = ValueType.LAZY; - String uiKey = value.substring( 5, value.length() - 1 ).trim(); - return (LazyValue) t -> { - return lazyUIManagerGet( uiKey ); - }; + // null + if( value.equals( "null" ) ) { + resultValueType[0] = ValueType.NULL; + return null; } ValueType valueType = ValueType.UNKNOWN; - // check whether value type is specified in the value - if( value.startsWith( "#" ) ) - valueType = ValueType.COLOR; - else if( value.startsWith( "\"" ) && value.endsWith( "\"" ) ) { - valueType = ValueType.STRING; - value = value.substring( 1, value.length() - 1 ); - } else if( value.startsWith( TYPE_PREFIX ) ) { - int end = value.indexOf( TYPE_PREFIX_END ); - if( end != -1 ) { - try { - String typeStr = value.substring( TYPE_PREFIX.length(), end ); - valueType = ValueType.valueOf( typeStr.toUpperCase( Locale.ENGLISH ) ); + if( javaValueType != null ) { + if( javaValueTypes == null ) { + // create lazy + javaValueTypes = new HashMap<>(); + javaValueTypes.put( String.class, ValueType.STRING ); + javaValueTypes.put( boolean.class, ValueType.BOOLEAN ); + javaValueTypes.put( Boolean.class, ValueType.BOOLEAN ); + javaValueTypes.put( char.class, ValueType.CHARACTER ); + javaValueTypes.put( Character.class, ValueType.CHARACTER ); + javaValueTypes.put( int.class, ValueType.INTEGER ); + javaValueTypes.put( Integer.class, ValueType.INTEGER ); + javaValueTypes.put( float.class, ValueType.FLOAT ); + javaValueTypes.put( Float.class, ValueType.FLOAT ); + javaValueTypes.put( Border.class, ValueType.BORDER ); + javaValueTypes.put( Icon.class, ValueType.ICON ); + javaValueTypes.put( Insets.class, ValueType.INSETS ); + javaValueTypes.put( Dimension.class, ValueType.DIMENSION ); + javaValueTypes.put( Color.class, ValueType.COLOR ); + } - // remove type from value - value = value.substring( end + TYPE_PREFIX_END.length() ); - } catch( IllegalArgumentException ex ) { - // ignore + // map java value type to parser value type + valueType = javaValueTypes.get( javaValueType ); + if( valueType == null ) + throw new IllegalArgumentException( "unsupported value type '" + javaValueType.getName() + "'" ); + + // remove '"' from strings + if( valueType == ValueType.STRING && value.startsWith( "\"" ) && value.endsWith( "\"" ) ) + value = value.substring( 1, value.length() - 1 ); + } else { + // false, true + switch( value ) { + case "false": resultValueType[0] = ValueType.BOOLEAN; return false; + case "true": resultValueType[0] = ValueType.BOOLEAN; return true; + } + + // check for function "lazy" + // Syntax: lazy(uiKey) + if( value.startsWith( "lazy(" ) && value.endsWith( ")" ) ) { + resultValueType[0] = ValueType.LAZY; + String uiKey = value.substring( 5, value.length() - 1 ).trim(); + return (LazyValue) t -> { + return lazyUIManagerGet( uiKey ); + }; + } + + // check whether value type is specified in the value + if( value.startsWith( "#" ) ) + valueType = ValueType.COLOR; + else if( value.startsWith( "\"" ) && value.endsWith( "\"" ) ) { + valueType = ValueType.STRING; + value = value.substring( 1, value.length() - 1 ); + } else if( value.startsWith( TYPE_PREFIX ) ) { + int end = value.indexOf( TYPE_PREFIX_END ); + if( end != -1 ) { + try { + String typeStr = value.substring( TYPE_PREFIX.length(), end ); + valueType = ValueType.valueOf( typeStr.toUpperCase( Locale.ENGLISH ) ); + + // remove type from value + value = value.substring( end + TYPE_PREFIX_END.length() ); + } catch( IllegalArgumentException ex ) { + // ignore + } } } - } - // determine value type from key - if( valueType == ValueType.UNKNOWN ) { - if( key.endsWith( "UI" ) ) - valueType = ValueType.STRING; - else if( key.endsWith( "Color" ) || - (key.endsWith( "ground" ) && - (key.endsWith( ".background" ) || key.endsWith( "Background" ) || key.equals( "background" ) || - key.endsWith( ".foreground" ) || key.endsWith( "Foreground" ) || key.equals( "foreground" ))) ) - valueType = ValueType.COLOR; - else if( key.endsWith( ".border" ) || key.endsWith( "Border" ) ) - valueType = ValueType.BORDER; - else if( key.endsWith( ".icon" ) || key.endsWith( "Icon" ) ) - valueType = ValueType.ICON; - else if( key.endsWith( ".margin" ) || key.equals( "margin" ) || - key.endsWith( ".padding" ) || key.equals( "padding" ) || - key.endsWith( "Margins" ) || key.endsWith( "Insets" ) ) - valueType = ValueType.INSETS; - else if( key.endsWith( "Size" ) ) - valueType = ValueType.DIMENSION; - else if( key.endsWith( "Width" ) || key.endsWith( "Height" ) ) - valueType = ValueType.INTEGER; - else if( key.endsWith( "Char" ) ) - valueType = ValueType.CHARACTER; - else if( key.endsWith( "grayFilter" ) ) - valueType = ValueType.GRAYFILTER; + // determine value type from key + if( valueType == ValueType.UNKNOWN ) { + if( key.endsWith( "UI" ) ) + valueType = ValueType.STRING; + else if( key.endsWith( "Color" ) || + (key.endsWith( "ground" ) && + (key.endsWith( ".background" ) || key.endsWith( "Background" ) || key.equals( "background" ) || + key.endsWith( ".foreground" ) || key.endsWith( "Foreground" ) || key.equals( "foreground" ))) ) + valueType = ValueType.COLOR; + else if( key.endsWith( ".border" ) || key.endsWith( "Border" ) ) + valueType = ValueType.BORDER; + else if( key.endsWith( ".icon" ) || key.endsWith( "Icon" ) ) + valueType = ValueType.ICON; + else if( key.endsWith( ".margin" ) || key.equals( "margin" ) || + key.endsWith( ".padding" ) || key.equals( "padding" ) || + key.endsWith( "Margins" ) || key.endsWith( "Insets" ) ) + valueType = ValueType.INSETS; + else if( key.endsWith( "Size" ) ) + valueType = ValueType.DIMENSION; + else if( key.endsWith( "Width" ) || key.endsWith( "Height" ) ) + valueType = ValueType.INTEGER; + else if( key.endsWith( "Char" ) ) + valueType = ValueType.CHARACTER; + else if( key.endsWith( "grayFilter" ) ) + valueType = ValueType.GRAYFILTER; + } } resultValueType[0] = valueType; @@ -377,6 +415,7 @@ class UIDefaultsLoader // parse value switch( valueType ) { case STRING: return value; + case BOOLEAN: return parseBoolean( value ); case CHARACTER: return parseCharacter( value ); case INTEGER: return parseInteger( value, true ); case FLOAT: return parseFloat( value, true ); @@ -869,6 +908,14 @@ class UIDefaultsLoader return val; } + private static Boolean parseBoolean( String value ) { + switch( value ) { + case "false": return false; + case "true": return true; + } + throw new IllegalArgumentException( "invalid boolean '" + value + "'" ); + } + private static Character parseCharacter( String value ) { if( value.length() != 1 ) throw new IllegalArgumentException( "invalid character '" + value + "'" ); diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatStyleSupport.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatStyleSupport.java index 882dd7e6..d1387ca7 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatStyleSupport.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatStyleSupport.java @@ -198,7 +198,7 @@ public class FlatStyleSupport if( key.startsWith( "[" ) ) key = key.substring( key.indexOf( ']' ) + 1 ); - return FlatLaf.parseDefaultsValue( key, value ); + return FlatLaf.parseDefaultsValue( key, value, null ); } /** diff --git a/flatlaf-core/src/test/java/com/formdev/flatlaf/TestUIDefaultsLoader.java b/flatlaf-core/src/test/java/com/formdev/flatlaf/TestUIDefaultsLoader.java new file mode 100644 index 00000000..b940debe --- /dev/null +++ b/flatlaf-core/src/test/java/com/formdev/flatlaf/TestUIDefaultsLoader.java @@ -0,0 +1,74 @@ +/* + * Copyright 2021 FormDev Software GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.formdev.flatlaf; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Insets; +import org.junit.jupiter.api.Test; + +/** + * @author Karl Tauber + */ +public class TestUIDefaultsLoader +{ + @Test + void parseValue() { + assertEquals( null, UIDefaultsLoader.parseValue( "dummy", "null", null ) ); + assertEquals( false, UIDefaultsLoader.parseValue( "dummy", "false", null ) ); + assertEquals( true, UIDefaultsLoader.parseValue( "dummy", "true", null ) ); + + assertEquals( "hello", UIDefaultsLoader.parseValue( "dummy", "hello", null ) ); + assertEquals( "hello", UIDefaultsLoader.parseValue( "dummy", "\"hello\"", null ) ); + assertEquals( "null", UIDefaultsLoader.parseValue( "dummy", "\"null\"", null ) ); + + assertEquals( 'a', UIDefaultsLoader.parseValue( "dummyChar", "a", null ) ); + assertEquals( 123, UIDefaultsLoader.parseValue( "dummy", "123", null ) ); + assertEquals( 123, UIDefaultsLoader.parseValue( "dummyWidth", "123", null ) ); + assertEquals( 1.23f, UIDefaultsLoader.parseValue( "dummy", "1.23", null ) ); + assertEquals( 1.23f, UIDefaultsLoader.parseValue( "dummyWidth", "{float}1.23", null ) ); + + assertEquals( new Insets( 2,2,2,2 ), UIDefaultsLoader.parseValue( "dummyInsets", "2,2,2,2", null ) ); + assertEquals( new Dimension( 2,2 ), UIDefaultsLoader.parseValue( "dummySize", "2,2", null ) ); + assertEquals( new Color( 0xff0000 ), UIDefaultsLoader.parseValue( "dummy", "#f00", null ) ); + assertEquals( new Color( 0xff0000 ), UIDefaultsLoader.parseValue( "dummyColor", "#f00", null ) ); + } + + @Test + void parseValueWithJavaType() { + assertEquals( null, UIDefaultsLoader.parseValue( "dummy", "null", String.class ) ); + assertEquals( false, UIDefaultsLoader.parseValue( "dummy", "false", boolean.class ) ); + assertEquals( true, UIDefaultsLoader.parseValue( "dummy", "true", Boolean.class ) ); + + assertEquals( "hello", UIDefaultsLoader.parseValue( "dummy", "hello", String.class ) ); + assertEquals( "hello", UIDefaultsLoader.parseValue( "dummy", "\"hello\"", String.class ) ); + assertEquals( "null", UIDefaultsLoader.parseValue( "dummy", "\"null\"", String.class ) ); + assertEquals( null, UIDefaultsLoader.parseValue( "dummy", "null", String.class ) ); + + assertEquals( 'a', UIDefaultsLoader.parseValue( "dummy", "a", char.class ) ); + assertEquals( 'a', UIDefaultsLoader.parseValue( "dummy", "a", Character.class ) ); + assertEquals( 123, UIDefaultsLoader.parseValue( "dummy", "123", int.class ) ); + assertEquals( 123, UIDefaultsLoader.parseValue( "dummy", "123", Integer.class ) ); + assertEquals( 1.23f, UIDefaultsLoader.parseValue( "dummy", "1.23", float.class ) ); + assertEquals( 1.23f, UIDefaultsLoader.parseValue( "dummy", "1.23", Float.class ) ); + + assertEquals( new Insets( 2,2,2,2 ), UIDefaultsLoader.parseValue( "dummy", "2,2,2,2", Insets.class ) ); + assertEquals( new Dimension( 2,2 ), UIDefaultsLoader.parseValue( "dummy", "2,2", Dimension.class ) ); + assertEquals( new Color( 0xff0000 ), UIDefaultsLoader.parseValue( "dummy", "#f00", Color.class ) ); + } +} diff --git a/flatlaf-theme-editor/src/main/java/com/formdev/flatlaf/UIDefaultsLoaderAccessor.java b/flatlaf-theme-editor/src/main/java/com/formdev/flatlaf/UIDefaultsLoaderAccessor.java index fb4698d1..d6b35f90 100644 --- a/flatlaf-theme-editor/src/main/java/com/formdev/flatlaf/UIDefaultsLoaderAccessor.java +++ b/flatlaf-theme-editor/src/main/java/com/formdev/flatlaf/UIDefaultsLoaderAccessor.java @@ -56,7 +56,8 @@ public class UIDefaultsLoaderAccessor Function resolver ) { ValueType[] resultValueType2 = new ValueType[1]; - Object result = UIDefaultsLoader.parseValue( key, value, resultValueType2, resolver, Collections.emptyList() ); + Object result = UIDefaultsLoader.parseValue( key, value, null, + resultValueType2, resolver, Collections.emptyList() ); resultValueType[0] = resultValueType2[0]; return result; }