Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Question: how to get real size of one span at position X? #1231

Closed
PavelTurk opened this issue May 24, 2024 · 15 comments · Fixed by #1232
Closed

Question: how to get real size of one span at position X? #1231

PavelTurk opened this issue May 24, 2024 · 15 comments · Fixed by #1232

Comments

@PavelTurk
Copy link
Contributor

When I do

for (var span : codeArea.getStyleSpans(0, codeArea.getText().length())){
    System.out.println(span);
}         

I get:

StyleSpan[length=6, style=[]]
StyleSpan[length=5, style=[MyStyle{...}]]
StyleSpan[length=434, style=[]]

So, I there are three spans. The length of the first is 6. Now I want to get the length of the span at position X. But when I do:

var spans = codeArea.getStyleSpans(3, 4);
System.out.println("L:" + spans.iterator().next().getLength());

I get:

L:1

Can I get ONE span at position X and its full length?

@Jugen
Copy link
Collaborator

Jugen commented May 27, 2024

Would codeArea.getStyleRangeAtPosition( pos ); maybe help ?

@PavelTurk
Copy link
Contributor Author

@Jugen Thank you for your answer. This is my code:

System.out.println("The position:" + position);
var range = this.textArea.getStyleRangeAtPosition(position - 1);
System.out.println("The range:" + range);

And this is output:

The position:304
The range:16, 20

Firstly I couldn't understand how it is possible. But it can be possible if returned range is considered in specific paragraph. But, what if my span takes two paragraphs? Then how can I take the multiparagraph span at position X? Or I misunderstand something.

@Jugen
Copy link
Collaborator

Jugen commented May 28, 2024

Yeah, I'm surprised by that as well !? Looking at the code reveals that the returned IndexRange is relative to the paragraph as you guessed.
I'd say this is a bug but I'm not going to change it since it's been like that for how long and may break somebody's code ? I will however add a note to the JavaDoc for those methods.

I'd suggest using getStyleRangeAtPosition( par, col ) instead where it makes more sense that the returned IndexRange is relative to the paragraph.

For style spans over multiple paragraphs you could try the following based on what is done in Paragraph:

// Must use this method of getStyleSpans or the one with parameter IndexRange and not any other !
var spans = codeArea.getStyleSpans( 0, codeArea.getLength() );
var offset = spans.offsetToPosition( targetPos, Bias.Backward );
var styleStart = offset.getMinor();
var styleEnd = styleStart + spans.getStyleSpan( offset.getMajor() ).getLength();

@PavelTurk
Copy link
Contributor Author

@Jugen Thank you for your answer. I couldn't make it work:

My code:

    public IndexRange getRealStyleRangeAt(int position) {
        // Must use this method of getStyleSpans or the one with parameter IndexRange and not any other !
        var spans = this.getStyleSpans(0, this.getLength());
        var offset = spans.offsetToPosition(position, Bias.Backward);
        var styleStart = offset.getMinor();
        var styleEnd = styleStart + spans.getStyleSpan( offset.getMajor() ).getLength();
        return new IndexRange(styleStart, styleEnd);
    }

Usage:

    System.out.println("The position:" + position);
    var range = this.textArea.getRealStyleRangeAt(position - 1);
    System.out.println("The range:" + range);

Output:

The position:302
The range:2, 5

@PavelTurk
Copy link
Contributor Author

PavelTurk commented May 28, 2024

I can provide test code, if it's required. I think it is necessary to add to RichTextFX API something like this.

@Jugen
Copy link
Collaborator

Jugen commented May 28, 2024

Please do provide test code, then I'll have go at it ....

@PavelTurk
Copy link
Contributor Author

This is code:

public class JavaFxTest7 extends Application {

    private final CodeArea codeArea = new CodeArea();

