Package org.fxmisc.richtext

Source Code of org.fxmisc.richtext.StyledTextArea

package org.fxmisc.richtext;

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

import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.IntFunction;
import java.util.function.UnaryOperator;

import javafx.beans.InvalidationListener;
import javafx.beans.binding.Binding;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableIntegerValue;
import javafx.beans.value.ObservableStringValue;
import javafx.beans.value.ObservableValue;
import javafx.css.CssMetaData;
import javafx.css.Styleable;
import javafx.css.StyleableObjectProperty;
import javafx.geometry.Insets;
import javafx.geometry.Point2D;
import javafx.scene.Node;
import javafx.scene.control.Control;
import javafx.scene.control.IndexRange;
import javafx.scene.control.Skin;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.CornerRadii;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
import javafx.stage.PopupWindow;

import org.fxmisc.easybind.EasyBind;
import org.fxmisc.richtext.CssProperties.EditableProperty;
import org.fxmisc.richtext.CssProperties.FontProperty;
import org.fxmisc.richtext.skin.StyledTextAreaBehavior;
import org.fxmisc.richtext.skin.StyledTextAreaVisual;
import org.fxmisc.undo.UndoManager;
import org.fxmisc.undo.UndoManagerFactory;
import org.fxmisc.wellbehaved.skin.Skins;
import org.reactfx.EventStream;
import org.reactfx.Guard;
import org.reactfx.Guardian;
import org.reactfx.Indicator;
import org.reactfx.SuspendableEventStream;
import org.reactfx.inhibeans.collection.Collections;
import org.reactfx.inhibeans.collection.ObservableList;

import com.sun.javafx.Utils;

