Highlighting Strings in JavaFX TextArea
Asked Answered
G

5

10

We are using JavaFX's TextArea control in our application, and trying to integrate it with Jazzy Spell Check API - as in, when a user enters a wrong word that is not in the dictionary, such word would be highlighted.

Is there a way to highlight a word in said control? I've seen no options for that in the JavaDocs, so if someone could suggest an approach?

It could be possible, I guess, to use the HTMLEditor component and color the words diferently with <font face="red=>wrongWord</font>. This, however, brings a whole lot of different problems with the spell checking, such as the html tags and words count.

Guernica answered 3/2, 2012 at 12:16 Comment(1)
Does this answer your question? JavaFX Spell checker using RichTextFX how to create right click suggestionsChina
F
13

The JavaFX TextArea control (as of 2.0.2) does not support rich text editing where text styles (fonts, etc) are mixed.

You can highlight contiguous strings of characters in the TextArea by manipulating the TextArea's selectRange, as in the following example:

public class TextHighlight extends Application {
  public static void main(String[] args) { Application.launch(args); }
  @Override public void start(Stage stage) {
    final TextArea text = new TextArea("Here is some textz to highlight");
    text.setStyle("-fx-highlight-fill: lightgray; -fx-highlight-text-fill: firebrick; -fx-font-size: 20px;");
    text.setEditable(false);
    text.addEventFilter(MouseEvent.ANY, new EventHandler<MouseEvent>() {
      @Override public void handle(MouseEvent t) { t.consume(); }
    });

    stage.setScene(new Scene(text));
    stage.show();

    Platform.runLater(new Runnable() {
      @Override public void run() { text.selectRange(13, 18); }
    });
  }
}

You could use the above code as a basis to switch the TextArea to read-only mode while spell checking is happening. Implement prompting to find and fix each word in turn until the spell check is complete. Perform the prompting in a separate dialog or panel. The Jazzy demo seems to work this way http://jazzy.sourceforge.net/demo.html, so it should be fairly easy to convert its Swing UI to JavaFX.


