FXMisc / RichTextFX

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

Tabulation and gaps #271

Open altavir opened 8 years ago

altavir commented 8 years ago

Hello, I am trying to implement a text viewer (no editing so far) using RichTextFx with tab stops meaning that I need for text with the same number of tabulation symbols start at the same position in different lines. The only problem is that I do not understand how to insert a gap with variable with inside the text. In TextFlow, I solved this problem by inserting an invisible Box with variable width, and it worked quite fine, since it also allows to change tab stops dynamically (the only problem with TextFlow is that I cannot select and copy text from it). Is there a way to insert gaps in RichTextFX?

JordanMartinez commented 8 years ago

Hello @altavir,

So you're trying to implement a text viewer whose text can be selected and copied but not edited (at least by the user)? Does it have similarities with #175?

altavir commented 8 years ago

Oh, indeed it does (my request is more simple since I do not need text to be editable). Missed that issue (strange, I've searched for "tabstop" ). In fact it is not that complicated to implement. You do not need to introduce tables. All you need to store a list of observable integer tabstop values and replace all \t character with resizeable gaps with width bound to next tabstops (tabstop minus current column position). One also needs to remember current number of tabulations in line, but it is not hard, really. Then you can either set these tabstops directly, or make some automatic function to calculate them. I can implement it myself, but I do not know how to insert a gap in the text.

JordanMartinez commented 8 years ago

All you need to store [is] a list of observable integer tabstop values and replace all \t character with resizeable gaps with width bound to next tabstops (tabstop minus current column position).

The issue there is the "resizeable gaps". It sounds to me like you're suggesting that we replace all \t characters with either some Node or some other whitespace character. The former isn't possible yet and may affect other things (line wrapping ?) and the latter would affect plainTextChanges() and richTextChanges().

I'm also assuming that this change would be incorporated into CodeArea, not StyledTextArea.

altavir commented 8 years ago

Maybe a Text node with modified style will do? I tried to apply some styles to \t character via InlineCssTextArea. It works (at least for translations), but the problem is that -fx-min-width is not applicable for Text node. I have not tried to use scaling instead, but probably it will work. Or perhaps one could extend Text class to include custom padding.

JordanMartinez commented 7 years ago

@altavir I think your goal of having resizeable gaps via nodes can now be done. Custom object support was added and you can design your own version of EditableStyledDocument.

altavir commented 7 years ago

Thanks, I will check it shortly and report.

altavir commented 7 years ago

I tried to work with 0.7 API but it seems to be unusable or at least overwhelmingly confusing. There are these new Ops (ops define the type of the segment, maybe it is easier to call them Types to avoid confusion) classes which I believe in fact are juat a factories and utility implementations for segments (won't it be easier to define abstract Segement class?). Old ReadOnlyStyledDocument is now defined with TextOps, but there are no implementations for TextOps even for plain text! If I understand correctly, there should be a singleton class that implements it, but I can't find it.

JordanMartinez commented 7 years ago

To say the least, it might help you to read through the Wiki. We've written some pages that explain some things to help people get started.

I tried to work with 0.7 API but it seems to be unusable or at least overwhelmingly confusing.

Unfortunately, it is confusing to a degree. I don't know if I'd say it's unusable.

There are these new Ops (ops define the type of the segment, maybe it is easier to call them Types to avoid confusion) classes

Actually, the Ops classes provide a way for the area to operate on your segment types (SEG) since it doesn't know what your type will be.

won't it be easier to define abstract Segement class?

Perhaps it would, but that would then restrict developers. Generics allow them full flexibility as to what a segment is.

Old ReadOnlyStyledDocument is now defined with TextOps, but there are no implementations for TextOps even for plain text! If I understand correctly, there should be a singleton class that implements it, but I can't find it.

If you read through the wiki, implementing such a thing shouldn't be too hard. Still, providing a plain text TextOps does make sense. However, we would probably need to refactor GenericStyledArea to become GenericAreaBase that does not include any style-related things, and then GenericStyledArea could extend that base and add the style-related things to it. Tomas made such a comment previously in another issue or PR, but I can't recall where.

altavir commented 7 years ago

I wrote the post only after I've read through the wiki. I finally found a way to generate Ops for text (not in the wiki, but in the examples). It is not obvious since JavaDoc for version 0.7 is sitll pretty empty.

I managed to make my old code work in 0.7. Still found a lot of confusing things. For example necessity to use paragraph style.

Event though I've read through the wiki, I do not completely understand how this object support is working. In fact the only thing I need is ability to place resizeable gaps that would be copied as \t on text select.

JordanMartinez commented 7 years ago

I wrote the post only after I've read through the wiki.

Oh.

I finally found a way to generate Ops for text (not in the wiki, but in the examples).

Mm... looks like we need to still write that part then. I'll add it to the todo list in #403.

It is not obvious since JavaDoc for version 0.7 is sitll pretty empty.

That tends to be the case with Tomas' projects because he believes in self-documenting code.

For example necessity to use paragraph style.

Yeah, just another reason to refactor the base area class to one without styles so that other can add only the styles they want into their area.

Event though I've read through the wiki, I do not completely understand how this object support is working.

You'd need to ask @afester about that as he's the one who wrote it.

In fact the only thing I need is ability to place resizeable gaps that would be copied as \t on text select.

Perhaps what you really need is not custom object support at all, but an EditableStyledDocument that stores your actual content and another EditableStyledDocument that maps the first one's content (e.g. turns \t into a "resizable gap" node). I wonder if a similar approach would be needed to implement something like #107.

altavir commented 7 years ago

I believe that what I really will need is to change a view, not model. Meaning subclassing a GenericStyledArea. I tried to do it when I first wrote the question. Sadly, it requires much more free time than I have.

By the way, I believe that text model presented in 0.7 is too complicated. Making everything generic is a good thing, but currently it is very hard to understand how it works. You need either write a very good documentation, or hide all the implementation details behind some convenient builders and utilities. I recently made my own attempt to build a more or less universal text model. It is not easy. But it could be made comprehensible.

JordanMartinez commented 6 years ago

Any updates on this @altavir The recent release 0.8.1 now includes more documentation.

altavir commented 6 years ago

Thanks for the heads up. Documentation is indeed better. I think it is good idea to link it against JDK using -link option to increase readability. I still would like to have tabulated representation, but sadly It is pretty low on my list of priorities right now. I will try to return to it in a month or sow and if it will work, I will get back to you.

JordanMartinez commented 6 years ago

In due time, no sooner, no later.

altavir commented 6 years ago

The documentation is still not very comprehensible. I am trying to migrate to 0.8.1 from 0.6.1 and currently I can't understand how to just append styled text to the InlineCssTextArea. I have style prepared and I just want to append read-only text fragment with this style. Could you provide an example about how to do it?

JordanMartinez commented 6 years ago

The documentation is still not very comprehensible.

With this general and rather vague statement, I am not sure whether it refers only to the issue you've just asked for help on (appending a read-only styled text fragment) or something else. Could you be more specific as to what about the documentation is not comprehensible and why? :wink: Otherwise I have no clear outcome to try to reach.

I am trying to migrate to 0.8.1 from 0.6.1 and currently I can't understand how to just append styled text to the InlineCssTextArea. I have style prepared and I just want to append read-only text fragment with this style. Could you provide an example about how to do it?

String text = //
S style = //
int position = area.getLength();
area.replace(position, position, text, style);

Personally, I wonder whether we should remove all the [replace/insert/append]Text methods because we're dealing with segments now, not text. It would also allow us to remove TextOps I think, too.

altavir commented 6 years ago

So I have to first add text and the replace it by styled text? Or I must replace empty text with styled text? I am not sure I understand.

As for documentation. If you are saying that is easier to use segments, could you point me to the place in documentation where they are described. I understand that writing good documentation is hard (my own project is much worse than yours), but currently I can't understand even basic concepts of your text model.

JordanMartinez commented 6 years ago

As for documentation. If you are saying that is easier to use segments, could you point me to the place in documentation where they are described. I understand that writing good documentation is hard (my own project is much worse than yours), but currently I can't understand even basic concepts of your text model.

I'll explain this first so you have a better understanding of the text model. If this seems very basic and "no duh..." it's because I'm trying to overcommunicate, not because I think you are dumb.

In RichTextFX's original design (this has changed since then, and I'll get to that in a bit), a segment used to be nothing more than a String for the text part and a Style (the S generic) for the style part. This was stored in the class called StyledText<S>, and allowed one to render rich text. However, the style part and the text part were stored in the same object rather than two separate objects. Thus, the data was stored in a List<StyledText<S>> object:

Text:  a lucky cat caught a mouse when wandering around the house
Style: | red font |   italics   | red and bold                  |
List:  | index 0  | index 1     | index 2

Since its a text area and not a text field, a Paragraph<S> object would store this List<StyledText<S>> object and each paragraph represents one line in the area itself. To represent the entire area's content, a StyledDocument<S> would store a list of such Paragraphs (i.e. List<Paragraph<S>>).

Later on, someone wanted to be able to align the text to the right, so they added paragraph styles (the PS generic) to the text model. Now, each Paragraph<PS, S> could be styled so that all of its text could be left/center/right-aligned. This changed StyledDocument<S> (and related underlying classes) to StyledDocument<PS, S>.

Later on, someone thought "But a rich text editor should be able to display more than just text! What if we wanted to render an image in-line with the text?" So, they introduced the idea of a segment (the SEG generic). If one wanted to render both text and images to the area, they could now do so. By making the SEG object completely generic, so that it does not specify any methods, it allows the developer complete freedom in what that object should be. To still get the methods we need to operate on that segment (e.g. concat, subsequence, length, etc.), the SegmentOps<SEG, S> (read: segment operations) interface was added. TextOps extends SegmentOps by specifing a method which can create a SEG object with only one String parameter: public SEG create(String creationInfo). As a result, this changed StyledDocument<PS, S> to StyledDocument<PS, SEG, S>. Breaking this down into something like algebraic data types via pseudo-java-code, you have:

public class StyledDocument {
  List<Paragraph> paragraphs

  public class Paragraph {
    PS paragraph style
    List<StyledSegment> styledSegs
  }

}

I recently introduced a change that makes Paragraph store the style objects and the segment objects separately, so StyledSegment isn't quite how its stored, but you get the idea.

But, coming back to your question, what exactly is SEG? It can be what it was before—text via the String type, which is what it is in InlineCssTextArea—that can be styled (via the S object). Or, it can be a combination of text and images. This can be done by using the type-container-like type, Either: Either<String, Image>. Such an object is either the left type (String) or the right type (Image). Theoretically, one could use it to render multiple objects, but this gets rather hectic since Java doesn't support type aliases Either<String, Either<Image, Either<Table, Video>>>.

S is either a CSS string via an inline approach (e.g. Node.setStyle("-fx-font-size: 12pt;");) or a style class approach (e.g. Node.getStyleClasses().add("another-style-class");). The Wiki explains that more.

I'll now address the first part of your reply:

So I have to first add text and the replace it by styled text? Or I must replace empty text with styled text? I am not sure I understand.

replace(int start, int end, StyledDocument replacement) will replace the portion of the underlying document from start to end with replacement. When start == end, nothing in the underlying document is being deleted; only the replacement is being added. When replacement is an empty document, only the portion of the document from start to end is deleted; nothing is added.

This code

int position = area.getLength();
area.replace(position, position, text, style);

specifies that you are not replacing anything in the document (because position == position), only adding the text and its style at the end of the document. (the replace(int, int, SEG, S) method's implementation creates a StyledDocument with the given segment and style before passing that to the replace method I mention in the preceding paragraph.

altavir commented 6 years ago

OK, it is much better. Maybe you should place this text into wiki along with some examples of basic operations?

JordanMartinez commented 6 years ago

This is sort of already explained (but maybe not quite to the degree of clarity as above) in the model package's package-info.java and in the "Core Classes" wiki page, excluding the history part. Likewise, the HyperlinkDemo package illustrates some of these ideas itself.

When I was writing #630, the PS, SEG, and S generics appear in so many classes. So I put the better explanation in the model package and StyledDocument-related classes. Otherwise, it felt like I was repeating myself.

Still, I think it would be a good idea to add this to the wiki, perhaps in the "core classes" page?

Jugen commented 6 years ago

I think it should have its own entry, maybe between Text Styles and Implementing custom objects ?

JordanMartinez commented 6 years ago

Would one of you two mind adding that then?

Jugen commented 6 years ago

Ok, I had a shot at it - see: What exactly is SEG ?

JordanMartinez commented 6 years ago

Not bad. One thing that should be clarified (which I didn't mention in my response) was that SEG should be immutable. Since you mention POJOs, I'm wondering whether that implies mutability or not. Also, I wonder if your example of a POJO (e.g. Person) makes sense in this context. RTFX is not a ListView, so I'm curious myself as to how one would render a Person object.

Also, this issue is starting to get blurred with another one: explaining what SEG is. Should we open a new issue to continue discussing it? Or has this been addressed so that we can return to the main issue?

Jugen commented 6 years ago

I think that it has been addressed, so we can return to the main issue :-)

As to "how one would render a Person object", one could for instance display the person's name and surname in a Hyperlink control, or maybe display a thumbnail pic of the person with ImageView.

BTW: In my own project when I implemented custom objects I created an AbstractSegment (as @altavir suggested above) that is basically a SEG wrapper, with Ops and node creator methods all wrapped up together. I think I mainly did it this way because I disliked the Either<A,B> generic all over the place, and I was more comfortable with the Interface/Abstract API pattern.

JordanMartinez commented 6 years ago

As to "how one would render a Person object", one could for instance display the person's name and surname in a Hyperlink control, or maybe display a thumbnail pic of the person with ImageView.

Adding that explanation makes a lot more sense to me. Could you update that in the wiki page, too, if you haven't already?

BTW: In my own project when I implemented custom objects I created an AbstractSegment (as @altavir suggested above) that is basically a SEG wrapper, with Ops and node creator methods all wrapped up together. I think I mainly did it this way because I disliked the Either<A,B> generic all over the place, and I was more comfortable with the Interface/Abstract API pattern.

I can see why you would go that route as their are different pros/cons than the Either approach.