/**
* Text editing control. Accepts user input (keyboard, mouse) and
* provides API to assign style to text ranges. It is suitable for
* syntax highlighting and rich-text editors.
*
* <p>Subclassing is allowed to define the type of style, e.g. inline
* style or style classes.</p>
*
* <h3>Overriding keyboard shortcuts</h3>
*
* {@code StyledTextArea} comes with {@link #onKeyTypedProperty()} and
* {@link #onKeyPressedProperty()} handlers installed to handle keyboard input.
* Ordinary character input is handled by the {@code onKeyTyped} handler and
* control key combinations (including Enter and Tab) are handled by the
* {@code onKeyPressed} handler. To add or override some keyboard shortcuts,
* but keep the rest in place, you would combine the default event handler with
* a new one that adds or overrides some of the default key combinations. This
* is how to bind {@code Ctrl+S} to the {@code save()} operation:
* <pre>
* {@code
* import static javafx.scene.input.KeyCode.*;
* import static javafx.scene.input.KeyCombination.*;
* import static org.fxmisc.wellbehaved.event.EventPattern.*;
*
* import org.fxmisc.wellbehaved.event.EventHandlerHelper;
*
* EventHandler<? super KeyEvent> ctrlS = EventHandlerHelper
*         .on(keyPressed(S, CONTROL_DOWN)).act(event -> save())
*         .create();
*
* EventHandlerHelper.install(area.onKeyPressedProperty(), ctrlS);
* }
* </pre>
*
* @param <S> type of style that can be applied to text.
*/
public class StyledTextArea<S> extends Control
implements
        TextEditingArea<S>,
        EditActions<S>,
        ClipboardActions<S>,
        NavigationActions<S>,
        UndoActions<S>,
        TwoDimensional {

    /**
     * Index range [0, 0).
     */
    public static final IndexRange EMPTY_RANGE = new IndexRange(0, 0);


    /* ********************************************************************** *
     *                                                                        *
     * Properties                                                             *
     *                                                                        *
     * Properties affect behavior and/or appearance of this control.          *
     *                                                                        *
     * They are readable and writable by the client code and never change by  *
     * other means, i.e. they contain either the default value or the value   *
     * set by the client code.                                                *
     *                                                                        *
     * ********************************************************************** */

    // editable property
    private final BooleanProperty editable = new EditableProperty<>(this);
    @Override public final boolean isEditable() { return editable.get(); }
    @Override public final void setEditable(boolean value) { editable.set(value); }
    @Override public final BooleanProperty editableProperty() { return editable; }

    // wrapText property
    private final BooleanProperty wrapText = new SimpleBooleanProperty(this, "wrapText");
    @Override public final boolean isWrapText() { return wrapText.get(); }
    @Override public final void setWrapText(boolean value) { wrapText.set(value); }
    @Override public final BooleanProperty wrapTextProperty() { return wrapText; }

    // undo manager
    private UndoManager undoManager;
    @Override
    public UndoManager getUndoManager() { return undoManager; }
    @Override
    public void setUndoManager(UndoManagerFactory undoManagerFactory) {
        undoManager.close();
        undoManager = preserveStyle
                ? createRichUndoManager(undoManagerFactory)
                : createPlainUndoManager(undoManagerFactory);
    }

    // font property
    /**
     * The default font to use where font is not specified otherwise.
     */
    private final StyleableObjectProperty<Font> font = new FontProperty<>(this);
    public final StyleableObjectProperty<Font> fontProperty() { return font; }
    public final void setFont(Font value) { font.setValue(value); }
    public final Font getFont() { return font.getValue(); }

    private final ObjectProperty<PopupWindow> popupWindow = new SimpleObjectProperty<>();
    public void setPopupWindow(PopupWindow popup) { popupWindow.set(popup); }
    public PopupWindow getPopupWindow() { return popupWindow.get(); }
    public ObjectProperty<PopupWindow> popupWindowProperty() { return popupWindow; }

    @Deprecated
    public void setPopupAtCaret(PopupWindow popup) { popupWindow.set(popup); }
    @Deprecated
    public PopupWindow getPopupAtCaret() { return popupWindow.get(); }
    @Deprecated
    public ObjectProperty<PopupWindow> popupAtCaretProperty() { return popupWindow; }

    private final ObjectProperty<Point2D> popupAnchorOffset = new SimpleObjectProperty<>();
    public void setPopupAnchorOffset(Point2D offset) { popupAnchorOffset.set(offset); }
    public Point2D getPopupAnchorOffset() { return popupAnchorOffset.get(); }
    public ObjectProperty<Point2D> popupAnchorOffsetProperty() { return popupAnchorOffset; }

    private final ObjectProperty<UnaryOperator<Point2D>> popupAnchorAdjustment = new SimpleObjectProperty<>();
    public void setPopupAnchorAdjustment(UnaryOperator<Point2D> f) { popupAnchorAdjustment.set(f); }
    public UnaryOperator<Point2D> getPopupAnchorAdjustment() { return popupAnchorAdjustment.get(); }
    public ObjectProperty<UnaryOperator<Point2D>> popupAnchorAdjustmentProperty() { return popupAnchorAdjustment; }

    private final ObjectProperty<PopupAlignment> popupAlignment = new SimpleObjectProperty<>(CARET_TOP);
    public void setPopupAlignment(PopupAlignment pos) { popupAlignment.set(pos); }
    public PopupAlignment getPopupAlignment() { return popupAlignment.get(); }
    public ObjectProperty<PopupAlignment> popupAlignmentProperty() { return popupAlignment; }

    /**
     * Defines how long the mouse has to stay still over the text before a
     * {@link MouseOverTextEvent} of type {@code MOUSE_OVER_TEXT_BEGIN} is
     * fired on this text area. When set to {@code null}, no
     * {@code MouseOverTextEvent}s are fired on this text area.
     *
     * <p>Default value is {@code null}.
     */
    private final ObjectProperty<Duration> mouseOverTextDelay = new SimpleObjectProperty<>(null);
    public void setMouseOverTextDelay(Duration delay) { mouseOverTextDelay.set(delay); }
    public Duration getMouseOverTextDelay() { return mouseOverTextDelay.get(); }
    public ObjectProperty<Duration> mouseOverTextDelayProperty() { return mouseOverTextDelay; }

    private final ObjectProperty<IntFunction<? extends Node>> paragraphGraphicFactory = new SimpleObjectProperty<>(null);
    public void setParagraphGraphicFactory(IntFunction<? extends Node> factory) { paragraphGraphicFactory.set(factory); }
    public IntFunction<? extends Node> getParagraphGraphicFactory() { return paragraphGraphicFactory.get(); }
    public ObjectProperty<IntFunction<? extends Node>> paragraphGraphicFactoryProperty() { return paragraphGraphicFactory; }

    /**
     * Indicates whether the initial style should also be used for plain text
     * inserted into this text area. When {@code false}, the style immediately
     * preceding the insertion position is used. Default value is {@code false}.
     */
    public void setUseInitialStyleForInsertion(boolean value) { content.useInitialStyleForInsertion.set(value); }
    public boolean getUseInitialStyleForInsertion() { return content.useInitialStyleForInsertion.get(); }
    public BooleanProperty useInitialStyleForInsertionProperty() { return content.useInitialStyleForInsertion; }


    /* ********************************************************************** *
     *                                                                        *
     * Observables                                                            *
     *                                                                        *
     * Observables are "dynamic" (i.e. changing) characteristics of this      *
     * control. They are not directly settable by the client code, but change *
     * in response to user input and/or API actions.                          *
     *                                                                        *
     * ********************************************************************** */

    // text
    private final org.reactfx.inhibeans.binding.Binding<String> text;
    @Override public final String getText() { return text.getValue(); }
    @Override public final ObservableValue<String> textProperty() { return text; }

    // rich text
    @Override public final StyledDocument<S> getDocument() { return content.snapshot(); };

    // length
    private final org.reactfx.inhibeans.binding.IntegerBinding length;
    @Override public final int getLength() { return length.get(); }
    @Override public final ObservableIntegerValue lengthProperty() { return length; }

    // caret position
    private final IntegerProperty internalCaretPosition = new SimpleIntegerProperty(0);
    private final org.reactfx.inhibeans.binding.IntegerBinding caretPosition =
            org.reactfx.inhibeans.binding.IntegerBinding.wrap(internalCaretPosition);
    @Override public final int getCaretPosition() { return caretPosition.get(); }
    @Override public final ObservableIntegerValue caretPositionProperty() { return caretPosition; }

    // selection anchor
    private final org.reactfx.inhibeans.property.SimpleIntegerProperty anchor =
            new org.reactfx.inhibeans.property.SimpleIntegerProperty(0);
    @Override public final int getAnchor() { return anchor.get(); }
    @Override public final ObservableIntegerValue anchorProperty() { return anchor; }

    // selection
    private final ObjectProperty<IndexRange> internalSelection = new SimpleObjectProperty<>(EMPTY_RANGE);
    private final org.reactfx.inhibeans.binding.ObjectBinding<IndexRange> selection =
            org.reactfx.inhibeans.binding.ObjectBinding.wrap(internalSelection);
    @Override public final IndexRange getSelection() { return selection.getValue(); }
    @Override public final ObservableValue<IndexRange> selectionProperty() { return selection; }

    // selected text
    private final org.reactfx.inhibeans.binding.StringBinding selectedText;
    @Override public final String getSelectedText() { return selectedText.get(); }
    @Override public final ObservableStringValue selectedTextProperty() { return selectedText; }

    // current paragraph index
    private final org.reactfx.inhibeans.binding.IntegerBinding currentParagraph;
    @Override public final int getCurrentParagraph() { return currentParagraph.get(); }
    @Override public final ObservableIntegerValue currentParagraphProperty() { return currentParagraph; }

    // caret column
    private final org.reactfx.inhibeans.binding.IntegerBinding caretColumn;
    @Override public final int getCaretColumn() { return caretColumn.get(); }
    @Override public final ObservableIntegerValue caretColumnProperty() { return caretColumn; }

    // paragraphs
    private final ObservableList<Paragraph<S>> paragraphs;
    @Override public ObservableList<Paragraph<S>> getParagraphs() {
        return paragraphs;
    }

    // beingUpdated
    private final Indicator beingUpdated = new Indicator();
    public Indicator beingUpdatedProperty() { return beingUpdated; }
    public boolean isBeingUpdated() { return beingUpdated.isOn(); }


    /* ********************************************************************** *
     *                                                                        *
     * Event streams                                                          *
     *                                                                        *
     * ********************************************************************** */

    // text changes
    private final SuspendableEventStream<PlainTextChange> plainTextChanges;
    @Override
    public final EventStream<PlainTextChange> plainTextChanges() { return plainTextChanges; }

    // rich text changes
    private final SuspendableEventStream<RichTextChange<S>> richTextChanges;
    @Override
    public final EventStream<RichTextChange<S>> richChanges() { return richTextChanges; }


    /* ********************************************************************** *
     *                                                                        *
     * Private fields                                                         *
     *                                                                        *
     * ********************************************************************** */

    private Position selectionStart2D;
    private Position selectionEnd2D;

    /**
     * content model
     */
    private final EditableStyledDocument<S> content;

    /**
     * Style used by default when no other style is provided.
     */
    private final S initialStyle;

    /**
     * Style applicator used by the default skin.
     */
    private final BiConsumer<Text, S> applyStyle;

    /**
     * Indicates whether style should be preserved on undo/redo,
     * copy/paste and text move.
     * TODO: Currently, only undo/redo respect this flag.
     */
    private final boolean preserveStyle;

    private final Guardian omniGuardian;


    /* ********************************************************************** *
     *                                                                        *
     * Constructors                                                           *
     *                                                                        *
     * ********************************************************************** */

    /**
     * Creates a text area with empty text content.
     *
     * @param initialStyle style to use in places where no other style is
     * specified (yet).
     * @param applyStyle function that, given a {@link Text} node and
     * a style, applies the style to the text node. This function is
     * used by the default skin to apply style to text nodes.
     */
    public StyledTextArea(S initialStyle, BiConsumer<Text, S> applyStyle) {
        this(initialStyle, applyStyle, true);
    }

    public <C> StyledTextArea(S initialStyle, BiConsumer<Text, S> applyStyle,
            boolean preserveStyle) {
        this.initialStyle = initialStyle;
        this.applyStyle = applyStyle;
        this.preserveStyle = preserveStyle;
        content = new EditableStyledDocument<>(initialStyle);
        paragraphs = Collections.wrap(content.getParagraphs());

        text = org.reactfx.inhibeans.binding.Binding.wrap(content.textProperty());
        length = org.reactfx.inhibeans.binding.IntegerBinding.wrap(content.lengthProperty());
        plainTextChanges = content.plainTextChanges().pausable();
        richTextChanges = content.richChanges().pausable();

        undoManager = preserveStyle
                ? createRichUndoManager(UndoManagerFactory.unlimitedHistoryFactory())
                : createPlainUndoManager(UndoManagerFactory.unlimitedHistoryFactory());

        Binding<Position> caretPosition2D = EasyBind.map(internalCaretPosition,
                p -> content.offsetToPosition(p.intValue(), Forward));
        paragraphs.addListener((InvalidationListener) (obs -> caretPosition2D.invalidate()));

        currentParagraph = org.reactfx.inhibeans.binding.IntegerBinding.wrap(
                EasyBind.map(caretPosition2D, p -> p.getMajor()));
        caretColumn = org.reactfx.inhibeans.binding.IntegerBinding.wrap(
                EasyBind.map(caretPosition2D, p -> p.getMinor()));

        selectionStart2D = position(0, 0);
        selectionEnd2D = position(0, 0);
        internalSelection.addListener(obs -> {
            IndexRange sel = internalSelection.get();
            selectionStart2D = offsetToPosition(sel.getStart(), Forward);
            selectionEnd2D = sel.getLength() == 0
                    ? selectionStart2D
                    : selectionStart2D.offsetBy(sel.getLength(), Backward);
        });

        selectedText = new org.reactfx.inhibeans.binding.StringBinding() {
            { bind(internalSelection, content.textProperty()); }
            @Override protected String computeValue() {
                return content.getText(internalSelection.get());
            }
        };

        omniGuardian = Guardian.combine(
                beingUpdated, // must be first, to be the last one to release
                text,
                length,
                caretPosition,
                anchor,
                selection,
                selectedText,
                currentParagraph,
                caretColumn,

                // add streams after properties, to be released before them
                plainTextChanges::suspend,
                richTextChanges::suspend,

                // paragraphs to be released first
                paragraphs);

        this.setBackground(new Background(new BackgroundFill(Color.WHITE, CornerRadii.EMPTY, Insets.EMPTY)));
        getStyleClass().add("styled-text-area");
    }


    /* ********************************************************************** *
     *                                                                        *
     * Queries                                                                *
     *                                                                        *
     * Queries are parameterized observables.                                 *
     *                                                                        *
     * ********************************************************************** */

    @Override
    public final String getText(int start, int end) {
        return content.getText(start, end);
    }

    @Override
    public String getText(int paragraph) {
        return paragraphs.get(paragraph).toString();
    }

    public Paragraph<S> getParagraph(int index) {
        return paragraphs.get(index);
    }

    @Override
    public StyledDocument<S> subDocument(int start, int end) {
        return content.subSequence(start, end);
    }

    @Override
    public StyledDocument<S> subDocument(int paragraphIndex) {
        return content.subDocument(paragraphIndex);
    }

    /**
     * Returns the selection range in the given paragraph.
     */
    public IndexRange getParagraphSelection(int paragraph) {
        int startPar = selectionStart2D.getMajor();
        int endPar = selectionEnd2D.getMajor();

        if(paragraph < startPar || paragraph > endPar) {
            return EMPTY_RANGE;
        }

        int start = paragraph == startPar ? selectionStart2D.getMinor() : 0;
        int end = paragraph == endPar ? selectionEnd2D.getMinor() : paragraphs.get(paragraph).length();

        // force selectionProperty() to be valid
        getSelection();

        return new IndexRange(start, end);
    }

    /**
     * Returns the style of the character with the given index.
     * If {@code index} points to a line terminator character,
     * the last style used in the paragraph terminated by that
     * line terminator is returned.
     */
    public S getStyleOfChar(int index) {
        return content.getStyleOfChar(index);
    }

    /**
     * Returns the style at the given position. That is the style of the
     * character immediately preceding {@code position}, except when
     * {@code position} points to a paragraph boundary, in which case it
     * is the style at the beginning of the latter paragraph.
     *
     * <p>In other words, most of the time {@code getStyleAtPosition(p)}
     * is equivalent to {@code getStyleOfChar(p-1)}, except when {@code p}
     * points to a paragraph boundary, in which case it is equivalent to
     * {@code getStyleOfChar(p)}.
     */
    public S getStyleAtPosition(int position) {
        return content.getStyleAtPosition(position);
    }

    /**
     * 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. If {@code position}
     * points to a boundary between two paragraphs, then the first styled range
     * of the latter paragraph is returned.
     */
    public IndexRange getStyleRangeAtPosition(int position) {
        return content.getStyleRangeAtPosition(position);
    }

    /**
     * Returns the styles in the given character range.
     */
    public StyleSpans<S> getStyleSpans(int from, int to) {
        return content.getStyleSpans(from, to);
    }

    /**
     * Returns the styles in the given character range.
     */
    public StyleSpans<S> getStyleSpans(IndexRange range) {
        return getStyleSpans(range.getStart(), range.getEnd());
    }

    /**
     * Returns the style of the character with the given index in the given
     * paragraph. If {@code index} is beyond the end of the paragraph, the
     * style at the end of line is returned. If {@code index} is negative, it
     * is the same as if it was 0.
     */
    public S getStyleOfChar(int paragraph, int index) {
        return content.getStyleOfChar(paragraph, index);
    }

    /**
     * Returns the style at the given position in the given paragraph.
     * This is equivalent to {@code getStyleOfChar(paragraph, position-1)}.
     */
    public S getStyleAtPosition(int paragraph, int position) {
        return content.getStyleOfChar(paragraph, position);
    }

    /**
     * Returns the range of homogeneous style that includes the given position
     * in the given paragraph. If {@code position} points to a boundary between
     * two styled ranges, then the range preceding {@code position} is returned.
     */
    public IndexRange getStyleRangeAtPosition(int paragraph, int position) {
        return content.getStyleRangeAtPosition(paragraph, position);
    }

    /**
     * Returns styles of the whole paragraph.
     */
    public StyleSpans<S> getStyleSpans(int paragraph) {
        return content.getStyleSpans(paragraph);
    }

    /**
     * Returns the styles in the given character range of the given paragraph.
     */
    public StyleSpans<S> getStyleSpans(int paragraph, int from, int to) {
        return content.getStyleSpans(paragraph, from, to);
    }

    /**
     * Returns the styles in the given character range of the given paragraph.
     */
    public StyleSpans<S> getStyleSpans(int paragraph, IndexRange range) {
        return getStyleSpans(paragraph, range.getStart(), range.getEnd());
    }

    @Override
    public Position position(int row, int col) {
        return content.position(row, col);
    }

    @Override
    public Position offsetToPosition(int charOffset, Bias bias) {
        return content.offsetToPosition(charOffset, bias);
    }


    /* ********************************************************************** *
     *                                                                        *
     * Actions                                                                *
     *                                                                        *
     * Actions change the state of this control. They typically cause a       *
     * change of one or more observables and/or produce an event.             *
     *                                                                        *
     * ********************************************************************** */

    /**
     * Sets style for the given character range.
     */
    public void setStyle(int from, int to, S style) {
        try(Guard g = omniGuardian.guard()) {
            content.setStyle(from, to, style);
        }
    }

    /**
     * Sets style for the whole paragraph.
     */
    public void setStyle(int paragraph, S style) {
        try(Guard g = omniGuardian.guard()) {
            content.setStyle(paragraph, style);
        }
    }

    /**
     * Sets style for the given range relative in the given paragraph.
     */
    public void setStyle(int paragraph, int from, int to, S style) {
        try(Guard g = omniGuardian.guard()) {
            content.setStyle(paragraph, from, to, style);
        }
    }

    /**
     * Set multiple style ranges at once. This is equivalent to
     * <pre>
     * for(StyleSpan{@code <S>} span: styleSpans) {
     *     setStyle(from, from + span.getLength(), span.getStyle());
     *     from += span.getLength();
     * }
     * </pre>
     * but the actual implementation is more efficient.
     */
    public void setStyleSpans(int from, StyleSpans<? extends S> styleSpans) {
        try(Guard g = omniGuardian.guard()) {
            content.setStyleSpans(from, styleSpans);
        }
    }

    /**
     * Set multiple style ranges of a paragraph at once. This is equivalent to
     * <pre>
     * for(StyleSpan{@code <S>} span: styleSpans) {
     *     setStyle(paragraph, from, from + span.getLength(), span.getStyle());
     *     from += span.getLength();
     * }
     * </pre>
     * but the actual implementation is more efficient.
     */
    public void setStyleSpans(int paragraph, int from, StyleSpans<? extends S> styleSpans) {
        try(Guard g = omniGuardian.guard()) {
            content.setStyleSpans(paragraph, from, styleSpans);
        }
    }

    /**
     * Resets the style of the given range to the initial style.
     */
    public void clearStyle(int from, int to) {
        setStyle(from, to, initialStyle);
    }

    /**
     * Resets the style of the given paragraph to the initial style.
     */
    public void clearStyle(int paragraph) {
        setStyle(paragraph, initialStyle);
    }

    /**
     * Resets the style of the given range in the given paragraph
     * to the initial style.
     */
    public void clearStyle(int paragraph, int from, int to) {
        setStyle(paragraph, from, to, initialStyle);
    }

    @Override
    public void replaceText(int start, int end, String text) {
        try(Guard g = omniGuardian.guard()) {
            start = Utils.clamp(0, start, getLength());
            end = Utils.clamp(0, end, getLength());

            content.replaceText(start, end, text);

            int newCaretPos = start + text.length();
            selectRange(newCaretPos, newCaretPos);
        }
    }

    @Override
    public void replace(int start, int end, StyledDocument<S> replacement) {
        try(Guard g = omniGuardian.guard()) {
            start = Utils.clamp(0, start, getLength());
            end = Utils.clamp(0, end, getLength());

            content.replace(start, end, replacement);

            int newCaretPos = start + replacement.length();
            selectRange(newCaretPos, newCaretPos);
        }
    }

    @Override
    public void selectRange(int anchor, int caretPosition) {
        try(Guard g = guard(this.caretPosition, currentParagraph, caretColumn, this.anchor, selection, selectedText)) {
            this.internalCaretPosition.set(Utils.clamp(0, caretPosition, getLength()));
            this.anchor.set(Utils.clamp(0, anchor, getLength()));
            this.internalSelection.set(IndexRange.normalize(getAnchor(), getCaretPosition()));
        }
    }

    @Override
    public void positionCaret(int pos) {
        try(Guard g = guard(caretPosition, currentParagraph, caretColumn)) {
            internalCaretPosition.set(pos);
        }
    }


    /* ********************************************************************** *
     *                                                                        *
     * Look &amp; feel                                                        *
     *                                                                        *
     * ********************************************************************** */

    @Override
    protected Skin<?> createDefaultSkin() {
        return Skins.<StyledTextArea<S>, StyledTextAreaVisual<S>>createSimpleSkin(
                this,
                area -> new StyledTextAreaVisual<>(area, applyStyle),
                StyledTextAreaBehavior::new);
    }

    @Override
    public List<CssMetaData<? extends Styleable, ?>> getControlCssMetaData() {
        List<CssMetaData<? extends Styleable, ?>> superMetaData = super.getControlCssMetaData();
        List<CssMetaData<? extends Styleable, ?>> myMetaData = Arrays.<CssMetaData<? extends Styleable, ?>>asList(
                font.getCssMetaData());
        List<CssMetaData<? extends Styleable, ?>> res = new ArrayList<>(superMetaData.size() + myMetaData.size());
        res.addAll(superMetaData);
        res.addAll(myMetaData);
        return res;
    }


    /* ********************************************************************** *
     *                                                                        *
     * Private methods                                                        *
     *                                                                        *
     * ********************************************************************** */

    private UndoManager createPlainUndoManager(UndoManagerFactory factory) {
        Consumer<PlainTextChange> apply = change -> replaceText(change.getPosition(), change.getPosition() + change.getRemoved().length(), change.getInserted());
        Consumer<PlainTextChange> undo = change -> replaceText(change.getPosition(), change.getPosition() + change.getInserted().length(), change.getRemoved());
        BiFunction<PlainTextChange, PlainTextChange, Optional<PlainTextChange>> merge = (change1, change2) -> change1.mergeWith(change2);
        return factory.create(plainTextChanges(), apply, undo, merge);
    }

    private UndoManager createRichUndoManager(UndoManagerFactory factory) {
        Consumer<RichTextChange<S>> apply = change -> replace(change.getPosition(), change.getPosition() + change.getRemoved().length(), change.getInserted());
        Consumer<RichTextChange<S>> undo = change -> replace(change.getPosition(), change.getPosition() + change.getInserted().length(), change.getRemoved());
        BiFunction<RichTextChange<S>, RichTextChange<S>, Optional<RichTextChange<S>>> merge = (change1, change2) -> change1.mergeWith(change2);
        return factory.create(richChanges(), apply, undo, merge);
    }

    private Guard guard(Guardian... guardians) {
        return Guardian.combine(beingUpdated, Guardian.combine(guardians)).guard();
    }
}
TOP

Related Classes of org.fxmisc.richtext.StyledTextArea

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.