diff --git a/richtextfx/src/main/java/org/fxmisc/richtext/ParagraphText.java b/richtextfx/src/main/java/org/fxmisc/richtext/ParagraphText.java index e4049f7bb..b8e66102f 100644 --- a/richtextfx/src/main/java/org/fxmisc/richtext/ParagraphText.java +++ b/richtextfx/src/main/java/org/fxmisc/richtext/ParagraphText.java @@ -1,5 +1,6 @@ package org.fxmisc.richtext; +import java.lang.ref.WeakReference; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; @@ -18,6 +19,7 @@ import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; import javafx.collections.FXCollections; import javafx.collections.MapChangeListener; import javafx.collections.ObservableMap; @@ -93,48 +95,11 @@ public ObjectProperty highlightTextFillProperty() { Val leftInset = Val.map(insetsProperty(), Insets::getLeft); Val topInset = Val.map(insetsProperty(), Insets::getTop); - ChangeListener requestLayout1 = (obs, ov, nv) -> requestLayout(); + ChangeListener requestLayout1 = new SelectionRangeChangeListener<>(this); + selections.addListener(new SelectionsSetListener<>(leftInset, requestLayout1, topInset, this)); - selections.addListener((MapChangeListener.Change, ? extends SelectionPath> change) -> { - if (change.wasAdded()) { - SelectionPath p = change.getValueAdded(); - p.rangeProperty().addListener(requestLayout1); - - p.layoutXProperty().bind(leftInset); - p.layoutYProperty().bind(topInset); - - getChildren().add(selectionShapeStartIndex, p); - updateSingleSelection(p); - } else if (change.wasRemoved()) { - SelectionPath p = change.getValueRemoved(); - p.rangeProperty().removeListener(requestLayout1); - - p.layoutXProperty().unbind(); - p.layoutYProperty().unbind(); - - getChildren().remove(p); - } - }); - - ChangeListener requestLayout2 = (obs, ov, nv) -> requestLayout(); - carets.addListener((SetChangeListener.Change change) -> { - if (change.wasAdded()) { - CaretNode caret = change.getElementAdded(); - caret.columnPositionProperty().addListener(requestLayout2); - caret.layoutXProperty().bind(leftInset); - caret.layoutYProperty().bind(topInset); - - getChildren().add(caret); - updateSingleCaret(caret); - } else if (change.wasRemoved()) { - CaretNode caret = change.getElementRemoved(); - caret.columnPositionProperty().removeListener(requestLayout2); - caret.layoutXProperty().unbind(); - caret.layoutYProperty().unbind(); - - getChildren().remove(caret); - } - }); + ChangeListener requestLayout2 = new CaretPositionChangeListener<>(this); + carets.addListener(new CaretsChangeListener<>(leftInset, requestLayout2, topInset, this)); // XXX: see the note at highlightTextFill // highlightTextFill.addListener(new ChangeListener() { @@ -437,6 +402,131 @@ protected void layoutChildren() { updateBackgroundShapes(); } + private static final class SelectionsSetListener implements + MapChangeListener, SelectionPath> { + private final Val leftInset; + private final ChangeListener requestLayout1; + private final Val topInset; + private final WeakReference> ref; + + public SelectionsSetListener( + Val leftInset, + ChangeListener requestLayout1, + Val topInset, + ParagraphText paragraphText) { + this.leftInset = leftInset; + this.requestLayout1 = requestLayout1; + this.topInset = topInset; + ref = new WeakReference<>(paragraphText); + } + + @Override + public void onChanged( + javafx.collections.MapChangeListener.Change, ? extends SelectionPath> change) { + ParagraphText paragraphText = ref.get(); + if (null == paragraphText) { + change.getMap().removeListener(this); + return; + } + + if (change.wasAdded()) { + SelectionPath p = change.getValueAdded(); + p.rangeProperty().addListener(requestLayout1); + + p.layoutXProperty().bind(leftInset); + p.layoutYProperty().bind(topInset); + + paragraphText.getChildren().add(paragraphText.selectionShapeStartIndex, p); + paragraphText.updateSingleSelection(p); + } else if (change.wasRemoved()) { + SelectionPath p = change.getValueRemoved(); + p.rangeProperty().removeListener(requestLayout1); + + p.layoutXProperty().unbind(); + p.layoutYProperty().unbind(); + + paragraphText.getChildren().remove(p); + } + } + } + + private static final class SelectionRangeChangeListener implements ChangeListener { + private final WeakReference> ref; + + public SelectionRangeChangeListener(ParagraphText paragraphText) { + ref = new WeakReference<>(paragraphText); + } + + @Override + public void changed(ObservableValue observable, IndexRange oldValue, IndexRange newValue) { + if (null == ref.get()) { + observable.removeListener(this); + } else { + ref.get().requestLayout(); + } + } + } + + private static final class CaretPositionChangeListener implements ChangeListener { + private final WeakReference> ref; + + public CaretPositionChangeListener(ParagraphText paragraphText) { + ref = new WeakReference<>(paragraphText); + } + + @Override + public void changed(ObservableValue observable, Integer oldValue, Integer newValue) { + if (null == ref.get()) { + observable.removeListener(this); + } else { + ref.get().requestLayout(); + } + } + } + + private static final class CaretsChangeListener implements SetChangeListener { + private final Val leftInset; + private final ChangeListener requestLayout2; + private final Val topInset; + private final WeakReference> ref; + + private CaretsChangeListener( + Val leftInset, + ChangeListener requestLayout2, + Val topInset, + ParagraphText paragraphText) { + ref = new WeakReference<>(paragraphText); + this.leftInset = leftInset; + this.requestLayout2 = requestLayout2; + this.topInset = topInset; + } + + @Override + public void onChanged(Change change) { + ParagraphText paragraphText = ref.get(); + if (null == paragraphText) { + change.getSet().removeListener(this); + return; + } + if (change.wasAdded()) { + CaretNode caret = change.getElementAdded(); + caret.columnPositionProperty().addListener(requestLayout2); + caret.layoutXProperty().bind(leftInset); + caret.layoutYProperty().bind(topInset); + + paragraphText.getChildren().add(caret); + paragraphText.updateSingleCaret(caret); + } else if (change.wasRemoved()) { + CaretNode caret = change.getElementRemoved(); + caret.columnPositionProperty().removeListener(requestLayout2); + caret.layoutXProperty().unbind(); + caret.layoutYProperty().unbind(); + + paragraphText.getChildren().remove(caret); + } + } + } + private static class CustomCssShapeHelper { private final List> ranges = new LinkedList<>();