Package org.fxmisc.richtext

Source Code of org.fxmisc.richtext.Paragraph

package org.fxmisc.richtext;

import static org.fxmisc.richtext.TwoDimensional.Bias.*;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;

import javafx.scene.control.IndexRange;

import org.fxmisc.richtext.TwoDimensional.Position;

public final class Paragraph<S> implements CharSequence {

    @SafeVarargs
    private static <T> List<T> list(T head, T... tail) {
        if(tail.length == 0) {
            return Collections.singletonList(head);
        } else {
            ArrayList<T> list = new ArrayList<>(1 + tail.length);
            list.add(head);
            for(T t: tail) list.add(t);
            return list;
        }
    }

    private final List<StyledText<S>> segments;
    private final Optional<LineTerminator> terminator;
    private final TwoLevelNavigator navigator;

    public Paragraph(String text, S style) {
        this(new StyledText<S>(text, style));
    }

    @SafeVarargs
    public Paragraph(StyledText<S> text, StyledText<S>... texts) {
        this(list(text, texts), Optional.empty());
    }

    private Paragraph(String text, S style, LineTerminator terminator) {
        this(text, style, Optional.of(terminator));
    }

    private Paragraph(String text, S style, Optional<LineTerminator> terminator) {
        this(new StyledText<>(text, style), terminator);
    }

    private Paragraph(StyledText<S> text, Optional<LineTerminator> terminator) {
        this(Arrays.asList(text), terminator);
    }

    private Paragraph(List<StyledText<S>> segments, LineTerminator terminator) {
        this(segments, Optional.of(terminator));
    }

    private Paragraph(List<StyledText<S>> segments, Optional<LineTerminator> terminator) {
        assert !segments.isEmpty();
        this.segments = segments;
        this.terminator = terminator;
        navigator = new TwoLevelNavigator(
                () -> segments.size(),
                i -> segments.get(i).length());
    }

    public List<StyledText<S>> getSegments() {
        return Collections.unmodifiableList(segments);
    }

    public Optional<LineTerminator> getLineTerminator() {
        return terminator;
    }

    private int length = -1;
    @Override
    public int length() {
        if(length == -1) {
            length = segments.stream().mapToInt(s -> s.length()).sum();
        }
        return length;
    }

    private int fullLength = -1;
    public int fullLength() {
        if(fullLength == -1) {
            fullLength = length() + terminator.map(LineTerminator::length).orElse(0);
        }
        return fullLength;
    }

    public boolean isTerminated() {
        return terminator.isPresent();
    }

    public Paragraph<S> terminate(LineTerminator terminator) {
        if(isTerminated()) {
            throw new IllegalStateException("Paragraph alread terminated.");
        }

        return new Paragraph<>(segments, terminator);
    }

    @Override
    public char charAt(int index) {
        Position pos = navigator.offsetToPosition(index, Forward);
        return segments.get(pos.getMajor()).charAt(pos.getMinor());
    }

    public String substring(int from, int to) {
        return fullText().substring(from, Math.min(to, fullLength()));
    }

    public String substring(int from) {
        return fullText().substring(from);
    }

    public Paragraph<S> concat(Paragraph<S> p) {
        if(isTerminated()) {
            throw new IllegalStateException("Cannot append to a paragraph with line terminator.");
        }

        if(length() == 0) {
            return p;
        }

        if(p.fullLength() == 0) {
            return this;
        }

        StyledText<S> left = segments.get(segments.size()-1);
        StyledText<S> right = p.segments.get(0);
        if(Objects.equals(left.getStyle(), right.getStyle())) {
            StyledText<S> segment = left.concat(right);
            List<StyledText<S>> segs = new ArrayList<>(segments.size() + p.segments.size() - 1);
            segs.addAll(segments.subList(0, segments.size()-1));
            segs.add(segment);
            segs.addAll(p.segments.subList(1, p.segments.size()));
            return new Paragraph<S>(segs, p.terminator);
        } else {
            List<StyledText<S>> segs = new ArrayList<>(segments.size() + p.segments.size());
            segs.addAll(segments);
            segs.addAll(p.segments);
            return new Paragraph<S>(segs, p.terminator);
        }
    }

