diff --git a/richtextfx/src/main/java/org/fxmisc/richtext/GenericStyledArea.java b/richtextfx/src/main/java/org/fxmisc/richtext/GenericStyledArea.java index a95a9c03..385eaa62 100644 --- a/richtextfx/src/main/java/org/fxmisc/richtext/GenericStyledArea.java +++ b/richtextfx/src/main/java/org/fxmisc/richtext/GenericStyledArea.java @@ -325,12 +325,6 @@ public class GenericStyledArea extends Region * * * ********************************************************************** */ - /** - * Text color for highlighted text. - */ - private final StyleableObjectProperty highlightTextFill - = new CustomStyleableProperty<>(Color.WHITE, "highlightTextFill", this, HIGHLIGHT_TEXT_FILL); - // editable property private final BooleanProperty editable = new SimpleBooleanProperty(this, "editable", true) { @Override @@ -1908,7 +1902,6 @@ private Cell, ParagraphBox> createCell( ParagraphBox box = new ParagraphBox<>(paragraph, applyParagraphStyle, nodeFactory); - box.highlightTextFillProperty().bind(highlightTextFill); box.wrapTextProperty().bind(wrapTextProperty()); box.graphicFactoryProperty().bind(paragraphGraphicFactoryProperty()); box.graphicOffset.bind(virtualFlow.breadthOffsetProperty()); @@ -2001,7 +1994,6 @@ public void updateIndex(int index) { @Override public void dispose() { - box.highlightTextFillProperty().unbind(); box.wrapTextProperty().unbind(); box.graphicFactoryProperty().unbind(); box.graphicOffset.unbind(); @@ -2125,33 +2117,4 @@ private void suspendVisibleParsWhile(Runnable runnable) { Suspendable.combine(beingUpdated, visibleParagraphs).suspendWhile(runnable); } - /* ********************************************************************** * - * * - * CSS * - * * - * ********************************************************************** */ - - private static final CssMetaData, Paint> HIGHLIGHT_TEXT_FILL = new CustomCssMetaData<>( - "-fx-highlight-text-fill", StyleConverter.getPaintConverter(), Color.WHITE, s -> s.highlightTextFill - ); - - private static final List> CSS_META_DATA_LIST; - - static { - List> styleables = new ArrayList<>(Region.getClassCssMetaData()); - - styleables.add(HIGHLIGHT_TEXT_FILL); - - CSS_META_DATA_LIST = Collections.unmodifiableList(styleables); - } - - @Override - public List> getCssMetaData() { - return CSS_META_DATA_LIST; - } - - public static List> getClassCssMetaData() { - return CSS_META_DATA_LIST; - } - } diff --git a/richtextfx/src/main/java/org/fxmisc/richtext/ParagraphBox.java b/richtextfx/src/main/java/org/fxmisc/richtext/ParagraphBox.java index 21668f69..52d38f02 100644 --- a/richtextfx/src/main/java/org/fxmisc/richtext/ParagraphBox.java +++ b/richtextfx/src/main/java/org/fxmisc/richtext/ParagraphBox.java @@ -133,8 +133,6 @@ public String toString() { ); } - public Property highlightTextFillProperty() { return text.highlightTextFillProperty(); } - Paragraph getParagraph() { return text.getParagraph(); } diff --git a/richtextfx/src/main/java/org/fxmisc/richtext/ParagraphText.java b/richtextfx/src/main/java/org/fxmisc/richtext/ParagraphText.java index eb665fb9..44a5d948 100644 --- a/richtextfx/src/main/java/org/fxmisc/richtext/ParagraphText.java +++ b/richtextfx/src/main/java/org/fxmisc/richtext/ParagraphText.java @@ -22,7 +22,6 @@ import org.reactfx.value.Val; import javafx.beans.property.ObjectProperty; -import javafx.beans.property.SimpleObjectProperty; import javafx.beans.value.ChangeListener; import javafx.collections.FXCollections; import javafx.collections.MapChangeListener; @@ -33,7 +32,6 @@ import javafx.geometry.Insets; import javafx.scene.Node; import javafx.scene.control.IndexRange; -import javafx.scene.paint.Color; import javafx.scene.paint.Paint; import javafx.scene.shape.LineTo; import javafx.scene.shape.MoveTo; @@ -62,14 +60,6 @@ class ParagraphText extends TextFlowExt { private final MapChangeListener, ? super SelectionPath> selectionPathListener; private final SetChangeListener caretNodeListener; - // FIXME: changing it currently has not effect, because - // Text.selectionFillProperty().set(newFill) doesn't work - // properly for Text node inside a TextFlow (as of JDK8-b100). - private final ObjectProperty highlightTextFill = new SimpleObjectProperty<>(Color.WHITE); - public ObjectProperty highlightTextFillProperty() { - return highlightTextFill; - } - private Paragraph paragraph; private final CustomCssShapeHelper backgroundShapeHelper; @@ -95,18 +85,25 @@ public ObjectProperty highlightTextFillProperty() { Val leftInset = Val.map(insetsProperty(), Insets::getLeft); Val topInset = Val.map(insetsProperty(), Insets::getTop); - ChangeListener selectionRangeListener = (obs, ov, nv) -> requestLayout(); + ChangeListener selectionFillListener = (obs, ov, nv) -> requestLayout(); + ChangeListener selectionRangeListener = (obs, prevRange, nv) -> { + resetTextSelection(prevRange); + requestLayout(); + }; selectionPathListener = change -> { if (change.wasRemoved()) { SelectionPath p = change.getValueRemoved(); + p.textFillProperty().removeListener(selectionFillListener); p.rangeProperty().removeListener(selectionRangeListener); p.layoutXProperty().unbind(); p.layoutYProperty().unbind(); + resetTextSelection(p.rangeProperty().getValue()); getChildren().remove(p); } if (change.wasAdded()) { SelectionPath p = change.getValueAdded(); + p.textFillProperty().addListener(selectionFillListener); p.rangeProperty().addListener(selectionRangeListener); p.layoutXProperty().bind(leftInset); p.layoutYProperty().bind(topInset); @@ -139,24 +136,8 @@ 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); -// } -// }); - // 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(t.fillProperty()); - } getChildren().add(n); }); @@ -230,9 +211,6 @@ void dispose() { selections.removeListener( selectionPathListener ); carets.removeListener( caretNodeListener ); - getChildren().stream().filter( n -> n instanceof TextExt ).map( n -> (TextExt) n ) - .forEach( t -> t.selectionFillProperty().unbind() ); - try { getChildren().clear(); } catch ( Exception EX ) {} } @@ -336,6 +314,7 @@ private void updateAllSelectionShapes() { private void updateSingleSelection(SelectionPath path) { path.getElements().setAll(getRangeShapeSafely(path.rangeProperty().getValue())); + updateTextSelection(path); } private PathElement[] getRangeShapeSafely(IndexRange range) { @@ -435,6 +414,78 @@ 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(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) + { + text.setSelectionFill(selection.getTextFill()); + + if (selStart <= charSoFar) text.setSelectionStart(0); + else text.setSelectionStart(selStart-charSoFar); + + if (selEnd > end) text.setSelectionEnd(len); + else + { + text.setSelectionEnd(selEnd-charSoFar); + break; + } + } + charSoFar = end; + } + else if (node.isManaged()) // custom user nodes + { + charSoFar++; + } + } + } + + 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++; + } + } + } + private void updateBackgroundShapes() { int start = 0; @@ -513,7 +564,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); diff --git a/richtextfx/src/main/java/org/fxmisc/richtext/SelectionPath.java b/richtextfx/src/main/java/org/fxmisc/richtext/SelectionPath.java index 8fe45987..817bbc27 100644 --- a/richtextfx/src/main/java/org/fxmisc/richtext/SelectionPath.java +++ b/richtextfx/src/main/java/org/fxmisc/richtext/SelectionPath.java @@ -24,6 +24,9 @@ public class SelectionPath extends Path { private final StyleableObjectProperty highlightFill = new CustomStyleableProperty<>(Color.DODGERBLUE, "highlightFill", this, HIGHLIGHT_FILL); + private final StyleableObjectProperty textFill + = new CustomStyleableProperty<>(Color.WHITE, "textFill", this, TEXT_FILL); + /** * Background fill for highlighted/selected text. Can be styled using "-fx-highlight-fill". */ @@ -31,6 +34,13 @@ public class SelectionPath extends Path { public final Paint getHighlightFill() { return highlightFill.get(); } public final void setHighlightFill(Paint paint) { highlightFill.set(paint); } + /** + * Text color for highlighted/selected text. Can be styled using "-fx-highlight-text-fill". + */ + public final ObjectProperty textFillProperty() { return textFill; } + public final Paint getTextFill() { return textFill.get(); } + public final void setTextFill(Paint paint) { textFill.set(paint); } + private final Val range; final Val rangeProperty() { return range; } @@ -53,12 +63,17 @@ public String toString() { "-fx-highlight-fill", StyleConverter.getPaintConverter(), Color.DODGERBLUE, s -> s.highlightFill ); + private static final CssMetaData TEXT_FILL = new CustomCssMetaData<>( + "-fx-highlight-text-fill", StyleConverter.getPaintConverter(), Color.WHITE, s -> s.textFill + ); + private static final List> CSS_META_DATA_LIST; static { List> styleables = new ArrayList<>(Path.getClassCssMetaData()); styleables.add(HIGHLIGHT_FILL); + styleables.add(TEXT_FILL); CSS_META_DATA_LIST = Collections.unmodifiableList(styleables); }