Undo manager for JavaFX

Related tags

GUI javafx undo-redo
Overview

This project is no longer being maintained. See this issue for more details.

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; and
  • optionally, a function to optionally merge two subsequent changes into a single 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.0

Gradle example

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

Sbt example

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

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)

Comments
  • Nonlinear Undo/Redo

    Nonlinear Undo/Redo

    WIP

    Here's the flowchart of the system that is read from left to right. A line of code (or pseudo code) is an orange box. Black arrows point from an orange box to its actual method's implementation. Method implementations, if/else blocks, and for loops are in groups. Finally, red lines indicate the order in which the code is run. nonlinear undo - flowchart

    And here's a better version read top to down. nonlinear undo - flowchart top-down

    I'm now working on implementing updateChangesPostUndoBubble in DocumentDAG. However, I just realized that I will probably need to change the way the NonlinearUndoManager attempts to merge a previously done change. I'm not yet sure how that will affect everything else.

    opened by JordanMartinez 27
  • UndoManager#forgetHistory should invalidate undoAvailable, redoAvailable, etc.

    UndoManager#forgetHistory should invalidate undoAvailable, redoAvailable, etc.

    If forgetHistory discards all previous history, should it invalidate the undoAvailable and redoAvailable properties? Also, should it automatically update the marked position to ensure it isn't pointing to some old, forgotten position?

    opened by ryanjaeb 11
  • Support for inspecting the change queue.

    Support for inspecting the change queue.

    It would be nice if changes could be named so that an undo / redo UI could display the name of the last change that will be undone or redone, e.g. "Undo 'Move Object'".

    opened by dlemmermann 9
  • Add Feature: UndoManager can ignore a change if it is an identity / empty (data) change

    Add Feature: UndoManager can ignore a change if it is an identity / empty (data) change

    Coming from TomasMikula/RichTextFX#486 (more specifically, Tomas comment on how to solve an issue):

    Another option is to give the UndoManager a way to know whether merge resulted in an empty change. This could be done for example by adding an extra parameter Predicate<C> isEmpty to the factory method create above. Undo manager would then test changes for emptiness and automatically discard empty changes without trying to apply them.

    opened by JordanMartinez 5
  • Maven Artifact has a corrupt dependency to react.fx

    Maven Artifact has a corrupt dependency to react.fx

    As you can see here:

    http://search.maven.org/#artifactdetails%7Corg.fxmisc.undo%7Cundofx%7C1.0.0%7Cjar

    The defined dependency:

    <dependency>
          <groupId>org.reactfx</groupId>
          <artifactId>reactfx</artifactId>
          <version>1.0+</version>
          <scope>compile</scope>
    </dependency>
    

    Has a plus sign which makes the dependency corrupt.

    opened by sialcasa 5
  • Dynamic event stream for UndoManager

    Dynamic event stream for UndoManager

    I was wondering if it is possible to add and remove event streams from the stream that is being used by the UndoManager. In my use case the user is creating a flow diagram and keeps adding nodes to it. Each node has an x and a y property. I want to "monitor" these coordinates via the help of two event streams and I want to add them to the event stream of the undo manager without loosing the current history. How can I do this?

    opened by dlemmermann 4
  • Non linear undo redo

    Non linear undo redo

    Rather than trying to store all changes in the graph and using the queues as proxy (my approach in #10), I decided to store the changes in the queues and use the graph to store the edges and the queues themselves. This way, the queues feel more queue-like and all three types of queues can be used in the same non-linear undo graph: an unlimited, a fixed-size, and a zero-size queue.

    opened by JordanMartinez 3
  • Change return ObservableBooleanValue to BooleanExpression or BooleanBinding

    Change return ObservableBooleanValue to BooleanExpression or BooleanBinding

    Can you change the return results for undoAvailableProperty and redoAvailableProperty to BooleanExpression or BooleanBinding? This would simplify binding Button.disableProperty().bind(UndoManager.undoAvailableProperty().not()) in RichTextFX

    opened by ruckc 3
  • Nonlinear undo

    Nonlinear undo

    This PR is the same as the previous one (#11), just starting from the latest commit and streamlined to remove my earlier mistakes. It does not yet include the NonlinearUndoManagerFactory because I want to work on implementing the FixedSizeNonlinearChangeQueue.

    opened by JordanMartinez 2
  • Non linear undo

    Non linear undo

    This isn't ready to be merged, but gives you an idea on how I'm trying to implement this.

    I still have a few things to figure out:

    • How does Current Position get updated now that its position is relative to other changes being added/forgotten/undone/redone?
      • A NonLinearChangeQueue needs two lists: the first for tracking all of the changes it has added (for forgetHistory()) and the second for tracking which undos/redos it can currently undo/redo. Every time a queue undos/redos/adds (and forgets?) a change, all other queue's 2nd list needs to be updated.
    • How should FixedSizeQueues be handled? The items to consider are the above situation and the creation of a new edit when a "bubbly" edit is split into two parts, the 'buried' edit that takes the place of the original, and the new 'bubbled' edit that gets put on the top of the graph?
    • How should NonLinearChangeQueue#equals method be implemented? Currently, I'm using an int ID that can be set by the NonLinearUndoManagerFactory, but I am not sure if another approach would be better.
    • How should the list of undoable/redoable changes be determined? This will use functional programming in some way
    • What is the timeline of updates (e.g. adding, undoing/redoing, forgetting history, etc.)?
    • Does the current approach insure that new updates are sorted accordingly? (where the first edit in either of the two lists in NonLinearChangeQueue was created before the next one)
    opened by JordanMartinez 2
  • [Question] Unexpected change received?

    [Question] Unexpected change received?

    This is probably me not understanding the changes in 1.2, but when I undo an action, then redo it, and then try to take a new action, the undo manager crashes with an Unexpected change received.

    Am I supposed to send redone changes to the change stream as well?

    opened by benedictleejh 2
  • mark(); isAtMarkedPosition(); issue

    mark(); isAtMarkedPosition(); issue

    Hello,

    I am experimenting with mark(); and isAtMarkedPosition();

    I type in some text let's say word "hello" and set the mark(); position at that point. When I type an extra character (it takes me to a different step) and do control+z to undo, the isAtMarkedPosition() returns false (it should be returning true). Also, when I type in a character and remove that character, then it also isAtMarkedPosition() returns false. The only time when isAtMarkedPosition(); returns false is when you type in "hello", mark();, and then do undo (control + z) and then do redo (control+shift+z). In all other cases, it returns false.

    opened by TheAndreiM 1
  • Compile fails in Scala 2.11.7 project

    Compile fails in Scala 2.11.7 project

    I'm using Scala 2.11.7 with UndoFX in my project, and when I try to compile, the compile fails with assertion failed: TVar<?0=null>.

    This only happens with UndoFX 1.2, and it was fine before on UndoFX 1.1.1, and the only changes I've made are to comply with 1.2's new interface. I've even changed from using SAM conversion to instantiate the undo manager to anonymous classes, and the failure still takes place. Even a stacktrace simply spits out internal Scala compiler errors, so I can't quite debug what's causing the error.

    My build tool is Gradle 2.7, if it means anything.

    I've also tried this on a fresh Gradle Scala project, and the same error results.

    This is the sample code for the fresh project that fails to compile:

    import java.util.function.{Consumer, Function}
    
    import org.fxmisc.undo.UndoManagerFactory
    import org.reactfx.EventSource
    
    object Main {
      val changes = new EventSource[Change]
    
      val undoManager = UndoManagerFactory.unlimitedHistoryUndoManager(
      changes,
      new Function[Change, Change] {
        override def apply(t: Change): Change = t.invert
      },
      new Consumer[Change] {
        override def accept(t: Change): Unit = t.apply()
      }
      )
    
    }
    
    case class Change(oldVal: Boolean, newVal: Boolean) {
      def invert: Change = Change(newVal, oldVal)
    
      def apply(): Unit = println(newVal)
    }
    
    opened by benedictleejh 4
Releases(v2.1.1)
Owner
null
A GUI-based file manager based on a Java file management and I/O framework using object-oriented programming ideas.

FileManager A GUI-based file manager based on a Java file management and I/O framework using object-oriented programming ideas. Enables folder creatio

Zongyu Wu 4 Feb 7, 2022
Tray Icon implementation for JavaFX applications. Say goodbye to using AWT's SystemTray icon, instead use a JavaFX Tray Icon.

FXTrayIcon Library intended for use in JavaFX applications that makes adding a System Tray icon easier. The FXTrayIcon class handles all the messy AWT

Dustin Redmond 248 Dec 30, 2022
Lib-Tile is a multi Maven project written in JavaFX and NetBeans IDE 8 and provides the functionalities to use and handle easily Tiles in your JavaFX application.

Lib-Tile Intention Lib-Tile is a multi Maven project written in JavaFX and NetBeans IDE and provides the functionalities to use and handle easily Tile

Peter Rogge 13 Apr 13, 2022
DataFX - is a JavaFX frameworks that provides additional features to create MVC based applications in JavaFX by providing routing and a context for CDI.

What you’ve stumbled upon here is a project that intends to make retrieving, massaging, populating, viewing, and editing data in JavaFX UI controls ea

Guigarage 110 Dec 29, 2022
Collection of Binding helpers for JavaFX(8)

Advanced-Bindings for JavaFX (8) advanced-bindings is a collection of useful helpers and custom binding implementations to simplify the development of

Manuel Mauky 63 Nov 19, 2022
Docking framework for JavaFX platform

Docking framework for JavaFX platform AnchorFX is a gratis and open source library for JavaFX to create graphical interfaces with docking features Anc

Alessio Vinerbi 197 Oct 15, 2022
A library of +70 ready-to-use animations for JavaFX

AnimateFX A library of ready-to-use animations for JavaFX Features: Custom animations Custom interpolators Play/Stop animation Play an animation after

Loïc Sculier 366 Jan 5, 2023
BootstrapFX: Bootstrap for JavaFX

BootstrapFX BootstrapFX is a partial port of Twitter Bootstrap for JavaFX. It mainly provides a CSS stylesheet that closely resembles the original whi

Kordamp 810 Dec 28, 2022
A Java framework for creating sophisticated calendar views (JavaFX 8, 9, 10, and 11)

CalendarFX A Java framework for creating sophisticated calendar views based on JavaFX. A detailed developer manual can be found online: CalendarFX 8 D

DLSC Software & Consulting GmbH 660 Jan 6, 2023
Allow runtime modification of JavaFX CSS

cssfx ⚠ WARNING ⚠ In version 11.3.0 we have relocated & refactored the project. maven groupId has been changed to fr.brouillard.oss java module name h

Matthieu Brouillard 134 Jan 2, 2023
A JavaFX UI framework to create fully customized undecorated windows

CustomStage A JavaFX undecorated stage which can fully be customized Donations If this project is helpful to you and love my work and feel like showin

Oshan Mendis 186 Jan 6, 2023
MDI components for JavaFX

DesktopPaneFX DesktopPaneFX is a JavaFX version of Swing’s JDesktopPane which can be used as a container for individual "child" similar to JInternalFr

Kordamp 58 Sep 23, 2022
Efficient VirtualFlow for JavaFX

Flowless Efficient VirtualFlow for JavaFX. VirtualFlow is a layout container that lays out cells in a vertical or horizontal flow. The main feature of

null 163 Nov 24, 2022
A framework for easily creating forms for a JavaFX UI.

FormsFX Forms for business application made easy. Creating forms in Java has never been this easy! Maven To use this framework as part of your Maven b

DLSC Software & Consulting GmbH 534 Dec 30, 2022
:icecream: iOS frosty/translucent effect to JavaFX

FroXty is JavaFX library which replicates the famous iOS translucent effect with ease. Set-up FroXty can be imported into your project either by downl

Giorgio Garofalo 33 Dec 11, 2022
💠 Undecorated JavaFX Scene with implemented move, resize, minimise, maximise, close and Windows Aero Snap controls.

Support me joining PI Network app with invitation code AlexKent FX-BorderlessScene ( Library ) ?? Undecorated JavaFX Scene with implemented move, resi

Alexander Kentros 125 Jan 4, 2023
Dynamic JavaFX form generation

FXForm 2 Stop coding forms: FXForm 2 can do it for you! About FXForm2 is a library providing automatic JavaFX form generation. How does it work? Write

dooApp 209 Jan 9, 2023
A JavaFX library that allows Java2D code (Graphics2D) to be used to draw to a Canvas node.

FXGraphics2D Version 2.1, 3 October 2020. Overview FXGraphics2D is a free implementation of Java's Graphics2D API that targets the JavaFX Canvas. It m

David Gilbert 184 Dec 31, 2022
Auto updating launcher for JavaFX Applications

FXLauncher Auto updating launcher for JavaFX Applications. Combined with JavaFX native packaging, you get a native installer with automatic app update

Edvin Syse 694 Dec 27, 2022