From d80dd94e906529a2e9ea0d2e720bf262d111a8dd Mon Sep 17 00:00:00 2001 From: kske Date: Sun, 12 Dec 2021 05:49:47 +0100 Subject: [PATCH 1/4] Allow retrieving the last change from a change manager --- .../dev/kske/undoredo/core/ChangeManager.java | 8 ++++++- .../undoredo/core/UnlimitedChangeManager.java | 5 ++++ .../kske/undoredo/core/ChangeManagerTest.java | 23 +++++++++++++++---- 3 files changed, 31 insertions(+), 5 deletions(-) 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..be9424b 100644 --- a/core/src/main/java/dev/kske/undoredo/core/ChangeManager.java +++ b/core/src/main/java/dev/kske/undoredo/core/ChangeManager.java @@ -1,6 +1,6 @@ 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 @@ -22,6 +22,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..b353a7d 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 == -1 ? 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..2508b56 100644 --- a/core/src/test/java/dev/kske/undoredo/core/ChangeManagerTest.java +++ b/core/src/test/java/dev/kske/undoredo/core/ChangeManagerTest.java @@ -35,12 +35,25 @@ class ChangeManagerTest { } /** - * Tests the consistency of the change list. + * Tests retrieving the last change. * * @since 0.0.1 */ @Test @Order(2) + 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(3) void testGetChanges() { assertTrue(manager.getChanges().isEmpty()); manager.addChange(change); @@ -54,7 +67,7 @@ class ChangeManagerTest { * @since 0.0.1 */ @Test - @Order(2) + @Order(4) 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(5) 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(6) void testMark() { assertTrue(manager.isAtMarkedIndex()); manager.addChange(change); -- 2.45.2 From d49772a1273e6d2932becef447e729eed547225a Mon Sep 17 00:00:00 2001 From: kske Date: Sun, 12 Dec 2021 05:59:28 +0100 Subject: [PATCH 2/4] Add javafx module The undo-redo-javafx module will contain the JavaFX-compatible API for Undo-Redo. --- javafx/.classpath | 27 ++++++++++++++++++ javafx/.project | 23 +++++++++++++++ javafx/pom.xml | 28 +++++++++++++++++++ .../kske/undoredo/javafx/package-info.java | 7 +++++ javafx/src/main/java/module-info.java | 13 +++++++++ pom.xml | 1 + 6 files changed, 99 insertions(+) create mode 100644 javafx/.classpath create mode 100644 javafx/.project create mode 100644 javafx/pom.xml create mode 100644 javafx/src/main/java/dev/kske/undoredo/javafx/package-info.java create mode 100644 javafx/src/main/java/module-info.java 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/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..654bed2 --- /dev/null +++ b/javafx/src/main/java/module-info.java @@ -0,0 +1,13 @@ +/** + * 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; + + requires dev.kske.undoredo.core; + requires javafx.base; +} diff --git a/pom.xml b/pom.xml index 70f0e57..c2ad885 100644 --- a/pom.xml +++ b/pom.xml @@ -14,6 +14,7 @@ core + javafx -- 2.45.2 From 1b6d7f3dde8ee3975085ac287b963b9f71136ad4 Mon Sep 17 00:00:00 2001 From: kske Date: Sun, 12 Dec 2021 08:05:24 +0100 Subject: [PATCH 3/4] Add ObservableChangeManager interface with wrapper implementation --- .../dev/kske/undoredo/core/ChangeManager.java | 2 +- .../undoredo/core/UnlimitedChangeManager.java | 2 +- .../undoredo/javafx/ChangeManagerWrapper.java | 118 ++++++++++++++++++ .../javafx/ObservableChangeManager.java | 43 +++++++ javafx/src/main/java/module-info.java | 2 +- 5 files changed, 164 insertions(+), 3 deletions(-) create mode 100644 javafx/src/main/java/dev/kske/undoredo/javafx/ChangeManagerWrapper.java create mode 100644 javafx/src/main/java/dev/kske/undoredo/javafx/ObservableChangeManager.java 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 be9424b..daffcfd 100644 --- a/core/src/main/java/dev/kske/undoredo/core/ChangeManager.java +++ b/core/src/main/java/dev/kske/undoredo/core/ChangeManager.java @@ -26,7 +26,7 @@ public interface ChangeManager { * @return the change that was applied last * @since 0.0.1 */ - Optional getLastChange(); + 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 b353a7d..4e64120 100644 --- a/core/src/main/java/dev/kske/undoredo/core/UnlimitedChangeManager.java +++ b/core/src/main/java/dev/kske/undoredo/core/UnlimitedChangeManager.java @@ -23,7 +23,7 @@ public final class UnlimitedChangeManager implements ChangeMan } @Override - public Optional getLastChange() { + public Optional getLastChange() { return index == -1 ? Optional.empty() : Optional.of(changes.get(index)); } 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..85029b5 --- /dev/null +++ b/javafx/src/main/java/dev/kske/undoredo/javafx/ChangeManagerWrapper.java @@ -0,0 +1,118 @@ +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. + * + * @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 { + + protected ReadOnlyObjectWrapper lastChange = + new ReadOnlyObjectWrapper<>(this, "lastChange"); + protected ReadOnlyBooleanWrapper atMarkedIndex = + new ReadOnlyBooleanWrapper(this, "atMarkedIndex"); + protected ReadOnlyBooleanWrapper undoAvailable = + new ReadOnlyBooleanWrapper(this, "undoAvailable"); + protected ReadOnlyBooleanWrapper redoAvailable = + new ReadOnlyBooleanWrapper(this, "redoAvailable"); + + protected final M manager; + + protected 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..09697af --- /dev/null +++ b/javafx/src/main/java/dev/kske/undoredo/javafx/ObservableChangeManager.java @@ -0,0 +1,43 @@ +package dev.kske.undoredo.javafx; + +import java.util.Optional; + +import javafx.beans.property.*; + +import dev.kske.undoredo.core.*; + +/** + * @param the change type to store in this change manager + * @author Kai S. K. Engelbart + * @since 0.0.1 + */ +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/module-info.java b/javafx/src/main/java/module-info.java index 654bed2..aa7f939 100644 --- a/javafx/src/main/java/module-info.java +++ b/javafx/src/main/java/module-info.java @@ -9,5 +9,5 @@ module dev.kske.undoredo.javafx { exports dev.kske.undoredo.javafx; requires dev.kske.undoredo.core; - requires javafx.base; + requires transitive javafx.base; } -- 2.45.2 From 0f1c3e06d8f045b70bb4a26f92f96624e63140b6 Mon Sep 17 00:00:00 2001 From: kske Date: Thu, 16 Dec 2021 10:22:37 +0100 Subject: [PATCH 4/4] JavaFX reflective access + Javadoc + @delvh * Allow reflective access to the JavaFX module for javafx.base * Add space to the unit test orders * Improve Javadoc * Add @delvh as developer --- .../dev/kske/undoredo/core/ChangeManager.java | 4 +++ .../undoredo/core/UnlimitedChangeManager.java | 2 +- .../kske/undoredo/core/ChangeManagerTest.java | 12 ++++----- .../undoredo/javafx/ChangeManagerWrapper.java | 26 +++++++++++++++---- .../javafx/ObservableChangeManager.java | 18 ++++++++----- javafx/src/main/java/module-info.java | 4 ++- pom.xml | 10 +++++++ 7 files changed, 56 insertions(+), 20 deletions(-) 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 daffcfd..5410cfc 100644 --- a/core/src/main/java/dev/kske/undoredo/core/ChangeManager.java +++ b/core/src/main/java/dev/kske/undoredo/core/ChangeManager.java @@ -6,6 +6,10 @@ 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 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 4e64120..4b0d13b 100644 --- a/core/src/main/java/dev/kske/undoredo/core/UnlimitedChangeManager.java +++ b/core/src/main/java/dev/kske/undoredo/core/UnlimitedChangeManager.java @@ -24,7 +24,7 @@ public final class UnlimitedChangeManager implements ChangeMan @Override public Optional getLastChange() { - return index == -1 ? Optional.empty() : Optional.of(changes.get(index)); + return index < 0 ? Optional.empty() : Optional.of(changes.get(index)); } @Override 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 2508b56..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,7 +27,7 @@ class ChangeManagerTest { * @since 0.0.1 */ @Test - @Order(1) + @Order(10) void testAddChange() { assertSame(0, wrapper.value); manager.addChange(change); @@ -40,7 +40,7 @@ class ChangeManagerTest { * @since 0.0.1 */ @Test - @Order(2) + @Order(20) void testLastChange() { assertTrue(manager.getLastChange().isEmpty()); manager.addChange(change); @@ -53,7 +53,7 @@ class ChangeManagerTest { * @since 0.0.1 */ @Test - @Order(3) + @Order(30) void testGetChanges() { assertTrue(manager.getChanges().isEmpty()); manager.addChange(change); @@ -67,7 +67,7 @@ class ChangeManagerTest { * @since 0.0.1 */ @Test - @Order(4) + @Order(40) void testUndo() { assertFalse(manager.isUndoAvailable()); assertFalse(manager.undo()); @@ -85,7 +85,7 @@ class ChangeManagerTest { * @since 0.0.1 */ @Test - @Order(5) + @Order(50) void testRedo() { assertFalse(manager.isRedoAvailable()); assertFalse(manager.redo()); @@ -106,7 +106,7 @@ class ChangeManagerTest { * @since 0.0.1 */ @Test - @Order(6) + @Order(60) void testMark() { assertTrue(manager.isAtMarkedIndex()); manager.addChange(change); diff --git a/javafx/src/main/java/dev/kske/undoredo/javafx/ChangeManagerWrapper.java b/javafx/src/main/java/dev/kske/undoredo/javafx/ChangeManagerWrapper.java index 85029b5..0c16cb6 100644 --- a/javafx/src/main/java/dev/kske/undoredo/javafx/ChangeManagerWrapper.java +++ b/javafx/src/main/java/dev/kske/undoredo/javafx/ChangeManagerWrapper.java @@ -9,6 +9,11 @@ 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 @@ -18,18 +23,29 @@ import dev.kske.undoredo.core.*; 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, "lastChange"); + new ReadOnlyObjectWrapper<>(this, LAST_CHANGE); protected ReadOnlyBooleanWrapper atMarkedIndex = - new ReadOnlyBooleanWrapper(this, "atMarkedIndex"); + new ReadOnlyBooleanWrapper(this, AT_MARKED_INDEX); protected ReadOnlyBooleanWrapper undoAvailable = - new ReadOnlyBooleanWrapper(this, "undoAvailable"); + new ReadOnlyBooleanWrapper(this, UNDO_AVAILABLE); protected ReadOnlyBooleanWrapper redoAvailable = - new ReadOnlyBooleanWrapper(this, "redoAvailable"); + new ReadOnlyBooleanWrapper(this, REDO_AVAILABLE); protected final M manager; - protected ChangeManagerWrapper(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; } diff --git a/javafx/src/main/java/dev/kske/undoredo/javafx/ObservableChangeManager.java b/javafx/src/main/java/dev/kske/undoredo/javafx/ObservableChangeManager.java index 09697af..4dad0e4 100644 --- a/javafx/src/main/java/dev/kske/undoredo/javafx/ObservableChangeManager.java +++ b/javafx/src/main/java/dev/kske/undoredo/javafx/ObservableChangeManager.java @@ -7,35 +7,39 @@ 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/module-info.java b/javafx/src/main/java/module-info.java index aa7f939..e296056 100644 --- a/javafx/src/main/java/module-info.java +++ b/javafx/src/main/java/module-info.java @@ -8,6 +8,8 @@ module dev.kske.undoredo.javafx { exports dev.kske.undoredo.javafx; - requires dev.kske.undoredo.core; + 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 c2ad885..4d3e67c 100644 --- a/pom.xml +++ b/pom.xml @@ -48,6 +48,16 @@ Europe/Berlin + + Leon Hofmeister + leon@kske.dev + https://git.kske.dev/delvh + + architect + developer + + Europe/Berlin + -- 2.45.2