-
Notifications
You must be signed in to change notification settings - Fork 236
Implementing custom objects
Things to write here:
- Gotchas and other quirks
- Bare minimum pattern to follow
The following three (3) conditions need to be met for undo/redo to work on custom segment objects (as noted by Jugen in #403):
- The
preserveStyle
parameter must be TRUE when invoking theGenericStyledArea
constructor. - A properly implemented
Codec
for yourSEG
must be set viaGenericStyledArea.setStyleCodecs()
- The
Object.equals( Object obj )
method must be overridden and properly implemented by yourSEG
Your area's nodeFactory
must return a TextExt
object to display that text, not the regular Text
object, in order for RichTextFX-specific CSS styling to work.
To implement a custom object, you will need to have three things:
- A non-empty immutable version of your object
- An empty immutable version of your object
- A
SegOps
(an object that handles segment operations on your object).
Ideally, you would use an interface for your custom object, have both object versions implement this interface, and use only one object for the empty version of your object. For example...
public interface CustomObject<S> {
// relevant information
// if you want to provide a Codec for your object, you should include it here
static <S> Codec<CustomObject<S>> codec(Codec<S> styleCodec) {
return new Codec<CustomObject<S>>() {
@Override
public String getName() {
return "CustomObject<" + styleCodec.getName() + ">";
}
@Override
public void encode(DataOutputStream os, LinkedImage<S> i) throws IOException {
// don't encode EmptyObject
if (i.getStyle() != null) {
// encode object code here...
// encode style
styleCodec.encode(os, i.getStyle());
}
}
@Override
public RealLinkedImage<S> decode(DataInputStream is) throws IOException {
// decode object code here
// decode style
S style = styleCodec.decode(is);
return new RealObject<>(/* object args */, style);
}
};
}
}
public class RealObject<S> implements CustomObject<S> {
// implementation
// Note: setters should always return a new immutable RealObject
}
public class EmptyObject<S> implements CustomObject<S> {
// style is null. No need to worry about NPEs being thrown
// if you implement length-related methods correctly (see next point)
private final S style = null;
public final S getStyle() { return style; }
// Note: setters should always return "this" or the single EmptyObject
}
Then, in your CustomObjectOps
class, you would write the following code. Be sure to be careful how you implement methods that deal with length! RealObject
will always have a length of 1
because charAt()
and getText()
will always return /ufffc
.
public class CustomObjectOps implements SegmentOps<CustomObject<S>> {
private final EmptyObject<S> emptySeg = new EmptyObject<>();
@Override
public int length(CustomObject<S> seg) {
// non-empty seg's length is always 1
return seg == emptySeg ? 0 : 1;
}
@Override
public char charAt(CustomObject<S> seg, int index) {
return seg == emptySeg ? '\0' : '\ufffc';
}
@Override
public String getText(CustomObject<S> seg) {
return seg == emptySeg ? "" : "\ufffc";
}
@Override
public CustomObject<S> subSequence(CustomObject<S> seg, int start, int end) {
if (start < 0) {
throw new IllegalArgumentException("Start cannot be negative. Start = " + start);
}
if (end > length(seg)) {
throw new IllegalArgumentException("End cannot be greater than segment's length");
}
return start == 0 && end == 1
? seg
: emptySeg;
}
@Override
public CustomObject<S> subSequence(CustomObject<S> seg, int start) {
if (start < 0) {
throw new IllegalArgumentException("Start cannot be negative. Start = " + start);
}
return start == 0
? seg
: emptySeg;
}
@Override
public S getStyle(CustomObject<S> seg) {
return seg.getStyle();
}
@Override
public CustomObject<S> setStyle(CustomObject<S> seg, S style) {
return seg.setStyle(style);
}
@Override
public Optional<CustomObject<S>> join(CustomObject<S> currentSeg, CustomObject<S> nextSeg) {
// a custom non-text object can never be merged with another such object
return Optional.empty();
}
@Override
public CustomObject<S> createEmpty() {
// save memory!
return emptySeg;
}
}
- Home
- General guidelines
- Core classes
- Text Styles: Inline, Style Class, and Custom Style Objects
- PS, S, and SEG: Explaining the main generics in RichTextFX
- Implementing custom objects
- How to Override the Default Behavior
- RichTextFX CSS Reference Guide
- Adding Support for Emojis
- Known Issues
- Test Automation