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

Are there modelToView/viewToModel equivalents for StyledTextArea #385

Closed
garybentley opened this issue Oct 22, 2016 · 12 comments
Closed

Are there modelToView/viewToModel equivalents for StyledTextArea #385

garybentley opened this issue Oct 22, 2016 · 12 comments

Comments

@garybentley
Copy link

I would need to be able to translate an absolute position in the text to a location within the viewport. Is this possible with StyledTextArea? The closest I've found is getSelectionBoundsOnScreen but of course that is private and doesn't take arguments.

Also being able to go the other way is also useful (i.e. viewToModel) but my experiments with hit(double, double) have just left me confused. I have no idea why a call of hit(100, 100) would return a position that is 74,000 characters into the text. Is there a way to translate a position (bounding box) to an absolute offset in the text?

@JordanMartinez
Copy link
Contributor

my experiments with hit(double, double) have just left me confused. I have no idea why a call of hit(100, 100) would return a position that is 74,000 characters into the text.

@garybentley Could you post a simple example of this occurring?

@garybentley
Copy link
Author

garybentley commented Nov 1, 2016

I've modified the demo: /~https://github.com/TomasMikula/RichTextFX/blob/master/richtextfx-demos/src/main/java/org/fxmisc/richtext/demo/JavaKeywords.java

with the following changes:

`
Path file = FileSystems.getDefault ().getPath ("d:/development/test-chapter.txt");

    sampleCode = new String (Files.readAllBytes (file), "utf-8");

    } catch (Exception e) {
            e.printStackTrace ();

    }
    codeArea.setWrapText (true);
    codeArea.replaceText(0, 0, sampleCode);

    VirtualizedScrollPane sp = new VirtualizedScrollPane<>(codeArea);
    sp.setHbarPolicy (javafx.scene.control.ScrollPane.ScrollBarPolicy.NEVER);

    Scene scene = new Scene(new StackPane(sp), 800, 600);
    scene.getStylesheets().add(JavaKeywordsAsync.class.getResource("java-keywords.css").toExternalForm());
    primaryStage.setScene(scene);
    primaryStage.setTitle("Java Keywords Demo");
    primaryStage.show();
    System.out.println ("Position: " + codeArea.position (5, 5));
    System.out.println ("Abs position: " + codeArea.getAbsolutePosition (5, 5));
    System.out.println ("Text: " + sampleCode.substring (codeArea.hit (100, 100).getInsertionIndex (), codeArea.hit (100, 100).getInsertionIndex () + 30));`

The key changes I've made are:

  • turning off horizontal scrollbars
  • wrapping the text

The test-chapter.txt file contains about 80k characters and is standard ascii text.

I'm sure I'm doing something wrong, I'm just not sure what.

@JordanMartinez
Copy link
Contributor

@garybentley I now understand what you're asking. The area uses a virtual flow as an optimization technique: it only displays just enough content to fill the "viewport," saving memory; it does not display every line as a TextFlow before clipping that content to only display a portion of it.

So, since you are loading a very long document, the area's viewport is scrolling to the bottom of the document when you call replaceText. Thus, when you call hit, it returns the 74,000th character because that's the character that's currently at the position in the area's viewport.

I'm rephrasing your question:

Is it possible to translate an absolute position in the text to a location within the viewport (model -> view) and vice versa (view -> model)?

In answer to this question:
When the text is being displayed in the viewport, yes via hit.
When the text is not being displayed in the viewport, No.

You could force the area to display specific lines via its show method, but that's currently package-private. My guess is this could be made public.

The other alternative is to move the caret and then force the viewport to follow it:

codeArea.moveTo(5, 5);
codeArea.requestFollowCaret();

However, this latter method, requestFollowCaret, is not in the 0.7-M2 release as it was only recently added and may not yet be in the latest snapshot version.

@garybentley
Copy link
Author

Thanks for the response.

So in relation to:

When the text is not being displayed in the viewport, No.

Can you let me know how I would go about finding the location. That is a starting point on how to add/change the code. Would I need to just go through the TextFlows and find the locations myself?

