diff --git a/flatlaf-theme-editor/src/main/java/com/formdev/flatlaf/themeeditor/FlatThemeEditorPane.java b/flatlaf-theme-editor/src/main/java/com/formdev/flatlaf/themeeditor/FlatThemeEditorPane.java index 8aa40543..99387bb2 100644 --- a/flatlaf-theme-editor/src/main/java/com/formdev/flatlaf/themeeditor/FlatThemeEditorPane.java +++ b/flatlaf-theme-editor/src/main/java/com/formdev/flatlaf/themeeditor/FlatThemeEditorPane.java @@ -21,7 +21,9 @@ import java.awt.Font; import java.io.IOException; import java.nio.charset.StandardCharsets; import javax.swing.JPanel; +import org.fife.ui.rsyntaxtextarea.AbstractTokenMakerFactory; import org.fife.ui.rsyntaxtextarea.FileLocation; +import org.fife.ui.rsyntaxtextarea.TokenMakerFactory; import org.fife.ui.rtextarea.RTextScrollPane; import com.formdev.flatlaf.util.UIScale; @@ -33,14 +35,21 @@ import com.formdev.flatlaf.util.UIScale; class FlatThemeEditorPane extends JPanel { + private static final String FLATLAF_STYLE = "text/flatlaf"; + private final RTextScrollPane scrollPane; private final FlatSyntaxTextArea textArea; FlatThemeEditorPane() { super( new BorderLayout() ); + // register FlatLaf token maker + AbstractTokenMakerFactory tmf = (AbstractTokenMakerFactory) TokenMakerFactory.getDefaultInstance(); + tmf.putMapping( FLATLAF_STYLE, FlatThemeTokenMaker.class.getName() ); + // create text area textArea = new FlatSyntaxTextArea(); + textArea.setSyntaxEditingStyle( FLATLAF_STYLE ); // create scroll pane scrollPane = new RTextScrollPane( textArea ); diff --git a/flatlaf-theme-editor/src/main/java/com/formdev/flatlaf/themeeditor/FlatThemeFileEditor.java b/flatlaf-theme-editor/src/main/java/com/formdev/flatlaf/themeeditor/FlatThemeFileEditor.java index 71f1e2b7..18e453aa 100644 --- a/flatlaf-theme-editor/src/main/java/com/formdev/flatlaf/themeeditor/FlatThemeFileEditor.java +++ b/flatlaf-theme-editor/src/main/java/com/formdev/flatlaf/themeeditor/FlatThemeFileEditor.java @@ -35,7 +35,7 @@ public class FlatThemeFileEditor public static void main( String[] args ) { File file = new File( args.length > 0 ? args[0] - : "../flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatLightLaf.properties" ); // TODO + : "theme-editor-test.properties" ); // TODO SwingUtilities.invokeLater( () -> { FlatLightLaf.install(); diff --git a/flatlaf-theme-editor/src/main/java/com/formdev/flatlaf/themeeditor/FlatThemeTokenMaker.java b/flatlaf-theme-editor/src/main/java/com/formdev/flatlaf/themeeditor/FlatThemeTokenMaker.java new file mode 100644 index 00000000..47a51831 --- /dev/null +++ b/flatlaf-theme-editor/src/main/java/com/formdev/flatlaf/themeeditor/FlatThemeTokenMaker.java @@ -0,0 +1,191 @@ +/* + * Copyright 2020 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.themeeditor; + +import org.fife.ui.rsyntaxtextarea.RSyntaxUtilities; +import org.fife.ui.rsyntaxtextarea.Token; +import org.fife.ui.rsyntaxtextarea.TokenMap; +import org.fife.ui.rsyntaxtextarea.TokenTypes; +import org.fife.ui.rsyntaxtextarea.modes.PropertiesFileTokenMaker; + +/** + * Token maker for FlatLaf properties files. + *
+ * Lets the super class parse the properties file and modify the added tokens.
+ * The super class uses {@link TokenTypes#RESERVED_WORD} for property keys and
+ * {@link TokenTypes#LITERAL_STRING_DOUBLE_QUOTE} for property values.
+ *
+ * @author Karl Tauber
+ */
+public class FlatThemeTokenMaker
+ extends PropertiesFileTokenMaker
+{
+ private static final int TOKEN_PROPERTY = Token.IDENTIFIER;
+ private static final int TOKEN_VARIABLE = Token.VARIABLE;
+ private static final int TOKEN_NUMBER = Token.LITERAL_NUMBER_DECIMAL_INT;
+ private static final int TOKEN_COLOR = Token.LITERAL_NUMBER_HEXADECIMAL;
+ private static final int TOKEN_STRING = Token.LITERAL_STRING_DOUBLE_QUOTE;
+ private static final int TOKEN_FUNCTION = Token.FUNCTION;
+ private static final int TOKEN_TYPE = Token.DATA_TYPE;
+
+ private final TokenMap tokenMap = new TokenMap();
+
+ public FlatThemeTokenMaker() {
+ // null, false, true
+ tokenMap.put( "null", Token.RESERVED_WORD );
+ tokenMap.put( "false", Token.LITERAL_BOOLEAN );
+ tokenMap.put( "true", Token.LITERAL_BOOLEAN );
+
+ // functions
+ tokenMap.put( "rgb", TOKEN_FUNCTION );
+ tokenMap.put( "rgba", TOKEN_FUNCTION );
+ tokenMap.put( "hsl", TOKEN_FUNCTION );
+ tokenMap.put( "hsla", TOKEN_FUNCTION );
+ tokenMap.put( "lighten", TOKEN_FUNCTION );
+ tokenMap.put( "darken", TOKEN_FUNCTION );
+ tokenMap.put( "lazy", TOKEN_FUNCTION );
+
+ // function options
+ tokenMap.put( "relative", Token.RESERVED_WORD );
+ tokenMap.put( "autoInverse", Token.RESERVED_WORD );
+ }
+
+ /**
+ * This method is only invoked from the super class.
+ */
+ @Override
+ public void addToken( char[] array, int start, int end, int tokenType, int startOffset, boolean hyperlink ) {
+// debugInputToken( array, start, end, tokenType, startOffset, hyperlink );
+
+ // ignore invalid token
+ if( end < start )
+ return;
+
+ if( tokenType == Token.RESERVED_WORD ) {
+ // key
+ int newTokenType = (array[start] == '@') ? TOKEN_VARIABLE : TOKEN_PROPERTY;
+ super.addToken( array, start, end, newTokenType, startOffset, hyperlink );
+ } else if( tokenType == Token.LITERAL_STRING_DOUBLE_QUOTE ) {
+ // value
+ tokenizeValue( array, start, end, startOffset );
+ } else if( tokenType == Token.VARIABLE ) {
+ // '{variable}'
+ super.addToken( array, start, end, TOKEN_TYPE, startOffset, hyperlink );
+ } else {
+ // comments or operators
+ super.addToken( array, start, end, tokenType, startOffset, hyperlink );
+ }
+ }
+
+ private void tokenizeValue( char[] array, int start, int end, int startOffset ) {
+ int newStartOffset = startOffset - start;
+
+ int currentTokenStart = start;
+ int currentTokenType = Token.NULL;
+
+ for( int i = start; i <= end; i++ ) {
+ int newTokenType;
+ char ch = array[i];
+ if( ch <= ' ' )
+ newTokenType = Token.WHITESPACE;
+ else if( ch == '#' || (currentTokenType == TOKEN_COLOR && RSyntaxUtilities.isHexCharacter( ch )) )
+ newTokenType = TOKEN_COLOR;
+ else if( ch == '$' || (currentTokenType == TOKEN_PROPERTY && isPropertyChar( ch )) )
+ newTokenType = TOKEN_PROPERTY;
+ else if( ch == '@' || (currentTokenType == TOKEN_VARIABLE && isPropertyChar( ch )) )
+ newTokenType = TOKEN_VARIABLE;
+ else if( currentTokenType != TOKEN_STRING && (RSyntaxUtilities.isDigit( ch ) || (currentTokenType == TOKEN_NUMBER && ch == '.')) )
+ newTokenType = TOKEN_NUMBER;
+ else if( ch == ',' || ch == '(' || ch == ')' || ch == '"' || ch == '%' )
+ newTokenType = TokenTypes.OPERATOR;
+ else
+ newTokenType = TOKEN_STRING;
+
+ if( currentTokenType == Token.NULL )
+ currentTokenType = newTokenType;
+ else if( newTokenType != currentTokenType ) {
+ addTokenImpl( array, currentTokenStart, i - 1, currentTokenType, newStartOffset + currentTokenStart );
+ currentTokenType = newTokenType;
+ currentTokenStart = i;
+ }
+ }
+
+ if( currentTokenType != Token.NULL )
+ addTokenImpl( array, currentTokenStart, end, currentTokenType, newStartOffset + currentTokenStart );
+ }
+
+ private void addTokenImpl( char[] array, int start, int end, int tokenType, int startOffset ) {
+ if( tokenType == TOKEN_STRING ) {
+ int type = tokenMap.get( array, start, end );
+ if( type != -1 )
+ tokenType = type;
+ }
+
+// debugOutputToken( array, start, end, tokenType );
+ super.addToken( array, start, end, tokenType, startOffset, false );
+ }
+
+ private boolean isPropertyChar( char ch ) {
+ return RSyntaxUtilities.isLetterOrDigit( ch ) || ch == '.' || ch == '_' || ch == '-';
+ }
+
+/*debug
+ private java.util.HashMap