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 +