Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

8301121: RichTextArea Control (Incubator) #1524

Open
wants to merge 88 commits into
base: master
Choose a base branch
from

Conversation

andy-goryachev-oracle
Copy link
Contributor

@andy-goryachev-oracle andy-goryachev-oracle commented Jul 30, 2024

Incubating a new feature - rich text control, RichTextArea, intended to bridge the functional gap with Swing and its StyledEditorKit/JEditorPane. The main design goal is to provide a control that is complete enough to be useful out-of-the box, as well as open to extension by the application developers.

This is a complex feature with a large API surface that would be nearly impossible to get right the first time, even after an extensive review. We are, therefore, introducing this in an incubating module, jfx.incubator.richtext. This will allow us to evolve the API in future releases without the strict compatibility constraints that other JavaFX modules have.

Please check out two manual test applications - one for RichTextArea (RichTextAreaDemoApp) and one for the CodeArea (CodeAreaDemoApp). Also, a small example provides a standalone rich text editor, see RichEditorDemoApp.

Because it's an incubating module, please focus on the public APIs rather than implementation. There will be changes to the implementation once/if the module is promoted to the core by popular demand. The goal of the incubator is to let the app developers try the new feature out.

References


Progress

  • Change must not contain extraneous whitespace
  • Commit message must refer to an issue
  • Change requires CSR request JDK-8344643 to be approved
  • Change must be properly reviewed (3 reviews required, with at least 3 Reviewers)

Warnings

 ⚠️ Patch contains a binary file (apps/samples/RichTextAreaDemo/src/com/oracle/demo/richtext/rta/animated.gif)
 ⚠️ Patch contains a binary file (modules/jfx.incubator.richtext/src/main/docs/jfx/incubator/scene/control/richtext/doc-files/CodeArea.png)
 ⚠️ Patch contains a binary file (modules/jfx.incubator.richtext/src/main/docs/jfx/incubator/scene/control/richtext/doc-files/RichTextArea.png)

Issues

  • JDK-8301121: RichTextArea Control (Incubator) (Enhancement - P2)
  • JDK-8343646: Public InputMap (Incubator) (Enhancement - P4)
  • JDK-8344643: RichTextArea Control (Incubator) (CSR)
  • JDK-8344643: RichTextArea Control (Incubator) (CSR)

Reviewers

Reviewing

Using git

Checkout this PR locally:
$ git fetch https://git.openjdk.org/jfx.git pull/1524/head:pull/1524
$ git checkout pull/1524

Update a local copy of the PR:
$ git checkout pull/1524
$ git pull https://git.openjdk.org/jfx.git pull/1524/head

Using Skara CLI tools

Checkout this PR locally:
$ git pr checkout 1524

View PR using the GUI difftool:
$ git pr show -t 1524

Using diff file

Download this PR as a diff file:
https://git.openjdk.org/jfx/pull/1524.diff

Using Webrev

Link to Webrev Comment

@bridgekeeper
Copy link

bridgekeeper bot commented Jul 30, 2024

👋 Welcome back angorya! A progress list of the required criteria for merging this PR into master will be added to the body of your pull request. There are additional pull request commands available for use with this pull request.

@openjdk
Copy link

openjdk bot commented Jul 30, 2024

❗ This change is not yet ready to be integrated.
See the Progress checklist in the description for automated requirements.

@andy-goryachev-oracle andy-goryachev-oracle marked this pull request as ready for review July 30, 2024 21:58
@openjdk openjdk bot added the rfr Ready for review label Jul 30, 2024
@mlbridge
Copy link

mlbridge bot commented Jul 30, 2024

Webrevs

@andy-goryachev-oracle
Copy link
Contributor Author

/reviewers 3

@openjdk
Copy link

openjdk bot commented Jul 30, 2024

@andy-goryachev-oracle
The total number of required reviews for this PR (including the jcheck configuration and the last /reviewers command) is now set to 3 (with at least 1 Reviewer, 2 Authors).

