FXMisc / RichTextFX

Rich-text area for JavaFX
BSD 2-Clause "Simplified" License
1.21k stars 236 forks source link

Getting exception when resize text area #863

Open garybentley opened 4 years ago

garybentley commented 4 years ago

Similar to #842 the following exception is occurring when I resize the text area (manually by resizing the surrounding window) and there is a paragraph graphic factory. However in this case my code isn't being touched at all. The exception seems to relate to the caret in some way, but it ONLY occurs if I am laying out nodes in the paragraph graphic and calling either getCharacterBoundsOnScreen or getParagraphBoundsOnScreen to get the position.

Any ideas on how to get around this? I'll try to create a reproducible example but I'm not hopeful, this isn't touching my code at all.

Exception is:

Index 7 out of bounds for length 7 java.base/jdk.internal.util.Preconditions.outOfBounds(Preconditions.java:64) java.base/jdk.internal.util.Preconditions.outOfBoundsCheckIndex(Preconditions.java:70) java.base/jdk.internal.util.Preconditions.checkIndex(Preconditions.java:248) java.base/java.util.Objects.checkIndex(Objects.java:372) java.base/java.util.ArrayList.get(ArrayList.java:458) javafx.base/com.sun.javafx.collections.ObservableListWrapper.get(ObservableListWrapper.java:89) javafx.base/com.sun.javafx.collections.VetoableListDecorator.get(VetoableListDecorator.java:306) javafx.graphics/javafx.scene.Parent.layout(Parent.java:1210) javafx.graphics/javafx.scene.Parent.layout(Parent.java:1213) org.fxmisc.flowless.VirtualFlow.getCellIfVisible(VirtualFlow.java:220) org.fxmisc.richtext.GenericStyledArea.getCaretBoundsOnScreen(GenericStyledArea.java:1043) org.fxmisc.richtext.CaretNode.lambda$new$6(CaretNode.java:232) org.reactfx.value.Val$3.computeValue(Val.java:722) org.reactfx.value.ValBase.getValue(ValBase.java:17) org.reactfx.value.SuspendableValWrapper.getValue(SuspendableValWrapper.java:23) org.reactfx.value.ChangeListenerWrapper.accept(Val.java:784) org.reactfx.util.AbstractReducingStreamNotifications.lambda$head$0(NotificationAccumulator.java:248) org.reactfx.ObservableBase.notifyObservers(ObservableBase.java:68) org.reactfx.ObservableBase.notifyObservers(ObservableBase.java:57) org.reactfx.SuspendableBase.handleEvent(SuspendableBase.java:82) org.reactfx.util.NonAccumulativeStreamNotifications.lambda$head$0(NotificationAccumulator.java:134) org.reactfx.ObservableBase.notifyObservers(ObservableBase.java:68) org.reactfx.ObservableBase.notifyObservers(ObservableBase.java:57) org.reactfx.ProperEventStream.emit(ProperEventStream.java:18) org.reactfx.util.AbstractReducingStreamNotifications.lambda$head$0(NotificationAccumulator.java:248) org.reactfx.ObservableBase.notifyObservers(ObservableBase.java:68) org.reactfx.ObservableBase.notifyObservers(ObservableBase.java:57) org.reactfx.value.ValBase.invalidate(ValBase.java:32) org.reactfx.value.Val$3.lambda$connect$0(Val.java:717) org.reactfx.util.NonAccumulativeStreamNotifications.lambda$head$0(NotificationAccumulator.java:134) org.reactfx.ObservableBase.notifyObservers(ObservableBase.java:68) org.reactfx.ObservableBase.notifyObservers(ObservableBase.java:57) org.reactfx.ProperEventStream.emit(ProperEventStream.java:18) org.reactfx.util.NonAccumulativeStreamNotifications.lambda$head$0(NotificationAccumulator.java:134) org.reactfx.ObservableBase.notifyObservers(ObservableBase.java:68) org.reactfx.ObservableBase.notifyObservers(ObservableBase.java:57) org.reactfx.SuspendableBase.handleEvent(SuspendableBase.java:82) org.reactfx.util.NonAccumulativeStreamNotifications.lambda$head$0(NotificationAccumulator.java:134) org.reactfx.ObservableBase.notifyObservers(ObservableBase.java:68) org.reactfx.ObservableBase.notifyObservers(ObservableBase.java:57) org.reactfx.ProperEventStream.emit(ProperEventStream.java:18) org.reactfx.util.NonAccumulativeStreamNotifications.lambda$head$0(NotificationAccumulator.java:134) org.reactfx.ObservableBase.notifyObservers(ObservableBase.java:68) org.reactfx.ObservableBase.notifyObservers(ObservableBase.java:57) org.reactfx.ProperEventStream.emit(ProperEventStream.java:18) org.reactfx.EventStreams$1.lambda$observeInputs$0(EventStreams.java:67) org.reactfx.value.InvalidationListenerWrapper.accept(Val.java:765) org.reactfx.util.AbstractReducingStreamNotifications.lambda$head$0(NotificationAccumulator.java:248) org.reactfx.ObservableBase.notifyObservers(ObservableBase.java:68) org.reactfx.ObservableBase.notifyObservers(ObservableBase.java:57) org.reactfx.value.ValBase.invalidate(ValBase.java:32) org.reactfx.value.OrElseConst.lambda$connect$0(OrElseConst.java:24) org.reactfx.value.InvalidationListenerWrapper.accept(Val.java:765) org.reactfx.util.AbstractReducingStreamNotifications.lambda$head$0(NotificationAccumulator.java:248) org.reactfx.ObservableBase.notifyObservers(ObservableBase.java:68) org.reactfx.ObservableBase.notifyObservers(ObservableBase.java:57) org.reactfx.value.ValBase.invalidate(ValBase.java:32) org.reactfx.value.Val$2.lambda$connect$0(Val.java:691) org.reactfx.value.InvalidationListenerWrapper.accept(Val.java:765) org.reactfx.util.AbstractReducingStreamNotifications.lambda$head$0(NotificationAccumulator.java:248) org.reactfx.ObservableBase.notifyObservers(ObservableBase.java:68) org.reactfx.ObservableBase.notifyObservers(ObservableBase.java:57) org.reactfx.value.ValBase.invalidate(ValBase.java:32) org.reactfx.value.Val$2.lambda$connect$0(Val.java:691) org.reactfx.value.InvalidationListenerWrapper.accept(Val.java:765) org.reactfx.util.AbstractReducingStreamNotifications.lambda$head$0(NotificationAccumulator.java:248) org.reactfx.ObservableBase.notifyObservers(ObservableBase.java:68) org.reactfx.ObservableBase.notifyObservers(ObservableBase.java:57) org.reactfx.value.ValBase.invalidate(ValBase.java:32) org.reactfx.value.Val$2.lambda$connect$0(Val.java:691) org.reactfx.value.InvalidationListenerWrapper.accept(Val.java:765) org.reactfx.util.AbstractReducingStreamNotifications.lambda$head$0(NotificationAccumulator.java:248) org.reactfx.ObservableBase.notifyObservers(ObservableBase.java:68) org.reactfx.ObservableBase.notifyObservers(ObservableBase.java:57) org.reactfx.value.ValBase.invalidate(ValBase.java:32) org.reactfx.value.Val$2.lambda$connect$0(Val.java:691) org.reactfx.collection.InvalidationListenerWrapper.onChange(LiveList.java:413) org.reactfx.collection.InvalidationListenerWrapper.onChange(LiveList.java:399) org.reactfx.util.ListNotifications.lambda$takeHead$0(NotificationAccumulator.java:317) org.reactfx.ObservableBase.notifyObservers(ObservableBase.java:68) org.reactfx.ObservableBase.notifyObservers(ObservableBase.java:57) org.reactfx.collection.ProperLiveList.fireModification(ProperLiveList.java:25) org.reactfx.collection.ProperLiveList.fireRemoveRange(ProperLiveList.java:76) org.reactfx.collection.MemoizationListImpl.forget(MemoizationList.java:188) org.fxmisc.flowless.CellListManager.cropTo(CellListManager.java:81) org.fxmisc.flowless.CellPositioner.cropTo(CellPositioner.java:29) org.fxmisc.flowless.Navigator.fillViewportFrom(Navigator.java:330) org.fxmisc.flowless.Navigator.visit(Navigator.java:130) org.fxmisc.flowless.StartOffStart.accept(TargetPosition.java:85) org.fxmisc.flowless.Navigator.layoutChildren(Navigator.java:78) javafx.graphics/javafx.scene.Parent.layout(Parent.java:1206) org.fxmisc.flowless.VirtualFlow.layoutChildren(VirtualFlow.java:257) javafx.graphics/javafx.scene.Parent.layout(Parent.java:1206) javafx.graphics/javafx.scene.Parent.layout(Parent.java:1213) javafx.graphics/javafx.scene.Parent.layout(Parent.java:1213) javafx.graphics/javafx.scene.Parent.layout(Parent.java:1213) javafx.graphics/javafx.scene.Parent.layout(Parent.java:1213) javafx.graphics/javafx.scene.Parent.layout(Parent.java:1213) javafx.graphics/javafx.scene.Parent.layout(Parent.java:1213) javafx.graphics/javafx.scene.Parent.layout(Parent.java:1213) javafx.graphics/javafx.scene.Parent.layout(Parent.java:1213) javafx.graphics/javafx.scene.Parent.layout(Parent.java:1213) javafx.graphics/javafx.scene.Parent.layout(Parent.java:1213) javafx.graphics/javafx.scene.Parent.layout(Parent.java:1213) javafx.graphics/javafx.scene.Scene.doLayoutPass(Scene.java:576) javafx.graphics/javafx.scene.Scene$ScenePulseListener.pulse(Scene.java:2482) javafx.graphics/com.sun.javafx.tk.Toolkit.lambda$runPulse$2(Toolkit.java:412) java.base/java.security.AccessController.doPrivileged(AccessController.java:389) javafx.graphics/com.sun.javafx.tk.Toolkit.runPulse(Toolkit.java:411) javafx.graphics/com.sun.javafx.tk.Toolkit.firePulse(Toolkit.java:438) javafx.graphics/com.sun.javafx.tk.quantum.QuantumToolkit.pulse(QuantumToolkit.java:562) javafx.graphics/com.sun.javafx.tk.quantum.QuantumToolkit.pulse(QuantumToolkit.java:542) javafx.graphics/com.sun.javafx.tk.quantum.QuantumToolkit.pulseFromQueue(QuantumToolkit.java:535) javafx.graphics/com.sun.javafx.tk.quantum.QuantumToolkit.lambda$runToolkit$11(QuantumToolkit.java:341) javafx.graphics/com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:96) javafx.graphics/com.sun.glass.ui.win.WinApplication._runLoop(Native Method) javafx.graphics/com.sun.glass.ui.win.WinApplication.lambda$runLoop$3(WinApplication.java:174) java.base/java.lang.Thread.run(Thread.java:835)

