All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.fxmisc.richtext.util.UndoUtils Maven / Gradle / Ivy

The newest version!
package org.fxmisc.richtext.util;

import org.fxmisc.richtext.GenericStyledArea;
import org.fxmisc.richtext.MultiChangeBuilder;
import org.fxmisc.richtext.model.PlainTextChange;
import org.fxmisc.richtext.model.RichTextChange;
import org.fxmisc.richtext.model.TextChange;
import org.fxmisc.undo.UndoManager;
import org.fxmisc.undo.UndoManagerFactory;
import org.fxmisc.undo.impl.MultiChangeUndoManagerImpl;
import org.fxmisc.undo.impl.UnlimitedChangeQueue;
import org.reactfx.SuspendableYes;
import org.reactfx.value.Val;

import javafx.beans.value.ObservableBooleanValue;

import java.time.Duration;
import java.util.List;
import java.util.function.Consumer;

/**
 * A class filled with factory methods to help easily construct an {@link UndoManager} for a {@link GenericStyledArea}.
 */
public final class UndoUtils {

    private UndoUtils() {
        throw new IllegalStateException("UndoUtils cannot be instantiated");
    }

    public static final Duration DEFAULT_PREVENT_MERGE_DELAY = Duration.ofMillis(500);

    /**
     * Constructs an UndoManager with an unlimited history:
     * if {@link GenericStyledArea#isPreserveStyle() the area's preserveStyle flag is true}, the returned UndoManager
     * can undo/redo multiple {@link RichTextChange}s; otherwise, it can undo/redo multiple {@link PlainTextChange}s.
     */
    public static  UndoManager defaultUndoManager(GenericStyledArea area) {
        return area.isPreserveStyle()
                ? richTextUndoManager(area)
                : plainTextUndoManager(area);
    }

    /**
     * Constructs an UndoManager with no history
     */
    public static UndoManager noOpUndoManager() {
        return new UndoManager() {

            private final Val alwaysFalse = Val.constant(false);

            @Override public boolean undo() { return false; }
            @Override public boolean redo() { return false; }
            @Override public Val undoAvailableProperty() { return alwaysFalse; }
            @Override public boolean isUndoAvailable() { return false; }
            @Override public Val redoAvailableProperty() { return alwaysFalse; }
            @Override public boolean isRedoAvailable() { return false; }
            @Override public boolean isPerformingAction() { return false; }
            @Override public boolean isAtMarkedPosition() { return false; }

            // not sure whether these may throw NPEs at some point
            @Override public Val nextUndoProperty() { return null; }
            @Override public Val nextRedoProperty() { return null; }
            @Override public ObservableBooleanValue performingActionProperty() { return null; }
            @Override public UndoPosition getCurrentPosition() { return null; }
            @Override public ObservableBooleanValue atMarkedPositionProperty() { return null; }

            // ignore these
            @Override public void preventMerge() { }
            @Override public void forgetHistory() { }
            @Override public void close() { }
        };
    }

    /* ********************************************************************** *
     *                                                                        *
     * UndoManager Factory Methods                                            *
     *                                                                        *
     * Code that constructs different kinds of UndoManagers for an area       *
     *                                                                        *
     * ********************************************************************** */

    /**
     * Returns an UndoManager with an unlimited history that can undo/redo {@link RichTextChange}s. New changes
     * emitted from the stream will not be merged with the previous change
     * after {@link #DEFAULT_PREVENT_MERGE_DELAY}
     */
    public static  UndoManager>> richTextUndoManager(
            GenericStyledArea area) {
        return richTextUndoManager(area, UndoManagerFactory.unlimitedHistoryFactory());
    }

    /**
     * Returns an UndoManager that can undo/redo {@link RichTextChange}s. New changes
     * emitted from the stream will not be merged with the previous change
     * after {@code preventMergeDelay}
     */
    public static  UndoManager>> richTextUndoManager(
            GenericStyledArea area, Duration preventMergeDelay) {
        return richTextUndoManager(area, UndoManagerFactory.unlimitedHistoryFactory(), preventMergeDelay);
    };

    /**
     * Returns an UndoManager that can undo/redo {@link RichTextChange}s. New changes
     * emitted from the stream will not be merged with the previous change
     * after {@link #DEFAULT_PREVENT_MERGE_DELAY}
     */
    public static  UndoManager>> richTextUndoManager(
            GenericStyledArea area, UndoManagerFactory factory) {
        return richTextUndoManager(area, factory, DEFAULT_PREVENT_MERGE_DELAY);
    };

    /**
     * Returns an UndoManager that can undo/redo {@link RichTextChange}s. New changes
     * emitted from the stream will not be merged with the previous change
     * after {@code preventMergeDelay}
     */
    public static  UndoManager>> richTextUndoManager(
            GenericStyledArea area, UndoManagerFactory factory, Duration preventMergeDelay) {
        return factory.createMultiChangeUM(area.multiRichChanges(),
                TextChange::invert,
                applyMultiRichTextChange(area),
                TextChange::mergeWith,
                TextChange::isIdentity,
                preventMergeDelay);
    };

