/UndoFX

Undo manager for JavaFX

Primary LanguageJavaBSD 2-Clause "Simplified" LicenseBSD-2-Clause

UndoFX

UndoFX is a general-purpose undo manager for JavaFX (or Java applications in general).

Highlights

Arbitrary type of change objects. Change objects don't have to implement any special interface, such as UndoableEdit in Swing.

No requirements on the API of the control. To add undo support for a control, you don't need the control to have a special API, such as addUndoableEditListener(UndoableEditListener) in Swing. This means you can add undo support to components that were not designed with undo support in mind, as long as you are able to observe changes on the component and the component provides API (of any sort) to reverse and reapply the effects of the changes.

Immutable change objects are encouraged. In contrast, UndoableEdit from Swing is mutable by design.

Suppports merging of successive changes.

Supports marking a state (position in the history) when the document was last saved.

API

public interface UndoManager {
    boolean undo();
    boolean redo();

    ObservableBooleanValue undoAvailableProperty();
    boolean isUndoAvailable();

    ObservableBooleanValue redoAvailableProperty();
    boolean isRedoAvailable();

    void preventMerge();

    void forgetHistory();

    void mark();
    UndoPosition getCurrentPosition();

    ObservableBooleanValue atMarkedPositionProperty();
    boolean isAtMarkedPosition();

    void close();

    interface UndoPosition {
        void mark();
        boolean isValid();
    }
}

undo() undoes the most recent change, if there is any change to undo. Returns true if a change was undone, false otherwise.

redo() reapplies a previously undone change, if there is any change to reapply. Returns true if a change was reapplied, false otherwise.

undoAvailable and redoAvailable properties indicate whether there is a change to be undone or redone, respectively.

preventMerge() explicitly prevents the next (upcoming) change from being merged with the latest one.

forgetHistory() forgets all changes prior to the current position in the change history.

mark() sets a mark at the current position in the change history. This is meant to be used when the document is saved.

getCurrentPosition() returns a handle to the current position in the change history. This handle can be used to mark this position later, even if it is not the current position anymore.

atMarkedPosition property indicates whether the mark is set on the current position in the change history. This can be used to tell whether there are any unsaved changes.

close() stops observing change events and should be called when the UndoManager is not used anymore to prevent leaks.

Getting an UndoManager instance

To get an instance of UndoManager you need:

  • a stream of change events.
  • a function to invert a change.
  • a function to apply a change. This function must reinsert its parameter into the stream of change events.
  • optionally, a function to merge two subsequent changes into a single change. The first parameter is the old change, and the second parameter is the newer change.

The stream of change events is a ReactFX EventStream. For an example of how you can construct one, have a look at the source code of the demo below.

The invert, apply, and merge functions are all instances of functional interfaces from JDK8, and thus can be instantiated using lambda expressions.

You also need to make sure that your change objects properly implement equals.

Once you have all these, you can use one of the factory methods from UndoManagerFactory to get an instance.

EventStream<MyChange> changes = ...;
UndoManager undoManager = UndoManagerFactory.unlimitedHistoryUndoManager(
        changes,
        change -> invertChange(change),
        change -> applyChange(change),
        (c1, c2) -> mergeChanges(c1, c2));

Demo

This demo lets the user change the color, radius and position of a circle, and subsequently undo and redo the performed changes.

Multiple changes of one property in a row are merged together, so, for example, multiple radius changes in a row are tracked as one change.

There is also a "Save" button that fakes a save operation. It is enabled only when changes have been made or undone since the last save.

Screenshot of the CircleProperties demo

Run from the pre-built JAR

Download the pre-built "fat" JAR file and run

java -cp undofx-demos-fat-2.1.0.jar org.fxmisc.undo.demo.CircleProperties

Run from the source repo

gradle CircleProperties

Source code

CircleProperties.java. See the highlighted lines for the gist of how the undo functionality is set up.

Requirements

JDK8

Dependencies

ReactFX. If you don't use Maven/Gradle/Sbt/Ivy to manage your dependencies, you will have to either place the ReactFX JAR on the classpath, or download the UndoFX fat JAR (see below) that has ReactFX included.

Use UndoFX in your project

Maven coordinates

Group ID Artifact ID Version
org.fxmisc.undo undofx 2.1.1

Gradle example

dependencies {
    compile group: 'org.fxmisc.undo', name: 'undofx', version: '2.1.1'
}

Sbt example

libraryDependencies += "org.fxmisc.undo" % "undofx" % "2.1.1"

Manual download

Download the JAR file or the fat JAR file (including dependencies) and place it on your classpath.

License

BSD 2-Clause License

Links

API Documentation (Javadoc)