Jugen commented 4 years ago

If it's being triggered by what happens in your caret bounds listener then try wrapping that section of code in a Platform.runlater lambda, something like:

area.caret????Property( (ob,ov,nv) -> Platform.runlater( () ->
{
    // Your code here .....
} ) );

Hopefully that will give the layout time to finish and then the index out of bounds exception won't happen.

garybentley commented 4 years ago

Unfortunately, none of my code is listening to the caret. The exception seems to suggest that there is a size mismatch when the area tries to recompute the caret. Digging a little further I think that the caret node is being removed from the scene graph while the layout is occurring.

If you look at line: 1210 from: https://github.com/javafxports/openjdk-jfx/blob/jfx-12/modules/javafx.graphics/src/main/java/javafx/scene/Parent.java

The number of children is being gained at the start of the loop but the index (from the exception) is out of bounds and becomes so while inside the loop suggesting that a node has been removed.

I think my call to getCharacterBoundsOnScreen/getParagraphBoundsOnScreen during the layoutChildren call in my paragraph graphic is triggering another layout but I can't work out precisely why.

Jugen commented 4 years ago

Try wrapping that section of code inside the paragraph graphic that calls getCharacterBoundsOnScreen or getParagraphBoundsOnScreen in a Platform.runlater block and see if that doesn't maybe help.

garybentley commented 4 years ago

Unfortunately it doesn't. The calls to getCharacterBoundsOnScreen/getParagraphBoundsOnScreen are being performed during the layoutChildren method, pushing them onto the next pulse just causes an infinite loop. I'll see if I can make the calls outside of the layoutChildren method.