JDK-8091012 : [Text] No way to distinguish between a hit to the last character and a hit beyond the end of text
  • Type: Enhancement
  • Component: javafx
  • Sub-Component: graphics
  • Affected Version: 8u20
  • Priority: P4
  • Status: Open
  • Resolution: Unresolved
  • Submitted: 2014-07-03
  • Updated: 2022-11-23
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
Other
tbdUnresolved
Related Reports
Relates :  
Relates :  
Relates :  
Relates :  
Description
When hitting a Text node, there is no way to distinguish between a hit to the trailing edge of the last character and a hit beyond the end of text. Both return a HitInfo

    charIndex: n-1, isLeading: false

where n is the length of the text. The code to showcase the hit beyond the end of text is below.
For a hit beyond the end of text, I would suggest to return a HitInfo

    charIndex: n, isLeading: true

instead. This resolves to the same insertion index, but provides a way to tell apart the two cases.

Note that making this change also resolves RT-37801.

I realize that impl_* methods are not public API, but I'm using them in RichTextFX [1] anyway because there's no public API for this functionality. If you are planning/willing to expose more public API on Text/TextFlow, I will be happy to summarize what private API is currently used in RichTextFX.


import javafx.geometry.Point2D;
import javafx.scene.text.Text;

import com.sun.javafx.scene.text.HitInfo;

public class HitEndOfText {

    public static void main(String[] args) {
        Text text = new Text("foo");
        HitInfo hit = text.impl_hitTestChar(new Point2D(9999, 1));
        System.out.println(hit);
        // prints: charIndex: 2, isLeading: false
        // expected: charIndex: 3, isLeading: true
    }

}

[1] https://github.com/TomasMikula/RichTextFX
Comments
I guess that could work as the new workaround.
25-11-2015

Ah, good point. You could use BreakIterator to calculate the next index. int i1 = hit.getCharIndex(); int i2 = hit.getInsertionIndex(); if (hit.isLeading()) { BreakIterator charIterator = BreakIterator.getCharacterInstance(); charIterator.setText(text); int next = charIterator.following(i1); if (next == BreakIterator.DONE) { i2 = i1 + 1; } else { i2 = next; } }
25-11-2015

That will produce a false negative for any direct hit to the leading half of a character: charIndex and insertionIndex will be equal, so the shape will be empty, so a miss will be reported, while it is a direct hit.
25-11-2015