    public Paragraph<S> concat(CharSequence str) {
        if(isTerminated()) {
            throw new IllegalStateException("Cannot append to a paragraph with line terminator.");
        }

        if(str.length() == 0) {
            return this;
        }

        List<StyledText<S>> segs = new ArrayList<>(segments);
        int lastIdx = segments.size() - 1;
        segs.set(lastIdx, segments.get(lastIdx).concat(str));
        return new Paragraph<S>(segs, Optional.empty());
    }

    public Paragraph<S> insert(int offset, CharSequence str) {
        if(offset < 0 || offset > length())
            throw new IndexOutOfBoundsException(String.valueOf(offset));

        Position pos = navigator.offsetToPosition(offset, Backward);
        int segIdx = pos.getMajor();
        int segPos = pos.getMinor();
        StyledText<S> seg = segments.get(segIdx);
        StyledText<S> replacement = seg.spliced(segPos, segPos, str);
        List<StyledText<S>> segs = new ArrayList<>(segments);
        segs.set(segIdx, replacement);
        return new Paragraph<S>(segs, terminator);
    }

    @Override
    public Paragraph<S> subSequence(int start, int end) {
        return trim(end).subSequence(start);
    }

    public Paragraph<S> trim(int length) {
        if(length >= fullLength()) {
            return this;
        } else if(length > length()) { // slicing the line terminator
            LineTerminator newTerminator = terminator.get().trim(length - length());
            return new Paragraph<>(segments, newTerminator);
        } else if(length == length()) { // cut off the line terminator
            return new Paragraph<>(segments, Optional.empty());
        } else { // length < length()
            Position pos = navigator.offsetToPosition(length, Backward);
            int segIdx = pos.getMajor();
            List<StyledText<S>> segs = new ArrayList<>(segIdx + 1);
            segs.addAll(segments.subList(0, segIdx));
            segs.add(segments.get(segIdx).subSequence(0, pos.getMinor()));
            return new Paragraph<S>(segs, Optional.empty());
        }
    }

    public Paragraph<S> subSequence(int start) {
        if(start < 0) {
            throw new IllegalArgumentException("start must not be negative (was: " + start + ")");
        } else if(start == 0) {
            return this;
        } else if(start < length()) {
            Position pos = navigator.offsetToPosition(start, Forward);
            int segIdx = pos.getMajor();
            List<StyledText<S>> segs = new ArrayList<>(segments.size() - segIdx);
            segs.add(segments.get(segIdx).subSequence(pos.getMinor()));
            segs.addAll(segments.subList(segIdx + 1, segments.size()));
            return new Paragraph<S>(segs, terminator);
        } else if(start < fullLength()) {
            LineTerminator newTerminator = terminator.get().subSequence(start - length());
            S lastStyle = segments.get(segments.size() - 1).getStyle();
            return new Paragraph<>("", lastStyle, newTerminator);
        } else if(isTerminated()) {
            throw new IndexOutOfBoundsException(start + " not in [0, " + fullLength() + ")");
        } else if(start == fullLength()) { // == length()
            S lastStyle = segments.get(segments.size() - 1).getStyle();
            return new Paragraph<>("", lastStyle);
        } else {
            throw new IndexOutOfBoundsException(start + " not in [0, " + length() + "]");
        }
    }

    public Paragraph<S> delete(int start, int end) {
        return trim(start).concat(subSequence(end));
    }

    public Paragraph<S> restyle(S style) {
        return new Paragraph<S>(toString(), style, terminator);
    }

    public Paragraph<S> restyle(int from, int to, S style) {
        if(from >= length()) {
            return this;
        } else {
            to = Math.min(to, length());
            Paragraph<S> left = subSequence(0, from);
            Paragraph<S> middle = new Paragraph<S>(substring(from, to), style);
            Paragraph<S> right = subSequence(to);
            return left.concat(middle).concat(right);
        }
    }

    public Paragraph<S> restyle(int from, StyleSpans<? extends S> styleSpans) {
        int len = styleSpans.length();
        if(styleSpans.equals(getStyleSpans(from, from + len))) {
            return this;
        }

        Paragraph<S> left = trim(from);
        Paragraph<S> right = subSequence(from + len);

        String middleString = substring(from, from + len);
        List<StyledText<S>> middleSegs = new ArrayList<>(styleSpans.getSpanCount());
        int offset = 0;
        for(StyleSpan<? extends S> span: styleSpans) {
            int end = offset + span.getLength();
            String text = middleString.substring(offset, end);
            middleSegs.add(new StyledText<>(text, span.getStyle()));
            offset = end;
        }
        Paragraph<S> middle = new Paragraph<>(middleSegs, Optional.empty());

        return left.concat(middle).concat(right);
    }