    @Override
    public void start(Stage stage) {
        var text = """
        <?xml version="1.0" encoding="UTF-8"?>
        <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
            <modelVersion>4.0.0</modelVersion>
            <groupId>com.foo</groupId>
            <artifactId>bar</artifactId>
            <version>1.0.0</version>
            <packaging>pom</packaging>
            <name>Foo</name>
        """;
        this.codeArea.appendText(text);
        VBox.setVgrow(codeArea, Priority.ALWAYS);
        StyleSpansBuilder<Collection<String>> ssb = new StyleSpansBuilder<>();
        ssb.add(Collections.singletonList("highlighted"), 3);
        codeArea.setStyleSpans(300, ssb.create());

        var button = new Button("Test");
        button.setOnAction((e) -> {
            var position = this.codeArea.getCaretPosition();
            System.out.println("The position:" + position);
            var range = this.getRealStyleRangeAt(position - 1);
            System.out.println("The range:" + range);
        });

        var css= this.getClass().getResource("test7.css").toExternalForm();
        Scene scene = new Scene(new VBox(codeArea, button), 600, 200);
        scene.getStylesheets().add(css);
        stage.setScene(scene);
        stage.show();
    }

    public IndexRange getRealStyleRangeAt(int position) {
        // Must use this method of getStyleSpans or the one with parameter IndexRange and not any other !
        var spans = this.codeArea.getStyleSpans(0, this.codeArea.getLength());
        var offset = spans.offsetToPosition(position, TwoDimensional.Bias.Backward);
        var styleStart = offset.getMinor();
        var styleEnd = styleStart + spans.getStyleSpan( offset.getMajor() ).getLength();
        return new IndexRange(styleStart, styleEnd);
    }

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

This is gif:
Peek 2024-05-28 18-24

@Jugen
Copy link
Collaborator

Jugen commented May 29, 2024

Try updating getRealStyleRangeAt to the following:

    public IndexRange getRealStyleRangeAt( int position )
    {
        var length = 0;
        for ( var ss : codeArea.getStyleSpans( 0, codeArea.getLength() ) )
        {
            if ( (length += ss.getLength()) >= position )
            {
                var start = length - ss.getLength();
                return new IndexRange( start, length );
            }
        }

        return new IndexRange( 0,0 );
    }

@PavelTurk
Copy link
Contributor Author

@Jugen This is what I wanted to avoid. As I understand RichTextFX has its own model for style span ranges and I wanted to use it instead if iterating all style spans. Or there is no chance to get REAL range for concrete position?

@Jugen
Copy link
Collaborator

Jugen commented May 29, 2024

Yeah, I don't like the iteration either but I don't know of another way as I haven't been able to find it in the model.
The StyleSpans interface extends TwoDimensional which only seems to be able to give us relative ranges and not absolute ones.
The API allows us to get a StyleSpan at a particular position and to tell us where in that StyleSpan the position is but it has no knowledge of it's own position and StyleSpans doesn't seem to know the absolute position either.

I can try and add a start field to StyleSpan which StyleSpansBuilder can update when creating StyleSpans. Then integrate getRealStyleRangeAt into StyleSpans which can then be reduced down to the following I think:

var offset = spans.offsetToPosition( position, Bias.Backward );
var span = spans.getStyleSpan( offset.getMajor() );
var spanStart = span.getStart();
return new IndexRange( spanStart, spanStart + span.getLength() );

@PavelTurk
Copy link
Contributor Author

PavelTurk commented May 29, 2024

@Jugen The only thing I can suggest is to distinguish relative ranges and absolute ones in API. Maybe something like this getAbsoluteStyleRangeAt(...).

@Jugen
Copy link
Collaborator

Jugen commented May 29, 2024

Please see the PR that I've submitted to try and address this issue for you.

@PavelTurk
Copy link
Contributor Author

@Jugen I've checked. It seems to work. At least, all my tests finally passed. Only one question - when I do:
textArea.setStyleSpans(position, ssb.create()) then this modification is saved to undo list as a separate action. But this is not right as it is not an user action. Could you say, how I can exclude my style modification from undo actions? By other words undo manager must ignore my style operation.

@Jugen
Copy link
Collaborator

Jugen commented May 29, 2024

Do the following three steps ....

  1. Declare private field:
private SuspendableYes suspendUndo = new SuspendableYes();
  1. Set undo manager when creating area:
textArea.setUndoManager( UndoUtils.richTextSuspendableUndoManager( textArea, suspendUndo ) );
  1. Use suspendUndo when required:
suspendUndo.suspendWhile( () -> textArea.setStyleSpans( position, ssb.create() ) );

@PavelTurk
Copy link
Contributor Author

@Jugen Yes, it helped. Thank you very much!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants