support of RichTextFX to have syntax highlighting in the groovy edit field of validation editor

This commit is contained in:
2016-07-23 16:56:48 +02:00
parent 68a68a51a9
commit 4801203378
5 changed files with 136 additions and 5 deletions

View File

@@ -30,6 +30,7 @@ dependencies {
compile group: 'com.google.code.gson', name: 'gson', version: '2.7' compile group: 'com.google.code.gson', name: 'gson', version: '2.7'
compile group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.6.2' compile group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.6.2'
compile group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.6.2' compile group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.6.2'
compile group: 'org.fxmisc.richtext', name: 'richtextfx', version: '0.6.10'
} }
task wrapper(type: Wrapper) { task wrapper(type: Wrapper) {

View File

@@ -32,18 +32,29 @@ import javafx.fxml.FXML;
import javafx.scene.control.*; import javafx.scene.control.*;
import ninja.javafx.smartcsv.fx.FXMLController; import ninja.javafx.smartcsv.fx.FXMLController;
import ninja.javafx.smartcsv.validation.ValidationConfiguration; import ninja.javafx.smartcsv.validation.ValidationConfiguration;
import org.fxmisc.richtext.CodeArea;
import org.fxmisc.richtext.LineNumberFactory;
import org.fxmisc.richtext.StyleSpans;
import org.fxmisc.richtext.StyleSpansBuilder;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.net.URL; import java.net.URL;
import java.util.Collection;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.ResourceBundle; import java.util.ResourceBundle;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static java.util.Arrays.asList; import static java.util.Arrays.asList;
import static java.util.stream.Collectors.joining; import static java.util.stream.Collectors.joining;
/** /**
* controller for editing column validations * controller for editing column validations
*
* RichText groovy highlighting code is based on the java example of
* https://github.com/TomasMikula/RichTextFX
*/ */
@Component @Component
public class ValidationEditorController extends FXMLController { public class ValidationEditorController extends FXMLController {
@@ -51,6 +62,39 @@ public class ValidationEditorController extends FXMLController {
private StringProperty selectedColumn = new SimpleStringProperty(); private StringProperty selectedColumn = new SimpleStringProperty();
private ValidationConfiguration validationConfiguration; private ValidationConfiguration validationConfiguration;
private static final String[] KEYWORDS = new String[] {
"abstract", "assert", "boolean", "break", "byte",
"case", "catch", "char", "class",
"continue", "def", "default", "do", "double", "else",
"enum", "extends", "false", "final", "finally", "float",
"for", "if", "implements", "import", "in",
"instanceof", "int", "interface", "length", "long", "native",
"new", "null", "package", "private", "property", "protected", "public",
"return", "short", "static", "super",
"switch", "synchronized", "this", "threadsafe", "throw", "throws",
"transient", "true", "try", "void", "volatile", "while"
};
private static final String KEYWORD_PATTERN = "\\b(" + String.join("|", KEYWORDS) + ")\\b";
private static final String PAREN_PATTERN = "\\(|\\)";
private static final String BRACE_PATTERN = "\\{|\\}";
private static final String BRACKET_PATTERN = "\\[|\\]";
private static final String SEMICOLON_PATTERN = "\\;";
private static final String STRING_PATTERN = "\"([^\"\\\\]|\\\\.)*\"";
private static final String STRING2_PATTERN = "'([^'\\\\]|\\\\.)*'";
private static final String COMMENT_PATTERN = "//[^\n]*" + "|" + "/\\*(.|\\R)*?\\*/";
private static final Pattern PATTERN = Pattern.compile(
"(?<KEYWORD>" + KEYWORD_PATTERN + ")"
+ "|(?<PAREN>" + PAREN_PATTERN + ")"
+ "|(?<BRACE>" + BRACE_PATTERN + ")"
+ "|(?<BRACKET>" + BRACKET_PATTERN + ")"
+ "|(?<SEMICOLON>" + SEMICOLON_PATTERN + ")"
+ "|(?<STRING>" + STRING_PATTERN + ")"
+ "|(?<STRING2>" + STRING2_PATTERN + ")"
+ "|(?<COMMENT>" + COMMENT_PATTERN + ")"
);
@FXML @FXML
private CheckBox notEmptyRuleCheckBox; private CheckBox notEmptyRuleCheckBox;
@@ -79,7 +123,7 @@ public class ValidationEditorController extends FXMLController {
private TextField valueOfRuleTextField; private TextField valueOfRuleTextField;
@FXML @FXML
private TextArea groovyRuleTextArea; private CodeArea groovyRuleTextArea;
@FXML @FXML
private CheckBox enableNotEmptyRule; private CheckBox enableNotEmptyRule;
@@ -133,7 +177,7 @@ public class ValidationEditorController extends FXMLController {
initTextInputControl(dateformatRuleTextField, enableDateRule); initTextInputControl(dateformatRuleTextField, enableDateRule);
initTextInputControl(regexpRuleTextField, enableRegexpRule); initTextInputControl(regexpRuleTextField, enableRegexpRule);
initTextInputControl(valueOfRuleTextField, enableValueOfRule); initTextInputControl(valueOfRuleTextField, enableValueOfRule);
initTextInputControl(groovyRuleTextArea, enableGroovyRule); initCodeAreaControl(groovyRuleTextArea, enableGroovyRule);
selectedColumn.addListener(observable -> { selectedColumn.addListener(observable -> {
updateForm(); updateForm();
@@ -275,7 +319,7 @@ public class ValidationEditorController extends FXMLController {
enableValueOfRule enableValueOfRule
); );
updateTextInputControl( updateCodeAreaControl(
groovyRuleTextArea, groovyRuleTextArea,
validationConfiguration.getGroovyRuleFor(getSelectedColumn()), validationConfiguration.getGroovyRuleFor(getSelectedColumn()),
enableGroovyRule enableGroovyRule
@@ -318,6 +362,15 @@ public class ValidationEditorController extends FXMLController {
} }
} }
private void updateCodeAreaControl(CodeArea rule, String value, CheckBox ruleEnabled) {
if (value == null) {
ruleEnabled.setSelected(false);
} else {
ruleEnabled.setSelected(true);
rule.replaceText(0, 0, value);
}
}
private void initCheckBox(CheckBox rule, CheckBox ruleEnabled) { private void initCheckBox(CheckBox rule, CheckBox ruleEnabled) {
rule.disableProperty().bind(ruleEnabled.selectedProperty().not()); rule.disableProperty().bind(ruleEnabled.selectedProperty().not());
ruleEnabled.selectedProperty().addListener((observable, oldValue, newValue) -> { ruleEnabled.selectedProperty().addListener((observable, oldValue, newValue) -> {
@@ -345,4 +398,43 @@ public class ValidationEditorController extends FXMLController {
}); });
} }
private void initCodeAreaControl(CodeArea rule, CheckBox ruleEnabled) {
rule.disableProperty().bind(ruleEnabled.selectedProperty().not());
ruleEnabled.selectedProperty().addListener((observable, oldValue, newValue) -> {
if (!newValue) {
rule.clear();
}
});
rule.setParagraphGraphicFactory(LineNumberFactory.get(rule));
rule.richChanges()
.filter(ch -> !ch.getInserted().equals(ch.getRemoved())) // XXX
.subscribe(change -> {
rule.setStyleSpans(0, computeHighlighting(rule.getText()));
});
}
private static StyleSpans<Collection<String>> computeHighlighting(String text) {
Matcher matcher = PATTERN.matcher(text);
int lastKwEnd = 0;
StyleSpansBuilder<Collection<String>> spansBuilder
= new StyleSpansBuilder<>();
while(matcher.find()) {
String styleClass =
matcher.group("KEYWORD") != null ? "keyword" :
matcher.group("PAREN") != null ? "paren" :
matcher.group("BRACE") != null ? "brace" :
matcher.group("BRACKET") != null ? "bracket" :
matcher.group("SEMICOLON") != null ? "semicolon" :
matcher.group("STRING") != null ? "string" :
matcher.group("STRING2") != null ? "string" :
matcher.group("COMMENT") != null ? "comment" :
null; /* never happens */ assert styleClass != null;
spansBuilder.add(Collections.emptyList(), matcher.start() - lastKwEnd);
spansBuilder.add(Collections.singleton(styleClass), matcher.end() - matcher.start());
lastKwEnd = matcher.end();
}
spansBuilder.add(Collections.emptyList(), text.length() - lastKwEnd);
return spansBuilder.create();
}
} }

View File

@@ -58,6 +58,7 @@
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" /> <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" /> <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" /> <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
</rowConstraints> </rowConstraints>
<children> <children>
<Label text="junit" /> <Label text="junit" />
@@ -80,7 +81,8 @@
<Hyperlink onAction="#openLinkInBrowser" text="http://fxexperience.com/controlsfx/" GridPane.columnIndex="1" GridPane.rowIndex="8" /> <Hyperlink onAction="#openLinkInBrowser" text="http://fxexperience.com/controlsfx/" GridPane.columnIndex="1" GridPane.rowIndex="8" />
<Label text="Apache Log4j 2" GridPane.rowIndex="9" /> <Label text="Apache Log4j 2" GridPane.rowIndex="9" />
<Hyperlink onAction="#openLinkInBrowser" text="http://logging.apache.org/log4j/2.x/" GridPane.columnIndex="1" GridPane.rowIndex="9" /> <Hyperlink onAction="#openLinkInBrowser" text="http://logging.apache.org/log4j/2.x/" GridPane.columnIndex="1" GridPane.rowIndex="9" />
<Label text="RichTextFX" GridPane.rowIndex="10" />
<Hyperlink onAction="#openLinkInBrowser" text="https://github.com/TomasMikula/RichTextFX" GridPane.columnIndex="1" GridPane.rowIndex="10" />
</children> </children>
<padding> <padding>
<Insets bottom="4.0" left="8.0" right="8.0" top="4.0" /> <Insets bottom="4.0" left="8.0" right="8.0" top="4.0" />

View File

@@ -180,3 +180,34 @@
-fx-font-size: 12px; -fx-font-size: 12px;
-fx-padding: 10 10 10 10; -fx-padding: 10 10 10 10;
} }
.keyword {
-fx-fill: purple;
-fx-font-weight: bold;
}
.semicolon {
-fx-font-weight: bold;
}
.paren {
-fx-fill: firebrick;
-fx-font-weight: bold;
}
.bracket {
-fx-fill: darkgreen;
-fx-font-weight: bold;
}
.brace {
-fx-fill: teal;
-fx-font-weight: bold;
}
.string {
-fx-fill: blue;
}
.comment {
-fx-fill: cadetblue;
}
.paragraph-box:has-caret {
-fx-background-color: #f2f9fc;
}

View File

@@ -10,6 +10,8 @@
<?import javafx.scene.layout.GridPane?> <?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.RowConstraints?> <?import javafx.scene.layout.RowConstraints?>
<?import org.fxmisc.richtext.CodeArea?>
<?import java.net.URL?>
<GridPane hgap="10.0" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefWidth="600.0" vgap="10.0" xmlns="http://javafx.com/javafx/8.0.101" xmlns:fx="http://javafx.com/fxml/1"> <GridPane hgap="10.0" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefWidth="600.0" vgap="10.0" xmlns="http://javafx.com/javafx/8.0.101" xmlns:fx="http://javafx.com/fxml/1">
<columnConstraints> <columnConstraints>
<ColumnConstraints halignment="CENTER" hgrow="NEVER" maxWidth="50.0" prefWidth="50.0" /> <ColumnConstraints halignment="CENTER" hgrow="NEVER" maxWidth="50.0" prefWidth="50.0" />
@@ -50,7 +52,7 @@
<TextField fx:id="dateformatRuleTextField" GridPane.columnIndex="2" GridPane.rowIndex="7" /> <TextField fx:id="dateformatRuleTextField" GridPane.columnIndex="2" GridPane.rowIndex="7" />
<TextField fx:id="regexpRuleTextField" GridPane.columnIndex="2" GridPane.rowIndex="8" /> <TextField fx:id="regexpRuleTextField" GridPane.columnIndex="2" GridPane.rowIndex="8" />
<TextField fx:id="valueOfRuleTextField" GridPane.columnIndex="2" GridPane.rowIndex="9" /> <TextField fx:id="valueOfRuleTextField" GridPane.columnIndex="2" GridPane.rowIndex="9" />
<TextArea fx:id="groovyRuleTextArea" prefHeight="300.0" prefWidth="200.0" GridPane.columnIndex="2" GridPane.rowIndex="10" GridPane.rowSpan="2" /> <CodeArea fx:id="groovyRuleTextArea" prefHeight="300.0" prefWidth="200.0" GridPane.columnIndex="2" GridPane.rowIndex="10" GridPane.rowSpan="2" />
<CheckBox fx:id="enableNotEmptyRule" mnemonicParsing="false" GridPane.rowIndex="1" /> <CheckBox fx:id="enableNotEmptyRule" mnemonicParsing="false" GridPane.rowIndex="1" />
<CheckBox fx:id="enableIntegerRule" mnemonicParsing="false" GridPane.rowIndex="2" /> <CheckBox fx:id="enableIntegerRule" mnemonicParsing="false" GridPane.rowIndex="2" />
<CheckBox fx:id="enableDoubleRule" mnemonicParsing="false" GridPane.rowIndex="3" /> <CheckBox fx:id="enableDoubleRule" mnemonicParsing="false" GridPane.rowIndex="3" />
@@ -68,4 +70,7 @@
<padding> <padding>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" /> <Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</padding> </padding>
<stylesheets>
<URL value="@/ninja/javafx/smartcsv/fx/smartcsv.css" />
</stylesheets>
</GridPane> </GridPane>