Alternately, you could use a JavaFX WebView control to wrap any of the many javascript/html based spell checkers (e.g. http://www.javascriptspellcheck.com/) using a technique similar to what is demonstrated here: http://jewelsea.wordpress.com/2011/12/11/codemirror-based-code-editor-for-javafx/.

Frentz answered 3/2, 2012 at 21:22 Comment(0)
E
17

RichTextFX allows you to add style to text ranges.

Edi answered 7/10, 2013 at 14:24 Comment(0)
F
13

The JavaFX TextArea control (as of 2.0.2) does not support rich text editing where text styles (fonts, etc) are mixed.

You can highlight contiguous strings of characters in the TextArea by manipulating the TextArea's selectRange, as in the following example:

public class TextHighlight extends Application {
  public static void main(String[] args) { Application.launch(args); }
  @Override public void start(Stage stage) {
    final TextArea text = new TextArea("Here is some textz to highlight");
    text.setStyle("-fx-highlight-fill: lightgray; -fx-highlight-text-fill: firebrick; -fx-font-size: 20px;");
    text.setEditable(false);
    text.addEventFilter(MouseEvent.ANY, new EventHandler<MouseEvent>() {
      @Override public void handle(MouseEvent t) { t.consume(); }
    });

    stage.setScene(new Scene(text));
    stage.show();

    Platform.runLater(new Runnable() {
      @Override public void run() { text.selectRange(13, 18); }
    });
  }
}

You could use the above code as a basis to switch the TextArea to read-only mode while spell checking is happening. Implement prompting to find and fix each word in turn until the spell check is complete. Perform the prompting in a separate dialog or panel. The Jazzy demo seems to work this way http://jazzy.sourceforge.net/demo.html, so it should be fairly easy to convert its Swing UI to JavaFX.


Alternately, you could use a JavaFX WebView control to wrap any of the many javascript/html based spell checkers (e.g. http://www.javascriptspellcheck.com/) using a technique similar to what is demonstrated here: http://jewelsea.wordpress.com/2011/12/11/codemirror-based-code-editor-for-javafx/.

Frentz answered 3/2, 2012 at 21:22 Comment(0)
M
7

With JavaFX 8 you can use TextFlow

You can define certain style classes for bold, red, green or any type of Texts and arrange them in TextFlow while assigning desired style class to each Text

Macrobiotics answered 29/5, 2014 at 6:42 Comment(0)
D
5

It is possible... sort of

I know this question is resolved, but I found a way to fix the issue and thought I'd post it for other people who also stumble upon this post. This is a little hacky, but it works in a pinch if you need to highlight text in a TextArea and don't want to accept the unsatisfactory "impossible" answer.

  1. Retrieve the background color of the TextArea and the foreground color of the text font
  2. Calculate the bin of highest contrast between the foreground and background color
  3. Place a rectangle around the desired text
  4. Set the color of the rectangle to the background color
  5. Set the blendmode of the rectangle to the bin of highest contrast
  6. Disable the rectangle

The rectangle will now blend perfectly with the background but change the color of the text. Disabling the rectangle means that it won'y appear as anything more than ascetic to the user - e.g actions like clicking on the rectangle will have no surprise effect.

You can slightly tweak this method if a variety of colors are desired.

I can post the implementation details if people are interested, skeptical, or if it's tricky to get working.

Disturbed answered 1/12, 2013 at 5:44 Comment(3)
I would like to know more about this solution. Especially #3. How do you know where to place the rectangle; what happens when the window/TextArea moves; how do you deal with exceptions like half-obscured or hidden text; what happens when the TextArea scrolls?Fca
Whoa, dude, that's hacky! kudos! ;-)Merissameristem
How do you decide where to put this rectangle? For instance, say I want to highlight characters 7-13 (and I don't know if that includes a line break!!), how can I decide where the top left of the rectangle goes?Merissameristem
V
0

I have a similar requirement and @en_Knight answer is quite promising to me. He has just provided the theoretical details only. Based on his inputs, I thought to give a try and below is what I am ended with.

There are many cases that still need to be handled. But atleast this should give enough idea of what @en_Knight is telling about.

Please check the below runnable demo::

import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.geometry.Bounds;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.effect.BlendMode;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.scene.shape.Path;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;

public class HighlightableTextAreaDemo extends Application {
    @Override
    public void start(Stage stage) throws Exception {
        VBox root = new VBox();
        root.setSpacing(10);
        root.setPadding(new Insets(10));
        Scene sc = new Scene(root, 600, 600);
        stage.setScene(sc);
        stage.show();


        final HighlightableTextArea highlightableTextArea = new HighlightableTextArea();
        highlightableTextArea.setText("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.");
        highlightableTextArea.getTextArea().setWrapText(true);
        highlightableTextArea.getTextArea().setStyle("-fx-font-size: 20px;");
        VBox.setVgrow(highlightableTextArea,Priority.ALWAYS);

        Button highlight = new Button("Highlight");
        TextField stF = new TextField("40");
        TextField enF = new TextField("50");
        HBox hb = new HBox(highlight,stF,enF);
        hb.setSpacing(10);
        highlight.setOnAction(e->{highlightableTextArea.highlight(Integer.parseInt(stF.getText()), Integer.parseInt(enF.getText()));});

        Button remove = new Button("Remove Highlight");
        remove.setOnAction(e->highlightableTextArea.removeHighlight());

        Label lbl = new Label("Resize the window to see if the highlight is moving with text");
        lbl.setStyle("-fx-font-size: 17px;-fx-font-style:italic;");
        HBox rb = new HBox(remove,lbl);
        rb.setSpacing(10);

        root.getChildren().addAll(hb,rb,highlightableTextArea);
    }

    public static void main(String[] args) {
        Application.launch(args);
    }

    /**
     * Custom TextArea Component.
     */
    class HighlightableTextArea extends StackPane {
        final TextArea textArea = new TextArea();
        int highlightStartPos = -1;
        int highlightEndPos = -1;
        boolean highlightInProgress = false;

        final Rectangle highlight = new Rectangle();

        private StringProperty text = new SimpleStringProperty();

        private Group selectionGroup;

        public final String getText() {
            return text.get();
        }

        public final void setText(String value) {
            text.set(value);
        }

        public final StringProperty textProperty() {
            return text;
        }

        public HighlightableTextArea() {
            highlight.setFill(Color.RED);
            highlight.setMouseTransparent(true);
            highlight.setBlendMode(BlendMode.DARKEN);

            textArea.textProperty().bindBidirectional(text);
            getChildren().add(textArea);
            setAlignment(Pos.TOP_LEFT);
            textArea.widthProperty().addListener((obs, oldVal, newVal) -> {
                if (highlightStartPos > -1 && highlightEndPos > -1 && selectionGroup != null) {
                    highlightInProgress = true;
                    textArea.selectRange(highlightStartPos, highlightEndPos);
                    Bounds bounds = selectionGroup.getBoundsInLocal();
                    updateHightlightBounds(bounds);
                }
            });
        }

        private void updateHightlightBounds(Bounds bounds) {
            if (bounds.getWidth() > 0) {
                if (!getChildren().contains(highlight)) {
                    getChildren().add(highlight);
                }
                highlight.setTranslateX(bounds.getMinX() + 1);
                highlight.setTranslateY(bounds.getMinY() + 1);
                highlight.setWidth(bounds.getWidth());
                highlight.setHeight(bounds.getHeight());
                Platform.runLater(() -> {
                    textArea.deselect();
                    highlightInProgress = false;
                });
            }
        }

        public TextArea getTextArea() {
            return textArea;
        }

        @Override
        protected void layoutChildren() {
            super.layoutChildren();
            if (selectionGroup == null) {
                final Region content = (Region) lookup(".content");
                // Looking for the Group node that is responsible for selection
                content.getChildrenUnmodifiable().stream().filter(node -> node instanceof Group).map(node -> (Group) node).filter(grp -> {
                    boolean notSelectionGroup = grp.getChildren().stream().anyMatch(node -> !(node instanceof Path));
                    return !notSelectionGroup;
                }).findFirst().ifPresent(n -> {
                    n.boundsInLocalProperty().addListener((obs, old, bil) -> {
                        if (highlightInProgress) {
                            updateHightlightBounds(bil);
                        }
                    });
                    selectionGroup = n;
                });
            }
        }

        public void highlight(int startPos, int endPos) {
            highlightInProgress = true;
            highlightStartPos = startPos;
            highlightEndPos = endPos;
            textArea.selectRange(startPos, endPos);
        }

        public void removeHighlight() {
            textArea.deselect();
            getChildren().remove(highlight);
            highlightStartPos = -1;
            highlightEndPos = -1;
        }

    }

}
Vampire answered 16/11, 2018 at 5:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.