Merge pull request 'JavaFX Integration' (#4) from f/javafx into develop
All checks were successful
zdm/undo-redo/pipeline/head This commit looks good
All checks were successful
zdm/undo-redo/pipeline/head This commit looks good
Reviewed-on: https://git.kske.dev/zdm/undo-redo/pulls/4 Reviewed-by: DieGurke <maxi@kske.dev> Reviewed-by: delvh <leon@kske.dev>
This commit is contained in:
commit
fa5c2419bf
@ -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.
|
||||
* <p>
|
||||
* 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 <C> the change type to store in this change manager
|
||||
* @author Maximilian Käfer
|
||||
@ -22,6 +26,12 @@ public interface ChangeManager<C extends Change> {
|
||||
*/
|
||||
void addChange(C change);
|
||||
|
||||
/**
|
||||
* @return the change that was applied last
|
||||
* @since 0.0.1
|
||||
*/
|
||||
Optional<C> getLastChange();
|
||||
|
||||
/**
|
||||
* Undoes the current change.
|
||||
*
|
||||
|
@ -22,6 +22,11 @@ public final class UnlimitedChangeManager<C extends Change> implements ChangeMan
|
||||
++index;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<C> getLastChange() {
|
||||
return index < 0 ? Optional.empty() : Optional.of(changes.get(index));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean undo() {
|
||||
if (isUndoAvailable()) {
|
||||
|
@ -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);
|
||||
|
27
javafx/.classpath
Normal file
27
javafx/.classpath
Normal file
@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="src" output="target/classes" path="src/main/java">
|
||||
<attributes>
|
||||
<attribute name="optional" value="true"/>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
|
||||
<attributes>
|
||||
<attribute name="optional" value="true"/>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
<attribute name="test" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="output" path="target/classes"/>
|
||||
</classpath>
|
23
javafx/.project
Normal file
23
javafx/.project
Normal file
@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>undo-redo-javafx</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.m2e.core.maven2Builder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
<nature>org.eclipse.m2e.core.maven2Nature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
28
javafx/pom.xml
Normal file
28
javafx/pom.xml
Normal file
@ -0,0 +1,28 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>javafx</artifactId>
|
||||
<name>Undo-Redo JavaFX Integration</name>
|
||||
|
||||
<parent>
|
||||
<groupId>dev.kske</groupId>
|
||||
<artifactId>undo-redo</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>dev.kske</groupId>
|
||||
<artifactId>undo-redo-core</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-base</artifactId>
|
||||
<version>11</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
@ -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.
|
||||
* <p>
|
||||
* 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 <C> the change type to store in this change manager
|
||||
* @param <M> the type of change manager to wrap
|
||||
* @author Kai S. K. Engelbart
|
||||
* @since 0.0.1
|
||||
*/
|
||||
public class ChangeManagerWrapper<C extends Change, M extends ChangeManager<C>>
|
||||
implements ObservableChangeManager<C> {
|
||||
|
||||
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<C> 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<C> 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<C> getChanges() {
|
||||
return manager.getChanges();
|
||||
}
|
||||
}
|
@ -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 <C> the change type to store in this change manager
|
||||
* @author Kai S. K. Engelbart
|
||||
* @since 0.0.1
|
||||
* @see ChangeManagerWrapper
|
||||
*/
|
||||
public interface ObservableChangeManager<C extends Change> extends ChangeManager<C> {
|
||||
|
||||
ReadOnlyObjectProperty<C> lastChangeProperty();
|
||||
|
||||
@Override
|
||||
default Optional<C> 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();
|
||||
}
|
||||
}
|
@ -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;
|
15
javafx/src/main/java/module-info.java
Normal file
15
javafx/src/main/java/module-info.java
Normal file
@ -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;
|
||||
}
|
11
pom.xml
11
pom.xml
@ -14,6 +14,7 @@
|
||||
|
||||
<modules>
|
||||
<module>core</module>
|
||||
<module>javafx</module>
|
||||
</modules>
|
||||
|
||||
<licenses>
|
||||
@ -47,6 +48,16 @@
|
||||
</roles>
|
||||
<timezone>Europe/Berlin</timezone>
|
||||
</developer>
|
||||
<developer>
|
||||
<name>Leon Hofmeister</name>
|
||||
<email>leon@kske.dev</email>
|
||||
<url>https://git.kske.dev/delvh</url>
|
||||
<roles>
|
||||
<role>architect</role>
|
||||
<role>developer</role>
|
||||
</roles>
|
||||
<timezone>Europe/Berlin</timezone>
|
||||
</developer>
|
||||
</developers>
|
||||
|
||||
<scm>
|
||||
|
Reference in New Issue
Block a user