I have a couple of other questions relating to tracking locations/moving things, am I better off creating a separate issue(s) or just adding them to this thread?

@JordanMartinez
Copy link
Contributor

Can you let me know how I would go about finding the location. That is a starting point on how to add/change the code. Would I need to just go through the TextFlows and find the locations myself?

It might help if you explain more of what you are trying to accomplish and your current idea for how to accomplish that.

I have a couple of other questions relating to tracking locations/moving things, am I better off creating a separate issue(s) or just adding them to this thread?

It's hard to say since I'm not sure what they are.

How about you post your questions here first. If they should be in a new issue, I can let you know, you can delete the post that has those questions, this issue can be closed, and we can deal with each one separately. If they shouldn't be, we can update this issue's title and deal with them here.

@garybentley
Copy link
Author

I have no idea currently on how to achieve anything to be honest, I'm just trying to evaluate RichTextFX as a replacement for JTextPane for FX.

I need to be able to move around the text view from a location in the text itself, i.e. an offset into the text string. Conversely I need to be able to determine from the view what offset into the text a position is. In JTextPane these are achieved by viewToModel and modelToView. For my writing application I have to move to various points in the text and highlight it as well as place icons in a left hand margin that are at the relevant place in the text view. So a user selects a chunk of text and adds an annotation, an icon appears in the margin to the left to indicate this annotation.

As to my other questions they relate to how to track a position in a document. As mentioned when the user selects a chunk of text I create a position (currently javax.swing.text.Position), it will then keep track of that location as the user adds/removes text to the view. Is this possible in RichTextFX? Again, if not, what's a good place to start to add it?

Another related question is how would I scroll the view to a particular position? The code seems to indicate that the scroll position is not always absolutely accurate, is this correct? This question flows from the others since once you have a position or offset you want to move to it in the view but you don't always want to move the caret.

@JordanMartinez
Copy link
Contributor

I need to be able to move around the text view from a location in the text itself, i.e. an offset into the text string.

Such as getTargetCaretOffsetX()? It's not public, but that's not hard to fix.

I have to move to various points in the text...

When you say move to various points in the text, are you referring to the caret? Or what the viewport is displaying? If the first, it sounds like the methods I added here. If the second, see my last response.

...as well as place icons in a left hand margin that are at the relevant place in the text view. So a user selects a chunk of text and adds an annotation, an icon appears in the margin to the left to indicate this annotation.

Sounds like a LineNumberFactory. See the demo

As to my other questions they relate to how to track a position in a document. As mentioned when the user selects a chunk of text I create a position (currently javax.swing.text.Position), it will then keep track of that location as the user adds/removes text to the view.

I'm not familiar with Swing, but I'm guessing that Position updates every time that text gets moved around in the area. So, when the user selects some text (or triggers the code in some way), it stores the original position. Then, when the user adds/deletes text in front of it, the position updates its index to point to the new position, correct? You could do something like that by subscribing to the area's plainTextChanges(). and use the length difference between its removed and inserted to calculate how much to bump that position.

Another related question is how would I scroll the view to a particular position? The code seems to indicate that the scroll position is not always absolutely accurate, is this correct?

Let me explain the estimatedScrollX/Y values. Since the viewport (the virtualflow) does not display all of the text in the document, it does not know how "tall" or "wide" it really is. Hence the 'estimate' part of the variable. If the text is not wrapped and the same font size is used throughout, then these values are accurate. However, if text is wrapped at some point, or different font sizes are used throughout the area, then these values are only estimates.

So, it depends on how accurate you want that to be. Calling setEstimateScrollY and passing in some value will give you very unpredictable results. Now, I believe that if you call moveTo([some position]); requestFollowCaret();, then you'll force the view to scroll to wherever you move that caret. The other option is to expose the API for calling VirtualFlow#show(int) to force the virtualflow to display a specific line.

@garybentley
Copy link
Author