* @author Andy Goryachev
* @since 999 TODO
*/
public class RichTextArea extends Control {
Copy link

@danielpeintner danielpeintner Jul 31, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The old TextField and TextArea controls extend TextInputControl.

I wonder whether there is a a good reason not doing the same for RichTextArea?
Doing so would allow to more easily switch between one or the other.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for a very good question!

The main reason is that RichTextArea (RTA) can work with a large text model which needs to be represented differently from TextInputControl's simple String text property. Other things, like position within the text is also very different, using {paragraphIndex,charOffset} instead of a simple int offset. The fact that the model is separated from the control also makes it very different.

At the end, there is simply no benefit in dragging the TextInputControl into the picture.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was hoping to be able to easily replace one (old) control with the new one. I mean RichTextArea still fulfilling the requirements of TextInputControl. Anyhow, I understand you rational not going along this path 👍

Let me provide the use-case I have in mind (which may explain better what I am looking for).
I would like to use/keep textfields and textareas with pure text. Anyhow, having the possibility to squiggly parts of the text showing spelling mistakes. Since I assume that loading many RichTextArea controls (i.e., in tables) will take much longer I expect to offer spelling suggestions (and RichTextArea loading) on request only.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like in this particular case you do want to preserve the semantics and APIs of TextArea, rather than switch to a new API.

You could simply extend TextArea/Skin and add functionality to superimpose the squiggly on top of TextArea. You'll need to listen to several properties and make sure the clipping is set up correctly and you handle the scrolling et cetera, but I think it's doable.

Of course, once you decide that you need to further decorate your text, with let's say highlighted areas, then you might as well use the new component.

By the way, CodeArea is specifically designed for that purpose, even has getText() and setText() - but no textProperty because it's designed to work with large documents.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You might be right about impact on performance when trying to use many RichTextArea (RTA) controls as table cells - it is indeed a heavier component, but as long as you ensure there are no memory leaks and your table does not show 1000s of cells on screen we should be ok.

If performance is an issue, a full blown editor might indeed be overkill, and you might be better off created a subclass of Labeled adorned with decorations instead. You can always set up RTA as an editor if you need to.

@andy-goryachev-oracle
Copy link
Contributor Author

/csr

@openjdk openjdk bot added the csr Need approved CSR to integrate pull request label Jul 31, 2024
@openjdk
Copy link

openjdk bot commented Jul 31, 2024

@andy-goryachev-oracle has indicated that a compatibility and specification (CSR) request is needed for this pull request.

@andy-goryachev-oracle please create a CSR request for issue JDK-8301121 with the correct fix version. This pull request cannot be integrated until the CSR request is approved.

@andy-goryachev-oracle
Copy link
Contributor Author

/reviewers 2 reviewers

@kevinrushforth kevinrushforth self-requested a review August 1, 2024 22:16
@openjdk
Copy link

openjdk bot commented Aug 1, 2024

@andy-goryachev-oracle
The total number of required reviews for this PR (including the jcheck configuration and the last /reviewers command) is now set to 2 (with at least 2 Reviewers).

@andy-goryachev-oracle
Copy link
Contributor Author

/reviewers 3

@openjdk
Copy link

openjdk bot commented Aug 1, 2024

@andy-goryachev-oracle
The total number of required reviews for this PR (including the jcheck configuration and the last /reviewers command) is now set to 3 (with at least 1 Reviewer, 2 Authors).

@andy-goryachev-oracle
Copy link
Contributor Author

how do I set 3 reviewers and at least 2 "R"eviewers?

@kevinrushforth
Copy link
Member

how do I set 3 reviewers and at least 2 "R"eviewers?

You can't. You can set it to /reviewers 2 reviewers and ask others to also review.

@andy-goryachev-oracle
Copy link
Contributor Author

/reviewers 2 reviewers

@openjdk
Copy link

openjdk bot commented Aug 1, 2024

@andy-goryachev-oracle
The total number of required reviews for this PR (including the jcheck configuration and the last /reviewers command) is now set to 2 (with at least 2 Reviewers).

* Should not be called with localY outside of this layout sliding window.
*/
private int binarySearch(double localY, int low, int high) {
//System.err.println(" binarySearch off=" + off + ", high=" + high + ", low=" + low); // FIX
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like remnants of the debugging session. Do you still need it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, indeed it is (to be cleaned up later).
I would encourage the reviewers to focus on the public API rather than the implementation at this stage.

@@ -446,6 +446,16 @@ <h2>Contents</h2>
</li>
</ul>
</li>
<li><a href="#incubator-modules">Incubator Modules</a>
<ul>
<li>jfx.incubator.scene.control.rich
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

module name typo : rich -> richtext

<table class="package" width="100%">
<tbody>
<tr>
<td>jfx.incubator.scene.control.rich</td>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

module name typo : rich -> richtext

public interface SyntaxDecorator {
/**
* Creates a {@link RichParagraph} from the paragraph plain text.
* The text string is guaranteed to contain neither newline nor carriage return symbols.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment seems unclear to me. Which text string ? There is no String parameter here, only a CodeTextModel with a paragraph index...

Copy link
Contributor Author

@andy-goryachev-oracle andy-goryachev-oracle Aug 14, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am going to rework the javadoc here. Thank you!

* This content provides in-memory storage in an {@code ArrayList} of {@code String}s.
*/
public static class InMemoryContent implements Content {
private final ArrayList<String> paragraphs = new ArrayList<>();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since paragraphs may be inserted anywhere, I would guess that using a LinkedList should be more efficient here. Otherwise, when adding a line break somewhere in the middle of a long text, a lot of array members must be copied.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right, but this is just a default implementation. you can still implement your own BasicTextModel.Content and pass it to the constructor.

Do you want to see an in-memory implementation optimized for large number of paragraphs or should it better be left up to the app developers?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we should definitely provide a very efficient implementation, because most app developers will not want to change it. We should definitely avoid another "TextArea situation", where the application breaks apart, if the text grows too big.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am strongly against LinkedList here: not only it adds 2 pointers to each element, but mostly because it provides no random access.

ArrayList should be sufficient for simple cases, and the developers are free to implement a better Content for their use case. Maybe it's a skip list, or BPlusTree, or something else.

I would also like to mention that the performance issue with TextArea is not the storage (at least it is not the first order issue), but the fact that it renders all the text (in a single Text node). RTA renders only the sliding window, which should be much faster. Of course, RTA will have issues with very long paragraphs, but that's a different story (and is mentioned in the Non-Goals section)

https://github.com/andy-goryachev-oracle/Test/blob/main/doc/RichTextArea/RichTextArea.md#non-goals

}

@Override
public RichParagraph createRichParagraph(CodeTextModel model, int index) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When implementing "real" syntax highlighting, the style of a paragraph will depend on the style of it's predecessors (e.g. multiline strings or multiline comments)...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct, this is just a demo.

The information passed to the SyntaxDecorator interface should be sufficient to support any "real" syntax, at least I hope it is. We are passing the model to each method, so it can re/construct the syntax or cache it as needed.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider this situation: When a user types some text into paragraph 2, it may change the styling in paragraph 2, 3, 4, ...
Will the createRichParagraph - method be called for each paragraph after each typed character? That would probably be inefficient; On the other hand, if it is only called for the paragraph the user typed in, that may not be enough.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this case the model fires a change event which defines the affected area. The view (the skin) determines which visible elements need to be updated, by calling StyledTextModel::getParagraph().

I don't think there will be efficiency issues because

  1. these visuals are likely to be updated anyway
  2. the number of requested paragraphs is limited by the size of the sliding window (i.e. screen size + some margin)

Copy link
Member

@kevinrushforth kevinrushforth left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here are my comments on the updated KeyBinding / KeyBinding.Builder API. Mostly it's just documentation suggestions with a couple method renaming suggestions.

* @param code the key code
* @return the KeyBinding
*/
public static KeyBinding ctrl(KeyCode code) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should spell out control in the public API methods and fields, matching KeyCode. It's fine to use "CTRL" as an abbreviation in the docs.

* @param code the key code
* @return the KeyBinding
*/
public static KeyBinding ctrlShift(KeyCode code) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ctrlShift -> controlShift for the name of the method.

* @param code the key code
* @return the KeyBinding, or null
*/
public static KeyBinding option(KeyCode code) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you intend to keep this method, please add a comment that, on macOS, it is equivalent to calling alt.

* Sets on KEY_RELEASED condition.
* @return the Builder instance
*/
public Builder onKeyReleased() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We use methods named onXxxx for handers, so the on is misleading here. I recommend one of the following alternatives:

  • keyReleased / keyTyped (for symmetry, we might consider adding keyPressed even though it is default)
  • eventType(EventType<KeyEvent>)

The first alternative seems more convenient. If you stick with that (basically a renamed version of what you have), I recommend adding a sentence that it will clear KEY_PRESSED and KEY_TYPED.

}

/**
* Sets the {@code alt} key down condition (the {@code Option} key on macOS).
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The docs for this method could be clarified, especially since there is also a no-arg alt() method with exactly the same description.

I presume that if condition is true then it sets the alt key down condition and if condition is false then it clears the alt key down condition? If so, say that (or something similar).

This applies to all of the modifer methods that take a boolean.

* Sets {@code control} key down condition.
* @return this Builder
*/
public Builder ctrl() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/ctrl/control/

}

/**
* Sets {@code command} key down condition.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You need to document what this method does on non-macOS platforms. Maybe something like

 * Sets {@code command} key down condition on macOS.
 *
 * <p>
 * Setting this condition on non-macOS platforms will result in the
 * {@code build} method returning {@code null}.

This comment also applies to the other macOS-specific key modifier methods.

}

/**
* Creates a new {@link KeyBinding} instance.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: add "from the current settings" after "instance"

// return equals(KeyBinding.from(ev));
// }

/** Key bindings builder */
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you expand this a bit? Also, end with a period. At a minimum, something like this:

