diff --git a/core/src/main/java/dev/kske/undoredo/core/ChangeManager.java b/core/src/main/java/dev/kske/undoredo/core/ChangeManager.java
index 7b887e7..5410cfc 100644
--- a/core/src/main/java/dev/kske/undoredo/core/ChangeManager.java
+++ b/core/src/main/java/dev/kske/undoredo/core/ChangeManager.java
@@ -1,11 +1,15 @@
package dev.kske.undoredo.core;
-import java.util.List;
+import java.util.*;
/**
* A change manager keeps track of subsequent changes and allows un- and redoing them. A specific
* change can be marked using {@link #mark()} to keep track of a saved state in the application that
* uses the manager.
+ *
+ * If you intend to listen to the state of a change manager, consider writing a wrapper
+ * implementation for an existing change manager that adds the necessary hooks. If you use JavaFX,
+ * take a look at the {@code dev.kske.undoredo.javafx} module.
*
* @param the change type to store in this change manager
* @author Maximilian Käfer
@@ -22,6 +26,12 @@ public interface ChangeManager {
*/
void addChange(C change);
+ /**
+ * @return the change that was applied last
+ * @since 0.0.1
+ */
+ Optional getLastChange();
+
/**
* Undoes the current change.
*
diff --git a/core/src/main/java/dev/kske/undoredo/core/UnlimitedChangeManager.java b/core/src/main/java/dev/kske/undoredo/core/UnlimitedChangeManager.java
index 8a0ff35..4b0d13b 100644
--- a/core/src/main/java/dev/kske/undoredo/core/UnlimitedChangeManager.java
+++ b/core/src/main/java/dev/kske/undoredo/core/UnlimitedChangeManager.java
@@ -22,6 +22,11 @@ public final class UnlimitedChangeManager implements ChangeMan
++index;
}
+ @Override
+ public Optional getLastChange() {
+ return index < 0 ? Optional.empty() : Optional.of(changes.get(index));
+ }
+
@Override
public boolean undo() {
if (isUndoAvailable()) {
diff --git a/core/src/test/java/dev/kske/undoredo/core/ChangeManagerTest.java b/core/src/test/java/dev/kske/undoredo/core/ChangeManagerTest.java
index 4643970..51ac613 100644
--- a/core/src/test/java/dev/kske/undoredo/core/ChangeManagerTest.java
+++ b/core/src/test/java/dev/kske/undoredo/core/ChangeManagerTest.java
@@ -27,20 +27,33 @@ class ChangeManagerTest {
* @since 0.0.1
*/
@Test
- @Order(1)
+ @Order(10)
void testAddChange() {
assertSame(0, wrapper.value);
manager.addChange(change);
assertSame(1, wrapper.value);
}
+ /**
+ * Tests retrieving the last change.
+ *
+ * @since 0.0.1
+ */
+ @Test
+ @Order(20)
+ void testLastChange() {
+ assertTrue(manager.getLastChange().isEmpty());
+ manager.addChange(change);
+ assertEquals(change, manager.getLastChange().get());
+ }
+
/**
* Tests the consistency of the change list.
*
* @since 0.0.1
*/
@Test
- @Order(2)
+ @Order(30)
void testGetChanges() {
assertTrue(manager.getChanges().isEmpty());
manager.addChange(change);
@@ -54,7 +67,7 @@ class ChangeManagerTest {
* @since 0.0.1
*/
@Test
- @Order(2)
+ @Order(40)
void testUndo() {
assertFalse(manager.isUndoAvailable());
assertFalse(manager.undo());
@@ -63,6 +76,7 @@ class ChangeManagerTest {
assertTrue(manager.undo());
assertFalse(manager.isUndoAvailable());
assertFalse(manager.undo());
+ assertTrue(manager.getLastChange().isEmpty());
}
/**
@@ -71,7 +85,7 @@ class ChangeManagerTest {
* @since 0.0.1
*/
@Test
- @Order(4)
+ @Order(50)
void testRedo() {
assertFalse(manager.isRedoAvailable());
assertFalse(manager.redo());
@@ -83,6 +97,7 @@ class ChangeManagerTest {
assertTrue(manager.redo());
assertFalse(manager.isRedoAvailable());
assertFalse(manager.redo());
+ assertEquals(change, manager.getLastChange().get());
}
/**
@@ -91,7 +106,7 @@ class ChangeManagerTest {
* @since 0.0.1
*/
@Test
- @Order(5)
+ @Order(60)
void testMark() {
assertTrue(manager.isAtMarkedIndex());
manager.addChange(change);
diff --git a/javafx/.classpath b/javafx/.classpath
new file mode 100644
index 0000000..4559ca0
--- /dev/null
+++ b/javafx/.classpath
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/javafx/.project b/javafx/.project
new file mode 100644
index 0000000..0e6e72e
--- /dev/null
+++ b/javafx/.project
@@ -0,0 +1,23 @@
+
+
+ undo-redo-javafx
+
+
+
+
+
+ org.eclipse.jdt.core.javabuilder
+
+
+
+
+ org.eclipse.m2e.core.maven2Builder
+
+
+
+
+
+ org.eclipse.jdt.core.javanature
+ org.eclipse.m2e.core.maven2Nature
+
+
diff --git a/javafx/pom.xml b/javafx/pom.xml
new file mode 100644
index 0000000..a836baa
--- /dev/null
+++ b/javafx/pom.xml
@@ -0,0 +1,28 @@
+
+ 4.0.0
+
+ javafx
+ Undo-Redo JavaFX Integration
+
+
+ dev.kske
+ undo-redo
+ 0.0.1-SNAPSHOT
+
+
+
+
+ dev.kske
+ undo-redo-core
+ 0.0.1-SNAPSHOT
+
+
+ org.openjfx
+ javafx-base
+ 11
+
+
+
+
diff --git a/javafx/src/main/java/dev/kske/undoredo/javafx/ChangeManagerWrapper.java b/javafx/src/main/java/dev/kske/undoredo/javafx/ChangeManagerWrapper.java
new file mode 100644
index 0000000..0c16cb6
--- /dev/null
+++ b/javafx/src/main/java/dev/kske/undoredo/javafx/ChangeManagerWrapper.java
@@ -0,0 +1,134 @@
+package dev.kske.undoredo.javafx;
+
+import java.util.List;
+
+import javafx.beans.property.*;
+
+import dev.kske.undoredo.core.*;
+
+/**
+ * Wraps an ordinary change manager into an observable change manager, providing the required
+ * properties for concrete implementations.
+ *
+ * The properties have the same name as their corresponding {@code -property()} methods and can be
+ * accessed reflectively from JavaFX, e.g. through
+ * {@link javafx.beans.binding.Bindings#select(Object, String...)}. Alternatively, the property
+ * names are available as constants.
+ *
+ * @param the change type to store in this change manager
+ * @param the type of change manager to wrap
+ * @author Kai S. K. Engelbart
+ * @since 0.0.1
+ */
+public class ChangeManagerWrapper>
+ implements ObservableChangeManager {
+
+ public static final String LAST_CHANGE = "lastChange";
+ public static final String AT_MARKED_INDEX = "atMarkedIndex";
+ public static final String UNDO_AVAILABLE = "undoAvailable";
+ public static final String REDO_AVAILABLE = "redoAvailable";
+
+ protected ReadOnlyObjectWrapper lastChange =
+ new ReadOnlyObjectWrapper<>(this, LAST_CHANGE);
+ protected ReadOnlyBooleanWrapper atMarkedIndex =
+ new ReadOnlyBooleanWrapper(this, AT_MARKED_INDEX);
+ protected ReadOnlyBooleanWrapper undoAvailable =
+ new ReadOnlyBooleanWrapper(this, UNDO_AVAILABLE);
+ protected ReadOnlyBooleanWrapper redoAvailable =
+ new ReadOnlyBooleanWrapper(this, REDO_AVAILABLE);
+
+ protected final M manager;
+
+ /**
+ * Initializes a change manager wrapper.
+ *
+ * @param manager the change manager to wrap
+ * @since 0.0.1
+ */
+ public ChangeManagerWrapper(M manager) {
+ this.manager = manager;
+ }
+
+ @Override
+ public void addChange(C change) {
+ manager.addChange(change);
+ updateProperties();
+ }
+
+ @Override
+ public boolean undo() {
+ if (manager.undo()) {
+ updateProperties();
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean redo() {
+ if (manager.redo()) {
+ updateProperties();
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void mark() {
+ manager.mark();
+ setAtMarkedIndex(manager.isAtMarkedIndex());
+ }
+
+ /**
+ * Sets the values of all properties to those present in the wrapped change manager.
+ *
+ * @since 0.0.1
+ */
+ private void updateProperties() {
+ setLastChange(manager.getLastChange().orElse(null));
+ setAtMarkedIndex(manager.isAtMarkedIndex());
+ setUndoAvailable(manager.isUndoAvailable());
+ setRedoAvailable(manager.isRedoAvailable());
+ }
+
+ @Override
+ public final ReadOnlyObjectProperty lastChangeProperty() {
+ return lastChange.getReadOnlyProperty();
+ }
+
+ protected final void setLastChange(C lastChange) {
+ this.lastChange.set(lastChange);
+ }
+
+ @Override
+ public final ReadOnlyBooleanProperty atMarkedIndexProperty() {
+ return atMarkedIndex.getReadOnlyProperty();
+ }
+
+ protected final void setAtMarkedIndex(boolean atMarkedIndex) {
+ this.atMarkedIndex.set(atMarkedIndex);
+ }
+
+ @Override
+ public final ReadOnlyBooleanProperty undoAvailableProperty() {
+ return undoAvailable.getReadOnlyProperty();
+ }
+
+ protected final void setUndoAvailable(boolean undoAvailable) {
+ this.undoAvailable.set(undoAvailable);
+ }
+
+ @Override
+ public final ReadOnlyBooleanProperty redoAvailableProperty() {
+ return redoAvailable.getReadOnlyProperty();
+ }
+
+ protected final void setRedoAvailable(boolean redoAvailable) {
+ this.redoAvailable.set(redoAvailable);
+ }
+
+ @Override
+ public List getChanges() {
+ return manager.getChanges();
+ }
+}
diff --git a/javafx/src/main/java/dev/kske/undoredo/javafx/ObservableChangeManager.java b/javafx/src/main/java/dev/kske/undoredo/javafx/ObservableChangeManager.java
new file mode 100644
index 0000000..4dad0e4
--- /dev/null
+++ b/javafx/src/main/java/dev/kske/undoredo/javafx/ObservableChangeManager.java
@@ -0,0 +1,47 @@
+package dev.kske.undoredo.javafx;
+
+import java.util.Optional;
+
+import javafx.beans.property.*;
+
+import dev.kske.undoredo.core.*;
+
+/**
+ * A change manager that exposes its state through JavaFX properties, thereby allowing a direct
+ * integration of Undo-Redo with JavaFX listeners and property bindings.
+ *
+ * @param the change type to store in this change manager
+ * @author Kai S. K. Engelbart
+ * @since 0.0.1
+ * @see ChangeManagerWrapper
+ */
+public interface ObservableChangeManager extends ChangeManager {
+
+ ReadOnlyObjectProperty lastChangeProperty();
+
+ @Override
+ default Optional getLastChange() {
+ return Optional.of(lastChangeProperty().get());
+ }
+
+ ReadOnlyBooleanProperty atMarkedIndexProperty();
+
+ @Override
+ default boolean isAtMarkedIndex() {
+ return atMarkedIndexProperty().get();
+ }
+
+ ReadOnlyBooleanProperty undoAvailableProperty();
+
+ @Override
+ default boolean isUndoAvailable() {
+ return undoAvailableProperty().get();
+ }
+
+ ReadOnlyBooleanProperty redoAvailableProperty();
+
+ @Override
+ default boolean isRedoAvailable() {
+ return redoAvailableProperty().get();
+ }
+}
diff --git a/javafx/src/main/java/dev/kske/undoredo/javafx/package-info.java b/javafx/src/main/java/dev/kske/undoredo/javafx/package-info.java
new file mode 100644
index 0000000..7e57a02
--- /dev/null
+++ b/javafx/src/main/java/dev/kske/undoredo/javafx/package-info.java
@@ -0,0 +1,7 @@
+/**
+ * Contains JavaFX-based wrapper API for integrating Undo-Redo with JavaFX.
+ *
+ * @author Kai S. K. Engelbart
+ * @since 0.0.1
+ */
+package dev.kske.undoredo.javafx;
diff --git a/javafx/src/main/java/module-info.java b/javafx/src/main/java/module-info.java
new file mode 100644
index 0000000..e296056
--- /dev/null
+++ b/javafx/src/main/java/module-info.java
@@ -0,0 +1,15 @@
+/**
+ * Contains JavaFX-based wrapper API for integrating Undo-Redo with JavaFX.
+ *
+ * @author Kai S. K. Engelbart
+ * @since 0.0.1
+ */
+module dev.kske.undoredo.javafx {
+
+ exports dev.kske.undoredo.javafx;
+
+ opens dev.kske.undoredo.javafx to javafx.base;
+
+ requires transitive dev.kske.undoredo.core;
+ requires transitive javafx.base;
+}
diff --git a/pom.xml b/pom.xml
index 70f0e57..4d3e67c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -14,6 +14,7 @@
core
+ javafx
@@ -47,6 +48,16 @@
Europe/Berlin
+
+ Leon Hofmeister
+ leon@kske.dev
+ https://git.kske.dev/delvh
+
+ architect
+ developer
+
+ Europe/Berlin
+