    /**
     * Returns the style of character with the given index.
     * If {@code charIdx < 0}, returns the style at the beginning of this paragraph.
     * If {@code charIdx >= this.length()}, returns the style at the end of this paragraph.
     */
    public S getStyleOfChar(int charIdx) {
        if(charIdx < 0) {
            return segments.get(0).getStyle();
        }

        Position pos = navigator.offsetToPosition(charIdx, Forward);
        return segments.get(pos.getMajor()).getStyle();
    }

    /**
     * Returns the style at the given position. That is the style of the
     * character immediately preceding {@code position}. If {@code position}
     * is 0, then the style of the first character (index 0) in this paragraph
     * is returned. If this paragraph is empty, then some style previously used
     * in this paragraph is returned.
     * If {@code position > this.length()}, then it is equivalent to
     * {@code position == this.length()}.
     *
     * <p>In other words, {@code getStyleAtPosition(p)} is equivalent to
     * {@code getStyleOfChar(p-1)}.
     */
    public S getStyleAtPosition(int position) {
        if(position < 0) {
            throw new IllegalArgumentException("Paragraph position cannot be negative (" + position + ")");
        }

        Position pos = navigator.offsetToPosition(position, Backward);
        return segments.get(pos.getMajor()).getStyle();
    }

    /**
     * Returns the range of homogeneous style that includes the given position.
     * If {@code position} points to a boundary between two styled ranges,
     * then the range preceding {@code position} is returned.
     */
    public IndexRange getStyleRangeAtPosition(int position) {
        Position pos = navigator.offsetToPosition(position, Backward);
        int start = position - pos.getMinor();
        int end = start + segments.get(pos.getMajor()).length();
        return new IndexRange(start, end);
    }

    public StyleSpans<S> getStyleSpans() {
        StyleSpansBuilder<S> builder = new StyleSpansBuilder<>(segments.size());
        for(StyledText<S> seg: segments) {
            builder.add(seg.getStyle(), seg.length());
        }
        return builder.create();
    }

    public StyleSpans<S> getStyleSpans(int from, int to) {
        Position start = navigator.offsetToPosition(from, Forward);
        Position end = to == from
                ? start
                : start.offsetBy(to - from, Backward);
        int startSegIdx = start.getMajor();
        int endSegIdx = end.getMajor();

        int n = endSegIdx - startSegIdx + 1;
        StyleSpansBuilder<S> builder = new StyleSpansBuilder<>(n);

        if(startSegIdx == endSegIdx) {
            StyledText<S> seg = segments.get(startSegIdx);
            builder.add(seg.getStyle(), to - from);
        } else {
            StyledText<S> startSeg = segments.get(startSegIdx);
            builder.add(startSeg.getStyle(), startSeg.length() - start.getMinor());

            for(int i = startSegIdx + 1; i < endSegIdx; ++i) {
                StyledText<S> seg = segments.get(i);
                builder.add(seg.getStyle(), seg.length());
            }

            StyledText<S> endSeg = segments.get(endSegIdx);
            builder.add(endSeg.getStyle(), end.getMinor());
        }

        return builder.create();
    }

    private String text = null;
    /**
     * Returns the string content of this paragraph,
     * excluding the line terminator.
     */
    @Override
    public String toString() {
        if(text == null) {
            StringBuilder sb = new StringBuilder(length());
            for(StyledText<S> seg: segments)
                sb.append(seg);
            text = sb.toString();
        }
        return text;
    }

    private String fullText = null;
    /**
     * Returns the string content of this paragraph,
     * including the line terminator.
     */
    public String fullText() {
        if(fullText == null) {
            if(isTerminated()) {
                fullText = toString() + terminator.get().asString();
            } else {
                fullText = toString();
            }
        }
        return fullText;
    }
}
TOP

Related Classes of org.fxmisc.richtext.Paragraph

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.