On Mon, 15 Jun 2026 18:40:05 GMT, Andy Goryachev <[email protected]> wrote:

>> ### Summary
>> 
>> This PR adds support for controlling tab stops: the `TAB_STOPS` paragraph 
>> attribute and the `defaultTabStops` property in the `RichTextModel`.  
>> 
>> While adding the paragraph attribute is a trivial process, adding a 
>> model-wide property requires adding of a mechanism to support document 
>> properties (something that was originally omitted in the first incubating 
>> release).  Using the document properties, we can now persist not only the 
>> default tab stops, but also provide the version umber for the storage format 
>> provided by the `RichTextFormatHandler`, which will enable support the 
>> format evolution in the future [0].
>> 
>> To showcase the feature, the `RichEditorDemoApp` gains a visual ruler and 
>> additional dialogs and menus.
>> 
>> <img width="822" height="381" alt="Screenshot 2026-03-04 at 14 00 30" 
>> src="https://github.com/user-attachments/assets/32251846-f11a-4e87-b74a-44c21d629550";
>>  />
>> 
>> 
>> ### Paragraph Attribute
>> 
>> Paragraph-specific tab stops are enabled by:
>> 
>> - `StyleAttributeMap.TAB_STOPS` constant
>> - `StyleAttributeMap.Builder.setTabStops(double ... positions)`
>> 
>> The tab stops are represented by the `TabStops` class which is basically an 
>> immutable list (we can't use List<TabStops> because it makes it impossible 
>> to offer compile-time type safety of `StyleAttribute`).
>> 
>> ### Default Tab Stops
>> 
>> After the last paragraph tab stop, or when no paragraph tab stops is set, 
>> the document provides a way to set default tab stops via the model's 
>> `defaultTabStops` property:
>> 
>> These changes support the new property and other document properties:
>> 
>> - document-wide properties support in the `StyledTextModel` base class
>> - `defaultTabStops` property in the `RichTextModel` and 
>> `RichTextFormatHandler`
>> - document properties `VERSION` and `DEFAULT_TAB_STOPS`
>> - `StyledSegment`: `ofDocumentProperties()` factory, 
>> `getDocumentProperties()`
>> 
>> ### Other Improvements
>> 
>> A number of other improvements were made along with the tab stop related 
>> changes:
>> 
>> - `character()`, `paragraph()`, and `document()` factory methods in the 
>> `StyleAttribute` class
>> - `isCharacterAttribute()`, `isParagraphAttribute()`, and 
>> `isDocumentAttribute()` methods in the `StyleAttribute` class
>> - `RichTextArea`: `documentArea` read-only property
>> 
>> 
>> ### Testing
>> 
>> In addition to the `RichEditorDemoApp`, the standalone Monkey Tester [1] 
>> provides additional options via context menu in the `CodeArea` and 
>> `RichTextArea` pages.
>> 
>> 
>> ### Questions to the...
>
> Andy Goryachev has updated the pull request with a new target base due to a 
> merge or a rebase. The pull request now contains 194 commits:
> 
>  - test standard attributes
>  - Merge branch 'master' into 8356042.ruler
>  - ws
>  - test doc props
>  - cleanup
>  - tab stops
>  - Merge branch 'master' into 8356042.ruler
>  - renamed doc
>  - docs
>  - Merge branch 'master' into 8356042.ruler
>  - ... and 184 more: https://git.openjdk.org/jfx/compare/738be0f1...87241d12

The updated API looks good with a couple naming suggestions. I left a few doc 
comments as well.

I also left a couple implementation comments. The main one being how the 
version number is treated. As I mentioned in one of the inline comments, I'm 
not sold on the notion that the Version string should be treated as "just 
another section in the docs" and as a type of StyleAttribute. That latter 
especially seems odd, since the version is only something you should encounter 
when reading or writing a file. It has relevance to the I/O of the file, not to 
the processing of the document once read.

I think this could be handled as a follow-up if you prefer, perhaps at the same 
time the Version string is made mandatory.

doc-files/controls/RichTextArea/RichTextArea_DataFormat_Incubator.md line 54:

> 52: 
> 53: The document is persisted as a UTF-8 encoded plain text file which 
> contains a sequence of segments.
> 54: The first segment is the document properties segment, followed by 
> segments representing the paragraphs.

