diff --git a/src/combinedeventstreamtest/ChangeA.java b/src/combinedeventstreamtest/ChangeA.java new file mode 100644 index 0000000..f765cbe --- /dev/null +++ b/src/combinedeventstreamtest/ChangeA.java @@ -0,0 +1,47 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package combinedeventstreamtest; + +import java.util.Optional; +import org.reactfx.Change; + +/** + * + * @author bwhitworth + */ +public class ChangeA extends ChangeBase { + + public ChangeA(DataModel model, double oldVal, double newVal) { + super(model, oldVal, newVal); + } + + public ChangeA(DataModel model, Change c) { + super(model, (Double)c.getOldValue(), (Double)c.getNewValue()); + } + + @Override + public ChangeBase invert() { + System.out.println("ChangeA invert "+this); + return new ChangeA(this.model, this.newValue, this.oldValue); + } + + @Override + public void redo() { + System.out.println("ChangeA redo "+this); + this.model.setA(this.newValue); + } + + @Override + public Optional mergeWith(UndoChange other) { + System.out.print("ChangeA attempting merge with "+other+"... "); + if(other instanceof ChangeA) { + System.out.println("merged"); + return Optional.of(new ChangeA(this.model, this.oldValue, ((ChangeA) other).newValue)); + } + System.out.println("did not merge"); + return Optional.empty(); + } +} diff --git a/src/combinedeventstreamtest/ChangeB.java b/src/combinedeventstreamtest/ChangeB.java new file mode 100644 index 0000000..fe6546f --- /dev/null +++ b/src/combinedeventstreamtest/ChangeB.java @@ -0,0 +1,48 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package combinedeventstreamtest; + +import java.util.Optional; +import org.reactfx.Change; + +/** + * + * @author bwhitworth + */ +public class ChangeB extends ChangeBase { + + public ChangeB(DataModel model, double oldVal, double newVal) { + super(model, oldVal, newVal); + } + + public ChangeB(DataModel model, Change c) { + super(model, (Double)c.getOldValue(), (Double)c.getNewValue()); + } + + @Override + public ChangeBase invert() { + System.out.println("ChangeB invert "+this); + return new ChangeB(this.model, this.newValue, this.oldValue); + } + + @Override + public void redo() { + System.out.println("ChangeB redo "+this); + this.model.setB(this.newValue); + } + + @Override + public Optional mergeWith(UndoChange other) { + System.out.print("ChangeB attempting merge with "+other+"... "); + if(other instanceof ChangeB) { + System.out.println("merged"); + return Optional.of(new ChangeB(this.model, this.oldValue, ((ChangeB) other).newValue)); + } + System.out.println("did not merge"); + return Optional.empty(); + } + +} diff --git a/src/combinedeventstreamtest/ChangeBase.java b/src/combinedeventstreamtest/ChangeBase.java new file mode 100644 index 0000000..7dac775 --- /dev/null +++ b/src/combinedeventstreamtest/ChangeBase.java @@ -0,0 +1,61 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package combinedeventstreamtest; + +import java.util.Objects; +import java.util.Optional; + +/** + * + * @author bwhitworth + */ +public abstract class ChangeBase implements UndoChange { + protected final T oldValue, newValue; + protected final DataModel model; + + protected ChangeBase(DataModel model, T oldValue, T newValue) { + this.model = model; + this.oldValue = oldValue; + this.newValue = newValue; + } + + public abstract ChangeBase invert(); + public abstract void redo(); + + public Optional> mergeWith(ChangeBase other) { + return Optional.empty(); + } + + @Override + public int hashCode() { + return Objects.hash(this.oldValue, this.newValue); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final ChangeBase other = (ChangeBase) obj; + if (!Objects.equals(this.oldValue, other.oldValue)) { + return false; + } + if (!Objects.equals(this.newValue, other.newValue)) { + return false; + } + if (!Objects.equals(this.model, other.model)) { + return false; + } + return true; + } + +} diff --git a/src/combinedeventstreamtest/ChangeBoth.java b/src/combinedeventstreamtest/ChangeBoth.java new file mode 100644 index 0000000..9aecc13 --- /dev/null +++ b/src/combinedeventstreamtest/ChangeBoth.java @@ -0,0 +1,93 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package combinedeventstreamtest; + +import java.util.Objects; +import java.util.Optional; +import org.reactfx.util.Tuple2; + +/** + * + * @author bwhitworth + */ +public class ChangeBoth implements UndoChange { + + private final ChangeA aChange; + private final ChangeB bChange; + + public ChangeBoth(ChangeA ac, ChangeB bc) { + this.aChange = ac; + this.bChange = bc; + } + + public ChangeBoth(Tuple2 tuple) { + this.aChange = ((ChangeBoth)tuple.get1()).aChange; + this.bChange = ((ChangeBoth)tuple.get2()).bChange; + } + + @Override + public UndoChange invert() { + System.out.println("ChangeBoth invert "+this); + return new ChangeBoth(new ChangeA(this.aChange.model, this.aChange.newValue, this.aChange.oldValue), + new ChangeB(this.bChange.model, this.bChange.newValue, this.bChange.oldValue)); + } + + @Override + public void redo() { + System.out.println("ChangeBoth redo "+this); + DataModel model = this.aChange.model; + model.setA(this.aChange.newValue); + model.setB(this.bChange.newValue); + } + + @Override + public Optional mergeWith(UndoChange other) { + System.out.print("ChangeBoth attempting merge with "+other+"... "); + if(other instanceof ChangeBoth) { + System.out.println("merged"); + ChangeBoth cb = (ChangeBoth)other; + ChangeA ac = (cb.aChange == null) ? this.aChange : cb.aChange; + ChangeB bc = (cb.bChange == null) ? this.bChange : cb.bChange; + return Optional.of( + new ChangeBoth( + new ChangeA(this.aChange.model, this.aChange.oldValue, ac.newValue), + new ChangeB(this.bChange.model, this.bChange.oldValue, bc.newValue) + ) + ); + } + System.out.println("did not merge"); + return Optional.empty(); + } + + @Override + public int hashCode() { + int hash = 7; + hash = 17 * hash + Objects.hashCode(this.aChange); + hash = 17 * hash + Objects.hashCode(this.bChange); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final ChangeBoth other = (ChangeBoth) obj; + if (!Objects.equals(this.aChange, other.aChange)) { + return false; + } + if (!Objects.equals(this.bChange, other.bChange)) { + return false; + } + return true; + } +} diff --git a/src/combinedeventstreamtest/CombinedEventStreamTest.java b/src/combinedeventstreamtest/CombinedEventStreamTest.java new file mode 100644 index 0000000..d9d6352 --- /dev/null +++ b/src/combinedeventstreamtest/CombinedEventStreamTest.java @@ -0,0 +1,118 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package combinedeventstreamtest; + +import javafx.application.Application; +import javafx.beans.property.DoubleProperty; +import javafx.collections.FXCollections; +import javafx.collections.ListChangeListener; +import javafx.collections.ObservableList; +import javafx.event.ActionEvent; +import javafx.scene.Scene; +import javafx.scene.control.Button; +import javafx.scene.layout.FlowPane; +import javafx.stage.Stage; +import org.fxmisc.undo.UndoManager; +import org.fxmisc.undo.UndoManagerFactory; +import org.reactfx.Change; +import org.reactfx.EventStream; +import org.reactfx.EventStreams; + +/** + * + * @author bwhitworth + */ +public class CombinedEventStreamTest extends Application { + + @Override + public void start(Stage primaryStage) { + //a data model + DataModel model = new DataModel(); + //event streams of property changes + EventStream changeAStream = EventStreams.changesOf(model.aProperty()) + .hook(c -> System.out.println("Change in A stream")) + .map(c -> new ChangeA(model, (Change)c)); + EventStream changeBStream = EventStreams.changesOf(model.bProperty()) + .hook(c -> System.out.println("Change in B stream")) + .map(c -> new ChangeB(model, (Change)c)); + //combine event streams + EventStream bothStream = EventStreams.merge(changeAStream, changeBStream); + +// EventStream bothStream = EventStreams.combine(changeAStream, changeBStream) +// .hook(c -> System.out.println("Change in Both stream")) +// .map(ChangeBoth::new); + + //undo manager + UndoManager um = UndoManagerFactory.unlimitedHistoryUndoManager( + bothStream, + c -> c.invert(), + c -> c.redo(), + (c1, c2) -> c1.mergeWith(c2) + ); + + //generate new A + Button aButton = new Button(); + aButton.setText("A change"); + aButton.setOnAction((ActionEvent event) -> { + System.out.print(model+"\t->\t"); + model.setA(Math.random()*10.0); + System.out.println(model); + }); + + //generate new B + Button bButton = new Button(); + bButton.setText("B change"); + bButton.setOnAction((ActionEvent event) -> { + System.out.print(model+"\t->\t"); + model.setB(Math.random()*10.0); + System.out.println(model); + }); + + //generate new A and B + Button bothButton = new Button("A and B change"); + bothButton.setOnAction(event -> { + System.out.print(model+"\t->\t"); + model.setA(Math.random()*10.0); + model.setB(Math.random()*10.0); + System.out.println(model); + }); + + //undo/redo buttons + Button undoButton = new Button("Undo"); + Button redoButton = new Button("Redo"); + undoButton.disableProperty().bind(um.undoAvailableProperty().map(x -> !x)); + redoButton.disableProperty().bind(um.redoAvailableProperty().map(x -> !x)); + undoButton.setOnAction(event -> { + System.out.print("undo "+model+"\t->\t"); + um.undo(); + System.out.println(model); + }); + redoButton.setOnAction(event -> { + System.out.print("redo "+model+"\t->\t"); + um.redo(); + System.out.println(model); + }); + + FlowPane root = new FlowPane(); + root.setHgap(5); + root.setVgap(5); + root.getChildren().addAll(aButton, bButton, bothButton, undoButton, redoButton); + + Scene scene = new Scene(root, 300, 250); + + primaryStage.setTitle("Combined stream test"); + primaryStage.setScene(scene); + primaryStage.show(); + } + + /** + * @param args the command line arguments + */ + public static void main(String[] args) { + launch(args); + } + +} diff --git a/src/combinedeventstreamtest/DataModel.java b/src/combinedeventstreamtest/DataModel.java new file mode 100644 index 0000000..2d45069 --- /dev/null +++ b/src/combinedeventstreamtest/DataModel.java @@ -0,0 +1,74 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package combinedeventstreamtest; + +import java.util.Objects; +import javafx.beans.property.DoubleProperty; +import javafx.beans.property.SimpleDoubleProperty; + +/** + * + * @author bwhitworth + */ +public class DataModel { + private DoubleProperty a, b; + + public DataModel() { + this.a = new SimpleDoubleProperty(); + this.b = new SimpleDoubleProperty(); + } + + public DoubleProperty aProperty() { + return this.a; + } + + public DoubleProperty bProperty() { + return this.b; + } + + public void setA(double a) { + this.a.set(a); + } + + public void setB(double b) { + this.b.set(b); + } + + @Override + public String toString() { + return this.a.doubleValue()+"\t"+this.b.doubleValue(); + } + + @Override + public int hashCode() { + int hash = 7; + hash = 67 * hash + Objects.hashCode(this.a); + hash = 67 * hash + Objects.hashCode(this.b); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final DataModel other = (DataModel) obj; + if (!Objects.equals(this.a, other.a)) { + return false; + } + if (!Objects.equals(this.b, other.b)) { + return false; + } + return true; + } + +} diff --git a/src/combinedeventstreamtest/UndoChange.java b/src/combinedeventstreamtest/UndoChange.java new file mode 100644 index 0000000..dab4cfe --- /dev/null +++ b/src/combinedeventstreamtest/UndoChange.java @@ -0,0 +1,19 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package combinedeventstreamtest; + +import java.util.Optional; + +/** + * + * @author bwhitworth + */ +public interface UndoChange { + public void redo(); + public UndoChange invert(); + public Optional mergeWith(UndoChange other); + +}