FULL PRODUCT VERSION :
java version "10-ea" 2018-03-20
Java(TM) SE Runtime Environment 18.3 (build 10-ea+37)
Java HotSpot(TM) 64-Bit Server VM 18.3 (build 10-ea+37, mixed mode)
ADDITIONAL OS VERSION INFORMATION :
Microsoft Windows [Version 10.0.16299.125]
A DESCRIPTION OF THE PROBLEM :
The description of the TextFlow HitTest is:
"Maps local point to index in the content."
This does not return the node index at the given coordinates. Instead it returns the character index within the Text node.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Create TextFlow contining several nodes. Add mouse listener to each node. Use mouse coordinates as input to TextFlow HitTest. Print the HitInfo that is returned.
Also print HitInfo for the Text node HitTest. The two HitInfo are identical. Compare actual node index to TextFlow HitInfo results; they don't match.
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
The TextFlow HitTest should return the node index at the input coordinates.
ACTUAL -
The TextFlow HitTest returned the same results as the Text node HitTest.
REPRODUCIBILITY :
This bug can be reproduced always.
---------- BEGIN SOURCE ----------
//
package tools;
//
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.scene.Node;
import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.geometry.Bounds;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.geometry.Point2D;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import javafx.scene.text.Text;
import javafx.scene.text.HitInfo;
import javafx.scene.text.TextFlow;
import javafx.scene.layout.VBox;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.StackPane;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.MouseButton;
import javafx.scene.paint.Color;
import javafx.collections.ObservableMap;
import javafx.event.EventHandler;
//
//
public class FlowHit extends Application {
/*
This app tests the HitTest method for Text and TextFlow. Both methods
return the same HitInfo.
Is this a bug in TextFlow?
*/
private static final Logger LOG = Logger.getLogger(FlowHit.class.getName());
//
//init fonts
private Font mainFont = Font.font("DejaVu Serif", FontWeight.NORMAL, 20);
//
//init panes
private BorderPane bp = new BorderPane();
private StackPane sp = new StackPane();
private VBox vbox = new VBox();
private TextFlow flow = new TextFlow();
//
//init sample string of words
private String s1 = "Electromagnetic waves cover a wide spectrum from radio waves to gamma rays. Waves can interfere. "
+ "Constructive interference occurs when two waves are in phase. ";
/**
* Default Constructor
*/
public FlowHit() {
}
public void test(){
try{
init();
addText(flow, s1);
vbox.getChildren().add(flow);
} catch (Exception ex) {
LOG.log(Level.SEVERE, ex.getMessage(), ex);
throw new RuntimeException(ex);
}
}
public void init(){
try{
//insets: top, right, bottom, left
bp.setPadding(new Insets(5, 5, 5, 5));
bp.setStyle(" -fx-border-color: green;-fx-border-width: 2px;-fx-border-style: solid;");
bp.setPrefSize(500,400);
//
sp.setPadding(new Insets(10, 5, 10, 5));
sp.setStyle(" -fx-border-color: blue;-fx-border-width: 1px;-fx-border-style: solid;");
//
vbox.setSpacing(8);
vbox.setPadding(new Insets(15, 5, 15, 5));
vbox.setStyle(" -fx-border-color: red;-fx-border-width: 1px;-fx-border-style: solid;");
//
} catch (Exception ex) {
LOG.log(Level.SEVERE, ex.getMessage(), ex);
throw new RuntimeException(ex);
}
}
public void addText(TextFlow flow, String s){
try{
LOG.log(Level.INFO, "\n\nText s: " + s);
//split string into words
String[] ss = s.split(" ");
int len = ss.length;
for(int i = 0; i<len; i++){
//create a word
Text tx = new Text(ss[i] + " ");
//set font on each word
tx.setFont(mainFont);
//add mouse click handler
tx.setOnMouseReleased(new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent event) {
if (event.getButton() == MouseButton.PRIMARY) {
event.consume();
double mx = event.getX();
double my = event.getY();
int index = flow.getChildren().indexOf(tx);
//check baseline offset
checkHit(flow, index, mx, my);
}
}
});
//add one word to flow
flow.getChildren().add(tx);
}
flow.setStyle(" -fx-border-color: orange;-fx-border-width: 1px;-fx-border-style: solid;");
} catch (Exception ex) {
LOG.log(Level.SEVERE, ex.getMessage(), ex);
throw new RuntimeException(ex);
}
}
public void checkHit(TextFlow flow, int index, double mx, double my){
//check hit info of selected node
try{
//
Node n = flow.getChildren().get(index);
Text t = (Text)n;
//create rectangle around selected node (may be commented out)
addBox(n);
//find mouse point
Point2D pt = new Point2D(mx, my);
//
//get Text HitInfo
HitInfo hitText = t.hitTest(pt);
//
//get TextFlow HitInfo
HitInfo hitFlow = flow.hitTest(pt);
LOG.log(Level.INFO, "\n index: " + index + ", text: " + t.getText() + ", pt:" + pt + ", hitText:" + hitText + ", hitFlow:" + hitFlow );
//
} catch (Exception ex) {
LOG.log(Level.SEVERE, ex.getMessage(), ex);
throw new RuntimeException(ex);
}
}
public void addBox(Node n){
//add box around of selected node
try{
//
//get text bounds within flow (parent)
Bounds parentB = n.getBoundsInParent();
// LOG.log(Level.INFO, "\n\n node bounds in parent: " + parentB);
//convert to scene coords
Bounds sceneB = flow.localToScene(parentB);
// LOG.log(Level.INFO, "\n\n scene bounds: " + sceneB);
//create rectangle in scene coords
Rectangle r = new Rectangle( sceneB.getMinX(), sceneB.getMinY(), sceneB.getWidth(), sceneB.getHeight() );
r.setStroke(Color.GREEN);
r.setFill(Color.TRANSPARENT);
//add rectangle to border pane
bp.getChildren().add(r);
//
} catch (Exception ex) {
LOG.log(Level.SEVERE, ex.getMessage(), ex);
throw new RuntimeException(ex);
}
}
@Override
public void start(Stage primaryStage) throws Exception{
//add textflow to vbox
test();
// add stack pane to outer pane
sp.setAlignment(Pos.CENTER);
sp.getChildren().add(vbox);
bp.setCenter(sp);
//
// setup scene and stage
Scene scene = new Scene( bp, 600, 500 );
primaryStage.setScene(scene);
primaryStage.sizeToScene();
primaryStage.show();
//check baselines of selected nodes
}
public static void main(String[] args) {
launch(args);
}
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
Added mouse listener to every node within each TextFlow, and use ObservableList<Node> getChildren() -> indexOf(Object o) to determine the node index at the mouse location. Don't use the TextFlow HitTest.