From cff7fb9263e544f2953e79fe16c9e095fb3006ed Mon Sep 17 00:00:00 2001 From: Austin Shalit Date: Sat, 30 Jul 2016 15:41:20 -0700 Subject: [PATCH 1/3] Appends "Edited" to the stage title on edit --- .../core/events/ConnectionAddedEvent.java | 2 +- .../core/events/ConnectionRemovedEvent.java | 2 +- .../grip/core/events/DirtiesSaveEvent.java | 19 ++++++++++++ .../grip/core/events/SocketChangedEvent.java | 14 ++++++++- .../events/SocketPreviewChangedEvent.java | 2 +- .../grip/core/events/SourceAddedEvent.java | 2 +- .../grip/core/events/SourceRemovedEvent.java | 2 +- .../wpi/grip/core/events/StepAddedEvent.java | 2 +- .../wpi/grip/core/events/StepMovedEvent.java | 2 +- .../grip/core/events/StepRemovedEvent.java | 2 +- .../wpi/grip/core/serialization/Project.java | 29 ++++++++++++++++++- ui/src/main/java/edu/wpi/grip/ui/Main.java | 11 ++++++- .../edu/wpi/grip/ui/MainWindowController.java | 3 +- 13 files changed, 79 insertions(+), 13 deletions(-) create mode 100644 core/src/main/java/edu/wpi/grip/core/events/DirtiesSaveEvent.java diff --git a/core/src/main/java/edu/wpi/grip/core/events/ConnectionAddedEvent.java b/core/src/main/java/edu/wpi/grip/core/events/ConnectionAddedEvent.java index b19734b7f4..ec67be3957 100644 --- a/core/src/main/java/edu/wpi/grip/core/events/ConnectionAddedEvent.java +++ b/core/src/main/java/edu/wpi/grip/core/events/ConnectionAddedEvent.java @@ -10,7 +10,7 @@ * An event that occurs when a new connection is added to the pipeline. This is triggered by the * user adding a connection with the GUI. */ -public class ConnectionAddedEvent implements RunPipelineEvent { +public class ConnectionAddedEvent implements RunPipelineEvent, DirtiesSaveEvent { private final Connection connection; /** diff --git a/core/src/main/java/edu/wpi/grip/core/events/ConnectionRemovedEvent.java b/core/src/main/java/edu/wpi/grip/core/events/ConnectionRemovedEvent.java index a5bc37eefa..a1165f3ad3 100644 --- a/core/src/main/java/edu/wpi/grip/core/events/ConnectionRemovedEvent.java +++ b/core/src/main/java/edu/wpi/grip/core/events/ConnectionRemovedEvent.java @@ -10,7 +10,7 @@ * An event that occurs when a connection is removed from the pipeline. This is triggered by the * user deleting a connection with the GUI. */ -public class ConnectionRemovedEvent { +public class ConnectionRemovedEvent implements DirtiesSaveEvent { private final Connection connection; /** diff --git a/core/src/main/java/edu/wpi/grip/core/events/DirtiesSaveEvent.java b/core/src/main/java/edu/wpi/grip/core/events/DirtiesSaveEvent.java new file mode 100644 index 0000000000..b66a591124 --- /dev/null +++ b/core/src/main/java/edu/wpi/grip/core/events/DirtiesSaveEvent.java @@ -0,0 +1,19 @@ +package edu.wpi.grip.core.events; + +/** + * An event that can potentially dirty the save file. + * + *

These events ensure that anything that changes causes the save file to be flagged as dirty and + * in need of being saved for the project to be deemed "clean" again. + */ +public interface DirtiesSaveEvent { + + /** + * Some events may have more logic regarding whether they make the save dirty or not. + * + * @return True if this event should dirty the project save + */ + default boolean doesDirtySave() { + return true; + } +} diff --git a/core/src/main/java/edu/wpi/grip/core/events/SocketChangedEvent.java b/core/src/main/java/edu/wpi/grip/core/events/SocketChangedEvent.java index 6cabaf41a9..dce54499a6 100644 --- a/core/src/main/java/edu/wpi/grip/core/events/SocketChangedEvent.java +++ b/core/src/main/java/edu/wpi/grip/core/events/SocketChangedEvent.java @@ -10,7 +10,7 @@ * An event that occurs when the value stored in a socket changes. This can happen, for example, as * the result of an operation completing, or as a response to user input. */ -public class SocketChangedEvent implements RunPipelineEvent { +public class SocketChangedEvent implements RunPipelineEvent, DirtiesSaveEvent { private final Socket socket; /** @@ -31,6 +31,18 @@ public boolean isRegarding(Socket socket) { return socket.equals(this.socket); } + /** + * This event will only dirty the save if the InputSocket does not have connections. + * Thus the value can only have been changed by a UI component. + * If the socket has connections then the value change is triggered by another socket's change. + * + * @return True if this should dirty the save. + */ + @Override + public boolean doesDirtySave() { + return socket.getDirection() == Socket.Direction.INPUT && socket.getConnections().isEmpty(); + } + @Override public boolean pipelineShouldRun() { /* diff --git a/core/src/main/java/edu/wpi/grip/core/events/SocketPreviewChangedEvent.java b/core/src/main/java/edu/wpi/grip/core/events/SocketPreviewChangedEvent.java index 4a7ab87801..ce58eb9afc 100644 --- a/core/src/main/java/edu/wpi/grip/core/events/SocketPreviewChangedEvent.java +++ b/core/src/main/java/edu/wpi/grip/core/events/SocketPreviewChangedEvent.java @@ -10,7 +10,7 @@ * An event that occurs when a {@link OutputSocket} is set to be either previewed or not previewed. * The GUI listens for these events so it knows which sockets to show previews for. */ -public class SocketPreviewChangedEvent { +public class SocketPreviewChangedEvent implements DirtiesSaveEvent { private final OutputSocket socket; /** diff --git a/core/src/main/java/edu/wpi/grip/core/events/SourceAddedEvent.java b/core/src/main/java/edu/wpi/grip/core/events/SourceAddedEvent.java index 6dde46a507..616ad970c9 100644 --- a/core/src/main/java/edu/wpi/grip/core/events/SourceAddedEvent.java +++ b/core/src/main/java/edu/wpi/grip/core/events/SourceAddedEvent.java @@ -13,7 +13,7 @@ * * @see Source */ -public class SourceAddedEvent { +public class SourceAddedEvent implements DirtiesSaveEvent { private final Source source; /** diff --git a/core/src/main/java/edu/wpi/grip/core/events/SourceRemovedEvent.java b/core/src/main/java/edu/wpi/grip/core/events/SourceRemovedEvent.java index 9b89263a65..f7f2e9cb20 100644 --- a/core/src/main/java/edu/wpi/grip/core/events/SourceRemovedEvent.java +++ b/core/src/main/java/edu/wpi/grip/core/events/SourceRemovedEvent.java @@ -13,7 +13,7 @@ * * @see Source */ -public class SourceRemovedEvent { +public class SourceRemovedEvent implements DirtiesSaveEvent { private final Source source; /** diff --git a/core/src/main/java/edu/wpi/grip/core/events/StepAddedEvent.java b/core/src/main/java/edu/wpi/grip/core/events/StepAddedEvent.java index 230b2ad3bd..5427bd056f 100644 --- a/core/src/main/java/edu/wpi/grip/core/events/StepAddedEvent.java +++ b/core/src/main/java/edu/wpi/grip/core/events/StepAddedEvent.java @@ -15,7 +15,7 @@ * An event that occurs when a new step is added to the pipeline. This is triggered by the user * adding a step with the GUI. */ -public class StepAddedEvent { +public class StepAddedEvent implements DirtiesSaveEvent { private final Step step; private final OptionalInt index; diff --git a/core/src/main/java/edu/wpi/grip/core/events/StepMovedEvent.java b/core/src/main/java/edu/wpi/grip/core/events/StepMovedEvent.java index 7dcc3ab3f9..3b9adc2786 100644 --- a/core/src/main/java/edu/wpi/grip/core/events/StepMovedEvent.java +++ b/core/src/main/java/edu/wpi/grip/core/events/StepMovedEvent.java @@ -9,7 +9,7 @@ /** * An event that occurs when a new step is moved from one position to another in the pipeline. */ -public class StepMovedEvent { +public class StepMovedEvent implements DirtiesSaveEvent { private final Step step; private final int distance; diff --git a/core/src/main/java/edu/wpi/grip/core/events/StepRemovedEvent.java b/core/src/main/java/edu/wpi/grip/core/events/StepRemovedEvent.java index 1f6b0eb1e7..005f6d5491 100644 --- a/core/src/main/java/edu/wpi/grip/core/events/StepRemovedEvent.java +++ b/core/src/main/java/edu/wpi/grip/core/events/StepRemovedEvent.java @@ -10,7 +10,7 @@ * An event that occurs when a new step is removed from the pipeline. This is triggered by the user * deleting a step from the GUI. */ -public class StepRemovedEvent { +public class StepRemovedEvent implements DirtiesSaveEvent { private final Step step; /** diff --git a/core/src/main/java/edu/wpi/grip/core/serialization/Project.java b/core/src/main/java/edu/wpi/grip/core/serialization/Project.java index e9a6416f3d..efc5a8355e 100644 --- a/core/src/main/java/edu/wpi/grip/core/serialization/Project.java +++ b/core/src/main/java/edu/wpi/grip/core/serialization/Project.java @@ -2,8 +2,10 @@ import edu.wpi.grip.core.Pipeline; import edu.wpi.grip.core.PipelineRunner; +import edu.wpi.grip.core.events.DirtiesSaveEvent; import com.google.common.annotations.VisibleForTesting; +import com.google.common.eventbus.Subscribe; import com.google.common.reflect.ClassPath; import com.thoughtworks.xstream.XStream; import com.thoughtworks.xstream.annotations.XStreamAlias; @@ -20,7 +22,8 @@ import java.io.Writer; import java.nio.charset.StandardCharsets; import java.util.Optional; - +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.value.ChangeListener; import javax.inject.Inject; import javax.inject.Singleton; @@ -36,6 +39,7 @@ public class Project { @Inject private PipelineRunner pipelineRunner; private Optional file = Optional.empty(); + private final SimpleBooleanProperty saveIsDirty = new SimpleBooleanProperty(false); @Inject public void initialize(StepConverter stepConverter, @@ -109,6 +113,7 @@ void open(Reader reader) { this.pipeline.clear(); this.xstream.fromXML(reader); pipelineRunner.startAsync(); + saveIsDirty.set(false); } /** @@ -124,5 +129,27 @@ public void save(File file) throws IOException { public void save(Writer writer) { this.xstream.toXML(this.pipeline, writer); + saveIsDirty.set(false); + } + + public boolean isSaveDirty() { + return saveIsDirty.get(); + } + + public void addDirtyListener(ChangeListener changeListener) { + saveIsDirty.addListener(changeListener); + } + + public void removeDirtyListener(ChangeListener changeListener) { + saveIsDirty.removeListener(changeListener); + } + + @Subscribe + public void onDirtiesSaveEvent(DirtiesSaveEvent dirtySaveEvent) { + // Only update the flag the save isn't already dirty + // We don't need to be redundantly checking if the event dirties the save + if (!saveIsDirty.get() && dirtySaveEvent.doesDirtySave()) { + saveIsDirty.set(true); + } } } diff --git a/ui/src/main/java/edu/wpi/grip/ui/Main.java b/ui/src/main/java/edu/wpi/grip/ui/Main.java index d9113dcc20..39d4a28029 100644 --- a/ui/src/main/java/edu/wpi/grip/ui/Main.java +++ b/ui/src/main/java/edu/wpi/grip/ui/Main.java @@ -43,6 +43,7 @@ public class Main extends Application { private final Object dialogLock = new Object(); private static final Logger logger = Logger.getLogger(Main.class.getName()); + private static final String MAIN_TITLE = "GRIP Computer Vision Engine"; /** * JavaFX insists on creating the main application with its own reflection code, so we can't @@ -123,9 +124,17 @@ public void start(Stage stage) throws IOException { cvOperations.addOperations(); notifyPreloader(new Preloader.ProgressNotification(0.9)); + project.addDirtyListener((observable, oldValue, newValue) -> { + if (newValue) { + stage.setTitle(MAIN_TITLE + " | Edited"); + } else { + stage.setTitle(MAIN_TITLE); + } + }); + // If this isn't here this can cause a deadlock on windows. See issue #297 stage.setOnCloseRequest(event -> SafeShutdown.exit(0, Platform::exit)); - stage.setTitle("GRIP Computer Vision Engine"); + stage.setTitle(MAIN_TITLE); stage.getIcons().add(new Image("/edu/wpi/grip/ui/icons/grip.png")); stage.setScene(new Scene(root)); notifyPreloader(new Preloader.ProgressNotification(1.0)); diff --git a/ui/src/main/java/edu/wpi/grip/ui/MainWindowController.java b/ui/src/main/java/edu/wpi/grip/ui/MainWindowController.java index 4850f14886..d41a01a808 100644 --- a/ui/src/main/java/edu/wpi/grip/ui/MainWindowController.java +++ b/ui/src/main/java/edu/wpi/grip/ui/MainWindowController.java @@ -93,7 +93,6 @@ protected void initialize() { .toString()); statusBar.setText(" Pipeline " + stateMessage); }), Platform::runLater); - } /** @@ -103,7 +102,7 @@ protected void initialize() { * @return true If the user has not chosen to */ private boolean showConfirmationDialogAndWait() { - if (!pipeline.getSteps().isEmpty()) { + if (!pipeline.getSteps().isEmpty() && project.isSaveDirty()) { final ButtonType save = new ButtonType("Save"); final ButtonType dontSave = ButtonType.NO; final ButtonType cancel = ButtonType.CANCEL; From 9d52740b2ac613a716c1a5952766332f33637bd3 Mon Sep 17 00:00:00 2001 From: Austin Shalit Date: Mon, 1 Aug 2016 12:23:31 -0700 Subject: [PATCH 2/3] Remove JavaFX from core --- .../wpi/grip/core/serialization/Project.java | 22 +++++-------------- ui/src/main/java/edu/wpi/grip/ui/Main.java | 12 +++++++++- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/core/src/main/java/edu/wpi/grip/core/serialization/Project.java b/core/src/main/java/edu/wpi/grip/core/serialization/Project.java index efc5a8355e..19be072e29 100644 --- a/core/src/main/java/edu/wpi/grip/core/serialization/Project.java +++ b/core/src/main/java/edu/wpi/grip/core/serialization/Project.java @@ -22,8 +22,6 @@ import java.io.Writer; import java.nio.charset.StandardCharsets; import java.util.Optional; -import javafx.beans.property.SimpleBooleanProperty; -import javafx.beans.value.ChangeListener; import javax.inject.Inject; import javax.inject.Singleton; @@ -39,7 +37,7 @@ public class Project { @Inject private PipelineRunner pipelineRunner; private Optional file = Optional.empty(); - private final SimpleBooleanProperty saveIsDirty = new SimpleBooleanProperty(false); + private boolean saveIsDirty = false; @Inject public void initialize(StepConverter stepConverter, @@ -113,7 +111,7 @@ void open(Reader reader) { this.pipeline.clear(); this.xstream.fromXML(reader); pipelineRunner.startAsync(); - saveIsDirty.set(false); + saveIsDirty = false; } /** @@ -129,27 +127,19 @@ public void save(File file) throws IOException { public void save(Writer writer) { this.xstream.toXML(this.pipeline, writer); - saveIsDirty.set(false); + saveIsDirty = false; } public boolean isSaveDirty() { - return saveIsDirty.get(); - } - - public void addDirtyListener(ChangeListener changeListener) { - saveIsDirty.addListener(changeListener); - } - - public void removeDirtyListener(ChangeListener changeListener) { - saveIsDirty.removeListener(changeListener); + return saveIsDirty; } @Subscribe public void onDirtiesSaveEvent(DirtiesSaveEvent dirtySaveEvent) { // Only update the flag the save isn't already dirty // We don't need to be redundantly checking if the event dirties the save - if (!saveIsDirty.get() && dirtySaveEvent.doesDirtySave()) { - saveIsDirty.set(true); + if (!saveIsDirty && dirtySaveEvent.doesDirtySave()) { + saveIsDirty = true; } } } diff --git a/ui/src/main/java/edu/wpi/grip/ui/Main.java b/ui/src/main/java/edu/wpi/grip/ui/Main.java index 39d4a28029..34129f192b 100644 --- a/ui/src/main/java/edu/wpi/grip/ui/Main.java +++ b/ui/src/main/java/edu/wpi/grip/ui/Main.java @@ -3,6 +3,7 @@ import edu.wpi.grip.core.GripCoreModule; import edu.wpi.grip.core.GripFileModule; import edu.wpi.grip.core.PipelineRunner; +import edu.wpi.grip.core.events.DirtiesSaveEvent; import edu.wpi.grip.core.events.UnexpectedThrowableEvent; import edu.wpi.grip.core.http.GripServer; import edu.wpi.grip.core.http.HttpPipelineSwitcher; @@ -31,6 +32,7 @@ import javafx.application.Application; import javafx.application.Platform; import javafx.application.Preloader; +import javafx.beans.property.SimpleBooleanProperty; import javafx.fxml.FXMLLoader; import javafx.scene.Parent; import javafx.scene.Scene; @@ -61,6 +63,7 @@ public class Main extends Application { @Inject private HttpPipelineSwitcher pipelineSwitcher; private Parent root; private boolean headless; + private final SimpleBooleanProperty dirty = new SimpleBooleanProperty(false); public static void main(String[] args) { launch(args); @@ -124,7 +127,7 @@ public void start(Stage stage) throws IOException { cvOperations.addOperations(); notifyPreloader(new Preloader.ProgressNotification(0.9)); - project.addDirtyListener((observable, oldValue, newValue) -> { + dirty.addListener((observable, oldValue, newValue) -> { if (newValue) { stage.setTitle(MAIN_TITLE + " | Edited"); } else { @@ -148,6 +151,13 @@ public void stop() { SafeShutdown.flagStopping(); } + @Subscribe + public void onDirtiesSaveEvent(DirtiesSaveEvent dirtySaveEvent) { + if (dirty.get() != dirtySaveEvent.doesDirtySave()) { + dirty.set(dirtySaveEvent.doesDirtySave()); + } + } + @Subscribe @SuppressWarnings("PMD.AvoidCatchingThrowable") public final void onUnexpectedThrowableEvent(UnexpectedThrowableEvent event) { From feeda2a0e9db392e6b3e7c9bbedd9a9c105c08ab Mon Sep 17 00:00:00 2001 From: Austin Shalit Date: Mon, 1 Aug 2016 12:44:17 -0700 Subject: [PATCH 3/3] Refactor to remove duplicate code. --- .../wpi/grip/core/events/SocketChangedEvent.java | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/core/src/main/java/edu/wpi/grip/core/events/SocketChangedEvent.java b/core/src/main/java/edu/wpi/grip/core/events/SocketChangedEvent.java index dce54499a6..cedeb845d4 100644 --- a/core/src/main/java/edu/wpi/grip/core/events/SocketChangedEvent.java +++ b/core/src/main/java/edu/wpi/grip/core/events/SocketChangedEvent.java @@ -40,22 +40,17 @@ public boolean isRegarding(Socket socket) { */ @Override public boolean doesDirtySave() { - return socket.getDirection() == Socket.Direction.INPUT && socket.getConnections().isEmpty(); + return pipelineShouldRun(); } @Override public boolean pipelineShouldRun() { /* * The pipeline should only flag an update when it is an input changing. A changed output - * doesn't mean - * the pipeline is dirty. - * If the socket is connected to another socket then then the input will only change - * because of the - * pipeline thread. - * In that case we don't want the pipeline to be releasing itself. - * If the connections are empty then the change must have come from the UI so we need to - * run the pipeline - * with the new values. + * doesn't mean the pipeline is dirty. If the socket is connected to another socket then then + * the input will only change because of the pipeline thread. In that case we don't want the + * pipeline to be releasing itself. If the connections are empty then the change must have come + * from the UI so we need to run the pipeline with the new values. */ return socket.getDirection().equals(Socket.Direction.INPUT) && socket.getConnections() .isEmpty();