When I talk about moving I mean moving the text within the view, the caret wouldn't move (although of course that functionality is needed as well). For example, my writing application lets writers find "problems" within the text and the text needs to jump from one problem to the next, basically moving the text within the viewport to make it visible. I might find a problem at an offset of 10,000 characters into the text, I would need to translate that 10,000 value into an X,Y offset into the view then scroll the view to the relevant Y (and potentially do something with the X, like display a popup).

I don't think that getTargetCaretOffsetX would be suitable since it takes no arguments and seems to indicate an X offset into the paragraph box (of the caret) however I would need both X and Y of the text location within the box. Is there a way to find an X,Y location for a text position within a paragraph box? I could then use that to build up the location I need by cycling over the paragraph boxes.

Similarly LineNumberFactory wouldn't be what I needed since the line can wrap and the icon may have to be placed below the start of the line. Although of course if I can determine the Y offset within the ParagraphBox then I could adjust it.

Do you have an idea of the performance impact of calculating the actual size of each ParagraphBox, i.e. so doing a full layout of all the text? This would only need to be done when the view size changes so it can be mitigated but that would then allow me to find the locations I need.

@JordanMartinez
Copy link
Contributor

When I talk about moving I mean moving the text within the view, the caret wouldn't move (although of course that functionality is needed as well). For example, my writing application lets writers find "problems" within the text and the text needs to jump from one problem to the next, basically moving the text within the viewport to make it visible. I might find a problem at an offset of 10,000 characters into the text, I would need to translate that 10,000 value into an X,Y offset into the view then scroll the view to the relevant Y (and potentially do something with the X, like display a popup).

So you're approach would be something like this?

  1. Find the (possibly undisplayed) problem's location in the document
  2. Display that location in the view
  3. Calculate the x,y coordinates of that problem (which is now displayed)
  4. Do something with the coordinates and other related info.

I don't think that getTargetCaretOffsetX would be suitable since it takes no arguments and seems to indicate an X offset into the paragraph box (of the caret) however I would need both X and Y of the text location within the box.

Right, an X position in terms of the text, not screen coordinates. For example, positions are the spaces around the characters a caret would go. if "|" represents a position, then getTargetCaretOffsetX could be any of these positions: "|a|p|p|l|e|"

Is there a way to find an X,Y location for a text position within a paragraph box? I could then use that to build up the location I need by cycling over the paragraph boxes.

I don't believe API exists for that right now probably because the project was designed around the MVC paradigm via the Control object, which did not expose the view for manipulation. Now that we've switched to (at least in my understanding) a MVVM paradigm, which exposes the view for manipulation, some situations like this one have not yet been accounted for.

Similarly LineNumberFactory wouldn't be what I needed since the line can wrap and the icon may have to be placed below the start of the line. Although of course if I can determine the Y offset within the ParagraphBox then I could adjust it.

#349 could potentially help with that. However, I'm not sure why Tomas hasn't merged the code. Otherwise, implementing such a feature may require some hackery, but I don't know as I haven't had to deal with this issue before myself.

Do you have an idea of the performance impact of calculating the actual size of each ParagraphBox, i.e. so doing a full layout of all the text? This would only need to be done when the view size changes so it can be mitigated but that would then allow me to find the locations I need.

Sorry but I don't know. FXMisc/Flowless#5 relates somewhat to this issue; however, creating an API for finding a text's location via a Point2D or more probably Bounds object sounds like the real solution you want.

@JordanMartinez
Copy link
Contributor

I don't think that getTargetCaretOffsetX would be suitable since it takes no arguments and seems to indicate an X offset into the paragraph box (of the caret) however I would need both X and Y of the text location within the box.

This is the same issue raised in #150, which is being addressed by #455.

@JordanMartinez
Copy link
Contributor

Is there a way to find an X,Y location for a text position within a paragraph box? I could then use that to build up the location I need by cycling over the paragraph boxes.

This is now possible via getCharacterBoundsOnScreen(int from, int to)

@JordanMartinez
Copy link
Contributor

Closing because the discussion seems finished and there are multiple issues/questions/ideas raised in this that need to be separated from one another.

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

No branches or pull requests

2 participants