    /**
     * Returns an UndoManager with an unlimited history that can undo/redo {@link RichTextChange}s. New changes
     * emitted from the stream will not be merged with the previous change after {@link #DEFAULT_PREVENT_MERGE_DELAY}
     * 

Note: that only styling changes may occur during suspension of the undo manager. */ public static UndoManager>> richTextSuspendableUndoManager( GenericStyledArea area, SuspendableYes suspendUndo) { return richTextSuspendableUndoManager(area, DEFAULT_PREVENT_MERGE_DELAY, suspendUndo); } /** * Returns an UndoManager with an unlimited history that can undo/redo {@link RichTextChange}s. New changes * emitted from the stream will not be merged with the previous change after {@code preventMergeDelay}. *

Note: that only styling changes may occur during suspension of the undo manager. */ public static UndoManager>> richTextSuspendableUndoManager( GenericStyledArea area, Duration preventMergeDelay, SuspendableYes suspendUndo) { RichTextChange.skipStyleComparison( true ); return new MultiChangeUndoManagerImpl<> ( new UnlimitedChangeQueue<>(), TextChange::invert, applyMultiRichTextChange(area), TextChange::mergeWith, TextChange::isIdentity, area.multiRichChanges().conditionOn(suspendUndo), preventMergeDelay ); }; /** * Returns an UndoManager with an unlimited history that can undo/redo {@link PlainTextChange}s. New changes * emitted from the stream will not be merged with the previous change * after {@link #DEFAULT_PREVENT_MERGE_DELAY} */ public static UndoManager> plainTextUndoManager( GenericStyledArea area) { return plainTextUndoManager(area, DEFAULT_PREVENT_MERGE_DELAY); } /** * Returns an UndoManager that can undo/redo {@link PlainTextChange}s. New changes * emitted from the stream will not be merged with the previous change * after {@code preventMergeDelay} */ public static UndoManager> plainTextUndoManager( GenericStyledArea area, Duration preventMergeDelay) { return plainTextUndoManager(area, UndoManagerFactory.unlimitedHistoryFactory(), preventMergeDelay); } /** * Returns an UndoManager that can undo/redo {@link PlainTextChange}s. New changes * emitted from the stream will not be merged with the previous change * after {@link #DEFAULT_PREVENT_MERGE_DELAY} */ public static UndoManager> plainTextUndoManager( GenericStyledArea area, UndoManagerFactory factory) { return plainTextUndoManager(area, factory, DEFAULT_PREVENT_MERGE_DELAY); } /** * Returns an UndoManager that can undo/redo {@link PlainTextChange}s. New changes * emitted from the stream will not be merged with the previous change * after {@code preventMergeDelay} */ public static UndoManager> plainTextUndoManager( GenericStyledArea area, UndoManagerFactory factory, Duration preventMergeDelay) { return factory.createMultiChangeUM(area.multiPlainChanges(), TextChange::invert, applyMultiPlainTextChange(area), TextChange::mergeWith, TextChange::isIdentity, preventMergeDelay); } /* ********************************************************************** * * * * Change Appliers * * * * Code that handles how a change should be applied to the area * * * * ********************************************************************** */ /** * Applies a {@link PlainTextChange} to the given area when the {@link UndoManager}'s change stream emits an event * by {@code area.replaceText(change.getPosition(), change.getRemovalEnd(), change.getInserted()}. */ public static Consumer applyPlainTextChange(GenericStyledArea area) { return change -> { area.replaceText(change.getPosition(), change.getRemovalEnd(), change.getInserted()); moveToChange( area, change ); }; } /** * Applies a {@link RichTextChange} to the given area when the {@link UndoManager}'s change stream emits an event * by {@code area.replace(change.getPosition(), change.getRemovalEnd(), change.getInserted()}. */ public static Consumer> applyRichTextChange( GenericStyledArea area) { return change -> { area.replace(change.getPosition(), change.getRemovalEnd(), change.getInserted()); moveToChange( area, change ); }; } /** * Applies a list of {@link PlainTextChange}s to the given area when the {@link UndoManager}'s change stream emits * an event by {@code area.replaceAbsolutely(change.getPosition(), change.getRemovalEnd(), change.getInserted()}. */ public static Consumer> applyMultiPlainTextChange( GenericStyledArea area) { return changeList -> { MultiChangeBuilder builder = area.createMultiChange(changeList.size()); for (PlainTextChange c : changeList) { builder.replaceTextAbsolutely(c.getPosition(), c.getRemovalEnd(), c.getInserted()); } builder.commit(); moveToChange( area, changeList.get( changeList.size()-1 ) ); }; } /** * Applies a list of {@link RichTextChange} to the given area when the {@link UndoManager}'s change stream emits * an event by {@code area.replaceAbsolutely(change.getPosition(), change.getRemovalEnd(), change.getInserted()}. */ public static Consumer>> applyMultiRichTextChange( GenericStyledArea area) { return changeList -> { MultiChangeBuilder builder = area.createMultiChange(changeList.size()); for (RichTextChange c : changeList) { builder.replaceAbsolutely(c.getPosition(), c.getRemovalEnd(), c.getInserted()); } builder.commit(); moveToChange( area, changeList.get( changeList.size()-1 ) ); }; } /* * Address #912 "After undo/redo, new text is inserted at the end". * Without breaking PositionTests. (org.fxmisc.richtext.api.caret) */ private static void moveToChange( GenericStyledArea area, TextChange chg ) { int pos = chg.getPosition(); int len = chg.getNetLength(); if ( len > 0 ) pos += len; area.moveTo( Math.min( pos, area.getLength() ) ); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy