From de63b0281f569a000a29580d4abf586168cf3a29 Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Fri, 18 Nov 2016 14:15:48 +0100 Subject: [PATCH 1/3] attempt to fix text color of selection (white selected text), BUT: 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. --- .../org/fxmisc/richtext/ParagraphText.java | 36 +++++++++++++++++-- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/richtextfx/src/main/java/org/fxmisc/richtext/ParagraphText.java b/richtextfx/src/main/java/org/fxmisc/richtext/ParagraphText.java index eb665fb9..ead69125 100644 --- a/richtextfx/src/main/java/org/fxmisc/richtext/ParagraphText.java +++ b/richtextfx/src/main/java/org/fxmisc/richtext/ParagraphText.java @@ -155,7 +155,7 @@ public ObjectProperty highlightTextFillProperty() { TextExt t = (TextExt) n; // XXX: binding selectionFill to textFill, // see the note at highlightTextFill - t.selectionFillProperty().bind(t.fillProperty()); + t.selectionFillProperty().bind(highlightTextFill); } getChildren().add(n); }); @@ -435,6 +435,35 @@ 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(); + + 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(); + + int textSelStart = Math.max(start, selStart); + int textSelEnd = Math.min(end, selEnd); + if (textSelEnd > textSelStart) { + textSelStart -= start; + textSelEnd -= start; + } else { + textSelStart = textSelEnd = -1; + } + text.setImpl_selectionStart(textSelStart); + text.setImpl_selectionEnd(textSelEnd); + + start = end; + } + } + private void updateBackgroundShapes() { int start = 0; @@ -482,8 +511,9 @@ public String toString() { @Override protected void layoutChildren() { super.layoutChildren(); - updateAllCaretShapes(); - updateAllSelectionShapes(); + updateCaretShape(); + updateSelectionShape(); + updateTextSelection(); updateBackgroundShapes(); } From 43a987f18e4157b7c8a29798855e2749b39c0805 Mon Sep 17 00:00:00 2001 From: Jurgen <5031427+Jugen@users.noreply.github.com> Date: Fri, 30 Aug 2024 11:05:01 +0200 Subject: [PATCH 2/3] update to accommodate multiple selections --- .../org/fxmisc/richtext/ParagraphText.java | 110 ++++++++++++------ 1 file changed, 75 insertions(+), 35 deletions(-) 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); From f5ddc95995413d7fb5fe54305762efbbb2b2bb0a Mon Sep 17 00:00:00 2001 From: Jurgen <5031427+Jugen@users.noreply.github.com> Date: Wed, 4 Sep 2024 14:58:06 +0200 Subject: [PATCH 3/3] add text fill property and CSS for each SelectionPath --- .../fxmisc/richtext/GenericStyledArea.java | 37 ------------------- .../org/fxmisc/richtext/ParagraphBox.java | 2 - .../org/fxmisc/richtext/ParagraphText.java | 27 ++------------ .../org/fxmisc/richtext/SelectionPath.java | 15 ++++++++ 4 files changed, 19 insertions(+), 62 deletions(-) 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 30432a9d..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,6 +85,7 @@ public ObjectProperty highlightTextFillProperty() { Val leftInset = Val.map(insetsProperty(), Insets::getLeft); Val topInset = Val.map(insetsProperty(), Insets::getTop); + ChangeListener selectionFillListener = (obs, ov, nv) -> requestLayout(); ChangeListener selectionRangeListener = (obs, prevRange, nv) -> { resetTextSelection(prevRange); requestLayout(); @@ -102,6 +93,7 @@ public ObjectProperty highlightTextFillProperty() { selectionPathListener = change -> { if (change.wasRemoved()) { SelectionPath p = change.getValueRemoved(); + p.textFillProperty().removeListener(selectionFillListener); p.rangeProperty().removeListener(selectionRangeListener); p.layoutXProperty().unbind(); p.layoutYProperty().unbind(); @@ -111,6 +103,7 @@ public ObjectProperty highlightTextFillProperty() { } if (change.wasAdded()) { SelectionPath p = change.getValueAdded(); + p.textFillProperty().addListener(selectionFillListener); p.rangeProperty().addListener(selectionRangeListener); p.layoutXProperty().bind(leftInset); p.layoutYProperty().bind(topInset); @@ -143,17 +136,8 @@ public ObjectProperty highlightTextFillProperty() { }; carets.addListener( caretNodeListener ); - 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; - t.selectionFillProperty().bind(highlightTextFill); - } getChildren().add(n); }); @@ -227,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 ) {} } @@ -457,7 +438,7 @@ private void updateTextSelection(SelectionPath selection) if (end > selStart) { - // TODO text.setSelectionFill(selection.getTextFill()); + text.setSelectionFill(selection.getTextFill()); if (selStart <= charSoFar) text.setSelectionStart(0); else text.setSelectionStart(selStart-charSoFar); 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); }