package org.fxmisc.richtext.model; import javafx.beans.value.ObservableValue; import org.reactfx.EventStream; import org.reactfx.EventStreamBase; import org.reactfx.Subscription; import org.reactfx.SuspendableNo; import org.reactfx.collection.LiveList; import org.reactfx.value.Val; import java.util.Arrays; import java.util.List; /** * Content model for {@link org.fxmisc.richtext.GenericStyledArea}. Specifies edit operations * on paragraph's styles, segments (like text), and segments' style, but does not worry about view-related aspects * (e.g. scrolling). * * @param <PS> the paragraph style type * @param <SEG> the segment type * @param <S> the segment's style */ public interface EditableStyledDocument<PS, SEG, S> extends StyledDocument<PS, SEG, S> { /* ********************************************************************** * * * * Observables * * * * Observables are "dynamic" (i.e. changing) characteristics of an object.* * They are not directly settable by the client code, but change in * * response to user input and/or API actions. * * * * ********************************************************************** */ ObservableValue<String> textProperty(); int getLength(); Val<Integer> lengthProperty(); @Override LiveList<Paragraph<PS, SEG, S>> getParagraphs(); /** * Read-only snapshot of the current state of this document. */ ReadOnlyStyledDocument<PS, SEG, S> snapshot(); /* ********************************************************************** * * * * Event streams * * * * ********************************************************************** */ /** * Returns an {@link EventStream} that emits a {@link List} of {@link RichTextChange}s every time a change is made * to this document, even when such a change does not modify the underlying document in any way. The emitted * list will only have one item in it unless one used {@link #replaceMulti(List)}. */ EventStream<List<RichTextChange<PS, SEG, S>>> multiRichChanges(); /** * Returns an {@link EventStream} that emits a {@link List} of {@link PlainTextChange}s every time a non-style * change is made to this document. A style change would include setting a segment's style, but not changing * that segment or setting a paragraph's style. A non-style change would include adding/removing/modifying a * segment itself. The emitted list will only have one item in it unless one used {@link #replaceMulti(List)}. */ default EventStream<List<PlainTextChange>> multiPlainChanges() { return multiRichChanges() // map to a List<PlainTextChange> .map(list -> Arrays.asList(list.stream() // filter out rich changes where the style was changed but text wasn't added/removed .filter(rtc -> !rtc.isPlainTextIdentity()) .map(RichTextChange::toPlainTextChange) .toArray(PlainTextChange[]::new))) // only emit non-empty lists .filter(list -> !list.isEmpty()); } /** * Returns an {@link EventStream} that emits each {@link PlainTextChange} in {@link #multiPlainChanges()}'s * emitted list. */ default EventStream<PlainTextChange> plainChanges() { return new EventStreamBase<PlainTextChange>() { @Override protected Subscription observeInputs() { return multiPlainChanges().subscribe(l -> l.forEach(this::emit)); } }; } /** * Returns an {@link EventStream} that emits each {@link RichTextChange} in {@link #multiRichChanges()}'s * emitted list. */ default EventStream<RichTextChange<PS, SEG, S>> richChanges() { return new EventStreamBase<RichTextChange<PS, SEG, S>>() { @Override protected Subscription observeInputs() { return multiRichChanges().subscribe(list -> list.forEach(this::emit)); } }; } SuspendableNo beingUpdatedProperty(); boolean isBeingUpdated(); /* ********************************************************************** * * * * Actions * * * * Actions change the state of the object. They typically cause a change * * of one or more observables and/or produce an event. * * * * ********************************************************************** */ /** * Replaces multiple portions of this document in one update. */ void replaceMulti(List<Replacement<PS, SEG, S>> replacements); /** * Convenience method for {@link #replace(int, int, StyledDocument)} using a {@link Replacement} argument. */ default void replace(Replacement<PS, SEG, S> replacement) { replace(replacement.getStart(), replacement.getEnd(), replacement.getDocument()); } /** * Replaces the portion of this document {@code "from..to"} with the given {@code replacement}. * * @param start the absolute position in the document that starts the portion to replace * @param end the absolute position in the document that ends the portion to replace * @param replacement the document that replaces the removed portion of this document */ void replace(int start, int end, StyledDocument<PS, SEG, S> replacement); /** * Sets the style of all segments in the given "from..to" range to the given style. * * @param from the absolute position in the document that starts the range to re-style * @param to the absolute position in the document that ends the range to re-style */ void setStyle(int from, int to, S style); /** * Sets all segments in the given paragraph to the given style */ void setStyle(int paragraphIndex, S style); /** * Sets the given range "fromCol..toCol" in the given paragraph to the given style */ void setStyle(int paragraphIndex, int fromCol, int toCol, S style); /** * Replaces the style spans for the given range {@code "from..(from + styleSpans.length())"} with the given * style spans */ void setStyleSpans(int from, StyleSpans<? extends S> styleSpens); /** * Replaces the style spans for the given range {@code "from..(from + styleSpans.length())"} in the given * paragraph with the given style spans */ void setStyleSpans(int paragraphIndex, int from, StyleSpans<? extends S> styleSpens); /** * Sets the given paragraph to the given paragraph style */ void setParagraphStyle(int paragraphIndex, PS style); }