|
1 | 1 | package org.fxmisc.richtext; |
2 | 2 |
|
3 | | -import java.lang.ref.WeakReference; |
4 | 3 | import java.util.Arrays; |
5 | 4 | import java.util.Collection; |
6 | 5 | import java.util.HashMap; |
|
19 | 18 | import javafx.beans.property.ObjectProperty; |
20 | 19 | import javafx.beans.property.SimpleObjectProperty; |
21 | 20 | import javafx.beans.value.ChangeListener; |
22 | | -import javafx.beans.value.ObservableValue; |
23 | 21 | import javafx.collections.FXCollections; |
24 | 22 | import javafx.collections.MapChangeListener; |
25 | 23 | import javafx.collections.ObservableMap; |
@@ -62,6 +60,9 @@ class ParagraphText<PS, SEG, S> extends TextFlowExt { |
62 | 60 | FXCollections.observableMap(new HashMap<>(1)); |
63 | 61 | public final ObservableMap<Selection<PS, SEG, S>, SelectionPath> selectionsProperty() { return selections; } |
64 | 62 |
|
| 63 | + private final ChangeListener<IndexRange> selectionRangeListener; |
| 64 | + private final ChangeListener<Integer> caretPositionListener; |
| 65 | + |
65 | 66 | // FIXME: changing it currently has not effect, because |
66 | 67 | // Text.impl_selectionFillProperty().set(newFill) doesn't work |
67 | 68 | // properly for Text node inside a TextFlow (as of JDK8-b100). |
@@ -95,11 +96,47 @@ public ObjectProperty<Paint> highlightTextFillProperty() { |
95 | 96 | Val<Double> leftInset = Val.map(insetsProperty(), Insets::getLeft); |
96 | 97 | Val<Double> topInset = Val.map(insetsProperty(), Insets::getTop); |
97 | 98 |
|
98 | | - ChangeListener<IndexRange> requestLayout1 = new SelectionRangeChangeListener<>(this); |
99 | | - selections.addListener(new SelectionsSetListener<>(leftInset, requestLayout1, topInset, this)); |
| 99 | + selectionRangeListener = (obs, ov, nv) -> requestLayout(); |
| 100 | + selections.addListener((MapChangeListener.Change<? extends Selection<PS, SEG, S>, ? extends SelectionPath> change) -> { |
| 101 | + if (change.wasRemoved()) { |
| 102 | + SelectionPath p = change.getValueRemoved(); |
| 103 | + p.rangeProperty().removeListener(selectionRangeListener); |
| 104 | + p.layoutXProperty().unbind(); |
| 105 | + p.layoutYProperty().unbind(); |
100 | 106 |
|
101 | | - ChangeListener<Integer> requestLayout2 = new CaretPositionChangeListener<>(this); |
102 | | - carets.addListener(new CaretsChangeListener<>(leftInset, requestLayout2, topInset, this)); |
| 107 | + getChildren().remove(p); |
| 108 | + } |
| 109 | + if (change.wasAdded()) { |
| 110 | + SelectionPath p = change.getValueAdded(); |
| 111 | + p.rangeProperty().addListener(selectionRangeListener); |
| 112 | + p.layoutXProperty().bind(leftInset); |
| 113 | + p.layoutYProperty().bind(topInset); |
| 114 | + |
| 115 | + getChildren().add(selectionShapeStartIndex, p); |
| 116 | + updateSingleSelection(p); |
| 117 | + } |
| 118 | + }); |
| 119 | + |
| 120 | + caretPositionListener = (obs, ov, nv) -> requestLayout(); |
| 121 | + carets.addListener((SetChangeListener.Change<? extends CaretNode> change) -> { |
| 122 | + if (change.wasRemoved()) { |
| 123 | + CaretNode caret = change.getElementRemoved(); |
| 124 | + caret.columnPositionProperty().removeListener(caretPositionListener); |
| 125 | + caret.layoutXProperty().unbind(); |
| 126 | + caret.layoutYProperty().unbind(); |
| 127 | + |
| 128 | + getChildren().remove(caret); |
| 129 | + } |
| 130 | + if (change.wasAdded()) { |
| 131 | + CaretNode caret = change.getElementAdded(); |
| 132 | + caret.columnPositionProperty().addListener(caretPositionListener); |
| 133 | + caret.layoutXProperty().bind(leftInset); |
| 134 | + caret.layoutYProperty().bind(topInset); |
| 135 | + |
| 136 | + getChildren().add(caret); |
| 137 | + updateSingleCaret(caret); |
| 138 | + } |
| 139 | + }); |
103 | 140 |
|
104 | 141 | // XXX: see the note at highlightTextFill |
105 | 142 | // highlightTextFill.addListener(new ChangeListener<Paint>() { |
@@ -182,6 +219,12 @@ public ObjectProperty<Paint> highlightTextFillProperty() { |
182 | 219 | ); |
183 | 220 | } |
184 | 221 |
|
| 222 | + void dispose() { |
| 223 | + // this removes listeners (in selections and carets listeners) and avoids memory leaks |
| 224 | + selections.clear(); |
| 225 | + carets.clear(); |
| 226 | + } |
| 227 | + |
185 | 228 | public Paragraph<PS, SEG, S> getParagraph() { |
186 | 229 | return paragraph; |
187 | 230 | } |
@@ -402,131 +445,6 @@ protected void layoutChildren() { |
402 | 445 | updateBackgroundShapes(); |
403 | 446 | } |
404 | 447 |
|
405 | | - private static final class SelectionsSetListener<PS, SEG, S> implements |
406 | | - MapChangeListener<Selection<PS, SEG, S>, SelectionPath> { |
407 | | - private final Val<Double> leftInset; |
408 | | - private final ChangeListener<IndexRange> requestLayout1; |
409 | | - private final Val<Double> topInset; |
410 | | - private final WeakReference<ParagraphText<PS, SEG, S>> ref; |
411 | | - |
412 | | - public SelectionsSetListener( |
413 | | - Val<Double> leftInset, |
414 | | - ChangeListener<IndexRange> requestLayout1, |
415 | | - Val<Double> topInset, |
416 | | - ParagraphText<PS, SEG, S> paragraphText) { |
417 | | - this.leftInset = leftInset; |
418 | | - this.requestLayout1 = requestLayout1; |
419 | | - this.topInset = topInset; |
420 | | - ref = new WeakReference<>(paragraphText); |
421 | | - } |
422 | | - |
423 | | - @Override |
424 | | - public void onChanged( |
425 | | - javafx.collections.MapChangeListener.Change<? extends Selection<PS, SEG, S>, ? extends SelectionPath> change) { |
426 | | - ParagraphText<PS, SEG, S> paragraphText = ref.get(); |
427 | | - if (null == paragraphText) { |
428 | | - change.getMap().removeListener(this); |
429 | | - return; |
430 | | - } |
431 | | - |
432 | | - if (change.wasAdded()) { |
433 | | - SelectionPath p = change.getValueAdded(); |
434 | | - p.rangeProperty().addListener(requestLayout1); |
435 | | - |
436 | | - p.layoutXProperty().bind(leftInset); |
437 | | - p.layoutYProperty().bind(topInset); |
438 | | - |
439 | | - paragraphText.getChildren().add(paragraphText.selectionShapeStartIndex, p); |
440 | | - paragraphText.updateSingleSelection(p); |
441 | | - } else if (change.wasRemoved()) { |
442 | | - SelectionPath p = change.getValueRemoved(); |
443 | | - p.rangeProperty().removeListener(requestLayout1); |
444 | | - |
445 | | - p.layoutXProperty().unbind(); |
446 | | - p.layoutYProperty().unbind(); |
447 | | - |
448 | | - paragraphText.getChildren().remove(p); |
449 | | - } |
450 | | - } |
451 | | - } |
452 | | - |
453 | | - private static final class SelectionRangeChangeListener<PS, SEG, S> implements ChangeListener<IndexRange> { |
454 | | - private final WeakReference<ParagraphText<PS, SEG, S>> ref; |
455 | | - |
456 | | - public SelectionRangeChangeListener(ParagraphText<PS, SEG, S> paragraphText) { |
457 | | - ref = new WeakReference<>(paragraphText); |
458 | | - } |
459 | | - |
460 | | - @Override |
461 | | - public void changed(ObservableValue<? extends IndexRange> observable, IndexRange oldValue, IndexRange newValue) { |
462 | | - if (null == ref.get()) { |
463 | | - observable.removeListener(this); |
464 | | - } else { |
465 | | - ref.get().requestLayout(); |
466 | | - } |
467 | | - } |
468 | | - } |
469 | | - |
470 | | - private static final class CaretPositionChangeListener<PS, SEG, S> implements ChangeListener<Integer> { |
471 | | - private final WeakReference<ParagraphText<PS, SEG, S>> ref; |
472 | | - |
473 | | - public CaretPositionChangeListener(ParagraphText<PS, SEG, S> paragraphText) { |
474 | | - ref = new WeakReference<>(paragraphText); |
475 | | - } |
476 | | - |
477 | | - @Override |
478 | | - public void changed(ObservableValue<? extends Integer> observable, Integer oldValue, Integer newValue) { |
479 | | - if (null == ref.get()) { |
480 | | - observable.removeListener(this); |
481 | | - } else { |
482 | | - ref.get().requestLayout(); |
483 | | - } |
484 | | - } |
485 | | - } |
486 | | - |
487 | | - private static final class CaretsChangeListener<PS, SEG, S> implements SetChangeListener<CaretNode> { |
488 | | - private final Val<Double> leftInset; |
489 | | - private final ChangeListener<Integer> requestLayout2; |
490 | | - private final Val<Double> topInset; |
491 | | - private final WeakReference<ParagraphText<PS, SEG, S>> ref; |
492 | | - |
493 | | - private CaretsChangeListener( |
494 | | - Val<Double> leftInset, |
495 | | - ChangeListener<Integer> requestLayout2, |
496 | | - Val<Double> topInset, |
497 | | - ParagraphText<PS, SEG, S> paragraphText) { |
498 | | - ref = new WeakReference<>(paragraphText); |
499 | | - this.leftInset = leftInset; |
500 | | - this.requestLayout2 = requestLayout2; |
501 | | - this.topInset = topInset; |
502 | | - } |
503 | | - |
504 | | - @Override |
505 | | - public void onChanged(Change<? extends CaretNode> change) { |
506 | | - ParagraphText<PS, SEG, S> paragraphText = ref.get(); |
507 | | - if (null == paragraphText) { |
508 | | - change.getSet().removeListener(this); |
509 | | - return; |
510 | | - } |
511 | | - if (change.wasAdded()) { |
512 | | - CaretNode caret = change.getElementAdded(); |
513 | | - caret.columnPositionProperty().addListener(requestLayout2); |
514 | | - caret.layoutXProperty().bind(leftInset); |
515 | | - caret.layoutYProperty().bind(topInset); |
516 | | - |
517 | | - paragraphText.getChildren().add(caret); |
518 | | - paragraphText.updateSingleCaret(caret); |
519 | | - } else if (change.wasRemoved()) { |
520 | | - CaretNode caret = change.getElementRemoved(); |
521 | | - caret.columnPositionProperty().removeListener(requestLayout2); |
522 | | - caret.layoutXProperty().unbind(); |
523 | | - caret.layoutYProperty().unbind(); |
524 | | - |
525 | | - paragraphText.getChildren().remove(caret); |
526 | | - } |
527 | | - } |
528 | | - } |
529 | | - |
530 | 448 | private static class CustomCssShapeHelper<T> { |
531 | 449 |
|
532 | 450 | private final List<Tuple2<T, IndexRange>> ranges = new LinkedList<>(); |
|
0 commit comments