I haven't tested this, but it seems that you could calculate the distance from the point to the character's bounding box using something like this (with the proposed new API for 9). HitInfo hit = text.hitTest(point); Path shape = new Path(text.rangeShape(hit.getCharIndex(), hit.getInsertionIndex())); if (shape.getBoundsInLocal().contains(point)) { // Direct hit }
25-11-2015

I agree that my proposed solution is not robust. I was just hoping that it would generally make sense, not hurt anyone and help in my specific use-case. Let's focus on the best way to determine the character index at (x, y), if any. Thanks for the proposed workaround.
09-07-2014

>y to paragraphIndex is common, y to lineIndex is not currently used anywhere in RichTextFX. There's y to paragraphIndex and then hit test on the paragraph's TextFlow. Other option is to get the offset returned by the hit test, find the line, find the line bounds, intersect it with the x,y point. You can see the offset to line index at the first of PrismTextLayot#getCaretShape(). At this point I don't think that changing impl_hitTestChar() is really going to help you much, you still have the before the line case and the wrapped line case. The change is probably fine, but there is a change is can break something in controls and we also need to make sure that "charIndex: n-1, isLeading: false" and "charIndex: n, isLeading: true" produce the exactly same caret shape (when used in getImpl_caretShape(). This needs to be true for complex text (Thai cluster for example) and a bidi (Arabic or Hebrew). Do you agree ?
09-07-2014

> Do you use one TextFlow per line/paragraph ? Yes. > If so I would imagine the y to lineIndex procedure to be very common, no ? y to paragraphIndex is common, y to lineIndex is not currently used anywhere in RichTextFX. There's y to paragraphIndex and then hit test on the paragraph's TextFlow. > You should probably keep track if the editor is using a single font (and no wrap), therefore fixed line height. In which case y to lineIndex is nothing bug a single division. I need RichTextFX to work in general, not just for code editors with no line wrapping. > That is a very big (and common) performance gain in an editor (helps with scroll per line, page, hit test, scrollbar configuration, etc). I may employ optimizations for this special case, but if the general case is slow, it's still bad.
07-07-2014

>OK, but then I first need to determine the line at y. Do you use one TextFlow per line/paragraph ? If so I would imagine the y to lineIndex procedure to be very common, no ? You should probably keep track if the editor is using a single font (and no wrap), therefore fixed line height. In which case y to lineIndex is nothing bug a single division. That is a very big (and common) performance gain in an editor (helps with scroll per line, page, hit test, scrollbar configuration, etc).
07-07-2014

OK, but then I first need to determine the line at y.
07-07-2014

>If there was API for that, it would be great. In my current workaround I'm doing something similar: I get the caret shape for the insertion position and then If you are already getting the TextLine (TextLayout#getLines()) then you have a getBounds() in there. These bounds should be good to you, if I'm not wrong they included the line.x which accounts for line alignment. Note you can have the same problem of the left (before the last char), specially easy to verify if the text center or right aligned (or mirrored, for bidi text). I believe you should fix this bug using the lines bounds. Maybe you don't care for wrapped lines today, but that can change, and your code will be ready to handle it.
07-07-2014

> Then you will have the bug (showing the tooltip when the cursor is beyond the last char) for wrapped lines, no ? In the general case, yes. I need it for a code editor with no line wrapping, so I didn't care about wrapped lines. > It seems to me you want to intersect the x,y with lines bounds before doing using getHitInfo() ? > i.e, "if (x > lineWidth) return;" If there was API for that, it would be great. In my current workaround I'm doing something similar: I get the caret shape for the insertion position and then if(x > caretBounds.getMinX()) return;
07-07-2014

>I meant the change to only affect the end of the whole text, Then you will have the bug (showing the tooltip when the cursor is beyond the last char) for wrapped lines, no ? It seems to me you want to intersect the x,y with lines bounds before doing using getHitInfo() ? i.e, "if (x > lineWidth) return;"
07-07-2014

I meant the change to only affect the end of the whole text, i.e. the end of the last line if text is wrapped. Then, for placing the caret, "charIndex: n, isLeading: true", is correct as well, right? I am trying to get the character index under the mouse. (And display a tooltip whose content depends on the word under cursor, as in [1]. I don't want to display any tooltip if the mouse is beyond the end of line.) Here's the list of what other private API I use in RichTextFX: * I get the TextLayout instance from TextFlow by reflection. * I call getHitInfo() on TextLayout (this one is covered by JIRA issues). * I call getCaretShape() and getRange() mathods on TextLayout. * I call getLines() on TextLayout by reflection and use it to: * get the number of visual lines in a TextFlow; * get line number for a logical position in text; * get vertical center of a line * using this to implement getHitInfo(double x, int lineIdx), i.e. hitting the TextFlow at the given line and x coordinate. [1] https://github.com/TomasMikula/RichTextFX#custom-tooltips
04-07-2014

Personally I think that "charIndex: n-1, isLeading: false" is correct. If you click beyond the end line you should put caret at "charIndex: n-1, isLeading: false". You need to think about wrapping when considering this problem, the solution you proposed does not work when clicking on beyond the end wrapped line. Are you trying to place the caret ? Is not, what are you trying to do ? As for API, see: https://javafx-jira.kenai.com/browse/RT-29257 https://javafx-jira.kenai.com/browse/RT-29077 https://javafx-jira.kenai.com/browse/RT-8060 https://javafx-jira.kenai.com/browse/RT-26961 is there anything else you need not listed above ?
03-07-2014

Felipe to evaluate.
03-07-2014