org.fxmisc.richtext.model.SimpleEditableStyledDocument Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of richtextfx Show documentation
Show all versions of richtextfx Show documentation
Rich-text area for JavaFX
package org.fxmisc.richtext.model;
import static org.fxmisc.richtext.model.TwoDimensional.Bias.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.reactfx.EventSource;
import org.reactfx.EventStream;
import org.reactfx.Subscription;
import org.reactfx.SuspendableNo;
import org.reactfx.collection.LiveList;
import org.reactfx.collection.LiveListBase;
import org.reactfx.collection.MaterializedListModification;
import org.reactfx.collection.QuasiListModification;
import org.reactfx.collection.UnmodifiableByDefaultLiveList;
import org.reactfx.util.BiIndex;
import org.reactfx.util.Lists;
import org.reactfx.value.Val;
/**
* Provides an implementation of {@link EditableStyledDocument}
*/
public final class SimpleEditableStyledDocument implements EditableStyledDocument {
private class ParagraphList
extends LiveListBase>
implements UnmodifiableByDefaultLiveList> {
@Override
public Paragraph get(int index) {
return doc.getParagraph(index);
}
@Override
public int size() {
return doc.getParagraphCount();
}
@Override
protected Subscription observeInputs() {
return parChanges.subscribe(mod -> {
mod = mod.trim();
QuasiListModification> qmod =
QuasiListModification.create(mod.getFrom(), mod.getRemoved(), mod.getAddedSize());
notifyObservers(qmod.asListChange());
});
}
}
private ReadOnlyStyledDocument doc;
private final EventSource> richChanges = new EventSource<>();
@Override public EventStream> richChanges() { return richChanges; }
private final Val text = Val.create(() -> doc.getText(), richChanges);
@Override public String getText() { return text.getValue(); }
@Override public Val textProperty() { return text; }
private final Val length = Val.create(() -> doc.length(), richChanges);
@Override public int getLength() { return length.getValue(); }
@Override public Val lengthProperty() { return length; }
@Override public int length() { return length.getValue(); }
private final EventSource>> parChanges =
new EventSource<>();
private final LiveList> paragraphs = new ParagraphList();
@Override
public LiveList> getParagraphs() {
return paragraphs;
}
@Override
public ReadOnlyStyledDocument snapshot() {
return doc;
}
private final SuspendableNo beingUpdated = new SuspendableNo();
@Override public final SuspendableNo beingUpdatedProperty() { return beingUpdated; }
@Override public final boolean isBeingUpdated() { return beingUpdated.get(); }
SimpleEditableStyledDocument(Paragraph initialParagraph) {
this.doc = new ReadOnlyStyledDocument<>(Collections.singletonList(initialParagraph));
}
/**
* Creates an empty {@link EditableStyledDocument}
*/
public SimpleEditableStyledDocument(PS initialParagraphStyle, S initialStyle) {
this(new Paragraph<>(initialParagraphStyle, "", initialStyle));
}
@Override
public Position position(int major, int minor) {
return doc.position(major, minor);
}
@Override
public Position offsetToPosition(int offset, Bias bias) {
return doc.offsetToPosition(offset, bias);
}
@Override
public void replace(int start, int end, StyledDocument replacement) {
ensureValidRange(start, end);
doc.replace(start, end, ReadOnlyStyledDocument.from(replacement)).exec(this::update);
}
@Override
public void setStyle(int from, int to, S style) {
ensureValidRange(from, to);
doc.replace(from, to, removed -> removed.mapParagraphs(par -> par.restyle(style))).exec(this::update);
}
@Override
public void setStyle(int paragraph, S style) {
ensureValidParagraphIndex(paragraph);
doc.replaceParagraph(paragraph, p -> p.restyle(style)).exec(this::update);
}
@Override
public void setStyle(int paragraph, int fromCol, int toCol, S style) {
ensureValidParagraphRange(paragraph, fromCol, toCol);
doc.replace(
new BiIndex(paragraph, fromCol),
new BiIndex(paragraph, toCol),
d -> d.mapParagraphs(p -> p.restyle(style))
).exec(this::update);
}
@Override
public void setStyleSpans(int from, StyleSpans extends S> styleSpans) {
int len = styleSpans.length();
ensureValidRange(from, from + len);
doc.replace(from, from + len, d -> {
Position i = styleSpans.position(0, 0);
List> pars = new ArrayList<>(d.getParagraphs().size());
for(Paragraph p: d.getParagraphs()) {
Position j = i.offsetBy(p.length(), Backward);
StyleSpans extends S> spans = styleSpans.subView(i, j);
pars.add(p.restyle(0, spans));
i = j.offsetBy(1, Forward); // skip the newline
}
return new ReadOnlyStyledDocument<>(pars);
}).exec(this::update);
}
@Override
public void setStyleSpans(int paragraph, int from, StyleSpans extends S> styleSpans) {
setStyleSpans(doc.position(paragraph, from).toOffset(), styleSpans);
}
@Override
public void setParagraphStyle(int parIdx, PS style) {
ensureValidParagraphIndex(parIdx);
doc.replaceParagraph(parIdx, p -> p.setParagraphStyle(style)).exec(this::update);
}
@Override
public StyledDocument concat(StyledDocument that) {
return doc.concat(that);
}
@Override
public StyledDocument subSequence(int start, int end) {
return doc.subSequence(start, end);
}
/* ********************************************************************** *
* *
* Private and package private methods *
* *
* ********************************************************************** */
private void ensureValidParagraphIndex(int parIdx) {
Lists.checkIndex(parIdx, doc.getParagraphCount());
}
private void ensureValidRange(int start, int end) {
Lists.checkRange(start, end, length());
}
private void ensureValidParagraphRange(int par, int start, int end) {
ensureValidParagraphIndex(par);
Lists.checkRange(start, end, fullLength(par));
}
private int fullLength(int par) {
int n = doc.getParagraphCount();
return doc.getParagraph(par).length() + (par == n-1 ? 0 : 1);
}
private void update(
ReadOnlyStyledDocument newValue,
RichTextChange change,
MaterializedListModification> parChange) {
this.doc = newValue;
beingUpdated.suspendWhile(() -> {
richChanges.push(change);
parChanges.push(parChange);
});
}
}