RichTextFX icon indicating copy to clipboard operation
RichTextFX copied to clipboard

Wrap text in CodeArea does not refresh properly

Open prat-man opened this issue 5 years ago • 4 comments

I am trying to enable and disable text wrapping in CodeArea dynamically. Unfortunately, if horizontal scrollbar is already showing, enabling text wrapping does not refresh the CodeArea.

Also, if I change font size dynamically, and wrap text is enabled, the CodeArea does not refresh properly either, and there are line gaps in the text if width becomes less than viewport width when font size is decreased.

I am enabling and disabling text wrap using codeArea.setWrapText(wrapFlag);

I am changing font size using codeArea.setStyle("-fx-font-size: " + fontSize + "px");

I must note that when I scroll vertically after either of this for some time, the CodeArea is refreshed. Can you please help me with this.

prat-man avatar Nov 13 '20 22:11 prat-man

You could try calling codeArea.requestLayout() after either setWrapText or setStyle.

Jugen avatar Nov 29 '20 12:11 Jugen

I have already tried calling codeArea.requestLayout() but to no avail.

I have built a demo to explain the scenario.

You can copy a large text which is wider than the width of the CodeArea from anywhere into the CodeArea in the example below and then click on Wrap Text button to reproduce the issue.

import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.ToggleButton;
import javafx.scene.layout.*;
import javafx.stage.Stage;
import org.fxmisc.flowless.VirtualizedScrollPane;
import org.fxmisc.richtext.CodeArea;

public class WrapTest extends Application {

    @Override
    public void start(Stage primaryStage) {
        primaryStage.setTitle("Wrap Text Test");

        // creating the layout
        // main vbox holding everything together
        VBox vBox = new VBox();
        vBox.setPadding(new Insets(10));
        vBox.setSpacing(10);

        // anchor pane holding the scrollable CodeArea
        AnchorPane anchorPane = new AnchorPane();
        VBox.setVgrow(anchorPane, Priority.ALWAYS);

        // the scrollable code area
        CodeArea codeArea = new CodeArea();
        VirtualizedScrollPane scrollPane = new VirtualizedScrollPane<>(codeArea);

        AnchorPane.setTopAnchor(scrollPane, 0.0);
        AnchorPane.setRightAnchor(scrollPane, 0.0);
        AnchorPane.setBottomAnchor(scrollPane, 0.0);
        AnchorPane.setLeftAnchor(scrollPane, 0.0);

        codeArea.setStyle("-fx-font-size: 20px;");
        codeArea.setStyle("-fx-border-color: lightgray;");

        anchorPane.getChildren().add(scrollPane);
        vBox.getChildren().add(anchorPane);

        // the hbox button panel
        HBox hBox = new HBox();

        // the wrap text toggle button
        ToggleButton wrapTextButton = new ToggleButton("Wrap Text");
        wrapTextButton.selectedProperty().addListener((obs, oldVal, newVal) -> {
            // toggle text wrap on the code area
            codeArea.setWrapText(newVal);

            // attempt to refresh layout
            codeArea.requestLayout();
        });
        hBox.getChildren().add(wrapTextButton);

        vBox.getChildren().add(hBox);

        primaryStage.setScene(new Scene(vBox, 300, 275));
        primaryStage.setMaximized(true);
        primaryStage.show();
    }

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

}

prat-man avatar Dec 01 '20 23:12 prat-man

Thanks for the demo :-) So .... RichTextFX uses another library called Flowless which governs the width of the control.

Flowless doesn't process it's entire contents to determine the max width but only the visible items, so when setWrapText(true) is invoked Flowless doesn't recalculate all the items but only what is visible. But since all of the visible items are now shorter than the max width it doesn't reset (at least that's my understanding of what's happening - the code is complicated).

Unfortunately the original author of Flowless isn't maintaining the code any more and because I'm not familiar with the code it's awfully difficult and time consuming to figure out how it works or how to change it. Besides it's not even clear which side is misbehaving RichTextFX or Flowless.

That's the bad news, the good news is that there's a horrible hack - behold:

// toggle text wrap on the code area
codeArea.setWrapText( wrap );

if ( wrap ) // brute force refresh :(
{
    final int c = codeArea.getCaretPosition();
    final int p = codeArea.firstVisibleParToAllParIndex();
    final StyledDocument doc = codeArea.subDocument( 0, codeArea.getLength() );

    codeArea.clear();

    codeArea.insert( 0, doc );
    codeArea.showParagraphAtTop( p );
    codeArea.moveTo( c );
}

I'm not sure about including / baking this into RichTextFX ? Anyway hope that it works okay for you .... ?

Jugen avatar Dec 02 '20 13:12 Jugen

Sorry for the late reply.

I tested your solution. It works great! Thanks! However, it does not play well some parts of my application so I need to figure those out.

But seriously, the solution/hack is a life saver. Thanks again!

prat-man avatar Dec 07 '20 21:12 prat-man