 * A builder for {@code KeyBinding} objects.

but maybe also adding an additional sentence or two along with a pointer to the KeyBinding.builder methods and the build method of this class?


/**
* Creates a {@link Builder} with the specified KeyCode.
* @param character the character
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this set the event type to KEY_TYPED? If so, then document that. If not...should it?

Copy link
Member

@kevinrushforth kevinrushforth left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One more gradle comment (on your last commit)

build.gradle Outdated
Comment on lines 4622 to 4562
//project(":incubator.mymod"),
// END: incubator placeholder
project(":incubator.input"),
project(":incubator.richtext"),
// END: incubator placeholder
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This part was correct before. Please revert. It should look like this when you are done:

        // Add an entry for each incubator module here, leaving the incubator
        // placeholder lines as an example.
        // BEGIN: incubator placeholder
        //project(":incubator.mymod"),
        // END: incubator placeholder
        project(":incubator.input"),
        project(":incubator.richtext"),

Copy link
Member

@kevinrushforth kevinrushforth left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Several public classes under the following directory tree are missing @since javadoc tags:

modules/jfx.incubator.richtext/src/main/java/jfx/incubator/scene/control/richtext/

Here is s list of classes with missing @since tags:

CodeArea.java
LineNumberDecorator.java
Marker.java
SelectionModel.java
SelectionSegment.java
SideDecorator.java
SingleSelectionModel.java
StyleAttributeHandler.java
StyleHandlerRegistry.java
StyleResolver.java
SyntaxDecorator.java
TextPos.java
model/BasicTextModel.java
model/CodeTextModel.java
model/DataFormatHandler.java
model/HtmlExportFormatHandler.java
model/ParagraphDirection.java
model/PlainTextFormatHandler.java
model/RichParagraph.java
model/RichTextFormatHandler.java
model/RichTextModel.java
model/RtfFormatHandler.java
model/SimpleViewOnlyStyledModel.java
model/StyleAttribute.java
model/StyleAttributeMap.java
model/StyledInput.java
model/StyledOutput.java
model/StyledSegment.java
model/StyledTextModelViewOnlyBase.java
skin/CellContext.java
skin/CodeAreaSkin.java
skin/RichTextAreaSkin.java

Additional related comments are inline.

@kevinrushforth
Copy link
Member

The updates look good. And thanks for adding the missing Unicode third-party readme file as discussed offline.

Copy link
Member

@kevinrushforth kevinrushforth left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here are some more API doc comments, and a few API naming suggestions.

Comment on lines 519 to 521
public Builder alt(boolean on) {
if (on) {
m.add(KCondition.ALT);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So you decided that the builders don't need the ability to clear a modifier key? That seems fine (it could be added later if needed, but seems unlikely).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not needed. the (only?) use case is to create a KeyBinding in one step.

}

/**
* Adds an event handler for the specified event type, at the control level.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: The phrase "at the control level" seems redundant (and a little awkward), since this is the input map for the control.

* or the associated behavior (the "default" function), or by the application.
* When such a mapping exists, the found function tag is matched to a function registered either by
* the application or by the skin.
* This mechanism allows for customizing the key mappings and the underlying functions independently and separately.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Additionally, the register(KeyBinding,Runnable) method allows mapping to a function directly, bypassing the function tag. You probably want to document this. You might also say that a KeyBinding is only mapped to a single value: either a FunctionTag or a Runnable (last one wins).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good point! it's a bit more complex - the mapping done via the InputMap takes precedence over skin mappings (which typically use FunctionTags), this fact is documented in register(KeyBinding k, Runnable function) method.

}

/**
* Unbinds the specified key binding.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a lack of symmetry in the name (also, we generally use "unbind" when talking about property bindings). Since "register" is used to create a mapping from a KeyBinding to a FunctionTag or Runnable, maybe rename this to "unregister"?

For that matter, what is the practical difference between this and restoreDefaultKeyBinding(KeyBinding)?

* @param tag the function tag
*/
// TODO this should not affect the skin input map, but perhaps place NULL for each found KeyBinding
public void unbind(FunctionTag tag) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs a different name than the other "unbind" method since the semantics are quite different. Since this removes all key bindings for a given function tag, I recommend renaming it to removeKeyBindingsFor, which goes nicely with getKeyBindingsFor.

And shouldn't there be an unregisterFunction(FunctionTag) method that is the inverse of registerFunction(FunctionTag, Runnable)? Or is that what restoreDefaultFunction(FunctionTag) does already?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you are right - no need for a separate unregister(FunctionTag), the restoreDefaultFunction(FunctionTag) does the job.

* @return the [start, end] text positions prior to the change
* @throws UnsupportedOperationException if the model is not {@link #isWritable() writable}
*/
public final TextPos[] undo(StyleResolver resolver) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be a List?

Copy link
Contributor Author

@andy-goryachev-oracle andy-goryachev-oracle Dec 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no, the two-element array [start, end] should be sufficient.

Comment on lines 180 to 182
// TODO remove later
@Deprecated
public static void dump(StyledTextModel model, PrintStream out) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's time to remove this.


@Override
protected void insertParagraph(int index, Supplier<Region> generator) {
// TODO
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the implication of not having implemented this yet?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For now, the RichTextModel does not support arbitrary Regions only text.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then should this throw an UnsupportedOperationException?

// TODO import is not yet working...
public class RtfFormatHandler extends DataFormatHandler {
/** The singleton instance of {@code RtfFormatHandler}. */
public static final RtfFormatHandler INSTANCE = new RtfFormatHandler();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A better pattern is to provide a static getInstance() getter rather than making the singleton field itself public. That's what we do elsewhere in the API. This applies to the other subclasses as well.


/**
* Determines whether the change is an edit (true) or affects styling only (false).
* @return true if change affects stylint only
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is backwards. Also, you spelled "styling" wrong, but since you need to remove it anyway, that problem will go away.

@kevinrushforth kevinrushforth self-requested a review December 13, 2024 18:15
@aghaisas
Copy link
Collaborator

/reviewers 3 reviewers

@openjdk
Copy link

openjdk bot commented Dec 16, 2024

@aghaisas
The total number of required reviews for this PR (including the jcheck configuration and the last /reviewers command) is now set to 3 (with at least 3 Reviewers).

Copy link
Collaborator

@aghaisas aghaisas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed files under - com.sun.jfx.incubator.scene.control.richtext
Done some sanity testing. Overall this is looking good now.

Copy link
Member

@kevinrushforth kevinrushforth left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most of the changes look good. I left a few comments.

Comment on lines +286 to +287
* {@link #registerKey(KeyBinding, FunctionTag)},
* or registered by the skin.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The "or registered by the skin" part seems doesn't seem right to me. Is that really what happens? And if so, why is it the right thing to do?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this call disables the key binding, regardless of who registered it.

the use case example is Eclipse's "Unbind Command":

Screenshot 2024-12-17 at 11 32 33

@@ -360,7 +368,7 @@ private Set<KeyBinding> collectKeyBindings(FunctionTag tag) {
* @param tag the function tag
*/
// TODO this should not affect the skin input map, but perhaps place NULL for each found KeyBinding
public void unbind(FunctionTag tag) {
public void unregister(FunctionTag tag) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For symmetry (and better clarity), I still think the name should have KeyBindingsFor in the name, perhaps removeKeyBindingsFor or unregisgterKeyBindingsFor.

*
* @param k the key binding
*/
public void unbind(KeyBinding k) {
public void unregister(KeyBinding k) {
map.put(k, NULL);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does this add a NULL rather than remove the mapping? I'm trying to understand the difference between this method and resetKeyBindings (maybe this is related to my earlier question above?). One thing to consider is that allowing NULL values means that there is no way to tell the difference between a key with a NULL value and a key that isn't in the map without calling map.contains, which you don't expose.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see my previous comment for the use case.

this method disables the key binding, regardless of whether it's done programmatically by the application (as in constructor of the custom RTA), or the skin.

if we are talking about implementation, the NULL basically blocks subsequent lookup in the skin input map.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. In that case, unregister is not a good name, since it is not the inverse of register. What it is really doing is registering a no-op binding. In which case... why is it needed at all? The application can just as easily do this:

    register(keyBinding, () -> {});

If there really is a compelling reason to keep this method, then we need to think of a better name. Maybe "disable" or "disableKeyBinding"?

@@ -207,13 +207,13 @@ public static class Builder {
}

/**
* Adds a squiggly line (as seen in a spell checker) with the given color.
* Adds a wave underline (typically used as a spell checker indicator) with the given color.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: "wave" --> "wavy"

* @param start the start offset
* @param length the end offset
* @param color the background color
* @return this {@code Builder} instance
*/
public Builder addSquiggly(int start, int length, Color color) {
public Builder wavyUnderline(int start, int length, Color color) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the name start with "add" (e.g., to match addHighlight)?

@@ -129,7 +129,7 @@ protected void removeRange(TextPos start, TextPos end) {

@Override
protected void insertParagraph(int index, Supplier<Region> generator) {
// TODO
throw new UnsupportedOperationException();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be documented?

Comment on lines 98 to 99
* The caller must ensure that the {@code text} does not contain newline symbols, as the behavior might be
* undefined.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: I would say "is undefined" (instead of "might be") meaning that we don't define or specify the behavior.

@@ -184,14 +188,14 @@ public SimpleViewOnlyStyledModel highlight(int start, int length, Color c) {
}

/**
* Adds a squiggly line (typically used as a spell checker indicator) to the specified range within the last paragraph.
* Adds a wave underline (typically used as a spell checker indicator) to the specified range within the last paragraph.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: "wave" --> "wavy"

@@ -135,7 +137,7 @@ public interface Listener {
/**
* Returns the plain text string for the specified paragraph. The returned text string cannot be null
* and must not contain any control characters other than TAB.
* The caller should never attempt to ask for a paragraph outside of the valid range.
* The caller must not attempt to ask for a paragraph outside of the valid range.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be worth adding the note about "doing otherwise might result in an exception..." as you mentioned below.

@@ -144,10 +146,13 @@ public interface Listener {

/**
* Returns a {@link RichParagraph} at the given model index.
* The callers must ensure that the value of {@code index} is within the valid document range,
* since doing otherwise might result in an exception or undetermied behavior.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo: "undetermied" --> "undetermined"

Copy link
Member

@kevinrushforth kevinrushforth left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The latest doc changes (and one method renaming) look good.

@andy-goryachev-oracle
Copy link
Contributor Author

Refreshed the javadoc/apidiff attachments in the CSR.

@kevinrushforth kevinrushforth self-requested a review December 18, 2024 17:43
Copy link
Member

@arapte arapte left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@andy-goryachev-oracle
Copy link
Contributor Author

Thank you, @arapte !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
csr Need approved CSR to integrate pull request rfr Ready for review
Development

Successfully merging this pull request may close these issues.

10 participants