diff --git a/richtextfx/src/main/java/org/fxmisc/richtext/ParagraphText.java b/richtextfx/src/main/java/org/fxmisc/richtext/ParagraphText.java index ead69125..30432a9d 100644 --- a/richtextfx/src/main/java/org/fxmisc/richtext/ParagraphText.java +++ b/richtextfx/src/main/java/org/fxmisc/richtext/ParagraphText.java @@ -95,7 +95,10 @@ public ObjectProperty highlightTextFillProperty() { Val leftInset = Val.map(insetsProperty(), Insets::getLeft); Val topInset = Val.map(insetsProperty(), Insets::getTop); - ChangeListener selectionRangeListener = (obs, ov, nv) -> requestLayout(); + ChangeListener selectionRangeListener = (obs, prevRange, nv) -> { + resetTextSelection(prevRange); + requestLayout(); + }; selectionPathListener = change -> { if (change.wasRemoved()) { SelectionPath p = change.getValueRemoved(); @@ -103,6 +106,7 @@ public ObjectProperty highlightTextFillProperty() { p.layoutXProperty().unbind(); p.layoutYProperty().unbind(); + resetTextSelection(p.rangeProperty().getValue()); getChildren().remove(p); } if (change.wasAdded()) { @@ -139,22 +143,15 @@ public ObjectProperty highlightTextFillProperty() { }; carets.addListener( caretNodeListener ); - // XXX: see the note at highlightTextFill -// highlightTextFill.addListener(new ChangeListener() { -// @Override -// public void changed(ObservableValue observable, -// Paint oldFill, Paint newFill) { -// for(PumpedUpText text: textNodes()) -// text.selectionFillProperty().set(newFill); -// } -// }); + highlightTextFill.addListener((ob,oldFill,newFill) -> getChildren().stream() + .filter( n -> n instanceof TextExt).map( n -> (TextExt) n ) + .forEach( t -> t.selectionFillProperty().set(newFill) ) + ); // populate with text nodes par.getStyledSegments().stream().map(nodeFactory).forEach(n -> { if (n instanceof TextExt) { TextExt t = (TextExt) n; - // XXX: binding selectionFill to textFill, - // see the note at highlightTextFill t.selectionFillProperty().bind(highlightTextFill); } getChildren().add(n); @@ -231,7 +228,7 @@ void dispose() { carets.removeListener( caretNodeListener ); getChildren().stream().filter( n -> n instanceof TextExt ).map( n -> (TextExt) n ) - .forEach( t -> t.selectionFillProperty().unbind() ); + .forEach( t -> t.selectionFillProperty().unbind() ); try { getChildren().clear(); } catch ( Exception EX ) {} @@ -336,6 +333,7 @@ private void updateAllSelectionShapes() { private void updateSingleSelection(SelectionPath path) { path.getElements().setAll(getRangeShapeSafely(path.rangeProperty().getValue())); + updateTextSelection(path); } private PathElement[] getRangeShapeSafely(IndexRange range) { @@ -435,32 +433,75 @@ private PathElement[] createRectangle(double topLeftX, double topLeftY, double b }; } + // XXX: Because of JDK bug https://bugs.openjdk.java.net/browse/JDK-8149134 // this does not work correctly if a paragraph contains more than one segment // and the selection is (also) in the second or later segments. // Visually the text color of the selection may be mix black & white. - private void updateTextSelection() { - int selStart = selection.get().getStart(); - int selEnd = selection.get().getEnd(); + private void updateTextSelection(SelectionPath selection) + { + IndexRange range = selection.rangeProperty().getValue(); + if (range.getLength() == 0) return; + + final int selStart = range.getStart(); + final int selEnd = range.getEnd(); + int charSoFar = 0; + + for (Node node : getChildren()) + { + if (node instanceof TextExt) + { + TextExt text = (TextExt) node; + int len = text.getText().length(); + int end = charSoFar + len; + + if (end > selStart) + { + // TODO text.setSelectionFill(selection.getTextFill()); - int start = 0; - FilteredList nodeList = getChildren().filtered(node -> node instanceof TextExt); - for (Node node : nodeList) { - TextExt text = (TextExt) node; - int end = start + text.getText().length(); + if (selStart <= charSoFar) text.setSelectionStart(0); + else text.setSelectionStart(selStart-charSoFar); - int textSelStart = Math.max(start, selStart); - int textSelEnd = Math.min(end, selEnd); - if (textSelEnd > textSelStart) { - textSelStart -= start; - textSelEnd -= start; - } else { - textSelStart = textSelEnd = -1; + if (selEnd > end) text.setSelectionEnd(len); + else + { + text.setSelectionEnd(selEnd-charSoFar); + break; + } + } + charSoFar = end; } - text.setImpl_selectionStart(textSelStart); - text.setImpl_selectionEnd(textSelEnd); + else if (node.isManaged()) // custom user nodes + { + charSoFar++; + } + } + } - start = end; + private void resetTextSelection(IndexRange range) + { + final int selStart = range.getStart(); + final int selEnd = range.getEnd(); + int charSoFar = 0; + + for (Node node : getChildren()) + { + if (node instanceof TextExt) + { + TextExt text = (TextExt) node; + charSoFar += text.getText().length(); + + if (charSoFar >= selStart) + { + text.setSelectionStart(-1); + text.setSelectionEnd(-1); + if (charSoFar >= selEnd) break; + } + } + else if (node.isManaged()) // custom user nodes + { + charSoFar++; + } } } @@ -511,9 +552,8 @@ public String toString() { @Override protected void layoutChildren() { super.layoutChildren(); - updateCaretShape(); - updateSelectionShape(); - updateTextSelection(); + updateAllCaretShapes(); + updateAllSelectionShapes(); updateBackgroundShapes(); } @@ -543,7 +583,7 @@ private void updateSharedShapeRange(T value, int start, int end, BiFunction ranges.add(Tuples.t(value, new IndexRange(start, end))); if (ranges.isEmpty()) { - addNewValueRange.run();; + addNewValueRange.run(); } else { int lastIndex = ranges.size() - 1; Tuple2 lastShapeValueRange = ranges.get(lastIndex);