From 6e5eff3e9a23d7695f89f8c8c534642f5dcb28fc Mon Sep 17 00:00:00 2001 From: Jurgen Date: Mon, 16 Mar 2020 10:59:26 +0200 Subject: [PATCH] Provide suspendable undo manager and test --- .../fxmisc/richtext/api/UndoManagerTests.java | 16 +++++++++ .../fxmisc/richtext/model/RichTextChange.java | 27 ++++++++++++++ .../org/fxmisc/richtext/util/UndoUtils.java | 35 +++++++++++++++++++ 3 files changed, 78 insertions(+) diff --git a/richtextfx/src/integrationTest/java/org/fxmisc/richtext/api/UndoManagerTests.java b/richtextfx/src/integrationTest/java/org/fxmisc/richtext/api/UndoManagerTests.java index f2359b192..00072eb17 100644 --- a/richtextfx/src/integrationTest/java/org/fxmisc/richtext/api/UndoManagerTests.java +++ b/richtextfx/src/integrationTest/java/org/fxmisc/richtext/api/UndoManagerTests.java @@ -9,10 +9,12 @@ import org.fxmisc.richtext.RichTextFXTestBase; import org.fxmisc.richtext.StyledTextArea; import org.fxmisc.richtext.model.SimpleEditableStyledDocument; +import org.fxmisc.richtext.model.RichTextChange; import org.fxmisc.richtext.model.TextChange; import org.fxmisc.richtext.util.UndoUtils; import org.junit.Test; import org.junit.runner.RunWith; +import org.reactfx.SuspendableYes; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -174,6 +176,20 @@ public void testForBug904() { interact( area::undo ); // should not throw Unexpected change received exception } + @Test + public void suspendable_UndoManager_skips_style_check() { + + SuspendableYes suspendUndo = new SuspendableYes(); + area.setUndoManager( UndoUtils.richTextSuspendableUndoManager( area, suspendUndo ) ); + write( "some text\n" ); + interact( () -> suspendUndo.suspendWhile( () -> area.setStyle( 5, 9, "-fx-font-weight: bold;" ) ) ); + write( "new line" ); + interact( area::undo ); // should not throw Unexpected change received exception + + area.setUndoManager( UndoUtils.defaultUndoManager( area ) ); + RichTextChange.skipStyleComparison( false ); + } + } public class UsingStyledTextArea extends RichTextFXTestBase { diff --git a/richtextfx/src/main/java/org/fxmisc/richtext/model/RichTextChange.java b/richtextfx/src/main/java/org/fxmisc/richtext/model/RichTextChange.java index 5fa34cbb9..c1520ee47 100644 --- a/richtextfx/src/main/java/org/fxmisc/richtext/model/RichTextChange.java +++ b/richtextfx/src/main/java/org/fxmisc/richtext/model/RichTextChange.java @@ -45,4 +45,31 @@ public final PlainTextChange toPlainTextChange() { public final boolean isPlainTextIdentity() { return removed.getText().equals(inserted.getText()); } + + private static boolean skipStyleComparison = false; + + public static void skipStyleComparison( boolean value ) + { + skipStyleComparison = value; + } + + /* + * This gets used, by the default UndoManagers supplied by UndoUtils, + * to check that a submitted undo/redo matches the change reported. + */ + public boolean equals( Object other ) + { + if( skipStyleComparison && other instanceof RichTextChange ) + { + PlainTextChange otherChange = ((RichTextChange) other).toPlainTextChange(); + boolean matches = toPlainTextChange().equals( otherChange ); + if ( ! matches ) System.err.println( + "Plain text comparison mismatch caused by text change" + +" during undo manager suspension (styling ignored)." + ); + return matches; + } + + return super.equals( other ); + } } diff --git a/richtextfx/src/main/java/org/fxmisc/richtext/util/UndoUtils.java b/richtextfx/src/main/java/org/fxmisc/richtext/util/UndoUtils.java index 7b6470a43..acf9c87ac 100644 --- a/richtextfx/src/main/java/org/fxmisc/richtext/util/UndoUtils.java +++ b/richtextfx/src/main/java/org/fxmisc/richtext/util/UndoUtils.java @@ -7,6 +7,9 @@ import org.fxmisc.richtext.model.TextChange; import org.fxmisc.undo.UndoManager; import org.fxmisc.undo.UndoManagerFactory; +import org.fxmisc.undo.impl.MultiChangeUndoManagerImpl; +import org.fxmisc.undo.impl.UnlimitedChangeQueue; +import org.reactfx.SuspendableYes; import org.reactfx.value.Val; import javafx.beans.value.ObservableBooleanValue; @@ -121,6 +124,38 @@ public static UndoManager>> richTex preventMergeDelay); }; + /** + * Returns an UndoManager with an unlimited history that can undo/redo {@link RichTextChange}s. New changes + * emitted from the stream will not be merged with the previous change after {@link #DEFAULT_PREVENT_MERGE_DELAY} + *

Note: that only styling changes may occur during suspension of the undo manager. + */ + public static UndoManager>> richTextSuspendableUndoManager( + GenericStyledArea area, SuspendableYes suspendUndo) { + return richTextSuspendableUndoManager(area, DEFAULT_PREVENT_MERGE_DELAY, suspendUndo); + } + + /** + * Returns an UndoManager with an unlimited history that can undo/redo {@link RichTextChange}s. New changes + * emitted from the stream will not be merged with the previous change after {@code preventMergeDelay}. + *

Note: that only styling changes may occur during suspension of the undo manager. + */ + public static UndoManager>> richTextSuspendableUndoManager( + GenericStyledArea area, Duration preventMergeDelay, SuspendableYes suspendUndo) { + + RichTextChange.skipStyleComparison( true ); + + return new MultiChangeUndoManagerImpl<> + ( + new UnlimitedChangeQueue<>(), + TextChange::invert, + applyMultiRichTextChange(area), + TextChange::mergeWith, + TextChange::isIdentity, + area.multiRichChanges().conditionOn(suspendUndo), + preventMergeDelay + ); + }; + /** * Returns an UndoManager with an unlimited history that can undo/redo {@link PlainTextChange}s. New changes * emitted from the stream will not be merged with the previous change