From b8db958f4179b8a225f2c21b2bd48a35a058e515 Mon Sep 17 00:00:00 2001 From: Andy Goryachev Date: Mon, 20 Oct 2025 15:25:42 -0700 Subject: [PATCH 01/18] allow undo --- .../demo/richtext/codearea/CodeAreaDemoPane.java | 6 +++--- .../com/oracle/demo/richtext/editor/Actions.java | 8 ++++---- .../oracle/demo/richtext/notebook/Actions.java | 6 +++--- .../demo/richtext/rta/RichTextAreaDemoPane.java | 4 ++-- .../scene/control/richtext/RichTextArea.java | 10 ++++++---- .../control/richtext/model/StyledTextModel.java | 5 ++--- .../scene/control/richtext/RichTextAreaTest.java | 10 +++++----- .../control/richtext/model/HTMLExportTest.java | 16 ++++++++-------- 8 files changed, 33 insertions(+), 32 deletions(-) diff --git a/apps/samples/RichTextAreaDemo/src/com/oracle/demo/richtext/codearea/CodeAreaDemoPane.java b/apps/samples/RichTextAreaDemo/src/com/oracle/demo/richtext/codearea/CodeAreaDemoPane.java index 759e035c319..7164f2041af 100644 --- a/apps/samples/RichTextAreaDemo/src/com/oracle/demo/richtext/codearea/CodeAreaDemoPane.java +++ b/apps/samples/RichTextAreaDemo/src/com/oracle/demo/richtext/codearea/CodeAreaDemoPane.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2024, Oracle and/or its affiliates. + * Copyright (c) 2023, 2025, Oracle and/or its affiliates. * All rights reserved. Use is subject to license terms. * * This file is available and licensed under the following license: @@ -328,7 +328,7 @@ protected void apply(StyleAttribute attr, V val) { TextPos ca = control.getCaretPosition(); TextPos an = control.getAnchorPosition(); StyleAttributeMap a = StyleAttributeMap.builder().set(attr, val).build(); - control.applyStyle(ca, an, a); + control.applyStyle(ca, an, a, true); } protected void setLineSpacing(double x) { @@ -339,7 +339,7 @@ private void applyStyle(StyleAttribute a, V val) { TextPos ca = control.getCaretPosition(); TextPos an = control.getAnchorPosition(); StyleAttributeMap m = StyleAttributeMap.of(a, val); - control.applyStyle(ca, an, m); + control.applyStyle(ca, an, m, true); } // diff --git a/apps/samples/RichTextAreaDemo/src/com/oracle/demo/richtext/editor/Actions.java b/apps/samples/RichTextAreaDemo/src/com/oracle/demo/richtext/editor/Actions.java index 7814deb767c..1b2d88ebe95 100644 --- a/apps/samples/RichTextAreaDemo/src/com/oracle/demo/richtext/editor/Actions.java +++ b/apps/samples/RichTextAreaDemo/src/com/oracle/demo/richtext/editor/Actions.java @@ -223,7 +223,7 @@ private void toggle(StyleAttribute attr) { StyleAttributeMap a = control.getActiveStyleAttributeMap(); boolean on = !a.getBoolean(attr); a = StyleAttributeMap.builder().set(attr, on).build(); - control.applyStyle(start, end, a); + control.applyStyle(start, end, a, true); } private void apply(StyleAttribute attr, T value) { @@ -240,7 +240,7 @@ private void apply(StyleAttribute attr, T value) { StyleAttributeMap a = control.getActiveStyleAttributeMap(); a = StyleAttributeMap.builder().set(attr, value).build(); - control.applyStyle(start, end, a); + control.applyStyle(start, end, a, true); } // TODO need to bind selected item in the combo @@ -414,7 +414,7 @@ private void toggleStyle(StyleAttribute attr) { StyleAttributeMap a = control.getActiveStyleAttributeMap(); boolean on = !a.getBoolean(attr); a = StyleAttributeMap.builder().set(attr, on).build(); - control.applyStyle(start, end, a); + control.applyStyle(start, end, a, true); updateSourceStyles(); } @@ -435,7 +435,7 @@ public void setTextStyle(TextStyle st) { } StyleAttributeMap a = Styles.getStyleAttributeMap(st); - control.applyStyle(start, end, a); + control.applyStyle(start, end, a, true); updateSourceStyles(); } diff --git a/apps/samples/RichTextAreaDemo/src/com/oracle/demo/richtext/notebook/Actions.java b/apps/samples/RichTextAreaDemo/src/com/oracle/demo/richtext/notebook/Actions.java index 408ebe6b517..20905e43928 100644 --- a/apps/samples/RichTextAreaDemo/src/com/oracle/demo/richtext/notebook/Actions.java +++ b/apps/samples/RichTextAreaDemo/src/com/oracle/demo/richtext/notebook/Actions.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2024, Oracle and/or its affiliates. + * Copyright (c) 2023, 2025, Oracle and/or its affiliates. * All rights reserved. Use is subject to license terms. * * This file is available and licensed under the following license: @@ -733,7 +733,7 @@ private void toggleStyle(StyleAttribute attr) { StyleAttributeMap a = c.getActiveStyleAttributeMap(); boolean on = !a.getBoolean(attr); a = StyleAttributeMap.builder().set(attr, on).build(); - c.applyStyle(start, end, a); + c.applyStyle(start, end, a, true); updateSourceStyles(); }); } @@ -756,7 +756,7 @@ public void setTextStyle(TextStyle st) { } StyleAttributeMap a = Styles.getStyleAttributeMap(st); - c.applyStyle(start, end, a); + c.applyStyle(start, end, a, true); updateSourceStyles(); }); } diff --git a/apps/samples/RichTextAreaDemo/src/com/oracle/demo/richtext/rta/RichTextAreaDemoPane.java b/apps/samples/RichTextAreaDemo/src/com/oracle/demo/richtext/rta/RichTextAreaDemoPane.java index f69a5109699..452159c6064 100644 --- a/apps/samples/RichTextAreaDemo/src/com/oracle/demo/richtext/rta/RichTextAreaDemoPane.java +++ b/apps/samples/RichTextAreaDemo/src/com/oracle/demo/richtext/rta/RichTextAreaDemoPane.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2024, Oracle and/or its affiliates. + * Copyright (c) 2023, 2025, Oracle and/or its affiliates. * All rights reserved. Use is subject to license terms. * * This file is available and licensed under the following license: @@ -662,7 +662,7 @@ private void applyStyle(StyleAttribute a, T val) { TextPos ca = control.getCaretPosition(); TextPos an = control.getAnchorPosition(); StyleAttributeMap m = StyleAttributeMap.of(a, val); - control.applyStyle(ca, an, m); + control.applyStyle(ca, an, m, true); } void dumpAccessibilityAttributes() { diff --git a/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/RichTextArea.java b/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/RichTextArea.java index a40789133b3..8422ab40356 100644 --- a/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/RichTextArea.java +++ b/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/RichTextArea.java @@ -1099,12 +1099,13 @@ public final TextPos appendText(StyledInput in) { * @param start the start of text range * @param end the end of text range * @param attrs the style attributes to apply + * @param allowUndo when true, creates an undo-redo entry * @throws NullPointerException if the model is {@code null} * @throws UnsupportedOperationException if the model is not {@link StyledTextModel#isWritable() writable} */ - public void applyStyle(TextPos start, TextPos end, StyleAttributeMap attrs) { + public void applyStyle(TextPos start, TextPos end, StyleAttributeMap attrs, boolean allowUndo) { StyledTextModel m = getModel(); - m.applyStyle(start, end, attrs, true); + m.applyStyle(start, end, attrs, true, allowUndo); } /** @@ -2157,12 +2158,13 @@ public void selectWordRight() { * @param start the start of text range * @param end the end of text range * @param attrs the style attributes to set + * @param allowUndo when true, creates an undo-redo entry * @throws NullPointerException if the model is {@code null} * @throws UnsupportedOperationException if the model is not {@link StyledTextModel#isWritable() writable} */ - public final void setStyle(TextPos start, TextPos end, StyleAttributeMap attrs) { + public final void setStyle(TextPos start, TextPos end, StyleAttributeMap attrs, boolean allowUndo) { StyledTextModel m = getModel(); - m.applyStyle(start, end, attrs, false); + m.applyStyle(start, end, attrs, false, allowUndo); } /** diff --git a/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/model/StyledTextModel.java b/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/model/StyledTextModel.java index f62ae75f30d..d529d96c148 100644 --- a/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/model/StyledTextModel.java +++ b/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/model/StyledTextModel.java @@ -728,16 +728,15 @@ public final TextPos replace(StyleResolver resolver, TextPos start, TextPos end, * Depending on {@code mergeAttributes} parameter, the attributes will either be merged with (true) or completely * replace the existing attributes within the range. The affected range might be wider than the range specified * when applying the paragraph attributes. - *

- * This operation is undoable. * * @param start the start of text range * @param end the end of text range * @param attrs the style attributes to set * @param mergeAttributes whether to merge or replace the attributes + * @param allowUndo when true, creates an undo-redo entry * @throws UnsupportedOperationException if the model is not {@link #isWritable() writable} */ - public final void applyStyle(TextPos start, TextPos end, StyleAttributeMap attrs, boolean mergeAttributes) { + public final void applyStyle(TextPos start, TextPos end, StyleAttributeMap attrs, boolean mergeAttributes, boolean allowUndo) { checkWritable(); if (start.compareTo(end) > 0) { diff --git a/modules/jfx.incubator.richtext/src/test/java/test/jfx/incubator/scene/control/richtext/RichTextAreaTest.java b/modules/jfx.incubator.richtext/src/test/java/test/jfx/incubator/scene/control/richtext/RichTextAreaTest.java index 8ca8a98a30d..fb44e8be564 100644 --- a/modules/jfx.incubator.richtext/src/test/java/test/jfx/incubator/scene/control/richtext/RichTextAreaTest.java +++ b/modules/jfx.incubator.richtext/src/test/java/test/jfx/incubator/scene/control/richtext/RichTextAreaTest.java @@ -282,7 +282,7 @@ public void appendTextFromStyledInput() { public void applyStyle() { TestStyledInput in = TestStyledInput.plainText("a\nb"); TextPos p = control.appendText(in); - control.applyStyle(TextPos.ZERO, TextPos.ofLeading(0, 1), BOLD); + control.applyStyle(TextPos.ZERO, TextPos.ofLeading(0, 1), BOLD, true); assertEquals(TextPos.ofLeading(1, 1), p); } @@ -543,7 +543,7 @@ public void pastePlainText() { @Test public void read() throws Exception { control.appendText("1 bold"); - control.applyStyle(TextPos.ofLeading(0, 2), TextPos.ofLeading(0, 6), BOLD); + control.applyStyle(TextPos.ofLeading(0, 2), TextPos.ofLeading(0, 6), BOLD, true); ByteArrayOutputStream out = new ByteArrayOutputStream(); control.write(out); byte[] b = out.toByteArray(); @@ -560,7 +560,7 @@ public void read() throws Exception { public void readDataFormat() throws Exception { DataFormat fmt = DataFormat.PLAIN_TEXT; control.appendText("1 bold"); - control.applyStyle(TextPos.ofLeading(0, 2), TextPos.ofLeading(0, 6), BOLD); + control.applyStyle(TextPos.ofLeading(0, 2), TextPos.ofLeading(0, 6), BOLD, true); ByteArrayOutputStream out = new ByteArrayOutputStream(); control.write(fmt, out); byte[] b = out.toByteArray(); @@ -661,7 +661,7 @@ public void undo() { @Test public void write() throws Exception { control.appendText("1 bold"); - control.applyStyle(TextPos.ofLeading(0, 2), TextPos.ofLeading(0, 6), BOLD); + control.applyStyle(TextPos.ofLeading(0, 2), TextPos.ofLeading(0, 6), BOLD, true); ByteArrayOutputStream out = new ByteArrayOutputStream(); control.write(out); byte[] b = out.toByteArray(); @@ -672,7 +672,7 @@ public void write() throws Exception { public void writeDataFormat() throws Exception { DataFormat fmt = DataFormat.PLAIN_TEXT; control.appendText("1 bold"); - control.applyStyle(TextPos.ofLeading(0, 2), TextPos.ofLeading(0, 6), BOLD); + control.applyStyle(TextPos.ofLeading(0, 2), TextPos.ofLeading(0, 6), BOLD, true); ByteArrayOutputStream out = new ByteArrayOutputStream(); control.write(fmt, out); byte[] b = out.toByteArray(); diff --git a/modules/jfx.incubator.richtext/src/test/java/test/jfx/incubator/scene/control/richtext/model/HTMLExportTest.java b/modules/jfx.incubator.richtext/src/test/java/test/jfx/incubator/scene/control/richtext/model/HTMLExportTest.java index f672978b3b8..f3d9dca5e31 100644 --- a/modules/jfx.incubator.richtext/src/test/java/test/jfx/incubator/scene/control/richtext/model/HTMLExportTest.java +++ b/modules/jfx.incubator.richtext/src/test/java/test/jfx/incubator/scene/control/richtext/model/HTMLExportTest.java @@ -65,24 +65,24 @@ public void characterAttributes() throws Exception { """); // bold - model.applyStyle(TextPos.ZERO, TextPos.ofLeading(0, 1), mk(StyleAttributeMap.BOLD, Boolean.TRUE), false); + model.applyStyle(TextPos.ZERO, TextPos.ofLeading(0, 1), mk(StyleAttributeMap.BOLD, Boolean.TRUE), false, false); checkContains("111

"); - model.applyStyle(TextPos.ZERO, TextPos.ofLeading(0, 1), mk(StyleAttributeMap.BOLD, Boolean.FALSE), false); + model.applyStyle(TextPos.ZERO, TextPos.ofLeading(0, 1), mk(StyleAttributeMap.BOLD, Boolean.FALSE), false, false); checkContains("111

"); // italic - model.applyStyle(TextPos.ZERO, TextPos.ofLeading(0, 1), mk(StyleAttributeMap.ITALIC, Boolean.TRUE), false); + model.applyStyle(TextPos.ZERO, TextPos.ofLeading(0, 1), mk(StyleAttributeMap.ITALIC, Boolean.TRUE), false, false); checkContains("111

"); - model.applyStyle(TextPos.ZERO, TextPos.ofLeading(0, 1), mk(StyleAttributeMap.ITALIC, Boolean.FALSE), false); + model.applyStyle(TextPos.ZERO, TextPos.ofLeading(0, 1), mk(StyleAttributeMap.ITALIC, Boolean.FALSE), false, false); checkContains("111

"); // strikethrough - model.applyStyle(TextPos.ZERO, TextPos.ofLeading(0, 1), mk(StyleAttributeMap.STRIKE_THROUGH, Boolean.TRUE), false); + model.applyStyle(TextPos.ZERO, TextPos.ofLeading(0, 1), mk(StyleAttributeMap.STRIKE_THROUGH, Boolean.TRUE), false, false); checkContains("111

"); - model.applyStyle(TextPos.ZERO, TextPos.ofLeading(0, 1), mk(StyleAttributeMap.STRIKE_THROUGH, Boolean.FALSE), false); + model.applyStyle(TextPos.ZERO, TextPos.ofLeading(0, 1), mk(StyleAttributeMap.STRIKE_THROUGH, Boolean.FALSE), false, false); checkContains("111

"); // underline - model.applyStyle(TextPos.ZERO, TextPos.ofLeading(0, 1), mk(StyleAttributeMap.UNDERLINE, Boolean.TRUE), false); + model.applyStyle(TextPos.ZERO, TextPos.ofLeading(0, 1), mk(StyleAttributeMap.UNDERLINE, Boolean.TRUE), false, false); checkContains("111

"); - model.applyStyle(TextPos.ZERO, TextPos.ofLeading(0, 1), mk(StyleAttributeMap.UNDERLINE, Boolean.FALSE), false); + model.applyStyle(TextPos.ZERO, TextPos.ofLeading(0, 1), mk(StyleAttributeMap.UNDERLINE, Boolean.FALSE), false, false); checkContains("111

"); } From e06c22de8d6da245f6538f928d374e630ff22f16 Mon Sep 17 00:00:00 2001 From: Andy Goryachev Date: Mon, 20 Oct 2025 15:53:59 -0700 Subject: [PATCH 02/18] tests --- .../scene/control/richtext/CodeArea.java | 2 +- .../richtext/model/StyledTextModel.java | 4 +-- .../control/richtext/RichTextAreaTest.java | 36 +++++++++++++++++-- 3 files changed, 36 insertions(+), 6 deletions(-) diff --git a/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/CodeArea.java b/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/CodeArea.java index 3ee442abc5c..57ed291b35d 100644 --- a/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/CodeArea.java +++ b/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/CodeArea.java @@ -77,7 +77,7 @@ * There are some differences that should be mentioned: *

    *
  • Model behavior: any direct changes to the styling, such as - * {@link #applyStyle(TextPos, TextPos, jfx.incubator.scene.control.richtext.model.StyleAttributeMap) applyStyle()}, + * {@link #applyStyle(TextPos, TextPos, jfx.incubator.scene.control.richtext.model.StyleAttributeMap, boolean) applyStyle()}, * will be ignored *
  • Line numbers: the {@code CodeArea} sets the {@link #leftDecoratorProperty()} to support the line numbers, * so applications should not set or bind that property. diff --git a/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/model/StyledTextModel.java b/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/model/StyledTextModel.java index d529d96c148..3722ae570b4 100644 --- a/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/model/StyledTextModel.java +++ b/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/model/StyledTextModel.java @@ -70,7 +70,7 @@ * Three methods participate in modification of the content: * {@link #replace(StyleResolver, TextPos, TextPos, String, boolean)}, * {@link #replace(StyleResolver, TextPos, TextPos, StyledInput, boolean)}, - * {@link #applyStyle(TextPos, TextPos, StyleAttributeMap, boolean)}. + * {@link #applyStyle(TextPos, TextPos, StyleAttributeMap, boolean, boolean)}. * These methods decompose the main modification into operations with individual paragraphs * and delegate these to subclasses. *

    @@ -248,7 +248,7 @@ public interface Listener { * clipboard). *

    * The methods that utilize the filtering are: - * {@link #applyStyle(TextPos, TextPos, StyleAttributeMap, boolean)}, + * {@link #applyStyle(TextPos, TextPos, StyleAttributeMap, boolean, boolean)}, * {@link #replace(StyleResolver, TextPos, TextPos, StyledInput, boolean)}, and * {@link #replace(StyleResolver, TextPos, TextPos, String, boolean)}. *

    diff --git a/modules/jfx.incubator.richtext/src/test/java/test/jfx/incubator/scene/control/richtext/RichTextAreaTest.java b/modules/jfx.incubator.richtext/src/test/java/test/jfx/incubator/scene/control/richtext/RichTextAreaTest.java index fb44e8be564..e35aa6469c5 100644 --- a/modules/jfx.incubator.richtext/src/test/java/test/jfx/incubator/scene/control/richtext/RichTextAreaTest.java +++ b/modules/jfx.incubator.richtext/src/test/java/test/jfx/incubator/scene/control/richtext/RichTextAreaTest.java @@ -280,10 +280,21 @@ public void appendTextFromStyledInput() { @Test public void applyStyle() { - TestStyledInput in = TestStyledInput.plainText("a\nb"); + TestStyledInput in = TestStyledInput.plainText("a\nbbb"); TextPos p = control.appendText(in); - control.applyStyle(TextPos.ZERO, TextPos.ofLeading(0, 1), BOLD, true); - assertEquals(TextPos.ofLeading(1, 1), p); + control.applyStyle(TextPos.ZERO, TextPos.ofLeading(1, 3), BOLD, true); + assertEquals(TextPos.ofLeading(1, 3), p); + control.select(TextPos.ofLeading(1, 0)); + assertEquals(BOLD, control.getActiveStyleAttributeMap()); + // allow undo + control.undo(); + assertEquals(StyleAttributeMap.EMPTY, control.getActiveStyleAttributeMap()); + // disallow undo + control.applyStyle(TextPos.ZERO, TextPos.ofLeading(1, 3), BOLD, false); + control.select(TextPos.ofLeading(1, 0)); + control.undo(); // undo previous BOLD + control.undo(); // undo initial appendText + assertEquals("", text()); } @Test @@ -609,6 +620,25 @@ public void replaceTextFromStyledInput() { assertEquals("1-4", text()); } + @Test + public void setStyle() { + TestStyledInput in = TestStyledInput.plainText("a\nbbb"); + TextPos p = control.appendText(in); + control.setStyle(TextPos.ZERO, TextPos.ofLeading(1, 3), BOLD, true); + assertEquals(TextPos.ofLeading(1, 3), p); + control.select(TextPos.ofLeading(1, 0)); + assertEquals(BOLD, control.getActiveStyleAttributeMap()); + // allow undo + control.undo(); + assertEquals(StyleAttributeMap.EMPTY, control.getActiveStyleAttributeMap()); + // disallow undo + control.setStyle(TextPos.ZERO, TextPos.ofLeading(1, 3), BOLD, false); + control.select(TextPos.ofLeading(1, 0)); + control.undo(); // undo previous BOLD + control.undo(); // undo initial appendText + assertEquals("", text()); + } + @Test public void select() { TextPos p = TextPos.ofLeading(0, 1); From cc54af75493f1f1f9ddae6135233a63e5e4def48 Mon Sep 17 00:00:00 2001 From: Andy Goryachev Date: Tue, 21 Oct 2025 09:57:04 -0700 Subject: [PATCH 03/18] append insert text --- .../demo/richtext/notebook/CellPane.java | 4 +- .../richtext/rta/RichTextAreaDemoPane.java | 8 ++-- .../demo/richtext/rta/UsageExamples.java | 14 +++---- .../scene/control/richtext/RichTextArea.java | 38 ++++++++++--------- .../control/richtext/RichTextAreaTest.java | 14 +++---- 5 files changed, 41 insertions(+), 37 deletions(-) diff --git a/apps/samples/RichTextAreaDemo/src/com/oracle/demo/richtext/notebook/CellPane.java b/apps/samples/RichTextAreaDemo/src/com/oracle/demo/richtext/notebook/CellPane.java index a61a67c7e48..45820258d76 100644 --- a/apps/samples/RichTextAreaDemo/src/com/oracle/demo/richtext/notebook/CellPane.java +++ b/apps/samples/RichTextAreaDemo/src/com/oracle/demo/richtext/notebook/CellPane.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2024, Oracle and/or its affiliates. + * Copyright (c) 2023, 2025, Oracle and/or its affiliates. * All rights reserved. Use is subject to license terms. * * This file is available and licensed under the following license: @@ -296,7 +296,7 @@ private List splitInTwo(TextPos p) { private void insert(RichTextArea src, TextPos start, TextPos end, RichTextArea tgt, TextPos pos) throws IOException { SegmentBuffer b = new SegmentBuffer(); src.getModel().export(start, end, b.getStyledOutput()); - tgt.insertText(pos, b.getStyledInput()); + tgt.insertText(pos, b.getStyledInput(), false); } public void setActive(boolean on) { diff --git a/apps/samples/RichTextAreaDemo/src/com/oracle/demo/richtext/rta/RichTextAreaDemoPane.java b/apps/samples/RichTextAreaDemo/src/com/oracle/demo/richtext/rta/RichTextAreaDemoPane.java index 452159c6064..9918f950f93 100644 --- a/apps/samples/RichTextAreaDemo/src/com/oracle/demo/richtext/rta/RichTextAreaDemoPane.java +++ b/apps/samples/RichTextAreaDemo/src/com/oracle/demo/richtext/rta/RichTextAreaDemoPane.java @@ -235,8 +235,8 @@ public StyleHandlerRegistry getStyleHandlerRegistry() { appendButton.setOnAction((ev) -> { StyleAttributeMap heading = StyleAttributeMap.builder().setBold(true).setFontSize(24).build(); StyleAttributeMap plain = StyleAttributeMap.builder().setFontFamily("Monospaced").build(); - control.appendText("Heading\n", heading); - control.appendText("Plain monospaced text.\n", plain); + control.appendText("Heading\n", heading, false); + control.appendText("Plain monospaced text.\n", plain, false); }); Button insertButton = new Button("Insert"); @@ -244,8 +244,8 @@ public StyleHandlerRegistry getStyleHandlerRegistry() { insertButton.setOnAction((ev) -> { StyleAttributeMap heading = StyleAttributeMap.builder().setBold(true).setFontSize(24).build(); StyleAttributeMap plain = StyleAttributeMap.builder().setFontFamily("Monospaced").build(); - control.insertText(TextPos.ZERO, "Plain monospaced text.\n", plain); - control.insertText(TextPos.ZERO, "Heading\n", heading); + control.insertText(TextPos.ZERO, "Plain monospaced text.\n", plain, false); + control.insertText(TextPos.ZERO, "Heading\n", heading, false); }); Button replaceSkin = new Button("Replace Skin"); diff --git a/apps/samples/RichTextAreaDemo/src/com/oracle/demo/richtext/rta/UsageExamples.java b/apps/samples/RichTextAreaDemo/src/com/oracle/demo/richtext/rta/UsageExamples.java index 7d9d3b1fc00..997cee828e7 100644 --- a/apps/samples/RichTextAreaDemo/src/com/oracle/demo/richtext/rta/UsageExamples.java +++ b/apps/samples/RichTextAreaDemo/src/com/oracle/demo/richtext/rta/UsageExamples.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2024, Oracle and/or its affiliates. + * Copyright (c) 2023, 2025, Oracle and/or its affiliates. * All rights reserved. Use is subject to license terms. * * This file is available and licensed under the following license: @@ -85,9 +85,9 @@ static RichTextArea appendStyledText() { RichTextArea textArea = new RichTextArea(); // build the content - textArea.appendText("RichTextArea\n", heading); - textArea.appendText("Example:\nText is ", StyleAttributeMap.EMPTY); - textArea.appendText("monospaced.\n", mono); + textArea.appendText("RichTextArea\n", heading, false); + textArea.appendText("Example:\nText is ", StyleAttributeMap.EMPTY, false); + textArea.appendText("monospaced.\n", mono, false); return textArea; } @@ -95,7 +95,7 @@ void richTextAreaExample() { RichTextArea textArea = new RichTextArea(); // insert two paragraphs "A" and "B" StyleAttributeMap bold = StyleAttributeMap.builder().setBold(true).build(); - textArea.appendText("A\nB", bold); + textArea.appendText("A\nB", bold, false); } private static CodeArea codeAreaExample() { @@ -192,10 +192,10 @@ public void start(Stage stage) throws Exception { System.out.println("caret: " + c); }); t.getInputMap().register(KeyBinding.of(KeyCode.F1), () -> { - t.insertText(TextPos.ZERO, "F1", StyleAttributeMap.EMPTY); + t.insertText(TextPos.ZERO, "F1", StyleAttributeMap.EMPTY, false); }); t.getInputMap().register(KeyBinding.of(KeyCode.F2), () -> { - t.insertText(TextPos.ZERO, "\n", StyleAttributeMap.EMPTY); + t.insertText(TextPos.ZERO, "\n", StyleAttributeMap.EMPTY, false); }); t.getInputMap().register(KeyBinding.of(KeyCode.F3), () -> { t.clearSelection(); diff --git a/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/RichTextArea.java b/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/RichTextArea.java index 8422ab40356..adcb0c49cbb 100644 --- a/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/RichTextArea.java +++ b/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/RichTextArea.java @@ -98,9 +98,9 @@ * * RichTextArea textArea = new RichTextArea(); * // build the content - * textArea.appendText("RichTextArea\n", heading); - * textArea.appendText("Example:\nText is ", StyleAttributeMap.EMPTY); - * textArea.appendText("monospaced.\n", mono); + * textArea.appendText("RichTextArea\n", heading, false); + * textArea.appendText("Example:\nText is ", StyleAttributeMap.EMPTY, false); + * textArea.appendText("monospaced.\n", mono, false); * } * Which results in the following visual representation: *

    @@ -1051,21 +1051,22 @@ public StyleableProperty getStyleableProperty(RichTextArea t) { * * @param text the text to append * @param attrs the style attributes + * @param allowUndo when true, creates an undo-redo entry * @return the text position at the end of the appended text, or null if editing is disabled * @throws NullPointerException if the model is {@code null} * @throws UnsupportedOperationException if the model is not {@link StyledTextModel#isWritable() writable} */ - public final TextPos appendText(String text, StyleAttributeMap attrs) { + public final TextPos appendText(String text, StyleAttributeMap attrs, boolean allowUndo) { TextPos p = getDocumentEnd(); - return insertText(p, text, attrs); + return insertText(p, text, attrs, allowUndo); } /** * Appends the styled text to the end of the document. Any embedded {@code "\n"} or {@code "\r\n"} - * sequences result in a new paragraph being added. + * sequences result in a new paragraph being added. This method creates an undo/redo entry. *

    * This convenience method is equivalent to calling - * {@code appendText(text, StyleAttributeMap.EMPTY);} + * {@code appendText(text, StyleAttributeMap.EMPTY, true);} * * @param text the text to append * @return the text position at the end of the appended text, or null if editing is disabled @@ -1073,7 +1074,7 @@ public final TextPos appendText(String text, StyleAttributeMap attrs) { * @throws UnsupportedOperationException if the model is not {@link StyledTextModel#isWritable() writable} */ public final TextPos appendText(String text) { - return appendText(text, StyleAttributeMap.EMPTY); + return appendText(text, StyleAttributeMap.EMPTY, true); } /** @@ -1081,13 +1082,14 @@ public final TextPos appendText(String text) { * sequences result in a new paragraph being added. * * @param in the input stream + * @param allowUndo when true, creates an undo-redo entry * @return the text position at the end of the appended text, or null if editing is disabled * @throws NullPointerException if the model is {@code null} * @throws UnsupportedOperationException if the model is not {@link StyledTextModel#isWritable() writable} */ - public final TextPos appendText(StyledInput in) { + public final TextPos appendText(StyledInput in, boolean allowUndo) { TextPos p = getDocumentEnd(); - return insertText(p, in); + return insertText(p, in, allowUndo); } /** @@ -1477,13 +1479,14 @@ public void insertTab() { * @param pos the insert position * @param text the text to inser * @param attrs the style attributes + * @param allowUndo when true, creates an undo-redo entry * @return the text position at the end of the appended text, or null if editing is disabled * @throws NullPointerException if the model is {@code null} * @throws UnsupportedOperationException if the model is not {@link StyledTextModel#isWritable() writable} */ - public final TextPos insertText(TextPos pos, String text, StyleAttributeMap attrs) { + public final TextPos insertText(TextPos pos, String text, StyleAttributeMap attrs, boolean allowUndo) { StyledInput in = StyledInput.of(text, attrs); - return replaceText(pos, pos, in, true); + return replaceText(pos, pos, in, allowUndo); } /** @@ -1491,12 +1494,13 @@ public final TextPos insertText(TextPos pos, String text, StyleAttributeMap attr * * @param pos the insert position * @param in the input stream + * @param allowUndo when true, creates an undo-redo entry * @return the text position at the end of the appended text, or null if editing is disabled * @throws NullPointerException if the model is {@code null} * @throws UnsupportedOperationException if the model is not {@link StyledTextModel#isWritable() writable} */ - public final TextPos insertText(TextPos pos, StyledInput in) { - return replaceText(pos, pos, in, true); + public final TextPos insertText(TextPos pos, StyledInput in, boolean allowUndo) { + return replaceText(pos, pos, in, allowUndo); } /** @@ -1841,14 +1845,14 @@ public final TextPos replaceText(TextPos start, TextPos end, String text, boolea * @param start the start text position * @param end the end text position * @param in the input stream - * @param createUndo when true, creates an undo-redo entry + * @param allowUndo when true, creates an undo-redo entry * @return the new caret position at the end of inserted text, or null if the change cannot be made * @throws NullPointerException if the model is {@code null} * @throws UnsupportedOperationException if the model is not {@link StyledTextModel#isWritable() writable} */ - public final TextPos replaceText(TextPos start, TextPos end, StyledInput in, boolean createUndo) { + public final TextPos replaceText(TextPos start, TextPos end, StyledInput in, boolean allowUndo) { StyledTextModel m = getModel(); - return m.replace(vflow(), start, end, in, createUndo); + return m.replace(vflow(), start, end, in, allowUndo); } /** diff --git a/modules/jfx.incubator.richtext/src/test/java/test/jfx/incubator/scene/control/richtext/RichTextAreaTest.java b/modules/jfx.incubator.richtext/src/test/java/test/jfx/incubator/scene/control/richtext/RichTextAreaTest.java index e35aa6469c5..fbbb0e8a5a0 100644 --- a/modules/jfx.incubator.richtext/src/test/java/test/jfx/incubator/scene/control/richtext/RichTextAreaTest.java +++ b/modules/jfx.incubator.richtext/src/test/java/test/jfx/incubator/scene/control/richtext/RichTextAreaTest.java @@ -264,7 +264,7 @@ public void appendText() { @Test public void appendTextWithStyles() { - TextPos p = control.appendText("a", BOLD); + TextPos p = control.appendText("a", BOLD, false); assertEquals(TextPos.ofLeading(0, 1), p); control.select(p); assertEquals(BOLD, control.getActiveStyleAttributeMap()); @@ -274,14 +274,14 @@ public void appendTextWithStyles() { @Test public void appendTextFromStyledInput() { TestStyledInput in = TestStyledInput.plainText("a\nb"); - TextPos p = control.appendText(in); + TextPos p = control.appendText(in, false); assertEquals(TextPos.ofLeading(1, 1), p); } @Test public void applyStyle() { TestStyledInput in = TestStyledInput.plainText("a\nbbb"); - TextPos p = control.appendText(in); + TextPos p = control.appendText(in, true); control.applyStyle(TextPos.ZERO, TextPos.ofLeading(1, 3), BOLD, true); assertEquals(TextPos.ofLeading(1, 3), p); control.select(TextPos.ofLeading(1, 0)); @@ -407,8 +407,8 @@ public void executeDefault() { @Test public void getActiveStyleAttributeMap() { - control.appendText("1234", BOLD); - control.appendText("5678", StyleAttributeMap.EMPTY); + control.appendText("1234", BOLD, false); + control.appendText("5678", StyleAttributeMap.EMPTY, false); control.select(TextPos.ofLeading(0, 2)); StyleAttributeMap a = control.getActiveStyleAttributeMap(); @@ -504,7 +504,7 @@ public void isUndoable() { @Test public void modelChangeClearsSelection() { - control.insertText(TextPos.ZERO, "1234", null); + control.insertText(TextPos.ZERO, "1234", null, false); control.selectAll(); SelectionSegment sel = control.getSelection(); assertFalse(sel.isCollapsed()); @@ -623,7 +623,7 @@ public void replaceTextFromStyledInput() { @Test public void setStyle() { TestStyledInput in = TestStyledInput.plainText("a\nbbb"); - TextPos p = control.appendText(in); + TextPos p = control.appendText(in, true); control.setStyle(TextPos.ZERO, TextPos.ofLeading(1, 3), BOLD, true); assertEquals(TextPos.ofLeading(1, 3), p); control.select(TextPos.ofLeading(1, 0)); From 765a2cf91cc45bd1bc0b5246cdd348b4fee6e9c5 Mon Sep 17 00:00:00 2001 From: Andy Goryachev Date: Tue, 21 Oct 2025 10:51:05 -0700 Subject: [PATCH 04/18] test --- .../control/richtext/RichTextAreaTest.java | 57 ++++++++++++++++++- 1 file changed, 54 insertions(+), 3 deletions(-) diff --git a/modules/jfx.incubator.richtext/src/test/java/test/jfx/incubator/scene/control/richtext/RichTextAreaTest.java b/modules/jfx.incubator.richtext/src/test/java/test/jfx/incubator/scene/control/richtext/RichTextAreaTest.java index fbbb0e8a5a0..9d66a2042b7 100644 --- a/modules/jfx.incubator.richtext/src/test/java/test/jfx/incubator/scene/control/richtext/RichTextAreaTest.java +++ b/modules/jfx.incubator.richtext/src/test/java/test/jfx/incubator/scene/control/richtext/RichTextAreaTest.java @@ -74,6 +74,8 @@ public class RichTextAreaTest { private RichTextArea control; private static final StyleAttributeMap BOLD = StyleAttributeMap.builder().setBold(true).build(); + private static final StyleAttributeMap ITALIC = StyleAttributeMap.builder().setItalic(true).build(); + private static final String NL = System.getProperty("line.separator"); @BeforeEach public void beforeEach() { @@ -260,22 +262,40 @@ public void appendText() { TextPos p = control.appendText("a"); assertEquals(TextPos.ofLeading(0, 1), p); assertEquals("a", text()); + // undo + control.undo(); + assertEquals("", text()); } @Test public void appendTextWithStyles() { - TextPos p = control.appendText("a", BOLD, false); + TextPos p = control.appendText("a", BOLD, true); assertEquals(TextPos.ofLeading(0, 1), p); control.select(p); assertEquals(BOLD, control.getActiveStyleAttributeMap()); assertEquals("a", text()); + // undo + control.undo(); + assertEquals("", text()); + // no undo + control.appendText("b", BOLD, false); + control.undo(); + assertEquals("b", text()); } @Test public void appendTextFromStyledInput() { - TestStyledInput in = TestStyledInput.plainText("a\nb"); - TextPos p = control.appendText(in, false); + TestStyledInput in = TestStyledInput.plainText("a" + NL + "b"); + TextPos p = control.appendText(in, true); assertEquals(TextPos.ofLeading(1, 1), p); + assertEquals("a" + NL + "b", text()); + // undo + control.undo(); + assertEquals("", text()); + // no undo + control.appendText(TestStyledInput.plainText("dd"), false); + control.undo(); + assertEquals("dd", text()); } @Test @@ -487,6 +507,37 @@ public void insertLineBreak() { control.insertLineBreak(); } + @Test + public void insertTextWithStyles() { + TextPos p = control.appendText("a", BOLD, true); + assertEquals(TextPos.ofLeading(0, 1), p); + p = control.insertText(TextPos.ZERO, "b", ITALIC, true); + assertEquals(TextPos.ofLeading(0, 1), p); + control.select(p); + assertEquals(ITALIC, control.getActiveStyleAttributeMap()); + assertEquals("ba", text()); + // undo + control.undo(); + assertEquals("a", text()); + // no undo + control.insertText(TextPos.ZERO, "ccc", BOLD, false); + assertEquals("ccca", text()); + control.undo(); // FIX non-undoable change messed up the state of the previous undo entries + // we cannot disable undo. the application must use clearUndoRedo() instead + assertEquals("cca", text()); // FIX + } + + @Test + public void insertTextFromStyledInput() { + TestStyledInput in = TestStyledInput.plainText("a" + NL + "b"); + TextPos p = control.appendText(in, true); + assertEquals(TextPos.ofLeading(1, 1), p); + assertEquals("a" + NL + "b", text()); + // undo + control.undo(); + assertEquals("", text()); + } + @Test public void isRedoable() { assertFalse(control.isRedoable()); From 85f857aefe1205b36340d1e303145f6b65a43760 Mon Sep 17 00:00:00 2001 From: Andy Goryachev Date: Tue, 21 Oct 2025 11:36:01 -0700 Subject: [PATCH 05/18] nl --- .../scene/control/richtext/RichTextAreaTest.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/modules/jfx.incubator.richtext/src/test/java/test/jfx/incubator/scene/control/richtext/RichTextAreaTest.java b/modules/jfx.incubator.richtext/src/test/java/test/jfx/incubator/scene/control/richtext/RichTextAreaTest.java index 9d66a2042b7..0c6057f559f 100644 --- a/modules/jfx.incubator.richtext/src/test/java/test/jfx/incubator/scene/control/richtext/RichTextAreaTest.java +++ b/modules/jfx.incubator.richtext/src/test/java/test/jfx/incubator/scene/control/richtext/RichTextAreaTest.java @@ -75,7 +75,6 @@ public class RichTextAreaTest { private RichTextArea control; private static final StyleAttributeMap BOLD = StyleAttributeMap.builder().setBold(true).build(); private static final StyleAttributeMap ITALIC = StyleAttributeMap.builder().setItalic(true).build(); - private static final String NL = System.getProperty("line.separator"); @BeforeEach public void beforeEach() { @@ -285,10 +284,10 @@ public void appendTextWithStyles() { @Test public void appendTextFromStyledInput() { - TestStyledInput in = TestStyledInput.plainText("a" + NL + "b"); + TestStyledInput in = TestStyledInput.plainText("a\nb"); TextPos p = control.appendText(in, true); assertEquals(TextPos.ofLeading(1, 1), p); - assertEquals("a" + NL + "b", text()); + assertEquals("a\nb", text()); // undo control.undo(); assertEquals("", text()); @@ -529,10 +528,10 @@ public void insertTextWithStyles() { @Test public void insertTextFromStyledInput() { - TestStyledInput in = TestStyledInput.plainText("a" + NL + "b"); + TestStyledInput in = TestStyledInput.plainText("a\nb"); TextPos p = control.appendText(in, true); assertEquals(TextPos.ofLeading(1, 1), p); - assertEquals("a" + NL + "b", text()); + assertEquals("a\nb", text()); // undo control.undo(); assertEquals("", text()); From 3e2f689358ee8349450bf3e674e85b0be8161d0e Mon Sep 17 00:00:00 2001 From: Andy Goryachev Date: Tue, 21 Oct 2025 13:21:51 -0700 Subject: [PATCH 06/18] removed allow undo parameter --- .../richtext/codearea/CodeAreaDemoPane.java | 4 +- .../oracle/demo/richtext/editor/Actions.java | 8 +- .../demo/richtext/notebook/Actions.java | 4 +- .../demo/richtext/notebook/CellPane.java | 2 +- .../richtext/notebook/CodeCellTextModel.java | 4 +- .../richtext/notebook/TextCellTextModel.java | 4 +- .../oracle/demo/richtext/rta/ModelChoice.java | 2 +- .../richtext/rta/RichTextAreaDemoPane.java | 10 +-- .../demo/richtext/rta/UsageExamples.java | 13 ++-- .../richtext/RichTextAreaBehavior.java | 16 ++-- .../control/richtext/SegmentStyledInput.java | 4 + .../richtext/StyledTextModelHelper.java | 58 +++++++++++++++ .../control/richtext/UndoableChange.java | 6 +- .../scene/control/richtext/CodeArea.java | 6 +- .../scene/control/richtext/RichTextArea.java | 59 +++++++-------- .../richtext/model/BasicTextModel.java | 6 +- .../richtext/model/StyledTextModel.java | 47 ++++++++---- .../control/richtext/RichTextAreaTest.java | 73 +++++++------------ .../richtext/model/HTMLExportTest.java | 18 ++--- .../richtext/model/TestRichTextModel.java | 16 ++-- .../model/TestRichTextModelAttributes.java | 16 ++-- .../control/richtext/support/RTUtil.java | 2 +- 22 files changed, 215 insertions(+), 163 deletions(-) create mode 100644 modules/jfx.incubator.richtext/src/main/java/com/sun/jfx/incubator/scene/control/richtext/StyledTextModelHelper.java diff --git a/apps/samples/RichTextAreaDemo/src/com/oracle/demo/richtext/codearea/CodeAreaDemoPane.java b/apps/samples/RichTextAreaDemo/src/com/oracle/demo/richtext/codearea/CodeAreaDemoPane.java index 7164f2041af..b7567bff5aa 100644 --- a/apps/samples/RichTextAreaDemo/src/com/oracle/demo/richtext/codearea/CodeAreaDemoPane.java +++ b/apps/samples/RichTextAreaDemo/src/com/oracle/demo/richtext/codearea/CodeAreaDemoPane.java @@ -328,7 +328,7 @@ protected void apply(StyleAttribute attr, V val) { TextPos ca = control.getCaretPosition(); TextPos an = control.getAnchorPosition(); StyleAttributeMap a = StyleAttributeMap.builder().set(attr, val).build(); - control.applyStyle(ca, an, a, true); + control.applyStyle(ca, an, a); } protected void setLineSpacing(double x) { @@ -339,7 +339,7 @@ private void applyStyle(StyleAttribute a, V val) { TextPos ca = control.getCaretPosition(); TextPos an = control.getAnchorPosition(); StyleAttributeMap m = StyleAttributeMap.of(a, val); - control.applyStyle(ca, an, m, true); + control.applyStyle(ca, an, m); } // diff --git a/apps/samples/RichTextAreaDemo/src/com/oracle/demo/richtext/editor/Actions.java b/apps/samples/RichTextAreaDemo/src/com/oracle/demo/richtext/editor/Actions.java index 1b2d88ebe95..7814deb767c 100644 --- a/apps/samples/RichTextAreaDemo/src/com/oracle/demo/richtext/editor/Actions.java +++ b/apps/samples/RichTextAreaDemo/src/com/oracle/demo/richtext/editor/Actions.java @@ -223,7 +223,7 @@ private void toggle(StyleAttribute attr) { StyleAttributeMap a = control.getActiveStyleAttributeMap(); boolean on = !a.getBoolean(attr); a = StyleAttributeMap.builder().set(attr, on).build(); - control.applyStyle(start, end, a, true); + control.applyStyle(start, end, a); } private void apply(StyleAttribute attr, T value) { @@ -240,7 +240,7 @@ private void apply(StyleAttribute attr, T value) { StyleAttributeMap a = control.getActiveStyleAttributeMap(); a = StyleAttributeMap.builder().set(attr, value).build(); - control.applyStyle(start, end, a, true); + control.applyStyle(start, end, a); } // TODO need to bind selected item in the combo @@ -414,7 +414,7 @@ private void toggleStyle(StyleAttribute attr) { StyleAttributeMap a = control.getActiveStyleAttributeMap(); boolean on = !a.getBoolean(attr); a = StyleAttributeMap.builder().set(attr, on).build(); - control.applyStyle(start, end, a, true); + control.applyStyle(start, end, a); updateSourceStyles(); } @@ -435,7 +435,7 @@ public void setTextStyle(TextStyle st) { } StyleAttributeMap a = Styles.getStyleAttributeMap(st); - control.applyStyle(start, end, a, true); + control.applyStyle(start, end, a); updateSourceStyles(); } diff --git a/apps/samples/RichTextAreaDemo/src/com/oracle/demo/richtext/notebook/Actions.java b/apps/samples/RichTextAreaDemo/src/com/oracle/demo/richtext/notebook/Actions.java index 20905e43928..546fa56fc80 100644 --- a/apps/samples/RichTextAreaDemo/src/com/oracle/demo/richtext/notebook/Actions.java +++ b/apps/samples/RichTextAreaDemo/src/com/oracle/demo/richtext/notebook/Actions.java @@ -733,7 +733,7 @@ private void toggleStyle(StyleAttribute attr) { StyleAttributeMap a = c.getActiveStyleAttributeMap(); boolean on = !a.getBoolean(attr); a = StyleAttributeMap.builder().set(attr, on).build(); - c.applyStyle(start, end, a, true); + c.applyStyle(start, end, a); updateSourceStyles(); }); } @@ -756,7 +756,7 @@ public void setTextStyle(TextStyle st) { } StyleAttributeMap a = Styles.getStyleAttributeMap(st); - c.applyStyle(start, end, a, true); + c.applyStyle(start, end, a); updateSourceStyles(); }); } diff --git a/apps/samples/RichTextAreaDemo/src/com/oracle/demo/richtext/notebook/CellPane.java b/apps/samples/RichTextAreaDemo/src/com/oracle/demo/richtext/notebook/CellPane.java index 45820258d76..772830434ef 100644 --- a/apps/samples/RichTextAreaDemo/src/com/oracle/demo/richtext/notebook/CellPane.java +++ b/apps/samples/RichTextAreaDemo/src/com/oracle/demo/richtext/notebook/CellPane.java @@ -296,7 +296,7 @@ private List splitInTwo(TextPos p) { private void insert(RichTextArea src, TextPos start, TextPos end, RichTextArea tgt, TextPos pos) throws IOException { SegmentBuffer b = new SegmentBuffer(); src.getModel().export(start, end, b.getStyledOutput()); - tgt.insertText(pos, b.getStyledInput(), false); + tgt.insertText(pos, b.getStyledInput()); } public void setActive(boolean on) { diff --git a/apps/samples/RichTextAreaDemo/src/com/oracle/demo/richtext/notebook/CodeCellTextModel.java b/apps/samples/RichTextAreaDemo/src/com/oracle/demo/richtext/notebook/CodeCellTextModel.java index 3ccffb7d9f9..d60e17eddff 100644 --- a/apps/samples/RichTextAreaDemo/src/com/oracle/demo/richtext/notebook/CodeCellTextModel.java +++ b/apps/samples/RichTextAreaDemo/src/com/oracle/demo/richtext/notebook/CodeCellTextModel.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2024, Oracle and/or its affiliates. + * Copyright (c) 2023, 2025, Oracle and/or its affiliates. * All rights reserved. Use is subject to license terms. * * This file is available and licensed under the following license: @@ -66,7 +66,7 @@ public void setModified(boolean on) { } public void setText(String text) { - replace(null, TextPos.ZERO, TextPos.ZERO, text, false); + replace(null, TextPos.ZERO, TextPos.ZERO, text); setModified(false); } diff --git a/apps/samples/RichTextAreaDemo/src/com/oracle/demo/richtext/notebook/TextCellTextModel.java b/apps/samples/RichTextAreaDemo/src/com/oracle/demo/richtext/notebook/TextCellTextModel.java index aebee7fd5e5..a027d69dcc8 100644 --- a/apps/samples/RichTextAreaDemo/src/com/oracle/demo/richtext/notebook/TextCellTextModel.java +++ b/apps/samples/RichTextAreaDemo/src/com/oracle/demo/richtext/notebook/TextCellTextModel.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2024, Oracle and/or its affiliates. + * Copyright (c) 2023, 2025, Oracle and/or its affiliates. * All rights reserved. Use is subject to license terms. * * This file is available and licensed under the following license: @@ -64,7 +64,7 @@ public void setModified(boolean on) { } public void setText(String text) { - replace(null, TextPos.ZERO, TextPos.ZERO, text, false); + replace(null, TextPos.ZERO, TextPos.ZERO, text); setModified(false); } diff --git a/apps/samples/RichTextAreaDemo/src/com/oracle/demo/richtext/rta/ModelChoice.java b/apps/samples/RichTextAreaDemo/src/com/oracle/demo/richtext/rta/ModelChoice.java index 456bcc69748..18d8452c36c 100644 --- a/apps/samples/RichTextAreaDemo/src/com/oracle/demo/richtext/rta/ModelChoice.java +++ b/apps/samples/RichTextAreaDemo/src/com/oracle/demo/richtext/rta/ModelChoice.java @@ -225,6 +225,6 @@ private static StyledTextModel writingSystems() { // TODO add to StyledModel? private static void append(StyledTextModel m, String text, StyleAttributeMap style) { TextPos p = m.getDocumentEnd(); - m.replace(null, p, p, StyledInput.of(text, style), false); + m.replace(null, p, p, StyledInput.of(text, style)); } } diff --git a/apps/samples/RichTextAreaDemo/src/com/oracle/demo/richtext/rta/RichTextAreaDemoPane.java b/apps/samples/RichTextAreaDemo/src/com/oracle/demo/richtext/rta/RichTextAreaDemoPane.java index 9918f950f93..6fff39edaf2 100644 --- a/apps/samples/RichTextAreaDemo/src/com/oracle/demo/richtext/rta/RichTextAreaDemoPane.java +++ b/apps/samples/RichTextAreaDemo/src/com/oracle/demo/richtext/rta/RichTextAreaDemoPane.java @@ -235,8 +235,8 @@ public StyleHandlerRegistry getStyleHandlerRegistry() { appendButton.setOnAction((ev) -> { StyleAttributeMap heading = StyleAttributeMap.builder().setBold(true).setFontSize(24).build(); StyleAttributeMap plain = StyleAttributeMap.builder().setFontFamily("Monospaced").build(); - control.appendText("Heading\n", heading, false); - control.appendText("Plain monospaced text.\n", plain, false); + control.appendText("Heading\n", heading); + control.appendText("Plain monospaced text.\n", plain); }); Button insertButton = new Button("Insert"); @@ -244,8 +244,8 @@ public StyleHandlerRegistry getStyleHandlerRegistry() { insertButton.setOnAction((ev) -> { StyleAttributeMap heading = StyleAttributeMap.builder().setBold(true).setFontSize(24).build(); StyleAttributeMap plain = StyleAttributeMap.builder().setFontFamily("Monospaced").build(); - control.insertText(TextPos.ZERO, "Plain monospaced text.\n", plain, false); - control.insertText(TextPos.ZERO, "Heading\n", heading, false); + control.insertText(TextPos.ZERO, "Plain monospaced text.\n", plain); + control.insertText(TextPos.ZERO, "Heading\n", heading); }); Button replaceSkin = new Button("Replace Skin"); @@ -662,7 +662,7 @@ private void applyStyle(StyleAttribute a, T val) { TextPos ca = control.getCaretPosition(); TextPos an = control.getAnchorPosition(); StyleAttributeMap m = StyleAttributeMap.of(a, val); - control.applyStyle(ca, an, m, true); + control.applyStyle(ca, an, m); } void dumpAccessibilityAttributes() { diff --git a/apps/samples/RichTextAreaDemo/src/com/oracle/demo/richtext/rta/UsageExamples.java b/apps/samples/RichTextAreaDemo/src/com/oracle/demo/richtext/rta/UsageExamples.java index 997cee828e7..b7e1a131308 100644 --- a/apps/samples/RichTextAreaDemo/src/com/oracle/demo/richtext/rta/UsageExamples.java +++ b/apps/samples/RichTextAreaDemo/src/com/oracle/demo/richtext/rta/UsageExamples.java @@ -85,9 +85,10 @@ static RichTextArea appendStyledText() { RichTextArea textArea = new RichTextArea(); // build the content - textArea.appendText("RichTextArea\n", heading, false); - textArea.appendText("Example:\nText is ", StyleAttributeMap.EMPTY, false); - textArea.appendText("monospaced.\n", mono, false); + textArea.appendText("RichTextArea\n", heading); + textArea.appendText("Example:\nText is ", StyleAttributeMap.EMPTY); + textArea.appendText("monospaced.\n", mono); + textArea.clearUndoRedo(); return textArea; } @@ -95,7 +96,7 @@ void richTextAreaExample() { RichTextArea textArea = new RichTextArea(); // insert two paragraphs "A" and "B" StyleAttributeMap bold = StyleAttributeMap.builder().setBold(true).build(); - textArea.appendText("A\nB", bold, false); + textArea.appendText("A\nB", bold); } private static CodeArea codeAreaExample() { @@ -192,10 +193,10 @@ public void start(Stage stage) throws Exception { System.out.println("caret: " + c); }); t.getInputMap().register(KeyBinding.of(KeyCode.F1), () -> { - t.insertText(TextPos.ZERO, "F1", StyleAttributeMap.EMPTY, false); + t.insertText(TextPos.ZERO, "F1", StyleAttributeMap.EMPTY); }); t.getInputMap().register(KeyBinding.of(KeyCode.F2), () -> { - t.insertText(TextPos.ZERO, "\n", StyleAttributeMap.EMPTY, false); + t.insertText(TextPos.ZERO, "\n", StyleAttributeMap.EMPTY); }); t.getInputMap().register(KeyBinding.of(KeyCode.F3), () -> { t.clearSelection(); diff --git a/modules/jfx.incubator.richtext/src/main/java/com/sun/jfx/incubator/scene/control/richtext/RichTextAreaBehavior.java b/modules/jfx.incubator.richtext/src/main/java/com/sun/jfx/incubator/scene/control/richtext/RichTextAreaBehavior.java index b6116967b5a..ecf05ad1ff6 100644 --- a/modules/jfx.incubator.richtext/src/main/java/com/sun/jfx/incubator/scene/control/richtext/RichTextAreaBehavior.java +++ b/modules/jfx.incubator.richtext/src/main/java/com/sun/jfx/incubator/scene/control/richtext/RichTextAreaBehavior.java @@ -289,7 +289,7 @@ protected boolean handleTypedChar(String typed) { end = start; } - TextPos p = m.replace(vflow, start, end, typed, true); + TextPos p = m.replace(vflow, start, end, typed); moveCaret(p, false); clearPhantomX(); @@ -355,7 +355,7 @@ public void insertLineBreak() { return; } - TextPos pos = m.replace(vflow, start, end, StyledInput.of("\n"), true); + TextPos pos = m.replace(vflow, start, end, StyledInput.of("\n")); moveCaret(pos, false); clearPhantomX(); } @@ -943,7 +943,7 @@ public void backspace() { start = TextPos.ofLeading(ix, off); } - control.getModel().replace(vflow, start, p, StyledInput.EMPTY, true); + control.getModel().replace(vflow, start, p, StyledInput.EMPTY); moveCaret(start, false); clearPhantomX(); } @@ -959,7 +959,7 @@ public void delete() { TextPos start = control.getCaretPosition(); TextPos end = nextCharacterVisually(start, true); if (end != null) { - control.getModel().replace(vflow, start, end, StyledInput.EMPTY, true); + control.getModel().replace(vflow, start, end, StyledInput.EMPTY); moveCaret(start, false); clearPhantomX(); } @@ -985,7 +985,7 @@ public void deleteParagraph() { TextPos p0 = TextPos.ofLeading(ix0, 0); TextPos p1 = clamp(TextPos.ofLeading(ix1 + 1, 0)); RichTextArea control = getControl(); - control.getModel().replace(vflow, p0, p1, StyledInput.EMPTY, true); + control.getModel().replace(vflow, p0, p1, StyledInput.EMPTY); clearPhantomX(); moveCaret(p0, false); } @@ -1002,7 +1002,7 @@ protected void deleteSelection() { TextPos start = sel.getMin(); TextPos end = sel.getMax(); RichTextArea control = getControl(); - control.getModel().replace(vflow, start, end, StyledInput.EMPTY, true); + control.getModel().replace(vflow, start, end, StyledInput.EMPTY); clearPhantomX(); moveCaret(start, false); } @@ -1189,7 +1189,7 @@ private void pasteLocal(DataFormat f) { StyleAttributeMap a = control.getActiveStyleAttributeMap(); try (StyledInput in = h.createStyledInput(text, a)) { - TextPos p = m.replace(vflow, start, end, in, true); + TextPos p = m.replace(vflow, start, end, in); moveCaret(p, false); } catch (IOException e) { control.errorFeedback(); @@ -1591,7 +1591,7 @@ private void deleteIgnoreSelection(BiFunction ge if (p != null) { control.clearSelection(); clearPhantomX(); - p = control.replaceText(caret, p, "", true); + p = control.replaceText(caret, p, ""); control.select(p); } } diff --git a/modules/jfx.incubator.richtext/src/main/java/com/sun/jfx/incubator/scene/control/richtext/SegmentStyledInput.java b/modules/jfx.incubator.richtext/src/main/java/com/sun/jfx/incubator/scene/control/richtext/SegmentStyledInput.java index 9c238830032..56f0c8c14b8 100644 --- a/modules/jfx.incubator.richtext/src/main/java/com/sun/jfx/incubator/scene/control/richtext/SegmentStyledInput.java +++ b/modules/jfx.incubator.richtext/src/main/java/com/sun/jfx/incubator/scene/control/richtext/SegmentStyledInput.java @@ -27,6 +27,7 @@ import java.io.IOException; import java.util.List; +import java.util.Objects; import jfx.incubator.scene.control.richtext.model.StyledInput; import jfx.incubator.scene.control.richtext.model.StyledSegment; @@ -35,6 +36,9 @@ public class SegmentStyledInput implements StyledInput { private int index; public SegmentStyledInput(StyledSegment[] segments) { + if(segments == null) { + segments = new StyledSegment[0]; + } this.segments = segments; } diff --git a/modules/jfx.incubator.richtext/src/main/java/com/sun/jfx/incubator/scene/control/richtext/StyledTextModelHelper.java b/modules/jfx.incubator.richtext/src/main/java/com/sun/jfx/incubator/scene/control/richtext/StyledTextModelHelper.java new file mode 100644 index 00000000000..e0e2d360ed5 --- /dev/null +++ b/modules/jfx.incubator.richtext/src/main/java/com/sun/jfx/incubator/scene/control/richtext/StyledTextModelHelper.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.jfx.incubator.scene.control.richtext; + +import com.sun.javafx.util.Utils; +import jfx.incubator.scene.control.richtext.StyleResolver; +import jfx.incubator.scene.control.richtext.TextPos; +import jfx.incubator.scene.control.richtext.model.StyledInput; +import jfx.incubator.scene.control.richtext.model.StyledTextModel; + +/** + * Provides access to internal methods in StyledTextModel. + */ +public class StyledTextModelHelper { + public interface Accessor { + public TextPos replace(StyledTextModel m, StyleResolver r, TextPos start, TextPos end, StyledInput in, boolean allowUndo); + } + + static { + Utils.forceInit(StyledTextModel.class); + } + + private static Accessor accessor; + + public static void setAccessor(Accessor a) { + if (accessor != null) { + throw new IllegalStateException(); + } + accessor = a; + } + + public static TextPos replace(StyledTextModel m, StyleResolver r, TextPos start, TextPos end, StyledInput in, boolean allowUndo) { + return accessor.replace(m, r, start, end, in, allowUndo); + } +} diff --git a/modules/jfx.incubator.richtext/src/main/java/com/sun/jfx/incubator/scene/control/richtext/UndoableChange.java b/modules/jfx.incubator.richtext/src/main/java/com/sun/jfx/incubator/scene/control/richtext/UndoableChange.java index edf3c81c492..229cc475e33 100644 --- a/modules/jfx.incubator.richtext/src/main/java/com/sun/jfx/incubator/scene/control/richtext/UndoableChange.java +++ b/modules/jfx.incubator.richtext/src/main/java/com/sun/jfx/incubator/scene/control/richtext/UndoableChange.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -102,12 +102,12 @@ public void undo(StyleResolver resolver) throws IOException { // undo SegmentStyledInput in = new SegmentStyledInput(undo); - model.replace(resolver, start, endAfter, in, false); + StyledTextModelHelper.replace(model, resolver, start, endAfter, in, false); } public void redo(StyleResolver resolver) throws IOException { SegmentStyledInput in = new SegmentStyledInput(redo); - model.replace(resolver, start, endBefore, in, false); + StyledTextModelHelper.replace(model, resolver, start, endBefore, in, false); } public UndoableChange getPrev() { diff --git a/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/CodeArea.java b/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/CodeArea.java index 57ed291b35d..e6dd39b9993 100644 --- a/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/CodeArea.java +++ b/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/CodeArea.java @@ -77,7 +77,7 @@ * There are some differences that should be mentioned: *

      *
    • Model behavior: any direct changes to the styling, such as - * {@link #applyStyle(TextPos, TextPos, jfx.incubator.scene.control.richtext.model.StyleAttributeMap, boolean) applyStyle()}, + * {@link #applyStyle(TextPos, TextPos, jfx.incubator.scene.control.richtext.model.StyleAttributeMap) applyStyle()}, * will be ignored *
    • Line numbers: the {@code CodeArea} sets the {@link #leftDecoratorProperty()} to support the line numbers, * so applications should not set or bind that property. @@ -436,14 +436,14 @@ public final String getText() { } /** - * Replaces text in this CodeArea. + * Replaces text in this CodeArea. This method creates an undo/redo entry. *

      * The caret gets reset to the start of the document, selection gets cleared, and an undo event gets created. * @param text the text string */ public final void setText(String text) { TextPos end = getDocumentEnd(); - getModel().replace(null, TextPos.ZERO, end, text, true); + getModel().replace(null, TextPos.ZERO, end, text); } private CodeTextModel codeModel() { diff --git a/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/RichTextArea.java b/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/RichTextArea.java index adcb0c49cbb..9b15eeb261b 100644 --- a/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/RichTextArea.java +++ b/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/RichTextArea.java @@ -101,6 +101,7 @@ * textArea.appendText("RichTextArea\n", heading, false); * textArea.appendText("Example:\nText is ", StyleAttributeMap.EMPTY, false); * textArea.appendText("monospaced.\n", mono, false); + * textArea.clearUndoRedo(); * } * Which results in the following visual representation: *

      @@ -1044,21 +1045,20 @@ public StyleableProperty getStyleableProperty(RichTextArea t) { /** * Appends the styled text to the end of the document. Any embedded {@code "\n"} or {@code "\r\n"} - * sequences result in a new paragraph being added. + * sequences result in a new paragraph being added. This method creates an undo/redo entry. *

      * It is up to the model to decide whether to accept all, some, or none of the * {@link jfx.incubator.scene.control.richtext.model.StyleAttribute StyleAttribute}s. * * @param text the text to append * @param attrs the style attributes - * @param allowUndo when true, creates an undo-redo entry * @return the text position at the end of the appended text, or null if editing is disabled * @throws NullPointerException if the model is {@code null} * @throws UnsupportedOperationException if the model is not {@link StyledTextModel#isWritable() writable} */ - public final TextPos appendText(String text, StyleAttributeMap attrs, boolean allowUndo) { + public final TextPos appendText(String text, StyleAttributeMap attrs) { TextPos p = getDocumentEnd(); - return insertText(p, text, attrs, allowUndo); + return insertText(p, text, attrs); } /** @@ -1074,22 +1074,21 @@ public final TextPos appendText(String text, StyleAttributeMap attrs, boolean al * @throws UnsupportedOperationException if the model is not {@link StyledTextModel#isWritable() writable} */ public final TextPos appendText(String text) { - return appendText(text, StyleAttributeMap.EMPTY, true); + return appendText(text, StyleAttributeMap.EMPTY); } /** * Appends the styled content to the end of the document. Any embedded {@code "\n"} or {@code "\r\n"} - * sequences result in a new paragraph being added. + * sequences result in a new paragraph being added. This method creates an undo/redo entry. * * @param in the input stream - * @param allowUndo when true, creates an undo-redo entry * @return the text position at the end of the appended text, or null if editing is disabled * @throws NullPointerException if the model is {@code null} * @throws UnsupportedOperationException if the model is not {@link StyledTextModel#isWritable() writable} */ - public final TextPos appendText(StyledInput in, boolean allowUndo) { + public final TextPos appendText(StyledInput in) { TextPos p = getDocumentEnd(); - return insertText(p, in, allowUndo); + return insertText(p, in); } /** @@ -1101,13 +1100,12 @@ public final TextPos appendText(StyledInput in, boolean allowUndo) { * @param start the start of text range * @param end the end of text range * @param attrs the style attributes to apply - * @param allowUndo when true, creates an undo-redo entry * @throws NullPointerException if the model is {@code null} * @throws UnsupportedOperationException if the model is not {@link StyledTextModel#isWritable() writable} */ - public void applyStyle(TextPos start, TextPos end, StyleAttributeMap attrs, boolean allowUndo) { + public void applyStyle(TextPos start, TextPos end, StyleAttributeMap attrs) { StyledTextModel m = getModel(); - m.applyStyle(start, end, attrs, true, allowUndo); + m.applyStyle(start, end, attrs, true); } /** @@ -1126,14 +1124,14 @@ public void backspace() { } /** - * Clears the document, creating an undo entry. + * Clears the document, creating an undo/redo entry. * * @throws NullPointerException if the model is {@code null} * @throws UnsupportedOperationException if the model is not {@link StyledTextModel#isWritable() writable} */ public final void clear() { TextPos end = getDocumentEnd(); - replaceText(TextPos.ZERO, end, StyledInput.EMPTY, true); + replaceText(TextPos.ZERO, end, StyledInput.EMPTY); } /** @@ -1474,33 +1472,31 @@ public void insertTab() { /** * Inserts the styled text at the specified position. Any embedded {@code "\n"} or {@code "\r\n"} - * sequences result in a new paragraph being added. + * sequences result in a new paragraph being added. This method creates an undo/redo entry. * * @param pos the insert position * @param text the text to inser * @param attrs the style attributes - * @param allowUndo when true, creates an undo-redo entry * @return the text position at the end of the appended text, or null if editing is disabled * @throws NullPointerException if the model is {@code null} * @throws UnsupportedOperationException if the model is not {@link StyledTextModel#isWritable() writable} */ - public final TextPos insertText(TextPos pos, String text, StyleAttributeMap attrs, boolean allowUndo) { + public final TextPos insertText(TextPos pos, String text, StyleAttributeMap attrs) { StyledInput in = StyledInput.of(text, attrs); - return replaceText(pos, pos, in, allowUndo); + return replaceText(pos, pos, in); } /** - * Inserts the styled content at the specified position. + * Inserts the styled content at the specified position. This method creates an undo/redo entry. * * @param pos the insert position * @param in the input stream - * @param allowUndo when true, creates an undo-redo entry * @return the text position at the end of the appended text, or null if editing is disabled * @throws NullPointerException if the model is {@code null} * @throws UnsupportedOperationException if the model is not {@link StyledTextModel#isWritable() writable} */ - public final TextPos insertText(TextPos pos, StyledInput in, boolean allowUndo) { - return replaceText(pos, pos, in, allowUndo); + public final TextPos insertText(TextPos pos, StyledInput in) { + return replaceText(pos, pos, in); } /** @@ -1824,35 +1820,33 @@ public void redo() { } /** - * Replaces the specified range with the new text. + * Replaces the specified range with the new text. This method creates an undo entry. * * @param start the start text position * @param end the end text position * @param text the input text - * @param allowUndo when true, creates an undo-redo entry * @return the new caret position at the end of inserted text, or null if the change cannot be made * @throws NullPointerException if the model is {@code null} * @throws UnsupportedOperationException if the model is not {@link StyledTextModel#isWritable() writable} */ - public final TextPos replaceText(TextPos start, TextPos end, String text, boolean allowUndo) { + public final TextPos replaceText(TextPos start, TextPos end, String text) { StyledTextModel m = getModel(); - return m.replace(vflow(), start, end, text, allowUndo); + return m.replace(vflow(), start, end, text); } /** - * Replaces the specified range with the new input. + * Replaces the specified range with the new input. This method creates an undo entry. * * @param start the start text position * @param end the end text position * @param in the input stream - * @param allowUndo when true, creates an undo-redo entry * @return the new caret position at the end of inserted text, or null if the change cannot be made * @throws NullPointerException if the model is {@code null} * @throws UnsupportedOperationException if the model is not {@link StyledTextModel#isWritable() writable} */ - public final TextPos replaceText(TextPos start, TextPos end, StyledInput in, boolean allowUndo) { + public final TextPos replaceText(TextPos start, TextPos end, StyledInput in) { StyledTextModel m = getModel(); - return m.replace(vflow(), start, end, in, allowUndo); + return m.replace(vflow(), start, end, in); } /** @@ -2162,13 +2156,12 @@ public void selectWordRight() { * @param start the start of text range * @param end the end of text range * @param attrs the style attributes to set - * @param allowUndo when true, creates an undo-redo entry * @throws NullPointerException if the model is {@code null} * @throws UnsupportedOperationException if the model is not {@link StyledTextModel#isWritable() writable} */ - public final void setStyle(TextPos start, TextPos end, StyleAttributeMap attrs, boolean allowUndo) { + public final void setStyle(TextPos start, TextPos end, StyleAttributeMap attrs) { StyledTextModel m = getModel(); - m.applyStyle(start, end, attrs, false, allowUndo); + m.applyStyle(start, end, attrs, false); } /** diff --git a/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/model/BasicTextModel.java b/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/model/BasicTextModel.java index 31616714671..476fbdae999 100644 --- a/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/model/BasicTextModel.java +++ b/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/model/BasicTextModel.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -123,7 +123,7 @@ public BasicTextModel() { /** * Inserts text at the specified position. - * This is a convenience shortcut for {@link #replace(StyleResolver, TextPos, TextPos, String, boolean)}. + * This is a convenience shortcut for {@link #replace(StyleResolver, TextPos, TextPos, String)}. * * @param p the insertion position * @param text the text to insert @@ -131,7 +131,7 @@ public BasicTextModel() { * @throws UnsupportedOperationException if the model is not {@link StyledTextModel#isWritable() writable} */ public void insertText(TextPos p, String text) { - replace(null, p, p, text, false); + replace(null, p, p, text); } @Override diff --git a/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/model/StyledTextModel.java b/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/model/StyledTextModel.java index 3722ae570b4..9fe5e10b18d 100644 --- a/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/model/StyledTextModel.java +++ b/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/model/StyledTextModel.java @@ -46,6 +46,7 @@ import com.sun.javafx.ModuleUtil; import com.sun.jfx.incubator.scene.control.richtext.Markers; import com.sun.jfx.incubator.scene.control.richtext.StyleAttributeMapHelper; +import com.sun.jfx.incubator.scene.control.richtext.StyledTextModelHelper; import com.sun.jfx.incubator.scene.control.richtext.UndoableChange; import com.sun.jfx.incubator.scene.control.richtext.util.RichUtils; import jfx.incubator.scene.control.richtext.Marker; @@ -68,9 +69,9 @@ *

      Editing

      * The model supports editing when {@link #isWritable()} returns {@code true}. * Three methods participate in modification of the content: - * {@link #replace(StyleResolver, TextPos, TextPos, String, boolean)}, - * {@link #replace(StyleResolver, TextPos, TextPos, StyledInput, boolean)}, - * {@link #applyStyle(TextPos, TextPos, StyleAttributeMap, boolean, boolean)}. + * {@link #replace(StyleResolver, TextPos, TextPos, String)}, + * {@link #replace(StyleResolver, TextPos, TextPos, StyledInput)}, + * {@link #applyStyle(TextPos, TextPos, StyleAttributeMap, boolean)}. * These methods decompose the main modification into operations with individual paragraphs * and delegate these to subclasses. *

      @@ -248,9 +249,9 @@ public interface Listener { * clipboard). *

      * The methods that utilize the filtering are: - * {@link #applyStyle(TextPos, TextPos, StyleAttributeMap, boolean, boolean)}, - * {@link #replace(StyleResolver, TextPos, TextPos, StyledInput, boolean)}, and - * {@link #replace(StyleResolver, TextPos, TextPos, String, boolean)}. + * {@link #applyStyle(TextPos, TextPos, StyleAttributeMap, boolean)}, + * {@link #replace(StyleResolver, TextPos, TextPos, StyledInput)}, and + * {@link #replace(StyleResolver, TextPos, TextPos, String)}. *

      * When this method returns {@code null}, no filtering is performed. *

      @@ -279,7 +280,10 @@ public int compareTo(FHPriority x) { private record FHKey(DataFormat format, boolean forExport) { } - static { ModuleUtil.incubatorWarning(); } + static { + ModuleUtil.incubatorWarning(); + initAccessor(); + } // TODO should it hold WeakReferences? private final CopyOnWriteArrayList listeners = new CopyOnWriteArrayList(); @@ -301,6 +305,15 @@ public StyledTextModel() { registerDataFormatHandler(PlainTextFormatHandler.getInstance(), true, false, 0); } + private static void initAccessor() { + StyledTextModelHelper.setAccessor(new StyledTextModelHelper.Accessor() { + @Override + public TextPos replace(StyledTextModel m, StyleResolver r, TextPos start, TextPos end, StyledInput in, boolean allowUndo) { + return m.replace(r, start, end, in, allowUndo); + } + }); + } + /** * Adds a {@link Listener} to this model. * @@ -621,17 +634,16 @@ public final TextPos getEndOfParagraphTextPos(int index) { * @param start start text position * @param end end text position * @param text text string to insert - * @param allowUndo when true, creates an undo-redo entry * @return the text position at the end of the inserted text, or null if the model is read only * @throws UnsupportedOperationException if the model is not {@link #isWritable() writable} */ - public final TextPos replace(StyleResolver resolver, TextPos start, TextPos end, String text, boolean allowUndo) { + public final TextPos replace(StyleResolver resolver, TextPos start, TextPos end, String text) { checkWritable(); // TODO pick the lowest from start,end. Possibly add (end) argument to getStyleAttributes? StyleAttributeMap a = getStyleAttributeMap(resolver, start); StyledInput in = StyledInput.of(text, a); - return replace(resolver, start, end, in, allowUndo); + return replace(resolver, start, end, in); } /** @@ -640,16 +652,21 @@ public final TextPos replace(StyleResolver resolver, TextPos start, TextPos end, * inserted in the beginning of the document, the style is taken from the following text segment. *

      * After the model applies the requested changes, an event is sent to all the registered listeners. + * This method creates an undo/redo entry. * * @param resolver the StyleResolver to use, can be null * @param start the start text position * @param end the end text position * @param input the input content stream - * @param allowUndo when true, creates an undo-redo entry * @return the text position at the end of the inserted text, or null if the model is read only * @throws UnsupportedOperationException if the model is not {@link #isWritable() writable} */ - public final TextPos replace(StyleResolver resolver, TextPos start, TextPos end, StyledInput input, boolean allowUndo) { + public final TextPos replace(StyleResolver resolver, TextPos start, TextPos end, StyledInput input) { + return replace(resolver, start, end, input, true); + } + + // only UndoableChange is allowed to disable undo/redo records + private final TextPos replace(StyleResolver resolver, TextPos start, TextPos end, StyledInput input, boolean allowUndo) { checkWritable(); // TODO clamp to document boundaries @@ -733,10 +750,9 @@ public final TextPos replace(StyleResolver resolver, TextPos start, TextPos end, * @param end the end of text range * @param attrs the style attributes to set * @param mergeAttributes whether to merge or replace the attributes - * @param allowUndo when true, creates an undo-redo entry * @throws UnsupportedOperationException if the model is not {@link #isWritable() writable} */ - public final void applyStyle(TextPos start, TextPos end, StyleAttributeMap attrs, boolean mergeAttributes, boolean allowUndo) { + public final void applyStyle(TextPos start, TextPos end, StyleAttributeMap attrs, boolean mergeAttributes) { checkWritable(); if (start.compareTo(end) > 0) { @@ -980,7 +996,8 @@ public final void read(StyleResolver r, DataFormat f, InputStream input) throws } String text = RichUtils.readString(input); StyledInput in = h.createStyledInput(text, null); - replace(r, TextPos.ZERO, end, in, false); + replace(r, TextPos.ZERO, end, in); + clearUndoRedo(); } /** diff --git a/modules/jfx.incubator.richtext/src/test/java/test/jfx/incubator/scene/control/richtext/RichTextAreaTest.java b/modules/jfx.incubator.richtext/src/test/java/test/jfx/incubator/scene/control/richtext/RichTextAreaTest.java index 0c6057f559f..fa2cfdaed22 100644 --- a/modules/jfx.incubator.richtext/src/test/java/test/jfx/incubator/scene/control/richtext/RichTextAreaTest.java +++ b/modules/jfx.incubator.richtext/src/test/java/test/jfx/incubator/scene/control/richtext/RichTextAreaTest.java @@ -75,6 +75,7 @@ public class RichTextAreaTest { private RichTextArea control; private static final StyleAttributeMap BOLD = StyleAttributeMap.builder().setBold(true).build(); private static final StyleAttributeMap ITALIC = StyleAttributeMap.builder().setItalic(true).build(); + private static final String NL = System.getProperty("line.separator"); @BeforeEach public void beforeEach() { @@ -268,7 +269,7 @@ public void appendText() { @Test public void appendTextWithStyles() { - TextPos p = control.appendText("a", BOLD, true); + TextPos p = control.appendText("a", BOLD); assertEquals(TextPos.ofLeading(0, 1), p); control.select(p); assertEquals(BOLD, control.getActiveStyleAttributeMap()); @@ -276,44 +277,30 @@ public void appendTextWithStyles() { // undo control.undo(); assertEquals("", text()); - // no undo - control.appendText("b", BOLD, false); - control.undo(); - assertEquals("b", text()); } @Test public void appendTextFromStyledInput() { TestStyledInput in = TestStyledInput.plainText("a\nb"); - TextPos p = control.appendText(in, true); + TextPos p = control.appendText(in); assertEquals(TextPos.ofLeading(1, 1), p); - assertEquals("a\nb", text()); + assertEquals("a" + NL + "b", text()); // undo control.undo(); assertEquals("", text()); - // no undo - control.appendText(TestStyledInput.plainText("dd"), false); - control.undo(); - assertEquals("dd", text()); } @Test public void applyStyle() { TestStyledInput in = TestStyledInput.plainText("a\nbbb"); - TextPos p = control.appendText(in, true); - control.applyStyle(TextPos.ZERO, TextPos.ofLeading(1, 3), BOLD, true); + TextPos p = control.appendText(in); + control.applyStyle(TextPos.ZERO, TextPos.ofLeading(1, 3), BOLD); assertEquals(TextPos.ofLeading(1, 3), p); control.select(TextPos.ofLeading(1, 0)); assertEquals(BOLD, control.getActiveStyleAttributeMap()); - // allow undo + // undo control.undo(); assertEquals(StyleAttributeMap.EMPTY, control.getActiveStyleAttributeMap()); - // disallow undo - control.applyStyle(TextPos.ZERO, TextPos.ofLeading(1, 3), BOLD, false); - control.select(TextPos.ofLeading(1, 0)); - control.undo(); // undo previous BOLD - control.undo(); // undo initial appendText - assertEquals("", text()); } @Test @@ -426,8 +413,8 @@ public void executeDefault() { @Test public void getActiveStyleAttributeMap() { - control.appendText("1234", BOLD, false); - control.appendText("5678", StyleAttributeMap.EMPTY, false); + control.appendText("1234", BOLD); + control.appendText("5678", StyleAttributeMap.EMPTY); control.select(TextPos.ofLeading(0, 2)); StyleAttributeMap a = control.getActiveStyleAttributeMap(); @@ -508,9 +495,9 @@ public void insertLineBreak() { @Test public void insertTextWithStyles() { - TextPos p = control.appendText("a", BOLD, true); + TextPos p = control.appendText("a", BOLD); assertEquals(TextPos.ofLeading(0, 1), p); - p = control.insertText(TextPos.ZERO, "b", ITALIC, true); + p = control.insertText(TextPos.ZERO, "b", ITALIC); assertEquals(TextPos.ofLeading(0, 1), p); control.select(p); assertEquals(ITALIC, control.getActiveStyleAttributeMap()); @@ -518,20 +505,14 @@ public void insertTextWithStyles() { // undo control.undo(); assertEquals("a", text()); - // no undo - control.insertText(TextPos.ZERO, "ccc", BOLD, false); - assertEquals("ccca", text()); - control.undo(); // FIX non-undoable change messed up the state of the previous undo entries - // we cannot disable undo. the application must use clearUndoRedo() instead - assertEquals("cca", text()); // FIX } @Test public void insertTextFromStyledInput() { TestStyledInput in = TestStyledInput.plainText("a\nb"); - TextPos p = control.appendText(in, true); + TextPos p = control.appendText(in); assertEquals(TextPos.ofLeading(1, 1), p); - assertEquals("a\nb", text()); + assertEquals("a" + NL + "b", text()); // undo control.undo(); assertEquals("", text()); @@ -554,7 +535,7 @@ public void isUndoable() { @Test public void modelChangeClearsSelection() { - control.insertText(TextPos.ZERO, "1234", null, false); + control.insertText(TextPos.ZERO, "1234", null); control.selectAll(); SelectionSegment sel = control.getSelection(); assertFalse(sel.isCollapsed()); @@ -604,7 +585,7 @@ public void pastePlainText() { @Test public void read() throws Exception { control.appendText("1 bold"); - control.applyStyle(TextPos.ofLeading(0, 2), TextPos.ofLeading(0, 6), BOLD, true); + control.applyStyle(TextPos.ofLeading(0, 2), TextPos.ofLeading(0, 6), BOLD); ByteArrayOutputStream out = new ByteArrayOutputStream(); control.write(out); byte[] b = out.toByteArray(); @@ -621,17 +602,21 @@ public void read() throws Exception { public void readDataFormat() throws Exception { DataFormat fmt = DataFormat.PLAIN_TEXT; control.appendText("1 bold"); - control.applyStyle(TextPos.ofLeading(0, 2), TextPos.ofLeading(0, 6), BOLD, true); + control.applyStyle(TextPos.ofLeading(0, 2), TextPos.ofLeading(0, 6), BOLD); ByteArrayOutputStream out = new ByteArrayOutputStream(); control.write(fmt, out); byte[] b = out.toByteArray(); String text1 = text(); control = new RichTextArea(); + control.appendText("should not see me"); ByteArrayInputStream in = new ByteArrayInputStream(b); control.read(fmt, in); String text2 = text(); assertEquals(text1, text2); + // read clears undo buffer + control.undo(); + assertEquals(text1, text2); } @Test @@ -658,7 +643,7 @@ public void redo() { @Test public void replaceText() { control.appendText("1234"); - control.replaceText(TextPos.ofLeading(0, 1), TextPos.ofLeading(0, 3), "-", false); + control.replaceText(TextPos.ofLeading(0, 1), TextPos.ofLeading(0, 3), "-"); assertEquals("1-4", text()); } @@ -666,27 +651,21 @@ public void replaceText() { public void replaceTextFromStyledInput() { TestStyledInput in = TestStyledInput.plainText("-"); control.appendText("1234"); - control.replaceText(TextPos.ofLeading(0, 1), TextPos.ofLeading(0, 3), in, false); + control.replaceText(TextPos.ofLeading(0, 1), TextPos.ofLeading(0, 3), in); assertEquals("1-4", text()); } @Test public void setStyle() { TestStyledInput in = TestStyledInput.plainText("a\nbbb"); - TextPos p = control.appendText(in, true); - control.setStyle(TextPos.ZERO, TextPos.ofLeading(1, 3), BOLD, true); + TextPos p = control.appendText(in); + control.setStyle(TextPos.ZERO, TextPos.ofLeading(1, 3), BOLD); assertEquals(TextPos.ofLeading(1, 3), p); control.select(TextPos.ofLeading(1, 0)); assertEquals(BOLD, control.getActiveStyleAttributeMap()); // allow undo control.undo(); assertEquals(StyleAttributeMap.EMPTY, control.getActiveStyleAttributeMap()); - // disallow undo - control.setStyle(TextPos.ZERO, TextPos.ofLeading(1, 3), BOLD, false); - control.select(TextPos.ofLeading(1, 0)); - control.undo(); // undo previous BOLD - control.undo(); // undo initial appendText - assertEquals("", text()); } @Test @@ -741,7 +720,7 @@ public void undo() { @Test public void write() throws Exception { control.appendText("1 bold"); - control.applyStyle(TextPos.ofLeading(0, 2), TextPos.ofLeading(0, 6), BOLD, true); + control.applyStyle(TextPos.ofLeading(0, 2), TextPos.ofLeading(0, 6), BOLD); ByteArrayOutputStream out = new ByteArrayOutputStream(); control.write(out); byte[] b = out.toByteArray(); @@ -752,7 +731,7 @@ public void write() throws Exception { public void writeDataFormat() throws Exception { DataFormat fmt = DataFormat.PLAIN_TEXT; control.appendText("1 bold"); - control.applyStyle(TextPos.ofLeading(0, 2), TextPos.ofLeading(0, 6), BOLD, true); + control.applyStyle(TextPos.ofLeading(0, 2), TextPos.ofLeading(0, 6), BOLD); ByteArrayOutputStream out = new ByteArrayOutputStream(); control.write(fmt, out); byte[] b = out.toByteArray(); diff --git a/modules/jfx.incubator.richtext/src/test/java/test/jfx/incubator/scene/control/richtext/model/HTMLExportTest.java b/modules/jfx.incubator.richtext/src/test/java/test/jfx/incubator/scene/control/richtext/model/HTMLExportTest.java index f3d9dca5e31..fdaab569a0e 100644 --- a/modules/jfx.incubator.richtext/src/test/java/test/jfx/incubator/scene/control/richtext/model/HTMLExportTest.java +++ b/modules/jfx.incubator.richtext/src/test/java/test/jfx/incubator/scene/control/richtext/model/HTMLExportTest.java @@ -51,7 +51,7 @@ public void beforeEach() { @Test public void characterAttributes() throws Exception { - model.replace(null, TextPos.ZERO, TextPos.ZERO, "111\n", false); + model.replace(null, TextPos.ZERO, TextPos.ZERO, "111\n"); check( """ @@ -65,24 +65,24 @@ public void characterAttributes() throws Exception { """); // bold - model.applyStyle(TextPos.ZERO, TextPos.ofLeading(0, 1), mk(StyleAttributeMap.BOLD, Boolean.TRUE), false, false); + model.applyStyle(TextPos.ZERO, TextPos.ofLeading(0, 1), mk(StyleAttributeMap.BOLD, Boolean.TRUE), false); checkContains("111

      "); - model.applyStyle(TextPos.ZERO, TextPos.ofLeading(0, 1), mk(StyleAttributeMap.BOLD, Boolean.FALSE), false, false); + model.applyStyle(TextPos.ZERO, TextPos.ofLeading(0, 1), mk(StyleAttributeMap.BOLD, Boolean.FALSE), false); checkContains("111

      "); // italic - model.applyStyle(TextPos.ZERO, TextPos.ofLeading(0, 1), mk(StyleAttributeMap.ITALIC, Boolean.TRUE), false, false); + model.applyStyle(TextPos.ZERO, TextPos.ofLeading(0, 1), mk(StyleAttributeMap.ITALIC, Boolean.TRUE), false); checkContains("111

      "); - model.applyStyle(TextPos.ZERO, TextPos.ofLeading(0, 1), mk(StyleAttributeMap.ITALIC, Boolean.FALSE), false, false); + model.applyStyle(TextPos.ZERO, TextPos.ofLeading(0, 1), mk(StyleAttributeMap.ITALIC, Boolean.FALSE), false); checkContains("111

      "); // strikethrough - model.applyStyle(TextPos.ZERO, TextPos.ofLeading(0, 1), mk(StyleAttributeMap.STRIKE_THROUGH, Boolean.TRUE), false, false); + model.applyStyle(TextPos.ZERO, TextPos.ofLeading(0, 1), mk(StyleAttributeMap.STRIKE_THROUGH, Boolean.TRUE), false); checkContains("111

      "); - model.applyStyle(TextPos.ZERO, TextPos.ofLeading(0, 1), mk(StyleAttributeMap.STRIKE_THROUGH, Boolean.FALSE), false, false); + model.applyStyle(TextPos.ZERO, TextPos.ofLeading(0, 1), mk(StyleAttributeMap.STRIKE_THROUGH, Boolean.FALSE), false); checkContains("111

      "); // underline - model.applyStyle(TextPos.ZERO, TextPos.ofLeading(0, 1), mk(StyleAttributeMap.UNDERLINE, Boolean.TRUE), false, false); + model.applyStyle(TextPos.ZERO, TextPos.ofLeading(0, 1), mk(StyleAttributeMap.UNDERLINE, Boolean.TRUE), false); checkContains("111

      "); - model.applyStyle(TextPos.ZERO, TextPos.ofLeading(0, 1), mk(StyleAttributeMap.UNDERLINE, Boolean.FALSE), false, false); + model.applyStyle(TextPos.ZERO, TextPos.ofLeading(0, 1), mk(StyleAttributeMap.UNDERLINE, Boolean.FALSE), false); checkContains("111

      "); } diff --git a/modules/jfx.incubator.richtext/src/test/java/test/jfx/incubator/scene/control/richtext/model/TestRichTextModel.java b/modules/jfx.incubator.richtext/src/test/java/test/jfx/incubator/scene/control/richtext/model/TestRichTextModel.java index 80fe8ad2553..92e97ed5559 100644 --- a/modules/jfx.incubator.richtext/src/test/java/test/jfx/incubator/scene/control/richtext/model/TestRichTextModel.java +++ b/modules/jfx.incubator.richtext/src/test/java/test/jfx/incubator/scene/control/richtext/model/TestRichTextModel.java @@ -51,7 +51,7 @@ public class TestRichTextModel { @Test public void insertLineBreak() { test(List.of(p()), List.of(p(), p()), (m) -> { - m.replace(null, TextPos.ZERO, TextPos.ZERO, "\n", true); + m.replace(null, TextPos.ZERO, TextPos.ZERO, "\n"); }); } @@ -66,7 +66,7 @@ public void delete() { p("", BOLD) ), (m) -> { - m.replace(null, t(0, 0), t(0, 6), "", false); + m.replace(null, t(0, 0), t(0, 6), ""); } ); @@ -79,7 +79,7 @@ public void delete() { p("", BOLD) ), (m) -> { - m.replace(null, t(0, 0), t(0, 2), "", false); + m.replace(null, t(0, 0), t(0, 2), ""); } ); @@ -93,7 +93,7 @@ public void delete() { p("aabb", BOLD) ), (m) -> { - m.replace(null, t(0, 2), t(1, 0), "", false); + m.replace(null, t(0, 2), t(1, 0), ""); } ); @@ -109,7 +109,7 @@ public void delete() { p("bb", BOLD) ), (m) -> { - m.replace(null, t(2, 0), t(1, 0), "", false); + m.replace(null, t(2, 0), t(1, 0), ""); } ); } @@ -146,7 +146,7 @@ protected void test(List initial, List expected, C for (RichParagraph par : initial) { if (newline) { TextPos p = m.getDocumentEnd(); - m.replace(null, p, p, "\n", false); + m.replace(null, p, p, "\n"); } else { newline = true; } @@ -158,7 +158,7 @@ protected void test(List initial, List expected, C StyledSegment[] segments = ss.toArray(StyledSegment[]::new); StyledInput in = new SegmentStyledInput(segments); TextPos p = m.getDocumentEnd(); - m.replace(null, p, p, in, false); + m.replace(null, p, p, in); } // test operation @@ -256,7 +256,7 @@ private static String checkEquals(List expected, List { - m.replace(null, TextPos.ZERO, TextPos.ZERO, "\n", false); + m.replace(null, TextPos.ZERO, TextPos.ZERO, "\n"); }, "{}{!}\n{}{!}" ); @@ -58,7 +58,7 @@ public void testInsertLineBreak() { t( null, (m) -> { - m.replace(null, TextPos.ZERO, TextPos.ZERO, "\n\n", false); + m.replace(null, TextPos.ZERO, TextPos.ZERO, "\n\n"); }, "{}{!}\n{}{!}\n{}{!}" ); @@ -67,7 +67,7 @@ public void testInsertLineBreak() { t( "{b}{i}0123{!}", (m) -> { - m.replace(null, TextPos.ZERO, TextPos.ZERO, "\n", false); + m.replace(null, TextPos.ZERO, TextPos.ZERO, "\n"); }, "{!}\n{b}{i}0123{!}" ); @@ -76,7 +76,7 @@ public void testInsertLineBreak() { t( "{b}{i}0123{!}", (m) -> { - m.replace(null, TextPos.ofLeading(0, 2), TextPos.ofLeading(0, 2), "\n", false); + m.replace(null, TextPos.ofLeading(0, 2), TextPos.ofLeading(0, 2), "\n"); }, "{b}{i}01{!}\n{0}23{!}" ); @@ -85,7 +85,7 @@ public void testInsertLineBreak() { t( "{b}{i}0123{!}", (m) -> { - m.replace(null, TextPos.ofLeading(0, 4), TextPos.ofLeading(0, 4), "\n", false); + m.replace(null, TextPos.ofLeading(0, 4), TextPos.ofLeading(0, 4), "\n"); }, "{b}{i}0123{!}\n{!}" ); @@ -96,7 +96,7 @@ public void testDeleteParagraphStart() { t( "{fs|24.0}{tc|808080}aaaaa: {fs|24.0}bbbbb{!}", (m) -> { - m.replace(null, p(0, 13), p(0, 0), "", false); + m.replace(null, p(0, 13), p(0, 0), ""); }, "{!}" ); @@ -107,7 +107,7 @@ public void testZeroWidthSegment() { t( "{fs|24.0}{tc|808080}a: {fs|24.0}b{!}\n{0}c: {1}d{!}", (m) -> { - m.replace(null, p(0, 4), p(1, 0), "", false); + m.replace(null, p(0, 4), p(1, 0), ""); }, "{fs|24.0}{tc|808080}a: {fs|24.0}b{0}c: {1}d{!}" ); @@ -125,7 +125,7 @@ private void t(String initial, Consumer op, String expected) { // set initial text if (initial != null) { StyledInput in = h.createStyledInput(initial, null); - TextPos end = m.replace(null, TextPos.ZERO, TextPos.ZERO, in, false); + TextPos end = m.replace(null, TextPos.ZERO, TextPos.ZERO, in); // check initial text StringWriter wr = new StringWriter(); StyledOutput out = RichTextFormatHandlerHelper.createStyledOutput(h, null, wr); diff --git a/modules/jfx.incubator.richtext/src/test/java/test/jfx/incubator/scene/control/richtext/support/RTUtil.java b/modules/jfx.incubator.richtext/src/test/java/test/jfx/incubator/scene/control/richtext/support/RTUtil.java index c5ecabea72e..645915b1c65 100644 --- a/modules/jfx.incubator.richtext/src/test/java/test/jfx/incubator/scene/control/richtext/support/RTUtil.java +++ b/modules/jfx.incubator.richtext/src/test/java/test/jfx/incubator/scene/control/richtext/support/RTUtil.java @@ -51,7 +51,7 @@ private RTUtil() { */ public static void setText(RichTextArea control, String text) { TextPos end = control.getDocumentEnd(); - control.replaceText(TextPos.ZERO, end, text, false); + control.replaceText(TextPos.ZERO, end, text); } /** From a8fe58d068289f3761fc3df3054d338aef21cf3a Mon Sep 17 00:00:00 2001 From: Andy Goryachev Date: Tue, 21 Oct 2025 13:43:08 -0700 Subject: [PATCH 07/18] cleanup --- .../com/oracle/demo/richtext/codearea/CodeAreaDemoPane.java | 2 +- .../src/com/oracle/demo/richtext/notebook/Actions.java | 2 +- .../com/oracle/demo/richtext/rta/RichTextAreaDemoPane.java | 2 +- .../incubator/scene/control/richtext/SegmentStyledInput.java | 4 ---- .../scene/control/richtext/model/StyledTextModel.java | 2 +- 5 files changed, 4 insertions(+), 8 deletions(-) diff --git a/apps/samples/RichTextAreaDemo/src/com/oracle/demo/richtext/codearea/CodeAreaDemoPane.java b/apps/samples/RichTextAreaDemo/src/com/oracle/demo/richtext/codearea/CodeAreaDemoPane.java index b7567bff5aa..759e035c319 100644 --- a/apps/samples/RichTextAreaDemo/src/com/oracle/demo/richtext/codearea/CodeAreaDemoPane.java +++ b/apps/samples/RichTextAreaDemo/src/com/oracle/demo/richtext/codearea/CodeAreaDemoPane.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2025, Oracle and/or its affiliates. + * Copyright (c) 2023, 2024, Oracle and/or its affiliates. * All rights reserved. Use is subject to license terms. * * This file is available and licensed under the following license: diff --git a/apps/samples/RichTextAreaDemo/src/com/oracle/demo/richtext/notebook/Actions.java b/apps/samples/RichTextAreaDemo/src/com/oracle/demo/richtext/notebook/Actions.java index 546fa56fc80..408ebe6b517 100644 --- a/apps/samples/RichTextAreaDemo/src/com/oracle/demo/richtext/notebook/Actions.java +++ b/apps/samples/RichTextAreaDemo/src/com/oracle/demo/richtext/notebook/Actions.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2025, Oracle and/or its affiliates. + * Copyright (c) 2023, 2024, Oracle and/or its affiliates. * All rights reserved. Use is subject to license terms. * * This file is available and licensed under the following license: diff --git a/apps/samples/RichTextAreaDemo/src/com/oracle/demo/richtext/rta/RichTextAreaDemoPane.java b/apps/samples/RichTextAreaDemo/src/com/oracle/demo/richtext/rta/RichTextAreaDemoPane.java index 6fff39edaf2..f69a5109699 100644 --- a/apps/samples/RichTextAreaDemo/src/com/oracle/demo/richtext/rta/RichTextAreaDemoPane.java +++ b/apps/samples/RichTextAreaDemo/src/com/oracle/demo/richtext/rta/RichTextAreaDemoPane.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2025, Oracle and/or its affiliates. + * Copyright (c) 2023, 2024, Oracle and/or its affiliates. * All rights reserved. Use is subject to license terms. * * This file is available and licensed under the following license: diff --git a/modules/jfx.incubator.richtext/src/main/java/com/sun/jfx/incubator/scene/control/richtext/SegmentStyledInput.java b/modules/jfx.incubator.richtext/src/main/java/com/sun/jfx/incubator/scene/control/richtext/SegmentStyledInput.java index 56f0c8c14b8..9c238830032 100644 --- a/modules/jfx.incubator.richtext/src/main/java/com/sun/jfx/incubator/scene/control/richtext/SegmentStyledInput.java +++ b/modules/jfx.incubator.richtext/src/main/java/com/sun/jfx/incubator/scene/control/richtext/SegmentStyledInput.java @@ -27,7 +27,6 @@ import java.io.IOException; import java.util.List; -import java.util.Objects; import jfx.incubator.scene.control.richtext.model.StyledInput; import jfx.incubator.scene.control.richtext.model.StyledSegment; @@ -36,9 +35,6 @@ public class SegmentStyledInput implements StyledInput { private int index; public SegmentStyledInput(StyledSegment[] segments) { - if(segments == null) { - segments = new StyledSegment[0]; - } this.segments = segments; } diff --git a/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/model/StyledTextModel.java b/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/model/StyledTextModel.java index 9fe5e10b18d..626268cc233 100644 --- a/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/model/StyledTextModel.java +++ b/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/model/StyledTextModel.java @@ -665,7 +665,7 @@ public final TextPos replace(StyleResolver resolver, TextPos start, TextPos end, return replace(resolver, start, end, input, true); } - // only UndoableChange is allowed to disable undo/redo records + // only UndoableChange is allowed to disable undo/redo records private final TextPos replace(StyleResolver resolver, TextPos start, TextPos end, StyledInput input, boolean allowUndo) { checkWritable(); From 4103d9546e76f88ccd438e643c97f8c7fe3a7014 Mon Sep 17 00:00:00 2001 From: Andy Goryachev Date: Tue, 21 Oct 2025 13:46:14 -0700 Subject: [PATCH 08/18] cleanup --- .../src/com/oracle/demo/richtext/notebook/CellPane.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/samples/RichTextAreaDemo/src/com/oracle/demo/richtext/notebook/CellPane.java b/apps/samples/RichTextAreaDemo/src/com/oracle/demo/richtext/notebook/CellPane.java index 772830434ef..a61a67c7e48 100644 --- a/apps/samples/RichTextAreaDemo/src/com/oracle/demo/richtext/notebook/CellPane.java +++ b/apps/samples/RichTextAreaDemo/src/com/oracle/demo/richtext/notebook/CellPane.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2025, Oracle and/or its affiliates. + * Copyright (c) 2023, 2024, Oracle and/or its affiliates. * All rights reserved. Use is subject to license terms. * * This file is available and licensed under the following license: From 84d02951b148497a32ef5435773b7946c0226126 Mon Sep 17 00:00:00 2001 From: Andy Goryachev Date: Wed, 22 Oct 2025 15:58:38 -0700 Subject: [PATCH 09/18] undo redo enabled logic --- .../demo/richtext/rta/UsageExamples.java | 3 +- .../scene/control/richtext/RichTextArea.java | 29 +++++++++++++++- .../richtext/model/StyledTextModel.java | 33 +++++++++++++++++-- 3 files changed, 60 insertions(+), 5 deletions(-) diff --git a/apps/samples/RichTextAreaDemo/src/com/oracle/demo/richtext/rta/UsageExamples.java b/apps/samples/RichTextAreaDemo/src/com/oracle/demo/richtext/rta/UsageExamples.java index b7e1a131308..008340043c6 100644 --- a/apps/samples/RichTextAreaDemo/src/com/oracle/demo/richtext/rta/UsageExamples.java +++ b/apps/samples/RichTextAreaDemo/src/com/oracle/demo/richtext/rta/UsageExamples.java @@ -85,10 +85,11 @@ static RichTextArea appendStyledText() { RichTextArea textArea = new RichTextArea(); // build the content + textArea.setUndoRedoEnabled(false); textArea.appendText("RichTextArea\n", heading); textArea.appendText("Example:\nText is ", StyleAttributeMap.EMPTY); textArea.appendText("monospaced.\n", mono); - textArea.clearUndoRedo(); + textArea.setUndoRedoEnabled(true); return textArea; } diff --git a/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/RichTextArea.java b/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/RichTextArea.java index 9b15eeb261b..258ebdbba13 100644 --- a/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/RichTextArea.java +++ b/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/RichTextArea.java @@ -98,10 +98,11 @@ * * RichTextArea textArea = new RichTextArea(); * // build the content + * textArea.setUndoRedoEnabled(false); * textArea.appendText("RichTextArea\n", heading, false); * textArea.appendText("Example:\nText is ", StyleAttributeMap.EMPTY, false); * textArea.appendText("monospaced.\n", mono, false); - * textArea.clearUndoRedo(); + * textArea.setUndoRedoEnabled(true); * } * Which results in the following visual representation: *

      @@ -790,6 +791,32 @@ public final boolean isUndoable() { return undoableProperty().get(); } + /** + * Indicates whether undo/redo functionality is enabled in the model. + * Returns {@code false} if the model is {@code null}. + * @return true if undo/redo functionality is enabled in the model + * @since 26 + */ + public final boolean isUndoRedoEnabled() { + StyledTextModel m = getModel(); + return (m == null ? false : m.isUndoRedoEnabled()); + } + + /** + * Controls whether undo/redo functionality is enabled in the model. + * Setting the value to {@code false} clears existing undo/redo entries. + * This method does nothing if the model is {@code null}. + * @param on true to enable undo/redo + * @since 26 + * @see #clearUndoRedo() + */ + public final void setUndoRedoEnabled(boolean on) { + StyledTextModel m = getModel(); + if (m != null) { + m.setUndoRedoEnabled(on); + } + } + /** * Determines whether the preferred height is the same as the content height. * When set to true, the vertical scroll bar is disabled. diff --git a/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/model/StyledTextModel.java b/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/model/StyledTextModel.java index 626268cc233..d43a6909c5a 100644 --- a/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/model/StyledTextModel.java +++ b/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/model/StyledTextModel.java @@ -293,6 +293,7 @@ private record FHKey(DataFormat format, boolean forExport) { } private final ReadOnlyBooleanWrapper undoable = new ReadOnlyBooleanWrapper(this, "undoable", false); private final ReadOnlyBooleanWrapper redoable = new ReadOnlyBooleanWrapper(this, "redoable", false); private UndoableChange undo = head; + private boolean undoRedoEnabled = true; /** * Constructs the instance of the model. @@ -662,7 +663,7 @@ public final TextPos replace(StyleResolver resolver, TextPos start, TextPos end, * @throws UnsupportedOperationException if the model is not {@link #isWritable() writable} */ public final TextPos replace(StyleResolver resolver, TextPos start, TextPos end, StyledInput input) { - return replace(resolver, start, end, input, true); + return replace(resolver, start, end, input, isUndoRedoEnabled()); } // only UndoableChange is allowed to disable undo/redo records @@ -778,7 +779,8 @@ public final void applyStyle(TextPos start, TextPos end, StyleAttributeMap attrs changed = true; } - UndoableChange ch = UndoableChange.create(this, evStart, evEnd); + boolean allowUndo = isUndoRedoEnabled(); + UndoableChange ch = allowUndo ? UndoableChange.create(this, evStart, evEnd) : null; if (pa != null) { // set paragraph attributes @@ -807,7 +809,9 @@ public final void applyStyle(TextPos start, TextPos end, StyleAttributeMap attrs if (changed) { fireStyleChangeEvent(evStart, evEnd); - add(ch, end); + if (allowUndo) { + add(ch, end); + } } } @@ -832,6 +836,29 @@ private StyleAttributeMap filterUnsupportedAttributes(StyleAttributeMap attrs) { return b.build(); } + /** + * Indicates whether undo/redo functionality is enabled. + * @return true if undo/redo functionality is enabled + * @since 26 + */ + public final boolean isUndoRedoEnabled() { + return undoRedoEnabled; + } + + /** + * Controls whether undo/redo functionality is enabled. + * Setting the value to {@code false} clears existing undo/redo entries. + * @param on true to enable undo/redo + * @see #clearUndoRedo() + * @since 26 + */ + public final void setUndoRedoEnabled(boolean on) { + undoRedoEnabled = on; + if (!on) { + clearUndoRedo(); + } + } + /** * Clears the undo-redo stack. */ From ffe6894c0b0e7686cc271b0a027f1bf7ccc7caa6 Mon Sep 17 00:00:00 2001 From: Andy Goryachev Date: Wed, 29 Oct 2025 14:50:51 -0700 Subject: [PATCH 10/18] review comments --- .../scene/control/richtext/CodeArea.java | 4 ++- .../scene/control/richtext/RichTextArea.java | 4 ++- .../richtext/model/StyledTextModel.java | 4 ++- .../control/richtext/RichTextAreaTest.java | 31 +++++++++++++++++++ 4 files changed, 40 insertions(+), 3 deletions(-) diff --git a/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/CodeArea.java b/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/CodeArea.java index e6dd39b9993..172efc3088c 100644 --- a/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/CodeArea.java +++ b/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/CodeArea.java @@ -436,7 +436,9 @@ public final String getText() { } /** - * Replaces text in this CodeArea. This method creates an undo/redo entry. + * Replaces text in this CodeArea. + * It creates an undo/redo entry if the model's + * {@link StyledTextModel#isUndoRedoEnabled() isUndoRedoEnabled()} returns {@code true}. *

      * The caret gets reset to the start of the document, selection gets cleared, and an undo event gets created. * @param text the text string diff --git a/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/RichTextArea.java b/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/RichTextArea.java index 258ebdbba13..18214eeae1f 100644 --- a/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/RichTextArea.java +++ b/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/RichTextArea.java @@ -1072,7 +1072,9 @@ public StyleableProperty getStyleableProperty(RichTextArea t) { /** * Appends the styled text to the end of the document. Any embedded {@code "\n"} or {@code "\r\n"} - * sequences result in a new paragraph being added. This method creates an undo/redo entry. + * sequences result in a new paragraph being added. + * It creates an undo/redo entry if the model's + * {@link StyledTextModel#isUndoRedoEnabled() isUndoRedoEnabled()} returns {@code true}. *

      * It is up to the model to decide whether to accept all, some, or none of the * {@link jfx.incubator.scene.control.richtext.model.StyleAttribute StyleAttribute}s. diff --git a/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/model/StyledTextModel.java b/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/model/StyledTextModel.java index d43a6909c5a..115e7de15af 100644 --- a/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/model/StyledTextModel.java +++ b/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/model/StyledTextModel.java @@ -630,6 +630,8 @@ public final TextPos getEndOfParagraphTextPos(int index) { * {@link #replace(StyleResolver, TextPos, TextPos, StyledInput, boolean)} * with the attributes provided by {@link #getStyleAttributeMap(StyleResolver, TextPos)} at the * {@code start} position. + * It creates an undo/redo entry if the model's + * {@link StyledTextModel#isUndoRedoEnabled() isUndoRedoEnabled()} returns {@code true}. * * @param resolver the StyleResolver to use * @param start start text position @@ -653,7 +655,7 @@ public final TextPos replace(StyleResolver resolver, TextPos start, TextPos end, * inserted in the beginning of the document, the style is taken from the following text segment. *

      * After the model applies the requested changes, an event is sent to all the registered listeners. - * This method creates an undo/redo entry. + * It creates an undo/redo entry if {@link #isUndoRedoEnabled()} returns {@code true}. * * @param resolver the StyleResolver to use, can be null * @param start the start text position diff --git a/modules/jfx.incubator.richtext/src/test/java/test/jfx/incubator/scene/control/richtext/RichTextAreaTest.java b/modules/jfx.incubator.richtext/src/test/java/test/jfx/incubator/scene/control/richtext/RichTextAreaTest.java index ccda971acf9..d17e2a0e085 100644 --- a/modules/jfx.incubator.richtext/src/test/java/test/jfx/incubator/scene/control/richtext/RichTextAreaTest.java +++ b/modules/jfx.incubator.richtext/src/test/java/test/jfx/incubator/scene/control/richtext/RichTextAreaTest.java @@ -764,4 +764,35 @@ public void testShim() { RichTextArea t = new RichTextArea(); VFlow f = RichTextAreaShim.vflow(t); } + + @Test + public void undoRedoEnabled() { + // api + assertTrue(control.isUndoRedoEnabled()); + control.setUndoRedoEnabled(false); + assertFalse(control.isUndoRedoEnabled()); + control.setModel(null); + control.setUndoRedoEnabled(true); + assertFalse(control.isUndoRedoEnabled()); + control.setModel(new RichTextModel()); + assertTrue(control.isUndoRedoEnabled()); + // undo-redo enabled + control.appendText("1"); + assertEquals("1", text()); + control.undo(); + assertEquals("", text()); + // undo-redo disabled + control.setUndoRedoEnabled(false); + control.appendText("2"); + assertEquals("2", text()); + control.undo(); + assertEquals("2", text()); + // disabling undo-redo clears undo stack + control.setUndoRedoEnabled(true); + control.appendText("3"); + assertEquals("23", text()); + assertTrue(control.isUndoable()); + control.setUndoRedoEnabled(false); + assertFalse(control.isUndoable()); + } } From 2d5db54e80dde8cc51967c75dce14713a00b062f Mon Sep 17 00:00:00 2001 From: Andy Goryachev Date: Thu, 30 Oct 2025 08:18:35 -0700 Subject: [PATCH 11/18] review comments --- .../jfx/incubator/scene/control/richtext/CodeArea.java | 4 ++-- .../jfx/incubator/scene/control/richtext/RichTextArea.java | 4 ++-- .../scene/control/richtext/model/StyledTextModel.java | 5 +++-- .../incubator/scene/control/richtext/RichTextAreaTest.java | 7 +++++++ 4 files changed, 14 insertions(+), 6 deletions(-) diff --git a/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/CodeArea.java b/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/CodeArea.java index 172efc3088c..10ab4fef317 100644 --- a/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/CodeArea.java +++ b/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/CodeArea.java @@ -437,8 +437,8 @@ public final String getText() { /** * Replaces text in this CodeArea. - * It creates an undo/redo entry if the model's - * {@link StyledTextModel#isUndoRedoEnabled() isUndoRedoEnabled()} returns {@code true}. + * It creates an undo/redo entry if the + * {@link #isUndoRedoEnabled()} returns {@code true}. *

      * The caret gets reset to the start of the document, selection gets cleared, and an undo event gets created. * @param text the text string diff --git a/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/RichTextArea.java b/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/RichTextArea.java index 18214eeae1f..befb9fb82b1 100644 --- a/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/RichTextArea.java +++ b/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/RichTextArea.java @@ -1073,8 +1073,8 @@ public StyleableProperty getStyleableProperty(RichTextArea t) { /** * Appends the styled text to the end of the document. Any embedded {@code "\n"} or {@code "\r\n"} * sequences result in a new paragraph being added. - * It creates an undo/redo entry if the model's - * {@link StyledTextModel#isUndoRedoEnabled() isUndoRedoEnabled()} returns {@code true}. + * It creates an undo/redo entry if + * {@link #isUndoRedoEnabled()} returns {@code true}. *

      * It is up to the model to decide whether to accept all, some, or none of the * {@link jfx.incubator.scene.control.richtext.model.StyleAttribute StyleAttribute}s. diff --git a/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/model/StyledTextModel.java b/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/model/StyledTextModel.java index 115e7de15af..ea511fa6542 100644 --- a/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/model/StyledTextModel.java +++ b/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/model/StyledTextModel.java @@ -630,8 +630,8 @@ public final TextPos getEndOfParagraphTextPos(int index) { * {@link #replace(StyleResolver, TextPos, TextPos, StyledInput, boolean)} * with the attributes provided by {@link #getStyleAttributeMap(StyleResolver, TextPos)} at the * {@code start} position. - * It creates an undo/redo entry if the model's - * {@link StyledTextModel#isUndoRedoEnabled() isUndoRedoEnabled()} returns {@code true}. + * It creates an undo/redo entry if + * {@link #isUndoRedoEnabled()} returns {@code true}. * * @param resolver the StyleResolver to use * @param start start text position @@ -841,6 +841,7 @@ private StyleAttributeMap filterUnsupportedAttributes(StyleAttributeMap attrs) { /** * Indicates whether undo/redo functionality is enabled. * @return true if undo/redo functionality is enabled + * @defaultValue {@code true} * @since 26 */ public final boolean isUndoRedoEnabled() { diff --git a/modules/jfx.incubator.richtext/src/test/java/test/jfx/incubator/scene/control/richtext/RichTextAreaTest.java b/modules/jfx.incubator.richtext/src/test/java/test/jfx/incubator/scene/control/richtext/RichTextAreaTest.java index d17e2a0e085..554a10827e3 100644 --- a/modules/jfx.incubator.richtext/src/test/java/test/jfx/incubator/scene/control/richtext/RichTextAreaTest.java +++ b/modules/jfx.incubator.richtext/src/test/java/test/jfx/incubator/scene/control/richtext/RichTextAreaTest.java @@ -794,5 +794,12 @@ public void undoRedoEnabled() { assertTrue(control.isUndoable()); control.setUndoRedoEnabled(false); assertFalse(control.isUndoable()); + control.setUndoRedoEnabled(true); + control.appendText("4"); + assertEquals("234", text()); + control.undo(); + assertEquals("23", text()); + control.undo(); + assertEquals("23", text()); } } From 361088e5403c7773e91fa19bc1e0e87aa5ccc1a6 Mon Sep 17 00:00:00 2001 From: Andy Goryachev Date: Thu, 30 Oct 2025 08:35:14 -0700 Subject: [PATCH 12/18] the --- .../java/jfx/incubator/scene/control/richtext/CodeArea.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/CodeArea.java b/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/CodeArea.java index 10ab4fef317..0e137500860 100644 --- a/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/CodeArea.java +++ b/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/CodeArea.java @@ -437,7 +437,7 @@ public final String getText() { /** * Replaces text in this CodeArea. - * It creates an undo/redo entry if the + * It creates an undo/redo entry if * {@link #isUndoRedoEnabled()} returns {@code true}. *

      * The caret gets reset to the start of the document, selection gets cleared, and an undo event gets created. From 187678ff19d25ff76e51add6e6d0858c6367ad78 Mon Sep 17 00:00:00 2001 From: Andy Goryachev Date: Thu, 30 Oct 2025 10:33:58 -0700 Subject: [PATCH 13/18] javadoc --- .../scene/control/richtext/RichTextArea.java | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/RichTextArea.java b/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/RichTextArea.java index befb9fb82b1..e64dcbccb3c 100644 --- a/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/RichTextArea.java +++ b/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/RichTextArea.java @@ -1092,7 +1092,9 @@ public final TextPos appendText(String text, StyleAttributeMap attrs) { /** * Appends the styled text to the end of the document. Any embedded {@code "\n"} or {@code "\r\n"} - * sequences result in a new paragraph being added. This method creates an undo/redo entry. + * sequences result in a new paragraph being added. + * It creates an undo/redo entry if + * {@link #isUndoRedoEnabled()} returns {@code true}. *

      * This convenience method is equivalent to calling * {@code appendText(text, StyleAttributeMap.EMPTY, true);} @@ -1108,7 +1110,9 @@ public final TextPos appendText(String text) { /** * Appends the styled content to the end of the document. Any embedded {@code "\n"} or {@code "\r\n"} - * sequences result in a new paragraph being added. This method creates an undo/redo entry. + * sequences result in a new paragraph being added. + * It creates an undo/redo entry if + * {@link #isUndoRedoEnabled()} returns {@code true}. * * @param in the input stream * @return the text position at the end of the appended text, or null if editing is disabled @@ -1501,7 +1505,9 @@ public void insertTab() { /** * Inserts the styled text at the specified position. Any embedded {@code "\n"} or {@code "\r\n"} - * sequences result in a new paragraph being added. This method creates an undo/redo entry. + * sequences result in a new paragraph being added. + * It creates an undo/redo entry if + * {@link #isUndoRedoEnabled()} returns {@code true}. * * @param pos the insert position * @param text the text to inser @@ -1516,7 +1522,9 @@ public final TextPos insertText(TextPos pos, String text, StyleAttributeMap attr } /** - * Inserts the styled content at the specified position. This method creates an undo/redo entry. + * Inserts the styled content at the specified position. + * It creates an undo/redo entry if + * {@link #isUndoRedoEnabled()} returns {@code true}. * * @param pos the insert position * @param in the input stream From 4170fbffa41e0f8c220fb8fe711a54166588eb07 Mon Sep 17 00:00:00 2001 From: Andy Goryachev Date: Thu, 30 Oct 2025 16:06:41 -0700 Subject: [PATCH 14/18] undo/redo javadoc in model only --- .../scene/control/richtext/CodeArea.java | 2 -- .../scene/control/richtext/RichTextArea.java | 16 +++------------- .../control/richtext/model/StyledTextModel.java | 5 ++++- 3 files changed, 7 insertions(+), 16 deletions(-) diff --git a/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/CodeArea.java b/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/CodeArea.java index 0e137500860..921c6c70afa 100644 --- a/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/CodeArea.java +++ b/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/CodeArea.java @@ -437,8 +437,6 @@ public final String getText() { /** * Replaces text in this CodeArea. - * It creates an undo/redo entry if - * {@link #isUndoRedoEnabled()} returns {@code true}. *

      * The caret gets reset to the start of the document, selection gets cleared, and an undo event gets created. * @param text the text string diff --git a/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/RichTextArea.java b/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/RichTextArea.java index e64dcbccb3c..720f90ff64f 100644 --- a/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/RichTextArea.java +++ b/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/RichTextArea.java @@ -1073,8 +1073,6 @@ public StyleableProperty getStyleableProperty(RichTextArea t) { /** * Appends the styled text to the end of the document. Any embedded {@code "\n"} or {@code "\r\n"} * sequences result in a new paragraph being added. - * It creates an undo/redo entry if - * {@link #isUndoRedoEnabled()} returns {@code true}. *

      * It is up to the model to decide whether to accept all, some, or none of the * {@link jfx.incubator.scene.control.richtext.model.StyleAttribute StyleAttribute}s. @@ -1093,8 +1091,6 @@ public final TextPos appendText(String text, StyleAttributeMap attrs) { /** * Appends the styled text to the end of the document. Any embedded {@code "\n"} or {@code "\r\n"} * sequences result in a new paragraph being added. - * It creates an undo/redo entry if - * {@link #isUndoRedoEnabled()} returns {@code true}. *

      * This convenience method is equivalent to calling * {@code appendText(text, StyleAttributeMap.EMPTY, true);} @@ -1111,8 +1107,6 @@ public final TextPos appendText(String text) { /** * Appends the styled content to the end of the document. Any embedded {@code "\n"} or {@code "\r\n"} * sequences result in a new paragraph being added. - * It creates an undo/redo entry if - * {@link #isUndoRedoEnabled()} returns {@code true}. * * @param in the input stream * @return the text position at the end of the appended text, or null if editing is disabled @@ -1157,7 +1151,7 @@ public void backspace() { } /** - * Clears the document, creating an undo/redo entry. + * Clears the document. * * @throws NullPointerException if the model is {@code null} * @throws UnsupportedOperationException if the model is not {@link StyledTextModel#isWritable() writable} @@ -1506,8 +1500,6 @@ public void insertTab() { /** * Inserts the styled text at the specified position. Any embedded {@code "\n"} or {@code "\r\n"} * sequences result in a new paragraph being added. - * It creates an undo/redo entry if - * {@link #isUndoRedoEnabled()} returns {@code true}. * * @param pos the insert position * @param text the text to inser @@ -1523,8 +1515,6 @@ public final TextPos insertText(TextPos pos, String text, StyleAttributeMap attr /** * Inserts the styled content at the specified position. - * It creates an undo/redo entry if - * {@link #isUndoRedoEnabled()} returns {@code true}. * * @param pos the insert position * @param in the input stream @@ -1857,7 +1847,7 @@ public void redo() { } /** - * Replaces the specified range with the new text. This method creates an undo entry. + * Replaces the specified range with the new text. * * @param start the start text position * @param end the end text position @@ -1872,7 +1862,7 @@ public final TextPos replaceText(TextPos start, TextPos end, String text) { } /** - * Replaces the specified range with the new input. This method creates an undo entry. + * Replaces the specified range with the new input. * * @param start the start text position * @param end the end text position diff --git a/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/model/StyledTextModel.java b/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/model/StyledTextModel.java index ea511fa6542..21cec6285be 100644 --- a/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/model/StyledTextModel.java +++ b/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/model/StyledTextModel.java @@ -744,7 +744,10 @@ private final TextPos replace(StyleResolver resolver, TextPos start, TextPos end } /** - * Applies the style attributes to the specified range in the document.

      + * Applies the style attributes to the specified range in the document. + * It creates an undo/redo entry if + * {@link #isUndoRedoEnabled()} returns {@code true}. + *

      * Depending on {@code mergeAttributes} parameter, the attributes will either be merged with (true) or completely * replace the existing attributes within the range. The affected range might be wider than the range specified * when applying the paragraph attributes. From 68d7524a11c886f3de9279c8cdf66653619cc7f5 Mon Sep 17 00:00:00 2001 From: Andy Goryachev Date: Fri, 31 Oct 2025 13:15:05 -0700 Subject: [PATCH 15/18] set text --- .../java/jfx/incubator/scene/control/richtext/CodeArea.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/CodeArea.java b/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/CodeArea.java index 921c6c70afa..d759437a4f4 100644 --- a/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/CodeArea.java +++ b/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/CodeArea.java @@ -438,7 +438,8 @@ public final String getText() { /** * Replaces text in this CodeArea. *

      - * The caret gets reset to the start of the document, selection gets cleared, and an undo event gets created. + * The caret gets reset to the start of the document, selection gets cleared, + * and an undo event is created if {@link #isUndoRedoEnabled()} returns {@code true}. * @param text the text string */ public final void setText(String text) { From 878429e33b3f5a706bac49bea7fd7367b732a510 Mon Sep 17 00:00:00 2001 From: Andy Goryachev <107069028+andy-goryachev-oracle@users.noreply.github.com> Date: Fri, 31 Oct 2025 14:36:30 -0700 Subject: [PATCH 16/18] suggestion Co-authored-by: Kevin Rushforth --- .../java/jfx/incubator/scene/control/richtext/CodeArea.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/CodeArea.java b/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/CodeArea.java index d759437a4f4..fd8f9d87725 100644 --- a/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/CodeArea.java +++ b/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/CodeArea.java @@ -438,8 +438,7 @@ public final String getText() { /** * Replaces text in this CodeArea. *

      - * The caret gets reset to the start of the document, selection gets cleared, - * and an undo event is created if {@link #isUndoRedoEnabled()} returns {@code true}. + * The caret gets reset to the start of the document and the selection gets cleared. * @param text the text string */ public final void setText(String text) { From b8e53ba2d011b0f6526f81695134cee60d3dce92 Mon Sep 17 00:00:00 2001 From: Andy Goryachev Date: Tue, 4 Nov 2025 13:00:21 -0800 Subject: [PATCH 17/18] javadoc --- .../scene/control/richtext/model/StyledTextModel.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/model/StyledTextModel.java b/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/model/StyledTextModel.java index 21cec6285be..f33f5417833 100644 --- a/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/model/StyledTextModel.java +++ b/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/model/StyledTextModel.java @@ -470,7 +470,7 @@ public int getParagraphLength(int index) { * @param end end of the range * @param out {@link StyledOutput} to receive the stream * @throws IOException when an I/O error occurs - * @see #replace(StyleResolver, TextPos, TextPos, StyledInput, boolean) + * @see #replace(StyleResolver, TextPos, TextPos, StyledInput) */ public final void export(TextPos start, TextPos end, StyledOutput out) throws IOException { int cmp = start.compareTo(end); @@ -627,7 +627,7 @@ public final TextPos getEndOfParagraphTextPos(int index) { * Replaces the given range with the provided plain text. *

      * This is a convenience method which eventually calls - * {@link #replace(StyleResolver, TextPos, TextPos, StyledInput, boolean)} + * {@link #replace(StyleResolver, TextPos, TextPos, StyledInput)} * with the attributes provided by {@link #getStyleAttributeMap(StyleResolver, TextPos)} at the * {@code start} position. * It creates an undo/redo entry if From 96453dbaf9ccca8d9a1ab09017d8f578e7123d0d Mon Sep 17 00:00:00 2001 From: Andy Goryachev Date: Thu, 6 Nov 2025 07:37:42 -0800 Subject: [PATCH 18/18] review comments --- .../incubator/scene/control/richtext/RichTextArea.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/RichTextArea.java b/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/RichTextArea.java index 824f659f5d9..721ebc1efef 100644 --- a/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/RichTextArea.java +++ b/modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/RichTextArea.java @@ -100,9 +100,9 @@ * RichTextArea textArea = new RichTextArea(); * // build the content * textArea.setUndoRedoEnabled(false); - * textArea.appendText("RichTextArea\n", heading, false); - * textArea.appendText("Example:\nText is ", StyleAttributeMap.EMPTY, false); - * textArea.appendText("monospaced.\n", mono, false); + * textArea.appendText("RichTextArea\n", heading); + * textArea.appendText("Example:\nText is ", StyleAttributeMap.EMPTY); + * textArea.appendText("monospaced.\n", mono); * textArea.setUndoRedoEnabled(true); * } * Which results in the following visual representation: @@ -1102,7 +1102,7 @@ public final TextPos appendText(String text, StyleAttributeMap attrs) { * sequences result in a new paragraph being added. *

      * This convenience method is equivalent to calling - * {@code appendText(text, StyleAttributeMap.EMPTY, true);} + * {@code appendText(text, StyleAttributeMap.EMPTY);} * * @param text the text to append * @return the text position at the end of the appended text, or null if editing is disabled