You need to say something about the version here, which is before the document 
properties.

modules/jfx.incubator.richtext/src/main/java/com/sun/jfx/incubator/scene/control/richtext/Converters.java
 line 203:

> 201:     private static TabStops toTabStops(String text) {
> 202:         if (text.length() == 0) {
> 203:             return null;

An empty list might be cleaner unless there is a need for `null` (e.g., if 
`null` is logically distinct from an empty list).

modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/model/RichTextFormatHandler.java
 line 544:

> 542:                         index += 2;
> 543:                         // TODO check if already exists
> 544:                         String ver = parseString();

In order for it to be effective, the version needs to be the first thing in the 
file in addition to there being only one of them (and it eventually needs to be 
mandatory). I'm still not sold on treating the version string as "just another 
section", but at a minimum, you need to check whether this is the first thing 
seen (not just the first version block), and fail if it isn't.

modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/model/RichTextModel.java
 line 56:

> 54:      * @since 27
> 55:      */
> 56:     public static final double DEFAULT_TAB_STOPS_8 = 0.0;

When I suggested using named static final fields (constants), I hadn't realized 
that this was a floating-point attribute. It still seems OK to do this, but we 
should be careful checking for equality (other than equality with 0).

I don't much care for the use of `8` in the name. That seems odd. Maybe 
`DEFAULT_TAB_STOPS_FIXED` or similar?

modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/model/StyleAttributeMap.java
 line 362:

> 360:      * This convenience method returns the value of {@link #TAB_STOPS} 
> attribute, or null.
> 361:      * @return the paragraph alignment attribute value
> 362:      */

Missing `@since 27`

modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/model/StyleAttributeMap.java
 line 672:

> 670:             for (int i = 0; i < positions.length; i++) {
> 671:                 ts[i] = new TabStop(positions[i]);
> 672:             }

The `ts` variable is unused and unneeded now, so you can removed these lines.

modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/model/StyledTextModel.java
 line 791:

> 789:             case DOCUMENT_PROPERTIES:
> 790:                 handleDocumentProperties(seg.getDocumentProperties(), 
> completeReplacement);
> 791:                 break;

Doesn't this need to handle VERSION (in any case, this seems like another 
reason not to treat version as just another attribute type).

modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/model/TabStops.java
 line 40:

> 38:  * @since 27
> 39:  */
> 40: public class TabStops implements List<TabStop> {

This seems like a good solution to allow using a list of stops (rather than the 
array you used to have). Since the list of stops is unmodifiable after 
constructions, perhaps the docs should say that?

Suggestion: Make this class `final` unless you have a a compelling reason to 
extend it.

modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/model/TabStops.java
 line 45:

> 43: 
> 44:     /**
> 45:      * Constructor.

I would use the same language as the `of` method -- it's a little terse as it 
stands/ Something like:


Creates a {@code TabStops} instance from tab stop positions.

modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/model/TabStops.java
 line 57:

> 55:      * @return the new instance
> 56:      */
> 57:     public static TabStops of(double ... positions) {

Minor: we usually omit the space before the `...`

-------------

PR Review: https://git.openjdk.org/jfx/pull/1800#pullrequestreview-4510117786
PR Review Comment: https://git.openjdk.org/jfx/pull/1800#discussion_r3424125008
PR Review Comment: https://git.openjdk.org/jfx/pull/1800#discussion_r3423996073
PR Review Comment: https://git.openjdk.org/jfx/pull/1800#discussion_r3424071413
PR Review Comment: https://git.openjdk.org/jfx/pull/1800#discussion_r3424016921
PR Review Comment: https://git.openjdk.org/jfx/pull/1800#discussion_r3424187790
PR Review Comment: https://git.openjdk.org/jfx/pull/1800#discussion_r3424132311
PR Review Comment: https://git.openjdk.org/jfx/pull/1800#discussion_r3424111163
PR Review Comment: https://git.openjdk.org/jfx/pull/1800#discussion_r3423845095
PR Review Comment: https://git.openjdk.org/jfx/pull/1800#discussion_r3423894939
PR Review Comment: https://git.openjdk.org/jfx/pull/1800#discussion_r3423904986

Reply via email to