diff --git a/richtextfx/src/main/java/org/fxmisc/richtext/GenericStyledArea.java b/richtextfx/src/main/java/org/fxmisc/richtext/GenericStyledArea.java index 4e1253da2..8c6b7bc2f 100644 --- a/richtextfx/src/main/java/org/fxmisc/richtext/GenericStyledArea.java +++ b/richtextfx/src/main/java/org/fxmisc/richtext/GenericStyledArea.java @@ -1463,6 +1463,7 @@ public void dispose() { box.wrapTextProperty().unbind(); box.graphicFactoryProperty().unbind(); box.graphicOffset.unbind(); + box.dispose(); firstParPseudoClass.unsubscribe(); lastParPseudoClass.unsubscribe(); diff --git a/richtextfx/src/main/java/org/fxmisc/richtext/ParagraphBox.java b/richtextfx/src/main/java/org/fxmisc/richtext/ParagraphBox.java index 75f4c6408..06e4f5496 100644 --- a/richtextfx/src/main/java/org/fxmisc/richtext/ParagraphBox.java +++ b/richtextfx/src/main/java/org/fxmisc/richtext/ParagraphBox.java @@ -22,7 +22,6 @@ import javafx.geometry.Insets; import javafx.geometry.Point2D; import javafx.scene.Node; -import javafx.scene.control.IndexRange; import javafx.scene.layout.Region; import javafx.scene.paint.Paint; import javafx.scene.text.TextFlow; @@ -118,6 +117,10 @@ public final ObservableMap, SelectionPath> selectionsPrope graphicOffset.addListener(obs -> requestLayout()); } + void dispose() { + text.dispose(); + } + @Override public String toString() { return String.format( diff --git a/richtextfx/src/main/java/org/fxmisc/richtext/ParagraphText.java b/richtextfx/src/main/java/org/fxmisc/richtext/ParagraphText.java index b8e66102f..9ec09565e 100644 --- a/richtextfx/src/main/java/org/fxmisc/richtext/ParagraphText.java +++ b/richtextfx/src/main/java/org/fxmisc/richtext/ParagraphText.java @@ -1,6 +1,5 @@ package org.fxmisc.richtext; -import java.lang.ref.WeakReference; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; @@ -19,7 +18,6 @@ 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; @@ -62,6 +60,9 @@ class ParagraphText extends TextFlowExt { FXCollections.observableMap(new HashMap<>(1)); public final ObservableMap, SelectionPath> selectionsProperty() { return selections; } + private final ChangeListener selectionRangeListener; + private final ChangeListener caretPositionListener; + // FIXME: changing it currently has not effect, because // Text.impl_selectionFillProperty().set(newFill) doesn't work // properly for Text node inside a TextFlow (as of JDK8-b100). @@ -95,11 +96,47 @@ public ObjectProperty highlightTextFillProperty() { Val leftInset = Val.map(insetsProperty(), Insets::getLeft); Val topInset = Val.map(insetsProperty(), Insets::getTop); - ChangeListener requestLayout1 = new SelectionRangeChangeListener<>(this); - selections.addListener(new SelectionsSetListener<>(leftInset, requestLayout1, topInset, this)); + selectionRangeListener = (obs, ov, nv) -> requestLayout(); + selections.addListener((MapChangeListener.Change, ? extends SelectionPath> change) -> { + if (change.wasRemoved()) { + SelectionPath p = change.getValueRemoved(); + p.rangeProperty().removeListener(selectionRangeListener); + p.layoutXProperty().unbind(); + p.layoutYProperty().unbind(); - ChangeListener requestLayout2 = new CaretPositionChangeListener<>(this); - carets.addListener(new CaretsChangeListener<>(leftInset, requestLayout2, topInset, this)); + getChildren().remove(p); + } + if (change.wasAdded()) { + SelectionPath p = change.getValueAdded(); + p.rangeProperty().addListener(selectionRangeListener); + p.layoutXProperty().bind(leftInset); + p.layoutYProperty().bind(topInset); + + getChildren().add(selectionShapeStartIndex, p); + updateSingleSelection(p); + } + }); + + caretPositionListener = (obs, ov, nv) -> requestLayout(); + carets.addListener((SetChangeListener.Change change) -> { + if (change.wasRemoved()) { + CaretNode caret = change.getElementRemoved(); + caret.columnPositionProperty().removeListener(caretPositionListener); + caret.layoutXProperty().unbind(); + caret.layoutYProperty().unbind(); + + getChildren().remove(caret); + } + if (change.wasAdded()) { + CaretNode caret = change.getElementAdded(); + caret.columnPositionProperty().addListener(caretPositionListener); + caret.layoutXProperty().bind(leftInset); + caret.layoutYProperty().bind(topInset); + + getChildren().add(caret); + updateSingleCaret(caret); + } + }); // XXX: see the note at highlightTextFill // highlightTextFill.addListener(new ChangeListener() { @@ -182,6 +219,12 @@ public ObjectProperty highlightTextFillProperty() { ); } + void dispose() { + // this removes listeners (in selections and carets listeners) and avoids memory leaks + selections.clear(); + carets.clear(); + } + public Paragraph getParagraph() { return paragraph; } @@ -402,131 +445,6 @@ 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<>();