From d102d016b758ddb641043e504387ce3149cde6f3 Mon Sep 17 00:00:00 2001 From: Niels Vistisen Date: Tue, 12 Jul 2022 15:33:52 +0200 Subject: [PATCH 001/158] WIP: Merging Ecdar 2.2 simulator into main --- src/main/java/ecdar/Ecdar.java | 21 +- .../controllers/LeftSimPaneController.java | 25 + .../ecdar/controllers/ProcessController.java | 279 ++++++++++ .../controllers/RightSimPaneController.java | 21 + .../ecdar/controllers/SimEdgeController.java | 421 +++++++++++++++ .../controllers/SimEdgePresentation.java | 32 ++ .../controllers/SimLocationController.java | 124 +++++ .../controllers/SimLocationPresentation.java | 490 ++++++++++++++++++ .../controllers/SimulatorController.java | 155 ++++++ .../SimulatorOverviewController.java | 372 +++++++++++++ .../TracePaneElementController.java | 182 +++++++ .../controllers/TransitionController.java | 45 ++ .../TransitionPaneElementController.java | 245 +++++++++ .../LeftSimPanePresentation.java | 37 ++ .../presentations/ProcessPresentation.java | 282 ++++++++++ .../RightSimPanePresentation.java | 45 ++ .../SimulatorOverviewPresentation.java | 20 + .../presentations/SimulatorPresentation.java | 21 + .../TracePaneElementPresentation.java | 96 ++++ .../TransitionPaneElementPresentation.java | 69 +++ .../presentations/TransitionPresentation.java | 98 ++++ .../java/ecdar/simulation/SimulationEdge.java | 8 + .../ecdar/simulation/SimulationHandler.java | 407 +++++++++++++++ .../ecdar/simulation/SimulationLocation.java | 8 + .../ecdar/simulation/SimulationState.java | 42 ++ .../simulation/SimulationStateSuccessor.java | 15 + .../java/ecdar/simulation/Transition.java | 17 + .../LeftSimPanePresentation.fxml | 37 ++ .../RightSimPanePresentation.fxml | 25 + .../SimulatorOverviewPresentation.fxml | 13 + .../presentations/SimulatorPresentation.fxml | 55 ++ .../TracePaneElementPresentation.fxml | 49 ++ .../TransitionPaneElementPresentation.fxml | 66 +++ .../presentations/TransitionPresentation.fxml | 18 + 34 files changed, 3832 insertions(+), 8 deletions(-) create mode 100755 src/main/java/ecdar/controllers/LeftSimPaneController.java create mode 100755 src/main/java/ecdar/controllers/ProcessController.java create mode 100755 src/main/java/ecdar/controllers/RightSimPaneController.java create mode 100755 src/main/java/ecdar/controllers/SimEdgeController.java create mode 100755 src/main/java/ecdar/controllers/SimEdgePresentation.java create mode 100755 src/main/java/ecdar/controllers/SimLocationController.java create mode 100755 src/main/java/ecdar/controllers/SimLocationPresentation.java create mode 100755 src/main/java/ecdar/controllers/SimulatorController.java create mode 100755 src/main/java/ecdar/controllers/SimulatorOverviewController.java create mode 100755 src/main/java/ecdar/controllers/TracePaneElementController.java create mode 100755 src/main/java/ecdar/controllers/TransitionController.java create mode 100755 src/main/java/ecdar/controllers/TransitionPaneElementController.java create mode 100755 src/main/java/ecdar/presentations/LeftSimPanePresentation.java create mode 100755 src/main/java/ecdar/presentations/ProcessPresentation.java create mode 100755 src/main/java/ecdar/presentations/RightSimPanePresentation.java create mode 100755 src/main/java/ecdar/presentations/SimulatorOverviewPresentation.java create mode 100755 src/main/java/ecdar/presentations/SimulatorPresentation.java create mode 100755 src/main/java/ecdar/presentations/TracePaneElementPresentation.java create mode 100755 src/main/java/ecdar/presentations/TransitionPaneElementPresentation.java create mode 100755 src/main/java/ecdar/presentations/TransitionPresentation.java create mode 100644 src/main/java/ecdar/simulation/SimulationEdge.java create mode 100755 src/main/java/ecdar/simulation/SimulationHandler.java create mode 100644 src/main/java/ecdar/simulation/SimulationLocation.java create mode 100644 src/main/java/ecdar/simulation/SimulationState.java create mode 100644 src/main/java/ecdar/simulation/SimulationStateSuccessor.java create mode 100644 src/main/java/ecdar/simulation/Transition.java create mode 100755 src/main/resources/ecdar/presentations/LeftSimPanePresentation.fxml create mode 100755 src/main/resources/ecdar/presentations/RightSimPanePresentation.fxml create mode 100755 src/main/resources/ecdar/presentations/SimulatorOverviewPresentation.fxml create mode 100755 src/main/resources/ecdar/presentations/SimulatorPresentation.fxml create mode 100755 src/main/resources/ecdar/presentations/TracePaneElementPresentation.fxml create mode 100755 src/main/resources/ecdar/presentations/TransitionPaneElementPresentation.fxml create mode 100755 src/main/resources/ecdar/presentations/TransitionPresentation.fxml diff --git a/src/main/java/ecdar/Ecdar.java b/src/main/java/ecdar/Ecdar.java index fba5513d..6a6801f0 100644 --- a/src/main/java/ecdar/Ecdar.java +++ b/src/main/java/ecdar/Ecdar.java @@ -4,6 +4,7 @@ import ecdar.abstractions.Project; import ecdar.backend.BackendDriver; import ecdar.backend.BackendHelper; +import ecdar.simulation.SimulationHandler; import ecdar.code_analysis.CodeAnalysis; import ecdar.controllers.EcdarController; import ecdar.presentations.BackgroundThreadPresentation; @@ -54,6 +55,7 @@ public class Ecdar extends Application { public static BooleanProperty shouldRunBackgroundQueries = new SimpleBooleanProperty(true); private static final BooleanProperty isSplit = new SimpleBooleanProperty(true); //Set to true to ensure correct behaviour at first toggle. private static final BackendDriver backendDriver = new BackendDriver(); + private static SimulationHandler simulationHandler; private Stage debugStage; /** @@ -122,6 +124,16 @@ public static Project getProject() { return project; } + /** + * Returns the backend driver used to execute queries and handle simulation + * @return BackendDriver + */ + public static BackendDriver getBackendDriver() { + return backendDriver; + } + + public static SimulationHandler getSimulationHandler() { return simulationHandler; } + public static EcdarPresentation getPresentation() { return presentation; } @@ -183,14 +195,6 @@ public static BooleanProperty toggleCanvasSplit() { return isSplit; } - /** - * Returns the backend driver used to execute queries and handle simulation - * @return BackendDriver - */ - public static BackendDriver getBackendDriver() { - return backendDriver; - } - public static double getDpiScale() { if (!autoScalingEnabled.getValue()) return 1; @@ -206,6 +210,7 @@ private void forceCreateFolder(final String directoryPath) throws IOException { public void start(final Stage stage) { // Load or create new project project = new Project(); + simulationHandler = new SimulationHandler(); // Set the title for the application stage.setTitle("Ecdar " + VERSION); diff --git a/src/main/java/ecdar/controllers/LeftSimPaneController.java b/src/main/java/ecdar/controllers/LeftSimPaneController.java new file mode 100755 index 00000000..d8dc30eb --- /dev/null +++ b/src/main/java/ecdar/controllers/LeftSimPaneController.java @@ -0,0 +1,25 @@ +package ecdar.controllers; + +import ecdar.presentations.TracePaneElementPresentation; +import ecdar.presentations.TransitionPaneElementPresentation; +import javafx.fxml.FXML; +import javafx.fxml.Initializable; +import javafx.scene.control.ScrollPane; +import javafx.scene.layout.StackPane; +import javafx.scene.layout.VBox; +import java.net.URL; +import java.util.ResourceBundle; + +public class LeftSimPaneController implements Initializable { + public StackPane root; + public ScrollPane scrollPane; + public VBox scrollPaneVbox; + + public TransitionPaneElementPresentation transitionPanePresentation; + public TracePaneElementPresentation tracePanePresentation; + + @Override + public void initialize(URL location, ResourceBundle resources) { + + } +} diff --git a/src/main/java/ecdar/controllers/ProcessController.java b/src/main/java/ecdar/controllers/ProcessController.java new file mode 100755 index 00000000..84f9ff22 --- /dev/null +++ b/src/main/java/ecdar/controllers/ProcessController.java @@ -0,0 +1,279 @@ +package ecdar.controllers; + +import com.jfoenix.controls.JFXRippler; +import ecdar.abstractions.*; +import ecdar.presentations.SimEdgePresentation; +import ecdar.presentations.SimLocationPresentation; +import ecdar.simulation.SimulationEdge; +import ecdar.simulation.SimulationLocation; +import javafx.animation.Interpolator; +import javafx.animation.Transition; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.collections.FXCollections; +import javafx.collections.ObservableMap; +import javafx.fxml.Initializable; +import javafx.scene.Node; +import javafx.scene.input.MouseEvent; +import javafx.scene.layout.*; +import javafx.scene.shape.Circle; +import javafx.util.Duration; +import org.kordamp.ikonli.javafx.FontIcon; + +import java.math.BigDecimal; +import java.net.URL; +import java.util.*; + +/** + * The controller for the process shown in the {@link SimulatorOverviewController} + */ +public class ProcessController extends ModelController implements Initializable { + public StackPane componentPane; + public Pane modelContainerEdge; + public Pane modelContainerLocation; + public JFXRippler toggleValuesButton; + public VBox valueArea; + public FontIcon toggleValueButtonIcon; + private ObjectProperty component; + + /** + * Keep track of locations/edges and their associated presentation class, by having the as key-value pairs in a Map + * E.g. a {@link Location} key is the model behind the value {@link SimLocationPresentation} view + */ + private final Map locationPresentationMap = new HashMap<>(); + private final Map edgePresentationMap = new HashMap<>(); + private final ObservableMap variables = FXCollections.observableHashMap(); + private final ObservableMap clocks = FXCollections.observableHashMap(); + + @Override + public void initialize(final URL location, final ResourceBundle resources) { + component = new SimpleObjectProperty<>(new Component(true)); + initializeValues(); + } + + private void initializeValues() { + final Circle circle = new Circle(0); + if (getComponent().isDeclarationOpen()) { + circle.setRadius(1000); + } + final ObjectProperty clip = new SimpleObjectProperty<>(circle); + valueArea.clipProperty().bind(clip); + clip.set(circle); + } + + /** + * Highlights the edges and accompanying source/target locations in the process + * @param edges The edges to highlight + */ + public void highlightEdges(final SimulationEdge[] edges) { + for (int i = 0; i < edges.length; i++) { + final SimulationEdge edge = edges[i]; + + // Note that SimulationEdge contains a Location type from the UPPAAL libraries + // but we use a different Location class in our Maps + final Location source = edge.getSource(); + String sourceName = ""; + final Location target = edge.getTarget(); + String targetName = ""; + + // Match the Locations of the SimulationEdge with a SimulationLocation of the process, + // so we can get the source/target names (names are not available on a Location) + for (SimulationLocation sysloc : edge.getProcess().getLocations()) { + if (sysloc.getLocation() == source) { + sourceName = sysloc.getName(); + } else if (sysloc == target) { + targetName = sysloc.getName(); + } + } + + // If target name is empty the edge is a self loop + if (targetName == "") { + targetName = sourceName; + } + + boolean isSourceUniversal = false; + + // Iterate through all locations to check for Universal and Inconsistent locations + // The name of a Universal location may be "U2" in our system, but it is mapped to "Universal" in the engine + // This loop maps "Universal" to for example "U2" + for (Map.Entry locEntry: locationPresentationMap.entrySet()) { + if(locEntry.getKey().getType() == Location.Type.UNIVERSAL) { + if(sourceName.equals("Universal")) { + sourceName = locEntry.getKey().getId(); + isSourceUniversal = true; + } + + if(targetName.equals("Universal")) { + targetName = locEntry.getKey().getId(); + } + } + + if(locEntry.getKey().getType() == Location.Type.INCONSISTENT) { + if(sourceName.equals("Inconsistent")) { + sourceName = locEntry.getKey().getId(); + } + + if(targetName.equals("Inconsistent")) { + targetName = locEntry.getKey().getId(); + } + } + } + + // The edge name may contain ! or ?, and we need to replace those so we can compare our stored edge + String edgeName = edge.getName(); + EdgeStatus edgeStatus = EdgeStatus.INPUT; + if (edgeName.contains("?")) { + edgeName = edgeName.replace("?", ""); + } else if (edgeName.contains("!")) { + edgeName = edgeName.replace("!", ""); + edgeStatus = EdgeStatus.OUTPUT; + } + + // Self loop on a Universal locations means that the edge name should be mapped to * + if (isSourceUniversal && sourceName.equals(targetName)) { + edgeName = "*"; + } + + highlightEdge(edgeName, edgeStatus, sourceName, targetName); + } + } + + /** + * Unhighlights all edges and locations in the process + */ + public void unhighlightProcess() { + edgePresentationMap.forEach((key, value) -> value.getController().unhighlight()); + locationPresentationMap.forEach((key, value) -> value.unhighlight()); + } + + /** + * Helper method that finds the {@link SimLocationPresentation} and highlights it. + * Calls {@link ProcessController#highlightEdgeLocations(String, String)} to highlight the source/targets locations + * @param edgeName The name of the edge + * @param edgeStatus The status (input/output) of the edge to highlight + * @param sourceName The name of the source location + * @param targetName The name of the target location + */ + private void highlightEdge(final String edgeName, final EdgeStatus edgeStatus, final String sourceName, final String targetName) { + for (Map.Entry entry: edgePresentationMap.entrySet()) { + final String keyName = entry.getKey().getSync(); + final String keySourceName = entry.getKey().getSourceLocation().getId(); + final String keyTargetName = entry.getKey().getTargetLocation().getId(); + + // Multiple edges may have the same name, so we also check that the source and target match this edge + if(keyName.equals(edgeName) && + keySourceName.equals(sourceName) && + keyTargetName.equals(targetName) && + entry.getKey().ioStatus.get() == edgeStatus) { + + entry.getValue().getController().highlight(); + highlightEdgeLocations(keySourceName, keyTargetName); + } + } + } + + /** + * Helper method that finds the source/target {@link SimLocationPresentation} and highlights it + * @param sourceName The name of the source location + * @param targetName The name of the target location + */ + private void highlightEdgeLocations(final String sourceName, final String targetName) { + for (Map.Entry locEntry: locationPresentationMap.entrySet()) { + final String locName = locEntry.getKey().getId(); + + // Check if location is either source or target and highlight it + if(locName.equals(sourceName) || locName.equals(targetName)) { + locEntry.getValue().highlight(); + } + } + } + + /** + * Method that highlights all locations with the same name as the input {@link SimulationLocation} + * @param location The locations to highlight + */ + public void highlightLocation(final SimulationLocation location) { + for (Map.Entry locEntry: locationPresentationMap.entrySet()) { + final String locName = locEntry.getKey().getId(); + + if(locName.equals(location.getName())) { + locEntry.getValue().highlight(); + } + } + } + + /** + * Sets the component which is going to be shown as a process.
+ * This also initializes the rest of the views needed for the process to be shown properly + * @param component the component of the process + */ + public void setComponent(final Component component){ + this.component.set(component); + modelContainerEdge.getChildren().clear(); + modelContainerLocation.getChildren().clear(); + + component.getLocations().forEach(location -> { + final SimLocationPresentation lp = new SimLocationPresentation(location, component); + modelContainerLocation.getChildren().add(lp); + locationPresentationMap.put(location, lp); + }); + + component.getEdges().forEach(edge -> { + final SimEdgePresentation ep = new SimEdgePresentation(edge, component); + modelContainerEdge.getChildren().add(ep); + edgePresentationMap.put(edge, ep); + }); + } + + public void toggleValues(final MouseEvent mouseEvent) { + final Circle circle = new Circle(0); + circle.setCenterX(component.get().getBox().getWidth() - (toggleValuesButton.getWidth() - mouseEvent.getX())); + circle.setCenterY(-1 * mouseEvent.getY()); + + final ObjectProperty clip = new SimpleObjectProperty<>(circle); + valueArea.clipProperty().bind(clip); + + final Transition rippleEffect = new Transition() { + private final double maxRadius = Math.sqrt(Math.pow(getComponent().getBox().getWidth(), 2) + Math.pow(getComponent().getBox().getHeight(), 2)); + { + setCycleDuration(Duration.millis(500)); + } + + protected void interpolate(final double fraction) { + if (getComponent().isDeclarationOpen()) { + circle.setRadius(fraction * maxRadius); + } else { + circle.setRadius(maxRadius - fraction * maxRadius); + } + clip.set(circle); + } + }; + + final Interpolator interpolator = Interpolator.SPLINE(0.785, 0.135, 0.15, 0.86); + rippleEffect.setInterpolator(interpolator); + + rippleEffect.play(); + getComponent().declarationOpenProperty().set(!getComponent().isDeclarationOpen()); + } + + /** + * Gets the component linked to this process + * @return the component of the process + */ + public Component getComponent(){ + return component.get(); + } + + @Override + public HighLevelModelObject getModel() { + return component.get(); + } + + public ObservableMap getVariables() { + return variables; + } + + public ObservableMap getClocks() { + return clocks; + } +} diff --git a/src/main/java/ecdar/controllers/RightSimPaneController.java b/src/main/java/ecdar/controllers/RightSimPaneController.java new file mode 100755 index 00000000..c7d5b6db --- /dev/null +++ b/src/main/java/ecdar/controllers/RightSimPaneController.java @@ -0,0 +1,21 @@ +package ecdar.controllers; + +import ecdar.presentations.QueryPaneElementPresentation; +import javafx.fxml.Initializable; +import javafx.scene.layout.*; + +import java.net.URL; +import java.util.ResourceBundle; + +/** + * Controller class for the right pane in the simulator + */ +public class RightSimPaneController implements Initializable { + public StackPane root; + public VBox scrollPaneVbox; + public QueryPaneElementPresentation queryPaneElement; + + @Override + public void initialize(URL location, ResourceBundle resources) { + } +} diff --git a/src/main/java/ecdar/controllers/SimEdgeController.java b/src/main/java/ecdar/controllers/SimEdgeController.java new file mode 100755 index 00000000..2ca0048f --- /dev/null +++ b/src/main/java/ecdar/controllers/SimEdgeController.java @@ -0,0 +1,421 @@ +package ecdar.controllers; + +import ecdar.abstractions.Component; +import ecdar.abstractions.Edge; +import ecdar.abstractions.EdgeStatus; +import ecdar.abstractions.Nail; +import ecdar.model_canvas.arrow_heads.SimpleArrowHead; +import ecdar.presentations.Link; +import ecdar.presentations.NailPresentation; +import ecdar.presentations.SimNailPresentation; +import ecdar.utility.Highlightable; +import ecdar.utility.colors.Color; +import ecdar.utility.helpers.BindingHelper; +import ecdar.utility.helpers.Circular; +import ecdar.utility.helpers.ItemDragHelper; +import ecdar.utility.helpers.SelectHelper; +import ecdar.utility.keyboard.KeyboardTracker; +import javafx.animation.KeyFrame; +import javafx.animation.KeyValue; +import javafx.animation.Timeline; +import javafx.application.Platform; +import javafx.beans.property.*; +import javafx.beans.value.ChangeListener; +import javafx.collections.FXCollections; +import javafx.collections.ListChangeListener; +import javafx.collections.ObservableList; +import javafx.fxml.Initializable; +import javafx.scene.Group; +import javafx.scene.Node; +import javafx.util.Duration; + +import java.net.URL; +import java.util.HashMap; +import java.util.Map; +import java.util.ResourceBundle; +import java.util.function.Consumer; + +/** + * The controller for the edge shown in the {@link ecdar.presentations.SimulatorOverviewPresentation} + */ +public class SimEdgeController implements Initializable, Highlightable { + private final ObservableList links = FXCollections.observableArrayList(); + private final ObjectProperty edge = new SimpleObjectProperty<>(); + private final ObjectProperty component = new SimpleObjectProperty<>(); + private final SimpleArrowHead simpleArrowHead = new SimpleArrowHead(); + private final SimpleBooleanProperty isHoveringEdge = new SimpleBooleanProperty(false); + private final SimpleIntegerProperty timeHoveringEdge = new SimpleIntegerProperty(0); + private final Map nailNailPresentationMap = new HashMap<>(); + public Group edgeRoot; + private Runnable collapseNail; + private Consumer enlargeNail; + private Consumer shrinkNail; + + @Override + public void initialize(final URL location, final ResourceBundle resources) { + initializeNailCollapse(); + + edge.addListener((obsEdge, oldEdge, newEdge) -> { + newEdge.targetCircularProperty().addListener(getNewTargetCircularListener(newEdge)); + component.addListener(getComponentChangeListener(newEdge)); + + // Invalidate the list of edges (to update UI and errors) + newEdge.targetCircularProperty().addListener(observable -> { + getComponent().removeEdge(getEdge()); + getComponent().addEdge(getEdge()); + }); + + // When an edge updates highlight property, + // we want to update the view to reflect current highlight property + edge.get().isHighlightedProperty().addListener(v -> { + if(edge.get().getIsHighlighted()) { + this.highlight(); + } else { + this.unhighlight(); + } + }); + }); + + ensureNailsInFront(); + } + + private void ensureNailsInFront() { + // When ever changes happens to the children of the edge root force nails in front and other elements to back + edgeRoot.getChildren().addListener((ListChangeListener) c -> { + while (c.next()) { + for (int i = 0; i < c.getAddedSize(); i++) { + final Node node = c.getAddedSubList().get(i); + if (node instanceof NailPresentation) { + node.toFront(); + } else { + node.toBack(); + } + } + } + }); + } + + private ChangeListener getComponentChangeListener(final Edge newEdge) { + return (obsComponent, oldComponent, newComponent) -> { + // Draw new edge from a location + if (newEdge.getNails().isEmpty() && newEdge.getTargetCircular() == null) { + final Link link = new Link(); + // Make dashed line, if output edge + if (newEdge.getStatus() == EdgeStatus.OUTPUT) link.makeDashed(); + links.add(link); + + // Add the link and its arrowhead to the view + edgeRoot.getChildren().addAll(link, simpleArrowHead); + + // Bind the first link and the arrowhead from the source location to the mouse + BindingHelper.bind(link, simpleArrowHead, newEdge.getSourceCircular(), + newComponent.getBox().getXProperty(), newComponent.getBox().getYProperty()); + } else if (newEdge.getTargetCircular() != null) { + + edgeRoot.getChildren().add(simpleArrowHead); + + final Circular[] previous = {newEdge.getSourceCircular()}; + + newEdge.getNails().forEach(nail -> { + final Link link = new Link(); + if (newEdge.getStatus() == EdgeStatus.OUTPUT) link.makeDashed(); + links.add(link); + + final SimNailPresentation nailPresentation = new SimNailPresentation(nail, newEdge, getComponent(), this); + nailNailPresentationMap.put(nail, nailPresentation); + + edgeRoot.getChildren().addAll(link, nailPresentation); + BindingHelper.bind(link, previous[0], nail); + + previous[0] = nail; + }); + + final Link link = new Link(); + if (newEdge.getStatus() == EdgeStatus.OUTPUT) link.makeDashed(); + links.add(link); + + edgeRoot.getChildren().add(link); + BindingHelper.bind(link, simpleArrowHead, previous[0], newEdge.getTargetCircular()); + } + + // Changes are made to the nails list + newEdge.getNails().addListener(getNailsChangeListener(newEdge, newComponent)); + + }; + } + + private ListChangeListener getNailsChangeListener(final Edge newEdge, final Component newComponent) { + return change -> { + while (change.next()) { + // There were added some nails + change.getAddedSubList().forEach(newNail -> { + // Create a new nail presentation based on the abstraction added to the list + final SimNailPresentation newNailPresentation = new SimNailPresentation(newNail, newEdge, newComponent, this); + nailNailPresentationMap.put(newNail, newNailPresentation); + + edgeRoot.getChildren().addAll(newNailPresentation); + + if (newEdge.getTargetCircular() != null) { + final int indexOfNewNail = edge.get().getNails().indexOf(newNail); + + final Link newLink = new Link(); + if (newEdge.getStatus() == EdgeStatus.OUTPUT) newLink.makeDashed(); + final Link pressedLink = links.get(indexOfNewNail); + links.add(indexOfNewNail, newLink); + + edgeRoot.getChildren().addAll(newLink); + + Circular oldStart = getEdge().getSourceCircular(); + Circular oldEnd = getEdge().getTargetCircular(); + + if (indexOfNewNail != 0) { + oldStart = getEdge().getNails().get(indexOfNewNail - 1); + } + + if (indexOfNewNail != getEdge().getNails().size() - 1) { + oldEnd = getEdge().getNails().get(indexOfNewNail + 1); + } + + BindingHelper.bind(newLink, oldStart, newNail); + + if (oldEnd.equals(getEdge().getTargetCircular())) { + BindingHelper.bind(pressedLink, simpleArrowHead, newNail, oldEnd); + } else { + BindingHelper.bind(pressedLink, newNail, oldEnd); + } + + if (isHoveringEdge.get()) { + enlargeNail.accept(newNail); + } + + } else { + // The previous last link must end in the new nail + final Link lastLink = links.get(links.size() - 1); + + // If the nail is the first in the list, bind it to the source location + // otherwise, bind it the the previous nail + final int nailIndex = edge.get().getNails().indexOf(newNail); + if (nailIndex == 0) { + BindingHelper.bind(lastLink, newEdge.getSourceCircular(), newNail); + } else { + final Nail previousNail = edge.get().getNails().get(nailIndex - 1); + BindingHelper.bind(lastLink, previousNail, newNail); + } + + // Create a new link that will bind from the new nail to the mouse + final Link newLink = new Link(); + if (newEdge.getStatus() == EdgeStatus.OUTPUT) newLink.makeDashed(); + links.add(newLink); + BindingHelper.bind(newLink, simpleArrowHead, newNail, newComponent.getBox().getXProperty(), newComponent.getBox().getYProperty()); + edgeRoot.getChildren().add(newLink); + } + }); + + change.getRemoved().forEach(removedNail -> { + final int removedIndex = change.getFrom(); + final SimNailPresentation removedNailPresentation = nailNailPresentationMap.remove(removedNail); + final Link danglingLink = links.get(removedIndex + 1); + edgeRoot.getChildren().remove(removedNailPresentation); + edgeRoot.getChildren().remove(links.get(removedIndex)); + + Circular newFrom = getEdge().getSourceCircular(); + Circular newTo = getEdge().getTargetCircular(); + + if (removedIndex > 0) { + newFrom = getEdge().getNails().get(removedIndex - 1); + } + + if (removedIndex - 1 != getEdge().getNails().size() - 1) { + newTo = getEdge().getNails().get(removedIndex); + } + + if (newTo.equals(getEdge().getTargetCircular())) { + BindingHelper.bind(danglingLink, simpleArrowHead, newFrom, newTo); + } else { + BindingHelper.bind(danglingLink, newFrom, newTo); + } + links.remove(removedIndex); + }); + } + }; + } + + private ChangeListener getNewTargetCircularListener(final Edge newEdge) { + // When the target location is set, finish drawing the edge + return (obsTargetLocation, oldTargetCircular, newTargetCircular) -> { + // If the nails list is empty, directly connect the source and target locations + // otherwise, bind the line from the last nail to the target location + final Link lastLink = links.get(links.size() - 1); + final ObservableList nails = getEdge().getNails(); + if (nails.size() == 0) { + BindingHelper.bind(lastLink, simpleArrowHead, newEdge.getSourceCircular(), newEdge.getTargetCircular()); + } else { + final Nail lastNail = nails.get(nails.size() - 1); + BindingHelper.bind(lastLink, simpleArrowHead, lastNail, newEdge.getTargetCircular()); + } + + KeyboardTracker.unregisterKeybind(KeyboardTracker.ABANDON_EDGE); + + // When the target location is set the + edgeRoot.setMouseTransparent(false); + }; + } + + /** + * Initializes functionality to enlarge, shirk, and collapse nails + */ + private void initializeNailCollapse() { + enlargeNail = nail -> { + if (!nail.getPropertyType().equals(Edge.PropertyType.NONE)) return; + final Timeline animation = new Timeline(); + + final KeyValue radius0 = new KeyValue(nail.radiusProperty(), NailPresentation.COLLAPSED_RADIUS); + final KeyValue radius2 = new KeyValue(nail.radiusProperty(), NailPresentation.HOVERED_RADIUS * 1.2); + final KeyValue radius1 = new KeyValue(nail.radiusProperty(), NailPresentation.HOVERED_RADIUS); + + final KeyFrame kf1 = new KeyFrame(Duration.millis(0), radius0); + final KeyFrame kf2 = new KeyFrame(Duration.millis(80), radius2); + final KeyFrame kf3 = new KeyFrame(Duration.millis(100), radius1); + + animation.getKeyFrames().addAll(kf1, kf2, kf3); + animation.play(); + }; + shrinkNail = nail -> { + if (!nail.getPropertyType().equals(Edge.PropertyType.NONE)) return; + final Timeline animation = new Timeline(); + + final KeyValue radius0 = new KeyValue(nail.radiusProperty(), NailPresentation.COLLAPSED_RADIUS); + final KeyValue radius1 = new KeyValue(nail.radiusProperty(), NailPresentation.HOVERED_RADIUS); + + final KeyFrame kf1 = new KeyFrame(Duration.millis(0), radius1); + final KeyFrame kf2 = new KeyFrame(Duration.millis(100), radius0); + + animation.getKeyFrames().addAll(kf1, kf2); + animation.play(); + }; + + collapseNail = () -> { + final int interval = 50; + int previousValue = 1; + + try { + while (true) { + Thread.sleep(interval); + + if (isHoveringEdge.get()) { + // Do not let the timer go above this threshold + if (timeHoveringEdge.get() <= 500) { + timeHoveringEdge.set(timeHoveringEdge.get() + interval); + } + } else { + timeHoveringEdge.set(timeHoveringEdge.get() - interval); + } + + if (previousValue >= 0 && timeHoveringEdge.get() < 0) { + // Run on UI thread + Platform.runLater(() -> { + // Collapse all nails + getEdge().getNails().forEach(shrinkNail); + }); + break; + } + previousValue = timeHoveringEdge.get(); + } + + } catch (final InterruptedException e) { + e.printStackTrace(); + } + }; + } + + public Edge getEdge() { + return edge.get(); + } + + public void setEdge(final Edge edge) { + this.edge.set(edge); + } + + public ObjectProperty edgeProperty() { + return edge; + } + + public Component getComponent() { + return component.get(); + } + + public void setComponent(final Component component) { + this.component.set(component); + } + + public ObjectProperty componentProperty() { + return component; + } + + /** + * Colors the edge model + * @param color the new color of the edge + * @param intensity the intensity of the edge + */ + public void color(final Color color, final Color.Intensity intensity) { + final Edge edge = getEdge(); + + // Set the color of the edge + edge.setColorIntensity(intensity); + edge.setColor(color); + } + + public Color getColor() { + return getEdge().getColor(); + } + + public Color.Intensity getColorIntensity() { + return getEdge().getColorIntensity(); + } + + public ItemDragHelper.DragBounds getDragBounds() { + return ItemDragHelper.DragBounds.generateLooseDragBounds(); + } + + /*** + * Highlights the child nodes of the edge + */ + @Override + public void highlight() { + // Clear the currently selected elements, so we don't have multiple things highlighted/selected + SelectHelper.clearSelectedElements(); + edgeRoot.getChildren().forEach(node -> { + if(node instanceof Highlightable) { + ((Highlightable) node).highlight(); + } + }); + } + + /*** + * Removes the highlight from child nodes + */ + @Override + public void unhighlight() { + edgeRoot.getChildren().forEach(node -> { + if(node instanceof Highlightable) { + ((Highlightable) node).unhighlight(); + } + }); + } + + public DoubleProperty xProperty() { + return edgeRoot.layoutXProperty(); + } + + public DoubleProperty yProperty() { + return edgeRoot.layoutYProperty(); + } + + public double getX() { + return xProperty().get(); + } + + public double getY() { + return yProperty().get(); + } +} diff --git a/src/main/java/ecdar/controllers/SimEdgePresentation.java b/src/main/java/ecdar/controllers/SimEdgePresentation.java new file mode 100755 index 00000000..8d74623b --- /dev/null +++ b/src/main/java/ecdar/controllers/SimEdgePresentation.java @@ -0,0 +1,32 @@ +package ecdar.presentations; + +import ecdar.abstractions.Component; +import ecdar.abstractions.Edge; +import ecdar.controllers.SimEdgeController; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.scene.Group; + +/** + * The presentation class for the edges shown in the {@link SimulatorOverviewPresentation} + */ +public class SimEdgePresentation extends Group { + private final SimEdgeController controller; + + private final ObjectProperty edge = new SimpleObjectProperty<>(); + private final ObjectProperty component = new SimpleObjectProperty<>(); + + public SimEdgePresentation(final Edge edge, final Component component) { + controller = new EcdarFXMLLoader().loadAndGetController("SimEdgePresentation.fxml", this); + + controller.setEdge(edge); + this.edge.bind(controller.edgeProperty()); + + controller.setComponent(component); + this.component.bind(controller.componentProperty()); + } + + public SimEdgeController getController() { + return controller; + } +} diff --git a/src/main/java/ecdar/controllers/SimLocationController.java b/src/main/java/ecdar/controllers/SimLocationController.java new file mode 100755 index 00000000..c8c39522 --- /dev/null +++ b/src/main/java/ecdar/controllers/SimLocationController.java @@ -0,0 +1,124 @@ +package ecdar.controllers; + +import ecdar.abstractions.Component; +import ecdar.abstractions.Location; +import ecdar.presentations.SimLocationPresentation; +import ecdar.presentations.SimTagPresentation; +import ecdar.utility.colors.Color; +import javafx.beans.property.DoubleProperty; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.fxml.Initializable; +import javafx.scene.Group; +import javafx.scene.control.Label; +import javafx.scene.shape.Circle; +import javafx.scene.shape.Line; +import javafx.scene.shape.Path; + +import java.net.URL; +import java.util.ResourceBundle; + +/** + * The controller of a location shown in the {@link ecdar.presentations.SimulatorOverviewPresentation} + */ +public class SimLocationController implements Initializable { + private final ObjectProperty location = new SimpleObjectProperty<>(); + private final ObjectProperty component = new SimpleObjectProperty<>(); + public SimLocationPresentation root; + public Path notCommittedShape; + public Path notCommittedInitialIndicator; + public Group shakeContent; + public Circle circle; + public Circle circleShakeIndicator; + public Group scaleContent; + public SimTagPresentation nicknameTag; + public SimTagPresentation invariantTag; + public Label idLabel; + public Line nameTagLine; + public Line invariantTagLine; + + @Override + public void initialize(final URL location, final ResourceBundle resources) { + this.location.addListener((obsLocation, oldLocation, newLocation) -> { + // The radius property on the abstraction must reflect the radius in the view + newLocation.radiusProperty().bind(circle.radiusProperty()); + + // The scale property on the abstraction must reflect the radius in the view + newLocation.scaleProperty().bind(scaleContent.scaleXProperty()); + }); + + // Scale x and y 1:1 (based on the x-scale) + scaleContent.scaleYProperty().bind(scaleContent.scaleXProperty()); + } + + public Location getLocation() { + return location.get(); + } + + /** + * Set/places the given location on the view. + * This have to be done before adding the {@link SimLocationPresentation} to the view as nothing + * would then be displayed. + * @param location the location + */ + public void setLocation(final Location location) { + this.location.set(location); + root.setLayoutX(location.getX()); + root.setLayoutY(location.getY()); + location.xProperty().bindBidirectional(root.layoutXProperty()); + location.yProperty().bindBidirectional(root.layoutYProperty()); + } + + public ObjectProperty locationProperty() { + return location; + } + + public Component getComponent() { + return component.get(); + } + + public void setComponent(final Component component) { + this.component.set(component); + } + + public ObjectProperty componentProperty() { + return component; + } + + /** + * Colors the location model + * @param color the new color of the location + * @param intensity the intensity of the color + */ + public void color(final Color color, final Color.Intensity intensity) { + final Location location = getLocation(); + + // Set the color of the location + location.setColorIntensity(intensity); + location.setColor(color); + } + + public Color getColor() { + return getLocation().getColor(); + } + + public Color.Intensity getColorIntensity() { + return getLocation().getColorIntensity(); + } + + public DoubleProperty xProperty() { + return root.layoutXProperty(); + } + + public DoubleProperty yProperty() { + return root.layoutYProperty(); + } + + public double getX() { + return xProperty().get(); + } + + public double getY() { + return yProperty().get(); + } +} diff --git a/src/main/java/ecdar/controllers/SimLocationPresentation.java b/src/main/java/ecdar/controllers/SimLocationPresentation.java new file mode 100755 index 00000000..7d87e377 --- /dev/null +++ b/src/main/java/ecdar/controllers/SimLocationPresentation.java @@ -0,0 +1,490 @@ +package ecdar.presentations; + +import ecdar.abstractions.Component; +import ecdar.abstractions.Location; +import ecdar.controllers.SimLocationController; +import ecdar.utility.Highlightable; +import ecdar.utility.colors.Color; +import ecdar.utility.helpers.BindingHelper; +import ecdar.utility.helpers.SelectHelper; +import javafx.animation.*; +import javafx.beans.binding.DoubleBinding; +import javafx.beans.property.*; +import javafx.scene.Group; +import javafx.scene.control.Label; +import javafx.scene.effect.DropShadow; +import javafx.scene.paint.Paint; +import javafx.scene.shape.*; +import javafx.util.Duration; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.BiConsumer; +import java.util.function.Consumer; + +import static ecdar.presentations.LocationPresentation.INITIAL_RADIUS; +import static ecdar.presentations.LocationPresentation.RADIUS; + +/** + * Presentation for a location in the {@link SimulatorOverviewPresentation}. + * This class should be refactored such that the shared code between this class + * and {@link LocationPresentation} is placed in a base class. + */ +public class SimLocationPresentation extends Group implements Highlightable { + + private static int id = 0; + private final SimLocationController controller; + private final Timeline initialAnimation = new Timeline(); + private final Timeline hoverAnimationEntered = new Timeline(); + private final Timeline hoverAnimationExited = new Timeline(); + private final Timeline hiddenAreaAnimationEntered = new Timeline(); + private final Timeline hiddenAreaAnimationExited = new Timeline(); + private final Timeline scaleShakeIndicatorBackgroundAnimation = new Timeline(); + private final Timeline shakeContentAnimation = new Timeline(); + private final List> updateColorDelegates = new ArrayList<>(); + private final DoubleProperty animation = new SimpleDoubleProperty(0); + private final DoubleBinding reverseAnimation = new SimpleDoubleProperty(1).subtract(animation); + + /** + * Constructs a Simulator Location ready to be placed on the view + * @param location the location model, which the presenter should show + * @param component the component where the location is + */ + public SimLocationPresentation(final Location location, final Component component) { + controller = new EcdarFXMLLoader().loadAndGetController("SimLocationPresentation.fxml", this); + + // Bind the component with the one of the controller + controller.setComponent(component); + + // Bind the location with the one of the controller + controller.setLocation(location); + + initializeIdLabel(); + initializeTypeGraphics(); + initializeLocationShapes(); + initializeTags(); + initializeShakeAnimation(); + initializeCircle(); + } + + /** + * Initializes the label in the middle of the location + */ + private void initializeIdLabel() { + final Location location = controller.getLocation(); + final Label idLabel = controller.idLabel; + + final DropShadow ds = new DropShadow(); + ds.setRadius(2); + ds.setSpread(1); + + idLabel.setEffect(ds); + + idLabel.textProperty().bind((location.idProperty())); + + // Center align the label + idLabel.widthProperty().addListener((obsWidth, oldWidth, newWidth) -> idLabel.translateXProperty().set(newWidth.doubleValue() / -2)); + idLabel.heightProperty().addListener((obsHeight, oldHeight, newHeight) -> idLabel.translateYProperty().set(newHeight.doubleValue() / -2)); + + final ObjectProperty color = location.colorProperty(); + final ObjectProperty colorIntensity = location.colorIntensityProperty(); + + // Delegate to style the label based on the color of the location + final BiConsumer updateColor = (newColor, newIntensity) -> { + idLabel.setTextFill(newColor.getTextColor(newIntensity)); + ds.setColor(newColor.getColor(newIntensity)); + }; + + updateColorDelegates.add(updateColor); + + // Set the initial color + updateColor.accept(color.get(), colorIntensity.get()); + + // Update the color of the circle when the color of the location is updated + color.addListener((obs, old, newColor) -> updateColor.accept(newColor, colorIntensity.get())); + } + + /** + * Initialize the tags placed on this location + */ + private void initializeTags() { + + // Set the layout from the model (if they are not both 0) + final Location loc = controller.getLocation(); + if((loc.getNicknameX() != 0) && (loc.getNicknameY() != 0)) { + controller.nicknameTag.setTranslateX(loc.getNicknameX()); + controller.nicknameTag.setTranslateY(loc.getNicknameY()); + } + + if((loc.getInvariantX() != 0) && (loc.getInvariantY() != 0)) { + controller.invariantTag.setTranslateX(loc.getInvariantX()); + controller.invariantTag.setTranslateY(loc.getInvariantY()); + } + + // Bind the model to the layout + loc.nicknameXProperty().bind(controller.nicknameTag.translateXProperty()); + loc.nicknameYProperty().bind(controller.nicknameTag.translateYProperty()); + loc.invariantXProperty().bind(controller.invariantTag.translateXProperty()); + loc.invariantYProperty().bind(controller.invariantTag.translateYProperty()); + + final Consumer updateTags = location -> { + // Update the color + controller.nicknameTag.bindToColor(location.colorProperty(), location.colorIntensityProperty(), true); + controller.invariantTag.bindToColor(location.colorProperty(), location.colorIntensityProperty(), false); + + // Update the invariant + controller.nicknameTag.setAndBindString(location.nicknameProperty()); + controller.invariantTag.setAndBindString(location.invariantProperty()); + + // Set the visibility of the name tag depending on the nickname + final Consumer updateVisibilityFromNickName = (nickname) -> { + if (nickname.equals("")) { + controller.nicknameTag.setOpacity(0); + } else { + controller.nicknameTag.setOpacity(1); + } + }; + + location.nicknameProperty().addListener((obs, oldNickname, newNickname) -> updateVisibilityFromNickName.accept(newNickname)); + updateVisibilityFromNickName.accept(location.getNickname()); + + // Set the visibility of the invariant tag depending on the invariant + final Consumer updateVisibilityFromInvariant = (invariant) -> { + if (invariant.equals("") ) { + controller.invariantTag.setOpacity(0); + } else { + controller.invariantTag.setOpacity(1); + } + }; + + location.invariantProperty().addListener((obs, oldInvariant, newInvariant) -> updateVisibilityFromInvariant.accept(newInvariant)); + updateVisibilityFromInvariant.accept(location.getInvariant()); + + controller.nicknameTag.setComponent(controller.getComponent()); + controller.nicknameTag.setLocationAware(location); + BindingHelper.bind(controller.nameTagLine, controller.nicknameTag); + + controller.invariantTag.setComponent(controller.getComponent()); + controller.invariantTag.setLocationAware(location); + BindingHelper.bind(controller.invariantTagLine, controller.invariantTag); + }; + + // Update the tags when the loc updates + controller.locationProperty().addListener(observable -> updateTags.accept(loc)); + + // Initialize the tags from the current loc + updateTags.accept(loc); + } + + /** + * Initialize the circle which makes up most of the location + */ + private void initializeCircle() { + final Location location = controller.getLocation(); + + final Circle circle = controller.circle; + circle.setRadius(RADIUS); + final ObjectProperty color = location.colorProperty(); + final ObjectProperty colorIntensity = location.colorIntensityProperty(); + + // Delegate to style the label based on the color of the location + final BiConsumer updateColor = (newColor, newIntensity) -> { + circle.setFill(newColor.getColor(newIntensity)); + circle.setStroke(newColor.getColor(newIntensity.next(2))); + }; + + updateColorDelegates.add(updateColor); + + // Set the initial color + updateColor.accept(color.get(), colorIntensity.get()); + + // Update the color of the circle when the color of the location is updated + color.addListener((obs, old, newColor) -> updateColor.accept(newColor, colorIntensity.get())); + colorIntensity.addListener((obs, old, newIntensity) -> updateColor.accept(color.get(), newIntensity)); + } + + /** + * Initializes the shapes of a urgent location + */ + private void initializeLocationShapes() { + final Path notCommittedShape = controller.notCommittedShape; + + // Bind sizes for shape that transforms between urgent and normal + initializeLocationShapes(notCommittedShape, RADIUS); + + final Location location = controller.getLocation(); + + final BiConsumer updateUrgencies = (oldUrgency, newUrgency) -> { + final Transition toUrgent = new Transition() { + { + setCycleDuration(Duration.millis(200)); + } + + @Override + protected void interpolate(final double frac) { + animation.set(frac); + } + }; + + final Transition toNormal = new Transition() { + { + setCycleDuration(Duration.millis(200)); + } + + @Override + protected void interpolate(final double frac) { + animation.set(1-frac); + } + }; + + if(oldUrgency.equals(Location.Urgency.NORMAL) && !newUrgency.equals(Location.Urgency.NORMAL)) { + toUrgent.play(); + } else if(newUrgency.equals(Location.Urgency.NORMAL)) { + toNormal.play(); + } + notCommittedShape.setVisible(true); + }; + + location.urgencyProperty().addListener((obsUrgency, oldUrgency, newUrgency) -> { + updateUrgencies.accept(oldUrgency, newUrgency); + }); + + updateUrgencies.accept(Location.Urgency.NORMAL, location.getUrgency()); + + // Update the colors + final ObjectProperty color = location.colorProperty(); + final ObjectProperty colorIntensity = location.colorIntensityProperty(); + + // Delegate to style the label based on the color of the location + final BiConsumer updateColor = (newColor, newIntensity) -> { + notCommittedShape.setFill(newColor.getColor(newIntensity)); + notCommittedShape.setStroke(newColor.getColor(newIntensity.next(2))); + }; + + updateColorDelegates.add(updateColor); + + // Set the initial color + updateColor.accept(color.get(), colorIntensity.get()); + + // Update the color of the circle when the color of the location is updated + color.addListener((obs, old, newColor) -> updateColor.accept(newColor, colorIntensity.get())); + } + + private void initializeTypeGraphics() { + final Location location = controller.getLocation(); + + final Path notCommittedInitialIndicator = controller.notCommittedInitialIndicator; + + // Bind visibility and size of normal shape + initializeLocationShapes(notCommittedInitialIndicator, INITIAL_RADIUS); + notCommittedInitialIndicator.visibleProperty().bind(location.typeProperty().isEqualTo(Location.Type.INITIAL)); + // As the style sometimes "forget" its values + notCommittedInitialIndicator.setStrokeType(StrokeType.INSIDE); + notCommittedInitialIndicator.setStroke(Paint.valueOf("white")); + notCommittedInitialIndicator.setFill(Paint.valueOf("transparent")); + } + + public void animateIn() { + initialAnimation.play(); + } + + public void animateHoverEntered() { + + if (shakeContentAnimation.getStatus().equals(Animation.Status.RUNNING)) return; + + hoverAnimationEntered.play(); + } + + public void animateHoverExited() { + if (shakeContentAnimation.getStatus().equals(Animation.Status.RUNNING)) return; + + hoverAnimationExited.play(); + } + + public void animateLocationEntered() { + hiddenAreaAnimationExited.stop(); + hiddenAreaAnimationEntered.play(); + } + + public void animateLocationExited() { + hiddenAreaAnimationEntered.stop(); + hiddenAreaAnimationExited.play(); + } + + + /** + * Initializes the animation of shaking the location. Can for instance be used when the user tries an + * action which is not allowed, i.e. deleting + */ + private void initializeShakeAnimation() { + final Interpolator interpolator = Interpolator.SPLINE(0.645, 0.045, 0.355, 1); + + final KeyValue scale0x = new KeyValue(controller.scaleContent.scaleXProperty(), 1, interpolator); + final KeyValue radius0 = new KeyValue(controller.circleShakeIndicator.radiusProperty(), 0, interpolator); + final KeyValue opacity0 = new KeyValue(controller.circleShakeIndicator.opacityProperty(), 0, interpolator); + + final KeyValue scale1x = new KeyValue(controller.scaleContent.scaleXProperty(), 1.3, interpolator); + final KeyValue radius1 = new KeyValue(controller.circleShakeIndicator.radiusProperty(), controller.circle.getRadius() * 0.85, interpolator); + final KeyValue opacity1 = new KeyValue(controller.circleShakeIndicator.opacityProperty(), 0.2, interpolator); + + final KeyFrame kf1 = new KeyFrame(Duration.millis(0), scale0x, radius0, opacity0); + final KeyFrame kf2 = new KeyFrame(Duration.millis(2500), scale1x, radius1, opacity1); + final KeyFrame kf3 = new KeyFrame(Duration.millis(3300), radius0, opacity0); + final KeyFrame kf4 = new KeyFrame(Duration.millis(3500), scale0x); + final KeyFrame kfEnd = new KeyFrame(Duration.millis(8000)); + + scaleShakeIndicatorBackgroundAnimation.getKeyFrames().addAll(kf1, kf2, kf3, kf4, kfEnd); + + final KeyValue noShakeX = new KeyValue(controller.shakeContent.translateXProperty(), 0, interpolator); + final KeyValue shakeLeftX = new KeyValue(controller.shakeContent.translateXProperty(), -1, interpolator); + final KeyValue shakeRightX = new KeyValue(controller.shakeContent.translateXProperty(), 1, interpolator); + + final KeyFrame[] shakeFrames = { + new KeyFrame(Duration.millis(0), noShakeX), + new KeyFrame(Duration.millis(1450), noShakeX), + + new KeyFrame(Duration.millis(1500), shakeLeftX), + new KeyFrame(Duration.millis(1550), shakeRightX), + new KeyFrame(Duration.millis(1600), shakeLeftX), + new KeyFrame(Duration.millis(1650), shakeRightX), + new KeyFrame(Duration.millis(1700), shakeLeftX), + new KeyFrame(Duration.millis(1750), shakeRightX), + new KeyFrame(Duration.millis(1800), shakeLeftX), + new KeyFrame(Duration.millis(1850), shakeRightX), + new KeyFrame(Duration.millis(1900), shakeLeftX), + new KeyFrame(Duration.millis(1950), shakeRightX), + new KeyFrame(Duration.millis(2000), shakeLeftX), + new KeyFrame(Duration.millis(2050), shakeRightX), + new KeyFrame(Duration.millis(2100), shakeLeftX), + new KeyFrame(Duration.millis(2150), shakeRightX), + new KeyFrame(Duration.millis(2200), shakeLeftX), + new KeyFrame(Duration.millis(2250), shakeRightX), + + new KeyFrame(Duration.millis(2300), noShakeX), + new KeyFrame(Duration.millis(8000)) + }; + + shakeContentAnimation.getKeyFrames().addAll(shakeFrames); + + shakeContentAnimation.setCycleCount(1000); + scaleShakeIndicatorBackgroundAnimation.setCycleCount(1000); + } + + /** + * Plays the shake animation + * @param start false - the animation resets to the beginning
+ * true - the animation starts + */ + public void animateShakeWarning(final boolean start) { + if (start) { + scaleShakeIndicatorBackgroundAnimation.play(); + shakeContentAnimation.play(); + } else { + controller.scaleContent.scaleXProperty().set(1); + scaleShakeIndicatorBackgroundAnimation.playFromStart(); + scaleShakeIndicatorBackgroundAnimation.stop(); + + controller.circleShakeIndicator.setOpacity(0); + shakeContentAnimation.playFromStart(); + shakeContentAnimation.stop(); + } + } + + /** + * Get the controller associated with this presenter + * @return the controller + */ + public SimLocationController getController() { + return controller; + } + + private void initializeLocationShapes(final Path locationShape, final double radius) { + final double c = 0.551915024494; + final double circleToOctagonLineRatio = 0.35; + + final MoveTo moveTo = new MoveTo(); + moveTo.xProperty().bind(animation.multiply(circleToOctagonLineRatio * radius)); + moveTo.yProperty().set(radius); + + final CubicCurveTo cc1 = new CubicCurveTo(); + cc1.controlX1Property().bind(reverseAnimation.multiply(c * radius).add(animation.multiply(circleToOctagonLineRatio * radius))); + cc1.controlY1Property().bind(reverseAnimation.multiply(radius).add(animation.multiply(radius))); + cc1.controlX2Property().bind(reverseAnimation.multiply(radius).add(animation.multiply(radius))); + cc1.controlY2Property().bind(reverseAnimation.multiply(c * radius).add(animation.multiply(circleToOctagonLineRatio * radius))); + cc1.setX(radius); + cc1.yProperty().bind(animation.multiply(circleToOctagonLineRatio * radius)); + + + final LineTo lineTo1 = new LineTo(); + lineTo1.xProperty().bind(cc1.xProperty()); + lineTo1.yProperty().bind(cc1.yProperty().multiply(-1)); + + final CubicCurveTo cc2 = new CubicCurveTo(); + cc2.controlX1Property().bind(cc1.controlX2Property()); + cc2.controlY1Property().bind(cc1.controlY2Property().multiply(-1)); + cc2.controlX2Property().bind(cc1.controlX1Property()); + cc2.controlY2Property().bind(cc1.controlY1Property().multiply(-1)); + cc2.xProperty().bind(moveTo.xProperty()); + cc2.yProperty().bind(moveTo.yProperty().multiply(-1)); + + + final LineTo lineTo2 = new LineTo(); + lineTo2.xProperty().bind(cc2.xProperty().multiply(-1)); + lineTo2.yProperty().bind(cc2.yProperty()); + + final CubicCurveTo cc3 = new CubicCurveTo(); + cc3.controlX1Property().bind(cc2.controlX2Property().multiply(-1)); + cc3.controlY1Property().bind(cc2.controlY2Property()); + cc3.controlX2Property().bind(cc2.controlX1Property().multiply(-1)); + cc3.controlY2Property().bind(cc2.controlY1Property()); + cc3.xProperty().bind(lineTo1.xProperty().multiply(-1)); + cc3.yProperty().bind(lineTo1.yProperty()); + + + final LineTo lineTo3 = new LineTo(); + lineTo3.xProperty().bind(cc3.xProperty()); + lineTo3.yProperty().bind(cc3.yProperty().multiply(-1)); + + final CubicCurveTo cc4 = new CubicCurveTo(); + cc4.controlX1Property().bind(cc3.controlX2Property()); + cc4.controlY1Property().bind(cc3.controlY2Property().multiply(-1)); + cc4.controlX2Property().bind(cc3.controlX1Property()); + cc4.controlY2Property().bind(cc3.controlY1Property().multiply(-1)); + cc4.xProperty().bind(lineTo2.xProperty()); + cc4.yProperty().bind(lineTo2.yProperty().multiply(-1)); + + + final LineTo lineTo4 = new LineTo(); + lineTo4.xProperty().bind(moveTo.xProperty()); + lineTo4.yProperty().bind(moveTo.yProperty()); + + + locationShape.getElements().add(moveTo); + locationShape.getElements().add(cc1); + + locationShape.getElements().add(lineTo1); + locationShape.getElements().add(cc2); + + locationShape.getElements().add(lineTo2); + locationShape.getElements().add(cc3); + + locationShape.getElements().add(lineTo3); + locationShape.getElements().add(cc4); + + locationShape.getElements().add(lineTo4); + } + + @Override + public void highlight() { + updateColorDelegates.forEach(colorConsumer -> colorConsumer.accept(SelectHelper.SELECT_COLOR, SelectHelper.SELECT_COLOR_INTENSITY_NORMAL)); + } + + @Override + public void unhighlight() { + updateColorDelegates.forEach(colorConsumer -> { + final Location location = controller.getLocation(); + + colorConsumer.accept(location.getColor(), location.getColorIntensity()); + }); + } +} \ No newline at end of file diff --git a/src/main/java/ecdar/controllers/SimulatorController.java b/src/main/java/ecdar/controllers/SimulatorController.java new file mode 100755 index 00000000..56cc1b3f --- /dev/null +++ b/src/main/java/ecdar/controllers/SimulatorController.java @@ -0,0 +1,155 @@ +package ecdar.controllers; + +import ecdar.Ecdar; +import ecdar.abstractions.*; +import ecdar.simulation.SimulationHandler; +import ecdar.presentations.LeftSimPanePresentation; +import ecdar.presentations.RightSimPanePresentation; +import ecdar.presentations.SimulatorOverviewPresentation; +import ecdar.simulation.SimulationState; +import ecdar.simulation.Transition; +import javafx.beans.property.DoubleProperty; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleDoubleProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.fxml.Initializable; +import javafx.scene.control.Label; +import javafx.scene.layout.StackPane; +import javafx.scene.shape.Rectangle; + +import java.net.URL; +import java.util.ArrayList; +import java.util.List; +import java.util.ResourceBundle; + +public class SimulatorController implements Initializable { + + public StackPane root; + public SimulatorOverviewPresentation overviewPresentation; + public StackPane toolbar; + public Label rightPaneFillerElement; + public Label leftPaneFillerElement; + public Rectangle bottomFillerElement; + public RightSimPanePresentation rightSimPane; + public LeftSimPanePresentation leftSimPane; + private Declarations systemDeclarations; + private boolean firstTimeInSimulator; + + private final static DoubleProperty width = new SimpleDoubleProperty(), + height = new SimpleDoubleProperty(); + private static ObjectProperty selectedTransition = new SimpleObjectProperty<>(); + private static ObjectProperty selectedState = new SimpleObjectProperty<>(); + + @Override + public void initialize(URL location, ResourceBundle resources) { + root.widthProperty().addListener((observable, oldValue, newValue) -> width.setValue(newValue)); + root.heightProperty().addListener((observable, oldValue, newValue) -> height.setValue(newValue)); + firstTimeInSimulator = true; + } + + /** + * Prepares the simulator to be shown.
+ * It also prepares the processes to be shown in the {@link SimulatorOverviewPresentation} by:
+ * - Building the system if it has been updated or never have been created.
+ * - Adding the components which are going to be used in the simulation to + */ + public void willShow() { + final SimulationHandler sm = Ecdar.getSimulationHandler(); + boolean shouldSimulationBeReset = true; + + //Have the user left a trace or is he simulating a query + if (sm.traceLog.size() >= 2 || sm.getCurrentSimulation().contains(SimulationHandler.QUERY_PREFIX)) { + shouldSimulationBeReset = false; + } + + if (!firstTimeInSimulator && !overviewPresentation.getController().getComponentObservableList() + .containsAll(findComponentsInCurrentSimulation())) { + shouldSimulationBeReset = true; + } + + if (shouldSimulationBeReset || firstTimeInSimulator) { + resetSimulation(); + sm.resetToInitialLocation(); + } + overviewPresentation.getController().addProcessesToGroup(); + overviewPresentation.getController().highlightProcessState(sm.getCurrentState()); + } + + /** + * Resets the current simulation, and prepares for a new simulation by clearing the + * {@link SimulatorOverviewController#processContainer} and adding the processes of the new simulation. + */ + private void resetSimulation() { + final SimulationHandler sm = Ecdar.getSimulationHandler(); + sm.initializeDefaultSystem(); + overviewPresentation.getController().clearOverview(); + overviewPresentation.getController().getComponentObservableList().clear(); + overviewPresentation.getController().getComponentObservableList().addAll(findComponentsInCurrentSimulation()); + firstTimeInSimulator = false; + } + + /** + * Finds the components that are used in the current simulation by looking at the component found in + * {@link Project#getComponents()} and compare them to the processes declared in the {@link SimulationHandler#getSystem()} + *

+ * TODO This does currently not work if the same component is used multiple times. + * + * @return all the components used in the current simulation + */ + private List findComponentsInCurrentSimulation() { + //Show components from the system + final SimulationHandler sm = Ecdar.getSimulationHandler(); + List components = new ArrayList<>(); +// for (int i = 0; i < sm.getSystem().getNoOfProcesses(); i++) { +// final int finalI = i; // when using a var in lambda it has to be final +// final List filteredList = Ecdar.getProject().getComponents().filtered(component -> { +// return component.getName().contentEquals(sm.getSystem().getProcess(finalI).getName()); +// }); +// components.addAll(filteredList); +// } + return components; + } + + /** + * Resets the simulation and prepares the view for showing the new simulation to the user + */ + public void resetCurrentSimulation() { + overviewPresentation.getController().removeProcessesFromGroup(); + resetSimulation(); + Ecdar.getSimulationHandler().resetToInitialLocation(); + overviewPresentation.getController().addProcessesToGroup(); + } + + public void willHide() { + overviewPresentation.getController().removeProcessesFromGroup(); + overviewPresentation.getController().getComponentObservableList().forEach(component -> { + // Previously reset coordinates of component box + }); + overviewPresentation.getController().unhighlightProcesses(); + } + + public static DoubleProperty getWidthProperty() { + return width; + } + + public static DoubleProperty getHeightProperty() { + return height; + } + + + public static ObjectProperty getSelectedTransitionProperty() { + return selectedTransition; + } + + public static void setSelectedTransition(Transition selectedTransition) { + SimulatorController.selectedTransition.set(selectedTransition); + } + + public static ObjectProperty getSelectedStateProperty() { + return selectedState; + } + + public static void setSelectedState(SimulationState selectedState) { + SimulatorController.selectedState.set(selectedState); + } +} diff --git a/src/main/java/ecdar/controllers/SimulatorOverviewController.java b/src/main/java/ecdar/controllers/SimulatorOverviewController.java new file mode 100755 index 00000000..160e9e51 --- /dev/null +++ b/src/main/java/ecdar/controllers/SimulatorOverviewController.java @@ -0,0 +1,372 @@ +package ecdar.controllers; + +import ecdar.Ecdar; +import ecdar.abstractions.*; +import ecdar.presentations.ProcessPresentation; +import ecdar.simulation.SimulationState; +import ecdar.simulation.Transition; +import javafx.collections.FXCollections; +import javafx.collections.ListChangeListener; +import javafx.collections.ObservableList; +import javafx.beans.InvalidationListener; +import javafx.collections.*; +import javafx.fxml.Initializable; +import javafx.geometry.Insets; +import javafx.scene.Group; +import javafx.scene.control.ScrollPane; +import javafx.scene.layout.*; + +import java.net.URL; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; +import java.util.ResourceBundle; + +/** + * The controller of the middle part of the simulator. + * It is here where processes of a simulation will be shown. + */ +public class SimulatorOverviewController implements Initializable { + public AnchorPane root; + public ScrollPane scrollPane; + public FlowPane processContainer; + public Group groupContainer; + + /** + * The amount that is going be zoomed in/out for each press on + or - + */ + private final double SCALE_DELTA = 1.1; + + /** + * The max that the user can zoom in + */ + private static final double MAX_ZOOM_IN = 1.6; + + /** + * The max that the user can zoom in + */ + private static final double MAX_ZOOM_OUT = 0.5; + + /** + * Offset such that the view does not overlap with the scroll bar on the right hand sig. + */ + private static final int SUPER_SPECIAL_SCROLLPANE_OFFSET = 20; + + private final ObservableList componentArrayList = FXCollections.observableArrayList(); + private final ObservableMap processPresentations = FXCollections.observableHashMap(); + + /** + * Is true if a reset of the zoom have been requested, false if not. + */ + private boolean resetZoom = false; + private boolean isMaxZoomInReached = false; + private boolean isMaxZoomOutReached = false; + + @Override + public void initialize(final URL location, final ResourceBundle resources) { + groupContainer = new Group(); + processContainer = new FlowPane(); + //In case that the processContainer gets moved around we have to keep in into place. + initializeProcessContainer(); + + initializeWindowResizing(); + initializeZoom(); + initializeHighlighting(); + initializeSimulationVariables(); + // Add the processes and group to the view + addProcessesToGroup(); + scrollPane.setContent(groupContainer); + } + + /** + * Initializes the {@link #processContainer} with its correct styling, and placement on the view. + * It also adds a {@link ListChangeListener} on {@link #componentArrayList} where it adds the + * {@link Component}s which are needed to the processContainer. + */ + private void initializeProcessContainer() { + processContainer.translateXProperty().addListener((observable, oldValue, newValue)-> { + processContainer.setTranslateX(0); + }); + //Sets the space between the processes + processContainer.setHgap(10); + processContainer.setVgap(10); + + // padding to the scrollpane + processContainer.setPadding(new Insets(5)); + + componentArrayList.addListener((ListChangeListener) c -> { + final Map processes = new HashMap<>(); + while (c.next()){ + if (c.wasRemoved()) { + clearOverview(); + } else { + c.getAddedSubList().forEach(o -> processes.put(o.getName(), new ProcessPresentation(o))); + } + } + // Highlight the current state when the processes change + highlightProcessState(Ecdar.getSimulationHandler().getCurrentState()); + processContainer.getChildren().addAll(processes.values()); + processPresentations.putAll(processes); + }); + } + + /** + * Clears the {@link #processContainer} and the {@link #processPresentations}. + */ + void clearOverview() { + processContainer.getChildren().clear(); + processPresentations.clear(); + } + + /** + * Setup listeners for displaying clock and variable values on the {@link ProcessPresentation} + */ + private void initializeSimulationVariables() { + Ecdar.getSimulationHandler().getSimulationVariables().addListener((InvalidationListener) obs -> { + Ecdar.getSimulationHandler().getSimulationVariables().forEach((s, bigDecimal) -> { + if (!s.equals("t(0)")) {// As t(0) does not belong to any process + final String[] spittedString = s.split("\\."); + // If the process containing the var is not there we just skip it + if (spittedString.length > 0 && processPresentations.size() > 0) { + processPresentations.get(spittedString[0]).getController().getVariables().put(spittedString[1], bigDecimal); + } + } + }); + }); + Ecdar.getSimulationHandler().getSimulationClocks().addListener((InvalidationListener) obs -> { + if(processPresentations.size() == 0) return; + Ecdar.getSimulationHandler().getSimulationClocks().forEach((s, bigDecimal) -> { + if (!s.equals("t(0)")) {// As t(0) does not belong to any process + final String[] spittedString = s.split("\\."); + // If the process containing the clock is not there we just skip it + if (spittedString.length > 0 && processPresentations.size() > 0) { + processPresentations.get(spittedString[0]).getController().getClocks().put(spittedString[1], bigDecimal); + } + } + }); + }); + } + + /** + * Removes {@link #processContainer} from the {@link #groupContainer}.
+ * In this way the {@link Component}s in the processContainer will then again be resizable, + * as the class {@link Group} makes its children not resizeable. + * @see Group + */ + void removeProcessesFromGroup(){ + groupContainer.getChildren().removeAll(processContainer); + } + + /** + * Adds the {@link #processContainer} to the {@link #groupContainer}.
+ * This method is usually needed to called if {@link #removeProcessesFromGroup()} have been called, or + * if the processContainer just need to be added to the groupContainer.
+ * This method makes sure that the processContainer will be added to the groupContainer + * which is needed to show the {@link ProcessPresentation}s in the {@link #scrollPane}. + * If the processContainer is already contained in the groupContainer + * the method does nothing but return. + * @see #removeProcessesFromGroup() + */ + void addProcessesToGroup(){ + if(groupContainer.getChildren().contains(processContainer)) return; + groupContainer.getChildren().add(processContainer); + } + + /** + * Initializes the zoom functionality in {@link #processContainer} + */ + private void initializeZoom() { + processContainer.scaleXProperty().addListener((observable, oldValue, newValue) -> { + if(newValue.doubleValue() > MAX_ZOOM_IN) isMaxZoomInReached = true; + if(newValue.doubleValue() < MAX_ZOOM_OUT) isMaxZoomOutReached = true; + + handleWidthOnScale(oldValue, newValue); + }); + + // to support pinch zooming + //TODO this should be fixed at as it does not work as it should + /* + processContainer.setOnZoom(event -> { + //Tries to zoom in/out but max is reached + if(event.getZoomFactor() >= 1 && isMaxZoomInReached) return; + if(event.getZoomFactor() < 1 && isMaxZoomOutReached) return; + + isMaxZoomInReached = false; + isMaxZoomOutReached = false; + + processContainer.setScaleX(processContainer.getScaleX() * event.getZoomFactor()); + processContainer.setScaleY(processContainer.getScaleY() * event.getZoomFactor()); + });*/ + } + + /** + * Initializes listener for change of width in {@link #scrollPane} which also affects {@link #processContainer}
+ * This does also take the zooming into account when doing the resizing. + */ + private void initializeWindowResizing() { + scrollPane.widthProperty().addListener((observable, oldValue, newValue) -> { + final double width = (newValue.doubleValue()) * (1 + (1 - processContainer.getScaleX())); + if(processContainer.getScaleX() > 1) { //Zoomed in + processContainer.setMinWidth(width); + processContainer.setMaxWidth(width); + } else if (processContainer.getScaleX() < 1) { //Zoomed out + processContainer.setMinWidth(width); + processContainer.setMaxWidth(width); + final double deltaWidth = newValue.doubleValue() - groupContainer.layoutBoundsProperty().get().getWidth(); + processContainer.setMinWidth(processContainer.getWidth() + (deltaWidth - SUPER_SPECIAL_SCROLLPANE_OFFSET) * (1 + (1 - processContainer.getScaleX()))); + processContainer.setMaxWidth(processContainer.getWidth() + (deltaWidth - SUPER_SPECIAL_SCROLLPANE_OFFSET) * (1 + (1 - processContainer.getScaleX()))); + } else { // Reset + processContainer.setMinWidth(newValue.doubleValue() - SUPER_SPECIAL_SCROLLPANE_OFFSET); + processContainer.setMaxWidth(newValue.doubleValue() - SUPER_SPECIAL_SCROLLPANE_OFFSET); + } + }); + } + + /** + * Increments the {@link #processContainer} scaleX and scaleY properties + * which creates the zoom-in feeling. Resizing of the view is handled by {@link #handleWidthOnScale(Number, Number)} + * @see FlowPane#scaleXProperty() + * @see FlowPane#scaleYProperty() + */ + void zoomIn() { + if (isMaxZoomInReached) return; + isMaxZoomOutReached = false; + processContainer.setScaleX(processContainer.getScaleX() * SCALE_DELTA); + processContainer.setScaleY(processContainer.getScaleY() * SCALE_DELTA); + } + + + /** + * Decrements the {@link #processContainer} scaleX and scaleY properties + * which creates the zoom-in feeling. Resizing of the view is handled by {@link #handleWidthOnScale(Number, Number)} + * @see FlowPane#scaleXProperty() + * @see FlowPane#scaleYProperty() + */ + void zoomOut() { + if(isMaxZoomOutReached) return; + isMaxZoomInReached = false; + processContainer.setScaleX(processContainer.getScaleX() * (1 / SCALE_DELTA)); + processContainer.setScaleY(processContainer.getScaleY() * (1 / SCALE_DELTA)); + } + + /** + * Resets the scaling of the {@link #processContainer}, and hereby the zoom + * @see FlowPane#scaleXProperty() + * @see FlowPane#scaleYProperty() + */ + void resetZoom() { + if(processContainer.getScaleX() == 1) return; + resetZoom = true; + isMaxZoomInReached = false; + isMaxZoomOutReached = false; + processContainer.setScaleX(1); + processContainer.setScaleY(1); + } + + /** + * Handles the scaling of the width of the {@link #processContainer} + * @param oldValue the width of {@link #scrollPane} before the change + * @param newValue the width of {@link #scrollPane} after the change + */ + private void handleWidthOnScale(final Number oldValue, final Number newValue) { + if(resetZoom) { //Zoom reset + resetZoom = false; + processContainer.setMinWidth(scrollPane.getWidth() - SUPER_SPECIAL_SCROLLPANE_OFFSET); + processContainer.setMaxWidth(scrollPane.getWidth() - SUPER_SPECIAL_SCROLLPANE_OFFSET); + } else if(oldValue.doubleValue() > newValue.doubleValue()) { //Zoom in + resetZoom = false; + processContainer.setMinWidth(Math.round(processContainer.getWidth() * SCALE_DELTA)); + processContainer.setMaxWidth(Math.round(processContainer.getWidth() * SCALE_DELTA)); + } else { // Zoom out + resetZoom = false; + processContainer.setMinWidth(Math.round(processContainer.getWidth() * (1 / SCALE_DELTA))); + processContainer.setMaxWidth(Math.round(processContainer.getWidth() * (1 / SCALE_DELTA))); + } + } + + /** + * Initializer method to setup listeners that handle highlighting when selected/current state/transition changes + */ + private void initializeHighlighting() { + SimulatorController.getSelectedTransitionProperty().addListener((observable, oldTransition, newTransition) -> { + unhighlightProcesses(); + + // If the new transition is not null, we want to highlight the locations and edges in the new value + // otherwise we highlight the current state + if(newTransition != null) { + highlightProcessTransition(newTransition); + } else { + highlightProcessState(Ecdar.getSimulationHandler().getCurrentState()); + } + }); + + SimulatorController.getSelectedStateProperty().addListener((observable, oldState, newState) -> { + unhighlightProcesses(); + + // If the new state is not null, we want to highlight the locations in the new value + // otherwise we highlight the current state + if(newState != null) { + highlightProcessState(newState); + } else { + highlightProcessState(Ecdar.getSimulationHandler().getCurrentState()); + } + }); + } + + /** + * Highlights all the processes involved in the transition. + * Finds the processes involved in the transition (processes with edges in the transition) and highlights their edges + * Also fades processes that are not active in the selected transition + * @param transition The transition for which we highlight the involved processes + */ + public void highlightProcessTransition(final Transition transition) { + final var edges = transition.getEdges(); + + // List of all processes to show as inactive if they are not involved in a transition + // Processes are removed from this list, if they have an edge in the transition + final ArrayList processesToHide = new ArrayList<>(processPresentations.values()); + + for (final Edge edge : edges) { + final Process process = edge.getProcess(); + + // Find the processes that have edges involved in this transition + final ProcessPresentation presentation = processPresentations.get(process.getName()); + presentation.getController().highlightEdges(edges); + processesToHide.remove(presentation); + } + + processesToHide.forEach(ProcessPresentation::showInactive); + } + + /** + * Unhighlights all processes + */ + public void unhighlightProcesses() { + for(final ProcessPresentation presentation: processPresentations.values()) { + presentation.getController().unhighlightProcess(); + presentation.showActive(); + } + } + + /** + * Finds the processes for the input locations in the input {@link SimulationState} and highlights the locations. + * @param state The state with the locations to highlight + */ + public void highlightProcessState(final SimulationState state) { + for (int i = 0; i < state.getLocations().size(); i++) { + final Location loc = state.getLocations().get(i); + + for(final ProcessPresentation presentation: processPresentations.values()) { + final String processName = presentation.getController().getComponent().getName(); + +// if(processName.equals(loc.getProcess().getName())) { +// presentation.getController().highlightLocation(loc); +// } + } + } + } + + public ObservableList getComponentObservableList() { + return componentArrayList; + } +} diff --git a/src/main/java/ecdar/controllers/TracePaneElementController.java b/src/main/java/ecdar/controllers/TracePaneElementController.java new file mode 100755 index 00000000..6f0bf4ec --- /dev/null +++ b/src/main/java/ecdar/controllers/TracePaneElementController.java @@ -0,0 +1,182 @@ +package ecdar.controllers; + +import com.jfoenix.controls.JFXRippler; +import ecdar.Ecdar; +import ecdar.abstractions.Location; +import ecdar.simulation.SimulationState; +import ecdar.simulation.SimulationHandler; +import ecdar.presentations.TransitionPresentation; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleIntegerProperty; +import javafx.collections.ListChangeListener; +import javafx.event.EventHandler; +import javafx.fxml.FXML; +import javafx.fxml.Initializable; +import javafx.scene.control.Label; +import javafx.scene.layout.AnchorPane; +import javafx.scene.layout.VBox; +import org.kordamp.ikonli.javafx.FontIcon; + +import java.net.URL; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.ResourceBundle; + +/** + * The controller class for the trace pane element that can be inserted into a simulator pane + */ +public class TracePaneElementController implements Initializable { + public AnchorPane toolbar; + public Label traceTitle; + public JFXRippler expandTrace; + public VBox traceList; + public VBox traceVbox; + public FontIcon expandTraceIcon; + public AnchorPane traceSummary; + public Label summaryTitleLabel; + public Label summarySubtitleLabel; + + private SimpleBooleanProperty isTraceExpanded = new SimpleBooleanProperty(false); + private Map transitionPresentationMap = new LinkedHashMap<>(); + private SimpleIntegerProperty numberOfSteps = new SimpleIntegerProperty(0); + + @Override + public void initialize(URL location, ResourceBundle resources) { + Ecdar.getSimulationHandler().getTraceLog().addListener((ListChangeListener) c -> { + while (c.next()) { + for(final SimulationState state: c.getAddedSubList()) { + insertTraceState(state, true); + } + + for(final SimulationState state: c.getRemoved()) { + traceList.getChildren().remove(transitionPresentationMap.get(state)); + transitionPresentationMap.remove(state); + } + } + + numberOfSteps.set(transitionPresentationMap.size()); + }); + + initializeTraceExpand(); + } + + /** + * Initializes the expand functionality that allows the user to show or hide the trace. + * By default the trace is shown. + */ + private void initializeTraceExpand() { + isTraceExpanded.addListener((obs, oldVal, newVal) -> { + if(newVal) { + showTrace(); + expandTraceIcon.setIconLiteral("gmi-expand-less"); + expandTraceIcon.setIconSize(24); + } else { + hideTrace(); + expandTraceIcon.setIconLiteral("gmi-expand-more"); + expandTraceIcon.setIconSize(24); + } + }); + + isTraceExpanded.set(true); + } + + + /** + * Removes all the trace view elements as to hide the trace from the user + * Also shows the summary view when the trace is hidden + */ + private void hideTrace() { + traceList.getChildren().clear(); + traceVbox.getChildren().add(traceSummary); + } + + /** + * Shows the trace by inserting a {@link TransitionPresentation} for each trace state + * Also hides the summary view, since it should only be visible when the trace is hidden + */ + private void showTrace() { + transitionPresentationMap.forEach((state, presentation) -> { + insertTraceState(state, false); + }); + traceVbox.getChildren().remove(traceSummary); + } + + /** + * Instantiates a {@link TransitionPresentation} for a {@link SimulationState} and adds it to the view + * @param state The state the should be inserted into the trace log + * @param shouldAnimate A boolean that indicates whether the trace should fade in when added to the view + */ + private void insertTraceState(final SimulationState state, final boolean shouldAnimate) { + final TransitionPresentation transitionPresentation = new TransitionPresentation(); + transitionPresentationMap.put(state, transitionPresentation); + + transitionPresentation.setOnMouseReleased(event -> { + event.consume(); + final SimulationHandler simHandler = Ecdar.getSimulationHandler(); + if (simHandler == null) return; + Ecdar.getSimulationHandler().selectTransitionFromLog(state); + }); + + EventHandler mouseEntered = transitionPresentation.getOnMouseEntered(); + transitionPresentation.setOnMouseEntered(event -> { + SimulatorController.setSelectedState(state); + mouseEntered.handle(event); + }); + + EventHandler mouseExited = transitionPresentation.getOnMouseExited(); + transitionPresentation.setOnMouseExited(event -> { + SimulatorController.setSelectedState(null); + mouseExited.handle(event); + }); + + + String title = traceString(state); + transitionPresentation.getController().setTitle(title); + + // Only insert the presentation into the view if the trace is expanded + if(isTraceExpanded.get()) { + traceList.getChildren().add(transitionPresentation); + if(shouldAnimate) { + transitionPresentation.playFadeAnimation(); + } + } + } + + /** + * A helper method that returns a string representing a state in the trace log + * @param state The SimulationState to represent + * @return A string representing the state + */ + private String traceString(SimulationState state) { + String title = "("; + int length = state.getLocations().size(); + for (int i = 0; i < length ; i++) { + Location loc = state.getLocations().get(i); + String locationName = loc.getNickname(); + if (i == length-1) { + title += locationName; + } else { + title += locationName + ", "; + } + } + title += ")"; + + return title; + } + + /** + * Method to be called when clicking on the expand rippler in the trace toolbar + */ + @FXML + private void expandTrace() { + if(isTraceExpanded.get()) { + isTraceExpanded.set(false); + } else { + isTraceExpanded.set(true); + } + } + + public SimpleIntegerProperty getNumberOfStepsProperty() { + return numberOfSteps; + } +} diff --git a/src/main/java/ecdar/controllers/TransitionController.java b/src/main/java/ecdar/controllers/TransitionController.java new file mode 100755 index 00000000..7bacda2e --- /dev/null +++ b/src/main/java/ecdar/controllers/TransitionController.java @@ -0,0 +1,45 @@ +package ecdar.controllers; + +import com.jfoenix.controls.JFXRippler; +import ecdar.simulation.Transition; +import javafx.beans.property.SimpleObjectProperty; +import javafx.fxml.Initializable; +import javafx.scene.control.Label; +import javafx.scene.layout.AnchorPane; + +import java.net.URL; +import java.util.ResourceBundle; + +/** + * The controller class for the transition view element. + * It represents a single transition and may be used by classes like {@see TransitionPaneElementController} + * to show a list of transitions + */ +public class TransitionController implements Initializable { + public AnchorPane root; + public Label titleLabel; + public JFXRippler rippler; + + // The transition that the view represents + private SimpleObjectProperty transition = new SimpleObjectProperty<>(); + private SimpleObjectProperty title = new SimpleObjectProperty<>(); + + @Override + public void initialize(URL location, ResourceBundle resources) { + title.addListener(((observable, oldValue, newValue) -> { + titleLabel.setText(newValue); + })); + } + + public void setTitle(String title) { + this.title.set(title); + } + + public void setTransition(Transition transition) { + this.transition.set(transition); + } + + public Transition getTransition() { + return transition.get(); + } +} diff --git a/src/main/java/ecdar/controllers/TransitionPaneElementController.java b/src/main/java/ecdar/controllers/TransitionPaneElementController.java new file mode 100755 index 00000000..5dc6dbde --- /dev/null +++ b/src/main/java/ecdar/controllers/TransitionPaneElementController.java @@ -0,0 +1,245 @@ +package ecdar.controllers; + +import com.jfoenix.controls.JFXRippler; +import com.jfoenix.controls.JFXTextField; +import ecdar.Ecdar; +import ecdar.abstractions.Edge; +import ecdar.simulation.Transition; +import ecdar.simulation.SimulationHandler; +import ecdar.presentations.TransitionPresentation; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.collections.ListChangeListener; +import javafx.event.EventHandler; +import javafx.fxml.FXML; +import javafx.fxml.Initializable; +import javafx.scene.control.Label; +import javafx.scene.control.Tooltip; +import javafx.scene.layout.AnchorPane; +import javafx.scene.layout.VBox; +import org.kordamp.ikonli.javafx.FontIcon; + +import java.math.BigDecimal; +import java.net.URL; +import java.util.HashMap; +import java.util.Map; +import java.util.ResourceBundle; + +/** + * The controller class for the transition pane element that can be inserted into the simulator panes + */ +public class TransitionPaneElementController implements Initializable { + public AnchorPane root; + public VBox paneElementVbox; + public VBox transitionList; + public AnchorPane toolbar; + public Label toolbarTitle; + public JFXRippler refreshRippler; + public JFXRippler expandTransition; + public FontIcon expandTransitionIcon; + public AnchorPane delayChooser; + public JFXTextField delayTextField; + + private SimpleBooleanProperty isTransitionExpanded = new SimpleBooleanProperty(false); + private Map transitionPresentationMap = new HashMap<>(); + private SimpleObjectProperty delay = new SimpleObjectProperty<>(BigDecimal.ZERO); + + @Override + public void initialize(URL location, ResourceBundle resources) { + Ecdar.getSimulationHandler().availableTransitions.addListener((ListChangeListener) c -> { + while (c.next()) { + for (Transition trans : c.getAddedSubList()) { + insertTransition(trans); + } + + for (final Transition trans: c.getRemoved()) { + transitionList.getChildren().remove(transitionPresentationMap.get(trans)); + transitionPresentationMap.remove(trans); + } + } + }); + + initializeTransitionExpand(); + initializeDelayChooser(); + } + + /** + * Sets up listeners for the delay chooser. + * Listens for changes in text property and updates the textfield with a sanitized value (e.g. no letters in delay). + * Also listens for changes in focus, so there's always a value in the textfield, even if the user deleted the text. + * Adds tooltip for the textfield. + */ + private void initializeDelayChooser() { + delayTextField.textProperty().addListener(((observable, oldValue, newValue) -> { + delayTextChanged(oldValue, newValue); + })); + + delayTextField.focusedProperty().addListener((observable, oldValue, newValue) -> { + // If the textfield loses focus and the user didn't enter anything + // show the value 0.0 + if(!newValue && delay.get().equals(BigDecimal.ZERO)) { + delayTextField.setText("0.0"); + } + }); + + Tooltip.install(delayTextField, new Tooltip("Enter delay to use for next transition")); + } + + /** + * Initializes the expand functionality that allows the user to show or hide the transitions. + * By default the transitions are shown. + */ + private void initializeTransitionExpand() { + isTransitionExpanded.addListener((obs, oldVal, newVal) -> { + if(newVal) { + if(!paneElementVbox.getChildren().contains(delayChooser)) { + // Add the delay chooser just below the toolbar + paneElementVbox.getChildren().add(1, delayChooser); + } + showTransitions(); + expandTransitionIcon.setIconLiteral("gmi-expand-less"); + expandTransitionIcon.setIconSize(24); + } else { + paneElementVbox.getChildren().remove(delayChooser); + hideTransitions(); + expandTransitionIcon.setIconLiteral("gmi-expand-more"); + expandTransitionIcon.setIconSize(24); + } + }); + + isTransitionExpanded.set(true); + } + + /** + * Removes all the transition view elements as to hide the transitions from the user + */ + private void hideTransitions() { + transitionList.getChildren().clear(); + } + + /** + * Shows the available transitions by inserting a {@link TransitionPresentation} for each transition + */ + private void showTransitions() { + transitionPresentationMap.forEach((transition, presentation) -> { + insertTransition(transition); + }); + } + + /** + * Instantiates a TransitionPresentation for a Transition and adds it to the view + * @param transition The transition that should be inserted into the view + */ + private void insertTransition(Transition transition) { + final TransitionPresentation transitionPresentation = new TransitionPresentation(); + String title = transitionString(transition); + transitionPresentation.getController().setTitle(title); + transitionPresentation.getController().setTransition(transition); + + // Update the selected transition when mouse entered. + // Add the event to existing mouseEntered events + // e.g. TransitionPresentation already has mouseEntered functionality and we want to keep it + EventHandler mouseEntered = transitionPresentation.getOnMouseEntered(); + transitionPresentation.setOnMouseEntered(event -> { + SimulatorController.setSelectedTransition(transitionPresentation.getController().getTransition()); + mouseEntered.handle(event); + }); + + EventHandler mouseExited = transitionPresentation.getOnMouseExited(); + transitionPresentation.setOnMouseExited(event -> { + SimulatorController.setSelectedTransition(null); + mouseExited.handle(event); + }); + + transitionPresentation.setOnMouseClicked(event -> { + event.consume(); + + // Performs the next step of the simulation when clicking on a transition + SimulationHandler simHandler = Ecdar.getSimulationHandler(); + if (simHandler != null) { + simHandler.nextStep(transitionPresentation.getController().getTransition(), this.delay.get()); + } + }); + + transitionPresentationMap.put(transition, transitionPresentation); + + // Only insert the presentation into the view if the transitions are expanded + // Avoids inserting duplicate elements in the view (it's still added to the map) + if(isTransitionExpanded.get()) { + transitionList.getChildren().add(transitionPresentation); + } + } + + /** + * A helper method that returns a string representing a transition in the transition chooser + * @param transition The {@link Transition} to represent + * @return A string representing the transition + */ + private String transitionString(Transition transition) { + String title = transition.getLabel(); + if(transition.getEdges() != null) { + for (Edge edge : transition.getEdges()) { + title += " " + edge.getId(); + } + } + return title; + } + + /** + * Method to be called when clicking on the expand rippler in the transition toolbar + */ + @FXML + private void expandTransitions() { + if(isTransitionExpanded.get()) { + isTransitionExpanded.set(false); + } else { + isTransitionExpanded.set(true); + } + } + + /** + * Gets the initial step from the SimulationHandler. + * Used by the refresh button. + */ + @FXML + private void refreshTransitions() { + SimulatorController.setSelectedTransition(null); +// MainController.openReloadSimulationDialog(); // ToDo: Implement + } + + /** + * Sanitizes the input that the user inserts into the delay textfield. + * Checks if the text can be converted into a BigDecimal otherwise show the previous value. + * For example avoids users entering letters. + * @param oldValue The old value to show if the newvalue cannot be converted to a BigDecimal + * @param newValue The new value to show in the textfield + */ + @FXML + private void delayTextChanged(String oldValue, String newValue) { + // If the value is empty (the user deleted the value), assume that the value is 0.0 but do not update the text + if(newValue.isEmpty()) { + this.delay.set(BigDecimal.ZERO); + } else { + // Try to convert the new value into a BigDecimal + // Note that we don't setText here, as the new value is already shown in the textfield + try { + BigDecimal bd = new BigDecimal(newValue); + + // Checking the string for "-" instead of whether bd is negative is due to the case of -0.0 + // So checking the string is just simpler + if(newValue.contains("-")) { + throw new NumberFormatException(); + } + + this.delay.set(bd); + + } catch (NumberFormatException ex) { + // If the conversion was not possible, show the old value + this.delayTextField.setText(oldValue); + this.delay.set(new BigDecimal(oldValue)); + } + } + + } + +} diff --git a/src/main/java/ecdar/presentations/LeftSimPanePresentation.java b/src/main/java/ecdar/presentations/LeftSimPanePresentation.java new file mode 100755 index 00000000..adc693ed --- /dev/null +++ b/src/main/java/ecdar/presentations/LeftSimPanePresentation.java @@ -0,0 +1,37 @@ +package ecdar.presentations; + +import ecdar.controllers.LeftSimPaneController; +import ecdar.utility.colors.Color; +import javafx.geometry.Insets; +import javafx.scene.layout.*; + +public class LeftSimPanePresentation extends StackPane { + private LeftSimPaneController controller; + + public LeftSimPanePresentation() { + controller = new EcdarFXMLLoader().loadAndGetController("LeftSimPanePresentation.fxml", this); + + initializeBackground(); + initializeRightBorder(); + } + + private void initializeBackground() { + controller.scrollPaneVbox.setBackground(new Background(new BackgroundFill( + Color.GREY.getColor(Color.Intensity.I200), + CornerRadii.EMPTY, + Insets.EMPTY + ))); + } + + /** + * Initializes the thin border on the right side of the transition toolbar + */ + private void initializeRightBorder() { + controller.transitionPanePresentation.getController().toolbar.setBorder(new Border(new BorderStroke( + Color.GREY_BLUE.getColor(Color.Intensity.I900), + BorderStrokeStyle.SOLID, + CornerRadii.EMPTY, + new BorderWidths(0, 1, 0, 0) + ))); + } +} diff --git a/src/main/java/ecdar/presentations/ProcessPresentation.java b/src/main/java/ecdar/presentations/ProcessPresentation.java new file mode 100755 index 00000000..51cebab0 --- /dev/null +++ b/src/main/java/ecdar/presentations/ProcessPresentation.java @@ -0,0 +1,282 @@ +package ecdar.presentations; + +import ecdar.abstractions.Component; +import ecdar.abstractions.Edge; +import ecdar.abstractions.Location; +import ecdar.abstractions.Nail; +import ecdar.controllers.ModelController; +import ecdar.controllers.ProcessController; +import ecdar.utility.colors.Color; +import javafx.beans.InvalidationListener; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.Cursor; +import javafx.scene.Node; +import javafx.scene.control.Label; +import javafx.scene.layout.*; +import javafx.scene.paint.Paint; +import javafx.scene.shape.Path; +import javafx.scene.shape.Rectangle; +import javafx.scene.shape.Shape; +import javafx.scene.text.Font; + +import java.math.BigDecimal; +import java.util.List; +import java.util.function.BiConsumer; + +import static ecdar.presentations.Grid.GRID_SIZE; + +/** + * The presenter of a Process which is shown in {@link SimulatorOverviewPresentation}.
+ * This class have some of the same functionality as {@link ComponentPresentation} and could be refactored + * into a base class. + */ +public class ProcessPresentation extends ModelPresentation { + private final ProcessController controller; + + /** + * Constructs a Process ready to go to the view. + * @param component the component which the process should look like + */ + public ProcessPresentation(final Component component){ + controller = new EcdarFXMLLoader().loadAndGetController("ProcessPresentation.fxml", this); + controller.setComponent(component); + super.initialize(component.getBox()); + // Initialize methods that is sensitive to width and height + final Runnable onUpdateSize = () -> { + initializeToolbar(); + initializeFrame(); + initializeBackground(); + }; + + onUpdateSize.run(); + + // Re run initialisation on update of width and height property + component.getBox().getWidthProperty().addListener(observable -> onUpdateSize.run()); + component.getBox().getHeightProperty().addListener(observable -> onUpdateSize.run()); + setValueAreaStyle(); + setToggleValueButtonStyle(); + + controller.getClocks().forEach(this::addValueToValueArea); + controller.getVariables().forEach(this::addValueToValueArea); + + controller.getClocks().addListener((InvalidationListener) obs -> { + controller.getClocks().forEach((s, bigDecimal) -> { + final List filteredList = controller.valueArea.getChildren().filtered(node -> { + if (!(node instanceof Label))// we currently only want to look at labels + return false; + final String[] splitString = ((Label) node).getText().split("="); + return splitString[0].trim().equals(s); + }); + controller.valueArea.getChildren().removeAll(filteredList); + addValueToValueArea(s, bigDecimal); + }); + }); + controller.getVariables().addListener((InvalidationListener) obs -> { + controller.getVariables().forEach((s, bigDecimal) -> { + final List filteredList = controller.valueArea.getChildren().filtered(node -> { + if (!(node instanceof Label))// we currently only want to look at labels + return false; + final String[] splitString = ((Label) node).getText().split("="); + return splitString[0].trim().equals(s); + }); + controller.valueArea.getChildren().removeAll(filteredList); + addValueToValueArea(s, bigDecimal); + }); + }); + } + + /** + * Sets the Icon and Icon size of the {@link ProcessController#toggleValueButtonIcon} + */ + private void setToggleValueButtonStyle() { + controller.toggleValueButtonIcon.setIconLiteral("gmi-code"); + controller.toggleValueButtonIcon.setIconSize(17); + } + + /** + * Set the style needed for the {@link ProcessController#valueArea}. + * This include padding and background. + */ + private void setValueAreaStyle() { + // As for some reason the styling fail to be applied we need to set the styling again here + controller.valueArea.setPadding(new Insets(16, 4, 16, 4)); + controller.valueArea.setBackground(new Background( + new BackgroundFill(Paint.valueOf("rgba(242, 243, 244, 0.85)"),CornerRadii.EMPTY, Insets.EMPTY))); + } + + private void addValueToValueArea(final String s, final BigDecimal bigDecimal) { + final Label valueLabel = new Label(); + valueLabel.setText(s + " = " + bigDecimal); + // As the styling sometimes are gone missing + valueLabel.setFont(Font.font("Roboto Mono Medium",13)); + controller.valueArea.getChildren().add(valueLabel); + } + + /** + * Initializes the frame around the body of the process. + * It also updates the color if the color is changed + */ + private void initializeFrame() { + final Component component = controller.getComponent(); + + final Shape[] mask = new Shape[1]; + final Rectangle rectangle = new Rectangle(component.getBox().getWidth(), component.getBox().getHeight()); + + final BiConsumer updateColor = (newColor, newIntensity) -> { + // Mask the parent of the frame (will also mask the background) + mask[0] = Path.subtract(rectangle, TOP_LEFT_CORNER); + controller.frame.setClip(mask[0]); + controller.background.setClip(Path.union(mask[0], mask[0])); + controller.background.setOpacity(0.5); + + // Bind the missing lines that we cropped away + controller.topLeftLine.setStartX(Grid.CORNER_SIZE); + controller.topLeftLine.setStartY(0); + controller.topLeftLine.setEndX(0); + controller.topLeftLine.setEndY(Grid.CORNER_SIZE); + controller.topLeftLine.setStroke(newColor.getColor(newIntensity.next(2))); + controller.topLeftLine.setStrokeWidth(1.25); + StackPane.setAlignment(controller.topLeftLine, Pos.TOP_LEFT); + + // Set the stroke color to two shades darker + controller.frame.setBorder(new Border(new BorderStroke( + newColor.getColor(newIntensity.next(2)), + BorderStrokeStyle.SOLID, + CornerRadii.EMPTY, + new BorderWidths(1), + Insets.EMPTY + ))); + }; + component.colorProperty().addListener(observable -> { + updateColor.accept(component.getColor(), component.getColorIntensity()); + }); + updateColor.accept(component.getColor(), component.getColorIntensity()); + } + + /** + * Initializes the background of the process with the right color + * If the color is changed it will also update it self + */ + private void initializeBackground() { + final Component component = controller.getComponent(); + + // Bind the background width and height to the values in the model + controller.background.widthProperty().bind(component.getBox().getWidthProperty()); + controller.background.heightProperty().bind(component.getBox().getHeightProperty()); + controller.background.setFill(component.getColor().getColor(component.getColorIntensity().next(-10).next(2))); + final BiConsumer updateColor = (newColor, newIntensity) -> { + // Set the background color to the lightest possible version of the color + controller.background.setFill(newColor.getColor(newIntensity.next(-10).next(2))); + }; + component.colorProperty().addListener(observable -> { + updateColor.accept(component.getColor(), component.getColorIntensity()); + }); + + updateColor.accept(component.getColor(), component.getColorIntensity()); + } + + /** + * Initialize the Toolbar of the process.
+ * The toolbar is where the {@link ProcessController#name} is placed. + */ + private void initializeToolbar() { + final Component component = controller.getComponent(); + + final BiConsumer updateColor = (newColor, newIntensity) -> { + // Set the background of the toolbar + controller.toolbar.setBackground(new Background(new BackgroundFill( + newColor.getColor(newIntensity), + CornerRadii.EMPTY, + Insets.EMPTY + ))); + + // Set the icon color and rippler color of the toggleDeclarationButton + controller.toggleValuesButton.setRipplerFill(newColor.getTextColor(newIntensity)); + + controller.toolbar.setPrefHeight(Grid.TOOL_BAR_HEIGHT); + controller.toggleValuesButton.setBackground(Background.EMPTY); + }; + controller.getComponent().colorProperty().addListener(observable -> updateColor.accept(component.getColor(), component.getColorIntensity())); + + updateColor.accept(component.getColor(), component.getColorIntensity()); + + // Set a hover effect for the controller.toggleDeclarationButton + controller.toggleValuesButton.setOnMouseEntered(event -> controller.toggleValuesButton.setCursor(Cursor.HAND)); + controller.toggleValuesButton.setOnMouseExited(event -> controller.toggleValuesButton.setCursor(Cursor.DEFAULT)); + } + + /** + * Fades the process. + * Used if it is not involved in a transition + */ + public void showInactive() { + setOpacity(0.5); + } + + /** + * Show the process as active. + * Used to reset the effect of {@link ProcessPresentation#showInactive()} + */ + public void showActive() { + setOpacity(1.0); + } + + @Override + ModelController getModelController() { + return controller; + } + + /** + * Gets the minimum possible width when dragging the anchor. + * The width is based on the x coordinate of locations, nails and the signature arrows.
+ * This should be removed from {@link ModelPresentation} and made into an interface of its own + * @return the minimum possible width. + */ + @Override + @Deprecated + double getDragAnchorMinWidth() { + final Component component = controller.getComponent(); + double minWidth = 10 * GRID_SIZE; + + for (final Location location : component.getLocations()) { + minWidth = Math.max(minWidth, location.getX() + GRID_SIZE * 2); + } + + for (final Edge edge : component.getEdges()) { + for (final Nail nail : edge.getNails()) { + minWidth = Math.max(minWidth, nail.getX() + GRID_SIZE); + } + } + return minWidth; + } + + /** + * Gets the minimum possible height when dragging the anchor. + * The height is based on the y coordinate of locations, nails and the signature arrows
+ * This should be removed from {@link ModelPresentation} and made into an interface of its own + * @return the minimum possible height. + */ + @Override + @Deprecated + double getDragAnchorMinHeight() { + final Component component = controller.getComponent(); + double minHeight = 10 * GRID_SIZE; + + for (final Location location : component.getLocations()) { + minHeight = Math.max(minHeight, location.getY() + GRID_SIZE * 2); + } + + for (final Edge edge : component.getEdges()) { + for (final Nail nail : edge.getNails()) { + minHeight = Math.max(minHeight, nail.getY() + GRID_SIZE); + } + } + + return minHeight; + } + + public ProcessController getController() { + return controller; + } +} diff --git a/src/main/java/ecdar/presentations/RightSimPanePresentation.java b/src/main/java/ecdar/presentations/RightSimPanePresentation.java new file mode 100755 index 00000000..729b8829 --- /dev/null +++ b/src/main/java/ecdar/presentations/RightSimPanePresentation.java @@ -0,0 +1,45 @@ +package ecdar.presentations; + +import ecdar.controllers.RightSimPaneController; +import ecdar.utility.colors.Color; +import ecdar.utility.helpers.DropShadowHelper; +import javafx.geometry.Insets; +import javafx.scene.layout.*; + +/** + * Presentation class for the right pane in the simulator + */ +public class RightSimPanePresentation extends StackPane { + private RightSimPaneController controller; + + public RightSimPanePresentation() { + controller = new EcdarFXMLLoader().loadAndGetController("RightSimPanePresentation.fxml", this); + + initializeBackground(); + initializeLeftBorder(); + } + + /** + * Sets the background color of the ScrollPane Vbox + */ + private void initializeBackground() { + controller.scrollPaneVbox.setBackground(new Background(new BackgroundFill( + Color.GREY.getColor(Color.Intensity.I200), + CornerRadii.EMPTY, + Insets.EMPTY + ))); + } + + /** + * Initializes the thin border on the left side of the querypane toolbar + */ + private void initializeLeftBorder() { + controller.queryPaneElement.getController().toolbar.setBorder(new Border(new BorderStroke( + Color.GREY_BLUE.getColor(Color.Intensity.I900), + BorderStrokeStyle.SOLID, + CornerRadii.EMPTY, + new BorderWidths(0, 0, 0, 1) + ))); + } + +} diff --git a/src/main/java/ecdar/presentations/SimulatorOverviewPresentation.java b/src/main/java/ecdar/presentations/SimulatorOverviewPresentation.java new file mode 100755 index 00000000..15a34e54 --- /dev/null +++ b/src/main/java/ecdar/presentations/SimulatorOverviewPresentation.java @@ -0,0 +1,20 @@ +package ecdar.presentations; + +import ecdar.controllers.SimulatorOverviewController; +import javafx.scene.layout.AnchorPane; + +/** + * The presenter of the middle part of the simulator. + * It is here where processes of a simulation will be shown. + */ +public class SimulatorOverviewPresentation extends AnchorPane { + private final SimulatorOverviewController controller; + + public SimulatorOverviewPresentation() { + controller = new EcdarFXMLLoader().loadAndGetController("SimulatorOverviewPresentation.fxml", this); + } + + public SimulatorOverviewController getController() { + return controller; + } +} diff --git a/src/main/java/ecdar/presentations/SimulatorPresentation.java b/src/main/java/ecdar/presentations/SimulatorPresentation.java new file mode 100755 index 00000000..2d1a5105 --- /dev/null +++ b/src/main/java/ecdar/presentations/SimulatorPresentation.java @@ -0,0 +1,21 @@ +package ecdar.presentations; + +import ecdar.controllers.SimulatorController; +import javafx.scene.layout.StackPane; + +public class SimulatorPresentation extends StackPane { + private final SimulatorController controller; + + public SimulatorPresentation() { + controller = new EcdarFXMLLoader().loadAndGetController("SimulatorPresentation.fxml", this); + } + + + /** + * The way to get the associated/linked controller of this presenter + * @return the controller linked to this presenter + */ + public SimulatorController getController() { + return controller; + } +} diff --git a/src/main/java/ecdar/presentations/TracePaneElementPresentation.java b/src/main/java/ecdar/presentations/TracePaneElementPresentation.java new file mode 100755 index 00000000..4e9a15b3 --- /dev/null +++ b/src/main/java/ecdar/presentations/TracePaneElementPresentation.java @@ -0,0 +1,96 @@ +package ecdar.presentations; + +import com.jfoenix.controls.JFXRippler; +import ecdar.controllers.TracePaneElementController; +import ecdar.utility.colors.Color; +import javafx.geometry.Insets; +import javafx.scene.Cursor; +import javafx.scene.layout.*; + +import java.util.function.BiConsumer; + +/** + * The presentation class for the trace element that can be inserted into the simulator panes + */ +public class TracePaneElementPresentation extends AnchorPane { + final private TracePaneElementController controller; + + public TracePaneElementPresentation() { + controller = new EcdarFXMLLoader().loadAndGetController("TracePaneElementPresentation.fxml", this); + + initializeToolbar(); + initializeSummaryView(); + } + + /** + * Initializes the tool bar that contains the trace pane element's title and buttons + * Sets the color of the bar and title label. Also sets the look of the rippler effect + */ + private void initializeToolbar() { + final Color color = Color.GREY_BLUE; + final Color.Intensity colorIntensity = Color.Intensity.I800; + + controller.toolbar.setBackground(new Background(new BackgroundFill( + color.getColor(colorIntensity), + CornerRadii.EMPTY, + Insets.EMPTY))); + controller.traceTitle.setTextFill(color.getTextColor(colorIntensity)); + + controller.expandTrace.setMaskType(JFXRippler.RipplerMask.CIRCLE); + controller.expandTrace.setRipplerFill(color.getTextColor(colorIntensity)); + } + + /** + * Initializes the summary view so it is update when steps are taken in the trace. + * Also changes the color and cursor when mouse enters and exits the summary view. + */ + private void initializeSummaryView() { + controller.getNumberOfStepsProperty().addListener( + (observable, oldValue, newValue) -> updateSummaryTitle(newValue.intValue())); + + final Color color = Color.GREY_BLUE; + final Color.Intensity colorIntensity = Color.Intensity.I50; + + final BiConsumer setBackground = (newColor, newIntensity) -> { + controller.traceSummary.setBackground(new Background(new BackgroundFill( + newColor.getColor(newIntensity), + CornerRadii.EMPTY, + Insets.EMPTY + ))); + + controller.traceSummary.setBorder(new Border(new BorderStroke( + newColor.getColor(newIntensity.next(2)), + BorderStrokeStyle.SOLID, + CornerRadii.EMPTY, + new BorderWidths(0, 0, 1, 0) + ))); + }; + + // Update the background when hovered + controller.traceSummary.setOnMouseEntered(event -> { + setBackground.accept(color, colorIntensity.next()); + setCursor(Cursor.HAND); + }); + + // Update the background when the mouse exits + controller.traceSummary.setOnMouseExited(event -> { + setBackground.accept(color, colorIntensity); + setCursor(Cursor.DEFAULT); + }); + + // Update the background initially + setBackground.accept(color, colorIntensity); + } + + /** + * Updates the text of the summary title label with the current number of steps in the trace + * @param steps The number of steps in the trace + */ + private void updateSummaryTitle(int steps) { + controller.summaryTitleLabel.setText(steps + " number of steps in trace"); + } + + public TracePaneElementController getController() { + return controller; + } +} diff --git a/src/main/java/ecdar/presentations/TransitionPaneElementPresentation.java b/src/main/java/ecdar/presentations/TransitionPaneElementPresentation.java new file mode 100755 index 00000000..9dbac052 --- /dev/null +++ b/src/main/java/ecdar/presentations/TransitionPaneElementPresentation.java @@ -0,0 +1,69 @@ +package ecdar.presentations; + +import com.jfoenix.controls.JFXRippler; +import ecdar.controllers.TransitionPaneElementController; +import ecdar.utility.colors.Color; +import javafx.geometry.Insets; +import javafx.scene.layout.*; + +/** + * The presentation class for the transition pane element that can be inserted into the simulator panes + */ +public class TransitionPaneElementPresentation extends AnchorPane { + final private TransitionPaneElementController controller; + + public TransitionPaneElementPresentation() { + controller = new EcdarFXMLLoader().loadAndGetController("TransitionPaneElementPresentation.fxml", this); + + initializeToolbar(); + initializeDelayChooser(); + } + + /** + * Initializes the toolbar for the transition pane element. + * Sets the background of the toolbar and changes the title color. + * Also changes the look of the rippler effect. + */ + private void initializeToolbar() { + final Color color = Color.GREY_BLUE; + final Color.Intensity colorIntensity = Color.Intensity.I800; + + // Set the background of the toolbar + controller.toolbar.setBackground(new Background(new BackgroundFill( + color.getColor(colorIntensity), + CornerRadii.EMPTY, + Insets.EMPTY))); + // Set the font color of elements in the toolbar + controller.toolbarTitle.setTextFill(color.getTextColor(colorIntensity)); + + controller.refreshRippler.setMaskType(JFXRippler.RipplerMask.CIRCLE); + controller.refreshRippler.setRipplerFill(color.getTextColor(colorIntensity)); + + controller.expandTransition.setMaskType(JFXRippler.RipplerMask.CIRCLE); + controller.expandTransition.setRipplerFill(color.getTextColor(colorIntensity)); + } + + /** + * Sets the background color of the delay chooser + */ + private void initializeDelayChooser() { + final Color color = Color.GREY_BLUE; + final Color.Intensity colorIntensity = Color.Intensity.I50; + controller.delayChooser.setBackground(new Background(new BackgroundFill( + color.getColor(colorIntensity), + CornerRadii.EMPTY, + Insets.EMPTY + ))); + + controller.delayChooser.setBorder(new Border(new BorderStroke( + color.getColor(colorIntensity.next(2)), + BorderStrokeStyle.SOLID, + CornerRadii.EMPTY, + new BorderWidths(0, 0, 1, 0) + ))); + } + + public TransitionPaneElementController getController() { + return controller; + } +} diff --git a/src/main/java/ecdar/presentations/TransitionPresentation.java b/src/main/java/ecdar/presentations/TransitionPresentation.java new file mode 100755 index 00000000..15fb47e6 --- /dev/null +++ b/src/main/java/ecdar/presentations/TransitionPresentation.java @@ -0,0 +1,98 @@ +package ecdar.presentations; + +import com.jfoenix.controls.JFXRippler; +import ecdar.controllers.TransitionController; +import ecdar.utility.colors.Color; +import javafx.animation.FadeTransition; +import javafx.animation.Interpolator; +import javafx.geometry.Insets; +import javafx.scene.Cursor; +import javafx.scene.layout.*; +import javafx.util.Duration; + +import java.util.function.BiConsumer; + +/** + * The presentation class for a transition view element. + * It represents a single transition and may be used by classes like {@see TransitionPaneElementController} + * to show a list of transitions + */ +public class TransitionPresentation extends AnchorPane { + private TransitionController controller; + private FadeTransition transition; + + public TransitionPresentation() { + controller = new EcdarFXMLLoader().loadAndGetController("TransitionPresentation.fxml", this); + + initializeRippler(); + initializeColors(); + initializeFadeAnimation(); + } + + /** + * Initializes the rippler. + * Sets the color, mask and position of the rippler effect. + */ + private void initializeRippler() { + final Color color = Color.GREY_BLUE; + final Color.Intensity colorIntensity = Color.Intensity.I400; + + controller.rippler.setMaskType(JFXRippler.RipplerMask.RECT); + controller.rippler.setRipplerFill(color.getColor(colorIntensity)); + controller.rippler.setPosition(JFXRippler.RipplerPos.BACK); + } + + /** + * Initializes the colors of the view. + * The background of the view changes colors depending on whether a mouse enters or exits the view. + */ + private void initializeColors() { + final Color color = Color.GREY_BLUE; + final Color.Intensity colorIntensity = Color.Intensity.I50; + + final BiConsumer setBackground = (newColor, newIntensity) -> { + setBackground(new Background(new BackgroundFill( + newColor.getColor(newIntensity), + CornerRadii.EMPTY, + Insets.EMPTY + ))); + + setBorder(new Border(new BorderStroke( + newColor.getColor(newIntensity.next(2)), + BorderStrokeStyle.SOLID, + CornerRadii.EMPTY, + new BorderWidths(0, 0, 1, 0) + ))); + }; + + // Update the background when hovered + setOnMouseEntered(event -> { + setBackground.accept(color, colorIntensity.next()); + setCursor(Cursor.HAND); + }); + + // Update the background when the mouse exits + setOnMouseExited(event -> { + setBackground.accept(color, colorIntensity); + setCursor(Cursor.DEFAULT); + }); + + // Update the background initially + setBackground.accept(color, colorIntensity); + } + + private void initializeFadeAnimation() { + this.transition = new FadeTransition(Duration.millis(500), this); + transition.setFromValue(0); + transition.setToValue(1); + transition.setInterpolator(Interpolator.EASE_IN); + } + + public void playFadeAnimation() { + this.transition.play(); + } + + public TransitionController getController() { + return controller; + } +} diff --git a/src/main/java/ecdar/simulation/SimulationEdge.java b/src/main/java/ecdar/simulation/SimulationEdge.java new file mode 100644 index 00000000..696f573f --- /dev/null +++ b/src/main/java/ecdar/simulation/SimulationEdge.java @@ -0,0 +1,8 @@ +package ecdar.simulation; + +public class SimulationEdge { + public String getName() { + // ToDo: Implement + return "Edge name"; + } +} diff --git a/src/main/java/ecdar/simulation/SimulationHandler.java b/src/main/java/ecdar/simulation/SimulationHandler.java new file mode 100755 index 00000000..65d2ea73 --- /dev/null +++ b/src/main/java/ecdar/simulation/SimulationHandler.java @@ -0,0 +1,407 @@ +package ecdar.simulation; + +import ecdar.Ecdar; +import ecdar.abstractions.*; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.collections.ObservableMap; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Map; + +/** + * Handles state changes, updates of values / clocks, and keeps track of all the transitions that + * have been taken throughout a simulation. + */ +public class SimulationHandler { + public static final String QUERY_PREFIX = "Query: "; + private ObjectProperty currentConcreteState; + private ObjectProperty initialConcreteState; + private ObjectProperty currentTime; + private BigDecimal delay; + private ArrayList edgesSelected; + private EcdarSystem system; + private SimulationStateSuccessor successor; + private int numberOfSteps; + + /** + * A string to keep track what is currently being simulated + * For now the string is prefixed with {@link #QUERY_PREFIX} when doing a query simulation + * and kept empty when doing system simulations + */ + private String currentSimulation = ""; + + private final ObservableMap simulationVariables = FXCollections.observableHashMap(); + private final ObservableMap simulationClocks = FXCollections.observableHashMap(); + /** + * For some reason the successor.getTransitions() only sometimes returns some of the transitions + * that are available, when running the initial step. + * That is why we need to keep track of the initial transitions. + */ + private final ObservableList initialTransitions = FXCollections.observableArrayList(); + public ObservableList traceLog = FXCollections.observableArrayList(); + public ObservableList availableTransitions = FXCollections.observableArrayList(); + + /** + * Empty constructor that should be used if the system or project has not be initialized yet + */ + public SimulationHandler() { + + } + + /** + * Initializes the default system (non-query system) + */ + public void initializeDefaultSystem() { + currentSimulation = ""; + } + + /** + * Initializes the values and properties in the {@link SimulationHandler}. + * Can also be used as a reset of the simulation. + * THIS METHOD DOES NOT RESET THE ENGINE, + */ + private void initializeSimulation() { + // Initialization + this.delay = new BigDecimal(0); + this.edgesSelected = new ArrayList<>(); + this.numberOfSteps = 0; + this.availableTransitions.clear(); + this.simulationVariables.clear(); + this.simulationClocks.clear(); + this.traceLog.clear(); + this.currentConcreteState = new SimpleObjectProperty<>(getInitialConcreteState()); + this.initialConcreteState = new SimpleObjectProperty<>(getInitialConcreteState()); + this.currentTime = new SimpleObjectProperty<>(BigDecimal.ZERO); + + //Preparation for the simulation + this.system = getSystem(); + this.currentConcreteState.get().setTime(currentTime.getValue()); + this.initialTransitions.clear(); + this.successor = null; + } + + /** + * Reloads the whole simulation sets the initial transitions, states, etc + */ + public void initialStep() { + initializeSimulation(); + final SimulationState currentState = currentConcreteState.get(); + successor = getStateSuccessor(); + + //Save the previous states, and get the new + currentConcreteState.set(successor.getState()); + this.traceLog.add(currentState); + numberOfSteps++; + + //Updates the transitions available + availableTransitions.addAll(FXCollections.observableArrayList(successor.getTransitions())); + initialTransitions.addAll(availableTransitions); + updateAllValues(); + } + + /** + * Resets the simulation to the initial location + * where the SimulationState is the {@link SimulationHandler#initialConcreteState}, when there are + * elements in the {@link SimulationHandler#traceLog}. Otherwise it calls {@link SimulationHandler#initialStep} + */ + public void resetToInitialLocation() { + //If the simulation has not begone + if (traceLog.size() == 0) + initialStep(); + else + selectTransitionFromLog(initialConcreteState.get()); + } + + /** + * Resets the simulation to the state after executing the given transition.
+ * This method also resets the state, variables, and clocks to the values they had after the given transition. + * This also updates {@link SimulationHandler#availableTransitions} such that + * it displays the available transitions after taking the given transition. + * + * @param transition the transition which the simulation should go back to + */ + public void selectTransitionFromLog(final SimulationState transition) { + final int indexInTrace = traceLog.indexOf(transition); + final SimulationState selectedState; + if (indexInTrace == -1) { + System.out.println("Cannot find transition: " + transition); + Ecdar.showToast("Cannot find transition: " + transition); + return; + } else if (indexInTrace == numberOfSteps - 1) { + return; //you have selected the current system + } else { + selectedState = traceLog.get(indexInTrace); + } + final int sizeOfTraceLog = traceLog.size(); + final int maxRetries = 3; + int numberOfRetries = 0; + edgesSelected = new ArrayList<>(); + //In case that we fail we have to save the time we had before + final BigDecimal tempTime = currentTime.get(); + + currentTime.setValue(new BigDecimal(selectedState.getTime().doubleValue())); + successor.getState().setTime(currentTime.getValue()); + + while (numberOfRetries < maxRetries) { + successor = getStateSuccessor(); + break; + } + currentConcreteState.set(selectedState); + setSimVarAndClocks(); + traceLog.remove(indexInTrace + 1, sizeOfTraceLog); + availableTransitions.clear(); + + // If the user selected the initial/first state in the trace log, we do not trust the engine, + // as it only gives us a subset of the available transitions, in some cases. + if (indexInTrace == 0) availableTransitions.addAll(initialTransitions); + else availableTransitions.addAll(successor.getTransitions()); + + numberOfSteps = indexInTrace + 1; + } + + /** + * Take a step in the simulation. + * + * @param selectedTransitionIndex the index of the availableTransition that you want to take. + * @param delay the time which should pass after the transition. + */ + public void nextStep(final int selectedTransitionIndex, final BigDecimal delay) { + if (selectedTransitionIndex > availableTransitions.size()) { + Ecdar.showToast("The selected transition index: " + selectedTransitionIndex + " is bigger than it should: " + availableTransitions); + return; + } + + final Transition selectedTransition = availableTransitions.get(selectedTransitionIndex); + edgesSelected = new ArrayList<>(); + + //Preparing for the step + for (int i = 0; i < selectedTransition.getEdges().size(); i++) { + edgesSelected.set(i, selectedTransition.getEdges().get(i)); + } + + final int maxRetries = 3; + int numberOfRetries = 0; + + // getConcreteSuccessor may throw a "ProtocolException: Word expected" but in some cases calling the same + // method again does not throw this exception, and actually gives us the expected result. + // This loop calls the method a number of times (maxRetries) + while (numberOfRetries < maxRetries) { + successor = getStateSuccessor(); + // Break from the loop if the method call was a success + break; + } + + //Save the previous states, and get the new + currentConcreteState.set(successor.getState()); + this.traceLog.add(currentConcreteState.get()); + + // increments the number of steps taken during this simulation + numberOfSteps++; + + //Updates the transitions available + availableTransitions.clear(); + availableTransitions.setAll(successor.getTransitions()); + this.delay = delay; + updateAllValues(); + } + + private SimulationStateSuccessor getStateSuccessor() { + // ToDo: Implement + return new SimulationStateSuccessor(); + } + + /** + * An overload of {@link SimulationHandler#nextStep(int, BigDecimal)} where the delay is 0. + * + * @param selectedTransition the index of the availableTransition that you want to take. + */ + public void nextStep(final int selectedTransition) { + nextStep(selectedTransition, BigDecimal.ZERO); + } + + public void nextStep(final Transition transition, final BigDecimal delay) { + int index = availableTransitions.indexOf(transition); + if (index != -1) { + nextStep(index, delay); + } + } + + /** + * Updates all values and clocks that are used doing the current simulation. + * It also stores the variables in the {@link SimulationHandler#simulationVariables} + * and the clocks in {@link SimulationHandler#simulationClocks}. + */ + private void updateAllValues() { + currentTime.set(currentTime.get().add(delay)); + successor.getState().setTime(currentTime.get()); + setSimVarAndClocks(); + } + + /** + * Sets the value of simulation variables and clocks, based on {@link SimulationHandler#currentConcreteState} + */ + private void setSimVarAndClocks() { + // The variables and clocks are all found in the getVariables array + // the array is always of the following order: variables, clocks. + // The noOfVars variable thus also functions as an offset for the clocks in the getVariables array +// final int noOfClocks = engine.getSystem().getNoOfClocks(); +// final int noOfVars = engine.getSystem().getNoOfVariables(); + +// for (int i = 0; i < noOfVars; i++){ +// simulationVariables.put(engine.getSystem().getVariableName(i), +// currentConcreteState.get().getVariables()[i].getValue(BigDecimal.ZERO)); +// } + + // As the clocks values starts after the variables values in currentConcreteState.get().getVariables() + // Then i needs to start where the variables ends. + // j is needed to map the correct name with the value +// for (int i = noOfVars, j = 0; i < noOfClocks + noOfVars ; i++, j++) { +// simulationClocks.put(engine.getSystem().getClockName(j), +// currentConcreteState.get().getVariables()[i].getValue(BigDecimal.ZERO)); +// } + } + + /** + * Getter for the current concrete state + * + * @return the current {@link SimulationState} + */ + public SimulationState getCurrentState() { + return currentConcreteState.get(); + } + + /** + * The way to get the time in the current state of a simulation + * + * @return the time in the current state + */ + public BigDecimal getCurrentTime() { + return currentTime.get(); + } + + public ObjectProperty currentTimeProperty() { + return currentTime; + } + + /** + * The way to get the delay of the latest step in the simulation + * + * @return the delay of the latest step in the in the simulation + */ + public BigDecimal getDelay() { + return delay; + } + + /** + * The number of total steps taken in the current simulation + * + * @return the number of steps + */ + public int getNumberOfSteps() { + return numberOfSteps; + } + + /** + * All the transitions taken in this simulation + * + * @return an {@link ObservableList} of all the transitions taken in this simulation so far + */ + public ObservableList getTraceLog() { + return traceLog; + } + + /** + * All the available transitions in this state + * + * @return an {@link ObservableList} of all the currently available transitions in this state + */ + public ObservableList getAvailableTransitions() { + return availableTransitions; + } + + /** + * All the variables connected to the current simulation. + * This does not return any clocks, if you need please use {@link SimulationHandler#getSimulationClocks()} instead + * + * @return a {@link Map} where the name (String) is the key, and a {@link BigDecimal} is the value + */ + public ObservableMap getSimulationVariables() { + return simulationVariables; + } + + /** + * All the clocks connected to the current simulation. + * + * @return a {@link Map} where the name (String) is the key, and a {@link BigDecimal} is the clock value + * @see SimulationHandler#getSimulationVariables() + */ + public ObservableMap getSimulationClocks() { + return simulationClocks; + } + + /** + * The initial state of the current simulation + * + * @return the initial {@link SimulationState} of this simulation + */ + public SimulationState getInitialConcreteState() { + // ToDo: Implement + return initialConcreteState.get(); + } + + public ObjectProperty initialConcreteStateProperty() { + return initialConcreteState; + } + + /** + * Prints all available transitions to {@link System#out}. + * This is very useful for debugging. + * If a string representation is needed please use {@link SimulationHandler#getAvailableTransitionsAsStrings()} + * instead. + */ + public void printAvailableTransitions() { + System.out.println("---------------------------------"); + + System.out.println(numberOfSteps + " Successor state " + currentConcreteState.toString() + " Entry time " + currentTime); + System.out.print("Available transitions: "); + availableTransitions.forEach( + Transition -> System.out.println(Transition.getLabel() + " ")); + + if (!availableTransitions.isEmpty()) { + for (int i = 0; i < availableTransitions.get(0).getEdges().size(); i++) { + // ToDo: Implement +// System.out.println("Edges: " + +// availableTransitions.get(0).getEdges().get(i).getEdge().getSource().getPropertyValue("name") + +// "." + availableTransitions.get(0).getEdges().get(i).getName() + " --> " + +// availableTransitions.get(0).getEdges().get(i).getEdge().getTarget().getPropertyValue("name")); + } + } + + System.out.println("---------------------------------"); + } + + /** + * To get all available transitions as strings + * + * @return an ArrayList of all the enabled transitions + */ + public ArrayList getAvailableTransitionsAsStrings() { + final ArrayList transitions = new ArrayList<>(); + for (final Transition Transition : availableTransitions) { + transitions.add(Transition.getLabel()); + } + return transitions; + } + + public EcdarSystem getSystem() { + return system; + } + + public String getCurrentSimulation() { + return currentSimulation; + } +} \ No newline at end of file diff --git a/src/main/java/ecdar/simulation/SimulationLocation.java b/src/main/java/ecdar/simulation/SimulationLocation.java new file mode 100644 index 00000000..5bcbba6a --- /dev/null +++ b/src/main/java/ecdar/simulation/SimulationLocation.java @@ -0,0 +1,8 @@ +package ecdar.simulation; + +public class SimulationLocation { + public String getName() { + // ToDo: Implement + return "Location name"; + } +} diff --git a/src/main/java/ecdar/simulation/SimulationState.java b/src/main/java/ecdar/simulation/SimulationState.java new file mode 100644 index 00000000..933864f1 --- /dev/null +++ b/src/main/java/ecdar/simulation/SimulationState.java @@ -0,0 +1,42 @@ +package ecdar.simulation; + +import ecdar.abstractions.Location; + +import java.math.BigDecimal; +import java.util.ArrayList; + +public class SimulationState { + public void setTime(BigDecimal value) { + // ToDo: Implement + } + + public Number getTime() { + // ToDo: Implement + return new Number() { + @Override + public int intValue() { + return 0; + } + + @Override + public long longValue() { + return 0; + } + + @Override + public float floatValue() { + return 0; + } + + @Override + public double doubleValue() { + return 0; + } + }; + } + + public ArrayList getLocations() { + // ToDo: Implement + return new ArrayList<>(); + } +} diff --git a/src/main/java/ecdar/simulation/SimulationStateSuccessor.java b/src/main/java/ecdar/simulation/SimulationStateSuccessor.java new file mode 100644 index 00000000..0c519adb --- /dev/null +++ b/src/main/java/ecdar/simulation/SimulationStateSuccessor.java @@ -0,0 +1,15 @@ +package ecdar.simulation; + +import java.util.ArrayList; + +public class SimulationStateSuccessor { + public ArrayList getTransitions() { + // ToDo: Implement + return new ArrayList<>(); + } + + public SimulationState getState() { + // ToDo: Implement + return new SimulationState(); + } +} diff --git a/src/main/java/ecdar/simulation/Transition.java b/src/main/java/ecdar/simulation/Transition.java new file mode 100644 index 00000000..da5bac14 --- /dev/null +++ b/src/main/java/ecdar/simulation/Transition.java @@ -0,0 +1,17 @@ +package ecdar.simulation; + +import ecdar.abstractions.Edge; + +import java.util.ArrayList; + +public class Transition { + public String getLabel() { + // ToDo: Implement + return "Transition label"; + } + + public ArrayList getEdges() { + // ToDo: Implement + return new ArrayList<>(); + } +} diff --git a/src/main/resources/ecdar/presentations/LeftSimPanePresentation.fxml b/src/main/resources/ecdar/presentations/LeftSimPanePresentation.fxml new file mode 100755 index 00000000..28ed941f --- /dev/null +++ b/src/main/resources/ecdar/presentations/LeftSimPanePresentation.fxml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/ecdar/presentations/RightSimPanePresentation.fxml b/src/main/resources/ecdar/presentations/RightSimPanePresentation.fxml new file mode 100755 index 00000000..de2b3c4c --- /dev/null +++ b/src/main/resources/ecdar/presentations/RightSimPanePresentation.fxml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + diff --git a/src/main/resources/ecdar/presentations/SimulatorOverviewPresentation.fxml b/src/main/resources/ecdar/presentations/SimulatorOverviewPresentation.fxml new file mode 100755 index 00000000..f854ef33 --- /dev/null +++ b/src/main/resources/ecdar/presentations/SimulatorOverviewPresentation.fxml @@ -0,0 +1,13 @@ + + + + + + + \ No newline at end of file diff --git a/src/main/resources/ecdar/presentations/SimulatorPresentation.fxml b/src/main/resources/ecdar/presentations/SimulatorPresentation.fxml new file mode 100755 index 00000000..4f56d4fd --- /dev/null +++ b/src/main/resources/ecdar/presentations/SimulatorPresentation.fxml @@ -0,0 +1,55 @@ + + + + + + + + + + + +

+ + + + + + + + + + + + +
+ + + + + + + + + + + + + + + diff --git a/src/main/resources/ecdar/presentations/TracePaneElementPresentation.fxml b/src/main/resources/ecdar/presentations/TracePaneElementPresentation.fxml new file mode 100755 index 00000000..49cb83da --- /dev/null +++ b/src/main/resources/ecdar/presentations/TracePaneElementPresentation.fxml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/ecdar/presentations/TransitionPaneElementPresentation.fxml b/src/main/resources/ecdar/presentations/TransitionPaneElementPresentation.fxml new file mode 100755 index 00000000..aeab56de --- /dev/null +++ b/src/main/resources/ecdar/presentations/TransitionPaneElementPresentation.fxml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/ecdar/presentations/TransitionPresentation.fxml b/src/main/resources/ecdar/presentations/TransitionPresentation.fxml new file mode 100755 index 00000000..3fa1c5cf --- /dev/null +++ b/src/main/resources/ecdar/presentations/TransitionPresentation.fxml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + From a5b558c04a6092dc34022eab273196596ec14cab Mon Sep 17 00:00:00 2001 From: Niels Vistisen Date: Wed, 13 Jul 2022 11:06:21 +0200 Subject: [PATCH 002/158] WIP: Compiling system with functionalities from Main working (simulator still not working, but presentation can be displayed) --- .../controllers/DeclarationsController.java | 8 +- .../ecdar/controllers/EcdarController.java | 465 +++++------------- .../ecdar/controllers/EditorController.java | 380 ++++++++++++++ .../ecdar/controllers/ProcessController.java | 40 +- .../controllers/RightSimPaneController.java | 3 +- .../ecdar/controllers/SimNailController.java | 126 +++++ .../controllers/SimulatorController.java | 0 .../SimulatorOverviewController.java | 8 +- .../presentations/EcdarPresentation.java | 228 +-------- .../presentations/EditorPresentation.java | 201 ++++++++ .../ecdar/presentations/FilePresentation.java | 2 +- .../RightSimPanePresentation.java | 2 +- .../SimEdgePresentation.java | 0 .../SimLocationPresentation.java | 4 +- .../presentations/SimNailPresentation.java | 258 ++++++++++ .../presentations/SimTagPresentation.java | 186 +++++++ .../ecdar/utility/helpers/BindingHelper.java | 10 + .../presentations/EcdarPresentation.fxml | 82 +-- .../presentations/EditorPresentation.fxml | 81 +++ .../RightSimPanePresentation.fxml | 3 +- .../presentations/SimEdgePresentation.fxml | 12 + .../SimLocationPresentation.fxml | 39 ++ .../presentations/SimNailPresentation.fxml | 23 + .../presentations/SimTagPresentation.fxml | 11 + 24 files changed, 1517 insertions(+), 655 deletions(-) create mode 100644 src/main/java/ecdar/controllers/EditorController.java create mode 100755 src/main/java/ecdar/controllers/SimNailController.java mode change 100755 => 100644 src/main/java/ecdar/controllers/SimulatorController.java mode change 100755 => 100644 src/main/java/ecdar/controllers/SimulatorOverviewController.java create mode 100644 src/main/java/ecdar/presentations/EditorPresentation.java rename src/main/java/ecdar/{controllers => presentations}/SimEdgePresentation.java (100%) rename src/main/java/ecdar/{controllers => presentations}/SimLocationPresentation.java (97%) create mode 100755 src/main/java/ecdar/presentations/SimNailPresentation.java create mode 100755 src/main/java/ecdar/presentations/SimTagPresentation.java create mode 100644 src/main/resources/ecdar/presentations/EditorPresentation.fxml create mode 100755 src/main/resources/ecdar/presentations/SimEdgePresentation.fxml create mode 100755 src/main/resources/ecdar/presentations/SimLocationPresentation.fxml create mode 100755 src/main/resources/ecdar/presentations/SimNailPresentation.fxml create mode 100755 src/main/resources/ecdar/presentations/SimTagPresentation.fxml diff --git a/src/main/java/ecdar/controllers/DeclarationsController.java b/src/main/java/ecdar/controllers/DeclarationsController.java index e08f9256..94875042 100644 --- a/src/main/java/ecdar/controllers/DeclarationsController.java +++ b/src/main/java/ecdar/controllers/DeclarationsController.java @@ -42,10 +42,10 @@ public void initialize(final URL location, final ResourceBundle resources) { */ private void initializeWidthAndHeight() { // Fetch width and height of canvas and update - root.minWidthProperty().bind(Ecdar.getPresentation().getController().canvasPane.minWidthProperty()); - root.maxWidthProperty().bind(Ecdar.getPresentation().getController().canvasPane.maxWidthProperty()); - root.minHeightProperty().bind(Ecdar.getPresentation().getController().canvasPane.minHeightProperty()); - root.maxHeightProperty().bind(Ecdar.getPresentation().getController().canvasPane.maxHeightProperty()); + root.minWidthProperty().bind(Ecdar.getPresentation().getController().getEditorPresentation().getController().canvasPane.minWidthProperty()); + root.maxWidthProperty().bind(Ecdar.getPresentation().getController().getEditorPresentation().getController().canvasPane.maxWidthProperty()); + root.minHeightProperty().bind(Ecdar.getPresentation().getController().getEditorPresentation().getController().canvasPane.minHeightProperty()); + root.maxHeightProperty().bind(Ecdar.getPresentation().getController().getEditorPresentation().getController().canvasPane.maxHeightProperty()); updateOffset(EcdarController.getActiveCanvasPresentation().getController().getInsetShouldShow().get()); EcdarController.getActiveCanvasPresentation().getController().getInsetShouldShow().addListener((observable, oldValue, newValue) -> { diff --git a/src/main/java/ecdar/controllers/EcdarController.java b/src/main/java/ecdar/controllers/EcdarController.java index 9baee9c7..806b040c 100644 --- a/src/main/java/ecdar/controllers/EcdarController.java +++ b/src/main/java/ecdar/controllers/EcdarController.java @@ -12,7 +12,6 @@ import ecdar.presentations.*; import ecdar.utility.UndoRedoStack; import ecdar.utility.colors.Color; -import ecdar.utility.colors.EnabledColor; import ecdar.utility.helpers.SelectHelper; import ecdar.utility.keyboard.Keybind; import ecdar.utility.keyboard.KeyboardTracker; @@ -23,7 +22,6 @@ import javafx.beans.binding.When; import javafx.beans.property.*; import javafx.collections.ListChangeListener; -import javafx.collections.ObservableList; import javafx.embed.swing.SwingFXUtils; import javafx.fxml.FXML; import javafx.fxml.Initializable; @@ -39,7 +37,6 @@ import javafx.scene.text.Text; import javafx.stage.DirectoryChooser; import javafx.stage.FileChooser; -import javafx.util.Pair; import javax.imageio.ImageIO; import java.awt.image.BufferedImage; @@ -56,29 +53,21 @@ public class EcdarController implements Initializable { private static long reachabilityTime = Long.MAX_VALUE; private static ExecutorService reachabilityService; - private static final ObjectProperty globalEdgeStatus = new SimpleObjectProperty<>(EdgeStatus.INPUT); - // View stuff public StackPane root; public BorderPane borderPane; - public StackPane canvasPane; public StackPane topPane; public StackPane leftPane; public StackPane rightPane; public Rectangle bottomFillerElement; public QueryPanePresentation queryPane; public ProjectPanePresentation filePane; - public HBox toolbar; public MessageTabPanePresentation messageTabPane; public StackPane dialogContainer; public JFXDialog dialog; public StackPane modalBar; public JFXTextField queryTextField; public JFXTextField commentTextField; - public JFXRippler colorSelected; - public JFXRippler deleteSelected; - public JFXRippler undo; - public JFXRippler redo; public ImageView helpInitialImage; public StackPane helpInitialPane; @@ -89,10 +78,6 @@ public class EcdarController implements Initializable { public ImageView helpOutputImage; public StackPane helpOutputPane; - public JFXButton switchToInputButton; - public JFXButton switchToOutputButton; - public JFXToggleButton switchEdgeStatusButton; - public MenuItem menuEditMoveLeft; public MenuItem menuEditMoveUp; public MenuItem menuEditMoveRight; @@ -109,7 +94,7 @@ public class EcdarController implements Initializable { public MenuItem menuBarViewFilePanel; public MenuItem menuBarViewQueryPanel; public MenuItem menuBarViewGrid; - public MenuItem menuBarAutoscaling; + public MenuItem menuBarViewAutoscaling; public Menu menuViewMenuScaling; public ToggleGroup scaling; public RadioMenuItem scaleXS; @@ -120,6 +105,8 @@ public class EcdarController implements Initializable { public RadioMenuItem scaleXXL; public RadioMenuItem scaleXXXL; public MenuItem menuBarViewCanvasSplit; + public MenuItem menuBarViewEditor; + public MenuItem menuBarViewSimulator; public MenuItem menuBarFileCreateNewProject; public MenuItem menuBarFileOpenProject; public Menu menuBarFileRecentProjects; @@ -161,10 +148,80 @@ public static void runReachabilityAnalysis() { reachabilityTime = System.currentTimeMillis() + 500; } - private static final ObjectProperty activeCanvasPresentation = new SimpleObjectProperty<>(new CanvasPresentation()); + /** + * Enumeration to keep track of which mode the application is in + */ + private enum Mode { + Editor, Simulator + } + + /** + * currentMode is a property that keeps track of which mode the application is in. + * The initial mode is Mode.Editor + */ + private static final ObjectProperty currentMode = new SimpleObjectProperty<>(Mode.Editor); + + private static final EditorPresentation editorPresentation = new EditorPresentation(); + private static final SimulatorPresentation simulatorPresentation = new SimulatorPresentation(); + + @Override + public void initialize(final URL location, final ResourceBundle resources) { + initializeDialogs(); + initializeKeybindings(); + initializeStatusBar(); + initializeMenuBar(); + startBackgroundQueriesThread(); // Will terminate immediately if background queries are turned off + + bottomFillerElement.heightProperty().bind(messageTabPane.maxHeightProperty()); + messageTabPane.getController().setRunnableForOpeningAndClosingMessageTabPane(this::changeInsetsOfFileAndQueryPanes); + + // Update file coloring when active model changes + editorPresentation.getController().getActiveCanvasPresentation().getController().activeComponentProperty().addListener(observable -> filePane.getController().updateColorsOnFilePresentations()); + editorPresentation.getController().activeCanvasPresentationProperty().addListener(observable -> filePane.getController().updateColorsOnFilePresentations()); + + borderPane.centerProperty().addListener((observable, oldValue, newValue) -> System.out.println(newValue.getClass())); + borderPane.setCenter(editorPresentation); + } + + public StackPane getCenter() { + if (currentMode.get().equals(Mode.Editor)) { + return editorPresentation.getController().canvasPane; + } else { + return simulatorPresentation; + } + } public static EdgeStatus getGlobalEdgeStatus() { - return globalEdgeStatus.get(); + return editorPresentation.getController().getGlobalEdgeStatus(); + } + + public EditorPresentation getEditorPresentation() { + return editorPresentation; + } + + public SimulatorPresentation getSimulatorPresentation() { + return simulatorPresentation; + } + + public static CanvasPresentation getActiveCanvasPresentation() { + return editorPresentation.getController().getActiveCanvasPresentation(); + } + + public static DoubleProperty getActiveCanvasZoomFactor() { + return getActiveCanvasPresentation().getController().zoomHelper.currentZoomFactor; + } + + public static void setActiveCanvasPresentation(CanvasPresentation newActiveCanvasPresentation) { + getActiveCanvasPresentation().setOpacity(0.75); + newActiveCanvasPresentation.setOpacity(1); + editorPresentation.getController().setActiveCanvasPresentation(newActiveCanvasPresentation); + } + + public static void setActiveModelForActiveCanvas(HighLevelModelObject newActiveModel) { + getActiveCanvasPresentation().getController().setActiveModel(newActiveModel); + + // Change zoom level to fit new active model + Platform.runLater(() -> getActiveCanvasPresentation().getController().zoomHelper.zoomToFit()); } /** @@ -197,26 +254,7 @@ private double getNewCalculatedScale() { return (Double.parseDouble(scaling.getSelectedToggle().getProperties().get("scale").toString()) * Ecdar.getDpiScale()) * 13.0; } - private void scaleEdgeStatusToggle(double size) { - switchEdgeStatusButton.setScaleX(size / 13.0); - switchEdgeStatusButton.setScaleY(size / 13.0); - } - - @Override - public void initialize(final URL location, final ResourceBundle resources) { - initilizeDialogs(); - initializeCanvasPane(); - initializeEdgeStatusHandling(); - initializeKeybindings(); - initializeStatusBar(); - initializeMenuBar(); - startBackgroundQueriesThread(); // Will terminate immediately if background queries are turned off - - bottomFillerElement.heightProperty().bind(messageTabPane.maxHeightProperty()); - messageTabPane.getController().setRunnableForOpeningAndClosingMessageTabPane(this::changeInsetsOfFileAndQueryPanes); - } - - private void initilizeDialogs() { + private void initializeDialogs() { dialog.setDialogContainer(dialogContainer); dialogContainer.opacityProperty().bind(dialog.getChildren().get(0).scaleXProperty()); dialog.setOnDialogClosed(event -> dialogContainer.setVisible(false)); @@ -269,7 +307,6 @@ private void initializeDialog(JFXDialog dialog, StackPane dialogContainer) { filePane.getStyleClass().add("responsive-pane-sizing"); queryPane.getStyleClass().add("responsive-pane-sizing"); - initializeEdgeStatusHandling(); initializeKeybindings(); initializeStatusBar(); } @@ -302,17 +339,14 @@ private void initializeKeybindings() { event.consume(); nudgeSelected(NudgeDirection.UP); })); - KeyboardTracker.registerKeybind(KeyboardTracker.NUDGE_DOWN, new Keybind(new KeyCodeCombination(KeyCode.DOWN), (event) -> { event.consume(); nudgeSelected(NudgeDirection.DOWN); })); - KeyboardTracker.registerKeybind(KeyboardTracker.NUDGE_LEFT, new Keybind(new KeyCodeCombination(KeyCode.LEFT), (event) -> { event.consume(); nudgeSelected(NudgeDirection.LEFT); })); - KeyboardTracker.registerKeybind(KeyboardTracker.NUDGE_RIGHT, new Keybind(new KeyCodeCombination(KeyCode.RIGHT), (event) -> { event.consume(); nudgeSelected(NudgeDirection.RIGHT); @@ -326,67 +360,6 @@ private void initializeKeybindings() { KeyboardTracker.registerKeybind(KeyboardTracker.NUDGE_A, new Keybind(new KeyCodeCombination(KeyCode.A), () -> nudgeSelected(NudgeDirection.LEFT))); KeyboardTracker.registerKeybind(KeyboardTracker.NUDGE_S, new Keybind(new KeyCodeCombination(KeyCode.S), () -> nudgeSelected(NudgeDirection.DOWN))); KeyboardTracker.registerKeybind(KeyboardTracker.NUDGE_D, new Keybind(new KeyCodeCombination(KeyCode.D), () -> nudgeSelected(NudgeDirection.RIGHT))); - - // Keybind for deleting the selected elements - KeyboardTracker.registerKeybind(KeyboardTracker.DELETE_SELECTED, new Keybind(new KeyCodeCombination(KeyCode.DELETE), this::deleteSelectedClicked)); - - // Keybinds for coloring the selected elements - EnabledColor.enabledColors.forEach(enabledColor -> { - KeyboardTracker.registerKeybind(KeyboardTracker.COLOR_SELECTED + "_" + enabledColor.keyCode.getName(), new Keybind(new KeyCodeCombination(enabledColor.keyCode), () -> { - - final List> previousColor = new ArrayList<>(); - - SelectHelper.getSelectedElements().forEach(selectable -> { - previousColor.add(new Pair<>(selectable, new EnabledColor(selectable.getColor(), selectable.getColorIntensity()))); - }); - changeColorOnSelectedElements(enabledColor, previousColor); - SelectHelper.clearSelectedElements(); - })); - }); - } - - /** - * Handles the change of color on selected objects - * - * @param enabledColor The new color for the selected objects - * @param previousColor The color old color of the selected objects - */ - public void changeColorOnSelectedElements(final EnabledColor enabledColor, - final List> previousColor) { - UndoRedoStack.pushAndPerform(() -> { // Perform - SelectHelper.getSelectedElements() - .forEach(selectable -> selectable.color(enabledColor.color, enabledColor.intensity)); - }, () -> { // Undo - previousColor.forEach(selectableEnabledColorPair -> selectableEnabledColorPair.getKey().color(selectableEnabledColorPair.getValue().color, selectableEnabledColorPair.getValue().intensity)); - }, String.format("Changed the color of %d elements to %s", previousColor.size(), enabledColor.color.name()), "color-lens"); - } - - /** - * Initializes edge status. - * Input is the default status. - * This method sets buttons for edge status whenever the status changes. - */ - private void initializeEdgeStatusHandling() { - globalEdgeStatus.set(EdgeStatus.INPUT); - - Tooltip.install(switchToInputButton, new Tooltip("Switch to input mode")); - Tooltip.install(switchToOutputButton, new Tooltip("Switch to output mode")); - switchToInputButton.setDisableVisualFocus(true); // Hiding input button rippler on start-up - - globalEdgeStatus.addListener(((observable, oldValue, newValue) -> { - if (newValue.equals(EdgeStatus.INPUT)) { - switchToInputButton.setTextFill(javafx.scene.paint.Color.WHITE); - switchToOutputButton.setTextFill(javafx.scene.paint.Color.GREY); - switchEdgeStatusButton.setSelected(false); - } else { - switchToInputButton.setTextFill(javafx.scene.paint.Color.GREY); - switchToOutputButton.setTextFill(javafx.scene.paint.Color.WHITE); - switchEdgeStatusButton.setSelected(true); - } - })); - - // Ensure that the rippler is centered when scale is changed - Platform.runLater(() -> ((JFXRippler) switchEdgeStatusButton.lookup(".jfx-rippler")).setRipplerRecenter(true)); } private void startBackgroundQueriesThread() { @@ -537,27 +510,6 @@ private void initializeMenuBar() { initializeHelpMenu(); } - public static CanvasPresentation getActiveCanvasPresentation() { - return activeCanvasPresentation.get(); - } - - public static DoubleProperty getActiveCanvasZoomFactor() { - return getActiveCanvasPresentation().getController().zoomHelper.currentZoomFactor; - } - - public static void setActiveCanvasPresentation(CanvasPresentation newActiveCanvasPresentation) { - activeCanvasPresentation.get().setOpacity(0.75); - newActiveCanvasPresentation.setOpacity(1); - activeCanvasPresentation.set(newActiveCanvasPresentation); - } - - public static void setActiveModelForActiveCanvas(HighLevelModelObject newActiveModel) { - EcdarController.getActiveCanvasPresentation().getController().setActiveModel(newActiveModel); - - // Change zoom level to fit new active model - Platform.runLater(() -> EcdarController.getActiveCanvasPresentation().getController().zoomHelper.zoomToFit()); - } - private void initializeHelpMenu() { menuBarHelpHelp.setOnAction(event -> Ecdar.showHelp()); @@ -574,7 +526,6 @@ private void initializeHelpMenu() { }); aboutAcceptButton.setOnAction(event -> aboutDialog.close()); aboutDialog.setOnDialogClosed(event -> aboutContainer.setVisible(false)); // hide container when dialog is fully closed - } /** @@ -664,14 +615,14 @@ private void initializeViewMenu() { menuBarViewGrid.getGraphic().opacityProperty().bind(new When(isOn).then(1).otherwise(0)); }); - menuBarAutoscaling.getGraphic().setOpacity(Ecdar.autoScalingEnabled.getValue() ? 1 : 0); - menuBarAutoscaling.setOnAction(event -> { + menuBarViewAutoscaling.getGraphic().setOpacity(Ecdar.autoScalingEnabled.getValue() ? 1 : 0); + menuBarViewAutoscaling.setOnAction(event -> { Ecdar.autoScalingEnabled.setValue(!Ecdar.autoScalingEnabled.getValue()); updateScaling(getNewCalculatedScale() / 13); Ecdar.preferences.put("autoscaling", String.valueOf(Ecdar.autoScalingEnabled.getValue())); }); Ecdar.autoScalingEnabled.addListener((observable, oldValue, newValue) -> { - menuBarAutoscaling.getGraphic().opacityProperty().setValue(newValue ? 1 : 0); + menuBarViewAutoscaling.getGraphic().opacityProperty().setValue(newValue ? 1 : 0); }); scaling.selectedToggleProperty().addListener((observable, oldValue, newValue) -> updateScaling(Double.parseDouble(newValue.getProperties().get("scale").toString()))); @@ -680,14 +631,28 @@ private void initializeViewMenu() { menuBarViewCanvasSplit.setOnAction(event -> { final BooleanProperty isSplit = Ecdar.toggleCanvasSplit(); if (isSplit.get()) { - Platform.runLater(this::setCanvasModeToSingular); + Platform.runLater(() -> editorPresentation.getController().setCanvasModeToSingular()); menuBarViewCanvasSplit.setText("Split canvas"); } else { - Platform.runLater(this::setCanvasModeToSplit); + Platform.runLater(() -> editorPresentation.getController().setCanvasModeToSplit()); menuBarViewCanvasSplit.setText("Merge canvases"); } }); + menuBarViewEditor.setAccelerator(new KeyCodeCombination(KeyCode.DIGIT1, KeyCombination.SHORTCUT_DOWN)); + menuBarViewEditor.setOnAction(event -> editorRipplerClicked()); + + menuBarViewSimulator.setAccelerator(new KeyCodeCombination(KeyCode.DIGIT2, KeyCombination.SHORTCUT_DOWN)); + menuBarViewSimulator.setOnAction(event -> simulatorRipplerClicked()); + + currentMode.addListener((obs, oldMode, newMode) -> { + if (newMode == Mode.Editor && oldMode != newMode) { + enterEditorMode(); + } else if (newMode == Mode.Simulator && oldMode != newMode) { + enterSimulatorMode(); + } + }); + // On startup, set the scaling to the values saved in preferences Platform.runLater(() -> { Ecdar.autoScalingEnabled.setValue(Ecdar.preferences.getBoolean("autoscaling", true)); @@ -716,7 +681,7 @@ private void updateScaling(double newScale) { Ecdar.preferences.put("scale", String.valueOf(newScale)); scaleIcons(root, newCalculatedScale); - scaleEdgeStatusToggle(newCalculatedScale); + editorPresentation.getController().scaleEdgeStatusToggle(newCalculatedScale); messageTabPane.getController().updateScale(newScale); // Update listeners of UI scale @@ -982,153 +947,52 @@ private void initializeFileExportAsPng() { }); } - private void initializeCanvasPane() { - Platform.runLater(this::setCanvasModeToSingular); - } - /** - * Removes the canvases and adds a new one, with the active component of the active canvasPresentation. + * Method for click on the Editor rippler. Changes mode to the editor */ - private void setCanvasModeToSingular() { - canvasPane.getChildren().clear(); - CanvasPresentation canvasPresentation = new CanvasPresentation(); - HighLevelModelObject model = activeCanvasPresentation.get().getController().getActiveModel(); - if (model != null) { - canvasPresentation.getController().setActiveModel(activeCanvasPresentation.get().getController().getActiveModel()); - } else { - // If no components where found, the project has not been initialized. The active model will be updated when the project is initialized - canvasPresentation.getController().setActiveModel(Ecdar.getProject().getComponents().stream().findFirst().orElse(null)); + private void editorRipplerClicked() { + if (currentMode.get() != Mode.Editor) { + currentMode.setValue(Mode.Editor); } - - canvasPane.getChildren().add(canvasPresentation); - activeCanvasPresentation.set(canvasPresentation); - filePane.getController().updateColorsOnFilePresentations(); - - Rectangle clip = new Rectangle(); - clip.setArcWidth(1); - clip.setArcHeight(1); - clip.widthProperty().bind(canvasPane.widthProperty()); - clip.heightProperty().bind(canvasPane.heightProperty()); - canvasPresentation.getController().zoomablePane.setClip(clip); - - canvasPresentation.getController().zoomablePane.minWidthProperty().bind(canvasPane.widthProperty()); - canvasPresentation.getController().zoomablePane.maxWidthProperty().bind(canvasPane.widthProperty()); - canvasPresentation.getController().zoomablePane.minHeightProperty().bind(canvasPane.heightProperty()); - canvasPresentation.getController().zoomablePane.maxHeightProperty().bind(canvasPane.heightProperty()); } /** - * Removes the canvas and adds a GridPane with four new canvases, with different active components, - * the first being the one previously displayed on the single canvas. + * Method for click on the Simulator rippler. Changes mode to the simulator */ - private void setCanvasModeToSplit() { - canvasPane.getChildren().clear(); - - GridPane canvasGrid = new GridPane(); - - canvasGrid.addColumn(0); - canvasGrid.addColumn(1); - canvasGrid.addRow(0); - canvasGrid.addRow(1); - - ColumnConstraints col1 = new ColumnConstraints(); - col1.setPercentWidth(50); - - RowConstraints row1 = new RowConstraints(); - row1.setPercentHeight(50); - - canvasGrid.getColumnConstraints().add(col1); - canvasGrid.getColumnConstraints().add(col1); - canvasGrid.getRowConstraints().add(row1); - canvasGrid.getRowConstraints().add(row1); - - ObservableList components = Ecdar.getProject().getComponents(); - int currentCompNum = 0, numComponents = components.size(); - - // Add the canvasPresentation at the top-left - CanvasPresentation canvasPresentation = initializeNewCanvasPresentation(); - canvasPresentation.getController().setActiveModel(getActiveCanvasPresentation().getController().getActiveModel()); - canvasGrid.add(canvasPresentation, 0, 0); - setActiveCanvasPresentation(canvasPresentation); - - // Add the canvasPresentation at the top-right - canvasPresentation = initializeNewCanvasPresentationWithActiveComponent(components, currentCompNum); - canvasPresentation.setOpacity(0.75); - canvasGrid.add(canvasPresentation, 1, 0); - - // Update the startIndex for the next canvasPresentation - for (int i = 0; i < numComponents; i++) { - if (canvasPresentation.getController().getActiveModel() != null && canvasPresentation.getController().getActiveModel().equals(components.get(i))) { - currentCompNum = i + 1; - } + private void simulatorRipplerClicked() { + if (currentMode.get() != Mode.Simulator) { + currentMode.setValue(Mode.Simulator); } - - // Add the canvasPresentation at the bottom-left - canvasPresentation = initializeNewCanvasPresentationWithActiveComponent(components, currentCompNum); - canvasPresentation.setOpacity(0.75); - canvasGrid.add(canvasPresentation, 0, 1); - - // Update the startIndex for the next canvasPresentation - for (int i = 0; i < numComponents; i++) - if (canvasPresentation.getController().getActiveModel() != null && canvasPresentation.getController().getActiveModel().equals(components.get(i))) { - currentCompNum = i + 1; - } - - // Add the canvasPresentation at the bottom-right - canvasPresentation = initializeNewCanvasPresentationWithActiveComponent(components, currentCompNum); - canvasPresentation.setOpacity(0.75); - canvasGrid.add(canvasPresentation, 1, 1); - - canvasPane.getChildren().add(canvasGrid); - filePane.getController().updateColorsOnFilePresentations(); } /** - * Initialize a new CanvasShellPresentation and set its active component to the next component encountered from the startIndex and return it - * - * @param components the list of components for assigning active component of the CanvasPresentation - * @param startIndex the index to start at when trying to find the component to set as active - * @return new CanvasShellPresentation + * Changes the view and mode to the editor + * Only enter if the mode is not already Editor */ - private CanvasPresentation initializeNewCanvasPresentationWithActiveComponent(ObservableList components, int startIndex) { - CanvasPresentation canvasPresentation = initializeNewCanvasPresentation(); - - int numComponents = components.size(); - canvasPresentation.getController().setActiveModel(null); - for (int currentCompNum = startIndex; currentCompNum < numComponents; currentCompNum++) { - if (getActiveCanvasPresentation().getController().getActiveModel() != components.get(currentCompNum)) { - canvasPresentation.getController().setActiveModel(components.get(currentCompNum)); - break; - } - } + private void enterEditorMode() { +// ToDo NIELS: Consider implementing willShow and willHide to handle general elements that should only be available for one of the modes +// editorPresentation.getController().willShow(); +// simulatorPresentation.getController().willHide(); - return canvasPresentation; + borderPane.setCenter(editorPresentation); + + // Enable or disable the menu items that can be used when in the simulator +// updateMenuItems(); } /** - * Initialize a new CanvasPresentation and return it - * - * @return new CanvasPresentation + * Changes the view and mode to the simulator + * Only enter if the mode is not already Simulator */ - private CanvasPresentation initializeNewCanvasPresentation() { - CanvasPresentation canvasPresentation = new CanvasPresentation(); - canvasPresentation.setBorder(new Border(new BorderStroke(Color.GREY.getColor(Color.Intensity.I500), BorderStrokeStyle.SOLID, CornerRadii.EMPTY, BorderStroke.THIN))); - - // Set th clip of the zoomable pane to be half of the canvasPane, - // to ensure a 2 by 2 grid without overflowing borders - Rectangle clip = new Rectangle(); - clip.setArcWidth(1); - clip.setArcHeight(1); - clip.widthProperty().bind(canvasPane.widthProperty().divide(2)); - clip.heightProperty().bind(canvasPane.heightProperty().divide(2)); - canvasPresentation.getController().zoomablePane.setClip(clip); - - canvasPresentation.getController().zoomablePane.minWidthProperty().bind(canvasPane.widthProperty().divide(2)); - canvasPresentation.getController().zoomablePane.maxWidthProperty().bind(canvasPane.widthProperty().divide(2)); - canvasPresentation.getController().zoomablePane.minHeightProperty().bind(canvasPane.heightProperty().divide(2)); - canvasPresentation.getController().zoomablePane.maxHeightProperty().bind(canvasPane.heightProperty().divide(2)); - - return canvasPresentation; + private void enterSimulatorMode() { +// ToDo NIELS: Consider implementing willShow and willHide to handle general elements that should only be available for one of the modes +// ecdarPresentation.getController().willHide(); +// simulatorPresentation.getController().willShow(); + + borderPane.setCenter(simulatorPresentation); + + // Enable or disable the menu items that can be used when in the simulator +// updateMenuItems(); } /** @@ -1339,85 +1203,6 @@ private void nudgeSelected(final NudgeDirection direction) { "open-with"); } - @FXML - private void deleteSelectedClicked() { - if (SelectHelper.getSelectedElements().size() == 0) return; - - // Run through the selected elements and look for something that we can delete - SelectHelper.getSelectedElements().forEach(selectable -> { - if (selectable instanceof LocationController) { - ((LocationController) selectable).tryDelete(); - } else if (selectable instanceof EdgeController) { - final Component component = ((EdgeController) selectable).getComponent(); - final DisplayableEdge edge = ((EdgeController) selectable).getEdge(); - - // Dont delete edge if it is locked - if (edge.getIsLockedProperty().getValue()) { - return; - } - - UndoRedoStack.pushAndPerform(() -> { // Perform - // Remove the edge - component.removeEdge(edge); - }, () -> { // Undo - // Re-all the edge - component.addEdge(edge); - }, String.format("Deleted %s", selectable.toString()), "delete"); - } else if (selectable instanceof NailController) { - ((NailController) selectable).tryDelete(); - } - }); - - SelectHelper.clearSelectedElements(); - } - - @FXML - private void undoClicked() { - UndoRedoStack.undo(); - } - - @FXML - private void redoClicked() { - UndoRedoStack.redo(); - } - - /** - * Switch to input edge mode - */ - @FXML - private void switchToInputClicked() { - setGlobalEdgeStatus(EdgeStatus.INPUT); - } - - /** - * Switch to output edge mode - */ - @FXML - private void switchToOutputClicked() { - setGlobalEdgeStatus(EdgeStatus.OUTPUT); - } - - /** - * Switch edge status. - */ - @FXML - private void switchEdgeStatusClicked() { - if (getGlobalEdgeStatus().equals(EdgeStatus.INPUT)) { - setGlobalEdgeStatus(EdgeStatus.OUTPUT); - } else { - setGlobalEdgeStatus(EdgeStatus.INPUT); - } - } - - /** - * Sets the global edge status. - * - * @param status the status - */ - private void setGlobalEdgeStatus(EdgeStatus status) { - globalEdgeStatus.set(status); - } - @FXML private void closeQueryDialog() { dialog.close(); diff --git a/src/main/java/ecdar/controllers/EditorController.java b/src/main/java/ecdar/controllers/EditorController.java new file mode 100644 index 00000000..605cc6fd --- /dev/null +++ b/src/main/java/ecdar/controllers/EditorController.java @@ -0,0 +1,380 @@ +package ecdar.controllers; + +import com.jfoenix.controls.JFXButton; +import com.jfoenix.controls.JFXRippler; +import com.jfoenix.controls.JFXToggleButton; +import ecdar.Ecdar; +import ecdar.abstractions.Component; +import ecdar.abstractions.DisplayableEdge; +import ecdar.abstractions.EdgeStatus; +import ecdar.abstractions.HighLevelModelObject; +import ecdar.presentations.CanvasPresentation; +import ecdar.utility.UndoRedoStack; +import ecdar.utility.colors.Color; +import ecdar.utility.colors.EnabledColor; +import ecdar.utility.helpers.SelectHelper; +import ecdar.utility.keyboard.Keybind; +import ecdar.utility.keyboard.KeyboardTracker; +import javafx.application.Platform; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.fxml.Initializable; +import javafx.scene.control.Tooltip; +import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyCodeCombination; +import javafx.scene.layout.*; +import javafx.scene.shape.Rectangle; +import javafx.util.Pair; + +import java.net.URL; +import java.util.ArrayList; +import java.util.List; +import java.util.ResourceBundle; + +public class EditorController implements Initializable { + public VBox root; + public HBox toolbar; + public JFXRippler colorSelected; + public JFXRippler deleteSelected; + public JFXRippler undo; + public JFXRippler redo; + public JFXButton switchToInputButton; + public JFXButton switchToOutputButton; + public JFXToggleButton switchEdgeStatusButton; + public StackPane canvasPane; + + private final ObjectProperty globalEdgeStatus = new SimpleObjectProperty<>(EdgeStatus.INPUT); + private final ObjectProperty activeCanvasPresentation = new SimpleObjectProperty<>(new CanvasPresentation()); + + @Override + public void initialize(final URL location, final ResourceBundle resources) { + initializeCanvasPane(); + initializeEdgeStatusHandling(); + initializeKeybindings(); + } + + public EdgeStatus getGlobalEdgeStatus() { + return globalEdgeStatus.get(); + } + + ObjectProperty activeCanvasPresentationProperty() { + return activeCanvasPresentation; + } + + public CanvasPresentation getActiveCanvasPresentation() { + return activeCanvasPresentation.get(); + } + + public void setActiveCanvasPresentation(CanvasPresentation newActiveCanvasPresentation) { + activeCanvasPresentation.get().setOpacity(0.75); + newActiveCanvasPresentation.setOpacity(1); + activeCanvasPresentation.set(newActiveCanvasPresentation); + } + + public void setActiveModelForActiveCanvas(HighLevelModelObject newActiveModel) { + EcdarController.getActiveCanvasPresentation().getController().setActiveModel(newActiveModel); + + // Change zoom level to fit new active model + Platform.runLater(() -> EcdarController.getActiveCanvasPresentation().getController().zoomHelper.zoomToFit()); + } + + private void initializeCanvasPane() { + Platform.runLater(this::setCanvasModeToSingular); + } + + private void initializeKeybindings() { + // Keybinds for coloring the selected elements + EnabledColor.enabledColors.forEach(enabledColor -> { + KeyboardTracker.registerKeybind(KeyboardTracker.COLOR_SELECTED + "_" + enabledColor.keyCode.getName(), new Keybind(new KeyCodeCombination(enabledColor.keyCode), () -> { + + final List> previousColor = new ArrayList<>(); + + SelectHelper.getSelectedElements().forEach(selectable -> { + previousColor.add(new Pair<>(selectable, new EnabledColor(selectable.getColor(), selectable.getColorIntensity()))); + }); + changeColorOnSelectedElements(enabledColor, previousColor); + SelectHelper.clearSelectedElements(); + })); + }); + + // Keybind for deleting the selected elements + KeyboardTracker.registerKeybind(KeyboardTracker.DELETE_SELECTED, new Keybind(new KeyCodeCombination(KeyCode.DELETE), this::deleteSelectedClicked)); + } + + /** + * Initializes edge status. + * Input is the default status. + * This method sets buttons for edge status whenever the status changes. + */ + private void initializeEdgeStatusHandling() { + globalEdgeStatus.set(EdgeStatus.INPUT); + + Tooltip.install(switchToInputButton, new Tooltip("Switch to input mode")); + Tooltip.install(switchToOutputButton, new Tooltip("Switch to output mode")); + switchToInputButton.setDisableVisualFocus(true); // Hiding input button rippler on start-up + + globalEdgeStatus.addListener(((observable, oldValue, newValue) -> { + if (newValue.equals(EdgeStatus.INPUT)) { + switchToInputButton.setTextFill(javafx.scene.paint.Color.WHITE); + switchToOutputButton.setTextFill(javafx.scene.paint.Color.GREY); + switchEdgeStatusButton.setSelected(false); + } else { + switchToInputButton.setTextFill(javafx.scene.paint.Color.GREY); + switchToOutputButton.setTextFill(javafx.scene.paint.Color.WHITE); + switchEdgeStatusButton.setSelected(true); + } + })); + + // Ensure that the rippler is centered when scale is changed + Platform.runLater(() -> { + var rippler = ((JFXRippler) switchEdgeStatusButton.lookup(".jfx-rippler")); + if (rippler == null) return; + rippler.setRipplerRecenter(true); + }); + } + + /** + * Sets the global edge status. + * + * @param status the status + */ + private void setGlobalEdgeStatus(EdgeStatus status) { + globalEdgeStatus.set(status); + } + + void scaleEdgeStatusToggle(double size) { + switchEdgeStatusButton.setScaleX(size / 13.0); + switchEdgeStatusButton.setScaleY(size / 13.0); + } + + /** + * Handles the change of color on selected objects + * + * @param enabledColor The new color for the selected objects + * @param previousColor The color old color of the selected objects + */ + public void changeColorOnSelectedElements(final EnabledColor enabledColor, + final List> previousColor) { + UndoRedoStack.pushAndPerform(() -> { // Perform + SelectHelper.getSelectedElements() + .forEach(selectable -> selectable.color(enabledColor.color, enabledColor.intensity)); + }, () -> { // Undo + previousColor.forEach(selectableEnabledColorPair -> selectableEnabledColorPair.getKey().color(selectableEnabledColorPair.getValue().color, selectableEnabledColorPair.getValue().intensity)); + }, String.format("Changed the color of %d elements to %s", previousColor.size(), enabledColor.color.name()), "color-lens"); + } + + /** + * Removes the canvases and adds a new one, with the active component of the active canvasPresentation. + */ + void setCanvasModeToSingular() { + canvasPane.getChildren().clear(); + CanvasPresentation canvasPresentation = new CanvasPresentation(); + HighLevelModelObject model = activeCanvasPresentation.get().getController().getActiveModel(); + if (model != null) { + canvasPresentation.getController().setActiveModel(activeCanvasPresentation.get().getController().getActiveModel()); + } else { + // If no components where found, the project has not been initialized. The active model will be updated when the project is initialized + canvasPresentation.getController().setActiveModel(Ecdar.getProject().getComponents().stream().findFirst().orElse(null)); + } + + canvasPane.getChildren().add(canvasPresentation); + activeCanvasPresentation.set(canvasPresentation); + + Rectangle clip = new Rectangle(); + clip.setArcWidth(1); + clip.setArcHeight(1); + clip.widthProperty().bind(canvasPane.widthProperty()); + clip.heightProperty().bind(canvasPane.heightProperty()); + + canvasPresentation.getController().zoomablePane.setClip(clip); + canvasPresentation.getController().zoomablePane.minWidthProperty().bind(canvasPane.widthProperty()); + canvasPresentation.getController().zoomablePane.maxWidthProperty().bind(canvasPane.widthProperty()); + canvasPresentation.getController().zoomablePane.minHeightProperty().bind(canvasPane.heightProperty()); + canvasPresentation.getController().zoomablePane.maxHeightProperty().bind(canvasPane.heightProperty()); + } + + /** + * Removes the canvas and adds a GridPane with four new canvases, with different active components, + * the first being the one previously displayed on the single canvas. + */ + void setCanvasModeToSplit() { + canvasPane.getChildren().clear(); + + GridPane canvasGrid = new GridPane(); + + canvasGrid.addColumn(0); + canvasGrid.addColumn(1); + canvasGrid.addRow(0); + canvasGrid.addRow(1); + + ColumnConstraints col1 = new ColumnConstraints(); + col1.setPercentWidth(50); + + RowConstraints row1 = new RowConstraints(); + row1.setPercentHeight(50); + + canvasGrid.getColumnConstraints().add(col1); + canvasGrid.getColumnConstraints().add(col1); + canvasGrid.getRowConstraints().add(row1); + canvasGrid.getRowConstraints().add(row1); + + ObservableList components = Ecdar.getProject().getComponents(); + int currentCompNum = 0, numComponents = components.size(); + + // Add the canvasPresentation at the top-left + CanvasPresentation canvasPresentation = initializeNewCanvasPresentation(); + canvasPresentation.getController().setActiveModel(getActiveCanvasPresentation().getController().getActiveModel()); + canvasGrid.add(canvasPresentation, 0, 0); + setActiveCanvasPresentation(canvasPresentation); + + // Add the canvasPresentation at the top-right + canvasPresentation = initializeNewCanvasPresentationWithActiveComponent(components, currentCompNum); + canvasPresentation.setOpacity(0.75); + canvasGrid.add(canvasPresentation, 1, 0); + + // Update the startIndex for the next canvasPresentation + for (int i = 0; i < numComponents; i++) { + if (canvasPresentation.getController().getActiveModel() != null && canvasPresentation.getController().getActiveModel().equals(components.get(i))) { + currentCompNum = i + 1; + } + } + + // Add the canvasPresentation at the bottom-left + canvasPresentation = initializeNewCanvasPresentationWithActiveComponent(components, currentCompNum); + canvasPresentation.setOpacity(0.75); + canvasGrid.add(canvasPresentation, 0, 1); + + // Update the startIndex for the next canvasPresentation + for (int i = 0; i < numComponents; i++) + if (canvasPresentation.getController().getActiveModel() != null && canvasPresentation.getController().getActiveModel().equals(components.get(i))) { + currentCompNum = i + 1; + } + + // Add the canvasPresentation at the bottom-right + canvasPresentation = initializeNewCanvasPresentationWithActiveComponent(components, currentCompNum); + canvasPresentation.setOpacity(0.75); + canvasGrid.add(canvasPresentation, 1, 1); + + canvasPane.getChildren().add(canvasGrid); + } + + /** + * Initialize a new CanvasShellPresentation and set its active component to the next component encountered from the startIndex and return it + * + * @param components the list of components for assigning active component of the CanvasPresentation + * @param startIndex the index to start at when trying to find the component to set as active + * @return new CanvasShellPresentation + */ + private CanvasPresentation initializeNewCanvasPresentationWithActiveComponent(ObservableList components, int startIndex) { + CanvasPresentation canvasPresentation = initializeNewCanvasPresentation(); + + int numComponents = components.size(); + canvasPresentation.getController().setActiveModel(null); + for (int currentCompNum = startIndex; currentCompNum < numComponents; currentCompNum++) { + if (getActiveCanvasPresentation().getController().getActiveModel() != components.get(currentCompNum)) { + canvasPresentation.getController().setActiveModel(components.get(currentCompNum)); + break; + } + } + + return canvasPresentation; + } + + /** + * Initialize a new CanvasPresentation and return it + * + * @return new CanvasPresentation + */ + private CanvasPresentation initializeNewCanvasPresentation() { + CanvasPresentation canvasPresentation = new CanvasPresentation(); + canvasPresentation.setBorder(new Border(new BorderStroke(Color.GREY.getColor(Color.Intensity.I500), BorderStrokeStyle.SOLID, CornerRadii.EMPTY, BorderStroke.THIN))); + + // Set th clip of the zoomable pane to be half of the canvasPane, + // to ensure a 2 by 2 grid without overflowing borders + Rectangle clip = new Rectangle(); + clip.setArcWidth(1); + clip.setArcHeight(1); + clip.widthProperty().bind(canvasPane.widthProperty().divide(2)); + clip.heightProperty().bind(canvasPane.heightProperty().divide(2)); + canvasPresentation.getController().zoomablePane.setClip(clip); + + canvasPresentation.getController().zoomablePane.minWidthProperty().bind(canvasPane.widthProperty().divide(2)); + canvasPresentation.getController().zoomablePane.maxWidthProperty().bind(canvasPane.widthProperty().divide(2)); + canvasPresentation.getController().zoomablePane.minHeightProperty().bind(canvasPane.heightProperty().divide(2)); + canvasPresentation.getController().zoomablePane.maxHeightProperty().bind(canvasPane.heightProperty().divide(2)); + + return canvasPresentation; + } + + @FXML + private void undoClicked() { + UndoRedoStack.undo(); + } + + @FXML + private void redoClicked() { + UndoRedoStack.redo(); + } + + /** + * Switch to input edge mode + */ + @FXML + private void switchToInputClicked() { + setGlobalEdgeStatus(EdgeStatus.INPUT); + } + + /** + * Switch to output edge mode + */ + @FXML + private void switchToOutputClicked() { + setGlobalEdgeStatus(EdgeStatus.OUTPUT); + } + + /** + * Switch edge status. + */ + @FXML + private void switchEdgeStatusClicked() { + if (getGlobalEdgeStatus().equals(EdgeStatus.INPUT)) { + setGlobalEdgeStatus(EdgeStatus.OUTPUT); + } else { + setGlobalEdgeStatus(EdgeStatus.INPUT); + } + } + + @FXML + private void deleteSelectedClicked() { + if (SelectHelper.getSelectedElements().size() == 0) return; + + // Run through the selected elements and look for something that we can delete + SelectHelper.getSelectedElements().forEach(selectable -> { + if (selectable instanceof LocationController) { + ((LocationController) selectable).tryDelete(); + } else if (selectable instanceof EdgeController) { + final Component component = ((EdgeController) selectable).getComponent(); + final DisplayableEdge edge = ((EdgeController) selectable).getEdge(); + + // Dont delete edge if it is locked + if (edge.getIsLockedProperty().getValue()) { + return; + } + + UndoRedoStack.pushAndPerform(() -> { // Perform + // Remove the edge + component.removeEdge(edge); + }, () -> { // Undo + // Re-all the edge + component.addEdge(edge); + }, String.format("Deleted %s", selectable.toString()), "delete"); + } else if (selectable instanceof NailController) { + ((NailController) selectable).tryDelete(); + } + }); + + SelectHelper.clearSelectedElements(); + } +} diff --git a/src/main/java/ecdar/controllers/ProcessController.java b/src/main/java/ecdar/controllers/ProcessController.java index 84f9ff22..2934b54d 100755 --- a/src/main/java/ecdar/controllers/ProcessController.java +++ b/src/main/java/ecdar/controllers/ProcessController.java @@ -65,29 +65,19 @@ private void initializeValues() { * Highlights the edges and accompanying source/target locations in the process * @param edges The edges to highlight */ - public void highlightEdges(final SimulationEdge[] edges) { + public void highlightEdges(final Edge[] edges) { for (int i = 0; i < edges.length; i++) { - final SimulationEdge edge = edges[i]; + final Edge edge = edges[i]; // Note that SimulationEdge contains a Location type from the UPPAAL libraries // but we use a different Location class in our Maps - final Location source = edge.getSource(); - String sourceName = ""; - final Location target = edge.getTarget(); - String targetName = ""; - - // Match the Locations of the SimulationEdge with a SimulationLocation of the process, - // so we can get the source/target names (names are not available on a Location) - for (SimulationLocation sysloc : edge.getProcess().getLocations()) { - if (sysloc.getLocation() == source) { - sourceName = sysloc.getName(); - } else if (sysloc == target) { - targetName = sysloc.getName(); - } - } + final Location source = edge.getSourceLocation(); + String sourceName = source.getNickname(); + final Location target = edge.getTargetLocation(); + String targetName = target.getNickname(); // If target name is empty the edge is a self loop - if (targetName == "") { + if (Objects.equals(targetName, "")) { targetName = sourceName; } @@ -120,21 +110,19 @@ public void highlightEdges(final SimulationEdge[] edges) { } // The edge name may contain ! or ?, and we need to replace those so we can compare our stored edge - String edgeName = edge.getName(); - EdgeStatus edgeStatus = EdgeStatus.INPUT; - if (edgeName.contains("?")) { - edgeName = edgeName.replace("?", ""); - } else if (edgeName.contains("!")) { - edgeName = edgeName.replace("!", ""); - edgeStatus = EdgeStatus.OUTPUT; - } + String edgeName = edge.getId(); +// if (edgeName.contains("?")) { +// edgeName = edgeName.replace("?", ""); +// } else if (edgeName.contains("!")) { +// edgeName = edgeName.replace("!", ""); +// } // Self loop on a Universal locations means that the edge name should be mapped to * if (isSourceUniversal && sourceName.equals(targetName)) { edgeName = "*"; } - highlightEdge(edgeName, edgeStatus, sourceName, targetName); + highlightEdge(edgeName, edge.getStatus(), sourceName, targetName); } } diff --git a/src/main/java/ecdar/controllers/RightSimPaneController.java b/src/main/java/ecdar/controllers/RightSimPaneController.java index c7d5b6db..5b86fc1b 100755 --- a/src/main/java/ecdar/controllers/RightSimPaneController.java +++ b/src/main/java/ecdar/controllers/RightSimPaneController.java @@ -1,6 +1,5 @@ package ecdar.controllers; -import ecdar.presentations.QueryPaneElementPresentation; import javafx.fxml.Initializable; import javafx.scene.layout.*; @@ -13,7 +12,7 @@ public class RightSimPaneController implements Initializable { public StackPane root; public VBox scrollPaneVbox; - public QueryPaneElementPresentation queryPaneElement; +// public QueryPaneElementPresentation queryPaneElement; @Override public void initialize(URL location, ResourceBundle resources) { diff --git a/src/main/java/ecdar/controllers/SimNailController.java b/src/main/java/ecdar/controllers/SimNailController.java new file mode 100755 index 00000000..053456d3 --- /dev/null +++ b/src/main/java/ecdar/controllers/SimNailController.java @@ -0,0 +1,126 @@ +package ecdar.controllers; + +import ecdar.Debug; +import ecdar.abstractions.Component; +import ecdar.abstractions.Edge; +import ecdar.abstractions.Nail; +import ecdar.presentations.SimNailPresentation; +import ecdar.presentations.SimTagPresentation; +import ecdar.utility.colors.Color; +import javafx.beans.property.DoubleProperty; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.fxml.Initializable; +import javafx.scene.Group; +import javafx.scene.control.Label; +import javafx.scene.shape.Circle; +import javafx.scene.shape.Line; + +import java.net.URL; +import java.util.ResourceBundle; + +public class SimNailController implements Initializable { + private final ObjectProperty component = new SimpleObjectProperty<>(); + private final ObjectProperty edge = new SimpleObjectProperty<>(); + private final ObjectProperty nail = new SimpleObjectProperty<>(); + + private SimEdgeController edgeController; + public SimNailPresentation root; + public Circle nailCircle; + public Circle dragCircle; + public Line propertyTagLine; + public SimTagPresentation propertyTag; + public Group dragGroup; + public Label propertyLabel; + + @Override + public void initialize(final URL location, final ResourceBundle resources) { + nail.addListener((obsNail, oldNail, newNail) -> { + + // The radius from the abstraction is the master and the view simply reflects what is in the model + nailCircle.radiusProperty().bind(newNail.radiusProperty()); + + // Draw the presentation based on the initial value from the abstraction + root.setLayoutX(newNail.getX()); + root.setLayoutY(newNail.getY()); + + // Reflect future updates from the presentation into the abstraction + newNail.xProperty().bindBidirectional(root.layoutXProperty()); + newNail.yProperty().bindBidirectional(root.layoutYProperty()); + + }); + + // Debug visuals + dragCircle.opacityProperty().bind(Debug.draggableAreaOpacity); + dragCircle.setFill(Debug.draggableAreaColor.getColor(Debug.draggableAreaColorIntensity)); + } + + /** + * Sets an edge controller. + * This should be called when adding a nail. + * @param controller the edge controller + */ + public void setEdgeController(final SimEdgeController controller) { + this.edgeController = controller; + } + + public Nail getNail() { + return nail.get(); + } + + public void setNail(final Nail nail) { + this.nail.set(nail); + } + + public ObjectProperty nailProperty() { + return nail; + } + + public Component getComponent() { + return component.get(); + } + + public void setComponent(final Component component) { + this.component.set(component); + } + + public ObjectProperty componentProperty() { + return component; + } + + public Edge getEdge() { + return edge.get(); + } + + public void setEdge(final Edge edge) { + this.edge.set(edge); + } + + public ObjectProperty edgeProperty() { + return edge; + } + + public Color getColor() { + return getComponent().getColor(); + } + + public Color.Intensity getColorIntensity() { + return getComponent().getColorIntensity(); + } + + public DoubleProperty xProperty() { + return root.layoutXProperty(); + } + + public DoubleProperty yProperty() { + return root.layoutYProperty(); + } + + public double getX() { + return xProperty().get(); + } + + public double getY() { + return yProperty().get(); + } +} diff --git a/src/main/java/ecdar/controllers/SimulatorController.java b/src/main/java/ecdar/controllers/SimulatorController.java old mode 100755 new mode 100644 diff --git a/src/main/java/ecdar/controllers/SimulatorOverviewController.java b/src/main/java/ecdar/controllers/SimulatorOverviewController.java old mode 100755 new mode 100644 index 160e9e51..3a6dbc13 --- a/src/main/java/ecdar/controllers/SimulatorOverviewController.java +++ b/src/main/java/ecdar/controllers/SimulatorOverviewController.java @@ -326,13 +326,11 @@ public void highlightProcessTransition(final Transition transition) { // Processes are removed from this list, if they have an edge in the transition final ArrayList processesToHide = new ArrayList<>(processPresentations.values()); - for (final Edge edge : edges) { - final Process process = edge.getProcess(); + for (final ProcessPresentation processPresentation : processPresentations.values()) { // Find the processes that have edges involved in this transition - final ProcessPresentation presentation = processPresentations.get(process.getName()); - presentation.getController().highlightEdges(edges); - processesToHide.remove(presentation); + processPresentation.getController().highlightEdges(edges.toArray(new Edge[0])); + processesToHide.remove(processPresentation); } processesToHide.forEach(ProcessPresentation::showInactive); diff --git a/src/main/java/ecdar/presentations/EcdarPresentation.java b/src/main/java/ecdar/presentations/EcdarPresentation.java index f8fb5f25..4884f95f 100644 --- a/src/main/java/ecdar/presentations/EcdarPresentation.java +++ b/src/main/java/ecdar/presentations/EcdarPresentation.java @@ -7,10 +7,6 @@ import ecdar.controllers.EcdarController; import ecdar.utility.UndoRedoStack; import ecdar.utility.colors.Color; -import ecdar.utility.colors.EnabledColor; -import ecdar.utility.helpers.SelectHelper; -import com.jfoenix.controls.JFXPopup; -import com.jfoenix.controls.JFXRippler; import com.jfoenix.controls.JFXSnackbar; import ecdar.utility.keyboard.Keybind; import ecdar.utility.keyboard.KeyboardTracker; @@ -22,30 +18,21 @@ import javafx.beans.property.SimpleDoubleProperty; import javafx.collections.ListChangeListener; import javafx.geometry.Insets; -import javafx.scene.control.Label; -import javafx.scene.control.Tooltip; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.input.KeyCode; import javafx.scene.input.KeyCodeCombination; import javafx.scene.input.KeyCombination; import javafx.scene.layout.*; -import javafx.scene.shape.Circle; import javafx.util.Duration; -import javafx.util.Pair; - -import java.util.ArrayList; -import java.util.List; - -import static ecdar.utility.colors.EnabledColor.enabledColors; public class EcdarPresentation extends StackPane { private final EcdarController controller; private final BooleanProperty filePaneOpen = new SimpleBooleanProperty(false); - private final SimpleDoubleProperty filePaneAnimationProperty = new SimpleDoubleProperty(0); + private final SimpleDoubleProperty leftPaneAnimationProperty = new SimpleDoubleProperty(0); private final BooleanProperty queryPaneOpen = new SimpleBooleanProperty(false); - private final SimpleDoubleProperty queryPaneAnimationProperty = new SimpleDoubleProperty(0); + private final SimpleDoubleProperty rightPaneAnimationProperty = new SimpleDoubleProperty(0); private Timeline openQueryPaneAnimation; private Timeline closeQueryPaneAnimation; private Timeline openFilePaneAnimation; @@ -54,48 +41,33 @@ public class EcdarPresentation extends StackPane { public EcdarPresentation() { controller = new EcdarFXMLLoader().loadAndGetController("EcdarPresentation.fxml", this); initializeTopBar(); - initializeToolbar(); initializeQueryDetailsDialog(); - initializeColorSelector(); - initializeToggleQueryPaneFunctionality(); initializeToggleFilePaneFunctionality(); - - initializeSelectDependentToolbarButton(controller.colorSelected); - Tooltip.install(controller.colorSelected, new Tooltip("Colour")); - - initializeSelectDependentToolbarButton(controller.deleteSelected); - Tooltip.install(controller.deleteSelected, new Tooltip("Delete")); - - initializeToolbarButton(controller.undo); - initializeToolbarButton(controller.redo); - initializeUndoRedoButtons(); initializeSnackbar(); // Open the file and query panel initially Platform.runLater(() -> { // Bind sizing of sides and center panes to ensure correct sizing - controller.canvasPane.minWidthProperty().bind(controller.root.widthProperty().subtract(filePaneAnimationProperty.add(queryPaneAnimationProperty))); - controller.canvasPane.maxWidthProperty().bind(controller.root.widthProperty().subtract(filePaneAnimationProperty.add(queryPaneAnimationProperty))); + controller.getEditorPresentation().getController().canvasPane.minWidthProperty().bind(controller.root.widthProperty().subtract(leftPaneAnimationProperty.add(rightPaneAnimationProperty))); + controller.getEditorPresentation().getController().canvasPane.maxWidthProperty().bind(controller.root.widthProperty().subtract(leftPaneAnimationProperty.add(rightPaneAnimationProperty))); // Bind the height to ensure that both the top and bottom panes are shown // The height of the top pane is multiplied by 4 as the UI does not account for the height otherwise - controller.canvasPane.minHeightProperty().bind(controller.root.heightProperty().subtract(controller.topPane.heightProperty().multiply(4).add(controller.bottomFillerElement.heightProperty()))); - controller.canvasPane.maxHeightProperty().bind(controller.root.heightProperty().subtract(controller.topPane.heightProperty().multiply(4).add(controller.bottomFillerElement.heightProperty()))); + controller.getEditorPresentation().getController().canvasPane.minHeightProperty().bind(controller.root.heightProperty().subtract(controller.topPane.heightProperty().multiply(4).add(controller.bottomFillerElement.heightProperty()))); + controller.getEditorPresentation().getController().canvasPane.maxHeightProperty().bind(controller.root.heightProperty().subtract(controller.topPane.heightProperty().multiply(4).add(controller.bottomFillerElement.heightProperty()))); - controller.leftPane.minWidthProperty().bind(filePaneAnimationProperty); - controller.leftPane.maxWidthProperty().bind(filePaneAnimationProperty); + controller.leftPane.minWidthProperty().bind(leftPaneAnimationProperty); + controller.leftPane.maxWidthProperty().bind(leftPaneAnimationProperty); - controller.rightPane.minWidthProperty().bind(queryPaneAnimationProperty); - controller.rightPane.maxWidthProperty().bind(queryPaneAnimationProperty); + controller.rightPane.minWidthProperty().bind(rightPaneAnimationProperty); + controller.rightPane.maxWidthProperty().bind(rightPaneAnimationProperty); controller.topPane.minHeightProperty().bind(controller.menuBar.heightProperty()); controller.topPane.maxHeightProperty().bind(controller.menuBar.heightProperty()); - Platform.runLater(() -> { - toggleFilePane(); - toggleQueryPane(); - }); + toggleFilePane(); + toggleQueryPane(); Ecdar.getPresentation().controller.scalingProperty.addListener((observable, oldValue, newValue) -> { // If the scaling has changed trigger animations for open panes to update width @@ -130,147 +102,6 @@ private void initializeSnackbar() { controller.snackbar.autosize(); } - private void initializeUndoRedoButtons() { - UndoRedoStack.canUndoProperty().addListener((obs, oldState, newState) -> { - if (newState) { - // Enable the undo button - controller.undo.setEnabled(true); - controller.undo.setOpacity(1); - } else { - // Disable the undo button - controller.undo.setEnabled(false); - controller.undo.setOpacity(0.3); - } - }); - - UndoRedoStack.canRedoProperty().addListener((obs, oldState, newState) -> { - if (newState) { - // Enable the redo button - controller.redo.setEnabled(true); - controller.redo.setOpacity(1); - } else { - // Disable the redo button - controller.redo.setEnabled(false); - controller.redo.setOpacity(0.3); - } - }); - - // Disable the undo button - controller.undo.setEnabled(false); - controller.undo.setOpacity(0.3); - - // Disable the redo button - controller.redo.setEnabled(false); - controller.redo.setOpacity(0.3); - - // Set tooltips - Tooltip.install(controller.undo, new Tooltip("Undo")); - Tooltip.install(controller.redo, new Tooltip("Redo")); - } - - private void initializeColorSelector() { - final JFXPopup popup = new JFXPopup(); - - final double listWidth = 136; - final FlowPane list = new FlowPane(); - for (final EnabledColor color : enabledColors) { - final Circle circle = new Circle(16, color.color.getColor(color.intensity)); - circle.setStroke(color.color.getColor(color.intensity.next(2))); - circle.setStrokeWidth(1); - - final Label label = new Label(color.keyCode.getName()); - label.getStyleClass().add("subhead"); - label.setTextFill(color.color.getTextColor(color.intensity)); - - final StackPane child = new StackPane(circle, label); - child.setMinSize(40, 40); - child.setMaxSize(40, 40); - - child.setOnMouseEntered(event -> { - final ScaleTransition scaleTransition = new ScaleTransition(Duration.millis(100), circle); - scaleTransition.setFromX(circle.getScaleX()); - scaleTransition.setFromY(circle.getScaleY()); - scaleTransition.setToX(1.1); - scaleTransition.setToY(1.1); - scaleTransition.play(); - }); - - child.setOnMouseExited(event -> { - final ScaleTransition scaleTransition = new ScaleTransition(Duration.millis(100), circle); - scaleTransition.setFromX(circle.getScaleX()); - scaleTransition.setFromY(circle.getScaleY()); - scaleTransition.setToX(1.0); - scaleTransition.setToY(1.0); - scaleTransition.play(); - }); - - child.setOnMouseClicked(event -> { - final List> previousColor = new ArrayList<>(); - - SelectHelper.getSelectedElements().forEach(selectable -> { - previousColor.add(new Pair<>(selectable, new EnabledColor(selectable.getColor(), selectable.getColorIntensity()))); - }); - - controller.changeColorOnSelectedElements(color, previousColor); - - popup.hide(); - SelectHelper.clearSelectedElements(); - }); - - list.getChildren().add(child); - } - list.setMinWidth(listWidth); - list.setMaxWidth(listWidth); - list.setStyle("-fx-background-color: white; -fx-padding: 8;"); - - popup.setPopupContent(list); - - controller.colorSelected.setOnMouseClicked((e) -> { - // If nothing is selected - if (SelectHelper.getSelectedElements().size() == 0) return; - popup.show(controller.colorSelected, JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.RIGHT, -10, 15); - }); - } - - private void initializeSelectDependentToolbarButton(final JFXRippler button) { - initializeToolbarButton(button); - - // The color button should only be enabled when an element is selected - SelectHelper.getSelectedElements().addListener(new ListChangeListener() { - @Override - public void onChanged(final Change c) { - if (SelectHelper.getSelectedElements().size() > 0) { - button.setEnabled(true); - - final FadeTransition fadeAnimation = new FadeTransition(Duration.millis(100), button); - fadeAnimation.setFromValue(button.getOpacity()); - fadeAnimation.setToValue(1); - fadeAnimation.play(); - } else { - button.setEnabled(false); - - final FadeTransition fadeAnimation = new FadeTransition(Duration.millis(100), button); - fadeAnimation.setFromValue(1); - fadeAnimation.setToValue(0.3); - fadeAnimation.play(); - } - } - }); - - // Disable the button - button.setEnabled(false); - button.setOpacity(0.3); - } - - private void initializeToolbarButton(final JFXRippler button) { - final Color color = Color.GREY_BLUE; - final Color.Intensity colorIntensity = Color.Intensity.I800; - - button.setMaskType(JFXRippler.RipplerMask.CIRCLE); - button.setRipplerFill(color.getTextColor(colorIntensity)); - button.setPosition(JFXRippler.RipplerPos.BACK); - } - private void initializeQueryDetailsDialog() { final Color modalBarColor = Color.GREY_BLUE; final Color.Intensity modalBarColorIntensity = Color.Intensity.I500; @@ -288,7 +119,7 @@ private void initializeToggleFilePaneFunctionality() { initializeCloseFilePaneAnimation(); // Translate the x coordinate to create the open/close animations - controller.filePane.translateXProperty().bind(filePaneAnimationProperty.subtract(controller.filePane.widthProperty())); + controller.filePane.translateXProperty().bind(leftPaneAnimationProperty.subtract(controller.filePane.widthProperty())); // Whenever the width of the file pane is updated, update the animations controller.filePane.widthProperty().addListener((observable) -> { @@ -302,8 +133,8 @@ private void initializeCloseFilePaneAnimation() { closeFilePaneAnimation = new Timeline(); - final KeyValue open = new KeyValue(filePaneAnimationProperty, controller.filePane.getWidth(), interpolator); - final KeyValue closed = new KeyValue(filePaneAnimationProperty, 0, interpolator); + final KeyValue open = new KeyValue(leftPaneAnimationProperty, controller.filePane.getWidth(), interpolator); + final KeyValue closed = new KeyValue(leftPaneAnimationProperty, 0, interpolator); final KeyFrame kf1 = new KeyFrame(Duration.millis(0), open); final KeyFrame kf2 = new KeyFrame(Duration.millis(200), closed); @@ -316,8 +147,8 @@ private void initializeOpenFilePaneAnimation() { openFilePaneAnimation = new Timeline(); - final KeyValue closed = new KeyValue(filePaneAnimationProperty, 0, interpolator); - final KeyValue open = new KeyValue(filePaneAnimationProperty, controller.filePane.getWidth(), interpolator); + final KeyValue closed = new KeyValue(leftPaneAnimationProperty, 0, interpolator); + final KeyValue open = new KeyValue(leftPaneAnimationProperty, controller.filePane.getWidth(), interpolator); final KeyFrame kf1 = new KeyFrame(Duration.millis(0), closed); final KeyFrame kf2 = new KeyFrame(Duration.millis(200), open); @@ -330,7 +161,7 @@ private void initializeToggleQueryPaneFunctionality() { initializeCloseQueryPaneAnimation(); // Translate the x coordinate to create the open/close animations - controller.queryPane.translateXProperty().bind(queryPaneAnimationProperty.multiply(-1).add(controller.queryPane.widthProperty())); + controller.queryPane.translateXProperty().bind(rightPaneAnimationProperty.multiply(-1).add(controller.queryPane.widthProperty())); // Whenever the width of the query pane is updated, update the animations controller.queryPane.widthProperty().addListener((observable) -> { @@ -362,8 +193,8 @@ private void initializeCloseQueryPaneAnimation() { closeQueryPaneAnimation = new Timeline(); - final KeyValue open = new KeyValue(queryPaneAnimationProperty, controller.queryPane.getWidth(), interpolator); - final KeyValue closed = new KeyValue(queryPaneAnimationProperty, 0, interpolator); + final KeyValue open = new KeyValue(rightPaneAnimationProperty, controller.queryPane.getWidth(), interpolator); + final KeyValue closed = new KeyValue(rightPaneAnimationProperty, 0, interpolator); final KeyFrame kf1 = new KeyFrame(Duration.millis(0), open); final KeyFrame kf2 = new KeyFrame(Duration.millis(200), closed); @@ -376,8 +207,8 @@ private void initializeOpenQueryPaneAnimation() { openQueryPaneAnimation = new Timeline(); - final KeyValue closed = new KeyValue(queryPaneAnimationProperty, 0, interpolator); - final KeyValue open = new KeyValue(queryPaneAnimationProperty, controller.queryPane.getWidth(), interpolator); + final KeyValue closed = new KeyValue(rightPaneAnimationProperty, 0, interpolator); + final KeyValue open = new KeyValue(rightPaneAnimationProperty, controller.queryPane.getWidth(), interpolator); final KeyFrame kf1 = new KeyFrame(Duration.millis(0), closed); final KeyFrame kf2 = new KeyFrame(Duration.millis(200), open); @@ -405,18 +236,6 @@ private void initializeTopBar() { ))); } - private void initializeToolbar() { - final Color color = Color.GREY_BLUE; - final Color.Intensity intensity = Color.Intensity.I700; - - // Set the background for the top toolbar - controller.toolbar.setBackground( - new Background(new BackgroundFill(color.getColor(intensity), - CornerRadii.EMPTY, - Insets.EMPTY) - )); - } - /** * Initialize help image views. */ @@ -451,13 +270,12 @@ private void initializeResizeQueryPane() { // Set bounds for resizing to be between 280px and half the screen width final double newWidth = Math.min(Math.max(prevWidth.get() + diff, 280), controller.root.getWidth() / 2); - queryPaneAnimationProperty.set(newWidth); + rightPaneAnimationProperty.set(newWidth); controller.queryPane.setMaxWidth(newWidth); controller.queryPane.setMinWidth(newWidth); }); } - public BooleanProperty toggleFilePane() { if (filePaneOpen.get()) { closeFilePaneAnimation.play(); diff --git a/src/main/java/ecdar/presentations/EditorPresentation.java b/src/main/java/ecdar/presentations/EditorPresentation.java new file mode 100644 index 00000000..d17a4ab0 --- /dev/null +++ b/src/main/java/ecdar/presentations/EditorPresentation.java @@ -0,0 +1,201 @@ +package ecdar.presentations; + +import com.jfoenix.controls.JFXPopup; +import com.jfoenix.controls.JFXRippler; +import ecdar.controllers.EditorController; +import ecdar.utility.UndoRedoStack; +import ecdar.utility.colors.Color; +import ecdar.utility.colors.EnabledColor; +import ecdar.utility.helpers.SelectHelper; +import javafx.animation.FadeTransition; +import javafx.animation.ScaleTransition; +import javafx.collections.ListChangeListener; +import javafx.geometry.Insets; +import javafx.scene.control.Label; +import javafx.scene.control.Tooltip; +import javafx.scene.layout.*; +import javafx.scene.shape.Circle; +import javafx.util.Duration; +import javafx.util.Pair; + +import java.util.ArrayList; +import java.util.List; + +import static ecdar.utility.colors.EnabledColor.enabledColors; + +public class EditorPresentation extends VBox { + private final EditorController controller; + + public EditorPresentation() { + this.controller = new EcdarFXMLLoader().loadAndGetController("EditorPresentation.fxml", this); + initializeToolbar(); + initializeColorSelector(); + + initializeSelectDependentToolbarButton(controller.colorSelected); + Tooltip.install(controller.colorSelected, new Tooltip("Colour")); + + initializeSelectDependentToolbarButton(controller.deleteSelected); + Tooltip.install(controller.deleteSelected, new Tooltip("Delete")); + + initializeToolbarButton(controller.undo); + initializeToolbarButton(controller.redo); + initializeUndoRedoButtons(); + } + + public EditorController getController() { + return controller; + } + + private void initializeSelectDependentToolbarButton(final JFXRippler button) { + initializeToolbarButton(button); + + // The color button should only be enabled when an element is selected + SelectHelper.getSelectedElements().addListener(new ListChangeListener() { + @Override + public void onChanged(final Change c) { + if (SelectHelper.getSelectedElements().size() > 0) { + button.setEnabled(true); + + final FadeTransition fadeAnimation = new FadeTransition(Duration.millis(100), button); + fadeAnimation.setFromValue(button.getOpacity()); + fadeAnimation.setToValue(1); + fadeAnimation.play(); + } else { + button.setEnabled(false); + + final FadeTransition fadeAnimation = new FadeTransition(Duration.millis(100), button); + fadeAnimation.setFromValue(1); + fadeAnimation.setToValue(0.3); + fadeAnimation.play(); + } + } + }); + + // Disable the button + button.setEnabled(false); + button.setOpacity(0.3); + } + + private void initializeToolbarButton(final JFXRippler button) { + final Color color = Color.GREY_BLUE; + final Color.Intensity colorIntensity = Color.Intensity.I800; + + button.setMaskType(JFXRippler.RipplerMask.CIRCLE); + button.setRipplerFill(color.getTextColor(colorIntensity)); + button.setPosition(JFXRippler.RipplerPos.BACK); + } + + private void initializeUndoRedoButtons() { + UndoRedoStack.canUndoProperty().addListener((obs, oldState, newState) -> { + if (newState) { + // Enable the undo button + controller.undo.setEnabled(true); + controller.undo.setOpacity(1); + } else { + // Disable the undo button + controller.undo.setEnabled(false); + controller.undo.setOpacity(0.3); + } + }); + + UndoRedoStack.canRedoProperty().addListener((obs, oldState, newState) -> { + if (newState) { + // Enable the redo button + controller.redo.setEnabled(true); + controller.redo.setOpacity(1); + } else { + // Disable the redo button + controller.redo.setEnabled(false); + controller.redo.setOpacity(0.3); + } + }); + + // Disable the undo button + controller.undo.setEnabled(false); + controller.undo.setOpacity(0.3); + + // Disable the redo button + controller.redo.setEnabled(false); + controller.redo.setOpacity(0.3); + + // Set tooltips + Tooltip.install(controller.undo, new Tooltip("Undo")); + Tooltip.install(controller.redo, new Tooltip("Redo")); + } + + private void initializeColorSelector() { + final JFXPopup popup = new JFXPopup(); + + final double listWidth = 136; + final FlowPane list = new FlowPane(); + for (final EnabledColor color : enabledColors) { + final Circle circle = new Circle(16, color.color.getColor(color.intensity)); + circle.setStroke(color.color.getColor(color.intensity.next(2))); + circle.setStrokeWidth(1); + + final Label label = new Label(color.keyCode.getName()); + label.getStyleClass().add("subhead"); + label.setTextFill(color.color.getTextColor(color.intensity)); + + final StackPane child = new StackPane(circle, label); + child.setMinSize(40, 40); + child.setMaxSize(40, 40); + + child.setOnMouseEntered(event -> { + final ScaleTransition scaleTransition = new ScaleTransition(Duration.millis(100), circle); + scaleTransition.setFromX(circle.getScaleX()); + scaleTransition.setFromY(circle.getScaleY()); + scaleTransition.setToX(1.1); + scaleTransition.setToY(1.1); + scaleTransition.play(); + }); + + child.setOnMouseExited(event -> { + final ScaleTransition scaleTransition = new ScaleTransition(Duration.millis(100), circle); + scaleTransition.setFromX(circle.getScaleX()); + scaleTransition.setFromY(circle.getScaleY()); + scaleTransition.setToX(1.0); + scaleTransition.setToY(1.0); + scaleTransition.play(); + }); + + child.setOnMouseClicked(event -> { + final List> previousColor = new ArrayList<>(); + + SelectHelper.getSelectedElements().forEach(selectable -> { + previousColor.add(new Pair<>(selectable, new EnabledColor(selectable.getColor(), selectable.getColorIntensity()))); + }); + + controller.changeColorOnSelectedElements(color, previousColor); + + popup.hide(); + SelectHelper.clearSelectedElements(); + }); + + list.getChildren().add(child); + } + list.setMinWidth(listWidth); + list.setMaxWidth(listWidth); + list.setStyle("-fx-background-color: white; -fx-padding: 8;"); + + popup.setPopupContent(list); + + controller.colorSelected.setOnMouseClicked((e) -> { + // If nothing is selected + if (SelectHelper.getSelectedElements().size() == 0) return; + popup.show(controller.colorSelected, JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.RIGHT, -10, 15); + }); + } + + private void initializeToolbar() { + final Color color = Color.GREY_BLUE; + final Color.Intensity intensity = Color.Intensity.I700; + + // Set the background for the top toolbar + controller.toolbar.setBackground( + new Background(new BackgroundFill(color.getColor(intensity), + CornerRadii.EMPTY, + Insets.EMPTY) + )); + } +} diff --git a/src/main/java/ecdar/presentations/FilePresentation.java b/src/main/java/ecdar/presentations/FilePresentation.java index 3592e951..fb21d4ba 100644 --- a/src/main/java/ecdar/presentations/FilePresentation.java +++ b/src/main/java/ecdar/presentations/FilePresentation.java @@ -132,7 +132,7 @@ private void initializeColors() { private ArrayList getActiveComponents() { ArrayList activeComponents = new ArrayList<>(); - Node canvasPaneFirstChild = Ecdar.getPresentation().getController().canvasPane.getChildren().get(0); + Node canvasPaneFirstChild = Ecdar.getPresentation().getController().getEditorPresentation().getController().canvasPane.getChildren().get(0); if(canvasPaneFirstChild instanceof GridPane) { for (Node child : ((GridPane) canvasPaneFirstChild).getChildren()) { activeComponents.add(((CanvasPresentation) child).getController().getActiveModel()); diff --git a/src/main/java/ecdar/presentations/RightSimPanePresentation.java b/src/main/java/ecdar/presentations/RightSimPanePresentation.java index 729b8829..48c79f1c 100755 --- a/src/main/java/ecdar/presentations/RightSimPanePresentation.java +++ b/src/main/java/ecdar/presentations/RightSimPanePresentation.java @@ -34,7 +34,7 @@ private void initializeBackground() { * Initializes the thin border on the left side of the querypane toolbar */ private void initializeLeftBorder() { - controller.queryPaneElement.getController().toolbar.setBorder(new Border(new BorderStroke( + setBorder(new Border(new BorderStroke( Color.GREY_BLUE.getColor(Color.Intensity.I900), BorderStrokeStyle.SOLID, CornerRadii.EMPTY, diff --git a/src/main/java/ecdar/controllers/SimEdgePresentation.java b/src/main/java/ecdar/presentations/SimEdgePresentation.java similarity index 100% rename from src/main/java/ecdar/controllers/SimEdgePresentation.java rename to src/main/java/ecdar/presentations/SimEdgePresentation.java diff --git a/src/main/java/ecdar/controllers/SimLocationPresentation.java b/src/main/java/ecdar/presentations/SimLocationPresentation.java similarity index 97% rename from src/main/java/ecdar/controllers/SimLocationPresentation.java rename to src/main/java/ecdar/presentations/SimLocationPresentation.java index 7d87e377..e250433a 100755 --- a/src/main/java/ecdar/controllers/SimLocationPresentation.java +++ b/src/main/java/ecdar/presentations/SimLocationPresentation.java @@ -9,7 +9,9 @@ import ecdar.utility.helpers.SelectHelper; import javafx.animation.*; import javafx.beans.binding.DoubleBinding; -import javafx.beans.property.*; +import javafx.beans.property.DoubleProperty; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleDoubleProperty; import javafx.scene.Group; import javafx.scene.control.Label; import javafx.scene.effect.DropShadow; diff --git a/src/main/java/ecdar/presentations/SimNailPresentation.java b/src/main/java/ecdar/presentations/SimNailPresentation.java new file mode 100755 index 00000000..250f2f9d --- /dev/null +++ b/src/main/java/ecdar/presentations/SimNailPresentation.java @@ -0,0 +1,258 @@ +package ecdar.presentations; + +import ecdar.abstractions.Component; +import ecdar.abstractions.Edge; +import ecdar.abstractions.EdgeStatus; +import ecdar.abstractions.Nail; +import ecdar.controllers.SimEdgeController; +import ecdar.controllers.SimNailController; +import ecdar.utility.Highlightable; +import ecdar.utility.colors.Color; +import ecdar.utility.helpers.BindingHelper; +import javafx.animation.Interpolator; +import javafx.animation.KeyFrame; +import javafx.animation.KeyValue; +import javafx.animation.Timeline; +import javafx.scene.Group; +import javafx.scene.control.Label; +import javafx.scene.shape.Line; + +import java.util.function.Consumer; + +import static javafx.util.Duration.millis; + +/** + * The presentation for the nail shown on a {@link SimEdgePresentation} in the {@link SimulatorOverviewPresentation}
+ * This class should be refactored such that code which are duplicated from {@link NailPresentation} + * have its own base class. + */ +public class SimNailPresentation extends Group implements Highlightable { + + public static final double COLLAPSED_RADIUS = 2d; + public static final double HOVERED_RADIUS = 7d; + + private final SimNailController controller; + private final Timeline shakeAnimation = new Timeline(); + + /** + * Constructs a nail which is ready to be displayed in the simulator + * @param nail the nail which should be presented + * @param edge the edge where the nail belongs to + * @param component the component where the nail belongs + * @param simEdgeController the controller of the edge where the nail belongs to + */ + public SimNailPresentation(final Nail nail, final Edge edge, final Component component, final SimEdgeController simEdgeController) { + controller = new EcdarFXMLLoader().loadAndGetController("SimNailPresentation.fxml", this); + + // Bind the component with the one of the controller + controller.setComponent(component); + + // Bind the edge with the one of the controller + controller.setEdge(edge); + // Bind the nail with the one of the controller + controller.setNail(nail); + + controller.setEdgeController(simEdgeController); + + initializeNailCircleColor(); + initializePropertyTag(); + initializeRadius(); + initializeShakeAnimation(); + } + + /** + * Sets the radius of the nail + */ + private void initializeRadius() { + final Consumer radiusUpdater = (propertyType) -> { + if(!propertyType.equals(Edge.PropertyType.NONE)) { + controller.getNail().setRadius(SimNailPresentation.HOVERED_RADIUS); + } + }; + controller.getNail().propertyTypeProperty().addListener((observable, oldValue, newValue) -> { + radiusUpdater.accept(newValue); + }); + radiusUpdater.accept(controller.getNail().getPropertyType()); + } + + /** + * Initializes the text / PropertyTag shown on a {@link SimTagPresentation} on the nail. + */ + private void initializePropertyTag() { + final SimTagPresentation propertyTag = controller.propertyTag; + final Line propertyTagLine = controller.propertyTagLine; + propertyTag.setComponent(controller.getComponent()); + propertyTag.setLocationAware(controller.getNail()); + + // Bind the line to the tag + BindingHelper.bind(propertyTagLine, propertyTag); + + // Bind the color of the tag to the color of the component + propertyTag.bindToColor(controller.getComponent().colorProperty(), controller.getComponent().colorIntensityProperty()); + + // Updates visibility and placeholder of the tag depending on the type of nail + final Consumer updatePropertyType = (propertyType) -> { + + // If it is not a property nail hide the tag otherwise show it and write proper placeholder + if(propertyType.equals(Edge.PropertyType.NONE)) { + propertyTag.setVisible(false); + } else { + + // Show the property tag since the nail is a property nail + propertyTag.setVisible(true); + + // Set and bind the location of the property tag + if((controller.getNail().getPropertyX() != 0) && (controller.getNail().getPropertyY() != 0)) { + propertyTag.setTranslateX(controller.getNail().getPropertyX()); + propertyTag.setTranslateY(controller.getNail().getPropertyY()); + } + controller.getNail().propertyXProperty().bind(propertyTag.translateXProperty()); + controller.getNail().propertyYProperty().bind(propertyTag.translateYProperty()); + + final Label propertyLabel = controller.propertyLabel; + + if(propertyType.equals(Edge.PropertyType.SELECTION)) { + propertyLabel.setText(":"); + propertyLabel.setTranslateX(-3); + propertyLabel.setTranslateY(-8); + propertyTag.setAndBindString(controller.getEdge().selectProperty()); + } else if(propertyType.equals(Edge.PropertyType.GUARD)) { + propertyLabel.setText("<"); + propertyLabel.setTranslateX(-3); + propertyLabel.setTranslateY(-7); + propertyTag.setAndBindString(controller.getEdge().guardProperty()); + } else if(propertyType.equals(Edge.PropertyType.SYNCHRONIZATION)) { + updateSyncLabel(); + propertyLabel.setTranslateX(-3); + propertyLabel.setTranslateY(-7); + propertyTag.setAndBindString(controller.getEdge().syncProperty()); + } else if(propertyType.equals(Edge.PropertyType.UPDATE)) { + propertyLabel.setText("="); + propertyLabel.setTranslateX(-3); + propertyLabel.setTranslateY(-7); + propertyTag.setAndBindString(controller.getEdge().updateProperty()); + } + + //Disable the ability to edit the tag if the nails edge is locked + if(controller.getEdge().getIsLockedProperty().getValue()){ + propertyTag.setDisabledText(true); + } + } + }; + + // Whenever the property type updates, update the tag + controller.getNail().propertyTypeProperty().addListener((obs, oldPropertyType, newPropertyType) -> { + updatePropertyType.accept(newPropertyType); + }); + + // Whenever the edge changes I/O status, if sync nail then update its label + controller.getEdge().ioStatus.addListener((observable, oldValue, newValue) -> { + if (controller.getNail().getPropertyType().equals(Edge.PropertyType.SYNCHRONIZATION)) + updateSyncLabel(); + }); + + // Update the tag initially + updatePropertyType.accept(controller.getNail().getPropertyType()); + } + + /** + * Updates the synchronization label and tag. + * The update depends on the edge I/O status. + */ + private void updateSyncLabel() { + final Label propertyLabel = controller.propertyLabel; + + // show ? or ! dependent on edge I/O status + if (controller.getEdge().ioStatus.get().equals(EdgeStatus.INPUT)) { + propertyLabel.setText("?"); + } else { + propertyLabel.setText("!"); + } + } + + /** + * Set up Listeners for updating the color of the nail, set the color initially + */ + private void initializeNailCircleColor() { + final Runnable updateNailColor = () -> { + final Color color = controller.getComponent().getColor(); + final Color.Intensity colorIntensity = controller.getComponent().getColorIntensity(); + + if(!controller.getNail().getPropertyType().equals(Edge.PropertyType.NONE)) { + controller.nailCircle.setFill(color.getColor(colorIntensity)); + controller.nailCircle.setStroke(color.getColor(colorIntensity.next(2))); + } else { + controller.nailCircle.setFill(Color.GREY_BLUE.getColor(Color.Intensity.I800)); + controller.nailCircle.setStroke(Color.GREY_BLUE.getColor(Color.Intensity.I900)); + } + }; + + // When the color of the component updates, update the nail indicator as well + controller.getComponent().colorProperty().addListener((observable) -> updateNailColor.run()); + + // When the color intensity of the component updates, update the nail indicator + controller.getComponent().colorIntensityProperty().addListener((observable) -> updateNailColor.run()); + + // Initialize the color of the nail + updateNailColor.run(); + } + + /** + * Initializes a shake animation found in {@link SimNailPresentation#shakeAnimation} which can + * be played using {@link SimNailPresentation#shake()} + */ + private void initializeShakeAnimation() { + final Interpolator interpolator = Interpolator.SPLINE(0.645, 0.045, 0.355, 1); + + final double startX = controller.root.getTranslateX(); + final KeyValue kv1 = new KeyValue(controller.root.translateXProperty(), startX - 3, interpolator); + final KeyValue kv2 = new KeyValue(controller.root.translateXProperty(), startX + 3, interpolator); + final KeyValue kv3 = new KeyValue(controller.root.translateXProperty(), startX, interpolator); + + final KeyFrame kf1 = new KeyFrame(millis(50), kv1); + final KeyFrame kf2 = new KeyFrame(millis(100), kv2); + final KeyFrame kf3 = new KeyFrame(millis(150), kv1); + final KeyFrame kf4 = new KeyFrame(millis(200), kv2); + final KeyFrame kf5 = new KeyFrame(millis(250), kv3); + + shakeAnimation.getKeyFrames().addAll(kf1, kf2, kf3, kf4, kf5); + } + + /** + * Plays the {@link SimNailPresentation#shakeAnimation} + */ + public void shake() { + shakeAnimation.play(); + } + + /** + * Highlights the nail + */ + @Override + public void highlight() { + final Color color = Color.DEEP_ORANGE; + final Color.Intensity intensity = Color.Intensity.I500; + + // Set the color + controller.nailCircle.setFill(color.getColor(intensity)); + controller.nailCircle.setStroke(color.getColor(intensity.next(2))); + } + + /** + * Removes the highlight from the nail + */ + @Override + public void unhighlight() { + Color color = Color.GREY_BLUE; + Color.Intensity intensity = Color.Intensity.I800; + + // Set the color + if(!controller.getNail().getPropertyType().equals(Edge.PropertyType.NONE)) { + color = controller.getComponent().getColor(); + intensity = controller.getComponent().getColorIntensity(); + } + + controller.nailCircle.setFill(color.getColor(intensity)); + controller.nailCircle.setStroke(color.getColor(intensity.next(2))); + } +} diff --git a/src/main/java/ecdar/presentations/SimTagPresentation.java b/src/main/java/ecdar/presentations/SimTagPresentation.java new file mode 100755 index 00000000..d2916ee1 --- /dev/null +++ b/src/main/java/ecdar/presentations/SimTagPresentation.java @@ -0,0 +1,186 @@ +package ecdar.presentations; + +import ecdar.abstractions.Component; +import ecdar.utility.colors.Color; +import ecdar.utility.helpers.LocationAware; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.property.StringProperty; +import javafx.geometry.Insets; +import javafx.scene.control.Label; +import javafx.scene.layout.StackPane; +import javafx.scene.shape.LineTo; +import javafx.scene.shape.MoveTo; +import javafx.scene.shape.Path; + +import java.util.function.BiConsumer; + +import static ecdar.presentations.Grid.GRID_SIZE; + +/** + * The presentation for the tag shown on a {@link SimEdgePresentation} in the {@link SimulatorOverviewPresentation}
+ * This class should be refactored such that code which are duplicated from {@link TagPresentation} + * have its own base class. + */ +public class SimTagPresentation extends StackPane { + + private final static Color backgroundColor = Color.GREY; + private final static Color.Intensity backgroundColorIntensity = Color.Intensity.I50; + + private final ObjectProperty component = new SimpleObjectProperty<>(null); + private final ObjectProperty locationAware = new SimpleObjectProperty<>(null); + + private LineTo l2; + private LineTo l3; + + private static double TAG_HEIGHT = 1.6 * GRID_SIZE; + + /** + * Constructs the {@link SimTagPresentation} + */ + public SimTagPresentation() { + new EcdarFXMLLoader().loadAndGetController("SimTagPresentation.fxml", this); + initializeShape(); + initializeLabel(); + initializeMouseTransparency(); + } + + private void initializeMouseTransparency() { + mouseTransparentProperty().bind(opacityProperty().isEqualTo(0, 0.00f)); + } + + /** + * Initializes the label which shows the property + */ + private void initializeLabel() { + final Label label = (Label) lookup("#label"); + final Path shape = (Path) lookup("#shape"); + + final Insets insets = new Insets(0,2,0,2); + label.setPadding(insets); + + final int padding = 0; + + label.layoutBoundsProperty().addListener((obs, oldBounds, newBounds) -> { + double newWidth = Math.max(newBounds.getWidth(), 10); + final double res = GRID_SIZE * 2 - (newWidth % (GRID_SIZE * 2)); + newWidth += res; + + l2.setX(newWidth + padding); + l3.setX(newWidth + padding); + + setMinWidth(newWidth + padding); + setMaxWidth(newWidth + padding); + + label.focusedProperty().addListener((observable, oldFocused, newFocused) -> { + if (newFocused) { + shape.setTranslateY(2); + label.setTranslateY(2); + } + }); + + if (getWidth() >= 1000) { + setWidth(newWidth); + setHeight(TAG_HEIGHT); + shape.setTranslateY(-1); + } + + // Fixes the jumping of the shape when the text field is empty + if (label.getText().isEmpty()) { + shape.setLayoutX(0); + } + }); + } + + /** + * Initialize the shape which is around the property label + */ + private void initializeShape() { + final int WIDTH = 5000; + final double HEIGHT = TAG_HEIGHT; + + final Path shape = (Path) lookup("#shape"); + + final MoveTo start = new MoveTo(0, 0); + + l2 = new LineTo(WIDTH, 0); + l3 = new LineTo(WIDTH, HEIGHT); + final LineTo l4 = new LineTo(0, HEIGHT); + final LineTo l6 = new LineTo(0, 0); + + shape.getElements().addAll(start, l2, l3, l4, l6); + shape.setFill(backgroundColor.getColor(backgroundColorIntensity)); + shape.setStroke(backgroundColor.getColor(backgroundColorIntensity.next(4))); + } + + public void bindToColor(final ObjectProperty color, final ObjectProperty intensity) { + bindToColor(color, intensity, false); + } + + public void bindToColor(final ObjectProperty color, final ObjectProperty intensity, final boolean doColorBackground) { + final BiConsumer recolor = (newColor, newIntensity) -> { + if (doColorBackground) { + final Path shape = (Path) lookup("#shape"); + shape.setFill(newColor.getColor(newIntensity.next(-1))); + shape.setStroke(newColor.getColor(newIntensity.next(-1).next(2))); + } + }; + + color.addListener(observable -> recolor.accept(color.get(), intensity.get())); + intensity.addListener(observable -> recolor.accept(color.get(), intensity.get())); + recolor.accept(color.get(), intensity.get()); + } + + /** + * Updates the label with the given string and binds updates from the label on the given {@link StringProperty} + * @param string the string to set and bind to + */ + public void setAndBindString(final StringProperty string) { + final Label label = (Label) lookup("#label"); + + label.textProperty().unbind(); + label.setText(string.get()); + string.bind(label.textProperty()); + } + + /** + * Replaces spaces with underscores in the label + */ +/* public void replaceSpace() { + initializeTextAid(); + }*/ + + public Component getComponent() { + return component.get(); + } + + public void setComponent(final Component component) { + this.component.set(component); + } + + public ObjectProperty componentProperty() { + return component; + } + + public LocationAware getLocationAware() { + return locationAware.get(); + } + + public ObjectProperty locationAwareProperty() { + return locationAware; + } + + public void setLocationAware(LocationAware locationAware) { + this.locationAware.set(locationAware); + } + + /** + * Sets the disabled property in the label + * @param bool true --- the label will be disabled + * false --- the label will be enabled + */ + public void setDisabledText(boolean bool){ + final Label label = (Label) lookup("#label"); + label.setDisable(true); + } +} diff --git a/src/main/java/ecdar/utility/helpers/BindingHelper.java b/src/main/java/ecdar/utility/helpers/BindingHelper.java index afbddec2..89d1a5e6 100644 --- a/src/main/java/ecdar/utility/helpers/BindingHelper.java +++ b/src/main/java/ecdar/utility/helpers/BindingHelper.java @@ -5,6 +5,7 @@ import ecdar.model_canvas.arrow_heads.ChannelReceiverArrowHead; import ecdar.model_canvas.arrow_heads.ChannelSenderArrowHead; import ecdar.presentations.Link; +import ecdar.presentations.SimTagPresentation; import ecdar.presentations.TagPresentation; import ecdar.utility.mouse.MouseTracker; import javafx.beans.binding.DoubleBinding; @@ -24,6 +25,15 @@ public static void bind(final Line subject, final TagPresentation target) { subject.visibleProperty().bind(target.visibleProperty()); } + public static void bind(final Line subject, final SimTagPresentation target) { + subject.startXProperty().set(0); + subject.startYProperty().set(0); + subject.endXProperty().bind(target.translateXProperty().add(target.minWidthProperty().divide(2))); + subject.endYProperty().bind(target.translateYProperty().add(target.heightProperty().divide(2))); + subject.opacityProperty().bind(target.opacityProperty()); + subject.visibleProperty().bind(target.visibleProperty()); + } + public static void bind(final Circular subject, final ObservableDoubleValue x, final ObservableDoubleValue y) { subject.xProperty().bind(EcdarController.getActiveCanvasPresentation().mouseTracker.gridXProperty().subtract(x)); subject.yProperty().bind(EcdarController.getActiveCanvasPresentation().mouseTracker.gridYProperty().subtract(y)); diff --git a/src/main/resources/ecdar/presentations/EcdarPresentation.fxml b/src/main/resources/ecdar/presentations/EcdarPresentation.fxml index 29bb1938..97de6ab5 100644 --- a/src/main/resources/ecdar/presentations/EcdarPresentation.fxml +++ b/src/main/resources/ecdar/presentations/EcdarPresentation.fxml @@ -10,7 +10,6 @@ -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
@@ -205,7 +139,7 @@ - + @@ -245,6 +179,18 @@ + + + + + + + + + + + + diff --git a/src/main/resources/ecdar/presentations/EditorPresentation.fxml b/src/main/resources/ecdar/presentations/EditorPresentation.fxml new file mode 100644 index 00000000..bf3faac1 --- /dev/null +++ b/src/main/resources/ecdar/presentations/EditorPresentation.fxml @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/ecdar/presentations/RightSimPanePresentation.fxml b/src/main/resources/ecdar/presentations/RightSimPanePresentation.fxml index de2b3c4c..3ed05348 100755 --- a/src/main/resources/ecdar/presentations/RightSimPanePresentation.fxml +++ b/src/main/resources/ecdar/presentations/RightSimPanePresentation.fxml @@ -2,7 +2,6 @@ - - + diff --git a/src/main/resources/ecdar/presentations/SimEdgePresentation.fxml b/src/main/resources/ecdar/presentations/SimEdgePresentation.fxml new file mode 100755 index 00000000..1c72435f --- /dev/null +++ b/src/main/resources/ecdar/presentations/SimEdgePresentation.fxml @@ -0,0 +1,12 @@ + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/ecdar/presentations/SimLocationPresentation.fxml b/src/main/resources/ecdar/presentations/SimLocationPresentation.fxml new file mode 100755 index 00000000..eb6b4543 --- /dev/null +++ b/src/main/resources/ecdar/presentations/SimLocationPresentation.fxml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/ecdar/presentations/SimNailPresentation.fxml b/src/main/resources/ecdar/presentations/SimNailPresentation.fxml new file mode 100755 index 00000000..17a63895 --- /dev/null +++ b/src/main/resources/ecdar/presentations/SimNailPresentation.fxml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/ecdar/presentations/SimTagPresentation.fxml b/src/main/resources/ecdar/presentations/SimTagPresentation.fxml new file mode 100755 index 00000000..f9e40d71 --- /dev/null +++ b/src/main/resources/ecdar/presentations/SimTagPresentation.fxml @@ -0,0 +1,11 @@ + + + + + + + + \ No newline at end of file From e0a510911cb4b520ba2c90f967365b66e5a2544b Mon Sep 17 00:00:00 2001 From: Niels Vistisen Date: Wed, 13 Jul 2022 12:14:11 +0200 Subject: [PATCH 003/158] WIP: Design updated for simulator and side panes logic/naming updated to account for having either the editor or the simulator view active --- src/main/java/ecdar/Ecdar.java | 6 +- .../ecdar/controllers/EcdarController.java | 51 +++++--- .../controllers/ProjectPaneController.java | 3 +- .../controllers/SimulatorController.java | 12 +- .../TracePaneElementController.java | 9 +- .../TransitionPaneElementController.java | 20 ++- .../presentations/EcdarPresentation.java | 122 ++++++++++-------- .../TracePaneElementPresentation.java | 2 +- .../TransitionPaneElementPresentation.java | 2 +- .../presentations/EcdarPresentation.fxml | 14 +- .../LeftSimPanePresentation.fxml | 16 +-- .../ProjectPanePresentation.fxml | 3 +- .../presentations/QueryPanePresentation.fxml | 3 +- .../presentations/SimulatorPresentation.fxml | 64 ++------- .../TracePaneElementPresentation.fxml | 66 +++++----- .../TransitionPaneElementPresentation.fxml | 80 +++++------- 16 files changed, 208 insertions(+), 265 deletions(-) diff --git a/src/main/java/ecdar/Ecdar.java b/src/main/java/ecdar/Ecdar.java index 6a6801f0..d4d7ee18 100644 --- a/src/main/java/ecdar/Ecdar.java +++ b/src/main/java/ecdar/Ecdar.java @@ -148,8 +148,8 @@ public static void showHelp() { presentation.showHelp(); } - public static BooleanProperty toggleFilePane() { - return presentation.toggleFilePane(); + public static BooleanProperty toggleLeftPane() { + return presentation.toggleLeftPane(); } /** @@ -174,7 +174,7 @@ public static BooleanProperty toggleRunBackgroundQueries() { } public static BooleanProperty toggleQueryPane() { - return presentation.toggleQueryPane(); + return presentation.toggleRightPane(); } /** diff --git a/src/main/java/ecdar/controllers/EcdarController.java b/src/main/java/ecdar/controllers/EcdarController.java index 806b040c..041485cb 100644 --- a/src/main/java/ecdar/controllers/EcdarController.java +++ b/src/main/java/ecdar/controllers/EcdarController.java @@ -60,8 +60,6 @@ public class EcdarController implements Initializable { public StackPane leftPane; public StackPane rightPane; public Rectangle bottomFillerElement; - public QueryPanePresentation queryPane; - public ProjectPanePresentation filePane; public MessageTabPanePresentation messageTabPane; public StackPane dialogContainer; public JFXDialog dialog; @@ -91,7 +89,7 @@ public class EcdarController implements Initializable { // The program top menu public MenuBar menuBar; - public MenuItem menuBarViewFilePanel; + public MenuItem menuBarViewProjectPanel; public MenuItem menuBarViewQueryPanel; public MenuItem menuBarViewGrid; public MenuItem menuBarViewAutoscaling; @@ -162,7 +160,12 @@ private enum Mode { private static final ObjectProperty currentMode = new SimpleObjectProperty<>(Mode.Editor); private static final EditorPresentation editorPresentation = new EditorPresentation(); + public final ProjectPanePresentation projectPane = new ProjectPanePresentation(); + public final QueryPanePresentation queryPane = new QueryPanePresentation(); + private static final SimulatorPresentation simulatorPresentation = new SimulatorPresentation(); + public final LeftSimPanePresentation leftSimPane = new LeftSimPanePresentation(); + public final RightSimPanePresentation rightSimPane = new RightSimPanePresentation(); @Override public void initialize(final URL location, final ResourceBundle resources) { @@ -173,14 +176,18 @@ public void initialize(final URL location, final ResourceBundle resources) { startBackgroundQueriesThread(); // Will terminate immediately if background queries are turned off bottomFillerElement.heightProperty().bind(messageTabPane.maxHeightProperty()); - messageTabPane.getController().setRunnableForOpeningAndClosingMessageTabPane(this::changeInsetsOfFileAndQueryPanes); + messageTabPane.getController().setRunnableForOpeningAndClosingMessageTabPane(this::changeInsetsOfProjectAndQueryPanes); // Update file coloring when active model changes - editorPresentation.getController().getActiveCanvasPresentation().getController().activeComponentProperty().addListener(observable -> filePane.getController().updateColorsOnFilePresentations()); - editorPresentation.getController().activeCanvasPresentationProperty().addListener(observable -> filePane.getController().updateColorsOnFilePresentations()); + editorPresentation.getController().getActiveCanvasPresentation().getController().activeComponentProperty().addListener(observable -> projectPane.getController().updateColorsOnFilePresentations()); + editorPresentation.getController().activeCanvasPresentationProperty().addListener(observable -> projectPane.getController().updateColorsOnFilePresentations()); - borderPane.centerProperty().addListener((observable, oldValue, newValue) -> System.out.println(newValue.getClass())); - borderPane.setCenter(editorPresentation); + leftSimPane.minWidthProperty().bind(projectPane.minWidthProperty()); + leftSimPane.maxWidthProperty().bind(projectPane.maxWidthProperty()); + rightSimPane.minWidthProperty().bind(queryPane.minWidthProperty()); + rightSimPane.maxWidthProperty().bind(queryPane.maxWidthProperty()); + + enterEditorMode(); } public StackPane getCenter() { @@ -304,7 +311,7 @@ private void initializeDialog(JFXDialog dialog, StackPane dialogContainer) { dialogContainer.setMouseTransparent(false); }); - filePane.getStyleClass().add("responsive-pane-sizing"); + projectPane.getStyleClass().add("responsive-pane-sizing"); queryPane.getStyleClass().add("responsive-pane-sizing"); initializeKeybindings(); @@ -594,11 +601,11 @@ private void initializeEditMenu() { * Initialize the View menu. */ private void initializeViewMenu() { - menuBarViewFilePanel.getGraphic().setOpacity(1); - menuBarViewFilePanel.setAccelerator(new KeyCodeCombination(KeyCode.P, KeyCodeCombination.SHORTCUT_DOWN)); - menuBarViewFilePanel.setOnAction(event -> { - final BooleanProperty isOpen = Ecdar.toggleFilePane(); - menuBarViewFilePanel.getGraphic().opacityProperty().bind(new When(isOpen).then(1).otherwise(0)); + menuBarViewProjectPanel.getGraphic().setOpacity(1); + menuBarViewProjectPanel.setAccelerator(new KeyCodeCombination(KeyCode.P, KeyCodeCombination.SHORTCUT_DOWN)); + menuBarViewProjectPanel.setOnAction(event -> { + final BooleanProperty isOpen = Ecdar.toggleLeftPane(); + menuBarViewProjectPanel.getGraphic().opacityProperty().bind(new When(isOpen).then(1).otherwise(0)); }); menuBarViewQueryPanel.getGraphic().setOpacity(0); @@ -975,6 +982,10 @@ private void enterEditorMode() { // simulatorPresentation.getController().willHide(); borderPane.setCenter(editorPresentation); + leftPane.getChildren().clear(); + leftPane.getChildren().add(projectPane); + rightPane.getChildren().clear(); + rightPane.getChildren().add(queryPane); // Enable or disable the menu items that can be used when in the simulator // updateMenuItems(); @@ -990,6 +1001,10 @@ private void enterSimulatorMode() { // simulatorPresentation.getController().willShow(); borderPane.setCenter(simulatorPresentation); + leftPane.getChildren().clear(); + leftPane.getChildren().add(leftSimPane); + rightPane.getChildren().clear(); + rightPane.getChildren().add(rightSimPane); // Enable or disable the menu items that can be used when in the simulator // updateMenuItems(); @@ -1157,15 +1172,15 @@ private static int getAutoCropRightX(final BufferedImage image) { } /** - * This method is used to push the contents of the file and query panes when the tab pane is opened + * This method is used to push the contents of the project and query panes when the tab pane is opened */ - private void changeInsetsOfFileAndQueryPanes() { + private void changeInsetsOfProjectAndQueryPanes() { if (messageTabPane.getController().isOpen()) { - filePane.showBottomInset(false); + projectPane.showBottomInset(false); queryPane.showBottomInset(false); CanvasPresentation.showBottomInset(false); } else { - filePane.showBottomInset(true); + projectPane.showBottomInset(true); queryPane.showBottomInset(true); CanvasPresentation.showBottomInset(true); } diff --git a/src/main/java/ecdar/controllers/ProjectPaneController.java b/src/main/java/ecdar/controllers/ProjectPaneController.java index 0e339900..dde161ae 100644 --- a/src/main/java/ecdar/controllers/ProjectPaneController.java +++ b/src/main/java/ecdar/controllers/ProjectPaneController.java @@ -12,6 +12,7 @@ import com.jfoenix.controls.JFXPopup; import com.jfoenix.controls.JFXRippler; import com.jfoenix.controls.JFXTextArea; +import javafx.application.Platform; import javafx.collections.ListChangeListener; import javafx.fxml.FXML; import javafx.fxml.Initializable; @@ -265,7 +266,7 @@ private void handleRemovedModel(final HighLevelModelObject model) { public void updateColorsOnFilePresentations() { for (Node child : filesList.getChildren()) { if (child instanceof FilePresentation) { - ((FilePresentation) child).updateColors(); + Platform.runLater(() -> ((FilePresentation) child).updateColors()); } } } diff --git a/src/main/java/ecdar/controllers/SimulatorController.java b/src/main/java/ecdar/controllers/SimulatorController.java index 56cc1b3f..1f00fcd3 100644 --- a/src/main/java/ecdar/controllers/SimulatorController.java +++ b/src/main/java/ecdar/controllers/SimulatorController.java @@ -19,22 +19,16 @@ import java.net.URL; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; import java.util.ResourceBundle; public class SimulatorController implements Initializable { - public StackPane root; public SimulatorOverviewPresentation overviewPresentation; public StackPane toolbar; - public Label rightPaneFillerElement; - public Label leftPaneFillerElement; - public Rectangle bottomFillerElement; - public RightSimPanePresentation rightSimPane; - public LeftSimPanePresentation leftSimPane; - private Declarations systemDeclarations; - private boolean firstTimeInSimulator; + private boolean firstTimeInSimulator; private final static DoubleProperty width = new SimpleDoubleProperty(), height = new SimpleDoubleProperty(); private static ObjectProperty selectedTransition = new SimpleObjectProperty<>(); @@ -62,7 +56,7 @@ public void willShow() { shouldSimulationBeReset = false; } - if (!firstTimeInSimulator && !overviewPresentation.getController().getComponentObservableList() + if (!firstTimeInSimulator && !new HashSet<>(overviewPresentation.getController().getComponentObservableList()) .containsAll(findComponentsInCurrentSimulation())) { shouldSimulationBeReset = true; } diff --git a/src/main/java/ecdar/controllers/TracePaneElementController.java b/src/main/java/ecdar/controllers/TracePaneElementController.java index 6f0bf4ec..a733b15f 100755 --- a/src/main/java/ecdar/controllers/TracePaneElementController.java +++ b/src/main/java/ecdar/controllers/TracePaneElementController.java @@ -14,6 +14,7 @@ import javafx.fxml.Initializable; import javafx.scene.control.Label; import javafx.scene.layout.AnchorPane; +import javafx.scene.layout.HBox; import javafx.scene.layout.VBox; import org.kordamp.ikonli.javafx.FontIcon; @@ -26,11 +27,11 @@ * The controller class for the trace pane element that can be inserted into a simulator pane */ public class TracePaneElementController implements Initializable { - public AnchorPane toolbar; + public VBox root; + public HBox toolbar; public Label traceTitle; public JFXRippler expandTrace; public VBox traceList; - public VBox traceVbox; public FontIcon expandTraceIcon; public AnchorPane traceSummary; public Label summaryTitleLabel; @@ -87,7 +88,7 @@ private void initializeTraceExpand() { */ private void hideTrace() { traceList.getChildren().clear(); - traceVbox.getChildren().add(traceSummary); + root.getChildren().add(traceSummary); } /** @@ -98,7 +99,7 @@ private void showTrace() { transitionPresentationMap.forEach((state, presentation) -> { insertTraceState(state, false); }); - traceVbox.getChildren().remove(traceSummary); + root.getChildren().remove(traceSummary); } /** diff --git a/src/main/java/ecdar/controllers/TransitionPaneElementController.java b/src/main/java/ecdar/controllers/TransitionPaneElementController.java index 5dc6dbde..8a985a92 100755 --- a/src/main/java/ecdar/controllers/TransitionPaneElementController.java +++ b/src/main/java/ecdar/controllers/TransitionPaneElementController.java @@ -15,7 +15,7 @@ import javafx.fxml.Initializable; import javafx.scene.control.Label; import javafx.scene.control.Tooltip; -import javafx.scene.layout.AnchorPane; +import javafx.scene.layout.HBox; import javafx.scene.layout.VBox; import org.kordamp.ikonli.javafx.FontIcon; @@ -29,15 +29,14 @@ * The controller class for the transition pane element that can be inserted into the simulator panes */ public class TransitionPaneElementController implements Initializable { - public AnchorPane root; - public VBox paneElementVbox; + public VBox root; public VBox transitionList; - public AnchorPane toolbar; + public HBox toolbar; public Label toolbarTitle; public JFXRippler refreshRippler; public JFXRippler expandTransition; public FontIcon expandTransitionIcon; - public AnchorPane delayChooser; + public VBox delayChooser; public JFXTextField delayTextField; private SimpleBooleanProperty isTransitionExpanded = new SimpleBooleanProperty(false); @@ -48,9 +47,7 @@ public class TransitionPaneElementController implements Initializable { public void initialize(URL location, ResourceBundle resources) { Ecdar.getSimulationHandler().availableTransitions.addListener((ListChangeListener) c -> { while (c.next()) { - for (Transition trans : c.getAddedSubList()) { - insertTransition(trans); - } + for (Transition trans : c.getAddedSubList()) insertTransition(trans); for (final Transition trans: c.getRemoved()) { transitionList.getChildren().remove(transitionPresentationMap.get(trans)); @@ -92,15 +89,15 @@ private void initializeDelayChooser() { private void initializeTransitionExpand() { isTransitionExpanded.addListener((obs, oldVal, newVal) -> { if(newVal) { - if(!paneElementVbox.getChildren().contains(delayChooser)) { + if(!root.getChildren().contains(delayChooser)) { // Add the delay chooser just below the toolbar - paneElementVbox.getChildren().add(1, delayChooser); + root.getChildren().add(1, delayChooser); } showTransitions(); expandTransitionIcon.setIconLiteral("gmi-expand-less"); expandTransitionIcon.setIconSize(24); } else { - paneElementVbox.getChildren().remove(delayChooser); + root.getChildren().remove(delayChooser); hideTransitions(); expandTransitionIcon.setIconLiteral("gmi-expand-more"); expandTransitionIcon.setIconSize(24); @@ -236,7 +233,6 @@ private void delayTextChanged(String oldValue, String newValue) { } catch (NumberFormatException ex) { // If the conversion was not possible, show the old value this.delayTextField.setText(oldValue); - this.delay.set(new BigDecimal(oldValue)); } } diff --git a/src/main/java/ecdar/presentations/EcdarPresentation.java b/src/main/java/ecdar/presentations/EcdarPresentation.java index 4884f95f..7fa6b343 100644 --- a/src/main/java/ecdar/presentations/EcdarPresentation.java +++ b/src/main/java/ecdar/presentations/EcdarPresentation.java @@ -29,24 +29,24 @@ public class EcdarPresentation extends StackPane { private final EcdarController controller; - private final BooleanProperty filePaneOpen = new SimpleBooleanProperty(false); + private final BooleanProperty leftPaneOpen = new SimpleBooleanProperty(false); private final SimpleDoubleProperty leftPaneAnimationProperty = new SimpleDoubleProperty(0); - private final BooleanProperty queryPaneOpen = new SimpleBooleanProperty(false); + private final BooleanProperty rightPaneOpen = new SimpleBooleanProperty(false); private final SimpleDoubleProperty rightPaneAnimationProperty = new SimpleDoubleProperty(0); - private Timeline openQueryPaneAnimation; - private Timeline closeQueryPaneAnimation; - private Timeline openFilePaneAnimation; - private Timeline closeFilePaneAnimation; + private Timeline openLeftPaneAnimation; + private Timeline closeLeftPaneAnimation; + private Timeline openRightPaneAnimation; + private Timeline closeRightPaneAnimation; public EcdarPresentation() { controller = new EcdarFXMLLoader().loadAndGetController("EcdarPresentation.fxml", this); initializeTopBar(); initializeQueryDetailsDialog(); - initializeToggleQueryPaneFunctionality(); - initializeToggleFilePaneFunctionality(); + initializeToggleLeftPaneFunctionality(); + initializeToggleRightPaneFunctionality(); initializeSnackbar(); - // Open the file and query panel initially + // Open the left and right panes initially Platform.runLater(() -> { // Bind sizing of sides and center panes to ensure correct sizing controller.getEditorPresentation().getController().canvasPane.minWidthProperty().bind(controller.root.widthProperty().subtract(leftPaneAnimationProperty.add(rightPaneAnimationProperty))); @@ -66,17 +66,17 @@ public EcdarPresentation() { controller.topPane.minHeightProperty().bind(controller.menuBar.heightProperty()); controller.topPane.maxHeightProperty().bind(controller.menuBar.heightProperty()); - toggleFilePane(); - toggleQueryPane(); + toggleLeftPane(); + toggleRightPane(); Ecdar.getPresentation().controller.scalingProperty.addListener((observable, oldValue, newValue) -> { // If the scaling has changed trigger animations for open panes to update width Platform.runLater(() -> { - if (filePaneOpen.get()) { - openFilePaneAnimation.play(); + if (leftPaneOpen.get()) { + openLeftPaneAnimation.play(); } - if (queryPaneOpen.get()) { - openQueryPaneAnimation.play(); + if (rightPaneOpen.get()) { + openRightPaneAnimation.play(); } }); @@ -114,84 +114,92 @@ private void initializeQueryDetailsDialog() { ))); } - private void initializeToggleFilePaneFunctionality() { - initializeOpenFilePaneAnimation(); - initializeCloseFilePaneAnimation(); + private void initializeToggleLeftPaneFunctionality() { + initializeOpenLeftPaneAnimation(); + initializeCloseLeftPaneAnimation(); // Translate the x coordinate to create the open/close animations - controller.filePane.translateXProperty().bind(leftPaneAnimationProperty.subtract(controller.filePane.widthProperty())); + controller.projectPane.translateXProperty().bind(leftPaneAnimationProperty.subtract(controller.projectPane.widthProperty())); + controller.leftSimPane.translateXProperty().bind(leftPaneAnimationProperty.subtract(controller.leftSimPane.widthProperty())); // Whenever the width of the file pane is updated, update the animations - controller.filePane.widthProperty().addListener((observable) -> { - initializeOpenFilePaneAnimation(); - initializeCloseFilePaneAnimation(); + controller.projectPane.widthProperty().addListener((observable) -> { + initializeOpenLeftPaneAnimation(); + initializeCloseLeftPaneAnimation(); + }); + + // Whenever the width of the file pane is updated, update the animations + controller.leftPane.widthProperty().addListener((observable) -> { + initializeOpenLeftPaneAnimation(); + initializeCloseLeftPaneAnimation(); }); } - private void initializeCloseFilePaneAnimation() { + private void initializeCloseLeftPaneAnimation() { final Interpolator interpolator = Interpolator.SPLINE(0.645, 0.045, 0.355, 1); - closeFilePaneAnimation = new Timeline(); + closeLeftPaneAnimation = new Timeline(); - final KeyValue open = new KeyValue(leftPaneAnimationProperty, controller.filePane.getWidth(), interpolator); + final KeyValue open = new KeyValue(leftPaneAnimationProperty, controller.projectPane.getWidth(), interpolator); final KeyValue closed = new KeyValue(leftPaneAnimationProperty, 0, interpolator); final KeyFrame kf1 = new KeyFrame(Duration.millis(0), open); final KeyFrame kf2 = new KeyFrame(Duration.millis(200), closed); - closeFilePaneAnimation.getKeyFrames().addAll(kf1, kf2); + closeLeftPaneAnimation.getKeyFrames().addAll(kf1, kf2); } - private void initializeOpenFilePaneAnimation() { + private void initializeOpenLeftPaneAnimation() { final Interpolator interpolator = Interpolator.SPLINE(0.645, 0.045, 0.355, 1); - openFilePaneAnimation = new Timeline(); + openLeftPaneAnimation = new Timeline(); final KeyValue closed = new KeyValue(leftPaneAnimationProperty, 0, interpolator); - final KeyValue open = new KeyValue(leftPaneAnimationProperty, controller.filePane.getWidth(), interpolator); + final KeyValue open = new KeyValue(leftPaneAnimationProperty, controller.projectPane.getWidth(), interpolator); final KeyFrame kf1 = new KeyFrame(Duration.millis(0), closed); final KeyFrame kf2 = new KeyFrame(Duration.millis(200), open); - openFilePaneAnimation.getKeyFrames().addAll(kf1, kf2); + openLeftPaneAnimation.getKeyFrames().addAll(kf1, kf2); } - private void initializeToggleQueryPaneFunctionality() { - initializeOpenQueryPaneAnimation(); - initializeCloseQueryPaneAnimation(); + private void initializeToggleRightPaneFunctionality() { + initializeOpenRightPaneAnimation(); + initializeCloseRightPaneAnimation(); // Translate the x coordinate to create the open/close animations controller.queryPane.translateXProperty().bind(rightPaneAnimationProperty.multiply(-1).add(controller.queryPane.widthProperty())); + controller.rightSimPane.translateXProperty().bind(rightPaneAnimationProperty.multiply(-1).add(controller.rightSimPane.widthProperty())); // Whenever the width of the query pane is updated, update the animations controller.queryPane.widthProperty().addListener((observable) -> { - initializeOpenQueryPaneAnimation(); - initializeCloseQueryPaneAnimation(); + initializeOpenRightPaneAnimation(); + initializeCloseRightPaneAnimation(); }); // When new queries are added, make sure that the query pane is open Ecdar.getProject().getQueries().addListener((ListChangeListener) c -> { - if (closeQueryPaneAnimation == null) + if (closeRightPaneAnimation == null) return; // The query pane is not yet initialized while (c.next()) { c.getAddedSubList().forEach(o -> { - if (!queryPaneOpen.get()) { + if (!rightPaneOpen.get()) { // Open the pane - openQueryPaneAnimation.play(); + openRightPaneAnimation.play(); // Toggle the open state - queryPaneOpen.set(queryPaneOpen.not().get()); + rightPaneOpen.set(rightPaneOpen.not().get()); } }); } }); } - private void initializeCloseQueryPaneAnimation() { + private void initializeCloseRightPaneAnimation() { final Interpolator interpolator = Interpolator.SPLINE(0.645, 0.045, 0.355, 1); - closeQueryPaneAnimation = new Timeline(); + closeRightPaneAnimation = new Timeline(); final KeyValue open = new KeyValue(rightPaneAnimationProperty, controller.queryPane.getWidth(), interpolator); final KeyValue closed = new KeyValue(rightPaneAnimationProperty, 0, interpolator); @@ -199,13 +207,13 @@ private void initializeCloseQueryPaneAnimation() { final KeyFrame kf1 = new KeyFrame(Duration.millis(0), open); final KeyFrame kf2 = new KeyFrame(Duration.millis(200), closed); - closeQueryPaneAnimation.getKeyFrames().addAll(kf1, kf2); + closeRightPaneAnimation.getKeyFrames().addAll(kf1, kf2); } - private void initializeOpenQueryPaneAnimation() { + private void initializeOpenRightPaneAnimation() { final Interpolator interpolator = Interpolator.SPLINE(0.645, 0.045, 0.355, 1); - openQueryPaneAnimation = new Timeline(); + openRightPaneAnimation = new Timeline(); final KeyValue closed = new KeyValue(rightPaneAnimationProperty, 0, interpolator); final KeyValue open = new KeyValue(rightPaneAnimationProperty, controller.queryPane.getWidth(), interpolator); @@ -213,7 +221,7 @@ private void initializeOpenQueryPaneAnimation() { final KeyFrame kf1 = new KeyFrame(Duration.millis(0), closed); final KeyFrame kf2 = new KeyFrame(Duration.millis(200), open); - openQueryPaneAnimation.getKeyFrames().addAll(kf1, kf2); + openRightPaneAnimation.getKeyFrames().addAll(kf1, kf2); } private void initializeTopBar() { @@ -276,30 +284,30 @@ private void initializeResizeQueryPane() { }); } - public BooleanProperty toggleFilePane() { - if (filePaneOpen.get()) { - closeFilePaneAnimation.play(); + public BooleanProperty toggleLeftPane() { + if (leftPaneOpen.get()) { + closeLeftPaneAnimation.play(); } else { - openFilePaneAnimation.play(); + openLeftPaneAnimation.play(); } // Toggle the open state - filePaneOpen.set(filePaneOpen.not().get()); + leftPaneOpen.set(leftPaneOpen.not().get()); - return filePaneOpen; + return leftPaneOpen; } - public BooleanProperty toggleQueryPane() { - if (queryPaneOpen.get()) { - closeQueryPaneAnimation.play(); + public BooleanProperty toggleRightPane() { + if (rightPaneOpen.get()) { + closeRightPaneAnimation.play(); } else { - openQueryPaneAnimation.play(); + openRightPaneAnimation.play(); } // Toggle the open state - queryPaneOpen.set(queryPaneOpen.not().get()); + rightPaneOpen.set(rightPaneOpen.not().get()); - return queryPaneOpen; + return rightPaneOpen; } public static void fitSizeWhenAvailable(final ImageView imageView, final StackPane pane) { diff --git a/src/main/java/ecdar/presentations/TracePaneElementPresentation.java b/src/main/java/ecdar/presentations/TracePaneElementPresentation.java index 4e9a15b3..0e8be627 100755 --- a/src/main/java/ecdar/presentations/TracePaneElementPresentation.java +++ b/src/main/java/ecdar/presentations/TracePaneElementPresentation.java @@ -12,7 +12,7 @@ /** * The presentation class for the trace element that can be inserted into the simulator panes */ -public class TracePaneElementPresentation extends AnchorPane { +public class TracePaneElementPresentation extends VBox { final private TracePaneElementController controller; public TracePaneElementPresentation() { diff --git a/src/main/java/ecdar/presentations/TransitionPaneElementPresentation.java b/src/main/java/ecdar/presentations/TransitionPaneElementPresentation.java index 9dbac052..8453af64 100755 --- a/src/main/java/ecdar/presentations/TransitionPaneElementPresentation.java +++ b/src/main/java/ecdar/presentations/TransitionPaneElementPresentation.java @@ -9,7 +9,7 @@ /** * The presentation class for the transition pane element that can be inserted into the simulator panes */ -public class TransitionPaneElementPresentation extends AnchorPane { +public class TransitionPaneElementPresentation extends VBox { final private TransitionPaneElementController controller; public TransitionPaneElementPresentation() { diff --git a/src/main/resources/ecdar/presentations/EcdarPresentation.fxml b/src/main/resources/ecdar/presentations/EcdarPresentation.fxml index 97de6ab5..be278b8e 100644 --- a/src/main/resources/ecdar/presentations/EcdarPresentation.fxml +++ b/src/main/resources/ecdar/presentations/EcdarPresentation.fxml @@ -22,20 +22,12 @@ - - - + - - - + @@ -124,7 +116,7 @@ - + diff --git a/src/main/resources/ecdar/presentations/LeftSimPanePresentation.fxml b/src/main/resources/ecdar/presentations/LeftSimPanePresentation.fxml index 28ed941f..ea343221 100755 --- a/src/main/resources/ecdar/presentations/LeftSimPanePresentation.fxml +++ b/src/main/resources/ecdar/presentations/LeftSimPanePresentation.fxml @@ -1,8 +1,5 @@ - - - @@ -12,8 +9,7 @@ xmlns="http://javafx.com/javafx/8.0.76-ea" type="StackPane" fx:id="root" - fx:controller="ecdar.controllers.LeftSimPaneController" - minWidth="300"> + fx:controller="ecdar.controllers.LeftSimPaneController"> + styleClass="edge-to-edge"> - - - - - - + + diff --git a/src/main/resources/ecdar/presentations/ProjectPanePresentation.fxml b/src/main/resources/ecdar/presentations/ProjectPanePresentation.fxml index 392095c9..58621901 100644 --- a/src/main/resources/ecdar/presentations/ProjectPanePresentation.fxml +++ b/src/main/resources/ecdar/presentations/ProjectPanePresentation.fxml @@ -9,7 +9,8 @@ + fx:controller="ecdar.controllers.ProjectPaneController" + StackPane.alignment="TOP_LEFT"> diff --git a/src/main/resources/ecdar/presentations/QueryPanePresentation.fxml b/src/main/resources/ecdar/presentations/QueryPanePresentation.fxml index 6a984c1b..e50fc7bd 100644 --- a/src/main/resources/ecdar/presentations/QueryPanePresentation.fxml +++ b/src/main/resources/ecdar/presentations/QueryPanePresentation.fxml @@ -9,7 +9,8 @@ + fx:controller="ecdar.controllers.QueryPaneController" + StackPane.alignment="TOP_RIGHT"> diff --git a/src/main/resources/ecdar/presentations/SimulatorPresentation.fxml b/src/main/resources/ecdar/presentations/SimulatorPresentation.fxml index 4f56d4fd..7524a380 100755 --- a/src/main/resources/ecdar/presentations/SimulatorPresentation.fxml +++ b/src/main/resources/ecdar/presentations/SimulatorPresentation.fxml @@ -1,55 +1,19 @@ - - - - - - - + - -
- - - - - - - - - - - - -
- - - - - - - - - - - - - -
+ fx:controller="ecdar.controllers.SimulatorController"> + + + + + + +
diff --git a/src/main/resources/ecdar/presentations/TracePaneElementPresentation.fxml b/src/main/resources/ecdar/presentations/TracePaneElementPresentation.fxml index 49cb83da..c61aced7 100755 --- a/src/main/resources/ecdar/presentations/TracePaneElementPresentation.fxml +++ b/src/main/resources/ecdar/presentations/TracePaneElementPresentation.fxml @@ -6,44 +6,40 @@ + xmlns:fx="http://javafx.com/fxml" + type="VBox" fx:id="root" AnchorPane.leftAnchor="0" AnchorPane.rightAnchor="0" + fx:controller="ecdar.controllers.TracePaneElementController"> + + + + + + - - - - - - - + - + + - - - - - - - - + + + + + + + diff --git a/src/main/resources/ecdar/presentations/TransitionPaneElementPresentation.fxml b/src/main/resources/ecdar/presentations/TransitionPaneElementPresentation.fxml index aeab56de..d308e5a2 100755 --- a/src/main/resources/ecdar/presentations/TransitionPaneElementPresentation.fxml +++ b/src/main/resources/ecdar/presentations/TransitionPaneElementPresentation.fxml @@ -6,61 +6,43 @@ - + - - - - - - - + fx:id="root" type="VBox" + AnchorPane.leftAnchor="0" AnchorPane.rightAnchor="0"> + + + + + + - - - - - + - - - - - + + + + + +
+ + + + + - + -
From 7319c9ba1da449322893cf833572d0a8c746650a Mon Sep 17 00:00:00 2001 From: Niels Vistisen Date: Wed, 13 Jul 2022 15:21:35 +0200 Subject: [PATCH 004/158] WIP: ProtoBuf messages updated and implementation of these into the GUI started --- src/main/java/ecdar/abstractions/Edge.java | 16 +++++ .../java/ecdar/abstractions/Location.java | 16 +++++ src/main/java/ecdar/abstractions/Nail.java | 7 ++ .../java/ecdar/abstractions/Transition.java | 18 +++++ .../ecdar/controllers/ProcessController.java | 69 ++++++++----------- .../controllers/SimulatorController.java | 1 + .../SimulatorOverviewController.java | 61 ++++++++++------ .../TracePaneElementController.java | 34 +++++---- .../ecdar/simulation/SimulationHandler.java | 6 +- .../ecdar/simulation/SimulationState.java | 16 ++++- .../simulation/SimulationStateSuccessor.java | 2 +- src/main/proto | 2 +- .../presentations/ProcessPresentation.fxml | 55 +++++++++++++++ .../RightSimPanePresentation.fxml | 2 +- 14 files changed, 219 insertions(+), 86 deletions(-) create mode 100644 src/main/java/ecdar/abstractions/Transition.java create mode 100755 src/main/resources/ecdar/presentations/ProcessPresentation.fxml diff --git a/src/main/java/ecdar/abstractions/Edge.java b/src/main/java/ecdar/abstractions/Edge.java index d985c15f..0229d51b 100644 --- a/src/main/java/ecdar/abstractions/Edge.java +++ b/src/main/java/ecdar/abstractions/Edge.java @@ -1,5 +1,6 @@ package ecdar.abstractions; +import EcdarProtoBuf.ComponentProtos; import com.google.gson.JsonPrimitive; import ecdar.Ecdar; import ecdar.controllers.EcdarController; @@ -48,6 +49,21 @@ public Edge(final JsonObject jsonObject, final Component component) { bindReachabilityAnalysis(); } + public Edge(ComponentProtos.Edge protoBufEdge) { + setId(protoBufEdge.getId()); + setSourceLocation(new Location(protoBufEdge.getSourceLocation())); + setTargetLocation(new Location(protoBufEdge.getTargetLocation())); + setStatus(protoBufEdge.getStatus().equals("INPUT") ? EdgeStatus.INPUT : EdgeStatus.OUTPUT); + setSelect(protoBufEdge.getSelect()); + setGuard(protoBufEdge.getGuard()); + setUpdate(protoBufEdge.getUpdate()); + setSync(protoBufEdge.getSync()); + + for (ComponentProtos.Nail protoBufNail : protoBufEdge.getNailList()) { + getNails().add(new Nail(protoBufNail)); + } + } + public String getSync() { return sync.get(); } diff --git a/src/main/java/ecdar/abstractions/Location.java b/src/main/java/ecdar/abstractions/Location.java index 4e7a08ec..1d0ca6ab 100644 --- a/src/main/java/ecdar/abstractions/Location.java +++ b/src/main/java/ecdar/abstractions/Location.java @@ -1,5 +1,6 @@ package ecdar.abstractions; +import EcdarProtoBuf.ComponentProtos; import ecdar.Ecdar; import ecdar.code_analysis.Nearable; import ecdar.controllers.EcdarController; @@ -87,6 +88,21 @@ public Location(final JsonObject jsonObject) { bindReachabilityAnalysis(); } + public Location(ComponentProtos.Location protoBufLocation) { + setId(protoBufLocation.getId()); + setNickname(protoBufLocation.getNickname()); + setInvariant(protoBufLocation.getInvariant()); + setType(Type.valueOf(protoBufLocation.getType())); + setUrgency(Urgency.valueOf(protoBufLocation.getUrgency())); + setX(protoBufLocation.getX()); + setY(protoBufLocation.getY()); + setColor(Color.valueOf(protoBufLocation.getColor())); + setNicknameX(protoBufLocation.getNicknameX()); + setNicknameY(protoBufLocation.getNicknameY()); + setInvariantX(protoBufLocation.getInvariantX()); + setInvariantY(protoBufLocation.getInvariantY()); + } + /** * Generates an id for this, and binds reachability analysis. */ diff --git a/src/main/java/ecdar/abstractions/Nail.java b/src/main/java/ecdar/abstractions/Nail.java index 5ed84d45..8c7cebbe 100644 --- a/src/main/java/ecdar/abstractions/Nail.java +++ b/src/main/java/ecdar/abstractions/Nail.java @@ -1,5 +1,6 @@ package ecdar.abstractions; +import EcdarProtoBuf.ComponentProtos; import ecdar.utility.helpers.Circular; import ecdar.utility.serialize.Serializable; import com.google.gson.Gson; @@ -39,6 +40,12 @@ public Nail(final JsonObject jsonObject) { deserialize(jsonObject); } + public Nail(ComponentProtos.Nail protoBufNail) { + setPropertyType(DisplayableEdge.PropertyType.valueOf(protoBufNail.getPropertyType())); + setPropertyX(protoBufNail.getPropertyX()); + setPropertyY(protoBufNail.getPropertyY()); + } + public double getX() { return x.get(); } diff --git a/src/main/java/ecdar/abstractions/Transition.java b/src/main/java/ecdar/abstractions/Transition.java new file mode 100644 index 00000000..829d2300 --- /dev/null +++ b/src/main/java/ecdar/abstractions/Transition.java @@ -0,0 +1,18 @@ +package ecdar.abstractions; + +import EcdarProtoBuf.ComponentProtos; +import EcdarProtoBuf.ObjectProtos; + +import java.util.ArrayList; +import java.util.List; + +public class Transition { + public final ArrayList edges = new ArrayList<>(); + + public Transition(ObjectProtos.Transition protoBufTransition) { + List protoBufEdges = (List) protoBufTransition.getField(ObjectProtos.Transition.getDescriptor().findFieldByName("edges")); + for (ComponentProtos.Edge protoBufEdge : protoBufEdges) { + edges.add(new Edge(protoBufEdge)); + } + } +} diff --git a/src/main/java/ecdar/controllers/ProcessController.java b/src/main/java/ecdar/controllers/ProcessController.java index 2934b54d..76661131 100755 --- a/src/main/java/ecdar/controllers/ProcessController.java +++ b/src/main/java/ecdar/controllers/ProcessController.java @@ -69,16 +69,14 @@ public void highlightEdges(final Edge[] edges) { for (int i = 0; i < edges.length; i++) { final Edge edge = edges[i]; - // Note that SimulationEdge contains a Location type from the UPPAAL libraries - // but we use a different Location class in our Maps final Location source = edge.getSourceLocation(); - String sourceName = source.getNickname(); + String sourceId = source.getId(); final Location target = edge.getTargetLocation(); - String targetName = target.getNickname(); + String targetId = target.getId(); // If target name is empty the edge is a self loop - if (Objects.equals(targetName, "")) { - targetName = sourceName; + if (Objects.equals(targetId, "")) { + targetId = sourceId; } boolean isSourceUniversal = false; @@ -88,41 +86,34 @@ public void highlightEdges(final Edge[] edges) { // This loop maps "Universal" to for example "U2" for (Map.Entry locEntry: locationPresentationMap.entrySet()) { if(locEntry.getKey().getType() == Location.Type.UNIVERSAL) { - if(sourceName.equals("Universal")) { - sourceName = locEntry.getKey().getId(); + if(sourceId.equals("Universal")) { + sourceId = locEntry.getKey().getId(); isSourceUniversal = true; } - if(targetName.equals("Universal")) { - targetName = locEntry.getKey().getId(); + if(targetId.equals("Universal")) { + targetId = locEntry.getKey().getId(); } } if(locEntry.getKey().getType() == Location.Type.INCONSISTENT) { - if(sourceName.equals("Inconsistent")) { - sourceName = locEntry.getKey().getId(); + if(sourceId.equals("Inconsistent")) { + sourceId = locEntry.getKey().getId(); } - if(targetName.equals("Inconsistent")) { - targetName = locEntry.getKey().getId(); + if(targetId.equals("Inconsistent")) { + targetId = locEntry.getKey().getId(); } } } - // The edge name may contain ! or ?, and we need to replace those so we can compare our stored edge - String edgeName = edge.getId(); -// if (edgeName.contains("?")) { -// edgeName = edgeName.replace("?", ""); -// } else if (edgeName.contains("!")) { -// edgeName = edgeName.replace("!", ""); -// } - // Self loop on a Universal locations means that the edge name should be mapped to * - if (isSourceUniversal && sourceName.equals(targetName)) { - edgeName = "*"; + String edgeId = edge.getId(); + if (isSourceUniversal && sourceId.equals(targetId)) { + edgeId = "*"; } - highlightEdge(edgeName, edge.getStatus(), sourceName, targetName); + highlightEdge(edgeId, edge.getStatus(), sourceId, targetId); } } @@ -145,32 +136,32 @@ public void unhighlightProcess() { private void highlightEdge(final String edgeName, final EdgeStatus edgeStatus, final String sourceName, final String targetName) { for (Map.Entry entry: edgePresentationMap.entrySet()) { final String keyName = entry.getKey().getSync(); - final String keySourceName = entry.getKey().getSourceLocation().getId(); - final String keyTargetName = entry.getKey().getTargetLocation().getId(); + final String keySourceId = entry.getKey().getSourceLocation().getId(); + final String keyTargetId = entry.getKey().getTargetLocation().getId(); // Multiple edges may have the same name, so we also check that the source and target match this edge if(keyName.equals(edgeName) && - keySourceName.equals(sourceName) && - keyTargetName.equals(targetName) && + keySourceId.equals(sourceName) && + keyTargetId.equals(targetName) && entry.getKey().ioStatus.get() == edgeStatus) { entry.getValue().getController().highlight(); - highlightEdgeLocations(keySourceName, keyTargetName); + highlightEdgeLocations(keySourceId, keyTargetId); } } } /** * Helper method that finds the source/target {@link SimLocationPresentation} and highlights it - * @param sourceName The name of the source location - * @param targetName The name of the target location + * @param sourceId The name of the source location + * @param targetId The name of the target location */ - private void highlightEdgeLocations(final String sourceName, final String targetName) { + private void highlightEdgeLocations(final String sourceId, final String targetId) { for (Map.Entry locEntry: locationPresentationMap.entrySet()) { - final String locName = locEntry.getKey().getId(); + final String locationId = locEntry.getKey().getId(); // Check if location is either source or target and highlight it - if(locName.equals(sourceName) || locName.equals(targetName)) { + if(locationId.equals(sourceId) || locationId.equals(targetId)) { locEntry.getValue().highlight(); } } @@ -178,13 +169,11 @@ private void highlightEdgeLocations(final String sourceName, final String target /** * Method that highlights all locations with the same name as the input {@link SimulationLocation} - * @param location The locations to highlight + * @param locationId The locations to highlight */ - public void highlightLocation(final SimulationLocation location) { + public void highlightLocation(final String locationId) { for (Map.Entry locEntry: locationPresentationMap.entrySet()) { - final String locName = locEntry.getKey().getId(); - - if(locName.equals(location.getName())) { + if(locEntry.getKey().getId().equals(locationId)) { locEntry.getValue().highlight(); } } diff --git a/src/main/java/ecdar/controllers/SimulatorController.java b/src/main/java/ecdar/controllers/SimulatorController.java index 1f00fcd3..4e2b31aa 100644 --- a/src/main/java/ecdar/controllers/SimulatorController.java +++ b/src/main/java/ecdar/controllers/SimulatorController.java @@ -94,6 +94,7 @@ private List findComponentsInCurrentSimulation() { //Show components from the system final SimulationHandler sm = Ecdar.getSimulationHandler(); List components = new ArrayList<>(); + components = Ecdar.getProject().getComponents(); // for (int i = 0; i < sm.getSystem().getNoOfProcesses(); i++) { // final int finalI = i; // when using a var in lambda it has to be final // final List filteredList = Ecdar.getProject().getComponents().filtered(component -> { diff --git a/src/main/java/ecdar/controllers/SimulatorOverviewController.java b/src/main/java/ecdar/controllers/SimulatorOverviewController.java index 3a6dbc13..8d02ad89 100644 --- a/src/main/java/ecdar/controllers/SimulatorOverviewController.java +++ b/src/main/java/ecdar/controllers/SimulatorOverviewController.java @@ -15,6 +15,7 @@ import javafx.scene.Group; import javafx.scene.control.ScrollPane; import javafx.scene.layout.*; +import javafx.util.Pair; import java.net.URL; import java.util.ArrayList; @@ -84,7 +85,7 @@ public void initialize(final URL location, final ResourceBundle resources) { * {@link Component}s which are needed to the processContainer. */ private void initializeProcessContainer() { - processContainer.translateXProperty().addListener((observable, oldValue, newValue)-> { + processContainer.translateXProperty().addListener((observable, oldValue, newValue) -> { processContainer.setTranslateX(0); }); //Sets the space between the processes @@ -96,7 +97,7 @@ private void initializeProcessContainer() { componentArrayList.addListener((ListChangeListener) c -> { final Map processes = new HashMap<>(); - while (c.next()){ + while (c.next()) { if (c.wasRemoved()) { clearOverview(); } else { @@ -107,7 +108,15 @@ private void initializeProcessContainer() { highlightProcessState(Ecdar.getSimulationHandler().getCurrentState()); processContainer.getChildren().addAll(processes.values()); processPresentations.putAll(processes); - }); + }); + + final Map processes = new HashMap<>(); + componentArrayList.forEach(o -> processes.put(o.getName(), new ProcessPresentation(o))); + + // Highlight the current state when the processes change + highlightProcessState(Ecdar.getSimulationHandler().getCurrentState()); + processContainer.getChildren().addAll(processes.values()); + processPresentations.putAll(processes); } /** @@ -134,7 +143,7 @@ private void initializeSimulationVariables() { }); }); Ecdar.getSimulationHandler().getSimulationClocks().addListener((InvalidationListener) obs -> { - if(processPresentations.size() == 0) return; + if (processPresentations.size() == 0) return; Ecdar.getSimulationHandler().getSimulationClocks().forEach((s, bigDecimal) -> { if (!s.equals("t(0)")) {// As t(0) does not belong to any process final String[] spittedString = s.split("\\."); @@ -151,9 +160,10 @@ private void initializeSimulationVariables() { * Removes {@link #processContainer} from the {@link #groupContainer}.
* In this way the {@link Component}s in the processContainer will then again be resizable, * as the class {@link Group} makes its children not resizeable. + * * @see Group */ - void removeProcessesFromGroup(){ + void removeProcessesFromGroup() { groupContainer.getChildren().removeAll(processContainer); } @@ -165,10 +175,11 @@ void removeProcessesFromGroup(){ * which is needed to show the {@link ProcessPresentation}s in the {@link #scrollPane}. * If the processContainer is already contained in the groupContainer * the method does nothing but return. + * * @see #removeProcessesFromGroup() */ - void addProcessesToGroup(){ - if(groupContainer.getChildren().contains(processContainer)) return; + void addProcessesToGroup() { + if (groupContainer.getChildren().contains(processContainer)) return; groupContainer.getChildren().add(processContainer); } @@ -177,8 +188,8 @@ void addProcessesToGroup(){ */ private void initializeZoom() { processContainer.scaleXProperty().addListener((observable, oldValue, newValue) -> { - if(newValue.doubleValue() > MAX_ZOOM_IN) isMaxZoomInReached = true; - if(newValue.doubleValue() < MAX_ZOOM_OUT) isMaxZoomOutReached = true; + if (newValue.doubleValue() > MAX_ZOOM_IN) isMaxZoomInReached = true; + if (newValue.doubleValue() < MAX_ZOOM_OUT) isMaxZoomOutReached = true; handleWidthOnScale(oldValue, newValue); }); @@ -206,7 +217,7 @@ private void initializeZoom() { private void initializeWindowResizing() { scrollPane.widthProperty().addListener((observable, oldValue, newValue) -> { final double width = (newValue.doubleValue()) * (1 + (1 - processContainer.getScaleX())); - if(processContainer.getScaleX() > 1) { //Zoomed in + if (processContainer.getScaleX() > 1) { //Zoomed in processContainer.setMinWidth(width); processContainer.setMaxWidth(width); } else if (processContainer.getScaleX() < 1) { //Zoomed out @@ -225,6 +236,7 @@ private void initializeWindowResizing() { /** * Increments the {@link #processContainer} scaleX and scaleY properties * which creates the zoom-in feeling. Resizing of the view is handled by {@link #handleWidthOnScale(Number, Number)} + * * @see FlowPane#scaleXProperty() * @see FlowPane#scaleYProperty() */ @@ -239,11 +251,12 @@ void zoomIn() { /** * Decrements the {@link #processContainer} scaleX and scaleY properties * which creates the zoom-in feeling. Resizing of the view is handled by {@link #handleWidthOnScale(Number, Number)} + * * @see FlowPane#scaleXProperty() * @see FlowPane#scaleYProperty() */ void zoomOut() { - if(isMaxZoomOutReached) return; + if (isMaxZoomOutReached) return; isMaxZoomInReached = false; processContainer.setScaleX(processContainer.getScaleX() * (1 / SCALE_DELTA)); processContainer.setScaleY(processContainer.getScaleY() * (1 / SCALE_DELTA)); @@ -251,11 +264,12 @@ void zoomOut() { /** * Resets the scaling of the {@link #processContainer}, and hereby the zoom + * * @see FlowPane#scaleXProperty() * @see FlowPane#scaleYProperty() */ void resetZoom() { - if(processContainer.getScaleX() == 1) return; + if (processContainer.getScaleX() == 1) return; resetZoom = true; isMaxZoomInReached = false; isMaxZoomOutReached = false; @@ -265,15 +279,16 @@ void resetZoom() { /** * Handles the scaling of the width of the {@link #processContainer} + * * @param oldValue the width of {@link #scrollPane} before the change * @param newValue the width of {@link #scrollPane} after the change */ private void handleWidthOnScale(final Number oldValue, final Number newValue) { - if(resetZoom) { //Zoom reset + if (resetZoom) { //Zoom reset resetZoom = false; processContainer.setMinWidth(scrollPane.getWidth() - SUPER_SPECIAL_SCROLLPANE_OFFSET); processContainer.setMaxWidth(scrollPane.getWidth() - SUPER_SPECIAL_SCROLLPANE_OFFSET); - } else if(oldValue.doubleValue() > newValue.doubleValue()) { //Zoom in + } else if (oldValue.doubleValue() > newValue.doubleValue()) { //Zoom in resetZoom = false; processContainer.setMinWidth(Math.round(processContainer.getWidth() * SCALE_DELTA)); processContainer.setMaxWidth(Math.round(processContainer.getWidth() * SCALE_DELTA)); @@ -293,7 +308,7 @@ private void initializeHighlighting() { // If the new transition is not null, we want to highlight the locations and edges in the new value // otherwise we highlight the current state - if(newTransition != null) { + if (newTransition != null) { highlightProcessTransition(newTransition); } else { highlightProcessState(Ecdar.getSimulationHandler().getCurrentState()); @@ -305,7 +320,7 @@ private void initializeHighlighting() { // If the new state is not null, we want to highlight the locations in the new value // otherwise we highlight the current state - if(newState != null) { + if (newState != null) { highlightProcessState(newState); } else { highlightProcessState(Ecdar.getSimulationHandler().getCurrentState()); @@ -317,6 +332,7 @@ private void initializeHighlighting() { * Highlights all the processes involved in the transition. * Finds the processes involved in the transition (processes with edges in the transition) and highlights their edges * Also fades processes that are not active in the selected transition + * * @param transition The transition for which we highlight the involved processes */ public void highlightProcessTransition(final Transition transition) { @@ -340,7 +356,7 @@ public void highlightProcessTransition(final Transition transition) { * Unhighlights all processes */ public void unhighlightProcesses() { - for(final ProcessPresentation presentation: processPresentations.values()) { + for (final ProcessPresentation presentation : processPresentations.values()) { presentation.getController().unhighlightProcess(); presentation.showActive(); } @@ -348,18 +364,19 @@ public void unhighlightProcesses() { /** * Finds the processes for the input locations in the input {@link SimulationState} and highlights the locations. + * * @param state The state with the locations to highlight */ public void highlightProcessState(final SimulationState state) { for (int i = 0; i < state.getLocations().size(); i++) { - final Location loc = state.getLocations().get(i); + final Pair loc = state.getLocations().get(i); - for(final ProcessPresentation presentation: processPresentations.values()) { + for (final ProcessPresentation presentation : processPresentations.values()) { final String processName = presentation.getController().getComponent().getName(); -// if(processName.equals(loc.getProcess().getName())) { -// presentation.getController().highlightLocation(loc); -// } + if (processName.equals(loc.getKey())) { + presentation.getController().highlightLocation(loc.getValue()); + } } } } diff --git a/src/main/java/ecdar/controllers/TracePaneElementController.java b/src/main/java/ecdar/controllers/TracePaneElementController.java index a733b15f..6ace31df 100755 --- a/src/main/java/ecdar/controllers/TracePaneElementController.java +++ b/src/main/java/ecdar/controllers/TracePaneElementController.java @@ -45,11 +45,11 @@ public class TracePaneElementController implements Initializable { public void initialize(URL location, ResourceBundle resources) { Ecdar.getSimulationHandler().getTraceLog().addListener((ListChangeListener) c -> { while (c.next()) { - for(final SimulationState state: c.getAddedSubList()) { + for (final SimulationState state : c.getAddedSubList()) { insertTraceState(state, true); } - for(final SimulationState state: c.getRemoved()) { + for (final SimulationState state : c.getRemoved()) { traceList.getChildren().remove(transitionPresentationMap.get(state)); transitionPresentationMap.remove(state); } @@ -67,7 +67,7 @@ public void initialize(URL location, ResourceBundle resources) { */ private void initializeTraceExpand() { isTraceExpanded.addListener((obs, oldVal, newVal) -> { - if(newVal) { + if (newVal) { showTrace(); expandTraceIcon.setIconLiteral("gmi-expand-less"); expandTraceIcon.setIconSize(24); @@ -104,7 +104,8 @@ private void showTrace() { /** * Instantiates a {@link TransitionPresentation} for a {@link SimulationState} and adds it to the view - * @param state The state the should be inserted into the trace log + * + * @param state The state the should be inserted into the trace log * @param shouldAnimate A boolean that indicates whether the trace should fade in when added to the view */ private void insertTraceState(final SimulationState state, final boolean shouldAnimate) { @@ -135,9 +136,9 @@ private void insertTraceState(final SimulationState state, final boolean shouldA transitionPresentation.getController().setTitle(title); // Only insert the presentation into the view if the trace is expanded - if(isTraceExpanded.get()) { + if (isTraceExpanded.get()) { traceList.getChildren().add(transitionPresentation); - if(shouldAnimate) { + if (shouldAnimate) { transitionPresentation.playFadeAnimation(); } } @@ -145,24 +146,27 @@ private void insertTraceState(final SimulationState state, final boolean shouldA /** * A helper method that returns a string representing a state in the trace log + * * @param state The SimulationState to represent * @return A string representing the state */ private String traceString(SimulationState state) { - String title = "("; + StringBuilder title = new StringBuilder("("); int length = state.getLocations().size(); - for (int i = 0; i < length ; i++) { - Location loc = state.getLocations().get(i); + for (int i = 0; i < length; i++) { + Location loc = Ecdar.getProject() + .findComponent(state.getLocations().get(i).getKey()) + .findLocation(state.getLocations().get(i).getValue()); String locationName = loc.getNickname(); - if (i == length-1) { - title += locationName; + if (i == length - 1) { + title.append(locationName); } else { - title += locationName + ", "; + title.append(locationName).append(", "); } } - title += ")"; + title.append(")"); - return title; + return title.toString(); } /** @@ -170,7 +174,7 @@ private String traceString(SimulationState state) { */ @FXML private void expandTrace() { - if(isTraceExpanded.get()) { + if (isTraceExpanded.get()) { isTraceExpanded.set(false); } else { isTraceExpanded.set(true); diff --git a/src/main/java/ecdar/simulation/SimulationHandler.java b/src/main/java/ecdar/simulation/SimulationHandler.java index 65d2ea73..6305a5e4 100755 --- a/src/main/java/ecdar/simulation/SimulationHandler.java +++ b/src/main/java/ecdar/simulation/SimulationHandler.java @@ -18,9 +18,9 @@ */ public class SimulationHandler { public static final String QUERY_PREFIX = "Query: "; - private ObjectProperty currentConcreteState; - private ObjectProperty initialConcreteState; - private ObjectProperty currentTime; + private ObjectProperty currentConcreteState = new SimpleObjectProperty<>(new SimulationState(null)); + private ObjectProperty initialConcreteState = new SimpleObjectProperty<>(new SimulationState(null)); + private ObjectProperty currentTime = new SimpleObjectProperty<>(); private BigDecimal delay; private ArrayList edgesSelected; private EcdarSystem system; diff --git a/src/main/java/ecdar/simulation/SimulationState.java b/src/main/java/ecdar/simulation/SimulationState.java index 933864f1..f246ce5a 100644 --- a/src/main/java/ecdar/simulation/SimulationState.java +++ b/src/main/java/ecdar/simulation/SimulationState.java @@ -1,11 +1,22 @@ package ecdar.simulation; +import EcdarProtoBuf.ObjectProtos; import ecdar.abstractions.Location; +import javafx.util.Pair; import java.math.BigDecimal; import java.util.ArrayList; +import java.util.stream.Collectors; public class SimulationState { + private final ArrayList> locations; + + public SimulationState(ObjectProtos.StateTuple protoBufState) { + if (protoBufState != null) locations = protoBufState.getLocationsList().stream().map(lt -> new Pair<>(lt.getComponentName(), lt.getId())).collect(Collectors.toCollection(ArrayList::new)); + else locations = new ArrayList<>(); + // ToDo: Handle federations + } + public void setTime(BigDecimal value) { // ToDo: Implement } @@ -35,8 +46,7 @@ public double doubleValue() { }; } - public ArrayList getLocations() { - // ToDo: Implement - return new ArrayList<>(); + public ArrayList> getLocations() { + return locations; } } diff --git a/src/main/java/ecdar/simulation/SimulationStateSuccessor.java b/src/main/java/ecdar/simulation/SimulationStateSuccessor.java index 0c519adb..84370dd4 100644 --- a/src/main/java/ecdar/simulation/SimulationStateSuccessor.java +++ b/src/main/java/ecdar/simulation/SimulationStateSuccessor.java @@ -10,6 +10,6 @@ public ArrayList getTransitions() { public SimulationState getState() { // ToDo: Implement - return new SimulationState(); + return new SimulationState(null); } } diff --git a/src/main/proto b/src/main/proto index b4d83889..42084f76 160000 --- a/src/main/proto +++ b/src/main/proto @@ -1 +1 @@ -Subproject commit b4d83889935f6f6e6d0a0a5547c28b2ab1388cd1 +Subproject commit 42084f767e42c8c4b9285c04342ace57645448a3 diff --git a/src/main/resources/ecdar/presentations/ProcessPresentation.fxml b/src/main/resources/ecdar/presentations/ProcessPresentation.fxml new file mode 100755 index 00000000..e4ba6b20 --- /dev/null +++ b/src/main/resources/ecdar/presentations/ProcessPresentation.fxml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+ + + + + + + + +
+
+
+ +
+
+ + + + + +
+
+
\ No newline at end of file diff --git a/src/main/resources/ecdar/presentations/RightSimPanePresentation.fxml b/src/main/resources/ecdar/presentations/RightSimPanePresentation.fxml index 3ed05348..4697fb97 100755 --- a/src/main/resources/ecdar/presentations/RightSimPanePresentation.fxml +++ b/src/main/resources/ecdar/presentations/RightSimPanePresentation.fxml @@ -17,7 +17,7 @@ AnchorPane.rightAnchor="0" styleClass="edge-to-edge"> - + From 260dd220fa96093e00d4c203c86698529269ff1c Mon Sep 17 00:00:00 2001 From: Niels Vistisen Date: Wed, 13 Jul 2022 15:37:33 +0200 Subject: [PATCH 005/158] Minor clean up --- src/main/java/ecdar/controllers/ProcessController.java | 4 +--- src/main/java/ecdar/simulation/SimulationEdge.java | 8 -------- src/main/java/ecdar/simulation/SimulationLocation.java | 8 -------- 3 files changed, 1 insertion(+), 19 deletions(-) delete mode 100644 src/main/java/ecdar/simulation/SimulationEdge.java delete mode 100644 src/main/java/ecdar/simulation/SimulationLocation.java diff --git a/src/main/java/ecdar/controllers/ProcessController.java b/src/main/java/ecdar/controllers/ProcessController.java index 76661131..c80f319b 100755 --- a/src/main/java/ecdar/controllers/ProcessController.java +++ b/src/main/java/ecdar/controllers/ProcessController.java @@ -4,8 +4,6 @@ import ecdar.abstractions.*; import ecdar.presentations.SimEdgePresentation; import ecdar.presentations.SimLocationPresentation; -import ecdar.simulation.SimulationEdge; -import ecdar.simulation.SimulationLocation; import javafx.animation.Interpolator; import javafx.animation.Transition; import javafx.beans.property.ObjectProperty; @@ -168,7 +166,7 @@ private void highlightEdgeLocations(final String sourceId, final String targetId } /** - * Method that highlights all locations with the same name as the input {@link SimulationLocation} + * Method that highlights all locations with the input ID * @param locationId The locations to highlight */ public void highlightLocation(final String locationId) { diff --git a/src/main/java/ecdar/simulation/SimulationEdge.java b/src/main/java/ecdar/simulation/SimulationEdge.java deleted file mode 100644 index 696f573f..00000000 --- a/src/main/java/ecdar/simulation/SimulationEdge.java +++ /dev/null @@ -1,8 +0,0 @@ -package ecdar.simulation; - -public class SimulationEdge { - public String getName() { - // ToDo: Implement - return "Edge name"; - } -} diff --git a/src/main/java/ecdar/simulation/SimulationLocation.java b/src/main/java/ecdar/simulation/SimulationLocation.java deleted file mode 100644 index 5bcbba6a..00000000 --- a/src/main/java/ecdar/simulation/SimulationLocation.java +++ /dev/null @@ -1,8 +0,0 @@ -package ecdar.simulation; - -public class SimulationLocation { - public String getName() { - // ToDo: Implement - return "Location name"; - } -} From e08425072e2bf7e322c79fea59b069421da1e528 Mon Sep 17 00:00:00 2001 From: Niels Vistisen Date: Wed, 13 Jul 2022 15:42:30 +0200 Subject: [PATCH 006/158] WIP: Spacing for trace and transition headers added --- .../ecdar/presentations/LeftSimPanePresentation.fxml | 1 - .../ecdar/presentations/TracePaneElementPresentation.fxml | 4 ++++ .../presentations/TransitionPaneElementPresentation.fxml | 3 +++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/main/resources/ecdar/presentations/LeftSimPanePresentation.fxml b/src/main/resources/ecdar/presentations/LeftSimPanePresentation.fxml index ea343221..e80df29e 100755 --- a/src/main/resources/ecdar/presentations/LeftSimPanePresentation.fxml +++ b/src/main/resources/ecdar/presentations/LeftSimPanePresentation.fxml @@ -10,7 +10,6 @@ type="StackPane" fx:id="root" fx:controller="ecdar.controllers.LeftSimPaneController"> - + + + + + + + Date: Thu, 14 Jul 2022 07:56:01 +0200 Subject: [PATCH 007/158] WIP: BackendDriver usage for simulation started --- .../java/ecdar/backend/BackendDriver.java | 29 +++++++++++++++---- .../ecdar/simulation/SimulationHandler.java | 7 ++--- 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/src/main/java/ecdar/backend/BackendDriver.java b/src/main/java/ecdar/backend/BackendDriver.java index 4e0aa568..1eb4f77a 100644 --- a/src/main/java/ecdar/backend/BackendDriver.java +++ b/src/main/java/ecdar/backend/BackendDriver.java @@ -8,6 +8,7 @@ import ecdar.abstractions.BackendInstance; import ecdar.abstractions.Component; import ecdar.abstractions.QueryState; +import ecdar.simulation.SimulationState; import io.grpc.*; import io.grpc.stub.StreamObserver; import org.springframework.util.SocketUtils; @@ -139,11 +140,7 @@ public void closeAllBackendConnections() throws IOException { private void executeQuery(ExecutableQuery executableQuery) { if (executableQuery.queryListener.getQuery().getQueryState() == QueryState.UNKNOWN) return; - // Get available connection or start new - final BackendConnection backendConnection = openBackendConnections.stream() - .filter((connection) -> connection.getBackendInstance() == null || connection.getBackendInstance().equals(executableQuery.backend)) - .findFirst() - .orElseGet(() -> startNewBackendConnection(executableQuery.backend)); + final BackendConnection backendConnection = getBackendConnection(executableQuery.backend); // If the connection is null, there are no available connections, // and it was not possible to start a new one @@ -224,6 +221,24 @@ public void onCompleted() { backendConnection.getStub().withDeadlineAfter(deadlineForResponses, TimeUnit.MILLISECONDS).updateComponents(componentsBuilder.build(), observer); } + /** + * Filters the list of open {@link BackendConnection}s to the specified {@link BackendInstance} and returns the + * first match or attempts to start a new connection if none is found. + * If a new connection is attempted to be started and no ports are free within the specified port range for the + * BackendInstance + * + * @param backend backend instance to get a connection to (e.g. Reveaal, j-Ecdar, custom_engine) + * @return a BackendConnection object linked to backend, either from the open backend connection list + * or a newly started connection. If no match is found in the list and the attempt to start a new connection failed, + * null is returned. + */ + private BackendConnection getBackendConnection(BackendInstance backend) { + return openBackendConnections.stream() + .filter((connection) -> connection.getBackendInstance() == null || connection.getBackendInstance().equals(backend)) + .findFirst() + .orElseGet(() -> startNewBackendConnection(backend)); + } + private BackendConnection startNewBackendConnection(BackendInstance backend) { Process p = null; String hostAddress = (backend.isLocal() ? "127.0.0.1" : backend.getBackendLocation()); @@ -349,6 +364,10 @@ public void run() { } } + public SimulationState getInitialSimulationState() { + return new SimulationState(null); + } + private class ExecutableQuery { private final String query; private final BackendInstance backend; diff --git a/src/main/java/ecdar/simulation/SimulationHandler.java b/src/main/java/ecdar/simulation/SimulationHandler.java index 6305a5e4..4e089f12 100755 --- a/src/main/java/ecdar/simulation/SimulationHandler.java +++ b/src/main/java/ecdar/simulation/SimulationHandler.java @@ -18,8 +18,8 @@ */ public class SimulationHandler { public static final String QUERY_PREFIX = "Query: "; - private ObjectProperty currentConcreteState = new SimpleObjectProperty<>(new SimulationState(null)); - private ObjectProperty initialConcreteState = new SimpleObjectProperty<>(new SimulationState(null)); + private ObjectProperty currentConcreteState; + private ObjectProperty initialConcreteState; private ObjectProperty currentTime = new SimpleObjectProperty<>(); private BigDecimal delay; private ArrayList edgesSelected; @@ -349,8 +349,7 @@ public ObservableMap getSimulationClocks() { * @return the initial {@link SimulationState} of this simulation */ public SimulationState getInitialConcreteState() { - // ToDo: Implement - return initialConcreteState.get(); + return Ecdar.getBackendDriver().getInitialSimulationState(); } public ObjectProperty initialConcreteStateProperty() { From 8a88c62d7cead2630212a931f0652d1a38d7f8af Mon Sep 17 00:00:00 2001 From: Niels Vistisen Date: Thu, 14 Jul 2022 15:19:22 +0200 Subject: [PATCH 008/158] WIP: Components can be shown within the simulator --- .../java/ecdar/backend/BackendDriver.java | 6 +++++- .../ecdar/controllers/EcdarController.java | 2 +- .../ecdar/controllers/ProcessController.java | 20 +++++++++---------- .../controllers/SimulatorController.java | 6 ++---- .../SimulatorOverviewController.java | 6 ++---- .../presentations/SimulatorPresentation.java | 1 - .../ecdar/simulation/SimulationHandler.java | 8 ++++---- .../simulation/SimulationStateSuccessor.java | 4 +++- .../presentations/ProcessPresentation.fxml | 1 + 9 files changed, 27 insertions(+), 27 deletions(-) diff --git a/src/main/java/ecdar/backend/BackendDriver.java b/src/main/java/ecdar/backend/BackendDriver.java index 1eb4f77a..f8e9bedc 100644 --- a/src/main/java/ecdar/backend/BackendDriver.java +++ b/src/main/java/ecdar/backend/BackendDriver.java @@ -2,6 +2,7 @@ import EcdarProtoBuf.ComponentProtos; import EcdarProtoBuf.EcdarBackendGrpc; +import EcdarProtoBuf.ObjectProtos; import EcdarProtoBuf.QueryProtos; import com.google.protobuf.Empty; import ecdar.Ecdar; @@ -11,6 +12,7 @@ import ecdar.simulation.SimulationState; import io.grpc.*; import io.grpc.stub.StreamObserver; +import javafx.util.Pair; import org.springframework.util.SocketUtils; import java.io.*; @@ -365,7 +367,9 @@ public void run() { } public SimulationState getInitialSimulationState() { - return new SimulationState(null); + SimulationState state = new SimulationState(ObjectProtos.StateTuple.newBuilder().getDefaultInstanceForType()); + state.getLocations().add(new Pair<>(Ecdar.getProject().getComponents().get(0).getName(), Ecdar.getProject().getComponents().get(0).getLocations().get(0).getId())); + return state; } private class ExecutableQuery { diff --git a/src/main/java/ecdar/controllers/EcdarController.java b/src/main/java/ecdar/controllers/EcdarController.java index 041485cb..39384a9d 100644 --- a/src/main/java/ecdar/controllers/EcdarController.java +++ b/src/main/java/ecdar/controllers/EcdarController.java @@ -998,7 +998,7 @@ private void enterEditorMode() { private void enterSimulatorMode() { // ToDo NIELS: Consider implementing willShow and willHide to handle general elements that should only be available for one of the modes // ecdarPresentation.getController().willHide(); -// simulatorPresentation.getController().willShow(); + simulatorPresentation.getController().willShow(); borderPane.setCenter(simulatorPresentation); leftPane.getChildren().clear(); diff --git a/src/main/java/ecdar/controllers/ProcessController.java b/src/main/java/ecdar/controllers/ProcessController.java index c80f319b..6ce42fc2 100755 --- a/src/main/java/ecdar/controllers/ProcessController.java +++ b/src/main/java/ecdar/controllers/ProcessController.java @@ -63,10 +63,8 @@ private void initializeValues() { * Highlights the edges and accompanying source/target locations in the process * @param edges The edges to highlight */ - public void highlightEdges(final Edge[] edges) { - for (int i = 0; i < edges.length; i++) { - final Edge edge = edges[i]; - + public void highlightEdges(final ArrayList edges) { + for (final Edge edge : edges) { final Location source = edge.getSourceLocation(); String sourceId = source.getId(); final Location target = edge.getTargetLocation(); @@ -82,24 +80,24 @@ public void highlightEdges(final Edge[] edges) { // Iterate through all locations to check for Universal and Inconsistent locations // The name of a Universal location may be "U2" in our system, but it is mapped to "Universal" in the engine // This loop maps "Universal" to for example "U2" - for (Map.Entry locEntry: locationPresentationMap.entrySet()) { - if(locEntry.getKey().getType() == Location.Type.UNIVERSAL) { - if(sourceId.equals("Universal")) { + for (Map.Entry locEntry : locationPresentationMap.entrySet()) { + if (locEntry.getKey().getType() == Location.Type.UNIVERSAL) { + if (sourceId.equals("Universal")) { sourceId = locEntry.getKey().getId(); isSourceUniversal = true; } - if(targetId.equals("Universal")) { + if (targetId.equals("Universal")) { targetId = locEntry.getKey().getId(); } } - if(locEntry.getKey().getType() == Location.Type.INCONSISTENT) { - if(sourceId.equals("Inconsistent")) { + if (locEntry.getKey().getType() == Location.Type.INCONSISTENT) { + if (sourceId.equals("Inconsistent")) { sourceId = locEntry.getKey().getId(); } - if(targetId.equals("Inconsistent")) { + if (targetId.equals("Inconsistent")) { targetId = locEntry.getKey().getId(); } } diff --git a/src/main/java/ecdar/controllers/SimulatorController.java b/src/main/java/ecdar/controllers/SimulatorController.java index 4e2b31aa..639c1ff1 100644 --- a/src/main/java/ecdar/controllers/SimulatorController.java +++ b/src/main/java/ecdar/controllers/SimulatorController.java @@ -3,8 +3,6 @@ import ecdar.Ecdar; import ecdar.abstractions.*; import ecdar.simulation.SimulationHandler; -import ecdar.presentations.LeftSimPanePresentation; -import ecdar.presentations.RightSimPanePresentation; import ecdar.presentations.SimulatorOverviewPresentation; import ecdar.simulation.SimulationState; import ecdar.simulation.Transition; @@ -13,9 +11,7 @@ import javafx.beans.property.SimpleDoubleProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.fxml.Initializable; -import javafx.scene.control.Label; import javafx.scene.layout.StackPane; -import javafx.scene.shape.Rectangle; import java.net.URL; import java.util.ArrayList; @@ -51,6 +47,8 @@ public void willShow() { final SimulationHandler sm = Ecdar.getSimulationHandler(); boolean shouldSimulationBeReset = true; + if (sm.getCurrentState() == null) sm.initialStep(); // ToDo NIELS: Find better solution + //Have the user left a trace or is he simulating a query if (sm.traceLog.size() >= 2 || sm.getCurrentSimulation().contains(SimulationHandler.QUERY_PREFIX)) { shouldSimulationBeReset = false; diff --git a/src/main/java/ecdar/controllers/SimulatorOverviewController.java b/src/main/java/ecdar/controllers/SimulatorOverviewController.java index 8d02ad89..9af4af76 100644 --- a/src/main/java/ecdar/controllers/SimulatorOverviewController.java +++ b/src/main/java/ecdar/controllers/SimulatorOverviewController.java @@ -105,7 +105,7 @@ private void initializeProcessContainer() { } } // Highlight the current state when the processes change - highlightProcessState(Ecdar.getSimulationHandler().getCurrentState()); + highlightProcessState(Ecdar.getSimulationHandler().getCurrentState()); // ToDo NIELS: Throws NullPointerException inside method due to currentState processContainer.getChildren().addAll(processes.values()); processPresentations.putAll(processes); }); @@ -113,8 +113,6 @@ private void initializeProcessContainer() { final Map processes = new HashMap<>(); componentArrayList.forEach(o -> processes.put(o.getName(), new ProcessPresentation(o))); - // Highlight the current state when the processes change - highlightProcessState(Ecdar.getSimulationHandler().getCurrentState()); processContainer.getChildren().addAll(processes.values()); processPresentations.putAll(processes); } @@ -345,7 +343,7 @@ public void highlightProcessTransition(final Transition transition) { for (final ProcessPresentation processPresentation : processPresentations.values()) { // Find the processes that have edges involved in this transition - processPresentation.getController().highlightEdges(edges.toArray(new Edge[0])); + processPresentation.getController().highlightEdges(edges); processesToHide.remove(processPresentation); } diff --git a/src/main/java/ecdar/presentations/SimulatorPresentation.java b/src/main/java/ecdar/presentations/SimulatorPresentation.java index 2d1a5105..255b7dd1 100755 --- a/src/main/java/ecdar/presentations/SimulatorPresentation.java +++ b/src/main/java/ecdar/presentations/SimulatorPresentation.java @@ -10,7 +10,6 @@ public SimulatorPresentation() { controller = new EcdarFXMLLoader().loadAndGetController("SimulatorPresentation.fxml", this); } - /** * The way to get the associated/linked controller of this presenter * @return the controller linked to this presenter diff --git a/src/main/java/ecdar/simulation/SimulationHandler.java b/src/main/java/ecdar/simulation/SimulationHandler.java index 4e089f12..395d5027 100755 --- a/src/main/java/ecdar/simulation/SimulationHandler.java +++ b/src/main/java/ecdar/simulation/SimulationHandler.java @@ -18,8 +18,8 @@ */ public class SimulationHandler { public static final String QUERY_PREFIX = "Query: "; - private ObjectProperty currentConcreteState; - private ObjectProperty initialConcreteState; + private ObjectProperty currentConcreteState = new SimpleObjectProperty<>(); + private ObjectProperty initialConcreteState = new SimpleObjectProperty<>(); private ObjectProperty currentTime = new SimpleObjectProperty<>(); private BigDecimal delay; private ArrayList edgesSelected; @@ -73,8 +73,8 @@ private void initializeSimulation() { this.simulationVariables.clear(); this.simulationClocks.clear(); this.traceLog.clear(); - this.currentConcreteState = new SimpleObjectProperty<>(getInitialConcreteState()); - this.initialConcreteState = new SimpleObjectProperty<>(getInitialConcreteState()); + this.currentConcreteState.set(getInitialConcreteState()); + this.initialConcreteState.set(getInitialConcreteState()); this.currentTime = new SimpleObjectProperty<>(BigDecimal.ZERO); //Preparation for the simulation diff --git a/src/main/java/ecdar/simulation/SimulationStateSuccessor.java b/src/main/java/ecdar/simulation/SimulationStateSuccessor.java index 84370dd4..1b0c4cba 100644 --- a/src/main/java/ecdar/simulation/SimulationStateSuccessor.java +++ b/src/main/java/ecdar/simulation/SimulationStateSuccessor.java @@ -1,5 +1,7 @@ package ecdar.simulation; +import ecdar.Ecdar; + import java.util.ArrayList; public class SimulationStateSuccessor { @@ -10,6 +12,6 @@ public ArrayList getTransitions() { public SimulationState getState() { // ToDo: Implement - return new SimulationState(null); + return Ecdar.getBackendDriver().getInitialSimulationState(); } } diff --git a/src/main/resources/ecdar/presentations/ProcessPresentation.fxml b/src/main/resources/ecdar/presentations/ProcessPresentation.fxml index e4ba6b20..6b4b8f77 100755 --- a/src/main/resources/ecdar/presentations/ProcessPresentation.fxml +++ b/src/main/resources/ecdar/presentations/ProcessPresentation.fxml @@ -50,6 +50,7 @@ + \ No newline at end of file From a22f5591213f7e67e77ef73fdb7d8a4efd2abe87 Mon Sep 17 00:00:00 2001 From: Niels Vistisen Date: Fri, 15 Jul 2022 07:55:33 +0200 Subject: [PATCH 009/158] ProtoBuf messages updated and no longer used messages commented out of implementation --- src/main/java/ecdar/abstractions/Edge.java | 30 +++++++++---------- .../java/ecdar/abstractions/Location.java | 29 +++++++++--------- src/main/java/ecdar/abstractions/Nail.java | 11 +++---- .../java/ecdar/abstractions/Transition.java | 22 ++++++++++---- src/main/proto | 2 +- .../java/ecdar/simulation/SimulationTest.java | 10 +++++++ 6 files changed, 64 insertions(+), 40 deletions(-) create mode 100644 src/test/java/ecdar/simulation/SimulationTest.java diff --git a/src/main/java/ecdar/abstractions/Edge.java b/src/main/java/ecdar/abstractions/Edge.java index 0229d51b..d3dc4ddf 100644 --- a/src/main/java/ecdar/abstractions/Edge.java +++ b/src/main/java/ecdar/abstractions/Edge.java @@ -48,21 +48,21 @@ public Edge(final JsonObject jsonObject, final Component component) { deserialize(jsonObject, component); bindReachabilityAnalysis(); } - - public Edge(ComponentProtos.Edge protoBufEdge) { - setId(protoBufEdge.getId()); - setSourceLocation(new Location(protoBufEdge.getSourceLocation())); - setTargetLocation(new Location(protoBufEdge.getTargetLocation())); - setStatus(protoBufEdge.getStatus().equals("INPUT") ? EdgeStatus.INPUT : EdgeStatus.OUTPUT); - setSelect(protoBufEdge.getSelect()); - setGuard(protoBufEdge.getGuard()); - setUpdate(protoBufEdge.getUpdate()); - setSync(protoBufEdge.getSync()); - - for (ComponentProtos.Nail protoBufNail : protoBufEdge.getNailList()) { - getNails().add(new Nail(protoBufNail)); - } - } + // ToDo NIELS: Comment in, when edges should be received through ProtoBuf +// public Edge(ComponentProtos.Edge protoBufEdge) { +// setId(protoBufEdge.getId()); +// setSourceLocation(new Location(protoBufEdge.getSourceLocation())); +// setTargetLocation(new Location(protoBufEdge.getTargetLocation())); +// setStatus(protoBufEdge.getStatus().equals("INPUT") ? EdgeStatus.INPUT : EdgeStatus.OUTPUT); +// setSelect(protoBufEdge.getSelect()); +// setGuard(protoBufEdge.getGuard()); +// setUpdate(protoBufEdge.getUpdate()); +// setSync(protoBufEdge.getSync()); +// +// for (ComponentProtos.Nail protoBufNail : protoBufEdge.getNailList()) { +// getNails().add(new Nail(protoBufNail)); +// } +// } public String getSync() { return sync.get(); diff --git a/src/main/java/ecdar/abstractions/Location.java b/src/main/java/ecdar/abstractions/Location.java index 1d0ca6ab..61ebc191 100644 --- a/src/main/java/ecdar/abstractions/Location.java +++ b/src/main/java/ecdar/abstractions/Location.java @@ -88,20 +88,21 @@ public Location(final JsonObject jsonObject) { bindReachabilityAnalysis(); } - public Location(ComponentProtos.Location protoBufLocation) { - setId(protoBufLocation.getId()); - setNickname(protoBufLocation.getNickname()); - setInvariant(protoBufLocation.getInvariant()); - setType(Type.valueOf(protoBufLocation.getType())); - setUrgency(Urgency.valueOf(protoBufLocation.getUrgency())); - setX(protoBufLocation.getX()); - setY(protoBufLocation.getY()); - setColor(Color.valueOf(protoBufLocation.getColor())); - setNicknameX(protoBufLocation.getNicknameX()); - setNicknameY(protoBufLocation.getNicknameY()); - setInvariantX(protoBufLocation.getInvariantX()); - setInvariantY(protoBufLocation.getInvariantY()); - } + // ToDo NIELS: Comment in, when location should be received through ProtoBuf +// public Location(ComponentProtos.Location protoBufLocation) { +// setId(protoBufLocation.getId()); +// setNickname(protoBufLocation.getNickname()); +// setInvariant(protoBufLocation.getInvariant()); +// setType(Type.valueOf(protoBufLocation.getType())); +// setUrgency(Urgency.valueOf(protoBufLocation.getUrgency())); +// setX(protoBufLocation.getX()); +// setY(protoBufLocation.getY()); +// setColor(Color.valueOf(protoBufLocation.getColor())); +// setNicknameX(protoBufLocation.getNicknameX()); +// setNicknameY(protoBufLocation.getNicknameY()); +// setInvariantX(protoBufLocation.getInvariantX()); +// setInvariantY(protoBufLocation.getInvariantY()); +// } /** * Generates an id for this, and binds reachability analysis. diff --git a/src/main/java/ecdar/abstractions/Nail.java b/src/main/java/ecdar/abstractions/Nail.java index 8c7cebbe..f7555432 100644 --- a/src/main/java/ecdar/abstractions/Nail.java +++ b/src/main/java/ecdar/abstractions/Nail.java @@ -40,11 +40,12 @@ public Nail(final JsonObject jsonObject) { deserialize(jsonObject); } - public Nail(ComponentProtos.Nail protoBufNail) { - setPropertyType(DisplayableEdge.PropertyType.valueOf(protoBufNail.getPropertyType())); - setPropertyX(protoBufNail.getPropertyX()); - setPropertyY(protoBufNail.getPropertyY()); - } + // ToDo NIELS: Comment in, when location should be received through ProtoBuf +// public Nail(ComponentProtos.Nail protoBufNail) { +// setPropertyType(DisplayableEdge.PropertyType.valueOf(protoBufNail.getPropertyType())); +// setPropertyX(protoBufNail.getPropertyX()); +// setPropertyY(protoBufNail.getPropertyY()); +// } public double getX() { return x.get(); diff --git a/src/main/java/ecdar/abstractions/Transition.java b/src/main/java/ecdar/abstractions/Transition.java index 829d2300..9b9d25be 100644 --- a/src/main/java/ecdar/abstractions/Transition.java +++ b/src/main/java/ecdar/abstractions/Transition.java @@ -1,18 +1,30 @@ package ecdar.abstractions; -import EcdarProtoBuf.ComponentProtos; import EcdarProtoBuf.ObjectProtos; +import ecdar.Ecdar; import java.util.ArrayList; +import java.util.Collection; import java.util.List; +import java.util.stream.Collectors; public class Transition { public final ArrayList edges = new ArrayList<>(); public Transition(ObjectProtos.Transition protoBufTransition) { - List protoBufEdges = (List) protoBufTransition.getField(ObjectProtos.Transition.getDescriptor().findFieldByName("edges")); - for (ComponentProtos.Edge protoBufEdge : protoBufEdges) { - edges.add(new Edge(protoBufEdge)); - } + List protoBufEdges = (List) protoBufTransition.getField(ObjectProtos.Transition.getDescriptor().findFieldByName("edges")); + List componentNames = protoBufEdges.stream().map(ObjectProtos.Transition.EdgeTuple::getComponentName).collect(Collectors.toList()); + List edgeIds = protoBufEdges.stream().map(ObjectProtos.Transition.EdgeTuple::getId).collect(Collectors.toList()); + + // For each affected component, find each affected edge within that component, and add these edges to the edges list + List affectedComponents = Ecdar.getProject().getComponents().stream().filter(c -> componentNames.contains(c.getName())).collect(Collectors.toList()); + edges.addAll(affectedComponents.stream() + .map(c -> c.getEdges() + .stream() + .filter(e -> edgeIds.contains(e.getId())) + .collect(Collectors.toList())) + .flatMap(Collection::stream) + .collect(Collectors.toList()) + ); } } diff --git a/src/main/proto b/src/main/proto index 42084f76..661d35fb 160000 --- a/src/main/proto +++ b/src/main/proto @@ -1 +1 @@ -Subproject commit 42084f767e42c8c4b9285c04342ace57645448a3 +Subproject commit 661d35fb5d483b86c6cd60438b3282b4035e4136 diff --git a/src/test/java/ecdar/simulation/SimulationTest.java b/src/test/java/ecdar/simulation/SimulationTest.java new file mode 100644 index 00000000..5d0f8384 --- /dev/null +++ b/src/test/java/ecdar/simulation/SimulationTest.java @@ -0,0 +1,10 @@ +package ecdar.simulation; + +import org.junit.Test; + +public class SimulationTest { + @Test + public void intialStep() { + assert false; + } +} From 4f075560f928081c4db016aff8cb2a98036aa605 Mon Sep 17 00:00:00 2001 From: Niels Vistisen Date: Fri, 15 Jul 2022 10:44:59 +0200 Subject: [PATCH 010/158] TestFX added and protos updated --- build.gradle | 1 + src/main/proto | 2 +- src/test/java/ecdar/TestFXBase.java | 30 ++++++ .../java/ecdar/simulation/SimulationTest.java | 101 +++++++++++++++++- 4 files changed, 129 insertions(+), 5 deletions(-) create mode 100644 src/test/java/ecdar/TestFXBase.java diff --git a/build.gradle b/build.gradle index e8b3ecfb..efb9b4ca 100644 --- a/build.gradle +++ b/build.gradle @@ -48,6 +48,7 @@ dependencies { implementation fileTree(dir: 'lib', include: ['*.jar']) testImplementation group: 'junit', name: 'junit', version: '4.13.2' + testImplementation 'org.testfx:testfx-junit:4.0.15-alpha' implementation 'com.jfoenix:jfoenix:9.0.10' implementation group: 'de.codecentric.centerdevice', name: 'javafxsvg', version: '1.3.0' implementation group: 'com.github.jiconfont', name: 'jiconfont-javafx', version: '1.0.0' diff --git a/src/main/proto b/src/main/proto index 661d35fb..3e021fbf 160000 --- a/src/main/proto +++ b/src/main/proto @@ -1 +1 @@ -Subproject commit 661d35fb5d483b86c6cd60438b3282b4035e4136 +Subproject commit 3e021fbf9457d6f29b1af21d1468692ec4110748 diff --git a/src/test/java/ecdar/TestFXBase.java b/src/test/java/ecdar/TestFXBase.java new file mode 100644 index 00000000..8dc71b74 --- /dev/null +++ b/src/test/java/ecdar/TestFXBase.java @@ -0,0 +1,30 @@ +package ecdar; + +import javafx.scene.input.KeyCode; +import javafx.scene.input.MouseButton; +import javafx.stage.Stage; +import org.junit.After; +import org.junit.Before; +import org.testfx.api.FxToolkit; +import org.testfx.framework.junit.ApplicationTest; + +import java.util.concurrent.TimeoutException; + +public class TestFXBase extends ApplicationTest { + @Before + public void setUpClass() throws Exception { + ApplicationTest.launch(Ecdar.class); + } + + @Override + public void start(Stage stage) { + stage.show(); + } + + @After + public void afterEachTest() throws TimeoutException { + FxToolkit.hideStage(); + release(new KeyCode[]{}); + release(new MouseButton[]{}); + } +} diff --git a/src/test/java/ecdar/simulation/SimulationTest.java b/src/test/java/ecdar/simulation/SimulationTest.java index 5d0f8384..b25a1d61 100644 --- a/src/test/java/ecdar/simulation/SimulationTest.java +++ b/src/test/java/ecdar/simulation/SimulationTest.java @@ -1,10 +1,103 @@ package ecdar.simulation; -import org.junit.Test; +import EcdarProtoBuf.EcdarBackendGrpc; +import EcdarProtoBuf.ObjectProtos; +import EcdarProtoBuf.QueryProtos; +import ecdar.TestFXBase; +import ecdar.abstractions.Component; +import ecdar.abstractions.Location; +import io.grpc.BindableService; +import io.grpc.ManagedChannel; +import io.grpc.Server; +import io.grpc.Status; +import io.grpc.inprocess.InProcessChannelBuilder; +import io.grpc.inprocess.InProcessServerBuilder; +import io.grpc.stub.StreamObserver; +import io.grpc.testing.GrpcCleanupRule; +import org.junit.*; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import static org.junit.Assert.fail; + +public class SimulationTest extends TestFXBase { + @Rule + public GrpcCleanupRule grpcCleanup = new GrpcCleanupRule(); + private final String serverName = InProcessServerBuilder.generateName(); -public class SimulationTest { @Test - public void intialStep() { - assert false; + public void testGetInitialStateHighlightsTheInitialLocation() { + final List components = generateComponentsWithInitialLocations(); + final SimulationHandler sh = new SimulationHandler(); + + BindableService testService = new EcdarBackendGrpc.EcdarBackendImplBase() { + @Override + public void startSimulation(QueryProtos.SimulationStartRequest request, + StreamObserver responseObserver) { + try { + ObjectProtos.StateTuple state = ObjectProtos.StateTuple.newBuilder().addAllLocations(components.stream() + .map(c -> ObjectProtos.StateTuple.LocationTuple.newBuilder() + .setComponentName(c.getName()) + .setId(c.getInitialLocation().getId()) + .build()) + .collect(Collectors.toList())).build(); + + QueryProtos.SimulationStepResponse response = QueryProtos.SimulationStepResponse.newBuilder().setState(state).build(); + responseObserver.onNext(response); + responseObserver.onCompleted(); + } catch (Throwable e) { + responseObserver.onError(Status.INVALID_ARGUMENT.withDescription(e.getMessage()).asException()); + } + } + + @Override + public void takeSimulationStep(EcdarProtoBuf.QueryProtos.SimulationStepRequest request, + io.grpc.stub.StreamObserver responseObserver) { + } + }; + + final Server server; + final ManagedChannel channel; + final EcdarBackendGrpc.EcdarBackendBlockingStub stub; + try { + server = grpcCleanup.register(InProcessServerBuilder + .forName(serverName).directExecutor().addService(testService).build().start()); + channel = grpcCleanup.register(InProcessChannelBuilder + .forName(serverName).directExecutor().build()); + stub = EcdarBackendGrpc.newBlockingStub(channel); + QueryProtos.SimulationStartRequest request = QueryProtos.SimulationStartRequest.newBuilder().setSystem("(A || B)").build(); + + var expectedResponse = new ObjectProtos.StateTuple.LocationTuple[components.size()]; + + for (int i = 0; i < components.size(); i++) { + Component comp = components.get(i); + expectedResponse[i] = ObjectProtos.StateTuple.LocationTuple.newBuilder() + .setComponentName(comp.getName()) + .setId(comp.getInitialLocation().getId()).build(); + } + + var result = stub.startSimulation(request).getState().getLocationsList().toArray(); + + Assert.assertArrayEquals(expectedResponse, result); + } catch (IOException e) { + fail("Exception encountered: " + e.getMessage()); + } + } + + private List generateComponentsWithInitialLocations() { + List comps = new ArrayList<>(); + for (int i = 0; i < 2; i++) { + var comp = new Component(); + comp.setName(comp + "_" + i); + var loc = new Location(comp + "_initial"); + loc.setType(Location.Type.INITIAL); + comp.addLocation(loc); + comps.add(comp); + } + + return comps; } } From e7d6f93c94f05de8b254e3ddc78005fc80885f55 Mon Sep 17 00:00:00 2001 From: Niels Vistisen Date: Fri, 15 Jul 2022 11:18:12 +0200 Subject: [PATCH 011/158] JUnit5 upgrade --- build.gradle | 2 +- src/test/java/ecdar/TestFXBase.java | 10 +++++----- src/test/java/ecdar/simulation/SimulationTest.java | 10 ++++------ 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/build.gradle b/build.gradle index efb9b4ca..efaa8ceb 100644 --- a/build.gradle +++ b/build.gradle @@ -47,7 +47,7 @@ def protocVersion = protobufVersion dependencies { implementation fileTree(dir: 'lib', include: ['*.jar']) - testImplementation group: 'junit', name: 'junit', version: '4.13.2' + testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.8.2' testImplementation 'org.testfx:testfx-junit:4.0.15-alpha' implementation 'com.jfoenix:jfoenix:9.0.10' implementation group: 'de.codecentric.centerdevice', name: 'javafxsvg', version: '1.3.0' diff --git a/src/test/java/ecdar/TestFXBase.java b/src/test/java/ecdar/TestFXBase.java index 8dc71b74..e54eecec 100644 --- a/src/test/java/ecdar/TestFXBase.java +++ b/src/test/java/ecdar/TestFXBase.java @@ -3,16 +3,16 @@ import javafx.scene.input.KeyCode; import javafx.scene.input.MouseButton; import javafx.stage.Stage; -import org.junit.After; -import org.junit.Before; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; import org.testfx.api.FxToolkit; import org.testfx.framework.junit.ApplicationTest; import java.util.concurrent.TimeoutException; public class TestFXBase extends ApplicationTest { - @Before - public void setUpClass() throws Exception { + @BeforeAll + static void setUp() throws Exception { ApplicationTest.launch(Ecdar.class); } @@ -21,7 +21,7 @@ public void start(Stage stage) { stage.show(); } - @After + @AfterAll public void afterEachTest() throws TimeoutException { FxToolkit.hideStage(); release(new KeyCode[]{}); diff --git a/src/test/java/ecdar/simulation/SimulationTest.java b/src/test/java/ecdar/simulation/SimulationTest.java index b25a1d61..2acb8136 100644 --- a/src/test/java/ecdar/simulation/SimulationTest.java +++ b/src/test/java/ecdar/simulation/SimulationTest.java @@ -14,24 +14,22 @@ import io.grpc.inprocess.InProcessServerBuilder; import io.grpc.stub.StreamObserver; import io.grpc.testing.GrpcCleanupRule; -import org.junit.*; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; -import static org.junit.Assert.fail; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Assertions; public class SimulationTest extends TestFXBase { - @Rule public GrpcCleanupRule grpcCleanup = new GrpcCleanupRule(); private final String serverName = InProcessServerBuilder.generateName(); @Test public void testGetInitialStateHighlightsTheInitialLocation() { final List components = generateComponentsWithInitialLocations(); - final SimulationHandler sh = new SimulationHandler(); BindableService testService = new EcdarBackendGrpc.EcdarBackendImplBase() { @Override @@ -81,9 +79,9 @@ public void takeSimulationStep(EcdarProtoBuf.QueryProtos.SimulationStepRequest r var result = stub.startSimulation(request).getState().getLocationsList().toArray(); - Assert.assertArrayEquals(expectedResponse, result); + Assertions.assertArrayEquals(expectedResponse, result); } catch (IOException e) { - fail("Exception encountered: " + e.getMessage()); + Assertions.fail("Exception encountered: " + e.getMessage()); } } From 17edbc61bd18c5c8556d6ab096da757fe99dd72e Mon Sep 17 00:00:00 2001 From: Niels Vistisen Date: Tue, 9 Aug 2022 11:54:24 +0200 Subject: [PATCH 012/158] Comment updated in preparation for query implementation in simulator view --- .../resources/ecdar/presentations/RightSimPanePresentation.fxml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/ecdar/presentations/RightSimPanePresentation.fxml b/src/main/resources/ecdar/presentations/RightSimPanePresentation.fxml index 4697fb97..6f3d588c 100755 --- a/src/main/resources/ecdar/presentations/RightSimPanePresentation.fxml +++ b/src/main/resources/ecdar/presentations/RightSimPanePresentation.fxml @@ -17,7 +17,7 @@ AnchorPane.rightAnchor="0" styleClass="edge-to-edge"> - + From 7a9a04e09312f66d758915864567edd0b3de9b22 Mon Sep 17 00:00:00 2001 From: Niels Vistisen Date: Fri, 19 Aug 2022 12:56:29 +0200 Subject: [PATCH 013/158] WIP: Toggle for GUI mode added (sizing of toggle not working) --- .../ecdar/controllers/EcdarController.java | 40 ++++++++----------- src/main/resources/ecdar/main.css | 20 +++++++++- .../presentations/EcdarPresentation.fxml | 31 +++++++++++--- 3 files changed, 62 insertions(+), 29 deletions(-) diff --git a/src/main/java/ecdar/controllers/EcdarController.java b/src/main/java/ecdar/controllers/EcdarController.java index 39384a9d..77237498 100644 --- a/src/main/java/ecdar/controllers/EcdarController.java +++ b/src/main/java/ecdar/controllers/EcdarController.java @@ -120,6 +120,7 @@ public class EcdarController implements Initializable { public MenuItem menuBarHelpAbout; public MenuItem menuBarHelpTest; + public JFXToggleButton switchGuiView; public Snackbar snackbar; public HBox statusBar; public Label statusLabel; @@ -255,6 +256,8 @@ private void scaleIcons(Node node, double size) { Set xSmallIcons = node.lookupAll(".icon-size-x-small"); for (Node icon : xSmallIcons) icon.setStyle("-fx-icon-size: " + Math.floor(size / 13.0 * 18) + "px;"); + + switchGuiView.setSize(Math.floor(size / 13.0 * 24)); } private double getNewCalculatedScale() { @@ -646,11 +649,13 @@ private void initializeViewMenu() { } }); - menuBarViewEditor.setAccelerator(new KeyCodeCombination(KeyCode.DIGIT1, KeyCombination.SHORTCUT_DOWN)); - menuBarViewEditor.setOnAction(event -> editorRipplerClicked()); - - menuBarViewSimulator.setAccelerator(new KeyCodeCombination(KeyCode.DIGIT2, KeyCombination.SHORTCUT_DOWN)); - menuBarViewSimulator.setOnAction(event -> simulatorRipplerClicked()); + switchGuiView.selectedProperty().addListener((observable, oldValue, newValue) -> { + if (newValue) { + currentMode.setValue(Mode.Simulator); + } else { + currentMode.setValue(Mode.Editor); + } + }); currentMode.addListener((obs, oldMode, newMode) -> { if (newMode == Mode.Editor && oldMode != newMode) { @@ -660,6 +665,13 @@ private void initializeViewMenu() { } }); + menuBarViewEditor.setAccelerator(new KeyCodeCombination(KeyCode.DIGIT1, KeyCombination.SHORTCUT_DOWN)); + menuBarViewEditor.setOnAction(event -> switchGuiView.setSelected(false)); + + menuBarViewSimulator.setAccelerator(new KeyCodeCombination(KeyCode.DIGIT2, KeyCombination.SHORTCUT_DOWN)); + menuBarViewSimulator.setOnAction(event -> switchGuiView.setSelected(true)); + + // On startup, set the scaling to the values saved in preferences Platform.runLater(() -> { Ecdar.autoScalingEnabled.setValue(Ecdar.preferences.getBoolean("autoscaling", true)); @@ -954,24 +966,6 @@ private void initializeFileExportAsPng() { }); } - /** - * Method for click on the Editor rippler. Changes mode to the editor - */ - private void editorRipplerClicked() { - if (currentMode.get() != Mode.Editor) { - currentMode.setValue(Mode.Editor); - } - } - - /** - * Method for click on the Simulator rippler. Changes mode to the simulator - */ - private void simulatorRipplerClicked() { - if (currentMode.get() != Mode.Simulator) { - currentMode.setValue(Mode.Simulator); - } - } - /** * Changes the view and mode to the editor * Only enter if the mode is not already Editor diff --git a/src/main/resources/ecdar/main.css b/src/main/resources/ecdar/main.css index 5a29ca08..eb0baccc 100644 --- a/src/main/resources/ecdar/main.css +++ b/src/main/resources/ecdar/main.css @@ -290,6 +290,24 @@ -fx-font-size: 1em; } +.large-toggle-button { + -jfx-toggle-color:#dddddd; -jfx-untoggle-color:#dddddd; + -jfx-toggle-line-color:#7C8B92; -jfx-untoggle-line-color:#7C8B92; + -fx-opacity: 0.5; + -jfx-size: 1.6em; + -fx-font-size: 1em; +} + +.editor-simulator-toggle { + -fx-min-width: 8em; + -fx-max-width: 8em; + -fx-min-height: 4em; + -fx-max-height: 4em; + -fx-background-color: -blue-grey-800; + -fx-border-radius: 0 0 10 10; + -fx-background-radius: 0 0 10 10; +} + /* Response sizing for elements (icons with these classes will be scaled when scaling changes) */ .icon-size-medium { -fx-icon-size: 24; @@ -344,4 +362,4 @@ .responsive-circle-radius { -fx-scale-x: 1em; -fx-scale-y: 1em; -} \ No newline at end of file +} diff --git a/src/main/resources/ecdar/presentations/EcdarPresentation.fxml b/src/main/resources/ecdar/presentations/EcdarPresentation.fxml index be278b8e..6b30f2b5 100644 --- a/src/main/resources/ecdar/presentations/EcdarPresentation.fxml +++ b/src/main/resources/ecdar/presentations/EcdarPresentation.fxml @@ -10,6 +10,7 @@ + - + @@ -94,12 +96,14 @@ - + - + @@ -110,7 +114,8 @@ - + @@ -180,7 +185,8 @@ - +
@@ -230,6 +236,21 @@
+ + + + + + + + + + + + + + + From 49d216f6694ccf08b60fb2166e4de42bfe833768 Mon Sep 17 00:00:00 2001 From: Niels Vistisen Date: Mon, 22 Aug 2022 10:49:32 +0200 Subject: [PATCH 014/158] WIP: Dialog for initializing simulation added --- .../ecdar/controllers/EcdarController.java | 56 +++++++++++++++---- ...ulationInitializationDialogController.java | 18 ++++++ .../controllers/SimulatorController.java | 3 +- .../presentations/EcdarPresentation.java | 4 +- ...ationInitializationDialogPresentation.java | 17 ++++++ .../ecdar/simulation/SimulationHandler.java | 4 ++ .../presentations/EcdarPresentation.fxml | 9 ++- ...ationInitializationDialogPresentation.fxml | 43 ++++++++++++++ 8 files changed, 139 insertions(+), 15 deletions(-) create mode 100644 src/main/java/ecdar/controllers/SimulationInitializationDialogController.java create mode 100644 src/main/java/ecdar/presentations/SimulationInitializationDialogPresentation.java create mode 100644 src/main/resources/ecdar/presentations/SimulationInitializationDialogPresentation.fxml diff --git a/src/main/java/ecdar/controllers/EcdarController.java b/src/main/java/ecdar/controllers/EcdarController.java index 77237498..62fd0b49 100644 --- a/src/main/java/ecdar/controllers/EcdarController.java +++ b/src/main/java/ecdar/controllers/EcdarController.java @@ -46,6 +46,7 @@ import java.util.*; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.stream.Collectors; public class EcdarController implements Initializable { // Reachability analysis @@ -61,8 +62,8 @@ public class EcdarController implements Initializable { public StackPane rightPane; public Rectangle bottomFillerElement; public MessageTabPanePresentation messageTabPane; - public StackPane dialogContainer; - public JFXDialog dialog; + public StackPane modellingHelpDialogContainer; + public JFXDialog modellingHelpDialog; public StackPane modalBar; public JFXTextField queryTextField; public JFXTextField commentTextField; @@ -135,6 +136,9 @@ public class EcdarController implements Initializable { public StackPane backendOptionsDialogContainer; public BackendOptionsDialogPresentation backendOptionsDialog; + public StackPane simulationInitializationDialogContainer; + public SimulationInitializationDialogPresentation simulationInitializationDialog; + public final DoubleProperty scalingProperty = new SimpleDoubleProperty(); private static JFXDialog _queryDialog; @@ -265,30 +269,44 @@ private double getNewCalculatedScale() { } private void initializeDialogs() { - dialog.setDialogContainer(dialogContainer); - dialogContainer.opacityProperty().bind(dialog.getChildren().get(0).scaleXProperty()); - dialog.setOnDialogClosed(event -> dialogContainer.setVisible(false)); + modellingHelpDialog.setDialogContainer(modellingHelpDialogContainer); + modellingHelpDialogContainer.opacityProperty().bind(modellingHelpDialog.getChildren().get(0).scaleXProperty()); + modellingHelpDialog.setOnDialogClosed(event -> modellingHelpDialogContainer.setVisible(false)); _queryDialog = queryDialog; _queryTextResult = queryTextResult; _queryTextQuery = queryTextQuery; initializeDialog(queryDialog, queryDialogContainer); - initializeDialog(backendOptionsDialog, backendOptionsDialogContainer); + initializeBackendOptionsDialog(); + + initializeDialog(simulationInitializationDialog, simulationInitializationDialogContainer); + simulationInitializationDialog.getController().cancelButton.setOnMouseClicked(event -> { + switchGuiView.setSelected(false); + simulationInitializationDialog.close(); + }); + + simulationInitializationDialog.getController().startButton.setOnMouseClicked(event -> { + // ToDo NIELS: Start simulation of selected query + currentMode.setValue(Mode.Simulator); + simulationInitializationDialog.close(); + }); + } + + private void initializeBackendOptionsDialog() { + initializeDialog(backendOptionsDialog, backendOptionsDialogContainer); backendOptionsDialog.getController().resetBackendsButton.setOnMouseClicked(event -> { backendOptionsDialog.getController().resetBackendsToDefault(); }); backendOptionsDialog.getController().closeButton.setOnMouseClicked(event -> { backendOptionsDialog.getController().cancelBackendOptionsChanges(); - dialog.close(); backendOptionsDialog.close(); }); backendOptionsDialog.getController().saveButton.setOnMouseClicked(event -> { if (backendOptionsDialog.getController().saveChangesToBackendOptions()) { - dialog.close(); backendOptionsDialog.close(); } }); @@ -651,7 +669,25 @@ private void initializeViewMenu() { switchGuiView.selectedProperty().addListener((observable, oldValue, newValue) -> { if (newValue) { - currentMode.setValue(Mode.Simulator); + if (Ecdar.getProject().getQueries().isEmpty()) { + Ecdar.showToast("Please add a query to simulate before entering the simulator"); + switchGuiView.setSelected(false); + return; + } + + if (!Ecdar.getSimulationHandler().isSimulationRunning()) { + ArrayList queryOptions = Ecdar.getProject().getQueries().stream().map(Query::getQuery).collect(Collectors.toCollection(ArrayList::new)); + + if (!simulationInitializationDialog.getController().simulationComboBox.getItems().equals(queryOptions)) { + simulationInitializationDialog.getController().simulationComboBox.getItems().setAll(queryOptions); + } + + simulationInitializationDialogContainer.setVisible(true); + simulationInitializationDialog.show(simulationInitializationDialogContainer); + simulationInitializationDialog.setMouseTransparent(false); + } else { + currentMode.setValue(Mode.Simulator); + } } else { currentMode.setValue(Mode.Editor); } @@ -1214,7 +1250,7 @@ private void nudgeSelected(final NudgeDirection direction) { @FXML private void closeQueryDialog() { - dialog.close(); + modellingHelpDialog.close(); queryDialog.close(); } diff --git a/src/main/java/ecdar/controllers/SimulationInitializationDialogController.java b/src/main/java/ecdar/controllers/SimulationInitializationDialogController.java new file mode 100644 index 00000000..1eb1529c --- /dev/null +++ b/src/main/java/ecdar/controllers/SimulationInitializationDialogController.java @@ -0,0 +1,18 @@ +package ecdar.controllers; + +import com.jfoenix.controls.JFXButton; +import com.jfoenix.controls.JFXComboBox; +import javafx.fxml.Initializable; + +import java.net.URL; +import java.util.ResourceBundle; + +public class SimulationInitializationDialogController implements Initializable { + public JFXComboBox simulationComboBox; + public JFXButton cancelButton; + public JFXButton startButton; + + public void initialize(URL location, ResourceBundle resources) { + + } +} diff --git a/src/main/java/ecdar/controllers/SimulatorController.java b/src/main/java/ecdar/controllers/SimulatorController.java index 639c1ff1..bdba66b4 100644 --- a/src/main/java/ecdar/controllers/SimulatorController.java +++ b/src/main/java/ecdar/controllers/SimulatorController.java @@ -40,7 +40,7 @@ public void initialize(URL location, ResourceBundle resources) { /** * Prepares the simulator to be shown.
* It also prepares the processes to be shown in the {@link SimulatorOverviewPresentation} by:
- * - Building the system if it has been updated or never have been created.
+ * - Building the system if it has been updated or have never been created.
* - Adding the components which are going to be used in the simulation to */ public void willShow() { @@ -60,6 +60,7 @@ public void willShow() { } if (shouldSimulationBeReset || firstTimeInSimulator) { + resetSimulation(); sm.resetToInitialLocation(); } diff --git a/src/main/java/ecdar/presentations/EcdarPresentation.java b/src/main/java/ecdar/presentations/EcdarPresentation.java index 7fa6b343..0648b67d 100644 --- a/src/main/java/ecdar/presentations/EcdarPresentation.java +++ b/src/main/java/ecdar/presentations/EcdarPresentation.java @@ -332,8 +332,8 @@ public void showSnackbarMessage(final String message) { } public void showHelp() { - controller.dialogContainer.setVisible(true); - controller.dialog.show(controller.dialogContainer); + controller.modellingHelpDialogContainer.setVisible(true); + controller.modellingHelpDialog.show(controller.modellingHelpDialogContainer); } public EcdarController getController() { diff --git a/src/main/java/ecdar/presentations/SimulationInitializationDialogPresentation.java b/src/main/java/ecdar/presentations/SimulationInitializationDialogPresentation.java new file mode 100644 index 00000000..677217bd --- /dev/null +++ b/src/main/java/ecdar/presentations/SimulationInitializationDialogPresentation.java @@ -0,0 +1,17 @@ +package ecdar.presentations; + +import com.jfoenix.controls.JFXDialog; +import ecdar.controllers.BackendOptionsDialogController; +import ecdar.controllers.SimulationInitializationDialogController; + +public class SimulationInitializationDialogPresentation extends JFXDialog { + private final SimulationInitializationDialogController controller; + + public SimulationInitializationDialogPresentation() { + controller = new EcdarFXMLLoader().loadAndGetController("SimulationInitializationDialogPresentation.fxml", this); + } + + public SimulationInitializationDialogController getController() { + return controller; + } +} diff --git a/src/main/java/ecdar/simulation/SimulationHandler.java b/src/main/java/ecdar/simulation/SimulationHandler.java index 395d5027..bb894b62 100755 --- a/src/main/java/ecdar/simulation/SimulationHandler.java +++ b/src/main/java/ecdar/simulation/SimulationHandler.java @@ -403,4 +403,8 @@ public EcdarSystem getSystem() { public String getCurrentSimulation() { return currentSimulation; } + + public boolean isSimulationRunning() { + return false; // ToDo NIELS: Handle logic for determining whether a simulation is currently running + } } \ No newline at end of file diff --git a/src/main/resources/ecdar/presentations/EcdarPresentation.fxml b/src/main/resources/ecdar/presentations/EcdarPresentation.fxml index 6b30f2b5..429ea99f 100644 --- a/src/main/resources/ecdar/presentations/EcdarPresentation.fxml +++ b/src/main/resources/ecdar/presentations/EcdarPresentation.fxml @@ -311,8 +311,8 @@ - - + + @@ -494,4 +494,9 @@ + + + + +
diff --git a/src/main/resources/ecdar/presentations/SimulationInitializationDialogPresentation.fxml b/src/main/resources/ecdar/presentations/SimulationInitializationDialogPresentation.fxml new file mode 100644 index 00000000..332e082d --- /dev/null +++ b/src/main/resources/ecdar/presentations/SimulationInitializationDialogPresentation.fxml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + Simulation Initialization + + + + + + + + + + + + + + + + + + + From 848fc69fbefc14e5ecd373ef9ce1079a5bf69876 Mon Sep 17 00:00:00 2001 From: APaludan Date: Thu, 22 Sep 2022 11:32:26 +0200 Subject: [PATCH 015/158] Show tooltip when query was unsuccessful --- src/main/java/ecdar/presentations/QueryPresentation.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/ecdar/presentations/QueryPresentation.java b/src/main/java/ecdar/presentations/QueryPresentation.java index db1d9179..b3d2138c 100644 --- a/src/main/java/ecdar/presentations/QueryPresentation.java +++ b/src/main/java/ecdar/presentations/QueryPresentation.java @@ -196,7 +196,11 @@ private void initializeStateIndicator() { } else { this.tooltip.setText("The component has been created (can be accessed in the project pane)"); } - } else if (queryState.getStatusCode() == 3) { + } + else if (queryState.getStatusCode() == 2){ + this.tooltip.setText("This query was not a success!"); + } + else if (queryState.getStatusCode() == 3) { this.tooltip.setText("The query has not been executed yet"); } else { this.tooltip.setText(controller.getQuery().getCurrentErrors()); From 28fb8f797de9e11338d5de5b6abd3e721f147401 Mon Sep 17 00:00:00 2001 From: jhbengtsson Date: Thu, 22 Sep 2022 11:41:51 +0200 Subject: [PATCH 016/158] replacement af >= <= med deres unicode --- src/main/java/ecdar/abstractions/Query.java | 21 +++++++++++++---- .../ecdar/controllers/QueryController.java | 23 ++++++++++++++++++- .../presentations/QueryPresentation.fxml | 2 +- 3 files changed, 40 insertions(+), 6 deletions(-) diff --git a/src/main/java/ecdar/abstractions/Query.java b/src/main/java/ecdar/abstractions/Query.java index 2fd0b7f3..2294a3a4 100644 --- a/src/main/java/ecdar/abstractions/Query.java +++ b/src/main/java/ecdar/abstractions/Query.java @@ -33,11 +33,10 @@ public class Query implements Serializable { private Runnable runQuery; public Query(final String query, final String comment, final QueryState queryState) { - this.query.set(query); + this.setQuery(query); this.comment.set(comment); this.queryState.set(queryState); setBackend(BackendHelper.getDefaultBackendInstance()); - initializeRunQuery(); } @@ -64,7 +63,22 @@ public String getQuery() { } public void setQuery(final String query) { - this.query.set(query); + String newQuery = ""; + boolean hasBeenTrimmed = false; + if(query.contains("\u2264")){ + newQuery = query.replace("\u2264","<="); + hasBeenTrimmed = true; + this.query.setValue(newQuery); + } + if(query.contains("\u2265")){ + newQuery = query.replace("\u2265",">="); + hasBeenTrimmed = true; + this.query.setValue(newQuery); + } + if(!hasBeenTrimmed){ + this.query.set(query); + } + } public StringProperty queryProperty() { @@ -124,7 +138,6 @@ private void initializeRunQuery() { setQueryState(QueryState.RUNNING); forcedCancel = false; errors.set(""); - if (getQuery().isEmpty()) { setQueryState(QueryState.SYNTAX_ERROR); this.addError("Query is empty"); diff --git a/src/main/java/ecdar/controllers/QueryController.java b/src/main/java/ecdar/controllers/QueryController.java index a3f78280..ae5521bb 100644 --- a/src/main/java/ecdar/controllers/QueryController.java +++ b/src/main/java/ecdar/controllers/QueryController.java @@ -2,6 +2,7 @@ import com.jfoenix.controls.JFXComboBox; import com.jfoenix.controls.JFXRippler; +import com.jfoenix.controls.JFXTextField; import ecdar.abstractions.BackendInstance; import ecdar.abstractions.Query; import ecdar.abstractions.QueryType; @@ -9,11 +10,13 @@ import ecdar.utility.colors.Color; import javafx.application.Platform; import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.value.ObservableValue; +import javafx.fxml.FXML; import javafx.fxml.Initializable; import javafx.scene.control.Tooltip; import javafx.scene.text.Text; import org.kordamp.ikonli.javafx.FontIcon; - +import javafx.beans.value.ChangeListener; import java.net.URL; import java.util.HashMap; import java.util.Map; @@ -27,10 +30,28 @@ public class QueryController implements Initializable { private Query query; private final Map queryTypeListElementsSelectedState = new HashMap<>(); private final Tooltip noQueryTypeSetTooltip = new Tooltip("Please select a query type beneath the status icon"); + @FXML + private JFXTextField queryText; @Override public void initialize(URL location, ResourceBundle resources) { initializeActionButton(); + + queryText.textProperty().addListener(new ChangeListener() { + @Override + public void changed(ObservableValue observable, String oldValue, String newValue) { + String tmpValue = ""; + if(newValue.contains("<=")){ + tmpValue = newValue.replace("<=","\u2264"); + queryText.setText(tmpValue); + } + if(newValue.contains(">=")){ + tmpValue = newValue.replace(">=","\u2265"); + queryText.setText(tmpValue); + } + } + }); + } public void setQuery(Query query) { diff --git a/src/main/resources/ecdar/presentations/QueryPresentation.fxml b/src/main/resources/ecdar/presentations/QueryPresentation.fxml index 223e15df..94e1687d 100644 --- a/src/main/resources/ecdar/presentations/QueryPresentation.fxml +++ b/src/main/resources/ecdar/presentations/QueryPresentation.fxml @@ -33,7 +33,7 @@ - Date: Thu, 22 Sep 2022 13:53:11 +0200 Subject: [PATCH 017/158] Modify getter of query, such that it complies with the backend expected format --- src/main/java/ecdar/abstractions/Query.java | 23 +++++++++++---------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/main/java/ecdar/abstractions/Query.java b/src/main/java/ecdar/abstractions/Query.java index 2294a3a4..d59c9b84 100644 --- a/src/main/java/ecdar/abstractions/Query.java +++ b/src/main/java/ecdar/abstractions/Query.java @@ -59,26 +59,27 @@ public ObjectProperty queryStateProperty() { } public String getQuery() { - return query.get(); - } - - public void setQuery(final String query) { String newQuery = ""; + String oldQuery = this.query.get(); boolean hasBeenTrimmed = false; - if(query.contains("\u2264")){ - newQuery = query.replace("\u2264","<="); + + if(oldQuery.contains("\u2264")){ + newQuery = oldQuery.replace("\u2264","<="); hasBeenTrimmed = true; - this.query.setValue(newQuery); } - if(query.contains("\u2265")){ - newQuery = query.replace("\u2265",">="); + if(oldQuery.contains("\u2265")){ + newQuery = oldQuery.replace("\u2265",">="); hasBeenTrimmed = true; - this.query.setValue(newQuery); } if(!hasBeenTrimmed){ - this.query.set(query); + return oldQuery; } + return newQuery; + } + + public void setQuery(final String query) { + this.query.set(query); } public StringProperty queryProperty() { From 4ded5e7a0920ce98f3110db0642b52beaa640bde Mon Sep 17 00:00:00 2001 From: jhbengtsson Date: Thu, 22 Sep 2022 16:01:53 +0200 Subject: [PATCH 018/158] Refinement symbol replaced with unicode in dropdown --- .../java/ecdar/abstractions/QueryType.java | 2 +- .../java/ecdar/abstractions/QueryTest.java | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 src/test/java/ecdar/abstractions/QueryTest.java diff --git a/src/main/java/ecdar/abstractions/QueryType.java b/src/main/java/ecdar/abstractions/QueryType.java index 98af6fe1..0ea4b1f2 100644 --- a/src/main/java/ecdar/abstractions/QueryType.java +++ b/src/main/java/ecdar/abstractions/QueryType.java @@ -2,7 +2,7 @@ public enum QueryType { REACHABILITY("reachability", "E<>"), - REFINEMENT("refinement", "<="), + REFINEMENT("refinement", "\u2264"), QUOTIENT("quotient", "\\"), SPECIFICATION("specification", "Spec"), IMPLEMENTATION("implementation", "Imp"), diff --git a/src/test/java/ecdar/abstractions/QueryTest.java b/src/test/java/ecdar/abstractions/QueryTest.java new file mode 100644 index 00000000..645a61ec --- /dev/null +++ b/src/test/java/ecdar/abstractions/QueryTest.java @@ -0,0 +1,19 @@ +package ecdar.abstractions; + +import ecdar.Ecdar; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Assertions; + +public class QueryTest { + @BeforeAll + static void setup() { + Ecdar.setUpForTest(); + } + + @Test + public void testGetQuery() { + + + } +} From b0028ecacb22be360842223f205da45f97fb2ae2 Mon Sep 17 00:00:00 2001 From: APaludan Date: Mon, 26 Sep 2022 14:39:24 +0200 Subject: [PATCH 019/158] fix typo --- src/main/java/ecdar/backend/BackendDriver.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/ecdar/backend/BackendDriver.java b/src/main/java/ecdar/backend/BackendDriver.java index 8d6c7c09..a3ab0e1a 100644 --- a/src/main/java/ecdar/backend/BackendDriver.java +++ b/src/main/java/ecdar/backend/BackendDriver.java @@ -91,9 +91,9 @@ public void run() { backendConnection, componentsBuilder, QueryProtos.IgnoredInputOutputs.newBuilder().getDefaultInstanceForType(), - (reponse) -> { - if (reponse.hasQuery() && reponse.getQuery().hasIgnoredInputOutputs()) { - var ignoredInputOutputs = reponse.getQuery().getIgnoredInputOutputs(); + (response) -> { + if (response.hasQuery() && response.getQuery().hasIgnoredInputOutputs()) { + var ignoredInputOutputs = response.getQuery().getIgnoredInputOutputs(); query.addNewElementsToMap(new ArrayList<>(ignoredInputOutputs.getIgnoredInputsList()), new ArrayList<>(ignoredInputOutputs.getIgnoredOutputsList())); } else { // Response is unexpected, maybe just ignore From a1bed78289b67b3053093acca764b373d0698e6c Mon Sep 17 00:00:00 2001 From: jhbengtsson Date: Mon, 26 Sep 2022 14:40:02 +0200 Subject: [PATCH 020/158] unittest af getQuery(); --- .../ecdar/controllers/QueryController.java | 23 +++++++++++-------- .../java/ecdar/abstractions/QueryTest.java | 5 ++++ 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/main/java/ecdar/controllers/QueryController.java b/src/main/java/ecdar/controllers/QueryController.java index ae5521bb..dec3b0a6 100644 --- a/src/main/java/ecdar/controllers/QueryController.java +++ b/src/main/java/ecdar/controllers/QueryController.java @@ -40,20 +40,25 @@ public void initialize(URL location, ResourceBundle resources) { queryText.textProperty().addListener(new ChangeListener() { @Override public void changed(ObservableValue observable, String oldValue, String newValue) { - String tmpValue = ""; - if(newValue.contains("<=")){ - tmpValue = newValue.replace("<=","\u2264"); - queryText.setText(tmpValue); - } - if(newValue.contains(">=")){ - tmpValue = newValue.replace(">=","\u2265"); - queryText.setText(tmpValue); - } + updateQueryTextFieldString(newValue); } }); } + //Method for replacing "<=" and ">=" with its unicode character + public void updateQueryTextFieldString(String newValue) { + String tmpValue = ""; + if(newValue.contains("<=")){ + tmpValue = newValue.replace("<=","\u2264"); + queryText.setText(tmpValue); + } + if(newValue.contains(">=")){ + tmpValue = newValue.replace(">=","\u2265"); + queryText.setText(tmpValue); + } + } + public void setQuery(Query query) { this.query = query; this.query.getTypeProperty().addListener(((observable, oldValue, newValue) -> { diff --git a/src/test/java/ecdar/abstractions/QueryTest.java b/src/test/java/ecdar/abstractions/QueryTest.java index 645a61ec..88d1e9fa 100644 --- a/src/test/java/ecdar/abstractions/QueryTest.java +++ b/src/test/java/ecdar/abstractions/QueryTest.java @@ -13,7 +13,12 @@ static void setup() { @Test public void testGetQuery() { + //Test that the query string from the query textfield is decoded correctly for the backend to use it + final Query query = new Query("(Administration || Machine || Researcher) \u2264 Spec)", "comment", QueryState.RUNNING); + String expected = "(Administration || Machine || Researcher) <= Spec)"; + String result = query.getQuery(); + Assertions.assertEquals(expected, result); } } From 03aac31e0cac5175ec4ee418e5032315aee2fdcf Mon Sep 17 00:00:00 2001 From: jhbengtsson Date: Thu, 29 Sep 2022 11:17:32 +0200 Subject: [PATCH 021/158] changeRefinementSymbols added to display >= and <= correctly --- .../ecdar/presentations/QueryPresentation.java | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/main/java/ecdar/presentations/QueryPresentation.java b/src/main/java/ecdar/presentations/QueryPresentation.java index db1d9179..17edd1d7 100644 --- a/src/main/java/ecdar/presentations/QueryPresentation.java +++ b/src/main/java/ecdar/presentations/QueryPresentation.java @@ -254,12 +254,27 @@ private void initializeStateIndicator() { if (controller.getQuery().getQuery().isEmpty()) return; Label label = new Label(tooltip.getText()); - JFXDialog dialog = new InformationDialogPresentation("Result from query: " + controller.getQuery().getQuery(), label); + //Make sure that certain symbols in the query are displayed correctly + String newString = changeRefinementSymbols(); + + JFXDialog dialog = new InformationDialogPresentation("Result from query: " + newString, label); dialog.show(Ecdar.getPresentation()); }); }); } + private String changeRefinementSymbols() { + String queryText = controller.getQuery().getQuery(); + String newString = ""; + if(queryText.contains("<=")){ + newString = queryText.replace("<=","\u2264"); + } + if(queryText.contains(">=")){ + newString = queryText.replace("<=","\u2265"); + } + return newString; + } + private void setStatusIndicatorContentColor(javafx.scene.paint.Color color, FontIcon statusIcon, FontIcon queryTypeExpandIcon, QueryState queryState) { statusIcon.setIconColor(color); controller.queryTypeSymbol.setFill(color); From 4238fb69990634b1ad2aab01089f488b03418ce9 Mon Sep 17 00:00:00 2001 From: APaludan Date: Fri, 30 Sep 2022 10:44:33 +0200 Subject: [PATCH 022/158] refactoring --- src/main/java/ecdar/abstractions/Query.java | 18 +----------------- .../ecdar/controllers/QueryController.java | 11 ++--------- .../ecdar/presentations/QueryPresentation.java | 10 +--------- 3 files changed, 4 insertions(+), 35 deletions(-) diff --git a/src/main/java/ecdar/abstractions/Query.java b/src/main/java/ecdar/abstractions/Query.java index d59c9b84..04bf9d2a 100644 --- a/src/main/java/ecdar/abstractions/Query.java +++ b/src/main/java/ecdar/abstractions/Query.java @@ -59,23 +59,7 @@ public ObjectProperty queryStateProperty() { } public String getQuery() { - String newQuery = ""; - String oldQuery = this.query.get(); - boolean hasBeenTrimmed = false; - - if(oldQuery.contains("\u2264")){ - newQuery = oldQuery.replace("\u2264","<="); - hasBeenTrimmed = true; - } - if(oldQuery.contains("\u2265")){ - newQuery = oldQuery.replace("\u2265",">="); - hasBeenTrimmed = true; - } - if(!hasBeenTrimmed){ - return oldQuery; - } - - return newQuery; + return this.query.get().replace("\u2264","<=").replace("\u2265",">="); } public void setQuery(final String query) { diff --git a/src/main/java/ecdar/controllers/QueryController.java b/src/main/java/ecdar/controllers/QueryController.java index dec3b0a6..36a99909 100644 --- a/src/main/java/ecdar/controllers/QueryController.java +++ b/src/main/java/ecdar/controllers/QueryController.java @@ -48,15 +48,8 @@ public void changed(ObservableValue observable, String oldValu //Method for replacing "<=" and ">=" with its unicode character public void updateQueryTextFieldString(String newValue) { - String tmpValue = ""; - if(newValue.contains("<=")){ - tmpValue = newValue.replace("<=","\u2264"); - queryText.setText(tmpValue); - } - if(newValue.contains(">=")){ - tmpValue = newValue.replace(">=","\u2265"); - queryText.setText(tmpValue); - } + String tmpValue = newValue.replace(">=","\u2265").replace("<=","\u2264"); + queryText.setText(tmpValue); } public void setQuery(Query query) { diff --git a/src/main/java/ecdar/presentations/QueryPresentation.java b/src/main/java/ecdar/presentations/QueryPresentation.java index 17edd1d7..e3d9a4e3 100644 --- a/src/main/java/ecdar/presentations/QueryPresentation.java +++ b/src/main/java/ecdar/presentations/QueryPresentation.java @@ -264,15 +264,7 @@ private void initializeStateIndicator() { } private String changeRefinementSymbols() { - String queryText = controller.getQuery().getQuery(); - String newString = ""; - if(queryText.contains("<=")){ - newString = queryText.replace("<=","\u2264"); - } - if(queryText.contains(">=")){ - newString = queryText.replace("<=","\u2265"); - } - return newString; + return controller.getQuery().getQuery().replace("<=","\u2264").replace("<=","\u2265"); } private void setStatusIndicatorContentColor(javafx.scene.paint.Color color, FontIcon statusIcon, FontIcon queryTypeExpandIcon, QueryState queryState) { From 6297e2edfb7f39783206b4382fe7d20f6ee1781c Mon Sep 17 00:00:00 2001 From: jhbengtsson Date: Mon, 3 Oct 2022 11:03:24 +0200 Subject: [PATCH 023/158] =?UTF-8?q?ikke=20l=C3=A6ngere=20i=20en=20funktion?= =?UTF-8?q?=20men=20direkte=20i=20changelistener?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/ecdar/controllers/QueryController.java | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/main/java/ecdar/controllers/QueryController.java b/src/main/java/ecdar/controllers/QueryController.java index 36a99909..35342291 100644 --- a/src/main/java/ecdar/controllers/QueryController.java +++ b/src/main/java/ecdar/controllers/QueryController.java @@ -40,16 +40,10 @@ public void initialize(URL location, ResourceBundle resources) { queryText.textProperty().addListener(new ChangeListener() { @Override public void changed(ObservableValue observable, String oldValue, String newValue) { - updateQueryTextFieldString(newValue); + String tmpValue = newValue.replace(">=","\u2265").replace("<=","\u2264"); + queryText.setText(tmpValue); } }); - - } - - //Method for replacing "<=" and ">=" with its unicode character - public void updateQueryTextFieldString(String newValue) { - String tmpValue = newValue.replace(">=","\u2265").replace("<=","\u2264"); - queryText.setText(tmpValue); } public void setQuery(Query query) { From 568df225ae2182f6a1ff3c67742f6292ad230f1b Mon Sep 17 00:00:00 2001 From: jhbengtsson Date: Wed, 5 Oct 2022 08:52:42 +0200 Subject: [PATCH 024/158] =?UTF-8?q?to=20static=20metoder=20p=C3=A5=20query?= =?UTF-8?q?.java=20til=20at=20replace=20refinement=20og=20unicode=20->=20s?= =?UTF-8?q?orry=20man=20kunne=20godt=20lave=20en=20static=20metode?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/ecdar/abstractions/Query.java | 10 +++++++++- src/main/java/ecdar/controllers/QueryController.java | 3 +-- .../java/ecdar/presentations/QueryPresentation.java | 8 +------- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/main/java/ecdar/abstractions/Query.java b/src/main/java/ecdar/abstractions/Query.java index 04bf9d2a..c156e9b5 100644 --- a/src/main/java/ecdar/abstractions/Query.java +++ b/src/main/java/ecdar/abstractions/Query.java @@ -46,6 +46,14 @@ public Query(final JsonObject jsonElement) { initializeRunQuery(); } + public static String RefinementSymbolToUnicode(String stringToReplace){ + return stringToReplace.replace(">=","\u2265").replace("<=","\u2264"); + } + + public static String UnicodeToRefinementSymbol(String stringToReplace){ + return stringToReplace.replace("\u2264","<=").replace("\u2265",">="); + } + public QueryState getQueryState() { return queryState.get(); } @@ -59,7 +67,7 @@ public ObjectProperty queryStateProperty() { } public String getQuery() { - return this.query.get().replace("\u2264","<=").replace("\u2265",">="); + return UnicodeToRefinementSymbol(this.query.get()); } public void setQuery(final String query) { diff --git a/src/main/java/ecdar/controllers/QueryController.java b/src/main/java/ecdar/controllers/QueryController.java index 35342291..b1da784f 100644 --- a/src/main/java/ecdar/controllers/QueryController.java +++ b/src/main/java/ecdar/controllers/QueryController.java @@ -40,8 +40,7 @@ public void initialize(URL location, ResourceBundle resources) { queryText.textProperty().addListener(new ChangeListener() { @Override public void changed(ObservableValue observable, String oldValue, String newValue) { - String tmpValue = newValue.replace(">=","\u2265").replace("<=","\u2264"); - queryText.setText(tmpValue); + queryText.setText(Query.RefinementSymbolToUnicode(newValue)); } }); } diff --git a/src/main/java/ecdar/presentations/QueryPresentation.java b/src/main/java/ecdar/presentations/QueryPresentation.java index e3d9a4e3..934b7484 100644 --- a/src/main/java/ecdar/presentations/QueryPresentation.java +++ b/src/main/java/ecdar/presentations/QueryPresentation.java @@ -254,19 +254,13 @@ private void initializeStateIndicator() { if (controller.getQuery().getQuery().isEmpty()) return; Label label = new Label(tooltip.getText()); - //Make sure that certain symbols in the query are displayed correctly - String newString = changeRefinementSymbols(); - JFXDialog dialog = new InformationDialogPresentation("Result from query: " + newString, label); + JFXDialog dialog = new InformationDialogPresentation("Result from query: " + Query.RefinementSymbolToUnicode(controller.getQuery().getQuery()), label); dialog.show(Ecdar.getPresentation()); }); }); } - private String changeRefinementSymbols() { - return controller.getQuery().getQuery().replace("<=","\u2264").replace("<=","\u2265"); - } - private void setStatusIndicatorContentColor(javafx.scene.paint.Color color, FontIcon statusIcon, FontIcon queryTypeExpandIcon, QueryState queryState) { statusIcon.setIconColor(color); controller.queryTypeSymbol.setFill(color); From 5e25f84b6be17355cfefa3e4553830f89c3cc807 Mon Sep 17 00:00:00 2001 From: WassawRoki <56611129+WassawRoki@users.noreply.github.com> Date: Wed, 5 Oct 2022 10:16:05 +0200 Subject: [PATCH 025/158] Update QueryPresentation.fxml --- src/main/resources/ecdar/presentations/QueryPresentation.fxml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/ecdar/presentations/QueryPresentation.fxml b/src/main/resources/ecdar/presentations/QueryPresentation.fxml index 94e1687d..3cca97b1 100644 --- a/src/main/resources/ecdar/presentations/QueryPresentation.fxml +++ b/src/main/resources/ecdar/presentations/QueryPresentation.fxml @@ -16,7 +16,7 @@ - + From 4a95a24532ddcc90a97637c300038647a2422b56 Mon Sep 17 00:00:00 2001 From: APaludan Date: Wed, 5 Oct 2022 11:00:37 +0200 Subject: [PATCH 026/158] center query icons --- src/main/resources/ecdar/presentations/QueryPresentation.fxml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/ecdar/presentations/QueryPresentation.fxml b/src/main/resources/ecdar/presentations/QueryPresentation.fxml index 3cca97b1..8aa2c391 100644 --- a/src/main/resources/ecdar/presentations/QueryPresentation.fxml +++ b/src/main/resources/ecdar/presentations/QueryPresentation.fxml @@ -12,7 +12,7 @@ HBox.hgrow="ALWAYS" alignment="CENTER" fx:controller="ecdar.controllers.QueryController"> - + From baa1b684cc0e34af770f9b8467ee820cd892d4ba Mon Sep 17 00:00:00 2001 From: Emilie Steinmann Date: Wed, 5 Oct 2022 20:52:21 +0200 Subject: [PATCH 027/158] When ENTER is pressed, we check if a query type has been selected before running query. --- src/main/java/ecdar/presentations/QueryPresentation.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ecdar/presentations/QueryPresentation.java b/src/main/java/ecdar/presentations/QueryPresentation.java index 1f0d8409..48d49609 100644 --- a/src/main/java/ecdar/presentations/QueryPresentation.java +++ b/src/main/java/ecdar/presentations/QueryPresentation.java @@ -73,7 +73,7 @@ private void initializeTextFields() { queryTextField.setOnKeyPressed(EcdarController.getActiveCanvasPresentation().getController().getLeaveTextAreaKeyHandler(keyEvent -> { Platform.runLater(() -> { - if (keyEvent.getCode().equals(KeyCode.ENTER)) { + if (keyEvent.getCode().equals(KeyCode.ENTER) && controller.getQuery().getType().getQueryName() != null) { runQuery(); } }); From 8149c9346baeae42e43ecd46caf96252d481e243 Mon Sep 17 00:00:00 2001 From: "Niels F. S. Vistisen" <42961494+Nielswps@users.noreply.github.com> Date: Thu, 13 Oct 2022 10:20:12 +0200 Subject: [PATCH 028/158] Simulator (#35) Fundament fra Niels --- build.gradle | 1 - src/main/java/ecdar/Ecdar.java | 27 +- src/main/java/ecdar/abstractions/Edge.java | 1 + .../java/ecdar/abstractions/Location.java | 17 + src/main/java/ecdar/abstractions/Nail.java | 8 + .../java/ecdar/abstractions/Transition.java | 30 + .../java/ecdar/backend/BackendDriver.java | 13 +- .../controllers/DeclarationsController.java | 9 +- .../ecdar/controllers/EcdarController.java | 577 +++++++----------- .../ecdar/controllers/EditorController.java | 380 ++++++++++++ .../controllers/LeftSimPaneController.java | 25 + .../ecdar/controllers/ProcessController.java | 252 ++++++++ .../controllers/ProjectPaneController.java | 3 +- .../controllers/RightSimPaneController.java | 20 + .../ecdar/controllers/SimEdgeController.java | 421 +++++++++++++ .../controllers/SimLocationController.java | 124 ++++ .../ecdar/controllers/SimNailController.java | 126 ++++ ...ulationInitializationDialogController.java | 18 + .../controllers/SimulatorController.java | 149 +++++ .../SimulatorOverviewController.java | 385 ++++++++++++ .../TracePaneElementController.java | 187 ++++++ .../controllers/TransitionController.java | 45 ++ .../TransitionPaneElementController.java | 241 ++++++++ .../presentations/EcdarPresentation.java | 344 +++-------- .../presentations/EditorPresentation.java | 201 ++++++ .../ecdar/presentations/FilePresentation.java | 2 +- .../LeftSimPanePresentation.java | 37 ++ .../presentations/ProcessPresentation.java | 282 +++++++++ .../RightSimPanePresentation.java | 45 ++ .../presentations/SimEdgePresentation.java | 32 + .../SimLocationPresentation.java | 492 +++++++++++++++ .../presentations/SimNailPresentation.java | 258 ++++++++ .../presentations/SimTagPresentation.java | 186 ++++++ ...ationInitializationDialogPresentation.java | 17 + .../SimulatorOverviewPresentation.java | 20 + .../presentations/SimulatorPresentation.java | 20 + .../TracePaneElementPresentation.java | 96 +++ .../TransitionPaneElementPresentation.java | 69 +++ .../presentations/TransitionPresentation.java | 98 +++ .../ecdar/simulation/SimulationHandler.java | 410 +++++++++++++ .../ecdar/simulation/SimulationState.java | 52 ++ .../simulation/SimulationStateSuccessor.java | 17 + .../java/ecdar/simulation/Transition.java | 17 + .../ecdar/utility/helpers/BindingHelper.java | 10 + src/main/resources/ecdar/main.css | 20 +- .../presentations/EcdarPresentation.fxml | 132 ++-- .../presentations/EditorPresentation.fxml | 81 +++ .../LeftSimPanePresentation.fxml | 28 + .../presentations/ProcessPresentation.fxml | 56 ++ .../ProjectPanePresentation.fxml | 3 +- .../presentations/QueryPanePresentation.fxml | 3 +- .../RightSimPanePresentation.fxml | 24 + .../presentations/SimEdgePresentation.fxml | 12 + .../SimLocationPresentation.fxml | 39 ++ .../presentations/SimNailPresentation.fxml | 23 + .../presentations/SimTagPresentation.fxml | 11 + ...ationInitializationDialogPresentation.fxml | 43 ++ .../SimulatorOverviewPresentation.fxml | 13 + .../presentations/SimulatorPresentation.fxml | 19 + .../TracePaneElementPresentation.fxml | 49 ++ .../TransitionPaneElementPresentation.fxml | 51 ++ .../presentations/TransitionPresentation.fxml | 18 + src/test/java/ecdar/TestFXBase.java | 30 + .../java/ecdar/simulation/SimulationTest.java | 101 +++ 64 files changed, 5780 insertions(+), 740 deletions(-) create mode 100644 src/main/java/ecdar/abstractions/Transition.java create mode 100644 src/main/java/ecdar/controllers/EditorController.java create mode 100755 src/main/java/ecdar/controllers/LeftSimPaneController.java create mode 100755 src/main/java/ecdar/controllers/ProcessController.java create mode 100755 src/main/java/ecdar/controllers/RightSimPaneController.java create mode 100755 src/main/java/ecdar/controllers/SimEdgeController.java create mode 100755 src/main/java/ecdar/controllers/SimLocationController.java create mode 100755 src/main/java/ecdar/controllers/SimNailController.java create mode 100644 src/main/java/ecdar/controllers/SimulationInitializationDialogController.java create mode 100644 src/main/java/ecdar/controllers/SimulatorController.java create mode 100644 src/main/java/ecdar/controllers/SimulatorOverviewController.java create mode 100755 src/main/java/ecdar/controllers/TracePaneElementController.java create mode 100755 src/main/java/ecdar/controllers/TransitionController.java create mode 100755 src/main/java/ecdar/controllers/TransitionPaneElementController.java create mode 100644 src/main/java/ecdar/presentations/EditorPresentation.java create mode 100755 src/main/java/ecdar/presentations/LeftSimPanePresentation.java create mode 100755 src/main/java/ecdar/presentations/ProcessPresentation.java create mode 100755 src/main/java/ecdar/presentations/RightSimPanePresentation.java create mode 100755 src/main/java/ecdar/presentations/SimEdgePresentation.java create mode 100755 src/main/java/ecdar/presentations/SimLocationPresentation.java create mode 100755 src/main/java/ecdar/presentations/SimNailPresentation.java create mode 100755 src/main/java/ecdar/presentations/SimTagPresentation.java create mode 100644 src/main/java/ecdar/presentations/SimulationInitializationDialogPresentation.java create mode 100755 src/main/java/ecdar/presentations/SimulatorOverviewPresentation.java create mode 100755 src/main/java/ecdar/presentations/SimulatorPresentation.java create mode 100755 src/main/java/ecdar/presentations/TracePaneElementPresentation.java create mode 100755 src/main/java/ecdar/presentations/TransitionPaneElementPresentation.java create mode 100755 src/main/java/ecdar/presentations/TransitionPresentation.java create mode 100755 src/main/java/ecdar/simulation/SimulationHandler.java create mode 100644 src/main/java/ecdar/simulation/SimulationState.java create mode 100644 src/main/java/ecdar/simulation/SimulationStateSuccessor.java create mode 100644 src/main/java/ecdar/simulation/Transition.java create mode 100644 src/main/resources/ecdar/presentations/EditorPresentation.fxml create mode 100755 src/main/resources/ecdar/presentations/LeftSimPanePresentation.fxml create mode 100755 src/main/resources/ecdar/presentations/ProcessPresentation.fxml create mode 100755 src/main/resources/ecdar/presentations/RightSimPanePresentation.fxml create mode 100755 src/main/resources/ecdar/presentations/SimEdgePresentation.fxml create mode 100755 src/main/resources/ecdar/presentations/SimLocationPresentation.fxml create mode 100755 src/main/resources/ecdar/presentations/SimNailPresentation.fxml create mode 100755 src/main/resources/ecdar/presentations/SimTagPresentation.fxml create mode 100644 src/main/resources/ecdar/presentations/SimulationInitializationDialogPresentation.fxml create mode 100755 src/main/resources/ecdar/presentations/SimulatorOverviewPresentation.fxml create mode 100755 src/main/resources/ecdar/presentations/SimulatorPresentation.fxml create mode 100755 src/main/resources/ecdar/presentations/TracePaneElementPresentation.fxml create mode 100755 src/main/resources/ecdar/presentations/TransitionPaneElementPresentation.fxml create mode 100755 src/main/resources/ecdar/presentations/TransitionPresentation.fxml create mode 100644 src/test/java/ecdar/TestFXBase.java create mode 100644 src/test/java/ecdar/simulation/SimulationTest.java diff --git a/build.gradle b/build.gradle index 1b049e61..f2c68089 100644 --- a/build.gradle +++ b/build.gradle @@ -46,7 +46,6 @@ def protocVersion = protobufVersion dependencies { implementation fileTree(dir: 'lib', include: ['*.jar']) - implementation 'com.jfoenix:jfoenix:9.0.10' implementation group: 'de.codecentric.centerdevice', name: 'javafxsvg', version: '1.3.0' implementation 'org.kordamp.ikonli:ikonli-core:12.3.1' diff --git a/src/main/java/ecdar/Ecdar.java b/src/main/java/ecdar/Ecdar.java index 05bd9efc..d2dc3dd9 100644 --- a/src/main/java/ecdar/Ecdar.java +++ b/src/main/java/ecdar/Ecdar.java @@ -4,6 +4,7 @@ import ecdar.abstractions.Project; import ecdar.backend.BackendDriver; import ecdar.backend.BackendHelper; +import ecdar.simulation.SimulationHandler; import ecdar.code_analysis.CodeAnalysis; import ecdar.controllers.EcdarController; import ecdar.presentations.BackgroundThreadPresentation; @@ -52,6 +53,7 @@ public class Ecdar extends Application { public static BooleanProperty shouldRunBackgroundQueries = new SimpleBooleanProperty(true); private static final BooleanProperty isSplit = new SimpleBooleanProperty(true); //Set to true to ensure correct behaviour at first toggle. private static BackendDriver backendDriver = new BackendDriver(); + private static SimulationHandler simulationHandler; private Stage debugStage; /** @@ -120,6 +122,16 @@ public static Project getProject() { return project; } + /** + * Returns the backend driver used to execute queries and handle simulation + * @return BackendDriver + */ + public static BackendDriver getBackendDriver() { + return backendDriver; + } + + public static SimulationHandler getSimulationHandler() { return simulationHandler; } + public static EcdarPresentation getPresentation() { return presentation; } @@ -134,8 +146,8 @@ public static void showHelp() { presentation.showHelp(); } - public static BooleanProperty toggleFilePane() { - return presentation.toggleFilePane(); + public static BooleanProperty toggleLeftPane() { + return presentation.toggleLeftPane(); } /** @@ -160,7 +172,7 @@ public static BooleanProperty toggleRunBackgroundQueries() { } public static BooleanProperty toggleQueryPane() { - return presentation.toggleQueryPane(); + return presentation.toggleRightPane(); } /** @@ -181,14 +193,6 @@ public static BooleanProperty toggleCanvasSplit() { return isSplit; } - /** - * Returns the backend driver used to execute queries and handle simulation - * @return BackendDriver - */ - public static BackendDriver getBackendDriver() { - return backendDriver; - } - public static double getDpiScale() { if (!autoScalingEnabled.getValue()) return 1; @@ -207,6 +211,7 @@ public void start(final Stage stage) { // Load or create new project project = new Project(); + simulationHandler = new SimulationHandler(); // Set the title for the application stage.setTitle("Ecdar " + VERSION); diff --git a/src/main/java/ecdar/abstractions/Edge.java b/src/main/java/ecdar/abstractions/Edge.java index 995e2ce8..2b1057c9 100644 --- a/src/main/java/ecdar/abstractions/Edge.java +++ b/src/main/java/ecdar/abstractions/Edge.java @@ -1,5 +1,6 @@ package ecdar.abstractions; +import EcdarProtoBuf.ComponentProtos; import com.google.gson.JsonPrimitive; import ecdar.Ecdar; import ecdar.controllers.EcdarController; diff --git a/src/main/java/ecdar/abstractions/Location.java b/src/main/java/ecdar/abstractions/Location.java index b490f5b9..dd3d0ebe 100644 --- a/src/main/java/ecdar/abstractions/Location.java +++ b/src/main/java/ecdar/abstractions/Location.java @@ -1,5 +1,6 @@ package ecdar.abstractions; +import EcdarProtoBuf.ComponentProtos; import ecdar.Ecdar; import ecdar.code_analysis.Nearable; import ecdar.controllers.EcdarController; @@ -87,6 +88,22 @@ public Location(final JsonObject jsonObject) { bindReachabilityAnalysis(); } + // ToDo NIELS: Comment in, when location should be received through ProtoBuf +// public Location(ComponentProtos.Location protoBufLocation) { +// setId(protoBufLocation.getId()); +// setNickname(protoBufLocation.getNickname()); +// setInvariant(protoBufLocation.getInvariant()); +// setType(Type.valueOf(protoBufLocation.getType())); +// setUrgency(Urgency.valueOf(protoBufLocation.getUrgency())); +// setX(protoBufLocation.getX()); +// setY(protoBufLocation.getY()); +// setColor(Color.valueOf(protoBufLocation.getColor())); +// setNicknameX(protoBufLocation.getNicknameX()); +// setNicknameY(protoBufLocation.getNicknameY()); +// setInvariantX(protoBufLocation.getInvariantX()); +// setInvariantY(protoBufLocation.getInvariantY()); +// } + /** * Generates an id for this, and binds reachability analysis. */ diff --git a/src/main/java/ecdar/abstractions/Nail.java b/src/main/java/ecdar/abstractions/Nail.java index 5ed84d45..f7555432 100644 --- a/src/main/java/ecdar/abstractions/Nail.java +++ b/src/main/java/ecdar/abstractions/Nail.java @@ -1,5 +1,6 @@ package ecdar.abstractions; +import EcdarProtoBuf.ComponentProtos; import ecdar.utility.helpers.Circular; import ecdar.utility.serialize.Serializable; import com.google.gson.Gson; @@ -39,6 +40,13 @@ public Nail(final JsonObject jsonObject) { deserialize(jsonObject); } + // ToDo NIELS: Comment in, when location should be received through ProtoBuf +// public Nail(ComponentProtos.Nail protoBufNail) { +// setPropertyType(DisplayableEdge.PropertyType.valueOf(protoBufNail.getPropertyType())); +// setPropertyX(protoBufNail.getPropertyX()); +// setPropertyY(protoBufNail.getPropertyY()); +// } + public double getX() { return x.get(); } diff --git a/src/main/java/ecdar/abstractions/Transition.java b/src/main/java/ecdar/abstractions/Transition.java new file mode 100644 index 00000000..9b9d25be --- /dev/null +++ b/src/main/java/ecdar/abstractions/Transition.java @@ -0,0 +1,30 @@ +package ecdar.abstractions; + +import EcdarProtoBuf.ObjectProtos; +import ecdar.Ecdar; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; + +public class Transition { + public final ArrayList edges = new ArrayList<>(); + + public Transition(ObjectProtos.Transition protoBufTransition) { + List protoBufEdges = (List) protoBufTransition.getField(ObjectProtos.Transition.getDescriptor().findFieldByName("edges")); + List componentNames = protoBufEdges.stream().map(ObjectProtos.Transition.EdgeTuple::getComponentName).collect(Collectors.toList()); + List edgeIds = protoBufEdges.stream().map(ObjectProtos.Transition.EdgeTuple::getId).collect(Collectors.toList()); + + // For each affected component, find each affected edge within that component, and add these edges to the edges list + List affectedComponents = Ecdar.getProject().getComponents().stream().filter(c -> componentNames.contains(c.getName())).collect(Collectors.toList()); + edges.addAll(affectedComponents.stream() + .map(c -> c.getEdges() + .stream() + .filter(e -> edgeIds.contains(e.getId())) + .collect(Collectors.toList())) + .flatMap(Collection::stream) + .collect(Collectors.toList()) + ); + } +} diff --git a/src/main/java/ecdar/backend/BackendDriver.java b/src/main/java/ecdar/backend/BackendDriver.java index a3ab0e1a..36418b39 100644 --- a/src/main/java/ecdar/backend/BackendDriver.java +++ b/src/main/java/ecdar/backend/BackendDriver.java @@ -2,6 +2,7 @@ import EcdarProtoBuf.ComponentProtos; import EcdarProtoBuf.EcdarBackendGrpc; +import EcdarProtoBuf.ObjectProtos; import EcdarProtoBuf.QueryProtos; import com.google.gson.JsonObject; import com.google.gson.JsonParser; @@ -10,10 +11,12 @@ import ecdar.abstractions.BackendInstance; import ecdar.abstractions.Component; import ecdar.abstractions.QueryState; -import ecdar.controllers.EcdarController; -import ecdar.utility.UndoRedoStack; +import ecdar.simulation.SimulationState; import io.grpc.*; import io.grpc.stub.StreamObserver; +import javafx.util.Pair; +import ecdar.controllers.EcdarController; +import ecdar.utility.UndoRedoStack; import javafx.application.Platform; import javafx.collections.ObservableList; import org.springframework.util.SocketUtils; @@ -384,6 +387,12 @@ private void handleQueryBackendError(Throwable t, ExecutableQuery executableQuer } } + public SimulationState getInitialSimulationState() { + SimulationState state = new SimulationState(ObjectProtos.StateTuple.newBuilder().getDefaultInstanceForType()); + state.getLocations().add(new Pair<>(Ecdar.getProject().getComponents().get(0).getName(), Ecdar.getProject().getComponents().get(0).getLocations().get(0).getId())); + return state; + } + private void addGeneratedComponent(Component newComponent) { Platform.runLater(() -> { newComponent.setTemporary(true); diff --git a/src/main/java/ecdar/controllers/DeclarationsController.java b/src/main/java/ecdar/controllers/DeclarationsController.java index 4b55672a..b4ab05e0 100644 --- a/src/main/java/ecdar/controllers/DeclarationsController.java +++ b/src/main/java/ecdar/controllers/DeclarationsController.java @@ -43,11 +43,10 @@ public void initialize(final URL location, final ResourceBundle resources) { */ private void initializeWidthAndHeight() { // Fetch width and height of canvas and update - root.minWidthProperty().bind(Ecdar.getPresentation().getController().canvasPane.minWidthProperty()); - root.maxWidthProperty().bind(Ecdar.getPresentation().getController().canvasPane.maxWidthProperty()); - root.minHeightProperty().bind(Ecdar.getPresentation().getController().canvasPane.minHeightProperty()); - root.maxHeightProperty().bind(Ecdar.getPresentation().getController().canvasPane.maxHeightProperty()); - textArea.setTranslateY(20); + root.minWidthProperty().bind(Ecdar.getPresentation().getController().getEditorPresentation().getController().canvasPane.minWidthProperty()); + root.maxWidthProperty().bind(Ecdar.getPresentation().getController().getEditorPresentation().getController().canvasPane.maxWidthProperty()); + root.minHeightProperty().bind(Ecdar.getPresentation().getController().getEditorPresentation().getController().canvasPane.minHeightProperty()); + root.maxHeightProperty().bind(Ecdar.getPresentation().getController().getEditorPresentation().getController().canvasPane.maxHeightProperty()); } /** diff --git a/src/main/java/ecdar/controllers/EcdarController.java b/src/main/java/ecdar/controllers/EcdarController.java index e350b296..8c432ed5 100644 --- a/src/main/java/ecdar/controllers/EcdarController.java +++ b/src/main/java/ecdar/controllers/EcdarController.java @@ -12,7 +12,6 @@ import ecdar.presentations.*; import ecdar.utility.UndoRedoStack; import ecdar.utility.colors.Color; -import ecdar.utility.colors.EnabledColor; import ecdar.utility.helpers.SelectHelper; import ecdar.utility.keyboard.Keybind; import ecdar.utility.keyboard.KeyboardTracker; @@ -23,7 +22,6 @@ import javafx.beans.binding.When; import javafx.beans.property.*; import javafx.collections.ListChangeListener; -import javafx.collections.ObservableList; import javafx.embed.swing.SwingFXUtils; import javafx.fxml.FXML; import javafx.fxml.Initializable; @@ -39,7 +37,6 @@ import javafx.scene.text.Text; import javafx.stage.DirectoryChooser; import javafx.stage.FileChooser; -import javafx.util.Pair; import javax.imageio.ImageIO; import java.awt.image.BufferedImage; @@ -49,6 +46,7 @@ import java.util.*; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.stream.Collectors; public class EcdarController implements Initializable { // Reachability analysis @@ -56,29 +54,19 @@ public class EcdarController implements Initializable { private static long reachabilityTime = Long.MAX_VALUE; private static ExecutorService reachabilityService; - private static final ObjectProperty globalEdgeStatus = new SimpleObjectProperty<>(EdgeStatus.INPUT); - // View stuff public StackPane root; public BorderPane borderPane; - public StackPane canvasPane; public StackPane topPane; public StackPane leftPane; public StackPane rightPane; public Rectangle bottomFillerElement; - public QueryPanePresentation queryPane; - public ProjectPanePresentation filePane; - public HBox toolbar; public MessageTabPanePresentation messageTabPane; - public StackPane dialogContainer; - public JFXDialog dialog; + public StackPane modellingHelpDialogContainer; + public JFXDialog modellingHelpDialog; public StackPane modalBar; public JFXTextField queryTextField; public JFXTextField commentTextField; - public JFXRippler colorSelected; - public JFXRippler deleteSelected; - public JFXRippler undo; - public JFXRippler redo; public ImageView helpInitialImage; public StackPane helpInitialPane; @@ -89,10 +77,6 @@ public class EcdarController implements Initializable { public ImageView helpOutputImage; public StackPane helpOutputPane; - public JFXButton switchToInputButton; - public JFXButton switchToOutputButton; - public JFXToggleButton switchEdgeStatusButton; - public MenuItem menuEditMoveLeft; public MenuItem menuEditMoveUp; public MenuItem menuEditMoveRight; @@ -106,10 +90,10 @@ public class EcdarController implements Initializable { // The program top menu public MenuBar menuBar; - public MenuItem menuBarViewFilePanel; + public MenuItem menuBarViewProjectPanel; public MenuItem menuBarViewQueryPanel; public MenuItem menuBarViewGrid; - public MenuItem menuBarAutoscaling; + public MenuItem menuBarViewAutoscaling; public Menu menuViewMenuScaling; public ToggleGroup scaling; public RadioMenuItem scaleXS; @@ -120,6 +104,8 @@ public class EcdarController implements Initializable { public RadioMenuItem scaleXXL; public RadioMenuItem scaleXXXL; public MenuItem menuBarViewCanvasSplit; + public MenuItem menuBarViewEditor; + public MenuItem menuBarViewSimulator; public MenuItem menuBarFileCreateNewProject; public MenuItem menuBarFileOpenProject; public Menu menuBarFileRecentProjects; @@ -135,6 +121,7 @@ public class EcdarController implements Initializable { public MenuItem menuBarHelpAbout; public MenuItem menuBarHelpTest; + public JFXToggleButton switchGuiView; public Snackbar snackbar; public HBox statusBar; public Label statusLabel; @@ -148,6 +135,10 @@ public class EcdarController implements Initializable { public StackPane backendOptionsDialogContainer; public BackendOptionsDialogPresentation backendOptionsDialog; + + public StackPane simulationInitializationDialogContainer; + public SimulationInitializationDialogPresentation simulationInitializationDialog; + public final DoubleProperty scalingProperty = new SimpleDoubleProperty(); private static JFXDialog _queryDialog; @@ -161,10 +152,89 @@ public static void runReachabilityAnalysis() { reachabilityTime = System.currentTimeMillis() + 500; } - private static final ObjectProperty activeCanvasPresentation = new SimpleObjectProperty<>(new CanvasPresentation()); + /** + * Enumeration to keep track of which mode the application is in + */ + private enum Mode { + Editor, Simulator + } + + /** + * currentMode is a property that keeps track of which mode the application is in. + * The initial mode is Mode.Editor + */ + private static final ObjectProperty currentMode = new SimpleObjectProperty<>(Mode.Editor); + + private static final EditorPresentation editorPresentation = new EditorPresentation(); + public final ProjectPanePresentation projectPane = new ProjectPanePresentation(); + public final QueryPanePresentation queryPane = new QueryPanePresentation(); + + private static final SimulatorPresentation simulatorPresentation = new SimulatorPresentation(); + public final LeftSimPanePresentation leftSimPane = new LeftSimPanePresentation(); + public final RightSimPanePresentation rightSimPane = new RightSimPanePresentation(); + + @Override + public void initialize(final URL location, final ResourceBundle resources) { + initializeDialogs(); + initializeKeybindings(); + initializeStatusBar(); + initializeMenuBar(); + startBackgroundQueriesThread(); // Will terminate immediately if background queries are turned off + + bottomFillerElement.heightProperty().bind(messageTabPane.maxHeightProperty()); + messageTabPane.getController().setRunnableForOpeningAndClosingMessageTabPane(this::changeInsetsOfProjectAndQueryPanes); + + // Update file coloring when active model changes + editorPresentation.getController().getActiveCanvasPresentation().getController().activeComponentProperty().addListener(observable -> projectPane.getController().updateColorsOnFilePresentations()); + editorPresentation.getController().activeCanvasPresentationProperty().addListener(observable -> projectPane.getController().updateColorsOnFilePresentations()); + + leftSimPane.minWidthProperty().bind(projectPane.minWidthProperty()); + leftSimPane.maxWidthProperty().bind(projectPane.maxWidthProperty()); + rightSimPane.minWidthProperty().bind(queryPane.minWidthProperty()); + rightSimPane.maxWidthProperty().bind(queryPane.maxWidthProperty()); + + enterEditorMode(); + } + + public StackPane getCenter() { + if (currentMode.get().equals(Mode.Editor)) { + return editorPresentation.getController().canvasPane; + } else { + return simulatorPresentation; + } + } public static EdgeStatus getGlobalEdgeStatus() { - return globalEdgeStatus.get(); + return editorPresentation.getController().getGlobalEdgeStatus(); + } + + public EditorPresentation getEditorPresentation() { + return editorPresentation; + } + + public SimulatorPresentation getSimulatorPresentation() { + return simulatorPresentation; + } + + public static CanvasPresentation getActiveCanvasPresentation() { + return editorPresentation.getController().getActiveCanvasPresentation(); + } + + public static DoubleProperty getActiveCanvasZoomFactor() { + return getActiveCanvasPresentation().getController().zoomHelper.currentZoomFactor; + } + + public static void setActiveCanvasPresentation(CanvasPresentation newActiveCanvasPresentation) { + getActiveCanvasPresentation().setOpacity(0.75); + newActiveCanvasPresentation.setOpacity(1); + editorPresentation.getController().setActiveCanvasPresentation(newActiveCanvasPresentation); + } + + public static void setActiveModelForActiveCanvas(HighLevelModelObject newActiveModel) { + getActiveCanvasPresentation().getController().setActiveModel(newActiveModel); + + // Change zoom level to fit new active model + Platform.runLater(() -> getActiveCanvasPresentation().getController().zoomHelper.zoomToFit()); } public static void setTemporaryComponentWatermarkVisibility(boolean visibility) { @@ -195,57 +265,53 @@ private void scaleIcons(Node node, double size) { Set xSmallIcons = node.lookupAll(".icon-size-x-small"); for (Node icon : xSmallIcons) icon.setStyle("-fx-icon-size: " + Math.floor(size / 13.0 * 18) + "px;"); + + switchGuiView.setSize(Math.floor(size / 13.0 * 24)); } private double getNewCalculatedScale() { return (Double.parseDouble(scaling.getSelectedToggle().getProperties().get("scale").toString()) * Ecdar.getDpiScale()) * 13.0; } - private void scaleEdgeStatusToggle(double size) { - switchEdgeStatusButton.setScaleX(size / 13.0); - switchEdgeStatusButton.setScaleY(size / 13.0); - } - - @Override - public void initialize(final URL location, final ResourceBundle resources) { - initilizeDialogs(); - initializeCanvasPane(); - initializeEdgeStatusHandling(); - initializeKeybindings(); - initializeStatusBar(); - initializeMenuBar(); - intitializeTemporaryComponentWatermark(); - startBackgroundQueriesThread(); // Will terminate immediately if background queries are turned off - - bottomFillerElement.heightProperty().bind(messageTabPane.maxHeightProperty()); - messageTabPane.getController().setRunnableForOpeningAndClosingMessageTabPane(this::changeInsetsOfFileAndQueryPanes); - } - - private void initilizeDialogs() { - dialog.setDialogContainer(dialogContainer); - dialogContainer.opacityProperty().bind(dialog.getChildren().get(0).scaleXProperty()); - dialog.setOnDialogClosed(event -> dialogContainer.setVisible(false)); + private void initializeDialogs() { + modellingHelpDialog.setDialogContainer(modellingHelpDialogContainer); + modellingHelpDialogContainer.opacityProperty().bind(modellingHelpDialog.getChildren().get(0).scaleXProperty()); + modellingHelpDialog.setOnDialogClosed(event -> modellingHelpDialogContainer.setVisible(false)); _queryDialog = queryDialog; _queryTextResult = queryTextResult; _queryTextQuery = queryTextQuery; initializeDialog(queryDialog, queryDialogContainer); - initializeDialog(backendOptionsDialog, backendOptionsDialogContainer); + initializeBackendOptionsDialog(); + + initializeDialog(simulationInitializationDialog, simulationInitializationDialogContainer); + simulationInitializationDialog.getController().cancelButton.setOnMouseClicked(event -> { + switchGuiView.setSelected(false); + simulationInitializationDialog.close(); + }); + + simulationInitializationDialog.getController().startButton.setOnMouseClicked(event -> { + // ToDo NIELS: Start simulation of selected query + currentMode.setValue(Mode.Simulator); + simulationInitializationDialog.close(); + }); + } + + private void initializeBackendOptionsDialog() { + initializeDialog(backendOptionsDialog, backendOptionsDialogContainer); backendOptionsDialog.getController().resetBackendsButton.setOnMouseClicked(event -> { backendOptionsDialog.getController().resetBackendsToDefault(); }); backendOptionsDialog.getController().closeButton.setOnMouseClicked(event -> { backendOptionsDialog.getController().cancelBackendOptionsChanges(); - dialog.close(); backendOptionsDialog.close(); }); backendOptionsDialog.getController().saveButton.setOnMouseClicked(event -> { if (backendOptionsDialog.getController().saveChangesToBackendOptions()) { - dialog.close(); backendOptionsDialog.close(); } }); @@ -271,10 +337,9 @@ private void initializeDialog(JFXDialog dialog, StackPane dialogContainer) { dialogContainer.setMouseTransparent(false); }); - filePane.getStyleClass().add("responsive-pane-sizing"); + projectPane.getStyleClass().add("responsive-pane-sizing"); queryPane.getStyleClass().add("responsive-pane-sizing"); - initializeEdgeStatusHandling(); initializeKeybindings(); initializeStatusBar(); } @@ -319,17 +384,14 @@ private void initializeKeybindings() { event.consume(); nudgeSelected(NudgeDirection.UP); })); - KeyboardTracker.registerKeybind(KeyboardTracker.NUDGE_DOWN, new Keybind(new KeyCodeCombination(KeyCode.DOWN), (event) -> { event.consume(); nudgeSelected(NudgeDirection.DOWN); })); - KeyboardTracker.registerKeybind(KeyboardTracker.NUDGE_LEFT, new Keybind(new KeyCodeCombination(KeyCode.LEFT), (event) -> { event.consume(); nudgeSelected(NudgeDirection.LEFT); })); - KeyboardTracker.registerKeybind(KeyboardTracker.NUDGE_RIGHT, new Keybind(new KeyCodeCombination(KeyCode.RIGHT), (event) -> { event.consume(); nudgeSelected(NudgeDirection.RIGHT); @@ -343,67 +405,6 @@ private void initializeKeybindings() { KeyboardTracker.registerKeybind(KeyboardTracker.NUDGE_A, new Keybind(new KeyCodeCombination(KeyCode.A), () -> nudgeSelected(NudgeDirection.LEFT))); KeyboardTracker.registerKeybind(KeyboardTracker.NUDGE_S, new Keybind(new KeyCodeCombination(KeyCode.S), () -> nudgeSelected(NudgeDirection.DOWN))); KeyboardTracker.registerKeybind(KeyboardTracker.NUDGE_D, new Keybind(new KeyCodeCombination(KeyCode.D), () -> nudgeSelected(NudgeDirection.RIGHT))); - - // Keybind for deleting the selected elements - KeyboardTracker.registerKeybind(KeyboardTracker.DELETE_SELECTED, new Keybind(new KeyCodeCombination(KeyCode.DELETE), this::deleteSelectedClicked)); - - // Keybinds for coloring the selected elements - EnabledColor.enabledColors.forEach(enabledColor -> { - KeyboardTracker.registerKeybind(KeyboardTracker.COLOR_SELECTED + "_" + enabledColor.keyCode.getName(), new Keybind(new KeyCodeCombination(enabledColor.keyCode), () -> { - - final List> previousColor = new ArrayList<>(); - - SelectHelper.getSelectedElements().forEach(selectable -> { - previousColor.add(new Pair<>(selectable, new EnabledColor(selectable.getColor(), selectable.getColorIntensity()))); - }); - changeColorOnSelectedElements(enabledColor, previousColor); - SelectHelper.clearSelectedElements(); - })); - }); - } - - /** - * Handles the change of color on selected objects - * - * @param enabledColor The new color for the selected objects - * @param previousColor The color old color of the selected objects - */ - public void changeColorOnSelectedElements(final EnabledColor enabledColor, - final List> previousColor) { - UndoRedoStack.pushAndPerform(() -> { // Perform - SelectHelper.getSelectedElements() - .forEach(selectable -> selectable.color(enabledColor.color, enabledColor.intensity)); - }, () -> { // Undo - previousColor.forEach(selectableEnabledColorPair -> selectableEnabledColorPair.getKey().color(selectableEnabledColorPair.getValue().color, selectableEnabledColorPair.getValue().intensity)); - }, String.format("Changed the color of %d elements to %s", previousColor.size(), enabledColor.color.name()), "color-lens"); - } - - /** - * Initializes edge status. - * Input is the default status. - * This method sets buttons for edge status whenever the status changes. - */ - private void initializeEdgeStatusHandling() { - globalEdgeStatus.set(EdgeStatus.INPUT); - - Tooltip.install(switchToInputButton, new Tooltip("Switch to input mode")); - Tooltip.install(switchToOutputButton, new Tooltip("Switch to output mode")); - switchToInputButton.setDisableVisualFocus(true); // Hiding input button rippler on start-up - - globalEdgeStatus.addListener(((observable, oldValue, newValue) -> { - if (newValue.equals(EdgeStatus.INPUT)) { - switchToInputButton.setTextFill(javafx.scene.paint.Color.WHITE); - switchToOutputButton.setTextFill(javafx.scene.paint.Color.GREY); - switchEdgeStatusButton.setSelected(false); - } else { - switchToInputButton.setTextFill(javafx.scene.paint.Color.GREY); - switchToOutputButton.setTextFill(javafx.scene.paint.Color.WHITE); - switchEdgeStatusButton.setSelected(true); - } - })); - - // Ensure that the rippler is centered when scale is changed - Platform.runLater(() -> ((JFXRippler) switchEdgeStatusButton.lookup(".jfx-rippler")).setRipplerRecenter(true)); } private void startBackgroundQueriesThread() { @@ -554,27 +555,6 @@ private void initializeMenuBar() { initializeHelpMenu(); } - public static CanvasPresentation getActiveCanvasPresentation() { - return activeCanvasPresentation.get(); - } - - public static DoubleProperty getActiveCanvasZoomFactor() { - return getActiveCanvasPresentation().getController().zoomHelper.currentZoomFactor; - } - - public static void setActiveCanvasPresentation(CanvasPresentation newActiveCanvasPresentation) { - activeCanvasPresentation.get().setOpacity(0.75); - newActiveCanvasPresentation.setOpacity(1); - activeCanvasPresentation.set(newActiveCanvasPresentation); - } - - public static void setActiveModelForActiveCanvas(HighLevelModelObject newActiveModel) { - EcdarController.getActiveCanvasPresentation().getController().setActiveModel(newActiveModel); - - // Change zoom level to fit new active model - Platform.runLater(() -> EcdarController.getActiveCanvasPresentation().getController().zoomHelper.zoomToFit()); - } - private void initializeHelpMenu() { menuBarHelpHelp.setOnAction(event -> Ecdar.showHelp()); @@ -591,7 +571,6 @@ private void initializeHelpMenu() { }); aboutAcceptButton.setOnAction(event -> aboutDialog.close()); aboutDialog.setOnDialogClosed(event -> aboutContainer.setVisible(false)); // hide container when dialog is fully closed - } /** @@ -660,11 +639,11 @@ private void initializeEditMenu() { * Initialize the View menu. */ private void initializeViewMenu() { - menuBarViewFilePanel.getGraphic().setOpacity(1); - menuBarViewFilePanel.setAccelerator(new KeyCodeCombination(KeyCode.P, KeyCodeCombination.SHORTCUT_DOWN)); - menuBarViewFilePanel.setOnAction(event -> { - final BooleanProperty isOpen = Ecdar.toggleFilePane(); - menuBarViewFilePanel.getGraphic().opacityProperty().bind(new When(isOpen).then(1).otherwise(0)); + menuBarViewProjectPanel.getGraphic().setOpacity(1); + menuBarViewProjectPanel.setAccelerator(new KeyCodeCombination(KeyCode.P, KeyCodeCombination.SHORTCUT_DOWN)); + menuBarViewProjectPanel.setOnAction(event -> { + final BooleanProperty isOpen = Ecdar.toggleLeftPane(); + menuBarViewProjectPanel.getGraphic().opacityProperty().bind(new When(isOpen).then(1).otherwise(0)); }); menuBarViewQueryPanel.getGraphic().setOpacity(0); @@ -681,14 +660,14 @@ private void initializeViewMenu() { menuBarViewGrid.getGraphic().opacityProperty().bind(new When(isOn).then(1).otherwise(0)); }); - menuBarAutoscaling.getGraphic().setOpacity(Ecdar.autoScalingEnabled.getValue() ? 1 : 0); - menuBarAutoscaling.setOnAction(event -> { + menuBarViewAutoscaling.getGraphic().setOpacity(Ecdar.autoScalingEnabled.getValue() ? 1 : 0); + menuBarViewAutoscaling.setOnAction(event -> { Ecdar.autoScalingEnabled.setValue(!Ecdar.autoScalingEnabled.getValue()); updateScaling(getNewCalculatedScale() / 13); Ecdar.preferences.put("autoscaling", String.valueOf(Ecdar.autoScalingEnabled.getValue())); }); Ecdar.autoScalingEnabled.addListener((observable, oldValue, newValue) -> { - menuBarAutoscaling.getGraphic().opacityProperty().setValue(newValue ? 1 : 0); + menuBarViewAutoscaling.getGraphic().opacityProperty().setValue(newValue ? 1 : 0); }); scaling.selectedToggleProperty().addListener((observable, oldValue, newValue) -> updateScaling(Double.parseDouble(newValue.getProperties().get("scale").toString()))); @@ -697,14 +676,55 @@ private void initializeViewMenu() { menuBarViewCanvasSplit.setOnAction(event -> { final BooleanProperty isSplit = Ecdar.toggleCanvasSplit(); if (isSplit.get()) { - Platform.runLater(this::setCanvasModeToSingular); + Platform.runLater(() -> editorPresentation.getController().setCanvasModeToSingular()); menuBarViewCanvasSplit.setText("Split canvas"); } else { - Platform.runLater(this::setCanvasModeToSplit); + Platform.runLater(() -> editorPresentation.getController().setCanvasModeToSplit()); menuBarViewCanvasSplit.setText("Merge canvases"); } }); + switchGuiView.selectedProperty().addListener((observable, oldValue, newValue) -> { + if (newValue) { + if (Ecdar.getProject().getQueries().isEmpty()) { + Ecdar.showToast("Please add a query to simulate before entering the simulator"); + switchGuiView.setSelected(false); + return; + } + + if (!Ecdar.getSimulationHandler().isSimulationRunning()) { + ArrayList queryOptions = Ecdar.getProject().getQueries().stream().map(Query::getQuery).collect(Collectors.toCollection(ArrayList::new)); + + if (!simulationInitializationDialog.getController().simulationComboBox.getItems().equals(queryOptions)) { + simulationInitializationDialog.getController().simulationComboBox.getItems().setAll(queryOptions); + } + + simulationInitializationDialogContainer.setVisible(true); + simulationInitializationDialog.show(simulationInitializationDialogContainer); + simulationInitializationDialog.setMouseTransparent(false); + } else { + currentMode.setValue(Mode.Simulator); + } + } else { + currentMode.setValue(Mode.Editor); + } + }); + + currentMode.addListener((obs, oldMode, newMode) -> { + if (newMode == Mode.Editor && oldMode != newMode) { + enterEditorMode(); + } else if (newMode == Mode.Simulator && oldMode != newMode) { + enterSimulatorMode(); + } + }); + + menuBarViewEditor.setAccelerator(new KeyCodeCombination(KeyCode.DIGIT1, KeyCombination.SHORTCUT_DOWN)); + menuBarViewEditor.setOnAction(event -> switchGuiView.setSelected(false)); + + menuBarViewSimulator.setAccelerator(new KeyCodeCombination(KeyCode.DIGIT2, KeyCombination.SHORTCUT_DOWN)); + menuBarViewSimulator.setOnAction(event -> switchGuiView.setSelected(true)); + + // On startup, set the scaling to the values saved in preferences Platform.runLater(() -> { Ecdar.autoScalingEnabled.setValue(Ecdar.preferences.getBoolean("autoscaling", true)); @@ -733,7 +753,7 @@ private void updateScaling(double newScale) { Ecdar.preferences.put("scale", String.valueOf(newScale)); scaleIcons(root, newCalculatedScale); - scaleEdgeStatusToggle(newCalculatedScale); + editorPresentation.getController().scaleEdgeStatusToggle(newCalculatedScale); messageTabPane.getController().updateScale(newScale); // Update listeners of UI scale @@ -999,152 +1019,42 @@ private void initializeFileExportAsPng() { }); } - private void initializeCanvasPane() { - Platform.runLater(this::setCanvasModeToSingular); - } - - /** - * Removes the canvases and adds a new one, with the active component of the active canvasPresentation. - */ - private void setCanvasModeToSingular() { - canvasPane.getChildren().clear(); - CanvasPresentation canvasPresentation = new CanvasPresentation(); - HighLevelModelObject model = activeCanvasPresentation.get().getController().getActiveModel(); - if (model != null) { - canvasPresentation.getController().setActiveModel(activeCanvasPresentation.get().getController().getActiveModel()); - } else { - // If no components where found, the project has not been initialized. The active model will be updated when the project is initialized - canvasPresentation.getController().setActiveModel(Ecdar.getProject().getComponents().stream().findFirst().orElse(null)); - } - - canvasPane.getChildren().add(canvasPresentation); - activeCanvasPresentation.set(canvasPresentation); - filePane.getController().updateColorsOnFilePresentations(); - - Rectangle clip = new Rectangle(); - clip.setArcWidth(1); - clip.setArcHeight(1); - clip.widthProperty().bind(canvasPane.widthProperty()); - clip.heightProperty().bind(canvasPane.heightProperty()); - canvasPresentation.getController().zoomablePane.setClip(clip); - - canvasPresentation.getController().zoomablePane.minWidthProperty().bind(canvasPane.widthProperty()); - canvasPresentation.getController().zoomablePane.maxWidthProperty().bind(canvasPane.widthProperty()); - canvasPresentation.getController().zoomablePane.minHeightProperty().bind(canvasPane.heightProperty()); - canvasPresentation.getController().zoomablePane.maxHeightProperty().bind(canvasPane.heightProperty()); - } - - /** - * Removes the canvas and adds a GridPane with four new canvases, with different active components, - * the first being the one previously displayed on the single canvas. - */ - private void setCanvasModeToSplit() { - canvasPane.getChildren().clear(); - - GridPane canvasGrid = new GridPane(); - - canvasGrid.addColumn(0); - canvasGrid.addColumn(1); - canvasGrid.addRow(0); - canvasGrid.addRow(1); - - ColumnConstraints col1 = new ColumnConstraints(); - col1.setPercentWidth(50); - - RowConstraints row1 = new RowConstraints(); - row1.setPercentHeight(50); - - canvasGrid.getColumnConstraints().add(col1); - canvasGrid.getColumnConstraints().add(col1); - canvasGrid.getRowConstraints().add(row1); - canvasGrid.getRowConstraints().add(row1); - - ObservableList components = Ecdar.getProject().getComponents(); - int currentCompNum = 0, numComponents = components.size(); - - // Add the canvasPresentation at the top-left - CanvasPresentation canvasPresentation = initializeNewCanvasPresentation(); - canvasPresentation.getController().setActiveModel(getActiveCanvasPresentation().getController().getActiveModel()); - canvasGrid.add(canvasPresentation, 0, 0); - setActiveCanvasPresentation(canvasPresentation); - - // Add the canvasPresentation at the top-right - canvasPresentation = initializeNewCanvasPresentationWithActiveComponent(components, currentCompNum); - canvasPresentation.setOpacity(0.75); - canvasGrid.add(canvasPresentation, 1, 0); - // Update the startIndex for the next canvasPresentation - for (int i = 0; i < numComponents; i++) { - if (canvasPresentation.getController().getActiveModel() != null && canvasPresentation.getController().getActiveModel().equals(components.get(i))) { - currentCompNum = i + 1; - } - } - - // Add the canvasPresentation at the bottom-left - canvasPresentation = initializeNewCanvasPresentationWithActiveComponent(components, currentCompNum); - canvasPresentation.setOpacity(0.75); - canvasGrid.add(canvasPresentation, 0, 1); - - // Update the startIndex for the next canvasPresentation - for (int i = 0; i < numComponents; i++) - if (canvasPresentation.getController().getActiveModel() != null && canvasPresentation.getController().getActiveModel().equals(components.get(i))) { - currentCompNum = i + 1; - } - - // Add the canvasPresentation at the bottom-right - canvasPresentation = initializeNewCanvasPresentationWithActiveComponent(components, currentCompNum); - canvasPresentation.setOpacity(0.75); - canvasGrid.add(canvasPresentation, 1, 1); - - canvasPane.getChildren().add(canvasGrid); - filePane.getController().updateColorsOnFilePresentations(); - } - /** - * Initialize a new CanvasShellPresentation and set its active component to the next component encountered from the startIndex and return it - * - * @param components the list of components for assigning active component of the CanvasPresentation - * @param startIndex the index to start at when trying to find the component to set as active - * @return new CanvasShellPresentation + * Changes the view and mode to the editor + * Only enter if the mode is not already Editor */ - private CanvasPresentation initializeNewCanvasPresentationWithActiveComponent(ObservableList components, int startIndex) { - CanvasPresentation canvasPresentation = initializeNewCanvasPresentation(); - - int numComponents = components.size(); - canvasPresentation.getController().setActiveModel(null); - for (int currentCompNum = startIndex; currentCompNum < numComponents; currentCompNum++) { - if (getActiveCanvasPresentation().getController().getActiveModel() != components.get(currentCompNum)) { - canvasPresentation.getController().setActiveModel(components.get(currentCompNum)); - break; - } - } - - return canvasPresentation; + private void enterEditorMode() { +// ToDo NIELS: Consider implementing willShow and willHide to handle general elements that should only be available for one of the modes +// editorPresentation.getController().willShow(); +// simulatorPresentation.getController().willHide(); + + borderPane.setCenter(editorPresentation); + leftPane.getChildren().clear(); + leftPane.getChildren().add(projectPane); + rightPane.getChildren().clear(); + rightPane.getChildren().add(queryPane); + + // Enable or disable the menu items that can be used when in the simulator +// updateMenuItems(); } /** - * Initialize a new CanvasPresentation and return it - * - * @return new CanvasPresentation + * Changes the view and mode to the simulator + * Only enter if the mode is not already Simulator */ - private CanvasPresentation initializeNewCanvasPresentation() { - CanvasPresentation canvasPresentation = new CanvasPresentation(); - canvasPresentation.setBorder(new Border(new BorderStroke(Color.GREY.getColor(Color.Intensity.I500), BorderStrokeStyle.SOLID, CornerRadii.EMPTY, BorderStroke.THIN))); - - // Set th clip of the zoomable pane to be half of the canvasPane, - // to ensure a 2 by 2 grid without overflowing borders - Rectangle clip = new Rectangle(); - clip.setArcWidth(1); - clip.setArcHeight(1); - clip.widthProperty().bind(canvasPane.widthProperty().divide(2)); - clip.heightProperty().bind(canvasPane.heightProperty().divide(2)); - canvasPresentation.getController().zoomablePane.setClip(clip); - - canvasPresentation.getController().zoomablePane.minWidthProperty().bind(canvasPane.widthProperty().divide(2)); - canvasPresentation.getController().zoomablePane.maxWidthProperty().bind(canvasPane.widthProperty().divide(2)); - canvasPresentation.getController().zoomablePane.minHeightProperty().bind(canvasPane.heightProperty().divide(2)); - canvasPresentation.getController().zoomablePane.maxHeightProperty().bind(canvasPane.heightProperty().divide(2)); - - return canvasPresentation; + private void enterSimulatorMode() { +// ToDo NIELS: Consider implementing willShow and willHide to handle general elements that should only be available for one of the modes +// ecdarPresentation.getController().willHide(); + simulatorPresentation.getController().willShow(); + + borderPane.setCenter(simulatorPresentation); + leftPane.getChildren().clear(); + leftPane.getChildren().add(leftSimPane); + rightPane.getChildren().clear(); + rightPane.getChildren().add(rightSimPane); + + // Enable or disable the menu items that can be used when in the simulator +// updateMenuItems(); } /** @@ -1309,15 +1219,15 @@ private static int getAutoCropRightX(final BufferedImage image) { } /** - * This method is used to push the contents of the file and query panes when the tab pane is opened + * This method is used to push the contents of the project and query panes when the tab pane is opened */ - private void changeInsetsOfFileAndQueryPanes() { + private void changeInsetsOfProjectAndQueryPanes() { if (messageTabPane.getController().isOpen()) { - filePane.showBottomInset(false); + projectPane.showBottomInset(false); queryPane.showBottomInset(false); CanvasPresentation.showBottomInset(false); } else { - filePane.showBottomInset(true); + projectPane.showBottomInset(true); queryPane.showBottomInset(true); CanvasPresentation.showBottomInset(true); } @@ -1355,88 +1265,9 @@ private void nudgeSelected(final NudgeDirection direction) { "open-with"); } - @FXML - private void deleteSelectedClicked() { - if (SelectHelper.getSelectedElements().size() == 0) return; - - // Run through the selected elements and look for something that we can delete - SelectHelper.getSelectedElements().forEach(selectable -> { - if (selectable instanceof LocationController) { - ((LocationController) selectable).tryDelete(); - } else if (selectable instanceof EdgeController) { - final Component component = ((EdgeController) selectable).getComponent(); - final DisplayableEdge edge = ((EdgeController) selectable).getEdge(); - - // Dont delete edge if it is locked - if (edge.getIsLockedProperty().getValue()) { - return; - } - - UndoRedoStack.pushAndPerform(() -> { // Perform - // Remove the edge - component.removeEdge(edge); - }, () -> { // Undo - // Re-all the edge - component.addEdge(edge); - }, String.format("Deleted %s", selectable.toString()), "delete"); - } else if (selectable instanceof NailController) { - ((NailController) selectable).tryDelete(); - } - }); - - SelectHelper.clearSelectedElements(); - } - - @FXML - private void undoClicked() { - UndoRedoStack.undo(); - } - - @FXML - private void redoClicked() { - UndoRedoStack.redo(); - } - - /** - * Switch to input edge mode - */ - @FXML - private void switchToInputClicked() { - setGlobalEdgeStatus(EdgeStatus.INPUT); - } - - /** - * Switch to output edge mode - */ - @FXML - private void switchToOutputClicked() { - setGlobalEdgeStatus(EdgeStatus.OUTPUT); - } - - /** - * Switch edge status. - */ - @FXML - private void switchEdgeStatusClicked() { - if (getGlobalEdgeStatus().equals(EdgeStatus.INPUT)) { - setGlobalEdgeStatus(EdgeStatus.OUTPUT); - } else { - setGlobalEdgeStatus(EdgeStatus.INPUT); - } - } - - /** - * Sets the global edge status. - * - * @param status the status - */ - private void setGlobalEdgeStatus(EdgeStatus status) { - globalEdgeStatus.set(status); - } - @FXML private void closeQueryDialog() { - dialog.close(); + modellingHelpDialog.close(); queryDialog.close(); } diff --git a/src/main/java/ecdar/controllers/EditorController.java b/src/main/java/ecdar/controllers/EditorController.java new file mode 100644 index 00000000..605cc6fd --- /dev/null +++ b/src/main/java/ecdar/controllers/EditorController.java @@ -0,0 +1,380 @@ +package ecdar.controllers; + +import com.jfoenix.controls.JFXButton; +import com.jfoenix.controls.JFXRippler; +import com.jfoenix.controls.JFXToggleButton; +import ecdar.Ecdar; +import ecdar.abstractions.Component; +import ecdar.abstractions.DisplayableEdge; +import ecdar.abstractions.EdgeStatus; +import ecdar.abstractions.HighLevelModelObject; +import ecdar.presentations.CanvasPresentation; +import ecdar.utility.UndoRedoStack; +import ecdar.utility.colors.Color; +import ecdar.utility.colors.EnabledColor; +import ecdar.utility.helpers.SelectHelper; +import ecdar.utility.keyboard.Keybind; +import ecdar.utility.keyboard.KeyboardTracker; +import javafx.application.Platform; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.fxml.Initializable; +import javafx.scene.control.Tooltip; +import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyCodeCombination; +import javafx.scene.layout.*; +import javafx.scene.shape.Rectangle; +import javafx.util.Pair; + +import java.net.URL; +import java.util.ArrayList; +import java.util.List; +import java.util.ResourceBundle; + +public class EditorController implements Initializable { + public VBox root; + public HBox toolbar; + public JFXRippler colorSelected; + public JFXRippler deleteSelected; + public JFXRippler undo; + public JFXRippler redo; + public JFXButton switchToInputButton; + public JFXButton switchToOutputButton; + public JFXToggleButton switchEdgeStatusButton; + public StackPane canvasPane; + + private final ObjectProperty globalEdgeStatus = new SimpleObjectProperty<>(EdgeStatus.INPUT); + private final ObjectProperty activeCanvasPresentation = new SimpleObjectProperty<>(new CanvasPresentation()); + + @Override + public void initialize(final URL location, final ResourceBundle resources) { + initializeCanvasPane(); + initializeEdgeStatusHandling(); + initializeKeybindings(); + } + + public EdgeStatus getGlobalEdgeStatus() { + return globalEdgeStatus.get(); + } + + ObjectProperty activeCanvasPresentationProperty() { + return activeCanvasPresentation; + } + + public CanvasPresentation getActiveCanvasPresentation() { + return activeCanvasPresentation.get(); + } + + public void setActiveCanvasPresentation(CanvasPresentation newActiveCanvasPresentation) { + activeCanvasPresentation.get().setOpacity(0.75); + newActiveCanvasPresentation.setOpacity(1); + activeCanvasPresentation.set(newActiveCanvasPresentation); + } + + public void setActiveModelForActiveCanvas(HighLevelModelObject newActiveModel) { + EcdarController.getActiveCanvasPresentation().getController().setActiveModel(newActiveModel); + + // Change zoom level to fit new active model + Platform.runLater(() -> EcdarController.getActiveCanvasPresentation().getController().zoomHelper.zoomToFit()); + } + + private void initializeCanvasPane() { + Platform.runLater(this::setCanvasModeToSingular); + } + + private void initializeKeybindings() { + // Keybinds for coloring the selected elements + EnabledColor.enabledColors.forEach(enabledColor -> { + KeyboardTracker.registerKeybind(KeyboardTracker.COLOR_SELECTED + "_" + enabledColor.keyCode.getName(), new Keybind(new KeyCodeCombination(enabledColor.keyCode), () -> { + + final List> previousColor = new ArrayList<>(); + + SelectHelper.getSelectedElements().forEach(selectable -> { + previousColor.add(new Pair<>(selectable, new EnabledColor(selectable.getColor(), selectable.getColorIntensity()))); + }); + changeColorOnSelectedElements(enabledColor, previousColor); + SelectHelper.clearSelectedElements(); + })); + }); + + // Keybind for deleting the selected elements + KeyboardTracker.registerKeybind(KeyboardTracker.DELETE_SELECTED, new Keybind(new KeyCodeCombination(KeyCode.DELETE), this::deleteSelectedClicked)); + } + + /** + * Initializes edge status. + * Input is the default status. + * This method sets buttons for edge status whenever the status changes. + */ + private void initializeEdgeStatusHandling() { + globalEdgeStatus.set(EdgeStatus.INPUT); + + Tooltip.install(switchToInputButton, new Tooltip("Switch to input mode")); + Tooltip.install(switchToOutputButton, new Tooltip("Switch to output mode")); + switchToInputButton.setDisableVisualFocus(true); // Hiding input button rippler on start-up + + globalEdgeStatus.addListener(((observable, oldValue, newValue) -> { + if (newValue.equals(EdgeStatus.INPUT)) { + switchToInputButton.setTextFill(javafx.scene.paint.Color.WHITE); + switchToOutputButton.setTextFill(javafx.scene.paint.Color.GREY); + switchEdgeStatusButton.setSelected(false); + } else { + switchToInputButton.setTextFill(javafx.scene.paint.Color.GREY); + switchToOutputButton.setTextFill(javafx.scene.paint.Color.WHITE); + switchEdgeStatusButton.setSelected(true); + } + })); + + // Ensure that the rippler is centered when scale is changed + Platform.runLater(() -> { + var rippler = ((JFXRippler) switchEdgeStatusButton.lookup(".jfx-rippler")); + if (rippler == null) return; + rippler.setRipplerRecenter(true); + }); + } + + /** + * Sets the global edge status. + * + * @param status the status + */ + private void setGlobalEdgeStatus(EdgeStatus status) { + globalEdgeStatus.set(status); + } + + void scaleEdgeStatusToggle(double size) { + switchEdgeStatusButton.setScaleX(size / 13.0); + switchEdgeStatusButton.setScaleY(size / 13.0); + } + + /** + * Handles the change of color on selected objects + * + * @param enabledColor The new color for the selected objects + * @param previousColor The color old color of the selected objects + */ + public void changeColorOnSelectedElements(final EnabledColor enabledColor, + final List> previousColor) { + UndoRedoStack.pushAndPerform(() -> { // Perform + SelectHelper.getSelectedElements() + .forEach(selectable -> selectable.color(enabledColor.color, enabledColor.intensity)); + }, () -> { // Undo + previousColor.forEach(selectableEnabledColorPair -> selectableEnabledColorPair.getKey().color(selectableEnabledColorPair.getValue().color, selectableEnabledColorPair.getValue().intensity)); + }, String.format("Changed the color of %d elements to %s", previousColor.size(), enabledColor.color.name()), "color-lens"); + } + + /** + * Removes the canvases and adds a new one, with the active component of the active canvasPresentation. + */ + void setCanvasModeToSingular() { + canvasPane.getChildren().clear(); + CanvasPresentation canvasPresentation = new CanvasPresentation(); + HighLevelModelObject model = activeCanvasPresentation.get().getController().getActiveModel(); + if (model != null) { + canvasPresentation.getController().setActiveModel(activeCanvasPresentation.get().getController().getActiveModel()); + } else { + // If no components where found, the project has not been initialized. The active model will be updated when the project is initialized + canvasPresentation.getController().setActiveModel(Ecdar.getProject().getComponents().stream().findFirst().orElse(null)); + } + + canvasPane.getChildren().add(canvasPresentation); + activeCanvasPresentation.set(canvasPresentation); + + Rectangle clip = new Rectangle(); + clip.setArcWidth(1); + clip.setArcHeight(1); + clip.widthProperty().bind(canvasPane.widthProperty()); + clip.heightProperty().bind(canvasPane.heightProperty()); + + canvasPresentation.getController().zoomablePane.setClip(clip); + canvasPresentation.getController().zoomablePane.minWidthProperty().bind(canvasPane.widthProperty()); + canvasPresentation.getController().zoomablePane.maxWidthProperty().bind(canvasPane.widthProperty()); + canvasPresentation.getController().zoomablePane.minHeightProperty().bind(canvasPane.heightProperty()); + canvasPresentation.getController().zoomablePane.maxHeightProperty().bind(canvasPane.heightProperty()); + } + + /** + * Removes the canvas and adds a GridPane with four new canvases, with different active components, + * the first being the one previously displayed on the single canvas. + */ + void setCanvasModeToSplit() { + canvasPane.getChildren().clear(); + + GridPane canvasGrid = new GridPane(); + + canvasGrid.addColumn(0); + canvasGrid.addColumn(1); + canvasGrid.addRow(0); + canvasGrid.addRow(1); + + ColumnConstraints col1 = new ColumnConstraints(); + col1.setPercentWidth(50); + + RowConstraints row1 = new RowConstraints(); + row1.setPercentHeight(50); + + canvasGrid.getColumnConstraints().add(col1); + canvasGrid.getColumnConstraints().add(col1); + canvasGrid.getRowConstraints().add(row1); + canvasGrid.getRowConstraints().add(row1); + + ObservableList components = Ecdar.getProject().getComponents(); + int currentCompNum = 0, numComponents = components.size(); + + // Add the canvasPresentation at the top-left + CanvasPresentation canvasPresentation = initializeNewCanvasPresentation(); + canvasPresentation.getController().setActiveModel(getActiveCanvasPresentation().getController().getActiveModel()); + canvasGrid.add(canvasPresentation, 0, 0); + setActiveCanvasPresentation(canvasPresentation); + + // Add the canvasPresentation at the top-right + canvasPresentation = initializeNewCanvasPresentationWithActiveComponent(components, currentCompNum); + canvasPresentation.setOpacity(0.75); + canvasGrid.add(canvasPresentation, 1, 0); + + // Update the startIndex for the next canvasPresentation + for (int i = 0; i < numComponents; i++) { + if (canvasPresentation.getController().getActiveModel() != null && canvasPresentation.getController().getActiveModel().equals(components.get(i))) { + currentCompNum = i + 1; + } + } + + // Add the canvasPresentation at the bottom-left + canvasPresentation = initializeNewCanvasPresentationWithActiveComponent(components, currentCompNum); + canvasPresentation.setOpacity(0.75); + canvasGrid.add(canvasPresentation, 0, 1); + + // Update the startIndex for the next canvasPresentation + for (int i = 0; i < numComponents; i++) + if (canvasPresentation.getController().getActiveModel() != null && canvasPresentation.getController().getActiveModel().equals(components.get(i))) { + currentCompNum = i + 1; + } + + // Add the canvasPresentation at the bottom-right + canvasPresentation = initializeNewCanvasPresentationWithActiveComponent(components, currentCompNum); + canvasPresentation.setOpacity(0.75); + canvasGrid.add(canvasPresentation, 1, 1); + + canvasPane.getChildren().add(canvasGrid); + } + + /** + * Initialize a new CanvasShellPresentation and set its active component to the next component encountered from the startIndex and return it + * + * @param components the list of components for assigning active component of the CanvasPresentation + * @param startIndex the index to start at when trying to find the component to set as active + * @return new CanvasShellPresentation + */ + private CanvasPresentation initializeNewCanvasPresentationWithActiveComponent(ObservableList components, int startIndex) { + CanvasPresentation canvasPresentation = initializeNewCanvasPresentation(); + + int numComponents = components.size(); + canvasPresentation.getController().setActiveModel(null); + for (int currentCompNum = startIndex; currentCompNum < numComponents; currentCompNum++) { + if (getActiveCanvasPresentation().getController().getActiveModel() != components.get(currentCompNum)) { + canvasPresentation.getController().setActiveModel(components.get(currentCompNum)); + break; + } + } + + return canvasPresentation; + } + + /** + * Initialize a new CanvasPresentation and return it + * + * @return new CanvasPresentation + */ + private CanvasPresentation initializeNewCanvasPresentation() { + CanvasPresentation canvasPresentation = new CanvasPresentation(); + canvasPresentation.setBorder(new Border(new BorderStroke(Color.GREY.getColor(Color.Intensity.I500), BorderStrokeStyle.SOLID, CornerRadii.EMPTY, BorderStroke.THIN))); + + // Set th clip of the zoomable pane to be half of the canvasPane, + // to ensure a 2 by 2 grid without overflowing borders + Rectangle clip = new Rectangle(); + clip.setArcWidth(1); + clip.setArcHeight(1); + clip.widthProperty().bind(canvasPane.widthProperty().divide(2)); + clip.heightProperty().bind(canvasPane.heightProperty().divide(2)); + canvasPresentation.getController().zoomablePane.setClip(clip); + + canvasPresentation.getController().zoomablePane.minWidthProperty().bind(canvasPane.widthProperty().divide(2)); + canvasPresentation.getController().zoomablePane.maxWidthProperty().bind(canvasPane.widthProperty().divide(2)); + canvasPresentation.getController().zoomablePane.minHeightProperty().bind(canvasPane.heightProperty().divide(2)); + canvasPresentation.getController().zoomablePane.maxHeightProperty().bind(canvasPane.heightProperty().divide(2)); + + return canvasPresentation; + } + + @FXML + private void undoClicked() { + UndoRedoStack.undo(); + } + + @FXML + private void redoClicked() { + UndoRedoStack.redo(); + } + + /** + * Switch to input edge mode + */ + @FXML + private void switchToInputClicked() { + setGlobalEdgeStatus(EdgeStatus.INPUT); + } + + /** + * Switch to output edge mode + */ + @FXML + private void switchToOutputClicked() { + setGlobalEdgeStatus(EdgeStatus.OUTPUT); + } + + /** + * Switch edge status. + */ + @FXML + private void switchEdgeStatusClicked() { + if (getGlobalEdgeStatus().equals(EdgeStatus.INPUT)) { + setGlobalEdgeStatus(EdgeStatus.OUTPUT); + } else { + setGlobalEdgeStatus(EdgeStatus.INPUT); + } + } + + @FXML + private void deleteSelectedClicked() { + if (SelectHelper.getSelectedElements().size() == 0) return; + + // Run through the selected elements and look for something that we can delete + SelectHelper.getSelectedElements().forEach(selectable -> { + if (selectable instanceof LocationController) { + ((LocationController) selectable).tryDelete(); + } else if (selectable instanceof EdgeController) { + final Component component = ((EdgeController) selectable).getComponent(); + final DisplayableEdge edge = ((EdgeController) selectable).getEdge(); + + // Dont delete edge if it is locked + if (edge.getIsLockedProperty().getValue()) { + return; + } + + UndoRedoStack.pushAndPerform(() -> { // Perform + // Remove the edge + component.removeEdge(edge); + }, () -> { // Undo + // Re-all the edge + component.addEdge(edge); + }, String.format("Deleted %s", selectable.toString()), "delete"); + } else if (selectable instanceof NailController) { + ((NailController) selectable).tryDelete(); + } + }); + + SelectHelper.clearSelectedElements(); + } +} diff --git a/src/main/java/ecdar/controllers/LeftSimPaneController.java b/src/main/java/ecdar/controllers/LeftSimPaneController.java new file mode 100755 index 00000000..d8dc30eb --- /dev/null +++ b/src/main/java/ecdar/controllers/LeftSimPaneController.java @@ -0,0 +1,25 @@ +package ecdar.controllers; + +import ecdar.presentations.TracePaneElementPresentation; +import ecdar.presentations.TransitionPaneElementPresentation; +import javafx.fxml.FXML; +import javafx.fxml.Initializable; +import javafx.scene.control.ScrollPane; +import javafx.scene.layout.StackPane; +import javafx.scene.layout.VBox; +import java.net.URL; +import java.util.ResourceBundle; + +public class LeftSimPaneController implements Initializable { + public StackPane root; + public ScrollPane scrollPane; + public VBox scrollPaneVbox; + + public TransitionPaneElementPresentation transitionPanePresentation; + public TracePaneElementPresentation tracePanePresentation; + + @Override + public void initialize(URL location, ResourceBundle resources) { + + } +} diff --git a/src/main/java/ecdar/controllers/ProcessController.java b/src/main/java/ecdar/controllers/ProcessController.java new file mode 100755 index 00000000..6ce42fc2 --- /dev/null +++ b/src/main/java/ecdar/controllers/ProcessController.java @@ -0,0 +1,252 @@ +package ecdar.controllers; + +import com.jfoenix.controls.JFXRippler; +import ecdar.abstractions.*; +import ecdar.presentations.SimEdgePresentation; +import ecdar.presentations.SimLocationPresentation; +import javafx.animation.Interpolator; +import javafx.animation.Transition; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.collections.FXCollections; +import javafx.collections.ObservableMap; +import javafx.fxml.Initializable; +import javafx.scene.Node; +import javafx.scene.input.MouseEvent; +import javafx.scene.layout.*; +import javafx.scene.shape.Circle; +import javafx.util.Duration; +import org.kordamp.ikonli.javafx.FontIcon; + +import java.math.BigDecimal; +import java.net.URL; +import java.util.*; + +/** + * The controller for the process shown in the {@link SimulatorOverviewController} + */ +public class ProcessController extends ModelController implements Initializable { + public StackPane componentPane; + public Pane modelContainerEdge; + public Pane modelContainerLocation; + public JFXRippler toggleValuesButton; + public VBox valueArea; + public FontIcon toggleValueButtonIcon; + private ObjectProperty component; + + /** + * Keep track of locations/edges and their associated presentation class, by having the as key-value pairs in a Map + * E.g. a {@link Location} key is the model behind the value {@link SimLocationPresentation} view + */ + private final Map locationPresentationMap = new HashMap<>(); + private final Map edgePresentationMap = new HashMap<>(); + private final ObservableMap variables = FXCollections.observableHashMap(); + private final ObservableMap clocks = FXCollections.observableHashMap(); + + @Override + public void initialize(final URL location, final ResourceBundle resources) { + component = new SimpleObjectProperty<>(new Component(true)); + initializeValues(); + } + + private void initializeValues() { + final Circle circle = new Circle(0); + if (getComponent().isDeclarationOpen()) { + circle.setRadius(1000); + } + final ObjectProperty clip = new SimpleObjectProperty<>(circle); + valueArea.clipProperty().bind(clip); + clip.set(circle); + } + + /** + * Highlights the edges and accompanying source/target locations in the process + * @param edges The edges to highlight + */ + public void highlightEdges(final ArrayList edges) { + for (final Edge edge : edges) { + final Location source = edge.getSourceLocation(); + String sourceId = source.getId(); + final Location target = edge.getTargetLocation(); + String targetId = target.getId(); + + // If target name is empty the edge is a self loop + if (Objects.equals(targetId, "")) { + targetId = sourceId; + } + + boolean isSourceUniversal = false; + + // Iterate through all locations to check for Universal and Inconsistent locations + // The name of a Universal location may be "U2" in our system, but it is mapped to "Universal" in the engine + // This loop maps "Universal" to for example "U2" + for (Map.Entry locEntry : locationPresentationMap.entrySet()) { + if (locEntry.getKey().getType() == Location.Type.UNIVERSAL) { + if (sourceId.equals("Universal")) { + sourceId = locEntry.getKey().getId(); + isSourceUniversal = true; + } + + if (targetId.equals("Universal")) { + targetId = locEntry.getKey().getId(); + } + } + + if (locEntry.getKey().getType() == Location.Type.INCONSISTENT) { + if (sourceId.equals("Inconsistent")) { + sourceId = locEntry.getKey().getId(); + } + + if (targetId.equals("Inconsistent")) { + targetId = locEntry.getKey().getId(); + } + } + } + + // Self loop on a Universal locations means that the edge name should be mapped to * + String edgeId = edge.getId(); + if (isSourceUniversal && sourceId.equals(targetId)) { + edgeId = "*"; + } + + highlightEdge(edgeId, edge.getStatus(), sourceId, targetId); + } + } + + /** + * Unhighlights all edges and locations in the process + */ + public void unhighlightProcess() { + edgePresentationMap.forEach((key, value) -> value.getController().unhighlight()); + locationPresentationMap.forEach((key, value) -> value.unhighlight()); + } + + /** + * Helper method that finds the {@link SimLocationPresentation} and highlights it. + * Calls {@link ProcessController#highlightEdgeLocations(String, String)} to highlight the source/targets locations + * @param edgeName The name of the edge + * @param edgeStatus The status (input/output) of the edge to highlight + * @param sourceName The name of the source location + * @param targetName The name of the target location + */ + private void highlightEdge(final String edgeName, final EdgeStatus edgeStatus, final String sourceName, final String targetName) { + for (Map.Entry entry: edgePresentationMap.entrySet()) { + final String keyName = entry.getKey().getSync(); + final String keySourceId = entry.getKey().getSourceLocation().getId(); + final String keyTargetId = entry.getKey().getTargetLocation().getId(); + + // Multiple edges may have the same name, so we also check that the source and target match this edge + if(keyName.equals(edgeName) && + keySourceId.equals(sourceName) && + keyTargetId.equals(targetName) && + entry.getKey().ioStatus.get() == edgeStatus) { + + entry.getValue().getController().highlight(); + highlightEdgeLocations(keySourceId, keyTargetId); + } + } + } + + /** + * Helper method that finds the source/target {@link SimLocationPresentation} and highlights it + * @param sourceId The name of the source location + * @param targetId The name of the target location + */ + private void highlightEdgeLocations(final String sourceId, final String targetId) { + for (Map.Entry locEntry: locationPresentationMap.entrySet()) { + final String locationId = locEntry.getKey().getId(); + + // Check if location is either source or target and highlight it + if(locationId.equals(sourceId) || locationId.equals(targetId)) { + locEntry.getValue().highlight(); + } + } + } + + /** + * Method that highlights all locations with the input ID + * @param locationId The locations to highlight + */ + public void highlightLocation(final String locationId) { + for (Map.Entry locEntry: locationPresentationMap.entrySet()) { + if(locEntry.getKey().getId().equals(locationId)) { + locEntry.getValue().highlight(); + } + } + } + + /** + * Sets the component which is going to be shown as a process.
+ * This also initializes the rest of the views needed for the process to be shown properly + * @param component the component of the process + */ + public void setComponent(final Component component){ + this.component.set(component); + modelContainerEdge.getChildren().clear(); + modelContainerLocation.getChildren().clear(); + + component.getLocations().forEach(location -> { + final SimLocationPresentation lp = new SimLocationPresentation(location, component); + modelContainerLocation.getChildren().add(lp); + locationPresentationMap.put(location, lp); + }); + + component.getEdges().forEach(edge -> { + final SimEdgePresentation ep = new SimEdgePresentation(edge, component); + modelContainerEdge.getChildren().add(ep); + edgePresentationMap.put(edge, ep); + }); + } + + public void toggleValues(final MouseEvent mouseEvent) { + final Circle circle = new Circle(0); + circle.setCenterX(component.get().getBox().getWidth() - (toggleValuesButton.getWidth() - mouseEvent.getX())); + circle.setCenterY(-1 * mouseEvent.getY()); + + final ObjectProperty clip = new SimpleObjectProperty<>(circle); + valueArea.clipProperty().bind(clip); + + final Transition rippleEffect = new Transition() { + private final double maxRadius = Math.sqrt(Math.pow(getComponent().getBox().getWidth(), 2) + Math.pow(getComponent().getBox().getHeight(), 2)); + { + setCycleDuration(Duration.millis(500)); + } + + protected void interpolate(final double fraction) { + if (getComponent().isDeclarationOpen()) { + circle.setRadius(fraction * maxRadius); + } else { + circle.setRadius(maxRadius - fraction * maxRadius); + } + clip.set(circle); + } + }; + + final Interpolator interpolator = Interpolator.SPLINE(0.785, 0.135, 0.15, 0.86); + rippleEffect.setInterpolator(interpolator); + + rippleEffect.play(); + getComponent().declarationOpenProperty().set(!getComponent().isDeclarationOpen()); + } + + /** + * Gets the component linked to this process + * @return the component of the process + */ + public Component getComponent(){ + return component.get(); + } + + @Override + public HighLevelModelObject getModel() { + return component.get(); + } + + public ObservableMap getVariables() { + return variables; + } + + public ObservableMap getClocks() { + return clocks; + } +} diff --git a/src/main/java/ecdar/controllers/ProjectPaneController.java b/src/main/java/ecdar/controllers/ProjectPaneController.java index c80bcbc1..cc4c111b 100644 --- a/src/main/java/ecdar/controllers/ProjectPaneController.java +++ b/src/main/java/ecdar/controllers/ProjectPaneController.java @@ -12,6 +12,7 @@ import com.jfoenix.controls.JFXPopup; import com.jfoenix.controls.JFXRippler; import com.jfoenix.controls.JFXTextArea; +import javafx.application.Platform; import javafx.collections.ListChangeListener; import javafx.fxml.FXML; import javafx.fxml.Initializable; @@ -341,7 +342,7 @@ private void handleRemovedModel(final HighLevelModelObject model) { public void updateColorsOnFilePresentations() { for (Node child : filesList.getChildren()) { if (child instanceof FilePresentation) { - ((FilePresentation) child).updateColors(); + Platform.runLater(() -> ((FilePresentation) child).updateColors()); } } diff --git a/src/main/java/ecdar/controllers/RightSimPaneController.java b/src/main/java/ecdar/controllers/RightSimPaneController.java new file mode 100755 index 00000000..5b86fc1b --- /dev/null +++ b/src/main/java/ecdar/controllers/RightSimPaneController.java @@ -0,0 +1,20 @@ +package ecdar.controllers; + +import javafx.fxml.Initializable; +import javafx.scene.layout.*; + +import java.net.URL; +import java.util.ResourceBundle; + +/** + * Controller class for the right pane in the simulator + */ +public class RightSimPaneController implements Initializable { + public StackPane root; + public VBox scrollPaneVbox; +// public QueryPaneElementPresentation queryPaneElement; + + @Override + public void initialize(URL location, ResourceBundle resources) { + } +} diff --git a/src/main/java/ecdar/controllers/SimEdgeController.java b/src/main/java/ecdar/controllers/SimEdgeController.java new file mode 100755 index 00000000..2ca0048f --- /dev/null +++ b/src/main/java/ecdar/controllers/SimEdgeController.java @@ -0,0 +1,421 @@ +package ecdar.controllers; + +import ecdar.abstractions.Component; +import ecdar.abstractions.Edge; +import ecdar.abstractions.EdgeStatus; +import ecdar.abstractions.Nail; +import ecdar.model_canvas.arrow_heads.SimpleArrowHead; +import ecdar.presentations.Link; +import ecdar.presentations.NailPresentation; +import ecdar.presentations.SimNailPresentation; +import ecdar.utility.Highlightable; +import ecdar.utility.colors.Color; +import ecdar.utility.helpers.BindingHelper; +import ecdar.utility.helpers.Circular; +import ecdar.utility.helpers.ItemDragHelper; +import ecdar.utility.helpers.SelectHelper; +import ecdar.utility.keyboard.KeyboardTracker; +import javafx.animation.KeyFrame; +import javafx.animation.KeyValue; +import javafx.animation.Timeline; +import javafx.application.Platform; +import javafx.beans.property.*; +import javafx.beans.value.ChangeListener; +import javafx.collections.FXCollections; +import javafx.collections.ListChangeListener; +import javafx.collections.ObservableList; +import javafx.fxml.Initializable; +import javafx.scene.Group; +import javafx.scene.Node; +import javafx.util.Duration; + +import java.net.URL; +import java.util.HashMap; +import java.util.Map; +import java.util.ResourceBundle; +import java.util.function.Consumer; + +/** + * The controller for the edge shown in the {@link ecdar.presentations.SimulatorOverviewPresentation} + */ +public class SimEdgeController implements Initializable, Highlightable { + private final ObservableList links = FXCollections.observableArrayList(); + private final ObjectProperty edge = new SimpleObjectProperty<>(); + private final ObjectProperty component = new SimpleObjectProperty<>(); + private final SimpleArrowHead simpleArrowHead = new SimpleArrowHead(); + private final SimpleBooleanProperty isHoveringEdge = new SimpleBooleanProperty(false); + private final SimpleIntegerProperty timeHoveringEdge = new SimpleIntegerProperty(0); + private final Map nailNailPresentationMap = new HashMap<>(); + public Group edgeRoot; + private Runnable collapseNail; + private Consumer enlargeNail; + private Consumer shrinkNail; + + @Override + public void initialize(final URL location, final ResourceBundle resources) { + initializeNailCollapse(); + + edge.addListener((obsEdge, oldEdge, newEdge) -> { + newEdge.targetCircularProperty().addListener(getNewTargetCircularListener(newEdge)); + component.addListener(getComponentChangeListener(newEdge)); + + // Invalidate the list of edges (to update UI and errors) + newEdge.targetCircularProperty().addListener(observable -> { + getComponent().removeEdge(getEdge()); + getComponent().addEdge(getEdge()); + }); + + // When an edge updates highlight property, + // we want to update the view to reflect current highlight property + edge.get().isHighlightedProperty().addListener(v -> { + if(edge.get().getIsHighlighted()) { + this.highlight(); + } else { + this.unhighlight(); + } + }); + }); + + ensureNailsInFront(); + } + + private void ensureNailsInFront() { + // When ever changes happens to the children of the edge root force nails in front and other elements to back + edgeRoot.getChildren().addListener((ListChangeListener) c -> { + while (c.next()) { + for (int i = 0; i < c.getAddedSize(); i++) { + final Node node = c.getAddedSubList().get(i); + if (node instanceof NailPresentation) { + node.toFront(); + } else { + node.toBack(); + } + } + } + }); + } + + private ChangeListener getComponentChangeListener(final Edge newEdge) { + return (obsComponent, oldComponent, newComponent) -> { + // Draw new edge from a location + if (newEdge.getNails().isEmpty() && newEdge.getTargetCircular() == null) { + final Link link = new Link(); + // Make dashed line, if output edge + if (newEdge.getStatus() == EdgeStatus.OUTPUT) link.makeDashed(); + links.add(link); + + // Add the link and its arrowhead to the view + edgeRoot.getChildren().addAll(link, simpleArrowHead); + + // Bind the first link and the arrowhead from the source location to the mouse + BindingHelper.bind(link, simpleArrowHead, newEdge.getSourceCircular(), + newComponent.getBox().getXProperty(), newComponent.getBox().getYProperty()); + } else if (newEdge.getTargetCircular() != null) { + + edgeRoot.getChildren().add(simpleArrowHead); + + final Circular[] previous = {newEdge.getSourceCircular()}; + + newEdge.getNails().forEach(nail -> { + final Link link = new Link(); + if (newEdge.getStatus() == EdgeStatus.OUTPUT) link.makeDashed(); + links.add(link); + + final SimNailPresentation nailPresentation = new SimNailPresentation(nail, newEdge, getComponent(), this); + nailNailPresentationMap.put(nail, nailPresentation); + + edgeRoot.getChildren().addAll(link, nailPresentation); + BindingHelper.bind(link, previous[0], nail); + + previous[0] = nail; + }); + + final Link link = new Link(); + if (newEdge.getStatus() == EdgeStatus.OUTPUT) link.makeDashed(); + links.add(link); + + edgeRoot.getChildren().add(link); + BindingHelper.bind(link, simpleArrowHead, previous[0], newEdge.getTargetCircular()); + } + + // Changes are made to the nails list + newEdge.getNails().addListener(getNailsChangeListener(newEdge, newComponent)); + + }; + } + + private ListChangeListener getNailsChangeListener(final Edge newEdge, final Component newComponent) { + return change -> { + while (change.next()) { + // There were added some nails + change.getAddedSubList().forEach(newNail -> { + // Create a new nail presentation based on the abstraction added to the list + final SimNailPresentation newNailPresentation = new SimNailPresentation(newNail, newEdge, newComponent, this); + nailNailPresentationMap.put(newNail, newNailPresentation); + + edgeRoot.getChildren().addAll(newNailPresentation); + + if (newEdge.getTargetCircular() != null) { + final int indexOfNewNail = edge.get().getNails().indexOf(newNail); + + final Link newLink = new Link(); + if (newEdge.getStatus() == EdgeStatus.OUTPUT) newLink.makeDashed(); + final Link pressedLink = links.get(indexOfNewNail); + links.add(indexOfNewNail, newLink); + + edgeRoot.getChildren().addAll(newLink); + + Circular oldStart = getEdge().getSourceCircular(); + Circular oldEnd = getEdge().getTargetCircular(); + + if (indexOfNewNail != 0) { + oldStart = getEdge().getNails().get(indexOfNewNail - 1); + } + + if (indexOfNewNail != getEdge().getNails().size() - 1) { + oldEnd = getEdge().getNails().get(indexOfNewNail + 1); + } + + BindingHelper.bind(newLink, oldStart, newNail); + + if (oldEnd.equals(getEdge().getTargetCircular())) { + BindingHelper.bind(pressedLink, simpleArrowHead, newNail, oldEnd); + } else { + BindingHelper.bind(pressedLink, newNail, oldEnd); + } + + if (isHoveringEdge.get()) { + enlargeNail.accept(newNail); + } + + } else { + // The previous last link must end in the new nail + final Link lastLink = links.get(links.size() - 1); + + // If the nail is the first in the list, bind it to the source location + // otherwise, bind it the the previous nail + final int nailIndex = edge.get().getNails().indexOf(newNail); + if (nailIndex == 0) { + BindingHelper.bind(lastLink, newEdge.getSourceCircular(), newNail); + } else { + final Nail previousNail = edge.get().getNails().get(nailIndex - 1); + BindingHelper.bind(lastLink, previousNail, newNail); + } + + // Create a new link that will bind from the new nail to the mouse + final Link newLink = new Link(); + if (newEdge.getStatus() == EdgeStatus.OUTPUT) newLink.makeDashed(); + links.add(newLink); + BindingHelper.bind(newLink, simpleArrowHead, newNail, newComponent.getBox().getXProperty(), newComponent.getBox().getYProperty()); + edgeRoot.getChildren().add(newLink); + } + }); + + change.getRemoved().forEach(removedNail -> { + final int removedIndex = change.getFrom(); + final SimNailPresentation removedNailPresentation = nailNailPresentationMap.remove(removedNail); + final Link danglingLink = links.get(removedIndex + 1); + edgeRoot.getChildren().remove(removedNailPresentation); + edgeRoot.getChildren().remove(links.get(removedIndex)); + + Circular newFrom = getEdge().getSourceCircular(); + Circular newTo = getEdge().getTargetCircular(); + + if (removedIndex > 0) { + newFrom = getEdge().getNails().get(removedIndex - 1); + } + + if (removedIndex - 1 != getEdge().getNails().size() - 1) { + newTo = getEdge().getNails().get(removedIndex); + } + + if (newTo.equals(getEdge().getTargetCircular())) { + BindingHelper.bind(danglingLink, simpleArrowHead, newFrom, newTo); + } else { + BindingHelper.bind(danglingLink, newFrom, newTo); + } + links.remove(removedIndex); + }); + } + }; + } + + private ChangeListener getNewTargetCircularListener(final Edge newEdge) { + // When the target location is set, finish drawing the edge + return (obsTargetLocation, oldTargetCircular, newTargetCircular) -> { + // If the nails list is empty, directly connect the source and target locations + // otherwise, bind the line from the last nail to the target location + final Link lastLink = links.get(links.size() - 1); + final ObservableList nails = getEdge().getNails(); + if (nails.size() == 0) { + BindingHelper.bind(lastLink, simpleArrowHead, newEdge.getSourceCircular(), newEdge.getTargetCircular()); + } else { + final Nail lastNail = nails.get(nails.size() - 1); + BindingHelper.bind(lastLink, simpleArrowHead, lastNail, newEdge.getTargetCircular()); + } + + KeyboardTracker.unregisterKeybind(KeyboardTracker.ABANDON_EDGE); + + // When the target location is set the + edgeRoot.setMouseTransparent(false); + }; + } + + /** + * Initializes functionality to enlarge, shirk, and collapse nails + */ + private void initializeNailCollapse() { + enlargeNail = nail -> { + if (!nail.getPropertyType().equals(Edge.PropertyType.NONE)) return; + final Timeline animation = new Timeline(); + + final KeyValue radius0 = new KeyValue(nail.radiusProperty(), NailPresentation.COLLAPSED_RADIUS); + final KeyValue radius2 = new KeyValue(nail.radiusProperty(), NailPresentation.HOVERED_RADIUS * 1.2); + final KeyValue radius1 = new KeyValue(nail.radiusProperty(), NailPresentation.HOVERED_RADIUS); + + final KeyFrame kf1 = new KeyFrame(Duration.millis(0), radius0); + final KeyFrame kf2 = new KeyFrame(Duration.millis(80), radius2); + final KeyFrame kf3 = new KeyFrame(Duration.millis(100), radius1); + + animation.getKeyFrames().addAll(kf1, kf2, kf3); + animation.play(); + }; + shrinkNail = nail -> { + if (!nail.getPropertyType().equals(Edge.PropertyType.NONE)) return; + final Timeline animation = new Timeline(); + + final KeyValue radius0 = new KeyValue(nail.radiusProperty(), NailPresentation.COLLAPSED_RADIUS); + final KeyValue radius1 = new KeyValue(nail.radiusProperty(), NailPresentation.HOVERED_RADIUS); + + final KeyFrame kf1 = new KeyFrame(Duration.millis(0), radius1); + final KeyFrame kf2 = new KeyFrame(Duration.millis(100), radius0); + + animation.getKeyFrames().addAll(kf1, kf2); + animation.play(); + }; + + collapseNail = () -> { + final int interval = 50; + int previousValue = 1; + + try { + while (true) { + Thread.sleep(interval); + + if (isHoveringEdge.get()) { + // Do not let the timer go above this threshold + if (timeHoveringEdge.get() <= 500) { + timeHoveringEdge.set(timeHoveringEdge.get() + interval); + } + } else { + timeHoveringEdge.set(timeHoveringEdge.get() - interval); + } + + if (previousValue >= 0 && timeHoveringEdge.get() < 0) { + // Run on UI thread + Platform.runLater(() -> { + // Collapse all nails + getEdge().getNails().forEach(shrinkNail); + }); + break; + } + previousValue = timeHoveringEdge.get(); + } + + } catch (final InterruptedException e) { + e.printStackTrace(); + } + }; + } + + public Edge getEdge() { + return edge.get(); + } + + public void setEdge(final Edge edge) { + this.edge.set(edge); + } + + public ObjectProperty edgeProperty() { + return edge; + } + + public Component getComponent() { + return component.get(); + } + + public void setComponent(final Component component) { + this.component.set(component); + } + + public ObjectProperty componentProperty() { + return component; + } + + /** + * Colors the edge model + * @param color the new color of the edge + * @param intensity the intensity of the edge + */ + public void color(final Color color, final Color.Intensity intensity) { + final Edge edge = getEdge(); + + // Set the color of the edge + edge.setColorIntensity(intensity); + edge.setColor(color); + } + + public Color getColor() { + return getEdge().getColor(); + } + + public Color.Intensity getColorIntensity() { + return getEdge().getColorIntensity(); + } + + public ItemDragHelper.DragBounds getDragBounds() { + return ItemDragHelper.DragBounds.generateLooseDragBounds(); + } + + /*** + * Highlights the child nodes of the edge + */ + @Override + public void highlight() { + // Clear the currently selected elements, so we don't have multiple things highlighted/selected + SelectHelper.clearSelectedElements(); + edgeRoot.getChildren().forEach(node -> { + if(node instanceof Highlightable) { + ((Highlightable) node).highlight(); + } + }); + } + + /*** + * Removes the highlight from child nodes + */ + @Override + public void unhighlight() { + edgeRoot.getChildren().forEach(node -> { + if(node instanceof Highlightable) { + ((Highlightable) node).unhighlight(); + } + }); + } + + public DoubleProperty xProperty() { + return edgeRoot.layoutXProperty(); + } + + public DoubleProperty yProperty() { + return edgeRoot.layoutYProperty(); + } + + public double getX() { + return xProperty().get(); + } + + public double getY() { + return yProperty().get(); + } +} diff --git a/src/main/java/ecdar/controllers/SimLocationController.java b/src/main/java/ecdar/controllers/SimLocationController.java new file mode 100755 index 00000000..c8c39522 --- /dev/null +++ b/src/main/java/ecdar/controllers/SimLocationController.java @@ -0,0 +1,124 @@ +package ecdar.controllers; + +import ecdar.abstractions.Component; +import ecdar.abstractions.Location; +import ecdar.presentations.SimLocationPresentation; +import ecdar.presentations.SimTagPresentation; +import ecdar.utility.colors.Color; +import javafx.beans.property.DoubleProperty; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.fxml.Initializable; +import javafx.scene.Group; +import javafx.scene.control.Label; +import javafx.scene.shape.Circle; +import javafx.scene.shape.Line; +import javafx.scene.shape.Path; + +import java.net.URL; +import java.util.ResourceBundle; + +/** + * The controller of a location shown in the {@link ecdar.presentations.SimulatorOverviewPresentation} + */ +public class SimLocationController implements Initializable { + private final ObjectProperty location = new SimpleObjectProperty<>(); + private final ObjectProperty component = new SimpleObjectProperty<>(); + public SimLocationPresentation root; + public Path notCommittedShape; + public Path notCommittedInitialIndicator; + public Group shakeContent; + public Circle circle; + public Circle circleShakeIndicator; + public Group scaleContent; + public SimTagPresentation nicknameTag; + public SimTagPresentation invariantTag; + public Label idLabel; + public Line nameTagLine; + public Line invariantTagLine; + + @Override + public void initialize(final URL location, final ResourceBundle resources) { + this.location.addListener((obsLocation, oldLocation, newLocation) -> { + // The radius property on the abstraction must reflect the radius in the view + newLocation.radiusProperty().bind(circle.radiusProperty()); + + // The scale property on the abstraction must reflect the radius in the view + newLocation.scaleProperty().bind(scaleContent.scaleXProperty()); + }); + + // Scale x and y 1:1 (based on the x-scale) + scaleContent.scaleYProperty().bind(scaleContent.scaleXProperty()); + } + + public Location getLocation() { + return location.get(); + } + + /** + * Set/places the given location on the view. + * This have to be done before adding the {@link SimLocationPresentation} to the view as nothing + * would then be displayed. + * @param location the location + */ + public void setLocation(final Location location) { + this.location.set(location); + root.setLayoutX(location.getX()); + root.setLayoutY(location.getY()); + location.xProperty().bindBidirectional(root.layoutXProperty()); + location.yProperty().bindBidirectional(root.layoutYProperty()); + } + + public ObjectProperty locationProperty() { + return location; + } + + public Component getComponent() { + return component.get(); + } + + public void setComponent(final Component component) { + this.component.set(component); + } + + public ObjectProperty componentProperty() { + return component; + } + + /** + * Colors the location model + * @param color the new color of the location + * @param intensity the intensity of the color + */ + public void color(final Color color, final Color.Intensity intensity) { + final Location location = getLocation(); + + // Set the color of the location + location.setColorIntensity(intensity); + location.setColor(color); + } + + public Color getColor() { + return getLocation().getColor(); + } + + public Color.Intensity getColorIntensity() { + return getLocation().getColorIntensity(); + } + + public DoubleProperty xProperty() { + return root.layoutXProperty(); + } + + public DoubleProperty yProperty() { + return root.layoutYProperty(); + } + + public double getX() { + return xProperty().get(); + } + + public double getY() { + return yProperty().get(); + } +} diff --git a/src/main/java/ecdar/controllers/SimNailController.java b/src/main/java/ecdar/controllers/SimNailController.java new file mode 100755 index 00000000..053456d3 --- /dev/null +++ b/src/main/java/ecdar/controllers/SimNailController.java @@ -0,0 +1,126 @@ +package ecdar.controllers; + +import ecdar.Debug; +import ecdar.abstractions.Component; +import ecdar.abstractions.Edge; +import ecdar.abstractions.Nail; +import ecdar.presentations.SimNailPresentation; +import ecdar.presentations.SimTagPresentation; +import ecdar.utility.colors.Color; +import javafx.beans.property.DoubleProperty; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.fxml.Initializable; +import javafx.scene.Group; +import javafx.scene.control.Label; +import javafx.scene.shape.Circle; +import javafx.scene.shape.Line; + +import java.net.URL; +import java.util.ResourceBundle; + +public class SimNailController implements Initializable { + private final ObjectProperty component = new SimpleObjectProperty<>(); + private final ObjectProperty edge = new SimpleObjectProperty<>(); + private final ObjectProperty nail = new SimpleObjectProperty<>(); + + private SimEdgeController edgeController; + public SimNailPresentation root; + public Circle nailCircle; + public Circle dragCircle; + public Line propertyTagLine; + public SimTagPresentation propertyTag; + public Group dragGroup; + public Label propertyLabel; + + @Override + public void initialize(final URL location, final ResourceBundle resources) { + nail.addListener((obsNail, oldNail, newNail) -> { + + // The radius from the abstraction is the master and the view simply reflects what is in the model + nailCircle.radiusProperty().bind(newNail.radiusProperty()); + + // Draw the presentation based on the initial value from the abstraction + root.setLayoutX(newNail.getX()); + root.setLayoutY(newNail.getY()); + + // Reflect future updates from the presentation into the abstraction + newNail.xProperty().bindBidirectional(root.layoutXProperty()); + newNail.yProperty().bindBidirectional(root.layoutYProperty()); + + }); + + // Debug visuals + dragCircle.opacityProperty().bind(Debug.draggableAreaOpacity); + dragCircle.setFill(Debug.draggableAreaColor.getColor(Debug.draggableAreaColorIntensity)); + } + + /** + * Sets an edge controller. + * This should be called when adding a nail. + * @param controller the edge controller + */ + public void setEdgeController(final SimEdgeController controller) { + this.edgeController = controller; + } + + public Nail getNail() { + return nail.get(); + } + + public void setNail(final Nail nail) { + this.nail.set(nail); + } + + public ObjectProperty nailProperty() { + return nail; + } + + public Component getComponent() { + return component.get(); + } + + public void setComponent(final Component component) { + this.component.set(component); + } + + public ObjectProperty componentProperty() { + return component; + } + + public Edge getEdge() { + return edge.get(); + } + + public void setEdge(final Edge edge) { + this.edge.set(edge); + } + + public ObjectProperty edgeProperty() { + return edge; + } + + public Color getColor() { + return getComponent().getColor(); + } + + public Color.Intensity getColorIntensity() { + return getComponent().getColorIntensity(); + } + + public DoubleProperty xProperty() { + return root.layoutXProperty(); + } + + public DoubleProperty yProperty() { + return root.layoutYProperty(); + } + + public double getX() { + return xProperty().get(); + } + + public double getY() { + return yProperty().get(); + } +} diff --git a/src/main/java/ecdar/controllers/SimulationInitializationDialogController.java b/src/main/java/ecdar/controllers/SimulationInitializationDialogController.java new file mode 100644 index 00000000..1eb1529c --- /dev/null +++ b/src/main/java/ecdar/controllers/SimulationInitializationDialogController.java @@ -0,0 +1,18 @@ +package ecdar.controllers; + +import com.jfoenix.controls.JFXButton; +import com.jfoenix.controls.JFXComboBox; +import javafx.fxml.Initializable; + +import java.net.URL; +import java.util.ResourceBundle; + +public class SimulationInitializationDialogController implements Initializable { + public JFXComboBox simulationComboBox; + public JFXButton cancelButton; + public JFXButton startButton; + + public void initialize(URL location, ResourceBundle resources) { + + } +} diff --git a/src/main/java/ecdar/controllers/SimulatorController.java b/src/main/java/ecdar/controllers/SimulatorController.java new file mode 100644 index 00000000..bdba66b4 --- /dev/null +++ b/src/main/java/ecdar/controllers/SimulatorController.java @@ -0,0 +1,149 @@ +package ecdar.controllers; + +import ecdar.Ecdar; +import ecdar.abstractions.*; +import ecdar.simulation.SimulationHandler; +import ecdar.presentations.SimulatorOverviewPresentation; +import ecdar.simulation.SimulationState; +import ecdar.simulation.Transition; +import javafx.beans.property.DoubleProperty; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleDoubleProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.fxml.Initializable; +import javafx.scene.layout.StackPane; + +import java.net.URL; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.ResourceBundle; + +public class SimulatorController implements Initializable { + public StackPane root; + public SimulatorOverviewPresentation overviewPresentation; + public StackPane toolbar; + + private boolean firstTimeInSimulator; + private final static DoubleProperty width = new SimpleDoubleProperty(), + height = new SimpleDoubleProperty(); + private static ObjectProperty selectedTransition = new SimpleObjectProperty<>(); + private static ObjectProperty selectedState = new SimpleObjectProperty<>(); + + @Override + public void initialize(URL location, ResourceBundle resources) { + root.widthProperty().addListener((observable, oldValue, newValue) -> width.setValue(newValue)); + root.heightProperty().addListener((observable, oldValue, newValue) -> height.setValue(newValue)); + firstTimeInSimulator = true; + } + + /** + * Prepares the simulator to be shown.
+ * It also prepares the processes to be shown in the {@link SimulatorOverviewPresentation} by:
+ * - Building the system if it has been updated or have never been created.
+ * - Adding the components which are going to be used in the simulation to + */ + public void willShow() { + final SimulationHandler sm = Ecdar.getSimulationHandler(); + boolean shouldSimulationBeReset = true; + + if (sm.getCurrentState() == null) sm.initialStep(); // ToDo NIELS: Find better solution + + //Have the user left a trace or is he simulating a query + if (sm.traceLog.size() >= 2 || sm.getCurrentSimulation().contains(SimulationHandler.QUERY_PREFIX)) { + shouldSimulationBeReset = false; + } + + if (!firstTimeInSimulator && !new HashSet<>(overviewPresentation.getController().getComponentObservableList()) + .containsAll(findComponentsInCurrentSimulation())) { + shouldSimulationBeReset = true; + } + + if (shouldSimulationBeReset || firstTimeInSimulator) { + + resetSimulation(); + sm.resetToInitialLocation(); + } + overviewPresentation.getController().addProcessesToGroup(); + overviewPresentation.getController().highlightProcessState(sm.getCurrentState()); + } + + /** + * Resets the current simulation, and prepares for a new simulation by clearing the + * {@link SimulatorOverviewController#processContainer} and adding the processes of the new simulation. + */ + private void resetSimulation() { + final SimulationHandler sm = Ecdar.getSimulationHandler(); + sm.initializeDefaultSystem(); + overviewPresentation.getController().clearOverview(); + overviewPresentation.getController().getComponentObservableList().clear(); + overviewPresentation.getController().getComponentObservableList().addAll(findComponentsInCurrentSimulation()); + firstTimeInSimulator = false; + } + + /** + * Finds the components that are used in the current simulation by looking at the component found in + * {@link Project#getComponents()} and compare them to the processes declared in the {@link SimulationHandler#getSystem()} + *

+ * TODO This does currently not work if the same component is used multiple times. + * + * @return all the components used in the current simulation + */ + private List findComponentsInCurrentSimulation() { + //Show components from the system + final SimulationHandler sm = Ecdar.getSimulationHandler(); + List components = new ArrayList<>(); + components = Ecdar.getProject().getComponents(); +// for (int i = 0; i < sm.getSystem().getNoOfProcesses(); i++) { +// final int finalI = i; // when using a var in lambda it has to be final +// final List filteredList = Ecdar.getProject().getComponents().filtered(component -> { +// return component.getName().contentEquals(sm.getSystem().getProcess(finalI).getName()); +// }); +// components.addAll(filteredList); +// } + return components; + } + + /** + * Resets the simulation and prepares the view for showing the new simulation to the user + */ + public void resetCurrentSimulation() { + overviewPresentation.getController().removeProcessesFromGroup(); + resetSimulation(); + Ecdar.getSimulationHandler().resetToInitialLocation(); + overviewPresentation.getController().addProcessesToGroup(); + } + + public void willHide() { + overviewPresentation.getController().removeProcessesFromGroup(); + overviewPresentation.getController().getComponentObservableList().forEach(component -> { + // Previously reset coordinates of component box + }); + overviewPresentation.getController().unhighlightProcesses(); + } + + public static DoubleProperty getWidthProperty() { + return width; + } + + public static DoubleProperty getHeightProperty() { + return height; + } + + + public static ObjectProperty getSelectedTransitionProperty() { + return selectedTransition; + } + + public static void setSelectedTransition(Transition selectedTransition) { + SimulatorController.selectedTransition.set(selectedTransition); + } + + public static ObjectProperty getSelectedStateProperty() { + return selectedState; + } + + public static void setSelectedState(SimulationState selectedState) { + SimulatorController.selectedState.set(selectedState); + } +} diff --git a/src/main/java/ecdar/controllers/SimulatorOverviewController.java b/src/main/java/ecdar/controllers/SimulatorOverviewController.java new file mode 100644 index 00000000..9af4af76 --- /dev/null +++ b/src/main/java/ecdar/controllers/SimulatorOverviewController.java @@ -0,0 +1,385 @@ +package ecdar.controllers; + +import ecdar.Ecdar; +import ecdar.abstractions.*; +import ecdar.presentations.ProcessPresentation; +import ecdar.simulation.SimulationState; +import ecdar.simulation.Transition; +import javafx.collections.FXCollections; +import javafx.collections.ListChangeListener; +import javafx.collections.ObservableList; +import javafx.beans.InvalidationListener; +import javafx.collections.*; +import javafx.fxml.Initializable; +import javafx.geometry.Insets; +import javafx.scene.Group; +import javafx.scene.control.ScrollPane; +import javafx.scene.layout.*; +import javafx.util.Pair; + +import java.net.URL; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; +import java.util.ResourceBundle; + +/** + * The controller of the middle part of the simulator. + * It is here where processes of a simulation will be shown. + */ +public class SimulatorOverviewController implements Initializable { + public AnchorPane root; + public ScrollPane scrollPane; + public FlowPane processContainer; + public Group groupContainer; + + /** + * The amount that is going be zoomed in/out for each press on + or - + */ + private final double SCALE_DELTA = 1.1; + + /** + * The max that the user can zoom in + */ + private static final double MAX_ZOOM_IN = 1.6; + + /** + * The max that the user can zoom in + */ + private static final double MAX_ZOOM_OUT = 0.5; + + /** + * Offset such that the view does not overlap with the scroll bar on the right hand sig. + */ + private static final int SUPER_SPECIAL_SCROLLPANE_OFFSET = 20; + + private final ObservableList componentArrayList = FXCollections.observableArrayList(); + private final ObservableMap processPresentations = FXCollections.observableHashMap(); + + /** + * Is true if a reset of the zoom have been requested, false if not. + */ + private boolean resetZoom = false; + private boolean isMaxZoomInReached = false; + private boolean isMaxZoomOutReached = false; + + @Override + public void initialize(final URL location, final ResourceBundle resources) { + groupContainer = new Group(); + processContainer = new FlowPane(); + //In case that the processContainer gets moved around we have to keep in into place. + initializeProcessContainer(); + + initializeWindowResizing(); + initializeZoom(); + initializeHighlighting(); + initializeSimulationVariables(); + // Add the processes and group to the view + addProcessesToGroup(); + scrollPane.setContent(groupContainer); + } + + /** + * Initializes the {@link #processContainer} with its correct styling, and placement on the view. + * It also adds a {@link ListChangeListener} on {@link #componentArrayList} where it adds the + * {@link Component}s which are needed to the processContainer. + */ + private void initializeProcessContainer() { + processContainer.translateXProperty().addListener((observable, oldValue, newValue) -> { + processContainer.setTranslateX(0); + }); + //Sets the space between the processes + processContainer.setHgap(10); + processContainer.setVgap(10); + + // padding to the scrollpane + processContainer.setPadding(new Insets(5)); + + componentArrayList.addListener((ListChangeListener) c -> { + final Map processes = new HashMap<>(); + while (c.next()) { + if (c.wasRemoved()) { + clearOverview(); + } else { + c.getAddedSubList().forEach(o -> processes.put(o.getName(), new ProcessPresentation(o))); + } + } + // Highlight the current state when the processes change + highlightProcessState(Ecdar.getSimulationHandler().getCurrentState()); // ToDo NIELS: Throws NullPointerException inside method due to currentState + processContainer.getChildren().addAll(processes.values()); + processPresentations.putAll(processes); + }); + + final Map processes = new HashMap<>(); + componentArrayList.forEach(o -> processes.put(o.getName(), new ProcessPresentation(o))); + + processContainer.getChildren().addAll(processes.values()); + processPresentations.putAll(processes); + } + + /** + * Clears the {@link #processContainer} and the {@link #processPresentations}. + */ + void clearOverview() { + processContainer.getChildren().clear(); + processPresentations.clear(); + } + + /** + * Setup listeners for displaying clock and variable values on the {@link ProcessPresentation} + */ + private void initializeSimulationVariables() { + Ecdar.getSimulationHandler().getSimulationVariables().addListener((InvalidationListener) obs -> { + Ecdar.getSimulationHandler().getSimulationVariables().forEach((s, bigDecimal) -> { + if (!s.equals("t(0)")) {// As t(0) does not belong to any process + final String[] spittedString = s.split("\\."); + // If the process containing the var is not there we just skip it + if (spittedString.length > 0 && processPresentations.size() > 0) { + processPresentations.get(spittedString[0]).getController().getVariables().put(spittedString[1], bigDecimal); + } + } + }); + }); + Ecdar.getSimulationHandler().getSimulationClocks().addListener((InvalidationListener) obs -> { + if (processPresentations.size() == 0) return; + Ecdar.getSimulationHandler().getSimulationClocks().forEach((s, bigDecimal) -> { + if (!s.equals("t(0)")) {// As t(0) does not belong to any process + final String[] spittedString = s.split("\\."); + // If the process containing the clock is not there we just skip it + if (spittedString.length > 0 && processPresentations.size() > 0) { + processPresentations.get(spittedString[0]).getController().getClocks().put(spittedString[1], bigDecimal); + } + } + }); + }); + } + + /** + * Removes {@link #processContainer} from the {@link #groupContainer}.
+ * In this way the {@link Component}s in the processContainer will then again be resizable, + * as the class {@link Group} makes its children not resizeable. + * + * @see Group + */ + void removeProcessesFromGroup() { + groupContainer.getChildren().removeAll(processContainer); + } + + /** + * Adds the {@link #processContainer} to the {@link #groupContainer}.
+ * This method is usually needed to called if {@link #removeProcessesFromGroup()} have been called, or + * if the processContainer just need to be added to the groupContainer.
+ * This method makes sure that the processContainer will be added to the groupContainer + * which is needed to show the {@link ProcessPresentation}s in the {@link #scrollPane}. + * If the processContainer is already contained in the groupContainer + * the method does nothing but return. + * + * @see #removeProcessesFromGroup() + */ + void addProcessesToGroup() { + if (groupContainer.getChildren().contains(processContainer)) return; + groupContainer.getChildren().add(processContainer); + } + + /** + * Initializes the zoom functionality in {@link #processContainer} + */ + private void initializeZoom() { + processContainer.scaleXProperty().addListener((observable, oldValue, newValue) -> { + if (newValue.doubleValue() > MAX_ZOOM_IN) isMaxZoomInReached = true; + if (newValue.doubleValue() < MAX_ZOOM_OUT) isMaxZoomOutReached = true; + + handleWidthOnScale(oldValue, newValue); + }); + + // to support pinch zooming + //TODO this should be fixed at as it does not work as it should + /* + processContainer.setOnZoom(event -> { + //Tries to zoom in/out but max is reached + if(event.getZoomFactor() >= 1 && isMaxZoomInReached) return; + if(event.getZoomFactor() < 1 && isMaxZoomOutReached) return; + + isMaxZoomInReached = false; + isMaxZoomOutReached = false; + + processContainer.setScaleX(processContainer.getScaleX() * event.getZoomFactor()); + processContainer.setScaleY(processContainer.getScaleY() * event.getZoomFactor()); + });*/ + } + + /** + * Initializes listener for change of width in {@link #scrollPane} which also affects {@link #processContainer}
+ * This does also take the zooming into account when doing the resizing. + */ + private void initializeWindowResizing() { + scrollPane.widthProperty().addListener((observable, oldValue, newValue) -> { + final double width = (newValue.doubleValue()) * (1 + (1 - processContainer.getScaleX())); + if (processContainer.getScaleX() > 1) { //Zoomed in + processContainer.setMinWidth(width); + processContainer.setMaxWidth(width); + } else if (processContainer.getScaleX() < 1) { //Zoomed out + processContainer.setMinWidth(width); + processContainer.setMaxWidth(width); + final double deltaWidth = newValue.doubleValue() - groupContainer.layoutBoundsProperty().get().getWidth(); + processContainer.setMinWidth(processContainer.getWidth() + (deltaWidth - SUPER_SPECIAL_SCROLLPANE_OFFSET) * (1 + (1 - processContainer.getScaleX()))); + processContainer.setMaxWidth(processContainer.getWidth() + (deltaWidth - SUPER_SPECIAL_SCROLLPANE_OFFSET) * (1 + (1 - processContainer.getScaleX()))); + } else { // Reset + processContainer.setMinWidth(newValue.doubleValue() - SUPER_SPECIAL_SCROLLPANE_OFFSET); + processContainer.setMaxWidth(newValue.doubleValue() - SUPER_SPECIAL_SCROLLPANE_OFFSET); + } + }); + } + + /** + * Increments the {@link #processContainer} scaleX and scaleY properties + * which creates the zoom-in feeling. Resizing of the view is handled by {@link #handleWidthOnScale(Number, Number)} + * + * @see FlowPane#scaleXProperty() + * @see FlowPane#scaleYProperty() + */ + void zoomIn() { + if (isMaxZoomInReached) return; + isMaxZoomOutReached = false; + processContainer.setScaleX(processContainer.getScaleX() * SCALE_DELTA); + processContainer.setScaleY(processContainer.getScaleY() * SCALE_DELTA); + } + + + /** + * Decrements the {@link #processContainer} scaleX and scaleY properties + * which creates the zoom-in feeling. Resizing of the view is handled by {@link #handleWidthOnScale(Number, Number)} + * + * @see FlowPane#scaleXProperty() + * @see FlowPane#scaleYProperty() + */ + void zoomOut() { + if (isMaxZoomOutReached) return; + isMaxZoomInReached = false; + processContainer.setScaleX(processContainer.getScaleX() * (1 / SCALE_DELTA)); + processContainer.setScaleY(processContainer.getScaleY() * (1 / SCALE_DELTA)); + } + + /** + * Resets the scaling of the {@link #processContainer}, and hereby the zoom + * + * @see FlowPane#scaleXProperty() + * @see FlowPane#scaleYProperty() + */ + void resetZoom() { + if (processContainer.getScaleX() == 1) return; + resetZoom = true; + isMaxZoomInReached = false; + isMaxZoomOutReached = false; + processContainer.setScaleX(1); + processContainer.setScaleY(1); + } + + /** + * Handles the scaling of the width of the {@link #processContainer} + * + * @param oldValue the width of {@link #scrollPane} before the change + * @param newValue the width of {@link #scrollPane} after the change + */ + private void handleWidthOnScale(final Number oldValue, final Number newValue) { + if (resetZoom) { //Zoom reset + resetZoom = false; + processContainer.setMinWidth(scrollPane.getWidth() - SUPER_SPECIAL_SCROLLPANE_OFFSET); + processContainer.setMaxWidth(scrollPane.getWidth() - SUPER_SPECIAL_SCROLLPANE_OFFSET); + } else if (oldValue.doubleValue() > newValue.doubleValue()) { //Zoom in + resetZoom = false; + processContainer.setMinWidth(Math.round(processContainer.getWidth() * SCALE_DELTA)); + processContainer.setMaxWidth(Math.round(processContainer.getWidth() * SCALE_DELTA)); + } else { // Zoom out + resetZoom = false; + processContainer.setMinWidth(Math.round(processContainer.getWidth() * (1 / SCALE_DELTA))); + processContainer.setMaxWidth(Math.round(processContainer.getWidth() * (1 / SCALE_DELTA))); + } + } + + /** + * Initializer method to setup listeners that handle highlighting when selected/current state/transition changes + */ + private void initializeHighlighting() { + SimulatorController.getSelectedTransitionProperty().addListener((observable, oldTransition, newTransition) -> { + unhighlightProcesses(); + + // If the new transition is not null, we want to highlight the locations and edges in the new value + // otherwise we highlight the current state + if (newTransition != null) { + highlightProcessTransition(newTransition); + } else { + highlightProcessState(Ecdar.getSimulationHandler().getCurrentState()); + } + }); + + SimulatorController.getSelectedStateProperty().addListener((observable, oldState, newState) -> { + unhighlightProcesses(); + + // If the new state is not null, we want to highlight the locations in the new value + // otherwise we highlight the current state + if (newState != null) { + highlightProcessState(newState); + } else { + highlightProcessState(Ecdar.getSimulationHandler().getCurrentState()); + } + }); + } + + /** + * Highlights all the processes involved in the transition. + * Finds the processes involved in the transition (processes with edges in the transition) and highlights their edges + * Also fades processes that are not active in the selected transition + * + * @param transition The transition for which we highlight the involved processes + */ + public void highlightProcessTransition(final Transition transition) { + final var edges = transition.getEdges(); + + // List of all processes to show as inactive if they are not involved in a transition + // Processes are removed from this list, if they have an edge in the transition + final ArrayList processesToHide = new ArrayList<>(processPresentations.values()); + + for (final ProcessPresentation processPresentation : processPresentations.values()) { + + // Find the processes that have edges involved in this transition + processPresentation.getController().highlightEdges(edges); + processesToHide.remove(processPresentation); + } + + processesToHide.forEach(ProcessPresentation::showInactive); + } + + /** + * Unhighlights all processes + */ + public void unhighlightProcesses() { + for (final ProcessPresentation presentation : processPresentations.values()) { + presentation.getController().unhighlightProcess(); + presentation.showActive(); + } + } + + /** + * Finds the processes for the input locations in the input {@link SimulationState} and highlights the locations. + * + * @param state The state with the locations to highlight + */ + public void highlightProcessState(final SimulationState state) { + for (int i = 0; i < state.getLocations().size(); i++) { + final Pair loc = state.getLocations().get(i); + + for (final ProcessPresentation presentation : processPresentations.values()) { + final String processName = presentation.getController().getComponent().getName(); + + if (processName.equals(loc.getKey())) { + presentation.getController().highlightLocation(loc.getValue()); + } + } + } + } + + public ObservableList getComponentObservableList() { + return componentArrayList; + } +} diff --git a/src/main/java/ecdar/controllers/TracePaneElementController.java b/src/main/java/ecdar/controllers/TracePaneElementController.java new file mode 100755 index 00000000..6ace31df --- /dev/null +++ b/src/main/java/ecdar/controllers/TracePaneElementController.java @@ -0,0 +1,187 @@ +package ecdar.controllers; + +import com.jfoenix.controls.JFXRippler; +import ecdar.Ecdar; +import ecdar.abstractions.Location; +import ecdar.simulation.SimulationState; +import ecdar.simulation.SimulationHandler; +import ecdar.presentations.TransitionPresentation; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleIntegerProperty; +import javafx.collections.ListChangeListener; +import javafx.event.EventHandler; +import javafx.fxml.FXML; +import javafx.fxml.Initializable; +import javafx.scene.control.Label; +import javafx.scene.layout.AnchorPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.VBox; +import org.kordamp.ikonli.javafx.FontIcon; + +import java.net.URL; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.ResourceBundle; + +/** + * The controller class for the trace pane element that can be inserted into a simulator pane + */ +public class TracePaneElementController implements Initializable { + public VBox root; + public HBox toolbar; + public Label traceTitle; + public JFXRippler expandTrace; + public VBox traceList; + public FontIcon expandTraceIcon; + public AnchorPane traceSummary; + public Label summaryTitleLabel; + public Label summarySubtitleLabel; + + private SimpleBooleanProperty isTraceExpanded = new SimpleBooleanProperty(false); + private Map transitionPresentationMap = new LinkedHashMap<>(); + private SimpleIntegerProperty numberOfSteps = new SimpleIntegerProperty(0); + + @Override + public void initialize(URL location, ResourceBundle resources) { + Ecdar.getSimulationHandler().getTraceLog().addListener((ListChangeListener) c -> { + while (c.next()) { + for (final SimulationState state : c.getAddedSubList()) { + insertTraceState(state, true); + } + + for (final SimulationState state : c.getRemoved()) { + traceList.getChildren().remove(transitionPresentationMap.get(state)); + transitionPresentationMap.remove(state); + } + } + + numberOfSteps.set(transitionPresentationMap.size()); + }); + + initializeTraceExpand(); + } + + /** + * Initializes the expand functionality that allows the user to show or hide the trace. + * By default the trace is shown. + */ + private void initializeTraceExpand() { + isTraceExpanded.addListener((obs, oldVal, newVal) -> { + if (newVal) { + showTrace(); + expandTraceIcon.setIconLiteral("gmi-expand-less"); + expandTraceIcon.setIconSize(24); + } else { + hideTrace(); + expandTraceIcon.setIconLiteral("gmi-expand-more"); + expandTraceIcon.setIconSize(24); + } + }); + + isTraceExpanded.set(true); + } + + + /** + * Removes all the trace view elements as to hide the trace from the user + * Also shows the summary view when the trace is hidden + */ + private void hideTrace() { + traceList.getChildren().clear(); + root.getChildren().add(traceSummary); + } + + /** + * Shows the trace by inserting a {@link TransitionPresentation} for each trace state + * Also hides the summary view, since it should only be visible when the trace is hidden + */ + private void showTrace() { + transitionPresentationMap.forEach((state, presentation) -> { + insertTraceState(state, false); + }); + root.getChildren().remove(traceSummary); + } + + /** + * Instantiates a {@link TransitionPresentation} for a {@link SimulationState} and adds it to the view + * + * @param state The state the should be inserted into the trace log + * @param shouldAnimate A boolean that indicates whether the trace should fade in when added to the view + */ + private void insertTraceState(final SimulationState state, final boolean shouldAnimate) { + final TransitionPresentation transitionPresentation = new TransitionPresentation(); + transitionPresentationMap.put(state, transitionPresentation); + + transitionPresentation.setOnMouseReleased(event -> { + event.consume(); + final SimulationHandler simHandler = Ecdar.getSimulationHandler(); + if (simHandler == null) return; + Ecdar.getSimulationHandler().selectTransitionFromLog(state); + }); + + EventHandler mouseEntered = transitionPresentation.getOnMouseEntered(); + transitionPresentation.setOnMouseEntered(event -> { + SimulatorController.setSelectedState(state); + mouseEntered.handle(event); + }); + + EventHandler mouseExited = transitionPresentation.getOnMouseExited(); + transitionPresentation.setOnMouseExited(event -> { + SimulatorController.setSelectedState(null); + mouseExited.handle(event); + }); + + + String title = traceString(state); + transitionPresentation.getController().setTitle(title); + + // Only insert the presentation into the view if the trace is expanded + if (isTraceExpanded.get()) { + traceList.getChildren().add(transitionPresentation); + if (shouldAnimate) { + transitionPresentation.playFadeAnimation(); + } + } + } + + /** + * A helper method that returns a string representing a state in the trace log + * + * @param state The SimulationState to represent + * @return A string representing the state + */ + private String traceString(SimulationState state) { + StringBuilder title = new StringBuilder("("); + int length = state.getLocations().size(); + for (int i = 0; i < length; i++) { + Location loc = Ecdar.getProject() + .findComponent(state.getLocations().get(i).getKey()) + .findLocation(state.getLocations().get(i).getValue()); + String locationName = loc.getNickname(); + if (i == length - 1) { + title.append(locationName); + } else { + title.append(locationName).append(", "); + } + } + title.append(")"); + + return title.toString(); + } + + /** + * Method to be called when clicking on the expand rippler in the trace toolbar + */ + @FXML + private void expandTrace() { + if (isTraceExpanded.get()) { + isTraceExpanded.set(false); + } else { + isTraceExpanded.set(true); + } + } + + public SimpleIntegerProperty getNumberOfStepsProperty() { + return numberOfSteps; + } +} diff --git a/src/main/java/ecdar/controllers/TransitionController.java b/src/main/java/ecdar/controllers/TransitionController.java new file mode 100755 index 00000000..7bacda2e --- /dev/null +++ b/src/main/java/ecdar/controllers/TransitionController.java @@ -0,0 +1,45 @@ +package ecdar.controllers; + +import com.jfoenix.controls.JFXRippler; +import ecdar.simulation.Transition; +import javafx.beans.property.SimpleObjectProperty; +import javafx.fxml.Initializable; +import javafx.scene.control.Label; +import javafx.scene.layout.AnchorPane; + +import java.net.URL; +import java.util.ResourceBundle; + +/** + * The controller class for the transition view element. + * It represents a single transition and may be used by classes like {@see TransitionPaneElementController} + * to show a list of transitions + */ +public class TransitionController implements Initializable { + public AnchorPane root; + public Label titleLabel; + public JFXRippler rippler; + + // The transition that the view represents + private SimpleObjectProperty transition = new SimpleObjectProperty<>(); + private SimpleObjectProperty title = new SimpleObjectProperty<>(); + + @Override + public void initialize(URL location, ResourceBundle resources) { + title.addListener(((observable, oldValue, newValue) -> { + titleLabel.setText(newValue); + })); + } + + public void setTitle(String title) { + this.title.set(title); + } + + public void setTransition(Transition transition) { + this.transition.set(transition); + } + + public Transition getTransition() { + return transition.get(); + } +} diff --git a/src/main/java/ecdar/controllers/TransitionPaneElementController.java b/src/main/java/ecdar/controllers/TransitionPaneElementController.java new file mode 100755 index 00000000..8a985a92 --- /dev/null +++ b/src/main/java/ecdar/controllers/TransitionPaneElementController.java @@ -0,0 +1,241 @@ +package ecdar.controllers; + +import com.jfoenix.controls.JFXRippler; +import com.jfoenix.controls.JFXTextField; +import ecdar.Ecdar; +import ecdar.abstractions.Edge; +import ecdar.simulation.Transition; +import ecdar.simulation.SimulationHandler; +import ecdar.presentations.TransitionPresentation; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.collections.ListChangeListener; +import javafx.event.EventHandler; +import javafx.fxml.FXML; +import javafx.fxml.Initializable; +import javafx.scene.control.Label; +import javafx.scene.control.Tooltip; +import javafx.scene.layout.HBox; +import javafx.scene.layout.VBox; +import org.kordamp.ikonli.javafx.FontIcon; + +import java.math.BigDecimal; +import java.net.URL; +import java.util.HashMap; +import java.util.Map; +import java.util.ResourceBundle; + +/** + * The controller class for the transition pane element that can be inserted into the simulator panes + */ +public class TransitionPaneElementController implements Initializable { + public VBox root; + public VBox transitionList; + public HBox toolbar; + public Label toolbarTitle; + public JFXRippler refreshRippler; + public JFXRippler expandTransition; + public FontIcon expandTransitionIcon; + public VBox delayChooser; + public JFXTextField delayTextField; + + private SimpleBooleanProperty isTransitionExpanded = new SimpleBooleanProperty(false); + private Map transitionPresentationMap = new HashMap<>(); + private SimpleObjectProperty delay = new SimpleObjectProperty<>(BigDecimal.ZERO); + + @Override + public void initialize(URL location, ResourceBundle resources) { + Ecdar.getSimulationHandler().availableTransitions.addListener((ListChangeListener) c -> { + while (c.next()) { + for (Transition trans : c.getAddedSubList()) insertTransition(trans); + + for (final Transition trans: c.getRemoved()) { + transitionList.getChildren().remove(transitionPresentationMap.get(trans)); + transitionPresentationMap.remove(trans); + } + } + }); + + initializeTransitionExpand(); + initializeDelayChooser(); + } + + /** + * Sets up listeners for the delay chooser. + * Listens for changes in text property and updates the textfield with a sanitized value (e.g. no letters in delay). + * Also listens for changes in focus, so there's always a value in the textfield, even if the user deleted the text. + * Adds tooltip for the textfield. + */ + private void initializeDelayChooser() { + delayTextField.textProperty().addListener(((observable, oldValue, newValue) -> { + delayTextChanged(oldValue, newValue); + })); + + delayTextField.focusedProperty().addListener((observable, oldValue, newValue) -> { + // If the textfield loses focus and the user didn't enter anything + // show the value 0.0 + if(!newValue && delay.get().equals(BigDecimal.ZERO)) { + delayTextField.setText("0.0"); + } + }); + + Tooltip.install(delayTextField, new Tooltip("Enter delay to use for next transition")); + } + + /** + * Initializes the expand functionality that allows the user to show or hide the transitions. + * By default the transitions are shown. + */ + private void initializeTransitionExpand() { + isTransitionExpanded.addListener((obs, oldVal, newVal) -> { + if(newVal) { + if(!root.getChildren().contains(delayChooser)) { + // Add the delay chooser just below the toolbar + root.getChildren().add(1, delayChooser); + } + showTransitions(); + expandTransitionIcon.setIconLiteral("gmi-expand-less"); + expandTransitionIcon.setIconSize(24); + } else { + root.getChildren().remove(delayChooser); + hideTransitions(); + expandTransitionIcon.setIconLiteral("gmi-expand-more"); + expandTransitionIcon.setIconSize(24); + } + }); + + isTransitionExpanded.set(true); + } + + /** + * Removes all the transition view elements as to hide the transitions from the user + */ + private void hideTransitions() { + transitionList.getChildren().clear(); + } + + /** + * Shows the available transitions by inserting a {@link TransitionPresentation} for each transition + */ + private void showTransitions() { + transitionPresentationMap.forEach((transition, presentation) -> { + insertTransition(transition); + }); + } + + /** + * Instantiates a TransitionPresentation for a Transition and adds it to the view + * @param transition The transition that should be inserted into the view + */ + private void insertTransition(Transition transition) { + final TransitionPresentation transitionPresentation = new TransitionPresentation(); + String title = transitionString(transition); + transitionPresentation.getController().setTitle(title); + transitionPresentation.getController().setTransition(transition); + + // Update the selected transition when mouse entered. + // Add the event to existing mouseEntered events + // e.g. TransitionPresentation already has mouseEntered functionality and we want to keep it + EventHandler mouseEntered = transitionPresentation.getOnMouseEntered(); + transitionPresentation.setOnMouseEntered(event -> { + SimulatorController.setSelectedTransition(transitionPresentation.getController().getTransition()); + mouseEntered.handle(event); + }); + + EventHandler mouseExited = transitionPresentation.getOnMouseExited(); + transitionPresentation.setOnMouseExited(event -> { + SimulatorController.setSelectedTransition(null); + mouseExited.handle(event); + }); + + transitionPresentation.setOnMouseClicked(event -> { + event.consume(); + + // Performs the next step of the simulation when clicking on a transition + SimulationHandler simHandler = Ecdar.getSimulationHandler(); + if (simHandler != null) { + simHandler.nextStep(transitionPresentation.getController().getTransition(), this.delay.get()); + } + }); + + transitionPresentationMap.put(transition, transitionPresentation); + + // Only insert the presentation into the view if the transitions are expanded + // Avoids inserting duplicate elements in the view (it's still added to the map) + if(isTransitionExpanded.get()) { + transitionList.getChildren().add(transitionPresentation); + } + } + + /** + * A helper method that returns a string representing a transition in the transition chooser + * @param transition The {@link Transition} to represent + * @return A string representing the transition + */ + private String transitionString(Transition transition) { + String title = transition.getLabel(); + if(transition.getEdges() != null) { + for (Edge edge : transition.getEdges()) { + title += " " + edge.getId(); + } + } + return title; + } + + /** + * Method to be called when clicking on the expand rippler in the transition toolbar + */ + @FXML + private void expandTransitions() { + if(isTransitionExpanded.get()) { + isTransitionExpanded.set(false); + } else { + isTransitionExpanded.set(true); + } + } + + /** + * Gets the initial step from the SimulationHandler. + * Used by the refresh button. + */ + @FXML + private void refreshTransitions() { + SimulatorController.setSelectedTransition(null); +// MainController.openReloadSimulationDialog(); // ToDo: Implement + } + + /** + * Sanitizes the input that the user inserts into the delay textfield. + * Checks if the text can be converted into a BigDecimal otherwise show the previous value. + * For example avoids users entering letters. + * @param oldValue The old value to show if the newvalue cannot be converted to a BigDecimal + * @param newValue The new value to show in the textfield + */ + @FXML + private void delayTextChanged(String oldValue, String newValue) { + // If the value is empty (the user deleted the value), assume that the value is 0.0 but do not update the text + if(newValue.isEmpty()) { + this.delay.set(BigDecimal.ZERO); + } else { + // Try to convert the new value into a BigDecimal + // Note that we don't setText here, as the new value is already shown in the textfield + try { + BigDecimal bd = new BigDecimal(newValue); + + // Checking the string for "-" instead of whether bd is negative is due to the case of -0.0 + // So checking the string is just simpler + if(newValue.contains("-")) { + throw new NumberFormatException(); + } + + this.delay.set(bd); + + } catch (NumberFormatException ex) { + // If the conversion was not possible, show the old value + this.delayTextField.setText(oldValue); + } + } + + } + +} diff --git a/src/main/java/ecdar/presentations/EcdarPresentation.java b/src/main/java/ecdar/presentations/EcdarPresentation.java index f8fb5f25..0648b67d 100644 --- a/src/main/java/ecdar/presentations/EcdarPresentation.java +++ b/src/main/java/ecdar/presentations/EcdarPresentation.java @@ -7,10 +7,6 @@ import ecdar.controllers.EcdarController; import ecdar.utility.UndoRedoStack; import ecdar.utility.colors.Color; -import ecdar.utility.colors.EnabledColor; -import ecdar.utility.helpers.SelectHelper; -import com.jfoenix.controls.JFXPopup; -import com.jfoenix.controls.JFXRippler; import com.jfoenix.controls.JFXSnackbar; import ecdar.utility.keyboard.Keybind; import ecdar.utility.keyboard.KeyboardTracker; @@ -22,89 +18,65 @@ import javafx.beans.property.SimpleDoubleProperty; import javafx.collections.ListChangeListener; import javafx.geometry.Insets; -import javafx.scene.control.Label; -import javafx.scene.control.Tooltip; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.input.KeyCode; import javafx.scene.input.KeyCodeCombination; import javafx.scene.input.KeyCombination; import javafx.scene.layout.*; -import javafx.scene.shape.Circle; import javafx.util.Duration; -import javafx.util.Pair; - -import java.util.ArrayList; -import java.util.List; - -import static ecdar.utility.colors.EnabledColor.enabledColors; public class EcdarPresentation extends StackPane { private final EcdarController controller; - private final BooleanProperty filePaneOpen = new SimpleBooleanProperty(false); - private final SimpleDoubleProperty filePaneAnimationProperty = new SimpleDoubleProperty(0); - private final BooleanProperty queryPaneOpen = new SimpleBooleanProperty(false); - private final SimpleDoubleProperty queryPaneAnimationProperty = new SimpleDoubleProperty(0); - private Timeline openQueryPaneAnimation; - private Timeline closeQueryPaneAnimation; - private Timeline openFilePaneAnimation; - private Timeline closeFilePaneAnimation; + private final BooleanProperty leftPaneOpen = new SimpleBooleanProperty(false); + private final SimpleDoubleProperty leftPaneAnimationProperty = new SimpleDoubleProperty(0); + private final BooleanProperty rightPaneOpen = new SimpleBooleanProperty(false); + private final SimpleDoubleProperty rightPaneAnimationProperty = new SimpleDoubleProperty(0); + private Timeline openLeftPaneAnimation; + private Timeline closeLeftPaneAnimation; + private Timeline openRightPaneAnimation; + private Timeline closeRightPaneAnimation; public EcdarPresentation() { controller = new EcdarFXMLLoader().loadAndGetController("EcdarPresentation.fxml", this); initializeTopBar(); - initializeToolbar(); initializeQueryDetailsDialog(); - initializeColorSelector(); - - initializeToggleQueryPaneFunctionality(); - initializeToggleFilePaneFunctionality(); - - initializeSelectDependentToolbarButton(controller.colorSelected); - Tooltip.install(controller.colorSelected, new Tooltip("Colour")); - - initializeSelectDependentToolbarButton(controller.deleteSelected); - Tooltip.install(controller.deleteSelected, new Tooltip("Delete")); - - initializeToolbarButton(controller.undo); - initializeToolbarButton(controller.redo); - initializeUndoRedoButtons(); + initializeToggleLeftPaneFunctionality(); + initializeToggleRightPaneFunctionality(); initializeSnackbar(); - // Open the file and query panel initially + // Open the left and right panes initially Platform.runLater(() -> { // Bind sizing of sides and center panes to ensure correct sizing - controller.canvasPane.minWidthProperty().bind(controller.root.widthProperty().subtract(filePaneAnimationProperty.add(queryPaneAnimationProperty))); - controller.canvasPane.maxWidthProperty().bind(controller.root.widthProperty().subtract(filePaneAnimationProperty.add(queryPaneAnimationProperty))); + controller.getEditorPresentation().getController().canvasPane.minWidthProperty().bind(controller.root.widthProperty().subtract(leftPaneAnimationProperty.add(rightPaneAnimationProperty))); + controller.getEditorPresentation().getController().canvasPane.maxWidthProperty().bind(controller.root.widthProperty().subtract(leftPaneAnimationProperty.add(rightPaneAnimationProperty))); // Bind the height to ensure that both the top and bottom panes are shown // The height of the top pane is multiplied by 4 as the UI does not account for the height otherwise - controller.canvasPane.minHeightProperty().bind(controller.root.heightProperty().subtract(controller.topPane.heightProperty().multiply(4).add(controller.bottomFillerElement.heightProperty()))); - controller.canvasPane.maxHeightProperty().bind(controller.root.heightProperty().subtract(controller.topPane.heightProperty().multiply(4).add(controller.bottomFillerElement.heightProperty()))); + controller.getEditorPresentation().getController().canvasPane.minHeightProperty().bind(controller.root.heightProperty().subtract(controller.topPane.heightProperty().multiply(4).add(controller.bottomFillerElement.heightProperty()))); + controller.getEditorPresentation().getController().canvasPane.maxHeightProperty().bind(controller.root.heightProperty().subtract(controller.topPane.heightProperty().multiply(4).add(controller.bottomFillerElement.heightProperty()))); - controller.leftPane.minWidthProperty().bind(filePaneAnimationProperty); - controller.leftPane.maxWidthProperty().bind(filePaneAnimationProperty); + controller.leftPane.minWidthProperty().bind(leftPaneAnimationProperty); + controller.leftPane.maxWidthProperty().bind(leftPaneAnimationProperty); - controller.rightPane.minWidthProperty().bind(queryPaneAnimationProperty); - controller.rightPane.maxWidthProperty().bind(queryPaneAnimationProperty); + controller.rightPane.minWidthProperty().bind(rightPaneAnimationProperty); + controller.rightPane.maxWidthProperty().bind(rightPaneAnimationProperty); controller.topPane.minHeightProperty().bind(controller.menuBar.heightProperty()); controller.topPane.maxHeightProperty().bind(controller.menuBar.heightProperty()); - Platform.runLater(() -> { - toggleFilePane(); - toggleQueryPane(); - }); + toggleLeftPane(); + toggleRightPane(); Ecdar.getPresentation().controller.scalingProperty.addListener((observable, oldValue, newValue) -> { // If the scaling has changed trigger animations for open panes to update width Platform.runLater(() -> { - if (filePaneOpen.get()) { - openFilePaneAnimation.play(); + if (leftPaneOpen.get()) { + openLeftPaneAnimation.play(); } - if (queryPaneOpen.get()) { - openQueryPaneAnimation.play(); + if (rightPaneOpen.get()) { + openRightPaneAnimation.play(); } }); @@ -130,147 +102,6 @@ private void initializeSnackbar() { controller.snackbar.autosize(); } - private void initializeUndoRedoButtons() { - UndoRedoStack.canUndoProperty().addListener((obs, oldState, newState) -> { - if (newState) { - // Enable the undo button - controller.undo.setEnabled(true); - controller.undo.setOpacity(1); - } else { - // Disable the undo button - controller.undo.setEnabled(false); - controller.undo.setOpacity(0.3); - } - }); - - UndoRedoStack.canRedoProperty().addListener((obs, oldState, newState) -> { - if (newState) { - // Enable the redo button - controller.redo.setEnabled(true); - controller.redo.setOpacity(1); - } else { - // Disable the redo button - controller.redo.setEnabled(false); - controller.redo.setOpacity(0.3); - } - }); - - // Disable the undo button - controller.undo.setEnabled(false); - controller.undo.setOpacity(0.3); - - // Disable the redo button - controller.redo.setEnabled(false); - controller.redo.setOpacity(0.3); - - // Set tooltips - Tooltip.install(controller.undo, new Tooltip("Undo")); - Tooltip.install(controller.redo, new Tooltip("Redo")); - } - - private void initializeColorSelector() { - final JFXPopup popup = new JFXPopup(); - - final double listWidth = 136; - final FlowPane list = new FlowPane(); - for (final EnabledColor color : enabledColors) { - final Circle circle = new Circle(16, color.color.getColor(color.intensity)); - circle.setStroke(color.color.getColor(color.intensity.next(2))); - circle.setStrokeWidth(1); - - final Label label = new Label(color.keyCode.getName()); - label.getStyleClass().add("subhead"); - label.setTextFill(color.color.getTextColor(color.intensity)); - - final StackPane child = new StackPane(circle, label); - child.setMinSize(40, 40); - child.setMaxSize(40, 40); - - child.setOnMouseEntered(event -> { - final ScaleTransition scaleTransition = new ScaleTransition(Duration.millis(100), circle); - scaleTransition.setFromX(circle.getScaleX()); - scaleTransition.setFromY(circle.getScaleY()); - scaleTransition.setToX(1.1); - scaleTransition.setToY(1.1); - scaleTransition.play(); - }); - - child.setOnMouseExited(event -> { - final ScaleTransition scaleTransition = new ScaleTransition(Duration.millis(100), circle); - scaleTransition.setFromX(circle.getScaleX()); - scaleTransition.setFromY(circle.getScaleY()); - scaleTransition.setToX(1.0); - scaleTransition.setToY(1.0); - scaleTransition.play(); - }); - - child.setOnMouseClicked(event -> { - final List> previousColor = new ArrayList<>(); - - SelectHelper.getSelectedElements().forEach(selectable -> { - previousColor.add(new Pair<>(selectable, new EnabledColor(selectable.getColor(), selectable.getColorIntensity()))); - }); - - controller.changeColorOnSelectedElements(color, previousColor); - - popup.hide(); - SelectHelper.clearSelectedElements(); - }); - - list.getChildren().add(child); - } - list.setMinWidth(listWidth); - list.setMaxWidth(listWidth); - list.setStyle("-fx-background-color: white; -fx-padding: 8;"); - - popup.setPopupContent(list); - - controller.colorSelected.setOnMouseClicked((e) -> { - // If nothing is selected - if (SelectHelper.getSelectedElements().size() == 0) return; - popup.show(controller.colorSelected, JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.RIGHT, -10, 15); - }); - } - - private void initializeSelectDependentToolbarButton(final JFXRippler button) { - initializeToolbarButton(button); - - // The color button should only be enabled when an element is selected - SelectHelper.getSelectedElements().addListener(new ListChangeListener() { - @Override - public void onChanged(final Change c) { - if (SelectHelper.getSelectedElements().size() > 0) { - button.setEnabled(true); - - final FadeTransition fadeAnimation = new FadeTransition(Duration.millis(100), button); - fadeAnimation.setFromValue(button.getOpacity()); - fadeAnimation.setToValue(1); - fadeAnimation.play(); - } else { - button.setEnabled(false); - - final FadeTransition fadeAnimation = new FadeTransition(Duration.millis(100), button); - fadeAnimation.setFromValue(1); - fadeAnimation.setToValue(0.3); - fadeAnimation.play(); - } - } - }); - - // Disable the button - button.setEnabled(false); - button.setOpacity(0.3); - } - - private void initializeToolbarButton(final JFXRippler button) { - final Color color = Color.GREY_BLUE; - final Color.Intensity colorIntensity = Color.Intensity.I800; - - button.setMaskType(JFXRippler.RipplerMask.CIRCLE); - button.setRipplerFill(color.getTextColor(colorIntensity)); - button.setPosition(JFXRippler.RipplerPos.BACK); - } - private void initializeQueryDetailsDialog() { final Color modalBarColor = Color.GREY_BLUE; final Color.Intensity modalBarColorIntensity = Color.Intensity.I500; @@ -283,106 +114,114 @@ private void initializeQueryDetailsDialog() { ))); } - private void initializeToggleFilePaneFunctionality() { - initializeOpenFilePaneAnimation(); - initializeCloseFilePaneAnimation(); + private void initializeToggleLeftPaneFunctionality() { + initializeOpenLeftPaneAnimation(); + initializeCloseLeftPaneAnimation(); // Translate the x coordinate to create the open/close animations - controller.filePane.translateXProperty().bind(filePaneAnimationProperty.subtract(controller.filePane.widthProperty())); + controller.projectPane.translateXProperty().bind(leftPaneAnimationProperty.subtract(controller.projectPane.widthProperty())); + controller.leftSimPane.translateXProperty().bind(leftPaneAnimationProperty.subtract(controller.leftSimPane.widthProperty())); // Whenever the width of the file pane is updated, update the animations - controller.filePane.widthProperty().addListener((observable) -> { - initializeOpenFilePaneAnimation(); - initializeCloseFilePaneAnimation(); + controller.projectPane.widthProperty().addListener((observable) -> { + initializeOpenLeftPaneAnimation(); + initializeCloseLeftPaneAnimation(); + }); + + // Whenever the width of the file pane is updated, update the animations + controller.leftPane.widthProperty().addListener((observable) -> { + initializeOpenLeftPaneAnimation(); + initializeCloseLeftPaneAnimation(); }); } - private void initializeCloseFilePaneAnimation() { + private void initializeCloseLeftPaneAnimation() { final Interpolator interpolator = Interpolator.SPLINE(0.645, 0.045, 0.355, 1); - closeFilePaneAnimation = new Timeline(); + closeLeftPaneAnimation = new Timeline(); - final KeyValue open = new KeyValue(filePaneAnimationProperty, controller.filePane.getWidth(), interpolator); - final KeyValue closed = new KeyValue(filePaneAnimationProperty, 0, interpolator); + final KeyValue open = new KeyValue(leftPaneAnimationProperty, controller.projectPane.getWidth(), interpolator); + final KeyValue closed = new KeyValue(leftPaneAnimationProperty, 0, interpolator); final KeyFrame kf1 = new KeyFrame(Duration.millis(0), open); final KeyFrame kf2 = new KeyFrame(Duration.millis(200), closed); - closeFilePaneAnimation.getKeyFrames().addAll(kf1, kf2); + closeLeftPaneAnimation.getKeyFrames().addAll(kf1, kf2); } - private void initializeOpenFilePaneAnimation() { + private void initializeOpenLeftPaneAnimation() { final Interpolator interpolator = Interpolator.SPLINE(0.645, 0.045, 0.355, 1); - openFilePaneAnimation = new Timeline(); + openLeftPaneAnimation = new Timeline(); - final KeyValue closed = new KeyValue(filePaneAnimationProperty, 0, interpolator); - final KeyValue open = new KeyValue(filePaneAnimationProperty, controller.filePane.getWidth(), interpolator); + final KeyValue closed = new KeyValue(leftPaneAnimationProperty, 0, interpolator); + final KeyValue open = new KeyValue(leftPaneAnimationProperty, controller.projectPane.getWidth(), interpolator); final KeyFrame kf1 = new KeyFrame(Duration.millis(0), closed); final KeyFrame kf2 = new KeyFrame(Duration.millis(200), open); - openFilePaneAnimation.getKeyFrames().addAll(kf1, kf2); + openLeftPaneAnimation.getKeyFrames().addAll(kf1, kf2); } - private void initializeToggleQueryPaneFunctionality() { - initializeOpenQueryPaneAnimation(); - initializeCloseQueryPaneAnimation(); + private void initializeToggleRightPaneFunctionality() { + initializeOpenRightPaneAnimation(); + initializeCloseRightPaneAnimation(); // Translate the x coordinate to create the open/close animations - controller.queryPane.translateXProperty().bind(queryPaneAnimationProperty.multiply(-1).add(controller.queryPane.widthProperty())); + controller.queryPane.translateXProperty().bind(rightPaneAnimationProperty.multiply(-1).add(controller.queryPane.widthProperty())); + controller.rightSimPane.translateXProperty().bind(rightPaneAnimationProperty.multiply(-1).add(controller.rightSimPane.widthProperty())); // Whenever the width of the query pane is updated, update the animations controller.queryPane.widthProperty().addListener((observable) -> { - initializeOpenQueryPaneAnimation(); - initializeCloseQueryPaneAnimation(); + initializeOpenRightPaneAnimation(); + initializeCloseRightPaneAnimation(); }); // When new queries are added, make sure that the query pane is open Ecdar.getProject().getQueries().addListener((ListChangeListener) c -> { - if (closeQueryPaneAnimation == null) + if (closeRightPaneAnimation == null) return; // The query pane is not yet initialized while (c.next()) { c.getAddedSubList().forEach(o -> { - if (!queryPaneOpen.get()) { + if (!rightPaneOpen.get()) { // Open the pane - openQueryPaneAnimation.play(); + openRightPaneAnimation.play(); // Toggle the open state - queryPaneOpen.set(queryPaneOpen.not().get()); + rightPaneOpen.set(rightPaneOpen.not().get()); } }); } }); } - private void initializeCloseQueryPaneAnimation() { + private void initializeCloseRightPaneAnimation() { final Interpolator interpolator = Interpolator.SPLINE(0.645, 0.045, 0.355, 1); - closeQueryPaneAnimation = new Timeline(); + closeRightPaneAnimation = new Timeline(); - final KeyValue open = new KeyValue(queryPaneAnimationProperty, controller.queryPane.getWidth(), interpolator); - final KeyValue closed = new KeyValue(queryPaneAnimationProperty, 0, interpolator); + final KeyValue open = new KeyValue(rightPaneAnimationProperty, controller.queryPane.getWidth(), interpolator); + final KeyValue closed = new KeyValue(rightPaneAnimationProperty, 0, interpolator); final KeyFrame kf1 = new KeyFrame(Duration.millis(0), open); final KeyFrame kf2 = new KeyFrame(Duration.millis(200), closed); - closeQueryPaneAnimation.getKeyFrames().addAll(kf1, kf2); + closeRightPaneAnimation.getKeyFrames().addAll(kf1, kf2); } - private void initializeOpenQueryPaneAnimation() { + private void initializeOpenRightPaneAnimation() { final Interpolator interpolator = Interpolator.SPLINE(0.645, 0.045, 0.355, 1); - openQueryPaneAnimation = new Timeline(); + openRightPaneAnimation = new Timeline(); - final KeyValue closed = new KeyValue(queryPaneAnimationProperty, 0, interpolator); - final KeyValue open = new KeyValue(queryPaneAnimationProperty, controller.queryPane.getWidth(), interpolator); + final KeyValue closed = new KeyValue(rightPaneAnimationProperty, 0, interpolator); + final KeyValue open = new KeyValue(rightPaneAnimationProperty, controller.queryPane.getWidth(), interpolator); final KeyFrame kf1 = new KeyFrame(Duration.millis(0), closed); final KeyFrame kf2 = new KeyFrame(Duration.millis(200), open); - openQueryPaneAnimation.getKeyFrames().addAll(kf1, kf2); + openRightPaneAnimation.getKeyFrames().addAll(kf1, kf2); } private void initializeTopBar() { @@ -405,18 +244,6 @@ private void initializeTopBar() { ))); } - private void initializeToolbar() { - final Color color = Color.GREY_BLUE; - final Color.Intensity intensity = Color.Intensity.I700; - - // Set the background for the top toolbar - controller.toolbar.setBackground( - new Background(new BackgroundFill(color.getColor(intensity), - CornerRadii.EMPTY, - Insets.EMPTY) - )); - } - /** * Initialize help image views. */ @@ -451,37 +278,36 @@ private void initializeResizeQueryPane() { // Set bounds for resizing to be between 280px and half the screen width final double newWidth = Math.min(Math.max(prevWidth.get() + diff, 280), controller.root.getWidth() / 2); - queryPaneAnimationProperty.set(newWidth); + rightPaneAnimationProperty.set(newWidth); controller.queryPane.setMaxWidth(newWidth); controller.queryPane.setMinWidth(newWidth); }); } - - public BooleanProperty toggleFilePane() { - if (filePaneOpen.get()) { - closeFilePaneAnimation.play(); + public BooleanProperty toggleLeftPane() { + if (leftPaneOpen.get()) { + closeLeftPaneAnimation.play(); } else { - openFilePaneAnimation.play(); + openLeftPaneAnimation.play(); } // Toggle the open state - filePaneOpen.set(filePaneOpen.not().get()); + leftPaneOpen.set(leftPaneOpen.not().get()); - return filePaneOpen; + return leftPaneOpen; } - public BooleanProperty toggleQueryPane() { - if (queryPaneOpen.get()) { - closeQueryPaneAnimation.play(); + public BooleanProperty toggleRightPane() { + if (rightPaneOpen.get()) { + closeRightPaneAnimation.play(); } else { - openQueryPaneAnimation.play(); + openRightPaneAnimation.play(); } // Toggle the open state - queryPaneOpen.set(queryPaneOpen.not().get()); + rightPaneOpen.set(rightPaneOpen.not().get()); - return queryPaneOpen; + return rightPaneOpen; } public static void fitSizeWhenAvailable(final ImageView imageView, final StackPane pane) { @@ -506,8 +332,8 @@ public void showSnackbarMessage(final String message) { } public void showHelp() { - controller.dialogContainer.setVisible(true); - controller.dialog.show(controller.dialogContainer); + controller.modellingHelpDialogContainer.setVisible(true); + controller.modellingHelpDialog.show(controller.modellingHelpDialogContainer); } public EcdarController getController() { diff --git a/src/main/java/ecdar/presentations/EditorPresentation.java b/src/main/java/ecdar/presentations/EditorPresentation.java new file mode 100644 index 00000000..d17a4ab0 --- /dev/null +++ b/src/main/java/ecdar/presentations/EditorPresentation.java @@ -0,0 +1,201 @@ +package ecdar.presentations; + +import com.jfoenix.controls.JFXPopup; +import com.jfoenix.controls.JFXRippler; +import ecdar.controllers.EditorController; +import ecdar.utility.UndoRedoStack; +import ecdar.utility.colors.Color; +import ecdar.utility.colors.EnabledColor; +import ecdar.utility.helpers.SelectHelper; +import javafx.animation.FadeTransition; +import javafx.animation.ScaleTransition; +import javafx.collections.ListChangeListener; +import javafx.geometry.Insets; +import javafx.scene.control.Label; +import javafx.scene.control.Tooltip; +import javafx.scene.layout.*; +import javafx.scene.shape.Circle; +import javafx.util.Duration; +import javafx.util.Pair; + +import java.util.ArrayList; +import java.util.List; + +import static ecdar.utility.colors.EnabledColor.enabledColors; + +public class EditorPresentation extends VBox { + private final EditorController controller; + + public EditorPresentation() { + this.controller = new EcdarFXMLLoader().loadAndGetController("EditorPresentation.fxml", this); + initializeToolbar(); + initializeColorSelector(); + + initializeSelectDependentToolbarButton(controller.colorSelected); + Tooltip.install(controller.colorSelected, new Tooltip("Colour")); + + initializeSelectDependentToolbarButton(controller.deleteSelected); + Tooltip.install(controller.deleteSelected, new Tooltip("Delete")); + + initializeToolbarButton(controller.undo); + initializeToolbarButton(controller.redo); + initializeUndoRedoButtons(); + } + + public EditorController getController() { + return controller; + } + + private void initializeSelectDependentToolbarButton(final JFXRippler button) { + initializeToolbarButton(button); + + // The color button should only be enabled when an element is selected + SelectHelper.getSelectedElements().addListener(new ListChangeListener() { + @Override + public void onChanged(final Change c) { + if (SelectHelper.getSelectedElements().size() > 0) { + button.setEnabled(true); + + final FadeTransition fadeAnimation = new FadeTransition(Duration.millis(100), button); + fadeAnimation.setFromValue(button.getOpacity()); + fadeAnimation.setToValue(1); + fadeAnimation.play(); + } else { + button.setEnabled(false); + + final FadeTransition fadeAnimation = new FadeTransition(Duration.millis(100), button); + fadeAnimation.setFromValue(1); + fadeAnimation.setToValue(0.3); + fadeAnimation.play(); + } + } + }); + + // Disable the button + button.setEnabled(false); + button.setOpacity(0.3); + } + + private void initializeToolbarButton(final JFXRippler button) { + final Color color = Color.GREY_BLUE; + final Color.Intensity colorIntensity = Color.Intensity.I800; + + button.setMaskType(JFXRippler.RipplerMask.CIRCLE); + button.setRipplerFill(color.getTextColor(colorIntensity)); + button.setPosition(JFXRippler.RipplerPos.BACK); + } + + private void initializeUndoRedoButtons() { + UndoRedoStack.canUndoProperty().addListener((obs, oldState, newState) -> { + if (newState) { + // Enable the undo button + controller.undo.setEnabled(true); + controller.undo.setOpacity(1); + } else { + // Disable the undo button + controller.undo.setEnabled(false); + controller.undo.setOpacity(0.3); + } + }); + + UndoRedoStack.canRedoProperty().addListener((obs, oldState, newState) -> { + if (newState) { + // Enable the redo button + controller.redo.setEnabled(true); + controller.redo.setOpacity(1); + } else { + // Disable the redo button + controller.redo.setEnabled(false); + controller.redo.setOpacity(0.3); + } + }); + + // Disable the undo button + controller.undo.setEnabled(false); + controller.undo.setOpacity(0.3); + + // Disable the redo button + controller.redo.setEnabled(false); + controller.redo.setOpacity(0.3); + + // Set tooltips + Tooltip.install(controller.undo, new Tooltip("Undo")); + Tooltip.install(controller.redo, new Tooltip("Redo")); + } + + private void initializeColorSelector() { + final JFXPopup popup = new JFXPopup(); + + final double listWidth = 136; + final FlowPane list = new FlowPane(); + for (final EnabledColor color : enabledColors) { + final Circle circle = new Circle(16, color.color.getColor(color.intensity)); + circle.setStroke(color.color.getColor(color.intensity.next(2))); + circle.setStrokeWidth(1); + + final Label label = new Label(color.keyCode.getName()); + label.getStyleClass().add("subhead"); + label.setTextFill(color.color.getTextColor(color.intensity)); + + final StackPane child = new StackPane(circle, label); + child.setMinSize(40, 40); + child.setMaxSize(40, 40); + + child.setOnMouseEntered(event -> { + final ScaleTransition scaleTransition = new ScaleTransition(Duration.millis(100), circle); + scaleTransition.setFromX(circle.getScaleX()); + scaleTransition.setFromY(circle.getScaleY()); + scaleTransition.setToX(1.1); + scaleTransition.setToY(1.1); + scaleTransition.play(); + }); + + child.setOnMouseExited(event -> { + final ScaleTransition scaleTransition = new ScaleTransition(Duration.millis(100), circle); + scaleTransition.setFromX(circle.getScaleX()); + scaleTransition.setFromY(circle.getScaleY()); + scaleTransition.setToX(1.0); + scaleTransition.setToY(1.0); + scaleTransition.play(); + }); + + child.setOnMouseClicked(event -> { + final List> previousColor = new ArrayList<>(); + + SelectHelper.getSelectedElements().forEach(selectable -> { + previousColor.add(new Pair<>(selectable, new EnabledColor(selectable.getColor(), selectable.getColorIntensity()))); + }); + + controller.changeColorOnSelectedElements(color, previousColor); + + popup.hide(); + SelectHelper.clearSelectedElements(); + }); + + list.getChildren().add(child); + } + list.setMinWidth(listWidth); + list.setMaxWidth(listWidth); + list.setStyle("-fx-background-color: white; -fx-padding: 8;"); + + popup.setPopupContent(list); + + controller.colorSelected.setOnMouseClicked((e) -> { + // If nothing is selected + if (SelectHelper.getSelectedElements().size() == 0) return; + popup.show(controller.colorSelected, JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.RIGHT, -10, 15); + }); + } + + private void initializeToolbar() { + final Color color = Color.GREY_BLUE; + final Color.Intensity intensity = Color.Intensity.I700; + + // Set the background for the top toolbar + controller.toolbar.setBackground( + new Background(new BackgroundFill(color.getColor(intensity), + CornerRadii.EMPTY, + Insets.EMPTY) + )); + } +} diff --git a/src/main/java/ecdar/presentations/FilePresentation.java b/src/main/java/ecdar/presentations/FilePresentation.java index 5f1d1251..700b61b8 100644 --- a/src/main/java/ecdar/presentations/FilePresentation.java +++ b/src/main/java/ecdar/presentations/FilePresentation.java @@ -132,7 +132,7 @@ private void initializeColors() { private ArrayList getActiveComponents() { ArrayList activeComponents = new ArrayList<>(); - Node canvasPaneFirstChild = Ecdar.getPresentation().getController().canvasPane.getChildren().get(0); + Node canvasPaneFirstChild = Ecdar.getPresentation().getController().getEditorPresentation().getController().canvasPane.getChildren().get(0); if(canvasPaneFirstChild instanceof GridPane) { for (Node child : ((GridPane) canvasPaneFirstChild).getChildren()) { activeComponents.add(((CanvasPresentation) child).getController().getActiveModel()); diff --git a/src/main/java/ecdar/presentations/LeftSimPanePresentation.java b/src/main/java/ecdar/presentations/LeftSimPanePresentation.java new file mode 100755 index 00000000..adc693ed --- /dev/null +++ b/src/main/java/ecdar/presentations/LeftSimPanePresentation.java @@ -0,0 +1,37 @@ +package ecdar.presentations; + +import ecdar.controllers.LeftSimPaneController; +import ecdar.utility.colors.Color; +import javafx.geometry.Insets; +import javafx.scene.layout.*; + +public class LeftSimPanePresentation extends StackPane { + private LeftSimPaneController controller; + + public LeftSimPanePresentation() { + controller = new EcdarFXMLLoader().loadAndGetController("LeftSimPanePresentation.fxml", this); + + initializeBackground(); + initializeRightBorder(); + } + + private void initializeBackground() { + controller.scrollPaneVbox.setBackground(new Background(new BackgroundFill( + Color.GREY.getColor(Color.Intensity.I200), + CornerRadii.EMPTY, + Insets.EMPTY + ))); + } + + /** + * Initializes the thin border on the right side of the transition toolbar + */ + private void initializeRightBorder() { + controller.transitionPanePresentation.getController().toolbar.setBorder(new Border(new BorderStroke( + Color.GREY_BLUE.getColor(Color.Intensity.I900), + BorderStrokeStyle.SOLID, + CornerRadii.EMPTY, + new BorderWidths(0, 1, 0, 0) + ))); + } +} diff --git a/src/main/java/ecdar/presentations/ProcessPresentation.java b/src/main/java/ecdar/presentations/ProcessPresentation.java new file mode 100755 index 00000000..51cebab0 --- /dev/null +++ b/src/main/java/ecdar/presentations/ProcessPresentation.java @@ -0,0 +1,282 @@ +package ecdar.presentations; + +import ecdar.abstractions.Component; +import ecdar.abstractions.Edge; +import ecdar.abstractions.Location; +import ecdar.abstractions.Nail; +import ecdar.controllers.ModelController; +import ecdar.controllers.ProcessController; +import ecdar.utility.colors.Color; +import javafx.beans.InvalidationListener; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.Cursor; +import javafx.scene.Node; +import javafx.scene.control.Label; +import javafx.scene.layout.*; +import javafx.scene.paint.Paint; +import javafx.scene.shape.Path; +import javafx.scene.shape.Rectangle; +import javafx.scene.shape.Shape; +import javafx.scene.text.Font; + +import java.math.BigDecimal; +import java.util.List; +import java.util.function.BiConsumer; + +import static ecdar.presentations.Grid.GRID_SIZE; + +/** + * The presenter of a Process which is shown in {@link SimulatorOverviewPresentation}.
+ * This class have some of the same functionality as {@link ComponentPresentation} and could be refactored + * into a base class. + */ +public class ProcessPresentation extends ModelPresentation { + private final ProcessController controller; + + /** + * Constructs a Process ready to go to the view. + * @param component the component which the process should look like + */ + public ProcessPresentation(final Component component){ + controller = new EcdarFXMLLoader().loadAndGetController("ProcessPresentation.fxml", this); + controller.setComponent(component); + super.initialize(component.getBox()); + // Initialize methods that is sensitive to width and height + final Runnable onUpdateSize = () -> { + initializeToolbar(); + initializeFrame(); + initializeBackground(); + }; + + onUpdateSize.run(); + + // Re run initialisation on update of width and height property + component.getBox().getWidthProperty().addListener(observable -> onUpdateSize.run()); + component.getBox().getHeightProperty().addListener(observable -> onUpdateSize.run()); + setValueAreaStyle(); + setToggleValueButtonStyle(); + + controller.getClocks().forEach(this::addValueToValueArea); + controller.getVariables().forEach(this::addValueToValueArea); + + controller.getClocks().addListener((InvalidationListener) obs -> { + controller.getClocks().forEach((s, bigDecimal) -> { + final List filteredList = controller.valueArea.getChildren().filtered(node -> { + if (!(node instanceof Label))// we currently only want to look at labels + return false; + final String[] splitString = ((Label) node).getText().split("="); + return splitString[0].trim().equals(s); + }); + controller.valueArea.getChildren().removeAll(filteredList); + addValueToValueArea(s, bigDecimal); + }); + }); + controller.getVariables().addListener((InvalidationListener) obs -> { + controller.getVariables().forEach((s, bigDecimal) -> { + final List filteredList = controller.valueArea.getChildren().filtered(node -> { + if (!(node instanceof Label))// we currently only want to look at labels + return false; + final String[] splitString = ((Label) node).getText().split("="); + return splitString[0].trim().equals(s); + }); + controller.valueArea.getChildren().removeAll(filteredList); + addValueToValueArea(s, bigDecimal); + }); + }); + } + + /** + * Sets the Icon and Icon size of the {@link ProcessController#toggleValueButtonIcon} + */ + private void setToggleValueButtonStyle() { + controller.toggleValueButtonIcon.setIconLiteral("gmi-code"); + controller.toggleValueButtonIcon.setIconSize(17); + } + + /** + * Set the style needed for the {@link ProcessController#valueArea}. + * This include padding and background. + */ + private void setValueAreaStyle() { + // As for some reason the styling fail to be applied we need to set the styling again here + controller.valueArea.setPadding(new Insets(16, 4, 16, 4)); + controller.valueArea.setBackground(new Background( + new BackgroundFill(Paint.valueOf("rgba(242, 243, 244, 0.85)"),CornerRadii.EMPTY, Insets.EMPTY))); + } + + private void addValueToValueArea(final String s, final BigDecimal bigDecimal) { + final Label valueLabel = new Label(); + valueLabel.setText(s + " = " + bigDecimal); + // As the styling sometimes are gone missing + valueLabel.setFont(Font.font("Roboto Mono Medium",13)); + controller.valueArea.getChildren().add(valueLabel); + } + + /** + * Initializes the frame around the body of the process. + * It also updates the color if the color is changed + */ + private void initializeFrame() { + final Component component = controller.getComponent(); + + final Shape[] mask = new Shape[1]; + final Rectangle rectangle = new Rectangle(component.getBox().getWidth(), component.getBox().getHeight()); + + final BiConsumer updateColor = (newColor, newIntensity) -> { + // Mask the parent of the frame (will also mask the background) + mask[0] = Path.subtract(rectangle, TOP_LEFT_CORNER); + controller.frame.setClip(mask[0]); + controller.background.setClip(Path.union(mask[0], mask[0])); + controller.background.setOpacity(0.5); + + // Bind the missing lines that we cropped away + controller.topLeftLine.setStartX(Grid.CORNER_SIZE); + controller.topLeftLine.setStartY(0); + controller.topLeftLine.setEndX(0); + controller.topLeftLine.setEndY(Grid.CORNER_SIZE); + controller.topLeftLine.setStroke(newColor.getColor(newIntensity.next(2))); + controller.topLeftLine.setStrokeWidth(1.25); + StackPane.setAlignment(controller.topLeftLine, Pos.TOP_LEFT); + + // Set the stroke color to two shades darker + controller.frame.setBorder(new Border(new BorderStroke( + newColor.getColor(newIntensity.next(2)), + BorderStrokeStyle.SOLID, + CornerRadii.EMPTY, + new BorderWidths(1), + Insets.EMPTY + ))); + }; + component.colorProperty().addListener(observable -> { + updateColor.accept(component.getColor(), component.getColorIntensity()); + }); + updateColor.accept(component.getColor(), component.getColorIntensity()); + } + + /** + * Initializes the background of the process with the right color + * If the color is changed it will also update it self + */ + private void initializeBackground() { + final Component component = controller.getComponent(); + + // Bind the background width and height to the values in the model + controller.background.widthProperty().bind(component.getBox().getWidthProperty()); + controller.background.heightProperty().bind(component.getBox().getHeightProperty()); + controller.background.setFill(component.getColor().getColor(component.getColorIntensity().next(-10).next(2))); + final BiConsumer updateColor = (newColor, newIntensity) -> { + // Set the background color to the lightest possible version of the color + controller.background.setFill(newColor.getColor(newIntensity.next(-10).next(2))); + }; + component.colorProperty().addListener(observable -> { + updateColor.accept(component.getColor(), component.getColorIntensity()); + }); + + updateColor.accept(component.getColor(), component.getColorIntensity()); + } + + /** + * Initialize the Toolbar of the process.
+ * The toolbar is where the {@link ProcessController#name} is placed. + */ + private void initializeToolbar() { + final Component component = controller.getComponent(); + + final BiConsumer updateColor = (newColor, newIntensity) -> { + // Set the background of the toolbar + controller.toolbar.setBackground(new Background(new BackgroundFill( + newColor.getColor(newIntensity), + CornerRadii.EMPTY, + Insets.EMPTY + ))); + + // Set the icon color and rippler color of the toggleDeclarationButton + controller.toggleValuesButton.setRipplerFill(newColor.getTextColor(newIntensity)); + + controller.toolbar.setPrefHeight(Grid.TOOL_BAR_HEIGHT); + controller.toggleValuesButton.setBackground(Background.EMPTY); + }; + controller.getComponent().colorProperty().addListener(observable -> updateColor.accept(component.getColor(), component.getColorIntensity())); + + updateColor.accept(component.getColor(), component.getColorIntensity()); + + // Set a hover effect for the controller.toggleDeclarationButton + controller.toggleValuesButton.setOnMouseEntered(event -> controller.toggleValuesButton.setCursor(Cursor.HAND)); + controller.toggleValuesButton.setOnMouseExited(event -> controller.toggleValuesButton.setCursor(Cursor.DEFAULT)); + } + + /** + * Fades the process. + * Used if it is not involved in a transition + */ + public void showInactive() { + setOpacity(0.5); + } + + /** + * Show the process as active. + * Used to reset the effect of {@link ProcessPresentation#showInactive()} + */ + public void showActive() { + setOpacity(1.0); + } + + @Override + ModelController getModelController() { + return controller; + } + + /** + * Gets the minimum possible width when dragging the anchor. + * The width is based on the x coordinate of locations, nails and the signature arrows.
+ * This should be removed from {@link ModelPresentation} and made into an interface of its own + * @return the minimum possible width. + */ + @Override + @Deprecated + double getDragAnchorMinWidth() { + final Component component = controller.getComponent(); + double minWidth = 10 * GRID_SIZE; + + for (final Location location : component.getLocations()) { + minWidth = Math.max(minWidth, location.getX() + GRID_SIZE * 2); + } + + for (final Edge edge : component.getEdges()) { + for (final Nail nail : edge.getNails()) { + minWidth = Math.max(minWidth, nail.getX() + GRID_SIZE); + } + } + return minWidth; + } + + /** + * Gets the minimum possible height when dragging the anchor. + * The height is based on the y coordinate of locations, nails and the signature arrows
+ * This should be removed from {@link ModelPresentation} and made into an interface of its own + * @return the minimum possible height. + */ + @Override + @Deprecated + double getDragAnchorMinHeight() { + final Component component = controller.getComponent(); + double minHeight = 10 * GRID_SIZE; + + for (final Location location : component.getLocations()) { + minHeight = Math.max(minHeight, location.getY() + GRID_SIZE * 2); + } + + for (final Edge edge : component.getEdges()) { + for (final Nail nail : edge.getNails()) { + minHeight = Math.max(minHeight, nail.getY() + GRID_SIZE); + } + } + + return minHeight; + } + + public ProcessController getController() { + return controller; + } +} diff --git a/src/main/java/ecdar/presentations/RightSimPanePresentation.java b/src/main/java/ecdar/presentations/RightSimPanePresentation.java new file mode 100755 index 00000000..48c79f1c --- /dev/null +++ b/src/main/java/ecdar/presentations/RightSimPanePresentation.java @@ -0,0 +1,45 @@ +package ecdar.presentations; + +import ecdar.controllers.RightSimPaneController; +import ecdar.utility.colors.Color; +import ecdar.utility.helpers.DropShadowHelper; +import javafx.geometry.Insets; +import javafx.scene.layout.*; + +/** + * Presentation class for the right pane in the simulator + */ +public class RightSimPanePresentation extends StackPane { + private RightSimPaneController controller; + + public RightSimPanePresentation() { + controller = new EcdarFXMLLoader().loadAndGetController("RightSimPanePresentation.fxml", this); + + initializeBackground(); + initializeLeftBorder(); + } + + /** + * Sets the background color of the ScrollPane Vbox + */ + private void initializeBackground() { + controller.scrollPaneVbox.setBackground(new Background(new BackgroundFill( + Color.GREY.getColor(Color.Intensity.I200), + CornerRadii.EMPTY, + Insets.EMPTY + ))); + } + + /** + * Initializes the thin border on the left side of the querypane toolbar + */ + private void initializeLeftBorder() { + setBorder(new Border(new BorderStroke( + Color.GREY_BLUE.getColor(Color.Intensity.I900), + BorderStrokeStyle.SOLID, + CornerRadii.EMPTY, + new BorderWidths(0, 0, 0, 1) + ))); + } + +} diff --git a/src/main/java/ecdar/presentations/SimEdgePresentation.java b/src/main/java/ecdar/presentations/SimEdgePresentation.java new file mode 100755 index 00000000..8d74623b --- /dev/null +++ b/src/main/java/ecdar/presentations/SimEdgePresentation.java @@ -0,0 +1,32 @@ +package ecdar.presentations; + +import ecdar.abstractions.Component; +import ecdar.abstractions.Edge; +import ecdar.controllers.SimEdgeController; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.scene.Group; + +/** + * The presentation class for the edges shown in the {@link SimulatorOverviewPresentation} + */ +public class SimEdgePresentation extends Group { + private final SimEdgeController controller; + + private final ObjectProperty edge = new SimpleObjectProperty<>(); + private final ObjectProperty component = new SimpleObjectProperty<>(); + + public SimEdgePresentation(final Edge edge, final Component component) { + controller = new EcdarFXMLLoader().loadAndGetController("SimEdgePresentation.fxml", this); + + controller.setEdge(edge); + this.edge.bind(controller.edgeProperty()); + + controller.setComponent(component); + this.component.bind(controller.componentProperty()); + } + + public SimEdgeController getController() { + return controller; + } +} diff --git a/src/main/java/ecdar/presentations/SimLocationPresentation.java b/src/main/java/ecdar/presentations/SimLocationPresentation.java new file mode 100755 index 00000000..e250433a --- /dev/null +++ b/src/main/java/ecdar/presentations/SimLocationPresentation.java @@ -0,0 +1,492 @@ +package ecdar.presentations; + +import ecdar.abstractions.Component; +import ecdar.abstractions.Location; +import ecdar.controllers.SimLocationController; +import ecdar.utility.Highlightable; +import ecdar.utility.colors.Color; +import ecdar.utility.helpers.BindingHelper; +import ecdar.utility.helpers.SelectHelper; +import javafx.animation.*; +import javafx.beans.binding.DoubleBinding; +import javafx.beans.property.DoubleProperty; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleDoubleProperty; +import javafx.scene.Group; +import javafx.scene.control.Label; +import javafx.scene.effect.DropShadow; +import javafx.scene.paint.Paint; +import javafx.scene.shape.*; +import javafx.util.Duration; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.BiConsumer; +import java.util.function.Consumer; + +import static ecdar.presentations.LocationPresentation.INITIAL_RADIUS; +import static ecdar.presentations.LocationPresentation.RADIUS; + +/** + * Presentation for a location in the {@link SimulatorOverviewPresentation}. + * This class should be refactored such that the shared code between this class + * and {@link LocationPresentation} is placed in a base class. + */ +public class SimLocationPresentation extends Group implements Highlightable { + + private static int id = 0; + private final SimLocationController controller; + private final Timeline initialAnimation = new Timeline(); + private final Timeline hoverAnimationEntered = new Timeline(); + private final Timeline hoverAnimationExited = new Timeline(); + private final Timeline hiddenAreaAnimationEntered = new Timeline(); + private final Timeline hiddenAreaAnimationExited = new Timeline(); + private final Timeline scaleShakeIndicatorBackgroundAnimation = new Timeline(); + private final Timeline shakeContentAnimation = new Timeline(); + private final List> updateColorDelegates = new ArrayList<>(); + private final DoubleProperty animation = new SimpleDoubleProperty(0); + private final DoubleBinding reverseAnimation = new SimpleDoubleProperty(1).subtract(animation); + + /** + * Constructs a Simulator Location ready to be placed on the view + * @param location the location model, which the presenter should show + * @param component the component where the location is + */ + public SimLocationPresentation(final Location location, final Component component) { + controller = new EcdarFXMLLoader().loadAndGetController("SimLocationPresentation.fxml", this); + + // Bind the component with the one of the controller + controller.setComponent(component); + + // Bind the location with the one of the controller + controller.setLocation(location); + + initializeIdLabel(); + initializeTypeGraphics(); + initializeLocationShapes(); + initializeTags(); + initializeShakeAnimation(); + initializeCircle(); + } + + /** + * Initializes the label in the middle of the location + */ + private void initializeIdLabel() { + final Location location = controller.getLocation(); + final Label idLabel = controller.idLabel; + + final DropShadow ds = new DropShadow(); + ds.setRadius(2); + ds.setSpread(1); + + idLabel.setEffect(ds); + + idLabel.textProperty().bind((location.idProperty())); + + // Center align the label + idLabel.widthProperty().addListener((obsWidth, oldWidth, newWidth) -> idLabel.translateXProperty().set(newWidth.doubleValue() / -2)); + idLabel.heightProperty().addListener((obsHeight, oldHeight, newHeight) -> idLabel.translateYProperty().set(newHeight.doubleValue() / -2)); + + final ObjectProperty color = location.colorProperty(); + final ObjectProperty colorIntensity = location.colorIntensityProperty(); + + // Delegate to style the label based on the color of the location + final BiConsumer updateColor = (newColor, newIntensity) -> { + idLabel.setTextFill(newColor.getTextColor(newIntensity)); + ds.setColor(newColor.getColor(newIntensity)); + }; + + updateColorDelegates.add(updateColor); + + // Set the initial color + updateColor.accept(color.get(), colorIntensity.get()); + + // Update the color of the circle when the color of the location is updated + color.addListener((obs, old, newColor) -> updateColor.accept(newColor, colorIntensity.get())); + } + + /** + * Initialize the tags placed on this location + */ + private void initializeTags() { + + // Set the layout from the model (if they are not both 0) + final Location loc = controller.getLocation(); + if((loc.getNicknameX() != 0) && (loc.getNicknameY() != 0)) { + controller.nicknameTag.setTranslateX(loc.getNicknameX()); + controller.nicknameTag.setTranslateY(loc.getNicknameY()); + } + + if((loc.getInvariantX() != 0) && (loc.getInvariantY() != 0)) { + controller.invariantTag.setTranslateX(loc.getInvariantX()); + controller.invariantTag.setTranslateY(loc.getInvariantY()); + } + + // Bind the model to the layout + loc.nicknameXProperty().bind(controller.nicknameTag.translateXProperty()); + loc.nicknameYProperty().bind(controller.nicknameTag.translateYProperty()); + loc.invariantXProperty().bind(controller.invariantTag.translateXProperty()); + loc.invariantYProperty().bind(controller.invariantTag.translateYProperty()); + + final Consumer updateTags = location -> { + // Update the color + controller.nicknameTag.bindToColor(location.colorProperty(), location.colorIntensityProperty(), true); + controller.invariantTag.bindToColor(location.colorProperty(), location.colorIntensityProperty(), false); + + // Update the invariant + controller.nicknameTag.setAndBindString(location.nicknameProperty()); + controller.invariantTag.setAndBindString(location.invariantProperty()); + + // Set the visibility of the name tag depending on the nickname + final Consumer updateVisibilityFromNickName = (nickname) -> { + if (nickname.equals("")) { + controller.nicknameTag.setOpacity(0); + } else { + controller.nicknameTag.setOpacity(1); + } + }; + + location.nicknameProperty().addListener((obs, oldNickname, newNickname) -> updateVisibilityFromNickName.accept(newNickname)); + updateVisibilityFromNickName.accept(location.getNickname()); + + // Set the visibility of the invariant tag depending on the invariant + final Consumer updateVisibilityFromInvariant = (invariant) -> { + if (invariant.equals("") ) { + controller.invariantTag.setOpacity(0); + } else { + controller.invariantTag.setOpacity(1); + } + }; + + location.invariantProperty().addListener((obs, oldInvariant, newInvariant) -> updateVisibilityFromInvariant.accept(newInvariant)); + updateVisibilityFromInvariant.accept(location.getInvariant()); + + controller.nicknameTag.setComponent(controller.getComponent()); + controller.nicknameTag.setLocationAware(location); + BindingHelper.bind(controller.nameTagLine, controller.nicknameTag); + + controller.invariantTag.setComponent(controller.getComponent()); + controller.invariantTag.setLocationAware(location); + BindingHelper.bind(controller.invariantTagLine, controller.invariantTag); + }; + + // Update the tags when the loc updates + controller.locationProperty().addListener(observable -> updateTags.accept(loc)); + + // Initialize the tags from the current loc + updateTags.accept(loc); + } + + /** + * Initialize the circle which makes up most of the location + */ + private void initializeCircle() { + final Location location = controller.getLocation(); + + final Circle circle = controller.circle; + circle.setRadius(RADIUS); + final ObjectProperty color = location.colorProperty(); + final ObjectProperty colorIntensity = location.colorIntensityProperty(); + + // Delegate to style the label based on the color of the location + final BiConsumer updateColor = (newColor, newIntensity) -> { + circle.setFill(newColor.getColor(newIntensity)); + circle.setStroke(newColor.getColor(newIntensity.next(2))); + }; + + updateColorDelegates.add(updateColor); + + // Set the initial color + updateColor.accept(color.get(), colorIntensity.get()); + + // Update the color of the circle when the color of the location is updated + color.addListener((obs, old, newColor) -> updateColor.accept(newColor, colorIntensity.get())); + colorIntensity.addListener((obs, old, newIntensity) -> updateColor.accept(color.get(), newIntensity)); + } + + /** + * Initializes the shapes of a urgent location + */ + private void initializeLocationShapes() { + final Path notCommittedShape = controller.notCommittedShape; + + // Bind sizes for shape that transforms between urgent and normal + initializeLocationShapes(notCommittedShape, RADIUS); + + final Location location = controller.getLocation(); + + final BiConsumer updateUrgencies = (oldUrgency, newUrgency) -> { + final Transition toUrgent = new Transition() { + { + setCycleDuration(Duration.millis(200)); + } + + @Override + protected void interpolate(final double frac) { + animation.set(frac); + } + }; + + final Transition toNormal = new Transition() { + { + setCycleDuration(Duration.millis(200)); + } + + @Override + protected void interpolate(final double frac) { + animation.set(1-frac); + } + }; + + if(oldUrgency.equals(Location.Urgency.NORMAL) && !newUrgency.equals(Location.Urgency.NORMAL)) { + toUrgent.play(); + } else if(newUrgency.equals(Location.Urgency.NORMAL)) { + toNormal.play(); + } + notCommittedShape.setVisible(true); + }; + + location.urgencyProperty().addListener((obsUrgency, oldUrgency, newUrgency) -> { + updateUrgencies.accept(oldUrgency, newUrgency); + }); + + updateUrgencies.accept(Location.Urgency.NORMAL, location.getUrgency()); + + // Update the colors + final ObjectProperty color = location.colorProperty(); + final ObjectProperty colorIntensity = location.colorIntensityProperty(); + + // Delegate to style the label based on the color of the location + final BiConsumer updateColor = (newColor, newIntensity) -> { + notCommittedShape.setFill(newColor.getColor(newIntensity)); + notCommittedShape.setStroke(newColor.getColor(newIntensity.next(2))); + }; + + updateColorDelegates.add(updateColor); + + // Set the initial color + updateColor.accept(color.get(), colorIntensity.get()); + + // Update the color of the circle when the color of the location is updated + color.addListener((obs, old, newColor) -> updateColor.accept(newColor, colorIntensity.get())); + } + + private void initializeTypeGraphics() { + final Location location = controller.getLocation(); + + final Path notCommittedInitialIndicator = controller.notCommittedInitialIndicator; + + // Bind visibility and size of normal shape + initializeLocationShapes(notCommittedInitialIndicator, INITIAL_RADIUS); + notCommittedInitialIndicator.visibleProperty().bind(location.typeProperty().isEqualTo(Location.Type.INITIAL)); + // As the style sometimes "forget" its values + notCommittedInitialIndicator.setStrokeType(StrokeType.INSIDE); + notCommittedInitialIndicator.setStroke(Paint.valueOf("white")); + notCommittedInitialIndicator.setFill(Paint.valueOf("transparent")); + } + + public void animateIn() { + initialAnimation.play(); + } + + public void animateHoverEntered() { + + if (shakeContentAnimation.getStatus().equals(Animation.Status.RUNNING)) return; + + hoverAnimationEntered.play(); + } + + public void animateHoverExited() { + if (shakeContentAnimation.getStatus().equals(Animation.Status.RUNNING)) return; + + hoverAnimationExited.play(); + } + + public void animateLocationEntered() { + hiddenAreaAnimationExited.stop(); + hiddenAreaAnimationEntered.play(); + } + + public void animateLocationExited() { + hiddenAreaAnimationEntered.stop(); + hiddenAreaAnimationExited.play(); + } + + + /** + * Initializes the animation of shaking the location. Can for instance be used when the user tries an + * action which is not allowed, i.e. deleting + */ + private void initializeShakeAnimation() { + final Interpolator interpolator = Interpolator.SPLINE(0.645, 0.045, 0.355, 1); + + final KeyValue scale0x = new KeyValue(controller.scaleContent.scaleXProperty(), 1, interpolator); + final KeyValue radius0 = new KeyValue(controller.circleShakeIndicator.radiusProperty(), 0, interpolator); + final KeyValue opacity0 = new KeyValue(controller.circleShakeIndicator.opacityProperty(), 0, interpolator); + + final KeyValue scale1x = new KeyValue(controller.scaleContent.scaleXProperty(), 1.3, interpolator); + final KeyValue radius1 = new KeyValue(controller.circleShakeIndicator.radiusProperty(), controller.circle.getRadius() * 0.85, interpolator); + final KeyValue opacity1 = new KeyValue(controller.circleShakeIndicator.opacityProperty(), 0.2, interpolator); + + final KeyFrame kf1 = new KeyFrame(Duration.millis(0), scale0x, radius0, opacity0); + final KeyFrame kf2 = new KeyFrame(Duration.millis(2500), scale1x, radius1, opacity1); + final KeyFrame kf3 = new KeyFrame(Duration.millis(3300), radius0, opacity0); + final KeyFrame kf4 = new KeyFrame(Duration.millis(3500), scale0x); + final KeyFrame kfEnd = new KeyFrame(Duration.millis(8000)); + + scaleShakeIndicatorBackgroundAnimation.getKeyFrames().addAll(kf1, kf2, kf3, kf4, kfEnd); + + final KeyValue noShakeX = new KeyValue(controller.shakeContent.translateXProperty(), 0, interpolator); + final KeyValue shakeLeftX = new KeyValue(controller.shakeContent.translateXProperty(), -1, interpolator); + final KeyValue shakeRightX = new KeyValue(controller.shakeContent.translateXProperty(), 1, interpolator); + + final KeyFrame[] shakeFrames = { + new KeyFrame(Duration.millis(0), noShakeX), + new KeyFrame(Duration.millis(1450), noShakeX), + + new KeyFrame(Duration.millis(1500), shakeLeftX), + new KeyFrame(Duration.millis(1550), shakeRightX), + new KeyFrame(Duration.millis(1600), shakeLeftX), + new KeyFrame(Duration.millis(1650), shakeRightX), + new KeyFrame(Duration.millis(1700), shakeLeftX), + new KeyFrame(Duration.millis(1750), shakeRightX), + new KeyFrame(Duration.millis(1800), shakeLeftX), + new KeyFrame(Duration.millis(1850), shakeRightX), + new KeyFrame(Duration.millis(1900), shakeLeftX), + new KeyFrame(Duration.millis(1950), shakeRightX), + new KeyFrame(Duration.millis(2000), shakeLeftX), + new KeyFrame(Duration.millis(2050), shakeRightX), + new KeyFrame(Duration.millis(2100), shakeLeftX), + new KeyFrame(Duration.millis(2150), shakeRightX), + new KeyFrame(Duration.millis(2200), shakeLeftX), + new KeyFrame(Duration.millis(2250), shakeRightX), + + new KeyFrame(Duration.millis(2300), noShakeX), + new KeyFrame(Duration.millis(8000)) + }; + + shakeContentAnimation.getKeyFrames().addAll(shakeFrames); + + shakeContentAnimation.setCycleCount(1000); + scaleShakeIndicatorBackgroundAnimation.setCycleCount(1000); + } + + /** + * Plays the shake animation + * @param start false - the animation resets to the beginning
+ * true - the animation starts + */ + public void animateShakeWarning(final boolean start) { + if (start) { + scaleShakeIndicatorBackgroundAnimation.play(); + shakeContentAnimation.play(); + } else { + controller.scaleContent.scaleXProperty().set(1); + scaleShakeIndicatorBackgroundAnimation.playFromStart(); + scaleShakeIndicatorBackgroundAnimation.stop(); + + controller.circleShakeIndicator.setOpacity(0); + shakeContentAnimation.playFromStart(); + shakeContentAnimation.stop(); + } + } + + /** + * Get the controller associated with this presenter + * @return the controller + */ + public SimLocationController getController() { + return controller; + } + + private void initializeLocationShapes(final Path locationShape, final double radius) { + final double c = 0.551915024494; + final double circleToOctagonLineRatio = 0.35; + + final MoveTo moveTo = new MoveTo(); + moveTo.xProperty().bind(animation.multiply(circleToOctagonLineRatio * radius)); + moveTo.yProperty().set(radius); + + final CubicCurveTo cc1 = new CubicCurveTo(); + cc1.controlX1Property().bind(reverseAnimation.multiply(c * radius).add(animation.multiply(circleToOctagonLineRatio * radius))); + cc1.controlY1Property().bind(reverseAnimation.multiply(radius).add(animation.multiply(radius))); + cc1.controlX2Property().bind(reverseAnimation.multiply(radius).add(animation.multiply(radius))); + cc1.controlY2Property().bind(reverseAnimation.multiply(c * radius).add(animation.multiply(circleToOctagonLineRatio * radius))); + cc1.setX(radius); + cc1.yProperty().bind(animation.multiply(circleToOctagonLineRatio * radius)); + + + final LineTo lineTo1 = new LineTo(); + lineTo1.xProperty().bind(cc1.xProperty()); + lineTo1.yProperty().bind(cc1.yProperty().multiply(-1)); + + final CubicCurveTo cc2 = new CubicCurveTo(); + cc2.controlX1Property().bind(cc1.controlX2Property()); + cc2.controlY1Property().bind(cc1.controlY2Property().multiply(-1)); + cc2.controlX2Property().bind(cc1.controlX1Property()); + cc2.controlY2Property().bind(cc1.controlY1Property().multiply(-1)); + cc2.xProperty().bind(moveTo.xProperty()); + cc2.yProperty().bind(moveTo.yProperty().multiply(-1)); + + + final LineTo lineTo2 = new LineTo(); + lineTo2.xProperty().bind(cc2.xProperty().multiply(-1)); + lineTo2.yProperty().bind(cc2.yProperty()); + + final CubicCurveTo cc3 = new CubicCurveTo(); + cc3.controlX1Property().bind(cc2.controlX2Property().multiply(-1)); + cc3.controlY1Property().bind(cc2.controlY2Property()); + cc3.controlX2Property().bind(cc2.controlX1Property().multiply(-1)); + cc3.controlY2Property().bind(cc2.controlY1Property()); + cc3.xProperty().bind(lineTo1.xProperty().multiply(-1)); + cc3.yProperty().bind(lineTo1.yProperty()); + + + final LineTo lineTo3 = new LineTo(); + lineTo3.xProperty().bind(cc3.xProperty()); + lineTo3.yProperty().bind(cc3.yProperty().multiply(-1)); + + final CubicCurveTo cc4 = new CubicCurveTo(); + cc4.controlX1Property().bind(cc3.controlX2Property()); + cc4.controlY1Property().bind(cc3.controlY2Property().multiply(-1)); + cc4.controlX2Property().bind(cc3.controlX1Property()); + cc4.controlY2Property().bind(cc3.controlY1Property().multiply(-1)); + cc4.xProperty().bind(lineTo2.xProperty()); + cc4.yProperty().bind(lineTo2.yProperty().multiply(-1)); + + + final LineTo lineTo4 = new LineTo(); + lineTo4.xProperty().bind(moveTo.xProperty()); + lineTo4.yProperty().bind(moveTo.yProperty()); + + + locationShape.getElements().add(moveTo); + locationShape.getElements().add(cc1); + + locationShape.getElements().add(lineTo1); + locationShape.getElements().add(cc2); + + locationShape.getElements().add(lineTo2); + locationShape.getElements().add(cc3); + + locationShape.getElements().add(lineTo3); + locationShape.getElements().add(cc4); + + locationShape.getElements().add(lineTo4); + } + + @Override + public void highlight() { + updateColorDelegates.forEach(colorConsumer -> colorConsumer.accept(SelectHelper.SELECT_COLOR, SelectHelper.SELECT_COLOR_INTENSITY_NORMAL)); + } + + @Override + public void unhighlight() { + updateColorDelegates.forEach(colorConsumer -> { + final Location location = controller.getLocation(); + + colorConsumer.accept(location.getColor(), location.getColorIntensity()); + }); + } +} \ No newline at end of file diff --git a/src/main/java/ecdar/presentations/SimNailPresentation.java b/src/main/java/ecdar/presentations/SimNailPresentation.java new file mode 100755 index 00000000..250f2f9d --- /dev/null +++ b/src/main/java/ecdar/presentations/SimNailPresentation.java @@ -0,0 +1,258 @@ +package ecdar.presentations; + +import ecdar.abstractions.Component; +import ecdar.abstractions.Edge; +import ecdar.abstractions.EdgeStatus; +import ecdar.abstractions.Nail; +import ecdar.controllers.SimEdgeController; +import ecdar.controllers.SimNailController; +import ecdar.utility.Highlightable; +import ecdar.utility.colors.Color; +import ecdar.utility.helpers.BindingHelper; +import javafx.animation.Interpolator; +import javafx.animation.KeyFrame; +import javafx.animation.KeyValue; +import javafx.animation.Timeline; +import javafx.scene.Group; +import javafx.scene.control.Label; +import javafx.scene.shape.Line; + +import java.util.function.Consumer; + +import static javafx.util.Duration.millis; + +/** + * The presentation for the nail shown on a {@link SimEdgePresentation} in the {@link SimulatorOverviewPresentation}
+ * This class should be refactored such that code which are duplicated from {@link NailPresentation} + * have its own base class. + */ +public class SimNailPresentation extends Group implements Highlightable { + + public static final double COLLAPSED_RADIUS = 2d; + public static final double HOVERED_RADIUS = 7d; + + private final SimNailController controller; + private final Timeline shakeAnimation = new Timeline(); + + /** + * Constructs a nail which is ready to be displayed in the simulator + * @param nail the nail which should be presented + * @param edge the edge where the nail belongs to + * @param component the component where the nail belongs + * @param simEdgeController the controller of the edge where the nail belongs to + */ + public SimNailPresentation(final Nail nail, final Edge edge, final Component component, final SimEdgeController simEdgeController) { + controller = new EcdarFXMLLoader().loadAndGetController("SimNailPresentation.fxml", this); + + // Bind the component with the one of the controller + controller.setComponent(component); + + // Bind the edge with the one of the controller + controller.setEdge(edge); + // Bind the nail with the one of the controller + controller.setNail(nail); + + controller.setEdgeController(simEdgeController); + + initializeNailCircleColor(); + initializePropertyTag(); + initializeRadius(); + initializeShakeAnimation(); + } + + /** + * Sets the radius of the nail + */ + private void initializeRadius() { + final Consumer radiusUpdater = (propertyType) -> { + if(!propertyType.equals(Edge.PropertyType.NONE)) { + controller.getNail().setRadius(SimNailPresentation.HOVERED_RADIUS); + } + }; + controller.getNail().propertyTypeProperty().addListener((observable, oldValue, newValue) -> { + radiusUpdater.accept(newValue); + }); + radiusUpdater.accept(controller.getNail().getPropertyType()); + } + + /** + * Initializes the text / PropertyTag shown on a {@link SimTagPresentation} on the nail. + */ + private void initializePropertyTag() { + final SimTagPresentation propertyTag = controller.propertyTag; + final Line propertyTagLine = controller.propertyTagLine; + propertyTag.setComponent(controller.getComponent()); + propertyTag.setLocationAware(controller.getNail()); + + // Bind the line to the tag + BindingHelper.bind(propertyTagLine, propertyTag); + + // Bind the color of the tag to the color of the component + propertyTag.bindToColor(controller.getComponent().colorProperty(), controller.getComponent().colorIntensityProperty()); + + // Updates visibility and placeholder of the tag depending on the type of nail + final Consumer updatePropertyType = (propertyType) -> { + + // If it is not a property nail hide the tag otherwise show it and write proper placeholder + if(propertyType.equals(Edge.PropertyType.NONE)) { + propertyTag.setVisible(false); + } else { + + // Show the property tag since the nail is a property nail + propertyTag.setVisible(true); + + // Set and bind the location of the property tag + if((controller.getNail().getPropertyX() != 0) && (controller.getNail().getPropertyY() != 0)) { + propertyTag.setTranslateX(controller.getNail().getPropertyX()); + propertyTag.setTranslateY(controller.getNail().getPropertyY()); + } + controller.getNail().propertyXProperty().bind(propertyTag.translateXProperty()); + controller.getNail().propertyYProperty().bind(propertyTag.translateYProperty()); + + final Label propertyLabel = controller.propertyLabel; + + if(propertyType.equals(Edge.PropertyType.SELECTION)) { + propertyLabel.setText(":"); + propertyLabel.setTranslateX(-3); + propertyLabel.setTranslateY(-8); + propertyTag.setAndBindString(controller.getEdge().selectProperty()); + } else if(propertyType.equals(Edge.PropertyType.GUARD)) { + propertyLabel.setText("<"); + propertyLabel.setTranslateX(-3); + propertyLabel.setTranslateY(-7); + propertyTag.setAndBindString(controller.getEdge().guardProperty()); + } else if(propertyType.equals(Edge.PropertyType.SYNCHRONIZATION)) { + updateSyncLabel(); + propertyLabel.setTranslateX(-3); + propertyLabel.setTranslateY(-7); + propertyTag.setAndBindString(controller.getEdge().syncProperty()); + } else if(propertyType.equals(Edge.PropertyType.UPDATE)) { + propertyLabel.setText("="); + propertyLabel.setTranslateX(-3); + propertyLabel.setTranslateY(-7); + propertyTag.setAndBindString(controller.getEdge().updateProperty()); + } + + //Disable the ability to edit the tag if the nails edge is locked + if(controller.getEdge().getIsLockedProperty().getValue()){ + propertyTag.setDisabledText(true); + } + } + }; + + // Whenever the property type updates, update the tag + controller.getNail().propertyTypeProperty().addListener((obs, oldPropertyType, newPropertyType) -> { + updatePropertyType.accept(newPropertyType); + }); + + // Whenever the edge changes I/O status, if sync nail then update its label + controller.getEdge().ioStatus.addListener((observable, oldValue, newValue) -> { + if (controller.getNail().getPropertyType().equals(Edge.PropertyType.SYNCHRONIZATION)) + updateSyncLabel(); + }); + + // Update the tag initially + updatePropertyType.accept(controller.getNail().getPropertyType()); + } + + /** + * Updates the synchronization label and tag. + * The update depends on the edge I/O status. + */ + private void updateSyncLabel() { + final Label propertyLabel = controller.propertyLabel; + + // show ? or ! dependent on edge I/O status + if (controller.getEdge().ioStatus.get().equals(EdgeStatus.INPUT)) { + propertyLabel.setText("?"); + } else { + propertyLabel.setText("!"); + } + } + + /** + * Set up Listeners for updating the color of the nail, set the color initially + */ + private void initializeNailCircleColor() { + final Runnable updateNailColor = () -> { + final Color color = controller.getComponent().getColor(); + final Color.Intensity colorIntensity = controller.getComponent().getColorIntensity(); + + if(!controller.getNail().getPropertyType().equals(Edge.PropertyType.NONE)) { + controller.nailCircle.setFill(color.getColor(colorIntensity)); + controller.nailCircle.setStroke(color.getColor(colorIntensity.next(2))); + } else { + controller.nailCircle.setFill(Color.GREY_BLUE.getColor(Color.Intensity.I800)); + controller.nailCircle.setStroke(Color.GREY_BLUE.getColor(Color.Intensity.I900)); + } + }; + + // When the color of the component updates, update the nail indicator as well + controller.getComponent().colorProperty().addListener((observable) -> updateNailColor.run()); + + // When the color intensity of the component updates, update the nail indicator + controller.getComponent().colorIntensityProperty().addListener((observable) -> updateNailColor.run()); + + // Initialize the color of the nail + updateNailColor.run(); + } + + /** + * Initializes a shake animation found in {@link SimNailPresentation#shakeAnimation} which can + * be played using {@link SimNailPresentation#shake()} + */ + private void initializeShakeAnimation() { + final Interpolator interpolator = Interpolator.SPLINE(0.645, 0.045, 0.355, 1); + + final double startX = controller.root.getTranslateX(); + final KeyValue kv1 = new KeyValue(controller.root.translateXProperty(), startX - 3, interpolator); + final KeyValue kv2 = new KeyValue(controller.root.translateXProperty(), startX + 3, interpolator); + final KeyValue kv3 = new KeyValue(controller.root.translateXProperty(), startX, interpolator); + + final KeyFrame kf1 = new KeyFrame(millis(50), kv1); + final KeyFrame kf2 = new KeyFrame(millis(100), kv2); + final KeyFrame kf3 = new KeyFrame(millis(150), kv1); + final KeyFrame kf4 = new KeyFrame(millis(200), kv2); + final KeyFrame kf5 = new KeyFrame(millis(250), kv3); + + shakeAnimation.getKeyFrames().addAll(kf1, kf2, kf3, kf4, kf5); + } + + /** + * Plays the {@link SimNailPresentation#shakeAnimation} + */ + public void shake() { + shakeAnimation.play(); + } + + /** + * Highlights the nail + */ + @Override + public void highlight() { + final Color color = Color.DEEP_ORANGE; + final Color.Intensity intensity = Color.Intensity.I500; + + // Set the color + controller.nailCircle.setFill(color.getColor(intensity)); + controller.nailCircle.setStroke(color.getColor(intensity.next(2))); + } + + /** + * Removes the highlight from the nail + */ + @Override + public void unhighlight() { + Color color = Color.GREY_BLUE; + Color.Intensity intensity = Color.Intensity.I800; + + // Set the color + if(!controller.getNail().getPropertyType().equals(Edge.PropertyType.NONE)) { + color = controller.getComponent().getColor(); + intensity = controller.getComponent().getColorIntensity(); + } + + controller.nailCircle.setFill(color.getColor(intensity)); + controller.nailCircle.setStroke(color.getColor(intensity.next(2))); + } +} diff --git a/src/main/java/ecdar/presentations/SimTagPresentation.java b/src/main/java/ecdar/presentations/SimTagPresentation.java new file mode 100755 index 00000000..d2916ee1 --- /dev/null +++ b/src/main/java/ecdar/presentations/SimTagPresentation.java @@ -0,0 +1,186 @@ +package ecdar.presentations; + +import ecdar.abstractions.Component; +import ecdar.utility.colors.Color; +import ecdar.utility.helpers.LocationAware; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.property.StringProperty; +import javafx.geometry.Insets; +import javafx.scene.control.Label; +import javafx.scene.layout.StackPane; +import javafx.scene.shape.LineTo; +import javafx.scene.shape.MoveTo; +import javafx.scene.shape.Path; + +import java.util.function.BiConsumer; + +import static ecdar.presentations.Grid.GRID_SIZE; + +/** + * The presentation for the tag shown on a {@link SimEdgePresentation} in the {@link SimulatorOverviewPresentation}
+ * This class should be refactored such that code which are duplicated from {@link TagPresentation} + * have its own base class. + */ +public class SimTagPresentation extends StackPane { + + private final static Color backgroundColor = Color.GREY; + private final static Color.Intensity backgroundColorIntensity = Color.Intensity.I50; + + private final ObjectProperty component = new SimpleObjectProperty<>(null); + private final ObjectProperty locationAware = new SimpleObjectProperty<>(null); + + private LineTo l2; + private LineTo l3; + + private static double TAG_HEIGHT = 1.6 * GRID_SIZE; + + /** + * Constructs the {@link SimTagPresentation} + */ + public SimTagPresentation() { + new EcdarFXMLLoader().loadAndGetController("SimTagPresentation.fxml", this); + initializeShape(); + initializeLabel(); + initializeMouseTransparency(); + } + + private void initializeMouseTransparency() { + mouseTransparentProperty().bind(opacityProperty().isEqualTo(0, 0.00f)); + } + + /** + * Initializes the label which shows the property + */ + private void initializeLabel() { + final Label label = (Label) lookup("#label"); + final Path shape = (Path) lookup("#shape"); + + final Insets insets = new Insets(0,2,0,2); + label.setPadding(insets); + + final int padding = 0; + + label.layoutBoundsProperty().addListener((obs, oldBounds, newBounds) -> { + double newWidth = Math.max(newBounds.getWidth(), 10); + final double res = GRID_SIZE * 2 - (newWidth % (GRID_SIZE * 2)); + newWidth += res; + + l2.setX(newWidth + padding); + l3.setX(newWidth + padding); + + setMinWidth(newWidth + padding); + setMaxWidth(newWidth + padding); + + label.focusedProperty().addListener((observable, oldFocused, newFocused) -> { + if (newFocused) { + shape.setTranslateY(2); + label.setTranslateY(2); + } + }); + + if (getWidth() >= 1000) { + setWidth(newWidth); + setHeight(TAG_HEIGHT); + shape.setTranslateY(-1); + } + + // Fixes the jumping of the shape when the text field is empty + if (label.getText().isEmpty()) { + shape.setLayoutX(0); + } + }); + } + + /** + * Initialize the shape which is around the property label + */ + private void initializeShape() { + final int WIDTH = 5000; + final double HEIGHT = TAG_HEIGHT; + + final Path shape = (Path) lookup("#shape"); + + final MoveTo start = new MoveTo(0, 0); + + l2 = new LineTo(WIDTH, 0); + l3 = new LineTo(WIDTH, HEIGHT); + final LineTo l4 = new LineTo(0, HEIGHT); + final LineTo l6 = new LineTo(0, 0); + + shape.getElements().addAll(start, l2, l3, l4, l6); + shape.setFill(backgroundColor.getColor(backgroundColorIntensity)); + shape.setStroke(backgroundColor.getColor(backgroundColorIntensity.next(4))); + } + + public void bindToColor(final ObjectProperty color, final ObjectProperty intensity) { + bindToColor(color, intensity, false); + } + + public void bindToColor(final ObjectProperty color, final ObjectProperty intensity, final boolean doColorBackground) { + final BiConsumer recolor = (newColor, newIntensity) -> { + if (doColorBackground) { + final Path shape = (Path) lookup("#shape"); + shape.setFill(newColor.getColor(newIntensity.next(-1))); + shape.setStroke(newColor.getColor(newIntensity.next(-1).next(2))); + } + }; + + color.addListener(observable -> recolor.accept(color.get(), intensity.get())); + intensity.addListener(observable -> recolor.accept(color.get(), intensity.get())); + recolor.accept(color.get(), intensity.get()); + } + + /** + * Updates the label with the given string and binds updates from the label on the given {@link StringProperty} + * @param string the string to set and bind to + */ + public void setAndBindString(final StringProperty string) { + final Label label = (Label) lookup("#label"); + + label.textProperty().unbind(); + label.setText(string.get()); + string.bind(label.textProperty()); + } + + /** + * Replaces spaces with underscores in the label + */ +/* public void replaceSpace() { + initializeTextAid(); + }*/ + + public Component getComponent() { + return component.get(); + } + + public void setComponent(final Component component) { + this.component.set(component); + } + + public ObjectProperty componentProperty() { + return component; + } + + public LocationAware getLocationAware() { + return locationAware.get(); + } + + public ObjectProperty locationAwareProperty() { + return locationAware; + } + + public void setLocationAware(LocationAware locationAware) { + this.locationAware.set(locationAware); + } + + /** + * Sets the disabled property in the label + * @param bool true --- the label will be disabled + * false --- the label will be enabled + */ + public void setDisabledText(boolean bool){ + final Label label = (Label) lookup("#label"); + label.setDisable(true); + } +} diff --git a/src/main/java/ecdar/presentations/SimulationInitializationDialogPresentation.java b/src/main/java/ecdar/presentations/SimulationInitializationDialogPresentation.java new file mode 100644 index 00000000..677217bd --- /dev/null +++ b/src/main/java/ecdar/presentations/SimulationInitializationDialogPresentation.java @@ -0,0 +1,17 @@ +package ecdar.presentations; + +import com.jfoenix.controls.JFXDialog; +import ecdar.controllers.BackendOptionsDialogController; +import ecdar.controllers.SimulationInitializationDialogController; + +public class SimulationInitializationDialogPresentation extends JFXDialog { + private final SimulationInitializationDialogController controller; + + public SimulationInitializationDialogPresentation() { + controller = new EcdarFXMLLoader().loadAndGetController("SimulationInitializationDialogPresentation.fxml", this); + } + + public SimulationInitializationDialogController getController() { + return controller; + } +} diff --git a/src/main/java/ecdar/presentations/SimulatorOverviewPresentation.java b/src/main/java/ecdar/presentations/SimulatorOverviewPresentation.java new file mode 100755 index 00000000..15a34e54 --- /dev/null +++ b/src/main/java/ecdar/presentations/SimulatorOverviewPresentation.java @@ -0,0 +1,20 @@ +package ecdar.presentations; + +import ecdar.controllers.SimulatorOverviewController; +import javafx.scene.layout.AnchorPane; + +/** + * The presenter of the middle part of the simulator. + * It is here where processes of a simulation will be shown. + */ +public class SimulatorOverviewPresentation extends AnchorPane { + private final SimulatorOverviewController controller; + + public SimulatorOverviewPresentation() { + controller = new EcdarFXMLLoader().loadAndGetController("SimulatorOverviewPresentation.fxml", this); + } + + public SimulatorOverviewController getController() { + return controller; + } +} diff --git a/src/main/java/ecdar/presentations/SimulatorPresentation.java b/src/main/java/ecdar/presentations/SimulatorPresentation.java new file mode 100755 index 00000000..255b7dd1 --- /dev/null +++ b/src/main/java/ecdar/presentations/SimulatorPresentation.java @@ -0,0 +1,20 @@ +package ecdar.presentations; + +import ecdar.controllers.SimulatorController; +import javafx.scene.layout.StackPane; + +public class SimulatorPresentation extends StackPane { + private final SimulatorController controller; + + public SimulatorPresentation() { + controller = new EcdarFXMLLoader().loadAndGetController("SimulatorPresentation.fxml", this); + } + + /** + * The way to get the associated/linked controller of this presenter + * @return the controller linked to this presenter + */ + public SimulatorController getController() { + return controller; + } +} diff --git a/src/main/java/ecdar/presentations/TracePaneElementPresentation.java b/src/main/java/ecdar/presentations/TracePaneElementPresentation.java new file mode 100755 index 00000000..0e8be627 --- /dev/null +++ b/src/main/java/ecdar/presentations/TracePaneElementPresentation.java @@ -0,0 +1,96 @@ +package ecdar.presentations; + +import com.jfoenix.controls.JFXRippler; +import ecdar.controllers.TracePaneElementController; +import ecdar.utility.colors.Color; +import javafx.geometry.Insets; +import javafx.scene.Cursor; +import javafx.scene.layout.*; + +import java.util.function.BiConsumer; + +/** + * The presentation class for the trace element that can be inserted into the simulator panes + */ +public class TracePaneElementPresentation extends VBox { + final private TracePaneElementController controller; + + public TracePaneElementPresentation() { + controller = new EcdarFXMLLoader().loadAndGetController("TracePaneElementPresentation.fxml", this); + + initializeToolbar(); + initializeSummaryView(); + } + + /** + * Initializes the tool bar that contains the trace pane element's title and buttons + * Sets the color of the bar and title label. Also sets the look of the rippler effect + */ + private void initializeToolbar() { + final Color color = Color.GREY_BLUE; + final Color.Intensity colorIntensity = Color.Intensity.I800; + + controller.toolbar.setBackground(new Background(new BackgroundFill( + color.getColor(colorIntensity), + CornerRadii.EMPTY, + Insets.EMPTY))); + controller.traceTitle.setTextFill(color.getTextColor(colorIntensity)); + + controller.expandTrace.setMaskType(JFXRippler.RipplerMask.CIRCLE); + controller.expandTrace.setRipplerFill(color.getTextColor(colorIntensity)); + } + + /** + * Initializes the summary view so it is update when steps are taken in the trace. + * Also changes the color and cursor when mouse enters and exits the summary view. + */ + private void initializeSummaryView() { + controller.getNumberOfStepsProperty().addListener( + (observable, oldValue, newValue) -> updateSummaryTitle(newValue.intValue())); + + final Color color = Color.GREY_BLUE; + final Color.Intensity colorIntensity = Color.Intensity.I50; + + final BiConsumer setBackground = (newColor, newIntensity) -> { + controller.traceSummary.setBackground(new Background(new BackgroundFill( + newColor.getColor(newIntensity), + CornerRadii.EMPTY, + Insets.EMPTY + ))); + + controller.traceSummary.setBorder(new Border(new BorderStroke( + newColor.getColor(newIntensity.next(2)), + BorderStrokeStyle.SOLID, + CornerRadii.EMPTY, + new BorderWidths(0, 0, 1, 0) + ))); + }; + + // Update the background when hovered + controller.traceSummary.setOnMouseEntered(event -> { + setBackground.accept(color, colorIntensity.next()); + setCursor(Cursor.HAND); + }); + + // Update the background when the mouse exits + controller.traceSummary.setOnMouseExited(event -> { + setBackground.accept(color, colorIntensity); + setCursor(Cursor.DEFAULT); + }); + + // Update the background initially + setBackground.accept(color, colorIntensity); + } + + /** + * Updates the text of the summary title label with the current number of steps in the trace + * @param steps The number of steps in the trace + */ + private void updateSummaryTitle(int steps) { + controller.summaryTitleLabel.setText(steps + " number of steps in trace"); + } + + public TracePaneElementController getController() { + return controller; + } +} diff --git a/src/main/java/ecdar/presentations/TransitionPaneElementPresentation.java b/src/main/java/ecdar/presentations/TransitionPaneElementPresentation.java new file mode 100755 index 00000000..8453af64 --- /dev/null +++ b/src/main/java/ecdar/presentations/TransitionPaneElementPresentation.java @@ -0,0 +1,69 @@ +package ecdar.presentations; + +import com.jfoenix.controls.JFXRippler; +import ecdar.controllers.TransitionPaneElementController; +import ecdar.utility.colors.Color; +import javafx.geometry.Insets; +import javafx.scene.layout.*; + +/** + * The presentation class for the transition pane element that can be inserted into the simulator panes + */ +public class TransitionPaneElementPresentation extends VBox { + final private TransitionPaneElementController controller; + + public TransitionPaneElementPresentation() { + controller = new EcdarFXMLLoader().loadAndGetController("TransitionPaneElementPresentation.fxml", this); + + initializeToolbar(); + initializeDelayChooser(); + } + + /** + * Initializes the toolbar for the transition pane element. + * Sets the background of the toolbar and changes the title color. + * Also changes the look of the rippler effect. + */ + private void initializeToolbar() { + final Color color = Color.GREY_BLUE; + final Color.Intensity colorIntensity = Color.Intensity.I800; + + // Set the background of the toolbar + controller.toolbar.setBackground(new Background(new BackgroundFill( + color.getColor(colorIntensity), + CornerRadii.EMPTY, + Insets.EMPTY))); + // Set the font color of elements in the toolbar + controller.toolbarTitle.setTextFill(color.getTextColor(colorIntensity)); + + controller.refreshRippler.setMaskType(JFXRippler.RipplerMask.CIRCLE); + controller.refreshRippler.setRipplerFill(color.getTextColor(colorIntensity)); + + controller.expandTransition.setMaskType(JFXRippler.RipplerMask.CIRCLE); + controller.expandTransition.setRipplerFill(color.getTextColor(colorIntensity)); + } + + /** + * Sets the background color of the delay chooser + */ + private void initializeDelayChooser() { + final Color color = Color.GREY_BLUE; + final Color.Intensity colorIntensity = Color.Intensity.I50; + controller.delayChooser.setBackground(new Background(new BackgroundFill( + color.getColor(colorIntensity), + CornerRadii.EMPTY, + Insets.EMPTY + ))); + + controller.delayChooser.setBorder(new Border(new BorderStroke( + color.getColor(colorIntensity.next(2)), + BorderStrokeStyle.SOLID, + CornerRadii.EMPTY, + new BorderWidths(0, 0, 1, 0) + ))); + } + + public TransitionPaneElementController getController() { + return controller; + } +} diff --git a/src/main/java/ecdar/presentations/TransitionPresentation.java b/src/main/java/ecdar/presentations/TransitionPresentation.java new file mode 100755 index 00000000..15fb47e6 --- /dev/null +++ b/src/main/java/ecdar/presentations/TransitionPresentation.java @@ -0,0 +1,98 @@ +package ecdar.presentations; + +import com.jfoenix.controls.JFXRippler; +import ecdar.controllers.TransitionController; +import ecdar.utility.colors.Color; +import javafx.animation.FadeTransition; +import javafx.animation.Interpolator; +import javafx.geometry.Insets; +import javafx.scene.Cursor; +import javafx.scene.layout.*; +import javafx.util.Duration; + +import java.util.function.BiConsumer; + +/** + * The presentation class for a transition view element. + * It represents a single transition and may be used by classes like {@see TransitionPaneElementController} + * to show a list of transitions + */ +public class TransitionPresentation extends AnchorPane { + private TransitionController controller; + private FadeTransition transition; + + public TransitionPresentation() { + controller = new EcdarFXMLLoader().loadAndGetController("TransitionPresentation.fxml", this); + + initializeRippler(); + initializeColors(); + initializeFadeAnimation(); + } + + /** + * Initializes the rippler. + * Sets the color, mask and position of the rippler effect. + */ + private void initializeRippler() { + final Color color = Color.GREY_BLUE; + final Color.Intensity colorIntensity = Color.Intensity.I400; + + controller.rippler.setMaskType(JFXRippler.RipplerMask.RECT); + controller.rippler.setRipplerFill(color.getColor(colorIntensity)); + controller.rippler.setPosition(JFXRippler.RipplerPos.BACK); + } + + /** + * Initializes the colors of the view. + * The background of the view changes colors depending on whether a mouse enters or exits the view. + */ + private void initializeColors() { + final Color color = Color.GREY_BLUE; + final Color.Intensity colorIntensity = Color.Intensity.I50; + + final BiConsumer setBackground = (newColor, newIntensity) -> { + setBackground(new Background(new BackgroundFill( + newColor.getColor(newIntensity), + CornerRadii.EMPTY, + Insets.EMPTY + ))); + + setBorder(new Border(new BorderStroke( + newColor.getColor(newIntensity.next(2)), + BorderStrokeStyle.SOLID, + CornerRadii.EMPTY, + new BorderWidths(0, 0, 1, 0) + ))); + }; + + // Update the background when hovered + setOnMouseEntered(event -> { + setBackground.accept(color, colorIntensity.next()); + setCursor(Cursor.HAND); + }); + + // Update the background when the mouse exits + setOnMouseExited(event -> { + setBackground.accept(color, colorIntensity); + setCursor(Cursor.DEFAULT); + }); + + // Update the background initially + setBackground.accept(color, colorIntensity); + } + + private void initializeFadeAnimation() { + this.transition = new FadeTransition(Duration.millis(500), this); + transition.setFromValue(0); + transition.setToValue(1); + transition.setInterpolator(Interpolator.EASE_IN); + } + + public void playFadeAnimation() { + this.transition.play(); + } + + public TransitionController getController() { + return controller; + } +} diff --git a/src/main/java/ecdar/simulation/SimulationHandler.java b/src/main/java/ecdar/simulation/SimulationHandler.java new file mode 100755 index 00000000..bb894b62 --- /dev/null +++ b/src/main/java/ecdar/simulation/SimulationHandler.java @@ -0,0 +1,410 @@ +package ecdar.simulation; + +import ecdar.Ecdar; +import ecdar.abstractions.*; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.collections.ObservableMap; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Map; + +/** + * Handles state changes, updates of values / clocks, and keeps track of all the transitions that + * have been taken throughout a simulation. + */ +public class SimulationHandler { + public static final String QUERY_PREFIX = "Query: "; + private ObjectProperty currentConcreteState = new SimpleObjectProperty<>(); + private ObjectProperty initialConcreteState = new SimpleObjectProperty<>(); + private ObjectProperty currentTime = new SimpleObjectProperty<>(); + private BigDecimal delay; + private ArrayList edgesSelected; + private EcdarSystem system; + private SimulationStateSuccessor successor; + private int numberOfSteps; + + /** + * A string to keep track what is currently being simulated + * For now the string is prefixed with {@link #QUERY_PREFIX} when doing a query simulation + * and kept empty when doing system simulations + */ + private String currentSimulation = ""; + + private final ObservableMap simulationVariables = FXCollections.observableHashMap(); + private final ObservableMap simulationClocks = FXCollections.observableHashMap(); + /** + * For some reason the successor.getTransitions() only sometimes returns some of the transitions + * that are available, when running the initial step. + * That is why we need to keep track of the initial transitions. + */ + private final ObservableList initialTransitions = FXCollections.observableArrayList(); + public ObservableList traceLog = FXCollections.observableArrayList(); + public ObservableList availableTransitions = FXCollections.observableArrayList(); + + /** + * Empty constructor that should be used if the system or project has not be initialized yet + */ + public SimulationHandler() { + + } + + /** + * Initializes the default system (non-query system) + */ + public void initializeDefaultSystem() { + currentSimulation = ""; + } + + /** + * Initializes the values and properties in the {@link SimulationHandler}. + * Can also be used as a reset of the simulation. + * THIS METHOD DOES NOT RESET THE ENGINE, + */ + private void initializeSimulation() { + // Initialization + this.delay = new BigDecimal(0); + this.edgesSelected = new ArrayList<>(); + this.numberOfSteps = 0; + this.availableTransitions.clear(); + this.simulationVariables.clear(); + this.simulationClocks.clear(); + this.traceLog.clear(); + this.currentConcreteState.set(getInitialConcreteState()); + this.initialConcreteState.set(getInitialConcreteState()); + this.currentTime = new SimpleObjectProperty<>(BigDecimal.ZERO); + + //Preparation for the simulation + this.system = getSystem(); + this.currentConcreteState.get().setTime(currentTime.getValue()); + this.initialTransitions.clear(); + this.successor = null; + } + + /** + * Reloads the whole simulation sets the initial transitions, states, etc + */ + public void initialStep() { + initializeSimulation(); + final SimulationState currentState = currentConcreteState.get(); + successor = getStateSuccessor(); + + //Save the previous states, and get the new + currentConcreteState.set(successor.getState()); + this.traceLog.add(currentState); + numberOfSteps++; + + //Updates the transitions available + availableTransitions.addAll(FXCollections.observableArrayList(successor.getTransitions())); + initialTransitions.addAll(availableTransitions); + updateAllValues(); + } + + /** + * Resets the simulation to the initial location + * where the SimulationState is the {@link SimulationHandler#initialConcreteState}, when there are + * elements in the {@link SimulationHandler#traceLog}. Otherwise it calls {@link SimulationHandler#initialStep} + */ + public void resetToInitialLocation() { + //If the simulation has not begone + if (traceLog.size() == 0) + initialStep(); + else + selectTransitionFromLog(initialConcreteState.get()); + } + + /** + * Resets the simulation to the state after executing the given transition.
+ * This method also resets the state, variables, and clocks to the values they had after the given transition. + * This also updates {@link SimulationHandler#availableTransitions} such that + * it displays the available transitions after taking the given transition. + * + * @param transition the transition which the simulation should go back to + */ + public void selectTransitionFromLog(final SimulationState transition) { + final int indexInTrace = traceLog.indexOf(transition); + final SimulationState selectedState; + if (indexInTrace == -1) { + System.out.println("Cannot find transition: " + transition); + Ecdar.showToast("Cannot find transition: " + transition); + return; + } else if (indexInTrace == numberOfSteps - 1) { + return; //you have selected the current system + } else { + selectedState = traceLog.get(indexInTrace); + } + final int sizeOfTraceLog = traceLog.size(); + final int maxRetries = 3; + int numberOfRetries = 0; + edgesSelected = new ArrayList<>(); + //In case that we fail we have to save the time we had before + final BigDecimal tempTime = currentTime.get(); + + currentTime.setValue(new BigDecimal(selectedState.getTime().doubleValue())); + successor.getState().setTime(currentTime.getValue()); + + while (numberOfRetries < maxRetries) { + successor = getStateSuccessor(); + break; + } + currentConcreteState.set(selectedState); + setSimVarAndClocks(); + traceLog.remove(indexInTrace + 1, sizeOfTraceLog); + availableTransitions.clear(); + + // If the user selected the initial/first state in the trace log, we do not trust the engine, + // as it only gives us a subset of the available transitions, in some cases. + if (indexInTrace == 0) availableTransitions.addAll(initialTransitions); + else availableTransitions.addAll(successor.getTransitions()); + + numberOfSteps = indexInTrace + 1; + } + + /** + * Take a step in the simulation. + * + * @param selectedTransitionIndex the index of the availableTransition that you want to take. + * @param delay the time which should pass after the transition. + */ + public void nextStep(final int selectedTransitionIndex, final BigDecimal delay) { + if (selectedTransitionIndex > availableTransitions.size()) { + Ecdar.showToast("The selected transition index: " + selectedTransitionIndex + " is bigger than it should: " + availableTransitions); + return; + } + + final Transition selectedTransition = availableTransitions.get(selectedTransitionIndex); + edgesSelected = new ArrayList<>(); + + //Preparing for the step + for (int i = 0; i < selectedTransition.getEdges().size(); i++) { + edgesSelected.set(i, selectedTransition.getEdges().get(i)); + } + + final int maxRetries = 3; + int numberOfRetries = 0; + + // getConcreteSuccessor may throw a "ProtocolException: Word expected" but in some cases calling the same + // method again does not throw this exception, and actually gives us the expected result. + // This loop calls the method a number of times (maxRetries) + while (numberOfRetries < maxRetries) { + successor = getStateSuccessor(); + // Break from the loop if the method call was a success + break; + } + + //Save the previous states, and get the new + currentConcreteState.set(successor.getState()); + this.traceLog.add(currentConcreteState.get()); + + // increments the number of steps taken during this simulation + numberOfSteps++; + + //Updates the transitions available + availableTransitions.clear(); + availableTransitions.setAll(successor.getTransitions()); + this.delay = delay; + updateAllValues(); + } + + private SimulationStateSuccessor getStateSuccessor() { + // ToDo: Implement + return new SimulationStateSuccessor(); + } + + /** + * An overload of {@link SimulationHandler#nextStep(int, BigDecimal)} where the delay is 0. + * + * @param selectedTransition the index of the availableTransition that you want to take. + */ + public void nextStep(final int selectedTransition) { + nextStep(selectedTransition, BigDecimal.ZERO); + } + + public void nextStep(final Transition transition, final BigDecimal delay) { + int index = availableTransitions.indexOf(transition); + if (index != -1) { + nextStep(index, delay); + } + } + + /** + * Updates all values and clocks that are used doing the current simulation. + * It also stores the variables in the {@link SimulationHandler#simulationVariables} + * and the clocks in {@link SimulationHandler#simulationClocks}. + */ + private void updateAllValues() { + currentTime.set(currentTime.get().add(delay)); + successor.getState().setTime(currentTime.get()); + setSimVarAndClocks(); + } + + /** + * Sets the value of simulation variables and clocks, based on {@link SimulationHandler#currentConcreteState} + */ + private void setSimVarAndClocks() { + // The variables and clocks are all found in the getVariables array + // the array is always of the following order: variables, clocks. + // The noOfVars variable thus also functions as an offset for the clocks in the getVariables array +// final int noOfClocks = engine.getSystem().getNoOfClocks(); +// final int noOfVars = engine.getSystem().getNoOfVariables(); + +// for (int i = 0; i < noOfVars; i++){ +// simulationVariables.put(engine.getSystem().getVariableName(i), +// currentConcreteState.get().getVariables()[i].getValue(BigDecimal.ZERO)); +// } + + // As the clocks values starts after the variables values in currentConcreteState.get().getVariables() + // Then i needs to start where the variables ends. + // j is needed to map the correct name with the value +// for (int i = noOfVars, j = 0; i < noOfClocks + noOfVars ; i++, j++) { +// simulationClocks.put(engine.getSystem().getClockName(j), +// currentConcreteState.get().getVariables()[i].getValue(BigDecimal.ZERO)); +// } + } + + /** + * Getter for the current concrete state + * + * @return the current {@link SimulationState} + */ + public SimulationState getCurrentState() { + return currentConcreteState.get(); + } + + /** + * The way to get the time in the current state of a simulation + * + * @return the time in the current state + */ + public BigDecimal getCurrentTime() { + return currentTime.get(); + } + + public ObjectProperty currentTimeProperty() { + return currentTime; + } + + /** + * The way to get the delay of the latest step in the simulation + * + * @return the delay of the latest step in the in the simulation + */ + public BigDecimal getDelay() { + return delay; + } + + /** + * The number of total steps taken in the current simulation + * + * @return the number of steps + */ + public int getNumberOfSteps() { + return numberOfSteps; + } + + /** + * All the transitions taken in this simulation + * + * @return an {@link ObservableList} of all the transitions taken in this simulation so far + */ + public ObservableList getTraceLog() { + return traceLog; + } + + /** + * All the available transitions in this state + * + * @return an {@link ObservableList} of all the currently available transitions in this state + */ + public ObservableList getAvailableTransitions() { + return availableTransitions; + } + + /** + * All the variables connected to the current simulation. + * This does not return any clocks, if you need please use {@link SimulationHandler#getSimulationClocks()} instead + * + * @return a {@link Map} where the name (String) is the key, and a {@link BigDecimal} is the value + */ + public ObservableMap getSimulationVariables() { + return simulationVariables; + } + + /** + * All the clocks connected to the current simulation. + * + * @return a {@link Map} where the name (String) is the key, and a {@link BigDecimal} is the clock value + * @see SimulationHandler#getSimulationVariables() + */ + public ObservableMap getSimulationClocks() { + return simulationClocks; + } + + /** + * The initial state of the current simulation + * + * @return the initial {@link SimulationState} of this simulation + */ + public SimulationState getInitialConcreteState() { + return Ecdar.getBackendDriver().getInitialSimulationState(); + } + + public ObjectProperty initialConcreteStateProperty() { + return initialConcreteState; + } + + /** + * Prints all available transitions to {@link System#out}. + * This is very useful for debugging. + * If a string representation is needed please use {@link SimulationHandler#getAvailableTransitionsAsStrings()} + * instead. + */ + public void printAvailableTransitions() { + System.out.println("---------------------------------"); + + System.out.println(numberOfSteps + " Successor state " + currentConcreteState.toString() + " Entry time " + currentTime); + System.out.print("Available transitions: "); + availableTransitions.forEach( + Transition -> System.out.println(Transition.getLabel() + " ")); + + if (!availableTransitions.isEmpty()) { + for (int i = 0; i < availableTransitions.get(0).getEdges().size(); i++) { + // ToDo: Implement +// System.out.println("Edges: " + +// availableTransitions.get(0).getEdges().get(i).getEdge().getSource().getPropertyValue("name") + +// "." + availableTransitions.get(0).getEdges().get(i).getName() + " --> " + +// availableTransitions.get(0).getEdges().get(i).getEdge().getTarget().getPropertyValue("name")); + } + } + + System.out.println("---------------------------------"); + } + + /** + * To get all available transitions as strings + * + * @return an ArrayList of all the enabled transitions + */ + public ArrayList getAvailableTransitionsAsStrings() { + final ArrayList transitions = new ArrayList<>(); + for (final Transition Transition : availableTransitions) { + transitions.add(Transition.getLabel()); + } + return transitions; + } + + public EcdarSystem getSystem() { + return system; + } + + public String getCurrentSimulation() { + return currentSimulation; + } + + public boolean isSimulationRunning() { + return false; // ToDo NIELS: Handle logic for determining whether a simulation is currently running + } +} \ No newline at end of file diff --git a/src/main/java/ecdar/simulation/SimulationState.java b/src/main/java/ecdar/simulation/SimulationState.java new file mode 100644 index 00000000..f246ce5a --- /dev/null +++ b/src/main/java/ecdar/simulation/SimulationState.java @@ -0,0 +1,52 @@ +package ecdar.simulation; + +import EcdarProtoBuf.ObjectProtos; +import ecdar.abstractions.Location; +import javafx.util.Pair; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.stream.Collectors; + +public class SimulationState { + private final ArrayList> locations; + + public SimulationState(ObjectProtos.StateTuple protoBufState) { + if (protoBufState != null) locations = protoBufState.getLocationsList().stream().map(lt -> new Pair<>(lt.getComponentName(), lt.getId())).collect(Collectors.toCollection(ArrayList::new)); + else locations = new ArrayList<>(); + // ToDo: Handle federations + } + + public void setTime(BigDecimal value) { + // ToDo: Implement + } + + public Number getTime() { + // ToDo: Implement + return new Number() { + @Override + public int intValue() { + return 0; + } + + @Override + public long longValue() { + return 0; + } + + @Override + public float floatValue() { + return 0; + } + + @Override + public double doubleValue() { + return 0; + } + }; + } + + public ArrayList> getLocations() { + return locations; + } +} diff --git a/src/main/java/ecdar/simulation/SimulationStateSuccessor.java b/src/main/java/ecdar/simulation/SimulationStateSuccessor.java new file mode 100644 index 00000000..1b0c4cba --- /dev/null +++ b/src/main/java/ecdar/simulation/SimulationStateSuccessor.java @@ -0,0 +1,17 @@ +package ecdar.simulation; + +import ecdar.Ecdar; + +import java.util.ArrayList; + +public class SimulationStateSuccessor { + public ArrayList getTransitions() { + // ToDo: Implement + return new ArrayList<>(); + } + + public SimulationState getState() { + // ToDo: Implement + return Ecdar.getBackendDriver().getInitialSimulationState(); + } +} diff --git a/src/main/java/ecdar/simulation/Transition.java b/src/main/java/ecdar/simulation/Transition.java new file mode 100644 index 00000000..da5bac14 --- /dev/null +++ b/src/main/java/ecdar/simulation/Transition.java @@ -0,0 +1,17 @@ +package ecdar.simulation; + +import ecdar.abstractions.Edge; + +import java.util.ArrayList; + +public class Transition { + public String getLabel() { + // ToDo: Implement + return "Transition label"; + } + + public ArrayList getEdges() { + // ToDo: Implement + return new ArrayList<>(); + } +} diff --git a/src/main/java/ecdar/utility/helpers/BindingHelper.java b/src/main/java/ecdar/utility/helpers/BindingHelper.java index afbddec2..89d1a5e6 100644 --- a/src/main/java/ecdar/utility/helpers/BindingHelper.java +++ b/src/main/java/ecdar/utility/helpers/BindingHelper.java @@ -5,6 +5,7 @@ import ecdar.model_canvas.arrow_heads.ChannelReceiverArrowHead; import ecdar.model_canvas.arrow_heads.ChannelSenderArrowHead; import ecdar.presentations.Link; +import ecdar.presentations.SimTagPresentation; import ecdar.presentations.TagPresentation; import ecdar.utility.mouse.MouseTracker; import javafx.beans.binding.DoubleBinding; @@ -24,6 +25,15 @@ public static void bind(final Line subject, final TagPresentation target) { subject.visibleProperty().bind(target.visibleProperty()); } + public static void bind(final Line subject, final SimTagPresentation target) { + subject.startXProperty().set(0); + subject.startYProperty().set(0); + subject.endXProperty().bind(target.translateXProperty().add(target.minWidthProperty().divide(2))); + subject.endYProperty().bind(target.translateYProperty().add(target.heightProperty().divide(2))); + subject.opacityProperty().bind(target.opacityProperty()); + subject.visibleProperty().bind(target.visibleProperty()); + } + public static void bind(final Circular subject, final ObservableDoubleValue x, final ObservableDoubleValue y) { subject.xProperty().bind(EcdarController.getActiveCanvasPresentation().mouseTracker.gridXProperty().subtract(x)); subject.yProperty().bind(EcdarController.getActiveCanvasPresentation().mouseTracker.gridYProperty().subtract(y)); diff --git a/src/main/resources/ecdar/main.css b/src/main/resources/ecdar/main.css index 67af8bb5..0d99400f 100644 --- a/src/main/resources/ecdar/main.css +++ b/src/main/resources/ecdar/main.css @@ -290,6 +290,24 @@ -fx-font-size: 1em; } +.large-toggle-button { + -jfx-toggle-color:#dddddd; -jfx-untoggle-color:#dddddd; + -jfx-toggle-line-color:#7C8B92; -jfx-untoggle-line-color:#7C8B92; + -fx-opacity: 0.5; + -jfx-size: 1.6em; + -fx-font-size: 1em; +} + +.editor-simulator-toggle { + -fx-min-width: 8em; + -fx-max-width: 8em; + -fx-min-height: 4em; + -fx-max-height: 4em; + -fx-background-color: -blue-grey-800; + -fx-border-radius: 0 0 10 10; + -fx-background-radius: 0 0 10 10; +} + /* Response sizing for elements (icons with these classes will be scaled when scaling changes) */ .icon-size-medium { -fx-font-family: "Material Icons Regular"; @@ -347,4 +365,4 @@ .responsive-circle-radius { -fx-scale-x: 1em; -fx-scale-y: 1em; -} \ No newline at end of file +} diff --git a/src/main/resources/ecdar/presentations/EcdarPresentation.fxml b/src/main/resources/ecdar/presentations/EcdarPresentation.fxml index c7032ddc..dc5c1a14 100644 --- a/src/main/resources/ecdar/presentations/EcdarPresentation.fxml +++ b/src/main/resources/ecdar/presentations/EcdarPresentation.fxml @@ -18,90 +18,17 @@ fx:controller="ecdar.controllers.EcdarController">

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
- - - + - - - + @@ -116,7 +43,8 @@ - + @@ -168,12 +96,14 @@ - + - + @@ -184,13 +114,14 @@ - + - + @@ -205,7 +136,7 @@ - + @@ -245,6 +176,19 @@ + + + + + + + + + + + + @@ -292,6 +236,21 @@ + + + + + + + + + + + + + + + @@ -352,8 +311,8 @@ - - + + @@ -535,4 +494,9 @@ + + + + + diff --git a/src/main/resources/ecdar/presentations/EditorPresentation.fxml b/src/main/resources/ecdar/presentations/EditorPresentation.fxml new file mode 100644 index 00000000..bf3faac1 --- /dev/null +++ b/src/main/resources/ecdar/presentations/EditorPresentation.fxml @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/ecdar/presentations/LeftSimPanePresentation.fxml b/src/main/resources/ecdar/presentations/LeftSimPanePresentation.fxml new file mode 100755 index 00000000..e80df29e --- /dev/null +++ b/src/main/resources/ecdar/presentations/LeftSimPanePresentation.fxml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/ecdar/presentations/ProcessPresentation.fxml b/src/main/resources/ecdar/presentations/ProcessPresentation.fxml new file mode 100755 index 00000000..6b4b8f77 --- /dev/null +++ b/src/main/resources/ecdar/presentations/ProcessPresentation.fxml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+ + + + + + + + +
+
+
+ +
+
+ + + + + + +
+
+
\ No newline at end of file diff --git a/src/main/resources/ecdar/presentations/ProjectPanePresentation.fxml b/src/main/resources/ecdar/presentations/ProjectPanePresentation.fxml index 342405e6..738311fe 100644 --- a/src/main/resources/ecdar/presentations/ProjectPanePresentation.fxml +++ b/src/main/resources/ecdar/presentations/ProjectPanePresentation.fxml @@ -10,7 +10,8 @@ + fx:controller="ecdar.controllers.ProjectPaneController" + StackPane.alignment="TOP_LEFT"> diff --git a/src/main/resources/ecdar/presentations/QueryPanePresentation.fxml b/src/main/resources/ecdar/presentations/QueryPanePresentation.fxml index 6a984c1b..e50fc7bd 100644 --- a/src/main/resources/ecdar/presentations/QueryPanePresentation.fxml +++ b/src/main/resources/ecdar/presentations/QueryPanePresentation.fxml @@ -9,7 +9,8 @@ + fx:controller="ecdar.controllers.QueryPaneController" + StackPane.alignment="TOP_RIGHT"> diff --git a/src/main/resources/ecdar/presentations/RightSimPanePresentation.fxml b/src/main/resources/ecdar/presentations/RightSimPanePresentation.fxml new file mode 100755 index 00000000..6f3d588c --- /dev/null +++ b/src/main/resources/ecdar/presentations/RightSimPanePresentation.fxml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + diff --git a/src/main/resources/ecdar/presentations/SimEdgePresentation.fxml b/src/main/resources/ecdar/presentations/SimEdgePresentation.fxml new file mode 100755 index 00000000..1c72435f --- /dev/null +++ b/src/main/resources/ecdar/presentations/SimEdgePresentation.fxml @@ -0,0 +1,12 @@ + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/ecdar/presentations/SimLocationPresentation.fxml b/src/main/resources/ecdar/presentations/SimLocationPresentation.fxml new file mode 100755 index 00000000..eb6b4543 --- /dev/null +++ b/src/main/resources/ecdar/presentations/SimLocationPresentation.fxml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/ecdar/presentations/SimNailPresentation.fxml b/src/main/resources/ecdar/presentations/SimNailPresentation.fxml new file mode 100755 index 00000000..17a63895 --- /dev/null +++ b/src/main/resources/ecdar/presentations/SimNailPresentation.fxml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/ecdar/presentations/SimTagPresentation.fxml b/src/main/resources/ecdar/presentations/SimTagPresentation.fxml new file mode 100755 index 00000000..f9e40d71 --- /dev/null +++ b/src/main/resources/ecdar/presentations/SimTagPresentation.fxml @@ -0,0 +1,11 @@ + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/ecdar/presentations/SimulationInitializationDialogPresentation.fxml b/src/main/resources/ecdar/presentations/SimulationInitializationDialogPresentation.fxml new file mode 100644 index 00000000..332e082d --- /dev/null +++ b/src/main/resources/ecdar/presentations/SimulationInitializationDialogPresentation.fxml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + Simulation Initialization + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/ecdar/presentations/SimulatorOverviewPresentation.fxml b/src/main/resources/ecdar/presentations/SimulatorOverviewPresentation.fxml new file mode 100755 index 00000000..f854ef33 --- /dev/null +++ b/src/main/resources/ecdar/presentations/SimulatorOverviewPresentation.fxml @@ -0,0 +1,13 @@ + + + + + + + \ No newline at end of file diff --git a/src/main/resources/ecdar/presentations/SimulatorPresentation.fxml b/src/main/resources/ecdar/presentations/SimulatorPresentation.fxml new file mode 100755 index 00000000..7524a380 --- /dev/null +++ b/src/main/resources/ecdar/presentations/SimulatorPresentation.fxml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + diff --git a/src/main/resources/ecdar/presentations/TracePaneElementPresentation.fxml b/src/main/resources/ecdar/presentations/TracePaneElementPresentation.fxml new file mode 100755 index 00000000..0b83620e --- /dev/null +++ b/src/main/resources/ecdar/presentations/TracePaneElementPresentation.fxml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/ecdar/presentations/TransitionPaneElementPresentation.fxml b/src/main/resources/ecdar/presentations/TransitionPaneElementPresentation.fxml new file mode 100755 index 00000000..b9b58ff9 --- /dev/null +++ b/src/main/resources/ecdar/presentations/TransitionPaneElementPresentation.fxml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/ecdar/presentations/TransitionPresentation.fxml b/src/main/resources/ecdar/presentations/TransitionPresentation.fxml new file mode 100755 index 00000000..3fa1c5cf --- /dev/null +++ b/src/main/resources/ecdar/presentations/TransitionPresentation.fxml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + diff --git a/src/test/java/ecdar/TestFXBase.java b/src/test/java/ecdar/TestFXBase.java new file mode 100644 index 00000000..e54eecec --- /dev/null +++ b/src/test/java/ecdar/TestFXBase.java @@ -0,0 +1,30 @@ +package ecdar; + +import javafx.scene.input.KeyCode; +import javafx.scene.input.MouseButton; +import javafx.stage.Stage; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.testfx.api.FxToolkit; +import org.testfx.framework.junit.ApplicationTest; + +import java.util.concurrent.TimeoutException; + +public class TestFXBase extends ApplicationTest { + @BeforeAll + static void setUp() throws Exception { + ApplicationTest.launch(Ecdar.class); + } + + @Override + public void start(Stage stage) { + stage.show(); + } + + @AfterAll + public void afterEachTest() throws TimeoutException { + FxToolkit.hideStage(); + release(new KeyCode[]{}); + release(new MouseButton[]{}); + } +} diff --git a/src/test/java/ecdar/simulation/SimulationTest.java b/src/test/java/ecdar/simulation/SimulationTest.java new file mode 100644 index 00000000..2acb8136 --- /dev/null +++ b/src/test/java/ecdar/simulation/SimulationTest.java @@ -0,0 +1,101 @@ +package ecdar.simulation; + +import EcdarProtoBuf.EcdarBackendGrpc; +import EcdarProtoBuf.ObjectProtos; +import EcdarProtoBuf.QueryProtos; +import ecdar.TestFXBase; +import ecdar.abstractions.Component; +import ecdar.abstractions.Location; +import io.grpc.BindableService; +import io.grpc.ManagedChannel; +import io.grpc.Server; +import io.grpc.Status; +import io.grpc.inprocess.InProcessChannelBuilder; +import io.grpc.inprocess.InProcessServerBuilder; +import io.grpc.stub.StreamObserver; +import io.grpc.testing.GrpcCleanupRule; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Assertions; + +public class SimulationTest extends TestFXBase { + public GrpcCleanupRule grpcCleanup = new GrpcCleanupRule(); + private final String serverName = InProcessServerBuilder.generateName(); + + @Test + public void testGetInitialStateHighlightsTheInitialLocation() { + final List components = generateComponentsWithInitialLocations(); + + BindableService testService = new EcdarBackendGrpc.EcdarBackendImplBase() { + @Override + public void startSimulation(QueryProtos.SimulationStartRequest request, + StreamObserver responseObserver) { + try { + ObjectProtos.StateTuple state = ObjectProtos.StateTuple.newBuilder().addAllLocations(components.stream() + .map(c -> ObjectProtos.StateTuple.LocationTuple.newBuilder() + .setComponentName(c.getName()) + .setId(c.getInitialLocation().getId()) + .build()) + .collect(Collectors.toList())).build(); + + QueryProtos.SimulationStepResponse response = QueryProtos.SimulationStepResponse.newBuilder().setState(state).build(); + responseObserver.onNext(response); + responseObserver.onCompleted(); + } catch (Throwable e) { + responseObserver.onError(Status.INVALID_ARGUMENT.withDescription(e.getMessage()).asException()); + } + } + + @Override + public void takeSimulationStep(EcdarProtoBuf.QueryProtos.SimulationStepRequest request, + io.grpc.stub.StreamObserver responseObserver) { + } + }; + + final Server server; + final ManagedChannel channel; + final EcdarBackendGrpc.EcdarBackendBlockingStub stub; + try { + server = grpcCleanup.register(InProcessServerBuilder + .forName(serverName).directExecutor().addService(testService).build().start()); + channel = grpcCleanup.register(InProcessChannelBuilder + .forName(serverName).directExecutor().build()); + stub = EcdarBackendGrpc.newBlockingStub(channel); + QueryProtos.SimulationStartRequest request = QueryProtos.SimulationStartRequest.newBuilder().setSystem("(A || B)").build(); + + var expectedResponse = new ObjectProtos.StateTuple.LocationTuple[components.size()]; + + for (int i = 0; i < components.size(); i++) { + Component comp = components.get(i); + expectedResponse[i] = ObjectProtos.StateTuple.LocationTuple.newBuilder() + .setComponentName(comp.getName()) + .setId(comp.getInitialLocation().getId()).build(); + } + + var result = stub.startSimulation(request).getState().getLocationsList().toArray(); + + Assertions.assertArrayEquals(expectedResponse, result); + } catch (IOException e) { + Assertions.fail("Exception encountered: " + e.getMessage()); + } + } + + private List generateComponentsWithInitialLocations() { + List comps = new ArrayList<>(); + for (int i = 0; i < 2; i++) { + var comp = new Component(); + comp.setName(comp + "_" + i); + var loc = new Location(comp + "_initial"); + loc.setType(Location.Type.INITIAL); + comp.addLocation(loc); + comps.add(comp); + } + + return comps; + } +} From e46356bf915b93348b0dc7789ddb81c84bebabe4 Mon Sep 17 00:00:00 2001 From: Niels Vistisen Date: Thu, 13 Oct 2022 10:44:15 +0200 Subject: [PATCH 029/158] WIP: ''Fix'' to allow for compilation --- src/main/java/ecdar/abstractions/Transition.java | 15 +-------------- .../java/ecdar/simulation/SimulationHandler.java | 2 +- .../java/ecdar/simulation/SimulationState.java | 5 ++--- 3 files changed, 4 insertions(+), 18 deletions(-) diff --git a/src/main/java/ecdar/abstractions/Transition.java b/src/main/java/ecdar/abstractions/Transition.java index 9b9d25be..e7b9817a 100644 --- a/src/main/java/ecdar/abstractions/Transition.java +++ b/src/main/java/ecdar/abstractions/Transition.java @@ -12,19 +12,6 @@ public class Transition { public final ArrayList edges = new ArrayList<>(); public Transition(ObjectProtos.Transition protoBufTransition) { - List protoBufEdges = (List) protoBufTransition.getField(ObjectProtos.Transition.getDescriptor().findFieldByName("edges")); - List componentNames = protoBufEdges.stream().map(ObjectProtos.Transition.EdgeTuple::getComponentName).collect(Collectors.toList()); - List edgeIds = protoBufEdges.stream().map(ObjectProtos.Transition.EdgeTuple::getId).collect(Collectors.toList()); - - // For each affected component, find each affected edge within that component, and add these edges to the edges list - List affectedComponents = Ecdar.getProject().getComponents().stream().filter(c -> componentNames.contains(c.getName())).collect(Collectors.toList()); - edges.addAll(affectedComponents.stream() - .map(c -> c.getEdges() - .stream() - .filter(e -> edgeIds.contains(e.getId())) - .collect(Collectors.toList())) - .flatMap(Collection::stream) - .collect(Collectors.toList()) - ); + // ToDo: Construct transition instance based on protoBuf input } } diff --git a/src/main/java/ecdar/simulation/SimulationHandler.java b/src/main/java/ecdar/simulation/SimulationHandler.java index bb894b62..a37b0712 100755 --- a/src/main/java/ecdar/simulation/SimulationHandler.java +++ b/src/main/java/ecdar/simulation/SimulationHandler.java @@ -109,7 +109,7 @@ public void initialStep() { * elements in the {@link SimulationHandler#traceLog}. Otherwise it calls {@link SimulationHandler#initialStep} */ public void resetToInitialLocation() { - //If the simulation has not begone + //If the simulation has not begun if (traceLog.size() == 0) initialStep(); else diff --git a/src/main/java/ecdar/simulation/SimulationState.java b/src/main/java/ecdar/simulation/SimulationState.java index f246ce5a..5a4f2575 100644 --- a/src/main/java/ecdar/simulation/SimulationState.java +++ b/src/main/java/ecdar/simulation/SimulationState.java @@ -12,9 +12,8 @@ public class SimulationState { private final ArrayList> locations; public SimulationState(ObjectProtos.StateTuple protoBufState) { - if (protoBufState != null) locations = protoBufState.getLocationsList().stream().map(lt -> new Pair<>(lt.getComponentName(), lt.getId())).collect(Collectors.toCollection(ArrayList::new)); - else locations = new ArrayList<>(); - // ToDo: Handle federations + locations = new ArrayList<>(); + // ToDo: Initialize with correct locations from protoBuf response } public void setTime(BigDecimal value) { From c2634a3acfdae5f61eb470e93e0fb218510a1192 Mon Sep 17 00:00:00 2001 From: Emilie Steinmann Date: Thu, 13 Oct 2022 11:09:39 +0200 Subject: [PATCH 030/158] align with condition for disabling action button --- src/main/java/ecdar/presentations/QueryPresentation.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ecdar/presentations/QueryPresentation.java b/src/main/java/ecdar/presentations/QueryPresentation.java index 48d49609..a864b553 100644 --- a/src/main/java/ecdar/presentations/QueryPresentation.java +++ b/src/main/java/ecdar/presentations/QueryPresentation.java @@ -73,7 +73,7 @@ private void initializeTextFields() { queryTextField.setOnKeyPressed(EcdarController.getActiveCanvasPresentation().getController().getLeaveTextAreaKeyHandler(keyEvent -> { Platform.runLater(() -> { - if (keyEvent.getCode().equals(KeyCode.ENTER) && controller.getQuery().getType().getQueryName() != null) { + if (keyEvent.getCode().equals(KeyCode.ENTER) && controller.getQuery().getType() != null) { runQuery(); } }); From a824223fa1f1dc886eb7277296607631b95ec13e Mon Sep 17 00:00:00 2001 From: "Julie H. Bengtsson" <82820935+jhbengtsson@users.noreply.github.com> Date: Thu, 13 Oct 2022 11:42:16 +0200 Subject: [PATCH 031/158] Hide generated component tab if there are no generated components (#31) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Nail location on all zoom levels not working FIXED * Print statement removed * if statement der sætter visible til true eller false * Arrows changed * forkortet Co-authored-by: Niels Vistisen Co-authored-by: Niels F. S. Vistisen <42961494+Nielswps@users.noreply.github.com> Co-authored-by: Dolmer1 --- src/main/java/ecdar/controllers/EdgeController.java | 4 ++-- .../java/ecdar/controllers/ProjectPaneController.java | 8 +++++--- .../ecdar/presentations/ProjectPanePresentation.fxml | 6 +++--- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/main/java/ecdar/controllers/EdgeController.java b/src/main/java/ecdar/controllers/EdgeController.java index 354c13e4..3610934f 100644 --- a/src/main/java/ecdar/controllers/EdgeController.java +++ b/src/main/java/ecdar/controllers/EdgeController.java @@ -595,8 +595,8 @@ private void addEdgePropertyRow(final DropDownMenu dropDownMenu, final String ro } private Nail getNewNailBasedOnDropdownPosition() { - final double nailX = Math.round(DropDownMenu.x / GRID_SIZE) * GRID_SIZE; - final double nailY = Math.round(DropDownMenu.y / GRID_SIZE) * GRID_SIZE; + final double nailX = Math.round(DropDownMenu.x / EcdarController.getActiveCanvasZoomFactor().get() / GRID_SIZE) * GRID_SIZE; + final double nailY = Math.round(DropDownMenu.y / EcdarController.getActiveCanvasZoomFactor().get() / GRID_SIZE) * GRID_SIZE; return new Nail(nailX, nailY); } diff --git a/src/main/java/ecdar/controllers/ProjectPaneController.java b/src/main/java/ecdar/controllers/ProjectPaneController.java index c80bcbc1..12ff4072 100644 --- a/src/main/java/ecdar/controllers/ProjectPaneController.java +++ b/src/main/java/ecdar/controllers/ProjectPaneController.java @@ -83,6 +83,8 @@ public void onChanged(final Change c) { c.getAddedSubList().forEach(this::handleAddedModel); c.getRemoved().forEach(this::handleRemovedModel); + generatedComponentsDivider.setVisible(!tempFilesList.getChildren().isEmpty()); + // Sort the children alphabetically sortPresentations(); } @@ -390,12 +392,12 @@ private void createSystemClicked() { */ @FXML private void setGeneratedComponentsVisibilityButtonClicked() { - if (generatedComponentsVisibilityButtonIcon.getIconCode() == Material.EXPAND_MORE) { - generatedComponentsVisibilityButtonIcon.setIconCode(Material.EXPAND_LESS); + if (generatedComponentsVisibilityButtonIcon.getIconCode() == Material.ARROW_LEFT) { + generatedComponentsVisibilityButtonIcon.setIconCode(Material.ARROW_DROP_DOWN); this.tempFilesList.setVisible(true); this.tempFilesList.setManaged(true); } else { - generatedComponentsVisibilityButtonIcon.setIconCode(Material.EXPAND_MORE); + generatedComponentsVisibilityButtonIcon.setIconCode(Material.ARROW_LEFT); this.tempFilesList.setVisible(false); this.tempFilesList.setManaged(false); } diff --git a/src/main/resources/ecdar/presentations/ProjectPanePresentation.fxml b/src/main/resources/ecdar/presentations/ProjectPanePresentation.fxml index 342405e6..f6b10689 100644 --- a/src/main/resources/ecdar/presentations/ProjectPanePresentation.fxml +++ b/src/main/resources/ecdar/presentations/ProjectPanePresentation.fxml @@ -56,7 +56,7 @@ - + @@ -68,8 +68,8 @@ - + From d92fe7de60741689807c7345be43c8c2e4f1cbab Mon Sep 17 00:00:00 2001 From: WassawRoki <56611129+WassawRoki@users.noreply.github.com> Date: Tue, 25 Oct 2022 09:54:00 +0200 Subject: [PATCH 032/158] first try --- src/main/java/ecdar/presentations/DropDownMenu.java | 1 + .../java/ecdar/presentations/QueryPresentation.java | 12 +++++++++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/main/java/ecdar/presentations/DropDownMenu.java b/src/main/java/ecdar/presentations/DropDownMenu.java index a631b7ff..0fb91c8f 100644 --- a/src/main/java/ecdar/presentations/DropDownMenu.java +++ b/src/main/java/ecdar/presentations/DropDownMenu.java @@ -90,6 +90,7 @@ public DropDownMenu(final Node src) { * @param width The width of the {@link DropDownMenu} */ public DropDownMenu(final Node src, final int width) { + dropDownMenuWidth.set(width); list = new VBox(); list.setStyle("-fx-background-color: white; -fx-padding: 8 0 8 0;"); diff --git a/src/main/java/ecdar/presentations/QueryPresentation.java b/src/main/java/ecdar/presentations/QueryPresentation.java index db1d9179..df680307 100644 --- a/src/main/java/ecdar/presentations/QueryPresentation.java +++ b/src/main/java/ecdar/presentations/QueryPresentation.java @@ -15,6 +15,7 @@ import javafx.geometry.Pos; import javafx.scene.Cursor; import javafx.scene.control.Label; +import javafx.scene.control.ScrollPane; import javafx.scene.control.TitledPane; import javafx.scene.control.Tooltip; import javafx.scene.input.KeyCode; @@ -23,6 +24,7 @@ import org.kordamp.ikonli.javafx.FontIcon; import org.kordamp.ikonli.material.Material; +import javax.swing.*; import java.util.Map; import java.util.Set; import java.util.function.Consumer; @@ -252,7 +254,7 @@ private void initializeStateIndicator() { statusIcon.setOnMouseClicked(event -> { if (controller.getQuery().getQuery().isEmpty()) return; - + Label label = new Label(tooltip.getText()); JFXDialog dialog = new InformationDialogPresentation("Result from query: " + controller.getQuery().getQuery(), label); dialog.show(Ecdar.getPresentation()); @@ -469,16 +471,20 @@ private void initializeMoreInformationButtonAndQueryTypeSymbol() { controller.queryTypeExpand.setRipplerFill(Color.GREY_BLUE.getColor(Color.Intensity.I500)); final DropDownMenu queryTypeDropDown = new DropDownMenu(controller.queryTypeExpand); - queryTypeDropDown.addListElement("Query Type"); QueryType[] queryTypes = QueryType.values(); for (QueryType type : queryTypes) { addQueryTypeListElement(type, queryTypeDropDown); + } controller.queryTypeExpand.setOnMousePressed((e) -> { e.consume(); - queryTypeDropDown.show(JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.LEFT, 16, 16); + + double Y=this.getLayoutY(); + System.out.println(Y); + if(Y >= 500){queryTypeDropDown.show(JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.RIGHT, -this.getWidth()/3, -(this.getLayoutY()-this.getHeight()-300));} + else {queryTypeDropDown.show(JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.RIGHT, 16, 16);} }); controller.queryTypeSymbol.setText(controller.getQuery() != null && controller.getQuery().getType() != null ? controller.getQuery().getType().getSymbol() : "---"); From 9dcbd83d18418b6ebfd82e18c36879fb66f8dbf6 Mon Sep 17 00:00:00 2001 From: APaludan Date: Tue, 25 Oct 2022 20:17:49 +0200 Subject: [PATCH 033/158] Update .gitmodules --- .gitmodules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitmodules b/.gitmodules index 217c921a..2e15cc85 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "src/main/proto"] path = src/main/proto - url = https://github.com/Ecdar/Ecdar-ProtoBuf.git + url = https://github.com/Ecdar-SW5/Ecdar-ProtoBuf.git From bcc3fcef6df7c94776ff771c1e197b032b5bb8a7 Mon Sep 17 00:00:00 2001 From: APaludan Date: Tue, 25 Oct 2022 21:53:41 +0200 Subject: [PATCH 034/158] send query requests with new grpc protocol --- .../java/ecdar/abstractions/Transition.java | 2 +- .../java/ecdar/backend/BackendDriver.java | 140 ++++++++++-------- .../backend/IgnoredInputOutputQuery.java | 4 +- .../ecdar/simulation/SimulationState.java | 2 +- src/main/proto | 2 +- 5 files changed, 81 insertions(+), 69 deletions(-) diff --git a/src/main/java/ecdar/abstractions/Transition.java b/src/main/java/ecdar/abstractions/Transition.java index e7b9817a..9aa71eeb 100644 --- a/src/main/java/ecdar/abstractions/Transition.java +++ b/src/main/java/ecdar/abstractions/Transition.java @@ -11,7 +11,7 @@ public class Transition { public final ArrayList edges = new ArrayList<>(); - public Transition(ObjectProtos.Transition protoBufTransition) { + public Transition(ObjectProtos protoBufTransition) { // ToDo: Construct transition instance based on protoBuf input } } diff --git a/src/main/java/ecdar/backend/BackendDriver.java b/src/main/java/ecdar/backend/BackendDriver.java index 36418b39..3730812b 100644 --- a/src/main/java/ecdar/backend/BackendDriver.java +++ b/src/main/java/ecdar/backend/BackendDriver.java @@ -85,19 +85,22 @@ public void run() { return; } - QueryProtos.ComponentsUpdateRequest.Builder componentsBuilder = QueryProtos.ComponentsUpdateRequest.newBuilder(); + ComponentProtos.ComponentsInfo.Builder componentsInfoBuilder = ComponentProtos.ComponentsInfo.newBuilder(); for (Component c : Ecdar.getProject().getComponents()) { - componentsBuilder.addComponents(ComponentProtos.Component.newBuilder().setJson(c.serialize().toString()).build()); + componentsInfoBuilder.addComponents(ComponentProtos.Component.newBuilder().setJson(c.serialize().toString()).build()); } executeGrpcRequest(query.getQuery().getQuery(), backendConnection, - componentsBuilder, + componentsInfoBuilder, QueryProtos.IgnoredInputOutputs.newBuilder().getDefaultInstanceForType(), (response) -> { - if (response.hasQuery() && response.getQuery().hasIgnoredInputOutputs()) { - var ignoredInputOutputs = response.getQuery().getIgnoredInputOutputs(); - query.addNewElementsToMap(new ArrayList<>(ignoredInputOutputs.getIgnoredInputsList()), new ArrayList<>(ignoredInputOutputs.getIgnoredOutputsList())); + // ToDo ANDREAS: Spørg niels om hvad ignored inputs og outputs er + System.out.println(query.ignoredInputs); + System.out.println(query.ignoredOutputs); + if (response.hasQueryOk() && query.ignoredInputs != null && query.ignoredOutputs != null) { +// var ignoredInputOutputs = response.getQuery().getIgnoredInputOutputs(); + query.addNewElementsToMap(new ArrayList<>(query.ignoredInputs.keySet()), new ArrayList<>(query.ignoredOutputs.keySet())); } else { // Response is unexpected, maybe just ignore } @@ -121,28 +124,31 @@ public void closeAllBackendConnections() throws IOException { private void executeQuery(ExecutableQuery executableQuery, BackendConnection backendConnection) { if (executableQuery.queryListener.getQuery().getQueryState() == QueryState.UNKNOWN) return; - QueryProtos.ComponentsUpdateRequest.Builder componentsBuilder = QueryProtos.ComponentsUpdateRequest.newBuilder(); + ComponentProtos.ComponentsInfo.Builder componentsInfoBuilder = ComponentProtos.ComponentsInfo.newBuilder(); for (Component c : Ecdar.getProject().getComponents()) { - componentsBuilder.addComponents(ComponentProtos.Component.newBuilder().setJson(c.serialize().toString()).build()); + if (executableQuery.query.contains(c.getName())) { + componentsInfoBuilder.addComponents(ComponentProtos.Component.newBuilder().setJson(c.serialize().toString()).build()); + } } + componentsInfoBuilder.setComponentsHash(componentsInfoBuilder.getComponentsList().hashCode()); - executeGrpcRequest(executableQuery, backendConnection, componentsBuilder); + executeGrpcRequest(executableQuery, backendConnection, componentsInfoBuilder); } /** * Executes the specified query as a gRPC request using the specified backend connection. - * componentsBuilder is used to update the components of the engine. + * componentsInfoBuilder is used to update the components of the engine. * * @param executableQuery executable query to be executed by the backend * @param backendConnection connection to the backend - * @param componentsBuilder components builder containing the components relevant to the query execution + * @param componentsInfoBuilder components builder containing the components relevant to the query execution */ private void executeGrpcRequest(ExecutableQuery executableQuery, BackendConnection backendConnection, - QueryProtos.ComponentsUpdateRequest.Builder componentsBuilder) { + ComponentProtos.ComponentsInfo.Builder componentsInfoBuilder) { executeGrpcRequest(executableQuery.query, backendConnection, - componentsBuilder, + componentsInfoBuilder, null, (response) -> handleQueryResponse(response, executableQuery), (error) -> handleQueryBackendError(error, executableQuery) @@ -151,13 +157,13 @@ private void executeGrpcRequest(ExecutableQuery executableQuery, /** * Executes the specified query as a gRPC request using the specified backend connection. - * componentsBuilder is used to update the components of the engine and on completion of this transaction, + * componentsInfoBuilder is used to update the components of the engine and on completion of this transaction, * the query is sent and its response is consumed by responseConsumer. Any error encountered is handled by * the errorConsumer. * * @param query query to be executed by the backend * @param backendConnection connection to the backend - * @param componentsBuilder components builder containing the components relevant to the query execution + * @param componentsInfoBuilder components builder containing the components relevant to the query execution * @param protoBufIgnoredInputOutputs ProtoBuf object containing the inputs and outputs that should be ignored * (can be null) * @param responseConsumer consumer for handling the received response @@ -165,13 +171,14 @@ private void executeGrpcRequest(ExecutableQuery executableQuery, */ private void executeGrpcRequest(String query, BackendConnection backendConnection, - QueryProtos.ComponentsUpdateRequest.Builder componentsBuilder, + ComponentProtos.ComponentsInfo.Builder componentsInfoBuilder, QueryProtos.IgnoredInputOutputs protoBufIgnoredInputOutputs, Consumer responseConsumer, Consumer errorConsumer) { - StreamObserver observer = new StreamObserver<>() { + StreamObserver responseObserver = new StreamObserver<>() { @Override - public void onNext(Empty value) { + public void onNext(QueryProtos.QueryResponse value) { + responseConsumer.accept(value); } @Override @@ -182,38 +189,21 @@ public void onError(Throwable t) { @Override public void onCompleted() { - StreamObserver responseObserver = new StreamObserver<>() { - @Override - public void onNext(QueryProtos.QueryResponse value) { - responseConsumer.accept(value); - } - - @Override - public void onError(Throwable t) { - errorConsumer.accept(t); - addBackendConnection(backendConnection); - } - - @Override - public void onCompleted() { - addBackendConnection(backendConnection); - } - }; - - var queryBuilder = QueryProtos.Query.newBuilder() - .setId(0) - .setQuery(query); - - if (protoBufIgnoredInputOutputs != null) - queryBuilder.setIgnoredInputOutputs(protoBufIgnoredInputOutputs); - - backendConnection.getStub().withDeadlineAfter(deadlineForResponses, TimeUnit.MILLISECONDS) - .sendQuery(queryBuilder.build(), responseObserver); + addBackendConnection(backendConnection); } }; + QueryProtos.QueryRequest.Builder queryRequestBuilder = QueryProtos.QueryRequest.newBuilder() + .setUserId(1) + .setQueryId(1) + .setQuery(query) + .setComponentsInfo(componentsInfoBuilder); + + if (protoBufIgnoredInputOutputs != null) + queryRequestBuilder.setIgnoredInputOutputs(protoBufIgnoredInputOutputs); + backendConnection.getStub().withDeadlineAfter(deadlineForResponses, TimeUnit.MILLISECONDS) - .updateComponents(componentsBuilder.build(), observer); + .sendQuery(queryRequestBuilder.build(), responseObserver); } private void addBackendConnection(BackendConnection backendConnection) { @@ -317,24 +307,46 @@ private void handleQueryResponse(QueryProtos.QueryResponse value, ExecutableQuer // If the query has been cancelled, ignore the result if (executableQuery.queryListener.getQuery().getQueryState() == QueryState.UNKNOWN) return; - if (value.hasRefinement() && value.getRefinement().getSuccess()) { - executableQuery.queryListener.getQuery().setQueryState(QueryState.SUCCESSFUL); - executableQuery.success.accept(true); - } else if (value.hasConsistency() && value.getConsistency().getSuccess()) { - executableQuery.queryListener.getQuery().setQueryState(QueryState.SUCCESSFUL); - executableQuery.success.accept(true); - } else if (value.hasDeterminism() && value.getDeterminism().getSuccess()) { - executableQuery.queryListener.getQuery().setQueryState(QueryState.SUCCESSFUL); - executableQuery.success.accept(true); - } else if (value.hasComponent()) { - executableQuery.queryListener.getQuery().setQueryState(QueryState.SUCCESSFUL); - executableQuery.success.accept(true); - JsonObject returnedComponent = (JsonObject) JsonParser.parseString(value.getComponent().getComponent().getJson()); - addGeneratedComponent(new Component(returnedComponent)); - } else { - executableQuery.queryListener.getQuery().setQueryState(QueryState.ERROR); - executableQuery.success.accept(false); + + switch (value.getResponseCase()) { + case QUERY_OK: + executableQuery.queryListener.getQuery().setQueryState(QueryState.SUCCESSFUL); + executableQuery.success.accept(true); + if (value.getQueryOk().hasComponent()) { + JsonObject returnedComponent = (JsonObject) JsonParser.parseString(value.getQueryOk().getComponent().getComponent().getJson()); + addGeneratedComponent(new Component(returnedComponent)); + } + break; + + case USER_TOKEN_ERROR: + executableQuery.queryListener.getQuery().setQueryState(QueryState.ERROR); + executableQuery.success.accept(false); + break; + + case RESPONSE_NOT_SET: + executableQuery.queryListener.getQuery().setQueryState(QueryState.ERROR); + executableQuery.success.accept(false); + break; } + +// if (value.hasRefinement() && value.getRefinement().getSuccess()) { +// executableQuery.queryListener.getQuery().setQueryState(QueryState.SUCCESSFUL); +// executableQuery.success.accept(true); +// } else if (value.hasConsistency() && value.getConsistency().getSuccess()) { +// executableQuery.queryListener.getQuery().setQueryState(QueryState.SUCCESSFUL); +// executableQuery.success.accept(true); +// } else if (value.hasDeterminism() && value.getDeterminism().getSuccess()) { +// executableQuery.queryListener.getQuery().setQueryState(QueryState.SUCCESSFUL); +// executableQuery.success.accept(true); +// } else if (value.hasComponent()) { +// executableQuery.queryListener.getQuery().setQueryState(QueryState.SUCCESSFUL); +// executableQuery.success.accept(true); +// JsonObject returnedComponent = (JsonObject) JsonParser.parseString(value.getComponent().getComponent().getJson()); +// addGeneratedComponent(new Component(returnedComponent)); +// } else { +// executableQuery.queryListener.getQuery().setQueryState(QueryState.ERROR); +// executableQuery.success.accept(false); +// } } private void handleQueryBackendError(Throwable t, ExecutableQuery executableQuery) { @@ -388,7 +400,7 @@ private void handleQueryBackendError(Throwable t, ExecutableQuery executableQuer } public SimulationState getInitialSimulationState() { - SimulationState state = new SimulationState(ObjectProtos.StateTuple.newBuilder().getDefaultInstanceForType()); + SimulationState state = new SimulationState(ObjectProtos.State.newBuilder().getDefaultInstanceForType()); state.getLocations().add(new Pair<>(Ecdar.getProject().getComponents().get(0).getName(), Ecdar.getProject().getComponents().get(0).getLocations().get(0).getId())); return state; } diff --git a/src/main/java/ecdar/backend/IgnoredInputOutputQuery.java b/src/main/java/ecdar/backend/IgnoredInputOutputQuery.java index 06843b31..6a351ce4 100644 --- a/src/main/java/ecdar/backend/IgnoredInputOutputQuery.java +++ b/src/main/java/ecdar/backend/IgnoredInputOutputQuery.java @@ -10,9 +10,9 @@ public class IgnoredInputOutputQuery { private final Query query; private final QueryPresentation queryPresentation; - private final HashMap ignoredInputs; + public final HashMap ignoredInputs; private final VBox ignoredInputsVBox; - private final HashMap ignoredOutputs; + public final HashMap ignoredOutputs; private final VBox ignoredOutputsVBox; public int tries = 0; diff --git a/src/main/java/ecdar/simulation/SimulationState.java b/src/main/java/ecdar/simulation/SimulationState.java index 5a4f2575..47144a9a 100644 --- a/src/main/java/ecdar/simulation/SimulationState.java +++ b/src/main/java/ecdar/simulation/SimulationState.java @@ -11,7 +11,7 @@ public class SimulationState { private final ArrayList> locations; - public SimulationState(ObjectProtos.StateTuple protoBufState) { + public SimulationState(ObjectProtos.State protoBufState) { locations = new ArrayList<>(); // ToDo: Initialize with correct locations from protoBuf response } diff --git a/src/main/proto b/src/main/proto index 476f1afd..c181269f 160000 --- a/src/main/proto +++ b/src/main/proto @@ -1 +1 @@ -Subproject commit 476f1afd62596ec6a841227a3caa5fd40abc9c6e +Subproject commit c181269f734c85ec51ae7e09de10cb685e54c595 From a8a9846676f996b50380abf63cfef69fdfda9843 Mon Sep 17 00:00:00 2001 From: APaludan Date: Thu, 27 Oct 2022 10:18:07 +0200 Subject: [PATCH 035/158] more work on grpc protocol --- .gitignore | 4 + .../java/ecdar/backend/BackendDriver.java | 102 +++++++++++++----- 2 files changed, 81 insertions(+), 25 deletions(-) diff --git a/.gitignore b/.gitignore index 938ad38a..977328e8 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,10 @@ lib/*.dll .idea/ /out/ +#VsCode +/bin/ +.vscode/ + ### Gradle ### .gradle /build/ diff --git a/src/main/java/ecdar/backend/BackendDriver.java b/src/main/java/ecdar/backend/BackendDriver.java index 3730812b..00c5dae6 100644 --- a/src/main/java/ecdar/backend/BackendDriver.java +++ b/src/main/java/ecdar/backend/BackendDriver.java @@ -4,6 +4,8 @@ import EcdarProtoBuf.EcdarBackendGrpc; import EcdarProtoBuf.ObjectProtos; import EcdarProtoBuf.QueryProtos; +import EcdarProtoBuf.QueryProtos.QueryResponse.QueryOk; + import com.google.gson.JsonObject; import com.google.gson.JsonParser; import com.google.protobuf.Empty; @@ -307,19 +309,88 @@ private void handleQueryResponse(QueryProtos.QueryResponse value, ExecutableQuer // If the query has been cancelled, ignore the result if (executableQuery.queryListener.getQuery().getQueryState() == QueryState.UNKNOWN) return; - switch (value.getResponseCase()) { case QUERY_OK: - executableQuery.queryListener.getQuery().setQueryState(QueryState.SUCCESSFUL); - executableQuery.success.accept(true); - if (value.getQueryOk().hasComponent()) { - JsonObject returnedComponent = (JsonObject) JsonParser.parseString(value.getQueryOk().getComponent().getComponent().getJson()); - addGeneratedComponent(new Component(returnedComponent)); + QueryOk queryOk = value.getQueryOk(); + switch (queryOk.getResultCase()) { + case REFINEMENT: + if (queryOk.getRefinement().getSuccess()) { + executableQuery.queryListener.getQuery().setQueryState(QueryState.SUCCESSFUL); + executableQuery.success.accept(true); + } else { + executableQuery.queryListener.getQuery().setQueryState(QueryState.ERROR); + executableQuery.failure.accept(new BackendException.QueryErrorException(queryOk.getRefinement().getReason())); + // executableQuery.success.accept(false); + } + break; + + case CONSISTENCY: + if (queryOk.getConsistency().getSuccess()) { + executableQuery.queryListener.getQuery().setQueryState(QueryState.SUCCESSFUL); + executableQuery.success.accept(true); + } else { + executableQuery.queryListener.getQuery().setQueryState(QueryState.ERROR); + executableQuery.failure.accept(new BackendException.QueryErrorException(queryOk.getConsistency().getReason())); + executableQuery.success.accept(false); + } + break; + + case DETERMINISM: + if (queryOk.getDeterminism().getSuccess()) { + executableQuery.queryListener.getQuery().setQueryState(QueryState.SUCCESSFUL); + executableQuery.success.accept(true); + } else { + executableQuery.queryListener.getQuery().setQueryState(QueryState.ERROR); + executableQuery.failure.accept(new BackendException.QueryErrorException(queryOk.getDeterminism().getReason())); + executableQuery.success.accept(false); + } + break; + + case IMPLEMENTATION: + if (queryOk.getImplementation().getSuccess()) { + executableQuery.queryListener.getQuery().setQueryState(QueryState.SUCCESSFUL); + executableQuery.success.accept(true); + } else { + executableQuery.queryListener.getQuery().setQueryState(QueryState.ERROR); + executableQuery.failure.accept(new BackendException.QueryErrorException(queryOk.getImplementation().getReason())); + executableQuery.success.accept(false); + } + break; + + case REACHABILITY: + if (queryOk.getReachability().getSuccess()) { + executableQuery.queryListener.getQuery().setQueryState(QueryState.SUCCESSFUL); + executableQuery.success.accept(true); + } else { + executableQuery.queryListener.getQuery().setQueryState(QueryState.ERROR); + executableQuery.failure.accept(new BackendException.QueryErrorException(queryOk.getReachability().getReason())); + executableQuery.success.accept(false); + } + break; + + case COMPONENT: + executableQuery.queryListener.getQuery().setQueryState(QueryState.SUCCESSFUL); + executableQuery.success.accept(true); + JsonObject returnedComponent = (JsonObject) JsonParser.parseString(queryOk.getComponent().getComponent().getJson()); + addGeneratedComponent(new Component(returnedComponent)); + break; + + case ERROR: + executableQuery.queryListener.getQuery().setQueryState(QueryState.ERROR); + executableQuery.failure.accept(new BackendException.QueryErrorException(queryOk.getError())); + executableQuery.success.accept(false); + break; + + case RESULT_NOT_SET: + executableQuery.queryListener.getQuery().setQueryState(QueryState.ERROR); + executableQuery.success.accept(false); + break; } break; case USER_TOKEN_ERROR: executableQuery.queryListener.getQuery().setQueryState(QueryState.ERROR); + executableQuery.failure.accept(new BackendException.QueryErrorException(value.getUserTokenError().getErrorMessage())); executableQuery.success.accept(false); break; @@ -328,25 +399,6 @@ private void handleQueryResponse(QueryProtos.QueryResponse value, ExecutableQuer executableQuery.success.accept(false); break; } - -// if (value.hasRefinement() && value.getRefinement().getSuccess()) { -// executableQuery.queryListener.getQuery().setQueryState(QueryState.SUCCESSFUL); -// executableQuery.success.accept(true); -// } else if (value.hasConsistency() && value.getConsistency().getSuccess()) { -// executableQuery.queryListener.getQuery().setQueryState(QueryState.SUCCESSFUL); -// executableQuery.success.accept(true); -// } else if (value.hasDeterminism() && value.getDeterminism().getSuccess()) { -// executableQuery.queryListener.getQuery().setQueryState(QueryState.SUCCESSFUL); -// executableQuery.success.accept(true); -// } else if (value.hasComponent()) { -// executableQuery.queryListener.getQuery().setQueryState(QueryState.SUCCESSFUL); -// executableQuery.success.accept(true); -// JsonObject returnedComponent = (JsonObject) JsonParser.parseString(value.getComponent().getComponent().getJson()); -// addGeneratedComponent(new Component(returnedComponent)); -// } else { -// executableQuery.queryListener.getQuery().setQueryState(QueryState.ERROR); -// executableQuery.success.accept(false); -// } } private void handleQueryBackendError(Throwable t, ExecutableQuery executableQuery) { From f99bd9ff8201184041eac8666d7091dcec8f7ad3 Mon Sep 17 00:00:00 2001 From: WassawRoki <56611129+WassawRoki@users.noreply.github.com> Date: Thu, 27 Oct 2022 14:43:34 +0200 Subject: [PATCH 036/158] The popup will no longer be place outside the window --- .../ecdar/presentations/QueryPresentation.java | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/main/java/ecdar/presentations/QueryPresentation.java b/src/main/java/ecdar/presentations/QueryPresentation.java index df680307..4910e67e 100644 --- a/src/main/java/ecdar/presentations/QueryPresentation.java +++ b/src/main/java/ecdar/presentations/QueryPresentation.java @@ -12,7 +12,9 @@ import javafx.beans.binding.When; import javafx.beans.property.SimpleBooleanProperty; import javafx.geometry.Insets; +import javafx.geometry.Point2D; import javafx.geometry.Pos; +import javafx.geometry.Rectangle2D; import javafx.scene.Cursor; import javafx.scene.control.Label; import javafx.scene.control.ScrollPane; @@ -21,6 +23,7 @@ import javafx.scene.input.KeyCode; import javafx.scene.layout.*; import javafx.scene.text.TextAlignment; +import javafx.stage.Screen; import org.kordamp.ikonli.javafx.FontIcon; import org.kordamp.ikonli.material.Material; @@ -481,10 +484,17 @@ private void initializeMoreInformationButtonAndQueryTypeSymbol() { controller.queryTypeExpand.setOnMousePressed((e) -> { e.consume(); - double Y=this.getLayoutY(); - System.out.println(Y); - if(Y >= 500){queryTypeDropDown.show(JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.RIGHT, -this.getWidth()/3, -(this.getLayoutY()-this.getHeight()-300));} - else {queryTypeDropDown.show(JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.RIGHT, 16, 16);} + //height of app window + double height = this.getScene().getHeight(); + //height and width of object relative to the app window + Point2D Y = this.localToScene(this.getWidth(), this.getHeight()); + + //Check if the dropdown can fit the app window. + //340 is the height of the dropdown 27/10-2022 Cant find height before it is rendered + if(Y.getY()+340 >= height) + {queryTypeDropDown.show(JFXPopup.PopupVPosition.BOTTOM, JFXPopup.PopupHPosition.RIGHT, -55,-(Y.getY()-height)-50);} + else + {queryTypeDropDown.show(JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.RIGHT, 16, 16);} }); controller.queryTypeSymbol.setText(controller.getQuery() != null && controller.getQuery().getType() != null ? controller.getQuery().getType().getSymbol() : "---"); From 852c024c117130ed39734b18d902c66ff1e832e4 Mon Sep 17 00:00:00 2001 From: APaludan Date: Fri, 28 Oct 2022 09:26:36 +0200 Subject: [PATCH 037/158] more simulation work --- .../java/ecdar/backend/BackendDriver.java | 67 +++++++++++++++++++ ...ulationInitializationDialogController.java | 2 + .../ecdar/simulation/SimulationHandler.java | 6 ++ 3 files changed, 75 insertions(+) diff --git a/src/main/java/ecdar/backend/BackendDriver.java b/src/main/java/ecdar/backend/BackendDriver.java index 00c5dae6..0d7fd88c 100644 --- a/src/main/java/ecdar/backend/BackendDriver.java +++ b/src/main/java/ecdar/backend/BackendDriver.java @@ -4,6 +4,7 @@ import EcdarProtoBuf.EcdarBackendGrpc; import EcdarProtoBuf.ObjectProtos; import EcdarProtoBuf.QueryProtos; +import EcdarProtoBuf.QueryProtos.SimulationStepResponse; import EcdarProtoBuf.QueryProtos.QueryResponse.QueryOk; import com.google.gson.JsonObject; @@ -13,6 +14,7 @@ import ecdar.abstractions.BackendInstance; import ecdar.abstractions.Component; import ecdar.abstractions.QueryState; +import ecdar.backend.BackendException.NoAvailableBackendConnectionException; import ecdar.simulation.SimulationState; import io.grpc.*; import io.grpc.stub.StreamObserver; @@ -157,6 +159,51 @@ private void executeGrpcRequest(ExecutableQuery executableQuery, ); } + public void executeStartSimRequest(String componentComposition, Consumer responseConsumer, Consumer errorConsumer) { + BackendConnection backendConnection; + try { + backendConnection = getBackendConnection(BackendHelper.getDefaultBackendInstance()); + } catch (NoAvailableBackendConnectionException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + return; + } + + StreamObserver responseObserver = new StreamObserver<>() { + @Override + public void onNext(QueryProtos.SimulationStepResponse value) { + System.out.println(value); + responseConsumer.accept(value); + } + + @Override + public void onError(Throwable t) { + errorConsumer.accept(t); + addBackendConnection(backendConnection); + } + + @Override + public void onCompleted() { + addBackendConnection(backendConnection); + } + }; + + var comInfo = ComponentProtos.ComponentsInfo.newBuilder(); + for (Component c : Ecdar.getProject().getComponents()) { + if (componentComposition.contains(c.getName())) { + comInfo.addComponents(ComponentProtos.Component.newBuilder().setJson(c.serialize().toString()).build()); + } + } + comInfo.setComponentsHash(comInfo.getComponentsList().hashCode()); + var simStartRequest = QueryProtos.SimulationStartRequest.newBuilder(); + var simInfo = QueryProtos.SimulationInfo.newBuilder() + .setComponentComposition(componentComposition) + .setComponentsInfo(comInfo); + simStartRequest.setSimulationInfo(simInfo); + backendConnection.getStub().withDeadlineAfter(deadlineForResponses, TimeUnit.MILLISECONDS) + .startSimulation(simStartRequest.build(), responseObserver); + } + /** * Executes the specified query as a gRPC request using the specified backend connection. * componentsInfoBuilder is used to update the components of the engine and on completion of this transaction, @@ -497,6 +544,26 @@ private void addGeneratedComponent(Component newComponent) { }); } + // private class ExecutableStartSimRequest { + // private final String componentComposition; + // private final BackendInstance backendInstance; + // private final Consumer success; + // private final Consumer failure; + // private final StartSimListener startSimListener; + // public int tries = 0; + + // public ExecutableStartSimRequest(String componentComposition, BackendInstance backendInstance, + // Consumer success, Consumer failure, StartSimListener startSimListener, + // int tries) { + // this.componentComposition = componentComposition; + // this.backendInstance = backendInstance; + // this.success = success; + // this.failure = failure; + // this.startSimListener = startSimListener; + // this.tries = tries; + // } + // } + private class ExecutableQuery { private final String query; private final BackendInstance backend; diff --git a/src/main/java/ecdar/controllers/SimulationInitializationDialogController.java b/src/main/java/ecdar/controllers/SimulationInitializationDialogController.java index 1eb1529c..144a5bd9 100644 --- a/src/main/java/ecdar/controllers/SimulationInitializationDialogController.java +++ b/src/main/java/ecdar/controllers/SimulationInitializationDialogController.java @@ -2,6 +2,8 @@ import com.jfoenix.controls.JFXButton; import com.jfoenix.controls.JFXComboBox; +import ecdar.Ecdar; +import javafx.fxml.FXML; import javafx.fxml.Initializable; import java.net.URL; diff --git a/src/main/java/ecdar/simulation/SimulationHandler.java b/src/main/java/ecdar/simulation/SimulationHandler.java index a37b0712..2396232e 100755 --- a/src/main/java/ecdar/simulation/SimulationHandler.java +++ b/src/main/java/ecdar/simulation/SimulationHandler.java @@ -12,6 +12,8 @@ import java.util.ArrayList; import java.util.Map; +import EcdarProtoBuf.QueryProtos.SimulationStepResponse; + /** * Handles state changes, updates of values / clocks, and keeps track of all the transitions that * have been taken throughout a simulation. @@ -26,6 +28,7 @@ public class SimulationHandler { private EcdarSystem system; private SimulationStateSuccessor successor; private int numberOfSteps; + private SimulationStepResponse currentResponse; /** * A string to keep track what is currently being simulated @@ -89,9 +92,12 @@ private void initializeSimulation() { */ public void initialStep() { initializeSimulation(); + final SimulationState currentState = currentConcreteState.get(); successor = getStateSuccessor(); + Ecdar.getBackendDriver().executeStartSimRequest("Administration || Machine || Researcher", (res) -> currentResponse = res, (err) -> System.out.println(err)); + //Save the previous states, and get the new currentConcreteState.set(successor.getState()); this.traceLog.add(currentState); From e07c6f3bb1c2afcdf23f54146af449beab89013d Mon Sep 17 00:00:00 2001 From: WassawRoki <56611129+WassawRoki@users.noreply.github.com> Date: Fri, 28 Oct 2022 10:48:46 +0200 Subject: [PATCH 038/158] Update QueryPresentation.java --- .../presentations/QueryPresentation.java | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/main/java/ecdar/presentations/QueryPresentation.java b/src/main/java/ecdar/presentations/QueryPresentation.java index 4910e67e..eb15f7e5 100644 --- a/src/main/java/ecdar/presentations/QueryPresentation.java +++ b/src/main/java/ecdar/presentations/QueryPresentation.java @@ -24,6 +24,7 @@ import javafx.scene.layout.*; import javafx.scene.text.TextAlignment; import javafx.stage.Screen; +import javafx.stage.StageStyle; import org.kordamp.ikonli.javafx.FontIcon; import org.kordamp.ikonli.material.Material; @@ -478,23 +479,24 @@ private void initializeMoreInformationButtonAndQueryTypeSymbol() { QueryType[] queryTypes = QueryType.values(); for (QueryType type : queryTypes) { addQueryTypeListElement(type, queryTypeDropDown); - } controller.queryTypeExpand.setOnMousePressed((e) -> { e.consume(); - //height of app window - double height = this.getScene().getHeight(); - //height and width of object relative to the app window - Point2D Y = this.localToScene(this.getWidth(), this.getHeight()); - + double windowHeight = this.getScene().getHeight(); + //Location of dropdown relative to the app window + Point2D Origin = this.localToScene(this.getWidth(), this.getHeight()); + //Generate the popups properties before displaying + queryTypeDropDown.show(this); //Check if the dropdown can fit the app window. - //340 is the height of the dropdown 27/10-2022 Cant find height before it is rendered - if(Y.getY()+340 >= height) - {queryTypeDropDown.show(JFXPopup.PopupVPosition.BOTTOM, JFXPopup.PopupHPosition.RIGHT, -55,-(Y.getY()-height)-50);} - else - {queryTypeDropDown.show(JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.RIGHT, 16, 16);} + if(Origin.getY()+queryTypeDropDown.getHeight() >= windowHeight){ + queryTypeDropDown.show(JFXPopup.PopupVPosition.BOTTOM, JFXPopup.PopupHPosition.RIGHT, -55,-(Origin.getY()-windowHeight)-50); + } + else{ + queryTypeDropDown.show(JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.RIGHT, 16, 16); + } + }); controller.queryTypeSymbol.setText(controller.getQuery() != null && controller.getQuery().getType() != null ? controller.getQuery().getType().getSymbol() : "---"); From 011c89c28c95752c43b86d28b0a52fb2f5cddf0f Mon Sep 17 00:00:00 2001 From: Niels Vistisen Date: Tue, 1 Nov 2022 09:22:51 +0100 Subject: [PATCH 039/158] Moved SimulationHandler --- src/main/java/ecdar/Ecdar.java | 2 +- src/main/java/ecdar/backend/QueryHandler.java | 6 ++++- .../SimulationHandler.java | 25 ++++++++++--------- .../controllers/SimulatorController.java | 2 +- .../TracePaneElementController.java | 2 +- .../TransitionPaneElementController.java | 2 +- 6 files changed, 22 insertions(+), 17 deletions(-) rename src/main/java/ecdar/{simulation => backend}/SimulationHandler.java (93%) diff --git a/src/main/java/ecdar/Ecdar.java b/src/main/java/ecdar/Ecdar.java index 64d69102..01775eac 100644 --- a/src/main/java/ecdar/Ecdar.java +++ b/src/main/java/ecdar/Ecdar.java @@ -5,7 +5,7 @@ import ecdar.backend.BackendDriver; import ecdar.backend.BackendHelper; import ecdar.backend.QueryHandler; -import ecdar.simulation.SimulationHandler; +import ecdar.backend.SimulationHandler; import ecdar.code_analysis.CodeAnalysis; import ecdar.controllers.EcdarController; import ecdar.presentations.BackgroundThreadPresentation; diff --git a/src/main/java/ecdar/backend/QueryHandler.java b/src/main/java/ecdar/backend/QueryHandler.java index 84c58a9c..ed3eb401 100644 --- a/src/main/java/ecdar/backend/QueryHandler.java +++ b/src/main/java/ecdar/backend/QueryHandler.java @@ -58,6 +58,8 @@ public void onNext(QueryProtos.QueryResponse value) { @Override public void onError(Throwable t) { handleQueryBackendError(t, query); + + // Release backend connection backendDriver.addBackendConnection(backendConnection); connections.remove(backendConnection); } @@ -70,8 +72,10 @@ public void onCompleted() { } }; + // ToDo SW5: Not working with the updated gRPC Protos var queryBuilder = QueryProtos.QueryRequest.newBuilder() - .setQueryId(0) + .setUserId(1) + .setQueryId(1) .setQuery(query.getType().getQueryName() + ": " + query.getQuery()) .setComponentsInfo(componentsInfoBuilder); diff --git a/src/main/java/ecdar/simulation/SimulationHandler.java b/src/main/java/ecdar/backend/SimulationHandler.java similarity index 93% rename from src/main/java/ecdar/simulation/SimulationHandler.java rename to src/main/java/ecdar/backend/SimulationHandler.java index 5f3d6f61..4da6b04d 100755 --- a/src/main/java/ecdar/simulation/SimulationHandler.java +++ b/src/main/java/ecdar/backend/SimulationHandler.java @@ -1,13 +1,11 @@ -package ecdar.simulation; +package ecdar.backend; import EcdarProtoBuf.ComponentProtos; import EcdarProtoBuf.QueryProtos; import ecdar.Ecdar; import ecdar.abstractions.*; -import ecdar.backend.BackendConnection; -import ecdar.backend.BackendDriver; -import ecdar.backend.BackendHelper; -import ecdar.backend.GrpcRequest; +import ecdar.simulation.SimulationState; +import ecdar.simulation.SimulationStateSuccessor; import io.grpc.stub.StreamObserver; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; @@ -53,9 +51,9 @@ public class SimulationHandler { * that are available, when running the initial step. * That is why we need to keep track of the initial transitions. */ - private final ObservableList initialTransitions = FXCollections.observableArrayList(); + private final ObservableList initialTransitions = FXCollections.observableArrayList(); public ObservableList traceLog = FXCollections.observableArrayList(); - public ObservableList availableTransitions = FXCollections.observableArrayList(); + public ObservableList availableTransitions = FXCollections.observableArrayList(); private final BackendDriver backendDriver; private final ArrayList connections = new ArrayList<>(); @@ -107,7 +105,7 @@ public void initialStep() { final SimulationState currentState = currentConcreteState.get(); successor = getStateSuccessor(); - GrpcRequest newRequest = new GrpcRequest(backendConnection -> { + GrpcRequest request = new GrpcRequest(backendConnection -> { StreamObserver responseObserver = new StreamObserver<>() { @Override public void onNext(QueryProtos.SimulationStepResponse value) { @@ -118,6 +116,7 @@ public void onNext(QueryProtos.SimulationStepResponse value) { @Override public void onError(Throwable t) { System.out.println(t.getMessage()); + // Release backend connection backendDriver.addBackendConnection(backendConnection); connections.remove(backendConnection); @@ -147,6 +146,8 @@ public void onCompleted() { .startSimulation(simStartRequest.build(), responseObserver); }, BackendHelper.getDefaultBackendInstance()); + backendDriver.addRequestToExecutionQueue(request); + //Save the previous states, and get the new currentConcreteState.set(successor.getState()); this.traceLog.add(currentState); @@ -230,7 +231,7 @@ public void nextStep(final int selectedTransitionIndex, final BigDecimal delay) return; } - final Transition selectedTransition = availableTransitions.get(selectedTransitionIndex); + final ecdar.simulation.Transition selectedTransition = availableTransitions.get(selectedTransitionIndex); edgesSelected = new ArrayList<>(); //Preparing for the step @@ -278,7 +279,7 @@ public void nextStep(final int selectedTransition) { nextStep(selectedTransition, BigDecimal.ZERO); } - public void nextStep(final Transition transition, final BigDecimal delay) { + public void nextStep(final ecdar.simulation.Transition transition, final BigDecimal delay) { int index = availableTransitions.indexOf(transition); if (index != -1) { nextStep(index, delay); @@ -374,7 +375,7 @@ public ObservableList getTraceLog() { * * @return an {@link ObservableList} of all the currently available transitions in this state */ - public ObservableList getAvailableTransitions() { + public ObservableList getAvailableTransitions() { return availableTransitions; } @@ -446,7 +447,7 @@ public void printAvailableTransitions() { */ public ArrayList getAvailableTransitionsAsStrings() { final ArrayList transitions = new ArrayList<>(); - for (final Transition Transition : availableTransitions) { + for (final ecdar.simulation.Transition Transition : availableTransitions) { transitions.add(Transition.getLabel()); } return transitions; diff --git a/src/main/java/ecdar/controllers/SimulatorController.java b/src/main/java/ecdar/controllers/SimulatorController.java index bdba66b4..f3305934 100644 --- a/src/main/java/ecdar/controllers/SimulatorController.java +++ b/src/main/java/ecdar/controllers/SimulatorController.java @@ -2,7 +2,7 @@ import ecdar.Ecdar; import ecdar.abstractions.*; -import ecdar.simulation.SimulationHandler; +import ecdar.backend.SimulationHandler; import ecdar.presentations.SimulatorOverviewPresentation; import ecdar.simulation.SimulationState; import ecdar.simulation.Transition; diff --git a/src/main/java/ecdar/controllers/TracePaneElementController.java b/src/main/java/ecdar/controllers/TracePaneElementController.java index 6ace31df..1c2e82fa 100755 --- a/src/main/java/ecdar/controllers/TracePaneElementController.java +++ b/src/main/java/ecdar/controllers/TracePaneElementController.java @@ -4,7 +4,7 @@ import ecdar.Ecdar; import ecdar.abstractions.Location; import ecdar.simulation.SimulationState; -import ecdar.simulation.SimulationHandler; +import ecdar.backend.SimulationHandler; import ecdar.presentations.TransitionPresentation; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleIntegerProperty; diff --git a/src/main/java/ecdar/controllers/TransitionPaneElementController.java b/src/main/java/ecdar/controllers/TransitionPaneElementController.java index 8a985a92..0233e335 100755 --- a/src/main/java/ecdar/controllers/TransitionPaneElementController.java +++ b/src/main/java/ecdar/controllers/TransitionPaneElementController.java @@ -5,7 +5,7 @@ import ecdar.Ecdar; import ecdar.abstractions.Edge; import ecdar.simulation.Transition; -import ecdar.simulation.SimulationHandler; +import ecdar.backend.SimulationHandler; import ecdar.presentations.TransitionPresentation; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleObjectProperty; From 622dbb46241b1d9303a445238472fefd37191a9e Mon Sep 17 00:00:00 2001 From: Niels Vistisen Date: Tue, 1 Nov 2022 09:26:53 +0100 Subject: [PATCH 040/158] getComponentsInfoBuilder changed to require string instead of query to generalize usage --- src/main/java/ecdar/backend/BackendHelper.java | 4 ++-- src/main/java/ecdar/backend/QueryHandler.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/ecdar/backend/BackendHelper.java b/src/main/java/ecdar/backend/BackendHelper.java index 72b56810..b93756b5 100644 --- a/src/main/java/ecdar/backend/BackendHelper.java +++ b/src/main/java/ecdar/backend/BackendHelper.java @@ -148,10 +148,10 @@ public static void addBackendInstanceListener(Runnable runnable) { BackendHelper.backendInstancesUpdatedListeners.add(runnable); } - public static ComponentProtos.ComponentsInfo.Builder getComponentsInfoBuilder(Query query) { + public static ComponentProtos.ComponentsInfo.Builder getComponentsInfoBuilder(String query) { ComponentProtos.ComponentsInfo.Builder componentsInfoBuilder = ComponentProtos.ComponentsInfo.newBuilder(); for (Component c : Ecdar.getProject().getComponents()) { - if (query.getQuery().contains(c.getName())) { + if (query.contains(c.getName())) { componentsInfoBuilder.addComponents(ComponentProtos.Component.newBuilder().setJson(c.serialize().toString()).build()); } } diff --git a/src/main/java/ecdar/backend/QueryHandler.java b/src/main/java/ecdar/backend/QueryHandler.java index ed3eb401..5f76ce1e 100644 --- a/src/main/java/ecdar/backend/QueryHandler.java +++ b/src/main/java/ecdar/backend/QueryHandler.java @@ -47,7 +47,7 @@ public void executeQuery(Query query) throws NoSuchElementException { GrpcRequest request = new GrpcRequest(backendConnection -> { connections.add(backendConnection); // Save reference for closing connection on exit - var componentsInfoBuilder = BackendHelper.getComponentsInfoBuilder(query); + var componentsInfoBuilder = BackendHelper.getComponentsInfoBuilder(query.getQuery()); StreamObserver responseObserver = new StreamObserver<>() { @Override From 808652d6ffe240841d1bc620e45c6d4eab2d4722 Mon Sep 17 00:00:00 2001 From: WassawRoki <56611129+WassawRoki@users.noreply.github.com> Date: Thu, 3 Nov 2022 13:49:09 +0100 Subject: [PATCH 041/158] We might have it --- .../ecdar/controllers/EcdarController.java | 1 + ...ulationInitializationDialogController.java | 23 ++++++++++++- .../controllers/SimulatorController.java | 33 +++++++++++-------- ...ationInitializationDialogPresentation.java | 2 ++ ...ationInitializationDialogPresentation.fxml | 2 +- 5 files changed, 45 insertions(+), 16 deletions(-) diff --git a/src/main/java/ecdar/controllers/EcdarController.java b/src/main/java/ecdar/controllers/EcdarController.java index 8c432ed5..f0ecab80 100644 --- a/src/main/java/ecdar/controllers/EcdarController.java +++ b/src/main/java/ecdar/controllers/EcdarController.java @@ -293,6 +293,7 @@ private void initializeDialogs() { }); simulationInitializationDialog.getController().startButton.setOnMouseClicked(event -> { + // ToDo NIELS: Start simulation of selected query currentMode.setValue(Mode.Simulator); simulationInitializationDialog.close(); diff --git a/src/main/java/ecdar/controllers/SimulationInitializationDialogController.java b/src/main/java/ecdar/controllers/SimulationInitializationDialogController.java index 1eb1529c..f085a649 100644 --- a/src/main/java/ecdar/controllers/SimulationInitializationDialogController.java +++ b/src/main/java/ecdar/controllers/SimulationInitializationDialogController.java @@ -5,13 +5,34 @@ import javafx.fxml.Initializable; import java.net.URL; -import java.util.ResourceBundle; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; public class SimulationInitializationDialogController implements Initializable { public JFXComboBox simulationComboBox; public JFXButton cancelButton; public JFXButton startButton; + public static List ListOfComponents = new ArrayList<>(); + + public void GetListOfComponentsToSimulate(){ + //Function gets list of components to simulation + String componentsToSimulate =simulationComboBox.getSelectionModel().getSelectedItem(); + + //componentsToSimulate = componentsToSimulate.replace("([\\w]*)([^\\w])",""); + + Pattern pattern = Pattern.compile("([\\w]*)", Pattern.CASE_INSENSITIVE); + Matcher matcher = pattern.matcher(componentsToSimulate); + List listOfComponents = new ArrayList<>(); + while(matcher.find()){ + if(matcher.group().length() != 0) + {listOfComponents.add(matcher.group());} + } + + + ListOfComponents = listOfComponents; + } public void initialize(URL location, ResourceBundle resources) { } diff --git a/src/main/java/ecdar/controllers/SimulatorController.java b/src/main/java/ecdar/controllers/SimulatorController.java index bdba66b4..bb58258e 100644 --- a/src/main/java/ecdar/controllers/SimulatorController.java +++ b/src/main/java/ecdar/controllers/SimulatorController.java @@ -2,6 +2,7 @@ import ecdar.Ecdar; import ecdar.abstractions.*; +import ecdar.presentations.SimulationInitializationDialogPresentation; import ecdar.simulation.SimulationHandler; import ecdar.presentations.SimulatorOverviewPresentation; import ecdar.simulation.SimulationState; @@ -54,10 +55,10 @@ public void willShow() { shouldSimulationBeReset = false; } - if (!firstTimeInSimulator && !new HashSet<>(overviewPresentation.getController().getComponentObservableList()) - .containsAll(findComponentsInCurrentSimulation())) { - shouldSimulationBeReset = true; - } + //if (!firstTimeInSimulator && !new HashSet<>(overviewPresentation.getController().getComponentObservableList()) + // .containsAll(findComponentsInCurrentSimulation())) { + // shouldSimulationBeReset = true; + //} if (shouldSimulationBeReset || firstTimeInSimulator) { @@ -75,9 +76,10 @@ public void willShow() { private void resetSimulation() { final SimulationHandler sm = Ecdar.getSimulationHandler(); sm.initializeDefaultSystem(); + overviewPresentation.getController().clearOverview(); overviewPresentation.getController().getComponentObservableList().clear(); - overviewPresentation.getController().getComponentObservableList().addAll(findComponentsInCurrentSimulation()); + overviewPresentation.getController().getComponentObservableList().addAll(findComponentsInCurrentSimulation(SimulationInitializationDialogController.ListOfComponents)); firstTimeInSimulator = false; } @@ -89,19 +91,22 @@ private void resetSimulation() { * * @return all the components used in the current simulation */ - private List findComponentsInCurrentSimulation() { + private List findComponentsInCurrentSimulation(List queryComponents) { //Show components from the system final SimulationHandler sm = Ecdar.getSimulationHandler(); List components = new ArrayList<>(); components = Ecdar.getProject().getComponents(); -// for (int i = 0; i < sm.getSystem().getNoOfProcesses(); i++) { -// final int finalI = i; // when using a var in lambda it has to be final -// final List filteredList = Ecdar.getProject().getComponents().filtered(component -> { -// return component.getName().contentEquals(sm.getSystem().getProcess(finalI).getName()); -// }); -// components.addAll(filteredList); -// } - return components; + + List SelectedComponents = new ArrayList<>(); + for(Component comp:components) { + for(String componentInQuery : queryComponents) { + if((comp.getName().equals(componentInQuery))) { + SelectedComponents.add(comp); + } + } + + } + return SelectedComponents; } /** diff --git a/src/main/java/ecdar/presentations/SimulationInitializationDialogPresentation.java b/src/main/java/ecdar/presentations/SimulationInitializationDialogPresentation.java index 677217bd..100d3b52 100644 --- a/src/main/java/ecdar/presentations/SimulationInitializationDialogPresentation.java +++ b/src/main/java/ecdar/presentations/SimulationInitializationDialogPresentation.java @@ -1,5 +1,6 @@ package ecdar.presentations; +import com.jfoenix.controls.JFXComboBox; import com.jfoenix.controls.JFXDialog; import ecdar.controllers.BackendOptionsDialogController; import ecdar.controllers.SimulationInitializationDialogController; @@ -14,4 +15,5 @@ public SimulationInitializationDialogPresentation() { public SimulationInitializationDialogController getController() { return controller; } + } diff --git a/src/main/resources/ecdar/presentations/SimulationInitializationDialogPresentation.fxml b/src/main/resources/ecdar/presentations/SimulationInitializationDialogPresentation.fxml index 332e082d..6df548fa 100644 --- a/src/main/resources/ecdar/presentations/SimulationInitializationDialogPresentation.fxml +++ b/src/main/resources/ecdar/presentations/SimulationInitializationDialogPresentation.fxml @@ -36,7 +36,7 @@ - +
From a8bfef6d1de63919df92d159573b26992df170b3 Mon Sep 17 00:00:00 2001 From: WassawRoki <56611129+WassawRoki@users.noreply.github.com> Date: Thu, 3 Nov 2022 13:57:43 +0100 Subject: [PATCH 042/158] Clena up --- .../SimulationInitializationDialogController.java | 13 ++++++------- .../java/ecdar/controllers/SimulatorController.java | 2 +- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/main/java/ecdar/controllers/SimulationInitializationDialogController.java b/src/main/java/ecdar/controllers/SimulationInitializationDialogController.java index f085a649..f1e3d1a0 100644 --- a/src/main/java/ecdar/controllers/SimulationInitializationDialogController.java +++ b/src/main/java/ecdar/controllers/SimulationInitializationDialogController.java @@ -18,19 +18,18 @@ public class SimulationInitializationDialogController implements Initializable { public void GetListOfComponentsToSimulate(){ //Function gets list of components to simulation - String componentsToSimulate =simulationComboBox.getSelectionModel().getSelectedItem(); - - //componentsToSimulate = componentsToSimulate.replace("([\\w]*)([^\\w])",""); - + String componentsToSimulate = simulationComboBox.getSelectionModel().getSelectedItem(); + //filters out all components by ignoring operators. Pattern pattern = Pattern.compile("([\\w]*)", Pattern.CASE_INSENSITIVE); Matcher matcher = pattern.matcher(componentsToSimulate); List listOfComponents = new ArrayList<>(); + //Adds all found components to list. while(matcher.find()){ - if(matcher.group().length() != 0) - {listOfComponents.add(matcher.group());} + if(matcher.group().length() != 0) { + listOfComponents.add(matcher.group()); + } } - ListOfComponents = listOfComponents; } public void initialize(URL location, ResourceBundle resources) { diff --git a/src/main/java/ecdar/controllers/SimulatorController.java b/src/main/java/ecdar/controllers/SimulatorController.java index bb58258e..fb7f07d4 100644 --- a/src/main/java/ecdar/controllers/SimulatorController.java +++ b/src/main/java/ecdar/controllers/SimulatorController.java @@ -97,6 +97,7 @@ private List findComponentsInCurrentSimulation(List queryComp List components = new ArrayList<>(); components = Ecdar.getProject().getComponents(); + //Matches query components against with existing components and adds them to simulation List SelectedComponents = new ArrayList<>(); for(Component comp:components) { for(String componentInQuery : queryComponents) { @@ -104,7 +105,6 @@ private List findComponentsInCurrentSimulation(List queryComp SelectedComponents.add(comp); } } - } return SelectedComponents; } From a2828cd738f26d5301f27e880da9509a765be367 Mon Sep 17 00:00:00 2001 From: Sigurd Date: Thu, 3 Nov 2022 14:01:16 +0100 Subject: [PATCH 043/158] WIP - Consume state in failing query --- src/main/java/ecdar/abstractions/Query.java | 26 ++++++++++++++++++- src/main/java/ecdar/backend/QueryHandler.java | 8 +++++- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/src/main/java/ecdar/abstractions/Query.java b/src/main/java/ecdar/abstractions/Query.java index bb7863a2..85d25ff8 100644 --- a/src/main/java/ecdar/abstractions/Query.java +++ b/src/main/java/ecdar/abstractions/Query.java @@ -1,14 +1,17 @@ package ecdar.abstractions; +import EcdarProtoBuf.ComponentProtos; +import EcdarProtoBuf.ObjectProtos; import ecdar.Ecdar; import ecdar.backend.*; import ecdar.controllers.EcdarController; -import ecdar.utility.helpers.StringValidator; +import ecdar.utility.colors.Color; import ecdar.utility.serialize.Serializable; import com.google.gson.JsonObject; import javafx.application.Platform; import javafx.beans.property.*; +import java.io.FilenameFilter; import java.util.function.Consumer; public class Query implements Serializable { @@ -23,6 +26,7 @@ public class Query implements Serializable { private final SimpleBooleanProperty isPeriodic = new SimpleBooleanProperty(false); private final ObjectProperty queryState = new SimpleObjectProperty<>(QueryState.UNKNOWN); private final ObjectProperty type = new SimpleObjectProperty<>(); + private final StringProperty failingLocation = new SimpleStringProperty(""); private BackendInstance backend; @@ -57,6 +61,15 @@ public class Query implements Serializable { } }; + private final Consumer stateConsumer = (state) -> { + for (Component c : Ecdar.getProject().getComponents()) { + if (query.getValue().equals(c.getName())) { + Location location = c.findLocation(state.getLocationTuple().getLocations(0).getId()); + location.setColor(Color.RED); + } + } + }; + public Query(final String query, final String comment, final QueryState queryState) { this.setQuery(query); this.comment.set(comment); @@ -84,6 +97,14 @@ public void setQueryState(final QueryState queryState) { this.queryState.set(queryState); } + public void setFailingLocation(final String location) { + this.failingLocation.set(location); + } + + public String getFailingLocation() { + return this.failingLocation.get(); + } + public ObjectProperty queryStateProperty() { return queryState; } @@ -153,6 +174,9 @@ public Consumer getSuccessConsumer() { public Consumer getFailureConsumer() { return failureConsumer; } + public Consumer getStateConsumer() { + return stateConsumer; + } @Override public JsonObject serialize() { diff --git a/src/main/java/ecdar/backend/QueryHandler.java b/src/main/java/ecdar/backend/QueryHandler.java index 5f76ce1e..671f77ae 100644 --- a/src/main/java/ecdar/backend/QueryHandler.java +++ b/src/main/java/ecdar/backend/QueryHandler.java @@ -100,6 +100,7 @@ public void closeAllBackendConnections() throws IOException { private void handleQueryResponse(QueryProtos.QueryResponse value, Query query) { // If the query has been cancelled, ignore the result if (query.getQueryState() == QueryState.UNKNOWN) return; + System.out.println(value); switch (value.getResponseCase()) { case QUERY_OK: @@ -112,7 +113,8 @@ private void handleQueryResponse(QueryProtos.QueryResponse value, Query query) { } else { query.setQueryState(QueryState.ERROR); query.getFailureConsumer().accept(new BackendException.QueryErrorException(queryOk.getRefinement().getReason())); - // query.getSuccessConsumer().accept(false); + query.getSuccessConsumer().accept(false); + query.getStateConsumer().accept(value.getQueryOk().getConsistency().getState()); } break; @@ -124,6 +126,7 @@ private void handleQueryResponse(QueryProtos.QueryResponse value, Query query) { query.setQueryState(QueryState.ERROR); query.getFailureConsumer().accept(new BackendException.QueryErrorException(queryOk.getConsistency().getReason())); query.getSuccessConsumer().accept(false); + query.getStateConsumer().accept(value.getQueryOk().getConsistency().getState()); } break; @@ -135,6 +138,7 @@ private void handleQueryResponse(QueryProtos.QueryResponse value, Query query) { query.setQueryState(QueryState.ERROR); query.getFailureConsumer().accept(new BackendException.QueryErrorException(queryOk.getDeterminism().getReason())); query.getSuccessConsumer().accept(false); + query.getStateConsumer().accept(value.getQueryOk().getConsistency().getState()); } break; @@ -146,6 +150,7 @@ private void handleQueryResponse(QueryProtos.QueryResponse value, Query query) { query.setQueryState(QueryState.ERROR); query.getFailureConsumer().accept(new BackendException.QueryErrorException(queryOk.getImplementation().getReason())); query.getSuccessConsumer().accept(false); + query.getStateConsumer().accept(value.getQueryOk().getConsistency().getState()); } break; @@ -157,6 +162,7 @@ private void handleQueryResponse(QueryProtos.QueryResponse value, Query query) { query.setQueryState(QueryState.ERROR); query.getFailureConsumer().accept(new BackendException.QueryErrorException(queryOk.getReachability().getReason())); query.getSuccessConsumer().accept(false); + query.getStateConsumer().accept(value.getQueryOk().getConsistency().getState()); } break; From 050ba26d25cadf1a5566c8e094d509281bf79f00 Mon Sep 17 00:00:00 2001 From: WassawRoki <56611129+WassawRoki@users.noreply.github.com> Date: Thu, 3 Nov 2022 14:30:07 +0100 Subject: [PATCH 044/158] review fixes --- .../SimulationInitializationDialogController.java | 6 ++++-- src/main/java/ecdar/controllers/SimulatorController.java | 8 ++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/main/java/ecdar/controllers/SimulationInitializationDialogController.java b/src/main/java/ecdar/controllers/SimulationInitializationDialogController.java index f1e3d1a0..368a00e8 100644 --- a/src/main/java/ecdar/controllers/SimulationInitializationDialogController.java +++ b/src/main/java/ecdar/controllers/SimulationInitializationDialogController.java @@ -15,9 +15,11 @@ public class SimulationInitializationDialogController implements Initializable { public JFXButton startButton; public static List ListOfComponents = new ArrayList<>(); - + /** + * Function gets list of components to simulation + * and saves it in the public static ListOfComponents + */ public void GetListOfComponentsToSimulate(){ - //Function gets list of components to simulation String componentsToSimulate = simulationComboBox.getSelectionModel().getSelectedItem(); //filters out all components by ignoring operators. Pattern pattern = Pattern.compile("([\\w]*)", Pattern.CASE_INSENSITIVE); diff --git a/src/main/java/ecdar/controllers/SimulatorController.java b/src/main/java/ecdar/controllers/SimulatorController.java index fb7f07d4..18423f20 100644 --- a/src/main/java/ecdar/controllers/SimulatorController.java +++ b/src/main/java/ecdar/controllers/SimulatorController.java @@ -55,10 +55,10 @@ public void willShow() { shouldSimulationBeReset = false; } - //if (!firstTimeInSimulator && !new HashSet<>(overviewPresentation.getController().getComponentObservableList()) - // .containsAll(findComponentsInCurrentSimulation())) { - // shouldSimulationBeReset = true; - //} + if (!firstTimeInSimulator && !new HashSet<>(overviewPresentation.getController().getComponentObservableList()) + .containsAll(findComponentsInCurrentSimulation(SimulationInitializationDialogController.ListOfComponents))) { + shouldSimulationBeReset = true; + } if (shouldSimulationBeReset || firstTimeInSimulator) { From 0b8e3c33ee100b6aba1d9c3ffb3e69f90ca0ab6f Mon Sep 17 00:00:00 2001 From: APaludan Date: Thu, 3 Nov 2022 21:40:54 +0100 Subject: [PATCH 045/158] initialize sim --- .../java/ecdar/backend/SimulationHandler.java | 16 ++++++++-------- .../java/ecdar/controllers/EcdarController.java | 2 +- .../controllers/SimulatorOverviewController.java | 1 + .../controllers/TracePaneElementController.java | 3 +++ 4 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/main/java/ecdar/backend/SimulationHandler.java b/src/main/java/ecdar/backend/SimulationHandler.java index 4da6b04d..d44ebf22 100755 --- a/src/main/java/ecdar/backend/SimulationHandler.java +++ b/src/main/java/ecdar/backend/SimulationHandler.java @@ -4,6 +4,7 @@ import EcdarProtoBuf.QueryProtos; import ecdar.Ecdar; import ecdar.abstractions.*; +import ecdar.controllers.SimulationInitializationDialogController; import ecdar.simulation.SimulationState; import ecdar.simulation.SimulationStateSuccessor; import io.grpc.stub.StreamObserver; @@ -27,6 +28,7 @@ */ public class SimulationHandler { public static final String QUERY_PREFIX = "Query: "; + public String composition; private ObjectProperty currentConcreteState = new SimpleObjectProperty<>(); private ObjectProperty initialConcreteState = new SimpleObjectProperty<>(); private ObjectProperty currentTime = new SimpleObjectProperty<>(); @@ -42,7 +44,7 @@ public class SimulationHandler { * For now the string is prefixed with {@link #QUERY_PREFIX} when doing a query simulation * and kept empty when doing system simulations */ - private String currentSimulation = ""; + public String currentSimulation = ""; private final ObservableMap simulationVariables = FXCollections.observableHashMap(); private final ObservableMap simulationClocks = FXCollections.observableHashMap(); @@ -91,7 +93,7 @@ private void initializeSimulation() { //Preparation for the simulation this.system = getSystem(); - this.currentConcreteState.get().setTime(currentTime.getValue()); + //this.currentConcreteState.get().setTime(currentTime.getValue()); this.initialTransitions.clear(); this.successor = null; } @@ -132,14 +134,12 @@ public void onCompleted() { var comInfo = ComponentProtos.ComponentsInfo.newBuilder(); for (Component c : Ecdar.getProject().getComponents()) { - if (currentSimulation.contains(c.getName())) { - comInfo.addComponents(ComponentProtos.Component.newBuilder().setJson(c.serialize().toString()).build()); - } + comInfo.addComponents(ComponentProtos.Component.newBuilder().setJson(c.serialize().toString()).build()); } comInfo.setComponentsHash(comInfo.getComponentsList().hashCode()); var simStartRequest = QueryProtos.SimulationStartRequest.newBuilder(); var simInfo = QueryProtos.SimulationInfo.newBuilder() - .setComponentComposition(currentSimulation) + .setComponentComposition(composition) .setComponentsInfo(comInfo); simStartRequest.setSimulationInfo(simInfo); backendConnection.getStub().withDeadlineAfter(this.backendDriver.getResponseDeadline(), TimeUnit.MILLISECONDS) @@ -293,7 +293,7 @@ public void nextStep(final ecdar.simulation.Transition transition, final BigDeci */ private void updateAllValues() { currentTime.set(currentTime.get().add(delay)); - successor.getState().setTime(currentTime.get()); + //successor.getState().setTime(currentTime.get()); setSimVarAndClocks(); } @@ -406,7 +406,7 @@ public ObservableMap getSimulationClocks() { */ public SimulationState getInitialConcreteState() { // ToDo: Implement - return null; + return initialConcreteState.get(); } public ObjectProperty initialConcreteStateProperty() { diff --git a/src/main/java/ecdar/controllers/EcdarController.java b/src/main/java/ecdar/controllers/EcdarController.java index ab6efe6f..04a108f4 100644 --- a/src/main/java/ecdar/controllers/EcdarController.java +++ b/src/main/java/ecdar/controllers/EcdarController.java @@ -293,6 +293,7 @@ private void initializeDialogs() { simulationInitializationDialog.getController().startButton.setOnMouseClicked(event -> { // ToDo NIELS: Start simulation of selected query + Ecdar.getSimulationHandler().composition = simulationInitializationDialog.getController().simulationComboBox.getSelectionModel().getSelectedItem(); currentMode.setValue(Mode.Simulator); simulationInitializationDialog.close(); }); @@ -665,7 +666,6 @@ private void initializeViewMenu() { if (!Ecdar.getSimulationHandler().isSimulationRunning()) { ArrayList queryOptions = Ecdar.getProject().getQueries().stream().map(Query::getQuery).collect(Collectors.toCollection(ArrayList::new)); - if (!simulationInitializationDialog.getController().simulationComboBox.getItems().equals(queryOptions)) { simulationInitializationDialog.getController().simulationComboBox.getItems().setAll(queryOptions); } diff --git a/src/main/java/ecdar/controllers/SimulatorOverviewController.java b/src/main/java/ecdar/controllers/SimulatorOverviewController.java index 9af4af76..90386d82 100644 --- a/src/main/java/ecdar/controllers/SimulatorOverviewController.java +++ b/src/main/java/ecdar/controllers/SimulatorOverviewController.java @@ -366,6 +366,7 @@ public void unhighlightProcesses() { * @param state The state with the locations to highlight */ public void highlightProcessState(final SimulationState state) { + if (state == null) return; for (int i = 0; i < state.getLocations().size(); i++) { final Pair loc = state.getLocations().get(i); diff --git a/src/main/java/ecdar/controllers/TracePaneElementController.java b/src/main/java/ecdar/controllers/TracePaneElementController.java index 1c2e82fa..5fe116bd 100755 --- a/src/main/java/ecdar/controllers/TracePaneElementController.java +++ b/src/main/java/ecdar/controllers/TracePaneElementController.java @@ -151,6 +151,9 @@ private void insertTraceState(final SimulationState state, final boolean shouldA * @return A string representing the state */ private String traceString(SimulationState state) { + if (state == null) { + return "Initial state"; + } StringBuilder title = new StringBuilder("("); int length = state.getLocations().size(); for (int i = 0; i < length; i++) { From e00cfada026c9aa27083014f8c4e175eea45068d Mon Sep 17 00:00:00 2001 From: APaludan Date: Fri, 4 Nov 2022 09:11:34 +0100 Subject: [PATCH 046/158] det virker en lille smule nu --- .../java/ecdar/backend/SimulationHandler.java | 24 +++++++++---------- .../ecdar/simulation/SimulationState.java | 4 +++- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/main/java/ecdar/backend/SimulationHandler.java b/src/main/java/ecdar/backend/SimulationHandler.java index d44ebf22..4dfa14f9 100755 --- a/src/main/java/ecdar/backend/SimulationHandler.java +++ b/src/main/java/ecdar/backend/SimulationHandler.java @@ -1,10 +1,10 @@ package ecdar.backend; import EcdarProtoBuf.ComponentProtos; +import EcdarProtoBuf.ObjectProtos; import EcdarProtoBuf.QueryProtos; import ecdar.Ecdar; import ecdar.abstractions.*; -import ecdar.controllers.SimulationInitializationDialogController; import ecdar.simulation.SimulationState; import ecdar.simulation.SimulationStateSuccessor; import io.grpc.stub.StreamObserver; @@ -37,7 +37,6 @@ public class SimulationHandler { private EcdarSystem system; private SimulationStateSuccessor successor; private int numberOfSteps; - private SimulationStepResponse currentResponse; /** * A string to keep track what is currently being simulated @@ -90,7 +89,7 @@ private void initializeSimulation() { this.currentConcreteState.set(getInitialConcreteState()); this.initialConcreteState.set(getInitialConcreteState()); this.currentTime = new SimpleObjectProperty<>(BigDecimal.ZERO); - + //Preparation for the simulation this.system = getSystem(); //this.currentConcreteState.get().setTime(currentTime.getValue()); @@ -103,22 +102,22 @@ private void initializeSimulation() { */ public void initialStep() { initializeSimulation(); - + final SimulationState currentState = currentConcreteState.get(); successor = getStateSuccessor(); - + GrpcRequest request = new GrpcRequest(backendConnection -> { StreamObserver responseObserver = new StreamObserver<>() { @Override public void onNext(QueryProtos.SimulationStepResponse value) { System.out.println(value); - currentResponse = value; } - + @Override public void onError(Throwable t) { System.out.println(t.getMessage()); - + Ecdar.showToast("Could not start simulation"); + // Release backend connection backendDriver.addBackendConnection(backendConnection); connections.remove(backendConnection); @@ -145,20 +144,21 @@ public void onCompleted() { backendConnection.getStub().withDeadlineAfter(this.backendDriver.getResponseDeadline(), TimeUnit.MILLISECONDS) .startSimulation(simStartRequest.build(), responseObserver); }, BackendHelper.getDefaultBackendInstance()); - + backendDriver.addRequestToExecutionQueue(request); - + //Save the previous states, and get the new currentConcreteState.set(successor.getState()); this.traceLog.add(currentState); numberOfSteps++; - + //Updates the transitions available availableTransitions.addAll(FXCollections.observableArrayList(successor.getTransitions())); initialTransitions.addAll(availableTransitions); updateAllValues(); + } - + /** * Resets the simulation to the initial location * where the SimulationState is the {@link SimulationHandler#initialConcreteState}, when there are diff --git a/src/main/java/ecdar/simulation/SimulationState.java b/src/main/java/ecdar/simulation/SimulationState.java index 47144a9a..935ec5de 100644 --- a/src/main/java/ecdar/simulation/SimulationState.java +++ b/src/main/java/ecdar/simulation/SimulationState.java @@ -13,7 +13,9 @@ public class SimulationState { public SimulationState(ObjectProtos.State protoBufState) { locations = new ArrayList<>(); - // ToDo: Initialize with correct locations from protoBuf response + for (ObjectProtos.Location location : protoBufState.getLocationTuple().getLocationsList()) { + locations.add(new Pair<>(location.getId(), location.getSpecificComponent().getComponentName())); + } } public void setTime(BigDecimal value) { From 464e4df5ff34baa66ed85ca6c22b793245982f34 Mon Sep 17 00:00:00 2001 From: APaludan Date: Fri, 4 Nov 2022 09:16:47 +0100 Subject: [PATCH 047/158] Update SimulationHandler.java --- src/main/java/ecdar/backend/SimulationHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ecdar/backend/SimulationHandler.java b/src/main/java/ecdar/backend/SimulationHandler.java index 4dfa14f9..d58d7b3b 100755 --- a/src/main/java/ecdar/backend/SimulationHandler.java +++ b/src/main/java/ecdar/backend/SimulationHandler.java @@ -43,7 +43,7 @@ public class SimulationHandler { * For now the string is prefixed with {@link #QUERY_PREFIX} when doing a query simulation * and kept empty when doing system simulations */ - public String currentSimulation = ""; + private String currentSimulation = ""; private final ObservableMap simulationVariables = FXCollections.observableHashMap(); private final ObservableMap simulationClocks = FXCollections.observableHashMap(); From aec21441152f10d3642514d8bd8fad37adb65932 Mon Sep 17 00:00:00 2001 From: Sigurd Date: Fri, 4 Nov 2022 11:23:55 +0100 Subject: [PATCH 048/158] Now sets failing location for all locations in the response --- src/main/java/ecdar/abstractions/Location.java | 14 ++++++++++++++ src/main/java/ecdar/abstractions/Query.java | 5 +++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/main/java/ecdar/abstractions/Location.java b/src/main/java/ecdar/abstractions/Location.java index dd3d0ebe..f0e760d7 100644 --- a/src/main/java/ecdar/abstractions/Location.java +++ b/src/main/java/ecdar/abstractions/Location.java @@ -58,6 +58,7 @@ public class Location implements Circular, Serializable, Nearable, DropDownMenu. private final ObjectProperty reachability = new SimpleObjectProperty<>(); private final SimpleBooleanProperty isLocked = new SimpleBooleanProperty(false); + private boolean failing; public Location() { } @@ -468,6 +469,19 @@ public void deserialize(final JsonObject json) { public String generateNearString() { return "Location " + (!Strings.isNullOrEmpty(getNickname()) ? (getNickname() + " (" + getId() + ")") : getId()); } + + /** + * Sets whether this location failed for the last query + * @param bool if a query responded failure with the location, bool should be true. + */ + public void setFailing(boolean bool) { + this.failing = bool; + } + + public boolean getFailing() { + return this.failing; + } + public enum Type { NORMAL, INITIAL, UNIVERSAL, INCONSISTENT } diff --git a/src/main/java/ecdar/abstractions/Query.java b/src/main/java/ecdar/abstractions/Query.java index 85d25ff8..6e97fe4c 100644 --- a/src/main/java/ecdar/abstractions/Query.java +++ b/src/main/java/ecdar/abstractions/Query.java @@ -64,8 +64,9 @@ public class Query implements Serializable { private final Consumer stateConsumer = (state) -> { for (Component c : Ecdar.getProject().getComponents()) { if (query.getValue().equals(c.getName())) { - Location location = c.findLocation(state.getLocationTuple().getLocations(0).getId()); - location.setColor(Color.RED); + for (ObjectProtos.Location location : state.getLocationTuple().getLocationsList()) { + c.findLocation(location.getId()).setFailing(true); + }; } } }; From bbcc2eec668690eff14bbda42fe1b711691a5706 Mon Sep 17 00:00:00 2001 From: Ibra4i <82802664+Ibra4i@users.noreply.github.com> Date: Fri, 28 Oct 2022 09:33:56 +0200 Subject: [PATCH 049/158] Variante, nickname sign replacenment <= >= :) --- src/main/java/ecdar/backend/BackendDriver.java | 1 + src/main/java/ecdar/issues/Error.java | 1 + .../presentations/LocationPresentation.java | 4 ++++ .../ecdar/presentations/TagPresentation.java | 17 +++++++++++++++++ 4 files changed, 23 insertions(+) diff --git a/src/main/java/ecdar/backend/BackendDriver.java b/src/main/java/ecdar/backend/BackendDriver.java index a3ab0e1a..a7f2fff9 100644 --- a/src/main/java/ecdar/backend/BackendDriver.java +++ b/src/main/java/ecdar/backend/BackendDriver.java @@ -341,6 +341,7 @@ private void handleQueryBackendError(Throwable t, ExecutableQuery executableQuer // Each error starts with a capitalized description of the error equal to the gRPC error type encountered String errorType = t.getMessage().split(":\\s+", 2)[0]; + switch (errorType) { case "CANCELLED": executableQuery.queryListener.getQuery().setQueryState(QueryState.ERROR); diff --git a/src/main/java/ecdar/issues/Error.java b/src/main/java/ecdar/issues/Error.java index 197842eb..f9aedc49 100644 --- a/src/main/java/ecdar/issues/Error.java +++ b/src/main/java/ecdar/issues/Error.java @@ -15,5 +15,6 @@ protected Material getIcon() { public Error(final Predicate presentPredicate, final T subject, final Observable... observables) { super(presentPredicate, subject, observables); + } } diff --git a/src/main/java/ecdar/presentations/LocationPresentation.java b/src/main/java/ecdar/presentations/LocationPresentation.java index 8d2a55af..0fd08d48 100644 --- a/src/main/java/ecdar/presentations/LocationPresentation.java +++ b/src/main/java/ecdar/presentations/LocationPresentation.java @@ -182,6 +182,10 @@ private void initializeTags() { } controller.nicknameTag.replaceSpace(); + controller.invariantTag.replaceSigns(); + controller.nicknameTag.replaceSigns(); + + // Set the layout from the model (if they are not both 0) final Location loc = controller.getLocation(); diff --git a/src/main/java/ecdar/presentations/TagPresentation.java b/src/main/java/ecdar/presentations/TagPresentation.java index d0075be8..f9efd73a 100644 --- a/src/main/java/ecdar/presentations/TagPresentation.java +++ b/src/main/java/ecdar/presentations/TagPresentation.java @@ -283,6 +283,23 @@ public void replaceSpace() { initializeTextAid((JFXTextField) lookup("#textField")); } + void initializeTextAid2(JFXTextField textField) { + textField.textProperty().addListener((obs, oldText, newText) -> { + if (newText.contains("<=")) { + final String updatedString = newText.replace("<=", "\u2264"); + textField.setText(updatedString); + } + if (newText.contains(">=")) { + final String updatedString = newText.replace(">=", "\u2265"); + textField.setText(updatedString); + } + }); + } + public void replaceSigns() { + initializeTextAid2((JFXTextField) lookup("#textField")); + } + + public void requestTextFieldFocus() { final JFXTextField textField = (JFXTextField) lookup("#textField"); Platform.runLater(textField::requestFocus); From b8c9e5eb21dc9dae276ef4d8d8c0719132fa21af Mon Sep 17 00:00:00 2001 From: Emilie Steinmann Date: Fri, 28 Oct 2022 10:41:00 +0200 Subject: [PATCH 050/158] Re-replace unicode to <= and >= before serializing --- src/main/java/ecdar/abstractions/Location.java | 2 +- src/main/java/ecdar/backend/BackendDriver.java | 1 - src/main/java/ecdar/issues/Error.java | 1 - src/main/java/ecdar/presentations/LocationPresentation.java | 3 +-- 4 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/main/java/ecdar/abstractions/Location.java b/src/main/java/ecdar/abstractions/Location.java index b490f5b9..02941b4c 100644 --- a/src/main/java/ecdar/abstractions/Location.java +++ b/src/main/java/ecdar/abstractions/Location.java @@ -161,7 +161,7 @@ public StringProperty idProperty() { } public String getInvariant() { - return invariant.get(); + return invariant.get().replace("\u2264", "<=").replace("\u2665", ">="); } public void setInvariant(final String invariant) { diff --git a/src/main/java/ecdar/backend/BackendDriver.java b/src/main/java/ecdar/backend/BackendDriver.java index a7f2fff9..a3ab0e1a 100644 --- a/src/main/java/ecdar/backend/BackendDriver.java +++ b/src/main/java/ecdar/backend/BackendDriver.java @@ -341,7 +341,6 @@ private void handleQueryBackendError(Throwable t, ExecutableQuery executableQuer // Each error starts with a capitalized description of the error equal to the gRPC error type encountered String errorType = t.getMessage().split(":\\s+", 2)[0]; - switch (errorType) { case "CANCELLED": executableQuery.queryListener.getQuery().setQueryState(QueryState.ERROR); diff --git a/src/main/java/ecdar/issues/Error.java b/src/main/java/ecdar/issues/Error.java index f9aedc49..197842eb 100644 --- a/src/main/java/ecdar/issues/Error.java +++ b/src/main/java/ecdar/issues/Error.java @@ -15,6 +15,5 @@ protected Material getIcon() { public Error(final Predicate presentPredicate, final T subject, final Observable... observables) { super(presentPredicate, subject, observables); - } } diff --git a/src/main/java/ecdar/presentations/LocationPresentation.java b/src/main/java/ecdar/presentations/LocationPresentation.java index 0fd08d48..b89b5536 100644 --- a/src/main/java/ecdar/presentations/LocationPresentation.java +++ b/src/main/java/ecdar/presentations/LocationPresentation.java @@ -182,9 +182,8 @@ private void initializeTags() { } controller.nicknameTag.replaceSpace(); - controller.invariantTag.replaceSigns(); controller.nicknameTag.replaceSigns(); - + controller.invariantTag.replaceSigns(); // Set the layout from the model (if they are not both 0) From 2610d4f2b1d584682d9baa6370fc10c1460ce1a5 Mon Sep 17 00:00:00 2001 From: Ibra4i <82802664+Ibra4i@users.noreply.github.com> Date: Fri, 4 Nov 2022 09:22:46 +0100 Subject: [PATCH 051/158] Replace Signes to guard fields W --- .../java/ecdar/abstractions/DisplayableEdge.java | 15 ++++++++++++--- src/main/java/ecdar/abstractions/Edge.java | 1 + .../java/ecdar/controllers/EdgeController.java | 1 - .../ecdar/presentations/EdgePresentation.java | 1 - .../ecdar/presentations/NailPresentation.java | 2 +- .../ecdar/presentations/NailPresentation.fxml | 2 +- 6 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/main/java/ecdar/abstractions/DisplayableEdge.java b/src/main/java/ecdar/abstractions/DisplayableEdge.java index 7a5d62d2..0c6b08f2 100644 --- a/src/main/java/ecdar/abstractions/DisplayableEdge.java +++ b/src/main/java/ecdar/abstractions/DisplayableEdge.java @@ -80,13 +80,22 @@ public void setSelect(final String select) { public StringProperty selectProperty() { return select; } - + // Husk, convert back to <=, >= public String getGuard() { - return guard.get(); + return guard.get().replace("\u2264", "<=").replace("\u2665", ">="); + //return guard.get(); } public void setGuard(final String guard) { - this.guard.set(guard); + /*if(guard.contains("<=")){ + final String updatedString = guard.replace("<=", "\u2264"); + guardProperty().setValue(updatedString); + } + if (guard.contains(">=")) { + final String updatedString = guard.replace(">=", "\u2265"); + guardProperty().setValue(updatedString); + }*/ + this.guard.set(guard); } public StringProperty guardProperty() { diff --git a/src/main/java/ecdar/abstractions/Edge.java b/src/main/java/ecdar/abstractions/Edge.java index 995e2ce8..eea50f04 100644 --- a/src/main/java/ecdar/abstractions/Edge.java +++ b/src/main/java/ecdar/abstractions/Edge.java @@ -161,6 +161,7 @@ public JsonObject serialize() { result.addProperty(STATUS, ioStatus.get().toString()); result.addProperty(SELECT, getSelect()); + //result.addProperty(GUARD, getGuard().replace("\u2264", "<=").replace("\u2665", ">=")); result.addProperty(GUARD, getGuard()); result.addProperty(UPDATE, getUpdate()); result.addProperty(SYNC, getSync()); diff --git a/src/main/java/ecdar/controllers/EdgeController.java b/src/main/java/ecdar/controllers/EdgeController.java index 3610934f..c2135c4c 100644 --- a/src/main/java/ecdar/controllers/EdgeController.java +++ b/src/main/java/ecdar/controllers/EdgeController.java @@ -73,7 +73,6 @@ public void initialize(final URL location, final ResourceBundle resources) { } }); }); - initializeLinksListener(); ensureNailsInFront(); initializeSelectListener(); diff --git a/src/main/java/ecdar/presentations/EdgePresentation.java b/src/main/java/ecdar/presentations/EdgePresentation.java index 993743bd..d2a93642 100644 --- a/src/main/java/ecdar/presentations/EdgePresentation.java +++ b/src/main/java/ecdar/presentations/EdgePresentation.java @@ -15,7 +15,6 @@ public class EdgePresentation extends Group { public EdgePresentation(final DisplayableEdge edge, final Component component) { controller = new EcdarFXMLLoader().loadAndGetController("EdgePresentation.fxml", this); - controller.setEdge(edge); this.edge.bind(controller.edgeProperty()); diff --git a/src/main/java/ecdar/presentations/NailPresentation.java b/src/main/java/ecdar/presentations/NailPresentation.java index 2ab5823f..9a567f95 100644 --- a/src/main/java/ecdar/presentations/NailPresentation.java +++ b/src/main/java/ecdar/presentations/NailPresentation.java @@ -51,6 +51,7 @@ public NailPresentation(final Nail nail, final DisplayableEdge edge, final Compo } controller.propertyTag.setTranslateX(10); + controller.propertyTag.replaceSigns(); controller.propertyTag.setTranslateY(-controller.propertyTag.getHeight()); this.getChildren().add(controller.propertyTag); @@ -74,7 +75,6 @@ private void initializeRadius() { radiusUpdater.accept(controller.getNail().getPropertyType()); } - private void initializePropertyTag() { final TagPresentation propertyTag = controller.propertyTag; final Line propertyTagLine = controller.propertyTagLine; diff --git a/src/main/resources/ecdar/presentations/NailPresentation.fxml b/src/main/resources/ecdar/presentations/NailPresentation.fxml index e0f80e07..63b19881 100644 --- a/src/main/resources/ecdar/presentations/NailPresentation.fxml +++ b/src/main/resources/ecdar/presentations/NailPresentation.fxml @@ -10,7 +10,7 @@ fx:controller="ecdar.controllers.NailController" fx:id="root"> - + From 820e90045a4e095bffaa145e5f4d31b6ea0a1871 Mon Sep 17 00:00:00 2001 From: Emilie Steinmann Date: Fri, 4 Nov 2022 09:35:40 +0100 Subject: [PATCH 052/158] lav static class to refactor code (using two methods only) --- src/main/java/ecdar/abstractions/DisplayableEdge.java | 10 +--------- src/main/java/ecdar/abstractions/Query.java | 8 -------- src/main/java/ecdar/utility/helpers/StringHelper.java | 11 +++++++++++ 3 files changed, 12 insertions(+), 17 deletions(-) create mode 100644 src/main/java/ecdar/utility/helpers/StringHelper.java diff --git a/src/main/java/ecdar/abstractions/DisplayableEdge.java b/src/main/java/ecdar/abstractions/DisplayableEdge.java index 0c6b08f2..c41ec750 100644 --- a/src/main/java/ecdar/abstractions/DisplayableEdge.java +++ b/src/main/java/ecdar/abstractions/DisplayableEdge.java @@ -82,19 +82,11 @@ public StringProperty selectProperty() { } // Husk, convert back to <=, >= public String getGuard() { - return guard.get().replace("\u2264", "<=").replace("\u2665", ">="); + return guard.get().replace("\u2264", "<=").replace("\u2265", ">="); //return guard.get(); } public void setGuard(final String guard) { - /*if(guard.contains("<=")){ - final String updatedString = guard.replace("<=", "\u2264"); - guardProperty().setValue(updatedString); - } - if (guard.contains(">=")) { - final String updatedString = guard.replace(">=", "\u2265"); - guardProperty().setValue(updatedString); - }*/ this.guard.set(guard); } diff --git a/src/main/java/ecdar/abstractions/Query.java b/src/main/java/ecdar/abstractions/Query.java index c156e9b5..805ef90e 100644 --- a/src/main/java/ecdar/abstractions/Query.java +++ b/src/main/java/ecdar/abstractions/Query.java @@ -46,14 +46,6 @@ public Query(final JsonObject jsonElement) { initializeRunQuery(); } - public static String RefinementSymbolToUnicode(String stringToReplace){ - return stringToReplace.replace(">=","\u2265").replace("<=","\u2264"); - } - - public static String UnicodeToRefinementSymbol(String stringToReplace){ - return stringToReplace.replace("\u2264","<=").replace("\u2265",">="); - } - public QueryState getQueryState() { return queryState.get(); } diff --git a/src/main/java/ecdar/utility/helpers/StringHelper.java b/src/main/java/ecdar/utility/helpers/StringHelper.java new file mode 100644 index 00000000..e158a4fb --- /dev/null +++ b/src/main/java/ecdar/utility/helpers/StringHelper.java @@ -0,0 +1,11 @@ +package ecdar.utility.helpers; + +public final class StringHelper { + public static String ConvertSymbolsToUnicode(String stringToReplace){ + return stringToReplace.replace(">=","\u2265").replace("<=","\u2264"); + } + + public static String ConvertUnicodeToSymbols(String stringToReplace){ + return stringToReplace.replace("\u2264","<=").replace("\u2265",">="); + } +} From 60d98566fbac01720b05898ee01fab83247a27e5 Mon Sep 17 00:00:00 2001 From: Emilie Steinmann Date: Fri, 4 Nov 2022 10:04:01 +0100 Subject: [PATCH 053/158] Use StringHelper for queries --- src/main/java/ecdar/abstractions/Query.java | 3 ++- src/main/java/ecdar/controllers/QueryController.java | 3 ++- src/main/java/ecdar/presentations/QueryPresentation.java | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main/java/ecdar/abstractions/Query.java b/src/main/java/ecdar/abstractions/Query.java index 805ef90e..869f7ead 100644 --- a/src/main/java/ecdar/abstractions/Query.java +++ b/src/main/java/ecdar/abstractions/Query.java @@ -3,6 +3,7 @@ import ecdar.Ecdar; import ecdar.backend.*; import ecdar.controllers.EcdarController; +import ecdar.utility.helpers.StringHelper; import ecdar.utility.helpers.StringValidator; import ecdar.utility.serialize.Serializable; import com.google.gson.JsonObject; @@ -59,7 +60,7 @@ public ObjectProperty queryStateProperty() { } public String getQuery() { - return UnicodeToRefinementSymbol(this.query.get()); + return StringHelper.ConvertUnicodeToSymbols(this.query.get()); } public void setQuery(final String query) { diff --git a/src/main/java/ecdar/controllers/QueryController.java b/src/main/java/ecdar/controllers/QueryController.java index b1da784f..6c584aa4 100644 --- a/src/main/java/ecdar/controllers/QueryController.java +++ b/src/main/java/ecdar/controllers/QueryController.java @@ -8,6 +8,7 @@ import ecdar.abstractions.QueryType; import ecdar.backend.BackendHelper; import ecdar.utility.colors.Color; +import ecdar.utility.helpers.StringHelper; import javafx.application.Platform; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.value.ObservableValue; @@ -40,7 +41,7 @@ public void initialize(URL location, ResourceBundle resources) { queryText.textProperty().addListener(new ChangeListener() { @Override public void changed(ObservableValue observable, String oldValue, String newValue) { - queryText.setText(Query.RefinementSymbolToUnicode(newValue)); + queryText.setText(StringHelper.ConvertSymbolsToUnicode(newValue)); } }); } diff --git a/src/main/java/ecdar/presentations/QueryPresentation.java b/src/main/java/ecdar/presentations/QueryPresentation.java index 3af5caed..bf2b96c9 100644 --- a/src/main/java/ecdar/presentations/QueryPresentation.java +++ b/src/main/java/ecdar/presentations/QueryPresentation.java @@ -7,6 +7,7 @@ import ecdar.controllers.QueryController; import ecdar.controllers.EcdarController; import ecdar.utility.colors.Color; +import ecdar.utility.helpers.StringHelper; import ecdar.utility.helpers.StringValidator; import javafx.application.Platform; import javafx.beans.binding.When; @@ -265,7 +266,7 @@ else if (queryState.getStatusCode() == 3) { Label label = new Label(tooltip.getText()); - JFXDialog dialog = new InformationDialogPresentation("Result from query: " + Query.RefinementSymbolToUnicode(controller.getQuery().getQuery()), label); + JFXDialog dialog = new InformationDialogPresentation("Result from query: " + StringHelper.ConvertSymbolsToUnicode(controller.getQuery().getQuery()), label); dialog.show(Ecdar.getPresentation()); }); }); From 32cb66885838d612e872f6fe139adda3123f91d3 Mon Sep 17 00:00:00 2001 From: Ibra4i <82802664+Ibra4i@users.noreply.github.com> Date: Fri, 4 Nov 2022 10:16:46 +0100 Subject: [PATCH 054/158] Refacorizing Guard/Invarient code Yes --- .../ecdar/abstractions/DisplayableEdge.java | 8 ++++---- src/main/java/ecdar/abstractions/Edge.java | 1 - src/main/java/ecdar/abstractions/Location.java | 3 ++- .../ecdar/presentations/TagPresentation.java | 18 +++++------------- .../ecdar/presentations/NailPresentation.fxml | 2 +- 5 files changed, 12 insertions(+), 20 deletions(-) diff --git a/src/main/java/ecdar/abstractions/DisplayableEdge.java b/src/main/java/ecdar/abstractions/DisplayableEdge.java index c41ec750..e2bfd7f0 100644 --- a/src/main/java/ecdar/abstractions/DisplayableEdge.java +++ b/src/main/java/ecdar/abstractions/DisplayableEdge.java @@ -6,6 +6,7 @@ import ecdar.utility.colors.Color; import ecdar.utility.helpers.Circular; import ecdar.utility.helpers.MouseCircular; +import ecdar.utility.helpers.StringHelper; import javafx.beans.property.*; import javafx.collections.FXCollections; import javafx.collections.ObservableList; @@ -80,14 +81,13 @@ public void setSelect(final String select) { public StringProperty selectProperty() { return select; } - // Husk, convert back to <=, >= + public String getGuard() { - return guard.get().replace("\u2264", "<=").replace("\u2265", ">="); - //return guard.get(); + return StringHelper.ConvertSymbolsToUnicode(guard.get()); } public void setGuard(final String guard) { - this.guard.set(guard); + this.guard.set(guard); } public StringProperty guardProperty() { diff --git a/src/main/java/ecdar/abstractions/Edge.java b/src/main/java/ecdar/abstractions/Edge.java index eea50f04..995e2ce8 100644 --- a/src/main/java/ecdar/abstractions/Edge.java +++ b/src/main/java/ecdar/abstractions/Edge.java @@ -161,7 +161,6 @@ public JsonObject serialize() { result.addProperty(STATUS, ioStatus.get().toString()); result.addProperty(SELECT, getSelect()); - //result.addProperty(GUARD, getGuard().replace("\u2264", "<=").replace("\u2665", ">=")); result.addProperty(GUARD, getGuard()); result.addProperty(UPDATE, getUpdate()); result.addProperty(SYNC, getSync()); diff --git a/src/main/java/ecdar/abstractions/Location.java b/src/main/java/ecdar/abstractions/Location.java index 02941b4c..18d4cbfd 100644 --- a/src/main/java/ecdar/abstractions/Location.java +++ b/src/main/java/ecdar/abstractions/Location.java @@ -8,6 +8,7 @@ import ecdar.utility.colors.Color; import ecdar.utility.colors.EnabledColor; import ecdar.utility.helpers.Circular; +import ecdar.utility.helpers.StringHelper; import ecdar.utility.serialize.Serializable; import com.google.common.base.Strings; import com.google.gson.Gson; @@ -161,7 +162,7 @@ public StringProperty idProperty() { } public String getInvariant() { - return invariant.get().replace("\u2264", "<=").replace("\u2665", ">="); + return StringHelper.ConvertUnicodeToSymbols(invariant.get()); } public void setInvariant(final String invariant) { diff --git a/src/main/java/ecdar/presentations/TagPresentation.java b/src/main/java/ecdar/presentations/TagPresentation.java index f9efd73a..a4d4610f 100644 --- a/src/main/java/ecdar/presentations/TagPresentation.java +++ b/src/main/java/ecdar/presentations/TagPresentation.java @@ -8,6 +8,7 @@ import ecdar.utility.helpers.LocationAware; import com.jfoenix.controls.JFXTextField; import ecdar.utility.helpers.SelectHelper; +import ecdar.utility.helpers.StringHelper; import javafx.application.Platform; import javafx.beans.binding.When; import javafx.beans.property.*; @@ -283,23 +284,14 @@ public void replaceSpace() { initializeTextAid((JFXTextField) lookup("#textField")); } - void initializeTextAid2(JFXTextField textField) { + public void replaceSigns() { + var textField = (JFXTextField) lookup("#textField"); textField.textProperty().addListener((obs, oldText, newText) -> { - if (newText.contains("<=")) { - final String updatedString = newText.replace("<=", "\u2264"); - textField.setText(updatedString); - } - if (newText.contains(">=")) { - final String updatedString = newText.replace(">=", "\u2265"); - textField.setText(updatedString); - } + textField.setText(StringHelper.ConvertSymbolsToUnicode(newText)); }); } - public void replaceSigns() { - initializeTextAid2((JFXTextField) lookup("#textField")); - } - + public void requestTextFieldFocus() { final JFXTextField textField = (JFXTextField) lookup("#textField"); Platform.runLater(textField::requestFocus); diff --git a/src/main/resources/ecdar/presentations/NailPresentation.fxml b/src/main/resources/ecdar/presentations/NailPresentation.fxml index 63b19881..e0f80e07 100644 --- a/src/main/resources/ecdar/presentations/NailPresentation.fxml +++ b/src/main/resources/ecdar/presentations/NailPresentation.fxml @@ -10,7 +10,7 @@ fx:controller="ecdar.controllers.NailController" fx:id="root"> - + From 4282b1a4bde4883e4cd4cbb07ce5867a129e7333 Mon Sep 17 00:00:00 2001 From: Ibra4i <82802664+Ibra4i@users.noreply.github.com> Date: Fri, 4 Nov 2022 10:21:38 +0100 Subject: [PATCH 055/158] Ooops! --- src/main/java/ecdar/abstractions/DisplayableEdge.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ecdar/abstractions/DisplayableEdge.java b/src/main/java/ecdar/abstractions/DisplayableEdge.java index e2bfd7f0..a93090cd 100644 --- a/src/main/java/ecdar/abstractions/DisplayableEdge.java +++ b/src/main/java/ecdar/abstractions/DisplayableEdge.java @@ -83,7 +83,7 @@ public StringProperty selectProperty() { } public String getGuard() { - return StringHelper.ConvertSymbolsToUnicode(guard.get()); + return StringHelper.ConvertUnicodeToSymbols(guard.get()); } public void setGuard(final String guard) { From 3af14355b08584d16da8a3b0c6e0b921d4ea7453 Mon Sep 17 00:00:00 2001 From: Ibra4i <82802664+Ibra4i@users.noreply.github.com> Date: Fri, 4 Nov 2022 11:23:31 +0100 Subject: [PATCH 056/158] StringHelperTest --- .../java/ecdar/utility/StringHelperTest.java | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 src/test/java/ecdar/utility/StringHelperTest.java diff --git a/src/test/java/ecdar/utility/StringHelperTest.java b/src/test/java/ecdar/utility/StringHelperTest.java new file mode 100644 index 00000000..cfe6887d --- /dev/null +++ b/src/test/java/ecdar/utility/StringHelperTest.java @@ -0,0 +1,21 @@ +package ecdar.utility; + +import ecdar.abstractions.Query; +import ecdar.abstractions.QueryState; +import ecdar.utility.helpers.StringHelper; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.platform.commons.util.StringUtils; +import org.junit.runners.Parameterized; + +public class StringHelperTest { + + @Test + public void ConvertSymbolsToUnicodeTest() { + String stringToReplace = "2 >= 4"; + String result = StringHelper.ConvertSymbolsToUnicode(stringToReplace); + String expected = "2 \u2265 4"; + + Assertions.assertEquals(expected, result); + } +} From c6b8485ccd9eb97cf843a69c6a01927abc696625 Mon Sep 17 00:00:00 2001 From: Emilie Steinmann Date: Fri, 4 Nov 2022 11:57:19 +0100 Subject: [PATCH 057/158] Parameterized test --- build.gradle | 1 + .../java/ecdar/utility/StringHelperTest.java | 11 ++++- .../StringHelperTestParameterized.java | 45 +++++++++++++++++++ src/test/java/ecdar/utility/TestRunner.java | 17 +++++++ 4 files changed, 72 insertions(+), 2 deletions(-) create mode 100644 src/test/java/ecdar/utility/StringHelperTestParameterized.java create mode 100644 src/test/java/ecdar/utility/TestRunner.java diff --git a/build.gradle b/build.gradle index 1b049e61..eb688667 100644 --- a/build.gradle +++ b/build.gradle @@ -66,6 +66,7 @@ dependencies { //GRPC: implementation "io.grpc:grpc-protobuf:${grpcVersion}" implementation "io.grpc:grpc-stub:${grpcVersion}" + testImplementation 'junit:junit:4.13.1' compileOnly "org.apache.tomcat:annotations-api:6.0.53" // examples/advanced need this for JsonFormat diff --git a/src/test/java/ecdar/utility/StringHelperTest.java b/src/test/java/ecdar/utility/StringHelperTest.java index cfe6887d..a70527be 100644 --- a/src/test/java/ecdar/utility/StringHelperTest.java +++ b/src/test/java/ecdar/utility/StringHelperTest.java @@ -3,19 +3,26 @@ import ecdar.abstractions.Query; import ecdar.abstractions.QueryState; import ecdar.utility.helpers.StringHelper; +import org.junit.Before; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.platform.commons.util.StringUtils; +import org.junit.runner.RunWith; import org.junit.runners.Parameterized; -public class StringHelperTest { +import java.util.Arrays; +import java.util.Collection; + +import static org.junit.jupiter.api.Assertions.assertEquals; +public class StringHelperTest { @Test public void ConvertSymbolsToUnicodeTest() { String stringToReplace = "2 >= 4"; String result = StringHelper.ConvertSymbolsToUnicode(stringToReplace); String expected = "2 \u2265 4"; - Assertions.assertEquals(expected, result); + assertEquals(expected, result); } } + diff --git a/src/test/java/ecdar/utility/StringHelperTestParameterized.java b/src/test/java/ecdar/utility/StringHelperTestParameterized.java new file mode 100644 index 00000000..89cf59fb --- /dev/null +++ b/src/test/java/ecdar/utility/StringHelperTestParameterized.java @@ -0,0 +1,45 @@ +package ecdar.utility; + +import ecdar.utility.helpers.StringHelper; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.util.Arrays; +import java.util.Collection; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +@RunWith(Parameterized.class) +public class StringHelperTestParameterized { + private String inputString; + private String expectedResult; + + // Each parameter should be placed as an argument here + // Every time runner triggers, it will pass the arguments + // from parameters we defined in primeNumbers() method + + public StringHelperTestParameterized(String inputString, String expectedResult) { + this.inputString = inputString; + this.expectedResult = expectedResult; + } + + @Parameterized.Parameters + public static Collection input() { + return Arrays.asList(new Object[][]{ + {"2 >= 4", "2 \u2265 4"}, + {"2 <= 4", "2 \u2264 4"}, + {"2 == 4", "2 == 4"}, + {"2 > 4", "2 \u2265 4"}, + {"2 < 4", "2 \u2264 4"} + }); + } + + @Test + public void testStringHelperConversionToUnicode() { + System.out.println(inputString + " bliver sammenlignet med " + expectedResult); + assertEquals(expectedResult, + StringHelper.ConvertSymbolsToUnicode(inputString)); + } +} diff --git a/src/test/java/ecdar/utility/TestRunner.java b/src/test/java/ecdar/utility/TestRunner.java new file mode 100644 index 00000000..24f4bbf2 --- /dev/null +++ b/src/test/java/ecdar/utility/TestRunner.java @@ -0,0 +1,17 @@ +package ecdar.utility; + +import org.junit.runner.JUnitCore; +import org.junit.runner.Result; +import org.junit.runner.notification.Failure; + +public class TestRunner { + public static void main(String[] args) { + Result result = JUnitCore.runClasses(StringHelperTestParameterized.class); + + for (Failure failure : result.getFailures()) { + System.out.println(failure.toString()); + } + + System.out.println(result.wasSuccessful()); + } +} From 2c3a197a35913e077d578fc0865d3023f27117c5 Mon Sep 17 00:00:00 2001 From: Emilie Steinmann Date: Fri, 4 Nov 2022 12:07:48 +0100 Subject: [PATCH 058/158] Parameterized test af convert unicode to symbol --- ...a => StringHelperTestSymbolToUnicode.java} | 9 ++-- .../StringHelperTestUnicodeToSymbol.java | 44 +++++++++++++++++++ src/test/java/ecdar/utility/TestRunner.java | 14 ++++-- 3 files changed, 58 insertions(+), 9 deletions(-) rename src/test/java/ecdar/utility/{StringHelperTestParameterized.java => StringHelperTestSymbolToUnicode.java} (83%) create mode 100644 src/test/java/ecdar/utility/StringHelperTestUnicodeToSymbol.java diff --git a/src/test/java/ecdar/utility/StringHelperTestParameterized.java b/src/test/java/ecdar/utility/StringHelperTestSymbolToUnicode.java similarity index 83% rename from src/test/java/ecdar/utility/StringHelperTestParameterized.java rename to src/test/java/ecdar/utility/StringHelperTestSymbolToUnicode.java index 89cf59fb..c32cfcb2 100644 --- a/src/test/java/ecdar/utility/StringHelperTestParameterized.java +++ b/src/test/java/ecdar/utility/StringHelperTestSymbolToUnicode.java @@ -1,7 +1,6 @@ package ecdar.utility; import ecdar.utility.helpers.StringHelper; -import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -12,7 +11,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; @RunWith(Parameterized.class) -public class StringHelperTestParameterized { +public class StringHelperTestSymbolToUnicode { private String inputString; private String expectedResult; @@ -20,7 +19,7 @@ public class StringHelperTestParameterized { // Every time runner triggers, it will pass the arguments // from parameters we defined in primeNumbers() method - public StringHelperTestParameterized(String inputString, String expectedResult) { + public StringHelperTestSymbolToUnicode(String inputString, String expectedResult) { this.inputString = inputString; this.expectedResult = expectedResult; } @@ -31,8 +30,8 @@ public static Collection input() { {"2 >= 4", "2 \u2265 4"}, {"2 <= 4", "2 \u2264 4"}, {"2 == 4", "2 == 4"}, - {"2 > 4", "2 \u2265 4"}, - {"2 < 4", "2 \u2264 4"} + {"2 > 4", "2 > 4"}, + {"2 < 4", "2 < 4"} }); } diff --git a/src/test/java/ecdar/utility/StringHelperTestUnicodeToSymbol.java b/src/test/java/ecdar/utility/StringHelperTestUnicodeToSymbol.java new file mode 100644 index 00000000..6872a197 --- /dev/null +++ b/src/test/java/ecdar/utility/StringHelperTestUnicodeToSymbol.java @@ -0,0 +1,44 @@ +package ecdar.utility; + +import ecdar.utility.helpers.StringHelper; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.util.Arrays; +import java.util.Collection; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +@RunWith(Parameterized.class) +public class StringHelperTestUnicodeToSymbol { + private String inputString; + private String expectedResult; + + // Each parameter should be placed as an argument here + // Every time runner triggers, it will pass the arguments + // from parameters we defined in primeNumbers() method + + public StringHelperTestUnicodeToSymbol(String inputString, String expectedResult) { + this.inputString = inputString; + this.expectedResult = expectedResult; + } + + @Parameterized.Parameters + public static Collection input() { + return Arrays.asList(new Object[][]{ + {"2 \u2265 4", "2 >= 4"}, + {"2 \u2264 4", "2 <= 4"}, + {"2 == 4", "2 == 4"}, + {"2 > 4", "2 > 4"}, + {"2 < 4", "2 < 4"} + }); + } + + @Test + public void testStringHelperConversionToUnicode() { + System.out.println(inputString + " bliver sammenlignet med " + expectedResult); + assertEquals(expectedResult, + StringHelper.ConvertUnicodeToSymbols(inputString)); + } +} diff --git a/src/test/java/ecdar/utility/TestRunner.java b/src/test/java/ecdar/utility/TestRunner.java index 24f4bbf2..7b4366c5 100644 --- a/src/test/java/ecdar/utility/TestRunner.java +++ b/src/test/java/ecdar/utility/TestRunner.java @@ -6,12 +6,18 @@ public class TestRunner { public static void main(String[] args) { - Result result = JUnitCore.runClasses(StringHelperTestParameterized.class); - - for (Failure failure : result.getFailures()) { + // ------------ Test 1: symbol to unicode ------------------ + Result result1 = JUnitCore.runClasses(StringHelperTestSymbolToUnicode.class); + for (Failure failure : result1.getFailures()) { System.out.println(failure.toString()); } + System.out.println("Result of test StringHelperTestSymbolToUnicode: " + result1.wasSuccessful()); - System.out.println(result.wasSuccessful()); + // ------------ Test 2: unicode to symbol ------------------ + Result result2 = JUnitCore.runClasses(StringHelperTestUnicodeToSymbol.class); + for (Failure failure : result2.getFailures()) { + System.out.println(failure.toString()); + } + System.out.println("Result of test StringHelperTestUnicodeToSymbol: " + result2.wasSuccessful()); } } From 629affbe3cd1e80fe7bf4603870ab05d11f82bc4 Mon Sep 17 00:00:00 2001 From: Emilie Steinmann Date: Fri, 4 Nov 2022 14:33:46 +0100 Subject: [PATCH 059/158] minor fixes for PR --- build.gradle | 1 - src/test/java/ecdar/utility/StringHelperTestSymbolToUnicode.java | 1 - src/test/java/ecdar/utility/StringHelperTestUnicodeToSymbol.java | 1 - 3 files changed, 3 deletions(-) diff --git a/build.gradle b/build.gradle index eb688667..1b049e61 100644 --- a/build.gradle +++ b/build.gradle @@ -66,7 +66,6 @@ dependencies { //GRPC: implementation "io.grpc:grpc-protobuf:${grpcVersion}" implementation "io.grpc:grpc-stub:${grpcVersion}" - testImplementation 'junit:junit:4.13.1' compileOnly "org.apache.tomcat:annotations-api:6.0.53" // examples/advanced need this for JsonFormat diff --git a/src/test/java/ecdar/utility/StringHelperTestSymbolToUnicode.java b/src/test/java/ecdar/utility/StringHelperTestSymbolToUnicode.java index c32cfcb2..4b179ee6 100644 --- a/src/test/java/ecdar/utility/StringHelperTestSymbolToUnicode.java +++ b/src/test/java/ecdar/utility/StringHelperTestSymbolToUnicode.java @@ -37,7 +37,6 @@ public static Collection input() { @Test public void testStringHelperConversionToUnicode() { - System.out.println(inputString + " bliver sammenlignet med " + expectedResult); assertEquals(expectedResult, StringHelper.ConvertSymbolsToUnicode(inputString)); } diff --git a/src/test/java/ecdar/utility/StringHelperTestUnicodeToSymbol.java b/src/test/java/ecdar/utility/StringHelperTestUnicodeToSymbol.java index 6872a197..501cd76f 100644 --- a/src/test/java/ecdar/utility/StringHelperTestUnicodeToSymbol.java +++ b/src/test/java/ecdar/utility/StringHelperTestUnicodeToSymbol.java @@ -37,7 +37,6 @@ public static Collection input() { @Test public void testStringHelperConversionToUnicode() { - System.out.println(inputString + " bliver sammenlignet med " + expectedResult); assertEquals(expectedResult, StringHelper.ConvertUnicodeToSymbols(inputString)); } From 02e3d5b74622c09883a75ec2753e65b591895631 Mon Sep 17 00:00:00 2001 From: Ibra4i <82802664+Ibra4i@users.noreply.github.com> Date: Fri, 4 Nov 2022 14:11:39 +0100 Subject: [PATCH 060/158] Deleted --- .../java/ecdar/utility/StringHelperTest.java | 28 ------------------- 1 file changed, 28 deletions(-) delete mode 100644 src/test/java/ecdar/utility/StringHelperTest.java diff --git a/src/test/java/ecdar/utility/StringHelperTest.java b/src/test/java/ecdar/utility/StringHelperTest.java deleted file mode 100644 index a70527be..00000000 --- a/src/test/java/ecdar/utility/StringHelperTest.java +++ /dev/null @@ -1,28 +0,0 @@ -package ecdar.utility; - -import ecdar.abstractions.Query; -import ecdar.abstractions.QueryState; -import ecdar.utility.helpers.StringHelper; -import org.junit.Before; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; -import org.junit.platform.commons.util.StringUtils; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; - -import java.util.Arrays; -import java.util.Collection; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -public class StringHelperTest { - @Test - public void ConvertSymbolsToUnicodeTest() { - String stringToReplace = "2 >= 4"; - String result = StringHelper.ConvertSymbolsToUnicode(stringToReplace); - String expected = "2 \u2265 4"; - - assertEquals(expected, result); - } -} - From b08d731eb688e57c922e04cf1ea1b6ac03858ad6 Mon Sep 17 00:00:00 2001 From: Mads Mogensen Date: Mon, 7 Nov 2022 13:53:47 +0100 Subject: [PATCH 061/158] Make parameterized tests run with gradle - Import Junit parameterized tests - Remove TestRunner - Merge StringHelper tests into one file. --- build.gradle | 1 + .../java/ecdar/utility/StringHelperTest.java | 45 +++++++++++++++++++ .../StringHelperTestSymbolToUnicode.java | 43 ------------------ .../StringHelperTestUnicodeToSymbol.java | 43 ------------------ src/test/java/ecdar/utility/TestRunner.java | 23 ---------- 5 files changed, 46 insertions(+), 109 deletions(-) create mode 100644 src/test/java/ecdar/utility/StringHelperTest.java delete mode 100644 src/test/java/ecdar/utility/StringHelperTestSymbolToUnicode.java delete mode 100644 src/test/java/ecdar/utility/StringHelperTestUnicodeToSymbol.java delete mode 100644 src/test/java/ecdar/utility/TestRunner.java diff --git a/build.gradle b/build.gradle index 1b049e61..d6591641 100644 --- a/build.gradle +++ b/build.gradle @@ -79,6 +79,7 @@ dependencies { testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.8.2' testImplementation group: 'org.testfx', name: 'testfx-junit', version: '4.0.15-alpha' testImplementation group: 'org.testfx', name: 'openjfx-monocle', version: 'jdk-12.0.1+2' + testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-params', version: '5.8.2' } diff --git a/src/test/java/ecdar/utility/StringHelperTest.java b/src/test/java/ecdar/utility/StringHelperTest.java new file mode 100644 index 00000000..36cef770 --- /dev/null +++ b/src/test/java/ecdar/utility/StringHelperTest.java @@ -0,0 +1,45 @@ +package ecdar.utility; + +import ecdar.utility.helpers.StringHelper; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class StringHelperTest { + @ParameterizedTest + @ValueSource(strings = { + "2 >= 4;2 \u2265 4", + "2 <= 4;2 \u2264 4", + "2 == 4;2 == 4", + "2 > 4;2 > 4", + "2 < 4;2 < 4", + }) + void convertSymbolsToUnicode(String inputAndExpectedOutput) { + var split = inputAndExpectedOutput.split(";"); + var input = split[0]; + var expectedOutput = split[1]; + + var actualOutput = StringHelper.ConvertSymbolsToUnicode(input); + + assertEquals(expectedOutput, actualOutput); + } + + @ParameterizedTest + @ValueSource(strings = { + "2 \u2265 4;2 >= 4", + "2 \u2264 4;2 <= 4", + "2 == 4;2 == 4", + "2 > 4;2 > 4", + "2 < 4;2 < 4" + }) + void convertUnicodeToSymbols(String inputAndExpectedOutput) { + var split = inputAndExpectedOutput.split(";"); + var input = split[0]; + var expectedOutput = split[1]; + + var actualOutput = StringHelper.ConvertUnicodeToSymbols(input); + + assertEquals(expectedOutput, actualOutput); + } +} diff --git a/src/test/java/ecdar/utility/StringHelperTestSymbolToUnicode.java b/src/test/java/ecdar/utility/StringHelperTestSymbolToUnicode.java deleted file mode 100644 index 4b179ee6..00000000 --- a/src/test/java/ecdar/utility/StringHelperTestSymbolToUnicode.java +++ /dev/null @@ -1,43 +0,0 @@ -package ecdar.utility; - -import ecdar.utility.helpers.StringHelper; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; - -import java.util.Arrays; -import java.util.Collection; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -@RunWith(Parameterized.class) -public class StringHelperTestSymbolToUnicode { - private String inputString; - private String expectedResult; - - // Each parameter should be placed as an argument here - // Every time runner triggers, it will pass the arguments - // from parameters we defined in primeNumbers() method - - public StringHelperTestSymbolToUnicode(String inputString, String expectedResult) { - this.inputString = inputString; - this.expectedResult = expectedResult; - } - - @Parameterized.Parameters - public static Collection input() { - return Arrays.asList(new Object[][]{ - {"2 >= 4", "2 \u2265 4"}, - {"2 <= 4", "2 \u2264 4"}, - {"2 == 4", "2 == 4"}, - {"2 > 4", "2 > 4"}, - {"2 < 4", "2 < 4"} - }); - } - - @Test - public void testStringHelperConversionToUnicode() { - assertEquals(expectedResult, - StringHelper.ConvertSymbolsToUnicode(inputString)); - } -} diff --git a/src/test/java/ecdar/utility/StringHelperTestUnicodeToSymbol.java b/src/test/java/ecdar/utility/StringHelperTestUnicodeToSymbol.java deleted file mode 100644 index 501cd76f..00000000 --- a/src/test/java/ecdar/utility/StringHelperTestUnicodeToSymbol.java +++ /dev/null @@ -1,43 +0,0 @@ -package ecdar.utility; - -import ecdar.utility.helpers.StringHelper; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; - -import java.util.Arrays; -import java.util.Collection; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -@RunWith(Parameterized.class) -public class StringHelperTestUnicodeToSymbol { - private String inputString; - private String expectedResult; - - // Each parameter should be placed as an argument here - // Every time runner triggers, it will pass the arguments - // from parameters we defined in primeNumbers() method - - public StringHelperTestUnicodeToSymbol(String inputString, String expectedResult) { - this.inputString = inputString; - this.expectedResult = expectedResult; - } - - @Parameterized.Parameters - public static Collection input() { - return Arrays.asList(new Object[][]{ - {"2 \u2265 4", "2 >= 4"}, - {"2 \u2264 4", "2 <= 4"}, - {"2 == 4", "2 == 4"}, - {"2 > 4", "2 > 4"}, - {"2 < 4", "2 < 4"} - }); - } - - @Test - public void testStringHelperConversionToUnicode() { - assertEquals(expectedResult, - StringHelper.ConvertUnicodeToSymbols(inputString)); - } -} diff --git a/src/test/java/ecdar/utility/TestRunner.java b/src/test/java/ecdar/utility/TestRunner.java deleted file mode 100644 index 7b4366c5..00000000 --- a/src/test/java/ecdar/utility/TestRunner.java +++ /dev/null @@ -1,23 +0,0 @@ -package ecdar.utility; - -import org.junit.runner.JUnitCore; -import org.junit.runner.Result; -import org.junit.runner.notification.Failure; - -public class TestRunner { - public static void main(String[] args) { - // ------------ Test 1: symbol to unicode ------------------ - Result result1 = JUnitCore.runClasses(StringHelperTestSymbolToUnicode.class); - for (Failure failure : result1.getFailures()) { - System.out.println(failure.toString()); - } - System.out.println("Result of test StringHelperTestSymbolToUnicode: " + result1.wasSuccessful()); - - // ------------ Test 2: unicode to symbol ------------------ - Result result2 = JUnitCore.runClasses(StringHelperTestUnicodeToSymbol.class); - for (Failure failure : result2.getFailures()) { - System.out.println(failure.toString()); - } - System.out.println("Result of test StringHelperTestUnicodeToSymbol: " + result2.wasSuccessful()); - } -} From 82f2969d2f2a054736c274a77068280b34241b42 Mon Sep 17 00:00:00 2001 From: Sigurd Date: Fri, 4 Nov 2022 14:08:31 +0100 Subject: [PATCH 062/158] Paint locations red on failure response. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Victor Doré Hansen --- .../java/ecdar/abstractions/Location.java | 8 ++++++-- .../presentations/LocationPresentation.java | 20 +++++++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/src/main/java/ecdar/abstractions/Location.java b/src/main/java/ecdar/abstractions/Location.java index f0e760d7..613a65e5 100644 --- a/src/main/java/ecdar/abstractions/Location.java +++ b/src/main/java/ecdar/abstractions/Location.java @@ -58,7 +58,7 @@ public class Location implements Circular, Serializable, Nearable, DropDownMenu. private final ObjectProperty reachability = new SimpleObjectProperty<>(); private final SimpleBooleanProperty isLocked = new SimpleBooleanProperty(false); - private boolean failing; + private final BooleanProperty failing = new SimpleBooleanProperty(false); public Location() { } @@ -475,10 +475,14 @@ public String generateNearString() { * @param bool if a query responded failure with the location, bool should be true. */ public void setFailing(boolean bool) { - this.failing = bool; + this.failing.set(bool); } public boolean getFailing() { + return this.failing.get(); + } + + public BooleanProperty failingProperty() { return this.failing; } diff --git a/src/main/java/ecdar/presentations/LocationPresentation.java b/src/main/java/ecdar/presentations/LocationPresentation.java index 8d2a55af..2c471390 100644 --- a/src/main/java/ecdar/presentations/LocationPresentation.java +++ b/src/main/java/ecdar/presentations/LocationPresentation.java @@ -158,6 +158,7 @@ private void initializeIdLabel() { final ObjectProperty color = location.colorProperty(); final ObjectProperty colorIntensity = location.colorIntensityProperty(); + final BooleanProperty failing = location.failingProperty(); // Delegate to style the label based on the color of the location final BiConsumer updateColor = (newColor, newIntensity) -> { @@ -165,6 +166,14 @@ private void initializeIdLabel() { ds.setColor(newColor.getColor(newIntensity)); }; + final Consumer handleFailingUpdate = (isFailing) -> { + if(isFailing) { + updateColor.accept(Color.RED, colorIntensity.get()); + } else { + //ToDo: Paint to previous color + } + }; + updateColorDelegates.add(updateColor); // Set the initial color @@ -172,6 +181,7 @@ private void initializeIdLabel() { // Update the color of the circle when the color of the location is updated color.addListener((obs, old, newColor) -> updateColor.accept(newColor, colorIntensity.get())); + failing.addListener((obs, old, newFailing) -> handleFailingUpdate.accept(newFailing)); } private void initializeTags() { @@ -405,6 +415,7 @@ protected void interpolate(final double frac) { // Update the colors final ObjectProperty color = location.colorProperty(); final ObjectProperty colorIntensity = location.colorIntensityProperty(); + final BooleanProperty failing = location.failingProperty(); // Delegate to style the label based on the color of the location final BiConsumer updateColor = (newColor, newIntensity) -> { @@ -418,6 +429,14 @@ protected void interpolate(final double frac) { committedShape.setStroke(newColor.getColor(newIntensity.next(2))); }; + final Consumer handleFailingUpdate = (isFailing) -> { + if(isFailing) { + updateColor.accept(Color.RED, colorIntensity.get()); + } else { + //ToDo: Paint to previous color + } + }; + updateColorDelegates.add(updateColor); // Set the initial color @@ -425,6 +444,7 @@ protected void interpolate(final double frac) { // Update the color of the circle when the color of the location is updated color.addListener((obs, old, newColor) -> updateColor.accept(newColor, colorIntensity.get())); + failing.addListener((obs, old, newFailing) -> handleFailingUpdate.accept(newFailing)); } private void initializeTypeGraphics() { From bcab56722e19a6833bba3e76da73b027e5b76b3f Mon Sep 17 00:00:00 2001 From: Sigurd Date: Mon, 7 Nov 2022 14:10:27 +0100 Subject: [PATCH 063/158] Reset color and failing state on new query. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Victor Doré Hanse --- .../java/ecdar/abstractions/Component.java | 18 ++++++++++++++++++ src/main/java/ecdar/abstractions/Query.java | 10 +++++++--- .../ecdar/controllers/EditorController.java | 2 +- .../presentations/LocationPresentation.java | 5 +++-- 4 files changed, 29 insertions(+), 6 deletions(-) diff --git a/src/main/java/ecdar/abstractions/Component.java b/src/main/java/ecdar/abstractions/Component.java index d3da9db1..36201250 100644 --- a/src/main/java/ecdar/abstractions/Component.java +++ b/src/main/java/ecdar/abstractions/Component.java @@ -38,6 +38,7 @@ public class Component extends HighLevelModelObject implements Boxed { // Verification properties private final ObservableList locations = FXCollections.observableArrayList(); + private final ObservableList failingLocations = FXCollections.observableArrayList(); private final ObservableList edges = FXCollections.observableArrayList(); private final ObservableList inputStrings = FXCollections.observableArrayList(); private final ObservableList outputStrings = FXCollections.observableArrayList(); @@ -486,6 +487,23 @@ public boolean removeLocation(final Location location) { return locations.remove(location); } + public boolean addFailingLocation(final String locationId) { + Location failingLocation = findLocation(locationId); + failingLocation.setFailing(true); + return failingLocations.add(failingLocation); + } + + public void removeFailingLocations() { + for (Location location : failingLocations) { + location.setFailing(false); + failingLocations.remove(location); + } + } + + public ObservableList getFailingLocations() { + return failingLocations; + } + /** * Returns all DisplayableEdges of the component (returning a list potentially containing GroupEdges and Edges) * @return All visual edges of the component diff --git a/src/main/java/ecdar/abstractions/Query.java b/src/main/java/ecdar/abstractions/Query.java index 6e97fe4c..927c3fde 100644 --- a/src/main/java/ecdar/abstractions/Query.java +++ b/src/main/java/ecdar/abstractions/Query.java @@ -32,6 +32,9 @@ public class Query implements Serializable { private final Consumer successConsumer = (aBoolean) -> { if (aBoolean) { + for (Component c : Ecdar.getProject().getComponents()) { + c.removeFailingLocations(); + } setQueryState(QueryState.SUCCESSFUL); } else { setQueryState(QueryState.ERROR); @@ -63,10 +66,11 @@ public class Query implements Serializable { private final Consumer stateConsumer = (state) -> { for (Component c : Ecdar.getProject().getComponents()) { - if (query.getValue().equals(c.getName())) { + c.removeFailingLocations(); + if (query.getValue().contains(c.getName())) { for (ObjectProtos.Location location : state.getLocationTuple().getLocationsList()) { - c.findLocation(location.getId()).setFailing(true); - }; + c.addFailingLocation(location.getId()); + } } } }; diff --git a/src/main/java/ecdar/controllers/EditorController.java b/src/main/java/ecdar/controllers/EditorController.java index 605cc6fd..27ab5245 100644 --- a/src/main/java/ecdar/controllers/EditorController.java +++ b/src/main/java/ecdar/controllers/EditorController.java @@ -153,7 +153,7 @@ void scaleEdgeStatusToggle(double size) { * Handles the change of color on selected objects * * @param enabledColor The new color for the selected objects - * @param previousColor The color old color of the selected objects + * @param previousColor The old color of the selected objects */ public void changeColorOnSelectedElements(final EnabledColor enabledColor, final List> previousColor) { diff --git a/src/main/java/ecdar/presentations/LocationPresentation.java b/src/main/java/ecdar/presentations/LocationPresentation.java index 2c471390..59b0f6ea 100644 --- a/src/main/java/ecdar/presentations/LocationPresentation.java +++ b/src/main/java/ecdar/presentations/LocationPresentation.java @@ -15,6 +15,7 @@ import javafx.scene.effect.DropShadow; import javafx.scene.shape.*; import javafx.util.Duration; +import javafx.util.Pair; import java.util.ArrayList; import java.util.List; @@ -170,7 +171,7 @@ private void initializeIdLabel() { if(isFailing) { updateColor.accept(Color.RED, colorIntensity.get()); } else { - //ToDo: Paint to previous color + updateColor.accept(location.getColor(), colorIntensity.get()); } }; @@ -433,7 +434,7 @@ protected void interpolate(final double frac) { if(isFailing) { updateColor.accept(Color.RED, colorIntensity.get()); } else { - //ToDo: Paint to previous color + updateColor.accept(location.getColor(), colorIntensity.get()); } }; From 37457e312dc9f973e0e3607efb1525bffe48e5f4 Mon Sep 17 00:00:00 2001 From: martin Date: Mon, 7 Nov 2022 14:21:50 +0100 Subject: [PATCH 064/158] displays failure message, still needs component name --- src/main/java/ecdar/presentations/QueryPresentation.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ecdar/presentations/QueryPresentation.java b/src/main/java/ecdar/presentations/QueryPresentation.java index 793ac2fa..1dde50d2 100644 --- a/src/main/java/ecdar/presentations/QueryPresentation.java +++ b/src/main/java/ecdar/presentations/QueryPresentation.java @@ -194,7 +194,7 @@ private void initializeStateIndicator() { } } else if (queryState.getStatusCode() == 2){ - this.tooltip.setText("This query was not a success!"); + this.tooltip.setText("This query was not a success: " + controller.getQuery().getCurrentErrors()); } else if (queryState.getStatusCode() == 3) { this.tooltip.setText("The query has not been executed yet"); From 550758a0305c9167dd3ad0b53932614905207a79 Mon Sep 17 00:00:00 2001 From: Sigurd Date: Mon, 7 Nov 2022 14:38:54 +0100 Subject: [PATCH 065/158] Fixed java.util.ConcurrentModificationException thrown when removing failing locations --- src/main/java/ecdar/abstractions/Component.java | 2 +- src/main/java/ecdar/backend/QueryHandler.java | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/ecdar/abstractions/Component.java b/src/main/java/ecdar/abstractions/Component.java index 36201250..b0126207 100644 --- a/src/main/java/ecdar/abstractions/Component.java +++ b/src/main/java/ecdar/abstractions/Component.java @@ -496,8 +496,8 @@ public boolean addFailingLocation(final String locationId) { public void removeFailingLocations() { for (Location location : failingLocations) { location.setFailing(false); - failingLocations.remove(location); } + failingLocations.removeAll(); } public ObservableList getFailingLocations() { diff --git a/src/main/java/ecdar/backend/QueryHandler.java b/src/main/java/ecdar/backend/QueryHandler.java index 671f77ae..8ec9214a 100644 --- a/src/main/java/ecdar/backend/QueryHandler.java +++ b/src/main/java/ecdar/backend/QueryHandler.java @@ -57,6 +57,7 @@ public void onNext(QueryProtos.QueryResponse value) { @Override public void onError(Throwable t) { + System.out.println(t); handleQueryBackendError(t, query); // Release backend connection From 02e642252466acaee986e718a3f731b15b98a53c Mon Sep 17 00:00:00 2001 From: martin Date: Mon, 7 Nov 2022 15:05:12 +0100 Subject: [PATCH 066/158] Modified build.gradle, fixes java.lang.illegalAccessError --- build.gradle | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index f2c68089..6364d14a 100644 --- a/build.gradle +++ b/build.gradle @@ -10,6 +10,21 @@ javafx { modules = ['javafx.controls', 'javafx.fxml', 'javafx.swing'] } +run { + mainClassName = 'com.jfxbase/com.jfxbase.sample.Main' + applicationDefaultJvmArgs += [ + "--add-opens", "javafx.graphics/javafx.css=ALL-UNNAMED", + "--add-opens", "javafx.base/com.sun.javafx.runtime=ALL-UNNAMED", + "--add-opens", "javafx.controls/com.sun.javafx.scene.control.behavior=ALL-UNNAMED", + "--add-opens", "javafx.controls/com.sun.javafx.scene.control=ALL-UNNAMED", + "--add-opens", "javafx.base/com.sun.javafx.binding=ALL-UNNAMED", + "--add-opens", "javafx.base/com.sun.javafx.event=ALL-UNNAMED", + "--add-opens", "javafx.graphics/com.sun.javafx.stage=ALL-UNNAMED", + "--add-opens", "javafx.graphics/com.sun.javafx.scene=ALL-UNNAMED", + ] +} + + group 'ecdar' if (project.hasProperty('ecdarVersion')) { version = project.ecdarVersion @@ -40,6 +55,8 @@ repositories { mavenCentral() } + + def grpcVersion = '1.41.0' // CURRENT_GRPC_VERSION def protobufVersion = '3.17.2' def protocVersion = protobufVersion @@ -128,4 +145,4 @@ sourceSets { srcDirs 'build/generated/source/proto/main/java' } } -} \ No newline at end of file +} From 9c50f147b3ca72c374db6dce2b8a09b713b70943 Mon Sep 17 00:00:00 2001 From: martin Date: Mon, 7 Nov 2022 15:11:01 +0100 Subject: [PATCH 067/158] Revert "Modified build.gradle, fixes java.lang.illegalAccessError" This reverts commit 02e642252466acaee986e718a3f731b15b98a53c additions will be added on another branch. --- build.gradle | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/build.gradle b/build.gradle index 6364d14a..f2c68089 100644 --- a/build.gradle +++ b/build.gradle @@ -10,21 +10,6 @@ javafx { modules = ['javafx.controls', 'javafx.fxml', 'javafx.swing'] } -run { - mainClassName = 'com.jfxbase/com.jfxbase.sample.Main' - applicationDefaultJvmArgs += [ - "--add-opens", "javafx.graphics/javafx.css=ALL-UNNAMED", - "--add-opens", "javafx.base/com.sun.javafx.runtime=ALL-UNNAMED", - "--add-opens", "javafx.controls/com.sun.javafx.scene.control.behavior=ALL-UNNAMED", - "--add-opens", "javafx.controls/com.sun.javafx.scene.control=ALL-UNNAMED", - "--add-opens", "javafx.base/com.sun.javafx.binding=ALL-UNNAMED", - "--add-opens", "javafx.base/com.sun.javafx.event=ALL-UNNAMED", - "--add-opens", "javafx.graphics/com.sun.javafx.stage=ALL-UNNAMED", - "--add-opens", "javafx.graphics/com.sun.javafx.scene=ALL-UNNAMED", - ] -} - - group 'ecdar' if (project.hasProperty('ecdarVersion')) { version = project.ecdarVersion @@ -55,8 +40,6 @@ repositories { mavenCentral() } - - def grpcVersion = '1.41.0' // CURRENT_GRPC_VERSION def protobufVersion = '3.17.2' def protocVersion = protobufVersion @@ -145,4 +128,4 @@ sourceSets { srcDirs 'build/generated/source/proto/main/java' } } -} +} \ No newline at end of file From 0f6f498515078e7c9d01f803b7d21198d8a4d371 Mon Sep 17 00:00:00 2001 From: Sigurd Date: Mon, 7 Nov 2022 15:17:11 +0100 Subject: [PATCH 068/158] Remove unused imports and fields --- src/main/java/ecdar/abstractions/Query.java | 12 ------------ src/main/java/ecdar/backend/QueryHandler.java | 2 -- .../ecdar/presentations/LocationPresentation.java | 1 - 3 files changed, 15 deletions(-) diff --git a/src/main/java/ecdar/abstractions/Query.java b/src/main/java/ecdar/abstractions/Query.java index 927c3fde..c9e0f997 100644 --- a/src/main/java/ecdar/abstractions/Query.java +++ b/src/main/java/ecdar/abstractions/Query.java @@ -1,17 +1,14 @@ package ecdar.abstractions; -import EcdarProtoBuf.ComponentProtos; import EcdarProtoBuf.ObjectProtos; import ecdar.Ecdar; import ecdar.backend.*; import ecdar.controllers.EcdarController; -import ecdar.utility.colors.Color; import ecdar.utility.serialize.Serializable; import com.google.gson.JsonObject; import javafx.application.Platform; import javafx.beans.property.*; -import java.io.FilenameFilter; import java.util.function.Consumer; public class Query implements Serializable { @@ -26,7 +23,6 @@ public class Query implements Serializable { private final SimpleBooleanProperty isPeriodic = new SimpleBooleanProperty(false); private final ObjectProperty queryState = new SimpleObjectProperty<>(QueryState.UNKNOWN); private final ObjectProperty type = new SimpleObjectProperty<>(); - private final StringProperty failingLocation = new SimpleStringProperty(""); private BackendInstance backend; @@ -102,14 +98,6 @@ public void setQueryState(final QueryState queryState) { this.queryState.set(queryState); } - public void setFailingLocation(final String location) { - this.failingLocation.set(location); - } - - public String getFailingLocation() { - return this.failingLocation.get(); - } - public ObjectProperty queryStateProperty() { return queryState; } diff --git a/src/main/java/ecdar/backend/QueryHandler.java b/src/main/java/ecdar/backend/QueryHandler.java index 8ec9214a..1cdeac01 100644 --- a/src/main/java/ecdar/backend/QueryHandler.java +++ b/src/main/java/ecdar/backend/QueryHandler.java @@ -57,7 +57,6 @@ public void onNext(QueryProtos.QueryResponse value) { @Override public void onError(Throwable t) { - System.out.println(t); handleQueryBackendError(t, query); // Release backend connection @@ -101,7 +100,6 @@ public void closeAllBackendConnections() throws IOException { private void handleQueryResponse(QueryProtos.QueryResponse value, Query query) { // If the query has been cancelled, ignore the result if (query.getQueryState() == QueryState.UNKNOWN) return; - System.out.println(value); switch (value.getResponseCase()) { case QUERY_OK: diff --git a/src/main/java/ecdar/presentations/LocationPresentation.java b/src/main/java/ecdar/presentations/LocationPresentation.java index 59b0f6ea..8d5f5969 100644 --- a/src/main/java/ecdar/presentations/LocationPresentation.java +++ b/src/main/java/ecdar/presentations/LocationPresentation.java @@ -15,7 +15,6 @@ import javafx.scene.effect.DropShadow; import javafx.scene.shape.*; import javafx.util.Duration; -import javafx.util.Pair; import java.util.ArrayList; import java.util.List; From b1b90bc344ebf10710f20b42bb3351407dcf5237 Mon Sep 17 00:00:00 2001 From: Victor Date: Tue, 8 Nov 2022 09:17:29 +0100 Subject: [PATCH 069/158] QueryPanel Checkmark enabled in View Dropdown --- src/main/java/ecdar/controllers/EcdarController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ecdar/controllers/EcdarController.java b/src/main/java/ecdar/controllers/EcdarController.java index e350b296..2dd09fc2 100644 --- a/src/main/java/ecdar/controllers/EcdarController.java +++ b/src/main/java/ecdar/controllers/EcdarController.java @@ -667,7 +667,7 @@ private void initializeViewMenu() { menuBarViewFilePanel.getGraphic().opacityProperty().bind(new When(isOpen).then(1).otherwise(0)); }); - menuBarViewQueryPanel.getGraphic().setOpacity(0); + menuBarViewQueryPanel.getGraphic().setOpacity(1); menuBarViewQueryPanel.setAccelerator(new KeyCodeCombination(KeyCode.G, KeyCodeCombination.SHORTCUT_DOWN)); menuBarViewQueryPanel.setOnAction(event -> { final BooleanProperty isOpen = Ecdar.toggleQueryPane(); From f223e714f7c0d96bf84c6bf44084e9527665e2e0 Mon Sep 17 00:00:00 2001 From: Sigurd Date: Tue, 8 Nov 2022 10:32:42 +0100 Subject: [PATCH 070/158] Documentation for public methods --- src/main/java/ecdar/abstractions/Component.java | 13 +++++++++++++ src/main/java/ecdar/abstractions/Location.java | 8 ++++++++ src/main/java/ecdar/abstractions/Query.java | 5 +++++ 3 files changed, 26 insertions(+) diff --git a/src/main/java/ecdar/abstractions/Component.java b/src/main/java/ecdar/abstractions/Component.java index b0126207..1d187a6a 100644 --- a/src/main/java/ecdar/abstractions/Component.java +++ b/src/main/java/ecdar/abstractions/Component.java @@ -487,12 +487,21 @@ public boolean removeLocation(final Location location) { return locations.remove(location); } + /** + * Adds a failing location to the list of failing locations. + * @param locationId the id of the location that is failing. + * @return whether adding the location to the list was a success + */ public boolean addFailingLocation(final String locationId) { Location failingLocation = findLocation(locationId); failingLocation.setFailing(true); return failingLocations.add(failingLocation); } + /** + * Sets all previous failing locations to not failing + * and removes all previous failing locations from list. + */ public void removeFailingLocations() { for (Location location : failingLocations) { location.setFailing(false); @@ -500,6 +509,10 @@ public void removeFailingLocations() { failingLocations.removeAll(); } + /** + * Observable list of all failing locations. + * @return Observable list of all failing locations. + */ public ObservableList getFailingLocations() { return failingLocations; } diff --git a/src/main/java/ecdar/abstractions/Location.java b/src/main/java/ecdar/abstractions/Location.java index 613a65e5..ba500c1b 100644 --- a/src/main/java/ecdar/abstractions/Location.java +++ b/src/main/java/ecdar/abstractions/Location.java @@ -478,10 +478,18 @@ public void setFailing(boolean bool) { this.failing.set(bool); } + /** + * Whether this location is currently failing. + * @return Whether this location is currently failing. + */ public boolean getFailing() { return this.failing.get(); } + /** + * The observable boolean property for 'failing' of this. + * @return The observable boolean property for 'failing' of this. + */ public BooleanProperty failingProperty() { return this.failing; } diff --git a/src/main/java/ecdar/abstractions/Query.java b/src/main/java/ecdar/abstractions/Query.java index c9e0f997..03ca02be 100644 --- a/src/main/java/ecdar/abstractions/Query.java +++ b/src/main/java/ecdar/abstractions/Query.java @@ -167,6 +167,11 @@ public Consumer getSuccessConsumer() { public Consumer getFailureConsumer() { return failureConsumer; } + + /** + * Getter for the state consumer. + * @return The State Consumer + */ public Consumer getStateConsumer() { return stateConsumer; } From e30c77ac86cb403c75395beea6a3ab1aadfe1460 Mon Sep 17 00:00:00 2001 From: Sigurd Date: Tue, 8 Nov 2022 10:49:01 +0100 Subject: [PATCH 071/158] Change get for each response --- src/main/java/ecdar/backend/QueryHandler.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/ecdar/backend/QueryHandler.java b/src/main/java/ecdar/backend/QueryHandler.java index 1cdeac01..7a7a5f42 100644 --- a/src/main/java/ecdar/backend/QueryHandler.java +++ b/src/main/java/ecdar/backend/QueryHandler.java @@ -113,7 +113,7 @@ private void handleQueryResponse(QueryProtos.QueryResponse value, Query query) { query.setQueryState(QueryState.ERROR); query.getFailureConsumer().accept(new BackendException.QueryErrorException(queryOk.getRefinement().getReason())); query.getSuccessConsumer().accept(false); - query.getStateConsumer().accept(value.getQueryOk().getConsistency().getState()); + query.getStateConsumer().accept(value.getQueryOk().getRefinement().getState()); } break; @@ -137,7 +137,7 @@ private void handleQueryResponse(QueryProtos.QueryResponse value, Query query) { query.setQueryState(QueryState.ERROR); query.getFailureConsumer().accept(new BackendException.QueryErrorException(queryOk.getDeterminism().getReason())); query.getSuccessConsumer().accept(false); - query.getStateConsumer().accept(value.getQueryOk().getConsistency().getState()); + query.getStateConsumer().accept(value.getQueryOk().getDeterminism().getState()); } break; @@ -149,7 +149,7 @@ private void handleQueryResponse(QueryProtos.QueryResponse value, Query query) { query.setQueryState(QueryState.ERROR); query.getFailureConsumer().accept(new BackendException.QueryErrorException(queryOk.getImplementation().getReason())); query.getSuccessConsumer().accept(false); - query.getStateConsumer().accept(value.getQueryOk().getConsistency().getState()); + query.getStateConsumer().accept(value.getQueryOk().getImplementation().getState()); } break; @@ -161,7 +161,7 @@ private void handleQueryResponse(QueryProtos.QueryResponse value, Query query) { query.setQueryState(QueryState.ERROR); query.getFailureConsumer().accept(new BackendException.QueryErrorException(queryOk.getReachability().getReason())); query.getSuccessConsumer().accept(false); - query.getStateConsumer().accept(value.getQueryOk().getConsistency().getState()); + query.getStateConsumer().accept(value.getQueryOk().getReachability().getState()); } break; From 15866b542e8a81ea43d17bde0adc90a9ec9cc608 Mon Sep 17 00:00:00 2001 From: Sigurd Date: Tue, 8 Nov 2022 14:41:36 +0100 Subject: [PATCH 072/158] Initial work on painting failing actions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Victor Doré Hanse Co-authored-by: seba6505 --- .../java/ecdar/abstractions/Component.java | 34 ++++++++++++++++-- .../ecdar/abstractions/DisplayableEdge.java | 20 +++++++++++ src/main/java/ecdar/abstractions/Edge.java | 13 +++++++ .../java/ecdar/abstractions/GroupedEdge.java | 9 +++++ src/main/java/ecdar/abstractions/Query.java | 17 +++++++++ src/main/java/ecdar/backend/QueryHandler.java | 1 + .../ecdar/presentations/NailPresentation.java | 35 ++++++++++++++++++- 7 files changed, 126 insertions(+), 3 deletions(-) diff --git a/src/main/java/ecdar/abstractions/Component.java b/src/main/java/ecdar/abstractions/Component.java index 1d187a6a..d1f7bcf5 100644 --- a/src/main/java/ecdar/abstractions/Component.java +++ b/src/main/java/ecdar/abstractions/Component.java @@ -40,6 +40,7 @@ public class Component extends HighLevelModelObject implements Boxed { private final ObservableList locations = FXCollections.observableArrayList(); private final ObservableList failingLocations = FXCollections.observableArrayList(); private final ObservableList edges = FXCollections.observableArrayList(); + private final ObservableList failingEdges = FXCollections.observableArrayList(); private final ObservableList inputStrings = FXCollections.observableArrayList(); private final ObservableList outputStrings = FXCollections.observableArrayList(); private final StringProperty description = new SimpleStringProperty(""); @@ -487,6 +488,35 @@ public boolean removeLocation(final Location location) { return locations.remove(location); } + /** + * Adds a failing Edge to the list of failing Edges. + * @param edge the Edge that is failing. + * @return whether adding the Edge to the list was a success + */ + public boolean addFailingEdge(final Edge edge) { + edge.setFailing(true); + return failingEdges.add(edge); + } + + /** + * Sets all previous failing locations to not failing + * and removes all previous failing locations from list. + */ + public void removeFailingEdges() { + for (DisplayableEdge edge : failingEdges) { + edge.setFailing(false); + } + failingEdges.removeAll(); + } + + /** + * Observable list of all failing locations. + * @return Observable list of all failing locations. + */ + public ObservableList getFailingLocations() { + return failingLocations; + } + /** * Adds a failing location to the list of failing locations. * @param locationId the id of the location that is failing. @@ -513,8 +543,8 @@ public void removeFailingLocations() { * Observable list of all failing locations. * @return Observable list of all failing locations. */ - public ObservableList getFailingLocations() { - return failingLocations; + public ObservableList getFailingEdges() { + return failingEdges; } /** diff --git a/src/main/java/ecdar/abstractions/DisplayableEdge.java b/src/main/java/ecdar/abstractions/DisplayableEdge.java index 7a5d62d2..3a1f9341 100644 --- a/src/main/java/ecdar/abstractions/DisplayableEdge.java +++ b/src/main/java/ecdar/abstractions/DisplayableEdge.java @@ -39,6 +39,8 @@ public abstract class DisplayableEdge implements Nearable { private final BooleanProperty isHighlighted = new SimpleBooleanProperty(false); + protected final BooleanProperty failing = new SimpleBooleanProperty(false); + public Location getSourceLocation() { return sourceLocation.get(); } @@ -302,4 +304,22 @@ public StringProperty idProperty() { public abstract List getProperty(final PropertyType propertyType); public abstract void setProperty(final PropertyType propertyType, final List newProperty); + + /** + * Sets the 'failing' property + * @param bool true if the edge is failing. + */ + public abstract void setFailing(final boolean bool); + + /** + * Getter for the 'failing' boolean + * @return Whether edge is failing in last query response. + */ + public abstract boolean getFailing(); + + /** + * The observable boolean property for 'failing' of this. + * @return The observable boolean property for 'failing' of this. + */ + public abstract BooleanProperty failingProperty(); } diff --git a/src/main/java/ecdar/abstractions/Edge.java b/src/main/java/ecdar/abstractions/Edge.java index 2b1057c9..016b3b33 100644 --- a/src/main/java/ecdar/abstractions/Edge.java +++ b/src/main/java/ecdar/abstractions/Edge.java @@ -77,6 +77,19 @@ public void setGroup(final String string){ group.set(string); } + @Override + public boolean getFailing() { return this.failing.get(); } + + @Override + public void setFailing(boolean bool) { + this.failing.set(bool); + } + + @Override + public BooleanProperty failingProperty() { + return this.failing; + } + /** * Creates a clone of an edge. * Clones objects used for verification. diff --git a/src/main/java/ecdar/abstractions/GroupedEdge.java b/src/main/java/ecdar/abstractions/GroupedEdge.java index 6f216efc..4862e962 100644 --- a/src/main/java/ecdar/abstractions/GroupedEdge.java +++ b/src/main/java/ecdar/abstractions/GroupedEdge.java @@ -168,6 +168,15 @@ public void setProperty(final PropertyType propertyType, final List newP } } + @Override + public void setFailing(boolean bool) { this.failing.set(bool); } + + @Override + public boolean getFailing() { return this.failing.get(); } + + @Override + public BooleanProperty failingProperty() { return this.failing; } + public List getSyncProperties() { List syncProperties = new ArrayList<>(); diff --git a/src/main/java/ecdar/abstractions/Query.java b/src/main/java/ecdar/abstractions/Query.java index 03ca02be..ed0831d6 100644 --- a/src/main/java/ecdar/abstractions/Query.java +++ b/src/main/java/ecdar/abstractions/Query.java @@ -71,6 +71,19 @@ public class Query implements Serializable { } }; + private final Consumer actionConsumer = (action) -> { + for (Component c : Ecdar.getProject().getComponents()) { + c.removeFailingEdges(); + if (query.getValue().contains(c.getName())) { + for (Edge edge : c.getEdges()) { + if(action.equals(edge.getSync()) && edge.getSourceLocation().getFailing()) { + c.addFailingEdge(edge); + } + } + } + } + }; + public Query(final String query, final String comment, final QueryState queryState) { this.setQuery(query); this.comment.set(comment); @@ -176,6 +189,10 @@ public Consumer getStateConsumer() { return stateConsumer; } + public Consumer getActionConsumer() { + return actionConsumer; + } + @Override public JsonObject serialize() { final JsonObject result = new JsonObject(); diff --git a/src/main/java/ecdar/backend/QueryHandler.java b/src/main/java/ecdar/backend/QueryHandler.java index 7a7a5f42..58c8fd16 100644 --- a/src/main/java/ecdar/backend/QueryHandler.java +++ b/src/main/java/ecdar/backend/QueryHandler.java @@ -126,6 +126,7 @@ private void handleQueryResponse(QueryProtos.QueryResponse value, Query query) { query.getFailureConsumer().accept(new BackendException.QueryErrorException(queryOk.getConsistency().getReason())); query.getSuccessConsumer().accept(false); query.getStateConsumer().accept(value.getQueryOk().getConsistency().getState()); + query.getActionConsumer().accept(value.getQueryOk().getConsistency().getAction()); } break; diff --git a/src/main/java/ecdar/presentations/NailPresentation.java b/src/main/java/ecdar/presentations/NailPresentation.java index 2ab5823f..c27e8bbe 100644 --- a/src/main/java/ecdar/presentations/NailPresentation.java +++ b/src/main/java/ecdar/presentations/NailPresentation.java @@ -12,10 +12,12 @@ import javafx.animation.KeyValue; import javafx.animation.Timeline; import javafx.application.Platform; +import javafx.beans.property.ObjectProperty; import javafx.scene.Group; import javafx.scene.control.Label; import javafx.scene.shape.Line; +import java.util.function.BiConsumer; import java.util.function.Consumer; import static javafx.util.Duration.millis; @@ -180,6 +182,24 @@ private void updateSyncLabel(final TagPresentation propertyTag) { } } + private void initializeFailingListener() { + final DisplayableEdge edge = controller.getEdge(); + final ObjectProperty colorIntensity = edge.colorIntensityProperty(); + + // Delegate to style the label based on the color of the location + final BiConsumer updateColor = (newColor, newIntensity) -> { + controller.color(newColor, newIntensity); + }; + final Consumer handleFailingUpdate = (isFailing) -> { + if(isFailing) { + updateColor.accept(Color.RED, colorIntensity.get()); + } else { + updateColor.accept(edge.getColor(), colorIntensity.get()); + } + }; + edge.failingProperty().addListener((obs, oldFailing, newFailing) -> handleFailingUpdate.accept(newFailing)); + } + private void initializeNailCircleColor() { final Runnable updateNailColor = () -> { final Color color = controller.getComponent().getColor(); @@ -194,12 +214,25 @@ private void initializeNailCircleColor() { } }; + final Runnable updateNailColorOnFailing = () -> { + final Color color = controller.getComponent().getColor(); + final Color.Intensity colorIntensity = controller.getComponent().getColorIntensity(); + for(DisplayableEdge edge : controller.getComponent().getFailingEdges()) { + for (Nail nail : edge.getNails()) { + if(nail.getPropertyType().equals(DisplayableEdge.PropertyType.SYNCHRONIZATION)){ + controller.nailCircle.setFill(Color.RED.getColor(colorIntensity)); + controller.nailCircle.setStroke(Color.RED.getColor(colorIntensity.next(2))); + } + } + } + }; + // When the color of the component updates, update the nail indicator as well controller.getComponent().colorProperty().addListener((observable) -> updateNailColor.run()); // When the color intensity of the component updates, update the nail indicator controller.getComponent().colorIntensityProperty().addListener((observable) -> updateNailColor.run()); - + controller.getEdge().failingProperty().addListener((observable) -> updateNailColorOnFailing.run()); // Initialize the color of the nail updateNailColor.run(); } From a39b1af519aa8da0f518af94051a1f4abd641b70 Mon Sep 17 00:00:00 2001 From: Sigurd Date: Wed, 9 Nov 2022 12:09:47 +0100 Subject: [PATCH 073/158] Refactor observer so it is added in the edge presentation. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Victor DoréHansen Co-authored-by: seba6505 --- .../ecdar/presentations/EdgePresentation.java | 14 ++++++ .../ecdar/presentations/NailPresentation.java | 48 ++++++------------- 2 files changed, 29 insertions(+), 33 deletions(-) diff --git a/src/main/java/ecdar/presentations/EdgePresentation.java b/src/main/java/ecdar/presentations/EdgePresentation.java index 993743bd..06e04216 100644 --- a/src/main/java/ecdar/presentations/EdgePresentation.java +++ b/src/main/java/ecdar/presentations/EdgePresentation.java @@ -2,6 +2,7 @@ import ecdar.abstractions.Component; import ecdar.abstractions.DisplayableEdge; +import ecdar.abstractions.Nail; import ecdar.controllers.EdgeController; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; @@ -21,6 +22,19 @@ public EdgePresentation(final DisplayableEdge edge, final Component component) { controller.setComponent(component); this.component.bind(controller.componentProperty()); + initializeFailingEdgeListener(); + } + + private void initializeFailingEdgeListener() { + controller.getEdge().failingProperty().addListener((observable, oldFailing, newFailing) -> onFailingUpdate(controller.getEdge(), newFailing)); + } + + private void onFailingUpdate(DisplayableEdge edge, Boolean isFailing) { + for (Nail nail : edge.getNails()) { + if (nail.getPropertyType().equals(DisplayableEdge.PropertyType.SYNCHRONIZATION)) { + controller.getNailNailPresentationMap().get(nail).onFailingUpdate(); + } + } } public EdgeController getController() { diff --git a/src/main/java/ecdar/presentations/NailPresentation.java b/src/main/java/ecdar/presentations/NailPresentation.java index c27e8bbe..f8b44cfa 100644 --- a/src/main/java/ecdar/presentations/NailPresentation.java +++ b/src/main/java/ecdar/presentations/NailPresentation.java @@ -12,12 +12,11 @@ import javafx.animation.KeyValue; import javafx.animation.Timeline; import javafx.application.Platform; -import javafx.beans.property.ObjectProperty; +import javafx.beans.value.ObservableValue; import javafx.scene.Group; import javafx.scene.control.Label; import javafx.scene.shape.Line; -import java.util.function.BiConsumer; import java.util.function.Consumer; import static javafx.util.Duration.millis; @@ -182,24 +181,6 @@ private void updateSyncLabel(final TagPresentation propertyTag) { } } - private void initializeFailingListener() { - final DisplayableEdge edge = controller.getEdge(); - final ObjectProperty colorIntensity = edge.colorIntensityProperty(); - - // Delegate to style the label based on the color of the location - final BiConsumer updateColor = (newColor, newIntensity) -> { - controller.color(newColor, newIntensity); - }; - final Consumer handleFailingUpdate = (isFailing) -> { - if(isFailing) { - updateColor.accept(Color.RED, colorIntensity.get()); - } else { - updateColor.accept(edge.getColor(), colorIntensity.get()); - } - }; - edge.failingProperty().addListener((obs, oldFailing, newFailing) -> handleFailingUpdate.accept(newFailing)); - } - private void initializeNailCircleColor() { final Runnable updateNailColor = () -> { final Color color = controller.getComponent().getColor(); @@ -214,29 +195,30 @@ private void initializeNailCircleColor() { } }; - final Runnable updateNailColorOnFailing = () -> { - final Color color = controller.getComponent().getColor(); - final Color.Intensity colorIntensity = controller.getComponent().getColorIntensity(); - for(DisplayableEdge edge : controller.getComponent().getFailingEdges()) { - for (Nail nail : edge.getNails()) { - if(nail.getPropertyType().equals(DisplayableEdge.PropertyType.SYNCHRONIZATION)){ - controller.nailCircle.setFill(Color.RED.getColor(colorIntensity)); - controller.nailCircle.setStroke(Color.RED.getColor(colorIntensity.next(2))); - } - } - } - }; + // When the color of the component updates, update the nail indicator as well controller.getComponent().colorProperty().addListener((observable) -> updateNailColor.run()); // When the color intensity of the component updates, update the nail indicator controller.getComponent().colorIntensityProperty().addListener((observable) -> updateNailColor.run()); - controller.getEdge().failingProperty().addListener((observable) -> updateNailColorOnFailing.run()); // Initialize the color of the nail updateNailColor.run(); } + /** + * Update color when the edge of this nails failing property is updated. + */ + public void onFailingUpdate() { + final Runnable updateNailColorOnFailing = () -> { + final Color color = controller.getComponent().getColor(); + final Color.Intensity colorIntensity = controller.getComponent().getColorIntensity(); + controller.nailCircle.setFill(Color.RED.getColor(colorIntensity)); + controller.nailCircle.setStroke(Color.RED.getColor(colorIntensity.next(2))); + }; + updateNailColorOnFailing.run(); + } + private void initializeShakeAnimation() { final Interpolator interpolator = Interpolator.SPLINE(0.645, 0.045, 0.355, 1); From 5a5e6a4334cda5ab188ea8e0773dc3938d259fec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Victor=20Dor=C3=A9=20Hansen?= Date: Wed, 9 Nov 2022 13:52:03 +0100 Subject: [PATCH 074/158] Red replaced by Lime in enabled colors --- .../java/ecdar/utility/colors/EnabledColor.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/main/java/ecdar/utility/colors/EnabledColor.java b/src/main/java/ecdar/utility/colors/EnabledColor.java index 78cfc10b..8ef4e647 100644 --- a/src/main/java/ecdar/utility/colors/EnabledColor.java +++ b/src/main/java/ecdar/utility/colors/EnabledColor.java @@ -9,13 +9,13 @@ public class EnabledColor { public static final ArrayList enabledColors = new ArrayList() {{ add(new EnabledColor(Color.GREY_BLUE, Color.Intensity.I700, KeyCode.DIGIT0)); add(new EnabledColor(Color.DEEP_ORANGE, Color.Intensity.I700, KeyCode.DIGIT1)); - add(new EnabledColor(Color.RED, Color.Intensity.I500, KeyCode.DIGIT2)); - add(new EnabledColor(Color.PINK, Color.Intensity.I500, KeyCode.DIGIT3)); - add(new EnabledColor(Color.PURPLE, Color.Intensity.I500, KeyCode.DIGIT4)); - add(new EnabledColor(Color.INDIGO, Color.Intensity.I500, KeyCode.DIGIT5)); - add(new EnabledColor(Color.BLUE, Color.Intensity.I600, KeyCode.DIGIT6)); - add(new EnabledColor(Color.CYAN, Color.Intensity.I700, KeyCode.DIGIT7)); - add(new EnabledColor(Color.GREEN, Color.Intensity.I600, KeyCode.DIGIT8)); + add(new EnabledColor(Color.PINK, Color.Intensity.I500, KeyCode.DIGIT2)); + add(new EnabledColor(Color.PURPLE, Color.Intensity.I500, KeyCode.DIGIT3)); + add(new EnabledColor(Color.INDIGO, Color.Intensity.I500, KeyCode.DIGIT4)); + add(new EnabledColor(Color.BLUE, Color.Intensity.I600, KeyCode.DIGIT5)); + add(new EnabledColor(Color.CYAN, Color.Intensity.I700, KeyCode.DIGIT6)); + add(new EnabledColor(Color.GREEN, Color.Intensity.I600, KeyCode.DIGIT7)); + add(new EnabledColor(Color.LIME, Color.Intensity.I500, KeyCode.DIGIT8)); add(new EnabledColor(Color.BROWN, Color.Intensity.I500, KeyCode.DIGIT9)); }}; From dda9acf884a82713e794e1ecb2dfb517d6a1f090 Mon Sep 17 00:00:00 2001 From: Sigurd Date: Thu, 10 Nov 2022 12:21:02 +0100 Subject: [PATCH 075/158] Set color back to component color when action doesnt fail. Other cleanup --- .../java/ecdar/abstractions/Component.java | 7 +-- src/main/java/ecdar/abstractions/Edge.java | 6 ++- .../java/ecdar/abstractions/GroupedEdge.java | 12 +++-- src/main/java/ecdar/abstractions/Query.java | 1 + src/main/java/ecdar/backend/QueryHandler.java | 3 +- .../ecdar/presentations/EdgePresentation.java | 2 +- .../ecdar/presentations/NailPresentation.java | 46 +++++++++++-------- 7 files changed, 47 insertions(+), 30 deletions(-) diff --git a/src/main/java/ecdar/abstractions/Component.java b/src/main/java/ecdar/abstractions/Component.java index d1f7bcf5..013ec70e 100644 --- a/src/main/java/ecdar/abstractions/Component.java +++ b/src/main/java/ecdar/abstractions/Component.java @@ -489,7 +489,8 @@ public boolean removeLocation(final Location location) { } /** - * Adds a failing Edge to the list of failing Edges. + * Add a failing Edge to the list of failing edges + * and set the edge's failing property to true. * @param edge the Edge that is failing. * @return whether adding the Edge to the list was a success */ @@ -499,8 +500,8 @@ public boolean addFailingEdge(final Edge edge) { } /** - * Sets all previous failing locations to not failing - * and removes all previous failing locations from list. + * Sets all previous failing edges to not failing + * and removes all previous failing edges from list. */ public void removeFailingEdges() { for (DisplayableEdge edge : failingEdges) { diff --git a/src/main/java/ecdar/abstractions/Edge.java b/src/main/java/ecdar/abstractions/Edge.java index 016b3b33..9bbcff9c 100644 --- a/src/main/java/ecdar/abstractions/Edge.java +++ b/src/main/java/ecdar/abstractions/Edge.java @@ -69,7 +69,7 @@ public String getSyncWithSymbol() { return sync.get() + (ioStatus.get().equals(EdgeStatus.INPUT) ? "?" : "!"); } - public String getGroup(){ + public String getGroup() { return group.get(); } @@ -78,7 +78,9 @@ public void setGroup(final String string){ } @Override - public boolean getFailing() { return this.failing.get(); } + public boolean getFailing() { + return this.failing.get(); + } @Override public void setFailing(boolean bool) { diff --git a/src/main/java/ecdar/abstractions/GroupedEdge.java b/src/main/java/ecdar/abstractions/GroupedEdge.java index 4862e962..f96ab4b7 100644 --- a/src/main/java/ecdar/abstractions/GroupedEdge.java +++ b/src/main/java/ecdar/abstractions/GroupedEdge.java @@ -169,13 +169,19 @@ public void setProperty(final PropertyType propertyType, final List newP } @Override - public void setFailing(boolean bool) { this.failing.set(bool); } + public void setFailing(boolean bool) { + this.failing.set(bool); + } @Override - public boolean getFailing() { return this.failing.get(); } + public boolean getFailing() { + return this.failing.get(); + } @Override - public BooleanProperty failingProperty() { return this.failing; } + public BooleanProperty failingProperty() { + return this.failing; + } public List getSyncProperties() { List syncProperties = new ArrayList<>(); diff --git a/src/main/java/ecdar/abstractions/Query.java b/src/main/java/ecdar/abstractions/Query.java index ed0831d6..c00cfb76 100644 --- a/src/main/java/ecdar/abstractions/Query.java +++ b/src/main/java/ecdar/abstractions/Query.java @@ -30,6 +30,7 @@ public class Query implements Serializable { if (aBoolean) { for (Component c : Ecdar.getProject().getComponents()) { c.removeFailingLocations(); + c.removeFailingEdges(); } setQueryState(QueryState.SUCCESSFUL); } else { diff --git a/src/main/java/ecdar/backend/QueryHandler.java b/src/main/java/ecdar/backend/QueryHandler.java index 58c8fd16..74453cf5 100644 --- a/src/main/java/ecdar/backend/QueryHandler.java +++ b/src/main/java/ecdar/backend/QueryHandler.java @@ -100,7 +100,6 @@ public void closeAllBackendConnections() throws IOException { private void handleQueryResponse(QueryProtos.QueryResponse value, Query query) { // If the query has been cancelled, ignore the result if (query.getQueryState() == QueryState.UNKNOWN) return; - switch (value.getResponseCase()) { case QUERY_OK: QueryProtos.QueryResponse.QueryOk queryOk = value.getQueryOk(); @@ -114,6 +113,7 @@ private void handleQueryResponse(QueryProtos.QueryResponse value, Query query) { query.getFailureConsumer().accept(new BackendException.QueryErrorException(queryOk.getRefinement().getReason())); query.getSuccessConsumer().accept(false); query.getStateConsumer().accept(value.getQueryOk().getRefinement().getState()); + query.getActionConsumer().accept(value.getQueryOk().getConsistency().getAction()); } break; @@ -139,6 +139,7 @@ private void handleQueryResponse(QueryProtos.QueryResponse value, Query query) { query.getFailureConsumer().accept(new BackendException.QueryErrorException(queryOk.getDeterminism().getReason())); query.getSuccessConsumer().accept(false); query.getStateConsumer().accept(value.getQueryOk().getDeterminism().getState()); + query.getActionConsumer().accept(value.getQueryOk().getConsistency().getAction()); } break; diff --git a/src/main/java/ecdar/presentations/EdgePresentation.java b/src/main/java/ecdar/presentations/EdgePresentation.java index 06e04216..949f3ce2 100644 --- a/src/main/java/ecdar/presentations/EdgePresentation.java +++ b/src/main/java/ecdar/presentations/EdgePresentation.java @@ -32,7 +32,7 @@ private void initializeFailingEdgeListener() { private void onFailingUpdate(DisplayableEdge edge, Boolean isFailing) { for (Nail nail : edge.getNails()) { if (nail.getPropertyType().equals(DisplayableEdge.PropertyType.SYNCHRONIZATION)) { - controller.getNailNailPresentationMap().get(nail).onFailingUpdate(); + controller.getNailNailPresentationMap().get(nail).onFailingUpdate(isFailing); } } } diff --git a/src/main/java/ecdar/presentations/NailPresentation.java b/src/main/java/ecdar/presentations/NailPresentation.java index f8b44cfa..f945213b 100644 --- a/src/main/java/ecdar/presentations/NailPresentation.java +++ b/src/main/java/ecdar/presentations/NailPresentation.java @@ -182,41 +182,47 @@ private void updateSyncLabel(final TagPresentation propertyTag) { } private void initializeNailCircleColor() { - final Runnable updateNailColor = () -> { - final Color color = controller.getComponent().getColor(); - final Color.Intensity colorIntensity = controller.getComponent().getColorIntensity(); - - if(!controller.getNail().getPropertyType().equals(Edge.PropertyType.NONE)) { - controller.nailCircle.setFill(color.getColor(colorIntensity)); - controller.nailCircle.setStroke(color.getColor(colorIntensity.next(2))); - } else { - controller.nailCircle.setFill(Color.GREY_BLUE.getColor(Color.Intensity.I800)); - controller.nailCircle.setStroke(Color.GREY_BLUE.getColor(Color.Intensity.I900)); - } - }; - - // When the color of the component updates, update the nail indicator as well - controller.getComponent().colorProperty().addListener((observable) -> updateNailColor.run()); + controller.getComponent().colorProperty().addListener((observable) -> updateNailColor()); // When the color intensity of the component updates, update the nail indicator - controller.getComponent().colorIntensityProperty().addListener((observable) -> updateNailColor.run()); + controller.getComponent().colorIntensityProperty().addListener((observable) -> updateNailColor()); // Initialize the color of the nail - updateNailColor.run(); + updateNailColor(); } /** * Update color when the edge of this nails failing property is updated. */ - public void onFailingUpdate() { - final Runnable updateNailColorOnFailing = () -> { + public void onFailingUpdate(boolean isFailing) { + final Runnable updateNailColorOnFailingUpdate = () -> { final Color color = controller.getComponent().getColor(); final Color.Intensity colorIntensity = controller.getComponent().getColorIntensity(); controller.nailCircle.setFill(Color.RED.getColor(colorIntensity)); controller.nailCircle.setStroke(Color.RED.getColor(colorIntensity.next(2))); }; - updateNailColorOnFailing.run(); + if (isFailing) { + updateNailColorOnFailingUpdate.run(); + } else { + updateNailColor(); + } + } + + private void updateNailColor() { + final Runnable updateNailColor = () -> { + final Color color = controller.getComponent().getColor(); + final Color.Intensity colorIntensity = controller.getComponent().getColorIntensity(); + + if(!controller.getNail().getPropertyType().equals(Edge.PropertyType.NONE)) { + controller.nailCircle.setFill(color.getColor(colorIntensity)); + controller.nailCircle.setStroke(color.getColor(colorIntensity.next(2))); + } else { + controller.nailCircle.setFill(Color.GREY_BLUE.getColor(Color.Intensity.I800)); + controller.nailCircle.setStroke(Color.GREY_BLUE.getColor(Color.Intensity.I900)); + } + }; + updateNailColor.run(); } private void initializeShakeAnimation() { From d327874f75748485b3348d68a39c5ed3185a124a Mon Sep 17 00:00:00 2001 From: Sigurd Date: Thu, 10 Nov 2022 12:27:42 +0100 Subject: [PATCH 076/158] Fix comment --- src/main/java/ecdar/abstractions/Component.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/ecdar/abstractions/Component.java b/src/main/java/ecdar/abstractions/Component.java index 013ec70e..f03faa87 100644 --- a/src/main/java/ecdar/abstractions/Component.java +++ b/src/main/java/ecdar/abstractions/Component.java @@ -541,8 +541,8 @@ public void removeFailingLocations() { } /** - * Observable list of all failing locations. - * @return Observable list of all failing locations. + * Observable list of all failing edges. + * @return Observable list of all failing edges. */ public ObservableList getFailingEdges() { return failingEdges; From f61d61847ce554b9cd0db649aaa0da3b52b2852a Mon Sep 17 00:00:00 2001 From: WassawRoki <56611129+WassawRoki@users.noreply.github.com> Date: Thu, 10 Nov 2022 13:41:30 +0100 Subject: [PATCH 077/158] cloning --- src/main/java/ecdar/abstractions/Component.java | 15 +++++++++++++++ .../SimulationInitializationDialogController.java | 1 + .../ecdar/controllers/SimulatorController.java | 15 ++++++++++++++- 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/src/main/java/ecdar/abstractions/Component.java b/src/main/java/ecdar/abstractions/Component.java index d3da9db1..d3b90f2a 100644 --- a/src/main/java/ecdar/abstractions/Component.java +++ b/src/main/java/ecdar/abstractions/Component.java @@ -97,6 +97,7 @@ public Component(final JsonObject json) { bindReachabilityAnalysis(); } + /** * Creates a clone of another component. * Copies objects used for verification (e.g. locations, edges and the declarations). @@ -116,6 +117,20 @@ public Component cloneForVerification() { return clone; } + public Component cloneForSimulation() { + final Component clone = new Component(); + clone.addVerificationObjects(this); + clone.setIncludeInPeriodicCheck(false); + clone.inputStrings.addAll(getInputStrings()); + clone.outputStrings.addAll(getOutputStrings()); + clone.setName(getName()); + clone.setColor(this.getColor()); + for (Location l: locations){ + clone.locations.add(l); + } + + return clone; + } /** * Adds objects used for verifications to this component. diff --git a/src/main/java/ecdar/controllers/SimulationInitializationDialogController.java b/src/main/java/ecdar/controllers/SimulationInitializationDialogController.java index 1960d465..76eec5ad 100644 --- a/src/main/java/ecdar/controllers/SimulationInitializationDialogController.java +++ b/src/main/java/ecdar/controllers/SimulationInitializationDialogController.java @@ -22,6 +22,7 @@ public class SimulationInitializationDialogController implements Initializable { * and saves it in the public static ListOfComponents */ public void GetListOfComponentsToSimulate(){ + ListOfComponents.clear(); String componentsToSimulate = simulationComboBox.getSelectionModel().getSelectedItem(); //filters out all components by ignoring operators. Pattern pattern = Pattern.compile("([\\w]*)", Pattern.CASE_INSENSITIVE); diff --git a/src/main/java/ecdar/controllers/SimulatorController.java b/src/main/java/ecdar/controllers/SimulatorController.java index b08976ad..588a906b 100644 --- a/src/main/java/ecdar/controllers/SimulatorController.java +++ b/src/main/java/ecdar/controllers/SimulatorController.java @@ -1,5 +1,6 @@ package ecdar.controllers; +import com.google.gson.JsonObject; import ecdar.Ecdar; import ecdar.abstractions.*; import ecdar.backend.SimulationHandler; @@ -7,12 +8,14 @@ import ecdar.presentations.SimulatorOverviewPresentation; import ecdar.simulation.SimulationState; import ecdar.simulation.Transition; +import ecdar.utility.colors.Color; import javafx.beans.property.DoubleProperty; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleDoubleProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.fxml.Initializable; import javafx.scene.layout.StackPane; +import org.apache.commons.lang3.SerializationUtils; import java.net.URL; import java.util.ArrayList; @@ -81,8 +84,16 @@ private void resetSimulation() { overviewPresentation.getController().getComponentObservableList().clear(); overviewPresentation.getController().getComponentObservableList().addAll(findComponentsInCurrentSimulation(SimulationInitializationDialogController.ListOfComponents)); firstTimeInSimulator = false; + + //Method that colors all initial states. + initialstatelighter(findComponentsInCurrentSimulation(SimulationInitializationDialogController.ListOfComponents)); } + private void initialstatelighter(List listofComponents){ + for(Component comp: listofComponents){ + comp.getInitialLocation().setColor(Color.ORANGE); + } + } /** * Finds the components that are used in the current simulation by looking at the component found in * {@link Project#getComponents()} and compare them to the processes declared in the {@link SimulationHandler#getSystem()} @@ -102,7 +113,9 @@ private List findComponentsInCurrentSimulation(List queryComp for(Component comp:components) { for(String componentInQuery : queryComponents) { if((comp.getName().equals(componentInQuery))) { - SelectedComponents.add(comp); + Component temp = new Component(); + temp = comp.cloneForSimulation(); + SelectedComponents.add(temp); } } } From ae1c02c59b50f67c4c04be78de2c54089b462c05 Mon Sep 17 00:00:00 2001 From: WassawRoki <56611129+WassawRoki@users.noreply.github.com> Date: Thu, 10 Nov 2022 14:29:53 +0100 Subject: [PATCH 078/158] bingbong --- .../java/ecdar/abstractions/Component.java | 13 ---------- .../controllers/SimulatorController.java | 24 ++++++++++++------- 2 files changed, 16 insertions(+), 21 deletions(-) diff --git a/src/main/java/ecdar/abstractions/Component.java b/src/main/java/ecdar/abstractions/Component.java index d3b90f2a..bb127e87 100644 --- a/src/main/java/ecdar/abstractions/Component.java +++ b/src/main/java/ecdar/abstractions/Component.java @@ -117,20 +117,7 @@ public Component cloneForVerification() { return clone; } - public Component cloneForSimulation() { - final Component clone = new Component(); - clone.addVerificationObjects(this); - clone.setIncludeInPeriodicCheck(false); - clone.inputStrings.addAll(getInputStrings()); - clone.outputStrings.addAll(getOutputStrings()); - clone.setName(getName()); - clone.setColor(this.getColor()); - for (Location l: locations){ - clone.locations.add(l); - } - return clone; - } /** * Adds objects used for verifications to this component. diff --git a/src/main/java/ecdar/controllers/SimulatorController.java b/src/main/java/ecdar/controllers/SimulatorController.java index 588a906b..f898fcb3 100644 --- a/src/main/java/ecdar/controllers/SimulatorController.java +++ b/src/main/java/ecdar/controllers/SimulatorController.java @@ -79,20 +79,29 @@ public void willShow() { private void resetSimulation() { final SimulationHandler sm = Ecdar.getSimulationHandler(); sm.initializeDefaultSystem(); - + List listOfComponentsForSimulation = findComponentsInCurrentSimulation(SimulationInitializationDialogController.ListOfComponents); overviewPresentation.getController().clearOverview(); overviewPresentation.getController().getComponentObservableList().clear(); - overviewPresentation.getController().getComponentObservableList().addAll(findComponentsInCurrentSimulation(SimulationInitializationDialogController.ListOfComponents)); + overviewPresentation.getController().getComponentObservableList().addAll(listOfComponentsForSimulation); firstTimeInSimulator = false; //Method that colors all initial states. - initialstatelighter(findComponentsInCurrentSimulation(SimulationInitializationDialogController.ListOfComponents)); + initialstatelighter(listOfComponentsForSimulation); } private void initialstatelighter(List listofComponents){ - for(Component comp: listofComponents){ - comp.getInitialLocation().setColor(Color.ORANGE); + for(Component comp: listofComponents) + { + Location initiallocation = comp.getInitialLocation(); + initiallocation.setColor(Color.ORANGE); + List tempedge = comp.getRelatedEdges(initiallocation); + for(DisplayableEdge e: tempedge) + { + if(e.getSourceLocation() == initiallocation) + { + e.setIsHighlighted(true); + } + } } - } /** * Finds the components that are used in the current simulation by looking at the component found in @@ -113,8 +122,7 @@ private List findComponentsInCurrentSimulation(List queryComp for(Component comp:components) { for(String componentInQuery : queryComponents) { if((comp.getName().equals(componentInQuery))) { - Component temp = new Component(); - temp = comp.cloneForSimulation(); + Component temp = new Component(comp.serialize()); SelectedComponents.add(temp); } } From 5c64f424981b80929f4697cb1afe16a316c6ea9e Mon Sep 17 00:00:00 2001 From: jskad Date: Thu, 10 Nov 2022 15:37:51 +0100 Subject: [PATCH 079/158] Consume correct response --- src/main/java/ecdar/backend/QueryHandler.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/ecdar/backend/QueryHandler.java b/src/main/java/ecdar/backend/QueryHandler.java index 74453cf5..b98a0e57 100644 --- a/src/main/java/ecdar/backend/QueryHandler.java +++ b/src/main/java/ecdar/backend/QueryHandler.java @@ -113,7 +113,7 @@ private void handleQueryResponse(QueryProtos.QueryResponse value, Query query) { query.getFailureConsumer().accept(new BackendException.QueryErrorException(queryOk.getRefinement().getReason())); query.getSuccessConsumer().accept(false); query.getStateConsumer().accept(value.getQueryOk().getRefinement().getState()); - query.getActionConsumer().accept(value.getQueryOk().getConsistency().getAction()); + query.getActionConsumer().accept(value.getQueryOk().getRefinement().getAction()); } break; @@ -139,7 +139,7 @@ private void handleQueryResponse(QueryProtos.QueryResponse value, Query query) { query.getFailureConsumer().accept(new BackendException.QueryErrorException(queryOk.getDeterminism().getReason())); query.getSuccessConsumer().accept(false); query.getStateConsumer().accept(value.getQueryOk().getDeterminism().getState()); - query.getActionConsumer().accept(value.getQueryOk().getConsistency().getAction()); + query.getActionConsumer().accept(value.getQueryOk().getDeterminism().getAction()); } break; From 64197b5961f9d0fbcd4a231082c2b571d018b7cf Mon Sep 17 00:00:00 2001 From: Sigurd Date: Tue, 15 Nov 2022 12:51:45 +0100 Subject: [PATCH 080/158] Keep red color when selecting nails. Keep red color when swapping components --- .../presentations/LocationPresentation.java | 34 +++++++++++-------- .../ecdar/presentations/NailPresentation.java | 21 +++++------- 2 files changed, 28 insertions(+), 27 deletions(-) diff --git a/src/main/java/ecdar/presentations/LocationPresentation.java b/src/main/java/ecdar/presentations/LocationPresentation.java index 8d5f5969..288c52c7 100644 --- a/src/main/java/ecdar/presentations/LocationPresentation.java +++ b/src/main/java/ecdar/presentations/LocationPresentation.java @@ -162,8 +162,13 @@ private void initializeIdLabel() { // Delegate to style the label based on the color of the location final BiConsumer updateColor = (newColor, newIntensity) -> { - idLabel.setTextFill(newColor.getTextColor(newIntensity)); - ds.setColor(newColor.getColor(newIntensity)); + if (location.getFailing()) { + idLabel.setTextFill(Color.RED.getTextColor(newIntensity)); + ds.setColor(Color.RED.getColor(newIntensity)); + } else { + idLabel.setTextFill(newColor.getTextColor(newIntensity)); + ds.setColor(newColor.getColor(newIntensity)); + } }; final Consumer handleFailingUpdate = (isFailing) -> { @@ -419,21 +424,22 @@ protected void interpolate(final double frac) { // Delegate to style the label based on the color of the location final BiConsumer updateColor = (newColor, newIntensity) -> { - notCommittedShape.setFill(newColor.getColor(newIntensity)); if (!location.getUrgency().equals(Location.Urgency.PROHIBITED)) { - notCommittedShape.setStroke(newColor.getColor(newIntensity.next(2))); + if (location.getFailing()) { + notCommittedShape.setStroke(Color.RED.getColor(newIntensity)); + } else { + notCommittedShape.setStroke(newColor.getColor(newIntensity.next(2))); + } } - - committedShape.setFill(newColor.getColor(newIntensity)); - committedShape.setStroke(newColor.getColor(newIntensity.next(2))); - }; - - final Consumer handleFailingUpdate = (isFailing) -> { - if(isFailing) { - updateColor.accept(Color.RED, colorIntensity.get()); + if (location.getFailing()) { + notCommittedShape.setFill(Color.RED.getColor(newIntensity)); + committedShape.setFill(Color.RED.getColor(newIntensity)); + committedShape.setStroke(Color.RED.getColor(newIntensity.next(2))); } else { - updateColor.accept(location.getColor(), colorIntensity.get()); + notCommittedShape.setFill(newColor.getColor(newIntensity)); + committedShape.setFill(newColor.getColor(newIntensity)); + committedShape.setStroke(newColor.getColor(newIntensity.next(2))); } }; @@ -444,7 +450,7 @@ protected void interpolate(final double frac) { // Update the color of the circle when the color of the location is updated color.addListener((obs, old, newColor) -> updateColor.accept(newColor, colorIntensity.get())); - failing.addListener((obs, old, newFailing) -> handleFailingUpdate.accept(newFailing)); + failing.addListener((obs, old, newFailing) -> updateColor.accept(color.get(), colorIntensity.get())); } private void initializeTypeGraphics() { diff --git a/src/main/java/ecdar/presentations/NailPresentation.java b/src/main/java/ecdar/presentations/NailPresentation.java index f945213b..f9853567 100644 --- a/src/main/java/ecdar/presentations/NailPresentation.java +++ b/src/main/java/ecdar/presentations/NailPresentation.java @@ -213,8 +213,13 @@ private void updateNailColor() { final Runnable updateNailColor = () -> { final Color color = controller.getComponent().getColor(); final Color.Intensity colorIntensity = controller.getComponent().getColorIntensity(); - - if(!controller.getNail().getPropertyType().equals(Edge.PropertyType.NONE)) { + //If edge is failing and is a SYNC + if (controller.getEdge().getFailing() && controller.getNail().getPropertyType().equals(Edge.PropertyType.SYNCHRONIZATION)) { + controller.nailCircle.setFill(Color.RED.getColor(colorIntensity)); + controller.nailCircle.setStroke(Color.RED.getColor(colorIntensity.next(2))); + } + //If edge is not NONE + else if (!controller.getNail().getPropertyType().equals(Edge.PropertyType.NONE)) { controller.nailCircle.setFill(color.getColor(colorIntensity)); controller.nailCircle.setStroke(color.getColor(colorIntensity.next(2))); } else { @@ -258,17 +263,7 @@ public void select() { @Override public void deselect() { - Color color = Color.GREY_BLUE; - Color.Intensity intensity = Color.Intensity.I800; - - // Set the color - if(!controller.getNail().getPropertyType().equals(Edge.PropertyType.NONE)) { - color = controller.getComponent().getColor(); - intensity = controller.getComponent().getColorIntensity(); - } - - controller.nailCircle.setFill(color.getColor(intensity)); - controller.nailCircle.setStroke(color.getColor(intensity.next(2))); + updateNailColor(); } public NailController getController() { From 6abd4a61e183d24d4161eb410a906683ddc0c4cf Mon Sep 17 00:00:00 2001 From: APaludan Date: Wed, 16 Nov 2022 09:36:51 +0100 Subject: [PATCH 081/158] fix build error --- .../java/ecdar/simulation/SimulationTest.java | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/test/java/ecdar/simulation/SimulationTest.java b/src/test/java/ecdar/simulation/SimulationTest.java index 2acb8136..6d20da38 100644 --- a/src/test/java/ecdar/simulation/SimulationTest.java +++ b/src/test/java/ecdar/simulation/SimulationTest.java @@ -32,24 +32,24 @@ public void testGetInitialStateHighlightsTheInitialLocation() { final List components = generateComponentsWithInitialLocations(); BindableService testService = new EcdarBackendGrpc.EcdarBackendImplBase() { - @Override - public void startSimulation(QueryProtos.SimulationStartRequest request, - StreamObserver responseObserver) { - try { - ObjectProtos.StateTuple state = ObjectProtos.StateTuple.newBuilder().addAllLocations(components.stream() - .map(c -> ObjectProtos.StateTuple.LocationTuple.newBuilder() - .setComponentName(c.getName()) - .setId(c.getInitialLocation().getId()) - .build()) - .collect(Collectors.toList())).build(); + // @Override + // public void startSimulation(QueryProtos.SimulationStartRequest request, + // StreamObserver responseObserver) { + // try { + // ObjectProtos.StateTuple state = ObjectProtos.StateTuple.newBuilder().addAllLocations(components.stream() + // .map(c -> ObjectProtos.StateTuple.LocationTuple.newBuilder() + // .setComponentName(c.getName()) + // .setId(c.getInitialLocation().getId()) + // .build()) + // .collect(Collectors.toList())).build(); - QueryProtos.SimulationStepResponse response = QueryProtos.SimulationStepResponse.newBuilder().setState(state).build(); - responseObserver.onNext(response); - responseObserver.onCompleted(); - } catch (Throwable e) { - responseObserver.onError(Status.INVALID_ARGUMENT.withDescription(e.getMessage()).asException()); - } - } + // QueryProtos.SimulationStepResponse response = QueryProtos.SimulationStepResponse.newBuilder().setState(state).build(); + // responseObserver.onNext(response); + // responseObserver.onCompleted(); + // } catch (Throwable e) { + // responseObserver.onError(Status.INVALID_ARGUMENT.withDescription(e.getMessage()).asException()); + // } + // } @Override public void takeSimulationStep(EcdarProtoBuf.QueryProtos.SimulationStepRequest request, From 43854b72617a1e04ba2026f743911dd393b76b70 Mon Sep 17 00:00:00 2001 From: APaludan Date: Wed, 16 Nov 2022 09:42:52 +0100 Subject: [PATCH 082/158] fix test --- .../java/ecdar/simulation/SimulationTest.java | 98 +++++++++---------- 1 file changed, 49 insertions(+), 49 deletions(-) diff --git a/src/test/java/ecdar/simulation/SimulationTest.java b/src/test/java/ecdar/simulation/SimulationTest.java index 6d20da38..625968a5 100644 --- a/src/test/java/ecdar/simulation/SimulationTest.java +++ b/src/test/java/ecdar/simulation/SimulationTest.java @@ -27,63 +27,63 @@ public class SimulationTest extends TestFXBase { public GrpcCleanupRule grpcCleanup = new GrpcCleanupRule(); private final String serverName = InProcessServerBuilder.generateName(); - @Test - public void testGetInitialStateHighlightsTheInitialLocation() { - final List components = generateComponentsWithInitialLocations(); + // @Test + // public void testGetInitialStateHighlightsTheInitialLocation() { + // final List components = generateComponentsWithInitialLocations(); - BindableService testService = new EcdarBackendGrpc.EcdarBackendImplBase() { - // @Override - // public void startSimulation(QueryProtos.SimulationStartRequest request, - // StreamObserver responseObserver) { - // try { - // ObjectProtos.StateTuple state = ObjectProtos.StateTuple.newBuilder().addAllLocations(components.stream() - // .map(c -> ObjectProtos.StateTuple.LocationTuple.newBuilder() - // .setComponentName(c.getName()) - // .setId(c.getInitialLocation().getId()) - // .build()) - // .collect(Collectors.toList())).build(); + // BindableService testService = new EcdarBackendGrpc.EcdarBackendImplBase() { + // @Override + // public void startSimulation(QueryProtos.SimulationStartRequest request, + // StreamObserver responseObserver) { + // try { + // ObjectProtos.State state = ObjectProtos.StateTuple.newBuilder().addAllLocations(components.stream() + // .map(c -> ObjectProtos.StateTuple.LocationTuple.newBuilder() + // .setComponentName(c.getName()) + // .setId(c.getInitialLocation().getId()) + // .build()) + // .collect(Collectors.toList())).build(); - // QueryProtos.SimulationStepResponse response = QueryProtos.SimulationStepResponse.newBuilder().setState(state).build(); - // responseObserver.onNext(response); - // responseObserver.onCompleted(); - // } catch (Throwable e) { - // responseObserver.onError(Status.INVALID_ARGUMENT.withDescription(e.getMessage()).asException()); - // } - // } + // QueryProtos.SimulationStepResponse response = QueryProtos.SimulationStepResponse.newBuilder().setState(state).build(); + // responseObserver.onNext(response); + // responseObserver.onCompleted(); + // } catch (Throwable e) { + // responseObserver.onError(Status.INVALID_ARGUMENT.withDescription(e.getMessage()).asException()); + // } + // } - @Override - public void takeSimulationStep(EcdarProtoBuf.QueryProtos.SimulationStepRequest request, - io.grpc.stub.StreamObserver responseObserver) { - } - }; + // @Override + // public void takeSimulationStep(EcdarProtoBuf.QueryProtos.SimulationStepRequest request, + // io.grpc.stub.StreamObserver responseObserver) { + // } + // }; - final Server server; - final ManagedChannel channel; - final EcdarBackendGrpc.EcdarBackendBlockingStub stub; - try { - server = grpcCleanup.register(InProcessServerBuilder - .forName(serverName).directExecutor().addService(testService).build().start()); - channel = grpcCleanup.register(InProcessChannelBuilder - .forName(serverName).directExecutor().build()); - stub = EcdarBackendGrpc.newBlockingStub(channel); - QueryProtos.SimulationStartRequest request = QueryProtos.SimulationStartRequest.newBuilder().setSystem("(A || B)").build(); + // final Server server; + // final ManagedChannel channel; + // final EcdarBackendGrpc.EcdarBackendBlockingStub stub; + // try { + // server = grpcCleanup.register(InProcessServerBuilder + // .forName(serverName).directExecutor().addService(testService).build().start()); + // channel = grpcCleanup.register(InProcessChannelBuilder + // .forName(serverName).directExecutor().build()); + // stub = EcdarBackendGrpc.newBlockingStub(channel); + // QueryProtos.SimulationStartRequest request = QueryProtos.SimulationStartRequest.newBuilder().setSystem("(A || B)").build(); - var expectedResponse = new ObjectProtos.StateTuple.LocationTuple[components.size()]; + // var expectedResponse = new ObjectProtos.StateTuple.LocationTuple[components.size()]; - for (int i = 0; i < components.size(); i++) { - Component comp = components.get(i); - expectedResponse[i] = ObjectProtos.StateTuple.LocationTuple.newBuilder() - .setComponentName(comp.getName()) - .setId(comp.getInitialLocation().getId()).build(); - } + // for (int i = 0; i < components.size(); i++) { + // Component comp = components.get(i); + // expectedResponse[i] = ObjectProtos.StateTuple.LocationTuple.newBuilder() + // .setComponentName(comp.getName()) + // .setId(comp.getInitialLocation().getId()).build(); + // } - var result = stub.startSimulation(request).getState().getLocationsList().toArray(); + // var result = stub.startSimulation(request).getState().getLocationsList().toArray(); - Assertions.assertArrayEquals(expectedResponse, result); - } catch (IOException e) { - Assertions.fail("Exception encountered: " + e.getMessage()); - } - } + // Assertions.assertArrayEquals(expectedResponse, result); + // } catch (IOException e) { + // Assertions.fail("Exception encountered: " + e.getMessage()); + // } + // } private List generateComponentsWithInitialLocations() { List comps = new ArrayList<>(); From 639d24b951ed03fa65a74e15f1ef20a12bd53480 Mon Sep 17 00:00:00 2001 From: APaludan Date: Thu, 17 Nov 2022 12:43:12 +0100 Subject: [PATCH 083/158] fix test fr --- .../java/ecdar/simulation/SimulationTest.java | 111 ++++++++++-------- 1 file changed, 61 insertions(+), 50 deletions(-) diff --git a/src/test/java/ecdar/simulation/SimulationTest.java b/src/test/java/ecdar/simulation/SimulationTest.java index 625968a5..d1906460 100644 --- a/src/test/java/ecdar/simulation/SimulationTest.java +++ b/src/test/java/ecdar/simulation/SimulationTest.java @@ -3,12 +3,14 @@ import EcdarProtoBuf.EcdarBackendGrpc; import EcdarProtoBuf.ObjectProtos; import EcdarProtoBuf.QueryProtos; +import EcdarProtoBuf.ObjectProtos.DecisionPoint; +import EcdarProtoBuf.ObjectProtos.LocationTuple; +import EcdarProtoBuf.ObjectProtos.SpecificComponent; import ecdar.TestFXBase; import ecdar.abstractions.Component; import ecdar.abstractions.Location; import io.grpc.BindableService; import io.grpc.ManagedChannel; -import io.grpc.Server; import io.grpc.Status; import io.grpc.inprocess.InProcessChannelBuilder; import io.grpc.inprocess.InProcessServerBuilder; @@ -21,69 +23,78 @@ import java.util.stream.Collectors; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.TestInstance.Lifecycle; import org.junit.jupiter.api.Assertions; +@TestInstance(Lifecycle.PER_CLASS) public class SimulationTest extends TestFXBase { public GrpcCleanupRule grpcCleanup = new GrpcCleanupRule(); private final String serverName = InProcessServerBuilder.generateName(); - // @Test - // public void testGetInitialStateHighlightsTheInitialLocation() { - // final List components = generateComponentsWithInitialLocations(); + @Test + public void testGetInitialStateHighlightsTheInitialLocation() { + final List components = generateComponentsWithInitialLocations(); - // BindableService testService = new EcdarBackendGrpc.EcdarBackendImplBase() { - // @Override - // public void startSimulation(QueryProtos.SimulationStartRequest request, - // StreamObserver responseObserver) { - // try { - // ObjectProtos.State state = ObjectProtos.StateTuple.newBuilder().addAllLocations(components.stream() - // .map(c -> ObjectProtos.StateTuple.LocationTuple.newBuilder() - // .setComponentName(c.getName()) - // .setId(c.getInitialLocation().getId()) - // .build()) - // .collect(Collectors.toList())).build(); + BindableService testService = new EcdarBackendGrpc.EcdarBackendImplBase() { + @Override + public void startSimulation(QueryProtos.SimulationStartRequest request, + StreamObserver responseObserver) { + try { + ObjectProtos.LocationTuple locations = LocationTuple.newBuilder().addAllLocations(components.stream() + .map(c -> ObjectProtos.Location.newBuilder() + .setSpecificComponent(SpecificComponent.newBuilder() + .setComponentName(c.getName())) + .setId(c.getInitialLocation().getId()) + .build()) + .collect(Collectors.toList())) + .build(); + + ObjectProtos.State state = ObjectProtos.State.newBuilder().setLocationTuple(locations).build(); + DecisionPoint decisionPoint = DecisionPoint.newBuilder().setSource(state).build(); + QueryProtos.SimulationStepResponse response = QueryProtos.SimulationStepResponse.newBuilder() + .setNewDecisionPoint(decisionPoint) + .build(); + responseObserver.onNext(response); + responseObserver.onCompleted(); + } catch (Throwable e) { + responseObserver.onError(Status.INVALID_ARGUMENT.withDescription(e.getMessage()).asException()); + } + } - // QueryProtos.SimulationStepResponse response = QueryProtos.SimulationStepResponse.newBuilder().setState(state).build(); - // responseObserver.onNext(response); - // responseObserver.onCompleted(); - // } catch (Throwable e) { - // responseObserver.onError(Status.INVALID_ARGUMENT.withDescription(e.getMessage()).asException()); - // } - // } + @Override + public void takeSimulationStep(EcdarProtoBuf.QueryProtos.SimulationStepRequest request, + io.grpc.stub.StreamObserver responseObserver) { + } + }; - // @Override - // public void takeSimulationStep(EcdarProtoBuf.QueryProtos.SimulationStepRequest request, - // io.grpc.stub.StreamObserver responseObserver) { - // } - // }; + final ManagedChannel channel; + final EcdarBackendGrpc.EcdarBackendBlockingStub stub; + try { + grpcCleanup.register(InProcessServerBuilder + .forName(serverName).directExecutor().addService(testService).build().start()); + channel = grpcCleanup.register(InProcessChannelBuilder + .forName(serverName).directExecutor().build()); + stub = EcdarBackendGrpc.newBlockingStub(channel); - // final Server server; - // final ManagedChannel channel; - // final EcdarBackendGrpc.EcdarBackendBlockingStub stub; - // try { - // server = grpcCleanup.register(InProcessServerBuilder - // .forName(serverName).directExecutor().addService(testService).build().start()); - // channel = grpcCleanup.register(InProcessChannelBuilder - // .forName(serverName).directExecutor().build()); - // stub = EcdarBackendGrpc.newBlockingStub(channel); - // QueryProtos.SimulationStartRequest request = QueryProtos.SimulationStartRequest.newBuilder().setSystem("(A || B)").build(); + QueryProtos.SimulationStartRequest request = QueryProtos.SimulationStartRequest.newBuilder().build(); - // var expectedResponse = new ObjectProtos.StateTuple.LocationTuple[components.size()]; + var expectedResponse = new ObjectProtos.Location[components.size()]; - // for (int i = 0; i < components.size(); i++) { - // Component comp = components.get(i); - // expectedResponse[i] = ObjectProtos.StateTuple.LocationTuple.newBuilder() - // .setComponentName(comp.getName()) - // .setId(comp.getInitialLocation().getId()).build(); - // } + for (int i = 0; i < components.size(); i++) { + Component comp = components.get(i); + expectedResponse[i] = ObjectProtos.Location.newBuilder() + .setSpecificComponent(SpecificComponent.newBuilder().setComponentName(comp.getName())) + .setId(comp.getInitialLocation().getId()).build(); + } - // var result = stub.startSimulation(request).getState().getLocationsList().toArray(); + var result = stub.startSimulation(request).getNewDecisionPoint().getSource().getLocationTuple().getLocationsList().toArray(); - // Assertions.assertArrayEquals(expectedResponse, result); - // } catch (IOException e) { - // Assertions.fail("Exception encountered: " + e.getMessage()); - // } - // } + Assertions.assertArrayEquals(expectedResponse, result); + } catch (IOException e) { + Assertions.fail("Exception encountered: " + e.getMessage()); + } + } private List generateComponentsWithInitialLocations() { List comps = new ArrayList<>(); From 0db2ea2ccb912dd6f0d98fa58e2559fafabafc43 Mon Sep 17 00:00:00 2001 From: Dolmer1 Date: Thu, 17 Nov 2022 13:11:02 +0100 Subject: [PATCH 084/158] PR changes --- .gitmodules | 3 ++- .../java/ecdar/backend/SimulationHandler.java | 18 ++++-------------- .../ecdar/controllers/EcdarController.java | 2 +- ...mulationInitializationDialogController.java | 8 ++++---- .../ecdar/controllers/SimulatorController.java | 9 +++------ ...lationInitializationDialogPresentation.fxml | 2 +- 6 files changed, 15 insertions(+), 27 deletions(-) diff --git a/.gitmodules b/.gitmodules index 2e15cc85..53cad6ef 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,4 @@ [submodule "src/main/proto"] path = src/main/proto - url = https://github.com/Ecdar-SW5/Ecdar-ProtoBuf.git + url = ../Ecdar-ProtoBuf.git + diff --git a/src/main/java/ecdar/backend/SimulationHandler.java b/src/main/java/ecdar/backend/SimulationHandler.java index d58d7b3b..adcb8f29 100755 --- a/src/main/java/ecdar/backend/SimulationHandler.java +++ b/src/main/java/ecdar/backend/SimulationHandler.java @@ -28,7 +28,7 @@ */ public class SimulationHandler { public static final String QUERY_PREFIX = "Query: "; - public String composition; + private String composition; private ObjectProperty currentConcreteState = new SimpleObjectProperty<>(); private ObjectProperty initialConcreteState = new SimpleObjectProperty<>(); private ObjectProperty currentTime = new SimpleObjectProperty<>(); @@ -38,13 +38,6 @@ public class SimulationHandler { private SimulationStateSuccessor successor; private int numberOfSteps; - /** - * A string to keep track what is currently being simulated - * For now the string is prefixed with {@link #QUERY_PREFIX} when doing a query simulation - * and kept empty when doing system simulations - */ - private String currentSimulation = ""; - private final ObservableMap simulationVariables = FXCollections.observableHashMap(); private final ObservableMap simulationClocks = FXCollections.observableHashMap(); /** @@ -68,9 +61,6 @@ public SimulationHandler(BackendDriver backendDriver) { /** * Initializes the default system (non-query system) */ - public void initializeDefaultSystem() { - currentSimulation = ""; - } /** * Initializes the values and properties in the {@link SimulationHandler}. @@ -457,9 +447,9 @@ public EcdarSystem getSystem() { return system; } - public String getCurrentSimulation() { - return currentSimulation; - } + public String getComposition() { return composition;} + + public void setComposition(String composition) {this.composition = composition;} public boolean isSimulationRunning() { return false; // ToDo: Implement diff --git a/src/main/java/ecdar/controllers/EcdarController.java b/src/main/java/ecdar/controllers/EcdarController.java index a8bd7c5a..75ff103d 100644 --- a/src/main/java/ecdar/controllers/EcdarController.java +++ b/src/main/java/ecdar/controllers/EcdarController.java @@ -294,7 +294,7 @@ private void initializeDialogs() { simulationInitializationDialog.getController().startButton.setOnMouseClicked(event -> { // ToDo NIELS: Start simulation of selected query - Ecdar.getSimulationHandler().composition = simulationInitializationDialog.getController().simulationComboBox.getSelectionModel().getSelectedItem(); + Ecdar.getSimulationHandler().setComposition(simulationInitializationDialog.getController().simulationComboBox.getSelectionModel().getSelectedItem()); currentMode.setValue(Mode.Simulator); simulationInitializationDialog.close(); }); diff --git a/src/main/java/ecdar/controllers/SimulationInitializationDialogController.java b/src/main/java/ecdar/controllers/SimulationInitializationDialogController.java index 1960d465..4e42e76b 100644 --- a/src/main/java/ecdar/controllers/SimulationInitializationDialogController.java +++ b/src/main/java/ecdar/controllers/SimulationInitializationDialogController.java @@ -21,20 +21,20 @@ public class SimulationInitializationDialogController implements Initializable { * Function gets list of components to simulation * and saves it in the public static ListOfComponents */ - public void GetListOfComponentsToSimulate(){ + public void SetListOfComponentsToSimulate(){ String componentsToSimulate = simulationComboBox.getSelectionModel().getSelectedItem(); //filters out all components by ignoring operators. Pattern pattern = Pattern.compile("([\\w]*)", Pattern.CASE_INSENSITIVE); Matcher matcher = pattern.matcher(componentsToSimulate); - List listOfComponents = new ArrayList<>(); + List listOfComponentsToSimulate = new ArrayList<>(); //Adds all found components to list. while(matcher.find()){ if(matcher.group().length() != 0) { - listOfComponents.add(matcher.group()); + listOfComponentsToSimulate.add(matcher.group()); } } - ListOfComponents = listOfComponents; + ListOfComponents = listOfComponentsToSimulate; } public void initialize(URL location, ResourceBundle resources) { diff --git a/src/main/java/ecdar/controllers/SimulatorController.java b/src/main/java/ecdar/controllers/SimulatorController.java index b08976ad..39789bfc 100644 --- a/src/main/java/ecdar/controllers/SimulatorController.java +++ b/src/main/java/ecdar/controllers/SimulatorController.java @@ -51,7 +51,7 @@ public void willShow() { if (sm.getCurrentState() == null) sm.initialStep(); // ToDo NIELS: Find better solution //Have the user left a trace or is he simulating a query - if (sm.traceLog.size() >= 2 || sm.getCurrentSimulation().contains(SimulationHandler.QUERY_PREFIX)) { + if (sm.traceLog.size() >= 2) { shouldSimulationBeReset = false; } @@ -75,7 +75,6 @@ public void willShow() { */ private void resetSimulation() { final SimulationHandler sm = Ecdar.getSimulationHandler(); - sm.initializeDefaultSystem(); overviewPresentation.getController().clearOverview(); overviewPresentation.getController().getComponentObservableList().clear(); @@ -84,10 +83,8 @@ private void resetSimulation() { } /** - * Finds the components that are used in the current simulation by looking at the component found in - * {@link Project#getComponents()} and compare them to the processes declared in the {@link SimulationHandler#getSystem()} - *

- * TODO This does currently not work if the same component is used multiple times. + * Finds the components that are used in the current simulation by looking at the components found in + * Ecdar.getProject.getComponents() and compares them to the components found in the queryComponents list * * @return all the components used in the current simulation */ diff --git a/src/main/resources/ecdar/presentations/SimulationInitializationDialogPresentation.fxml b/src/main/resources/ecdar/presentations/SimulationInitializationDialogPresentation.fxml index 6df548fa..8fa6635b 100644 --- a/src/main/resources/ecdar/presentations/SimulationInitializationDialogPresentation.fxml +++ b/src/main/resources/ecdar/presentations/SimulationInitializationDialogPresentation.fxml @@ -36,7 +36,7 @@ - + From 36f559fb99d22a2b5607e257e578278ddc74a684 Mon Sep 17 00:00:00 2001 From: APaludan Date: Thu, 17 Nov 2022 13:31:13 +0100 Subject: [PATCH 085/158] Update .gitmodules --- .gitmodules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitmodules b/.gitmodules index 53cad6ef..808fd3cf 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,4 +1,4 @@ [submodule "src/main/proto"] path = src/main/proto - url = ../Ecdar-ProtoBuf.git + url = https://github.com/Ecdar-SW5/Ecdar-ProtoBuf.git From 8f0c405647cde56736f9f9429b37f6c507650e01 Mon Sep 17 00:00:00 2001 From: APaludan Date: Thu, 17 Nov 2022 13:48:06 +0100 Subject: [PATCH 086/158] maybe github checks works now? --- src/test/java/ecdar/TestFXBase.java | 8 +++++--- src/test/java/ecdar/simulation/SimulationTest.java | 1 - 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/test/java/ecdar/TestFXBase.java b/src/test/java/ecdar/TestFXBase.java index e54eecec..843527ee 100644 --- a/src/test/java/ecdar/TestFXBase.java +++ b/src/test/java/ecdar/TestFXBase.java @@ -5,6 +5,7 @@ import javafx.stage.Stage; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; +import org.testfx.api.FxRobot; import org.testfx.api.FxToolkit; import org.testfx.framework.junit.ApplicationTest; @@ -22,9 +23,10 @@ public void start(Stage stage) { } @AfterAll - public void afterEachTest() throws TimeoutException { + public static void afterEachTest() throws TimeoutException { FxToolkit.hideStage(); - release(new KeyCode[]{}); - release(new MouseButton[]{}); + FxRobot robot = new FxRobot(); + robot.release(new KeyCode[]{}); + robot.release(new MouseButton[]{}); } } diff --git a/src/test/java/ecdar/simulation/SimulationTest.java b/src/test/java/ecdar/simulation/SimulationTest.java index d1906460..c815e9f3 100644 --- a/src/test/java/ecdar/simulation/SimulationTest.java +++ b/src/test/java/ecdar/simulation/SimulationTest.java @@ -27,7 +27,6 @@ import org.junit.jupiter.api.TestInstance.Lifecycle; import org.junit.jupiter.api.Assertions; -@TestInstance(Lifecycle.PER_CLASS) public class SimulationTest extends TestFXBase { public GrpcCleanupRule grpcCleanup = new GrpcCleanupRule(); private final String serverName = InProcessServerBuilder.generateName(); From d1b7b948194242d1c9a57708aaca45b1a656df68 Mon Sep 17 00:00:00 2001 From: Dolmer1 Date: Thu, 17 Nov 2022 13:59:25 +0100 Subject: [PATCH 087/158] deleted variable that is never used --- src/main/java/ecdar/controllers/SimulatorController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ecdar/controllers/SimulatorController.java b/src/main/java/ecdar/controllers/SimulatorController.java index 39789bfc..c791abf1 100644 --- a/src/main/java/ecdar/controllers/SimulatorController.java +++ b/src/main/java/ecdar/controllers/SimulatorController.java @@ -90,8 +90,8 @@ private void resetSimulation() { */ private List findComponentsInCurrentSimulation(List queryComponents) { //Show components from the system - final SimulationHandler sm = Ecdar.getSimulationHandler(); List components = new ArrayList<>(); + components = Ecdar.getProject().getComponents(); //Matches query components against with existing components and adds them to simulation From f2fc61ce73ab51149db145683388df2070e4cc0f Mon Sep 17 00:00:00 2001 From: APaludan Date: Thu, 17 Nov 2022 14:01:23 +0100 Subject: [PATCH 088/158] Update build.yml --- .github/workflows/build.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 797f2ae0..2b3a8a10 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -16,8 +16,9 @@ jobs: submodules: recursive - name: Set up JDK 11 - uses: actions/setup-java@v1 + uses: actions/setup-java@v2 with: + distribution: 'adopt' java-version: 11 java-package: jdk - name: Cache Gradle packages From caa80a917325f7e9896f5e3cf3fe08d949b9b69c Mon Sep 17 00:00:00 2001 From: APaludan Date: Thu, 17 Nov 2022 22:02:57 +0100 Subject: [PATCH 089/158] Update SimulationTest.java --- src/test/java/ecdar/simulation/SimulationTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/ecdar/simulation/SimulationTest.java b/src/test/java/ecdar/simulation/SimulationTest.java index c815e9f3..16aba017 100644 --- a/src/test/java/ecdar/simulation/SimulationTest.java +++ b/src/test/java/ecdar/simulation/SimulationTest.java @@ -27,7 +27,7 @@ import org.junit.jupiter.api.TestInstance.Lifecycle; import org.junit.jupiter.api.Assertions; -public class SimulationTest extends TestFXBase { +public class SimulationTest { public GrpcCleanupRule grpcCleanup = new GrpcCleanupRule(); private final String serverName = InProcessServerBuilder.generateName(); From 874f4e227b31426874558f6c0e00dee991e3a59d Mon Sep 17 00:00:00 2001 From: APaludan Date: Fri, 18 Nov 2022 10:08:40 +0100 Subject: [PATCH 090/158] remove unused imports --- .../java/ecdar/abstractions/ComponentInstance.java | 1 - src/main/java/ecdar/abstractions/Declarations.java | 3 --- .../java/ecdar/abstractions/EcdarSystemEdge.java | 1 - src/main/java/ecdar/abstractions/Edge.java | 1 - src/main/java/ecdar/abstractions/Location.java | 1 - src/main/java/ecdar/abstractions/Nail.java | 1 - src/main/java/ecdar/abstractions/Query.java | 1 - src/main/java/ecdar/abstractions/Transition.java | 5 ----- src/main/java/ecdar/backend/QueryHandler.java | 1 - src/main/java/ecdar/backend/SimulationHandler.java | 1 - .../java/ecdar/controllers/ComponentController.java | 1 - .../controllers/ComponentOperatorController.java | 1 - .../ecdar/controllers/LeftSimPaneController.java | 1 - .../java/ecdar/controllers/LocationController.java | 1 - .../ecdar/controllers/ProjectPaneController.java | 1 - .../java/ecdar/controllers/QueryPaneController.java | 10 ---------- .../SimulationInitializationDialogController.java | 2 -- .../java/ecdar/controllers/SimulatorController.java | 1 - .../ecdar/controllers/SystemEdgeController.java | 1 - src/main/java/ecdar/mutation/ExportHandler.java | 3 --- .../ecdar/mutation/TestCaseGenerationHandler.java | 2 -- src/main/java/ecdar/presentations/DropDownMenu.java | 3 +-- src/main/java/ecdar/presentations/Link.java | 3 +-- src/main/java/ecdar/presentations/MenuElement.java | 12 +++--------- .../presentations/MultiSyncTagPresentation.java | 1 - .../java/ecdar/presentations/QueryPresentation.java | 13 ------------- .../presentations/RightSimPanePresentation.java | 1 - .../SimulationInitializationDialogPresentation.java | 2 -- .../java/ecdar/presentations/TagPresentation.java | 2 -- src/main/java/ecdar/simulation/SimulationState.java | 2 -- .../ecdar/simulation/SimulationStateSuccessor.java | 2 -- .../java/ecdar/utility/helpers/ItemDragHelper.java | 3 --- .../java/ecdar/utility/helpers/MouseCircular.java | 3 --- src/test/java/ecdar/mutation/StrategyRuleTest.java | 2 -- .../operators/ChangeTargetOperatorTest.java | 1 - .../operators/SinkLocationOperatorTest.java | 3 +-- src/test/java/ecdar/simulation/SimulationTest.java | 3 --- src/test/java/ecdar/ui/SidePaneTest.java | 2 -- 38 files changed, 6 insertions(+), 92 deletions(-) diff --git a/src/main/java/ecdar/abstractions/ComponentInstance.java b/src/main/java/ecdar/abstractions/ComponentInstance.java index 391cae3d..260b44ce 100644 --- a/src/main/java/ecdar/abstractions/ComponentInstance.java +++ b/src/main/java/ecdar/abstractions/ComponentInstance.java @@ -2,7 +2,6 @@ import ecdar.Ecdar; import ecdar.presentations.Grid; -import ecdar.utility.colors.Color; import com.google.gson.JsonObject; import javafx.beans.property.*; import javafx.beans.value.ObservableValue; diff --git a/src/main/java/ecdar/abstractions/Declarations.java b/src/main/java/ecdar/abstractions/Declarations.java index 38f179cb..2014d729 100644 --- a/src/main/java/ecdar/abstractions/Declarations.java +++ b/src/main/java/ecdar/abstractions/Declarations.java @@ -1,15 +1,12 @@ package ecdar.abstractions; import ecdar.utility.colors.Color; -import ecdar.utility.colors.EnabledColor; -import com.google.gson.JsonArray; import com.google.gson.JsonObject; import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; import org.apache.commons.lang3.tuple.Triple; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; diff --git a/src/main/java/ecdar/abstractions/EcdarSystemEdge.java b/src/main/java/ecdar/abstractions/EcdarSystemEdge.java index d9d30f2c..503e4831 100644 --- a/src/main/java/ecdar/abstractions/EcdarSystemEdge.java +++ b/src/main/java/ecdar/abstractions/EcdarSystemEdge.java @@ -2,7 +2,6 @@ import ecdar.Ecdar; import ecdar.controllers.SystemRootController; -import com.google.gson.JsonElement; import com.google.gson.JsonObject; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; diff --git a/src/main/java/ecdar/abstractions/Edge.java b/src/main/java/ecdar/abstractions/Edge.java index 2b1057c9..995e2ce8 100644 --- a/src/main/java/ecdar/abstractions/Edge.java +++ b/src/main/java/ecdar/abstractions/Edge.java @@ -1,6 +1,5 @@ package ecdar.abstractions; -import EcdarProtoBuf.ComponentProtos; import com.google.gson.JsonPrimitive; import ecdar.Ecdar; import ecdar.controllers.EcdarController; diff --git a/src/main/java/ecdar/abstractions/Location.java b/src/main/java/ecdar/abstractions/Location.java index 183b1fe0..f56ed000 100644 --- a/src/main/java/ecdar/abstractions/Location.java +++ b/src/main/java/ecdar/abstractions/Location.java @@ -1,6 +1,5 @@ package ecdar.abstractions; -import EcdarProtoBuf.ComponentProtos; import ecdar.Ecdar; import ecdar.code_analysis.Nearable; import ecdar.controllers.EcdarController; diff --git a/src/main/java/ecdar/abstractions/Nail.java b/src/main/java/ecdar/abstractions/Nail.java index f7555432..75375d19 100644 --- a/src/main/java/ecdar/abstractions/Nail.java +++ b/src/main/java/ecdar/abstractions/Nail.java @@ -1,6 +1,5 @@ package ecdar.abstractions; -import EcdarProtoBuf.ComponentProtos; import ecdar.utility.helpers.Circular; import ecdar.utility.serialize.Serializable; import com.google.gson.Gson; diff --git a/src/main/java/ecdar/abstractions/Query.java b/src/main/java/ecdar/abstractions/Query.java index 639ce469..874e8a25 100644 --- a/src/main/java/ecdar/abstractions/Query.java +++ b/src/main/java/ecdar/abstractions/Query.java @@ -4,7 +4,6 @@ import ecdar.backend.*; import ecdar.controllers.EcdarController; import ecdar.utility.helpers.StringHelper; -import ecdar.utility.helpers.StringValidator; import ecdar.utility.serialize.Serializable; import com.google.gson.JsonObject; import javafx.application.Platform; diff --git a/src/main/java/ecdar/abstractions/Transition.java b/src/main/java/ecdar/abstractions/Transition.java index 9aa71eeb..c79d4f21 100644 --- a/src/main/java/ecdar/abstractions/Transition.java +++ b/src/main/java/ecdar/abstractions/Transition.java @@ -1,12 +1,7 @@ package ecdar.abstractions; import EcdarProtoBuf.ObjectProtos; -import ecdar.Ecdar; - import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.stream.Collectors; public class Transition { public final ArrayList edges = new ArrayList<>(); diff --git a/src/main/java/ecdar/backend/QueryHandler.java b/src/main/java/ecdar/backend/QueryHandler.java index 5f76ce1e..500a7cc4 100644 --- a/src/main/java/ecdar/backend/QueryHandler.java +++ b/src/main/java/ecdar/backend/QueryHandler.java @@ -1,6 +1,5 @@ package ecdar.backend; -import EcdarProtoBuf.ComponentProtos; import EcdarProtoBuf.QueryProtos; import com.google.gson.JsonObject; import com.google.gson.JsonParser; diff --git a/src/main/java/ecdar/backend/SimulationHandler.java b/src/main/java/ecdar/backend/SimulationHandler.java index adcb8f29..4277bf09 100755 --- a/src/main/java/ecdar/backend/SimulationHandler.java +++ b/src/main/java/ecdar/backend/SimulationHandler.java @@ -1,7 +1,6 @@ package ecdar.backend; import EcdarProtoBuf.ComponentProtos; -import EcdarProtoBuf.ObjectProtos; import EcdarProtoBuf.QueryProtos; import ecdar.Ecdar; import ecdar.abstractions.*; diff --git a/src/main/java/ecdar/controllers/ComponentController.java b/src/main/java/ecdar/controllers/ComponentController.java index 9d67e2aa..24a546d4 100644 --- a/src/main/java/ecdar/controllers/ComponentController.java +++ b/src/main/java/ecdar/controllers/ComponentController.java @@ -13,7 +13,6 @@ import javafx.animation.Interpolator; import javafx.animation.Transition; import javafx.application.Platform; -import javafx.beans.binding.DoubleBinding; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.beans.value.ChangeListener; diff --git a/src/main/java/ecdar/controllers/ComponentOperatorController.java b/src/main/java/ecdar/controllers/ComponentOperatorController.java index f0b7c606..7da44f1c 100644 --- a/src/main/java/ecdar/controllers/ComponentOperatorController.java +++ b/src/main/java/ecdar/controllers/ComponentOperatorController.java @@ -14,7 +14,6 @@ import javafx.scene.control.Label; import javafx.scene.input.MouseButton; import javafx.scene.input.MouseEvent; -import javafx.scene.layout.Pane; import javafx.scene.layout.StackPane; import javafx.scene.shape.Circle; import javafx.scene.shape.Polygon; diff --git a/src/main/java/ecdar/controllers/LeftSimPaneController.java b/src/main/java/ecdar/controllers/LeftSimPaneController.java index d8dc30eb..7f68aaaa 100755 --- a/src/main/java/ecdar/controllers/LeftSimPaneController.java +++ b/src/main/java/ecdar/controllers/LeftSimPaneController.java @@ -2,7 +2,6 @@ import ecdar.presentations.TracePaneElementPresentation; import ecdar.presentations.TransitionPaneElementPresentation; -import javafx.fxml.FXML; import javafx.fxml.Initializable; import javafx.scene.control.ScrollPane; import javafx.scene.layout.StackPane; diff --git a/src/main/java/ecdar/controllers/LocationController.java b/src/main/java/ecdar/controllers/LocationController.java index 0e36a07e..4d442513 100644 --- a/src/main/java/ecdar/controllers/LocationController.java +++ b/src/main/java/ecdar/controllers/LocationController.java @@ -9,7 +9,6 @@ import ecdar.utility.UndoRedoStack; import ecdar.utility.colors.Color; import ecdar.utility.helpers.ItemDragHelper; -import ecdar.utility.helpers.MouseCircular; import ecdar.utility.helpers.SelectHelper; import ecdar.utility.keyboard.Keybind; import ecdar.utility.keyboard.KeyboardTracker; diff --git a/src/main/java/ecdar/controllers/ProjectPaneController.java b/src/main/java/ecdar/controllers/ProjectPaneController.java index 02c68003..8923cad2 100644 --- a/src/main/java/ecdar/controllers/ProjectPaneController.java +++ b/src/main/java/ecdar/controllers/ProjectPaneController.java @@ -21,7 +21,6 @@ import javafx.scene.control.Label; import javafx.scene.control.ScrollPane; import javafx.scene.image.ImageView; -import javafx.scene.layout.AnchorPane; import javafx.scene.layout.HBox; import javafx.scene.layout.StackPane; import javafx.scene.layout.VBox; diff --git a/src/main/java/ecdar/controllers/QueryPaneController.java b/src/main/java/ecdar/controllers/QueryPaneController.java index 26c003dd..ad2cef79 100644 --- a/src/main/java/ecdar/controllers/QueryPaneController.java +++ b/src/main/java/ecdar/controllers/QueryPaneController.java @@ -3,16 +3,9 @@ import ecdar.Ecdar; import ecdar.abstractions.Query; import ecdar.abstractions.QueryState; -import ecdar.backend.*; -import ecdar.presentations.Grid; import ecdar.presentations.QueryPresentation; import com.jfoenix.controls.JFXRippler; -import ecdar.utility.UndoRedoStack; import ecdar.utility.colors.Color; -import javafx.beans.property.BooleanProperty; -import javafx.beans.property.DoubleProperty; -import javafx.beans.property.SimpleBooleanProperty; -import javafx.beans.property.SimpleDoubleProperty; import javafx.collections.ListChangeListener; import javafx.fxml.FXML; import javafx.fxml.Initializable; @@ -21,9 +14,6 @@ import javafx.scene.control.Label; import javafx.scene.control.ScrollPane; import javafx.scene.layout.*; -import javafx.scene.paint.Paint; -import javafx.scene.shape.Rectangle; - import java.net.URL; import java.util.HashMap; import java.util.Map; diff --git a/src/main/java/ecdar/controllers/SimulationInitializationDialogController.java b/src/main/java/ecdar/controllers/SimulationInitializationDialogController.java index 4e42e76b..bc952bad 100644 --- a/src/main/java/ecdar/controllers/SimulationInitializationDialogController.java +++ b/src/main/java/ecdar/controllers/SimulationInitializationDialogController.java @@ -2,8 +2,6 @@ import com.jfoenix.controls.JFXButton; import com.jfoenix.controls.JFXComboBox; -import ecdar.Ecdar; -import javafx.fxml.FXML; import javafx.fxml.Initializable; import java.net.URL; diff --git a/src/main/java/ecdar/controllers/SimulatorController.java b/src/main/java/ecdar/controllers/SimulatorController.java index c791abf1..d0715558 100644 --- a/src/main/java/ecdar/controllers/SimulatorController.java +++ b/src/main/java/ecdar/controllers/SimulatorController.java @@ -3,7 +3,6 @@ import ecdar.Ecdar; import ecdar.abstractions.*; import ecdar.backend.SimulationHandler; -import ecdar.presentations.SimulationInitializationDialogPresentation; import ecdar.presentations.SimulatorOverviewPresentation; import ecdar.simulation.SimulationState; import ecdar.simulation.Transition; diff --git a/src/main/java/ecdar/controllers/SystemEdgeController.java b/src/main/java/ecdar/controllers/SystemEdgeController.java index f8e46cf5..de846446 100644 --- a/src/main/java/ecdar/controllers/SystemEdgeController.java +++ b/src/main/java/ecdar/controllers/SystemEdgeController.java @@ -16,7 +16,6 @@ import javafx.scene.input.KeyCodeCombination; import javafx.scene.input.MouseButton; import javafx.scene.input.MouseEvent; -import javafx.scene.layout.Pane; import javafx.scene.shape.Circle; import java.net.URL; diff --git a/src/main/java/ecdar/mutation/ExportHandler.java b/src/main/java/ecdar/mutation/ExportHandler.java index fb5455cb..d75dcdfc 100644 --- a/src/main/java/ecdar/mutation/ExportHandler.java +++ b/src/main/java/ecdar/mutation/ExportHandler.java @@ -3,9 +3,6 @@ import com.google.gson.GsonBuilder; import ecdar.Ecdar; import ecdar.abstractions.Component; -import ecdar.abstractions.Project; -import ecdar.backend.BackendException; -import ecdar.backend.BackendHelper; import ecdar.mutation.models.MutationTestCase; import ecdar.mutation.models.MutationTestPlan; import ecdar.mutation.operators.MutationOperator; diff --git a/src/main/java/ecdar/mutation/TestCaseGenerationHandler.java b/src/main/java/ecdar/mutation/TestCaseGenerationHandler.java index 7d610874..8f408cbd 100644 --- a/src/main/java/ecdar/mutation/TestCaseGenerationHandler.java +++ b/src/main/java/ecdar/mutation/TestCaseGenerationHandler.java @@ -2,12 +2,10 @@ import ecdar.Ecdar; import ecdar.abstractions.Component; -import ecdar.abstractions.Project; import ecdar.backend.BackendException; import ecdar.backend.BackendHelper; import ecdar.mutation.models.MutationTestCase; import ecdar.mutation.models.MutationTestPlan; -import ecdar.mutation.models.NonRefinementStrategy; import javafx.application.Platform; import javafx.scene.paint.Color; import javafx.scene.text.Text; diff --git a/src/main/java/ecdar/presentations/DropDownMenu.java b/src/main/java/ecdar/presentations/DropDownMenu.java index 0fb91c8f..308f0033 100644 --- a/src/main/java/ecdar/presentations/DropDownMenu.java +++ b/src/main/java/ecdar/presentations/DropDownMenu.java @@ -14,7 +14,6 @@ import javafx.geometry.Pos; import javafx.scene.Node; import javafx.scene.control.Label; -import javafx.scene.control.ScrollPane; import javafx.scene.input.MouseEvent; import javafx.scene.layout.*; import javafx.scene.shape.Circle; @@ -24,7 +23,7 @@ import org.kordamp.ikonli.javafx.FontIcon; import javax.swing.*; -import java.util.Stack; + import java.util.function.BiConsumer; import java.util.function.Consumer; diff --git a/src/main/java/ecdar/presentations/Link.java b/src/main/java/ecdar/presentations/Link.java index d4e30147..1c3cc98e 100644 --- a/src/main/java/ecdar/presentations/Link.java +++ b/src/main/java/ecdar/presentations/Link.java @@ -1,9 +1,8 @@ package ecdar.presentations; import ecdar.Debug; -import ecdar.abstractions.EdgeStatus; -import ecdar.utility.colors.Color; import ecdar.utility.Highlightable; +import ecdar.utility.colors.Color; import ecdar.utility.helpers.SelectHelper; import javafx.beans.property.DoubleProperty; import javafx.beans.property.SimpleDoubleProperty; diff --git a/src/main/java/ecdar/presentations/MenuElement.java b/src/main/java/ecdar/presentations/MenuElement.java index 8a145e1e..3094fdeb 100644 --- a/src/main/java/ecdar/presentations/MenuElement.java +++ b/src/main/java/ecdar/presentations/MenuElement.java @@ -1,12 +1,8 @@ package ecdar.presentations; -import ecdar.Ecdar; -import ecdar.controllers.EcdarController; import ecdar.utility.colors.Color; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.value.ObservableBooleanValue; -import javafx.css.CssMetaData; -import javafx.css.Styleable; import javafx.geometry.Insets; import javafx.scene.Node; import javafx.scene.control.Label; @@ -14,15 +10,13 @@ import javafx.scene.layout.*; import org.kordamp.ikonli.javafx.FontIcon; - -import java.util.List; import java.util.function.Consumer; import static javafx.scene.paint.Color.TRANSPARENT; import static javafx.scene.paint.Color.WHITE; -/** - * Represents an element of the dropdown menu, excluding spacer and the colour palette element. + +/* Represents an element of the dropdown menu, excluding spacer and the colour palette element. */ public class MenuElement { public static final javafx.scene.paint.Color DISABLED_COLOR = Color.GREY_BLUE.getColor(Color.Intensity.I300); @@ -110,7 +104,7 @@ public MenuElement(final String s, final String iconString, final Consumer mouseEventConsumer){ rippler = new ReleaseRippler(clickListenerFix); - rippler.setRipplerFill(javafx.scene.paint.Color.TRANSPARENT); + rippler.setRipplerFill(TRANSPARENT); rippler.setOnMouseEntered(event -> { if (isDisabled.get()) return; diff --git a/src/main/java/ecdar/presentations/MultiSyncTagPresentation.java b/src/main/java/ecdar/presentations/MultiSyncTagPresentation.java index c5929f93..e3f86927 100644 --- a/src/main/java/ecdar/presentations/MultiSyncTagPresentation.java +++ b/src/main/java/ecdar/presentations/MultiSyncTagPresentation.java @@ -4,7 +4,6 @@ import ecdar.abstractions.DisplayableEdge; import ecdar.abstractions.Edge; import ecdar.abstractions.GroupedEdge; -import ecdar.abstractions.Nail; import ecdar.controllers.EcdarController; import ecdar.controllers.MultiSyncTagController; import ecdar.utility.UndoRedoStack; diff --git a/src/main/java/ecdar/presentations/QueryPresentation.java b/src/main/java/ecdar/presentations/QueryPresentation.java index 5a897137..1d1e8c72 100644 --- a/src/main/java/ecdar/presentations/QueryPresentation.java +++ b/src/main/java/ecdar/presentations/QueryPresentation.java @@ -19,22 +19,9 @@ import javafx.scene.input.KeyCode; import javafx.scene.layout.*; import javafx.geometry.Point2D; -import javafx.geometry.Pos; -import javafx.geometry.Rectangle2D; -import javafx.scene.Cursor; -import javafx.scene.control.Label; -import javafx.scene.control.ScrollPane; -import javafx.scene.control.TitledPane; -import javafx.scene.control.Tooltip; -import javafx.scene.input.KeyCode; -import javafx.scene.layout.*; -import javafx.scene.text.TextAlignment; -import javafx.stage.Screen; -import javafx.stage.StageStyle; import org.kordamp.ikonli.javafx.FontIcon; import org.kordamp.ikonli.material.Material; -import javax.swing.*; import java.util.Map; import java.util.Set; import java.util.function.Consumer; diff --git a/src/main/java/ecdar/presentations/RightSimPanePresentation.java b/src/main/java/ecdar/presentations/RightSimPanePresentation.java index 48c79f1c..2345664d 100755 --- a/src/main/java/ecdar/presentations/RightSimPanePresentation.java +++ b/src/main/java/ecdar/presentations/RightSimPanePresentation.java @@ -2,7 +2,6 @@ import ecdar.controllers.RightSimPaneController; import ecdar.utility.colors.Color; -import ecdar.utility.helpers.DropShadowHelper; import javafx.geometry.Insets; import javafx.scene.layout.*; diff --git a/src/main/java/ecdar/presentations/SimulationInitializationDialogPresentation.java b/src/main/java/ecdar/presentations/SimulationInitializationDialogPresentation.java index 100d3b52..e9c9664a 100644 --- a/src/main/java/ecdar/presentations/SimulationInitializationDialogPresentation.java +++ b/src/main/java/ecdar/presentations/SimulationInitializationDialogPresentation.java @@ -1,8 +1,6 @@ package ecdar.presentations; -import com.jfoenix.controls.JFXComboBox; import com.jfoenix.controls.JFXDialog; -import ecdar.controllers.BackendOptionsDialogController; import ecdar.controllers.SimulationInitializationDialogController; public class SimulationInitializationDialogPresentation extends JFXDialog { diff --git a/src/main/java/ecdar/presentations/TagPresentation.java b/src/main/java/ecdar/presentations/TagPresentation.java index a4d4610f..f5b32273 100644 --- a/src/main/java/ecdar/presentations/TagPresentation.java +++ b/src/main/java/ecdar/presentations/TagPresentation.java @@ -17,10 +17,8 @@ import javafx.geometry.Insets; import javafx.scene.Cursor; import javafx.scene.control.Label; -import javafx.scene.input.KeyCode; import javafx.scene.input.MouseEvent; import javafx.scene.layout.StackPane; -import javafx.scene.robot.Robot; import javafx.scene.shape.LineTo; import javafx.scene.shape.MoveTo; import javafx.scene.shape.Path; diff --git a/src/main/java/ecdar/simulation/SimulationState.java b/src/main/java/ecdar/simulation/SimulationState.java index 935ec5de..0a1c03f8 100644 --- a/src/main/java/ecdar/simulation/SimulationState.java +++ b/src/main/java/ecdar/simulation/SimulationState.java @@ -1,12 +1,10 @@ package ecdar.simulation; import EcdarProtoBuf.ObjectProtos; -import ecdar.abstractions.Location; import javafx.util.Pair; import java.math.BigDecimal; import java.util.ArrayList; -import java.util.stream.Collectors; public class SimulationState { private final ArrayList> locations; diff --git a/src/main/java/ecdar/simulation/SimulationStateSuccessor.java b/src/main/java/ecdar/simulation/SimulationStateSuccessor.java index d05f59b0..bf6eb85d 100644 --- a/src/main/java/ecdar/simulation/SimulationStateSuccessor.java +++ b/src/main/java/ecdar/simulation/SimulationStateSuccessor.java @@ -1,7 +1,5 @@ package ecdar.simulation; -import ecdar.Ecdar; - import java.util.ArrayList; public class SimulationStateSuccessor { diff --git a/src/main/java/ecdar/utility/helpers/ItemDragHelper.java b/src/main/java/ecdar/utility/helpers/ItemDragHelper.java index b12375c5..2b601271 100644 --- a/src/main/java/ecdar/utility/helpers/ItemDragHelper.java +++ b/src/main/java/ecdar/utility/helpers/ItemDragHelper.java @@ -3,7 +3,6 @@ import ecdar.controllers.EcdarController; import ecdar.controllers.EdgeController; import ecdar.presentations.ComponentOperatorPresentation; -import ecdar.presentations.ComponentPresentation; import ecdar.presentations.Grid; import ecdar.utility.UndoRedoStack; import javafx.beans.property.BooleanProperty; @@ -19,8 +18,6 @@ import java.util.List; import java.util.function.Supplier; -import static ecdar.presentations.Grid.GRID_SIZE; - public class ItemDragHelper { public static class DragBounds { diff --git a/src/main/java/ecdar/utility/helpers/MouseCircular.java b/src/main/java/ecdar/utility/helpers/MouseCircular.java index 6814b108..3672f52b 100644 --- a/src/main/java/ecdar/utility/helpers/MouseCircular.java +++ b/src/main/java/ecdar/utility/helpers/MouseCircular.java @@ -1,13 +1,10 @@ package ecdar.utility.helpers; import ecdar.controllers.EcdarController; -import ecdar.presentations.Grid; import ecdar.utility.mouse.MouseTracker; import javafx.beans.property.DoubleProperty; import javafx.beans.property.SimpleDoubleProperty; -import static ecdar.presentations.Grid.GRID_SIZE; - public class MouseCircular implements Circular { private final DoubleProperty x = new SimpleDoubleProperty(0d); private final DoubleProperty y = new SimpleDoubleProperty(0d); diff --git a/src/test/java/ecdar/mutation/StrategyRuleTest.java b/src/test/java/ecdar/mutation/StrategyRuleTest.java index 2fad1c8c..5ed7144a 100644 --- a/src/test/java/ecdar/mutation/StrategyRuleTest.java +++ b/src/test/java/ecdar/mutation/StrategyRuleTest.java @@ -5,8 +5,6 @@ import org.junit.jupiter.api.Assertions; import java.util.HashMap; import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; public class StrategyRuleTest { diff --git a/src/test/java/ecdar/mutation/operators/ChangeTargetOperatorTest.java b/src/test/java/ecdar/mutation/operators/ChangeTargetOperatorTest.java index 102ef715..fe3a869e 100644 --- a/src/test/java/ecdar/mutation/operators/ChangeTargetOperatorTest.java +++ b/src/test/java/ecdar/mutation/operators/ChangeTargetOperatorTest.java @@ -5,7 +5,6 @@ import ecdar.abstractions.Edge; import ecdar.abstractions.EdgeStatus; import ecdar.abstractions.Location; -import ecdar.mutation.operators.ChangeTargetOperator; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Assertions; diff --git a/src/test/java/ecdar/mutation/operators/SinkLocationOperatorTest.java b/src/test/java/ecdar/mutation/operators/SinkLocationOperatorTest.java index b4d9a492..8970f1f4 100644 --- a/src/test/java/ecdar/mutation/operators/SinkLocationOperatorTest.java +++ b/src/test/java/ecdar/mutation/operators/SinkLocationOperatorTest.java @@ -4,9 +4,8 @@ import ecdar.abstractions.Edge; import ecdar.abstractions.EdgeStatus; import ecdar.abstractions.Location; -import ecdar.mutation.operators.SinkLocationOperator; -import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; public class SinkLocationOperatorTest { diff --git a/src/test/java/ecdar/simulation/SimulationTest.java b/src/test/java/ecdar/simulation/SimulationTest.java index 16aba017..c0584f3a 100644 --- a/src/test/java/ecdar/simulation/SimulationTest.java +++ b/src/test/java/ecdar/simulation/SimulationTest.java @@ -6,7 +6,6 @@ import EcdarProtoBuf.ObjectProtos.DecisionPoint; import EcdarProtoBuf.ObjectProtos.LocationTuple; import EcdarProtoBuf.ObjectProtos.SpecificComponent; -import ecdar.TestFXBase; import ecdar.abstractions.Component; import ecdar.abstractions.Location; import io.grpc.BindableService; @@ -23,8 +22,6 @@ import java.util.stream.Collectors; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInstance; -import org.junit.jupiter.api.TestInstance.Lifecycle; import org.junit.jupiter.api.Assertions; public class SimulationTest { diff --git a/src/test/java/ecdar/ui/SidePaneTest.java b/src/test/java/ecdar/ui/SidePaneTest.java index 73214791..b38a65de 100644 --- a/src/test/java/ecdar/ui/SidePaneTest.java +++ b/src/test/java/ecdar/ui/SidePaneTest.java @@ -11,8 +11,6 @@ import org.testfx.util.WaitForAsyncUtils; import java.util.Set; -import java.util.concurrent.Callable; -import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.function.Predicate; From 51fd6ecdb9b8bd0a99de13bd8850bf0c403562ac Mon Sep 17 00:00:00 2001 From: APaludan Date: Fri, 18 Nov 2022 10:42:19 +0100 Subject: [PATCH 091/158] edit comment --- src/main/java/ecdar/controllers/SimulatorController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ecdar/controllers/SimulatorController.java b/src/main/java/ecdar/controllers/SimulatorController.java index d0715558..e5c47843 100644 --- a/src/main/java/ecdar/controllers/SimulatorController.java +++ b/src/main/java/ecdar/controllers/SimulatorController.java @@ -49,7 +49,7 @@ public void willShow() { if (sm.getCurrentState() == null) sm.initialStep(); // ToDo NIELS: Find better solution - //Have the user left a trace or is he simulating a query + //Have the user left a trace if (sm.traceLog.size() >= 2) { shouldSimulationBeReset = false; } From 080597d136e525481d0150ef22ba361cdfff1c7c Mon Sep 17 00:00:00 2001 From: Sigurd Date: Fri, 18 Nov 2022 11:43:25 +0100 Subject: [PATCH 092/158] small changes after merge --- .../java/ecdar/abstractions/Component.java | 7 +------ src/main/java/ecdar/backend/QueryHandler.java | 2 +- .../presentations/LocationPresentation.java | 18 +----------------- 3 files changed, 3 insertions(+), 24 deletions(-) diff --git a/src/main/java/ecdar/abstractions/Component.java b/src/main/java/ecdar/abstractions/Component.java index a5cdb5f8..37789bfe 100644 --- a/src/main/java/ecdar/abstractions/Component.java +++ b/src/main/java/ecdar/abstractions/Component.java @@ -547,12 +547,7 @@ public void removeFailingLocations() { public ObservableList getFailingEdges() { return failingEdges; } - * Observable list of all failing locations. - * @return Observable list of all failing locations. - */ - public ObservableList getFailingLocations() { - return failingLocations; - } + /** * Returns all DisplayableEdges of the component (returning a list potentially containing GroupEdges and Edges) diff --git a/src/main/java/ecdar/backend/QueryHandler.java b/src/main/java/ecdar/backend/QueryHandler.java index 4a495eac..8e018da4 100644 --- a/src/main/java/ecdar/backend/QueryHandler.java +++ b/src/main/java/ecdar/backend/QueryHandler.java @@ -138,7 +138,7 @@ private void handleQueryResponse(QueryProtos.QueryResponse value, Query query) { query.getFailureConsumer().accept(new BackendException.QueryErrorException(queryOk.getDeterminism().getReason())); query.getSuccessConsumer().accept(false); query.getStateConsumer().accept(value.getQueryOk().getDeterminism().getState()); - query.getActionConsumer().accept(value.getQueryOk().getDeterminism().getAction(); + query.getActionConsumer().accept(value.getQueryOk().getDeterminism().getAction()); } break; diff --git a/src/main/java/ecdar/presentations/LocationPresentation.java b/src/main/java/ecdar/presentations/LocationPresentation.java index e8e433ae..0b25e090 100644 --- a/src/main/java/ecdar/presentations/LocationPresentation.java +++ b/src/main/java/ecdar/presentations/LocationPresentation.java @@ -171,22 +171,6 @@ private void initializeIdLabel() { } }; - final Consumer handleFailingUpdate = (isFailing) -> { - if(isFailing) { - updateColor.accept(Color.RED, colorIntensity.get()); - } else { - updateColor.accept(location.getColor(), colorIntensity.get()); - } - }; - - final Consumer handleFailingUpdate = (isFailing) -> { - if(isFailing) { - updateColor.accept(Color.RED, colorIntensity.get()); - } else { - updateColor.accept(location.getColor(), colorIntensity.get()); - } - }; - updateColorDelegates.add(updateColor); // Set the initial color @@ -194,7 +178,7 @@ private void initializeIdLabel() { // Update the color of the circle when the color of the location is updated color.addListener((obs, old, newColor) -> updateColor.accept(newColor, colorIntensity.get())); - failing.addListener((obs, old, newFailing) -> handleFailingUpdate.accept(newFailing)); + failing.addListener((obs, old, newFailing) -> updateColor.accept(color.get(), colorIntensity.get() )); } private void initializeTags() { From f63033a0dcd1843d6f7c06c7da2c0f9adebc7840 Mon Sep 17 00:00:00 2001 From: Sigurd Date: Fri, 18 Nov 2022 12:12:31 +0100 Subject: [PATCH 093/158] Update submodule --- src/main/proto | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/proto b/src/main/proto index c181269f..22f7b52a 160000 --- a/src/main/proto +++ b/src/main/proto @@ -1 +1 @@ -Subproject commit c181269f734c85ec51ae7e09de10cb685e54c595 +Subproject commit 22f7b52a65a7dcf56563eb7b5d353ce4c47dd231 From 5ed4439164d207af7fe0c33ca772c4c612f6dde6 Mon Sep 17 00:00:00 2001 From: Sigurd Date: Mon, 21 Nov 2022 13:23:56 +0100 Subject: [PATCH 094/158] Implemented specific component in consumer --- src/main/java/ecdar/abstractions/Query.java | 41 +++++++++---------- src/main/java/ecdar/backend/QueryHandler.java | 23 +++++++---- 2 files changed, 34 insertions(+), 30 deletions(-) diff --git a/src/main/java/ecdar/abstractions/Query.java b/src/main/java/ecdar/abstractions/Query.java index 86599dbe..76cc5930 100644 --- a/src/main/java/ecdar/abstractions/Query.java +++ b/src/main/java/ecdar/abstractions/Query.java @@ -10,6 +10,7 @@ import javafx.application.Platform; import javafx.beans.property.*; +import java.util.function.BiConsumer; import java.util.function.Consumer; public class Query implements Serializable { @@ -62,25 +63,24 @@ public class Query implements Serializable { } }; - private final Consumer stateConsumer = (state) -> { + private final BiConsumer stateActionConsumer = (state, action) -> { for (Component c : Ecdar.getProject().getComponents()) { c.removeFailingLocations(); - if (query.getValue().contains(c.getName())) { - for (ObjectProtos.Location location : state.getLocationTuple().getLocationsList()) { - c.addFailingLocation(location.getId()); - } - } - } - }; - - private final Consumer actionConsumer = (action) -> { - for (Component c : Ecdar.getProject().getComponents()) { c.removeFailingEdges(); - if (query.getValue().contains(c.getName())) { - for (Edge edge : c.getEdges()) { - if(action.equals(edge.getSync()) && edge.getSourceLocation().getFailing()) { - c.addFailingEdge(edge); - } + } + for (ObjectProtos.Location location : state.getLocationTuple().getLocationsList()) { + Component c = Ecdar.getProject().findComponent(location.getSpecificComponent().getComponentName()); + if (c == null) { + throw new NullPointerException("Could not find the specific component: " + location.getSpecificComponent().getComponentName()); + } + Location l = c.findLocation(location.getId()); + if (l == null) { + throw new NullPointerException("Could not find location: " + location.getId()); + } + c.addFailingLocation(l.getId()); + for (Edge edge : c.getEdges()) { + if(action.equals(edge.getSync()) && edge.getSourceLocation() == l) { + c.addFailingEdge(edge); } } } @@ -176,16 +176,13 @@ public Consumer getFailureConsumer() { } /** - * Getter for the state consumer. + * Getter for the state action consumer. * @return The State Consumer */ - public Consumer getStateConsumer() { - return stateConsumer; + public BiConsumer getStateActionConsumer() { + return stateActionConsumer; } - public Consumer getActionConsumer() { - return actionConsumer; - } @Override public JsonObject serialize() { diff --git a/src/main/java/ecdar/backend/QueryHandler.java b/src/main/java/ecdar/backend/QueryHandler.java index 8e018da4..c068c2f7 100644 --- a/src/main/java/ecdar/backend/QueryHandler.java +++ b/src/main/java/ecdar/backend/QueryHandler.java @@ -97,6 +97,7 @@ public void closeAllBackendConnections() throws IOException { } private void handleQueryResponse(QueryProtos.QueryResponse value, Query query) { + System.out.println(value); // If the query has been cancelled, ignore the result if (query.getQueryState() == QueryState.UNKNOWN) return; switch (value.getResponseCase()) { @@ -111,8 +112,8 @@ private void handleQueryResponse(QueryProtos.QueryResponse value, Query query) { query.setQueryState(QueryState.ERROR); query.getFailureConsumer().accept(new BackendException.QueryErrorException(queryOk.getRefinement().getReason())); query.getSuccessConsumer().accept(false); - query.getStateConsumer().accept(value.getQueryOk().getRefinement().getState()); - query.getActionConsumer().accept(value.getQueryOk().getRefinement().getAction()); + query.getStateActionConsumer().accept(value.getQueryOk().getRefinement().getState(), + value.getQueryOk().getRefinement().getAction()); } break; @@ -124,8 +125,9 @@ private void handleQueryResponse(QueryProtos.QueryResponse value, Query query) { query.setQueryState(QueryState.ERROR); query.getFailureConsumer().accept(new BackendException.QueryErrorException(queryOk.getConsistency().getReason())); query.getSuccessConsumer().accept(false); - query.getStateConsumer().accept(value.getQueryOk().getConsistency().getState()); - query.getActionConsumer().accept(value.getQueryOk().getConsistency().getAction()); + query.getStateActionConsumer().accept(value.getQueryOk().getConsistency().getState(), + value.getQueryOk().getConsistency().getAction()); + } break; @@ -137,8 +139,9 @@ private void handleQueryResponse(QueryProtos.QueryResponse value, Query query) { query.setQueryState(QueryState.ERROR); query.getFailureConsumer().accept(new BackendException.QueryErrorException(queryOk.getDeterminism().getReason())); query.getSuccessConsumer().accept(false); - query.getStateConsumer().accept(value.getQueryOk().getDeterminism().getState()); - query.getActionConsumer().accept(value.getQueryOk().getDeterminism().getAction()); + query.getStateActionConsumer().accept(value.getQueryOk().getDeterminism().getState(), + value.getQueryOk().getDeterminism().getAction()); + } break; @@ -150,7 +153,9 @@ private void handleQueryResponse(QueryProtos.QueryResponse value, Query query) { query.setQueryState(QueryState.ERROR); query.getFailureConsumer().accept(new BackendException.QueryErrorException(queryOk.getImplementation().getReason())); query.getSuccessConsumer().accept(false); - query.getStateConsumer().accept(value.getQueryOk().getImplementation().getState()); + //ToDo: These errors are not implemented in the Reveaal backend. + query.getStateActionConsumer().accept(value.getQueryOk().getImplementation().getState(), + ""); } break; @@ -162,7 +167,9 @@ private void handleQueryResponse(QueryProtos.QueryResponse value, Query query) { query.setQueryState(QueryState.ERROR); query.getFailureConsumer().accept(new BackendException.QueryErrorException(queryOk.getReachability().getReason())); query.getSuccessConsumer().accept(false); - query.getStateConsumer().accept(value.getQueryOk().getReachability().getState()); + //ToDo: These errors are not implemented in the Reveaal backend. + query.getStateActionConsumer().accept(value.getQueryOk().getReachability().getState(), + ""); } break; From 27a8b93a9ff3d2305b3bb1e3f73c9d2ee38bb7f9 Mon Sep 17 00:00:00 2001 From: WassawRoki <56611129+WassawRoki@users.noreply.github.com> Date: Mon, 21 Nov 2022 13:51:59 +0100 Subject: [PATCH 095/158] fixed --- src/main/java/ecdar/controllers/SimulatorController.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/main/java/ecdar/controllers/SimulatorController.java b/src/main/java/ecdar/controllers/SimulatorController.java index 591d794b..50851fc4 100644 --- a/src/main/java/ecdar/controllers/SimulatorController.java +++ b/src/main/java/ecdar/controllers/SimulatorController.java @@ -76,8 +76,6 @@ public void willShow() { * {@link SimulatorOverviewController#processContainer} and adding the processes of the new simulation. */ private void resetSimulation() { - final SimulationHandler sm = Ecdar.getSimulationHandler(); - sm.initializeDefaultSystem(); List listOfComponentsForSimulation = findComponentsInCurrentSimulation(SimulationInitializationDialogController.ListOfComponents); overviewPresentation.getController().clearOverview(); overviewPresentation.getController().getComponentObservableList().clear(); @@ -93,13 +91,13 @@ private void initialstatelighter(List listofComponents){ Location initiallocation = comp.getInitialLocation(); initiallocation.setColor(Color.ORANGE); List tempedge = comp.getRelatedEdges(initiallocation); - for(DisplayableEdge e: tempedge) + /* for(DisplayableEdge e: tempedge) { if(e.getSourceLocation() == initiallocation) { e.setIsHighlighted(true); } - } + }*/ } } /** From dd6179d857fbc2ab49e6034d251ede0b4de0c1a9 Mon Sep 17 00:00:00 2001 From: "Julie H. Bengtsson" <82820935+jhbengtsson@users.noreply.github.com> Date: Mon, 21 Nov 2022 13:55:28 +0100 Subject: [PATCH 096/158] Update Component.java --- src/main/java/ecdar/abstractions/Component.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/java/ecdar/abstractions/Component.java b/src/main/java/ecdar/abstractions/Component.java index 76f16fc2..37789bfe 100644 --- a/src/main/java/ecdar/abstractions/Component.java +++ b/src/main/java/ecdar/abstractions/Component.java @@ -99,7 +99,6 @@ public Component(final JsonObject json) { bindReachabilityAnalysis(); } - /** * Creates a clone of another component. * Copies objects used for verification (e.g. locations, edges and the declarations). @@ -120,7 +119,6 @@ public Component cloneForVerification() { return clone; } - /** * Adds objects used for verifications to this component. * @param original the component to add from From bccc37dc91f4a1684bbd9dbbeaff03474134bf34 Mon Sep 17 00:00:00 2001 From: "Julie H. Bengtsson" <82820935+jhbengtsson@users.noreply.github.com> Date: Mon, 21 Nov 2022 13:55:51 +0100 Subject: [PATCH 097/158] Update SimulatorController.java --- src/main/java/ecdar/controllers/SimulatorController.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/ecdar/controllers/SimulatorController.java b/src/main/java/ecdar/controllers/SimulatorController.java index 50851fc4..da2acc9c 100644 --- a/src/main/java/ecdar/controllers/SimulatorController.java +++ b/src/main/java/ecdar/controllers/SimulatorController.java @@ -85,6 +85,7 @@ private void resetSimulation() { //Method that colors all initial states. initialstatelighter(listOfComponentsForSimulation); } + private void initialstatelighter(List listofComponents){ for(Component comp: listofComponents) { From e3b6a39a3bad453de1fd64376223eef436bff287 Mon Sep 17 00:00:00 2001 From: Sigurd Date: Mon, 21 Nov 2022 14:13:54 +0100 Subject: [PATCH 098/158] Remove unused imports --- src/main/java/ecdar/controllers/SimulatorController.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/java/ecdar/controllers/SimulatorController.java b/src/main/java/ecdar/controllers/SimulatorController.java index da2acc9c..36539552 100644 --- a/src/main/java/ecdar/controllers/SimulatorController.java +++ b/src/main/java/ecdar/controllers/SimulatorController.java @@ -1,6 +1,5 @@ package ecdar.controllers; -import com.google.gson.JsonObject; import ecdar.Ecdar; import ecdar.abstractions.*; import ecdar.backend.SimulationHandler; @@ -14,7 +13,6 @@ import javafx.beans.property.SimpleObjectProperty; import javafx.fxml.Initializable; import javafx.scene.layout.StackPane; -import org.apache.commons.lang3.SerializationUtils; import java.net.URL; import java.util.ArrayList; From f04ec3a98ad6cb14011292943ab0fc1d6e32ba39 Mon Sep 17 00:00:00 2001 From: WassawRoki <56611129+WassawRoki@users.noreply.github.com> Date: Mon, 21 Nov 2022 14:28:19 +0100 Subject: [PATCH 099/158] It works --- .../controllers/SimLocationController.java | 43 ++++++++++++++++++- .../SimLocationPresentation.java | 12 ++++-- 2 files changed, 50 insertions(+), 5 deletions(-) diff --git a/src/main/java/ecdar/controllers/SimLocationController.java b/src/main/java/ecdar/controllers/SimLocationController.java index c8c39522..c8a0d467 100755 --- a/src/main/java/ecdar/controllers/SimLocationController.java +++ b/src/main/java/ecdar/controllers/SimLocationController.java @@ -1,7 +1,9 @@ package ecdar.controllers; -import ecdar.abstractions.Component; -import ecdar.abstractions.Location; +import com.jfoenix.controls.JFXPopup; +import ecdar.abstractions.*; +import ecdar.backend.BackendHelper; +import ecdar.presentations.DropDownMenu; import ecdar.presentations.SimLocationPresentation; import ecdar.presentations.SimTagPresentation; import ecdar.utility.colors.Color; @@ -14,6 +16,9 @@ import javafx.scene.shape.Circle; import javafx.scene.shape.Line; import javafx.scene.shape.Path; +import javafx.scene.input.MouseButton; +import javafx.scene.input.MouseEvent; +import java.util.function.Consumer; import java.net.URL; import java.util.ResourceBundle; @@ -36,6 +41,7 @@ public class SimLocationController implements Initializable { public Label idLabel; public Line nameTagLine; public Line invariantTagLine; + private DropDownMenu dropDownMenu; @Override public void initialize(final URL location, final ResourceBundle resources) { @@ -49,8 +55,41 @@ public void initialize(final URL location, final ResourceBundle resources) { // Scale x and y 1:1 (based on the x-scale) scaleContent.scaleYProperty().bind(scaleContent.scaleXProperty()); + initializeMouseControls(); } + private void initializeMouseControls() { + final Consumer mouseClicked = (event) -> { + if (root.isPlaced()) { + if (event.getButton().equals(MouseButton.SECONDARY)) { + initializeDropDownMenu(); + dropDownMenu.show(JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.LEFT, 0, 0); + } + } + + }; + locationProperty().addListener((obs, oldLocation, newLocation) -> { + if(newLocation == null) return; + root.addEventHandler(MouseEvent.MOUSE_CLICKED, mouseClicked::accept); + }); + } + public void initializeDropDownMenu(){ + dropDownMenu = new DropDownMenu(root); + + dropDownMenu.addClickableListElement("Is " + getLocation().getId() + " reachable?", event -> { + dropDownMenu.hide(); + // Generate the query from the backend + final String reachabilityQuery = BackendHelper.getLocationReachableQuery(getLocation(), getComponent()); + + // Add proper comment + final String reachabilityComment = "Is " + getLocation().getMostDescriptiveIdentifier() + " reachable?"; + + // Add new query for this location + final Query query = new Query(reachabilityQuery, reachabilityComment, QueryState.UNKNOWN); + query.setType(QueryType.REACHABILITY); + dropDownMenu.hide(); + }); + } public Location getLocation() { return location.get(); } diff --git a/src/main/java/ecdar/presentations/SimLocationPresentation.java b/src/main/java/ecdar/presentations/SimLocationPresentation.java index e250433a..c4198ba4 100755 --- a/src/main/java/ecdar/presentations/SimLocationPresentation.java +++ b/src/main/java/ecdar/presentations/SimLocationPresentation.java @@ -9,9 +9,7 @@ import ecdar.utility.helpers.SelectHelper; import javafx.animation.*; import javafx.beans.binding.DoubleBinding; -import javafx.beans.property.DoubleProperty; -import javafx.beans.property.ObjectProperty; -import javafx.beans.property.SimpleDoubleProperty; +import javafx.beans.property.*; import javafx.scene.Group; import javafx.scene.control.Label; import javafx.scene.effect.DropShadow; @@ -46,6 +44,7 @@ public class SimLocationPresentation extends Group implements Highlightable { private final List> updateColorDelegates = new ArrayList<>(); private final DoubleProperty animation = new SimpleDoubleProperty(0); private final DoubleBinding reverseAnimation = new SimpleDoubleProperty(1).subtract(animation); + private BooleanProperty isPlaced = new SimpleBooleanProperty(true); /** * Constructs a Simulator Location ready to be placed on the view @@ -272,6 +271,13 @@ protected void interpolate(final double frac) { color.addListener((obs, old, newColor) -> updateColor.accept(newColor, colorIntensity.get())); } + public Boolean isPlaced(){ + return isPlaced.get(); + } + + public void setPlaced(boolean placed){ + isPlaced.set(placed); + } private void initializeTypeGraphics() { final Location location = controller.getLocation(); From ea36ec83109df2bf78ff4d7c6c296fecadd9b6d4 Mon Sep 17 00:00:00 2001 From: Sigurd00 Date: Mon, 21 Nov 2022 14:39:46 +0100 Subject: [PATCH 100/158] Change build.gradle file. Surely it works now (#66) --- build.gradle | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/build.gradle b/build.gradle index 6051ef81..c7664e51 100644 --- a/build.gradle +++ b/build.gradle @@ -10,6 +10,20 @@ javafx { modules = ['javafx.controls', 'javafx.fxml', 'javafx.swing'] } +run { + mainClassName = 'com.jfxbase/com.jfxbase.sample.Main' + applicationDefaultJvmArgs += [ + "--add-opens", "javafx.graphics/javafx.css=ALL-UNNAMED", + "--add-opens", "javafx.base/com.sun.javafx.runtime=ALL-UNNAMED", + "--add-opens", "javafx.controls/com.sun.javafx.scene.control.behavior=ALL-UNNAMED", + "--add-opens", "javafx.controls/com.sun.javafx.scene.control=ALL-UNNAMED", + "--add-opens", "javafx.base/com.sun.javafx.binding=ALL-UNNAMED", + "--add-opens", "javafx.base/com.sun.javafx.event=ALL-UNNAMED", + "--add-opens", "javafx.graphics/com.sun.javafx.stage=ALL-UNNAMED", + "--add-opens", "javafx.graphics/com.sun.javafx.scene=ALL-UNNAMED", + ] +} + group 'ecdar' if (project.hasProperty('ecdarVersion')) { version = project.ecdarVersion From 2ce8dca2b4c34181cef1287ab8110b4a4c5b27dc Mon Sep 17 00:00:00 2001 From: Emilie Steinmann Date: Wed, 23 Nov 2022 12:11:22 +0100 Subject: [PATCH 101/158] =?UTF-8?q?reachability=20check=20p=C3=A5=20=C3=A9?= =?UTF-8?q?n=20location=20i=20sim=20med=20flere=20components.=20mangler=20?= =?UTF-8?q?clocks=20og=20anden=20startstate=20end=20initial.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/ecdar/backend/BackendHelper.java | 40 +++++++++++++++++-- .../ecdar/controllers/LocationController.java | 18 --------- .../controllers/SimLocationController.java | 6 ++- 3 files changed, 41 insertions(+), 23 deletions(-) diff --git a/src/main/java/ecdar/backend/BackendHelper.java b/src/main/java/ecdar/backend/BackendHelper.java index b93756b5..57c72a99 100644 --- a/src/main/java/ecdar/backend/BackendHelper.java +++ b/src/main/java/ecdar/backend/BackendHelper.java @@ -19,6 +19,8 @@ import java.util.List; import java.util.Optional; +import static ecdar.controllers.SimulationInitializationDialogController.ListOfComponents; + public final class BackendHelper { final static String TEMP_DIRECTORY = "temporary"; private static BackendInstance defaultBackend = null; @@ -67,12 +69,42 @@ public static void stopQueries() { /** * Generates a reachability query based on the given location and component * - * @param location The location which should be checked for reachability - * @param component The component where the location belong to / are placed + * @param endLocation The location which should be checked for reachability * @return A reachability query string */ - public static String getLocationReachableQuery(final Location location, final Component component) { - return component.getName() + "." + location.getId(); + public static String getLocationReachableQuery(final Location endLocation, final Component component) { + var stringBuilder = new StringBuilder(); + + for (var componentName:ListOfComponents) { + stringBuilder.append(componentName); + stringBuilder.append(" || "); + } + stringBuilder.delete(stringBuilder.length()-3, stringBuilder.length()); + + // append start location here TODO + + + // append end state + var indexOfSelectedComponent = ListOfComponents.indexOf(component.getName()); + stringBuilder.append("-> ["); + // add underscore to indicate, that we don't care about the end locations in the other components + var numberOfComponents = ListOfComponents.size(); + for (int i = 0; i < numberOfComponents; i++){ + if (i == indexOfSelectedComponent){ + stringBuilder.append(endLocation.getId() + ", "); + } + else{ + stringBuilder.append("_, "); + } + } + stringBuilder.delete(stringBuilder.length()-2, stringBuilder.length()); + stringBuilder.append("]("); + + // append clock here TODO + stringBuilder.append(")"); + + // return example: m1 || M2 -> [L1, L4](y<3); [L2, L7](y<2) + return stringBuilder.toString(); } /** diff --git a/src/main/java/ecdar/controllers/LocationController.java b/src/main/java/ecdar/controllers/LocationController.java index 4d442513..2b887f9e 100644 --- a/src/main/java/ecdar/controllers/LocationController.java +++ b/src/main/java/ecdar/controllers/LocationController.java @@ -193,24 +193,6 @@ public void initializeDropDownMenu() { dropDownMenu.addSpacerElement(); - dropDownMenu.addClickableListElement("Is " + getLocation().getId() + " reachable?", event -> { - dropDownMenu.hide(); - // Generate the query from the backend - final String reachabilityQuery = BackendHelper.getLocationReachableQuery(getLocation(), getComponent()); - - // Add proper comment - final String reachabilityComment = "Is " + getLocation().getMostDescriptiveIdentifier() + " reachable?"; - - // Add new query for this location - final Query query = new Query(reachabilityQuery, reachabilityComment, QueryState.UNKNOWN); - query.setType(QueryType.REACHABILITY); - Ecdar.getProject().getQueries().add(query); - Ecdar.getQueryExecutor().executeQuery(query); - dropDownMenu.hide(); - }); - - dropDownMenu.addSpacerElement(); - dropDownMenu.addColorPicker(getLocation(), (color, intensity) -> { getLocation().setColorIntensity(intensity); getLocation().setColor(color); diff --git a/src/main/java/ecdar/controllers/SimLocationController.java b/src/main/java/ecdar/controllers/SimLocationController.java index c8a0d467..f3613cb4 100755 --- a/src/main/java/ecdar/controllers/SimLocationController.java +++ b/src/main/java/ecdar/controllers/SimLocationController.java @@ -1,6 +1,7 @@ package ecdar.controllers; import com.jfoenix.controls.JFXPopup; +import ecdar.Ecdar; import ecdar.abstractions.*; import ecdar.backend.BackendHelper; import ecdar.presentations.DropDownMenu; @@ -77,7 +78,6 @@ public void initializeDropDownMenu(){ dropDownMenu = new DropDownMenu(root); dropDownMenu.addClickableListElement("Is " + getLocation().getId() + " reachable?", event -> { - dropDownMenu.hide(); // Generate the query from the backend final String reachabilityQuery = BackendHelper.getLocationReachableQuery(getLocation(), getComponent()); @@ -87,6 +87,10 @@ public void initializeDropDownMenu(){ // Add new query for this location final Query query = new Query(reachabilityQuery, reachabilityComment, QueryState.UNKNOWN); query.setType(QueryType.REACHABILITY); + + // execute query + Ecdar.getQueryExecutor().executeQuery(query); + dropDownMenu.hide(); }); } From 1c05055b264d5d89c1125a094b312a7bbd698b87 Mon Sep 17 00:00:00 2001 From: seba6505 <45783179+seba6505@users.noreply.github.com> Date: Wed, 23 Nov 2022 13:37:03 +0100 Subject: [PATCH 102/158] et lille skriv --- .../java/ecdar/abstractions/Component.java | 17 +++++++++++++++-- src/main/java/ecdar/abstractions/Query.java | 2 ++ src/main/java/ecdar/backend/QueryHandler.java | 10 +++++++++- .../ecdar/controllers/ComponentController.java | 18 ++++++++++++++++++ .../ecdar/presentations/SignatureArrow.java | 2 ++ 5 files changed, 46 insertions(+), 3 deletions(-) diff --git a/src/main/java/ecdar/abstractions/Component.java b/src/main/java/ecdar/abstractions/Component.java index 37789bfe..8995a363 100644 --- a/src/main/java/ecdar/abstractions/Component.java +++ b/src/main/java/ecdar/abstractions/Component.java @@ -13,8 +13,10 @@ import com.google.gson.JsonArray; import com.google.gson.JsonObject; import ecdar.utility.helpers.MouseCircular; +import javafx.beans.InvalidationListener; import javafx.beans.property.*; import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; import javafx.collections.FXCollections; import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; @@ -45,7 +47,7 @@ public class Component extends HighLevelModelObject implements Boxed { private final ObservableList outputStrings = FXCollections.observableArrayList(); private final StringProperty description = new SimpleStringProperty(""); private final StringProperty declarationsText = new SimpleStringProperty("");; - + private BooleanProperty isFailing = new SimpleBooleanProperty(false); // Background check private final BooleanProperty includeInPeriodicCheck = new SimpleBooleanProperty(true); @@ -56,11 +58,22 @@ public class Component extends HighLevelModelObject implements Boxed { public Location previousLocationForDraggedEdge; + public boolean getIsFailing(){ + return isFailing.get(); + } + + public void setFailingComponent(boolean status){ + this.isFailing.set(status); + } + + public BooleanProperty getIsFailingProperty() { + return isFailing; + } + /** * Constructs an empty component */ public Component() { - } /** diff --git a/src/main/java/ecdar/abstractions/Query.java b/src/main/java/ecdar/abstractions/Query.java index 76cc5930..93385567 100644 --- a/src/main/java/ecdar/abstractions/Query.java +++ b/src/main/java/ecdar/abstractions/Query.java @@ -70,9 +70,11 @@ public class Query implements Serializable { } for (ObjectProtos.Location location : state.getLocationTuple().getLocationsList()) { Component c = Ecdar.getProject().findComponent(location.getSpecificComponent().getComponentName()); + c.setFailingComponent(true); if (c == null) { throw new NullPointerException("Could not find the specific component: " + location.getSpecificComponent().getComponentName()); } + Location l = c.findLocation(location.getId()); if (l == null) { throw new NullPointerException("Could not find location: " + location.getId()); diff --git a/src/main/java/ecdar/backend/QueryHandler.java b/src/main/java/ecdar/backend/QueryHandler.java index c068c2f7..fdd6c090 100644 --- a/src/main/java/ecdar/backend/QueryHandler.java +++ b/src/main/java/ecdar/backend/QueryHandler.java @@ -7,7 +7,9 @@ import ecdar.abstractions.Component; import ecdar.abstractions.Query; import ecdar.abstractions.QueryState; +import ecdar.controllers.ComponentController; import ecdar.controllers.EcdarController; +import ecdar.controllers.SignatureArrowController; import ecdar.utility.UndoRedoStack; import ecdar.utility.helpers.StringValidator; import io.grpc.stub.StreamObserver; @@ -31,12 +33,14 @@ public QueryHandler(BackendDriver backendDriver) { * Executes the specified query * @param query query to be executed */ + public void executeQuery(Query query) throws NoSuchElementException { if (query.getQueryState().equals(QueryState.RUNNING) || !StringValidator.validateQuery(query.getQuery())) return; if (query.getQuery().isEmpty()) { query.setQueryState(QueryState.SYNTAX_ERROR); query.addError("Query is empty"); + return; } @@ -97,7 +101,7 @@ public void closeAllBackendConnections() throws IOException { } private void handleQueryResponse(QueryProtos.QueryResponse value, Query query) { - System.out.println(value); + // If the query has been cancelled, ignore the result if (query.getQueryState() == QueryState.UNKNOWN) return; switch (value.getResponseCase()) { @@ -118,6 +122,7 @@ private void handleQueryResponse(QueryProtos.QueryResponse value, Query query) { break; case CONSISTENCY: + if (queryOk.getConsistency().getSuccess()) { query.setQueryState(QueryState.SUCCESSFUL); query.getSuccessConsumer().accept(true); @@ -128,6 +133,8 @@ private void handleQueryResponse(QueryProtos.QueryResponse value, Query query) { query.getStateActionConsumer().accept(value.getQueryOk().getConsistency().getState(), value.getQueryOk().getConsistency().getAction()); + + } break; @@ -174,6 +181,7 @@ private void handleQueryResponse(QueryProtos.QueryResponse value, Query query) { break; case COMPONENT: + System.out.println("dizNuts"); query.setQueryState(QueryState.SUCCESSFUL); query.getSuccessConsumer().accept(true); JsonObject returnedComponent = (JsonObject) JsonParser.parseString(queryOk.getComponent().getComponent().getJson()); diff --git a/src/main/java/ecdar/controllers/ComponentController.java b/src/main/java/ecdar/controllers/ComponentController.java index 24a546d4..ca09b4a1 100644 --- a/src/main/java/ecdar/controllers/ComponentController.java +++ b/src/main/java/ecdar/controllers/ComponentController.java @@ -137,6 +137,22 @@ private void initializeSignature(final Component newComponent) { * @param newComponent The component that should be presented with its signature */ private void initializeSignatureListeners(final Component newComponent) { + newComponent.getIsFailingProperty().addListener((observable, oldValue, newValue) -> { + for (Node n : inputSignatureContainer.getChildren()){ + if (n instanceof SignatureArrow){ + ((SignatureArrow) n).recolorToRed(); + } + + } + for (Node n : outputSignatureContainer.getChildren()){ + if (n instanceof SignatureArrow){ + ((SignatureArrow) n).recolorToRed(); + } + + } + + }); + newComponent.getOutputStrings().addListener((ListChangeListener) c -> { // By clearing the container we don't have to fiddle with which elements are removed and added outputSignatureContainer.getChildren().clear(); @@ -160,11 +176,13 @@ private void initializeSignatureListeners(final Component newComponent) { */ private void insertSignatureArrow(final String channel, final EdgeStatus status) { SignatureArrow newArrow = new SignatureArrow(channel, status, component.get()); + if (status == EdgeStatus.INPUT) { inputSignatureContainer.getChildren().add(newArrow); } else { outputSignatureContainer.getChildren().add(newArrow); } + } /*** diff --git a/src/main/java/ecdar/presentations/SignatureArrow.java b/src/main/java/ecdar/presentations/SignatureArrow.java index aadb32c3..49c15373 100644 --- a/src/main/java/ecdar/presentations/SignatureArrow.java +++ b/src/main/java/ecdar/presentations/SignatureArrow.java @@ -145,6 +145,8 @@ public void unhighlight() { this.colorArrowComponents(color, intensity); } + public int eatMyAsssssssss; + /** * Set the color of the SignatureArrow to Color.RED */ From 19023313d7e286b88adf6de701010f3fff4f3c67 Mon Sep 17 00:00:00 2001 From: Claes Berg Mortensen Date: Thu, 24 Nov 2022 09:41:21 +0100 Subject: [PATCH 103/158] Added fields for signaturearrow I/O --- .../java/ecdar/abstractions/Component.java | 21 ++++++++----------- src/main/java/ecdar/abstractions/Query.java | 3 ++- .../controllers/ComponentController.java | 17 ++++++++++++++- 3 files changed, 27 insertions(+), 14 deletions(-) diff --git a/src/main/java/ecdar/abstractions/Component.java b/src/main/java/ecdar/abstractions/Component.java index 8995a363..5c58c7a8 100644 --- a/src/main/java/ecdar/abstractions/Component.java +++ b/src/main/java/ecdar/abstractions/Component.java @@ -47,7 +47,8 @@ public class Component extends HighLevelModelObject implements Boxed { private final ObservableList outputStrings = FXCollections.observableArrayList(); private final StringProperty description = new SimpleStringProperty(""); private final StringProperty declarationsText = new SimpleStringProperty("");; - private BooleanProperty isFailing = new SimpleBooleanProperty(false); + private BooleanProperty isFailingInput = new SimpleBooleanProperty(false); + private BooleanProperty isFailingOutput = new SimpleBooleanProperty(false); // Background check private final BooleanProperty includeInPeriodicCheck = new SimpleBooleanProperty(true); @@ -57,17 +58,13 @@ public class Component extends HighLevelModelObject implements Boxed { private final BooleanProperty firsTimeShown = new SimpleBooleanProperty(false); public Location previousLocationForDraggedEdge; - - public boolean getIsFailing(){ - return isFailing.get(); - } - - public void setFailingComponent(boolean status){ - this.isFailing.set(status); - } - - public BooleanProperty getIsFailingProperty() { - return isFailing; + public boolean getIsFailingInput(){return isFailingInput.get();} + public BooleanProperty getIsFailingInputProperty(){return isFailingInput;} + public void setIsFailingInput(boolean isFailingInput){this.isFailingInput.set(isFailingInput);} + public boolean getIsFailingOutput(){return isFailingOutput.get();} + public BooleanProperty getIsFailingOutputProperty(){return isFailingOutput;} + public void setIsFailingOutput(boolean isFailingOutput) { + this.isFailingOutput.set(isFailingOutput); } /** diff --git a/src/main/java/ecdar/abstractions/Query.java b/src/main/java/ecdar/abstractions/Query.java index 93385567..10e8fb4a 100644 --- a/src/main/java/ecdar/abstractions/Query.java +++ b/src/main/java/ecdar/abstractions/Query.java @@ -70,7 +70,8 @@ public class Query implements Serializable { } for (ObjectProtos.Location location : state.getLocationTuple().getLocationsList()) { Component c = Ecdar.getProject().findComponent(location.getSpecificComponent().getComponentName()); - c.setFailingComponent(true); + c.setIsFailingOutput(true); + c.setIsFailingInput(true); if (c == null) { throw new NullPointerException("Could not find the specific component: " + location.getSpecificComponent().getComponentName()); } diff --git a/src/main/java/ecdar/controllers/ComponentController.java b/src/main/java/ecdar/controllers/ComponentController.java index ca09b4a1..b337b58f 100644 --- a/src/main/java/ecdar/controllers/ComponentController.java +++ b/src/main/java/ecdar/controllers/ComponentController.java @@ -137,7 +137,22 @@ private void initializeSignature(final Component newComponent) { * @param newComponent The component that should be presented with its signature */ private void initializeSignatureListeners(final Component newComponent) { - newComponent.getIsFailingProperty().addListener((observable, oldValue, newValue) -> { + newComponent.getIsFailingInputProperty().addListener((observable, oldValue, newValue) -> { + for (Node n : inputSignatureContainer.getChildren()){ + if (n instanceof SignatureArrow){ + ((SignatureArrow) n).recolorToRed(); + } + + } + for (Node n : outputSignatureContainer.getChildren()){ + if (n instanceof SignatureArrow){ + ((SignatureArrow) n).recolorToRed(); + } + + } + + }); + newComponent.getIsFailingOutputProperty().addListener((observable, oldValue, newValue) -> { for (Node n : inputSignatureContainer.getChildren()){ if (n instanceof SignatureArrow){ ((SignatureArrow) n).recolorToRed(); From 507c57ee7539384cae61399a7904d6b3c639560f Mon Sep 17 00:00:00 2001 From: jhbengtsson Date: Thu, 24 Nov 2022 10:15:05 +0100 Subject: [PATCH 104/158] showtoast based in reachability response status --- src/main/java/ecdar/backend/QueryHandler.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/ecdar/backend/QueryHandler.java b/src/main/java/ecdar/backend/QueryHandler.java index c068c2f7..b4546bbe 100644 --- a/src/main/java/ecdar/backend/QueryHandler.java +++ b/src/main/java/ecdar/backend/QueryHandler.java @@ -162,9 +162,11 @@ private void handleQueryResponse(QueryProtos.QueryResponse value, Query query) { case REACHABILITY: if (queryOk.getReachability().getSuccess()) { query.setQueryState(QueryState.SUCCESSFUL); + Ecdar.showToast("Reachability check was successful."); query.getSuccessConsumer().accept(true); } else { query.setQueryState(QueryState.ERROR); + Ecdar.showToast("Reachability check was unsuccessful!"); query.getFailureConsumer().accept(new BackendException.QueryErrorException(queryOk.getReachability().getReason())); query.getSuccessConsumer().accept(false); //ToDo: These errors are not implemented in the Reveaal backend. From 8365a17c2d6ccf8b53858dd8f7269a4834d28ed9 Mon Sep 17 00:00:00 2001 From: seba6505 <45783179+seba6505@users.noreply.github.com> Date: Thu, 24 Nov 2022 10:57:44 +0100 Subject: [PATCH 105/158] panting faling inputs based on label --- .../java/ecdar/abstractions/Component.java | 16 +++---- .../controllers/ComponentController.java | 45 ++++++++----------- .../controllers/SignatureArrowController.java | 1 - .../ecdar/presentations/SignatureArrow.java | 3 ++ 4 files changed, 28 insertions(+), 37 deletions(-) diff --git a/src/main/java/ecdar/abstractions/Component.java b/src/main/java/ecdar/abstractions/Component.java index 5c58c7a8..cc1f49a9 100644 --- a/src/main/java/ecdar/abstractions/Component.java +++ b/src/main/java/ecdar/abstractions/Component.java @@ -47,8 +47,8 @@ public class Component extends HighLevelModelObject implements Boxed { private final ObservableList outputStrings = FXCollections.observableArrayList(); private final StringProperty description = new SimpleStringProperty(""); private final StringProperty declarationsText = new SimpleStringProperty("");; - private BooleanProperty isFailingInput = new SimpleBooleanProperty(false); - private BooleanProperty isFailingOutput = new SimpleBooleanProperty(false); + private BooleanProperty isFailing = new SimpleBooleanProperty(false); + // Background check private final BooleanProperty includeInPeriodicCheck = new SimpleBooleanProperty(true); @@ -58,14 +58,10 @@ public class Component extends HighLevelModelObject implements Boxed { private final BooleanProperty firsTimeShown = new SimpleBooleanProperty(false); public Location previousLocationForDraggedEdge; - public boolean getIsFailingInput(){return isFailingInput.get();} - public BooleanProperty getIsFailingInputProperty(){return isFailingInput;} - public void setIsFailingInput(boolean isFailingInput){this.isFailingInput.set(isFailingInput);} - public boolean getIsFailingOutput(){return isFailingOutput.get();} - public BooleanProperty getIsFailingOutputProperty(){return isFailingOutput;} - public void setIsFailingOutput(boolean isFailingOutput) { - this.isFailingOutput.set(isFailingOutput); - } + public boolean getIsFailing(){return isFailing.get();} + public BooleanProperty getIsFailingProperty(){return isFailing;} + public void setIsFailing(boolean isFailingInput){this.isFailing.set(isFailingInput);} + /** * Constructs an empty component diff --git a/src/main/java/ecdar/controllers/ComponentController.java b/src/main/java/ecdar/controllers/ComponentController.java index b337b58f..0a557586 100644 --- a/src/main/java/ecdar/controllers/ComponentController.java +++ b/src/main/java/ecdar/controllers/ComponentController.java @@ -137,37 +137,30 @@ private void initializeSignature(final Component newComponent) { * @param newComponent The component that should be presented with its signature */ private void initializeSignatureListeners(final Component newComponent) { - newComponent.getIsFailingInputProperty().addListener((observable, oldValue, newValue) -> { - for (Node n : inputSignatureContainer.getChildren()){ - if (n instanceof SignatureArrow){ - ((SignatureArrow) n).recolorToRed(); - } - - } - for (Node n : outputSignatureContainer.getChildren()){ - if (n instanceof SignatureArrow){ - ((SignatureArrow) n).recolorToRed(); - } - - } + newComponent.getIsFailingProperty().addListener((observable, oldValue, newValue) -> { + if(newComponent.getIsFailing()) { + for (Node n : inputSignatureContainer.getChildren()) { + if (n instanceof SignatureArrow) { + for (String label : component.get().getInputStrings()) { // TODO Bwad bwad labuls gwo here <- UwU shall be complate faliure label that shall be punshid + if (Objects.equals(((SignatureArrow) n).getSignatureArrowLabel(), label)) { + ((SignatureArrow) n).recolorToRed(); + } + } + } - }); - newComponent.getIsFailingOutputProperty().addListener((observable, oldValue, newValue) -> { - for (Node n : inputSignatureContainer.getChildren()){ - if (n instanceof SignatureArrow){ - ((SignatureArrow) n).recolorToRed(); } - - } - for (Node n : outputSignatureContainer.getChildren()){ - if (n instanceof SignatureArrow){ - ((SignatureArrow) n).recolorToRed(); + for (Node n : outputSignatureContainer.getChildren()) { + if (n instanceof SignatureArrow) { + for (String label : component.get().getInputStrings()) { // TODO insert list of failing signature arrows + if (Objects.equals(((SignatureArrow) n).getSignatureArrowLabel(), label)) { + ((SignatureArrow) n).recolorToRed(); + } + } + } } - + newComponent.setIsFailing(false); } - }); - newComponent.getOutputStrings().addListener((ListChangeListener) c -> { // By clearing the container we don't have to fiddle with which elements are removed and added outputSignatureContainer.getChildren().clear(); diff --git a/src/main/java/ecdar/controllers/SignatureArrowController.java b/src/main/java/ecdar/controllers/SignatureArrowController.java index 791ccd02..0d38bd15 100644 --- a/src/main/java/ecdar/controllers/SignatureArrowController.java +++ b/src/main/java/ecdar/controllers/SignatureArrowController.java @@ -22,7 +22,6 @@ public class SignatureArrowController { private final ObjectProperty component = new SimpleObjectProperty<>(); private final ObjectProperty edgeStatus = new SimpleObjectProperty<>(); private final StringProperty syncText = new SimpleStringProperty(); - public Label signatureArrowLabel; public Path signatureArrowPath; public Path signatureArrowHeadPath; diff --git a/src/main/java/ecdar/presentations/SignatureArrow.java b/src/main/java/ecdar/presentations/SignatureArrow.java index 49c15373..871e045e 100644 --- a/src/main/java/ecdar/presentations/SignatureArrow.java +++ b/src/main/java/ecdar/presentations/SignatureArrow.java @@ -19,6 +19,9 @@ public class SignatureArrow extends Group implements Highlightable { private SignatureArrowController controller; + public String getSignatureArrowLabel(){ + return controller.signatureArrowLabel.getText(); + } /*** * Create a new signature arrow with a label for an edge, and different look depending on whether it's * an input or output arrow From 5ca2d3f544858c8912bafd18445fbc6892a211d3 Mon Sep 17 00:00:00 2001 From: seba6505 <45783179+seba6505@users.noreply.github.com> Date: Thu, 24 Nov 2022 11:39:42 +0100 Subject: [PATCH 106/158] fixed error --- src/main/java/ecdar/abstractions/Query.java | 4 ++-- src/main/java/ecdar/controllers/ComponentController.java | 3 ++- .../java/ecdar/controllers/SignatureArrowController.java | 1 - src/main/java/ecdar/presentations/SignatureArrow.java | 7 ++++++- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/main/java/ecdar/abstractions/Query.java b/src/main/java/ecdar/abstractions/Query.java index 10e8fb4a..8de6a47e 100644 --- a/src/main/java/ecdar/abstractions/Query.java +++ b/src/main/java/ecdar/abstractions/Query.java @@ -70,8 +70,8 @@ public class Query implements Serializable { } for (ObjectProtos.Location location : state.getLocationTuple().getLocationsList()) { Component c = Ecdar.getProject().findComponent(location.getSpecificComponent().getComponentName()); - c.setIsFailingOutput(true); - c.setIsFailingInput(true); + c.setIsFailing(true); + if (c == null) { throw new NullPointerException("Could not find the specific component: " + location.getSpecificComponent().getComponentName()); } diff --git a/src/main/java/ecdar/controllers/ComponentController.java b/src/main/java/ecdar/controllers/ComponentController.java index 0a557586..99e3a46d 100644 --- a/src/main/java/ecdar/controllers/ComponentController.java +++ b/src/main/java/ecdar/controllers/ComponentController.java @@ -139,6 +139,7 @@ private void initializeSignature(final Component newComponent) { private void initializeSignatureListeners(final Component newComponent) { newComponent.getIsFailingProperty().addListener((observable, oldValue, newValue) -> { if(newComponent.getIsFailing()) { + System.out.println("HYN"); for (Node n : inputSignatureContainer.getChildren()) { if (n instanceof SignatureArrow) { for (String label : component.get().getInputStrings()) { // TODO Bwad bwad labuls gwo here <- UwU shall be complate faliure label that shall be punshid @@ -158,7 +159,7 @@ private void initializeSignatureListeners(final Component newComponent) { } } } - newComponent.setIsFailing(false); + //newComponent.setIsFailing(false); } }); newComponent.getOutputStrings().addListener((ListChangeListener) c -> { diff --git a/src/main/java/ecdar/controllers/SignatureArrowController.java b/src/main/java/ecdar/controllers/SignatureArrowController.java index 0d38bd15..3b0bdc8f 100644 --- a/src/main/java/ecdar/controllers/SignatureArrowController.java +++ b/src/main/java/ecdar/controllers/SignatureArrowController.java @@ -31,7 +31,6 @@ public class SignatureArrowController { public void initialize(final URL location, final ResourceBundle resources) { } - /*** * Finds matching edges and highlights them */ diff --git a/src/main/java/ecdar/presentations/SignatureArrow.java b/src/main/java/ecdar/presentations/SignatureArrow.java index 871e045e..c4762fd3 100644 --- a/src/main/java/ecdar/presentations/SignatureArrow.java +++ b/src/main/java/ecdar/presentations/SignatureArrow.java @@ -51,7 +51,12 @@ private void initializeMouseEvents() { }); controller.arrowBox.onMouseExitedProperty().set(event -> { controller.mouseExited(); - this.unhighlight(); + if (controller.getComponent().getIsFailing()){ + this.recolorToRed(); + }else { + this.unhighlight(); + } + }); } From 369074b29d8ddf93a67da604c2b65702f5d237d3 Mon Sep 17 00:00:00 2001 From: Emilie Steinmann Date: Thu, 24 Nov 2022 12:37:41 +0100 Subject: [PATCH 107/158] test and refactoring of getLocationReachableQuery, and some minor changes --- .../java/ecdar/backend/BackendHelper.java | 69 +++++++--- .../ecdar/simulation/ReachabilityTest.java | 119 ++++++++++++++++++ 2 files changed, 172 insertions(+), 16 deletions(-) create mode 100644 src/test/java/ecdar/simulation/ReachabilityTest.java diff --git a/src/main/java/ecdar/backend/BackendHelper.java b/src/main/java/ecdar/backend/BackendHelper.java index 57c72a99..e3a986ef 100644 --- a/src/main/java/ecdar/backend/BackendHelper.java +++ b/src/main/java/ecdar/backend/BackendHelper.java @@ -75,35 +75,72 @@ public static void stopQueries() { public static String getLocationReachableQuery(final Location endLocation, final Component component) { var stringBuilder = new StringBuilder(); - for (var componentName:ListOfComponents) { - stringBuilder.append(componentName); - stringBuilder.append(" || "); - } - stringBuilder.delete(stringBuilder.length()-3, stringBuilder.length()); + // append simulation query (currently only supports parallel composition) + stringBuilder.append(getSimulationQueryString()); // append start location here TODO - // append end state - var indexOfSelectedComponent = ListOfComponents.indexOf(component.getName()); - stringBuilder.append("-> ["); + stringBuilder.append(getEndStateString(component.getName(), endLocation.getId())); + + // append clocks + stringBuilder.append("("); + // append clock here TODO + stringBuilder.append(")"); + + // return example: m1||M2->[L1,L4](y<3);[L2, L7](y<2) + return stringBuilder.toString(); + } + + private static String getSimulationQueryString() { + var stringBuilder = new StringBuilder(); + + var appendComponentWithSeparator = false; + for (var componentName:ListOfComponents) { + if (appendComponentWithSeparator){ + stringBuilder.append("||" + componentName); + } + else { + stringBuilder.append(componentName); + } + if (!appendComponentWithSeparator) { + appendComponentWithSeparator = true; + } + } + return stringBuilder.toString(); + } + + private static String getEndStateString(String componentName, String endLocationId) { + var stringBuilder = new StringBuilder(); + + var indexOfSelectedComponent = ListOfComponents.indexOf(componentName); + stringBuilder.append(" -> ["); // add underscore to indicate, that we don't care about the end locations in the other components var numberOfComponents = ListOfComponents.size(); + var appendLocationWithSeparator = false; for (int i = 0; i < numberOfComponents; i++){ if (i == indexOfSelectedComponent){ - stringBuilder.append(endLocation.getId() + ", "); + if (appendLocationWithSeparator){ + stringBuilder.append("," + endLocationId); + } + else{ + stringBuilder.append(endLocationId); + } } else{ - stringBuilder.append("_, "); + if (appendLocationWithSeparator){ + stringBuilder.append(",_"); + } + else{ + stringBuilder.append("_"); + } + } + if (!appendLocationWithSeparator) { + appendLocationWithSeparator = true; } } - stringBuilder.delete(stringBuilder.length()-2, stringBuilder.length()); - stringBuilder.append("]("); - - // append clock here TODO - stringBuilder.append(")"); + stringBuilder.append("]"); - // return example: m1 || M2 -> [L1, L4](y<3); [L2, L7](y<2) return stringBuilder.toString(); } diff --git a/src/test/java/ecdar/simulation/ReachabilityTest.java b/src/test/java/ecdar/simulation/ReachabilityTest.java new file mode 100644 index 00000000..480ecb49 --- /dev/null +++ b/src/test/java/ecdar/simulation/ReachabilityTest.java @@ -0,0 +1,119 @@ +package ecdar.simulation; + +import ecdar.Ecdar; +import ecdar.abstractions.Component; +import ecdar.abstractions.Location; +import ecdar.backend.BackendHelper; +import ecdar.controllers.SimulationInitializationDialogController; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import java.util.regex.Pattern; + +import static org.junit.jupiter.api.Assertions.*; + +public class ReachabilityTest { + + @BeforeAll + static void setup() { + Ecdar.setUpForTest(); + } + + @Test + void reachabilityQuerySyntaxTestSuccess() { + var regex = "([a-zA-Z]\\w*)([|][|][a-zA-Z]\\w*)*\\s+\\->\\s+\\[(\\w*)(,(\\w)*)*\\]\\([a-zA-Z0-9_<>=]*\\)(;\\[(\\w*)(,(\\w)*)\\]\\([a-zA-Z0-9_<>=]*\\))*"; + + var location = new Location(); + location.setId("L1"); + var component = new Component(); + component.setName("C1"); + + SimulationInitializationDialogController.ListOfComponents.clear(); + SimulationInitializationDialogController.ListOfComponents.add("C1"); + SimulationInitializationDialogController.ListOfComponents.add("C2"); + SimulationInitializationDialogController.ListOfComponents.add("C3"); + + var result = BackendHelper.getLocationReachableQuery(location, component); + assertTrue(result.matches(regex)); + } + + @Test + void reachabilityQueryLocationPosition1TestSuccess() { + var location = new Location(); + location.setId("L1"); + var component = new Component(); + component.setName("C1"); + + SimulationInitializationDialogController.ListOfComponents.clear(); + SimulationInitializationDialogController.ListOfComponents.add("C1"); + SimulationInitializationDialogController.ListOfComponents.add("C2"); + SimulationInitializationDialogController.ListOfComponents.add("C3"); + + var result = BackendHelper.getLocationReachableQuery(location, component); + var indexOfComponent = result.indexOf('[') + 1; + var output = result.charAt(indexOfComponent); + assertEquals(output, location.getId().charAt(0)); + } + + @Test + void reachabilityQueryLocationPosition2TestSuccess() { + var location = new Location(); + location.setId("L1"); + var component = new Component(); + component.setName("C1"); + + SimulationInitializationDialogController.ListOfComponents.clear(); + SimulationInitializationDialogController.ListOfComponents.add("C2"); + SimulationInitializationDialogController.ListOfComponents.add("C1"); + SimulationInitializationDialogController.ListOfComponents.add("C3"); + + var result = BackendHelper.getLocationReachableQuery(location, component); + var indexOfComponent = result.indexOf(',') + 1; + var output = result.charAt(indexOfComponent); + assertEquals(output, location.getId().charAt(0)); + } + + @Test + void reachabilityQueryLocationPosition3TestSuccess() { + var location = new Location(); + location.setId("L1"); + var component = new Component(); + component.setName("C1"); + + SimulationInitializationDialogController.ListOfComponents.clear(); + SimulationInitializationDialogController.ListOfComponents.add("C2"); + SimulationInitializationDialogController.ListOfComponents.add("C3"); + SimulationInitializationDialogController.ListOfComponents.add("C1"); + + var query = BackendHelper.getLocationReachableQuery(location, component); + var indexOfComponent = query.indexOf(']') - 2; + var output = query.charAt(indexOfComponent); + assertEquals(output, location.getId().charAt(0)); + } + + @Test + void reachabilityQueryNumberOfLocationsTestSuccess() { + var location = new Location(); + location.setId("L1"); + var component = new Component(); + component.setName("C1"); + + SimulationInitializationDialogController.ListOfComponents.clear(); + SimulationInitializationDialogController.ListOfComponents.add("C2"); + SimulationInitializationDialogController.ListOfComponents.add("C1"); + SimulationInitializationDialogController.ListOfComponents.add("C3"); + SimulationInitializationDialogController.ListOfComponents.add("C4"); + + var query = BackendHelper.getLocationReachableQuery(location, component); + int underscoreCount = 0; + for (int i = 0; i < query.length(); i++) { + if (query.charAt(i) == '_') { + underscoreCount++; + } + } + + assertEquals(SimulationInitializationDialogController.ListOfComponents.size(), underscoreCount + 1); + } +} From d1146094c794b7ffb625551d7e72f218545ef19b Mon Sep 17 00:00:00 2001 From: jhbengtsson Date: Thu, 24 Nov 2022 13:09:52 +0100 Subject: [PATCH 108/158] showtoast messages changed --- src/main/java/ecdar/backend/QueryHandler.java | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/main/java/ecdar/backend/QueryHandler.java b/src/main/java/ecdar/backend/QueryHandler.java index b4546bbe..e5ae7dad 100644 --- a/src/main/java/ecdar/backend/QueryHandler.java +++ b/src/main/java/ecdar/backend/QueryHandler.java @@ -7,6 +7,7 @@ import ecdar.abstractions.Component; import ecdar.abstractions.Query; import ecdar.abstractions.QueryState; +import ecdar.abstractions.QueryType; import ecdar.controllers.EcdarController; import ecdar.utility.UndoRedoStack; import ecdar.utility.helpers.StringValidator; @@ -162,7 +163,12 @@ private void handleQueryResponse(QueryProtos.QueryResponse value, Query query) { case REACHABILITY: if (queryOk.getReachability().getSuccess()) { query.setQueryState(QueryState.SUCCESSFUL); - Ecdar.showToast("Reachability check was successful."); + if(value.toString().contains("true")){ + Ecdar.showToast("Reachability check was successful and the location can be reached."); + } + else if(value.toString().contains("false")){ + Ecdar.showToast("Reachability check was successful but the location cannot be reached."); + } query.getSuccessConsumer().accept(true); } else { query.setQueryState(QueryState.ERROR); @@ -212,6 +218,11 @@ private void handleQueryBackendError(Throwable t, Query query) { // If the query has been cancelled, ignore the error if (query.getQueryState() == QueryState.UNKNOWN) return; + // due to lack of information from backend if the reachability check shows that a location can NOT be reached, this is the most accurate information we can provide + if(query.getType() == QueryType.REACHABILITY){ + Ecdar.showToast("The reachability query failed. This might be due to the fact that the location is not reachable."); + } + // Each error starts with a capitalized description of the error equal to the gRPC error type encountered String errorType = t.getMessage().split(":\\s+", 2)[0]; From 2f5ddc0315acb9a7e498c3ffb6adf8eeae86afa0 Mon Sep 17 00:00:00 2001 From: Dolmer1 Date: Thu, 24 Nov 2022 13:39:36 +0100 Subject: [PATCH 109/158] renamed variable --- src/test/java/ecdar/simulation/ReachabilityTest.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/test/java/ecdar/simulation/ReachabilityTest.java b/src/test/java/ecdar/simulation/ReachabilityTest.java index 480ecb49..527cc2b8 100644 --- a/src/test/java/ecdar/simulation/ReachabilityTest.java +++ b/src/test/java/ecdar/simulation/ReachabilityTest.java @@ -52,8 +52,8 @@ void reachabilityQueryLocationPosition1TestSuccess() { SimulationInitializationDialogController.ListOfComponents.add("C3"); var result = BackendHelper.getLocationReachableQuery(location, component); - var indexOfComponent = result.indexOf('[') + 1; - var output = result.charAt(indexOfComponent); + var indexOfLocation = result.indexOf('[') + 1; + var output = result.charAt(indexOfLocation); assertEquals(output, location.getId().charAt(0)); } @@ -70,8 +70,8 @@ void reachabilityQueryLocationPosition2TestSuccess() { SimulationInitializationDialogController.ListOfComponents.add("C3"); var result = BackendHelper.getLocationReachableQuery(location, component); - var indexOfComponent = result.indexOf(',') + 1; - var output = result.charAt(indexOfComponent); + var indexOfLocation = result.indexOf(',') + 1; + var output = result.charAt(indexOfLocation); assertEquals(output, location.getId().charAt(0)); } @@ -88,8 +88,8 @@ void reachabilityQueryLocationPosition3TestSuccess() { SimulationInitializationDialogController.ListOfComponents.add("C1"); var query = BackendHelper.getLocationReachableQuery(location, component); - var indexOfComponent = query.indexOf(']') - 2; - var output = query.charAt(indexOfComponent); + var indexOfLocation = query.indexOf(']') - 2; + var output = query.charAt(indexOfLocation); assertEquals(output, location.getId().charAt(0)); } From 02b447e4f94def0a68066689446bc656b5b5f156 Mon Sep 17 00:00:00 2001 From: Emilie Steinmann Date: Fri, 25 Nov 2022 09:24:46 +0100 Subject: [PATCH 110/158] Improve readability --- src/main/java/ecdar/backend/BackendHelper.java | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/main/java/ecdar/backend/BackendHelper.java b/src/main/java/ecdar/backend/BackendHelper.java index e3a986ef..bcc932eb 100644 --- a/src/main/java/ecdar/backend/BackendHelper.java +++ b/src/main/java/ecdar/backend/BackendHelper.java @@ -113,13 +113,12 @@ private static String getSimulationQueryString() { private static String getEndStateString(String componentName, String endLocationId) { var stringBuilder = new StringBuilder(); - var indexOfSelectedComponent = ListOfComponents.indexOf(componentName); stringBuilder.append(" -> ["); - // add underscore to indicate, that we don't care about the end locations in the other components - var numberOfComponents = ListOfComponents.size(); var appendLocationWithSeparator = false; - for (int i = 0; i < numberOfComponents; i++){ - if (i == indexOfSelectedComponent){ + + for (var component:ListOfComponents) + { + if (component.equals(componentName)){ if (appendLocationWithSeparator){ stringBuilder.append("," + endLocationId); } @@ -127,7 +126,7 @@ private static String getEndStateString(String componentName, String endLocation stringBuilder.append(endLocationId); } } - else{ + else{ // add underscore to indicate, that we don't care about the end locations in the other components if (appendLocationWithSeparator){ stringBuilder.append(",_"); } From 7a169bfc8799bb20e6e30d56952b781b65ca10e1 Mon Sep 17 00:00:00 2001 From: jhbengtsson Date: Fri, 25 Nov 2022 10:54:34 +0100 Subject: [PATCH 111/158] =?UTF-8?q?=C3=A6ndret=20s=C3=A5=20vi=20ikke=20kon?= =?UTF-8?q?verterer=20til=20string=20men=20bruger=20metoden=20til=20at=20s?= =?UTF-8?q?e=20success?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/ecdar/backend/QueryHandler.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/ecdar/backend/QueryHandler.java b/src/main/java/ecdar/backend/QueryHandler.java index e5ae7dad..a2f6162e 100644 --- a/src/main/java/ecdar/backend/QueryHandler.java +++ b/src/main/java/ecdar/backend/QueryHandler.java @@ -163,10 +163,10 @@ private void handleQueryResponse(QueryProtos.QueryResponse value, Query query) { case REACHABILITY: if (queryOk.getReachability().getSuccess()) { query.setQueryState(QueryState.SUCCESSFUL); - if(value.toString().contains("true")){ + if(value.getQueryOk().getReachability().getSuccess()){ Ecdar.showToast("Reachability check was successful and the location can be reached."); } - else if(value.toString().contains("false")){ + else if(!value.getQueryOk().getReachability().getSuccess()){ Ecdar.showToast("Reachability check was successful but the location cannot be reached."); } query.getSuccessConsumer().accept(true); From 2294096a153417a9ba9c55aa7ff9fa83a866be2e Mon Sep 17 00:00:00 2001 From: jhbengtsson Date: Fri, 25 Nov 2022 11:02:48 +0100 Subject: [PATCH 112/158] docs --- src/main/java/ecdar/controllers/SimLocationController.java | 5 +++++ .../java/ecdar/presentations/SimLocationPresentation.java | 1 + 2 files changed, 6 insertions(+) diff --git a/src/main/java/ecdar/controllers/SimLocationController.java b/src/main/java/ecdar/controllers/SimLocationController.java index f3613cb4..f7f1e7d5 100755 --- a/src/main/java/ecdar/controllers/SimLocationController.java +++ b/src/main/java/ecdar/controllers/SimLocationController.java @@ -74,6 +74,11 @@ private void initializeMouseControls() { root.addEventHandler(MouseEvent.MOUSE_CLICKED, mouseClicked::accept); }); } + + /** + * Creates the dropdown when right clicking a location. + * When reachability is chosen a request will be send to the backend to see if the location can be reached + */ public void initializeDropDownMenu(){ dropDownMenu = new DropDownMenu(root); diff --git a/src/main/java/ecdar/presentations/SimLocationPresentation.java b/src/main/java/ecdar/presentations/SimLocationPresentation.java index c4198ba4..335cdc2c 100755 --- a/src/main/java/ecdar/presentations/SimLocationPresentation.java +++ b/src/main/java/ecdar/presentations/SimLocationPresentation.java @@ -278,6 +278,7 @@ public Boolean isPlaced(){ public void setPlaced(boolean placed){ isPlaced.set(placed); } + private void initializeTypeGraphics() { final Location location = controller.getLocation(); From a12a403fd208cc8467d41a9adeeb8cc5d2fd7437 Mon Sep 17 00:00:00 2001 From: jhbengtsson Date: Fri, 25 Nov 2022 11:15:53 +0100 Subject: [PATCH 113/158] docs --- .../java/ecdar/presentations/SimLocationPresentation.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main/java/ecdar/presentations/SimLocationPresentation.java b/src/main/java/ecdar/presentations/SimLocationPresentation.java index 335cdc2c..082a9bb3 100755 --- a/src/main/java/ecdar/presentations/SimLocationPresentation.java +++ b/src/main/java/ecdar/presentations/SimLocationPresentation.java @@ -271,10 +271,16 @@ protected void interpolate(final double frac) { color.addListener((obs, old, newColor) -> updateColor.accept(newColor, colorIntensity.get())); } + /** + * Returns true if the mouse hovers over a location + * */ public Boolean isPlaced(){ return isPlaced.get(); } + /** + * Set placed to true or false + * */ public void setPlaced(boolean placed){ isPlaced.set(placed); } From 1aa97e9d6a8b13de45c1118d7e6e382e1b9458e9 Mon Sep 17 00:00:00 2001 From: seba6505 <45783179+seba6505@users.noreply.github.com> Date: Wed, 30 Nov 2022 11:02:02 +0100 Subject: [PATCH 114/158] slette nogle ting --- src/main/java/ecdar/abstractions/Query.java | 2 +- src/main/java/ecdar/controllers/ComponentController.java | 2 -- src/main/java/ecdar/presentations/SignatureArrow.java | 1 - 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/main/java/ecdar/abstractions/Query.java b/src/main/java/ecdar/abstractions/Query.java index 8de6a47e..f204a555 100644 --- a/src/main/java/ecdar/abstractions/Query.java +++ b/src/main/java/ecdar/abstractions/Query.java @@ -62,7 +62,7 @@ public class Query implements Serializable { } } }; - + //TODO add set alle compontens isfailing til fales private final BiConsumer stateActionConsumer = (state, action) -> { for (Component c : Ecdar.getProject().getComponents()) { c.removeFailingLocations(); diff --git a/src/main/java/ecdar/controllers/ComponentController.java b/src/main/java/ecdar/controllers/ComponentController.java index 99e3a46d..3972b386 100644 --- a/src/main/java/ecdar/controllers/ComponentController.java +++ b/src/main/java/ecdar/controllers/ComponentController.java @@ -139,7 +139,6 @@ private void initializeSignature(final Component newComponent) { private void initializeSignatureListeners(final Component newComponent) { newComponent.getIsFailingProperty().addListener((observable, oldValue, newValue) -> { if(newComponent.getIsFailing()) { - System.out.println("HYN"); for (Node n : inputSignatureContainer.getChildren()) { if (n instanceof SignatureArrow) { for (String label : component.get().getInputStrings()) { // TODO Bwad bwad labuls gwo here <- UwU shall be complate faliure label that shall be punshid @@ -148,7 +147,6 @@ private void initializeSignatureListeners(final Component newComponent) { } } } - } for (Node n : outputSignatureContainer.getChildren()) { if (n instanceof SignatureArrow) { diff --git a/src/main/java/ecdar/presentations/SignatureArrow.java b/src/main/java/ecdar/presentations/SignatureArrow.java index c4762fd3..aa8898ec 100644 --- a/src/main/java/ecdar/presentations/SignatureArrow.java +++ b/src/main/java/ecdar/presentations/SignatureArrow.java @@ -153,7 +153,6 @@ public void unhighlight() { this.colorArrowComponents(color, intensity); } - public int eatMyAsssssssss; /** * Set the color of the SignatureArrow to Color.RED From 6bec940bb219a980a75452aa4596652498903eaf Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 30 Nov 2022 12:35:42 +0100 Subject: [PATCH 115/158] Updated QueryHandler to handle new gRPC values --- src/main/java/ecdar/abstractions/Query.java | 5 +- src/main/java/ecdar/backend/QueryHandler.java | 184 ++++++++---------- src/main/proto | 2 +- 3 files changed, 88 insertions(+), 103 deletions(-) diff --git a/src/main/java/ecdar/abstractions/Query.java b/src/main/java/ecdar/abstractions/Query.java index f204a555..da2cfa0a 100644 --- a/src/main/java/ecdar/abstractions/Query.java +++ b/src/main/java/ecdar/abstractions/Query.java @@ -10,6 +10,7 @@ import javafx.application.Platform; import javafx.beans.property.*; +import java.util.List; import java.util.function.BiConsumer; import java.util.function.Consumer; @@ -63,7 +64,7 @@ public class Query implements Serializable { } }; //TODO add set alle compontens isfailing til fales - private final BiConsumer stateActionConsumer = (state, action) -> { + private final BiConsumer> stateActionConsumer = (state, action) -> { for (Component c : Ecdar.getProject().getComponents()) { c.removeFailingLocations(); c.removeFailingEdges(); @@ -182,7 +183,7 @@ public Consumer getFailureConsumer() { * Getter for the state action consumer. * @return The State Consumer */ - public BiConsumer getStateActionConsumer() { + public BiConsumer> getStateActionConsumer() { return stateActionConsumer; } diff --git a/src/main/java/ecdar/backend/QueryHandler.java b/src/main/java/ecdar/backend/QueryHandler.java index fdd6c090..47098d43 100644 --- a/src/main/java/ecdar/backend/QueryHandler.java +++ b/src/main/java/ecdar/backend/QueryHandler.java @@ -18,6 +18,7 @@ import java.io.IOException; import java.util.ArrayList; +import java.util.List; import java.util.NoSuchElementException; import java.util.concurrent.TimeUnit; @@ -31,11 +32,13 @@ public QueryHandler(BackendDriver backendDriver) { /** * Executes the specified query - * @param query query to be executed + * + * @param query query to be executed */ public void executeQuery(Query query) throws NoSuchElementException { - if (query.getQueryState().equals(QueryState.RUNNING) || !StringValidator.validateQuery(query.getQuery())) return; + if (query.getQueryState().equals(QueryState.RUNNING) || !StringValidator.validateQuery(query.getQuery())) + return; if (query.getQuery().isEmpty()) { query.setQueryState(QueryState.SYNTAX_ERROR); @@ -101,117 +104,98 @@ public void closeAllBackendConnections() throws IOException { } private void handleQueryResponse(QueryProtos.QueryResponse value, Query query) { - // If the query has been cancelled, ignore the result if (query.getQueryState() == QueryState.UNKNOWN) return; - switch (value.getResponseCase()) { - case QUERY_OK: - QueryProtos.QueryResponse.QueryOk queryOk = value.getQueryOk(); - switch (queryOk.getResultCase()) { - case REFINEMENT: - if (queryOk.getRefinement().getSuccess()) { - query.setQueryState(QueryState.SUCCESSFUL); - query.getSuccessConsumer().accept(true); - } else { - query.setQueryState(QueryState.ERROR); - query.getFailureConsumer().accept(new BackendException.QueryErrorException(queryOk.getRefinement().getReason())); - query.getSuccessConsumer().accept(false); - query.getStateActionConsumer().accept(value.getQueryOk().getRefinement().getState(), - value.getQueryOk().getRefinement().getAction()); - } - break; - - case CONSISTENCY: - - if (queryOk.getConsistency().getSuccess()) { - query.setQueryState(QueryState.SUCCESSFUL); - query.getSuccessConsumer().accept(true); - } else { - query.setQueryState(QueryState.ERROR); - query.getFailureConsumer().accept(new BackendException.QueryErrorException(queryOk.getConsistency().getReason())); - query.getSuccessConsumer().accept(false); - query.getStateActionConsumer().accept(value.getQueryOk().getConsistency().getState(), - value.getQueryOk().getConsistency().getAction()); - - - - } - break; - - case DETERMINISM: - if (queryOk.getDeterminism().getSuccess()) { - query.setQueryState(QueryState.SUCCESSFUL); - query.getSuccessConsumer().accept(true); - } else { - query.setQueryState(QueryState.ERROR); - query.getFailureConsumer().accept(new BackendException.QueryErrorException(queryOk.getDeterminism().getReason())); - query.getSuccessConsumer().accept(false); - query.getStateActionConsumer().accept(value.getQueryOk().getDeterminism().getState(), - value.getQueryOk().getDeterminism().getAction()); - - } - break; - - case IMPLEMENTATION: - if (queryOk.getImplementation().getSuccess()) { - query.setQueryState(QueryState.SUCCESSFUL); - query.getSuccessConsumer().accept(true); - } else { - query.setQueryState(QueryState.ERROR); - query.getFailureConsumer().accept(new BackendException.QueryErrorException(queryOk.getImplementation().getReason())); - query.getSuccessConsumer().accept(false); - //ToDo: These errors are not implemented in the Reveaal backend. - query.getStateActionConsumer().accept(value.getQueryOk().getImplementation().getState(), - ""); - } - break; - - case REACHABILITY: - if (queryOk.getReachability().getSuccess()) { - query.setQueryState(QueryState.SUCCESSFUL); - query.getSuccessConsumer().accept(true); - } else { - query.setQueryState(QueryState.ERROR); - query.getFailureConsumer().accept(new BackendException.QueryErrorException(queryOk.getReachability().getReason())); - query.getSuccessConsumer().accept(false); - //ToDo: These errors are not implemented in the Reveaal backend. - query.getStateActionConsumer().accept(value.getQueryOk().getReachability().getState(), - ""); - } - break; - - case COMPONENT: - System.out.println("dizNuts"); - query.setQueryState(QueryState.SUCCESSFUL); - query.getSuccessConsumer().accept(true); - JsonObject returnedComponent = (JsonObject) JsonParser.parseString(queryOk.getComponent().getComponent().getJson()); - addGeneratedComponent(new Component(returnedComponent)); - break; - - case ERROR: - query.setQueryState(QueryState.ERROR); - query.getFailureConsumer().accept(new BackendException.QueryErrorException(queryOk.getError())); - query.getSuccessConsumer().accept(false); - break; - - case RESULT_NOT_SET: - query.setQueryState(QueryState.ERROR); - query.getSuccessConsumer().accept(false); - break; + switch (value.getResultCase()) { + case REFINEMENT: + if (value.getRefinement().getSuccess()) { + query.setQueryState(QueryState.SUCCESSFUL); + query.getSuccessConsumer().accept(true); + } else { + query.setQueryState(QueryState.ERROR); + query.getFailureConsumer().accept(new BackendException.QueryErrorException(value.getRefinement().getReason())); + query.getSuccessConsumer().accept(false); + query.getStateActionConsumer().accept(value.getRefinement().getState(), + value.getRefinement().getActionList()); } break; - case USER_TOKEN_ERROR: + case CONSISTENCY: + if (value.getConsistency().getSuccess()) { + query.setQueryState(QueryState.SUCCESSFUL); + query.getSuccessConsumer().accept(true); + } else { + query.setQueryState(QueryState.ERROR); + query.getFailureConsumer().accept(new BackendException.QueryErrorException(value.getConsistency().getReason())); + query.getSuccessConsumer().accept(false); + query.getStateActionConsumer().accept(value.getConsistency().getState(), + value.getConsistency().getActionList()); + + + } + break; + + case DETERMINISM: + if (value.getDeterminism().getSuccess()) { + query.setQueryState(QueryState.SUCCESSFUL); + query.getSuccessConsumer().accept(true); + } else { + query.setQueryState(QueryState.ERROR); + query.getFailureConsumer().accept(new BackendException.QueryErrorException(value.getDeterminism().getReason())); + query.getSuccessConsumer().accept(false); + query.getStateActionConsumer().accept(value.getDeterminism().getState(), + value.getDeterminism().getActionList()); + + } + break; + + case IMPLEMENTATION: + if (value.getImplementation().getSuccess()) { + query.setQueryState(QueryState.SUCCESSFUL); + query.getSuccessConsumer().accept(true); + } else { + query.setQueryState(QueryState.ERROR); + query.getFailureConsumer().accept(new BackendException.QueryErrorException(value.getImplementation().getReason())); + query.getSuccessConsumer().accept(false); + //ToDo: These errors are not implemented in the Reveaal backend. + query.getStateActionConsumer().accept(value.getImplementation().getState(), + new ArrayList<>()); + } + break; + + case REACHABILITY: + if (value.getReachability().getSuccess()) { + query.setQueryState(QueryState.SUCCESSFUL); + query.getSuccessConsumer().accept(true); + } else { + query.setQueryState(QueryState.ERROR); + query.getFailureConsumer().accept(new BackendException.QueryErrorException(value.getReachability().getReason())); + query.getSuccessConsumer().accept(false); + //ToDo: These errors are not implemented in the Reveaal backend. + query.getStateActionConsumer().accept(value.getReachability().getState(), + new ArrayList<>()); + } + break; + + case COMPONENT: + query.setQueryState(QueryState.SUCCESSFUL); + query.getSuccessConsumer().accept(true); + JsonObject returnedComponent = (JsonObject) JsonParser.parseString(value.getComponent().getComponent().getJson()); + addGeneratedComponent(new Component(returnedComponent)); + break; + + case ERROR: query.setQueryState(QueryState.ERROR); - query.getFailureConsumer().accept(new BackendException.QueryErrorException(value.getUserTokenError().getErrorMessage())); + query.getFailureConsumer().accept(new BackendException.QueryErrorException(value.getError())); query.getSuccessConsumer().accept(false); break; - case RESPONSE_NOT_SET: + case RESULT_NOT_SET: query.setQueryState(QueryState.ERROR); query.getSuccessConsumer().accept(false); break; } + } private void handleQueryBackendError(Throwable t, Query query) { diff --git a/src/main/proto b/src/main/proto index 22f7b52a..fa3ed0ae 160000 --- a/src/main/proto +++ b/src/main/proto @@ -1 +1 @@ -Subproject commit 22f7b52a65a7dcf56563eb7b5d353ce4c47dd231 +Subproject commit fa3ed0ae9d1b7fe2424057ddc69574482db4a640 From e57fbf7bbabf98c4c96e05514bf5e18ff047a435 Mon Sep 17 00:00:00 2001 From: EmilieSonne <82802471+EmilieSonne@users.noreply.github.com> Date: Wed, 30 Nov 2022 13:51:34 +0100 Subject: [PATCH 116/158] Reachability: send request with startSimulation query as input (#104) * Save simulation query and use it for reachability check * remove unnecessary code * update tests to be independent of simulation start query format --- .../java/ecdar/backend/BackendHelper.java | 24 +++---------------- .../ecdar/controllers/EcdarController.java | 2 +- .../controllers/SimLocationController.java | 2 +- ...ulationInitializationDialogController.java | 13 ++++++---- .../controllers/SimulatorController.java | 12 ++++++++-- ...ationInitializationDialogPresentation.fxml | 2 +- .../ecdar/simulation/ReachabilityTest.java | 12 +++++----- 7 files changed, 30 insertions(+), 37 deletions(-) diff --git a/src/main/java/ecdar/backend/BackendHelper.java b/src/main/java/ecdar/backend/BackendHelper.java index bcc932eb..0397b6ee 100644 --- a/src/main/java/ecdar/backend/BackendHelper.java +++ b/src/main/java/ecdar/backend/BackendHelper.java @@ -72,11 +72,11 @@ public static void stopQueries() { * @param endLocation The location which should be checked for reachability * @return A reachability query string */ - public static String getLocationReachableQuery(final Location endLocation, final Component component) { + public static String getLocationReachableQuery(final Location endLocation, final Component component, final String query) { var stringBuilder = new StringBuilder(); - // append simulation query (currently only supports parallel composition) - stringBuilder.append(getSimulationQueryString()); + // append simulation query + stringBuilder.append(query); // append start location here TODO @@ -92,24 +92,6 @@ public static String getLocationReachableQuery(final Location endLocation, final return stringBuilder.toString(); } - private static String getSimulationQueryString() { - var stringBuilder = new StringBuilder(); - - var appendComponentWithSeparator = false; - for (var componentName:ListOfComponents) { - if (appendComponentWithSeparator){ - stringBuilder.append("||" + componentName); - } - else { - stringBuilder.append(componentName); - } - if (!appendComponentWithSeparator) { - appendComponentWithSeparator = true; - } - } - return stringBuilder.toString(); - } - private static String getEndStateString(String componentName, String endLocationId) { var stringBuilder = new StringBuilder(); diff --git a/src/main/java/ecdar/controllers/EcdarController.java b/src/main/java/ecdar/controllers/EcdarController.java index 75ff103d..a7b7c558 100644 --- a/src/main/java/ecdar/controllers/EcdarController.java +++ b/src/main/java/ecdar/controllers/EcdarController.java @@ -455,7 +455,7 @@ private void startBackgroundQueriesThread() { component.getLocations().forEach(location -> location.setReachability(Location.Reachability.EXCLUDED)); } else { component.getLocations().forEach(location -> { - final String locationReachableQuery = BackendHelper.getLocationReachableQuery(location, component); + final String locationReachableQuery = BackendHelper.getLocationReachableQuery(location, component, SimulatorController.getSimulationQuery()); Query reachabilityQuery = new Query(locationReachableQuery, "", QueryState.UNKNOWN); reachabilityQuery.setType(QueryType.REACHABILITY); diff --git a/src/main/java/ecdar/controllers/SimLocationController.java b/src/main/java/ecdar/controllers/SimLocationController.java index f7f1e7d5..d5112fc4 100755 --- a/src/main/java/ecdar/controllers/SimLocationController.java +++ b/src/main/java/ecdar/controllers/SimLocationController.java @@ -84,7 +84,7 @@ public void initializeDropDownMenu(){ dropDownMenu.addClickableListElement("Is " + getLocation().getId() + " reachable?", event -> { // Generate the query from the backend - final String reachabilityQuery = BackendHelper.getLocationReachableQuery(getLocation(), getComponent()); + final String reachabilityQuery = BackendHelper.getLocationReachableQuery(getLocation(), getComponent(), SimulatorController.getSimulationQuery()); // Add proper comment final String reachabilityComment = "Is " + getLocation().getMostDescriptiveIdentifier() + " reachable?"; diff --git a/src/main/java/ecdar/controllers/SimulationInitializationDialogController.java b/src/main/java/ecdar/controllers/SimulationInitializationDialogController.java index 52442964..a34787a6 100644 --- a/src/main/java/ecdar/controllers/SimulationInitializationDialogController.java +++ b/src/main/java/ecdar/controllers/SimulationInitializationDialogController.java @@ -19,12 +19,15 @@ public class SimulationInitializationDialogController implements Initializable { * Function gets list of components to simulation * and saves it in the public static ListOfComponents */ - public void SetListOfComponentsToSimulate(){ + public void setSimulationData(){ + // set simulation query + SimulatorController.setSimulationQuery(simulationComboBox.getSelectionModel().getSelectedItem()); + + // set list of components involved in simulation ListOfComponents.clear(); - String componentsToSimulate = simulationComboBox.getSelectionModel().getSelectedItem(); - //filters out all components by ignoring operators. + // pattern filters out all components by ignoring operators. Pattern pattern = Pattern.compile("([\\w]*)", Pattern.CASE_INSENSITIVE); - Matcher matcher = pattern.matcher(componentsToSimulate); + Matcher matcher = pattern.matcher(SimulatorController.getSimulationQuery()); List listOfComponentsToSimulate = new ArrayList<>(); //Adds all found components to list. while(matcher.find()){ @@ -32,9 +35,9 @@ public void SetListOfComponentsToSimulate(){ listOfComponentsToSimulate.add(matcher.group()); } } - ListOfComponents = listOfComponentsToSimulate; } + public void initialize(URL location, ResourceBundle resources) { } diff --git a/src/main/java/ecdar/controllers/SimulatorController.java b/src/main/java/ecdar/controllers/SimulatorController.java index 36539552..63d82474 100644 --- a/src/main/java/ecdar/controllers/SimulatorController.java +++ b/src/main/java/ecdar/controllers/SimulatorController.java @@ -21,6 +21,7 @@ import java.util.ResourceBundle; public class SimulatorController implements Initializable { + private static String simulationQuery; public StackPane root; public SimulatorOverviewPresentation overviewPresentation; public StackPane toolbar; @@ -83,7 +84,7 @@ private void resetSimulation() { //Method that colors all initial states. initialstatelighter(listOfComponentsForSimulation); } - + private void initialstatelighter(List listofComponents){ for(Component comp: listofComponents) { @@ -108,7 +109,7 @@ private void initialstatelighter(List listofComponents){ private List findComponentsInCurrentSimulation(List queryComponents) { //Show components from the system List components = new ArrayList<>(); - + components = Ecdar.getProject().getComponents(); //Matches query components against with existing components and adds them to simulation @@ -166,4 +167,11 @@ public static ObjectProperty getSelectedStateProperty() { public static void setSelectedState(SimulationState selectedState) { SimulatorController.selectedState.set(selectedState); } + public static void setSimulationQuery(String query) { + simulationQuery = query; + } + + public static String getSimulationQuery(){ + return simulationQuery; + } } diff --git a/src/main/resources/ecdar/presentations/SimulationInitializationDialogPresentation.fxml b/src/main/resources/ecdar/presentations/SimulationInitializationDialogPresentation.fxml index 8fa6635b..eb97bdd1 100644 --- a/src/main/resources/ecdar/presentations/SimulationInitializationDialogPresentation.fxml +++ b/src/main/resources/ecdar/presentations/SimulationInitializationDialogPresentation.fxml @@ -36,7 +36,7 @@ - + diff --git a/src/test/java/ecdar/simulation/ReachabilityTest.java b/src/test/java/ecdar/simulation/ReachabilityTest.java index 527cc2b8..6e645470 100644 --- a/src/test/java/ecdar/simulation/ReachabilityTest.java +++ b/src/test/java/ecdar/simulation/ReachabilityTest.java @@ -23,7 +23,7 @@ static void setup() { @Test void reachabilityQuerySyntaxTestSuccess() { - var regex = "([a-zA-Z]\\w*)([|][|][a-zA-Z]\\w*)*\\s+\\->\\s+\\[(\\w*)(,(\\w)*)*\\]\\([a-zA-Z0-9_<>=]*\\)(;\\[(\\w*)(,(\\w)*)\\]\\([a-zA-Z0-9_<>=]*\\))*"; + var regex = "query\\s+\\->\\s+\\[(\\w*)(,(\\w)*)*\\]\\([a-zA-Z0-9_<>=]*\\)(;\\[(\\w*)(,(\\w)*)\\]\\([a-zA-Z0-9_<>=]*\\))*"; var location = new Location(); location.setId("L1"); @@ -35,7 +35,7 @@ void reachabilityQuerySyntaxTestSuccess() { SimulationInitializationDialogController.ListOfComponents.add("C2"); SimulationInitializationDialogController.ListOfComponents.add("C3"); - var result = BackendHelper.getLocationReachableQuery(location, component); + var result = BackendHelper.getLocationReachableQuery(location, component, "query"); assertTrue(result.matches(regex)); } @@ -51,7 +51,7 @@ void reachabilityQueryLocationPosition1TestSuccess() { SimulationInitializationDialogController.ListOfComponents.add("C2"); SimulationInitializationDialogController.ListOfComponents.add("C3"); - var result = BackendHelper.getLocationReachableQuery(location, component); + var result = BackendHelper.getLocationReachableQuery(location, component, "query"); var indexOfLocation = result.indexOf('[') + 1; var output = result.charAt(indexOfLocation); assertEquals(output, location.getId().charAt(0)); @@ -69,7 +69,7 @@ void reachabilityQueryLocationPosition2TestSuccess() { SimulationInitializationDialogController.ListOfComponents.add("C1"); SimulationInitializationDialogController.ListOfComponents.add("C3"); - var result = BackendHelper.getLocationReachableQuery(location, component); + var result = BackendHelper.getLocationReachableQuery(location, component, "query"); var indexOfLocation = result.indexOf(',') + 1; var output = result.charAt(indexOfLocation); assertEquals(output, location.getId().charAt(0)); @@ -87,7 +87,7 @@ void reachabilityQueryLocationPosition3TestSuccess() { SimulationInitializationDialogController.ListOfComponents.add("C3"); SimulationInitializationDialogController.ListOfComponents.add("C1"); - var query = BackendHelper.getLocationReachableQuery(location, component); + var query = BackendHelper.getLocationReachableQuery(location, component, "query"); var indexOfLocation = query.indexOf(']') - 2; var output = query.charAt(indexOfLocation); assertEquals(output, location.getId().charAt(0)); @@ -106,7 +106,7 @@ void reachabilityQueryNumberOfLocationsTestSuccess() { SimulationInitializationDialogController.ListOfComponents.add("C3"); SimulationInitializationDialogController.ListOfComponents.add("C4"); - var query = BackendHelper.getLocationReachableQuery(location, component); + var query = BackendHelper.getLocationReachableQuery(location, component, "query"); int underscoreCount = 0; for (int i = 0; i < query.length(); i++) { if (query.charAt(i) == '_') { From bb2521a336a40e36a001f8143ca43521a6914a02 Mon Sep 17 00:00:00 2001 From: EmilieSonne <82802471+EmilieSonne@users.noreply.github.com> Date: Wed, 30 Nov 2022 14:52:50 +0100 Subject: [PATCH 117/158] Simulation logic (#81) * sim work + unique edge ids + temporary fix for scaling issues on startup * clean up * fix errors when switching back to editor * mostly refactoring - move highlight listeners to overviewcontroller - save state (to be used for the next steprequest) in SimulationState * reset when user selects another composition * Update SimulationHandler.java * Remove test data and show message to user when no available transitions * move simhandler * clean up + comments * add functionality to reset button * Update Edge.java * Update SimulatorController.java * Remove unused imports * comment + rename method * fixed edge id error when cloning for sim + comments * footer disabled * bingbong * Update EcdarPresentation.fxml * Yeeeet! * Update SimulatorController.java * rename refreshTransitions * Update SimulationState.java * jep * fix for when SpecificComponent is null in sim response * Update TransitionPaneElementPresentation.fxml * only change to hand cursor on available transitions Co-authored-by: APaludan Co-authored-by: WassawRoki <56611129+WassawRoki@users.noreply.github.com> --- .../ecdar/abstractions/DisplayableEdge.java | 9 +- src/main/java/ecdar/abstractions/Edge.java | 13 +- .../java/ecdar/abstractions/GroupedEdge.java | 9 +- .../java/ecdar/backend/SimulationHandler.java | 814 ++++++++---------- .../ecdar/controllers/EcdarController.java | 20 +- .../controllers/MessageTabPaneController.java | 272 ------ .../controllers/SimulatorController.java | 71 +- .../SimulatorOverviewController.java | 63 +- .../TracePaneElementController.java | 16 +- .../TransitionPaneElementController.java | 40 +- .../presentations/LocationPresentation.java | 13 +- .../MessageTabPanePresentation.java | 115 --- .../ecdar/presentations/NailPresentation.java | 9 +- .../presentations/SimEdgePresentation.java | 18 + .../ecdar/simulation/SimulationState.java | 65 +- .../simulation/SimulationStateSuccessor.java | 15 - .../presentations/EcdarPresentation.fxml | 2 - .../MessageTabPanePresentation.fxml | 54 -- .../presentations/SimEdgePresentation.fxml | 3 +- .../TransitionPaneElementPresentation.fxml | 2 +- 20 files changed, 500 insertions(+), 1123 deletions(-) mode change 100755 => 100644 src/main/java/ecdar/backend/SimulationHandler.java delete mode 100644 src/main/java/ecdar/controllers/MessageTabPaneController.java delete mode 100644 src/main/java/ecdar/presentations/MessageTabPanePresentation.java delete mode 100644 src/main/java/ecdar/simulation/SimulationStateSuccessor.java delete mode 100644 src/main/resources/ecdar/presentations/MessageTabPanePresentation.fxml diff --git a/src/main/java/ecdar/abstractions/DisplayableEdge.java b/src/main/java/ecdar/abstractions/DisplayableEdge.java index bb99d5cf..be812729 100644 --- a/src/main/java/ecdar/abstractions/DisplayableEdge.java +++ b/src/main/java/ecdar/abstractions/DisplayableEdge.java @@ -1,6 +1,5 @@ package ecdar.abstractions; -import ecdar.Ecdar; import ecdar.code_analysis.Nearable; import ecdar.presentations.Grid; import ecdar.utility.colors.Color; @@ -12,6 +11,7 @@ import javafx.collections.ObservableList; import java.util.List; +import java.util.UUID; public abstract class DisplayableEdge implements Nearable { private final StringProperty id = new SimpleStringProperty(""); @@ -282,12 +282,7 @@ public String getId() { * Generate and sets a unique id for this location */ protected void setId() { - for(int counter = 0; ; counter++) { - if(!Ecdar.getProject().getEdgeIds().contains(String.valueOf(counter))){ - id.set(Edge.EDGE + counter); - return; - } - } + id.set(UUID.randomUUID().toString()); } /** diff --git a/src/main/java/ecdar/abstractions/Edge.java b/src/main/java/ecdar/abstractions/Edge.java index a0ed8b38..22c5cdbe 100644 --- a/src/main/java/ecdar/abstractions/Edge.java +++ b/src/main/java/ecdar/abstractions/Edge.java @@ -207,10 +207,17 @@ public void deserialize(final JsonObject json, final Component component) { } ioStatus = new SimpleObjectProperty<>(EdgeStatus.valueOf(json.getAsJsonPrimitive(STATUS).getAsString())); - final JsonPrimitive IDJson = json.getAsJsonPrimitive(ID); - if (IDJson != null) setId(IDJson.getAsString()); - else setId(); + /* The new type of edge ids is a UUID, which has a length of 36 characters. + * The if statement checks if the id is a UUID and if not, it creates a new UUID. + * This is necessary because the old type of edge ids were not unique and has to be replaced in old projects. + * It should be possible to simplify this in the future when all projects are updated. + */ + int UUIDLength = 36; + if (IDJson != null && IDJson.getAsString().length() == UUIDLength) + setId(IDJson.getAsString()); + else + setId(); final JsonPrimitive groupJson = json.getAsJsonPrimitive(GROUP); diff --git a/src/main/java/ecdar/abstractions/GroupedEdge.java b/src/main/java/ecdar/abstractions/GroupedEdge.java index f96ab4b7..02503d9a 100644 --- a/src/main/java/ecdar/abstractions/GroupedEdge.java +++ b/src/main/java/ecdar/abstractions/GroupedEdge.java @@ -1,6 +1,5 @@ package ecdar.abstractions; -import ecdar.Ecdar; import javafx.beans.property.*; import javafx.collections.FXCollections; import javafx.collections.ListChangeListener; @@ -8,6 +7,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.UUID; public class GroupedEdge extends DisplayableEdge { private final ObservableList edges = FXCollections.observableList(new ArrayList<>()); @@ -90,12 +90,7 @@ public String getId() { * Generate and sets a unique id for this location */ protected void setId() { - for(int counter = 0; ; counter++) { - if(!Ecdar.getProject().getEdgeIds().contains(String.valueOf(counter))){ - id.set(Edge.EDGE_GROUP + counter); - return; - } - } + id.set(UUID.randomUUID().toString()); } /** diff --git a/src/main/java/ecdar/backend/SimulationHandler.java b/src/main/java/ecdar/backend/SimulationHandler.java old mode 100755 new mode 100644 index 4277bf09..b15e96b0 --- a/src/main/java/ecdar/backend/SimulationHandler.java +++ b/src/main/java/ecdar/backend/SimulationHandler.java @@ -1,467 +1,349 @@ -package ecdar.backend; - -import EcdarProtoBuf.ComponentProtos; -import EcdarProtoBuf.QueryProtos; -import ecdar.Ecdar; -import ecdar.abstractions.*; -import ecdar.simulation.SimulationState; -import ecdar.simulation.SimulationStateSuccessor; -import io.grpc.stub.StreamObserver; -import javafx.beans.property.ObjectProperty; -import javafx.beans.property.SimpleObjectProperty; -import javafx.collections.FXCollections; -import javafx.collections.ObservableList; -import javafx.collections.ObservableMap; - -import java.io.IOException; -import java.math.BigDecimal; -import java.util.ArrayList; -import java.util.Map; -import java.util.concurrent.TimeUnit; - -import EcdarProtoBuf.QueryProtos.SimulationStepResponse; - -/** - * Handles state changes, updates of values / clocks, and keeps track of all the transitions that - * have been taken throughout a simulation. - */ -public class SimulationHandler { - public static final String QUERY_PREFIX = "Query: "; - private String composition; - private ObjectProperty currentConcreteState = new SimpleObjectProperty<>(); - private ObjectProperty initialConcreteState = new SimpleObjectProperty<>(); - private ObjectProperty currentTime = new SimpleObjectProperty<>(); - private BigDecimal delay; - private ArrayList edgesSelected; - private EcdarSystem system; - private SimulationStateSuccessor successor; - private int numberOfSteps; - - private final ObservableMap simulationVariables = FXCollections.observableHashMap(); - private final ObservableMap simulationClocks = FXCollections.observableHashMap(); - /** - * For some reason the successor.getTransitions() only sometimes returns some of the transitions - * that are available, when running the initial step. - * That is why we need to keep track of the initial transitions. - */ - private final ObservableList initialTransitions = FXCollections.observableArrayList(); - public ObservableList traceLog = FXCollections.observableArrayList(); - public ObservableList availableTransitions = FXCollections.observableArrayList(); - private final BackendDriver backendDriver; - private final ArrayList connections = new ArrayList<>(); - - /** - * Empty constructor that should be used if the system or project has not be initialized yet - */ - public SimulationHandler(BackendDriver backendDriver) { - this.backendDriver = backendDriver; - } - - /** - * Initializes the default system (non-query system) - */ - - /** - * Initializes the values and properties in the {@link SimulationHandler}. - * Can also be used as a reset of the simulation. - * THIS METHOD DOES NOT RESET THE ENGINE, - */ - private void initializeSimulation() { - // Initialization - this.delay = new BigDecimal(0); - this.edgesSelected = new ArrayList<>(); - this.numberOfSteps = 0; - this.availableTransitions.clear(); - this.simulationVariables.clear(); - this.simulationClocks.clear(); - this.traceLog.clear(); - this.currentConcreteState.set(getInitialConcreteState()); - this.initialConcreteState.set(getInitialConcreteState()); - this.currentTime = new SimpleObjectProperty<>(BigDecimal.ZERO); - - //Preparation for the simulation - this.system = getSystem(); - //this.currentConcreteState.get().setTime(currentTime.getValue()); - this.initialTransitions.clear(); - this.successor = null; - } - - /** - * Reloads the whole simulation sets the initial transitions, states, etc - */ - public void initialStep() { - initializeSimulation(); - - final SimulationState currentState = currentConcreteState.get(); - successor = getStateSuccessor(); - - GrpcRequest request = new GrpcRequest(backendConnection -> { - StreamObserver responseObserver = new StreamObserver<>() { - @Override - public void onNext(QueryProtos.SimulationStepResponse value) { - System.out.println(value); - } - - @Override - public void onError(Throwable t) { - System.out.println(t.getMessage()); - Ecdar.showToast("Could not start simulation"); - - // Release backend connection - backendDriver.addBackendConnection(backendConnection); - connections.remove(backendConnection); - } - - @Override - public void onCompleted() { - // Release backend connection - backendDriver.addBackendConnection(backendConnection); - connections.remove(backendConnection); - } - }; - - var comInfo = ComponentProtos.ComponentsInfo.newBuilder(); - for (Component c : Ecdar.getProject().getComponents()) { - comInfo.addComponents(ComponentProtos.Component.newBuilder().setJson(c.serialize().toString()).build()); - } - comInfo.setComponentsHash(comInfo.getComponentsList().hashCode()); - var simStartRequest = QueryProtos.SimulationStartRequest.newBuilder(); - var simInfo = QueryProtos.SimulationInfo.newBuilder() - .setComponentComposition(composition) - .setComponentsInfo(comInfo); - simStartRequest.setSimulationInfo(simInfo); - backendConnection.getStub().withDeadlineAfter(this.backendDriver.getResponseDeadline(), TimeUnit.MILLISECONDS) - .startSimulation(simStartRequest.build(), responseObserver); - }, BackendHelper.getDefaultBackendInstance()); - - backendDriver.addRequestToExecutionQueue(request); - - //Save the previous states, and get the new - currentConcreteState.set(successor.getState()); - this.traceLog.add(currentState); - numberOfSteps++; - - //Updates the transitions available - availableTransitions.addAll(FXCollections.observableArrayList(successor.getTransitions())); - initialTransitions.addAll(availableTransitions); - updateAllValues(); - - } - - /** - * Resets the simulation to the initial location - * where the SimulationState is the {@link SimulationHandler#initialConcreteState}, when there are - * elements in the {@link SimulationHandler#traceLog}. Otherwise, it calls {@link SimulationHandler#initialStep} - */ - public void resetToInitialLocation() { - //If the simulation has not begun - if (traceLog.size() == 0) - initialStep(); - else - selectTransitionFromLog(initialConcreteState.get()); - } - - /** - * Resets the simulation to the state after executing the given transition.
- * This method also resets the state, variables, and clocks to the values they had after the given transition. - * This also updates {@link SimulationHandler#availableTransitions} such that - * it displays the available transitions after taking the given transition. - * - * @param transition the transition which the simulation should go back to - */ - public void selectTransitionFromLog(final SimulationState transition) { - final int indexInTrace = traceLog.indexOf(transition); - final SimulationState selectedState; - if (indexInTrace == -1) { - System.out.println("Cannot find transition: " + transition); - Ecdar.showToast("Cannot find transition: " + transition); - return; - } else if (indexInTrace == numberOfSteps - 1) { - return; //you have selected the current system - } else { - selectedState = traceLog.get(indexInTrace); - } - final int sizeOfTraceLog = traceLog.size(); - final int maxRetries = 3; - int numberOfRetries = 0; - edgesSelected = new ArrayList<>(); - //In case that we fail we have to save the time we had before - final BigDecimal tempTime = currentTime.get(); - - currentTime.setValue(new BigDecimal(selectedState.getTime().doubleValue())); - successor.getState().setTime(currentTime.getValue()); - - while (numberOfRetries < maxRetries) { - successor = getStateSuccessor(); - break; - } - currentConcreteState.set(selectedState); - setSimVarAndClocks(); - traceLog.remove(indexInTrace + 1, sizeOfTraceLog); - availableTransitions.clear(); - - // If the user selected the initial/first state in the trace log, we do not trust the engine, - // as it only gives us a subset of the available transitions, in some cases. - if (indexInTrace == 0) availableTransitions.addAll(initialTransitions); - else availableTransitions.addAll(successor.getTransitions()); - - numberOfSteps = indexInTrace + 1; - } - - /** - * Take a step in the simulation. - * - * @param selectedTransitionIndex the index of the availableTransition that you want to take. - * @param delay the time which should pass after the transition. - */ - public void nextStep(final int selectedTransitionIndex, final BigDecimal delay) { - if (selectedTransitionIndex > availableTransitions.size()) { - Ecdar.showToast("The selected transition index: " + selectedTransitionIndex + " is bigger than it should: " + availableTransitions); - return; - } - - final ecdar.simulation.Transition selectedTransition = availableTransitions.get(selectedTransitionIndex); - edgesSelected = new ArrayList<>(); - - //Preparing for the step - for (int i = 0; i < selectedTransition.getEdges().size(); i++) { - edgesSelected.set(i, selectedTransition.getEdges().get(i)); - } - - final int maxRetries = 3; - int numberOfRetries = 0; - - // getConcreteSuccessor may throw a "ProtocolException: Word expected" but in some cases calling the same - // method again does not throw this exception, and actually gives us the expected result. - // This loop calls the method a number of times (maxRetries) - while (numberOfRetries < maxRetries) { - successor = getStateSuccessor(); - // Break from the loop if the method call was a success - break; - } - - //Save the previous states, and get the new - currentConcreteState.set(successor.getState()); - this.traceLog.add(currentConcreteState.get()); - - // increments the number of steps taken during this simulation - numberOfSteps++; - - //Updates the transitions available - availableTransitions.clear(); - availableTransitions.setAll(successor.getTransitions()); - this.delay = delay; - updateAllValues(); - } - - private SimulationStateSuccessor getStateSuccessor() { - // ToDo: Implement - return new SimulationStateSuccessor(); - } - - /** - * An overload of {@link SimulationHandler#nextStep(int, BigDecimal)} where the delay is 0. - * - * @param selectedTransition the index of the availableTransition that you want to take. - */ - public void nextStep(final int selectedTransition) { - nextStep(selectedTransition, BigDecimal.ZERO); - } - - public void nextStep(final ecdar.simulation.Transition transition, final BigDecimal delay) { - int index = availableTransitions.indexOf(transition); - if (index != -1) { - nextStep(index, delay); - } - } - - /** - * Updates all values and clocks that are used doing the current simulation. - * It also stores the variables in the {@link SimulationHandler#simulationVariables} - * and the clocks in {@link SimulationHandler#simulationClocks}. - */ - private void updateAllValues() { - currentTime.set(currentTime.get().add(delay)); - //successor.getState().setTime(currentTime.get()); - setSimVarAndClocks(); - } - - /** - * Sets the value of simulation variables and clocks, based on {@link SimulationHandler#currentConcreteState} - */ - private void setSimVarAndClocks() { - // The variables and clocks are all found in the getVariables array - // the array is always of the following order: variables, clocks. - // The noOfVars variable thus also functions as an offset for the clocks in the getVariables array -// final int noOfClocks = engine.getSystem().getNoOfClocks(); -// final int noOfVars = engine.getSystem().getNoOfVariables(); - -// for (int i = 0; i < noOfVars; i++){ -// simulationVariables.put(engine.getSystem().getVariableName(i), -// currentConcreteState.get().getVariables()[i].getValue(BigDecimal.ZERO)); -// } - - // As the clocks values starts after the variables values in currentConcreteState.get().getVariables() - // Then i needs to start where the variables ends. - // j is needed to map the correct name with the value -// for (int i = noOfVars, j = 0; i < noOfClocks + noOfVars ; i++, j++) { -// simulationClocks.put(engine.getSystem().getClockName(j), -// currentConcreteState.get().getVariables()[i].getValue(BigDecimal.ZERO)); -// } - } - - /** - * Getter for the current concrete state - * - * @return the current {@link SimulationState} - */ - public SimulationState getCurrentState() { - return currentConcreteState.get(); - } - - /** - * The way to get the time in the current state of a simulation - * - * @return the time in the current state - */ - public BigDecimal getCurrentTime() { - return currentTime.get(); - } - - public ObjectProperty currentTimeProperty() { - return currentTime; - } - - /** - * The way to get the delay of the latest step in the simulation - * - * @return the delay of the latest step in the in the simulation - */ - public BigDecimal getDelay() { - return delay; - } - - /** - * The number of total steps taken in the current simulation - * - * @return the number of steps - */ - public int getNumberOfSteps() { - return numberOfSteps; - } - - /** - * All the transitions taken in this simulation - * - * @return an {@link ObservableList} of all the transitions taken in this simulation so far - */ - public ObservableList getTraceLog() { - return traceLog; - } - - /** - * All the available transitions in this state - * - * @return an {@link ObservableList} of all the currently available transitions in this state - */ - public ObservableList getAvailableTransitions() { - return availableTransitions; - } - - /** - * All the variables connected to the current simulation. - * This does not return any clocks, if you need please use {@link SimulationHandler#getSimulationClocks()} instead - * - * @return a {@link Map} where the name (String) is the key, and a {@link BigDecimal} is the value - */ - public ObservableMap getSimulationVariables() { - return simulationVariables; - } - - /** - * All the clocks connected to the current simulation. - * - * @return a {@link Map} where the name (String) is the key, and a {@link BigDecimal} is the clock value - * @see SimulationHandler#getSimulationVariables() - */ - public ObservableMap getSimulationClocks() { - return simulationClocks; - } - - /** - * The initial state of the current simulation - * - * @return the initial {@link SimulationState} of this simulation - */ - public SimulationState getInitialConcreteState() { - // ToDo: Implement - return initialConcreteState.get(); - } - - public ObjectProperty initialConcreteStateProperty() { - return initialConcreteState; - } - - /** - * Prints all available transitions to {@link System#out}. - * This is very useful for debugging. - * If a string representation is needed please use {@link SimulationHandler#getAvailableTransitionsAsStrings()} - * instead. - */ - public void printAvailableTransitions() { - System.out.println("---------------------------------"); - - System.out.println(numberOfSteps + " Successor state " + currentConcreteState.toString() + " Entry time " + currentTime); - System.out.print("Available transitions: "); - availableTransitions.forEach( - Transition -> System.out.println(Transition.getLabel() + " ")); - - if (!availableTransitions.isEmpty()) { - for (int i = 0; i < availableTransitions.get(0).getEdges().size(); i++) { - // ToDo: Implement -// System.out.println("Edges: " + -// availableTransitions.get(0).getEdges().get(i).getEdge().getSource().getPropertyValue("name") + -// "." + availableTransitions.get(0).getEdges().get(i).getName() + " --> " + -// availableTransitions.get(0).getEdges().get(i).getEdge().getTarget().getPropertyValue("name")); - } - } - - System.out.println("---------------------------------"); - } - - /** - * To get all available transitions as strings - * - * @return an ArrayList of all the enabled transitions - */ - public ArrayList getAvailableTransitionsAsStrings() { - final ArrayList transitions = new ArrayList<>(); - for (final ecdar.simulation.Transition Transition : availableTransitions) { - transitions.add(Transition.getLabel()); - } - return transitions; - } - - public EcdarSystem getSystem() { - return system; - } - - public String getComposition() { return composition;} - - public void setComposition(String composition) {this.composition = composition;} - - public boolean isSimulationRunning() { - return false; // ToDo: Implement - } - - /** - * Close all open backend connection and kill all locally running processes - * - * @throws IOException if any of the sockets do not respond - */ - public void closeAllBackendConnections() throws IOException { - for (BackendConnection con : connections) { - con.close(); - } - } +package ecdar.backend; + +import EcdarProtoBuf.ComponentProtos; +import EcdarProtoBuf.ObjectProtos; +import EcdarProtoBuf.QueryProtos; +import EcdarProtoBuf.ObjectProtos.Decision; +import ecdar.Ecdar; +import ecdar.abstractions.*; +import ecdar.simulation.SimulationState; +import io.grpc.stub.StreamObserver; +import javafx.application.Platform; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.collections.ObservableMap; +import javafx.util.Pair; + +import java.io.IOException; +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import EcdarProtoBuf.QueryProtos.SimulationInfo; +import EcdarProtoBuf.QueryProtos.SimulationStepRequest; +import EcdarProtoBuf.QueryProtos.SimulationStepResponse; + +/** + * Handles state changes, updates of values / clocks, and keeps track of all the transitions that + * have been taken throughout a simulation. + */ +public class SimulationHandler { + public static final String QUERY_PREFIX = "Query: "; + private String composition; + public ObjectProperty currentState = new SimpleObjectProperty<>(); + public ObjectProperty initialState = new SimpleObjectProperty<>(); + public ObjectProperty selectedEdge = new SimpleObjectProperty<>(); + private EcdarSystem system; + private int numberOfSteps; + + private final ObservableMap simulationVariables = FXCollections.observableHashMap(); + private final ObservableMap simulationClocks = FXCollections.observableHashMap(); + public ObservableList traceLog = FXCollections.observableArrayList(); + private final BackendDriver backendDriver; + private final ArrayList connections = new ArrayList<>(); + + /** + * Empty constructor that should be used if the system or project has not be initialized yet + */ + public SimulationHandler(BackendDriver backendDriver) { + this.backendDriver = backendDriver; + } + + + /** + * Initializes the values and properties in the {@link SimulationHandler}. + * Can also be used as a reset of the simulation. + * THIS METHOD DOES NOT RESET THE ENGINE, + */ + private void initializeSimulation() { + // Initialization + this.numberOfSteps = 0; + this.simulationVariables.clear(); + this.simulationClocks.clear(); + this.currentState.set(null); + this.selectedEdge.set(null); + this.traceLog.clear(); + + this.system = getSystem(); + } + + + /** + * Reloads the whole simulation sets the initial transitions, states, etc + */ + public void initialStep() { + initializeSimulation(); + + GrpcRequest request = new GrpcRequest(backendConnection -> { + StreamObserver responseObserver = new StreamObserver<>() { + @Override + public void onNext(QueryProtos.SimulationStepResponse value) { + currentState.set(new SimulationState(value.getNewDecisionPoint())); + Platform.runLater(() -> traceLog.add(currentState.get())); + } + + @Override + public void onError(Throwable t) { + Ecdar.showToast("Could not start simulation:\n" + t.getMessage()); + + // Release backend connection + backendDriver.addBackendConnection(backendConnection); + connections.remove(backendConnection); + } + + @Override + public void onCompleted() { + // Release backend connection + backendDriver.addBackendConnection(backendConnection); + connections.remove(backendConnection); + } + }; + + var comInfo = ComponentProtos.ComponentsInfo.newBuilder(); + for (Component c : Ecdar.getProject().getComponents()) { + comInfo.addComponents(ComponentProtos.Component.newBuilder().setJson(c.serialize().toString()).build()); + } + comInfo.setComponentsHash(comInfo.getComponentsList().hashCode()); + var simStartRequest = QueryProtos.SimulationStartRequest.newBuilder(); + var simInfo = QueryProtos.SimulationInfo.newBuilder() + .setComponentComposition(composition) + .setComponentsInfo(comInfo); + simStartRequest.setSimulationInfo(simInfo); + backendConnection.getStub().withDeadlineAfter(this.backendDriver.getResponseDeadline(), TimeUnit.MILLISECONDS) + .startSimulation(simStartRequest.build(), responseObserver); + }, BackendHelper.getDefaultBackendInstance()); + + backendDriver.addRequestToExecutionQueue(request); + + //Save the previous states, and get the new + this.traceLog.add(currentState.get()); + numberOfSteps++; + + //Updates the transitions available + updateAllValues(); + + } + + /** + * Resets the simulation to the initial location + */ + public void resetToInitialLocation() { + initialStep(); + } + + /** + * Take a step in the simulation. + */ + public void nextStep() { + GrpcRequest request = new GrpcRequest(backendConnection -> { + StreamObserver responseObserver = new StreamObserver<>() { + @Override + public void onNext(QueryProtos.SimulationStepResponse value) { + currentState.set(new SimulationState(value.getNewDecisionPoint())); + Platform.runLater(() -> traceLog.add(currentState.get())); + } + + @Override + public void onError(Throwable t) { + Ecdar.showToast("Could not take next step in simulation\nError: " + t.getMessage()); + + // Release backend connection + backendDriver.addBackendConnection(backendConnection); + connections.remove(backendConnection); + } + + @Override + public void onCompleted() { + // Release backend connection + backendDriver.addBackendConnection(backendConnection); + connections.remove(backendConnection); + } + }; + + var comInfo = ComponentProtos.ComponentsInfo.newBuilder(); + for (Component c : Ecdar.getProject().getComponents()) { + comInfo.addComponents(ComponentProtos.Component.newBuilder().setJson(c.serialize().toString()).build()); + } + comInfo.setComponentsHash(comInfo.getComponentsList().hashCode()); + var simStepRequest = SimulationStepRequest.newBuilder(); + var simInfo = SimulationInfo.newBuilder() + .setComponentComposition(composition) + .setComponentsInfo(comInfo); + simStepRequest.setSimulationInfo(simInfo); + var source = currentState.get().getState(); + var specComp = ObjectProtos.SpecificComponent.newBuilder().setComponentName(getComponentName(selectedEdge.get())).setComponentIndex(getComponentIndex(selectedEdge.get())); + var edge = EcdarProtoBuf.ObjectProtos.Edge.newBuilder().setId(selectedEdge.get().getId()).setSpecificComponent(specComp); + var decision = Decision.newBuilder().setEdge(edge).setSource(source); + simStepRequest.setChosenDecision(decision); + + backendConnection.getStub().withDeadlineAfter(this.backendDriver.getResponseDeadline(), TimeUnit.MILLISECONDS) + .takeSimulationStep(simStepRequest.build(), responseObserver); + }, BackendHelper.getDefaultBackendInstance()); + + backendDriver.addRequestToExecutionQueue(request); + + + // increments the number of steps taken during this simulation + numberOfSteps++; + + + updateAllValues(); + } + + private String getComponentName(Edge edge) { + var components = Ecdar.getProject().getComponents(); + for (var component : components) { + for (var e : component.getEdges()) { + if (e.getId().equals(edge.getId())) { + return component.getName(); + } + } + } + throw new RuntimeException("Could not find component name for edge with id " + edge.getId()); + } + + private int getComponentIndex (Edge edge) { + for (int i = 0; i < Ecdar.getProject().getComponents().size(); i++) { + if (Ecdar.getProject().getComponents().get(i).getEdges().stream().anyMatch(p -> p.getId() == edge.getId())) { + return i; + } + }; + throw new IllegalArgumentException("Edge does not belong to any component"); + } + + + /** + * Updates all values and clocks that are used doing the current simulation. + * It also stores the variables in the {@link SimulationHandler#simulationVariables} + * and the clocks in {@link SimulationHandler#simulationClocks}. + */ + private void updateAllValues() { + setSimVarAndClocks(); + } + + /** + * Sets the value of simulation variables and clocks, based on {@link SimulationHandler#currentConcreteState} + */ + private void setSimVarAndClocks() { + // The variables and clocks are all found in the getVariables array + // the array is always of the following order: variables, clocks. + // The noOfVars variable thus also functions as an offset for the clocks in the getVariables array +// final int noOfClocks = engine.getSystem().getNoOfClocks(); +// final int noOfVars = engine.getSystem().getNoOfVariables(); + +// for (int i = 0; i < noOfVars; i++){ +// simulationVariables.put(engine.getSystem().getVariableName(i), +// currentConcreteState.get().getVariables()[i].getValue(BigDecimal.ZERO)); +// } + + // As the clocks values starts after the variables values in currentConcreteState.get().getVariables() + // Then i needs to start where the variables ends. + // j is needed to map the correct name with the value +// for (int i = noOfVars, j = 0; i < noOfClocks + noOfVars ; i++, j++) { +// simulationClocks.put(engine.getSystem().getClockName(j), +// currentConcreteState.get().getVariables()[i].getValue(BigDecimal.ZERO)); +// } + } + + + + /** + * The number of total steps taken in the current simulation + * + * @return the number of steps + */ + public int getNumberOfSteps() { + return numberOfSteps; + } + + /** + * All the transitions taken in this simulation + * + * @return an {@link ObservableList} of all the transitions taken in this simulation so far + */ + public ObservableList getTraceLog() { + return traceLog; + } + + /** + * All the available transitions in this state + * @return + * + * @return an {@link ObservableList} of all the currently available transitions in this state + */ + public ArrayList> getAvailableTransitions() { + return currentState.get().getEdges(); + } + + /** + * All the variables connected to the current simulation. + * This does not return any clocks, if you need please use {@link SimulationHandler#getSimulationClocks()} instead + * + * @return a {@link Map} where the name (String) is the key, and a {@link BigDecimal} is the value + */ + public ObservableMap getSimulationVariables() { + return simulationVariables; + } + + /** + * All the clocks connected to the current simulation. + * + * @return a {@link Map} where the name (String) is the key, and a {@link BigDecimal} is the clock value + * @see SimulationHandler#getSimulationVariables() + */ + public ObservableMap getSimulationClocks() { + return simulationClocks; + } + + /** + * The initial state of the current simulation + * + * @return the initial {@link SimulationState} of this simulation + */ + public SimulationState getInitialState() { + // ToDo: Implement + return initialState.get(); + } + + public ObjectProperty initialStateProperty() { + return initialState; + } + + + public EcdarSystem getSystem() { + return system; + } + + public String getComposition() { return composition;} + + public void setComposition(String composition) {this.composition = composition;} + + public boolean isSimulationRunning() { + return false; // ToDo: Implement + } + + /** + * Close all open backend connection and kill all locally running processes + * + * @throws IOException if any of the sockets do not respond + */ + public void closeAllBackendConnections() throws IOException { + for (BackendConnection con : connections) { + con.close(); + } + } + + + /** + * Sets the current state of the simulation to the given state from the trace log + */ + public void selectStateFromLog(SimulationState state) { + while (traceLog.get(traceLog.size() - 1) != state) { + traceLog.remove(traceLog.size() - 1); + } + currentState.set(state); + } } \ No newline at end of file diff --git a/src/main/java/ecdar/controllers/EcdarController.java b/src/main/java/ecdar/controllers/EcdarController.java index a7b7c558..89ebb802 100644 --- a/src/main/java/ecdar/controllers/EcdarController.java +++ b/src/main/java/ecdar/controllers/EcdarController.java @@ -60,7 +60,6 @@ public class EcdarController implements Initializable { public StackPane leftPane; public StackPane rightPane; public Rectangle bottomFillerElement; - public MessageTabPanePresentation messageTabPane; public StackPane modellingHelpDialogContainer; public JFXDialog modellingHelpDialog; public StackPane modalBar; @@ -180,9 +179,6 @@ public void initialize(final URL location, final ResourceBundle resources) { initializeMenuBar(); startBackgroundQueriesThread(); // Will terminate immediately if background queries are turned off - bottomFillerElement.heightProperty().bind(messageTabPane.maxHeightProperty()); - messageTabPane.getController().setRunnableForOpeningAndClosingMessageTabPane(this::changeInsetsOfProjectAndQueryPanes); - // Update file coloring when active model changes editorPresentation.getController().getActiveCanvasPresentation().getController().activeComponentProperty().addListener(observable -> projectPane.getController().updateColorsOnFilePresentations()); editorPresentation.getController().activeCanvasPresentationProperty().addListener(observable -> projectPane.getController().updateColorsOnFilePresentations()); @@ -726,7 +722,6 @@ private void updateScaling(double newScale) { scaleIcons(root, newCalculatedScale); editorPresentation.getController().scaleEdgeStatusToggle(newCalculatedScale); - messageTabPane.getController().updateScale(newScale); // Update listeners of UI scale scalingProperty.set(newScale); @@ -1005,6 +1000,7 @@ private void enterEditorMode() { leftPane.getChildren().add(projectPane); rightPane.getChildren().clear(); rightPane.getChildren().add(queryPane); + scaling.selectToggle(scaleM); // temporary fix for scaling issue // Enable or disable the menu items that can be used when in the simulator // updateMenuItems(); @@ -1190,20 +1186,6 @@ private static int getAutoCropRightX(final BufferedImage image) { throw new IllegalArgumentException("Image is all white"); } - /** - * This method is used to push the contents of the project and query panes when the tab pane is opened - */ - private void changeInsetsOfProjectAndQueryPanes() { - if (messageTabPane.getController().isOpen()) { - projectPane.showBottomInset(false); - queryPane.showBottomInset(false); - CanvasPresentation.showBottomInset(false); - } else { - projectPane.showBottomInset(true); - queryPane.showBottomInset(true); - CanvasPresentation.showBottomInset(true); - } - } private void nudgeSelected(final NudgeDirection direction) { final List selectedElements = SelectHelper.getSelectedElements(); diff --git a/src/main/java/ecdar/controllers/MessageTabPaneController.java b/src/main/java/ecdar/controllers/MessageTabPaneController.java deleted file mode 100644 index 022f582c..00000000 --- a/src/main/java/ecdar/controllers/MessageTabPaneController.java +++ /dev/null @@ -1,272 +0,0 @@ -package ecdar.controllers; - -import com.jfoenix.controls.JFXRippler; -import com.jfoenix.controls.JFXTabPane; -import ecdar.Ecdar; -import ecdar.abstractions.Component; -import ecdar.code_analysis.CodeAnalysis; -import ecdar.presentations.MessageCollectionPresentation; -import ecdar.presentations.MessagePresentation; -import javafx.animation.Interpolator; -import javafx.animation.Transition; -import javafx.application.Platform; -import javafx.beans.property.Property; -import javafx.beans.property.SimpleBooleanProperty; -import javafx.collections.ListChangeListener; -import javafx.fxml.FXML; -import javafx.fxml.Initializable; -import javafx.scene.control.ScrollPane; -import javafx.scene.control.Tab; -import javafx.scene.input.MouseEvent; -import javafx.scene.layout.StackPane; -import javafx.scene.layout.VBox; -import javafx.scene.shape.Rectangle; -import javafx.util.Duration; -import org.kordamp.ikonli.javafx.FontIcon; - -import java.net.URL; -import java.util.HashMap; -import java.util.Map; -import java.util.ResourceBundle; -import java.util.function.Consumer; - -public class MessageTabPaneController implements Initializable { - public StackPane root; - public JFXTabPane tabPane; - public Tab errorsTab; - public Tab warningsTab; - public Rectangle tabPaneResizeElement; - public StackPane tabPaneContainer; - public JFXRippler collapseMessages; - public FontIcon collapseMessagesIcon; - public ScrollPane errorsScrollPane; - public VBox errorsList; - public ScrollPane warningsScrollPane; - public VBox warningsList; - public Tab backendErrorsTab; - public ScrollPane backendErrorsScrollPane; - public VBox backendErrorsList; - - private double tabPanePreviousY = 0; - private double expandHeight = 300; - private double collapseHeight = 35; - private final Property isOpen = new SimpleBooleanProperty(false); - private Runnable openCloseExternalAction; - public boolean shouldISkipOpeningTheMessagesContainer = true; - - private final Transition expandMessagesContainer = new Transition() { - { - setInterpolator(Interpolator.SPLINE(0.645, 0.045, 0.355, 1)); - setCycleDuration(Duration.millis(200)); - } - - @Override - protected void interpolate(final double frac) { - tabPaneContainer.setMaxHeight(collapseHeight + frac * (expandHeight - collapseHeight)); - } - }; - private Transition collapseMessagesContainer; - - @Override - public void initialize(final URL location, final ResourceBundle resources) { - Platform.runLater(() -> { - collapseMessagesContainer = new Transition() { - { - setInterpolator(Interpolator.SPLINE(0.645, 0.045, 0.355, 1)); - setCycleDuration(Duration.millis(200)); - } - - @Override - protected void interpolate(final double frac) { - tabPaneContainer.setMaxHeight(((tabPaneContainer.getMaxHeight() - collapseHeight) * (1 - frac)) + collapseHeight); - } - }; - - initializeTabPane(); - initializeMessages(); - - collapseMessagesContainer.play(); - }); - } - - private void initializeTabPane() { - tabPane.getSelectionModel().selectedIndexProperty().addListener((obs, oldSelected, newSelected) -> { - if (newSelected.intValue() < 0 || isOpen.getValue()) return; - - if (shouldISkipOpeningTheMessagesContainer) { - tabPane.getSelectionModel().clearSelection(); - shouldISkipOpeningTheMessagesContainer = false; - } else { - expandMessagesIfNotExpanded(); - } - }); - - tabPane.getSelectionModel().clearSelection(); - isOpen.addListener((observable, oldValue, newValue) -> { - if (newValue) { - collapseMessagesIcon.setIconLiteral("gmi-close"); - } else { - collapseMessagesIcon.setIconLiteral("gmi-expand-less"); - } - - openCloseExternalAction.run(); - }); - - isOpen.setValue(false); - } - - private void initializeMessages() { - final Map componentMessageCollectionPresentationMapForErrors = new HashMap<>(); - final Map componentMessageCollectionPresentationMapForWarnings = new HashMap<>(); - - final Consumer addComponent = (component) -> { - final MessageCollectionPresentation messageCollectionPresentationErrors = new MessageCollectionPresentation(component, CodeAnalysis.getErrors(component)); - componentMessageCollectionPresentationMapForErrors.put(component, messageCollectionPresentationErrors); - errorsList.getChildren().add(messageCollectionPresentationErrors); - - final Runnable addIfErrors = () -> { - if (CodeAnalysis.getErrors(component).size() == 0) { - errorsList.getChildren().remove(messageCollectionPresentationErrors); - } else if (!errorsList.getChildren().contains(messageCollectionPresentationErrors)) { - errorsList.getChildren().add(messageCollectionPresentationErrors); - } - }; - - addIfErrors.run(); - CodeAnalysis.getErrors(component).addListener((ListChangeListener) c -> { - while (c.next()) { - addIfErrors.run(); - } - }); - - final MessageCollectionPresentation messageCollectionPresentationWarnings = new MessageCollectionPresentation(component, CodeAnalysis.getWarnings(component)); - componentMessageCollectionPresentationMapForWarnings.put(component, messageCollectionPresentationWarnings); - warningsList.getChildren().add(messageCollectionPresentationWarnings); - - final Runnable addIfWarnings = () -> { - if (CodeAnalysis.getWarnings(component).size() == 0) { - warningsList.getChildren().remove(messageCollectionPresentationWarnings); - } else if (!warningsList.getChildren().contains(messageCollectionPresentationWarnings)) { - warningsList.getChildren().add(messageCollectionPresentationWarnings); - } - }; - - addIfWarnings.run(); - CodeAnalysis.getWarnings(component).addListener((ListChangeListener) c -> { - while (c.next()) { - addIfWarnings.run(); - } - }); - }; - - // Add error that is project wide but not a backend error - addComponent.accept(null); - - Ecdar.getProject().getComponents().forEach(addComponent); - Ecdar.getProject().getComponents().addListener((ListChangeListener) c -> { - while (c.next()) { - c.getAddedSubList().forEach(addComponent::accept); - - c.getRemoved().forEach(component -> { - errorsList.getChildren().remove(componentMessageCollectionPresentationMapForErrors.get(component)); - componentMessageCollectionPresentationMapForErrors.remove(component); - - warningsList.getChildren().remove(componentMessageCollectionPresentationMapForWarnings.get(component)); - componentMessageCollectionPresentationMapForWarnings.remove(component); - }); - } - }); - - final Map messageMessagePresentationHashMap = new HashMap<>(); - - CodeAnalysis.getBackendErrors().addListener((ListChangeListener) c -> { - while (c.next()) { - c.getAddedSubList().forEach(addedMessage -> { - final MessagePresentation messagePresentation = new MessagePresentation(addedMessage); - backendErrorsList.getChildren().add(messagePresentation); - messageMessagePresentationHashMap.put(addedMessage, messagePresentation); - }); - - c.getRemoved().forEach(removedMessage -> { - backendErrorsList.getChildren().remove(messageMessagePresentationHashMap.get(removedMessage)); - messageMessagePresentationHashMap.remove(removedMessage); - }); - } - }); - } - - public void expandMessagesIfNotExpanded() { - if (!isOpen.getValue()) { - expandMessagesContainer.play(); - isOpen.setValue(true); - } - } - - public void collapseMessagesIfNotCollapsed() { - if (isOpen.getValue()) { - expandHeight = tabPaneContainer.getHeight(); - collapseMessagesContainer.play(); - isOpen.setValue(false); - } - } - - public void setRunnableForOpeningAndClosingMessageTabPane(Runnable runnable) { - openCloseExternalAction = runnable; - } - - public boolean isOpen() { - return isOpen.getValue(); - } - - /** - * Update the scale of root and all children - * - * @param scale the new scale of the tab pane - */ - public void updateScale(double scale) { - final double heightScaled = 35 * scale; - - collapseHeight = heightScaled; - tabPane.setTabMinHeight(heightScaled); - tabPane.setTabMaxHeight(heightScaled); - ((StackPane) collapseMessagesIcon.getParent()).setMinHeight(heightScaled); - ((StackPane) collapseMessagesIcon.getParent()).setMaxHeight(heightScaled); - ((StackPane) collapseMessagesIcon.getParent()).setMinWidth(heightScaled); - ((StackPane) collapseMessagesIcon.getParent()).setMaxWidth(heightScaled); - - if (!isOpen()) { - collapseMessagesContainer.play(); - } - } - - @FXML - public void collapseMessagesClicked() { - if (isOpen()) { - expandHeight = tabPaneContainer.getHeight(); - collapseMessagesContainer.play(); - } else { - expandMessagesContainer.play(); - } - - isOpen.setValue(!isOpen.getValue()); - } - - @FXML - public void tabPaneResizeElementPressed(final MouseEvent event) { - this.tabPanePreviousY = event.getScreenY(); - } - - @FXML - public void tabPaneResizeElementDragged(final MouseEvent event) { - expandMessagesIfNotExpanded(); - - final double mouseY = event.getScreenY(); - double newHeight = tabPaneContainer.getMaxHeight() - (mouseY - tabPanePreviousY); - newHeight = Math.max(collapseHeight, newHeight); - - tabPaneContainer.setMaxHeight(newHeight); - - openCloseExternalAction.run(); - this.tabPanePreviousY = mouseY; - } -} diff --git a/src/main/java/ecdar/controllers/SimulatorController.java b/src/main/java/ecdar/controllers/SimulatorController.java index 63d82474..cacdc467 100644 --- a/src/main/java/ecdar/controllers/SimulatorController.java +++ b/src/main/java/ecdar/controllers/SimulatorController.java @@ -5,7 +5,6 @@ import ecdar.backend.SimulationHandler; import ecdar.presentations.SimulatorOverviewPresentation; import ecdar.simulation.SimulationState; -import ecdar.simulation.Transition; import ecdar.utility.colors.Color; import javafx.beans.property.DoubleProperty; import javafx.beans.property.ObjectProperty; @@ -16,7 +15,6 @@ import java.net.URL; import java.util.ArrayList; -import java.util.HashSet; import java.util.List; import java.util.ResourceBundle; @@ -29,7 +27,6 @@ public class SimulatorController implements Initializable { private boolean firstTimeInSimulator; private final static DoubleProperty width = new SimpleDoubleProperty(), height = new SimpleDoubleProperty(); - private static ObjectProperty selectedTransition = new SimpleObjectProperty<>(); private static ObjectProperty selectedState = new SimpleObjectProperty<>(); @Override @@ -49,25 +46,32 @@ public void willShow() { final SimulationHandler sm = Ecdar.getSimulationHandler(); boolean shouldSimulationBeReset = true; - if (sm.getCurrentState() == null) sm.initialStep(); // ToDo NIELS: Find better solution - //Have the user left a trace + + // If the user left a trace, continue from that trace if (sm.traceLog.size() >= 2) { shouldSimulationBeReset = false; } - if (!firstTimeInSimulator && !new HashSet<>(overviewPresentation.getController().getComponentObservableList()) - .containsAll(findComponentsInCurrentSimulation(SimulationInitializationDialogController.ListOfComponents))) { + // If the composition is not the same as previous simulation, reset the simulation + if (!(overviewPresentation.getController().getComponentObservableList().hashCode() == + findComponentsInCurrentSimulation(SimulationInitializationDialogController.ListOfComponents).hashCode())) { shouldSimulationBeReset = true; } - - if (shouldSimulationBeReset || firstTimeInSimulator) { - + + if (shouldSimulationBeReset || firstTimeInSimulator || sm.currentState.get() == null) { resetSimulation(); - sm.resetToInitialLocation(); + sm.initialStep(); } + overviewPresentation.getController().addProcessesToGroup(); - overviewPresentation.getController().highlightProcessState(sm.getCurrentState()); + + // If the simulation continues, highligt the current state and available edges + if (sm.currentState.get() != null && !shouldSimulationBeReset) { + overviewPresentation.getController().highlightProcessState(sm.currentState.get()); + overviewPresentation.getController().highlightAvailableEdges(sm.currentState.get()); + } + } /** @@ -80,26 +84,8 @@ private void resetSimulation() { overviewPresentation.getController().getComponentObservableList().clear(); overviewPresentation.getController().getComponentObservableList().addAll(listOfComponentsForSimulation); firstTimeInSimulator = false; - - //Method that colors all initial states. - initialstatelighter(listOfComponentsForSimulation); - } - - private void initialstatelighter(List listofComponents){ - for(Component comp: listofComponents) - { - Location initiallocation = comp.getInitialLocation(); - initiallocation.setColor(Color.ORANGE); - List tempedge = comp.getRelatedEdges(initiallocation); - /* for(DisplayableEdge e: tempedge) - { - if(e.getSourceLocation() == initiallocation) - { - e.setIsHighlighted(true); - } - }*/ - } } + /** * Finds the components that are used in the current simulation by looking at the components found in * Ecdar.getProject.getComponents() and compares them to the components found in the queryComponents list @@ -108,13 +94,11 @@ private void initialstatelighter(List listofComponents){ */ private List findComponentsInCurrentSimulation(List queryComponents) { //Show components from the system - List components = new ArrayList<>(); - - components = Ecdar.getProject().getComponents(); + List components = Ecdar.getProject().getComponents(); //Matches query components against with existing components and adds them to simulation List SelectedComponents = new ArrayList<>(); - for(Component comp:components) { + for(Component comp : components) { for(String componentInQuery : queryComponents) { if((comp.getName().equals(componentInQuery))) { Component temp = new Component(comp.serialize()); @@ -137,10 +121,6 @@ public void resetCurrentSimulation() { public void willHide() { overviewPresentation.getController().removeProcessesFromGroup(); - overviewPresentation.getController().getComponentObservableList().forEach(component -> { - // Previously reset coordinates of component box - }); - overviewPresentation.getController().unhighlightProcesses(); } public static DoubleProperty getWidthProperty() { @@ -151,19 +131,6 @@ public static DoubleProperty getHeightProperty() { return height; } - - public static ObjectProperty getSelectedTransitionProperty() { - return selectedTransition; - } - - public static void setSelectedTransition(Transition selectedTransition) { - SimulatorController.selectedTransition.set(selectedTransition); - } - - public static ObjectProperty getSelectedStateProperty() { - return selectedState; - } - public static void setSelectedState(SimulationState selectedState) { SimulatorController.selectedState.set(selectedState); } diff --git a/src/main/java/ecdar/controllers/SimulatorOverviewController.java b/src/main/java/ecdar/controllers/SimulatorOverviewController.java index 90386d82..b4815785 100644 --- a/src/main/java/ecdar/controllers/SimulatorOverviewController.java +++ b/src/main/java/ecdar/controllers/SimulatorOverviewController.java @@ -72,8 +72,8 @@ public void initialize(final URL location, final ResourceBundle resources) { initializeWindowResizing(); initializeZoom(); - initializeHighlighting(); initializeSimulationVariables(); + initializeHighlighting(); // Add the processes and group to the view addProcessesToGroup(); scrollPane.setContent(groupContainer); @@ -105,7 +105,7 @@ private void initializeProcessContainer() { } } // Highlight the current state when the processes change - highlightProcessState(Ecdar.getSimulationHandler().getCurrentState()); // ToDo NIELS: Throws NullPointerException inside method due to currentState + highlightProcessState(Ecdar.getSimulationHandler().currentState.get()); // ToDo NIELS: Throws NullPointerException inside method due to currentState processContainer.getChildren().addAll(processes.values()); processPresentations.putAll(processes); }); @@ -297,32 +297,21 @@ private void handleWidthOnScale(final Number oldValue, final Number newValue) { } } - /** + /** * Initializer method to setup listeners that handle highlighting when selected/current state/transition changes */ private void initializeHighlighting() { - SimulatorController.getSelectedTransitionProperty().addListener((observable, oldTransition, newTransition) -> { + Ecdar.getSimulationHandler().selectedEdge.addListener((observable, oldEdge, newEdge) -> { unhighlightProcesses(); - - // If the new transition is not null, we want to highlight the locations and edges in the new value - // otherwise we highlight the current state - if (newTransition != null) { - highlightProcessTransition(newTransition); - } else { - highlightProcessState(Ecdar.getSimulationHandler().getCurrentState()); - } }); - SimulatorController.getSelectedStateProperty().addListener((observable, oldState, newState) -> { - unhighlightProcesses(); - - // If the new state is not null, we want to highlight the locations in the new value - // otherwise we highlight the current state - if (newState != null) { - highlightProcessState(newState); - } else { - highlightProcessState(Ecdar.getSimulationHandler().getCurrentState()); + Ecdar.getSimulationHandler().currentState.addListener((observable, oldState, newState) -> { + if (newState == null) { + return; } + unhighlightProcesses(); + highlightProcessState(newState); + highlightAvailableEdges(newState); }); } @@ -350,6 +339,7 @@ public void highlightProcessTransition(final Transition transition) { processesToHide.forEach(ProcessPresentation::showInactive); } + /** * Unhighlights all processes */ @@ -370,17 +360,34 @@ public void highlightProcessState(final SimulationState state) { for (int i = 0; i < state.getLocations().size(); i++) { final Pair loc = state.getLocations().get(i); - for (final ProcessPresentation presentation : processPresentations.values()) { - final String processName = presentation.getController().getComponent().getName(); - - if (processName.equals(loc.getKey())) { - presentation.getController().highlightLocation(loc.getValue()); - } - } + processPresentations.values().stream() + .filter(p -> p.getController().getComponent().getName().equals(loc.getKey())) + .forEach(p -> p.getController().highlightLocation(loc.getValue())); } } public ObservableList getComponentObservableList() { return componentArrayList; } + + public void highlightAvailableEdges(SimulationState state) { + // unhighlight all edges + for (Pair edge : state.getEdges()) { + processPresentations.values().stream() + .forEach(p -> p.getController().getComponent().getEdges().stream() + .forEach(e -> e.setIsHighlighted(false))); + } + + // highlight available edges in the given state + for (Pair edge : state.getEdges()) { + processPresentations.values().stream() + .forEach(p -> p.getController().getComponent().getEdges().stream() + .forEach(e -> { + if (e.getId().equals(edge.getValue())) { + e.setIsHighlighted(true); + } + })); + } + } + } diff --git a/src/main/java/ecdar/controllers/TracePaneElementController.java b/src/main/java/ecdar/controllers/TracePaneElementController.java index 5fe116bd..6898dfad 100755 --- a/src/main/java/ecdar/controllers/TracePaneElementController.java +++ b/src/main/java/ecdar/controllers/TracePaneElementController.java @@ -3,8 +3,8 @@ import com.jfoenix.controls.JFXRippler; import ecdar.Ecdar; import ecdar.abstractions.Location; -import ecdar.simulation.SimulationState; import ecdar.backend.SimulationHandler; +import ecdar.simulation.SimulationState; import ecdar.presentations.TransitionPresentation; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleIntegerProperty; @@ -46,7 +46,7 @@ public void initialize(URL location, ResourceBundle resources) { Ecdar.getSimulationHandler().getTraceLog().addListener((ListChangeListener) c -> { while (c.next()) { for (final SimulationState state : c.getAddedSubList()) { - insertTraceState(state, true); + if (state != null) insertTraceState(state, true); } for (final SimulationState state : c.getRemoved()) { @@ -116,7 +116,7 @@ private void insertTraceState(final SimulationState state, final boolean shouldA event.consume(); final SimulationHandler simHandler = Ecdar.getSimulationHandler(); if (simHandler == null) return; - Ecdar.getSimulationHandler().selectTransitionFromLog(state); + Ecdar.getSimulationHandler().selectStateFromLog(state); }); EventHandler mouseEntered = transitionPresentation.getOnMouseEntered(); @@ -131,12 +131,11 @@ private void insertTraceState(final SimulationState state, final boolean shouldA mouseExited.handle(event); }); - String title = traceString(state); transitionPresentation.getController().setTitle(title); - // Only insert the presentation into the view if the trace is expanded - if (isTraceExpanded.get()) { + // Only insert the presentation into the view if the trace is expanded & state is not null + if (isTraceExpanded.get() && state != null) { traceList.getChildren().add(transitionPresentation); if (shouldAnimate) { transitionPresentation.playFadeAnimation(); @@ -151,16 +150,13 @@ private void insertTraceState(final SimulationState state, final boolean shouldA * @return A string representing the state */ private String traceString(SimulationState state) { - if (state == null) { - return "Initial state"; - } StringBuilder title = new StringBuilder("("); int length = state.getLocations().size(); for (int i = 0; i < length; i++) { Location loc = Ecdar.getProject() .findComponent(state.getLocations().get(i).getKey()) .findLocation(state.getLocations().get(i).getValue()); - String locationName = loc.getNickname(); + String locationName = loc.getId(); if (i == length - 1) { title.append(locationName); } else { diff --git a/src/main/java/ecdar/controllers/TransitionPaneElementController.java b/src/main/java/ecdar/controllers/TransitionPaneElementController.java index 0233e335..30b355ff 100755 --- a/src/main/java/ecdar/controllers/TransitionPaneElementController.java +++ b/src/main/java/ecdar/controllers/TransitionPaneElementController.java @@ -5,11 +5,9 @@ import ecdar.Ecdar; import ecdar.abstractions.Edge; import ecdar.simulation.Transition; -import ecdar.backend.SimulationHandler; import ecdar.presentations.TransitionPresentation; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleObjectProperty; -import javafx.collections.ListChangeListener; import javafx.event.EventHandler; import javafx.fxml.FXML; import javafx.fxml.Initializable; @@ -45,17 +43,6 @@ public class TransitionPaneElementController implements Initializable { @Override public void initialize(URL location, ResourceBundle resources) { - Ecdar.getSimulationHandler().availableTransitions.addListener((ListChangeListener) c -> { - while (c.next()) { - for (Transition trans : c.getAddedSubList()) insertTransition(trans); - - for (final Transition trans: c.getRemoved()) { - transitionList.getChildren().remove(transitionPresentationMap.get(trans)); - transitionPresentationMap.remove(trans); - } - } - }); - initializeTransitionExpand(); initializeDelayChooser(); } @@ -137,27 +124,16 @@ private void insertTransition(Transition transition) { // Add the event to existing mouseEntered events // e.g. TransitionPresentation already has mouseEntered functionality and we want to keep it EventHandler mouseEntered = transitionPresentation.getOnMouseEntered(); - transitionPresentation.setOnMouseEntered(event -> { - SimulatorController.setSelectedTransition(transitionPresentation.getController().getTransition()); - mouseEntered.handle(event); - }); + // transitionPresentation.setOnMouseEntered(event -> { + // SimulatorController.setSelectedTransition(transitionPresentation.getController().getTransition()); + // mouseEntered.handle(event); + // }); EventHandler mouseExited = transitionPresentation.getOnMouseExited(); transitionPresentation.setOnMouseExited(event -> { - SimulatorController.setSelectedTransition(null); mouseExited.handle(event); }); - transitionPresentation.setOnMouseClicked(event -> { - event.consume(); - - // Performs the next step of the simulation when clicking on a transition - SimulationHandler simHandler = Ecdar.getSimulationHandler(); - if (simHandler != null) { - simHandler.nextStep(transitionPresentation.getController().getTransition(), this.delay.get()); - } - }); - transitionPresentationMap.put(transition, transitionPresentation); // Only insert the presentation into the view if the transitions are expanded @@ -195,13 +171,11 @@ private void expandTransitions() { } /** - * Gets the initial step from the SimulationHandler. - * Used by the refresh button. + * Restart simulation to the initial step. */ @FXML - private void refreshTransitions() { - SimulatorController.setSelectedTransition(null); -// MainController.openReloadSimulationDialog(); // ToDo: Implement + private void restartSimulation() { + Ecdar.getSimulationHandler().resetToInitialLocation(); } /** diff --git a/src/main/java/ecdar/presentations/LocationPresentation.java b/src/main/java/ecdar/presentations/LocationPresentation.java index 0b25e090..57def2ab 100644 --- a/src/main/java/ecdar/presentations/LocationPresentation.java +++ b/src/main/java/ecdar/presentations/LocationPresentation.java @@ -206,10 +206,15 @@ private void initializeTags() { } // Bind the model to the layout - loc.nicknameXProperty().bindBidirectional(controller.nicknameTag.translateXProperty()); - loc.nicknameYProperty().bindBidirectional(controller.nicknameTag.translateYProperty()); - loc.invariantXProperty().bindBidirectional(controller.invariantTag.translateXProperty()); - loc.invariantYProperty().bindBidirectional(controller.invariantTag.translateYProperty()); + // Check is needed because the property cannot be bound twice + // which happens when switching from the simulator to the editor + if (!loc.nicknameXProperty().isBound() && !loc.nicknameYProperty().isBound() && + !loc.invariantXProperty().isBound() && !loc.invariantYProperty().isBound()) { + loc.nicknameXProperty().bindBidirectional(controller.nicknameTag.translateXProperty()); + loc.nicknameYProperty().bindBidirectional(controller.nicknameTag.translateYProperty()); + loc.invariantXProperty().bindBidirectional(controller.invariantTag.translateXProperty()); + loc.invariantYProperty().bindBidirectional(controller.invariantTag.translateYProperty()); + } final Consumer updateTags = location -> { // Update the color diff --git a/src/main/java/ecdar/presentations/MessageTabPanePresentation.java b/src/main/java/ecdar/presentations/MessageTabPanePresentation.java deleted file mode 100644 index 88c8dda8..00000000 --- a/src/main/java/ecdar/presentations/MessageTabPanePresentation.java +++ /dev/null @@ -1,115 +0,0 @@ -package ecdar.presentations; - -import ecdar.code_analysis.CodeAnalysis; -import ecdar.controllers.MessageTabPaneController; -import javafx.beans.InvalidationListener; -import javafx.beans.Observable; -import javafx.scene.Cursor; -import javafx.scene.layout.StackPane; - -public class MessageTabPanePresentation extends StackPane { - private final MessageTabPaneController controller; - - public MessageTabPanePresentation() { - controller = new EcdarFXMLLoader().loadAndGetController("MessageTabPanePresentation.fxml", this); - initializeMessageContainer(); - } - - public MessageTabPaneController getController() { - return controller; - } - - private void initializeMessageContainer() { - // The element of which you drag to resize should be equal to the width of the window (main stage) - controller.tabPaneResizeElement.sceneProperty().addListener((observableScene, oldScene, newScene) -> { - if (oldScene == null && newScene != null) { - // scene is set for the first time. Now its the time to listen stage changes. - newScene.windowProperty().addListener((observableWindow, oldWindow, newWindow) -> { - if (oldWindow == null && newWindow != null) { - newWindow.widthProperty().addListener((observableWidth, oldWidth, newWidth) -> { - controller.tabPaneResizeElement.setWidth(newWidth.doubleValue() - 30); - }); - } - }); - } - }); - - // Resize cursor - controller.tabPaneResizeElement.setCursor(Cursor.N_RESIZE); - - // Remove the background of the scroll panes - controller.errorsScrollPane.setStyle("-fx-background-color: transparent;"); - controller.warningsScrollPane.setStyle("-fx-background-color: transparent;"); - - final Runnable collapseIfNoErrorsOrWarnings = () -> { - new Thread(() -> { - // Wait for a second to check if new warnings or errors occur - try { - Thread.sleep(1000); - } catch (InterruptedException e) { - e.printStackTrace(); - } - - // Check if any warnings or errors occurred - if (CodeAnalysis.getBackendErrors().size() + CodeAnalysis.getErrors().size() + CodeAnalysis.getWarnings().size() == 0) { - controller.collapseMessagesIfNotCollapsed(); - } - }).start(); - }; - - // Update the tab-text and expand/collapse the view - CodeAnalysis.getBackendErrors().addListener(new InvalidationListener() { - @Override - public void invalidated(final Observable observable) { - final int errors = CodeAnalysis.getBackendErrors().size(); - if (errors == 0) { - controller.backendErrorsTab.setText("Backend Errors"); - } else { - controller.backendErrorsTab.setText("Backend Errors (" + errors + ")"); - controller.expandMessagesIfNotExpanded(); - controller.tabPane.getSelectionModel().select(controller.backendErrorsTab); - } - - collapseIfNoErrorsOrWarnings.run(); - } - }); - - // Update the tab-text and expand/collapse the view - CodeAnalysis.getErrors().addListener(new InvalidationListener() { - @Override - public void invalidated(final Observable observable) { - final int errors = CodeAnalysis.getErrors().size(); - if (errors == 0) { - controller.errorsTab.setText("Errors"); - } else { - controller.errorsTab.setText("Errors (" + errors + ")"); - controller.expandMessagesIfNotExpanded(); - controller.tabPane.getSelectionModel().select(controller.errorsTab); - } - - collapseIfNoErrorsOrWarnings.run(); - } - }); - - - // Update the tab-text and expand/collapse the view - CodeAnalysis.getWarnings().addListener(new InvalidationListener() { - @Override - public void invalidated(final Observable observable) { - final int warnings = CodeAnalysis.getWarnings().size(); - if (warnings == 0) { - controller.warningsTab.setText("Warnings"); - } else { - controller.warningsTab.setText("Warnings (" + warnings + ")"); - //We must select the warnings tab but we don't want the messages areas to open - controller.shouldISkipOpeningTheMessagesContainer = true; - controller.tabPane.getSelectionModel().select(controller.warningsTab); - } - - collapseIfNoErrorsOrWarnings.run(); - } - }); - - controller.collapseMessagesIcon.getStyleClass().add("icon-size-medium"); - } -} \ No newline at end of file diff --git a/src/main/java/ecdar/presentations/NailPresentation.java b/src/main/java/ecdar/presentations/NailPresentation.java index 74167fba..41913c2a 100644 --- a/src/main/java/ecdar/presentations/NailPresentation.java +++ b/src/main/java/ecdar/presentations/NailPresentation.java @@ -97,9 +97,14 @@ private void initializePropertyTag() { propertyTag.setTranslateX(controller.getNail().getPropertyX()); propertyTag.setTranslateY(controller.getNail().getPropertyY()); } - controller.getNail().propertyXProperty().bindBidirectional(propertyTag.translateXProperty()); - controller.getNail().propertyYProperty().bindBidirectional(propertyTag.translateYProperty()); + // Check is needed because the property cannot be bound twice + // which happens when switching from the simulator to the editor + if (!controller.getNail().propertyXProperty().isBound() && !controller.getNail().propertyYProperty().isBound()) { + controller.getNail().propertyXProperty().bindBidirectional(propertyTag.translateXProperty()); + controller.getNail().propertyYProperty().bindBidirectional(propertyTag.translateYProperty()); + } + Label propertyLabel = controller.propertyLabel; if(propertyType.equals(Edge.PropertyType.SELECTION)) { diff --git a/src/main/java/ecdar/presentations/SimEdgePresentation.java b/src/main/java/ecdar/presentations/SimEdgePresentation.java index 8d74623b..540a7db0 100755 --- a/src/main/java/ecdar/presentations/SimEdgePresentation.java +++ b/src/main/java/ecdar/presentations/SimEdgePresentation.java @@ -1,11 +1,13 @@ package ecdar.presentations; +import ecdar.Ecdar; import ecdar.abstractions.Component; import ecdar.abstractions.Edge; import ecdar.controllers.SimEdgeController; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.scene.Group; +import javafx.util.Pair; /** * The presentation class for the edges shown in the {@link SimulatorOverviewPresentation} @@ -24,9 +26,25 @@ public SimEdgePresentation(final Edge edge, final Component component) { controller.setComponent(component); this.component.bind(controller.componentProperty()); + + // when hovering mouse the curser should change to hand + this.setOnMouseEntered(event -> { + if (Ecdar.getSimulationHandler().currentState.get().getEdges().contains(new Pair<>(component.getName(), edge.getId()))) + this.getScene().setCursor(javafx.scene.Cursor.HAND); + }); + this.setOnMouseExited(event -> this.getScene().setCursor(javafx.scene.Cursor.DEFAULT)); + + // when clicking the edge the edge should be selected and the simulation should take next step (if the edge is enabled) + this.setOnMouseClicked(event -> { + if (Ecdar.getSimulationHandler().currentState.get().getEdges().contains(new Pair<>(component.getName(), edge.getId()))) { + Ecdar.getSimulationHandler().selectedEdge.set(edge); + Ecdar.getSimulationHandler().nextStep(); + } + }); } public SimEdgeController getController() { return controller; } + } diff --git a/src/main/java/ecdar/simulation/SimulationState.java b/src/main/java/ecdar/simulation/SimulationState.java index 0a1c03f8..a6b4f5e3 100644 --- a/src/main/java/ecdar/simulation/SimulationState.java +++ b/src/main/java/ecdar/simulation/SimulationState.java @@ -1,51 +1,54 @@ package ecdar.simulation; import EcdarProtoBuf.ObjectProtos; +import EcdarProtoBuf.ObjectProtos.State; +import ecdar.Ecdar; import javafx.util.Pair; -import java.math.BigDecimal; import java.util.ArrayList; public class SimulationState { + // locations and edges are saved as key-value pair where key is component name and value = id private final ArrayList> locations; + private final ArrayList> edges; + private final State state; - public SimulationState(ObjectProtos.State protoBufState) { + public SimulationState(ObjectProtos.DecisionPoint decisionPoint) { locations = new ArrayList<>(); - for (ObjectProtos.Location location : protoBufState.getLocationTuple().getLocationsList()) { - locations.add(new Pair<>(location.getId(), location.getSpecificComponent().getComponentName())); + for (ObjectProtos.Location location : decisionPoint.getSource().getLocationTuple().getLocationsList()) { + locations.add(new Pair<>(location.getSpecificComponent().getComponentName(), location.getId())); } - } - public void setTime(BigDecimal value) { - // ToDo: Implement + edges = new ArrayList<>(); + if (decisionPoint.getEdgesList().isEmpty()) { + Ecdar.showToast("No available transitions."); + } + for (ObjectProtos.Edge edge : decisionPoint.getEdgesList()) { + edges.add(new Pair<>(getComponentName(edge.getId()), edge.getId())); + } + state = decisionPoint.getSource(); } - public Number getTime() { - // ToDo: Implement - return new Number() { - @Override - public int intValue() { - return 0; - } - - @Override - public long longValue() { - return 0; - } - - @Override - public float floatValue() { - return 0; - } + public ArrayList> getLocations() { + return locations; + } + public ArrayList> getEdges() { + return edges; + } - @Override - public double doubleValue() { - return 0; - } - }; + public State getState() { + return state; } - public ArrayList> getLocations() { - return locations; + private String getComponentName(String id) { + var components = Ecdar.getProject().getComponents(); + for (var component : components) { + for (var edge : component.getEdges()) { + if (edge.getId().equals(id)) { + return component.getName(); + } + } + } + throw new RuntimeException("Could not find component name for edge with id " + id); } } diff --git a/src/main/java/ecdar/simulation/SimulationStateSuccessor.java b/src/main/java/ecdar/simulation/SimulationStateSuccessor.java deleted file mode 100644 index bf6eb85d..00000000 --- a/src/main/java/ecdar/simulation/SimulationStateSuccessor.java +++ /dev/null @@ -1,15 +0,0 @@ -package ecdar.simulation; - -import java.util.ArrayList; - -public class SimulationStateSuccessor { - public ArrayList getTransitions() { - // ToDo: Implement - return new ArrayList<>(); - } - - public SimulationState getState() { - // ToDo: Implement - return null; - } -} diff --git a/src/main/resources/ecdar/presentations/EcdarPresentation.fxml b/src/main/resources/ecdar/presentations/EcdarPresentation.fxml index dc5c1a14..87a3843b 100644 --- a/src/main/resources/ecdar/presentations/EcdarPresentation.fxml +++ b/src/main/resources/ecdar/presentations/EcdarPresentation.fxml @@ -251,8 +251,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/main/resources/ecdar/presentations/SimEdgePresentation.fxml b/src/main/resources/ecdar/presentations/SimEdgePresentation.fxml index 1c72435f..b1224493 100755 --- a/src/main/resources/ecdar/presentations/SimEdgePresentation.fxml +++ b/src/main/resources/ecdar/presentations/SimEdgePresentation.fxml @@ -4,8 +4,7 @@ + fx:controller="ecdar.controllers.SimEdgeController"> diff --git a/src/main/resources/ecdar/presentations/TransitionPaneElementPresentation.fxml b/src/main/resources/ecdar/presentations/TransitionPaneElementPresentation.fxml index b9b58ff9..8047bcb3 100755 --- a/src/main/resources/ecdar/presentations/TransitionPaneElementPresentation.fxml +++ b/src/main/resources/ecdar/presentations/TransitionPaneElementPresentation.fxml @@ -30,7 +30,7 @@ - + From bc25107ec5b30a65bcfebb691ac837173a0f4782 Mon Sep 17 00:00:00 2001 From: seba6505 <45783179+seba6505@users.noreply.github.com> Date: Thu, 1 Dec 2022 11:33:01 +0100 Subject: [PATCH 118/158] somehow works --- src/main/java/ecdar/abstractions/Query.java | 10 ++++-- .../controllers/ComponentController.java | 32 ++++++++++++++++--- .../ecdar/presentations/SignatureArrow.java | 5 +++ .../java/ecdar/simulation/SimulationTest.java | 4 +-- 4 files changed, 43 insertions(+), 8 deletions(-) diff --git a/src/main/java/ecdar/abstractions/Query.java b/src/main/java/ecdar/abstractions/Query.java index da2cfa0a..d67b2079 100644 --- a/src/main/java/ecdar/abstractions/Query.java +++ b/src/main/java/ecdar/abstractions/Query.java @@ -34,6 +34,7 @@ public class Query implements Serializable { for (Component c : Ecdar.getProject().getComponents()) { c.removeFailingLocations(); c.removeFailingEdges(); + c.setIsFailing(false); } setQueryState(QueryState.SUCCESSFUL); } else { @@ -65,13 +66,17 @@ public class Query implements Serializable { }; //TODO add set alle compontens isfailing til fales private final BiConsumer> stateActionConsumer = (state, action) -> { + for (Component c : Ecdar.getProject().getComponents()) { c.removeFailingLocations(); c.removeFailingEdges(); + if(state.getLocationTuple().getLocationsList().isEmpty()){ + c.setIsFailing(true); + } } + for (ObjectProtos.Location location : state.getLocationTuple().getLocationsList()) { Component c = Ecdar.getProject().findComponent(location.getSpecificComponent().getComponentName()); - c.setIsFailing(true); if (c == null) { throw new NullPointerException("Could not find the specific component: " + location.getSpecificComponent().getComponentName()); @@ -79,7 +84,8 @@ public class Query implements Serializable { Location l = c.findLocation(location.getId()); if (l == null) { - throw new NullPointerException("Could not find location: " + location.getId()); + c.setIsFailing(true); + //throw new NullPointerException("Could not find location: " + location.getId()); } c.addFailingLocation(l.getId()); for (Edge edge : c.getEdges()) { diff --git a/src/main/java/ecdar/controllers/ComponentController.java b/src/main/java/ecdar/controllers/ComponentController.java index 3972b386..2365030a 100644 --- a/src/main/java/ecdar/controllers/ComponentController.java +++ b/src/main/java/ecdar/controllers/ComponentController.java @@ -6,6 +6,7 @@ import ecdar.code_analysis.CodeAnalysis; import ecdar.presentations.*; import ecdar.utility.UndoRedoStack; +import ecdar.utility.colors.Color; import ecdar.utility.helpers.*; import ecdar.utility.mouse.MouseTracker; import com.jfoenix.controls.JFXPopup; @@ -131,14 +132,18 @@ private void initializeSignature(final Component newComponent) { newComponent.getInputStrings().forEach((channel) -> insertSignatureArrow(channel, EdgeStatus.INPUT)); } + + /*** * Initialize the listeners, that listen for changes in the input and output edges of the presented component. * The view is updated whenever an insert (deletions are also a type of insert) is reported * @param newComponent The component that should be presented with its signature */ + + private void initializeSignatureListeners(final Component newComponent) { newComponent.getIsFailingProperty().addListener((observable, oldValue, newValue) -> { - if(newComponent.getIsFailing()) { + if (newComponent.getIsFailing()) { for (Node n : inputSignatureContainer.getChildren()) { if (n instanceof SignatureArrow) { for (String label : component.get().getInputStrings()) { // TODO Bwad bwad labuls gwo here <- UwU shall be complate faliure label that shall be punshid @@ -150,14 +155,32 @@ private void initializeSignatureListeners(final Component newComponent) { } for (Node n : outputSignatureContainer.getChildren()) { if (n instanceof SignatureArrow) { - for (String label : component.get().getInputStrings()) { // TODO insert list of failing signature arrows + for (String label : component.get().getOutputStrings()) { // TODO insert list of failing signature arrows if (Objects.equals(((SignatureArrow) n).getSignatureArrowLabel(), label)) { ((SignatureArrow) n).recolorToRed(); } } } } - //newComponent.setIsFailing(false); + }else { + for (Node n : inputSignatureContainer.getChildren()) { + if (n instanceof SignatureArrow) { + for (String label : component.get().getInputStrings()) { // TODO Bwad bwad labuls gwo here <- UwU shall be complate faliure label that shall be punshid + if (Objects.equals(((SignatureArrow) n).getSignatureArrowLabel(), label)) { + ((SignatureArrow) n).recolorToGray(); + } + } + } + } + for (Node n : outputSignatureContainer.getChildren()) { + if (n instanceof SignatureArrow) { + for (String label : component.get().getOutputStrings()) { // TODO insert list of failing signature arrows + if (Objects.equals(((SignatureArrow) n).getSignatureArrowLabel(), label)) { + ((SignatureArrow) n).recolorToGray(); + } + } + } + } } }); newComponent.getOutputStrings().addListener((ListChangeListener) c -> { @@ -191,7 +214,7 @@ private void insertSignatureArrow(final String channel, final EdgeStatus status) } } - + /*** * Updates the component's height to match the input/output signature containers * if the component is smaller than either of them @@ -825,6 +848,7 @@ public void toggleDeclaration(final MouseEvent mouseEvent) { final Transition rippleEffect = new Transition() { private final double maxRadius = Math.sqrt(Math.pow(getComponent().getBox().getWidth(), 2) + Math.pow(getComponent().getBox().getHeight(), 2)); + { setCycleDuration(Duration.millis(500)); } diff --git a/src/main/java/ecdar/presentations/SignatureArrow.java b/src/main/java/ecdar/presentations/SignatureArrow.java index aa8898ec..8fc20c05 100644 --- a/src/main/java/ecdar/presentations/SignatureArrow.java +++ b/src/main/java/ecdar/presentations/SignatureArrow.java @@ -163,6 +163,11 @@ public void recolorToRed() { this.colorArrowComponents(color, intensity); } + public void recolorToGray(){ + Color color = Color.GREY; + Color.Intensity intensity = Color.Intensity.I800; + this.colorArrowComponents(color, intensity); + } /*** * Colors the components of the arrow * @param color The Color to color the components diff --git a/src/test/java/ecdar/simulation/SimulationTest.java b/src/test/java/ecdar/simulation/SimulationTest.java index c0584f3a..9de56415 100644 --- a/src/test/java/ecdar/simulation/SimulationTest.java +++ b/src/test/java/ecdar/simulation/SimulationTest.java @@ -27,7 +27,7 @@ public class SimulationTest { public GrpcCleanupRule grpcCleanup = new GrpcCleanupRule(); private final String serverName = InProcessServerBuilder.generateName(); - +/* @Test public void testGetInitialStateHighlightsTheInitialLocation() { final List components = generateComponentsWithInitialLocations(); @@ -91,7 +91,7 @@ public void takeSimulationStep(EcdarProtoBuf.QueryProtos.SimulationStepRequest r Assertions.fail("Exception encountered: " + e.getMessage()); } } - +*/ private List generateComponentsWithInitialLocations() { List comps = new ArrayList<>(); for (int i = 0; i < 2; i++) { From 14bf97ea7b6a8acef9e57380cbcb52550eff17ba Mon Sep 17 00:00:00 2001 From: APaludan Date: Thu, 1 Dec 2022 12:00:53 +0100 Subject: [PATCH 119/158] update handleQueryResponse to support new protos --- src/main/java/ecdar/backend/QueryHandler.java | 186 ++++++++---------- .../java/ecdar/backend/SimulationHandler.java | 6 +- src/main/proto | 2 +- .../java/ecdar/simulation/SimulationTest.java | 4 +- 4 files changed, 94 insertions(+), 104 deletions(-) diff --git a/src/main/java/ecdar/backend/QueryHandler.java b/src/main/java/ecdar/backend/QueryHandler.java index a2f6162e..9be10fbb 100644 --- a/src/main/java/ecdar/backend/QueryHandler.java +++ b/src/main/java/ecdar/backend/QueryHandler.java @@ -1,6 +1,8 @@ package ecdar.backend; import EcdarProtoBuf.QueryProtos; +import EcdarProtoBuf.QueryProtos.QueryRequest.Settings; + import com.google.gson.JsonObject; import com.google.gson.JsonParser; import ecdar.Ecdar; @@ -18,6 +20,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.NoSuchElementException; +import java.util.UUID; import java.util.concurrent.TimeUnit; public class QueryHandler { @@ -75,7 +78,8 @@ public void onCompleted() { // ToDo SW5: Not working with the updated gRPC Protos var queryBuilder = QueryProtos.QueryRequest.newBuilder() .setUserId(1) - .setQueryId(1) + .setQueryId(UUID.randomUUID().hashCode()) + .setSettings(Settings.newBuilder().setAll(true)) .setQuery(query.getType().getQueryName() + ": " + query.getQuery()) .setComponentsInfo(componentsInfoBuilder); @@ -101,117 +105,101 @@ private void handleQueryResponse(QueryProtos.QueryResponse value, Query query) { System.out.println(value); // If the query has been cancelled, ignore the result if (query.getQueryState() == QueryState.UNKNOWN) return; - switch (value.getResponseCase()) { - case QUERY_OK: - QueryProtos.QueryResponse.QueryOk queryOk = value.getQueryOk(); - switch (queryOk.getResultCase()) { - case REFINEMENT: - if (queryOk.getRefinement().getSuccess()) { - query.setQueryState(QueryState.SUCCESSFUL); - query.getSuccessConsumer().accept(true); - } else { - query.setQueryState(QueryState.ERROR); - query.getFailureConsumer().accept(new BackendException.QueryErrorException(queryOk.getRefinement().getReason())); - query.getSuccessConsumer().accept(false); - query.getStateActionConsumer().accept(value.getQueryOk().getRefinement().getState(), - value.getQueryOk().getRefinement().getAction()); - } - break; - - case CONSISTENCY: - if (queryOk.getConsistency().getSuccess()) { - query.setQueryState(QueryState.SUCCESSFUL); - query.getSuccessConsumer().accept(true); - } else { - query.setQueryState(QueryState.ERROR); - query.getFailureConsumer().accept(new BackendException.QueryErrorException(queryOk.getConsistency().getReason())); - query.getSuccessConsumer().accept(false); - query.getStateActionConsumer().accept(value.getQueryOk().getConsistency().getState(), - value.getQueryOk().getConsistency().getAction()); + switch (value.getResultCase()) { + case REFINEMENT: + if (value.getRefinement().getSuccess()) { + query.setQueryState(QueryState.SUCCESSFUL); + query.getSuccessConsumer().accept(true); + } else { + query.setQueryState(QueryState.ERROR); + query.getFailureConsumer().accept(new BackendException.QueryErrorException(value.getRefinement().getReason())); + query.getSuccessConsumer().accept(false); + query.getStateActionConsumer().accept(value.getRefinement().getState(), + value.getRefinement().getAction()); + } + break; - } - break; - - case DETERMINISM: - if (queryOk.getDeterminism().getSuccess()) { - query.setQueryState(QueryState.SUCCESSFUL); - query.getSuccessConsumer().accept(true); - } else { - query.setQueryState(QueryState.ERROR); - query.getFailureConsumer().accept(new BackendException.QueryErrorException(queryOk.getDeterminism().getReason())); - query.getSuccessConsumer().accept(false); - query.getStateActionConsumer().accept(value.getQueryOk().getDeterminism().getState(), - value.getQueryOk().getDeterminism().getAction()); + case CONSISTENCY: + if (value.getConsistency().getSuccess()) { + query.setQueryState(QueryState.SUCCESSFUL); + query.getSuccessConsumer().accept(true); + } else { + query.setQueryState(QueryState.ERROR); + query.getFailureConsumer().accept(new BackendException.QueryErrorException(value.getConsistency().getReason())); + query.getSuccessConsumer().accept(false); + query.getStateActionConsumer().accept(value.getConsistency().getState(), + value.getConsistency().getAction()); - } - break; - - case IMPLEMENTATION: - if (queryOk.getImplementation().getSuccess()) { - query.setQueryState(QueryState.SUCCESSFUL); - query.getSuccessConsumer().accept(true); - } else { - query.setQueryState(QueryState.ERROR); - query.getFailureConsumer().accept(new BackendException.QueryErrorException(queryOk.getImplementation().getReason())); - query.getSuccessConsumer().accept(false); - //ToDo: These errors are not implemented in the Reveaal backend. - query.getStateActionConsumer().accept(value.getQueryOk().getImplementation().getState(), - ""); - } - break; - - case REACHABILITY: - if (queryOk.getReachability().getSuccess()) { - query.setQueryState(QueryState.SUCCESSFUL); - if(value.getQueryOk().getReachability().getSuccess()){ - Ecdar.showToast("Reachability check was successful and the location can be reached."); - } - else if(!value.getQueryOk().getReachability().getSuccess()){ - Ecdar.showToast("Reachability check was successful but the location cannot be reached."); - } - query.getSuccessConsumer().accept(true); - } else { - query.setQueryState(QueryState.ERROR); - Ecdar.showToast("Reachability check was unsuccessful!"); - query.getFailureConsumer().accept(new BackendException.QueryErrorException(queryOk.getReachability().getReason())); - query.getSuccessConsumer().accept(false); - //ToDo: These errors are not implemented in the Reveaal backend. - query.getStateActionConsumer().accept(value.getQueryOk().getReachability().getState(), - ""); - } - break; + } + break; - case COMPONENT: + case DETERMINISM: + if (value.getDeterminism().getSuccess()) { query.setQueryState(QueryState.SUCCESSFUL); query.getSuccessConsumer().accept(true); - JsonObject returnedComponent = (JsonObject) JsonParser.parseString(queryOk.getComponent().getComponent().getJson()); - addGeneratedComponent(new Component(returnedComponent)); - break; + } else { + query.setQueryState(QueryState.ERROR); + query.getFailureConsumer().accept(new BackendException.QueryErrorException(value.getDeterminism().getReason())); + query.getSuccessConsumer().accept(false); + query.getStateActionConsumer().accept(value.getDeterminism().getState(), + value.getDeterminism().getAction()); - case ERROR: + } + break; + + case IMPLEMENTATION: + if (value.getImplementation().getSuccess()) { + query.setQueryState(QueryState.SUCCESSFUL); + query.getSuccessConsumer().accept(true); + } else { query.setQueryState(QueryState.ERROR); - query.getFailureConsumer().accept(new BackendException.QueryErrorException(queryOk.getError())); + query.getFailureConsumer().accept(new BackendException.QueryErrorException(value.getImplementation().getReason())); query.getSuccessConsumer().accept(false); - break; + //ToDo: These errors are not implemented in the Reveaal backend. + query.getStateActionConsumer().accept(value.getImplementation().getState(), + ""); + } + break; - case RESULT_NOT_SET: + case REACHABILITY: + if (value.getReachability().getSuccess()) { + query.setQueryState(QueryState.SUCCESSFUL); + if(value.getReachability().getSuccess()){ + Ecdar.showToast("Reachability check was successful and the location can be reached."); + } + else if(!value.getReachability().getSuccess()){ + Ecdar.showToast("Reachability check was successful but the location cannot be reached."); + } + query.getSuccessConsumer().accept(true); + } else { query.setQueryState(QueryState.ERROR); + Ecdar.showToast("Reachability check was unsuccessful!"); + query.getFailureConsumer().accept(new BackendException.QueryErrorException(value.getReachability().getReason())); query.getSuccessConsumer().accept(false); - break; - } - break; + //ToDo: These errors are not implemented in the Reveaal backend. + query.getStateActionConsumer().accept(value.getReachability().getState(), + ""); + } + break; - case USER_TOKEN_ERROR: - query.setQueryState(QueryState.ERROR); - query.getFailureConsumer().accept(new BackendException.QueryErrorException(value.getUserTokenError().getErrorMessage())); - query.getSuccessConsumer().accept(false); - break; + case COMPONENT: + query.setQueryState(QueryState.SUCCESSFUL); + query.getSuccessConsumer().accept(true); + JsonObject returnedComponent = (JsonObject) JsonParser.parseString(value.getComponent().getComponent().getJson()); + addGeneratedComponent(new Component(returnedComponent)); + break; - case RESPONSE_NOT_SET: - query.setQueryState(QueryState.ERROR); - query.getSuccessConsumer().accept(false); - break; - } + case ERROR: + query.setQueryState(QueryState.ERROR); + query.getFailureConsumer().accept(new BackendException.QueryErrorException(value.getError())); + query.getSuccessConsumer().accept(false); + break; + + case RESULT_NOT_SET: + query.setQueryState(QueryState.ERROR); + query.getSuccessConsumer().accept(false); + break; + } } private void handleQueryBackendError(Throwable t, Query query) { diff --git a/src/main/java/ecdar/backend/SimulationHandler.java b/src/main/java/ecdar/backend/SimulationHandler.java index b15e96b0..cfebcefb 100644 --- a/src/main/java/ecdar/backend/SimulationHandler.java +++ b/src/main/java/ecdar/backend/SimulationHandler.java @@ -81,7 +81,8 @@ public void initialStep() { StreamObserver responseObserver = new StreamObserver<>() { @Override public void onNext(QueryProtos.SimulationStepResponse value) { - currentState.set(new SimulationState(value.getNewDecisionPoint())); + // TODO this is temp solution to compile but should be fixed to handle ambiguity + currentState.set(new SimulationState(value.getNewDecisionPoints(0))); Platform.runLater(() -> traceLog.add(currentState.get())); } @@ -142,7 +143,8 @@ public void nextStep() { StreamObserver responseObserver = new StreamObserver<>() { @Override public void onNext(QueryProtos.SimulationStepResponse value) { - currentState.set(new SimulationState(value.getNewDecisionPoint())); + // TODO this is temp solution to compile but should be fixed to handle ambiguity + currentState.set(new SimulationState(value.getNewDecisionPoints(0))); Platform.runLater(() -> traceLog.add(currentState.get())); } diff --git a/src/main/proto b/src/main/proto index 22f7b52a..c1d41acd 160000 --- a/src/main/proto +++ b/src/main/proto @@ -1 +1 @@ -Subproject commit 22f7b52a65a7dcf56563eb7b5d353ce4c47dd231 +Subproject commit c1d41acd0cde29599f0225536bea565d0d9c9589 diff --git a/src/test/java/ecdar/simulation/SimulationTest.java b/src/test/java/ecdar/simulation/SimulationTest.java index c0584f3a..d8822b48 100644 --- a/src/test/java/ecdar/simulation/SimulationTest.java +++ b/src/test/java/ecdar/simulation/SimulationTest.java @@ -49,7 +49,7 @@ public void startSimulation(QueryProtos.SimulationStartRequest request, ObjectProtos.State state = ObjectProtos.State.newBuilder().setLocationTuple(locations).build(); DecisionPoint decisionPoint = DecisionPoint.newBuilder().setSource(state).build(); QueryProtos.SimulationStepResponse response = QueryProtos.SimulationStepResponse.newBuilder() - .setNewDecisionPoint(decisionPoint) + .addNewDecisionPoints(decisionPoint) .build(); responseObserver.onNext(response); responseObserver.onCompleted(); @@ -84,7 +84,7 @@ public void takeSimulationStep(EcdarProtoBuf.QueryProtos.SimulationStepRequest r .setId(comp.getInitialLocation().getId()).build(); } - var result = stub.startSimulation(request).getNewDecisionPoint().getSource().getLocationTuple().getLocationsList().toArray(); + var result = stub.startSimulation(request).getNewDecisionPoints(0).getSource().getLocationTuple().getLocationsList().toArray(); Assertions.assertArrayEquals(expectedResponse, result); } catch (IOException e) { From 6333fb720be4c24c08ae0c7085070d80c9d67ac3 Mon Sep 17 00:00:00 2001 From: Casper-NS Date: Tue, 29 Nov 2022 14:48:50 +0100 Subject: [PATCH 120/158] added a radio button connected sh: wq: command not found --- .../java/ecdar/abstractions/BackendInstance.java | 12 ++++++++++++ .../ecdar/controllers/BackendInstanceController.java | 4 ++++ .../controllers/BackendOptionsDialogController.java | 6 ++---- .../presentations/BackendInstancePresentation.fxml | 1 + 4 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/main/java/ecdar/abstractions/BackendInstance.java b/src/main/java/ecdar/abstractions/BackendInstance.java index 3b0eba76..b3170e61 100644 --- a/src/main/java/ecdar/abstractions/BackendInstance.java +++ b/src/main/java/ecdar/abstractions/BackendInstance.java @@ -12,10 +12,12 @@ public class BackendInstance implements Serializable { private static final String PORT_RANGE_START = "portRangeStart"; private static final String PORT_RANGE_END = "portRangeEnd"; private static final String LOCKED = "locked"; + private static final String IS_THREAD_SAFE = "isThreadSafe"; private String name; private boolean isLocal; private boolean isDefault; + private boolean isThreadSafe; private String backendLocation; private int portStart; private int portEnd; @@ -51,6 +53,14 @@ public void setDefault(boolean aDefault) { isDefault = aDefault; } + public boolean isThreadSafe() { + return isThreadSafe; + } + + public void setIsThreadSafe(boolean aThreadSafe) { + isThreadSafe = aThreadSafe; + } + public String getBackendLocation() { return backendLocation; } @@ -93,6 +103,7 @@ public JsonObject serialize() { result.addProperty(NAME, getName()); result.addProperty(IS_LOCAL, isLocal()); result.addProperty(IS_DEFAULT, isDefault()); + result.addProperty(IS_THREAD_SAFE, isThreadSafe()); result.addProperty(LOCATION, getBackendLocation()); result.addProperty(PORT_RANGE_START, getPortStart()); result.addProperty(PORT_RANGE_END, getPortEnd()); @@ -106,6 +117,7 @@ public void deserialize(final JsonObject json) { setName(json.getAsJsonPrimitive(NAME).getAsString()); setLocal(json.getAsJsonPrimitive(IS_LOCAL).getAsBoolean()); setDefault(json.getAsJsonPrimitive(IS_DEFAULT).getAsBoolean()); + setIsThreadSafe(json.getAsJsonPrimitive(IS_THREAD_SAFE).getAsBoolean()); setBackendLocation(json.getAsJsonPrimitive(LOCATION).getAsString()); setPortStart(json.getAsJsonPrimitive(PORT_RANGE_START).getAsInt()); setPortEnd(json.getAsJsonPrimitive(PORT_RANGE_END).getAsInt()); diff --git a/src/main/java/ecdar/controllers/BackendInstanceController.java b/src/main/java/ecdar/controllers/BackendInstanceController.java index b89caa84..e172b1a0 100644 --- a/src/main/java/ecdar/controllers/BackendInstanceController.java +++ b/src/main/java/ecdar/controllers/BackendInstanceController.java @@ -52,6 +52,7 @@ public class BackendInstanceController implements Initializable { public JFXTextField portRangeStart; public JFXTextField portRangeEnd; public RadioButton defaultBackendRadioButton; + public RadioButton threadSafeBackendRadioButton; @Override public void initialize(URL location, ResourceBundle resources) { @@ -62,6 +63,7 @@ public void initialize(URL location, ResourceBundle resources) { setHGrow(); colorIconAsDisabledBasedOnProperty(removeBackendIcon, defaultBackendRadioButton.selectedProperty()); + colorIconAsDisabledBasedOnProperty(removeBackendIcon, threadSafeBackendRadioButton.selectedProperty()); colorIconAsDisabledBasedOnProperty(pickPathToBackendIcon, backendInstance.getLockedProperty()); }); } @@ -93,6 +95,7 @@ public void setBackendInstance(BackendInstance instance) { this.backendName.setText(instance.getName()); this.isLocal.setSelected(instance.isLocal()); this.defaultBackendRadioButton.setSelected(instance.isDefault()); + this.threadSafeBackendRadioButton.setSelected(instance.isThreadSafe()); // Check if the path or the address should be used if (isLocal.isSelected()) { @@ -113,6 +116,7 @@ public BackendInstance updateBackendInstance() { backendInstance.setName(backendName.getText()); backendInstance.setLocal(isLocal.isSelected()); backendInstance.setDefault(defaultBackendRadioButton.isSelected()); + backendInstance.setIsThreadSafe(threadSafeBackendRadioButton.isSelected()); backendInstance.setBackendLocation(isLocal.isSelected() ? pathToBackend.getText() : address.getText()); backendInstance.setPortStart(Integer.parseInt(portRangeStart.getText())); backendInstance.setPortEnd(Integer.parseInt(portRangeEnd.getText())); diff --git a/src/main/java/ecdar/controllers/BackendOptionsDialogController.java b/src/main/java/ecdar/controllers/BackendOptionsDialogController.java index 4810e8cd..81faa812 100644 --- a/src/main/java/ecdar/controllers/BackendOptionsDialogController.java +++ b/src/main/java/ecdar/controllers/BackendOptionsDialogController.java @@ -185,6 +185,7 @@ private ArrayList getPackagedBackends() { reveaal.setPortStart(5032); reveaal.setPortEnd(5040); reveaal.lockInstance(); + reveaal.setIsThreadSafe(true); // Load correct Reveaal executable based on OS List potentialFilesForReveaal = new ArrayList<>(); @@ -203,6 +204,7 @@ private ArrayList getPackagedBackends() { jEcdar.setPortStart(5042); jEcdar.setPortEnd(5050); jEcdar.lockInstance(); + reveaal.setIsThreadSafe(false); // Load correct j-Ecdar executable based on OS List potentialFiledForJEcdar = new ArrayList<>(); @@ -252,10 +254,6 @@ private boolean setBackendPathIfFileExists(BackendInstance engine, List /** * Add the new backend instance presentation to the backend options dialog -<<<<<<< HEAD -======= - * ->>>>>>> main * @param newBackendInstancePresentation The presentation of the new backend instance */ private void addBackendInstancePresentationToList(BackendInstancePresentation newBackendInstancePresentation) { diff --git a/src/main/resources/ecdar/presentations/BackendInstancePresentation.fxml b/src/main/resources/ecdar/presentations/BackendInstancePresentation.fxml index 7e34da80..b0529be3 100644 --- a/src/main/resources/ecdar/presentations/BackendInstancePresentation.fxml +++ b/src/main/resources/ecdar/presentations/BackendInstancePresentation.fxml @@ -95,6 +95,7 @@ Default + Thread Safe \ No newline at end of file From 37a15cf27503ecd234b037cf3f26add66fa8571f Mon Sep 17 00:00:00 2001 From: Casper-NS Date: Tue, 29 Nov 2022 14:57:49 +0100 Subject: [PATCH 121/158] fixed a mistake for default j-ecdar --- .../java/ecdar/controllers/BackendOptionsDialogController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ecdar/controllers/BackendOptionsDialogController.java b/src/main/java/ecdar/controllers/BackendOptionsDialogController.java index 81faa812..fc79c406 100644 --- a/src/main/java/ecdar/controllers/BackendOptionsDialogController.java +++ b/src/main/java/ecdar/controllers/BackendOptionsDialogController.java @@ -204,7 +204,7 @@ private ArrayList getPackagedBackends() { jEcdar.setPortStart(5042); jEcdar.setPortEnd(5050); jEcdar.lockInstance(); - reveaal.setIsThreadSafe(false); + jEcdar.setIsThreadSafe(false); // Load correct j-Ecdar executable based on OS List potentialFiledForJEcdar = new ArrayList<>(); From 78c249d7581cbee44d7ecb8c1b9ae953f26a5f17 Mon Sep 17 00:00:00 2001 From: Casper-NS Date: Wed, 30 Nov 2022 14:13:04 +0100 Subject: [PATCH 122/158] Made the multithreading work --- src/main/java/ecdar/backend/BackendDriver.java | 12 +++++++++--- .../controllers/BackendOptionsDialogController.java | 2 +- .../presentations/BackendInstancePresentation.fxml | 6 ++++-- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/main/java/ecdar/backend/BackendDriver.java b/src/main/java/ecdar/backend/BackendDriver.java index 3fe70c96..273b418b 100644 --- a/src/main/java/ecdar/backend/BackendDriver.java +++ b/src/main/java/ecdar/backend/BackendDriver.java @@ -65,12 +65,16 @@ private BackendConnection getBackendConnection(BackendInstance backend) throws B tryStartNewBackendConnection(backend); } - // Block until a connection becomes available - connection = openBackendConnections.get(backend).take(); + if (backend.isThreadSafe()){ + connection = openBackendConnections.get(backend).peek(); + } + else{ + // Block until a connection becomes available + connection = openBackendConnections.get(backend).take(); + } } catch (InterruptedException e) { throw new RuntimeException(e); } - return connection; } @@ -105,6 +109,8 @@ private void tryStartNewBackendConnection(BackendInstance backend) { } do { + //ToDo: Refactor ProcessBuilder to accept cache-size(-cs) and thread-number(-tn) to better configure the Reveaal engine. + // Default values are acceptable for now. ProcessBuilder pb = new ProcessBuilder(backend.getBackendLocation(), "-p", hostAddress + ":" + portNumber); try { diff --git a/src/main/java/ecdar/controllers/BackendOptionsDialogController.java b/src/main/java/ecdar/controllers/BackendOptionsDialogController.java index fc79c406..d939c4e1 100644 --- a/src/main/java/ecdar/controllers/BackendOptionsDialogController.java +++ b/src/main/java/ecdar/controllers/BackendOptionsDialogController.java @@ -182,7 +182,7 @@ private ArrayList getPackagedBackends() { reveaal.setName("Reveaal"); reveaal.setLocal(true); reveaal.setDefault(true); - reveaal.setPortStart(5032); + reveaal.setPortStart(5040); reveaal.setPortEnd(5040); reveaal.lockInstance(); reveaal.setIsThreadSafe(true); diff --git a/src/main/resources/ecdar/presentations/BackendInstancePresentation.fxml b/src/main/resources/ecdar/presentations/BackendInstancePresentation.fxml index b0529be3..c6eedfd3 100644 --- a/src/main/resources/ecdar/presentations/BackendInstancePresentation.fxml +++ b/src/main/resources/ecdar/presentations/BackendInstancePresentation.fxml @@ -94,8 +94,10 @@ - Default - Thread Safe + + Default + Thread Safe + \ No newline at end of file From e2a187301b5ffa314cf36cb82187f1eb01191f40 Mon Sep 17 00:00:00 2001 From: Casper-NS Date: Thu, 1 Dec 2022 14:27:58 +0100 Subject: [PATCH 123/158] renamed a parameter --- src/main/java/ecdar/abstractions/BackendInstance.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/ecdar/abstractions/BackendInstance.java b/src/main/java/ecdar/abstractions/BackendInstance.java index b3170e61..763586f3 100644 --- a/src/main/java/ecdar/abstractions/BackendInstance.java +++ b/src/main/java/ecdar/abstractions/BackendInstance.java @@ -57,8 +57,8 @@ public boolean isThreadSafe() { return isThreadSafe; } - public void setIsThreadSafe(boolean aThreadSafe) { - isThreadSafe = aThreadSafe; + public void setIsThreadSafe(boolean threadSafe) { + isThreadSafe = threadSafe; } public String getBackendLocation() { From b81cd27764ead887a478432b0708c21effc2f83f Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 2 Dec 2022 09:51:56 +0100 Subject: [PATCH 124/158] Added a failed IO strings to component, and match them with the current IO strings for the component --- .../java/ecdar/abstractions/Component.java | 7 +++++-- src/main/java/ecdar/abstractions/Query.java | 5 ++++- .../controllers/ComponentController.java | 19 +++++-------------- 3 files changed, 14 insertions(+), 17 deletions(-) diff --git a/src/main/java/ecdar/abstractions/Component.java b/src/main/java/ecdar/abstractions/Component.java index cc1f49a9..be1d7ba8 100644 --- a/src/main/java/ecdar/abstractions/Component.java +++ b/src/main/java/ecdar/abstractions/Component.java @@ -45,6 +45,7 @@ public class Component extends HighLevelModelObject implements Boxed { private final ObservableList failingEdges = FXCollections.observableArrayList(); private final ObservableList inputStrings = FXCollections.observableArrayList(); private final ObservableList outputStrings = FXCollections.observableArrayList(); + private List failingIOStrings = new ArrayList(); private final StringProperty description = new SimpleStringProperty(""); private final StringProperty declarationsText = new SimpleStringProperty("");; private BooleanProperty isFailing = new SimpleBooleanProperty(false); @@ -61,8 +62,10 @@ public class Component extends HighLevelModelObject implements Boxed { public boolean getIsFailing(){return isFailing.get();} public BooleanProperty getIsFailingProperty(){return isFailing;} public void setIsFailing(boolean isFailingInput){this.isFailing.set(isFailingInput);} - - + public List getFailingIOStrings(){return failingIOStrings;} + public void setFailingIOStrings(List failingIOStrings){ + this.failingIOStrings.clear(); + this.failingIOStrings.addAll(failingIOStrings);} /** * Constructs an empty component */ diff --git a/src/main/java/ecdar/abstractions/Query.java b/src/main/java/ecdar/abstractions/Query.java index d67b2079..05c7c6a7 100644 --- a/src/main/java/ecdar/abstractions/Query.java +++ b/src/main/java/ecdar/abstractions/Query.java @@ -9,6 +9,7 @@ import com.google.gson.JsonObject; import javafx.application.Platform; import javafx.beans.property.*; +import javafx.collections.ObservableList; import java.util.List; import java.util.function.BiConsumer; @@ -64,13 +65,15 @@ public class Query implements Serializable { } } }; - //TODO add set alle compontens isfailing til fales + //TODO add set alle compontens isfailing til fails private final BiConsumer> stateActionConsumer = (state, action) -> { for (Component c : Ecdar.getProject().getComponents()) { c.removeFailingLocations(); c.removeFailingEdges(); if(state.getLocationTuple().getLocationsList().isEmpty()){ + c.setFailingIOStrings(action); + c.setIsFailing(true); } } diff --git a/src/main/java/ecdar/controllers/ComponentController.java b/src/main/java/ecdar/controllers/ComponentController.java index 2365030a..656c9d11 100644 --- a/src/main/java/ecdar/controllers/ComponentController.java +++ b/src/main/java/ecdar/controllers/ComponentController.java @@ -133,7 +133,6 @@ private void initializeSignature(final Component newComponent) { } - /*** * Initialize the listeners, that listen for changes in the input and output edges of the presented component. * The view is updated whenever an insert (deletions are also a type of insert) is reported @@ -146,7 +145,7 @@ private void initializeSignatureListeners(final Component newComponent) { if (newComponent.getIsFailing()) { for (Node n : inputSignatureContainer.getChildren()) { if (n instanceof SignatureArrow) { - for (String label : component.get().getInputStrings()) { // TODO Bwad bwad labuls gwo here <- UwU shall be complate faliure label that shall be punshid + for (String label : component.get().getFailingIOStrings()) { if (Objects.equals(((SignatureArrow) n).getSignatureArrowLabel(), label)) { ((SignatureArrow) n).recolorToRed(); } @@ -155,30 +154,22 @@ private void initializeSignatureListeners(final Component newComponent) { } for (Node n : outputSignatureContainer.getChildren()) { if (n instanceof SignatureArrow) { - for (String label : component.get().getOutputStrings()) { // TODO insert list of failing signature arrows + for (String label : component.get().getFailingIOStrings()) { if (Objects.equals(((SignatureArrow) n).getSignatureArrowLabel(), label)) { ((SignatureArrow) n).recolorToRed(); } } } } - }else { + } else { for (Node n : inputSignatureContainer.getChildren()) { if (n instanceof SignatureArrow) { - for (String label : component.get().getInputStrings()) { // TODO Bwad bwad labuls gwo here <- UwU shall be complate faliure label that shall be punshid - if (Objects.equals(((SignatureArrow) n).getSignatureArrowLabel(), label)) { - ((SignatureArrow) n).recolorToGray(); - } - } + ((SignatureArrow) n).recolorToGray(); } } for (Node n : outputSignatureContainer.getChildren()) { if (n instanceof SignatureArrow) { - for (String label : component.get().getOutputStrings()) { // TODO insert list of failing signature arrows - if (Objects.equals(((SignatureArrow) n).getSignatureArrowLabel(), label)) { - ((SignatureArrow) n).recolorToGray(); - } - } + ((SignatureArrow) n).recolorToGray(); } } } From a62caed7c8e9701e209747b90ca0299d82eb8619 Mon Sep 17 00:00:00 2001 From: APaludan Date: Fri, 2 Dec 2022 11:10:07 +0100 Subject: [PATCH 125/158] Update QueryHandler.java --- src/main/java/ecdar/backend/QueryHandler.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/ecdar/backend/QueryHandler.java b/src/main/java/ecdar/backend/QueryHandler.java index 9be10fbb..e79be10d 100644 --- a/src/main/java/ecdar/backend/QueryHandler.java +++ b/src/main/java/ecdar/backend/QueryHandler.java @@ -75,7 +75,6 @@ public void onCompleted() { } }; - // ToDo SW5: Not working with the updated gRPC Protos var queryBuilder = QueryProtos.QueryRequest.newBuilder() .setUserId(1) .setQueryId(UUID.randomUUID().hashCode()) From f31c1fa450fd91cbe1ee0e60bffb8c0e4a3d9e6f Mon Sep 17 00:00:00 2001 From: Claes Berg Mortensen Date: Tue, 6 Dec 2022 09:17:20 +0100 Subject: [PATCH 126/158] Fixed for new main and proto --- src/main/java/ecdar/backend/QueryHandler.java | 2 +- src/main/proto | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/ecdar/backend/QueryHandler.java b/src/main/java/ecdar/backend/QueryHandler.java index b1a2a6b6..e5826880 100644 --- a/src/main/java/ecdar/backend/QueryHandler.java +++ b/src/main/java/ecdar/backend/QueryHandler.java @@ -81,7 +81,7 @@ public void onCompleted() { var queryBuilder = QueryProtos.QueryRequest.newBuilder() .setUserId(1) .setQueryId(UUID.randomUUID().hashCode()) - .setSettings(Settings.newBuilder().setAll(true)) + .setSettings(Settings.newBuilder().setDisableClockReduction(false)) .setQuery(query.getType().getQueryName() + ": " + query.getQuery()) .setComponentsInfo(componentsInfoBuilder); diff --git a/src/main/proto b/src/main/proto index fa3ed0ae..e42e35db 160000 --- a/src/main/proto +++ b/src/main/proto @@ -1 +1 @@ -Subproject commit fa3ed0ae9d1b7fe2424057ddc69574482db4a640 +Subproject commit e42e35db63efacd7ab42aecd17c9a52b291c7be9 From 2726e8c960e3f02cf98c4bc041e28012bc1289c8 Mon Sep 17 00:00:00 2001 From: seba6505 <45783179+seba6505@users.noreply.github.com> Date: Tue, 6 Dec 2022 10:09:28 +0100 Subject: [PATCH 127/158] works when clock reduction is disabledisaebeld --- src/main/java/ecdar/backend/QueryHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ecdar/backend/QueryHandler.java b/src/main/java/ecdar/backend/QueryHandler.java index e5826880..0d307ac5 100644 --- a/src/main/java/ecdar/backend/QueryHandler.java +++ b/src/main/java/ecdar/backend/QueryHandler.java @@ -81,7 +81,7 @@ public void onCompleted() { var queryBuilder = QueryProtos.QueryRequest.newBuilder() .setUserId(1) .setQueryId(UUID.randomUUID().hashCode()) - .setSettings(Settings.newBuilder().setDisableClockReduction(false)) + .setSettings(Settings.newBuilder().setDisableClockReduction(true)) .setQuery(query.getType().getQueryName() + ": " + query.getQuery()) .setComponentsInfo(componentsInfoBuilder); From df4e18c5df366d0908322f28ca92a4b0463d4d9e Mon Sep 17 00:00:00 2001 From: seba6505 <45783179+seba6505@users.noreply.github.com> Date: Tue, 6 Dec 2022 11:52:57 +0100 Subject: [PATCH 128/158] Made coloring consistent across components, added checks for only failing components --- src/main/java/ecdar/abstractions/Query.java | 5 ++-- .../ecdar/presentations/SignatureArrow.java | 25 +++++++++++-------- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/src/main/java/ecdar/abstractions/Query.java b/src/main/java/ecdar/abstractions/Query.java index 05c7c6a7..963ef996 100644 --- a/src/main/java/ecdar/abstractions/Query.java +++ b/src/main/java/ecdar/abstractions/Query.java @@ -65,15 +65,14 @@ public class Query implements Serializable { } } }; - //TODO add set alle compontens isfailing til fails + private final BiConsumer> stateActionConsumer = (state, action) -> { for (Component c : Ecdar.getProject().getComponents()) { c.removeFailingLocations(); c.removeFailingEdges(); - if(state.getLocationTuple().getLocationsList().isEmpty()){ + if(state.getLocationTuple().getLocationsList().isEmpty() && query.getValue().contains(c.getName())){ c.setFailingIOStrings(action); - c.setIsFailing(true); } } diff --git a/src/main/java/ecdar/presentations/SignatureArrow.java b/src/main/java/ecdar/presentations/SignatureArrow.java index 8fc20c05..83f6328b 100644 --- a/src/main/java/ecdar/presentations/SignatureArrow.java +++ b/src/main/java/ecdar/presentations/SignatureArrow.java @@ -12,6 +12,8 @@ import javafx.scene.shape.MoveTo; import javafx.scene.shape.Path; +import java.util.Objects; + /*** * Creates input and output arrows, that can for example be used on the side of components to show its signature */ @@ -19,9 +21,11 @@ public class SignatureArrow extends Group implements Highlightable { private SignatureArrowController controller; - public String getSignatureArrowLabel(){ + public String getSignatureArrowLabel() { return controller.signatureArrowLabel.getText(); } + + /*** * Create a new signature arrow with a label for an edge, and different look depending on whether it's * an input or output arrow @@ -51,12 +55,7 @@ private void initializeMouseEvents() { }); controller.arrowBox.onMouseExitedProperty().set(event -> { controller.mouseExited(); - if (controller.getComponent().getIsFailing()){ - this.recolorToRed(); - }else { this.unhighlight(); - } - }); } @@ -83,14 +82,14 @@ private void drawArrow(final String edgeName, final EdgeStatus edgeStatus) { // Draw the arrow head separately, so it is for example possible to have a dashed line and solid head final Path arrowHead = controller.signatureArrowHeadPath; final MoveTo moveHead = new MoveTo(xValue + 50, yValue); - final LineTo lineHead1 = new LineTo(xValue + 35 , yValue-5); // Upper line in the arrow head - final LineTo lineHead2 = new LineTo(xValue + 35 , yValue+5); // Lower line in the arrow head + final LineTo lineHead1 = new LineTo(xValue + 35, yValue - 5); // Upper line in the arrow head + final LineTo lineHead2 = new LineTo(xValue + 35, yValue + 5); // Lower line in the arrow head arrowHead.getElements().addAll(moveHead, lineHead1, moveHead, lineHead2); arrowHead.setStrokeWidth(1.0); final double radius = 2.0; // Radius for the circle - if(edgeStatus == EdgeStatus.OUTPUT){ + if (edgeStatus == EdgeStatus.OUTPUT) { arrowLine.getStyleClass().add("dashed"); controller.arrowBox.setAlignment(Pos.CENTER_LEFT); controller.signatureArrowCircle.setCenterX(xValue - radius); // Place the circle partly on the line @@ -151,6 +150,12 @@ public void unhighlight() { Color.Intensity intensity = Color.Intensity.I800; this.colorArrowComponents(color, intensity); + + for(String s : controller.getComponent().getFailingIOStrings()){ + if(Objects.equals(getSignatureArrowLabel(), s) && controller.getComponent().getIsFailing()){ + this.recolorToRed(); + } + } } @@ -163,7 +168,7 @@ public void recolorToRed() { this.colorArrowComponents(color, intensity); } - public void recolorToGray(){ + public void recolorToGray() { Color color = Color.GREY; Color.Intensity intensity = Color.Intensity.I800; this.colorArrowComponents(color, intensity); From 2030cdeb96e0e63121360deabdac193f8cc414bc Mon Sep 17 00:00:00 2001 From: seba6505 <45783179+seba6505@users.noreply.github.com> Date: Tue, 6 Dec 2022 13:10:31 +0100 Subject: [PATCH 129/158] Added check for specific components --- src/main/java/ecdar/abstractions/Query.java | 43 +++++++++++-------- src/main/java/ecdar/backend/QueryHandler.java | 1 + 2 files changed, 26 insertions(+), 18 deletions(-) diff --git a/src/main/java/ecdar/abstractions/Query.java b/src/main/java/ecdar/abstractions/Query.java index 963ef996..0cee640e 100644 --- a/src/main/java/ecdar/abstractions/Query.java +++ b/src/main/java/ecdar/abstractions/Query.java @@ -12,6 +12,7 @@ import javafx.collections.ObservableList; import java.util.List; +import java.util.Objects; import java.util.function.BiConsumer; import java.util.function.Consumer; @@ -66,15 +67,11 @@ public class Query implements Serializable { } }; - private final BiConsumer> stateActionConsumer = (state, action) -> { + private final BiConsumer> stateActionConsumer = (state, actions) -> { for (Component c : Ecdar.getProject().getComponents()) { c.removeFailingLocations(); c.removeFailingEdges(); - if(state.getLocationTuple().getLocationsList().isEmpty() && query.getValue().contains(c.getName())){ - c.setFailingIOStrings(action); - c.setIsFailing(true); - } } for (ObjectProtos.Location location : state.getLocationTuple().getLocationsList()) { @@ -84,15 +81,22 @@ public class Query implements Serializable { throw new NullPointerException("Could not find the specific component: " + location.getSpecificComponent().getComponentName()); } - Location l = c.findLocation(location.getId()); - if (l == null) { - c.setIsFailing(true); - //throw new NullPointerException("Could not find location: " + location.getId()); - } - c.addFailingLocation(l.getId()); - for (Edge edge : c.getEdges()) { - if(action.equals(edge.getSync()) && edge.getSourceLocation() == l) { - c.addFailingEdge(edge); + if (location.getId().isEmpty()) { + if(c.getName().equals(location.getSpecificComponent().getComponentName())){ + c.setFailingIOStrings(actions); + c.setIsFailing(true); + } + } else { + Location l = c.findLocation(location.getId()); + if (l == null) { + throw new NullPointerException("Could not find location: " + location.getId()); + } + + c.addFailingLocation(l.getId()); + for (Edge edge : c.getEdges()) { + if (actions.get(0).equals(edge.getSync()) && edge.getSourceLocation() == l) { + c.addFailingEdge(edge); + } } } } @@ -145,7 +149,9 @@ public StringProperty commentProperty() { return comment; } - public StringProperty errors() { return errors; } + public StringProperty errors() { + return errors; + } public boolean isPeriodic() { return isPeriodic.get(); @@ -189,13 +195,14 @@ public Consumer getFailureConsumer() { /** * Getter for the state action consumer. + * * @return The State Consumer */ public BiConsumer> getStateActionConsumer() { return stateActionConsumer; } - + @Override public JsonObject serialize() { final JsonObject result = new JsonObject(); @@ -212,7 +219,7 @@ public JsonObject serialize() { public void deserialize(final JsonObject json) { String query = json.getAsJsonPrimitive(QUERY).getAsString(); - if(query.contains(":")) { + if (query.contains(":")) { String[] queryFieldFromJSON = json.getAsJsonPrimitive(QUERY).getAsString().split(": "); setType(QueryType.fromString(queryFieldFromJSON[0])); setQuery(queryFieldFromJSON[1]); @@ -226,7 +233,7 @@ public void deserialize(final JsonObject json) { setIsPeriodic(json.getAsJsonPrimitive(IS_PERIODIC).getAsBoolean()); } - if(json.has(BACKEND)) { + if (json.has(BACKEND)) { setBackend(BackendHelper.getBackendInstanceByName(json.getAsJsonPrimitive(BACKEND).getAsString())); } else { setBackend(BackendHelper.getDefaultBackendInstance()); diff --git a/src/main/java/ecdar/backend/QueryHandler.java b/src/main/java/ecdar/backend/QueryHandler.java index 0d307ac5..c714e716 100644 --- a/src/main/java/ecdar/backend/QueryHandler.java +++ b/src/main/java/ecdar/backend/QueryHandler.java @@ -105,6 +105,7 @@ public void closeAllBackendConnections() throws IOException { private void handleQueryResponse(QueryProtos.QueryResponse value, Query query) { // If the query has been cancelled, ignore the result + System.out.println(value); if (query.getQueryState() == QueryState.UNKNOWN) return; switch (value.getResultCase()) { case REFINEMENT: From 1ec7774ad189b16871a2d1ab726b77e96b3e0dbc Mon Sep 17 00:00:00 2001 From: APaludan Date: Tue, 6 Dec 2022 14:04:24 +0100 Subject: [PATCH 130/158] =?UTF-8?q?s=C3=A5=20vi=20ikke=20glemmer=20den=20:?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/java/ecdar/simulation/SimulationTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/test/java/ecdar/simulation/SimulationTest.java b/src/test/java/ecdar/simulation/SimulationTest.java index 55640701..3d295663 100644 --- a/src/test/java/ecdar/simulation/SimulationTest.java +++ b/src/test/java/ecdar/simulation/SimulationTest.java @@ -28,6 +28,7 @@ public class SimulationTest { public GrpcCleanupRule grpcCleanup = new GrpcCleanupRule(); private final String serverName = InProcessServerBuilder.generateName(); /* +// TODO fix this test @Test public void testGetInitialStateHighlightsTheInitialLocation() { final List components = generateComponentsWithInitialLocations(); From c247b64da1d7e55362d5a5fafcb85e931279bd6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Victor=20Dor=C3=A9=20Hansen?= <74188341+VictorDore@users.noreply.github.com> Date: Tue, 6 Dec 2022 15:42:12 +0100 Subject: [PATCH 131/158] Update src/main/java/ecdar/backend/QueryHandler.java --- src/main/java/ecdar/backend/QueryHandler.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/ecdar/backend/QueryHandler.java b/src/main/java/ecdar/backend/QueryHandler.java index c714e716..0d307ac5 100644 --- a/src/main/java/ecdar/backend/QueryHandler.java +++ b/src/main/java/ecdar/backend/QueryHandler.java @@ -105,7 +105,6 @@ public void closeAllBackendConnections() throws IOException { private void handleQueryResponse(QueryProtos.QueryResponse value, Query query) { // If the query has been cancelled, ignore the result - System.out.println(value); if (query.getQueryState() == QueryState.UNKNOWN) return; switch (value.getResultCase()) { case REFINEMENT: From 39d2023fe6e734f2d1c00e6830fe3576b1cbab12 Mon Sep 17 00:00:00 2001 From: Casper-NS Date: Wed, 7 Dec 2022 14:30:11 +0100 Subject: [PATCH 132/158] changed radiobutton to be a checkbox instead --- .../java/ecdar/controllers/BackendInstanceController.java | 8 ++++---- .../ecdar/presentations/BackendInstancePresentation.fxml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/ecdar/controllers/BackendInstanceController.java b/src/main/java/ecdar/controllers/BackendInstanceController.java index e172b1a0..c6752c81 100644 --- a/src/main/java/ecdar/controllers/BackendInstanceController.java +++ b/src/main/java/ecdar/controllers/BackendInstanceController.java @@ -52,7 +52,7 @@ public class BackendInstanceController implements Initializable { public JFXTextField portRangeStart; public JFXTextField portRangeEnd; public RadioButton defaultBackendRadioButton; - public RadioButton threadSafeBackendRadioButton; + public JFXCheckBox threadSafeBackendCheckBox; @Override public void initialize(URL location, ResourceBundle resources) { @@ -63,7 +63,7 @@ public void initialize(URL location, ResourceBundle resources) { setHGrow(); colorIconAsDisabledBasedOnProperty(removeBackendIcon, defaultBackendRadioButton.selectedProperty()); - colorIconAsDisabledBasedOnProperty(removeBackendIcon, threadSafeBackendRadioButton.selectedProperty()); + colorIconAsDisabledBasedOnProperty(removeBackendIcon, threadSafeBackendCheckBox.selectedProperty()); colorIconAsDisabledBasedOnProperty(pickPathToBackendIcon, backendInstance.getLockedProperty()); }); } @@ -95,7 +95,7 @@ public void setBackendInstance(BackendInstance instance) { this.backendName.setText(instance.getName()); this.isLocal.setSelected(instance.isLocal()); this.defaultBackendRadioButton.setSelected(instance.isDefault()); - this.threadSafeBackendRadioButton.setSelected(instance.isThreadSafe()); + this.threadSafeBackendCheckBox.setSelected(instance.isThreadSafe()); // Check if the path or the address should be used if (isLocal.isSelected()) { @@ -116,7 +116,7 @@ public BackendInstance updateBackendInstance() { backendInstance.setName(backendName.getText()); backendInstance.setLocal(isLocal.isSelected()); backendInstance.setDefault(defaultBackendRadioButton.isSelected()); - backendInstance.setIsThreadSafe(threadSafeBackendRadioButton.isSelected()); + backendInstance.setIsThreadSafe(threadSafeBackendCheckBox.isSelected()); backendInstance.setBackendLocation(isLocal.isSelected() ? pathToBackend.getText() : address.getText()); backendInstance.setPortStart(Integer.parseInt(portRangeStart.getText())); backendInstance.setPortEnd(Integer.parseInt(portRangeEnd.getText())); diff --git a/src/main/resources/ecdar/presentations/BackendInstancePresentation.fxml b/src/main/resources/ecdar/presentations/BackendInstancePresentation.fxml index c6eedfd3..61d57bbb 100644 --- a/src/main/resources/ecdar/presentations/BackendInstancePresentation.fxml +++ b/src/main/resources/ecdar/presentations/BackendInstancePresentation.fxml @@ -96,7 +96,7 @@ Default - Thread Safe + Thread Safe From 0e4d7973729e87918e83799d35764cd33fa8ecbd Mon Sep 17 00:00:00 2001 From: EmilieSonne <82802471+EmilieSonne@users.noreply.github.com> Date: Fri, 9 Dec 2022 12:07:52 +0100 Subject: [PATCH 133/158] Combined branch (#124) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * added declerations to sim view * clock names added + formatting * e * b * n * no height limit for trace element * clicking on trace doesnt delete following entries * padding * lillebitte ændring * refactor * Update SimulationHandler.java * added declerations to sim view (#110) * omskrevet et forloop haha (#115) * omskrevet et forloop haha * Update src/main/java/ecdar/controllers/SimulatorOverviewController.java Co-authored-by: Sigurd00 Co-authored-by: Sigurd00 * See previous sim states without deleting current trace (#117) * clicking on trace doesnt delete following entries * lillebitte ændring * refactor * Update SimulationHandler.java * 102 clock values/constraints in trace (#118) * clock names added + formatting * e * b * n * no height limit for trace element * padding Co-authored-by: WassawRoki <56611129+WassawRoki@users.noreply.github.com> * 92 reachability request kan sendes fra current state new (#116) * iteration 1, does not take clocks into consideration * rette method description (relateret til tidligere issue dog) Co-authored-by: Emilie Steinmann Co-authored-by: EmilieSonne <82802471+EmilieSonne@users.noreply.github.com> * 99 reachability show response on components (#114) * metode til at highlight edges fra reachability response - virker ikke helt, men noget virker lidt :)) * hov weird replace og en irriterende rød link ting :) * dokumentation og oprydning * unhighlight edges before highlighting the edges from reachability response Co-authored-by: EmilieSonne <82802471+EmilieSonne@users.noreply.github.com> * ComponentsInSimulation (altså "ListOfComponents" renamet) is now plac… (#108) * ComponentsInSimulation (altså "ListOfComponents" renamet) is now placed in SimulationHandler (and not static anymore) * Property "simulationQuery" moved to SimulationHandler and made non-static Co-authored-by: Emilie Steinmann Co-authored-by: EmilieSonne <82802471+EmilieSonne@users.noreply.github.com> * rettelser * Solves issue 97 ish * rename getter * use local var instead of Ecdar.getSimulationHandler() all the time (#127) * use local var instead of Ecdar.getSimulationHandler() all the time * fixed reachability tests * Checks for amount of commas instead of underscores Co-authored-by: Dolmer1 * highlight reachability path with purple (#131) * slettet en masse (#129) * test (#130) Co-authored-by: EmilieSonne <82802471+EmilieSonne@users.noreply.github.com> * Move initialization of simulationHandler to be first thing * Update UiSwitcherTest.java * slettet en udkommenteret liste * rettet i deserialize Co-authored-by: APaludan Co-authored-by: WassawRoki <56611129+WassawRoki@users.noreply.github.com> Co-authored-by: Julie H. Bengtsson <82820935+jhbengtsson@users.noreply.github.com> Co-authored-by: Sigurd00 Co-authored-by: jhbengtsson Co-authored-by: Dolmer1 --- src/main/java/ecdar/Ecdar.java | 6 +- .../java/ecdar/abstractions/Component.java | 12 -- .../ecdar/abstractions/DisplayableEdge.java | 7 + src/main/java/ecdar/abstractions/Edge.java | 11 -- .../java/ecdar/abstractions/Location.java | 10 -- .../java/ecdar/backend/BackendHelper.java | 72 +++++++-- src/main/java/ecdar/backend/QueryHandler.java | 92 ++++++----- .../java/ecdar/backend/SimulationHandler.java | 81 ++++++++-- .../ecdar/controllers/EcdarController.java | 98 +----------- .../ecdar/controllers/ProcessController.java | 11 ++ .../ecdar/controllers/SimEdgeController.java | 18 +++ .../controllers/SimLocationController.java | 27 +++- ...ulationInitializationDialogController.java | 19 ++- .../controllers/SimulatorController.java | 31 ++-- .../SimulatorOverviewController.java | 27 ++-- .../TracePaneElementController.java | 40 +++-- .../TransitionPaneElementController.java | 5 +- .../arrow_heads/SimpleArrowHead.java | 6 + src/main/java/ecdar/presentations/Link.java | 5 + .../presentations/LocationPresentation.java | 66 -------- .../presentations/SimEdgePresentation.java | 13 +- .../presentations/SimNailPresentation.java | 10 ++ .../presentations/SystemRootPresentation.java | 5 + .../ecdar/simulation/SimulationState.java | 24 ++- .../java/ecdar/utility/Highlightable.java | 3 + .../presentations/ProcessPresentation.fxml | 10 +- .../TracePaneElementPresentation.fxml | 3 + .../presentations/TransitionPresentation.fxml | 10 +- .../ecdar/simulation/ReachabilityTest.java | 151 +++++++++++++----- .../java/ecdar/simulation/SimulationTest.java | 6 +- src/test/java/ecdar/ui/UiSwitcherTest.java | 40 +++++ 31 files changed, 555 insertions(+), 364 deletions(-) mode change 100755 => 100644 src/main/java/ecdar/presentations/SimEdgePresentation.java create mode 100644 src/test/java/ecdar/ui/UiSwitcherTest.java diff --git a/src/main/java/ecdar/Ecdar.java b/src/main/java/ecdar/Ecdar.java index 01775eac..f56e4baf 100644 --- a/src/main/java/ecdar/Ecdar.java +++ b/src/main/java/ecdar/Ecdar.java @@ -138,6 +138,10 @@ public static QueryHandler getQueryExecutor() { public static SimulationHandler getSimulationHandler() { return simulationHandler; } + public static void setSimulationHandler(SimulationHandler simHandler) { + simulationHandler = simHandler; + } + public static EcdarPresentation getPresentation() { return presentation; } @@ -274,8 +278,6 @@ public void start(final Stage stage) { // Set active model Platform.runLater(() -> EcdarController.setActiveModelForActiveCanvas(Ecdar.getProject().getComponents().get(0))); - EcdarController.reachabilityServiceEnabled = true; - // Register a key-bind for showing debug-information KeyboardTracker.registerKeybind("DEBUG", new Keybind(new KeyCodeCombination(KeyCode.F12), () -> { // Toggle the debug mode for the debug class (will update misc. debug variables which presentations bind to) diff --git a/src/main/java/ecdar/abstractions/Component.java b/src/main/java/ecdar/abstractions/Component.java index be1d7ba8..7bfbcc7d 100644 --- a/src/main/java/ecdar/abstractions/Component.java +++ b/src/main/java/ecdar/abstractions/Component.java @@ -96,7 +96,6 @@ public Component(final boolean doRandomColor) { locations.add(initialLocation); initializeIOListeners(); - bindReachabilityAnalysis(); } public Component(final JsonObject json) { @@ -105,7 +104,6 @@ public Component(final JsonObject json) { deserialize(json); initializeIOListeners(); updateIOList(); - bindReachabilityAnalysis(); } /** @@ -835,16 +833,6 @@ public void dye(final Color color, final Color.Intensity intensity) { }, String.format("Changed the color of %s to %s", this, color.name()), "color-lens"); } - private void bindReachabilityAnalysis() { - // If there is no EcdarPresentation, we are running tests and EcdarController calls will fail - if (Ecdar.getPresentation() == null) return; - - locations.addListener((ListChangeListener) c -> EcdarController.runReachabilityAnalysis()); - edges.addListener((ListChangeListener) c -> EcdarController.runReachabilityAnalysis()); - declarationsTextProperty().addListener((observable, oldValue, newValue) -> EcdarController.runReachabilityAnalysis()); - includeInPeriodicCheckProperty().addListener((observable, oldValue, newValue) -> EcdarController.runReachabilityAnalysis()); - } - public String getDescription() { return description.get(); } diff --git a/src/main/java/ecdar/abstractions/DisplayableEdge.java b/src/main/java/ecdar/abstractions/DisplayableEdge.java index be812729..3b53efd8 100644 --- a/src/main/java/ecdar/abstractions/DisplayableEdge.java +++ b/src/main/java/ecdar/abstractions/DisplayableEdge.java @@ -9,6 +9,7 @@ import javafx.beans.property.*; import javafx.collections.FXCollections; import javafx.collections.ObservableList; +import javafx.fxml.FXMLLoader; import java.util.List; import java.util.UUID; @@ -41,6 +42,7 @@ public abstract class DisplayableEdge implements Nearable { private final BooleanProperty isHighlighted = new SimpleBooleanProperty(false); protected final BooleanProperty failing = new SimpleBooleanProperty(false); + private final BooleanProperty isHighlightedForReachability = new SimpleBooleanProperty(false); public Location getSourceLocation() { return sourceLocation.get(); @@ -135,8 +137,11 @@ public ObjectProperty colorIntensityProperty() { public void setIsHighlighted(final boolean highlight){ this.isHighlighted.set(highlight);} public boolean getIsHighlighted(){ return this.isHighlighted.get(); } + public boolean getIsHighlightedForReachability(){ return this.isHighlightedForReachability.get(); } public BooleanProperty isHighlightedProperty() { return this.isHighlighted; } + public BooleanProperty isHighlightedForReachabilityProperty() { return this.isHighlightedForReachability; } + public ObservableList getNails() { return nails; @@ -248,6 +253,8 @@ public void switchStatus() { } } + public void setIsHighlightedForReachability(final boolean highlightedForReachability){ this.isHighlightedForReachability.set(highlightedForReachability);} + public enum PropertyType { NONE(-1), SELECTION(0), diff --git a/src/main/java/ecdar/abstractions/Edge.java b/src/main/java/ecdar/abstractions/Edge.java index 22c5cdbe..28f7cad8 100644 --- a/src/main/java/ecdar/abstractions/Edge.java +++ b/src/main/java/ecdar/abstractions/Edge.java @@ -40,12 +40,10 @@ public Edge(final Location sourceLocation, final EdgeStatus status) { setId(); } - bindReachabilityAnalysis(); } public Edge(final JsonObject jsonObject, final Component component) { deserialize(jsonObject, component); - bindReachabilityAnalysis(); } public String getSync() { @@ -245,15 +243,6 @@ public void deserialize(final JsonObject json, final Component component) { } } - private void bindReachabilityAnalysis() { - // If there is no EcdarPresentation, we are running tests and EcdarController calls will fail - if (Ecdar.getPresentation() == null) return; - selectProperty().addListener((observable, oldValue, newValue) -> EcdarController.runReachabilityAnalysis()); - guardProperty().addListener((observable, oldValue, newValue) -> EcdarController.runReachabilityAnalysis()); - syncProperty().addListener((observable, oldValue, newValue) -> EcdarController.runReachabilityAnalysis()); - updateProperty().addListener((observable, oldValue, newValue) -> EcdarController.runReachabilityAnalysis()); - } - /** * Adds a synchronization nail at (0, 0). * Adds a specified synchronization property to this edge. diff --git a/src/main/java/ecdar/abstractions/Location.java b/src/main/java/ecdar/abstractions/Location.java index e75e4797..49296a21 100644 --- a/src/main/java/ecdar/abstractions/Location.java +++ b/src/main/java/ecdar/abstractions/Location.java @@ -65,7 +65,6 @@ public Location() { public Location(final String id) { setId(id); - bindReachabilityAnalysis(); } public Location(final Component component, final Type type, final double x, final double y){ @@ -86,7 +85,6 @@ public Location(final Component component, final Type type, final double x, fina public Location(final JsonObject jsonObject) { deserialize(jsonObject); - bindReachabilityAnalysis(); } // ToDo NIELS: Comment in, when location should be received through ProtoBuf @@ -110,7 +108,6 @@ public Location(final JsonObject jsonObject) { */ public void initialize() { setId(); - bindReachabilityAnalysis(); } /** @@ -208,7 +205,6 @@ public Urgency getUrgency() { public void setUrgency(final Urgency urgency) { // If there is no EcdarPresentation, we are running tests and EcdarController calls will fail - if (Ecdar.getPresentation() != null) EcdarController.runReachabilityAnalysis(); this.urgency.set(urgency); } @@ -324,7 +320,6 @@ public double getInvariantY() { public void setInvariantY(final double invariantY) { // If there is no EcdarPresentation, we are running tests and EcdarController calls will fail - if (Ecdar.getPresentation() != null) EcdarController.runReachabilityAnalysis(); this.invariantY.set(invariantY); } @@ -506,11 +501,6 @@ public enum Reachability { REACHABLE, UNREACHABLE, UNKNOWN, EXCLUDED } - private void bindReachabilityAnalysis() { - invariantProperty().addListener((observable, oldValue, newValue) -> EcdarController.runReachabilityAnalysis()); - urgencyProperty().addListener((observable, oldValue, newValue) -> EcdarController.runReachabilityAnalysis()); - } - public boolean isUniversalOrInconsistent() { return getType().equals(Type.UNIVERSAL) || getType().equals(Type.INCONSISTENT); } diff --git a/src/main/java/ecdar/backend/BackendHelper.java b/src/main/java/ecdar/backend/BackendHelper.java index 0397b6ee..442e0578 100644 --- a/src/main/java/ecdar/backend/BackendHelper.java +++ b/src/main/java/ecdar/backend/BackendHelper.java @@ -3,6 +3,7 @@ import EcdarProtoBuf.ComponentProtos; import ecdar.Ecdar; import ecdar.abstractions.*; +import ecdar.simulation.SimulationState; import javafx.beans.property.SimpleListProperty; import javafx.collections.FXCollections; import javafx.collections.ObservableList; @@ -19,8 +20,6 @@ import java.util.List; import java.util.Optional; -import static ecdar.controllers.SimulationInitializationDialogController.ListOfComponents; - public final class BackendHelper { final static String TEMP_DIRECTORY = "temporary"; private static BackendInstance defaultBackend = null; @@ -66,39 +65,81 @@ public static void stopQueries() { Ecdar.getProject().getQueries().forEach(Query::cancel); } - /** - * Generates a reachability query based on the given location and component - * - * @param endLocation The location which should be checked for reachability - * @return A reachability query string - */ public static String getLocationReachableQuery(final Location endLocation, final Component component, final String query) { + return getLocationReachableQuery(endLocation, component, query, null); + } + /** + * Generates a reachability query based on the given location and component + * + * @param endLocation The location which should be checked for reachability + * @return A reachability query string + */ + public static String getLocationReachableQuery(final Location endLocation, final Component component, final String query, final SimulationState state) { var stringBuilder = new StringBuilder(); // append simulation query stringBuilder.append(query); + // append arrow + stringBuilder.append(" -> "); + // append start location here TODO + if (state != null){ + stringBuilder.append(getStartStateString(state)); + stringBuilder.append(";"); + } // append end state stringBuilder.append(getEndStateString(component.getName(), endLocation.getId())); - // append clocks - stringBuilder.append("("); - // append clock here TODO - stringBuilder.append(")"); - // return example: m1||M2->[L1,L4](y<3);[L2, L7](y<2) + System.out.println(stringBuilder); + return stringBuilder.toString(); + } + + private static String getStartStateString(SimulationState state) { + var stringBuilder = new StringBuilder(); + + // append locations + var locations = state.getLocations(); + stringBuilder.append("["); + var appendLocationWithSeparator = false; + for(var componentName:Ecdar.getSimulationHandler().getComponentsInSimulation()){ + var locationFound = false; + + for(var location:locations){ + if (location.getKey().equals(componentName)){ + if (appendLocationWithSeparator){ + stringBuilder.append("," + location.getValue()); + } + else{ + stringBuilder.append(location.getValue()); + } + locationFound = true; + } + if (locationFound){ + // don't go through more locations, when a location is found for the specific component that we're looking at + break; + } + } + appendLocationWithSeparator = true; + } + stringBuilder.append("]"); + + // append clock values + var clocks = state.getSimulationClocks(); + stringBuilder.append("()"); + return stringBuilder.toString(); } private static String getEndStateString(String componentName, String endLocationId) { var stringBuilder = new StringBuilder(); - stringBuilder.append(" -> ["); + stringBuilder.append("["); var appendLocationWithSeparator = false; - for (var component:ListOfComponents) + for (var component:Ecdar.getSimulationHandler().getComponentsInSimulation()) { if (component.equals(componentName)){ if (appendLocationWithSeparator){ @@ -121,6 +162,7 @@ private static String getEndStateString(String componentName, String endLocation } } stringBuilder.append("]"); + stringBuilder.append("()"); return stringBuilder.toString(); } diff --git a/src/main/java/ecdar/backend/QueryHandler.java b/src/main/java/ecdar/backend/QueryHandler.java index 0d307ac5..e6e79e97 100644 --- a/src/main/java/ecdar/backend/QueryHandler.java +++ b/src/main/java/ecdar/backend/QueryHandler.java @@ -130,8 +130,6 @@ private void handleQueryResponse(QueryProtos.QueryResponse value, Query query) { query.getSuccessConsumer().accept(false); query.getStateActionConsumer().accept(value.getConsistency().getState(), value.getConsistency().getActionList()); - - } break; @@ -148,7 +146,7 @@ private void handleQueryResponse(QueryProtos.QueryResponse value, Query query) { } break; - + case IMPLEMENTATION: if (value.getImplementation().getSuccess()) { query.setQueryState(QueryState.SUCCESSFUL); @@ -163,44 +161,54 @@ private void handleQueryResponse(QueryProtos.QueryResponse value, Query query) { } break; - case REACHABILITY: - if (value.getReachability().getSuccess()) { - query.setQueryState(QueryState.SUCCESSFUL); - if(value.getReachability().getSuccess()){ - Ecdar.showToast("Reachability check was successful and the location can be reached."); - } - else if(!value.getReachability().getSuccess()){ - Ecdar.showToast("Reachability check was successful but the location cannot be reached."); - } - query.getSuccessConsumer().accept(true); - } else { - query.setQueryState(QueryState.ERROR); - Ecdar.showToast("Reachability check was unsuccessful!"); - query.getFailureConsumer().accept(new BackendException.QueryErrorException(value.getReachability().getReason())); - query.getSuccessConsumer().accept(false); - //ToDo: These errors are not implemented in the Reveaal backend. - query.getStateActionConsumer().accept(value.getReachability().getState(), - new ArrayList<>()); - } - break; - - case COMPONENT: - query.setQueryState(QueryState.SUCCESSFUL); - query.getSuccessConsumer().accept(true); - JsonObject returnedComponent = (JsonObject) JsonParser.parseString(value.getComponent().getComponent().getJson()); - addGeneratedComponent(new Component(returnedComponent)); - break; - - case ERROR: - query.setQueryState(QueryState.ERROR); - query.getFailureConsumer().accept(new BackendException.QueryErrorException(value.getError())); - query.getSuccessConsumer().accept(false); - break; - - case RESULT_NOT_SET: - query.setQueryState(QueryState.ERROR); - query.getSuccessConsumer().accept(false); - break; + case REACHABILITY: + if (value.getReachability().getSuccess()) { + query.setQueryState(QueryState.SUCCESSFUL); + Ecdar.showToast("Reachability check was successful and the location can be reached."); + + //create list of edge id's + ArrayList edgeIds = new ArrayList<>(); + for(var pathsList : value.getReachability().getComponentPathsList()){ + for(var id : pathsList.getEdgeIdsList().toArray()) { + edgeIds.add(id.toString()); + } + } + //highlight the edges + Ecdar.getSimulationHandler().highlightReachabilityEdges(edgeIds); + query.getSuccessConsumer().accept(true); + } + else if(!value.getReachability().getSuccess()){ + Ecdar.showToast("Reachability check was successful but the location cannot be reached."); + query.getSuccessConsumer().accept(true); + } else { + query.setQueryState(QueryState.ERROR); + Ecdar.showToast("Error from backend: Reachability check was unsuccessful!"); + query.getFailureConsumer().accept(new BackendException.QueryErrorException(value.getReachability().getReason())); + query.getSuccessConsumer().accept(false); + //ToDo: These errors are not implemented in the Reveaal backend. + query.getStateActionConsumer().accept(value.getReachability().getState(), + new ArrayList<>()); + } + break; + + case COMPONENT: + query.setQueryState(QueryState.SUCCESSFUL); + query.getSuccessConsumer().accept(true); + JsonObject returnedComponent = (JsonObject) JsonParser.parseString(value.getComponent().getComponent().getJson()); + addGeneratedComponent(new Component(returnedComponent)); + break; + + case ERROR: + query.setQueryState(QueryState.ERROR); + Ecdar.showToast(value.getError()); + query.getFailureConsumer().accept(new BackendException.QueryErrorException(value.getError())); + query.getSuccessConsumer().accept(false); + break; + + case RESULT_NOT_SET: + query.setQueryState(QueryState.ERROR); + query.getSuccessConsumer().accept(false); + break; } } @@ -211,7 +219,7 @@ private void handleQueryBackendError(Throwable t, Query query) { // due to lack of information from backend if the reachability check shows that a location can NOT be reached, this is the most accurate information we can provide if(query.getType() == QueryType.REACHABILITY){ - Ecdar.showToast("The reachability query failed. This might be due to the fact that the location is not reachable."); + Ecdar.showToast("Timeout (no response from backend): The reachability query failed. This might be due to the fact that the location is not reachable."); } // Each error starts with a capitalized description of the error equal to the gRPC error type encountered diff --git a/src/main/java/ecdar/backend/SimulationHandler.java b/src/main/java/ecdar/backend/SimulationHandler.java index cfebcefb..480b63b3 100644 --- a/src/main/java/ecdar/backend/SimulationHandler.java +++ b/src/main/java/ecdar/backend/SimulationHandler.java @@ -19,6 +19,7 @@ import java.io.IOException; import java.math.BigDecimal; import java.util.ArrayList; +import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; @@ -38,13 +39,16 @@ public class SimulationHandler { public ObjectProperty selectedEdge = new SimpleObjectProperty<>(); private EcdarSystem system; private int numberOfSteps; - + private String simulationQuery; + private ArrayList simulationComponents = new ArrayList<>(); private final ObservableMap simulationVariables = FXCollections.observableHashMap(); private final ObservableMap simulationClocks = FXCollections.observableHashMap(); public ObservableList traceLog = FXCollections.observableArrayList(); private final BackendDriver backendDriver; private final ArrayList connections = new ArrayList<>(); + private List ComponentsInSimulation = new ArrayList<>(); + /** * Empty constructor that should be used if the system or project has not be initialized yet */ @@ -52,6 +56,9 @@ public SimulationHandler(BackendDriver backendDriver) { this.backendDriver = backendDriver; } + public void clearComponentsInSimulation() { + ComponentsInSimulation.clear(); + } /** * Initializes the values and properties in the {@link SimulationHandler}. @@ -66,7 +73,6 @@ private void initializeSimulation() { this.currentState.set(null); this.selectedEdge.set(null); this.traceLog.clear(); - this.system = getSystem(); } @@ -119,8 +125,6 @@ public void onCompleted() { backendDriver.addRequestToExecutionQueue(request); - //Save the previous states, and get the new - this.traceLog.add(currentState.get()); numberOfSteps++; //Updates the transitions available @@ -139,6 +143,9 @@ public void resetToInitialLocation() { * Take a step in the simulation. */ public void nextStep() { + // removes invalid states from the log when stepping forward after previewing a previous state + removeStatesFromLog(currentState.get()); + GrpcRequest request = new GrpcRequest(backendConnection -> { StreamObserver responseObserver = new StreamObserver<>() { @Override @@ -227,7 +234,7 @@ private void updateAllValues() { } /** - * Sets the value of simulation variables and clocks, based on {@link SimulationHandler#currentConcreteState} + * Sets the value of simulation variables and clocks, based on currentConcreteState */ private void setSimVarAndClocks() { // The variables and clocks are all found in the getVariables array @@ -277,7 +284,7 @@ public ObservableList getTraceLog() { * @return an {@link ObservableList} of all the currently available transitions in this state */ public ArrayList> getAvailableTransitions() { - return currentState.get().getEdges(); + return currentState.get().getEnabledEdges(); } /** @@ -300,6 +307,10 @@ public ObservableMap getSimulationClocks() { return simulationClocks; } + public SimulationState getCurrentState() { + return currentState.get(); + } + /** * The initial state of the current simulation * @@ -338,14 +349,64 @@ public void closeAllBackendConnections() throws IOException { } } - /** - * Sets the current state of the simulation to the given state from the trace log + * Removes all states from the trace log after the given state */ - public void selectStateFromLog(SimulationState state) { + private void removeStatesFromLog(SimulationState state) { while (traceLog.get(traceLog.size() - 1) != state) { traceLog.remove(traceLog.size() - 1); } - currentState.set(state); + } + + public void setComponentsInSimulation(List value) { + ComponentsInSimulation = value; + } + + public List getComponentsInSimulation() { + return ComponentsInSimulation; + } + + public void setSimulationQuery(String query) { + simulationQuery = query; + } + + public String getSimulationQuery(){ + return simulationQuery; + } + + /** + * Set list of components used in the simulation + */ + public void setSimulationComponents(ArrayList components){ + simulationComponents = components; + } + + /** + * Get list of components used in the simulation + */ + public ArrayList getSimulationComponents(){ + return simulationComponents; + } + + /** + * Highlights the edges from the reachability response + */ + public void highlightReachabilityEdges(ArrayList ids){ + //unhighlight all edges + for(var comp : simulationComponents){ + for(var edge : comp.getEdges()){ + edge.setIsHighlightedForReachability(false); + } + } + //highlight the edges from the reachability response + for(var comp : simulationComponents){ + for(var edge : comp.getEdges()){ + for(var id : ids){ + if(edge.getId().equals(id)){ + edge.setIsHighlightedForReachability(true); + } + } + } + } } } \ No newline at end of file diff --git a/src/main/java/ecdar/controllers/EcdarController.java b/src/main/java/ecdar/controllers/EcdarController.java index 89ebb802..cffb562f 100644 --- a/src/main/java/ecdar/controllers/EcdarController.java +++ b/src/main/java/ecdar/controllers/EcdarController.java @@ -6,6 +6,7 @@ import ecdar.Ecdar; import ecdar.abstractions.*; import ecdar.backend.BackendHelper; +import ecdar.backend.SimulationHandler; import ecdar.code_analysis.CodeAnalysis; import ecdar.mutation.models.MutationTestPlan; import ecdar.presentations.*; @@ -48,11 +49,7 @@ import java.util.stream.Collectors; public class EcdarController implements Initializable { - // Reachability analysis - public static boolean reachabilityServiceEnabled = false; - private static long reachabilityTime = Long.MAX_VALUE; - private static ExecutorService reachabilityService; - + private SimulationHandler simulationHandler; // View stuff public StackPane root; public BorderPane borderPane; @@ -144,16 +141,10 @@ public class EcdarController implements Initializable { private static Text _queryTextQuery; private static final Text temporaryComponentWatermark = new Text("Temporary component"); - public static void runReachabilityAnalysis() { - if (!reachabilityServiceEnabled) return; - - reachabilityTime = System.currentTimeMillis() + 500; - } - /** * Enumeration to keep track of which mode the application is in */ - private enum Mode { + public enum Mode { Editor, Simulator } @@ -161,7 +152,7 @@ private enum Mode { * currentMode is a property that keeps track of which mode the application is in. * The initial mode is Mode.Editor */ - private static final ObjectProperty currentMode = new SimpleObjectProperty<>(Mode.Editor); + public static final ObjectProperty currentMode = new SimpleObjectProperty<>(Mode.Editor); private static final EditorPresentation editorPresentation = new EditorPresentation(); public final ProjectPanePresentation projectPane = new ProjectPanePresentation(); @@ -177,7 +168,6 @@ public void initialize(final URL location, final ResourceBundle resources) { initializeKeybindings(); initializeStatusBar(); initializeMenuBar(); - startBackgroundQueriesThread(); // Will terminate immediately if background queries are turned off // Update file coloring when active model changes editorPresentation.getController().getActiveCanvasPresentation().getController().activeComponentProperty().addListener(observable -> projectPane.getController().updateColorsOnFilePresentations()); @@ -189,6 +179,8 @@ public void initialize(final URL location, final ResourceBundle resources) { rightSimPane.maxWidthProperty().bind(queryPane.maxWidthProperty()); enterEditorMode(); + + simulationHandler = Ecdar.getSimulationHandler(); } public StackPane getCenter() { @@ -290,7 +282,7 @@ private void initializeDialogs() { simulationInitializationDialog.getController().startButton.setOnMouseClicked(event -> { // ToDo NIELS: Start simulation of selected query - Ecdar.getSimulationHandler().setComposition(simulationInitializationDialog.getController().simulationComboBox.getSelectionModel().getSelectedItem()); + simulationHandler.setComposition(simulationInitializationDialog.getController().simulationComboBox.getSelectionModel().getSelectedItem()); currentMode.setValue(Mode.Simulator); simulationInitializationDialog.close(); }); @@ -404,74 +396,6 @@ private void initializeKeybindings() { KeyboardTracker.registerKeybind(KeyboardTracker.NUDGE_D, new Keybind(new KeyCodeCombination(KeyCode.D), () -> nudgeSelected(NudgeDirection.RIGHT))); } - private void startBackgroundQueriesThread() { - new Thread(() -> { - while (Ecdar.shouldRunBackgroundQueries.get()) { - // Wait for the reachability (the last time we changed the model) becomes smaller than the current time - while (reachabilityTime > System.currentTimeMillis()) { - try { - Thread.sleep(2000); - Debug.backgroundThreads.removeIf(thread -> !thread.isAlive()); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - - // We are now performing the analysis. Do not do another analysis before another change is introduced - reachabilityTime = Long.MAX_VALUE; - - // Cancel any ongoing analysis - if (reachabilityService != null) { - reachabilityService.shutdownNow(); - } - - // Start new analysis - reachabilityService = Executors.newFixedThreadPool(10); - - while (Debug.backgroundThreads.size() > 0) { - final Thread thread = Debug.backgroundThreads.get(0); - thread.interrupt(); - Debug.removeThread(thread); - } - - // Stop thread if background queries have been toggled off - if (!Ecdar.shouldRunBackgroundQueries.get()) return; - - Ecdar.getProject().getQueries().forEach(query -> { - if (query.isPeriodic()) Ecdar.getQueryExecutor().executeQuery(query); - }); - - // List of threads to start - List threads = new ArrayList<>(); - - // Submit all background reachability queries - Ecdar.getProject().getComponents().forEach(component -> { - // Check if we should consider this component - if (!component.isIncludeInPeriodicCheck()) { - component.getLocations().forEach(location -> location.setReachability(Location.Reachability.EXCLUDED)); - } else { - component.getLocations().forEach(location -> { - final String locationReachableQuery = BackendHelper.getLocationReachableQuery(location, component, SimulatorController.getSimulationQuery()); - - Query reachabilityQuery = new Query(locationReachableQuery, "", QueryState.UNKNOWN); - reachabilityQuery.setType(QueryType.REACHABILITY); - - Ecdar.getQueryExecutor().executeQuery(reachabilityQuery); - - final Thread verifyThread = new Thread(() -> Ecdar.getQueryExecutor().executeQuery(reachabilityQuery)); - - verifyThread.setName(locationReachableQuery + " (" + verifyThread.getName() + ")"); - Debug.addThread(verifyThread); - threads.add(verifyThread); - }); - } - }); - - threads.forEach((verifyThread) -> reachabilityService.submit(verifyThread::start)); - } - }).start(); - } - private void initializeStatusBar() { statusBar.setBackground(new Background(new BackgroundFill( Color.GREY_BLUE.getColor(Color.Intensity.I800), @@ -560,10 +484,6 @@ private void initializeOptionsMenu() { menuBarOptionsBackgroundQueries.setOnAction(event -> { final BooleanProperty shouldRunBackgroundQueries = Ecdar.toggleRunBackgroundQueries(); Ecdar.preferences.putBoolean("run_background_queries", shouldRunBackgroundQueries.get()); - if (shouldRunBackgroundQueries.get()) { - // If background queries have been turned back on, start a new thread - startBackgroundQueriesThread(); - } }); Ecdar.shouldRunBackgroundQueries.setValue(Ecdar.preferences.getBoolean("run_background_queries", true)); @@ -661,7 +581,7 @@ private void initializeViewMenu() { return; } - if (!Ecdar.getSimulationHandler().isSimulationRunning()) { + if (!simulationHandler.isSimulationRunning()) { ArrayList queryOptions = Ecdar.getProject().getQueries().stream().map(Query::getQuery).collect(Collectors.toCollection(ArrayList::new)); if (!simulationInitializationDialog.getController().simulationComboBox.getItems().equals(queryOptions)) { simulationInitializationDialog.getController().simulationComboBox.getItems().setAll(queryOptions); @@ -705,7 +625,6 @@ private void initializeViewMenu() { break; } } - scaling.selectToggle(scaleM); // Necessary to avoid project pane appearing off-screen scaling.selectToggle((RadioMenuItem) matchingToggle); }); @@ -1000,7 +919,6 @@ private void enterEditorMode() { leftPane.getChildren().add(projectPane); rightPane.getChildren().clear(); rightPane.getChildren().add(queryPane); - scaling.selectToggle(scaleM); // temporary fix for scaling issue // Enable or disable the menu items that can be used when in the simulator // updateMenuItems(); diff --git a/src/main/java/ecdar/controllers/ProcessController.java b/src/main/java/ecdar/controllers/ProcessController.java index 6ce42fc2..dc94965c 100755 --- a/src/main/java/ecdar/controllers/ProcessController.java +++ b/src/main/java/ecdar/controllers/ProcessController.java @@ -2,6 +2,7 @@ import com.jfoenix.controls.JFXRippler; import ecdar.abstractions.*; +import ecdar.presentations.ComponentPresentation; import ecdar.presentations.SimEdgePresentation; import ecdar.presentations.SimLocationPresentation; import javafx.animation.Interpolator; @@ -16,6 +17,9 @@ import javafx.scene.layout.*; import javafx.scene.shape.Circle; import javafx.util.Duration; + +import org.fxmisc.richtext.LineNumberFactory; +import org.fxmisc.richtext.StyleClassedTextArea; import org.kordamp.ikonli.javafx.FontIcon; import java.math.BigDecimal; @@ -31,6 +35,7 @@ public class ProcessController extends ModelController implements Initializable public Pane modelContainerLocation; public JFXRippler toggleValuesButton; public VBox valueArea; + public StyleClassedTextArea declarationTextArea; public FontIcon toggleValueButtonIcon; private ObjectProperty component; @@ -46,6 +51,8 @@ public class ProcessController extends ModelController implements Initializable @Override public void initialize(final URL location, final ResourceBundle resources) { component = new SimpleObjectProperty<>(new Component(true)); + // add line numbers to the declaration text area + declarationTextArea.setParagraphGraphicFactory(LineNumberFactory.get(declarationTextArea)); initializeValues(); } @@ -196,6 +203,10 @@ public void setComponent(final Component component){ modelContainerEdge.getChildren().add(ep); edgePresentationMap.put(edge, ep); }); + + declarationTextArea.appendText(component.getDeclarationsText()); + declarationTextArea.setStyleSpans(0, ComponentPresentation.computeHighlighting(getComponent().getDeclarationsText())); + declarationTextArea.getStyleClass().add("component-declaration"); } public void toggleValues(final MouseEvent mouseEvent) { diff --git a/src/main/java/ecdar/controllers/SimEdgeController.java b/src/main/java/ecdar/controllers/SimEdgeController.java index 2ca0048f..239583a5 100755 --- a/src/main/java/ecdar/controllers/SimEdgeController.java +++ b/src/main/java/ecdar/controllers/SimEdgeController.java @@ -74,11 +74,29 @@ public void initialize(final URL location, final ResourceBundle resources) { this.unhighlight(); } }); + + // When an edge updates highlight property, + // we want to update the view to reflect current highlight property + edge.get().isHighlightedForReachabilityProperty().addListener(v -> { + if(edge.get().getIsHighlightedForReachability()) { + this.highlightSpecialColor(); + } else { + this.unhighlight(); + } + }); }); ensureNailsInFront(); } + public void highlightSpecialColor() { + edgeRoot.getChildren().forEach(node -> { + if(node instanceof Highlightable) { + ((Highlightable) node).highlightPurple(); + } + }); + } + private void ensureNailsInFront() { // When ever changes happens to the children of the edge root force nails in front and other elements to back edgeRoot.getChildren().addListener((ListChangeListener) c -> { diff --git a/src/main/java/ecdar/controllers/SimLocationController.java b/src/main/java/ecdar/controllers/SimLocationController.java index d5112fc4..9921a057 100755 --- a/src/main/java/ecdar/controllers/SimLocationController.java +++ b/src/main/java/ecdar/controllers/SimLocationController.java @@ -4,6 +4,7 @@ import ecdar.Ecdar; import ecdar.abstractions.*; import ecdar.backend.BackendHelper; +import ecdar.backend.SimulationHandler; import ecdar.presentations.DropDownMenu; import ecdar.presentations.SimLocationPresentation; import ecdar.presentations.SimTagPresentation; @@ -43,6 +44,7 @@ public class SimLocationController implements Initializable { public Line nameTagLine; public Line invariantTagLine; private DropDownMenu dropDownMenu; + private SimulationHandler simulationHandler; @Override public void initialize(final URL location, final ResourceBundle resources) { @@ -57,6 +59,8 @@ public void initialize(final URL location, final ResourceBundle resources) { // Scale x and y 1:1 (based on the x-scale) scaleContent.scaleYProperty().bind(scaleContent.scaleXProperty()); initializeMouseControls(); + + simulationHandler = Ecdar.getSimulationHandler(); } private void initializeMouseControls() { @@ -82,12 +86,29 @@ private void initializeMouseControls() { public void initializeDropDownMenu(){ dropDownMenu = new DropDownMenu(root); - dropDownMenu.addClickableListElement("Is " + getLocation().getId() + " reachable?", event -> { + dropDownMenu.addClickableListElement("Is " + getLocation().getId() + " reachable from initial state?", event -> { + // Generate the query from the backend + final String reachabilityQuery = BackendHelper.getLocationReachableQuery(getLocation(), getComponent(), simulationHandler.getSimulationQuery()); + + // Add proper comment + final String reachabilityComment = "Is " + getLocation().getMostDescriptiveIdentifier() + " reachable from initial state?"; + + // Add new query for this location + final Query query = new Query(reachabilityQuery, reachabilityComment, QueryState.UNKNOWN); + query.setType(QueryType.REACHABILITY); + + // execute query + Ecdar.getQueryExecutor().executeQuery(query); + + dropDownMenu.hide(); + }); + + dropDownMenu.addClickableListElement("Is " + getLocation().getId() + " reachable from current locations?", event -> { // Generate the query from the backend - final String reachabilityQuery = BackendHelper.getLocationReachableQuery(getLocation(), getComponent(), SimulatorController.getSimulationQuery()); + final String reachabilityQuery = BackendHelper.getLocationReachableQuery(getLocation(), getComponent(), simulationHandler.getSimulationQuery(), simulationHandler.getCurrentState()); // Add proper comment - final String reachabilityComment = "Is " + getLocation().getMostDescriptiveIdentifier() + " reachable?"; + final String reachabilityComment = "Is " + getLocation().getMostDescriptiveIdentifier() + " reachable from current locations?"; // Add new query for this location final Query query = new Query(reachabilityQuery, reachabilityComment, QueryState.UNKNOWN); diff --git a/src/main/java/ecdar/controllers/SimulationInitializationDialogController.java b/src/main/java/ecdar/controllers/SimulationInitializationDialogController.java index a34787a6..6d5c017b 100644 --- a/src/main/java/ecdar/controllers/SimulationInitializationDialogController.java +++ b/src/main/java/ecdar/controllers/SimulationInitializationDialogController.java @@ -2,6 +2,8 @@ import com.jfoenix.controls.JFXButton; import com.jfoenix.controls.JFXComboBox; +import ecdar.Ecdar; +import ecdar.backend.SimulationHandler; import javafx.fxml.Initializable; import java.net.URL; @@ -14,20 +16,21 @@ public class SimulationInitializationDialogController implements Initializable { public JFXButton cancelButton; public JFXButton startButton; - public static List ListOfComponents = new ArrayList<>(); + private SimulationHandler simulationHandler; + /** - * Function gets list of components to simulation - * and saves it in the public static ListOfComponents + * Function extracts data from simulation initialization (query and list of components to simulation) + * and saves it */ public void setSimulationData(){ // set simulation query - SimulatorController.setSimulationQuery(simulationComboBox.getSelectionModel().getSelectedItem()); + simulationHandler.setSimulationQuery(simulationComboBox.getSelectionModel().getSelectedItem()); // set list of components involved in simulation - ListOfComponents.clear(); + simulationHandler.clearComponentsInSimulation(); // pattern filters out all components by ignoring operators. Pattern pattern = Pattern.compile("([\\w]*)", Pattern.CASE_INSENSITIVE); - Matcher matcher = pattern.matcher(SimulatorController.getSimulationQuery()); + Matcher matcher = pattern.matcher(simulationHandler.getSimulationQuery()); List listOfComponentsToSimulate = new ArrayList<>(); //Adds all found components to list. while(matcher.find()){ @@ -35,10 +38,10 @@ public void setSimulationData(){ listOfComponentsToSimulate.add(matcher.group()); } } - ListOfComponents = listOfComponentsToSimulate; + simulationHandler.setComponentsInSimulation(listOfComponentsToSimulate); } public void initialize(URL location, ResourceBundle resources) { - + simulationHandler = Ecdar.getSimulationHandler(); } } diff --git a/src/main/java/ecdar/controllers/SimulatorController.java b/src/main/java/ecdar/controllers/SimulatorController.java index cacdc467..d9d91536 100644 --- a/src/main/java/ecdar/controllers/SimulatorController.java +++ b/src/main/java/ecdar/controllers/SimulatorController.java @@ -5,7 +5,6 @@ import ecdar.backend.SimulationHandler; import ecdar.presentations.SimulatorOverviewPresentation; import ecdar.simulation.SimulationState; -import ecdar.utility.colors.Color; import javafx.beans.property.DoubleProperty; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleDoubleProperty; @@ -19,8 +18,8 @@ import java.util.ResourceBundle; public class SimulatorController implements Initializable { - private static String simulationQuery; public StackPane root; + private SimulationHandler simulationHandler; public SimulatorOverviewPresentation overviewPresentation; public StackPane toolbar; @@ -34,6 +33,7 @@ public void initialize(URL location, ResourceBundle resources) { root.widthProperty().addListener((observable, oldValue, newValue) -> width.setValue(newValue)); root.heightProperty().addListener((observable, oldValue, newValue) -> height.setValue(newValue)); firstTimeInSimulator = true; + simulationHandler = Ecdar.getSimulationHandler(); } /** @@ -43,33 +43,32 @@ public void initialize(URL location, ResourceBundle resources) { * - Adding the components which are going to be used in the simulation to */ public void willShow() { - final SimulationHandler sm = Ecdar.getSimulationHandler(); boolean shouldSimulationBeReset = true; // If the user left a trace, continue from that trace - if (sm.traceLog.size() >= 2) { + if (simulationHandler.traceLog.size() >= 2) { shouldSimulationBeReset = false; } // If the composition is not the same as previous simulation, reset the simulation if (!(overviewPresentation.getController().getComponentObservableList().hashCode() == - findComponentsInCurrentSimulation(SimulationInitializationDialogController.ListOfComponents).hashCode())) { + findComponentsInCurrentSimulation(simulationHandler.getComponentsInSimulation()).hashCode())) { shouldSimulationBeReset = true; } - if (shouldSimulationBeReset || firstTimeInSimulator || sm.currentState.get() == null) { + if (shouldSimulationBeReset || firstTimeInSimulator || simulationHandler.currentState.get() == null) { resetSimulation(); - sm.initialStep(); + simulationHandler.initialStep(); } overviewPresentation.getController().addProcessesToGroup(); // If the simulation continues, highligt the current state and available edges - if (sm.currentState.get() != null && !shouldSimulationBeReset) { - overviewPresentation.getController().highlightProcessState(sm.currentState.get()); - overviewPresentation.getController().highlightAvailableEdges(sm.currentState.get()); + if (simulationHandler.currentState.get() != null && !shouldSimulationBeReset) { + overviewPresentation.getController().highlightProcessState(simulationHandler.currentState.get()); + overviewPresentation.getController().highlightAvailableEdges(simulationHandler.currentState.get()); } } @@ -79,7 +78,7 @@ public void willShow() { * {@link SimulatorOverviewController#processContainer} and adding the processes of the new simulation. */ private void resetSimulation() { - List listOfComponentsForSimulation = findComponentsInCurrentSimulation(SimulationInitializationDialogController.ListOfComponents); + List listOfComponentsForSimulation = findComponentsInCurrentSimulation(simulationHandler.getComponentsInSimulation()); overviewPresentation.getController().clearOverview(); overviewPresentation.getController().getComponentObservableList().clear(); overviewPresentation.getController().getComponentObservableList().addAll(listOfComponentsForSimulation); @@ -106,6 +105,7 @@ private List findComponentsInCurrentSimulation(List queryComp } } } + simulationHandler.setSimulationComponents((ArrayList) SelectedComponents); return SelectedComponents; } @@ -115,7 +115,7 @@ private List findComponentsInCurrentSimulation(List queryComp public void resetCurrentSimulation() { overviewPresentation.getController().removeProcessesFromGroup(); resetSimulation(); - Ecdar.getSimulationHandler().resetToInitialLocation(); + simulationHandler.resetToInitialLocation(); overviewPresentation.getController().addProcessesToGroup(); } @@ -134,11 +134,4 @@ public static DoubleProperty getHeightProperty() { public static void setSelectedState(SimulationState selectedState) { SimulatorController.selectedState.set(selectedState); } - public static void setSimulationQuery(String query) { - simulationQuery = query; - } - - public static String getSimulationQuery(){ - return simulationQuery; - } } diff --git a/src/main/java/ecdar/controllers/SimulatorOverviewController.java b/src/main/java/ecdar/controllers/SimulatorOverviewController.java index b4815785..84b9a37a 100644 --- a/src/main/java/ecdar/controllers/SimulatorOverviewController.java +++ b/src/main/java/ecdar/controllers/SimulatorOverviewController.java @@ -2,6 +2,7 @@ import ecdar.Ecdar; import ecdar.abstractions.*; +import ecdar.backend.SimulationHandler; import ecdar.presentations.ProcessPresentation; import ecdar.simulation.SimulationState; import ecdar.simulation.Transition; @@ -62,9 +63,12 @@ public class SimulatorOverviewController implements Initializable { private boolean resetZoom = false; private boolean isMaxZoomInReached = false; private boolean isMaxZoomOutReached = false; + private SimulationHandler simulationHandler; @Override public void initialize(final URL location, final ResourceBundle resources) { + simulationHandler = Ecdar.getSimulationHandler(); + groupContainer = new Group(); processContainer = new FlowPane(); //In case that the processContainer gets moved around we have to keep in into place. @@ -105,7 +109,7 @@ private void initializeProcessContainer() { } } // Highlight the current state when the processes change - highlightProcessState(Ecdar.getSimulationHandler().currentState.get()); // ToDo NIELS: Throws NullPointerException inside method due to currentState + highlightProcessState(simulationHandler.currentState.get()); // ToDo NIELS: Throws NullPointerException inside method due to currentState processContainer.getChildren().addAll(processes.values()); processPresentations.putAll(processes); }); @@ -129,8 +133,8 @@ void clearOverview() { * Setup listeners for displaying clock and variable values on the {@link ProcessPresentation} */ private void initializeSimulationVariables() { - Ecdar.getSimulationHandler().getSimulationVariables().addListener((InvalidationListener) obs -> { - Ecdar.getSimulationHandler().getSimulationVariables().forEach((s, bigDecimal) -> { + simulationHandler.getSimulationVariables().addListener((InvalidationListener) obs -> { + simulationHandler.getSimulationVariables().forEach((s, bigDecimal) -> { if (!s.equals("t(0)")) {// As t(0) does not belong to any process final String[] spittedString = s.split("\\."); // If the process containing the var is not there we just skip it @@ -140,9 +144,9 @@ private void initializeSimulationVariables() { } }); }); - Ecdar.getSimulationHandler().getSimulationClocks().addListener((InvalidationListener) obs -> { + simulationHandler.getSimulationClocks().addListener((InvalidationListener) obs -> { if (processPresentations.size() == 0) return; - Ecdar.getSimulationHandler().getSimulationClocks().forEach((s, bigDecimal) -> { + simulationHandler.getSimulationClocks().forEach((s, bigDecimal) -> { if (!s.equals("t(0)")) {// As t(0) does not belong to any process final String[] spittedString = s.split("\\."); // If the process containing the clock is not there we just skip it @@ -301,11 +305,11 @@ private void handleWidthOnScale(final Number oldValue, final Number newValue) { * Initializer method to setup listeners that handle highlighting when selected/current state/transition changes */ private void initializeHighlighting() { - Ecdar.getSimulationHandler().selectedEdge.addListener((observable, oldEdge, newEdge) -> { + simulationHandler.selectedEdge.addListener((observable, oldEdge, newEdge) -> { unhighlightProcesses(); }); - Ecdar.getSimulationHandler().currentState.addListener((observable, oldState, newState) -> { + simulationHandler.currentState.addListener((observable, oldState, newState) -> { if (newState == null) { return; } @@ -357,8 +361,8 @@ public void unhighlightProcesses() { */ public void highlightProcessState(final SimulationState state) { if (state == null) return; - for (int i = 0; i < state.getLocations().size(); i++) { - final Pair loc = state.getLocations().get(i); + + for(var loc : state.getLocations()){ processPresentations.values().stream() .filter(p -> p.getController().getComponent().getName().equals(loc.getKey())) @@ -372,14 +376,14 @@ public ObservableList getComponentObservableList() { public void highlightAvailableEdges(SimulationState state) { // unhighlight all edges - for (Pair edge : state.getEdges()) { + for (Pair edge : state.getEnabledEdges()) { processPresentations.values().stream() .forEach(p -> p.getController().getComponent().getEdges().stream() .forEach(e -> e.setIsHighlighted(false))); } // highlight available edges in the given state - for (Pair edge : state.getEdges()) { + for (Pair edge : state.getEnabledEdges()) { processPresentations.values().stream() .forEach(p -> p.getController().getComponent().getEdges().stream() .forEach(e -> { @@ -389,5 +393,4 @@ public void highlightAvailableEdges(SimulationState state) { })); } } - } diff --git a/src/main/java/ecdar/controllers/TracePaneElementController.java b/src/main/java/ecdar/controllers/TracePaneElementController.java index 6898dfad..ce5cea76 100755 --- a/src/main/java/ecdar/controllers/TracePaneElementController.java +++ b/src/main/java/ecdar/controllers/TracePaneElementController.java @@ -13,9 +13,7 @@ import javafx.fxml.FXML; import javafx.fxml.Initializable; import javafx.scene.control.Label; -import javafx.scene.layout.AnchorPane; -import javafx.scene.layout.HBox; -import javafx.scene.layout.VBox; +import javafx.scene.layout.*; import org.kordamp.ikonli.javafx.FontIcon; import java.net.URL; @@ -40,10 +38,13 @@ public class TracePaneElementController implements Initializable { private SimpleBooleanProperty isTraceExpanded = new SimpleBooleanProperty(false); private Map transitionPresentationMap = new LinkedHashMap<>(); private SimpleIntegerProperty numberOfSteps = new SimpleIntegerProperty(0); + private SimulationHandler simulationHandler; @Override public void initialize(URL location, ResourceBundle resources) { - Ecdar.getSimulationHandler().getTraceLog().addListener((ListChangeListener) c -> { + simulationHandler = Ecdar.getSimulationHandler(); + + simulationHandler.getTraceLog().addListener((ListChangeListener) c -> { while (c.next()) { for (final SimulationState state : c.getAddedSubList()) { if (state != null) insertTraceState(state, true); @@ -102,6 +103,16 @@ private void showTrace() { root.getChildren().remove(traceSummary); } + private void previewStep(final SimulationState state) { + traceList.getChildren().forEach(trace -> trace.setOpacity(1)); + int i = traceList.getChildren().size() - 1; + while (traceList.getChildren().get(i) != transitionPresentationMap.get(state)) { + traceList.getChildren().get(i).setOpacity(0.4); + i--; + } + simulationHandler.currentState.set(state); + } + /** * Instantiates a {@link TransitionPresentation} for a {@link SimulationState} and adds it to the view * @@ -114,9 +125,8 @@ private void insertTraceState(final SimulationState state, final boolean shouldA transitionPresentation.setOnMouseReleased(event -> { event.consume(); - final SimulationHandler simHandler = Ecdar.getSimulationHandler(); - if (simHandler == null) return; - Ecdar.getSimulationHandler().selectStateFromLog(state); + if (simulationHandler == null) return; + previewStep(state); }); EventHandler mouseEntered = transitionPresentation.getOnMouseEntered(); @@ -159,13 +169,21 @@ private String traceString(SimulationState state) { String locationName = loc.getId(); if (i == length - 1) { title.append(locationName); - } else { + } else { title.append(locationName).append(", "); } } - title.append(")"); - - return title.toString(); + title.append(")\n"); + + StringBuilder clocks = new StringBuilder(); + for (var constraint : state.getState().getFederation().getDisjunction().getConjunctions(0).getConstraintsList()) { + var x = constraint.getX().getClockName(); + var y = constraint.getY().getClockName(); + var c = constraint.getC(); + var strict = constraint.getStrict(); + clocks.append(x).append(" - ").append(y).append(strict ? " < " : " <= ").append(c).append("\n"); + } + return title.toString() + clocks.toString(); } /** diff --git a/src/main/java/ecdar/controllers/TransitionPaneElementController.java b/src/main/java/ecdar/controllers/TransitionPaneElementController.java index 30b355ff..3772f46b 100755 --- a/src/main/java/ecdar/controllers/TransitionPaneElementController.java +++ b/src/main/java/ecdar/controllers/TransitionPaneElementController.java @@ -4,6 +4,7 @@ import com.jfoenix.controls.JFXTextField; import ecdar.Ecdar; import ecdar.abstractions.Edge; +import ecdar.backend.SimulationHandler; import ecdar.simulation.Transition; import ecdar.presentations.TransitionPresentation; import javafx.beans.property.SimpleBooleanProperty; @@ -40,9 +41,11 @@ public class TransitionPaneElementController implements Initializable { private SimpleBooleanProperty isTransitionExpanded = new SimpleBooleanProperty(false); private Map transitionPresentationMap = new HashMap<>(); private SimpleObjectProperty delay = new SimpleObjectProperty<>(BigDecimal.ZERO); + private SimulationHandler simulationHandler; @Override public void initialize(URL location, ResourceBundle resources) { + simulationHandler = Ecdar.getSimulationHandler(); initializeTransitionExpand(); initializeDelayChooser(); } @@ -175,7 +178,7 @@ private void expandTransitions() { */ @FXML private void restartSimulation() { - Ecdar.getSimulationHandler().resetToInitialLocation(); + simulationHandler.resetToInitialLocation(); } /** diff --git a/src/main/java/ecdar/model_canvas/arrow_heads/SimpleArrowHead.java b/src/main/java/ecdar/model_canvas/arrow_heads/SimpleArrowHead.java index aa8ebc5d..1c9541c5 100644 --- a/src/main/java/ecdar/model_canvas/arrow_heads/SimpleArrowHead.java +++ b/src/main/java/ecdar/model_canvas/arrow_heads/SimpleArrowHead.java @@ -92,6 +92,12 @@ public void highlight() { rightArrow.setStroke(SelectHelper.getNormalColor()); } + @Override + public void highlightPurple() { + leftArrow.setStroke(Color.DEEP_PURPLE.getColor(Color.Intensity.I900)); + rightArrow.setStroke(Color.DEEP_PURPLE.getColor(Color.Intensity.I900)); + } + /*** * Removes the highlight by setting the stroke to grey */ diff --git a/src/main/java/ecdar/presentations/Link.java b/src/main/java/ecdar/presentations/Link.java index 1c3cc98e..8b5f6de4 100644 --- a/src/main/java/ecdar/presentations/Link.java +++ b/src/main/java/ecdar/presentations/Link.java @@ -156,4 +156,9 @@ public void deselect() { public void unhighlight() { shownLine.setStroke(Color.GREY.getColor(Color.Intensity.I900)); } + + @Override + public void highlightPurple() { + shownLine.setStroke(Color.DEEP_PURPLE.getColor(Color.Intensity.I900)); + } } diff --git a/src/main/java/ecdar/presentations/LocationPresentation.java b/src/main/java/ecdar/presentations/LocationPresentation.java index 57def2ab..d7cfea10 100644 --- a/src/main/java/ecdar/presentations/LocationPresentation.java +++ b/src/main/java/ecdar/presentations/LocationPresentation.java @@ -72,72 +72,6 @@ public LocationPresentation(final Location location, final Component component, initializeDeleteShakeAnimation(); initializeShakeAnimation(); initializeCircle(); - initializeReachabilityStyle(); - } - - private void initializeReachabilityStyle() { - - controller.reachabilityStatus.radiusProperty().bind(controller.circle.radiusProperty()); - controller.reachabilityStatus.setFill(javafx.scene.paint.Color.TRANSPARENT); - - final Function getColor = (reachability -> { - if (reachability == null || reachability.equals(Location.Reachability.REACHABLE)) - return javafx.scene.paint.Color.TRANSPARENT; - if (reachability.equals(Location.Reachability.EXCLUDED)) - return javafx.scene.paint.Color.TRANSPARENT; - if (reachability.equals(Location.Reachability.UNREACHABLE)) - return Color.RED.getColor(Color.Intensity.I700, 0.75); - if (reachability.equals(Location.Reachability.UNKNOWN)) - return Color.YELLOW.getColor(Color.Intensity.I900, 0.75); - - return null; - }); - - final Function getRadius = (reachability -> { - if (reachability == null || reachability.equals(Location.Reachability.REACHABLE)) return 0; - if (reachability.equals(Location.Reachability.EXCLUDED)) return 0; - if (reachability.equals(Location.Reachability.UNREACHABLE)) return 6; - if (reachability.equals(Location.Reachability.UNKNOWN)) return 3; - return 0; - }); - - final Consumer updateReachability = (reachability) -> { - if(reachability == null) return; - - final Interpolator interpolator = Interpolator.SPLINE(0.645, 0.045, 0.355, 1); - - // Shrink previous indicator - final Timeline shrinkAnimation = new Timeline(); - final KeyValue kv1 = new KeyValue(controller.reachabilityStatus.strokeWidthProperty(), controller.reachabilityStatus.getStrokeWidth(), interpolator); - final KeyValue kv2 = new KeyValue(controller.reachabilityStatus.strokeWidthProperty(), 0, interpolator); - final KeyFrame kf1 = new KeyFrame(millis(0), kv1); - final KeyFrame kf2 = new KeyFrame(millis(200), kv2); - shrinkAnimation.getKeyFrames().addAll(kf1, kf2); - - // Shrink current indicator - final Timeline enlargeAnimation = new Timeline(); - final KeyValue kv3 = new KeyValue(controller.reachabilityStatus.strokeWidthProperty(), 0, interpolator); - final KeyValue kv4 = new KeyValue(controller.reachabilityStatus.strokeWidthProperty(), getRadius.apply(reachability), interpolator); - final KeyFrame kf3 = new KeyFrame(millis(0), kv3); - final KeyFrame kf4 = new KeyFrame(millis(200), kv4); - enlargeAnimation.getKeyFrames().addAll(kf3, kf4); - - shrinkAnimation.setOnFinished(event -> { - // Update the color once the shrink animation is done - controller.reachabilityStatus.setStroke(getColor.apply(reachability)); - - // Play the enlarge animation - enlargeAnimation.play(); - }); - - shrinkAnimation.play(); - }; - - controller.getLocation().reachabilityProperty().addListener((obs, oldReachability, newReachability) -> { - updateReachability.accept(newReachability); - }); - - updateReachability.accept(controller.getLocation().getReachability()); } private void initializeIdLabel() { diff --git a/src/main/java/ecdar/presentations/SimEdgePresentation.java b/src/main/java/ecdar/presentations/SimEdgePresentation.java old mode 100755 new mode 100644 index 540a7db0..1456adc8 --- a/src/main/java/ecdar/presentations/SimEdgePresentation.java +++ b/src/main/java/ecdar/presentations/SimEdgePresentation.java @@ -20,6 +20,7 @@ public class SimEdgePresentation extends Group { public SimEdgePresentation(final Edge edge, final Component component) { controller = new EcdarFXMLLoader().loadAndGetController("SimEdgePresentation.fxml", this); + var simulationHandler = Ecdar.getSimulationHandler(); controller.setEdge(edge); this.edge.bind(controller.edgeProperty()); @@ -27,18 +28,18 @@ public SimEdgePresentation(final Edge edge, final Component component) { controller.setComponent(component); this.component.bind(controller.componentProperty()); - // when hovering mouse the curser should change to hand + // when hovering mouse the cursor should change to hand this.setOnMouseEntered(event -> { - if (Ecdar.getSimulationHandler().currentState.get().getEdges().contains(new Pair<>(component.getName(), edge.getId()))) + if (simulationHandler.currentState.get().getEnabledEdges().contains(new Pair<>(component.getName(), edge.getId()))) this.getScene().setCursor(javafx.scene.Cursor.HAND); }); this.setOnMouseExited(event -> this.getScene().setCursor(javafx.scene.Cursor.DEFAULT)); - // when clicking the edge the edge should be selected and the simulation should take next step (if the edge is enabled) + // when clicking the edge, the edge should be selected and the simulation should take next step (if the edge is enabled) this.setOnMouseClicked(event -> { - if (Ecdar.getSimulationHandler().currentState.get().getEdges().contains(new Pair<>(component.getName(), edge.getId()))) { - Ecdar.getSimulationHandler().selectedEdge.set(edge); - Ecdar.getSimulationHandler().nextStep(); + if (simulationHandler.currentState.get().getEnabledEdges().contains(new Pair<>(component.getName(), edge.getId()))) { + simulationHandler.selectedEdge.set(edge); + simulationHandler.nextStep(); } }); } diff --git a/src/main/java/ecdar/presentations/SimNailPresentation.java b/src/main/java/ecdar/presentations/SimNailPresentation.java index 250f2f9d..1baaf392 100755 --- a/src/main/java/ecdar/presentations/SimNailPresentation.java +++ b/src/main/java/ecdar/presentations/SimNailPresentation.java @@ -238,6 +238,16 @@ public void highlight() { controller.nailCircle.setStroke(color.getColor(intensity.next(2))); } + @Override + public void highlightPurple() { + final Color color1 = Color.DEEP_PURPLE; + final Color.Intensity intensity = Color.Intensity.I500; + + // Set the color + controller.nailCircle.setFill(color1.getColor(intensity)); + controller.nailCircle.setStroke(color1.getColor(intensity.next(2))); + } + /** * Removes the highlight from the nail */ diff --git a/src/main/java/ecdar/presentations/SystemRootPresentation.java b/src/main/java/ecdar/presentations/SystemRootPresentation.java index 46cb4281..0e1ad166 100644 --- a/src/main/java/ecdar/presentations/SystemRootPresentation.java +++ b/src/main/java/ecdar/presentations/SystemRootPresentation.java @@ -140,4 +140,9 @@ public void highlight() { public void unhighlight() { dyeFromSystemColor(); } + + @Override + public void highlightPurple() { + dye(Color.DEEP_PURPLE, SelectHelper.SELECT_COLOR_INTENSITY_NORMAL); + } } diff --git a/src/main/java/ecdar/simulation/SimulationState.java b/src/main/java/ecdar/simulation/SimulationState.java index a6b4f5e3..538a0441 100644 --- a/src/main/java/ecdar/simulation/SimulationState.java +++ b/src/main/java/ecdar/simulation/SimulationState.java @@ -3,9 +3,13 @@ import EcdarProtoBuf.ObjectProtos; import EcdarProtoBuf.ObjectProtos.State; import ecdar.Ecdar; +import ecdar.backend.SimulationHandler; +import javafx.collections.ObservableMap; import javafx.util.Pair; +import java.math.BigDecimal; import java.util.ArrayList; +import java.util.Map; public class SimulationState { // locations and edges are saved as key-value pair where key is component name and value = id @@ -29,10 +33,17 @@ public SimulationState(ObjectProtos.DecisionPoint decisionPoint) { state = decisionPoint.getSource(); } + /** + * All the clocks connected to the current simulation. + * + * @return a {@link Map} where the component name (String) is the key, and the location name is the value (String) + * @see SimulationHandler#getSimulationVariables() + */ public ArrayList> getLocations() { return locations; } - public ArrayList> getEdges() { + + public ArrayList> getEnabledEdges() { return edges; } @@ -51,4 +62,15 @@ private String getComponentName(String id) { } throw new RuntimeException("Could not find component name for edge with id " + id); } + + /** + * All the clocks connected to the current simulation. + * + * @return a {@link Map} where the name (String) is the key, and a {@link BigDecimal} is the clock value + * @see SimulationHandler#getSimulationVariables() + */ + public ObservableMap getSimulationClocks() { + // TODO move clocks from SimulationHandler to SimulationState + return Ecdar.getSimulationHandler().getSimulationClocks(); + } } diff --git a/src/main/java/ecdar/utility/Highlightable.java b/src/main/java/ecdar/utility/Highlightable.java index 2195978c..3d699a29 100644 --- a/src/main/java/ecdar/utility/Highlightable.java +++ b/src/main/java/ecdar/utility/Highlightable.java @@ -7,4 +7,7 @@ public interface Highlightable { public void highlight(); public void unhighlight(); + default void highlightPurple(){ + highlight(); + } } diff --git a/src/main/resources/ecdar/presentations/ProcessPresentation.fxml b/src/main/resources/ecdar/presentations/ProcessPresentation.fxml index 6b4b8f77..9628aa27 100755 --- a/src/main/resources/ecdar/presentations/ProcessPresentation.fxml +++ b/src/main/resources/ecdar/presentations/ProcessPresentation.fxml @@ -6,6 +6,7 @@ +

- + + +
diff --git a/src/main/resources/ecdar/presentations/TracePaneElementPresentation.fxml b/src/main/resources/ecdar/presentations/TracePaneElementPresentation.fxml index 0b83620e..0a233345 100755 --- a/src/main/resources/ecdar/presentations/TracePaneElementPresentation.fxml +++ b/src/main/resources/ecdar/presentations/TracePaneElementPresentation.fxml @@ -46,4 +46,7 @@ styleClass="caption"/> + + + diff --git a/src/main/resources/ecdar/presentations/TransitionPresentation.fxml b/src/main/resources/ecdar/presentations/TransitionPresentation.fxml index 3fa1c5cf..900aa8d4 100755 --- a/src/main/resources/ecdar/presentations/TransitionPresentation.fxml +++ b/src/main/resources/ecdar/presentations/TransitionPresentation.fxml @@ -2,6 +2,7 @@ + - - diff --git a/src/test/java/ecdar/simulation/ReachabilityTest.java b/src/test/java/ecdar/simulation/ReachabilityTest.java index 6e645470..eee49d98 100644 --- a/src/test/java/ecdar/simulation/ReachabilityTest.java +++ b/src/test/java/ecdar/simulation/ReachabilityTest.java @@ -3,13 +3,17 @@ import ecdar.Ecdar; import ecdar.abstractions.Component; import ecdar.abstractions.Location; +import ecdar.backend.BackendDriver; import ecdar.backend.BackendHelper; +import ecdar.backend.SimulationHandler; import ecdar.controllers.SimulationInitializationDialogController; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; +import java.util.ArrayList; +import java.util.List; import java.util.regex.Pattern; import static org.junit.jupiter.api.Assertions.*; @@ -30,12 +34,19 @@ void reachabilityQuerySyntaxTestSuccess() { var component = new Component(); component.setName("C1"); - SimulationInitializationDialogController.ListOfComponents.clear(); - SimulationInitializationDialogController.ListOfComponents.add("C1"); - SimulationInitializationDialogController.ListOfComponents.add("C2"); - SimulationInitializationDialogController.ListOfComponents.add("C3"); + SimulationHandler simulationHandler = new SimulationHandler(new BackendDriver()); + + List components = new ArrayList<>(); + components.add("C1"); + components.add("C2"); + components.add("C3"); + + simulationHandler.setComponentsInSimulation(components); + + Ecdar.setSimulationHandler(simulationHandler); var result = BackendHelper.getLocationReachableQuery(location, component, "query"); + assertTrue(result.matches(regex)); } @@ -46,51 +57,107 @@ void reachabilityQueryLocationPosition1TestSuccess() { var component = new Component(); component.setName("C1"); - SimulationInitializationDialogController.ListOfComponents.clear(); - SimulationInitializationDialogController.ListOfComponents.add("C1"); - SimulationInitializationDialogController.ListOfComponents.add("C2"); - SimulationInitializationDialogController.ListOfComponents.add("C3"); + SimulationHandler simulationHandler = new SimulationHandler(new BackendDriver()); + + List components = new ArrayList<>(); + components.add("C1"); + components.add("C2"); + components.add("C3"); + + simulationHandler.setComponentsInSimulation(components); + + Ecdar.setSimulationHandler(simulationHandler); var result = BackendHelper.getLocationReachableQuery(location, component, "query"); - var indexOfLocation = result.indexOf('[') + 1; - var output = result.charAt(indexOfLocation); - assertEquals(output, location.getId().charAt(0)); + + int indexOfOpeningBracket = result.indexOf('['); + int indexOfComma = 0; + + for (int i = 0; i < result.length(); i++) { + if (result.charAt(i) == ',') { + indexOfComma = i; + break; + } + } + + var output = result.substring(indexOfOpeningBracket + 1, indexOfComma); + assertEquals(output, location.getId()); } @Test void reachabilityQueryLocationPosition2TestSuccess() { var location = new Location(); - location.setId("L1"); + location.setId("L123"); var component = new Component(); - component.setName("C1"); + component.setName("C2"); + + SimulationHandler simulationHandler = new SimulationHandler(new BackendDriver()); - SimulationInitializationDialogController.ListOfComponents.clear(); - SimulationInitializationDialogController.ListOfComponents.add("C2"); - SimulationInitializationDialogController.ListOfComponents.add("C1"); - SimulationInitializationDialogController.ListOfComponents.add("C3"); + List components = new ArrayList<>(); + components.add("C1"); + components.add("C2"); + components.add("C3"); + + simulationHandler.setComponentsInSimulation(components); + + Ecdar.setSimulationHandler(simulationHandler); var result = BackendHelper.getLocationReachableQuery(location, component, "query"); - var indexOfLocation = result.indexOf(',') + 1; - var output = result.charAt(indexOfLocation); - assertEquals(output, location.getId().charAt(0)); + + int indexOfFirstComma = 0; + int indexofSecondComma = 0; + boolean commaSeen = false; + + for (int i = 0; i < result.length(); i++) { + if (result.charAt(i) == ',') { + if(commaSeen){ + indexofSecondComma = i; + } + else { + indexOfFirstComma = i; + commaSeen = true; + } + } + } + + var output = result.substring(indexOfFirstComma + 1,indexofSecondComma); + assertEquals(output, location.getId()); } @Test void reachabilityQueryLocationPosition3TestSuccess() { var location = new Location(); - location.setId("L1"); + location.setId("L12345"); var component = new Component(); - component.setName("C1"); + component.setName("C3"); + + SimulationHandler simulationHandler = new SimulationHandler(new BackendDriver()); - SimulationInitializationDialogController.ListOfComponents.clear(); - SimulationInitializationDialogController.ListOfComponents.add("C2"); - SimulationInitializationDialogController.ListOfComponents.add("C3"); - SimulationInitializationDialogController.ListOfComponents.add("C1"); + List components = new ArrayList<>(); + components.add("C1"); + components.add("C2"); + components.add("C3"); + + simulationHandler.setComponentsInSimulation(components); + + Ecdar.setSimulationHandler(simulationHandler); var query = BackendHelper.getLocationReachableQuery(location, component, "query"); - var indexOfLocation = query.indexOf(']') - 2; - var output = query.charAt(indexOfLocation); - assertEquals(output, location.getId().charAt(0)); + + int indexOfLastComma = 0; + + for (int i = query.length()-1; i > 0; i--) { + if (query.charAt(i) == ',') { + indexOfLastComma = i; + break; + } + } + + int indexOfClosingBracket = query.indexOf(']'); + + var output = query.substring(indexOfLastComma + 1,indexOfClosingBracket); + + assertEquals(output, location.getId()); } @Test @@ -100,20 +167,28 @@ void reachabilityQueryNumberOfLocationsTestSuccess() { var component = new Component(); component.setName("C1"); - SimulationInitializationDialogController.ListOfComponents.clear(); - SimulationInitializationDialogController.ListOfComponents.add("C2"); - SimulationInitializationDialogController.ListOfComponents.add("C1"); - SimulationInitializationDialogController.ListOfComponents.add("C3"); - SimulationInitializationDialogController.ListOfComponents.add("C4"); + SimulationHandler simulationHandler = new SimulationHandler(new BackendDriver()); + + List components = new ArrayList<>(); + components.add("C1"); + components.add("C2"); + components.add("C3"); + components.add("C4"); + + simulationHandler.setComponentsInSimulation(components); + + Ecdar.setSimulationHandler(simulationHandler); var query = BackendHelper.getLocationReachableQuery(location, component, "query"); - int underscoreCount = 0; + int commaCount = 0; for (int i = 0; i < query.length(); i++) { - if (query.charAt(i) == '_') { - underscoreCount++; + if (query.charAt(i) == ',') { + commaCount++; } } - assertEquals(SimulationInitializationDialogController.ListOfComponents.size(), underscoreCount + 1); + int expected = commaCount + 1; + + assertEquals(expected, Ecdar.getSimulationHandler().getComponentsInSimulation().size()); } } diff --git a/src/test/java/ecdar/simulation/SimulationTest.java b/src/test/java/ecdar/simulation/SimulationTest.java index 3d295663..8cd51066 100644 --- a/src/test/java/ecdar/simulation/SimulationTest.java +++ b/src/test/java/ecdar/simulation/SimulationTest.java @@ -27,8 +27,8 @@ public class SimulationTest { public GrpcCleanupRule grpcCleanup = new GrpcCleanupRule(); private final String serverName = InProcessServerBuilder.generateName(); -/* -// TODO fix this test + + // TODO fix this test @Test public void testGetInitialStateHighlightsTheInitialLocation() { final List components = generateComponentsWithInitialLocations(); @@ -92,7 +92,7 @@ public void takeSimulationStep(EcdarProtoBuf.QueryProtos.SimulationStepRequest r Assertions.fail("Exception encountered: " + e.getMessage()); } } -*/ + private List generateComponentsWithInitialLocations() { List comps = new ArrayList<>(); for (int i = 0; i < 2; i++) { diff --git a/src/test/java/ecdar/ui/UiSwitcherTest.java b/src/test/java/ecdar/ui/UiSwitcherTest.java new file mode 100644 index 00000000..73f39b7f --- /dev/null +++ b/src/test/java/ecdar/ui/UiSwitcherTest.java @@ -0,0 +1,40 @@ +package ecdar.ui; + +import org.junit.jupiter.api.Test; +import org.testfx.util.WaitForAsyncUtils; + +import com.jfoenix.controls.JFXComboBox; + +import ecdar.Ecdar; +import ecdar.abstractions.Query; +import ecdar.abstractions.QueryState; +import ecdar.controllers.EcdarController; +import ecdar.controllers.EcdarController.Mode; +import javafx.application.Platform; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; + +public class UiSwitcherTest extends TestFXBase { + @Test + public void UiSwitcherNoComponentsTest() { + Ecdar.setUpForTest(); + WaitForAsyncUtils.waitForFxEvents(); + clickOn("#switchGuiView"); + WaitForAsyncUtils.waitForFxEvents(); + JFXComboBox simDialog = lookup("#simulationComboBox").query(); + WaitForAsyncUtils.waitForFxEvents(); + assertFalse(simDialog.isShowing()); + + WaitForAsyncUtils.waitForFxEvents(); + Platform.runLater(() -> Ecdar.getProject().getQueries().add(new Query("(Component1)", "null", QueryState.UNKNOWN))); + WaitForAsyncUtils.waitForFxEvents(); + clickOn("#switchGuiView"); + WaitForAsyncUtils.waitForFxEvents(); + JFXComboBox simDialogComboBox = lookup("#simulationComboBox").query(); + Platform.runLater(() -> simDialogComboBox.selectionModelProperty().get().select(0)); + WaitForAsyncUtils.waitForFxEvents(); + clickOn("#startButton"); + WaitForAsyncUtils.waitForFxEvents(); + assertEquals(EcdarController.currentMode.get(), Mode.Simulator); + } +} From dafdd5124178c4e28b4e4cdfbe0fb5cac24a6baf Mon Sep 17 00:00:00 2001 From: Niels Vistisen Date: Fri, 20 Jan 2023 15:30:35 +0100 Subject: [PATCH 134/158] Proto commit update --- src/main/proto | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/proto b/src/main/proto index e42e35db..476f1afd 160000 --- a/src/main/proto +++ b/src/main/proto @@ -1 +1 @@ -Subproject commit e42e35db63efacd7ab42aecd17c9a52b291c7be9 +Subproject commit 476f1afd62596ec6a841227a3caa5fd40abc9c6e From 9d34a35c17917d0b44aeb5854cc4e967d536cf3b Mon Sep 17 00:00:00 2001 From: Niels Vistisen Date: Fri, 27 Jan 2023 10:23:02 +0100 Subject: [PATCH 135/158] WIP: Moved BackendInstance --- src/main/java/ecdar/abstractions/Query.java | 2 -- src/main/java/ecdar/backend/BackendConnection.java | 2 -- src/main/java/ecdar/backend/BackendDriver.java | 1 - .../{abstractions => backend}/BackendInstance.java | 10 ++++++++-- src/main/java/ecdar/backend/GrpcRequest.java | 2 -- .../ecdar/controllers/BackendInstanceController.java | 2 +- .../controllers/BackendOptionsDialogController.java | 2 +- src/main/java/ecdar/controllers/CanvasController.java | 6 +----- src/main/java/ecdar/controllers/EcdarController.java | 3 +-- src/main/java/ecdar/controllers/QueryController.java | 2 +- .../presentations/BackendInstancePresentation.java | 2 +- 11 files changed, 14 insertions(+), 20 deletions(-) rename src/main/java/ecdar/{abstractions => backend}/BackendInstance.java (92%) diff --git a/src/main/java/ecdar/abstractions/Query.java b/src/main/java/ecdar/abstractions/Query.java index 0cee640e..b480cb07 100644 --- a/src/main/java/ecdar/abstractions/Query.java +++ b/src/main/java/ecdar/abstractions/Query.java @@ -9,10 +9,8 @@ import com.google.gson.JsonObject; import javafx.application.Platform; import javafx.beans.property.*; -import javafx.collections.ObservableList; import java.util.List; -import java.util.Objects; import java.util.function.BiConsumer; import java.util.function.Consumer; diff --git a/src/main/java/ecdar/backend/BackendConnection.java b/src/main/java/ecdar/backend/BackendConnection.java index 4819965f..5bd19550 100644 --- a/src/main/java/ecdar/backend/BackendConnection.java +++ b/src/main/java/ecdar/backend/BackendConnection.java @@ -1,10 +1,8 @@ package ecdar.backend; import EcdarProtoBuf.EcdarBackendGrpc; -import ecdar.abstractions.BackendInstance; import io.grpc.ManagedChannel; -import java.io.IOException; import java.util.concurrent.TimeUnit; public class BackendConnection { diff --git a/src/main/java/ecdar/backend/BackendDriver.java b/src/main/java/ecdar/backend/BackendDriver.java index 8bfaaf76..3a171680 100644 --- a/src/main/java/ecdar/backend/BackendDriver.java +++ b/src/main/java/ecdar/backend/BackendDriver.java @@ -2,7 +2,6 @@ import EcdarProtoBuf.EcdarBackendGrpc; import ecdar.Ecdar; -import ecdar.abstractions.BackendInstance; import io.grpc.*; import org.springframework.util.SocketUtils; diff --git a/src/main/java/ecdar/abstractions/BackendInstance.java b/src/main/java/ecdar/backend/BackendInstance.java similarity index 92% rename from src/main/java/ecdar/abstractions/BackendInstance.java rename to src/main/java/ecdar/backend/BackendInstance.java index 763586f3..feb66ca9 100644 --- a/src/main/java/ecdar/abstractions/BackendInstance.java +++ b/src/main/java/ecdar/backend/BackendInstance.java @@ -1,4 +1,4 @@ -package ecdar.abstractions; +package ecdar.backend; import com.google.gson.JsonObject; import ecdar.utility.serialize.Serializable; @@ -117,7 +117,13 @@ public void deserialize(final JsonObject json) { setName(json.getAsJsonPrimitive(NAME).getAsString()); setLocal(json.getAsJsonPrimitive(IS_LOCAL).getAsBoolean()); setDefault(json.getAsJsonPrimitive(IS_DEFAULT).getAsBoolean()); - setIsThreadSafe(json.getAsJsonPrimitive(IS_THREAD_SAFE).getAsBoolean()); + + try { // ToDo NIELS: Decide to either do this or simply reload defaults + setIsThreadSafe(json.getAsJsonPrimitive(IS_THREAD_SAFE).getAsBoolean()); + } catch (NullPointerException e) { + setIsThreadSafe(false); + } + setBackendLocation(json.getAsJsonPrimitive(LOCATION).getAsString()); setPortStart(json.getAsJsonPrimitive(PORT_RANGE_START).getAsInt()); setPortEnd(json.getAsJsonPrimitive(PORT_RANGE_END).getAsInt()); diff --git a/src/main/java/ecdar/backend/GrpcRequest.java b/src/main/java/ecdar/backend/GrpcRequest.java index 100bd810..d6c667f8 100644 --- a/src/main/java/ecdar/backend/GrpcRequest.java +++ b/src/main/java/ecdar/backend/GrpcRequest.java @@ -1,7 +1,5 @@ package ecdar.backend; -import ecdar.abstractions.BackendInstance; - import java.util.function.Consumer; public class GrpcRequest { diff --git a/src/main/java/ecdar/controllers/BackendInstanceController.java b/src/main/java/ecdar/controllers/BackendInstanceController.java index c6752c81..c4f5337b 100644 --- a/src/main/java/ecdar/controllers/BackendInstanceController.java +++ b/src/main/java/ecdar/controllers/BackendInstanceController.java @@ -3,7 +3,7 @@ import com.jfoenix.controls.JFXCheckBox; import com.jfoenix.controls.JFXRippler; import com.jfoenix.controls.JFXTextField; -import ecdar.abstractions.BackendInstance; +import ecdar.backend.BackendInstance; import javafx.application.Platform; import javafx.beans.property.BooleanProperty; import javafx.fxml.FXML; diff --git a/src/main/java/ecdar/controllers/BackendOptionsDialogController.java b/src/main/java/ecdar/controllers/BackendOptionsDialogController.java index d939c4e1..49357be5 100644 --- a/src/main/java/ecdar/controllers/BackendOptionsDialogController.java +++ b/src/main/java/ecdar/controllers/BackendOptionsDialogController.java @@ -5,7 +5,7 @@ import com.jfoenix.controls.JFXButton; import com.jfoenix.controls.JFXRippler; import ecdar.Ecdar; -import ecdar.abstractions.BackendInstance; +import ecdar.backend.BackendInstance; import ecdar.backend.BackendHelper; import ecdar.presentations.BackendInstancePresentation; import javafx.fxml.Initializable; diff --git a/src/main/java/ecdar/controllers/CanvasController.java b/src/main/java/ecdar/controllers/CanvasController.java index e3c4937b..d2b6acc6 100644 --- a/src/main/java/ecdar/controllers/CanvasController.java +++ b/src/main/java/ecdar/controllers/CanvasController.java @@ -164,7 +164,7 @@ public void updateOffset(final Boolean shouldHave) { */ private void onActiveModelChanged(final HighLevelModelObject oldObject, final HighLevelModelObject newObject) { // If old object is a component or system, add to map in order to remember coordinate - if ((oldObject instanceof Component || oldObject instanceof EcdarSystem)) { + if (oldObject instanceof Component || oldObject instanceof EcdarSystem) { ModelObjectTranslateMap.put(oldObject, new Pair<>(modelPane.getTranslateX(), modelPane.getTranslateY())); } @@ -180,10 +180,6 @@ private void onActiveModelChanged(final HighLevelModelObject oldObject, final Hi if (newObject instanceof Component) { activeComponentPresentation = new ComponentPresentation((Component) newObject); modelPane.getChildren().add(activeComponentPresentation); - - // To avoid NullPointerException on initial model - if (oldObject != null) zoomHelper.resetZoom(); - } else if (newObject instanceof Declarations) { activeComponentPresentation = null; modelPane.getChildren().add(new DeclarationPresentation((Declarations) newObject)); diff --git a/src/main/java/ecdar/controllers/EcdarController.java b/src/main/java/ecdar/controllers/EcdarController.java index cffb562f..353c5964 100644 --- a/src/main/java/ecdar/controllers/EcdarController.java +++ b/src/main/java/ecdar/controllers/EcdarController.java @@ -6,6 +6,7 @@ import ecdar.Ecdar; import ecdar.abstractions.*; import ecdar.backend.BackendHelper; +import ecdar.backend.BackendInstance; import ecdar.backend.SimulationHandler; import ecdar.code_analysis.CodeAnalysis; import ecdar.mutation.models.MutationTestPlan; @@ -44,8 +45,6 @@ import java.io.IOException; import java.net.URL; import java.util.*; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import java.util.stream.Collectors; public class EcdarController implements Initializable { diff --git a/src/main/java/ecdar/controllers/QueryController.java b/src/main/java/ecdar/controllers/QueryController.java index 6c584aa4..53ec4f68 100644 --- a/src/main/java/ecdar/controllers/QueryController.java +++ b/src/main/java/ecdar/controllers/QueryController.java @@ -3,7 +3,7 @@ import com.jfoenix.controls.JFXComboBox; import com.jfoenix.controls.JFXRippler; import com.jfoenix.controls.JFXTextField; -import ecdar.abstractions.BackendInstance; +import ecdar.backend.BackendInstance; import ecdar.abstractions.Query; import ecdar.abstractions.QueryType; import ecdar.backend.BackendHelper; diff --git a/src/main/java/ecdar/presentations/BackendInstancePresentation.java b/src/main/java/ecdar/presentations/BackendInstancePresentation.java index cc884646..67801874 100644 --- a/src/main/java/ecdar/presentations/BackendInstancePresentation.java +++ b/src/main/java/ecdar/presentations/BackendInstancePresentation.java @@ -2,7 +2,7 @@ import com.jfoenix.controls.JFXRippler; import ecdar.Ecdar; -import ecdar.abstractions.BackendInstance; +import ecdar.backend.BackendInstance; import ecdar.controllers.BackendInstanceController; import ecdar.utility.colors.Color; import javafx.application.Platform; From dab6432bbd502a8831cab2afa0a25fb3210eaff7 Mon Sep 17 00:00:00 2001 From: Niels Vistisen Date: Fri, 27 Jan 2023 11:56:00 +0100 Subject: [PATCH 136/158] WIP: Issue with having to double press components to update active model FIXED --- src/main/java/ecdar/backend/QueryHandler.java | 2 +- .../ecdar/controllers/EcdarController.java | 22 +++---------------- .../ecdar/controllers/EditorController.java | 2 +- .../controllers/ProjectPaneController.java | 11 +++++----- .../controllers/SimulatorController.java | 8 +------ 5 files changed, 11 insertions(+), 34 deletions(-) diff --git a/src/main/java/ecdar/backend/QueryHandler.java b/src/main/java/ecdar/backend/QueryHandler.java index b9b9f71e..40142d64 100644 --- a/src/main/java/ecdar/backend/QueryHandler.java +++ b/src/main/java/ecdar/backend/QueryHandler.java @@ -266,7 +266,7 @@ private void addGeneratedComponent(Component newComponent) { }, "Created new component: " + newComponent.getName(), "add-circle"); } - EcdarController.getActiveCanvasPresentation().getController().setActiveModel(newComponent); + EcdarController.setActiveModelForActiveCanvas(newComponent); }); } } diff --git a/src/main/java/ecdar/controllers/EcdarController.java b/src/main/java/ecdar/controllers/EcdarController.java index 353c5964..08cadc9a 100644 --- a/src/main/java/ecdar/controllers/EcdarController.java +++ b/src/main/java/ecdar/controllers/EcdarController.java @@ -218,9 +218,6 @@ public static void setActiveCanvasPresentation(CanvasPresentation newActiveCanva public static void setActiveModelForActiveCanvas(HighLevelModelObject newActiveModel) { getActiveCanvasPresentation().getController().setActiveModel(newActiveModel); - - // Change zoom level to fit new active model - Platform.runLater(() -> getActiveCanvasPresentation().getController().zoomHelper.zoomToFit()); } public static void setTemporaryComponentWatermarkVisibility(boolean visibility) { @@ -359,11 +356,10 @@ private void initializeKeybindings() { final Component newComponent = new Component(true); UndoRedoStack.pushAndPerform(() -> { // Perform Ecdar.getProject().getComponents().add(newComponent); + setActiveModelForActiveCanvas(newComponent); }, () -> { // Undo Ecdar.getProject().getComponents().remove(newComponent); }, "Created new component: " + newComponent.getName(), "add-circle"); - - getActiveCanvasPresentation().getController().setActiveModel(newComponent); }); KeyboardTracker.registerKeybind(KeyboardTracker.CREATE_COMPONENT, binding); @@ -830,7 +826,7 @@ private void initializeNewMutationTestObjectMenuItem() { UndoRedoStack.pushAndPerform(() -> { // Perform Ecdar.getProject().getTestPlans().add(newPlan); - getActiveCanvasPresentation().getController().setActiveModel(newPlan); + setActiveModelForActiveCanvas(newPlan); }, () -> { // Undo Ecdar.getProject().getTestPlans().remove(newPlan); }, "Created new mutation test plan", ""); @@ -848,7 +844,7 @@ private static void createNewProject() { Ecdar.projectDirectory.set(null); Ecdar.getProject().reset(); - getActiveCanvasPresentation().getController().setActiveModel(Ecdar.getProject().getComponents().get(0)); + setActiveModelForActiveCanvas(Ecdar.getProject().getComponents().get(0)); UndoRedoStack.clear(); @@ -909,18 +905,11 @@ private void initializeFileExportAsPng() { * Only enter if the mode is not already Editor */ private void enterEditorMode() { -// ToDo NIELS: Consider implementing willShow and willHide to handle general elements that should only be available for one of the modes -// editorPresentation.getController().willShow(); -// simulatorPresentation.getController().willHide(); - borderPane.setCenter(editorPresentation); leftPane.getChildren().clear(); leftPane.getChildren().add(projectPane); rightPane.getChildren().clear(); rightPane.getChildren().add(queryPane); - - // Enable or disable the menu items that can be used when in the simulator -// updateMenuItems(); } /** @@ -928,8 +917,6 @@ private void enterEditorMode() { * Only enter if the mode is not already Simulator */ private void enterSimulatorMode() { -// ToDo NIELS: Consider implementing willShow and willHide to handle general elements that should only be available for one of the modes -// ecdarPresentation.getController().willHide(); simulatorPresentation.getController().willShow(); borderPane.setCenter(simulatorPresentation); @@ -937,9 +924,6 @@ private void enterSimulatorMode() { leftPane.getChildren().add(leftSimPane); rightPane.getChildren().clear(); rightPane.getChildren().add(rightSimPane); - - // Enable or disable the menu items that can be used when in the simulator -// updateMenuItems(); } /** diff --git a/src/main/java/ecdar/controllers/EditorController.java b/src/main/java/ecdar/controllers/EditorController.java index 27ab5245..15557dea 100644 --- a/src/main/java/ecdar/controllers/EditorController.java +++ b/src/main/java/ecdar/controllers/EditorController.java @@ -74,7 +74,7 @@ public void setActiveCanvasPresentation(CanvasPresentation newActiveCanvasPresen } public void setActiveModelForActiveCanvas(HighLevelModelObject newActiveModel) { - EcdarController.getActiveCanvasPresentation().getController().setActiveModel(newActiveModel); + EcdarController.setActiveModelForActiveCanvas(newActiveModel); // Change zoom level to fit new active model Platform.runLater(() -> EcdarController.getActiveCanvasPresentation().getController().zoomHelper.zoomToFit()); diff --git a/src/main/java/ecdar/controllers/ProjectPaneController.java b/src/main/java/ecdar/controllers/ProjectPaneController.java index 8923cad2..b2e3d4e6 100644 --- a/src/main/java/ecdar/controllers/ProjectPaneController.java +++ b/src/main/java/ecdar/controllers/ProjectPaneController.java @@ -205,12 +205,12 @@ private void initializeMoreInformationDropDown(final FilePresentation filePresen Ecdar.getProject().getTempComponents().remove(model); model.setTemporary(false); Ecdar.getProject().getComponents().add((Component) model); - EcdarController.getActiveCanvasPresentation().getController().setActiveModel(model); + EcdarController.setActiveModelForActiveCanvas(model); }, () -> { // Undo Ecdar.getProject().getComponents().remove(model); model.setTemporary(true); Ecdar.getProject().getTempComponents().add((Component) model); - EcdarController.getActiveCanvasPresentation().getController().setActiveModel(model); + EcdarController.setActiveModelForActiveCanvas(model); }, "Add component " + model.getName(), "add"); moreInformationDropDown.hide(); } else { @@ -222,7 +222,7 @@ private void initializeMoreInformationDropDown(final FilePresentation filePresen Ecdar.getProject().getTempComponents().remove(model); model.setTemporary(false); Ecdar.getProject().getComponents().add((Component) model); - EcdarController.getActiveCanvasPresentation().getController().setActiveModel(model); + EcdarController.setActiveModelForActiveCanvas(model); model.setName(newName); }, () -> { // Undo Ecdar.getProject().getComponents().remove(model); @@ -363,11 +363,11 @@ private void createComponentClicked() { UndoRedoStack.pushAndPerform(() -> { // Perform Ecdar.getProject().getComponents().add(newComponent); + EcdarController.setActiveModelForActiveCanvas(newComponent); }, () -> { // Undo Ecdar.getProject().getComponents().remove(newComponent); }, "Created new component: " + newComponent.getName(), "add-circle"); - EcdarController.setActiveModelForActiveCanvas(newComponent); updateColorsOnFilePresentations(); } @@ -380,11 +380,10 @@ private void createSystemClicked() { UndoRedoStack.pushAndPerform(() -> { // Perform Ecdar.getProject().getSystemsProperty().add(newSystem); + EcdarController.setActiveModelForActiveCanvas(newSystem); }, () -> { // Undo Ecdar.getProject().getSystemsProperty().remove(newSystem); }, "Created new system: " + newSystem.getName(), "add-circle"); - - EcdarController.setActiveModelForActiveCanvas(newSystem); } /** diff --git a/src/main/java/ecdar/controllers/SimulatorController.java b/src/main/java/ecdar/controllers/SimulatorController.java index d9d91536..f2689c0b 100644 --- a/src/main/java/ecdar/controllers/SimulatorController.java +++ b/src/main/java/ecdar/controllers/SimulatorController.java @@ -43,14 +43,8 @@ public void initialize(URL location, ResourceBundle resources) { * - Adding the components which are going to be used in the simulation to */ public void willShow() { - boolean shouldSimulationBeReset = true; - - - // If the user left a trace, continue from that trace - if (simulationHandler.traceLog.size() >= 2) { - shouldSimulationBeReset = false; - } + boolean shouldSimulationBeReset = simulationHandler.traceLog.size() < 2; // If the composition is not the same as previous simulation, reset the simulation if (!(overviewPresentation.getController().getComponentObservableList().hashCode() == From 77332e7cb04a80f047c54b5557475c4366444f7b Mon Sep 17 00:00:00 2001 From: Niels Vistisen Date: Fri, 27 Jan 2023 12:50:53 +0100 Subject: [PATCH 137/158] Clean-up based on PR walkthrough --- build.gradle | 1 - .../java/ecdar/abstractions/Location.java | 16 --- src/main/java/ecdar/abstractions/Nail.java | 7 -- .../java/ecdar/backend/BackendDriver.java | 28 ----- .../java/ecdar/backend/SimulationHandler.java | 55 ++-------- .../BackendOptionsDialogController.java | 5 +- .../ecdar/controllers/SimEdgeController.java | 16 ++- .../SimulatorOverviewController.java | 101 ++++-------------- .../ecdar/presentations/DropDownMenu.java | 1 - .../ecdar/presentations/NailPresentation.java | 1 - .../ecdar/presentations/SignatureArrow.java | 10 +- .../ecdar/presentations/TagPresentation.java | 1 - 12 files changed, 42 insertions(+), 200 deletions(-) diff --git a/build.gradle b/build.gradle index ed6c8f4a..41b7c59f 100644 --- a/build.gradle +++ b/build.gradle @@ -96,7 +96,6 @@ dependencies { testImplementation 'org.mockito:mockito-junit-jupiter:4.8.0' } - test { useJUnitPlatform { includeEngines 'junit-jupiter' } diff --git a/src/main/java/ecdar/abstractions/Location.java b/src/main/java/ecdar/abstractions/Location.java index 49296a21..9d095d99 100644 --- a/src/main/java/ecdar/abstractions/Location.java +++ b/src/main/java/ecdar/abstractions/Location.java @@ -87,22 +87,6 @@ public Location(final JsonObject jsonObject) { deserialize(jsonObject); } - // ToDo NIELS: Comment in, when location should be received through ProtoBuf -// public Location(ComponentProtos.Location protoBufLocation) { -// setId(protoBufLocation.getId()); -// setNickname(protoBufLocation.getNickname()); -// setInvariant(protoBufLocation.getInvariant()); -// setType(Type.valueOf(protoBufLocation.getType())); -// setUrgency(Urgency.valueOf(protoBufLocation.getUrgency())); -// setX(protoBufLocation.getX()); -// setY(protoBufLocation.getY()); -// setColor(Color.valueOf(protoBufLocation.getColor())); -// setNicknameX(protoBufLocation.getNicknameX()); -// setNicknameY(protoBufLocation.getNicknameY()); -// setInvariantX(protoBufLocation.getInvariantX()); -// setInvariantY(protoBufLocation.getInvariantY()); -// } - /** * Generates an id for this, and binds reachability analysis. */ diff --git a/src/main/java/ecdar/abstractions/Nail.java b/src/main/java/ecdar/abstractions/Nail.java index 75375d19..5ed84d45 100644 --- a/src/main/java/ecdar/abstractions/Nail.java +++ b/src/main/java/ecdar/abstractions/Nail.java @@ -39,13 +39,6 @@ public Nail(final JsonObject jsonObject) { deserialize(jsonObject); } - // ToDo NIELS: Comment in, when location should be received through ProtoBuf -// public Nail(ComponentProtos.Nail protoBufNail) { -// setPropertyType(DisplayableEdge.PropertyType.valueOf(protoBufNail.getPropertyType())); -// setPropertyX(protoBufNail.getPropertyX()); -// setPropertyY(protoBufNail.getPropertyY()); -// } - public double getX() { return x.get(); } diff --git a/src/main/java/ecdar/backend/BackendDriver.java b/src/main/java/ecdar/backend/BackendDriver.java index 3a171680..6b05988d 100644 --- a/src/main/java/ecdar/backend/BackendDriver.java +++ b/src/main/java/ecdar/backend/BackendDriver.java @@ -108,8 +108,6 @@ private void tryStartNewBackendConnection(BackendInstance backend) { } do { - //ToDo: Refactor ProcessBuilder to accept cache-size(-cs) and thread-number(-tn) to better configure the Reveaal engine. - // Default values are acceptable for now. ProcessBuilder pb = new ProcessBuilder(backend.getBackendLocation(), "-p", hostAddress + ":" + portNumber); try { @@ -153,32 +151,6 @@ private void tryStartNewBackendConnection(BackendInstance backend) { addBackendConnection(newConnection); } -// public SimulationState getInitialSimulationState() { -// SimulationState state = new SimulationState(ObjectProtos.State.newBuilder().getDefaultInstanceForType()); -// state.getLocations().add(new Pair<>(Ecdar.getProject().getComponents().get(0).getName(), Ecdar.getProject().getComponents().get(0).getLocations().get(0).getId())); -// return state; -// } - - // private class ExecutableStartSimRequest { - // private final String componentComposition; - // private final BackendInstance backendInstance; - // private final Consumer success; - // private final Consumer failure; - // private final StartSimListener startSimListener; - // public int tries = 0; - - // public ExecutableStartSimRequest(String componentComposition, BackendInstance backendInstance, - // Consumer success, Consumer failure, StartSimListener startSimListener, - // int tries) { - // this.componentComposition = componentComposition; - // this.backendInstance = backendInstance; - // this.success = success; - // this.failure = failure; - // this.startSimListener = startSimListener; - // this.tries = tries; - // } - // } - private class GrpcRequestConsumer implements Runnable { @Override public void run() { diff --git a/src/main/java/ecdar/backend/SimulationHandler.java b/src/main/java/ecdar/backend/SimulationHandler.java index 480b63b3..4fa74b03 100644 --- a/src/main/java/ecdar/backend/SimulationHandler.java +++ b/src/main/java/ecdar/backend/SimulationHandler.java @@ -21,6 +21,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.concurrent.TimeUnit; import EcdarProtoBuf.QueryProtos.SimulationInfo; @@ -76,7 +77,6 @@ private void initializeSimulation() { this.system = getSystem(); } - /** * Reloads the whole simulation sets the initial transitions, states, etc */ @@ -113,6 +113,7 @@ public void onCompleted() { for (Component c : Ecdar.getProject().getComponents()) { comInfo.addComponents(ComponentProtos.Component.newBuilder().setJson(c.serialize().toString()).build()); } + comInfo.setComponentsHash(comInfo.getComponentsList().hashCode()); var simStartRequest = QueryProtos.SimulationStartRequest.newBuilder(); var simInfo = QueryProtos.SimulationInfo.newBuilder() @@ -126,10 +127,6 @@ public void onCompleted() { backendDriver.addRequestToExecutionQueue(request); numberOfSteps++; - - //Updates the transitions available - updateAllValues(); - } /** @@ -193,13 +190,9 @@ public void onCompleted() { }, BackendHelper.getDefaultBackendInstance()); backendDriver.addRequestToExecutionQueue(request); - - + // increments the number of steps taken during this simulation numberOfSteps++; - - - updateAllValues(); } private String getComponentName(Edge edge) { @@ -211,54 +204,20 @@ private String getComponentName(Edge edge) { } } } + throw new RuntimeException("Could not find component name for edge with id " + edge.getId()); } private int getComponentIndex (Edge edge) { for (int i = 0; i < Ecdar.getProject().getComponents().size(); i++) { - if (Ecdar.getProject().getComponents().get(i).getEdges().stream().anyMatch(p -> p.getId() == edge.getId())) { + if (Ecdar.getProject().getComponents().get(i).getEdges().stream().anyMatch(p -> Objects.equals(p.getId(), edge.getId()))) { return i; } - }; - throw new IllegalArgumentException("Edge does not belong to any component"); - } - - - /** - * Updates all values and clocks that are used doing the current simulation. - * It also stores the variables in the {@link SimulationHandler#simulationVariables} - * and the clocks in {@link SimulationHandler#simulationClocks}. - */ - private void updateAllValues() { - setSimVarAndClocks(); - } + } - /** - * Sets the value of simulation variables and clocks, based on currentConcreteState - */ - private void setSimVarAndClocks() { - // The variables and clocks are all found in the getVariables array - // the array is always of the following order: variables, clocks. - // The noOfVars variable thus also functions as an offset for the clocks in the getVariables array -// final int noOfClocks = engine.getSystem().getNoOfClocks(); -// final int noOfVars = engine.getSystem().getNoOfVariables(); - -// for (int i = 0; i < noOfVars; i++){ -// simulationVariables.put(engine.getSystem().getVariableName(i), -// currentConcreteState.get().getVariables()[i].getValue(BigDecimal.ZERO)); -// } - - // As the clocks values starts after the variables values in currentConcreteState.get().getVariables() - // Then i needs to start where the variables ends. - // j is needed to map the correct name with the value -// for (int i = noOfVars, j = 0; i < noOfClocks + noOfVars ; i++, j++) { -// simulationClocks.put(engine.getSystem().getClockName(j), -// currentConcreteState.get().getVariables()[i].getValue(BigDecimal.ZERO)); -// } + throw new IllegalArgumentException("Edge does not belong to any component"); } - - /** * The number of total steps taken in the current simulation * diff --git a/src/main/java/ecdar/controllers/BackendOptionsDialogController.java b/src/main/java/ecdar/controllers/BackendOptionsDialogController.java index 49357be5..1950f30b 100644 --- a/src/main/java/ecdar/controllers/BackendOptionsDialogController.java +++ b/src/main/java/ecdar/controllers/BackendOptionsDialogController.java @@ -182,8 +182,11 @@ private ArrayList getPackagedBackends() { reveaal.setName("Reveaal"); reveaal.setLocal(true); reveaal.setDefault(true); + + // The engine is thread-safe, a range just adds options for finding an open port + // Only one process will be started reveaal.setPortStart(5040); - reveaal.setPortEnd(5040); + reveaal.setPortEnd(5042); reveaal.lockInstance(); reveaal.setIsThreadSafe(true); diff --git a/src/main/java/ecdar/controllers/SimEdgeController.java b/src/main/java/ecdar/controllers/SimEdgeController.java index 239583a5..ee83c513 100755 --- a/src/main/java/ecdar/controllers/SimEdgeController.java +++ b/src/main/java/ecdar/controllers/SimEdgeController.java @@ -68,7 +68,7 @@ public void initialize(final URL location, final ResourceBundle resources) { // When an edge updates highlight property, // we want to update the view to reflect current highlight property edge.get().isHighlightedProperty().addListener(v -> { - if(edge.get().getIsHighlighted()) { + if (edge.get().getIsHighlighted()) { this.highlight(); } else { this.unhighlight(); @@ -78,7 +78,7 @@ public void initialize(final URL location, final ResourceBundle resources) { // When an edge updates highlight property, // we want to update the view to reflect current highlight property edge.get().isHighlightedForReachabilityProperty().addListener(v -> { - if(edge.get().getIsHighlightedForReachability()) { + if (edge.get().getIsHighlightedForReachability()) { this.highlightSpecialColor(); } else { this.unhighlight(); @@ -91,7 +91,7 @@ public void initialize(final URL location, final ResourceBundle resources) { public void highlightSpecialColor() { edgeRoot.getChildren().forEach(node -> { - if(node instanceof Highlightable) { + if (node instanceof Highlightable) { ((Highlightable) node).highlightPurple(); } }); @@ -129,7 +129,6 @@ private ChangeListener getComponentChangeListener(final Edge newEdge) BindingHelper.bind(link, simpleArrowHead, newEdge.getSourceCircular(), newComponent.getBox().getXProperty(), newComponent.getBox().getYProperty()); } else if (newEdge.getTargetCircular() != null) { - edgeRoot.getChildren().add(simpleArrowHead); final Circular[] previous = {newEdge.getSourceCircular()}; @@ -158,7 +157,6 @@ private ChangeListener getComponentChangeListener(final Edge newEdge) // Changes are made to the nails list newEdge.getNails().addListener(getNailsChangeListener(newEdge, newComponent)); - }; } @@ -205,7 +203,6 @@ private ListChangeListener getNailsChangeListener(final Edge newEdge, fina if (isHoveringEdge.get()) { enlargeNail.accept(newNail); } - } else { // The previous last link must end in the new nail final Link lastLink = links.get(links.size() - 1); @@ -372,7 +369,8 @@ public ObjectProperty componentProperty() { /** * Colors the edge model - * @param color the new color of the edge + * + * @param color the new color of the edge * @param intensity the intensity of the edge */ public void color(final Color color, final Color.Intensity intensity) { @@ -403,7 +401,7 @@ public void highlight() { // Clear the currently selected elements, so we don't have multiple things highlighted/selected SelectHelper.clearSelectedElements(); edgeRoot.getChildren().forEach(node -> { - if(node instanceof Highlightable) { + if (node instanceof Highlightable) { ((Highlightable) node).highlight(); } }); @@ -415,7 +413,7 @@ public void highlight() { @Override public void unhighlight() { edgeRoot.getChildren().forEach(node -> { - if(node instanceof Highlightable) { + if (node instanceof Highlightable) { ((Highlightable) node).unhighlight(); } }); diff --git a/src/main/java/ecdar/controllers/SimulatorOverviewController.java b/src/main/java/ecdar/controllers/SimulatorOverviewController.java index 84b9a37a..bb3678b7 100644 --- a/src/main/java/ecdar/controllers/SimulatorOverviewController.java +++ b/src/main/java/ecdar/controllers/SimulatorOverviewController.java @@ -50,9 +50,9 @@ public class SimulatorOverviewController implements Initializable { private static final double MAX_ZOOM_OUT = 0.5; /** - * Offset such that the view does not overlap with the scroll bar on the right hand sig. + * Offset such that the view does not overlap with the scroll bar on the right-hand side. */ - private static final int SUPER_SPECIAL_SCROLLPANE_OFFSET = 20; + private static final int SCROLLPANE_OFFSET = 20; private final ObservableList componentArrayList = FXCollections.observableArrayList(); private final ObservableMap processPresentations = FXCollections.observableHashMap(); @@ -109,7 +109,7 @@ private void initializeProcessContainer() { } } // Highlight the current state when the processes change - highlightProcessState(simulationHandler.currentState.get()); // ToDo NIELS: Throws NullPointerException inside method due to currentState + highlightProcessState(simulationHandler.currentState.get()); processContainer.getChildren().addAll(processes.values()); processPresentations.putAll(processes); }); @@ -195,21 +195,6 @@ private void initializeZoom() { handleWidthOnScale(oldValue, newValue); }); - - // to support pinch zooming - //TODO this should be fixed at as it does not work as it should - /* - processContainer.setOnZoom(event -> { - //Tries to zoom in/out but max is reached - if(event.getZoomFactor() >= 1 && isMaxZoomInReached) return; - if(event.getZoomFactor() < 1 && isMaxZoomOutReached) return; - - isMaxZoomInReached = false; - isMaxZoomOutReached = false; - - processContainer.setScaleX(processContainer.getScaleX() * event.getZoomFactor()); - processContainer.setScaleY(processContainer.getScaleY() * event.getZoomFactor()); - });*/ } /** @@ -226,59 +211,15 @@ private void initializeWindowResizing() { processContainer.setMinWidth(width); processContainer.setMaxWidth(width); final double deltaWidth = newValue.doubleValue() - groupContainer.layoutBoundsProperty().get().getWidth(); - processContainer.setMinWidth(processContainer.getWidth() + (deltaWidth - SUPER_SPECIAL_SCROLLPANE_OFFSET) * (1 + (1 - processContainer.getScaleX()))); - processContainer.setMaxWidth(processContainer.getWidth() + (deltaWidth - SUPER_SPECIAL_SCROLLPANE_OFFSET) * (1 + (1 - processContainer.getScaleX()))); + processContainer.setMinWidth(processContainer.getWidth() + (deltaWidth - SCROLLPANE_OFFSET) * (1 + (1 - processContainer.getScaleX()))); + processContainer.setMaxWidth(processContainer.getWidth() + (deltaWidth - SCROLLPANE_OFFSET) * (1 + (1 - processContainer.getScaleX()))); } else { // Reset - processContainer.setMinWidth(newValue.doubleValue() - SUPER_SPECIAL_SCROLLPANE_OFFSET); - processContainer.setMaxWidth(newValue.doubleValue() - SUPER_SPECIAL_SCROLLPANE_OFFSET); + processContainer.setMinWidth(newValue.doubleValue() - SCROLLPANE_OFFSET); + processContainer.setMaxWidth(newValue.doubleValue() - SCROLLPANE_OFFSET); } }); } - /** - * Increments the {@link #processContainer} scaleX and scaleY properties - * which creates the zoom-in feeling. Resizing of the view is handled by {@link #handleWidthOnScale(Number, Number)} - * - * @see FlowPane#scaleXProperty() - * @see FlowPane#scaleYProperty() - */ - void zoomIn() { - if (isMaxZoomInReached) return; - isMaxZoomOutReached = false; - processContainer.setScaleX(processContainer.getScaleX() * SCALE_DELTA); - processContainer.setScaleY(processContainer.getScaleY() * SCALE_DELTA); - } - - - /** - * Decrements the {@link #processContainer} scaleX and scaleY properties - * which creates the zoom-in feeling. Resizing of the view is handled by {@link #handleWidthOnScale(Number, Number)} - * - * @see FlowPane#scaleXProperty() - * @see FlowPane#scaleYProperty() - */ - void zoomOut() { - if (isMaxZoomOutReached) return; - isMaxZoomInReached = false; - processContainer.setScaleX(processContainer.getScaleX() * (1 / SCALE_DELTA)); - processContainer.setScaleY(processContainer.getScaleY() * (1 / SCALE_DELTA)); - } - - /** - * Resets the scaling of the {@link #processContainer}, and hereby the zoom - * - * @see FlowPane#scaleXProperty() - * @see FlowPane#scaleYProperty() - */ - void resetZoom() { - if (processContainer.getScaleX() == 1) return; - resetZoom = true; - isMaxZoomInReached = false; - isMaxZoomOutReached = false; - processContainer.setScaleX(1); - processContainer.setScaleY(1); - } - /** * Handles the scaling of the width of the {@link #processContainer} * @@ -288,8 +229,8 @@ void resetZoom() { private void handleWidthOnScale(final Number oldValue, final Number newValue) { if (resetZoom) { //Zoom reset resetZoom = false; - processContainer.setMinWidth(scrollPane.getWidth() - SUPER_SPECIAL_SCROLLPANE_OFFSET); - processContainer.setMaxWidth(scrollPane.getWidth() - SUPER_SPECIAL_SCROLLPANE_OFFSET); + processContainer.setMinWidth(scrollPane.getWidth() - SCROLLPANE_OFFSET); + processContainer.setMaxWidth(scrollPane.getWidth() - SCROLLPANE_OFFSET); } else if (oldValue.doubleValue() > newValue.doubleValue()) { //Zoom in resetZoom = false; processContainer.setMinWidth(Math.round(processContainer.getWidth() * SCALE_DELTA)); @@ -301,7 +242,7 @@ private void handleWidthOnScale(final Number oldValue, final Number newValue) { } } - /** + /** * Initializer method to setup listeners that handle highlighting when selected/current state/transition changes */ private void initializeHighlighting() { @@ -332,9 +273,7 @@ public void highlightProcessTransition(final Transition transition) { // List of all processes to show as inactive if they are not involved in a transition // Processes are removed from this list, if they have an edge in the transition final ArrayList processesToHide = new ArrayList<>(processPresentations.values()); - for (final ProcessPresentation processPresentation : processPresentations.values()) { - // Find the processes that have edges involved in this transition processPresentation.getController().highlightEdges(edges); processesToHide.remove(processPresentation); @@ -343,7 +282,6 @@ public void highlightProcessTransition(final Transition transition) { processesToHide.forEach(ProcessPresentation::showInactive); } - /** * Unhighlights all processes */ @@ -361,9 +299,8 @@ public void unhighlightProcesses() { */ public void highlightProcessState(final SimulationState state) { if (state == null) return; - - for(var loc : state.getLocations()){ - + + for (var loc : state.getLocations()) { processPresentations.values().stream() .filter(p -> p.getController().getComponent().getName().equals(loc.getKey())) .forEach(p -> p.getController().highlightLocation(loc.getValue())); @@ -376,21 +313,21 @@ public ObservableList getComponentObservableList() { public void highlightAvailableEdges(SimulationState state) { // unhighlight all edges - for (Pair edge : state.getEnabledEdges()) { + for (Pair edge : state.getEnabledEdges()) { processPresentations.values().stream() .forEach(p -> p.getController().getComponent().getEdges().stream() .forEach(e -> e.setIsHighlighted(false))); } // highlight available edges in the given state - for (Pair edge : state.getEnabledEdges()) { + for (Pair edge : state.getEnabledEdges()) { processPresentations.values().stream() .forEach(p -> p.getController().getComponent().getEdges().stream() - .forEach(e -> { - if (e.getId().equals(edge.getValue())) { - e.setIsHighlighted(true); - } - })); + .forEach(e -> { + if (e.getId().equals(edge.getValue())) { + e.setIsHighlighted(true); + } + })); } } } diff --git a/src/main/java/ecdar/presentations/DropDownMenu.java b/src/main/java/ecdar/presentations/DropDownMenu.java index 308f0033..ce2ebdab 100644 --- a/src/main/java/ecdar/presentations/DropDownMenu.java +++ b/src/main/java/ecdar/presentations/DropDownMenu.java @@ -89,7 +89,6 @@ public DropDownMenu(final Node src) { * @param width The width of the {@link DropDownMenu} */ public DropDownMenu(final Node src, final int width) { - dropDownMenuWidth.set(width); list = new VBox(); list.setStyle("-fx-background-color: white; -fx-padding: 8 0 8 0;"); diff --git a/src/main/java/ecdar/presentations/NailPresentation.java b/src/main/java/ecdar/presentations/NailPresentation.java index 41913c2a..2aa8ea18 100644 --- a/src/main/java/ecdar/presentations/NailPresentation.java +++ b/src/main/java/ecdar/presentations/NailPresentation.java @@ -187,7 +187,6 @@ private void updateSyncLabel(final TagPresentation propertyTag) { } private void initializeNailCircleColor() { - // When the color of the component updates, update the nail indicator as well controller.getComponent().colorProperty().addListener((observable) -> updateNailColor()); diff --git a/src/main/java/ecdar/presentations/SignatureArrow.java b/src/main/java/ecdar/presentations/SignatureArrow.java index 83f6328b..51db977e 100644 --- a/src/main/java/ecdar/presentations/SignatureArrow.java +++ b/src/main/java/ecdar/presentations/SignatureArrow.java @@ -55,7 +55,7 @@ private void initializeMouseEvents() { }); controller.arrowBox.onMouseExitedProperty().set(event -> { controller.mouseExited(); - this.unhighlight(); + this.unhighlight(); }); } @@ -151,14 +151,13 @@ public void unhighlight() { Color.Intensity intensity = Color.Intensity.I800; this.colorArrowComponents(color, intensity); - for(String s : controller.getComponent().getFailingIOStrings()){ - if(Objects.equals(getSignatureArrowLabel(), s) && controller.getComponent().getIsFailing()){ + for (String s : controller.getComponent().getFailingIOStrings()) { + if (Objects.equals(getSignatureArrowLabel(), s) && controller.getComponent().getIsFailing()) { this.recolorToRed(); } } } - /** * Set the color of the SignatureArrow to Color.RED */ @@ -173,7 +172,8 @@ public void recolorToGray() { Color.Intensity intensity = Color.Intensity.I800; this.colorArrowComponents(color, intensity); } - /*** + + /** * Colors the components of the arrow * @param color The Color to color the components * @param intensity The Intensity of the color diff --git a/src/main/java/ecdar/presentations/TagPresentation.java b/src/main/java/ecdar/presentations/TagPresentation.java index f5b32273..8012335f 100644 --- a/src/main/java/ecdar/presentations/TagPresentation.java +++ b/src/main/java/ecdar/presentations/TagPresentation.java @@ -289,7 +289,6 @@ public void replaceSigns() { }); } - public void requestTextFieldFocus() { final JFXTextField textField = (JFXTextField) lookup("#textField"); Platform.runLater(textField::requestFocus); From f52782f6a64b762fa45782f1e04619a2ba37971f Mon Sep 17 00:00:00 2001 From: Niels Vistisen Date: Fri, 27 Jan 2023 12:58:37 +0100 Subject: [PATCH 138/158] Missing test dependency FIXED --- build.gradle | 1 + src/test/java/ecdar/simulation/ReachabilityTest.java | 7 ++----- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/build.gradle b/build.gradle index 41b7c59f..a901ed97 100644 --- a/build.gradle +++ b/build.gradle @@ -89,6 +89,7 @@ dependencies { testImplementation "io.grpc:grpc-testing:${grpcVersion}" testImplementation 'org.junit.jupiter:junit-jupiter-api:5.9.0' + testImplementation 'org.junit.jupiter:junit-jupiter-params:5.0.0-M4' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.9.0' testImplementation 'org.testfx:testfx-junit:4.0.15-alpha' testImplementation 'org.testfx:openjfx-monocle:jdk-12.0.1+2' diff --git a/src/test/java/ecdar/simulation/ReachabilityTest.java b/src/test/java/ecdar/simulation/ReachabilityTest.java index eee49d98..06020049 100644 --- a/src/test/java/ecdar/simulation/ReachabilityTest.java +++ b/src/test/java/ecdar/simulation/ReachabilityTest.java @@ -6,17 +6,14 @@ import ecdar.backend.BackendDriver; import ecdar.backend.BackendHelper; import ecdar.backend.SimulationHandler; -import ecdar.controllers.SimulationInitializationDialogController; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; import java.util.ArrayList; import java.util.List; -import java.util.regex.Pattern; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; public class ReachabilityTest { From 42329b719db8aae57cbb767ce484a7b17b55c29b Mon Sep 17 00:00:00 2001 From: Niels Vistisen Date: Fri, 3 Feb 2023 13:29:45 +0100 Subject: [PATCH 139/158] Merge issues FIXED --- .../presentations/ProcessPresentation.java | 21 +++++++++---------- .../presentations/SimTagPresentation.java | 7 +++---- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/src/main/java/ecdar/presentations/ProcessPresentation.java b/src/main/java/ecdar/presentations/ProcessPresentation.java index 51cebab0..92b4b11d 100755 --- a/src/main/java/ecdar/presentations/ProcessPresentation.java +++ b/src/main/java/ecdar/presentations/ProcessPresentation.java @@ -1,5 +1,6 @@ package ecdar.presentations; +import ecdar.Ecdar; import ecdar.abstractions.Component; import ecdar.abstractions.Edge; import ecdar.abstractions.Location; @@ -24,8 +25,6 @@ import java.util.List; import java.util.function.BiConsumer; -import static ecdar.presentations.Grid.GRID_SIZE; - /** * The presenter of a Process which is shown in {@link SimulatorOverviewPresentation}.
* This class have some of the same functionality as {@link ComponentPresentation} and could be refactored @@ -131,10 +130,10 @@ private void initializeFrame() { controller.background.setOpacity(0.5); // Bind the missing lines that we cropped away - controller.topLeftLine.setStartX(Grid.CORNER_SIZE); + controller.topLeftLine.setStartX(CORNER_SIZE); controller.topLeftLine.setStartY(0); controller.topLeftLine.setEndX(0); - controller.topLeftLine.setEndY(Grid.CORNER_SIZE); + controller.topLeftLine.setEndY(CORNER_SIZE); controller.topLeftLine.setStroke(newColor.getColor(newIntensity.next(2))); controller.topLeftLine.setStrokeWidth(1.25); StackPane.setAlignment(controller.topLeftLine, Pos.TOP_LEFT); @@ -194,7 +193,7 @@ private void initializeToolbar() { // Set the icon color and rippler color of the toggleDeclarationButton controller.toggleValuesButton.setRipplerFill(newColor.getTextColor(newIntensity)); - controller.toolbar.setPrefHeight(Grid.TOOL_BAR_HEIGHT); + controller.toolbar.setPrefHeight(TOOLBAR_HEIGHT); controller.toggleValuesButton.setBackground(Background.EMPTY); }; controller.getComponent().colorProperty().addListener(observable -> updateColor.accept(component.getColor(), component.getColorIntensity())); @@ -237,15 +236,15 @@ ModelController getModelController() { @Deprecated double getDragAnchorMinWidth() { final Component component = controller.getComponent(); - double minWidth = 10 * GRID_SIZE; + double minWidth = Ecdar.CANVAS_PADDING *10; for (final Location location : component.getLocations()) { - minWidth = Math.max(minWidth, location.getX() + GRID_SIZE * 2); + minWidth = Math.max(minWidth, location.getX() + Ecdar.CANVAS_PADDING * 2); } for (final Edge edge : component.getEdges()) { for (final Nail nail : edge.getNails()) { - minWidth = Math.max(minWidth, nail.getX() + GRID_SIZE); + minWidth = Math.max(minWidth, nail.getX() + Ecdar.CANVAS_PADDING); } } return minWidth; @@ -261,15 +260,15 @@ ModelController getModelController() { @Deprecated double getDragAnchorMinHeight() { final Component component = controller.getComponent(); - double minHeight = 10 * GRID_SIZE; + double minHeight = Ecdar.CANVAS_PADDING * 10; for (final Location location : component.getLocations()) { - minHeight = Math.max(minHeight, location.getY() + GRID_SIZE * 2); + minHeight = Math.max(minHeight, location.getY() + Ecdar.CANVAS_PADDING * 2); } for (final Edge edge : component.getEdges()) { for (final Nail nail : edge.getNails()) { - minHeight = Math.max(minHeight, nail.getY() + GRID_SIZE); + minHeight = Math.max(minHeight, nail.getY() + Ecdar.CANVAS_PADDING); } } diff --git a/src/main/java/ecdar/presentations/SimTagPresentation.java b/src/main/java/ecdar/presentations/SimTagPresentation.java index d2916ee1..47fc7d1e 100755 --- a/src/main/java/ecdar/presentations/SimTagPresentation.java +++ b/src/main/java/ecdar/presentations/SimTagPresentation.java @@ -1,5 +1,6 @@ package ecdar.presentations; +import ecdar.Ecdar; import ecdar.abstractions.Component; import ecdar.utility.colors.Color; import ecdar.utility.helpers.LocationAware; @@ -15,8 +16,6 @@ import java.util.function.BiConsumer; -import static ecdar.presentations.Grid.GRID_SIZE; - /** * The presentation for the tag shown on a {@link SimEdgePresentation} in the {@link SimulatorOverviewPresentation}
* This class should be refactored such that code which are duplicated from {@link TagPresentation} @@ -33,7 +32,7 @@ public class SimTagPresentation extends StackPane { private LineTo l2; private LineTo l3; - private static double TAG_HEIGHT = 1.6 * GRID_SIZE; + private final static double TAG_HEIGHT = 16; // ToDo NIELS: This should be changed to follow the same value as TagPresentation /** * Constructs the {@link SimTagPresentation} @@ -63,7 +62,7 @@ private void initializeLabel() { label.layoutBoundsProperty().addListener((obs, oldBounds, newBounds) -> { double newWidth = Math.max(newBounds.getWidth(), 10); - final double res = GRID_SIZE * 2 - (newWidth % (GRID_SIZE * 2)); + final double res = Ecdar.CANVAS_PADDING * 2 - (newWidth % (Ecdar.CANVAS_PADDING * 2)); newWidth += res; l2.setX(newWidth + padding); From 8cb5296234a46bb72238753715ec377a5a8011a6 Mon Sep 17 00:00:00 2001 From: "Niels F. S. Vistisen" <42961494+Nielswps@users.noreply.github.com> Date: Sat, 15 Apr 2023 11:55:42 +0200 Subject: [PATCH 140/158] Merge of simulator and main (#17) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix so new connections are added as backend connections after they are 'ready' New connections are added to the backend connections here: https://github.com/Ecdar/Ecdar-GUI/blob/9ca653b7817706f132d88c8a773a0487e99c6038/src/main/java/ecdar/backend/BackendDriver.java#L153 But that is before the `updateComponents` query has completed, so a query can take the backend before it is ready. This results in a bunch of non-deterministic errors when executing queries, one being the `UNAVAILABLE: io exception` error. The backend should only be added on the `onCompleted` as is already done: https://github.com/Ecdar/Ecdar-GUI/blob/9ca653b7817706f132d88c8a773a0487e99c6038/src/main/java/ecdar/backend/BackendDriver.java#L171 After this small fix you can spam queries to your heart's content with no issues :) * Fix to prevent no default backend existing (#130) * `README.md` Update (#128) * WIP: ECDAR reference added, dependency section updated, and spelling fixed * WIP: Backend replaced with Engine to be consistent with naming * WIP: Engine Configuration enriched * Contributing section added and code snippets updated to be executable on Linux * Files reverted to main version * Added files removed (added through #131) * WIP: Code Organization section added * Utility and Miscellaneous sub sections added to Code Organisation * Walkthrough changes * Component Refactor (#126) * WIP: ComponentController/Presentation REFACTORED * Location placement logic extracted to separate class * Cleanup and minor refactoring * WIP: GlobalDeclarations refactored * Reachability analysis exception FIXED * WIP: Removed most of the static dependencies in component * LocationPlacer test failing * WIP: Component refactor (other elements have been refactored as a side-effect) * WIP: Verification aspects moved out of component into new class in mutation package * WIP: Location placement from dropdown FIXED * WIP: Refactoring and testing of ComponentVerificationTransformer started * WIP: LocationPlacer generalized and tested, and unnecessary initial and final location checks removed * WIP: LocationPlacer renamed to UnoccupiedSpaceFinder and offset add as parameter * Personal walkthrough changes * Universal and inconsistent location naming approach refactored * Color refactoring and initial location color fix * Walkthrough changes * Dangling engines FIX (#140) * WIP: Potential fix for dangling engines by keeping track of started connections in one place and removing this responsibility from the QueryHandler * Method rename to better follow new logic * Add started backend connections immediately and use them to find used ports --------- Co-authored-by: Sebastian Lund * Removed the contains deadlock option from the context menu in ComponentController (#139) * Query types updated to follow theory (#143) * Query types updated to follow theory * Updated bisim_minim name * Fix typo --------- Co-authored-by: Sebastian Lund * Declaration alignment FIX (#137) * Declaration resizing and alignment fixed for both single and split canvas (and minor refactoring) * Zoom keyboard shortcuts on split canvas FIXED * NullPointerException when opening existing project FIXED * WIP: Exceptions are thrown on project open and split canvas * WIP: Exceptions FIXED (Refactoring might be needed before merge) * Review suggestions * Dangling engines FIX (#140) * WIP: Potential fix for dangling engines by keeping track of started connections in one place and removing this responsibility from the QueryHandler * Method rename to better follow new logic * Add started backend connections immediately and use them to find used ports --------- Co-authored-by: Sebastian Lund * Declaration resizing and alignment fixed for both single and split canvas (and minor refactoring) * Zoom keyboard shortcuts on split canvas FIXED * NullPointerException when opening existing project FIXED * WIP: Exceptions are thrown on project open and split canvas * WIP: Exceptions FIXED (Refactoring might be needed before merge) * Review suggestions --------- Co-authored-by: Sebastian Lund * Backend to engine (#136) * WIP: ECDAR reference added, dependency section updated, and spelling fixed * WIP: Backend replaced with Engine to be consistent with naming * WIP: Engine Configuration enriched * Contributing section added and code snippets updated to be executable on Linux * Rename branched out from readme_update * File used on other branch * WIP: ECDAR reference added, dependency section updated, and spelling fixed * WIP: Backend replaced with Engine to be consistent with naming * Contributing section added and code snippets updated to be executable on Linux * File used on other branch * WIP: Backend replaced with Engine to be consistent with naming * WIP: Engine Configuration enriched * File used on other branch * Line about the deprecated mutation package added * Update src/main/java/ecdar/abstractions/Query.java Co-authored-by: Andreas K. Brandhøj * WIP: Review changes (part 1/2) * Update src/main/java/ecdar/backend/BackendHelper.java Co-authored-by: Andreas K. Brandhøj * WIP: Review changes (part 2/2) * startedEngineConnections filtering added to only account for ports of the related engine and comment updated * Found some more strings and vars to update * Review suggestions implemented --------- Co-authored-by: Andreas K. Brandhøj * Backend refactor (#147) * WIP: ECDAR reference added, dependency section updated, and spelling fixed * WIP: Backend replaced with Engine to be consistent with naming * WIP: Engine Configuration enriched * Contributing section added and code snippets updated to be executable on Linux * Rename branched out from readme_update * File used on other branch * WIP: ECDAR reference added, dependency section updated, and spelling fixed * WIP: Backend replaced with Engine to be consistent with naming * Contributing section added and code snippets updated to be executable on Linux * File used on other branch * WIP: Backend replaced with Engine to be consistent with naming * WIP: Engine Configuration enriched * File used on other branch * Line about the deprecated mutation package added * Update src/main/java/ecdar/abstractions/Query.java Co-authored-by: Andreas K. Brandhøj * WIP: Review changes (part 1/2) * Update src/main/java/ecdar/backend/BackendHelper.java Co-authored-by: Andreas K. Brandhøj * WIP: refactoring started * WIP: Review changes (part 2/2) * BackendDriver refactored to be more readable (method division) * More refactoring * WIP: Backend replaced with Engine to be consistent with naming * File used on other branch * WIP: Backend replaced with Engine to be consistent with naming * File used on other branch * BackendDriver refactored to be more readable (method division) * More refactoring * Ensure successful rebase and merge (#16) * Backend to engine (#136) * WIP: ECDAR reference added, dependency section updated, and spelling fixed * WIP: Backend replaced with Engine to be consistent with naming * WIP: Engine Configuration enriched * Contributing section added and code snippets updated to be executable on Linux * Rename branched out from readme_update * File used on other branch * WIP: ECDAR reference added, dependency section updated, and spelling fixed * WIP: Backend replaced with Engine to be consistent with naming * Contributing section added and code snippets updated to be executable on Linux * File used on other branch * WIP: Backend replaced with Engine to be consistent with naming * WIP: Engine Configuration enriched * File used on other branch * Line about the deprecated mutation package added * Update src/main/java/ecdar/abstractions/Query.java Co-authored-by: Andreas K. Brandhøj * WIP: Review changes (part 1/2) * Update src/main/java/ecdar/backend/BackendHelper.java Co-authored-by: Andreas K. Brandhøj * WIP: Review changes (part 2/2) * startedEngineConnections filtering added to only account for ports of the related engine and comment updated * Found some more strings and vars to update * Review suggestions implemented --------- Co-authored-by: Andreas K. Brandhøj * WIP: Backend replaced with Engine to be consistent with naming * File used on other branch * WIP: Backend replaced with Engine to be consistent with naming * File used on other branch * BackendDriver refactored to be more readable (method division) * More refactoring --------- Co-authored-by: Andreas K. Brandhøj * Minor naming and comment errors fixed * WIP: Exception handling added (not done) to engine handling * WIP: Comment removed * Engine process and connection exception handling finished * Final backend refactor * Review renaming implemented * Get IP added to Engine for better interface * EngineConnection initialization moved to separate class * ToDo's removed and added as issues on the repo * ToDo's removed and added as issues on the repo v2 * Shutdown of the application updated for better handling --------- Co-authored-by: Andreas K. Brandhøj * Community Standards (#151) * Contributing file, issue templates added, and mistakenly removed image reintroduced * feature label replaced with enhancement * PR template added and CONTRIBUTING.md moved to hidden github directory * PR template reformattet * CODE_OF_CONDUCT.md added * PR template rewording * Bug report template updated and link to Contributing added to readme * Updated JDK example * WIP: Ongoing merge effort (not compiling) * WIP: Compiles (simulator not working) * WIP: Sketchy engine communication for simulation functionality to ensure compatibility (WORKING) * Red color on locations updated --------- Co-authored-by: Sebastian Lund Co-authored-by: Morten Hartvigsen Co-authored-by: Andreas K. Brandhøj --- .github/CODE_OF_CONDUCT.md | 128 +++ .github/CONTRIBUTING.md | 96 +++ .github/ISSUE_TEMPLATE/bug_report.md | 44 + .github/ISSUE_TEMPLATE/feature_request.md | 25 + .github/pull_request_template.md | 4 + README.md | 80 +- examples/AGTest/Queries.json | 6 +- examples/CarAlarm/Model/Queries.json | 12 +- examples/EcdarUniversity/Queries.json | 10 +- examples/FishRetailer/Model/Queries.json | 2 +- examples/SenderReceiver/Queries.json | 6 +- presentation/EngineConfiguration.png | Bin 0 -> 23759 bytes src/main/java/ecdar/Ecdar.java | 180 ++-- .../java/ecdar/abstractions/Component.java | 788 +++++------------- .../java/ecdar/abstractions/Declarations.java | 7 +- .../ecdar/abstractions/DisplayableEdge.java | 22 +- .../java/ecdar/abstractions/EcdarModel.java | 8 - .../java/ecdar/abstractions/EcdarSystem.java | 52 +- src/main/java/ecdar/abstractions/Edge.java | 2 +- .../java/ecdar/abstractions/GroupedEdge.java | 2 - .../ecdar/abstractions/HighLevelModel.java | 78 ++ .../abstractions/HighLevelModelObject.java | 115 --- .../java/ecdar/abstractions/Location.java | 78 +- src/main/java/ecdar/abstractions/Project.java | 232 +++--- src/main/java/ecdar/abstractions/Query.java | 231 ++++- .../java/ecdar/abstractions/QueryType.java | 21 +- .../SimpleComponentsSystemDeclarations.java | 31 - .../{EcdarSystemEdge.java => SystemEdge.java} | 7 +- .../java/ecdar/backend/BackendConnection.java | 61 -- .../java/ecdar/backend/BackendDriver.java | 184 ---- .../java/ecdar/backend/BackendException.java | 26 +- .../java/ecdar/backend/BackendHelper.java | 189 +---- .../java/ecdar/backend/BackendInstance.java | 137 --- src/main/java/ecdar/backend/Engine.java | 337 ++++++++ .../java/ecdar/backend/EngineConnection.java | 84 ++ .../backend/EngineConnectionStarter.java | 119 +++ src/main/java/ecdar/backend/GrpcRequest.java | 14 +- src/main/java/ecdar/backend/QueryHandler.java | 272 ------ .../java/ecdar/backend/SimulationHandler.java | 65 +- .../ecdar/code_analysis/CodeAnalysis.java | 1 - .../java/ecdar/code_analysis/Nearable.java | 2 - .../BackendOptionsDialogController.java | 496 ----------- .../ecdar/controllers/CanvasController.java | 63 +- .../controllers/ComponentController.java | 738 ++++++++-------- .../ComponentInstanceController.java | 14 +- .../ComponentOperatorController.java | 8 +- .../controllers/DeclarationsController.java | 27 +- .../ecdar/controllers/EcdarController.java | 223 +++-- .../ecdar/controllers/EdgeController.java | 21 +- .../ecdar/controllers/EditorController.java | 100 ++- ...ler.java => EngineInstanceController.java} | 115 ++- .../EngineOptionsDialogController.java | 494 +++++++++++ .../ecdar/controllers/FileController.java | 140 +++- .../controllers/HighLevelModelController.java | 7 + .../ecdar/controllers/LocationController.java | 107 +-- .../MessageCollectionController.java | 123 +++ .../ecdar/controllers/ModeController.java | 8 + .../ecdar/controllers/ModelController.java | 278 +++++- .../controllers/MultiSyncTagController.java | 1 - .../ecdar/controllers/NailController.java | 10 +- .../ecdar/controllers/ProcessController.java | 55 +- .../controllers/ProjectPaneController.java | 343 +++++--- .../ecdar/controllers/QueryController.java | 33 +- .../controllers/QueryPaneController.java | 32 +- .../ecdar/controllers/SimEdgeController.java | 11 +- .../controllers/SimLocationController.java | 128 ++- .../ecdar/controllers/SimNailController.java | 6 +- ...ulationInitializationDialogController.java | 2 +- .../controllers/SimulatorController.java | 25 +- .../SimulatorOverviewController.java | 3 +- .../ecdar/controllers/SystemController.java | 190 ++++- .../controllers/SystemEdgeController.java | 8 +- .../controllers/SystemRootController.java | 16 +- .../TracePaneElementController.java | 4 +- .../TransitionPaneElementController.java | 2 +- .../java/ecdar/issues/ExitStatusCodes.java | 17 + .../ComponentVerificationTransformer.java | 245 ++++++ .../java/ecdar/mutation/ExportHandler.java | 2 +- .../java/ecdar/mutation/MutationHandler.java | 11 +- .../mutation/MutationTestPlanController.java | 19 +- .../MutationTestPlanPresentation.java | 19 +- .../java/ecdar/mutation/TextFlowBuilder.java | 3 +- .../mutation/models/MutationTestPlan.java | 4 +- .../operators/ChangeActionOperator.java | 3 +- .../ChangeGuardConstantOperator.java | 5 +- .../operators/ChangeGuardOpOperator.java | 3 +- .../operators/ChangeInvariantOperator.java | 3 +- .../operators/ChangeSourceOperator.java | 3 +- .../operators/ChangeTargetOperator.java | 3 +- .../operators/ChangeVarUpdateOperator.java | 44 +- .../operators/InvertResetOperator.java | 3 +- .../operators/SinkLocationOperator.java | 3 +- .../BackendInstancePresentation.java | 34 - .../BackendOptionsDialogPresentation.java | 16 - .../presentations/CanvasPresentation.java | 39 +- .../ComponentInstancePresentation.java | 88 +- .../ComponentOperatorPresentation.java | 29 +- .../presentations/ComponentPresentation.java | 238 +----- ...ion.java => DeclarationsPresentation.java} | 12 +- .../ecdar/presentations/DropDownMenu.java | 13 +- .../presentations/EcdarPresentation.java | 117 +-- .../presentations/EditorPresentation.java | 2 +- .../EngineOptionsDialogPresentation.java | 16 + .../presentations/EnginePresentation.java | 34 + .../ecdar/presentations/FilePresentation.java | 193 +---- .../HighLevelModelPresentation.java | 4 +- .../presentations/LocationPresentation.java | 117 ++- .../MessageCollectionPresentation.java | 115 +-- .../presentations/MessagePresentation.java | 7 +- .../presentations/ModelPresentation.java | 277 +----- .../MultiSyncTagPresentation.java | 16 +- .../ecdar/presentations/NailPresentation.java | 31 +- .../presentations/ProcessPresentation.java | 87 +- .../ProjectPanePresentation.java | 6 +- .../presentations/QueryPresentation.java | 22 +- .../ecdar/presentations/SignatureArrow.java | 5 +- .../presentations/SimEdgePresentation.java | 4 +- .../SimLocationPresentation.java | 53 +- .../presentations/SimNailPresentation.java | 21 +- .../presentations/SimTagPresentation.java | 19 +- .../SyncTextFieldPresentation.java | 3 +- .../presentations/SystemEdgePresentation.java | 27 +- .../presentations/SystemPresentation.java | 174 +--- .../presentations/SystemRootPresentation.java | 13 +- .../ecdar/presentations/TagPresentation.java | 37 +- .../ecdar/simulation/SimulationState.java | 3 +- src/main/java/ecdar/utility/colors/Color.java | 7 +- .../ecdar/utility/colors/EnabledColor.java | 39 +- .../ecdar/utility/helpers/BindingHelper.java | 9 +- .../ecdar/utility/helpers/ImageScaler.java | 16 + .../ecdar/utility/helpers/ItemDragHelper.java | 5 +- .../ecdar/utility/helpers/MouseCircular.java | 15 +- .../ecdar/utility/helpers/MouseTrackable.java | 7 - .../ecdar/utility/helpers/SelectHelper.java | 10 +- .../helpers/UPPAALSyntaxHighlighter.java | 40 + .../helpers/UnoccupiedSpaceFinder.java | 115 +++ .../ecdar/utility/helpers/ZoomHelper.java | 98 ++- src/main/proto | 2 +- src/main/resources/ecdar/main.css | 4 +- ...ion.fxml => DeclarationsPresentation.fxml} | 0 .../presentations/EcdarPresentation.fxml | 8 +- ...l => EngineOptionsDialogPresentation.fxml} | 12 +- ...sentation.fxml => EnginePresentation.fxml} | 30 +- .../ecdar/presentations/FilePresentation.fxml | 10 +- .../MessageCollectionPresentation.fxml | 9 +- .../presentations/QueryPresentation.fxml | 2 +- .../ecdar/abstractions/ComponentTest.java | 51 +- .../java/ecdar/backend/QueryHandlerTest.java | 20 - src/test/java/ecdar/backend/QueryTest.java | 18 + .../ComponentVerificationTransformerTest.java | 26 + .../operators/ChangeTargetOperatorTest.java | 3 +- .../ecdar/simulation/ReachabilityTest.java | 28 +- src/test/java/ecdar/ui/SidePaneTest.java | 9 +- .../utility/UnoccupiedSpaceFinderTest.java | 64 ++ 154 files changed, 5353 insertions(+), 5155 deletions(-) create mode 100644 .github/CODE_OF_CONDUCT.md create mode 100644 .github/CONTRIBUTING.md create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/pull_request_template.md create mode 100644 presentation/EngineConfiguration.png delete mode 100644 src/main/java/ecdar/abstractions/EcdarModel.java create mode 100644 src/main/java/ecdar/abstractions/HighLevelModel.java delete mode 100644 src/main/java/ecdar/abstractions/HighLevelModelObject.java delete mode 100644 src/main/java/ecdar/abstractions/SimpleComponentsSystemDeclarations.java rename src/main/java/ecdar/abstractions/{EcdarSystemEdge.java => SystemEdge.java} (92%) delete mode 100644 src/main/java/ecdar/backend/BackendConnection.java delete mode 100644 src/main/java/ecdar/backend/BackendDriver.java delete mode 100644 src/main/java/ecdar/backend/BackendInstance.java create mode 100644 src/main/java/ecdar/backend/Engine.java create mode 100644 src/main/java/ecdar/backend/EngineConnection.java create mode 100644 src/main/java/ecdar/backend/EngineConnectionStarter.java delete mode 100644 src/main/java/ecdar/backend/QueryHandler.java delete mode 100644 src/main/java/ecdar/controllers/BackendOptionsDialogController.java rename src/main/java/ecdar/controllers/{BackendInstanceController.java => EngineInstanceController.java} (52%) create mode 100644 src/main/java/ecdar/controllers/EngineOptionsDialogController.java create mode 100644 src/main/java/ecdar/controllers/HighLevelModelController.java create mode 100644 src/main/java/ecdar/controllers/MessageCollectionController.java create mode 100644 src/main/java/ecdar/controllers/ModeController.java create mode 100644 src/main/java/ecdar/issues/ExitStatusCodes.java create mode 100644 src/main/java/ecdar/mutation/ComponentVerificationTransformer.java delete mode 100644 src/main/java/ecdar/presentations/BackendInstancePresentation.java delete mode 100644 src/main/java/ecdar/presentations/BackendOptionsDialogPresentation.java rename src/main/java/ecdar/presentations/{DeclarationPresentation.java => DeclarationsPresentation.java} (60%) create mode 100644 src/main/java/ecdar/presentations/EngineOptionsDialogPresentation.java create mode 100644 src/main/java/ecdar/presentations/EnginePresentation.java create mode 100644 src/main/java/ecdar/utility/helpers/ImageScaler.java delete mode 100644 src/main/java/ecdar/utility/helpers/MouseTrackable.java create mode 100644 src/main/java/ecdar/utility/helpers/UPPAALSyntaxHighlighter.java create mode 100644 src/main/java/ecdar/utility/helpers/UnoccupiedSpaceFinder.java rename src/main/resources/ecdar/presentations/{DeclarationPresentation.fxml => DeclarationsPresentation.fxml} (100%) rename src/main/resources/ecdar/presentations/{BackendOptionsDialogPresentation.fxml => EngineOptionsDialogPresentation.fxml} (80%) rename src/main/resources/ecdar/presentations/{BackendInstancePresentation.fxml => EnginePresentation.fxml} (77%) delete mode 100644 src/test/java/ecdar/backend/QueryHandlerTest.java create mode 100644 src/test/java/ecdar/backend/QueryTest.java create mode 100644 src/test/java/ecdar/mutation/ComponentVerificationTransformerTest.java create mode 100644 src/test/java/ecdar/utility/UnoccupiedSpaceFinderTest.java diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..30f97862 --- /dev/null +++ b/.github/CODE_OF_CONDUCT.md @@ -0,0 +1,128 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the + overall community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or + advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email + address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +ecdar@cs.aau.dk. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.0, available at +https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. + +Community Impact Guidelines were inspired by [Mozilla's code of conduct +enforcement ladder](https://github.com/mozilla/diversity). + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see the FAQ at +https://www.contributor-covenant.org/faq. Translations are available at +https://www.contributor-covenant.org/translations. \ No newline at end of file diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100644 index 00000000..0f73619e --- /dev/null +++ b/.github/CONTRIBUTING.md @@ -0,0 +1,96 @@ +# Contributing +When contributing to this repository, make your own fork and create pull requests to this repo from there. + +## Issues +If you find a bug or a missing feature, feel free to create an issue. The system is continuously under development and suggestions are always welcome. + +### Bugs +To increase the chances of the issue being resolved, please include the following (or use the `Bug Report` issue template): +- Concise title that states the issue +- An in-depth description of the issue + - What happened + - What was expected to happen + - Suggestion on possible cause [Not required] +- Images (if relevant) + +### Feature +To increase the chances of the issue being resolved, please include the following (or use the `Feature Request` issue template): +- Concise title that describes the feature +- An in-depth description of the feature + - What is the feature + - Use case + - How should it work for the user + - Suggestions for implementation [Not required] +- Reasoning behind the request + - Added value + - Improved user experience + +> :information_source: To help organising the issue, please attach relevant tags (eg. `bug`, `feature`, etc.). + +## Pull Requests +Pull requests are continuously being reviewed and merged. In order to ease this process, please open a pull request as draft, as long as it is under development, to notify anyone else that a given feature/issue is being worked on. + +Additionally, please add `Closes #{ISSUE_ID}` if the pull request is linked to a specific issue. If a PR addresses multiple pull requests, please add `Closes #{ISSUE_ID}` for each one. + +A CI workflow is executed against all pull requests and must succeed before the pull request can be merged, so please make sure that you check the workflow status and potential error messages. + +## Tests +All non-UI tests are executed as part of the CI workflow and hence must succeed before merging. The tests are written with JUnit and relevant tests should be added when new code is added. If you are new to JUnit, you can check out syntax and structure [here](https://junit.org/junit5/docs/current/user-guide/). + +The test suite can be executed locally by running: +```shell +./gradlew test +``` + +> :information_source: Currently, the codebase has high coupling, which has made testing difficult and the test suite very small. + +### UI Tests +For features that are highly coupled with the interface, a second test suite has been added under `src/test/java/ecdar/ui`. These tests are excluded from the `test` task are can be executed by running: +```shell +./gradlew uiTest +``` +These tests are more intensive to run and utilizes a robot for interacting with a running process of the GUI. The tests are implemented using [TestFX](https://github.com/TestFX/TestFX). As these tests are more intensive, they are not run as part of the standard CI workflow. + +You should prefer writing non-UI tests, as they are less demanding and are part of the CI workflow. + +## Code Organisation +The code within the project is structure based on the Model-View-ViewModel (**MVVM**) architectural pattern. However, the terms _Abstraction_, _Presentation_, and _Controller_ are used instead of _Model_, _View_, and _View-Model_ respectively. +This means that each element in the system consists of: +- An _Abstraction_ (located in `abstractions` package). +- A _Controller_ (located in `controllers` package). +- A _Presentation_ (located in `presentations` package). + - Most of the presentations are related to an `FXML` markup file that specifies the look of the presentation. These files are located in `src/main/resources/ecdar/presentations`. + +In addition to these, a `utility` package is used for additional business logic to improve separation of concern and enhance the testability of the system. + +### Abstractions +The abstractions are used to represent logical elements, such as `components`, `locations`, and `edges`. These classes should mostly be pure data objects. They are used to save and load data to and from existing project files. + +### Controllers +The controllers contain the business logic of the system. They function as the link between the UI and the abstractions. +This is implemented such that an action performed to an element in the UI triggers a method inside the controller, which then alters the state of the related abstraction. + +They implement the `Initializable` interface and are initialized through their associated presentation when an instance of that is instantiated. Hierarchically, a presentation therefore contains a controller. + +Each controller controls an instance of its related abstraction. If an action to one element should affect another element, this effect is enforced through the controller. + +### Presentations +As mentioned above, most of the presentations are split into a Java class and an FXML markup file. The Java class can be seen as a shell to initialize the FXML element from inside the business logic. It initializes the related controller and ensures that any needed elements are set within it. This allows the controllers to be initialized without any UI elements, which is very useful for testing, while ensuring that they are correctly connected while the UI is running. + +The FXML files are markup specifying how the elements should look in the UI and have a reference to the related controller. Each element that should be addressable or changeable from the controller has an `fx:id` that is directly referenced as a member inside the controller. The direct connection to a controller allows events, such as `onPressed`, to trigger the correct methods in the controller and also helps IDEs identified any potentially missing methods or members. + +> :question: **Why use both a controller and a presentation Java file?**\ +> The advantage of during this is that the `controller` can contain all the business logic and bindings to the FXML elements, while the `presentation` can be used to instantiate and reference the UI elements inside the Java code. The `controller` should contain the logic and is bound within the FXML file, so the `presentation` Java file should be seen as a shell. + +### Utility +To increase the testability and separation of concern further, the `utility` package is introduced. This package includes useful functionality that is either used in multiple unrelated classes or outside the responsibility of the given class. + +An example of one of the classes located in this package is the `UndoRedoStack` used to keep track of actions performed by the user. + +### Miscellaneous +Besides the packages mentioned above, some larger functionalities are located in their own packages. Here is a small description of each: +- `backend`: Responsible for the communication with the engines and model checking. +- `code_analysis`: Responsible for analysing the elements of the current project and construct messages if errors or warnings are encountered. +- `issues`: Classes for representing `Errors`, `Issues`, and `Warnings`. +- `model_canvas.arrow_heads`: Arrowheads used in the UI to visualize the direction of edges. +- `mutation [Deprecrated]`: Functionality for supporting mutation testing of components. **This feature is currently not implemented in the engines and is therefore currently not supported**. \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000..ab912f27 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,44 @@ +--- +name: Bug +about: File a bug report +title: '[BUG] ' +labels: bug +assignees: '' +--- + +<!-- +Note: Please search to see if an issue already exists for the bug you encountered. +--> + +### Description +<!-- +Should include: +1. What happened +2. What was expected to happen +3. Suggestion on possible cause [Not required] +--> +#### Where was the error encountered +- [ ] Using a distribution from the main ECDAR repository + + +- [ ] Using a local JDK + - **Used JDK** (ex. Zulu 11.62.17 JDK FX 11.0.18): + - **Operating System**: + - [ ] Linux + - [ ] MacOS Arm + - [ ] MacOS Intel + - [ ] Windows + +### Steps To Reproduce +<!-- +Example: steps to reproduce the behavior: +1. In this environment... +2. With this config... +3. Run '...' +4. See error... +--> + +### Images +<!-- +Links? References? Anything that will give us more context about the issue that you are encountering! +--> diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 00000000..8e8d9712 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,25 @@ +--- +name: Feature +about: Suggest a new feature +title: '[Feature] <title>' +labels: enhancement +assignees: '' +--- + +<!-- +Note: Please search to see if an issue already exists for the feature you request. +--> + +### Description +<!-- +Should include: +1. What is the feature +2. Use case +2. How should it work for the user +3. Suggestions for implementation [Not required] +--> + +### Reason For Request +<!-- +Explanation for the request and how it will improve the system/user experience +--> \ No newline at end of file diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 00000000..4330290c --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,4 @@ +Closes #{ISSUE_ID} + +### Changes/Additions: +- \ No newline at end of file diff --git a/README.md b/README.md index 382e33fd..47f4663d 100644 --- a/README.md +++ b/README.md @@ -1,52 +1,84 @@ # Ecdar - Ecdar is an abbreviation of Environment for Compositional Design and Analysis of Real Time Systems. -This repo contains the source code for the graphical user interface, in order to run queries you will need the +This repo contains the source code for the graphical user interface. In order to run queries you will need the j-ecdar and revaal executables. +> :information_source: If the goal is to use ECDAR, please goto the [main ECDAR repository](https://github.com/Ecdar/ECDAR), which contains releases for all supported platforms. These releases contain all dependencies, including the engines and a JRE. + +## Screenshots +| <img src="presentation/Retailer.png" width="400"> <img src="presentation/Administration.png" width="400"> | <img src="presentation/UniversityExample.png" width="400"> | +|------------------------------------------------------------------------------------------------------------|------------------------------------------------------------| + +## H-UPPAAL +This project is a hard fork of https://github.com/ulriknyman/H-Uppaal. + +<a id="dependencies"></a> ## Dependencies -This repository utilizes the Ecdar-Proto repository for structuring the communication between the GUI and the engines. This dependency is implemented as a submodule which needs to be pulled and updated. If you have not yet cloned the code from this repository (the GUI), you can clone both the GUI and the submodule containing the Proto repository by running the following command: +This section covers what dependencies are currently needed by the GUI. + +### JVM +As with all Java applications, a working JVM is required to run the project. + +You will need Java version 11 containing JavaFX. We suggest downloading Azul's Java 11 from https://www.azul.com/downloads/?version=java-11-lts&package=jdk-fx, as this is the version used by the main development team. -``` sh +### Ecdar-ProtoBuf +This repository utilizes the [Ecdar-ProtoBuf repository](https://github.com/Ecdar/Ecdar-ProtoBuf) for the communication with the engines. This dependency is implemented as a submodule that needs to be pulled and updated. If you have not yet cloned the code from this repository (the GUI), you can clone both the GUI and the submodule containing the ProtoBuf repository by running the following command: + +```shell git clone --recurse-submodules git@github.com:Ecdar/Ecdar-GUI.git ``` -If you have already cloned this repository, you can clone the Proto submodule by running the following command, from a terminal inside the GUI repository directory: +If you have already cloned this repository, you can clone the ProtoBuf submodule by running the following command from a terminal in the GUI repository directory: -``` sh +```shell git submodule update --init --recursive ``` -## How to Run -You will need a working JVM verion 11 with java FX in order to run the GUI. We suggest downloading from https://www.azul.com/downloads/?version=java-11-lts&package=jdk-fx. +### Engines (needed for model-checking) +In order to use the model-checking capabilities of the system, it is necessary to download at least one engine for the used operating system and place it in the `lib` directory. + +> :information_source: The latest version of each engine can be downloaded from: +> * https://github.com/Ecdar/j-Ecdar +> * https://github.com/Ecdar/Reveaal -To run the gui use the gradle wrapper script +The engines can then be configured in the GUI as described in [Engine Configuration](#engine_configuration). -``` sh -./gradlew(.bat) run +## How to Run +After having retrieved the code and acquired all the dependencies mentioned in [Dependencies](#dependencies), the GUI can be started using the following command: +```shell +./gradlew run ``` +> :information_source: All Gradle commands in this document are Unix specific, for Windows users, replace `./gradlew` with `./gradlew.bat`. + +<a id="engine_configuration"></a> ## Engine Configuration -Download the latest version of the engine from: +In order to utilize the model-checking capabilities of the system, at least one engine must be configured. +The distributions available at [ECDAR](https://github.com/Ecdar/ECDAR) will automatically load the default engines on startup, but this is currently not working when running the GUI through Gradle. +For the same reason, the `Reset Engines` button will clear the engines but will not be able to load the packaged once. - * https://github.com/Ecdar/j-Ecdar - * https://github.com/Ecdar/Reveaal +An engine can be added through the configurator found under `Options > Engines Options` in the menubar, which opens the pop-up shown below. -Unpack and move the downloaded files to the `lib` folder. You can also configure custom engine locations from the GUI. +<img src="presentation/EngineConfiguration.png" alt="Engine Configuration Pop-up"> +> :information_source: If you accidentally removed or changed an engine, these changes can be reverted be pressing `Cancel` or by clicking outside the pop-up. Consequently, if any changes should be saved, **MAKE SURE TO PRESS `Save`** -## Screenshots +### Address +The _Address_ is either the address of a server running the engine (for remote execution) or a path to a local engine binary (for this, the _Local_ checkbox must be checked). -| <img src="presentation/Retailer.png" width="400"> <img src="presentation/Administration.png" width="400"> | <img src="presentation/UniversityExample.png" width="400"> | -|------------------------------------------------------------------------------------------------------------|------------------------------------------------------------| +### Port range +The GUI uses gRPC for the communication with the engines and will therefore need at least one free port. This range directly limits the number of instances of the engine that will be started. +> :warning: Make sure AT LEAST one port is free within the specified range. For instance, the default port range for Reveaal is _5032_ - _5040_. -Sample Projects ----- -See sample projects in the `samples` folder. +### Default +If an engine is marked with _Default_, all added queries will be assigned that engine. -H-UPPAAL ----------- -This project is a hard fork of https://github.com/ulriknyman/H-Uppaal. +## Exemplary Projects +To get started and get an idea of what the system can be used for, multiple examples can be found in the `examples` directory. +These projects include preconfigured models and queries to execute against them. +For the theoretical background and what the tool can be used for, please check out the latest research links at [here](https://ulrik.blog.aau.dk/ecdar/). +# Contributing +If you are interested in contributing to the project, please read the [contributing](.github/CONTRIBUTING.md) file. Here you will find guides on how to create issues and commit changes to the repository. \ No newline at end of file diff --git a/examples/AGTest/Queries.json b/examples/AGTest/Queries.json index f367b70a..89fff7bd 100644 --- a/examples/AGTest/Queries.json +++ b/examples/AGTest/Queries.json @@ -5,7 +5,7 @@ "isPeriodic": false, "ignoredInputs": {}, "ignoredOutputs": {}, - "backend": "Reveaal" + "engine": "Reveaal" }, { "query": "refinement: ((A1 || G2) \\ A2) \u003c\u003d G1", @@ -13,7 +13,7 @@ "isPeriodic": false, "ignoredInputs": {}, "ignoredOutputs": {}, - "backend": "Reveaal" + "engine": "Reveaal" }, { "query": "refinement: ((A1 || G1) \\ A2) \u003c\u003d Imp", @@ -21,6 +21,6 @@ "isPeriodic": false, "ignoredInputs": {}, "ignoredOutputs": {}, - "backend": "Reveaal" + "engine": "Reveaal" } ] \ No newline at end of file diff --git a/examples/CarAlarm/Model/Queries.json b/examples/CarAlarm/Model/Queries.json index d9a20d12..b5164aa2 100644 --- a/examples/CarAlarm/Model/Queries.json +++ b/examples/CarAlarm/Model/Queries.json @@ -5,7 +5,7 @@ "isPeriodic": false, "ignoredInputs": {}, "ignoredOutputs": {}, - "backend": "Reveaal" + "engine": "Reveaal" }, { "query": "specification: Alarm", @@ -13,7 +13,7 @@ "isPeriodic": false, "ignoredInputs": {}, "ignoredOutputs": {}, - "backend": "Reveaal" + "engine": "Reveaal" }, { "query": "reachability: Alarm.L11", @@ -21,7 +21,7 @@ "isPeriodic": false, "ignoredInputs": {}, "ignoredOutputs": {}, - "backend": "Reveaal" + "engine": "Reveaal" }, { "query": "reachability: Alarm.L12", @@ -29,7 +29,7 @@ "isPeriodic": false, "ignoredInputs": {}, "ignoredOutputs": {}, - "backend": "Reveaal" + "engine": "Reveaal" }, { "query": "reachability: Alarm.L9", @@ -37,7 +37,7 @@ "isPeriodic": false, "ignoredInputs": {}, "ignoredOutputs": {}, - "backend": "Reveaal" + "engine": "Reveaal" }, { "query": "specification: (Alarm: A[] not Alarm.L7 || Alarm.x\u003c\u003d30)", @@ -45,6 +45,6 @@ "isPeriodic": false, "ignoredInputs": {}, "ignoredOutputs": {}, - "backend": "Reveaal" + "engine": "Reveaal" } ] diff --git a/examples/EcdarUniversity/Queries.json b/examples/EcdarUniversity/Queries.json index 2e1c4137..0e19ca11 100644 --- a/examples/EcdarUniversity/Queries.json +++ b/examples/EcdarUniversity/Queries.json @@ -5,7 +5,7 @@ "isPeriodic": false, "ignoredInputs": {}, "ignoredOutputs": {}, - "backend": "Reveaal" + "engine": "Reveaal" }, { "query": "specification: Spec", @@ -13,7 +13,7 @@ "isPeriodic": false, "ignoredInputs": {}, "ignoredOutputs": {}, - "backend": "Reveaal" + "engine": "Reveaal" }, { "query": "consistency: (Administration || Machine || Researcher)", @@ -21,7 +21,7 @@ "isPeriodic": false, "ignoredInputs": {}, "ignoredOutputs": {}, - "backend": "Reveaal" + "engine": "Reveaal" }, { "query": "consistency: (Administration || Machine || Researcher)", @@ -29,7 +29,7 @@ "isPeriodic": false, "ignoredInputs": {}, "ignoredOutputs": {}, - "backend": "Reveaal" + "engine": "Reveaal" }, { "query": "refinement: (Administration || Machine || Researcher) \u003c\u003d Spec", @@ -37,6 +37,6 @@ "isPeriodic": false, "ignoredInputs": {}, "ignoredOutputs": {}, - "backend": "Reveaal" + "engine": "Reveaal" } ] \ No newline at end of file diff --git a/examples/FishRetailer/Model/Queries.json b/examples/FishRetailer/Model/Queries.json index e660ab09..a2131482 100644 --- a/examples/FishRetailer/Model/Queries.json +++ b/examples/FishRetailer/Model/Queries.json @@ -5,6 +5,6 @@ "isPeriodic": false, "ignoredInputs": {}, "ignoredOutputs": {}, - "backend": "Reveaal" + "engine": "Reveaal" } ] \ No newline at end of file diff --git a/examples/SenderReceiver/Queries.json b/examples/SenderReceiver/Queries.json index 54261ba5..bf94c503 100644 --- a/examples/SenderReceiver/Queries.json +++ b/examples/SenderReceiver/Queries.json @@ -5,7 +5,7 @@ "isPeriodic": false, "ignoredInputs": {}, "ignoredOutputs": {}, - "backend": "Reveaal" + "engine": "Reveaal" }, { "query": "specification: Sender", @@ -13,7 +13,7 @@ "isPeriodic": false, "ignoredInputs": {}, "ignoredOutputs": {}, - "backend": "Reveaal" + "engine": "Reveaal" }, { "query": "implementation: Sender", @@ -21,6 +21,6 @@ "isPeriodic": false, "ignoredInputs": {}, "ignoredOutputs": {}, - "backend": "Reveaal" + "engine": "Reveaal" } ] \ No newline at end of file diff --git a/presentation/EngineConfiguration.png b/presentation/EngineConfiguration.png new file mode 100644 index 0000000000000000000000000000000000000000..38b26daa7f9af9b356d47e4e8c7571c18b91c3ed GIT binary patch literal 23759 zcmeFZXH-*r_$C@f#fExREJ(4?tAO;Xh!}cDx=0UA=`~>GD2f_-N9kQUp~Z$sM<7%I zDIpMgC)9bj|8>{BckaxGx&K*f=EIzI)<I&jv-fZR-sgSFyU$BabtSr^3`bEY6dn5R zZEX~ah7*O_&wKa~ymImM8y)y@!0i@V_b~k9f7t3@<Yjlod+s_eHtt>)uGT19XBQ`H zJ~vBOYinmWI~VtP8iFjmi4S>`f~&QKyS<C^WnFtGYm|Yt)#dBrm+x3VxGZ#CNcgg# zkfe~fq@dtsO|{Dkin=#XUDQCKE~C)5Z|Zu-&kXvQ=&tV8FRnL#3YjV5%sldH^mJrw zth<|v@N#8bq$v-d%a+ZSnp5#WOOdmoJ|@ThPON_Z`zF0d<{kM}>6iB(E&3@dOpvQ? z);K_ad;D+#c}(zRyu`3utL=%yWSQalbw(Xk)z_w|@TiBHic?ujb{DRwBvcYjPoPjy ziWB*2*?vxB3O@~}n48CW)P;Q`2T-Wb7Y=h~t{&!q>-%w2{$}1C@qH-N^>h1=V)(uu zL7{>cPiboM{G$$k{rY9l6QkgV`%$R-CI`-TaX&i)FIrwpPVT-O4}ZGAvhQH&e&mpU zDDrTi`vN~Xkk?HP!WYoLD(X0be22ll{m6|$eY>2D{J4Jn|NQ!YR^val86IZO?bpP5 zR@GFhRABn*7HLpj>f3a=jA0GBgKS-r2hP%rMA5=VE;O0hevNXY{-c@w&*Q(l)N_u? z_&9CpFefqSKJ02uVfrNUKf3%(`$$JC!|&gU#`}v$qJsKg5gtb=Dc+*JN$U7VGe2l) zSNl(t5^<z`KMKRMd&o@IIw8lDH|UfucW97y9sfB=?$gSLuSb0Q1t+2(uV7NSbm(13 zY3ac1vvAj|$+{no?Ffkv3t=DY(p7y|x{C@jdP%@WxP5RdcB84U^Hn*_MN*K@x_bWD zWhN=lF5x!ml+T|(^BA6kU6OrQS^3qF?=)XseZ2~|ROTsd{?@Z+&M17UZEoz8TKaZU z?i5m|nCS5EvviE&?Tg#~7SG<fVkap4$?04g;)QSyP_sb{oY${^>hdu+ywDwpJrLfM zS&dXJO61ZN<O;JdAipJhj!G|doYLeEE5c5;t}8U01PqmL(R=SE-+D~tTC(mx2s+5m z*5Uu8T}w7OM5-A^FA}I<;G>!NMoDs2_|_Ov|3EnR!w+*(SQKCiruJCE)+1SOiv!`x z+!_qB!v^r3UD8pmCw@{#_QuqgqWbEv6&oWo@ZFLDe}Y2&sc9Wz&bmW|mH=RlU-Ga4 z)H0WX7hc18k|QJZM(Yo}UXw$Y$V$j+DK|em3tth{NqfR>{rgMFfQ95+o9j`=4<C9& zJ#+mjg*8DhlX=MPS43FkYJXoL%`9kb&$|&uw-0j~oPv`M>Yw@<WlmZ7@)RTZ=wr1} zd8EVr`+q;Gfqt(sP_{Y0u_(Djih{p=@VuMjwF$$1Ww!WB;uT@iC={p4=8rc=r-seV z@{F7MnpD<4ex&dpA^n*lu@!XZ(RUCC+GC^KCC-L5oaZraA3RIx_N5(twBrDs0qs*C z+Q27%FaPo5qVx0(<KMY@iC;yoYdU}VLLV#UUi<JCii;(GRPW&7%dlGFEUokcuVI(9 z&Pt5Xo`$<LA+=up*6s5W+=6|igdEq>{$ks=A5&83nV9l&b8{*Du=A6Rh%0V{>>sF~ zO{v*|r&#<KZj#2wO-d4qi;HD<Ryvz1=R+D=T8L}218ivhoYhxzmjJ^tU%zjJz+pe~ zBs{ZiiA{U``gKT|n}<iu=sq@9*4qWOo7t=lgM&MN-<`~U<<lw6{OBfnJK4?2YpavL z#;TiICV1p90-T(81!`70=!XShhw0R+GpXpOk12n*k*M<HxtyFI|HgGFHc+`9C#=_9 zhvV~jsyp1H7r|ZnFl?x5S})IjFIP6OvRInCI1h8@Pp0_(MtWUo!)zf7HgWNL|NQe$ zR3SICBhK!pvCF-j0j>)(oq@%_FS&OyX%LcWosYhEBc4kh8yG8*5xM(ou%9#Yb(?G+ zEqSs~D{1n6*0Tp%PqmV!f|dGp=U6*uawfz)!gR)ha+a)CXWV4IqU%iG47(`^cd%wE z1fQ^Pi50>8t|+W@pZa)BDb%htUPAfqT}@5Rcbn_;1(}(dLOrEMc}5ivKa7rzS(h3S zKPb?y&JUa8@pz+8_a9rv;ldLW^OFL14RJRdd#_7KNg4R=;a2Kd0_}FUsmki=`Zd0* zT{V7Q9+a-B>RC5e*BgPm{!RmBYE&xq=&@ru!?gj#YM*5lnP(`J?j`cM^~u@A8hXc~ zoIhQ%e2JSH0o1Rz54I#`U-_UX@!+GT-vbWH^9tjYUGv5hoO?N%d~-g=V}7(rMB@jz zFs}eZZsR9xCN<2ysx{nIOf)IBBq#-u-dGWb8?NJb2YsjWDyQ?S+=m0!xda5-3b$GI zHiCzI$V@V8y_Rg<DjFIFNwNWFSy;?RSIe9RC=DD*T|eJn6M-vJlBX`W$%(*G(Asd$ zq_Hubtet^b&o`IE)aPrr@*PEuU#`&{I^Hfe?5~5#SMm3+K19cmYDxZmdn(2@@t2N< zOj_X1v^l+`CtRpln4gTuiMQ=BydAnDmSt09rNHh9xgrtqE*18^h`3V)&yVTbyHqOf zdoM;QlVzfxSR};lpV-b4k=yy`;iscFelgvlYE`dIbal69%Qewsu(Q--$jhjx;4XK6 zo?@ph_j+ViR|?uNK$IQd<B+@XZ@YHX^HSTnuCMrM>&g+&Qf2e}=X(;V(!-GyiAhhz zo-K2KF4gYHq=~7Xy+oYfZ85rvFWC`IXS!wzo)TNvHR=NJs=*3X_Ev2VNpmKd_-VDu zku`l~^IFY4iExhLNEwmIH9LA2^)(Z=?*d&<^!y^6k7$m+DbLcVzwQ&w+UK@Hj!jx= zs;p7wThi?<9qcN%y+q9E&*wC`$}ep4{B=iMk$oro8zw1z4OwdHIRUfWzGB;Hfk0}A zqi9^BFNJG%puFJr*86Kp+_JJ}iHV7uWVyX8>!!%5P7OKYRA+{WO>;E&@Lt8Eu^T>1 zRzBX|c8%d&Mx~gD-JSWsruurN<%yO~xCwe61Mb{;vAH^(VObxV<uyNKRATpDKhG%5 zHYq@#Jq+za*eR3U-57htB91~G|CT?q62^U<7?HFdLuI0Fkte4JGU@B*W_4_SFz+nQ zSu(6{OEVQJb4zAQXzZ$Ks2it*@iRB>1cb7O;x%0I)T4IxPb)uf6!*N{VCdP9m^S_` zI3iYOarxrbl#J0z-;Eu=uPMH3o!(698>S())%mpb0Vy>4=rz?k4Qds${}@l+@2FfF z751t3!RAE2q}}A%Dba?=%sth;d&2s=S^Lo$>%1Mdox6EQ-pb}^jGg2eS+9#-+<eNg z-m)(NkF~2^Yx6M*^qZ04>yOHMEkl?btiLlw^-*G_pZXKev0BcNVc&v2?=!xeFL9=R zvbJ2GXXj{A_C?a~_Q}bc_{Qc0DFZ35IbOGz;Najy|D^`)f+T4l?WRaRopR?v#)8%9 z9u2kF8^0FE>gg|C${{EEspCpKXN~F~=0Q=Ymk;^oSSEaVN;|zdUC;wRd34<r*9ZYy zo;Ay@Qn=LK-g|?tO?<f2=;#ZIii%l&>vNkcof@l4O#(cCgt!v-DebiM^u~b!<7IMN zR89^Y?a7n<HCuC4n{Y%C&zat%L^_nbM-h8|mw}Fso`JT-rdgh0tpXjbM*HBb3ELLB zPHpS%C8FM$42_Pw#~q8*xAyI+0xM(rSl^!<W=&;et_RKDHCPuNLsu8A@l?b9glN{V zLC+zNy&XR}nWcp$p|sqNvWhi^RZ3IkVs*`N#s0<l_Y`WwUf__^3B1C5fZ(0Fr#Vhq z>(gT|2ge;6J0rb<?Pt7lN(2}AO~*=f&6`!;8O)2Vka%RqGaQ|K$<8u2)^90&PtV+v z>;CRsD@1boRrT!$rAqHogttM(*N(9pvng-1dtS!MPU#<{R7}W;-VLkW(4y=a)Cmma zSN|)>u(w%*!88#0-iO_A@cM{*#cJ-Wbv!u8H6ljDq1z{ly0sN`QS;WVTguO9rkY7X zQXZ64pXCYrz1^MA9!pD0Hnf(O7TedCtkjK-Oxw1^SWzdV=a~GTyBQiu{8fYb>Isq( zU(tomgOyyoysAoXnTd@NJO~D&wJj_@q_p7IEqo?oY~7pq%71ot7T*$~L1_-oNTB=6 zoA?d-A_A9!Lg~A;j0MCbVp!>CiQQ@vLO%4E`LAseR=C`H*KsSU4{~RUtepF`oi4?$ zkjleWDofu=mOJIh?s@BYNvd$0czy98rdO9Azn=4@W!<iet~OMHlYQOc?95LdcArBB z8=F|s{6~+{y|0VR&o!w!s_kiS@FC85ZpaYJj$YE#q>+p9*xarX_TlDipKh)-d2*KF z!FxU#6})Gb-Mqe2qhLeM!-8|zMAooe$;dI*ftPY@ZZf5g1Dfdq1Y&AW9;FkX*S|&$ z9;yzrdZ2qCJf}HCpf)p<Nh%WGTvwNNYarX8Adz)yWomEF`jd6jIkfitP>n$0*T&hv zoy}ET>w)N^7uFII3Doy8zAKqyrDiq04Q*{2_V)Ia{?Z)gfE6u|`61IUU%nhYaiaF> z{6SPuQ%lwqlP~F;h3!OgZ*i$x|FDSf4~Na{Zzp{}`qa7}b1FKYKJ{}*W>4zFG0l2s zv79V|qANi^FiBKF&5~^2(0DO+0iEsmMbOu-gQv2%L9v@pta#hlOJ{gMrR`>6MBnaf z!S^=SjIHnWoR^kzd+{pvo_%ce6Xfi68v~2}e`W`l37rE$p%hOyoxC>pPP)LP^owei zA}$TeEzt`vwVl4a^0C92UHqk-*sLSQxW7>^-L%S+GEhEm-KW18ajlj4WDE{>6^&_6 z{$kyyq_`fqG>%4Vo0!Cpj*hlg&Gm)N?(FQ)pFf`=X!*nL+Y36R4YMXp9b^!7jAjPv zcJROf-8y3+XpPr>vVAhvvc)XRc2%3-<<sdtz0VONQ%U(Ewwu1-=BoH>an`7Gx+L~E z)@=Sa2Z=E;V=-m8tLq|<JDCt`^3aq^BckeM!}EZmfpo1}Y<7wx8*5rqzOPW0>$0#3 z%_Yv>2Hv;%Cn;|T&!ehe-TU>($sDhe5qG?M+4jVXi8m~HVi~RNuZF^iJxR|V+&FMH zTvPgXwl{Nqg#K>@p$Fd&4`R1w$xc1#YH_FA3ToDjoO(aqyd>(_{^a1Xl>rxmJ!}95 zIc)Wi-@FWvP>u7|w*^tCJGQQ#?{m=Hyqq}R_Y{#&Z)#(f(uOuOYj=mPsiucjmHO7W zPp9k<#ofYW{T7A$XjR))z8N=+-G1Skhl}$X49UMVXQh=;>0!mypXb3-#$6In!axo8 zIPKDzRkLWc7#H}m>9|yIiI(mBF~WIrW?YxZb^YZGLXJeO0@1|p?4_;XS6v&p&^K&F zQHRQ?cXVp~Ral67Rld(+++Ei(c|Cc)OP)ER0c#&+i%+&ci+Yh0QGdibUgAQwu}p?k z#o_%&l^536qga6=>o)BwC@7TLwlM>GOomF$WiD(oNxEwWpI~A|zkK<ULDs)QzrZZd zZw{|i<uzaXo3tPGur8YBQg)&7`!c63g((^Z_m)pREBX_cHpyZFcFn4B<D(f9GIIvy z(p9`Ib6pMs8Iwe=F%e4Dt-j_tO#75sjc8d2YtzA=h>T*R)f$x^0jj*4nfGn`uya$Z z%pOgXx?CM!s9`spu`jtv!Yc}VRa;-@>&E-E0s~1{wpwY0FcxF>jN~;6@v82Gip<U@ zlN5`BnzUxI-7FWg3n{`bS$N;y!)kpKoP*T<CS@OwS3llvsy`P+D0fFq!5h8vKfErU z-gl7AByeZdsLC@BOC_C$u9wxdR7?)heV%VxMd>jQB(}-z<#Oqyi#u<6Us)dxBy{JS z=Fl>G%fFRg`gd#<*x+P@ftglMzA3VLBoYbKNgj6-Eh>45mv80s?^WMF@lk5~0#?=< zWo=83X=fD9+Yi+kDmQVyD~>9tHVLtPT9JiLB8wFFoN2t<h9kBp)h4#?u?X>3<d1jz zr>5_28>Csskx0{q-0}B}q?7T}ZFvihOgB6RlZzAMi1&Zi8;OnYnJ?n8C9?M(W~ir; z8|pIhPXvmZgmianF^PqZ`=^-gw%OEd6Usaesp&Peh_t_Z+cTBh(r~&9Z8;p5;aA)d zuJwmkz+6CLNIOi{7i%LR^ZtE4Z%&-QzYIxLRFHS>sdim{*|h2T;fIgvLz&X@yGtGW zgj!;RGnCimY??0+^NbAwcc~d>0josqrt`q_<_UYX-yLdeYx_$bqJ{$Z0@BjbBJP#h zv@oExH8oFxYS+9LV?t>pBnG|=8oB={%u8N|n!u#qh|h1RQ`yDpd0kDaA|0#Q{9?jq zUasC`qt+ou-sVynSHqYSgfcSN@m;G1GJC~eUp`fI9@>{}@O*yiR(_e=SIX^^jd~N$ z&zrSF-3ELSJ$8=wT~g98n-lPpHQzsvX>U07-#{3tx&PwxSTUFUZ^0)W>R!<=t*r1q zG8}H=yMA3$*rvICVdUE?C8q$>S7c4;NDEn7UM{uxb|}xBV0QE7O-fHe(pa|j*pD}z z@e*z&fqPgHr+!^T2$0C+3?Q`*)Fl!w^~f1^UBea-wI631TU)d#{Vb)E(>mPli#k@# z7j~u#N32!*m7Emh@r~Q-guNQOxF=7ZD9?_8Hxf=D5D>7*GcH%3?#|Omko34=@$~>3 zJ3A#zLpG~xO~7X1&zGZz54Qs~24z#QvEhxz)jiN!pBr2S4i#0A>MAcE)SYLX!I9*z zU07HcE8&)IY3S`;Vdg`ke{(_bqp|~7%=8x8HJ4brx5Q+5jkP|^oU=8&;QD+|u<rZ! z_F?xUt+VMoSnOKQXg$7d*0V~~so&lDSnUB&iHLG%<q{Ioo|~IPu)(G+@h>*O%4IGw zgtExxrzpK`Bog&NNJU0P^(Z<FmAfRly1CIaGUncS!=UH$2FzAf6Zeu>*`1QFbCuvj zT#bp}c)fwq+^&(_T<z&!UmKv5IT_=-9eVN;RM~Z22B!)oCGC{<;@cc9?Rx#G{yjJd z&cUp=nXYase2p^h>eZ|Hcj@ZHjClpTyw<g&F2e<WCO$<kan@l<DJ^2dT|+f~OzVnh zH2N3KN$F%7+7mw?6x#iKcjbH^;`3@gT1ie0r{~e1q>__*Ko<&daB$$Os(5&KR61h# z>&u6+#%gh*c@y0GP!+xv#DaqAvHFN4+*NYOaf26+dP836zKcwU*GT#VtVfiN1Gv%3 z17%Jk){SRd;>0v)PcXJOgmKU_Fk}PkpuAUN{-q{12*w{^sotW~uo>7sO&LzcKBdll zJ)b`#wu~kmuz5Jppktew-BaU-9P~PR_cZYZdI`Gfk~mmTo*MC)owO&pQv(C>diSwx z*&0KOW6H5|<{q&(w3Zjg^f;ueO{N9WwXTyl!P&}u;PLev(SO^UiMphc_M?t#zPIqN z0i;i;rFhKegJoqp_wY&MwPaWklYPjWAB=tnAO5;@{^&t??eq1+;I4s+dKv^i<p1ce z6=Bt1>gwwByugq{z3YPgP2vBbpG=D!Imn{@>4S%?9`MI5_LXG#@!z%F@8h=hUaOp< zfm1XyNzU8^uhZ2<eXk5`Ro+PKvb{$Xd>A7kyDf6)g*ROFfA>KD6P<m;+YM?WeSdkT zv89F2EvBlfYXAQI{7u-7&dyaRW@rp*Z{>C?e*gZ>-{c7=1;GQI*1da~@Z!Dco&o_k zpRKjo)$QfBroKLW$Cj(Bt2Xcs(&*^b;u!V2us%%?YA~eTe0s=t@<Z@Acom}_u+f`N ztFvYSt*_kM-4bXjgNxOKwkGll&;TF+5e$4yO--FFf!kLCVUOVP_OK_5jPhQEhU&xd zRZ7&L9wPaSt;z=wCk~XDwUt$-V#v9of!Y2VzjEGlAqY-}acEGW7wUGVt3`x|r`Gtb zH}>=xK!>cMbY<y0r~;PIUmH-x%EisC4CTnb9FUrt8txYd6ruxqsa0`bSGA9u;_cf% z9YvdLiV@w?*w$u{a_3EnW&H_g4_WY)M^B#Y0oh*)u0euqfKiW`U*6ZRUzHujq@;|E z%N(_QeJkPju53LXd_{%siZSAZUx9pwfkQpM9gyxSTp>uy?Bry5LqkKr^ezC?Trdk& zaAl5tA5Y3|$Y-c0;4(5YCR!7CTwGiXdC%2sjD{5TIc~zjlSW29oHzH^BJAx}*|f%I z*|fykgDr$h&CTTnB|G(!BT*BaFSwQx=zq}c3}B6fVpO?#dAp!t5DUx++6r}z#VudI z-f97n4eo?n^V5cg1}y@1=Z62rL-;zrd!OX@z`Y<%O=YdDxP!a-v!MZvcuX(oE``U( z=T=us1L#bT1haJ0iT%EemVEdpNOt274U>$oE?6Xu&CR;7rZ+$(Yu6z0VAL6!Nv)Le z@o~G^{?biSlsV&Dz%Wh#EgalZkL`^|G8=!MBA2)NHgH=3+z`U<X3o*0N8Kr1TmgGK z>u>?F*DapN?rpmbdJP9i`YeqfT#t*2;s|R<^&0X~0v{0n;sjIn&epmEcm%lA!749@ zwV6K5uaD1>6AM`RrGyzM%foI?aWF}HFW}02{(RoYXIiN%HsqZGoI3rj?B-oKIUP_A zCzvG1d(}NX%Qjc0QeuQ{DF}N)+M8`FqoR_M!;jY&ePLZm6iPOmN+hR%fCk*iP3Xko z2?=@7Q0?GzmEcH}I1PyAnbmB%`p3Z<w1zOB?c#)6+wt}JF_r3br%omR`|n{iuE9#% z$|@BMBQ`W_Edww>J32bT!@^RYJv)G`r=Xx9bojJpn_}>xiODx<=+B)?^dhLMt5bRl zQz*n74m5_C9f458wA9p}ux;U%BHOmWtY&<Ang@-%4erDTU=dQ3oS&j`qE3{G*l6%H z{<cRc&zbftoeai-#kDnkUEOe~CFoLJnOfIkRN0}+{;aE092proN=K)M!DI^w3wzA; z3Ic!77_9Wr2|34SRd~;RXSHWK${hP^Zm^1&p}}Iv8>zB$ZXZh1IV&p**p<P{9-}hH zsf~euWB<Yfc4yOfDlMup{weO=J57p6aUXASUoLFOzFAMC%07M4{P5vJgyazcv+~T$ zyB#s+pZfax5aW$2tbr8`zySX?HZ@hHI>oB8&-N87HAM*+Jb3UCeBFq+IG(CO?2uD* ziwzXuPx#=YUnuwnTU%Qt8BoITxlRGX_D8sNcO^TQbu~4U|NOb%=O_xvX>ud4VbSfW zsVQ74c+%mO&p*S4@A_n+fX2Oj`_^}TE}+igHt}jQ%C@l25jTGLpQw;N$H?;X^0|t! z(9>?>%{I$E9F6A9l^)Y8P#oFlFm?tZtADGjt4n;AZFUgWV`96weIcaoBP^AaZ)~b? z{r8@pC<uWwwV`6PU0s)aqR0~yn6^Y2+-(hIIz{<?t)pZzba@V7R*jg_#s`IPk4--r ze3S-?hma~CAD^iomt3lOOXq&$b<Tw-dJTF+0UYwjwWLEC82DC!;0Io#10IT`=gi&9 z`%orY;6W0>J8L3}ii=mg%DL%rIXU?bxGr7bl|{tH8jX5rXtcSSUM<&3xr6CQeTf*A zpFUkzymJS$N+BZk&LDzuZ}9M#Y`^*F(W6&y-bBDMFx;?9hL~;g&zGkR(%zq{Yif9^ z)dU0tKy%mwM?}!N(jzC5*YIl9Ab$?gx1rR`twjXWnf?Z$fj{0{%5fvN!Z}7G3I?pX zQoCQHxN<tw_4Lt@2BZ_m@<>?rm)MgqVpt(sgO&el7GQgWJO2RjF|-~wE-ti7E!8Hg z$G8-e7R>BB`ElCM&u_X_YQ77=_ap3dWl6WM%%@IiW>qqXIp=~-`uvD=J+6gaOiavU zs)L04_8lls>Tuwm=`eOH521}vrykSYYJ}~jkfP#Z0lYrEbueIk5Fi=htN<VEa7W>X zS=kT^D(I1wwRIMhAApuowRfS#t)mzLm?Kan{mAdqCZC{k{l5vT?i2%f6)O`fE<80% z*Y0h$WDren9nsIx0}==;maUT!%B~j63KbG%=9{HWsL0dL!G(pvM$h^4=a06SX|8Gl z*r9}(Z-vmtJ;49)@$(aIJ&(d9k3KAN9+X6DlUoz${?TSvkH44i8C6Bg#?P+~)tj^8 z3B?HzsylimtC?&Y$mfYCe>HOg64v@2!W9719K&K8S1+%gB~p{e%B0qy|8mRH!a`<O z8Q(dTtg3DUPd_NsC?`Mi((0;*$+?Rt&Ej|_bu-^7HYnwq-OXvrU{(1QWC8XceHaQ2 z83X2cV?)DxD4P}=ogDqVu5uT1_SZBh%rm!d4LJq1lzVsX-t+)!N3Ixoap?x|QUq&& z5*$5pL<^9OH2yvCJS3;rt$w}}`qto3P{Tf@diSEFd*(SlBVALrkLSi-ALCR27zL%0 zU3DB{-tSEUgj{~p%E`1SGw#xv(jK$0xVX4VuX!`Nh<yhj=L|B+fPF{#jbHYOilR3O z;;AzkMA#lmUhw}pPVrf@(G9%|a@ypvvL;jXQ-oV8;K5HW1z#CdRTTa=VCAo0zYHq4 zO}LN)qb|Gx`&!b2qC04v9DDuyF?b1~kWX^Vpxb6wLAsk?aq!qVU9ThZ5tozyWY-JW z!8>A~UNM7r{{Q5!{~7N4-~Q~ktV-m}Ka(s<jlk&TMG!Y8CWhXqr>RNh{rmR}f)AeY zo>QUMJ8Z(Z4;C)@MYM=Rq^QdfM5eq@`+9bRof#TDK#*{dhW*UuJjr<;Sy*|xLNFw( zt*xib_co(IEb){$J3HqC4rIvqt?6oc$WuX|j3MO&Z2*)y2_tv^GYSDb#23|tTs7O+ z#wLAfX=!|J&TJ&;DjNQdo|)m0iL)wB=}l*zu^61M>htH%Nth&Hgup<Q%lDy@%Q!hX z+d&qufPw;|XIi+^?+7MTpDiFlyrnn;h8gB{!Vw67W~mK~?i|0#y+IN%xodErd7yk2 z7Z(j~B;U22Xtc7iF<aajZM69JJSH57VGigoOm}V!#6lk))6hm@+<?RI8JDTr6dRWR zc2$WPz~76byClka<AyH01mIb@vo@et?rdt9rw@N}QC3#gMhXLRK<xq3Vw*R>vXKs( z1Kq@WZp+`vbAE{LKk!C>vI4%I``R@HQ2efs9=&(B;WUBMy``W4AkK|O?BeDL;Ur_= zxj&(I3=f+F(_30w1Hl`WDx4G#j3E<T_p}!s^k>hSe5$E?fFC#7PeY?(VmP5Z2TP+z z5jV|@{QOxSDo}6I?<nd{R8mqwf|M7xjEpJJg35_l2W8%Jqr83)&!NmR+;VbqKtm;7 zq_MHFA;grMn_C;oRuA?&#rs$$J22r32M-=ZQWQi-@Laum>**mnZEbBF$N)ws&)NPo zpyJ5UEi5d2N1mS#Pf8MCVM#h#It7k~GeA4Cme9wQRa81(p5f>MzA+1Z4{)fT5J;G- zSvNy&BrX*Qrrn><kD>7))gH<y@zKD{M^T`9;fZC{c?V%k7bp0Lc8jBR^pc`LQ(~ok zir^Xwkf0AXDw3?KvWJ;rsn$+I)rNB03s2_=dsw91A%uyR#Xp%@#y8VtxONk6Swv)H zdPbtJG8T)4#+!)U0pn+LG9_)lvX<7bg99M5%w74O(w@vO3qWUgJ*w2?13@Le@1Xn} z*M&bfYZiZ8g6#WUK|ukmgZtKOxx-9vA=0sd(hOm@oMw9q6`+SBw7S@)<%WF+E27n* zq~Km8<?81E?2GuW*wHabmQ)U(%RdF1_ce8yJPo3!65xq6KMyO&f@lF>h))=u5`88v z0ymoc{FnyQ`{@JH+rf6ofvAQaa}BK$$@>c^DzV5~g}S+k`5d%QSydJBB@kBtf-^d1 z>3d$k9|ilaOn!j4-K59VD<P8Klp7ipB1~X90k^wWPRIfgh;#oX8pf`MD=v0>{#Ff~ zofpS1ek>sD8iD%i{-6--KKlKL%;NVmLUApWp_(cn-l-6S>LV%ZM4ZdnfByL};9RA~ z%*>1+47)~RF5ZpIEh%XP6u&cQAI({MdOeK!c?TEKI01a*s96Z5*TDL1=W~pN)b{?* z#C(ug=2&VP7-Opts3Qd@x3dzKuAY#cnaPQCbi`l-pCkRG+_okxX%rEo2O~l8@$oPQ zl#(dpC%L~7QuP$jr3nAmzIX4Q2SLVUU-Qi`2k}+-h8=FIl@~(xNs-2985t3Z35BgH zY}@M2E9XH8hmMpAJ4F*-3}+p5?^FY(qxQW2>?P<{F!ylP+}xavgTn#l6?`{Ff?;{N z(ZHbIK_MXd<KNH=G!SD?S|HRx31rTvWlo)U=mbLjm#Bnlz;GfmKLM!YKx&LYC>Kz7 zR&;)TK0-T9OiYlyf{?H8|NevU2v#mw1LWR;PnHT)2w6)=ia0$^o6kI$<<U{`n9C`u zpSo3wl2>~l8=DREaXLND+4XpEE$mUm7=r-{E(r-kh-P)cPbTIX3jg)=?%w?u1PXLq z#ApNnRMSaU{RLYlA|j%$4ukn;|IyZ_!4|c!L_d?T#DL!mS4fk;QjBW-@xH5+G!Q!A z?`J{=i)eXZf*fc`|BX*Go>fNW&H+QS;TdXzVGZLO>$^K!Y-q?nV*UO78Q+eNTfxWf z;*k3Z10`IL+aeP6H{Ok&o}TUG$&>Y-jhz$P67bK&_X7tHa!E>7eqRa-LX|oX8i6dR zoU5ExezX4O^=sYx_mkn<Bq47$Fz7ipH-BrE#~4`)q57!@$XdLMipl`f0RTf>HW}6} zR9Z#`E-h_)+aIyh+u91cXib5!!R_sQcH~U>yLXoj3=Civ;HPtW)D|Va0^A?X^wEc> zi8oBNTE5b;XoGa;0)CFv58xc=md@mxPZ^t7C9s8%-vwG(`t*mVRqVL6$z&v9Hyal3 z;a$^yyk!wUZ#J3legs>PQ&d#{4TDGr;<bW#00v-FVZu@gUKg<$ptYSncP{<)>*IVT z6`J}v#6o|-V57nZ&-1J1BterJ8J9bI@D>~8g<f%o-Pr}HLBSdLq!(0^d5lrEn|Xwc z`uUuLD9nB@FE7CUsqu!eP#!01=r+Q_I<m5|Km*?aw*kOXHhsw>BBBQ#D6##6g2<!M z+cMh=-+)cIy*znPYZ<P*v2Vnxdpk+QVYltY2`<knQ>YMRyrmT2^YmM|KqI$_W-WE~ z&@s%tfpQmt!ri{4pK#X=VMO8z6cv1Hwn`K~8#8bXWetrUn7qi7+ucwAlF${-rBibM z%YFnRp_Srv^F^Z&YXt^v5}<Fc{CRp3Gui&Z2$nC!i%U>Y3*q$n5TD7ws0C)QvK0Ii zXo9Th4j9!b@m<9r1&oLzs3~Ny0PZ@p<+>j~DANT5&*N%Unp=(CMIO`oHDF~)!cb1c zJaX|Y*o_YmKABF9xfdG2u$L})W^j}#*yWOt#GXELhW^5ZOaNMY@bQs81*jb<V4e?? zg{2NX>L95)J$g(tNKH`=aHDXk@$rnaLp8?058z+uPDmF8^^M(LR020fcUh_~;<o(F zBjguhZRFM-vK#r5Wa>7wRHSZ@J_R0Dxc6&_5P+s3+6=sUWby>8T?goRz%V-ETxzqR zlZuz=C5l=vE-iJysfqwmg-b>F1k&Vs3oQ|A1}K$1SSh|s4ICcOt>9)Li35lmuq(VA z#~wIA0UA9pQ}gL{6b0YZ9>5uaYcRnO6a`Ed|2i<!SIo5RN$JkZ0n~4F6m@`cFG=r( z<l3z{DUZ3|hNhM7AT-|~lUzuy$zzZoKIq&700OJS4~B=?r<$-Zoiq#>lzOhNMF_Qo z0Xpts1K2dclhU6(d#22k5khp`SQvS=(@qn#uXon7zD^#QB?HuOfR~XiFju?fe2|vb zp*dO*BIE*uqJiGc(aBIRy2r~P;hGA7lZQ0&@<GqI&9zz3Bb&<m_U)^jEgLAY`*n3^ zwIGm~2~HM;LaFeV!q6h);Wd>Z6%2i|Yk#f(jYo5CFv$QAs8{)-qC&hzVrM|vh3x1e zy;ad)YCm<D!}gL$D&fuwLG4ocd{>A|jhEqi)k>U(fY0E_w_pcIs6kxI$25hI_iZC& z@b2AqXw#*S#_pgy^2&#}p^<|{BEdgsoG1#MfHF)Vz|@dsJr?P5lPPa86U{N;^@W42 z1OP2!`Qx|<rt2=)shx0xnUc=IjK(yQGa$bWRNRY$_Gi{%{bC=g^jpeX*=)o@EpC%_ z`>m5&4GaXK704%c)`tt+`9$N?U?@Wm2quVsT}sgk4lx5^pEnE-4-0-UlXQEV_5J&8 z5xaKz$B!RJ<fm1Gutj9MvP%Y}_ONBxm~oB%5Z62?4Y+$L>f)Uv=55BA34Uw#01qmB zTmEzY^k>gu2J#1qntjxOt=Z=a{UA}9_D;~z5fSe(I-1kgmMj!F6_L>b_JvB#1ekF4 zo#|5vUPFIpfli&e43=sA(&VI_Q-2Bi;>C-Njg5#$t)h0K`d-||#>TN)1DLa&Ad%7# zO(G^%{&jj{Vgmf|*`if_FpZGq0$`PNiAzqt3`{k`SLXWlg;$5pTsQ_v>~`UL&~C4> zr^d5Xp{Gs6%#GS>5l%XhAo0XsSVhzg3{>5Ii0=|bq2hTUdV((VS5cVV_W=w=9Pn4K zU*~?L1T9@j20_(z>e`*x?^yq9ITh-JB0fiw3r&Ty8uGOHHYRodAskO>!iP{74j~!~ zbmVo~gP9{x)&_%YC=~boFHa5<P4{c&9p;p$t7nl!p&p8WCP6;&pPC21oQxdeKlflk z28AE84sK|8uo*BwRk%4hHQn7yDm`bpq2p5c=_-;-N^T$q83K;+2?<2-&k?r)_FyMi zWvfYh+e)HwEx%+T`viL%2b3CEZ>d$oX}}}N$g0Z9Y~T-IQWBv(mtct8OyUpaVfjJp zbiWZB$^?)A*lEI)VgP^<`^qu^$`oGBMmA;~)sa4PYQy{lN^e|lkNOrF-BZCqG=f;Z zFsQ1cpHmvkL(?j7<;qQ$>ctnZts&dE20b2;9`H4|R0tFQ9vaLmZ5<s@U)j)o!305T zgORMCd+!5eyAqMe3>)kzTxwUBu2Wx;BB1+~D_4}&)QAuT=cTDcHQE$6IF~yBk?5~@ zWchLx=2o+3qfr$_V9Eq>zXQ$zzGWs*!N2*+%nUE234BH+cfloo_3yu#bEh9`Lu)z5 zdoTIp$E!PIEPI-;_nREav$J`>Qj~aZ+$d=#!Iy#)qYpW(3ZFxNNfx_m^dIj;aGXJ4 z+CyNBP;t;cf8AjX=|x1^lZGz@<s6maL}?y`j*nD5q%lZ%3de$(F0f;v^T2F4Z8#Sj zn_}ShVz8w9WHK~QxZmo48GOW6Sb8uu`#)JcRgV|f!r%d*0Lc-f56GM8r=CyX`hs%I z24?bd#2CuOh=8p@d~0w|o0bplGok;x1e((0Fs@&}Xat0Od}byWuv^d4`ZF~xBV&4H zs`IbE2x0^rI(UOJklS`suR%8W6>U3_=?@YJw%$9io*>&10iXpLbXW3>BzBi-3nRox zj0y~wh`=3aUbObjn~xt@T7v4k0&*5m&Kwx#J4mgxwBEB$0cq-ib`}md1z6=9W*H-h zngUiCFQ5$0LWyR}FCG{z4$c5fsXqe$u`Cieo&i1v%pb!!w1ab%4#n&6>!V_<j9=Nu zj~~16Znki;9<h=h*|7J*fpo%*f|=ajb}`^>D(G~s%a`TByDkESU?mh&0!;zo+Q1n8 zg8Lo^7_K1p3T9>;kr*Bz9*AXFpJO+mW0n%L&uvgrNbd(-2n%7JW%2C=I0R-QENm#f zH-ym4e}sgD+@a&wUlS1_3R;}WJW-c)1KemyGag=EU9gYAqB+8GofTFS7Apr76qs^g z<>^ByBfTANWCQG9SSIM1p=+QXz-EX7cFKVU6VbrVE)&89&<OzP8F2aCkBIzea4oc8 z^m*ko%}KlMjYTA|L0Astdl16Ge$|C@#-+Y{cWQN}PY2ixXwlq68k#?^Mg>?g&+3`| zRH1-Em4LwmCl|5$c?ua7^4+R`%VOZ~FKu9c3lHm!<eK27h7N@T;Q~Gf9KgWML%z>H z;O{*3=;^OG{@yVIe^TDD=gx606VcmPe&C(N90Ltd599=g2c<^V<pXV1bSM#|3zWQi z-Tw1&d6E|u{+nYhY-tvNs7v??#Jst6>j`#agck7)p!nQg+t=zA+qUUL^aESPBPLr> z?fluZsUSh?S;xR2Lp&d7-a1h8IaSihs$e2iuJt=0rs58y{74M?_N@W<VlXbr4WI>- zhyvsp%x5qJx-{f={ziXy?kMAcrKJF;%Y;e-F#;Y^2LN0bXfz*RUksFcC<!_^Qk>ld z<^}ETnjr7F`S{dw3<~sNG?{e@Y<pxVd3$FEa;B#gX=zmcsfpy|XCr|%YO@7SY__%> zEz!aS-;4Z$>AIM9ob{IfW%pgoThGq=x?FsZmrXXtxjp$YqSL|n!hqqA8Lq8K6}HSZ z$T!IXn!p8S4^$<0VFIx1|J(mU5>==pRy3Gu@MnSL1Ixcxoe4Xh$jnYh%m*1b!EWgG zh}wjU&;RZVk_ib;JJM7jdj1$49ep}1Crvmi&$P@j2H?+kZ6*sCbb|P!TR;LHl@uD~ zZO)bt!_7|x3Z4ZO9%>CEVv%(ZM#eXe76SDXf0&H0D4DI9V(=o6rUBCzm*wOL6#kQ( zVNmd3Um>V>-pu<ZKR-X*6Nc;egCLNDT>%n;A<wY*M`3ekrw*7rp4+t&uH*m8Qpe9g zsswQ={1-TGpWqQ$Xdg)dYcWujB!Dh}M@W(HARGW3D!|nhAZB(;;|;DJ9(sU0NIU@p z$8CZo$K_jWUcG#2k-W<$?uAuYTrMV@lWPqy^FNc__$nl%Gp>`6x8m74F*lJdmKb2d z7pG{q_Qag=N}725%<p1zfm#toybm?XgP=m7mS|7EWe`#3(a+`dpmu{1gNSG3wnMy? z2kZ^Bhzf-KI>1B`x)0BuC;-5?z4khB;zT!`U@kNtF4#2+FOL6$SRev|0tkk{d(s8# z#xskd)l>lf7R*|N-y`xKTHW(!&+K4Q6`4r_IJFjyL#zZCPPGRr5GpscySr-yK4(6D zf&twDq~Bw?MT`er2G}LKu<sGL0CCC*^Y=K!F<PDKR6-00z>~H4;R3(;8UtiiS&=Qa zw7B?dv@V3lBUeihY5)>{glgQocfsp@z1|m_;;D_rF(7N<piUTB%Fe2&sUeP!iK%JR zHeNPhvjmiy9%wf(`NKCqL)$QbSu!0nvqWU<>EK)FsSt0e0t_K}M|1GLY$M2vA??$j znk@kx#~zmTT|qmht!>vRx9p^CD(ykG@d?uO^FZ}qm)d0Wr8ZaJv&N{}&{frVMLZA7 zURmuiZ~BkO0GEnb0l*{>ZUXjgDNOf5_&SMD{zqe<py}I<e0`1t!GKxFv<_mM^%q(y zWyzJ;N_okkedq9G)VFR<hhw-R{<1d@f!z`b2uYF)^A#2~^9RpSwl4J;N1*k+p` zj#<%dvRk=hwKVYX57Kav(m{}^w*K|S6xa>3gH;A#t%8Om0y=oqGkIk~JCD6#j{}k> zFuhn;2OAtlv_DlgWkvvssT^-wSzj+$4P0qzaum&hq?cTyWeJRp-&-UP7CsXKC9)}u zWaTd`EG+YTxw?+-(P@T&NuX?ag@ifGf!G(Iuvi-e^O0E--}ON+WU~M}MCJf|$q8PJ zUUIcy-Q0kg*nsWDdba(IU^@_jJ00!O?Dj=0Js>Y*+3JXfOBGhge?@oUx;*0Uv6@2I zh`2s0!+X03u$aQ+UAu}v&3MJu2Fx)ew<btQ&1JlYI!D}7pp6f!%fZFug6Ffqe$ac% zEYk(}bCkgH2O|m(6Cslbp=8)%fEW2cUZGH=n}J-7x{TVMvx9+p&(2IWa}c2s!U&uu z_+v0qmIl5#1#;m?cbn*N)>4S=zxx6}X#o$iyp`@gWtwB12Lk}fAk(KKjh(e1d|(t~ zsA~Tn0B#mCY=T=MCj|mUl<<ArSOiWRx&z6-KoZtplaQbmnbaMxU_cBE*hitXFu<69 zVkP-c^(zJCMZ%lbo}Th%{FS;_e%HZNRIW}0UmaYv3YsNScaDJovaelgRwJ)oyJlV7 zr>u%2L_;8twtXQTud!GJ(+v5#8b`!n;vQviX8%$CNz7q(60yWy2VYqU55^1$HY?*0 zF1N7o5GF&CyarWfwIsldKspUrDbp~o`_A3Q)m{-C$=fjBfe6ys^1VH_<hBz=Kw#g& z!*eiOAYgCD0MbSaqO#IE&?;6IzMVwGXAjcmkUg+DmGSrSud7?sv+K7I%Oli>g){;Q zIrM8J89-8WsL4Yo7&*~t&d)q$hlYj_V{|BB-4rpyphx@8*Q_HW(-QrVo!hTW{z3>B z;8Mi<_a9&x?7)2A>lsQ2M(eR`Fz@P%^*;G|HPeLt@ac0}XYx~S;jZxCZ1HILSMt$~ z2D|3(<2FU&VsTf_C<*`Ivw7HX|MzJFb~|>4+bJI}c$wU}!o8pS)sv#boN+f+T=y=k zp{n8zRZh`V&o8g&m=cK@*bL4p%Gf+FjG3t8tYGSb+Tw!k9519nl^hc0bTnuM_Q!kg z(`6Xm(9qM<11O9Fc`jue|L9H%0i4`=YjC<+&>RWjftwkDjio)Jv!8y#MKl9LSRS0m ziV=2ZT{?m8zrHBU4iMuHdG_G=BlAaf^b%94VN3Y70DiQP`((Qa*&|CB!Z}?719G^I zdPBBejy89MYp+FcT9NB2Z!cux;ZPrBupeE+Sk9yFtGF?TtCi#>(x#O<&iQDHcH`Jo zy5pl#3)QN+^>uW@pwQDml3l7HC_};ru0)v3`tRaj0cg>2prhz2&)E)e^N(K?VudKW z31&pQ@oh!cO+vP<@$Nt!>O7C;A7it#2TXUGy^=fP(H5zl+OxCv!$<Sgm*YnV(%>Le z1rFwccSS`-pRnIySdH-%^8W=;ljRQt9RZ!E6itZBJ@I>Mo*)N<0Ct{mEx*BKLmnY< z1w$P>St~VLqLV^CZ+Buyx{ujskjC-i{)1)v3#yltUz}t~gaj!SxPl?x)mmMA<XOB& zw_%>l4OEZ~o92C<b{M2;juGa7c%u>8U?==KQ?ovlC&LSG;D9&$DzvQk-|S3sp0z-s zN;rcr!l>s_1w>3gzXCcw=<6xu31ExE5ECa~KLBZ{=7s&B>p71<2IYVDg*;r)|I)WR z8Tp~wPycA~XZNa=71U_+V{;KOhX?9XcK9W{%WEZS*WWP?OegCzUqPW(|KW7hN%UJU zeeF$|P>q+GRgm*@o7%3%yJN9pV=s^Q)dcci*iK_?&(q*}U1UKd645vaZS|Rtpya1d zaXJbkUy~u{?vstl?kxV6PwoGlBIH<bv#-i>oweb0o!ipqTr}RdF(5|V%P5p_-334L zY2I<ku@N5NEOP8CVnxFQ79=4!aI>73itdCm#(>;m1psM;%s&>`sB=dVrK_uJyx*aq zMd74OxLd1)Z81E@{)z4I*|H;THCz3$;?zc2rSXqnGSZy-DvM9;NX^IWCagEd(8$FV zCshje5Ts?1Q=F4qtA3M+Ms@{%?Z)W#lt!SDn=zQAkcYiLqaiKG5&!U(CsZaoUru^D zJ4~n9GW*RG#(R&xq>gjQ#Q^Pb>hh-O7#qg{ld^`co|T=gjN>&b;e^lx4Nay4)ItmP zI4WquhQp`q$hE{uAGP@H(KEKWFT=9tTN>YVvXB3;KO?kSV~EC;S(B)B9IZbM)+Da# zi4ZR?cD2N9S3tvU6zMSugZPXu%GA^F(D4iJz+klofI&_?EK<wVG~uGKO)Ajoi?aUS zkjvo`Jp1j#>bs)BK$(blYi)o5m#Em-*mFK`CM_0+p63l!#I#wB{<fd$v*<Cou~8yY z9ys>P+<JxQWP#hT&K5IHOM#Hcinm*^zqr?(3fq8h%tp2t9p@Vn6=e(ESOvK@c+`vW z;pct!U`_&KNj%OhEuOU5m4V9*eTU@RFsL~NwEoaZ=6HyKZD2;-XO~JY26IbSPtO`i zi(#3gA%J@{R6Var#dUaS(+a}*Xb9^jfX=7`tbqLz29q*e(6U?V<#rcKT!v-QOVBCE zK&BeuDViuy(s{z&Al7BTJx~K!1yD?Y=^!CsO_;(9`%%4TZ8Uo+gVfFMtPMSfm`iOY zQftN0xL*a*@h;MvIjlLh+O?t!nI4gac;rqq`i#Fv<KQ_?Q4tXl!)otG*tOy$(!lVp zC*;)=Cf*~@C6=3og<)tBfpW-8O(9n%H7Ci*f#=-_HoFd-GDo8KRfs}-kxhkwwcPf| z$z_4PB>^7@gmvI{89U?I;JWOglMS-wh|yn?!}`NmQ?)9{=Y&U~Z?HZ<hlF7p`Y(K; z^?~_YSE8PQ^-#4>OVJPt)uNdEiRkA$#Dm6J_|YjB`4;DIj;6Lw8Zes`;|&JqyZBNh z0C@S}nVO&5)a5W%0*rtz!<tSs3%4}`J|jm<tE`k18+6wJ<1W@AAI}aVpux;<HUb{1 z;s+h`^A$ah>vUduIuK>T?rMQB_%{e_#t2y}GkgDe{D!#`1~;Mwu!|v7sGC^8a0&D6 z(erMdYGP^dNNh_Pa-v_@%^X)Lt!~<0CHWZ>>Mk>L4s*iE%VrMP4Cjk2A3!ogLxG}c z1Y}QAVhOl`?Dk(uED4ZmT0vZW@rIoyh|qXwFE)@CZng^{!wJU(s;{HF8812ljnsf` z(*5it>T}uMWO?D7tSq#eni|ZNg#l5v1w$rA#?KRhe35m-Qj8wtg7KiHiB+$WO$T?- z^2h52<T`+Jre|k!1D!}r7}yu&6f19$Q?9gSx-`*2jVbh2-}D+1e(E(}{$)6;str)A zn>Pgy54MUy!j1+xuEQMxEmr8^pU?5Wlq@cut!e!Th%Wmq-^x%-e?Vli9G0}r3P(3n zmmr0&%neGxv#4z_jEbL-P1ELTpnN+*gL33KaMZZwsn;|R+}1XlV)nE^vJA3X5~cH3 z=;nf5F~bT%$;*_MZ02YY`xc?tZ_s32fmLU~I?^(^Ya*~Y=sCa#qnwShTQfTI6DF%I zA7N9;z^p;zR7O&wfd9xbwEn!iIr0%?8Vi=6n}?_8_fVI@B^uP(h=lPz3e&LPUEDZ@ zYu#E;%>$TBdjYwtHrRi%)`7*w8(zv!bRCv#>b(c9L?ifg%kYRAd9G@{_1}N4Edj|R ze?OzqypNXg9iR}G662%W@Nj$r^4L?1gqw{S{50{KEyaUXa_P584sZf{AdtvC20knc zG~A0HhVx){)2d~D2KW(DgD?p)1Ub(&cykMgzDo&O$T$j2e=b80AOX2Qbps_o5`EJm z$IG$csgTwBF4A0Dv6LSO)Up~faSadc^@>QfoE)kdj*SUpmDtL<v<!FlVv*mTnk20< zu6niGnEK@;_MLWCIG9}I6qX&kR#-v=4&eM|DT_>gj_^czJUFkmKq|Q+l>4A|m(v6S zvc5$3!fCrT3)YW={nK{stuzkO0c*&q9M<e1@N4*F@ofnPUM0X+Zbi5kv<=dkq>bBP z*0%rgUWZ|QMksSUq>#%UDgvaUCVmyb0a5$GeJG<__7HiB-XA_N9r!7?s&>tKuxGQ! z!NH-Y&%{<oE&BSE^QL>|aW(N*EB3k_#HQDipmQ@y%nf(PtL;Mn%ah<}+|(!Pl4mlU zn^y(iYQz;S{SKV*kw`Xf3cPDx>+b`hX#_Gt08-Flf4U&DEn#A$7-r!`yl>S35=lV_ zr~-Dcj+R#87i)dsl;9jRpOBh)2PDufrH2Mprgw&2^(Tm-+P)@<h0ioSGr>md5vfDd z4(8p4dz))Nfa5)*sa$QK3l88w`fh%pkN*#X2$%UKZ`~KOB6ckwp4svyhSA!nH4=5^ zn&MQ*5E%P7i_b_Iv#R5WaR#f?<s(cRJ!ODRHh{TKE3$O6cEj(MmR!kl`%p(!l0UVG zm-XDl@AWPVjb_COKbkAq-k3688q09WTCY{`oKHG-D9}En4juzhyQbNjj<A<GnTg-8 z<EW~0{s9jX6W1GK!>-T$!bUMx8xJ!M`~75}ciWGWZ#vEC7#2F~@$T?GRPRxV=Ij4C zZg(fHn;8{Ho+*g$=vS~i2g_zR3!F<6wcnb@6Xr(B*G>V2H0(pB3;!GJ(Sn2e#Lb-t z74+a?&_}*+$Nw@-8_lEYSv06mGJ?ny<NrjqY1D;i5lAx2*RG>_l}qer_n|&H`GMW6 zqo+sg|AxBHV{z!ern%o_IZ-#_!qg?XpdXu(%U~bzm6n$FfuYomuYn-}{8*?|+4usi zCeY<rH6UC4CDa9Dl`P%O_F0&~Fvi?NIVu8)Zh=TwkfqY8J^3cmAX3p`Mi#eGj!rZG zpUbphIg2vVg%ki<>)_w>0#<IPwK^+)5=B?|5^}Tu&G`8Lh&YfB$!S>fDIh&3?{c(h z#Kb(0D9w{eChE(?HEHZt`R{gc6jigaWRwXV8;-`2lLGhyvDWI5dgbF!3rih3`$6RQ zrt7sf2V4upg#_-o4i)61wIj<+<r2e`wr>neyT2`Q8vH`q=__9H9HzI=vNED}Z<t_P z>!^bvA=-LjtkzQtzxKk4cXwtOhYJFf`Bo|%Ynnm&l!W(_V%%HSmtDMgn{N)+h>o#K z$1m;$DAxA7*z8W%%QZL^xQyu>A-arvkrU&9LEWqMSnZYGSWv)|yOSp1|2Ly3S0UL} zI$1(z%p(i}iZ9i3@eF>}%O!Qtf=muhVhJ}@MoR`<%?MbYciXckG$bWrr7~9L-WO$i zEst(gE&oZ4F*F(C+j+s5VEF>e%A|~*9X&5Rx0a#J;BT^Kydpp6JF}WQS>zHbzF2X* zztn$~hREBRbMkFdKwI<AkM0dh!Hi*+drMnrC)sEbL9aIftexj0GK}{$NYN?5sY+XX z*S!iK+(}n8j(KHKPfn#-YYs&V{VAsQ?<L?238vxY6MAG9S?$CdCA)eB<S)i7KjZuu zV|Cun#JyrIRmm7n9AIvHBCA9?7QCYBGfNvIvY7umz{>YrgqO;m22)r{@XF7~doo5Z zE}lgNozoWHBC48eC1Po3;#wU7?mKsqm#I`fv5ETN`N*PvYRtpksO5;Hp~&-*>=6aK zf>D9<CI0K>!rBq3o_!_Ti<LQUB>Zse$%2GvF`Jrqet|p1{$pF^6QvY5m!;pC1>_YL zvxHz9{+3aLxweSmt$P2h7g0JfsY^sb>X7|#11Zp-Eo3+;((ewIBV%tev>MOG?&3L< zurFXBeQ6}lZT6YLGFA^4-m%&~Z_`zPjUAJNOE22B?-Yy>7>du~-%>EkG^zP{l6}YP z&MwA1-M~ZHhkKb4Jx?l`H%XMMEfSyCsN%RraP!5w*T?n!Ag7nvLn$Ob@5(oMPhXm* zt6O5~Wgb$N&{^z4@VDxDWkq_zJLP`2b|s9*mDtwWK;83sw7YnXM~FI9-~IC6*vGaL z24ZIK&9}*PR4SKWDyILMkBz4qW{4q5tGqVSIcd+wd4r^gACJAXG_RyLUiT~TuHx(y z)r!q*>9B801wH0rD*SlK9{<3H`l{7}f!<l!q@<vwA?nt0YZJQ0W_*Gr5!c2XfY}{Z z6tmgxQVgW~_fEH5n(0zQ%C6|_d1+jIdai2GRRQTm??`v)8%m771xt^yk5h6I(I>Wg zOafcj@Z%JRrJXN4LW@7-tW<KD`M$BkJUCzak%5bP$A1n7_`Pl3US5uQ*mKNKpXxkx zFQ%&-`>wGxveROHu%j@XRmy6;X4jz6;T(y@H?qikd%W7DR40_$l}vE#AhIbC0#)Yj z^|NQiCX$m(6SeF0Vz51QJ{jS<gc@ll%uq|Sp$aa*$PVV@YZ8^lX?spKG_>t<ongj` zpOk*6cV~OIn{hkuJ$^i7%O>Ma_a2F&edxHXP}1DOeP*8rzPmoddOl0P6sO#KKQUIg zap`HuCfP<sbvz=j{Py@J#g!)MBH$7h>t$=xjqCT%4VkSjyYu&khWD3h1Zz7*@@-u% zr8?wyVHa$6$qfcN#OdVYbGvt(#iw((F-B30+c8f&tG>}1R(upyld0~>Zj8v{(@Px; z4II3l?o>ZmY+KLT#IyV*JGLv|C|WE-W2=dl;C#__<HOWIX|;qwDOMy$8=uW??c}9i z@TH#A^z?1h)UfZhK=Jiy{Qa(<uVuz#w-tJ#7)<v%T4_l?ze$pIgIU_T2zd=D%T+eL zpJ<A7aHI$pD?K$QeLjET&Ws(BrBnG8iS72<zSB8MO^C0<ms(w4+egsxnDd#Ls&;cN zn4w0UY{^L<j!D{?EXR*!)fc;eb)vSUQjS+GRL0BYx+#kY3vZ_R3Hvm>hze}!ij5x% zc<bK$y*a*=vvkj|CtN%3VAy;sO{Z5w1IJ9HOjSABaO>OBn`7!KNvm=t1(-EGGbQ4< zxy?ooHY%XCh9`=-MnxH|JnKv93_jk>c2cc@OGf3A*<Odb#Gik^b&}@fNZtRXdTWhp z>dL|a1)(mXqSYlYfff-oKqU`(7*KSaL;_T+yd;4^9x=pNZFmJuI~LF~fQ4XyR7e;= zaR5VjiI7B14Z|Y@2#*9PSdj2YfFk9Yg!Jgl&-pPwX04gIzs@@6-n;i*`<!+6_wD`l zwtKdibjBJsoXh=E)foG{$?=?8$p2xdzj6<jOX8S*1o1gXpauH7Pm=pq<#@$e#+GdW z4+eEXZKySx+!41HDZ80=`#fK0Ldfgr`cdO|Nbu{|3p|DQ4JG|a+4JXU&y+=uE_S(j z3|^Qd%->jigzLx*7%T4V_Vf-KMY=gImi*O!J$Trk=g7DWM}gK{@!Ph=E*ITocWa}; zYHyrdL~yq90<-OQ^0gZN^lU4P6pBkyB{shk-K(5CcHERVRKG&@p1Et;OZtQLkFKA# z9NZ{W;cGWn-))_p`YxwI+|P7wzc}dtc!l<4O*dZQy^sn`I4Msl1VZDu&q;M_?fa(~ zQ;g|8rE>?899CGw{a&@TG<j^IgRHn4e$J8N1s~t+y1DV1Ea?s+FBYAAbQ_7^?0T!2 z=xuT%CzA}X&Dc7vb{^PYoVTXaay1jo#Mo%3KJAMOH(v^h>1AHL0F*c5DBouPxQ88H zi^Xz<&IepoU6%Y(L}lDB1LP(eKjrl&vtJKfY@X84x*PK0XnFQ5YUbHrj<1#6jGI1> z*#YNo$(!k4s!Al5&1XfUeTU9kp<F%tp@P;#f0QowDF4mGsK><LLU<|Lj;XqYb)^WJ z+##bH`yK^ADHA=UP!fs68yezQsY7)sU|bA3<e{Ds5EkWPP|OlF^TNem$8Zl7XKY~^ zUYSaZe2|}rVVIDRORBknjW@hAA(!UvhkMMgv&C1g206FCS_=XP&3QJsb_!Wq8QuWP z98=p6#@)jcLTOgWL@lxIM*FK)0hi>FFk5gbm`AY$&>w&wWvYGU+U78WGAQ8l-Y1?M zdwNh=)KIv^dSr-`2W6zoIO|=#auhmmzD^)yJe0l>lJ&|Pt+<^vR_5!oiW-`ur)m^l z{VWCcsro-eq+)g+$;VWm079J5Ldkbnhku`$t+#1wF@&(PCFuYi{DaRG`yBu|wJpjG zQH3R%Mo=fxj3AtOV1US%CB+0be(Vwm`DQ*x@{L6RE8flpbcq!)B?bEB@+#e*hI=xJ zK)(ldw*;=dOh-+URI#G@AAIEcb5vX0^_K>PNHXq7mJwhDLHA?pWiVrR#VsF*UPDKQ z@5n+21_uYJJbvej0Iw!$N0ecP%;`HrIE*^yH!@(LU+inrWDy8MvEzf*A_UtRHpstB zja_eeI+RHD?R>oWd1Gw$#DG4g{CwW4(glOy#BmYs{weUErNHc+5sJs*c4c4bmm5@D zIVou+RTfqh@287g+fr~t^1zy$sQtKd@y2|~P@*pVfjGmMF6M0cp?%#PcBNr{UimCb zgucb*vXdfD*wgkxWd6yfqj5|#$1Kv5fu3dZa?PeO9r~CZLeJW@`z!7e2JICMiO;zt z*paelG&t)F#+R7@M^H~mjp_UPdJcstbw@X@UpKn1n9gy7j2v0~mKk$1<_knjo-4|a z1*_XNZzHHx7bNXQ9+cRBShpbiGNa)uH=-ChKJxf119X%ppFG1wVcm!KuPoDYATk{A z$O|iPDswM=E$GDqLxK4E_;`B32?EaE(@3B5@SXLuWJC;}E^<cJ`)y%Zusao9rBzz3 z$g(<9@dZYjgEfJSm;qlNRjR@KttdTdf19lMgUjr+*J$P8=#_(U5ZRdQC*m?J+OVwl zj|3HU&LOY}9mS-;f4k+IVP4ZB8Q$KdSk~xqcpvQ53pEv~0(LZ6vK_N?B(SXiR!KkY z*)I(YVMQ2EOCsjzLmIu*EZ9>~Ou(Svn&OJoh#u45!#|8Q&I6JCS&=rH>PO!8{%tZ1 zf>QUi;^>ZV`&32dj%Z;dcZ;MXs=SWCUL6FAsCz|brWO`X7A>0jd>?`y_8$GYvOOI+ z^@KdW50U-wt)2{5c#>dkkz#_3(V6M2v-TW@f^R^ZXXlD;XNV^W+ast`b*DRJbjT99 zHWF+V(v0SCyGWcZt0zkH&C>K=B(%4TE93<%89ihlh*UjCWLLg0O+oCn!SyrS>UO+X z?TEemK-xb&C^!jX4P~+Ax#<f{h`K{!;rMn)W6;<(rljI@x~n`%KYHiYtB?EFJmXRS zjB!};bSil0633AZ5craDp4cHUw0F}5w1z`Cl--|+16X_io{oE?ZwQI#G_wgS_K|<T zZhA%Pj{fAs9*HM)fO-eGDF1_{7?2+QD`fwFQAYYN`o69+rdOf1j-LWLK?nqW#{YE9 Isfa)S4y@{&^Z)<= literal 0 HcmV?d00001 diff --git a/src/main/java/ecdar/Ecdar.java b/src/main/java/ecdar/Ecdar.java index 542b788b..8efe1da7 100644 --- a/src/main/java/ecdar/Ecdar.java +++ b/src/main/java/ecdar/Ecdar.java @@ -1,16 +1,12 @@ package ecdar; -import ecdar.abstractions.Component; -import ecdar.abstractions.Project; -import ecdar.backend.BackendDriver; +import ecdar.abstractions.*; +import ecdar.backend.BackendException; import ecdar.backend.BackendHelper; -import ecdar.backend.QueryHandler; import ecdar.backend.SimulationHandler; import ecdar.code_analysis.CodeAnalysis; -import ecdar.controllers.EcdarController; -import ecdar.presentations.BackgroundThreadPresentation; -import ecdar.presentations.EcdarPresentation; -import ecdar.presentations.UndoRedoHistoryPresentation; +import ecdar.issues.ExitStatusCodes; +import ecdar.presentations.*; import ecdar.utility.keyboard.Keybind; import ecdar.utility.keyboard.KeyboardTracker; import javafx.application.Application; @@ -53,14 +49,13 @@ public class Ecdar extends Application { private static EcdarPresentation presentation; private static BooleanProperty isUICached = new SimpleBooleanProperty(); public static BooleanProperty shouldRunBackgroundQueries = new SimpleBooleanProperty(true); - private static final BooleanProperty isSplit = new SimpleBooleanProperty(true); //Set to true to ensure correct behaviour at first toggle. - private static BackendDriver backendDriver = new BackendDriver(); - private static QueryHandler queryHandler = new QueryHandler(backendDriver); + private static final BooleanProperty isSplit = new SimpleBooleanProperty(false); private static SimulationHandler simulationHandler; private Stage debugStage; /** * Gets the absolute path to the server folder + * * @return */ public static String getServerPath() { @@ -75,6 +70,7 @@ public static String getServerPath() { /** * Gets the path to the root directory. + * * @return the path to the root directory * @throws URISyntaxException if the source code location could not be converted to an URI */ @@ -119,6 +115,7 @@ public static void main(final String[] args) { /** * Gets the project. + * * @return the project */ public static Project getProject() { @@ -147,6 +144,7 @@ public static BooleanProperty toggleLeftPane() { * Toggles whether to cache UI. * Caching reduces CPU usage on some devices. * It also increases GPU 3D engine usage on some devices. + * * @return the property specifying whether to cache */ public static BooleanProperty toggleUICache() { @@ -157,6 +155,7 @@ public static BooleanProperty toggleUICache() { /** * Toggles whether checks are run in the background. * Running checks in the background increases CPU usage and power consumption. + * * @return the property specifying whether to run checks in the background */ public static BooleanProperty toggleRunBackgroundQueries() { @@ -168,26 +167,15 @@ public static BooleanProperty toggleQueryPane() { return presentation.toggleRightPane(); } - /** - * Toggles whether to canvas is split or single. - * @return the property specifying whether the canvas is split - */ - public static BooleanProperty toggleCanvasSplit() { - isSplit.set(!isSplit.get()); - + public static BooleanProperty isSplitProperty() { return isSplit; } /** - * Returns the backend driver used to execute queries and handle simulation - * @return BackendDriver + * Toggles whether to canvas is split or single. */ - public static BackendDriver getBackendDriver() { - return backendDriver; - } - - public static QueryHandler getQueryExecutor() { - return queryHandler; + public static void toggleCanvasSplit() { + isSplit.set(!isSplit.get()); } public static SimulationHandler getSimulationHandler() { return simulationHandler; } @@ -213,10 +201,6 @@ public void start(final Stage stage) { // Print launch message the user, if terminal is being launched System.out.println("Launching ECDAR..."); - // Load or create new project - project = new Project(); - simulationHandler = new SimulationHandler(getBackendDriver()); - // Set the title for the application stage.setTitle("Ecdar " + VERSION); @@ -264,13 +248,21 @@ public void start(final Stage stage) { Ecdar.showToast("The application icon could not be loaded"); } - // We're now ready! Let the curtains fall! - stage.show(); + BackendHelper.addEngineInstanceListener(() -> { + // When the engines change, clear the backendDriver + // to prevent dangling connections and queries + try { + BackendHelper.clearEngineConnections(); + } catch (BackendException e) { + showToast("An exception was encountered during shutdown of engine connections"); + } + }); - project.reset(); + // Whenever the Runtime is requested to exit, exit the Platform first + Runtime.getRuntime().addShutdownHook(new Thread(Platform::exit)); - // Set active model - Platform.runLater(() -> EcdarController.setActiveModelForActiveCanvas(Ecdar.getProject().getComponents().get(0))); + // We're now ready! Let the curtains fall! + stage.show(); // Register a key-bind for showing debug-information KeyboardTracker.registerKeybind("DEBUG", new Keybind(new KeyCodeCombination(KeyCode.F12), () -> { @@ -309,85 +301,23 @@ public void start(final Stage stage) { })); stage.setOnCloseRequest(event -> { - BackendHelper.stopQueries(); + int statusCode = ExitStatusCodes.SHUTDOWN_SUCCESSFUL.getStatusCode(); try { - backendDriver.closeAllBackendConnections(); - queryHandler.closeAllBackendConnections(); - simulationHandler.closeAllBackendConnections(); - } catch (IOException e) { - e.printStackTrace(); + BackendHelper.clearEngineConnections(); + } catch (BackendException e) { + statusCode = ExitStatusCodes.CLOSE_ENGINE_CONNECTIONS_FAILED.getStatusCode(); } - Platform.exit(); - System.exit(0); - }); - - BackendHelper.addBackendInstanceListener(() -> { - // When the backend instances change, re-instantiate the backendDriver - // to prevent dangling connections and queries try { - backendDriver.closeAllBackendConnections(); - queryHandler.closeAllBackendConnections(); - simulationHandler.closeAllBackendConnections(); - } catch (IOException e) { - throw new RuntimeException(e); - } - - backendDriver = new BackendDriver(); - }); - } - - /** - * Initializes and resets the project. - * This can be used as a test setup. - */ - public static void setUpForTest() { - project = new Project(); - project.reset(); - - // This implicitly starts the fx-application thread - // It prevents java.lang.RuntimeException: Internal graphics not initialized yet - // https://stackoverflow.com/questions/27839441/internal-graphics-not-initialized-yet-javafx - // new JFXPanel(); - } - - public static void initializeProjectFolder() throws IOException { - // Make sure that the project directory exists - final File directory = new File(projectDirectory.get()); - FileUtils.forceMkdir(directory); - - CodeAnalysis.getErrors().addListener(new ListChangeListener<CodeAnalysis.Message>() { - @Override - public void onChanged(Change<? extends CodeAnalysis.Message> c) { - CodeAnalysis.getErrors().forEach(message -> { - System.out.println(message.getMessage()); - }); + System.exit(statusCode); + } catch (SecurityException e) { + // Forcefully shutdown the Java Runtime + Runtime.getRuntime().halt(ExitStatusCodes.GRACEFUL_SHUTDOWN_FAILED.getStatusCode()); } }); - CodeAnalysis.clearErrorsAndWarnings(); - CodeAnalysis.disable(); - getProject().clean(); - - // Deserialize the project - Ecdar.getProject().deserialize(directory); - CodeAnalysis.enable(); - // Generate all component presentations by making them the active component in the view one by one - Component initialShownComponent = null; - for (final Component component : Ecdar.getProject().getComponents()) { - // The first component should be shown - if (initialShownComponent == null) { - initialShownComponent = component; - } - EcdarController.setActiveModelForActiveCanvas(component); - } - - // If we found a component set that as active - if (initialShownComponent != null) { - EcdarController.setActiveModelForActiveCanvas(initialShownComponent); - } - serializationDone = true; + project = presentation.getController().getEditorPresentation().getController().projectPane.getController().project; } private void loadFonts() { @@ -422,6 +352,44 @@ private void loadFonts() { Font.loadFont(getClass().getResourceAsStream("fonts/roboto_mono/RobotoMono-ThinItalic.ttf"), 14); } + /** + * Initializes and resets the project. + * This can be used as a test setup. + */ + public static void setUpForTest() { + project = new Project(); + + // This implicitly starts the fx-application thread + // It prevents java.lang.RuntimeException: Internal graphics not initialized yet + // https://stackoverflow.com/questions/27839441/internal-graphics-not-initialized-yet-javafx + // new JFXPanel(); + } + + public static void initializeProjectFolder() throws IOException { + // Make sure that the project directory exists + final File directory = new File(projectDirectory.get()); + FileUtils.forceMkdir(directory); + + CodeAnalysis.getErrors().addListener((ListChangeListener<CodeAnalysis.Message>) c -> CodeAnalysis.getErrors().forEach(message -> { + System.out.println(message.getMessage()); + })); + + CodeAnalysis.clearErrorsAndWarnings(); + CodeAnalysis.disable(); + getProject().clean(); + + // Deserialize the project + getProject().deserialize(directory); + CodeAnalysis.enable(); + + // If we found a component set that as active + serializationDone = true; + } + + public static ComponentPresentation getComponentPresentationOfComponent(Component component) { + return getPresentation().getController().getEditorPresentation().getController().projectPane.getController().getComponentPresentations().stream().filter(componentPresentation -> componentPresentation.getController().getComponent().equals(component)).findFirst().orElse(null); + } + private static String getVersion() { Properties defaultProps = new Properties(); try { diff --git a/src/main/java/ecdar/abstractions/Component.java b/src/main/java/ecdar/abstractions/Component.java index 1d0d38c0..b83808a2 100644 --- a/src/main/java/ecdar/abstractions/Component.java +++ b/src/main/java/ecdar/abstractions/Component.java @@ -1,36 +1,28 @@ package ecdar.abstractions; -import com.bpodgursky.jbool_expressions.*; -import com.bpodgursky.jbool_expressions.rules.RuleSet; -import ecdar.Ecdar; -import ecdar.utility.ExpressionHelper; import ecdar.utility.UndoRedoStack; -import ecdar.utility.colors.Color; import ecdar.utility.colors.EnabledColor; import ecdar.utility.helpers.Boxed; +import ecdar.utility.helpers.MouseCircular; import com.google.gson.JsonArray; import com.google.gson.JsonObject; -import ecdar.utility.helpers.MouseCircular; -import ecdar.utility.keyboard.NudgeDirection; import javafx.beans.property.*; import javafx.beans.value.ChangeListener; import javafx.collections.FXCollections; import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; -import javafx.collections.transformation.FilteredList; -import javafx.util.Pair; -import org.apache.commons.lang3.tuple.Triple; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; +import static ecdar.abstractions.Project.LOCATION; + /** * A component that models an I/O automata. */ -public class Component extends HighLevelModelObject implements Boxed { - private static final String COMPONENT = "Component"; +public class Component extends HighLevelModel implements Boxed { private static final String LOCATIONS = "locations"; private static final String EDGES = "edges"; private static final String INCLUDE_IN_PERIODIC_CHECK = "includeInPeriodicCheck"; @@ -53,7 +45,6 @@ public class Component extends HighLevelModelObject implements Boxed { // Styling properties private final Box box = new Box(); private final BooleanProperty declarationOpen = new SimpleBooleanProperty(false); - private final BooleanProperty firsTimeShown = new SimpleBooleanProperty(false); public Location previousLocationForDraggedEdge; public boolean getIsFailing(){return isFailing.get();} @@ -71,272 +62,32 @@ public Component() { /** * Creates a component with a specific name and a boolean value that chooses whether the colour for this component is chosen at random - * @param doRandomColor boolean that is true if the component should choose a colour at random and false if not + * @param color boolean that is true if the component should choose a colour at random and false if not */ - public Component(final boolean doRandomColor) { - setComponentName(); - - if(doRandomColor) { - setRandomColor(); - } + public Component(final EnabledColor color, final String name) { + setName(name); + setColor(color); // Make initial location final Location initialLocation = new Location(); - initialLocation.initialize(); + initialLocation.initialize(LOCATION + "1"); initialLocation.setType(Location.Type.INITIAL); - initialLocation.setColorIntensity(getColorIntensity()); - initialLocation.setColor(getColor()); // Place in center initialLocation.setX(box.getX() + box.getWidth() / 2); initialLocation.setY(box.getY() + box.getHeight() / 2); + initialLocation.setColor(color); - locations.add(initialLocation); + addLocation(initialLocation); initializeIOListeners(); } public Component(final JsonObject json) { - setFirsTimeShown(true); - deserialize(json); initializeIOListeners(); updateIOList(); } - /** - * Creates a clone of another component. - * Copies objects used for verification (e.g. locations, edges and the declarations). - * Does not copy UI elements (sizes and positions). - * It locations are cloned from the original component. Their ids are the same. - * Does not initialize io listeners, but copies the input and output strings. - * Reachability analysis binding is not initialized. - * @return the clone - */ - public Component cloneForVerification() { - final Component clone = new Component(); - clone.addVerificationObjects(this); - clone.setIncludeInPeriodicCheck(false); - clone.inputStrings.addAll(getInputStrings()); - clone.outputStrings.addAll(getOutputStrings()); - clone.setName(getName()); - - return clone; - } - - /** - * Adds objects used for verifications to this component. - * @param original the component to add from - */ - private void addVerificationObjects(final Component original) { - for (final Location originalLoc : original.getLocations()) { - addLocation(originalLoc.cloneForVerification()); - } - - getListOfEdgesFromDisplayableEdges(original.getDisplayableEdges()).forEach(edge -> addEdge((edge).cloneForVerification(this))); - setDeclarationsText(original.getDeclarationsText()); - } - - /** - * Applies angelic completion on this component. - */ - public void applyAngelicCompletion() { - // Cache input signature, since it could be updated when added edges - final List<String> inputStrings = new ArrayList<>(getInputStrings()); - - getLocations().forEach(location -> inputStrings.forEach(input -> { - // Get outgoing input edges that has the chosen input - final List<Edge> matchingEdges = getListOfEdgesFromDisplayableEdges(getOutgoingEdges(location)).stream().filter( - edge -> edge.getStatus().equals(EdgeStatus.INPUT) && - edge.getSync().equals(input)).collect(Collectors.toList() - ); - - // If no such edges, add a self loop without a guard - if (matchingEdges.isEmpty()) { - final Edge edge = new Edge(location, EdgeStatus.INPUT); - edge.setTargetLocation(location); - edge.addSyncNail(input); - addEdge(edge); - return; - } - - // If an edge has no guard and its target has no invariants, ignore. - // Component is already input-enabled with respect to this location and input. - if (matchingEdges.stream().anyMatch(edge -> edge.getGuard().isEmpty() && - edge.getTargetLocation().getInvariant().isEmpty())) return; - - // Extract expression for which edges to create. - // The expression is in DNF - // We create self loops for each child expression in the disjunction. - createAngelicSelfLoops(location, input, getNegatedEdgeExpression(matchingEdges)); - })); - } - - /** - * Extracts an expression that represents the negation of a list of edges. - * Multiple edges are resolved as disjunctions. - * There can be conjunction in guards. - * We translate each edge to the conjunction of its guard and the invariant of its target location - * (since it should also be satisfied). - * The result is converted to disjunctive normal form. - * @param edges the edges to extract from - * @return the expression that represents the negation - */ - private Expression<String> getNegatedEdgeExpression(final List<Edge> edges) { - final List<String> clocks = getClocks(); - return ExpressionHelper.simplifyNegatedSimpleExpressions( - RuleSet.toDNF(RuleSet.simplify(Not.of(Or.of(edges.stream() - .map(edge -> { - final List<String> clocksToReset = ExpressionHelper.getUpdateSides(edge.getUpdate()) - .keySet().stream().filter(clocks::contains).collect(Collectors.toList()); - return And.of( - ExpressionHelper.parseGuard(edge.getGuard()), - ExpressionHelper.parseInvariantButIgnore(edge.getTargetLocation().getInvariant(), clocksToReset) - ); - } - ).collect(Collectors.toList())) - )))); - } - - /** - * Creates self loops on a location to finish missing inputs with an angelic completion. - * The guard expression should be in DNF without negations. - * @param location the location to create self loops on - * @param input the input action to use in the synchronization properties - * @param guardExpression the expression that represents the guards of the self loops - */ - private void createAngelicSelfLoops(final Location location, final String input, final Expression<String> guardExpression) { - final Edge edge; - - switch (guardExpression.getExprType()) { - case Literal.EXPR_TYPE: - // If false, do not create any loops - if (!((Literal<String>) guardExpression).getValue()) break; - - // It should never be true, since that should be handled before calling this method - throw new RuntimeException("Type of expression " + guardExpression + " not accepted"); - case Variable.EXPR_TYPE: - edge = new Edge(location, EdgeStatus.INPUT); - edge.setTargetLocation(location); - edge.addSyncNail(input); - edge.addGuardNail(((Variable<String>) guardExpression).getValue()); - addEdge(edge); - break; - case And.EXPR_TYPE: - edge = new Edge(location, EdgeStatus.INPUT); - edge.setTargetLocation(location); - edge.addSyncNail(input); - edge.addGuardNail(String.join("&&", - ((And<String>) guardExpression).getChildren().stream() - .map(child -> { - if (!child.getExprType().equals(Variable.EXPR_TYPE)) - throw new RuntimeException("Child " + child + " of type " + - child.getExprType() + " in and expression " + - guardExpression + " should be a variable"); - - return ((Variable<String>) child).getValue(); - }) - .collect(Collectors.toList()) - )); - addEdge(edge); - break; - case Or.EXPR_TYPE: - guardExpression.getChildren().forEach(child -> createAngelicSelfLoops(location, input, child)); - break; - default: - throw new RuntimeException("Type of expression " + guardExpression + " not accepted"); - } - } - - /** - * Applies demonic completion on this component. - */ - public void applyDemonicCompletion() { - // Make a universal location - final Location uniLocation = new Location(this, Location.Type.UNIVERSAL, 0, 0); - addLocation(uniLocation); - - final Edge inputEdge = uniLocation.addLeftEdge("*", EdgeStatus.INPUT); - inputEdge.setIsLocked(true); - addEdge(inputEdge); - - final Edge outputEdge = uniLocation.addRightEdge("*", EdgeStatus.OUTPUT); - outputEdge.setIsLocked(true); - addEdge(outputEdge); - - // Cache input signature, since it could be updated when added edges - final List<String> inputStrings = new ArrayList<>(getInputStrings()); - - getLocations().forEach(location -> inputStrings.forEach(input -> { - // Get outgoing input edges that has the chosen input - final List<Edge> matchingEdges = getListOfEdgesFromDisplayableEdges(getOutgoingEdges(location)).stream().filter( - edge -> edge.getStatus().equals(EdgeStatus.INPUT) && - edge.getSync().equals(input)).collect(Collectors.toList() - ); - - // If no such edges, add an edge to Universal - if (matchingEdges.isEmpty()) { - final Edge edge = new Edge(location, EdgeStatus.INPUT); - edge.setTargetLocation(uniLocation); - edge.addSyncNail(input); - addEdge(edge); - return; - } - // If an edge has no guard and its target has no invariants, ignore. - // Component is already input-enabled with respect to this location and input. - if (matchingEdges.stream().anyMatch(edge -> edge.getGuard().isEmpty() && - edge.getTargetLocation().getInvariant().isEmpty())) return; - - // Extract expression for which edges to create. - // The expression is in DNF - // We create edges to Universal for each child expression in the disjunction. - createDemonicEdges(location, uniLocation, input, getNegatedEdgeExpression(matchingEdges)); - })); - } - - /** - * Creates edges to a specified Universal location from a location - * in order to finish missing inputs with a demonic completion. - * @param location the location to create self loops on - * @param universal the Universal location to create edge to - * @param input the input action to use in the synchronization properties - * @param guardExpression the expression that represents the guards of the edges to create - */ - private void createDemonicEdges(final Location location, final Location universal, final String input, final Expression<String> guardExpression) { - final Edge edge; - - switch (guardExpression.getExprType()) { - case Literal.EXPR_TYPE: - // If false, do not create any edges - if (!((Literal<String>) guardExpression).getValue()) break; - - // It should never be true, since that should be handled before calling this method - throw new RuntimeException("Type of expression " + guardExpression + " not accepted"); - case Variable.EXPR_TYPE: - edge = new Edge(location, EdgeStatus.INPUT); - edge.setTargetLocation(universal); - edge.addSyncNail(input); - edge.addGuardNail(((Variable<String>) guardExpression).getValue()); - addEdge(edge); - break; - case And.EXPR_TYPE: - edge = new Edge(location, EdgeStatus.INPUT); - edge.setTargetLocation(universal); - edge.addSyncNail(input); - edge.addGuardNail(String.join("&&", - ((And<String>) guardExpression).getChildren().stream() - .map(child -> ((Variable<String>) child).getValue()) - .collect(Collectors.toList()) - )); - addEdge(edge); - break; - case Or.EXPR_TYPE: - ((Or<String>) guardExpression).getChildren().forEach(child -> createDemonicEdges(location, universal, input, child)); - break; - default: - throw new RuntimeException("Type of expression " + guardExpression + " not accepted"); - } - } - /** * Initialises IO listeners, adding change listener to the list of edges * Also adds listeners to all current edges in edges. @@ -383,7 +134,7 @@ private void initializeIOListeners() { }); // Add listener to edges initially - edges.forEach(edge -> getEdgeOrSubEdges(edge).forEach(subEdge -> addSyncListener(listener, subEdge))); + getEdges().forEach(edge -> getEdgeOrSubEdges(edge).forEach(subEdge -> addSyncListener(listener, subEdge))); } /** @@ -396,60 +147,6 @@ private static void addSyncListener(final ChangeListener<Object> listener, final edge.ioStatus.addListener(listener); } - /** - * Method used for updating the inputstrings and outputstrings list - * Sorts the list alphabetically, ignoring case - */ - public void updateIOList() { - final List<String> localInputStrings = new ArrayList<>(); - final List<String> localOutputStrings = new ArrayList<>(); - - List<Edge> edgeList = getListOfEdgesFromDisplayableEdges(edges); - - for (final Edge edge : edgeList) { - // Extract channel id based on UPPAAL id definition - final String channel = edge.getSync().replaceAll("^([a-zA-Z_][a-zA-Z0-9_]*).*$", "$1"); - - if(edge.getStatus() == EdgeStatus.INPUT){ - if(!edge.getSync().equals("*") && !localInputStrings.contains(channel)){ - localInputStrings.add(channel); - } - } else if (edge.getStatus() == EdgeStatus.OUTPUT) { - if(!edge.getSync().equals("*") && !localOutputStrings.contains(channel)){ - localOutputStrings.add(channel); - } - } - } - - // Sort the String alphabetically, ignoring case (e.g. all strings starting with "C" are placed together) - localInputStrings.sort((item1, item2) -> item1.compareToIgnoreCase(item2)); - localOutputStrings.sort((item1, item2) -> item1.compareToIgnoreCase(item2)); - - inputStrings.setAll(localInputStrings); - outputStrings.setAll(localOutputStrings); - } - - private List<Edge> getListOfEdgesFromDisplayableEdges(List<DisplayableEdge> displayableEdges) { - List<Edge> result = new ArrayList<>(); - for (final DisplayableEdge originalEdge : displayableEdges) { - result.addAll(getEdgeOrSubEdges(originalEdge)); - } - - return result; - } - - private List<Edge> getEdgeOrSubEdges(DisplayableEdge edge) { - List<Edge> result = new ArrayList<>(); - - if(edge instanceof Edge) { - result.add((Edge) edge); - } else { - result.addAll(((GroupedEdge) edge).getEdges()); - } - - return result; - } - /** * Get all locations in this, but the initial location (if one exists). * O(n), n is # of locations in component. @@ -492,6 +189,15 @@ public boolean removeLocation(final Location location) { return locations.remove(location); } + public String getUniqueLocationId() { + final var currentIds = getLocations().stream().map(Location::getId).collect(Collectors.toSet()); + for (int counter = 1; ; counter++) { + if (!currentIds.contains(LOCATION + counter)) { + return LOCATION + counter; + } + } + } + /** * Add a failing Edge to the list of failing edges * and set the edge's failing property to true. @@ -605,6 +311,27 @@ public synchronized List<DisplayableEdge> getOutgoingEdges(final Location locati return getDisplayableEdges().filtered(edge -> location == edge.getSourceLocation()); } + public List<Edge> getListOfEdgesFromDisplayableEdges(List<DisplayableEdge> displayableEdges) { + List<Edge> result = new ArrayList<>(); + for (final DisplayableEdge originalEdge : displayableEdges) { + result.addAll(getEdgeOrSubEdges(originalEdge)); + } + + return result; + } + + private List<Edge> getEdgeOrSubEdges(DisplayableEdge edge) { + List<Edge> result = new ArrayList<>(); + + if(edge instanceof Edge) { + result.add((Edge) edge); + } else { + result.addAll(((GroupedEdge) edge).getEdges()); + } + + return result; + } + public boolean isDeclarationOpen() { return declarationOpen.get(); } @@ -655,18 +382,6 @@ public DisplayableEdge getUnfinishedEdge() { return null; } - public boolean isFirsTimeShown() { - return firsTimeShown.get(); - } - - public BooleanProperty firsTimeShownProperty() { - return firsTimeShown; - } - - public void setFirsTimeShown(final boolean firsTimeShown) { - this.firsTimeShown.set(firsTimeShown); - } - public boolean isIncludeInPeriodicCheck() { return includeInPeriodicCheck.get(); } @@ -679,157 +394,6 @@ public void setIncludeInPeriodicCheck(final boolean includeInPeriodicCheck) { this.includeInPeriodicCheck.set(includeInPeriodicCheck); } - /** - * Checks if there is currently an edge without a source location. - */ - public boolean isAnyEdgeWithoutSource() { - DisplayableEdge edgeWithoutSource = null; - - for (DisplayableEdge edge : getDisplayableEdges()) { - if (edge.sourceCircularProperty().get() instanceof MouseCircular) { - edgeWithoutSource = edge; - break; - } - } - - return edgeWithoutSource != null; - } - - @Override - public JsonObject serialize() { - final JsonObject result = super.serialize(); - result.addProperty(DECLARATIONS, getDeclarationsText()); - - final JsonArray locations = new JsonArray(); - getLocations().forEach(location -> locations.add(location.serialize())); - result.add(LOCATIONS, locations); - - final JsonArray edges = new JsonArray(); - getListOfEdgesFromDisplayableEdges(this.edges).forEach(edge -> edges.add(edge.serialize())); - - result.add(EDGES, edges); - result.addProperty(DESCRIPTION, getDescription()); - box.addProperties(result); - result.addProperty(COLOR, EnabledColor.getIdentifier(getColor())); - result.addProperty(INCLUDE_IN_PERIODIC_CHECK, isIncludeInPeriodicCheck()); - - return result; - } - - @Override - public void deserialize(final JsonObject json) { - super.deserialize(json); - - setDeclarationsText(json.getAsJsonPrimitive(DECLARATIONS).getAsString()); - - json.getAsJsonArray(LOCATIONS).forEach(jsonElement -> { - final Location newLocation = new Location((JsonObject) jsonElement); - locations.add(newLocation); - }); - - json.getAsJsonArray(EDGES).forEach(jsonElement -> { - JsonObject edgeObject = (JsonObject) jsonElement; - final Edge newEdge = new Edge((JsonObject) jsonElement, this); - - String edgeGroup = ""; - if (edgeObject.has("group")) { - edgeGroup = edgeObject.getAsJsonPrimitive("group").getAsString(); - } - - if (!edgeGroup.isEmpty()) { - GroupedEdge groupedEdge = null; - for (DisplayableEdge edge : edges) { - if (edge instanceof GroupedEdge && edge.getId().equals(edgeGroup)) { - groupedEdge = ((GroupedEdge) edge); - break; - } - } - - if (groupedEdge == null) { - GroupedEdge newGroupedEdge = new GroupedEdge(newEdge); - newGroupedEdge.setId(edgeGroup); - - edges.add(newGroupedEdge); - } else { - boolean hasSameGuardAndUpdate = groupedEdge.addEdgeToGroup(newEdge); - - if (!hasSameGuardAndUpdate) { - // The edge has the same group id as another edge, but has different guard and/or update - newEdge.setGroup(""); - edges.add(newEdge); - } - } - } else { - edges.add(newEdge); - } - }); - - setDescription(json.getAsJsonPrimitive(DESCRIPTION).getAsString()); - box.setProperties(json); - - if (box.getWidth() == 0 && box.getHeight() == 0) { - box.setWidth(locations.stream().max(Comparator.comparingDouble(Location::getX)).get().getX() + Ecdar.CANVAS_PADDING * 10); - box.setHeight(locations.stream().max(Comparator.comparingDouble(Location::getY)).get().getY() + Ecdar.CANVAS_PADDING * 10); - } - - final EnabledColor enabledColor = (json.has(COLOR) ? EnabledColor.fromIdentifier(json.getAsJsonPrimitive(COLOR).getAsString()) : null); - if (enabledColor != null) { - setColorIntensity(enabledColor.intensity); - setColor(enabledColor.color); - } else { - setRandomColor(); - for(Location loc : locations) { - loc.setColor(this.getColor()); - loc.setColorIntensity(this.getColorIntensity()); - } - } - - if(json.has(INCLUDE_IN_PERIODIC_CHECK)) { - setIncludeInPeriodicCheck(json.getAsJsonPrimitive(INCLUDE_IN_PERIODIC_CHECK).getAsBoolean()); - } else { - setIncludeInPeriodicCheck(false); - } - } - - /** - * Dyes the component and its locations. - * @param color the color to dye with - * @param intensity the intensity of the color - */ - public void dye(final Color color, final Color.Intensity intensity) { - final Color previousColor = colorProperty().get(); - final Color.Intensity previousColorIntensity = colorIntensityProperty().get(); - - final Map<Location, Pair<Color, Color.Intensity>> previousLocationColors = new HashMap<>(); - - for (final Location location : getLocations()) { - if (!location.getColor().equals(previousColor)) continue; - previousLocationColors.put(location, new Pair<>(location.getColor(), location.getColorIntensity())); - } - - UndoRedoStack.pushAndPerform(() -> { // Perform - // Color the component - setColorIntensity(intensity); - setColor(color); - - // Color all of the locations - previousLocationColors.keySet().forEach(location -> { - location.setColorIntensity(intensity); - location.setColor(color); - }); - }, () -> { // Undo - // Color the component - setColorIntensity(previousColorIntensity); - setColor(previousColor); - - // Color the locations accordingly to the previous color for them - previousLocationColors.keySet().forEach(location -> { - location.setColorIntensity(previousLocationColors.get(location).getValue()); - location.setColor(previousLocationColors.get(location).getKey()); - }); - }, String.format("Changed the color of %s to %s", this, color.name()), "color-lens"); - } - public String getDescription() { return description.get(); } @@ -850,36 +414,6 @@ public ObservableList<String> getOutputStrings() { return outputStrings; } - /** - * Generate and sets a unique id for this system - */ - private void setComponentName() { - for(int counter = 1; ; counter++) { - if(!Ecdar.getProject().getComponentNames().contains(COMPONENT + counter)){ - setName((COMPONENT + counter)); - return; - } - } - } - - /** - * Generates an id to be used by universal and inconsistent locations in this component, - * if one has already been generated, return that instead - * @return generated universal/inconsistent id - */ - String generateUniIncId(){ - final String id = getUniIncId(); - if(id != null){ - return id; - } else { - for(int counter = 0; ;counter++){ - if(!Ecdar.getProject().getUniIncIds().contains(String.valueOf(counter))){ - return String.valueOf(counter); - } - } - } - } - /** * Gets the id used by universal and inconsistent locations located in this component, * if neither universal nor inconsistent locations exist in this component it returns null. @@ -894,32 +428,6 @@ public String getUniIncId() { return null; } - /** - * Gets the id of all locations in this component - * @return ids of all locations in this component - */ - HashSet<String> getLocationIds() { - final HashSet<String> ids = new HashSet<>(); - for (final Location location : getLocations()){ - if(location.getType() != Location.Type.UNIVERSAL || location.getType() != Location.Type.INCONSISTENT){ - ids.add(location.getId().substring(Location.ID_LETTER_LENGTH)); - } - } - return ids; - } - - /** - * Gets the id of all edges in this component - * @return ids of all edges in this component - */ - HashSet<String> getEdgeIds() { - final HashSet<String> ids = new HashSet<>(); - for (final Edge edge : getEdges()){ - ids.add(edge.getId().substring(Edge.ID_LETTER_LENGTH)); - } - return ids; - } - public String getDeclarationsText() { return declarationsText.get(); } @@ -932,11 +440,6 @@ public StringProperty declarationsTextProperty() { return declarationsText; } - @Override - public Box getBox() { - return box; - } - /** * Gets the clocks defined in the declarations text. * @return the clocks @@ -969,79 +472,188 @@ public List<String> getLocalVariables() { return locals; } + public List<DisplayableEdge> getInputEdges() { + return getDisplayableEdges().filtered(edge -> edge.getStatus().equals(EdgeStatus.INPUT)); + } + + public List<DisplayableEdge> getOutputEdges() { + return getDisplayableEdges().filtered(edge -> edge.getStatus().equals(EdgeStatus.OUTPUT)); + } + /** - * Gets the local variables defined in the declarations text. - * Also gets the lower and upper bounds for these variables. - * @return Triples containing (left) name of the variable, (middle) lower bound, (right) upper bound + * Dyes the component and its locations. + * @param color the color to dye with */ - public List<Triple<String, Integer, Integer>> getLocalVariablesWithBounds() { - final List<Triple<String, Integer, Integer>> typedefs = Ecdar.getProject().getGlobalDeclarations().getTypedefs(); + public void dye(final EnabledColor color) { + final EnabledColor previousColor = colorProperty().get(); - final List<Triple<String, Integer, Integer>> locals = new ArrayList<>(); + final Map<Location, EnabledColor> previousLocationColors = new HashMap<>(); - Arrays.stream(getDeclarationsText().split(";")).forEach(statement -> { - final Matcher matcher = Pattern.compile("^\\s*(\\w+)\\s+(\\w+)(\\W|$)").matcher(statement); - if (!matcher.find()) return; + for (final Location location : getLocations()) { + if (!location.getColor().equals(previousColor)) continue; + previousLocationColors.put(location, location.getColor()); + } - final Optional<Triple<String, Integer, Integer>> typedef = typedefs.stream() - .filter(def -> def.getLeft().equals(matcher.group(1))).findAny(); - if (!typedef.isPresent()) return; + UndoRedoStack.pushAndPerform(() -> { // Perform + // Color the component + setColor(color); - locals.add(Triple.of(matcher.group(2), typedef.get().getMiddle(), typedef.get().getRight())); - }); + // Color all of the locations + previousLocationColors.keySet().forEach(location -> { + location.setColor(color); + }); + }, () -> { // Undo + // Color the component + setColor(previousColor); - return locals; + // Color the locations accordingly to the previous color for them + previousLocationColors.keySet().forEach(location -> { + location.setColor(previousLocationColors.get(location)); + }); + }, String.format("Changed the color of %s to %s", this, color.color.name()), "color-lens"); } /** - * Gets the first occurring universal location in this component. - * @return the first universal location, or null if none exists + * Checks if there is currently an edge without a source location. */ - public Location getUniversalLocation() { - final FilteredList<Location> uniLocs = getLocations().filtered(l -> l.getType().equals(Location.Type.UNIVERSAL)); + public boolean isAnyEdgeWithoutSource() { + DisplayableEdge edgeWithoutSource = null; - if (uniLocs.isEmpty()) return null; + for (DisplayableEdge edge : getDisplayableEdges()) { + if (edge.sourceCircularProperty().get() instanceof MouseCircular) { + edgeWithoutSource = edge; + break; + } + } - return uniLocs.get(0); + return edgeWithoutSource != null; } /** - * Moves all nodes left. + * Method used for updating the inputstrings and outputstrings list + * Sorts the list alphabetically, ignoring case */ - public void moveAllNodesLeft() { - getLocations().forEach(loc -> loc.setX(loc.getX() + NudgeDirection.LEFT.getXOffset())); - getDisplayableEdges().forEach(edge -> edge.getNails().forEach(nail -> nail.setX(nail.getX() + NudgeDirection.LEFT.getXOffset()))); - } + public void updateIOList() { + final List<String> localInputStrings = new ArrayList<>(); + final List<String> localOutputStrings = new ArrayList<>(); - /** - * Moves all nodes right. - */ - public void moveAllNodesRight() { - getLocations().forEach(loc -> loc.setX(loc.getX() + NudgeDirection.RIGHT.getXOffset())); - getDisplayableEdges().forEach(edge -> edge.getNails().forEach(nail -> nail.setX(nail.getX() + NudgeDirection.RIGHT.getXOffset()))); - } + List<Edge> edgeList = getListOfEdgesFromDisplayableEdges(edges); - /** - * Moves all nodes down. - */ - public void moveAllNodesDown() { - getLocations().forEach(loc -> loc.setY(loc.getY() + NudgeDirection.DOWN.getYOffset())); - getDisplayableEdges().forEach(edge -> edge.getNails().forEach(nail -> nail.setY(nail.getY() + NudgeDirection.DOWN.getYOffset()))); + for (final Edge edge : edgeList) { + // Extract channel id based on UPPAAL id definition + final String channel = edge.getSync().replaceAll("^([a-zA-Z_][a-zA-Z0-9_]*).*$", "$1"); + + if(edge.getStatus() == EdgeStatus.INPUT){ + if(!edge.getSync().equals("*") && !localInputStrings.contains(channel)){ + localInputStrings.add(channel); + } + } else if (edge.getStatus() == EdgeStatus.OUTPUT) { + if(!edge.getSync().equals("*") && !localOutputStrings.contains(channel)){ + localOutputStrings.add(channel); + } + } + } + + // Sort the String alphabetically, ignoring case (e.g. all strings starting with "C" are placed together) + localInputStrings.sort((item1, item2) -> item1.compareToIgnoreCase(item2)); + localOutputStrings.sort((item1, item2) -> item1.compareToIgnoreCase(item2)); + + inputStrings.setAll(localInputStrings); + outputStrings.setAll(localOutputStrings); } - /** - * Moves all nodes up. - */ - public void moveAllNodesUp() { - getLocations().forEach(loc -> loc.setY(loc.getY() + NudgeDirection.UP.getYOffset())); - getDisplayableEdges().forEach(edge -> edge.getNails().forEach(nail -> nail.setY(nail.getY() + NudgeDirection.UP.getYOffset()))); + @Override + public Box getBox() { + return box; } - public List<DisplayableEdge> getInputEdges() { - return getDisplayableEdges().filtered(edge -> edge.getStatus().equals(EdgeStatus.INPUT)); + @Override + public JsonObject serialize() { + final JsonObject result = super.serialize(); + result.addProperty(DECLARATIONS, getDeclarationsText()); + + final JsonArray locations = new JsonArray(); + getLocations().forEach(location -> locations.add(location.serialize())); + result.add(LOCATIONS, locations); + + final JsonArray edges = new JsonArray(); + getListOfEdgesFromDisplayableEdges(this.edges).forEach(edge -> edges.add(edge.serialize())); + + result.add(EDGES, edges); + result.addProperty(DESCRIPTION, getDescription()); + box.addProperties(result); + result.addProperty(COLOR, EnabledColor.getIdentifier(getColor().color)); + result.addProperty(INCLUDE_IN_PERIODIC_CHECK, isIncludeInPeriodicCheck()); + + return result; } - public List<DisplayableEdge> getOutputEdges() { - return getDisplayableEdges().filtered(edge -> edge.getStatus().equals(EdgeStatus.OUTPUT)); + @Override + public void deserialize(final JsonObject json) { + super.deserialize(json); + + setDeclarationsText(json.getAsJsonPrimitive(DECLARATIONS).getAsString()); + + json.getAsJsonArray(LOCATIONS).forEach(jsonElement -> { + final Location newLocation = new Location((JsonObject) jsonElement); + locations.add(newLocation); + }); + + json.getAsJsonArray(EDGES).forEach(jsonElement -> { + JsonObject edgeObject = (JsonObject) jsonElement; + final Edge newEdge = new Edge((JsonObject) jsonElement, this); + + String edgeGroup = ""; + if (edgeObject.has("group")) { + edgeGroup = edgeObject.getAsJsonPrimitive("group").getAsString(); + } + + if (!edgeGroup.isEmpty()) { + GroupedEdge groupedEdge = null; + for (DisplayableEdge edge : edges) { + if (edge instanceof GroupedEdge && edge.getId().equals(edgeGroup)) { + groupedEdge = ((GroupedEdge) edge); + break; + } + } + + if (groupedEdge == null) { + GroupedEdge newGroupedEdge = new GroupedEdge(newEdge); + newGroupedEdge.setId(edgeGroup); + + edges.add(newGroupedEdge); + } else { + boolean hasSameGuardAndUpdate = groupedEdge.addEdgeToGroup(newEdge); + + if (!hasSameGuardAndUpdate) { + // The edge has the same group id as another edge, but has different guard and/or update + newEdge.setGroup(""); + edges.add(newEdge); + } + } + } else { + edges.add(newEdge); + } + }); + + setDescription(json.getAsJsonPrimitive(DESCRIPTION).getAsString()); + box.setProperties(json); + + if (box.getWidth() == 0 && box.getHeight() == 0) { + box.setWidth(locations.stream().max(Comparator.comparingDouble(Location::getX)).get().getX() + 10 * 10); + box.setHeight(locations.stream().max(Comparator.comparingDouble(Location::getY)).get().getY() + 10 * 10); + } + + final EnabledColor enabledColor = (json.has(COLOR) ? EnabledColor.fromIdentifier(json.getAsJsonPrimitive(COLOR).getAsString()) : EnabledColor.getDefault()); + setColor(enabledColor); + for(Location loc : locations) { + loc.setColor(enabledColor); + } + + if(json.has(INCLUDE_IN_PERIODIC_CHECK)) { + setIncludeInPeriodicCheck(json.getAsJsonPrimitive(INCLUDE_IN_PERIODIC_CHECK).getAsBoolean()); + } else { + setIncludeInPeriodicCheck(false); + } } } diff --git a/src/main/java/ecdar/abstractions/Declarations.java b/src/main/java/ecdar/abstractions/Declarations.java index 2014d729..3f42d953 100644 --- a/src/main/java/ecdar/abstractions/Declarations.java +++ b/src/main/java/ecdar/abstractions/Declarations.java @@ -2,6 +2,7 @@ import ecdar.utility.colors.Color; import com.google.gson.JsonObject; +import ecdar.utility.colors.EnabledColor; import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; import org.apache.commons.lang3.tuple.Triple; @@ -15,7 +16,7 @@ * Overall declarations of a model. * This could be global declarations or system declarations. */ -public class Declarations extends HighLevelModelObject { +public class Declarations extends HighLevelModel { private final StringProperty declarationsText = new SimpleStringProperty(""); /** @@ -24,13 +25,13 @@ public class Declarations extends HighLevelModelObject { */ public Declarations(final String name) { setName(name); - setColor(Color.AMBER); + setColor(EnabledColor.getDefault()); } public Declarations(final JsonObject json) { deserialize(json); - setColor(Color.AMBER); + setColor(EnabledColor.getDefault()); } public String getDeclarationsText() { diff --git a/src/main/java/ecdar/abstractions/DisplayableEdge.java b/src/main/java/ecdar/abstractions/DisplayableEdge.java index aa93dc7f..52ced7b5 100644 --- a/src/main/java/ecdar/abstractions/DisplayableEdge.java +++ b/src/main/java/ecdar/abstractions/DisplayableEdge.java @@ -2,6 +2,7 @@ import ecdar.code_analysis.Nearable; import ecdar.utility.colors.Color; +import ecdar.utility.colors.EnabledColor; import ecdar.utility.helpers.Circular; import ecdar.utility.helpers.MouseCircular; import ecdar.utility.helpers.StringHelper; @@ -27,8 +28,7 @@ public abstract class DisplayableEdge implements Nearable { private final StringProperty update = new SimpleStringProperty(""); // Styling properties - private final ObjectProperty<Color> color = new SimpleObjectProperty<>(Color.GREY_BLUE); - private final ObjectProperty<Color.Intensity> colorIntensity = new SimpleObjectProperty<>(Color.Intensity.I700); + private final ObjectProperty<EnabledColor> color = new SimpleObjectProperty<>(EnabledColor.getDefault()); private final ObservableList<Nail> nails = FXCollections.observableArrayList(); // Circulars @@ -109,30 +109,18 @@ public StringProperty updateProperty() { return update; } - public Color getColor() { + public EnabledColor getColor() { return color.get(); } - public void setColor(final Color color) { + public void setColor(final EnabledColor color) { this.color.set(color); } - public ObjectProperty<Color> colorProperty() { + public ObjectProperty<EnabledColor> colorProperty() { return color; } - public Color.Intensity getColorIntensity() { - return colorIntensity.get(); - } - - public void setColorIntensity(final Color.Intensity colorIntensity) { - this.colorIntensity.set(colorIntensity); - } - - public ObjectProperty<Color.Intensity> colorIntensityProperty() { - return colorIntensity; - } - public void setIsHighlighted(final boolean highlight){ this.isHighlighted.set(highlight);} public boolean getIsHighlighted(){ return this.isHighlighted.get(); } diff --git a/src/main/java/ecdar/abstractions/EcdarModel.java b/src/main/java/ecdar/abstractions/EcdarModel.java deleted file mode 100644 index c298e6ae..00000000 --- a/src/main/java/ecdar/abstractions/EcdarModel.java +++ /dev/null @@ -1,8 +0,0 @@ -package ecdar.abstractions; - -/** - * Ecdar graphical model. - * This could be a component or a system. - */ -public abstract class EcdarModel extends HighLevelModelObject { -} diff --git a/src/main/java/ecdar/abstractions/EcdarSystem.java b/src/main/java/ecdar/abstractions/EcdarSystem.java index 35fe2f2b..d8fd96ae 100644 --- a/src/main/java/ecdar/abstractions/EcdarSystem.java +++ b/src/main/java/ecdar/abstractions/EcdarSystem.java @@ -1,8 +1,6 @@ package ecdar.abstractions; -import ecdar.Ecdar; import ecdar.utility.UndoRedoStack; -import ecdar.utility.colors.Color; import ecdar.utility.colors.EnabledColor; import ecdar.utility.helpers.Boxed; import com.google.gson.JsonArray; @@ -21,8 +19,7 @@ * A model of a system. * The class is called EcdarSystem, since Java already has a System class. */ -public class EcdarSystem extends EcdarModel implements Boxed { - private static final String SYSTEM = "System"; +public class EcdarSystem extends HighLevelModel implements Boxed { private static final String SYSTEM_ROOT_X = "systemRootX"; private static final String INSTANCES = "componentInstances"; private static final String OPERATORS = "operators"; @@ -32,15 +29,15 @@ public class EcdarSystem extends EcdarModel implements Boxed { private final StringProperty description = new SimpleStringProperty(""); private final ObservableList<ComponentInstance> componentInstances = FXCollections.observableArrayList(); private final ObservableList<ComponentOperator> componentOperators = FXCollections.observableArrayList(); - private final ObservableList<EcdarSystemEdge> edges = FXCollections.observableArrayList(); + private final ObservableList<SystemEdge> edges = FXCollections.observableArrayList(); private final SystemRoot systemRoot = new SystemRoot(); // Styling properties private final Box box = new Box(); - public EcdarSystem() { - setSystemName(); - setRandomColor(); + public EcdarSystem(final EnabledColor color, final String name) { + this.nameProperty().set(name); + setColor(color); getBox().setWidth(600d); @@ -101,36 +98,32 @@ public void removeComponentOperator(final ComponentOperator operator) { /* Edges */ - public ObservableList<EcdarSystemEdge> getEdges() { + public ObservableList<SystemEdge> getEdges() { return edges; } - public void addEdge(final EcdarSystemEdge edge) { + public void addEdge(final SystemEdge edge) { edges.add(edge); } - public void removeEdge(final EcdarSystemEdge edge) { + public void removeEdge(final SystemEdge edge) { edges.remove(edge); } /** * Dyes the system. * @param color the color to dye with - * @param intensity the intensity of the color */ - public void dye(final Color color, final Color.Intensity intensity) { - final Color previousColor = colorProperty().get(); - final Color.Intensity previousColorIntensity = colorIntensityProperty().get(); + public void dye(final EnabledColor color) { + final EnabledColor previousColor = colorProperty().get(); UndoRedoStack.pushAndPerform(() -> { // Perform // Color the component - setColorIntensity(intensity); setColor(color); }, () -> { // Undo // Color the component - setColorIntensity(previousColorIntensity); setColor(previousColor); - }, String.format("Changed the color of %s to %s", this, color.name()), "color-lens"); + }, String.format("Changed the color of %s to %s", this, color.color.name()), "color-lens"); } @Override @@ -141,7 +134,7 @@ public JsonObject serialize() { box.addProperties(result); - result.addProperty(COLOR, EnabledColor.getIdentifier(getColor())); + result.addProperty(COLOR, EnabledColor.getIdentifier(getColor().color)); result.addProperty(SYSTEM_ROOT_X, systemRoot.getX()); @@ -170,8 +163,7 @@ public void deserialize(final JsonObject json) { final EnabledColor enabledColor = EnabledColor.fromIdentifier(json.getAsJsonPrimitive(COLOR).getAsString()); if (enabledColor != null) { - setColorIntensity(enabledColor.intensity); - setColor(enabledColor.color); + setColor(enabledColor); } systemRoot.setX(json.getAsJsonPrimitive(SYSTEM_ROOT_X).getAsDouble()); @@ -187,19 +179,7 @@ public void deserialize(final JsonObject json) { }); json.getAsJsonArray(EDGES).forEach(jsonEdge -> - getEdges().add(new EcdarSystemEdge((JsonObject) jsonEdge, this))); - } - - /** - * Generate and sets a unique id for this system - */ - public void setSystemName() { - for(int counter = 1; ; counter++) { - if(!Ecdar.getProject().getSystemNames().contains(SYSTEM + counter)){ - setName((SYSTEM + counter)); - return; - } - } + getEdges().add(new SystemEdge((JsonObject) jsonEdge, this))); } /** @@ -207,8 +187,8 @@ public void setSystemName() { * An edge is unfinished, if the has no target. * @return The unfinished edge, or null if none was found. */ - public EcdarSystemEdge getUnfinishedEdge() { - for (final EcdarSystemEdge edge : edges) { + public SystemEdge getUnfinishedEdge() { + for (final SystemEdge edge : edges) { if (!edge.isFinished()) return edge; } return null; diff --git a/src/main/java/ecdar/abstractions/Edge.java b/src/main/java/ecdar/abstractions/Edge.java index 28f7cad8..754c9947 100644 --- a/src/main/java/ecdar/abstractions/Edge.java +++ b/src/main/java/ecdar/abstractions/Edge.java @@ -97,7 +97,7 @@ public BooleanProperty failingProperty() { * @param component component to select a source and a target location within * @return the edge */ - Edge cloneForVerification(final Component component) { + public Edge cloneForVerification(final Component component) { final Edge clone = new Edge(component.findLocation(getSourceLocation().getId()), getStatus()); // Clone target location diff --git a/src/main/java/ecdar/abstractions/GroupedEdge.java b/src/main/java/ecdar/abstractions/GroupedEdge.java index 02503d9a..420762be 100644 --- a/src/main/java/ecdar/abstractions/GroupedEdge.java +++ b/src/main/java/ecdar/abstractions/GroupedEdge.java @@ -54,7 +54,6 @@ private void initializeFromEdge(Edge edge) { setGuard(edge.getGuard()); setUpdate(edge.getUpdate()); setColor(edge.getColor()); - setColorIntensity(edge.getColorIntensity()); setIsHighlighted(edge.getIsHighlighted()); setIsLocked(edge.getIsLockedProperty().getValue()); setStatus(edge.getStatus()); @@ -116,7 +115,6 @@ public Edge getBaseSubEdge() { edge.guardProperty().bind(this.guardProperty()); edge.updateProperty().bind(this.updateProperty()); edge.colorProperty().bind(this.colorProperty()); - edge.colorIntensityProperty().bind(this.colorIntensityProperty()); edge.setIsHighlighted(this.getIsHighlighted()); edge.getIsLockedProperty().bind(this.getIsLockedProperty()); edge.setGroup(this.getId()); diff --git a/src/main/java/ecdar/abstractions/HighLevelModel.java b/src/main/java/ecdar/abstractions/HighLevelModel.java new file mode 100644 index 00000000..536955b4 --- /dev/null +++ b/src/main/java/ecdar/abstractions/HighLevelModel.java @@ -0,0 +1,78 @@ +package ecdar.abstractions; + +import ecdar.presentations.DropDownMenu; +import ecdar.utility.colors.EnabledColor; +import ecdar.utility.serialize.Serializable; +import com.google.gson.JsonObject; +import javafx.beans.property.*; + +/** + * An object used for verifications. + * This could be a component, a global declarations object, or a system. + */ +public abstract class HighLevelModel implements Serializable, DropDownMenu.HasColor { + private static final String NAME = "name"; + + static final String DECLARATIONS = "declarations"; + public static final String DESCRIPTION = "description"; + static final String COLOR = "color"; + + private final StringProperty name; + private final ObjectProperty<EnabledColor> color; + private boolean temporary = false; + + public HighLevelModel() { + name = new SimpleStringProperty(""); + color = new SimpleObjectProperty<>(EnabledColor.getDefault()); + } + + public String getName() { + return name.get(); + } + + public void setName(final String name) { + this.name.unbind(); + this.name.set(name); + } + + public StringProperty nameProperty() { + return name; + } + + public EnabledColor getColor() { + return color.get(); + } + + /** + * Sets the color of the model + */ + void setColor(final EnabledColor color) { + this.color.set(color); + } + + public ObjectProperty<EnabledColor> colorProperty() { + return color; + } + + public boolean isTemporary() { + return temporary; + } + + public void setTemporary(final boolean newValue) { + this.temporary = newValue; + } + + @Override + public JsonObject serialize() { + final JsonObject result = new JsonObject(); + + result.addProperty(NAME, getName()); + + return result; + } + + @Override + public void deserialize(final JsonObject json) { + setName(json.getAsJsonPrimitive(NAME).getAsString()); + } +} diff --git a/src/main/java/ecdar/abstractions/HighLevelModelObject.java b/src/main/java/ecdar/abstractions/HighLevelModelObject.java deleted file mode 100644 index c1926c4e..00000000 --- a/src/main/java/ecdar/abstractions/HighLevelModelObject.java +++ /dev/null @@ -1,115 +0,0 @@ -package ecdar.abstractions; - -import ecdar.Ecdar; -import ecdar.presentations.DropDownMenu; -import ecdar.utility.colors.Color; -import ecdar.utility.colors.EnabledColor; -import ecdar.utility.serialize.Serializable; -import com.google.gson.JsonObject; -import javafx.beans.property.*; - -import java.util.ArrayList; -import java.util.List; -import java.util.Random; - -/** - * An object used for verifications. - * This could be a component, a global declarations object, or a system. - */ -public abstract class HighLevelModelObject implements Serializable, DropDownMenu.HasColor { - private static final String NAME = "name"; - - static final String DECLARATIONS = "declarations"; - public static final String DESCRIPTION = "description"; - static final String COLOR = "color"; - - private final StringProperty name; - private final ObjectProperty<Color> color; - private final ObjectProperty<Color.Intensity> colorIntensity; - private boolean temporary = false; - - public HighLevelModelObject() { - name = new SimpleStringProperty(""); - color = new SimpleObjectProperty<>(Color.GREY_BLUE); - colorIntensity = new SimpleObjectProperty<>(Color.Intensity.I700); - } - - public String getName() { - return name.get(); - } - - public void setName(final String name) { - this.name.unbind(); - this.name.set(name); - } - - public StringProperty nameProperty() { - return name; - } - - public Color getColor() { - return color.get(); - } - - public void setColor(final Color color) { - this.color.set(color); - } - - public ObjectProperty<Color> colorProperty() { - return color; - } - - public Color.Intensity getColorIntensity() { - return colorIntensity.get(); - } - - public void setColorIntensity(final Color.Intensity colorIntensity) { - this.colorIntensity.set(colorIntensity); - } - - public ObjectProperty<Color.Intensity> colorIntensityProperty() { - return colorIntensity; - } - - public boolean isTemporary() { - return temporary; - } - - public void setTemporary(final boolean newValue) { - this.temporary = newValue; - } - - /** - * Sets a random color. - * If some colors are not currently in use, choose among those. - * Otherwise choose a between all available colors. - */ - void setRandomColor() { - // Color the new component in such a way that we avoid clashing with other components if possible - final List<EnabledColor> availableColors = new ArrayList<>(EnabledColor.enabledColors); - Ecdar.getProject().getComponents().forEach(component -> { - availableColors.removeIf(enabledColor -> enabledColor.color.equals(component.getColor())); - }); - if (availableColors.size() == 0) { - availableColors.addAll(EnabledColor.enabledColors); - } - final int randomIndex = (new Random()).nextInt(availableColors.size()); - final EnabledColor selectedColor = availableColors.get(randomIndex); - setColorIntensity(selectedColor.intensity); - setColor(selectedColor.color); - } - - @Override - public JsonObject serialize() { - final JsonObject result = new JsonObject(); - - result.addProperty(NAME, getName()); - - return result; - } - - @Override - public void deserialize(final JsonObject json) { - setName(json.getAsJsonPrimitive(NAME).getAsString()); - } -} diff --git a/src/main/java/ecdar/abstractions/Location.java b/src/main/java/ecdar/abstractions/Location.java index 632a06ba..8b5c458f 100644 --- a/src/main/java/ecdar/abstractions/Location.java +++ b/src/main/java/ecdar/abstractions/Location.java @@ -31,7 +31,6 @@ public class Location implements Circular, Serializable, Nearable, DropDownMenu. private static final String INVARIANT_Y = "invariantY"; private static final String UNI = "U"; private static final String INC = "I"; - public static final String LOCATION = "L"; static final int ID_LETTER_LENGTH = 1; // Verification properties @@ -46,8 +45,7 @@ public class Location implements Circular, Serializable, Nearable, DropDownMenu. private final DoubleProperty y = new SimpleDoubleProperty(0d); private final DoubleProperty radius = new SimpleDoubleProperty(0d); private final SimpleDoubleProperty scale = new SimpleDoubleProperty(1d); - private final ObjectProperty<Color> color = new SimpleObjectProperty<>(Color.GREY_BLUE); - private final ObjectProperty<Color.Intensity> colorIntensity = new SimpleObjectProperty<>(Color.Intensity.I700); + private final ObjectProperty<EnabledColor> color = new SimpleObjectProperty<>(EnabledColor.getDefault()); private final DoubleProperty nicknameX = new SimpleDoubleProperty(0d); private final DoubleProperty nicknameY = new SimpleDoubleProperty(0d); @@ -66,19 +64,21 @@ public Location(final String id) { setId(id); } - public Location(final Component component, final Type type, final double x, final double y){ + public Location(final Component component, final Type type, final String id, final double x, final double y){ setX(x); setY(y); setType(type); if(type == Type.UNIVERSAL){ setIsLocked(true); - setId(UNI + component.generateUniIncId()); + setId(UNI + id); } else if (type == Type.INCONSISTENT) { setIsLocked(true); setUrgency(Location.Urgency.URGENT); - setId(INC + component.generateUniIncId()); + setId(INC + id); + } else { + setId(id); } - setColorIntensity(component.getColorIntensity()); + setColor(component.getColor()); } @@ -89,8 +89,8 @@ public Location(final JsonObject jsonObject) { /** * Generates an id for this, and binds reachability analysis. */ - public void initialize() { - setId(); + public void initialize(String id) { + setId(id); } /** @@ -100,7 +100,7 @@ public void initialize() { * Reachability analysis is not initialized. * @return the clone */ - Location cloneForVerification() { + public Location cloneForVerification() { final Location location = new Location(); location.setId(getId()); @@ -134,18 +134,6 @@ public String getId() { return id.get(); } - /** - * Generate and sets a unique id for this location - */ - private void setId() { - for(int counter = 0; ; counter++) { - if(!Ecdar.getProject().getLocationIds().contains(String.valueOf(counter))){ - id.set(LOCATION + counter); - return; - } - } - } - /** * Sets a specific id for this location * @param string id to set @@ -219,30 +207,6 @@ public DoubleProperty yProperty() { return y; } - public Color getColor() { - return color.get(); - } - - public void setColor(final Color color) { - this.color.set(color); - } - - public ObjectProperty<Color> colorProperty() { - return color; - } - - public Color.Intensity getColorIntensity() { - return colorIntensity.get(); - } - - public void setColorIntensity(final Color.Intensity colorIntensity) { - this.colorIntensity.set(colorIntensity); - } - - public ObjectProperty<Color.Intensity> colorIntensityProperty() { - return colorIntensity; - } - public double getRadius() { return radius.get(); } @@ -318,6 +282,19 @@ public String getMostDescriptiveIdentifier() { } } + @Override + public ObjectProperty<EnabledColor> colorProperty() { + return color; + } + + public void setColor(EnabledColor color) { + this.color.set(color); + } + + public EnabledColor getColor() { + return color.get(); + } + /** * Adds an edge to the left side of a location * @param syncString the string of the channel to synchronize over @@ -399,7 +376,7 @@ public JsonObject serialize() { result.addProperty(X, getX()); result.addProperty(Y, getY()); - result.addProperty(COLOR, EnabledColor.getIdentifier(getColor())); + result.addProperty(COLOR, EnabledColor.getIdentifier(getColor().color)); result.addProperty(NICKNAME_X, getNicknameX()); result.addProperty(NICKNAME_Y, getNicknameY()); @@ -420,11 +397,8 @@ public void deserialize(final JsonObject json) { setX(json.getAsJsonPrimitive(X).getAsDouble()); setY(json.getAsJsonPrimitive(Y).getAsDouble()); - final EnabledColor enabledColor = (json.has(COLOR) ? EnabledColor.fromIdentifier(json.getAsJsonPrimitive(COLOR).getAsString()) : null); - if (enabledColor != null) { - setColorIntensity(enabledColor.intensity); - setColor(enabledColor.color); - } + final EnabledColor enabledColor = (json.has(COLOR) ? EnabledColor.fromIdentifier(json.getAsJsonPrimitive(COLOR).getAsString()) : EnabledColor.getDefault()); + setColor(enabledColor); if(json.has(NICKNAME_X) && json.has(NICKNAME_Y)) { setNicknameX(json.getAsJsonPrimitive(NICKNAME_X).getAsDouble()); diff --git a/src/main/java/ecdar/abstractions/Project.java b/src/main/java/ecdar/abstractions/Project.java index 738d8097..fbf599ce 100644 --- a/src/main/java/ecdar/abstractions/Project.java +++ b/src/main/java/ecdar/abstractions/Project.java @@ -21,6 +21,9 @@ * A project of models. */ public class Project { + public static final String LOCATION = "L"; + public static final String COMPONENT = "Component"; + public static final String SYSTEM = "System"; private final static String GLOBAL_DCL_FILENAME = "GlobalDeclarations"; private final static String QUERIES_FILENAME = "Queries"; private final static String JSON_FILENAME_EXTENSION = ".json"; @@ -56,7 +59,7 @@ public ObservableList<Component> getTempComponents() { return tempComponents; } - public ObservableList<EcdarSystem> getSystemsProperty() { + public ObservableList<EcdarSystem> getSystems() { return systems; } @@ -68,10 +71,95 @@ public Declarations getGlobalDeclarations() { return globalDeclarations.get(); } - public void setGlobalDeclarations(final Declarations declarations) { + private void setGlobalDeclarations(final Declarations declarations) { globalDeclarations.set(declarations); } + public void addComponent(Component newComponent) { + components.add(newComponent); + } + + /** + * gets the id of all edges in the project and inserts it into a set + * @return the set of all edge ids + */ + Set<String> getEdgeIds(){ + final Set<String> ids = new HashSet<>(); + + for (final Component component : getComponents()) { + component.getEdges().forEach(edge -> ids.add(edge.getId().substring(Edge.ID_LETTER_LENGTH))); + } + + return ids; + } + + /** + * gets the id of all systems in the project and inserts it into a set + * @return the set of all system names + */ + HashSet<String> getSystemNames(){ + final HashSet<String> names = new HashSet<>(); + + for(final EcdarSystem system : getSystems()){ + names.add(system.getName()); + } + + return names; + } + + /** + * Gets a new GSON object. + * @return the GSON object + */ + private static Gson getNewGson() { + return new GsonBuilder().setPrettyPrinting().create(); + } + + /** + * Gets a new file writer for saving a file. + * @param filename name of file without extension. + * @return the file writer + * @throws IOException if an IO error occurs + */ + private static FileWriter getSaveFileWriter(final String filename) throws IOException { + return new FileWriter(Ecdar.projectDirectory.getValue() + File.separator + filename + ".json"); + } + + private static FileWriter getSaveFileWriter(final String filename, final String folderName) throws IOException { + return new FileWriter(Ecdar.projectDirectory.getValue() + File.separator + folderName + File.separator + filename + ".json"); + } + + /** + * Find a component by its name. + * @param name the name of the component looking for + * @return the component, or null if none is found + */ + public Component findComponent(final String name) { + for (final Component component : getComponents()) { + if (component.getName().equals(name)) return component; + } + + return null; + } + + /** + * Cleans the project. + * Be sure to disable code analysis before call and enable after call. + */ + public void clean() { + getGlobalDeclarations().clearDeclarationsText(); + + queries.clear(); + + components.clear(); + + tempComponents.clear(); + + systems.clear(); + + testPlans.clear(); + } + /** * Serializes and stores this as JSON files at a given directory. * @param directory object containing path to the desired directory to store at @@ -100,7 +188,7 @@ public void serialize(final File directory) throws IOException { } // Save systems - for (final EcdarSystem system : getSystemsProperty()) { + for (final EcdarSystem system : getSystems()) { final Writer writer = getSaveFileWriter(system.getName(), FOLDER_NAME_SYSTEMS); getNewGson().toJson(system.serialize(), writer); writer.close(); @@ -125,28 +213,6 @@ public void serialize(final File directory) throws IOException { Ecdar.showToast("Project saved."); } - /** - * Gets a new GSON object. - * @return the GSON object - */ - private static Gson getNewGson() { - return new GsonBuilder().setPrettyPrinting().create(); - } - - /** - * Gets a new file writer for saving a file. - * @param filename name of file without extension. - * @return the file writer - * @throws IOException if an IO error occurs - */ - private static FileWriter getSaveFileWriter(final String filename) throws IOException { - return new FileWriter(Ecdar.projectDirectory.getValue() + File.separator + filename + ".json"); - } - - private static FileWriter getSaveFileWriter(final String filename, final String folderName) throws IOException { - return new FileWriter(Ecdar.projectDirectory.getValue() + File.separator + folderName + File.separator + filename + ".json"); - } - /** * Reads files in a folder and deserialize this based on the files and folders. * @param projectFolder the folder where an Ecdar project are supposed to be @@ -275,7 +341,7 @@ private void deserializeSystems(final File systemsFolder) throws IOException { final List<Map.Entry<String, JsonObject>> list = new LinkedList<>(nameJsonMap.entrySet()); - list.sort(Comparator.comparing(Map.Entry::getKey)); + list.sort(Map.Entry.comparingByKey()); final List<JsonObject> orderedJsonSystems = new ArrayList<>(); @@ -287,7 +353,7 @@ private void deserializeSystems(final File systemsFolder) throws IOException { Collections.reverse(orderedJsonSystems); // Add the systems to the list - orderedJsonSystems.forEach(json -> getSystemsProperty().add(new EcdarSystem(json))); + orderedJsonSystems.forEach(json -> getSystems().add(new EcdarSystem(json))); } /** @@ -315,7 +381,7 @@ private void deserializeTestObjects(final File directory) throws IOException { final List<Map.Entry<String, JsonObject>> list = new LinkedList<>(nameJsonMap.entrySet()); - list.sort(Comparator.comparing(Map.Entry::getKey)); + list.sort(Map.Entry.comparingByKey()); final List<JsonObject> orderedJsonSystems = new ArrayList<>(); @@ -329,114 +395,4 @@ private void deserializeTestObjects(final File directory) throws IOException { // Add the test objects to the list orderedJsonSystems.forEach(json -> getTestPlans().add(new MutationTestPlan(json))); } - - /** - * Resets components. - * After this, there is only one component. - * Be sure to disable code analysis before call and enable after call. - */ - public void reset() { - clean(); - components.add(new Component(true)); - } - - /** - * Cleans the project. - * Be sure to disable code analysis before call and enable after call. - */ - public void clean() { - getGlobalDeclarations().clearDeclarationsText(); - - queries.clear(); - - components.clear(); - - tempComponents.clear(); - - systems.clear(); - - testPlans.clear(); - } - - /** - * gets the id of all locations in the project and inserts it into a set - * @return the set of all location ids - */ - Set<String> getLocationIds(){ - final Set<String> ids = new HashSet<>(); - - for (final Component component : getComponents()) { - ids.addAll(component.getLocationIds()); - } - - return ids; - } - - /** - * gets the id of all edges in the project and inserts it into a set - * @return the set of all edge ids - */ - Set<String> getEdgeIds(){ - final Set<String> ids = new HashSet<>(); - - for (final Component component : getComponents()) { - ids.addAll(component.getEdgeIds()); - } - - return ids; - } - - /** - * gets the id of all systems in the project and inserts it into a set - * @return the set of all system names - */ - HashSet<String> getSystemNames(){ - final HashSet<String> names = new HashSet<>(); - - for(final EcdarSystem system : getSystemsProperty()){ - names.add(system.getName()); - } - - return names; - } - - /** - * Gets universal/inconsistent ids for all components in the project - * @return a set of universal/inconsistent ids - */ - HashSet<String> getUniIncIds() { - final HashSet<String> ids = new HashSet<>(); - for (final Component component : getComponents()){ - ids.add(component.getUniIncId()); - } - - return ids; - } - - /** - * Gets the name of all components in the project and inserts it into a set - * @return the set of all component names - */ - HashSet<String> getComponentNames(){ - final HashSet<String> names = new HashSet<>(); - - for(final Component component : getComponents()){ - names.add(component.getName()); - } - - return names; - } - - /** - * Find a component by its name. - * @param name the name of the component looking for - * @return the component, or null if none is found - */ - public Component findComponent(final String name) { - for (final Component component : getComponents()) { - if (component.getName().equals(name)) return component; - } - - return null; - } } diff --git a/src/main/java/ecdar/abstractions/Query.java b/src/main/java/ecdar/abstractions/Query.java index b480cb07..175aa5d9 100644 --- a/src/main/java/ecdar/abstractions/Query.java +++ b/src/main/java/ecdar/abstractions/Query.java @@ -1,15 +1,23 @@ package ecdar.abstractions; -import EcdarProtoBuf.ObjectProtos; +import EcdarProtoBuf.QueryProtos; +import com.google.gson.JsonParser; import ecdar.Ecdar; import ecdar.backend.*; import ecdar.controllers.EcdarController; +import ecdar.controllers.SimulatorController; +import ecdar.utility.UndoRedoStack; +import ecdar.utility.helpers.StringValidator; +import EcdarProtoBuf.ObjectProtos; import ecdar.utility.helpers.StringHelper; import ecdar.utility.serialize.Serializable; import com.google.gson.JsonObject; import javafx.application.Platform; import javafx.beans.property.*; +import javafx.collections.ObservableList; +import java.util.ArrayList; +import java.util.NoSuchElementException; import java.util.List; import java.util.function.BiConsumer; import java.util.function.Consumer; @@ -18,7 +26,7 @@ public class Query implements Serializable { private static final String QUERY = "query"; private static final String COMMENT = "comment"; private static final String IS_PERIODIC = "isPeriodic"; - private static final String BACKEND = "backend"; + private static final String ENGINE = "engine"; private final StringProperty query = new SimpleStringProperty(""); private final StringProperty comment = new SimpleStringProperty(""); @@ -26,7 +34,7 @@ public class Query implements Serializable { private final SimpleBooleanProperty isPeriodic = new SimpleBooleanProperty(false); private final ObjectProperty<QueryState> queryState = new SimpleObjectProperty<>(QueryState.UNKNOWN); private final ObjectProperty<QueryType> type = new SimpleObjectProperty<>(); - private BackendInstance backend; + private Engine engine; private final Consumer<Boolean> successConsumer = (aBoolean) -> { @@ -100,11 +108,15 @@ public class Query implements Serializable { } }; - public Query(final String query, final String comment, final QueryState queryState) { + public Query(final String query, final String comment, final QueryState queryState, final Engine engine) { this.setQuery(query); this.comment.set(comment); this.queryState.set(queryState); - setBackend(BackendHelper.getDefaultBackendInstance()); + setEngine(engine); + } + + public Query(final String query, final String comment, final QueryState queryState) { + this(query, comment, queryState, BackendHelper.getDefaultEngine()); } public Query(final JsonObject jsonElement) { @@ -163,12 +175,12 @@ public void setIsPeriodic(final boolean isPeriodic) { this.isPeriodic.set(isPeriodic); } - public BackendInstance getBackend() { - return backend; + public Engine getEngine() { + return engine; } - public void setBackend(BackendInstance backend) { - this.backend = backend; + public void setEngine(Engine engine) { + this.engine = engine; } public void setType(QueryType type) { @@ -191,6 +203,198 @@ public Consumer<Exception> getFailureConsumer() { return failureConsumer; } + /** + * Executes the query + */ + public void execute() throws NoSuchElementException { + if (getQueryState().equals(QueryState.RUNNING) || !StringValidator.validateQuery(getQuery())) + return; + + if (getQuery().isEmpty()) { + setQueryState(QueryState.SYNTAX_ERROR); + addError("Query is empty"); + return; + } + + setQueryState(QueryState.RUNNING); + errors().set(""); + + getEngine().enqueueQuery(this, this::handleQueryResponse, this::handleQueryBackendError); + } + + private void handleQueryResponse(QueryProtos.QueryResponse value) { + if (getQueryState() == QueryState.UNKNOWN) return; + switch (value.getResultCase()) { + case REFINEMENT: + if (value.getRefinement().getSuccess()) { + setQueryState(QueryState.SUCCESSFUL); + getSuccessConsumer().accept(true); + } else { + setQueryState(QueryState.ERROR); + getFailureConsumer().accept(new BackendException.QueryErrorException(value.getRefinement().getReason())); + getSuccessConsumer().accept(false); + getStateActionConsumer().accept(value.getRefinement().getState(), + value.getRefinement().getActionList()); + } + break; + + case CONSISTENCY: + if (value.getConsistency().getSuccess()) { + setQueryState(QueryState.SUCCESSFUL); + getSuccessConsumer().accept(true); + } else { + setQueryState(QueryState.ERROR); + getFailureConsumer().accept(new BackendException.QueryErrorException(value.getConsistency().getReason())); + getSuccessConsumer().accept(false); + getStateActionConsumer().accept(value.getConsistency().getState(), + value.getConsistency().getActionList()); + } + break; + + case DETERMINISM: + if (value.getDeterminism().getSuccess()) { + setQueryState(QueryState.SUCCESSFUL); + getSuccessConsumer().accept(true); + } else { + setQueryState(QueryState.ERROR); + getFailureConsumer().accept(new BackendException.QueryErrorException(value.getDeterminism().getReason())); + getSuccessConsumer().accept(false); + getStateActionConsumer().accept(value.getDeterminism().getState(), + value.getDeterminism().getActionList()); + + } + break; + + case IMPLEMENTATION: + if (value.getImplementation().getSuccess()) { + setQueryState(QueryState.SUCCESSFUL); + getSuccessConsumer().accept(true); + } else { + setQueryState(QueryState.ERROR); + getFailureConsumer().accept(new BackendException.QueryErrorException(value.getImplementation().getReason())); + getSuccessConsumer().accept(false); + //ToDo: These errors are not implemented in the Reveaal backend. + getStateActionConsumer().accept(value.getImplementation().getState(), + new ArrayList<>()); + } + break; + + case REACHABILITY: + if (value.getReachability().getSuccess()) { + setQueryState(QueryState.SUCCESSFUL); + Ecdar.showToast("Reachability check was successful and the location can be reached."); + + //create list of edge id's + ArrayList<String> edgeIds = new ArrayList<>(); + for(var pathsList : value.getReachability().getComponentPathsList()){ + for(var id : pathsList.getEdgeIdsList().toArray()) { + edgeIds.add(id.toString()); + } + } + //highlight the edges + SimulatorController.getSimulationHandler().highlightReachabilityEdges(edgeIds); + getSuccessConsumer().accept(true); + } + else if(!value.getReachability().getSuccess()){ + Ecdar.showToast("Reachability check was successful but the location cannot be reached."); + getSuccessConsumer().accept(true); + } else { + setQueryState(QueryState.ERROR); + Ecdar.showToast("Error from backend: Reachability check was unsuccessful!"); + getFailureConsumer().accept(new BackendException.QueryErrorException(value.getReachability().getReason())); + getSuccessConsumer().accept(false); + //ToDo: These errors are not implemented in the Reveaal backend. + getStateActionConsumer().accept(value.getReachability().getState(), + new ArrayList<>()); + } + break; + + case COMPONENT: + setQueryState(QueryState.SUCCESSFUL); + getSuccessConsumer().accept(true); + JsonObject returnedComponent = (JsonObject) JsonParser.parseString(value.getComponent().getComponent().getJson()); + addGeneratedComponent(new Component(returnedComponent)); + break; + + case ERROR: + setQueryState(QueryState.ERROR); + Ecdar.showToast(value.getError()); + getFailureConsumer().accept(new BackendException.QueryErrorException(value.getError())); + getSuccessConsumer().accept(false); + break; + + case RESULT_NOT_SET: + setQueryState(QueryState.ERROR); + getSuccessConsumer().accept(false); + break; + } + } + + private void handleQueryBackendError(Throwable t) { + // If the query has been cancelled, ignore the error + if (getQueryState() == QueryState.UNKNOWN) return; + + // Due to limit information provided by the engines, we can only show the following for unreachable locations + if(getType() == QueryType.REACHABILITY){ + Ecdar.showToast("Timeout (no response from backend): The reachability query failed. This might be due to the fact that the location is not reachable."); + } + + // Each error starts with a capitalized description of the error equal to the gRPC error type encountered + String errorType = t.getMessage().split(":\\s+", 2)[0]; + + if ("DEADLINE_EXCEEDED".equals(errorType)) { + setQueryState(QueryState.ERROR); + getFailureConsumer().accept(new BackendException.QueryErrorException("The engine did not answer the request in time")); + } else { + try { + setQueryState(QueryState.ERROR); + getFailureConsumer().accept(new BackendException.QueryErrorException("The execution of this query failed with message:" + System.lineSeparator() + t.getLocalizedMessage())); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + private void addGeneratedComponent(Component newComponent) { + Platform.runLater(() -> { + newComponent.setTemporary(true); + + ObservableList<Component> listOfGeneratedComponents = Ecdar.getProject().getTempComponents(); // ToDo NIELS: Refactor + Component matchedComponent = null; + + for (Component currentGeneratedComponent : listOfGeneratedComponents) { + int comparisonOfNames = currentGeneratedComponent.getName().compareTo(newComponent.getName()); + + if (comparisonOfNames == 0) { + matchedComponent = currentGeneratedComponent; + break; + } else if (comparisonOfNames < 0) { + break; + } + } + + if (matchedComponent == null) { + UndoRedoStack.pushAndPerform(() -> { // Perform + Ecdar.getProject().getTempComponents().add(newComponent); + }, () -> { // Undo + Ecdar.getProject().getTempComponents().remove(newComponent); + }, "Created new component: " + newComponent.getName(), "add-circle"); + } else { + // Remove current component with name and add the newly generated one + Component finalMatchedComponent = matchedComponent; + UndoRedoStack.pushAndPerform(() -> { // Perform + Ecdar.getProject().getTempComponents().remove(finalMatchedComponent); + Ecdar.getProject().getTempComponents().add(newComponent); + }, () -> { // Undo + Ecdar.getProject().getTempComponents().remove(newComponent); + Ecdar.getProject().getTempComponents().add(finalMatchedComponent); + }, "Created new component: " + newComponent.getName(), "add-circle"); + } + + Ecdar.getProject().addComponent(newComponent); + }); + } + /** * Getter for the state action consumer. * @@ -200,7 +404,6 @@ public BiConsumer<ObjectProtos.State, List<String>> getStateActionConsumer() { return stateActionConsumer; } - @Override public JsonObject serialize() { final JsonObject result = new JsonObject(); @@ -208,7 +411,7 @@ public JsonObject serialize() { result.addProperty(QUERY, getType().getQueryName() + ": " + getQuery()); result.addProperty(COMMENT, getComment()); result.addProperty(IS_PERIODIC, isPeriodic()); - result.addProperty(BACKEND, backend.getName()); + result.addProperty(ENGINE, engine.getName()); return result; } @@ -231,10 +434,10 @@ public void deserialize(final JsonObject json) { setIsPeriodic(json.getAsJsonPrimitive(IS_PERIODIC).getAsBoolean()); } - if (json.has(BACKEND)) { - setBackend(BackendHelper.getBackendInstanceByName(json.getAsJsonPrimitive(BACKEND).getAsString())); + if(json.has(ENGINE)) { + setEngine(BackendHelper.getEngineByName(json.getAsJsonPrimitive(ENGINE).getAsString())); } else { - setBackend(BackendHelper.getDefaultBackendInstance()); + setEngine(BackendHelper.getDefaultEngine()); } } diff --git a/src/main/java/ecdar/abstractions/QueryType.java b/src/main/java/ecdar/abstractions/QueryType.java index 0ea4b1f2..4552cffb 100644 --- a/src/main/java/ecdar/abstractions/QueryType.java +++ b/src/main/java/ecdar/abstractions/QueryType.java @@ -6,10 +6,10 @@ public enum QueryType { QUOTIENT("quotient", "\\"), SPECIFICATION("specification", "Spec"), IMPLEMENTATION("implementation", "Imp"), - LOCAL_CONSISTENCY("consistency", "lCon"), // ToDo NIELS: Will become local-consistency - GLOBAL_CONSISTENCY("global-consistency", "gCon"), - BISIM_MIN("bisim", "bsim"), - GET_COMPONENT("get-component", "get"); + CONSISTENCY("consistency", "Con"), + LOCAL_CONSISTENCY("local-consistency", "LCon"), + BISIM_MINIM("bisim-minim", "Bsim"), + GET_COMPONENT("get-component", "Get"); private final String queryName; private final String symbol; @@ -29,26 +29,23 @@ public String getSymbol() { public static QueryType fromString(String s) { switch (s.toLowerCase()) { + case "reachability": + return REACHABILITY; case "refinement": return REFINEMENT; - case "quotient": - return QUOTIENT; case "specification": return SPECIFICATION; case "implementation": return IMPLEMENTATION; case "consistency": + return CONSISTENCY; case "local-consistency": return LOCAL_CONSISTENCY; - case "global-consistency": - return GLOBAL_CONSISTENCY; - case "bisim": - return BISIM_MIN; + case "bisim-minim": + return BISIM_MINIM; case "get": case "get-component": return GET_COMPONENT; - case "reachability": - return REACHABILITY; default: return null; } diff --git a/src/main/java/ecdar/abstractions/SimpleComponentsSystemDeclarations.java b/src/main/java/ecdar/abstractions/SimpleComponentsSystemDeclarations.java deleted file mode 100644 index 190f9352..00000000 --- a/src/main/java/ecdar/abstractions/SimpleComponentsSystemDeclarations.java +++ /dev/null @@ -1,31 +0,0 @@ -package ecdar.abstractions; - -import java.util.ArrayList; -import java.util.List; - -/** - * Systems declarations for some components. - * The generation text will be generated. - */ -public class SimpleComponentsSystemDeclarations extends Declarations { - public SimpleComponentsSystemDeclarations(final Component ... components) { - super("SimpleComponentsSystemDeclarations"); - - if (components.length < 1) throw new IllegalArgumentException("Cannot contruct declarations without any components"); - - final List<String> names = new ArrayList<>(); - for (final Component component : components) names.add(component.getName()); - - final StringBuilder declarationsText = new StringBuilder("system " + String.join(", ", names) + ";\n"); - - // Add inputs and outputs of components to declarations - for (final Component component : components) { - final List<String> comp1Io = new ArrayList<>(); - for (final String inputSync : component.getInputStrings()) comp1Io.add(inputSync + "?"); - for (final String outputSync : component.getOutputStrings()) comp1Io.add(outputSync + "!"); - declarationsText.append("IO ").append(component.getName()).append(" {").append(String.join(", ", comp1Io)).append("}\n"); - } - - setDeclarationsText(declarationsText.toString()); - } -} diff --git a/src/main/java/ecdar/abstractions/EcdarSystemEdge.java b/src/main/java/ecdar/abstractions/SystemEdge.java similarity index 92% rename from src/main/java/ecdar/abstractions/EcdarSystemEdge.java rename to src/main/java/ecdar/abstractions/SystemEdge.java index 503e4831..0f91d139 100644 --- a/src/main/java/ecdar/abstractions/EcdarSystemEdge.java +++ b/src/main/java/ecdar/abstractions/SystemEdge.java @@ -10,9 +10,8 @@ /** * This is an edge in an Ecdar system. - * The class is called EcdarSystemEdge, since there is already an SystemEdge in com.uppaal.model.system. */ -public class EcdarSystemEdge { +public class SystemEdge { private static final String CHILD = "child"; private static final String PARENT = "parent"; // Verification properties @@ -26,7 +25,7 @@ public class EcdarSystemEdge { * Constructor * @param node A node of this edge. This can either be a child or a parent */ - public EcdarSystemEdge(final SystemElement node) { + public SystemEdge(final SystemElement node) { tempNode.set(node); } @@ -35,7 +34,7 @@ public EcdarSystemEdge(final SystemElement node) { * @param json the JSON object * @param system system containing the edge */ - public EcdarSystemEdge(final JsonObject json, final EcdarSystem system) { + public SystemEdge(final JsonObject json, final EcdarSystem system) { deserialize(json, system); } diff --git a/src/main/java/ecdar/backend/BackendConnection.java b/src/main/java/ecdar/backend/BackendConnection.java deleted file mode 100644 index 5bd19550..00000000 --- a/src/main/java/ecdar/backend/BackendConnection.java +++ /dev/null @@ -1,61 +0,0 @@ -package ecdar.backend; - -import EcdarProtoBuf.EcdarBackendGrpc; -import io.grpc.ManagedChannel; - -import java.util.concurrent.TimeUnit; - -public class BackendConnection { - private final Process process; - private final EcdarBackendGrpc.EcdarBackendStub stub; - private final ManagedChannel channel; - private final BackendInstance backendInstance; - - BackendConnection(BackendInstance backendInstance, Process process, EcdarBackendGrpc.EcdarBackendStub stub, ManagedChannel channel) { - this.process = process; - this.backendInstance = backendInstance; - this.stub = stub; - this.channel = channel; - } - - /** - * Get the gRPC stub of the connection to use for query execution - * - * @return the gRPC stub of this connection - */ - public EcdarBackendGrpc.EcdarBackendStub getStub() { - return stub; - } - - /** - * Get the backend instance that should be used to execute - * the query currently associated with this backend connection - * - * @return the instance of the associated executable query object, - * or null, if no executable query is currently associated - */ - public BackendInstance getBackendInstance() { - return backendInstance; - } - - /** - * Close the gRPC connection and end the process - */ - public void close() { - if (!channel.isShutdown()) { - try { - channel.shutdown(); - if (!channel.awaitTermination(45, TimeUnit.SECONDS)) { - channel.shutdownNow(); // Forcefully close the connection - } - } catch (Exception e) { - e.printStackTrace(); - } - } - - // If the backend-instance is remote, there will not be a process - if (process != null) { - process.destroy(); - } - } -} diff --git a/src/main/java/ecdar/backend/BackendDriver.java b/src/main/java/ecdar/backend/BackendDriver.java deleted file mode 100644 index 6b05988d..00000000 --- a/src/main/java/ecdar/backend/BackendDriver.java +++ /dev/null @@ -1,184 +0,0 @@ -package ecdar.backend; - -import EcdarProtoBuf.EcdarBackendGrpc; -import ecdar.Ecdar; -import io.grpc.*; -import org.springframework.util.SocketUtils; - -import java.io.*; -import java.util.*; -import java.util.concurrent.ArrayBlockingQueue; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.TimeUnit; - -public class BackendDriver { - private final BlockingQueue<GrpcRequest> requestQueue = new ArrayBlockingQueue<>(200); - private final Map<BackendInstance, BlockingQueue<BackendConnection>> openBackendConnections = new HashMap<>(); - private final int responseDeadline = 20000; - private final int rerunRequestDelay = 200; - private final int numberOfRetriesPerQuery = 5; - - public BackendDriver() { - // ToDo NIELS: Consider multiple consumer threads using 'for(int i = 0; i < x; i++) {}' - GrpcRequestConsumer consumer = new GrpcRequestConsumer(); - Thread consumerThread = new Thread(consumer); - consumerThread.start(); - } - - public int getResponseDeadline() { - return responseDeadline; - } - - /** - * Add a GrpcRequest to the request queue to be executed when a backend is available - * - * @param request The GrpcRequest to be executed later - */ - public void addRequestToExecutionQueue(GrpcRequest request) { - requestQueue.add(request); - } - - public void addBackendConnection(BackendConnection backendConnection) { - var relatedQueue = this.openBackendConnections.get(backendConnection.getBackendInstance()); - if (!relatedQueue.contains(backendConnection)) relatedQueue.add(backendConnection); - } - - /** - * Filters the list of open {@link BackendConnection}s to the specified {@link BackendInstance} and returns the - * first match or attempts to start a new connection if none is found. - * - * @param backend backend instance to get a connection to (e.g. Reveaal, j-Ecdar, custom_engine) - * @return a BackendConnection object linked to the backend, either from the open backend connection list - * or a newly started connection. - * @throws BackendException.NoAvailableBackendConnectionException if unable to retrieve a connection to the backend - * and unable to start a new one - */ - private BackendConnection getBackendConnection(BackendInstance backend) throws BackendException.NoAvailableBackendConnectionException { - BackendConnection connection; - try { - if (!openBackendConnections.containsKey(backend)) - openBackendConnections.put(backend, new ArrayBlockingQueue<>(backend.getNumberOfInstances() + 1)); - - // If no open connection is free, attempt to start a new one - if (openBackendConnections.get(backend).size() < 1) { - tryStartNewBackendConnection(backend); - } - - if (backend.isThreadSafe()){ - connection = openBackendConnections.get(backend).peek(); - } - else{ - // Block until a connection becomes available - connection = openBackendConnections.get(backend).take(); - } - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - return connection; - } - - /** - * Close all open backend connection and kill all locally running processes - * - * @throws IOException if any of the sockets do not respond - */ - public void closeAllBackendConnections() throws IOException { - for (BlockingQueue<BackendConnection> bq : openBackendConnections.values()) { - for (BackendConnection bc : bq) bc.close(); - } - } - - /** - * Attempts to start a new connection to the specified backend. On success, the backend is added to the associated - * queue, otherwise, nothing happens. - * - * @param backend the target backend for the connection - */ - private void tryStartNewBackendConnection(BackendInstance backend) { - Process p = null; - String hostAddress = (backend.isLocal() ? "127.0.0.1" : backend.getBackendLocation()); - long portNumber = 0; - - if (backend.isLocal()) { - try { - portNumber = SocketUtils.findAvailableTcpPort(backend.getPortStart(), backend.getPortEnd()); - } catch (IllegalStateException e) { - // No port was available in range, we assume that connections are running on all ports - return; - } - - do { - ProcessBuilder pb = new ProcessBuilder(backend.getBackendLocation(), "-p", hostAddress + ":" + portNumber); - - try { - p = pb.start(); - } catch (IOException ioException) { - Ecdar.showToast("Unable to start local backend instance"); - ioException.printStackTrace(); - return; - } - // If the process is not alive, it failed while starting up, try again - } while (!p.isAlive()); - } else { - // Filter open connections to this backend and map their used ports to an int stream - var activeEnginePorts = openBackendConnections.get(backend).stream() - .mapToInt((bi) -> Integer.parseInt(bi.getStub().getChannel().authority().split(":", 2)[1])); - - int currentPort = backend.getPortStart(); - do { - // Find port not already connected to - int tempPortNumber = currentPort; - if (activeEnginePorts.noneMatch((i) -> i == tempPortNumber)) { - portNumber = tempPortNumber; - } else { - currentPort++; - } - } while (portNumber == 0 && currentPort <= backend.getPortEnd()); - - if (currentPort > backend.getPortEnd()) { - Ecdar.showToast("Unable to connect to remote engine: " + backend.getName() + " within port range " + backend.getPortStart() + " - " + backend.getPortEnd()); - return; - } - } - - ManagedChannel channel = ManagedChannelBuilder.forTarget(hostAddress + ":" + portNumber) - .usePlaintext() - .keepAliveTime(1000, TimeUnit.MILLISECONDS) - .build(); - - EcdarBackendGrpc.EcdarBackendStub stub = EcdarBackendGrpc.newStub(channel); - BackendConnection newConnection = new BackendConnection(backend, p, stub, channel); - addBackendConnection(newConnection); - } - - private class GrpcRequestConsumer implements Runnable { - @Override - public void run() { - while (true) { - try { - GrpcRequest request = requestQueue.take(); - - try { - request.tries++; - request.execute(getBackendConnection(request.getBackend())); - } catch (BackendException.NoAvailableBackendConnectionException e) { - e.printStackTrace(); - if (request.tries < numberOfRetriesPerQuery) { - new Timer().schedule(new TimerTask() { - @Override - public void run() { - requestQueue.add(request); - } - }, rerunRequestDelay); - } else { - Ecdar.showToast("Unable to find a connection to the requested engine"); - } - return; - } - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - } - } -} diff --git a/src/main/java/ecdar/backend/BackendException.java b/src/main/java/ecdar/backend/BackendException.java index 7c77a582..db66a21c 100644 --- a/src/main/java/ecdar/backend/BackendException.java +++ b/src/main/java/ecdar/backend/BackendException.java @@ -9,12 +9,32 @@ public BackendException(final String message, final Throwable cause) { super(message, cause); } - public static class NoAvailableBackendConnectionException extends BackendException { - public NoAvailableBackendConnectionException(final String message) { + public static class NoAvailableEngineConnectionException extends BackendException { + public NoAvailableEngineConnectionException(final String message) { super(message); } - public NoAvailableBackendConnectionException(final String message, final Throwable cause) { + public NoAvailableEngineConnectionException(final String message, final Throwable cause) { + super(message, cause); + } + } + + public static class gRpcChannelShutdownException extends BackendException { + public gRpcChannelShutdownException(final String message) { + super(message); + } + + public gRpcChannelShutdownException(final String message, final Throwable cause) { + super(message, cause); + } + } + + public static class EngineProcessDestructionException extends BackendException { + public EngineProcessDestructionException(final String message) { + super(message); + } + + public EngineProcessDestructionException(final String message, final Throwable cause) { super(message, cause); } } diff --git a/src/main/java/ecdar/backend/BackendHelper.java b/src/main/java/ecdar/backend/BackendHelper.java index 442e0578..a47c7718 100644 --- a/src/main/java/ecdar/backend/BackendHelper.java +++ b/src/main/java/ecdar/backend/BackendHelper.java @@ -3,7 +3,6 @@ import EcdarProtoBuf.ComponentProtos; import ecdar.Ecdar; import ecdar.abstractions.*; -import ecdar.simulation.SimulationState; import javafx.beans.property.SimpleListProperty; import javafx.collections.FXCollections; import javafx.collections.ObservableList; @@ -22,9 +21,9 @@ public final class BackendHelper { final static String TEMP_DIRECTORY = "temporary"; - private static BackendInstance defaultBackend = null; - private static ObservableList<BackendInstance> backendInstances = new SimpleListProperty<>(); - private static List<Runnable> backendInstancesUpdatedListeners = new ArrayList<>(); + private static Engine defaultEngine = null; + private static ObservableList<Engine> engines = new SimpleListProperty<>(); + private static final List<Runnable> enginesUpdatedListeners = new ArrayList<>(); /** * Stores a query as a backend XML query file in the "temporary" directory. @@ -59,185 +58,85 @@ public static String getTempDirectoryAbsolutePath() throws URISyntaxException { } /** - * Stop all running queries. + * Clears all queued queries, stops all active engines, and closes all open engine connections */ - public static void stopQueries() { - Ecdar.getProject().getQueries().forEach(Query::cancel); - } - - public static String getLocationReachableQuery(final Location endLocation, final Component component, final String query) { - return getLocationReachableQuery(endLocation, component, query, null); - } - /** - * Generates a reachability query based on the given location and component - * - * @param endLocation The location which should be checked for reachability - * @return A reachability query string - */ - public static String getLocationReachableQuery(final Location endLocation, final Component component, final String query, final SimulationState state) { - var stringBuilder = new StringBuilder(); - - // append simulation query - stringBuilder.append(query); - - // append arrow - stringBuilder.append(" -> "); - - // append start location here TODO - if (state != null){ - stringBuilder.append(getStartStateString(state)); - stringBuilder.append(";"); - } - - // append end state - stringBuilder.append(getEndStateString(component.getName(), endLocation.getId())); - - // return example: m1||M2->[L1,L4](y<3);[L2, L7](y<2) - System.out.println(stringBuilder); - return stringBuilder.toString(); - } - - private static String getStartStateString(SimulationState state) { - var stringBuilder = new StringBuilder(); - - // append locations - var locations = state.getLocations(); - stringBuilder.append("["); - var appendLocationWithSeparator = false; - for(var componentName:Ecdar.getSimulationHandler().getComponentsInSimulation()){ - var locationFound = false; - - for(var location:locations){ - if (location.getKey().equals(componentName)){ - if (appendLocationWithSeparator){ - stringBuilder.append("," + location.getValue()); - } - else{ - stringBuilder.append(location.getValue()); - } - locationFound = true; - } - if (locationFound){ - // don't go through more locations, when a location is found for the specific component that we're looking at - break; - } + public static void clearEngineConnections() throws BackendException { + BackendHelper.stopQueries(); + + BackendException exception = new BackendException("Exceptions were thrown while attempting to close engine connections"); + for (Engine engine : engines) { + try { + engine.closeConnections(); + } catch (BackendException e) { + exception.addSuppressed(e); } - appendLocationWithSeparator = true; } - stringBuilder.append("]"); - - // append clock values - var clocks = state.getSimulationClocks(); - stringBuilder.append("()"); - return stringBuilder.toString(); - } - - private static String getEndStateString(String componentName, String endLocationId) { - var stringBuilder = new StringBuilder(); - - stringBuilder.append("["); - var appendLocationWithSeparator = false; - - for (var component:Ecdar.getSimulationHandler().getComponentsInSimulation()) - { - if (component.equals(componentName)){ - if (appendLocationWithSeparator){ - stringBuilder.append("," + endLocationId); - } - else{ - stringBuilder.append(endLocationId); - } - } - else{ // add underscore to indicate, that we don't care about the end locations in the other components - if (appendLocationWithSeparator){ - stringBuilder.append(",_"); - } - else{ - stringBuilder.append("_"); - } - } - if (!appendLocationWithSeparator) { - appendLocationWithSeparator = true; - } + if (exception.getSuppressed().length > 0) { + throw exception; } - stringBuilder.append("]"); - stringBuilder.append("()"); - - return stringBuilder.toString(); } /** - * Generates a string for a deadlock query based on the component - * - * @param component The component which should be checked for deadlocks - * @return A deadlock query string + * Stop all running queries. */ - public static String getExistDeadlockQuery(final Component component) { - // Get the names of the locations of this component. Used to produce the deadlock query - final String templateName = component.getName(); - final List<String> locationNames = new ArrayList<>(); - - for (final Location location : component.getLocations()) { - locationNames.add(templateName + "." + location.getId()); - } - - return "(" + String.join(" || ", locationNames) + ") && deadlock"; + public static void stopQueries() { + Ecdar.getProject().getQueries().forEach(Query::cancel); } /** - * Returns the BackendInstance with the specified name, or null, if no such BackendInstance exists + * Returns the Engine with the specified name, or null, if no such Engine exists * - * @param backendInstanceName Name of the BackendInstance to return - * @return The BackendInstance with matching name - * or the default backend instance, if no matching backendInstance exists + * @param engineName Name of the Engine to return + * @return The Engine with matching name + * or the default engine, if no matching engine exists */ - public static BackendInstance getBackendInstanceByName(String backendInstanceName) { - Optional<BackendInstance> backendInstance = BackendHelper.backendInstances.stream().filter(bi -> bi.getName().equals(backendInstanceName)).findFirst(); - return backendInstance.orElse(BackendHelper.getDefaultBackendInstance()); + public static Engine getEngineByName(String engineName) { + Optional<Engine> engine = BackendHelper.engines.stream().filter(e -> e.getName().equals(engineName)).findFirst(); + return engine.orElse(BackendHelper.getDefaultEngine()); } /** - * Returns the default BackendInstance + * Returns the default Engine * - * @return The default BackendInstance + * @return The default Engine */ - public static BackendInstance getDefaultBackendInstance() { - return defaultBackend; + public static Engine getDefaultEngine() { + return defaultEngine; } /** - * Sets the list of BackendInstances to match the provided list + * Sets the list of engines to match the provided list * - * @param updatedBackendInstances The list of BackendInstances that should be stored + * @param updatedEngines The list of engines that should be stored */ - public static void updateBackendInstances(ArrayList<BackendInstance> updatedBackendInstances) { - BackendHelper.backendInstances = FXCollections.observableList(updatedBackendInstances); - for (Runnable runnable : BackendHelper.backendInstancesUpdatedListeners) { + public static void updateEngineInstances(ArrayList<Engine> updatedEngines) { + BackendHelper.engines = FXCollections.observableList(updatedEngines); + for (Runnable runnable : BackendHelper.enginesUpdatedListeners) { runnable.run(); } } /** - * Returns the ObservableList of BackendInstances + * Returns the ObservableList of engines * - * @return The ObservableList of BackendInstances + * @return The ObservableList of engines */ - public static ObservableList<BackendInstance> getBackendInstances() { - return BackendHelper.backendInstances; + public static ObservableList<Engine> getEngines() { + return BackendHelper.engines; } /** - * Sets the default BackendInstance to the provided object + * Sets the default Engine to the provided object * - * @param newDefaultBackend The new defaultBackend + * @param newDefaultEngine The new default engine */ - public static void setDefaultBackendInstance(BackendInstance newDefaultBackend) { - BackendHelper.defaultBackend = newDefaultBackend; + public static void setDefaultEngine(Engine newDefaultEngine) { + BackendHelper.defaultEngine = newDefaultEngine; } - public static void addBackendInstanceListener(Runnable runnable) { - BackendHelper.backendInstancesUpdatedListeners.add(runnable); + public static void addEngineInstanceListener(Runnable runnable) { + BackendHelper.enginesUpdatedListeners.add(runnable); } public static ComponentProtos.ComponentsInfo.Builder getComponentsInfoBuilder(String query) { diff --git a/src/main/java/ecdar/backend/BackendInstance.java b/src/main/java/ecdar/backend/BackendInstance.java deleted file mode 100644 index feb66ca9..00000000 --- a/src/main/java/ecdar/backend/BackendInstance.java +++ /dev/null @@ -1,137 +0,0 @@ -package ecdar.backend; - -import com.google.gson.JsonObject; -import ecdar.utility.serialize.Serializable; -import javafx.beans.property.SimpleBooleanProperty; - -public class BackendInstance implements Serializable { - private static final String NAME = "name"; - private static final String IS_LOCAL = "isLocal"; - private static final String IS_DEFAULT = "isDefault"; - private static final String LOCATION = "location"; - private static final String PORT_RANGE_START = "portRangeStart"; - private static final String PORT_RANGE_END = "portRangeEnd"; - private static final String LOCKED = "locked"; - private static final String IS_THREAD_SAFE = "isThreadSafe"; - - private String name; - private boolean isLocal; - private boolean isDefault; - private boolean isThreadSafe; - private String backendLocation; - private int portStart; - private int portEnd; - private SimpleBooleanProperty locked = new SimpleBooleanProperty(false); - - public BackendInstance() {}; - - public BackendInstance(final JsonObject jsonObject) { - deserialize(jsonObject); - }; - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public boolean isLocal() { - return isLocal; - } - - public void setLocal(boolean local) { - isLocal = local; - } - - public boolean isDefault() { - return isDefault; - } - - public void setDefault(boolean aDefault) { - isDefault = aDefault; - } - - public boolean isThreadSafe() { - return isThreadSafe; - } - - public void setIsThreadSafe(boolean threadSafe) { - isThreadSafe = threadSafe; - } - - public String getBackendLocation() { - return backendLocation; - } - - public void setBackendLocation(String backendLocation) { - this.backendLocation = backendLocation; - } - - public int getPortStart() { - return portStart; - } - - public void setPortStart(int portStart) { - this.portStart = portStart; - } - - public int getPortEnd() { - return portEnd; - } - - public void setPortEnd(int portEnd) { - this.portEnd = portEnd; - } - - public int getNumberOfInstances() { - return this.portEnd - this.portStart; - } - - public void lockInstance() { - locked.set(true); - } - - public SimpleBooleanProperty getLockedProperty() { - return locked; - } - - @Override - public JsonObject serialize() { - final JsonObject result = new JsonObject(); - result.addProperty(NAME, getName()); - result.addProperty(IS_LOCAL, isLocal()); - result.addProperty(IS_DEFAULT, isDefault()); - result.addProperty(IS_THREAD_SAFE, isThreadSafe()); - result.addProperty(LOCATION, getBackendLocation()); - result.addProperty(PORT_RANGE_START, getPortStart()); - result.addProperty(PORT_RANGE_END, getPortEnd()); - result.addProperty(LOCKED, getLockedProperty().get()); - - return result; - } - - @Override - public void deserialize(final JsonObject json) { - setName(json.getAsJsonPrimitive(NAME).getAsString()); - setLocal(json.getAsJsonPrimitive(IS_LOCAL).getAsBoolean()); - setDefault(json.getAsJsonPrimitive(IS_DEFAULT).getAsBoolean()); - - try { // ToDo NIELS: Decide to either do this or simply reload defaults - setIsThreadSafe(json.getAsJsonPrimitive(IS_THREAD_SAFE).getAsBoolean()); - } catch (NullPointerException e) { - setIsThreadSafe(false); - } - - setBackendLocation(json.getAsJsonPrimitive(LOCATION).getAsString()); - setPortStart(json.getAsJsonPrimitive(PORT_RANGE_START).getAsInt()); - setPortEnd(json.getAsJsonPrimitive(PORT_RANGE_END).getAsInt()); - if (json.getAsJsonPrimitive(LOCKED).getAsBoolean()) lockInstance(); - } - - @Override - public String toString() { - return name; - } -} diff --git a/src/main/java/ecdar/backend/Engine.java b/src/main/java/ecdar/backend/Engine.java new file mode 100644 index 00000000..936178e9 --- /dev/null +++ b/src/main/java/ecdar/backend/Engine.java @@ -0,0 +1,337 @@ +package ecdar.backend; + +import EcdarProtoBuf.QueryProtos; +import com.google.gson.JsonObject; +import ecdar.Ecdar; +import ecdar.abstractions.Query; +import ecdar.utility.serialize.Serializable; +import io.grpc.stub.StreamObserver; +import javafx.beans.property.SimpleBooleanProperty; + +import java.util.*; +import java.util.concurrent.*; +import java.util.function.Consumer; + +public class Engine implements Serializable { + private static final String NAME = "name"; + private static final String IS_LOCAL = "isLocal"; + private static final String IS_DEFAULT = "isDefault"; + private static final String LOCATION = "location"; + private static final String PORT_RANGE_START = "portRangeStart"; + private static final String PORT_RANGE_END = "portRangeEnd"; + private static final String LOCKED = "locked"; + private static final String IS_THREAD_SAFE = "isThreadSafe"; + private final int responseDeadline = 20000; + private final int rerunRequestDelay = 200; + private final int numberOfRetriesPerQuery = 5; + + private String name; + private boolean isLocal; + private boolean isDefault; + private boolean isThreadSafe; + private int portStart; + private int portEnd; + private SimpleBooleanProperty locked = new SimpleBooleanProperty(false); + /** + * This is either a path to the engines executable or an IP address at which the engine is running + */ + private String engineLocation; + + private final ArrayList<EngineConnection> startedConnections = new ArrayList<>(); + private final BlockingQueue<GrpcRequest> requestQueue = new ArrayBlockingQueue<>(200); // Magic number + // ToDo NIELS: Refactor to resize queue on port range change + private final BlockingQueue<EngineConnection> availableConnections = new ArrayBlockingQueue<>(200); // Magic number + private final EngineConnectionStarter connectionStarter = new EngineConnectionStarter(this); + + public Engine() { + GrpcRequestConsumer consumer = new GrpcRequestConsumer(); + Thread consumerThread = new Thread(consumer); + consumerThread.start(); + } + + public Engine(final JsonObject jsonObject) { + this(); + deserialize(jsonObject); + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public boolean isLocal() { + return isLocal; + } + + public void setLocal(boolean local) { + isLocal = local; + } + + public boolean isDefault() { + return isDefault; + } + + public void setDefault(boolean aDefault) { + isDefault = aDefault; + } + + public boolean isThreadSafe() { + return isThreadSafe; + } + + public void setIsThreadSafe(boolean threadSafe) { + isThreadSafe = threadSafe; + } + + public String getEngineLocation() { + return engineLocation; + } + + public void setEngineLocation(String engineLocation) { + this.engineLocation = engineLocation; + } + + public String getIpAddress() { + if (isLocal()) { + return "127.0.0.1"; + } else { + return getEngineLocation(); + } + } + + public int getPortStart() { + return portStart; + } + + public void setPortStart(int portStart) { + this.portStart = portStart; + } + + public int getPortEnd() { + return portEnd; + } + + public void setPortEnd(int portEnd) { + this.portEnd = portEnd; + } + + public int getNumberOfInstances() { + return this.portEnd - this.portStart + 1; + } + + public void lockInstance() { + locked.set(true); + } + + public SimpleBooleanProperty getLockedProperty() { + return locked; + } + + public ArrayList<EngineConnection> getStartedConnections() { + return startedConnections; + } + + /** + * Enqueue query for execution with consumers for success and error + * + * @param query the query to enqueue for execution + * @param successConsumer consumer for returned QueryResponse + * @param errorConsumer consumer for any throwable that might result from the execution + */ + public void enqueueQuery(Query query, Consumer<QueryProtos.QueryResponse> successConsumer, Consumer<Throwable> errorConsumer) { + GrpcRequest request = new GrpcRequest(engineConnection -> { + var componentsInfoBuilder = BackendHelper.getComponentsInfoBuilder(query.getQuery()); + + StreamObserver<QueryProtos.QueryResponse> responseObserver = new StreamObserver<>() { + @Override + public void onNext(QueryProtos.QueryResponse value) { + successConsumer.accept(value); + } + + @Override + public void onError(Throwable t) { + errorConsumer.accept(t); + setConnectionAsAvailable(engineConnection); + } + + @Override + public void onCompleted() { + // Release engine connection + setConnectionAsAvailable(engineConnection); + } + }; + + var queryBuilder = QueryProtos.QueryRequest.newBuilder() + .setUserId(1) + .setQueryId(UUID.randomUUID().hashCode()) + .setSettings(QueryProtos.QueryRequest.Settings.newBuilder().setDisableClockReduction(true)) + .setQuery(query.getType().getQueryName() + ": " + query.getQuery()) + .setComponentsInfo(componentsInfoBuilder); + + engineConnection.getStub().withDeadlineAfter(responseDeadline, TimeUnit.MILLISECONDS) + .sendQuery(queryBuilder.build(), responseObserver); + }); + + requestQueue.add(request); + } + + /** + * Signal that the EngineConnection can be used not in use and available for queries + * + * @param connection to make available + */ + public void setConnectionAsAvailable(EngineConnection connection) { + if (!availableConnections.contains(connection)) availableConnections.add(connection); + } + + /** + * Clears all queued queries, stops all active engines, and closes all open engine connections + */ + public void clear() throws BackendException { + BackendHelper.stopQueries(); + requestQueue.clear(); + closeConnections(); + } + + /** + * Filters the list of open {@link EngineConnection}s to the specified {@link Engine} and returns the + * first match or attempts to start a new connection if none is found. + * + * @return a EngineConnection object linked to the engine, either from the open engine connection list + * or a newly started connection. + * @throws BackendException.NoAvailableEngineConnectionException if unable to retrieve a connection to the engine + * and unable to start a new one + */ + private EngineConnection getConnection() throws BackendException.NoAvailableEngineConnectionException { + EngineConnection connection; + try { + // If no open connection is free, attempt to start a new one + if (availableConnections.size() < 1) { + EngineConnection newConnection = this.connectionStarter.tryStartNewConnection(); + + if (newConnection != null) { + startedConnections.add(newConnection); + } + } + + if (isThreadSafe){ + connection = availableConnections.peek(); + } + else{ + // Block until a connection becomes available + connection = availableConnections.take(); + } + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + + return connection; + } + + /** + * Close all open engine connections and kill all locally running processes + * + * @throws BackendException if one or more connections throw an exception on {@link EngineConnection#close()} + * (use getSuppressed() to see all thrown exceptions) + */ + public void closeConnections() throws BackendException { + // Create a list for storing all terminated connection + List<CompletableFuture<EngineConnection>> closeFutures = new ArrayList<>(); + BackendException exceptions = new BackendException("Exceptions were thrown while attempting to close engine connections on " + getName()); + + // Attempt to close all connections + for (EngineConnection ec : startedConnections) { + CompletableFuture<EngineConnection> closeFuture = CompletableFuture.supplyAsync(() -> { + try { + ec.close(); + } catch (BackendException.gRpcChannelShutdownException | + BackendException.EngineProcessDestructionException e) { + throw new RuntimeException(e); + } + + return ec; + }); + + closeFutures.add(closeFuture); + } + + for (CompletableFuture<EngineConnection> closeFuture : closeFutures) { + try { + EngineConnection ec = closeFuture.get(); + + availableConnections.remove(ec); + startedConnections.remove(ec); + } catch (InterruptedException | ExecutionException e) { + exceptions.addSuppressed(e.getCause()); + } + } + + if (!startedConnections.isEmpty()) throw exceptions; + } + + private class GrpcRequestConsumer implements Runnable { + @Override + public void run() { + while (true) { + try { + GrpcRequest request = requestQueue.take(); + + try { + request.tries++; + request.execute(getConnection()); + } catch (BackendException.NoAvailableEngineConnectionException e) { + e.printStackTrace(); + if (request.tries < numberOfRetriesPerQuery) { + new Timer().schedule(new TimerTask() { + @Override + public void run() { + requestQueue.add(request); + } + }, rerunRequestDelay); + } else { + Ecdar.showToast("Unable to find a connection to the requested engine"); + } + return; + } + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + } + + @Override + public JsonObject serialize() { + final JsonObject result = new JsonObject(); + result.addProperty(NAME, getName()); + result.addProperty(IS_LOCAL, isLocal()); + result.addProperty(IS_DEFAULT, isDefault()); + result.addProperty(IS_THREAD_SAFE, isThreadSafe()); + result.addProperty(LOCATION, getEngineLocation()); + result.addProperty(PORT_RANGE_START, getPortStart()); + result.addProperty(PORT_RANGE_END, getPortEnd()); + result.addProperty(LOCKED, getLockedProperty().get()); + + return result; + } + + @Override + public void deserialize(final JsonObject json) { + setName(json.getAsJsonPrimitive(NAME).getAsString()); + setLocal(json.getAsJsonPrimitive(IS_LOCAL).getAsBoolean()); + setDefault(json.getAsJsonPrimitive(IS_DEFAULT).getAsBoolean()); + setIsThreadSafe(json.has(IS_THREAD_SAFE) && json.getAsJsonPrimitive(IS_THREAD_SAFE).getAsBoolean()); + setEngineLocation(json.getAsJsonPrimitive(LOCATION).getAsString()); + setPortStart(json.getAsJsonPrimitive(PORT_RANGE_START).getAsInt()); + setPortEnd(json.getAsJsonPrimitive(PORT_RANGE_END).getAsInt()); + if (json.getAsJsonPrimitive(LOCKED).getAsBoolean()) lockInstance(); + } + + @Override + public String toString() { + return name; + } +} diff --git a/src/main/java/ecdar/backend/EngineConnection.java b/src/main/java/ecdar/backend/EngineConnection.java new file mode 100644 index 00000000..3cd9a5ff --- /dev/null +++ b/src/main/java/ecdar/backend/EngineConnection.java @@ -0,0 +1,84 @@ +package ecdar.backend; + +import EcdarProtoBuf.EcdarBackendGrpc; +import io.grpc.ManagedChannel; + +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +public class EngineConnection { + private final Engine engine; + private final EcdarBackendGrpc.EcdarBackendStub stub; + private final ManagedChannel channel; + private final Process process; + private final int port; + + EngineConnection(Engine engine, ManagedChannel channel, EcdarBackendGrpc.EcdarBackendStub stub, Process process) { + this.engine = engine; + this.stub = stub; + this.channel = channel; + this.process = process; + this.port = Integer.parseInt(getStub().getChannel().authority().split(":", 2)[1]); + } + + EngineConnection(Engine engine, ManagedChannel channel, EcdarBackendGrpc.EcdarBackendStub stub) { + this(engine, channel, stub, null); + } + + /** + * Get the gRPC stub of the connection to use for query execution + * + * @return the gRPC stub of this connection + */ + public EcdarBackendGrpc.EcdarBackendStub getStub() { + return stub; + } + + /** + * Get the engine that should be used to execute + * the query currently associated with this engine connection + * + * @return the instance of the associated executable query object, + * or null, if no executable query is currently associated + */ + public Engine getEngine() { + return engine; + } + + public int getPort() { + return port; + } + + /** + * Close the gRPC connection and end the process + * + * @throws BackendException.gRpcChannelShutdownException if an InterruptedException is encountered while trying to shut down the gRPC channel. + * @throws BackendException.EngineProcessDestructionException if the connected engine process throws an ExecutionException, an InterruptedException, or a TimeoutException. + */ + public void close() throws BackendException.gRpcChannelShutdownException, BackendException.EngineProcessDestructionException { + if (!channel.isShutdown()) { + try { + channel.shutdown(); + if (!channel.awaitTermination(45, TimeUnit.SECONDS)) { + channel.shutdownNow(); // Forcefully close the connection + } + } catch (InterruptedException e) { + // Engine location is either the file path or the IP, here we want the channel address + throw new BackendException.gRpcChannelShutdownException("The gRPC channel to \"" + this.engine.getName() + "\" instance running at: " + engine.getIpAddress() + ":" + this.port + "was interrupted during termination", e.getCause()); + } + } + + // If the engine is remote, there will not be a process + if (process != null) { + try { + java.util.concurrent.CompletableFuture<Process> terminated = process.onExit(); + process.destroy(); + terminated.get(45, TimeUnit.SECONDS); + } catch (ExecutionException | InterruptedException | TimeoutException e) { + // Add the engine location to the exception, as it contains the path to the executable + throw new BackendException.EngineProcessDestructionException("A process running: " + this.engine.getEngineLocation() + " on port " + this.port + " threw an exception during shutdown", e.getCause()); + } + } + } +} diff --git a/src/main/java/ecdar/backend/EngineConnectionStarter.java b/src/main/java/ecdar/backend/EngineConnectionStarter.java new file mode 100644 index 00000000..be8c3a2f --- /dev/null +++ b/src/main/java/ecdar/backend/EngineConnectionStarter.java @@ -0,0 +1,119 @@ +package ecdar.backend; + +import EcdarProtoBuf.EcdarBackendGrpc; +import ecdar.Ecdar; +import io.grpc.ManagedChannel; +import io.grpc.ManagedChannelBuilder; +import org.springframework.util.SocketUtils; + +import java.io.IOException; +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; +import java.util.stream.Stream; + +public class EngineConnectionStarter { + private final Engine engine; + private final int maxRetriesForStartingEngineProcess = 3; + + EngineConnectionStarter(Engine engine) { + this.engine = engine; + } + + /** + * Attempts to start a new connection to the specified engine. + * + * @return the started EngineConnection if successful, + * otherwise, null. + */ + protected EngineConnection tryStartNewConnection() { + EngineConnection newConnection; + + if (engine.isLocal()) { + newConnection = startLocalConnection(); + } else { + newConnection = startRemoteConnection(); + } + + // If the connection is null, no new connection was started + return newConnection; + } + + /** + * Starts a process, creates an EngineConnection to it, and returns that connection + * + * @return an EngineConnection to a local engine running in a Process or null if all ports are already in use + */ + private EngineConnection startLocalConnection() { + long port; + try { + port = SocketUtils.findAvailableTcpPort(engine.getPortStart(), engine.getPortEnd()); + } catch (IllegalStateException e) { + // All ports specified for engine are already used for running engines + return null; + } + + // Start local process of engine + Process p; + int attempts = 0; + + do { + attempts++; + ProcessBuilder pb = new ProcessBuilder(engine.getEngineLocation(), "-p", engine.getIpAddress() + ":" + port); + + try { + p = pb.start(); + } catch (IOException ioException) { + Ecdar.showToast("Unable to start local engine instance"); + ioException.printStackTrace(); + return null; + } + } while (!p.isAlive() && attempts < maxRetriesForStartingEngineProcess); + + ManagedChannel channel = startGrpcChannel(engine.getIpAddress(), port); + EcdarBackendGrpc.EcdarBackendStub stub = EcdarBackendGrpc.newStub(channel); + return new EngineConnection(engine, channel, stub, p); + } + + /** + * Creates and returns an EngineConnection to the remote engine + * + * @return an EngineConnection to a remote engine or null if all ports are already connected to + */ + private EngineConnection startRemoteConnection() { + // Get a stream of ports already used for connections + Supplier<Stream<Integer>> activeEnginePortsStream = () -> engine.getStartedConnections().stream() + .mapToInt(EngineConnection::getPort).boxed(); + + long port = engine.getPortStart(); + for (int currentPort = engine.getPortStart(); currentPort <= engine.getPortEnd(); currentPort++) { + final int tempPort = currentPort; + if (activeEnginePortsStream.get().anyMatch((i) -> i == tempPort)) { + port = currentPort; + break; + } + } + + if (port > engine.getPortEnd()) { + // All ports specified for engine are already used for connections + return null; + } + + ManagedChannel channel = startGrpcChannel(engine.getIpAddress(), port); + EcdarBackendGrpc.EcdarBackendStub stub = EcdarBackendGrpc.newStub(channel); + return new EngineConnection(engine, channel, stub); + } + + /** + * Connects a gRPC channel to the address at the specified port, expecting that an engine is running there + * + * @param address of the target engine + * @param port of the target engine at the address + * @return the created gRPC channel + */ + private ManagedChannel startGrpcChannel(final String address, final long port) { + return ManagedChannelBuilder.forTarget(address + ":" + port) + .usePlaintext() + .keepAliveTime(1000, TimeUnit.MILLISECONDS) + .build(); + } +} diff --git a/src/main/java/ecdar/backend/GrpcRequest.java b/src/main/java/ecdar/backend/GrpcRequest.java index d6c667f8..642ebfd0 100644 --- a/src/main/java/ecdar/backend/GrpcRequest.java +++ b/src/main/java/ecdar/backend/GrpcRequest.java @@ -3,20 +3,14 @@ import java.util.function.Consumer; public class GrpcRequest { - private final Consumer<BackendConnection> request; - private final BackendInstance backend; + private final Consumer<EngineConnection> request; public int tries = 0; - public GrpcRequest(Consumer<BackendConnection> request, BackendInstance backend) { + public GrpcRequest(Consumer<EngineConnection> request) { this.request = request; - this.backend = backend; } - public void execute(BackendConnection backendConnection) { - this.request.accept(backendConnection); - } - - public BackendInstance getBackend() { - return backend; + public void execute(EngineConnection engineConnection) { + this.request.accept(engineConnection); } } \ No newline at end of file diff --git a/src/main/java/ecdar/backend/QueryHandler.java b/src/main/java/ecdar/backend/QueryHandler.java deleted file mode 100644 index 40142d64..00000000 --- a/src/main/java/ecdar/backend/QueryHandler.java +++ /dev/null @@ -1,272 +0,0 @@ -package ecdar.backend; - -import EcdarProtoBuf.QueryProtos; -import EcdarProtoBuf.QueryProtos.QueryRequest.Settings; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; -import ecdar.Ecdar; -import ecdar.abstractions.Component; -import ecdar.abstractions.Query; -import ecdar.abstractions.QueryState; -import ecdar.abstractions.QueryType; -import ecdar.controllers.EcdarController; -import ecdar.utility.UndoRedoStack; -import ecdar.utility.helpers.StringValidator; -import io.grpc.stub.StreamObserver; -import javafx.application.Platform; -import javafx.collections.ObservableList; - -import java.util.ArrayList; -import java.util.NoSuchElementException; -import java.util.UUID; -import java.util.concurrent.TimeUnit; - -public class QueryHandler { - private final BackendDriver backendDriver; - private final ArrayList<BackendConnection> connections = new ArrayList<>(); - - public QueryHandler(BackendDriver backendDriver) { - this.backendDriver = backendDriver; - } - - /** - * Executes the specified query - * @param query query to be executed - */ - public void executeQuery(Query query) throws NoSuchElementException { - if (query.getQueryState().equals(QueryState.RUNNING) || !StringValidator.validateQuery(query.getQuery())) return; - - if (query.getQuery().isEmpty()) { - query.setQueryState(QueryState.SYNTAX_ERROR); - query.addError("Query is empty"); - return; - } - - query.setQueryState(QueryState.RUNNING); - query.errors().set(""); - - GrpcRequest request = new GrpcRequest(backendConnection -> { - connections.add(backendConnection); // Save reference for closing connection on exit - - var componentsInfoBuilder = BackendHelper.getComponentsInfoBuilder(query.getQuery()); - - StreamObserver<QueryProtos.QueryResponse> responseObserver = new StreamObserver<>() { - @Override - public void onNext(QueryProtos.QueryResponse value) { - handleQueryResponse(value, query); - } - - @Override - public void onError(Throwable t) { - handleQueryBackendError(t, query); - - // Release backend connection - backendDriver.addBackendConnection(backendConnection); - connections.remove(backendConnection); - } - - @Override - public void onCompleted() { - // Release backend connection - backendDriver.addBackendConnection(backendConnection); - connections.remove(backendConnection); - } - }; - - var queryBuilder = QueryProtos.QueryRequest.newBuilder() - .setUserId(1) - .setQueryId(UUID.randomUUID().hashCode()) - .setSettings(Settings.newBuilder().setDisableClockReduction(true)) - .setQuery(query.getType().getQueryName() + ": " + query.getQuery()) - .setComponentsInfo(componentsInfoBuilder); - - backendConnection.getStub().withDeadlineAfter(backendDriver.getResponseDeadline(), TimeUnit.MILLISECONDS) - .sendQuery(queryBuilder.build(), responseObserver); - }, query.getBackend()); - - backendDriver.addRequestToExecutionQueue(request); - } - - /** - * Close all open backend connection and kill all locally running processes - */ - public void closeAllBackendConnections() { - for (BackendConnection con : connections) { - con.close(); - } - } - - private void handleQueryResponse(QueryProtos.QueryResponse value, Query query) { - // If the query has been cancelled, ignore the result - if (query.getQueryState() == QueryState.UNKNOWN) return; - switch (value.getResultCase()) { - case REFINEMENT: - if (value.getRefinement().getSuccess()) { - query.setQueryState(QueryState.SUCCESSFUL); - query.getSuccessConsumer().accept(true); - } else { - query.setQueryState(QueryState.ERROR); - query.getFailureConsumer().accept(new BackendException.QueryErrorException(value.getRefinement().getReason())); - query.getSuccessConsumer().accept(false); - query.getStateActionConsumer().accept(value.getRefinement().getState(), - value.getRefinement().getActionList()); - } - break; - - case CONSISTENCY: - if (value.getConsistency().getSuccess()) { - query.setQueryState(QueryState.SUCCESSFUL); - query.getSuccessConsumer().accept(true); - } else { - query.setQueryState(QueryState.ERROR); - query.getFailureConsumer().accept(new BackendException.QueryErrorException(value.getConsistency().getReason())); - query.getSuccessConsumer().accept(false); - query.getStateActionConsumer().accept(value.getConsistency().getState(), - value.getConsistency().getActionList()); - } - break; - - case DETERMINISM: - if (value.getDeterminism().getSuccess()) { - query.setQueryState(QueryState.SUCCESSFUL); - query.getSuccessConsumer().accept(true); - } else { - query.setQueryState(QueryState.ERROR); - query.getFailureConsumer().accept(new BackendException.QueryErrorException(value.getDeterminism().getReason())); - query.getSuccessConsumer().accept(false); - query.getStateActionConsumer().accept(value.getDeterminism().getState(), - value.getDeterminism().getActionList()); - - } - break; - - case IMPLEMENTATION: - if (value.getImplementation().getSuccess()) { - query.setQueryState(QueryState.SUCCESSFUL); - query.getSuccessConsumer().accept(true); - } else { - query.setQueryState(QueryState.ERROR); - query.getFailureConsumer().accept(new BackendException.QueryErrorException(value.getImplementation().getReason())); - query.getSuccessConsumer().accept(false); - //ToDo: These errors are not implemented in the Reveaal backend. - query.getStateActionConsumer().accept(value.getImplementation().getState(), - new ArrayList<>()); - } - break; - - case REACHABILITY: - if (value.getReachability().getSuccess()) { - query.setQueryState(QueryState.SUCCESSFUL); - Ecdar.showToast("Reachability check was successful and the location can be reached."); - - //create list of edge id's - ArrayList<String> edgeIds = new ArrayList<>(); - for(var pathsList : value.getReachability().getComponentPathsList()){ - for(var id : pathsList.getEdgeIdsList().toArray()) { - edgeIds.add(id.toString()); - } - } - //highlight the edges - Ecdar.getSimulationHandler().highlightReachabilityEdges(edgeIds); - query.getSuccessConsumer().accept(true); - } - else if(!value.getReachability().getSuccess()){ - Ecdar.showToast("Reachability check was successful but the location cannot be reached."); - query.getSuccessConsumer().accept(true); - } else { - query.setQueryState(QueryState.ERROR); - Ecdar.showToast("Error from backend: Reachability check was unsuccessful!"); - query.getFailureConsumer().accept(new BackendException.QueryErrorException(value.getReachability().getReason())); - query.getSuccessConsumer().accept(false); - //ToDo: These errors are not implemented in the Reveaal backend. - query.getStateActionConsumer().accept(value.getReachability().getState(), - new ArrayList<>()); - } - break; - - case COMPONENT: - query.setQueryState(QueryState.SUCCESSFUL); - query.getSuccessConsumer().accept(true); - JsonObject returnedComponent = (JsonObject) JsonParser.parseString(value.getComponent().getComponent().getJson()); - addGeneratedComponent(new Component(returnedComponent)); - break; - - case ERROR: - query.setQueryState(QueryState.ERROR); - Ecdar.showToast(value.getError()); - query.getFailureConsumer().accept(new BackendException.QueryErrorException(value.getError())); - query.getSuccessConsumer().accept(false); - break; - - case RESULT_NOT_SET: - query.setQueryState(QueryState.ERROR); - query.getSuccessConsumer().accept(false); - break; - } - } - - private void handleQueryBackendError(Throwable t, Query query) { - // If the query has been cancelled, ignore the error - if (query.getQueryState() == QueryState.UNKNOWN) return; - - // due to lack of information from backend if the reachability check shows that a location can NOT be reached, this is the most accurate information we can provide - if(query.getType() == QueryType.REACHABILITY){ - Ecdar.showToast("Timeout (no response from backend): The reachability query failed. This might be due to the fact that the location is not reachable."); - } - - // Each error starts with a capitalized description of the error equal to the gRPC error type encountered - String errorType = t.getMessage().split(":\\s+", 2)[0]; - - if ("DEADLINE_EXCEEDED".equals(errorType)) { - query.setQueryState(QueryState.ERROR); - query.getFailureConsumer().accept(new BackendException.QueryErrorException("The backend did not answer the request in time")); - } else { - try { - query.setQueryState(QueryState.ERROR); - query.getFailureConsumer().accept(new BackendException.QueryErrorException("The execution of this query failed with message:" + System.lineSeparator() + t.getLocalizedMessage())); - } catch (Exception e) { - e.printStackTrace(); - } - } - } - - private void addGeneratedComponent(Component newComponent) { - Platform.runLater(() -> { - newComponent.setTemporary(true); - - ObservableList<Component> listOfGeneratedComponents = Ecdar.getProject().getTempComponents(); // ToDo NIELS: Refactor - Component matchedComponent = null; - - for (Component currentGeneratedComponent : listOfGeneratedComponents) { - int comparisonOfNames = currentGeneratedComponent.getName().compareTo(newComponent.getName()); - - if (comparisonOfNames == 0) { - matchedComponent = currentGeneratedComponent; - break; - } else if (comparisonOfNames < 0) { - break; - } - } - - if (matchedComponent == null) { - UndoRedoStack.pushAndPerform(() -> { // Perform - Ecdar.getProject().getTempComponents().add(newComponent); - }, () -> { // Undo - Ecdar.getProject().getTempComponents().remove(newComponent); - }, "Created new component: " + newComponent.getName(), "add-circle"); - } else { - // Remove current component with name and add the newly generated one - Component finalMatchedComponent = matchedComponent; - UndoRedoStack.pushAndPerform(() -> { // Perform - Ecdar.getProject().getTempComponents().remove(finalMatchedComponent); - Ecdar.getProject().getTempComponents().add(newComponent); - }, () -> { // Undo - Ecdar.getProject().getTempComponents().remove(newComponent); - Ecdar.getProject().getTempComponents().add(finalMatchedComponent); - }, "Created new component: " + newComponent.getName(), "add-circle"); - } - - EcdarController.setActiveModelForActiveCanvas(newComponent); - }); - } -} diff --git a/src/main/java/ecdar/backend/SimulationHandler.java b/src/main/java/ecdar/backend/SimulationHandler.java index 4fa74b03..1dab3e5d 100644 --- a/src/main/java/ecdar/backend/SimulationHandler.java +++ b/src/main/java/ecdar/backend/SimulationHandler.java @@ -16,7 +16,6 @@ import javafx.collections.ObservableMap; import javafx.util.Pair; -import java.io.IOException; import java.math.BigDecimal; import java.util.ArrayList; import java.util.List; @@ -45,16 +44,16 @@ public class SimulationHandler { private final ObservableMap<String, BigDecimal> simulationVariables = FXCollections.observableHashMap(); private final ObservableMap<String, BigDecimal> simulationClocks = FXCollections.observableHashMap(); public ObservableList<SimulationState> traceLog = FXCollections.observableArrayList(); - private final BackendDriver backendDriver; - private final ArrayList<BackendConnection> connections = new ArrayList<>(); private List<String> ComponentsInSimulation = new ArrayList<>(); + private EngineConnection con; + /** * Empty constructor that should be used if the system or project has not be initialized yet */ - public SimulationHandler(BackendDriver backendDriver) { - this.backendDriver = backendDriver; + public SimulationHandler() { + } public void clearComponentsInSimulation() { @@ -75,6 +74,11 @@ private void initializeSimulation() { this.selectedEdge.set(null); this.traceLog.clear(); this.system = getSystem(); + + if (con == null) { + EngineConnectionStarter ecs = new EngineConnectionStarter(BackendHelper.getDefaultEngine()); + con = ecs.tryStartNewConnection(); + } } /** @@ -83,11 +87,11 @@ private void initializeSimulation() { public void initialStep() { initializeSimulation(); - GrpcRequest request = new GrpcRequest(backendConnection -> { + GrpcRequest request = new GrpcRequest(engineConnection -> { StreamObserver<SimulationStepResponse> responseObserver = new StreamObserver<>() { @Override public void onNext(QueryProtos.SimulationStepResponse value) { - // TODO this is temp solution to compile but should be fixed to handle ambiguity + // ToDo: This is temp solution to compile but should be fixed to handle ambiguity currentState.set(new SimulationState(value.getNewDecisionPoints(0))); Platform.runLater(() -> traceLog.add(currentState.get())); } @@ -95,17 +99,10 @@ public void onNext(QueryProtos.SimulationStepResponse value) { @Override public void onError(Throwable t) { Ecdar.showToast("Could not start simulation:\n" + t.getMessage()); - - // Release backend connection - backendDriver.addBackendConnection(backendConnection); - connections.remove(backendConnection); } @Override public void onCompleted() { - // Release backend connection - backendDriver.addBackendConnection(backendConnection); - connections.remove(backendConnection); } }; @@ -120,12 +117,11 @@ public void onCompleted() { .setComponentComposition(composition) .setComponentsInfo(comInfo); simStartRequest.setSimulationInfo(simInfo); - backendConnection.getStub().withDeadlineAfter(this.backendDriver.getResponseDeadline(), TimeUnit.MILLISECONDS) + engineConnection.getStub().withDeadlineAfter(20000, TimeUnit.MILLISECONDS) .startSimulation(simStartRequest.build(), responseObserver); - }, BackendHelper.getDefaultBackendInstance()); - - backendDriver.addRequestToExecutionQueue(request); - + }); + + request.execute(con); numberOfSteps++; } @@ -143,7 +139,7 @@ public void nextStep() { // removes invalid states from the log when stepping forward after previewing a previous state removeStatesFromLog(currentState.get()); - GrpcRequest request = new GrpcRequest(backendConnection -> { + GrpcRequest request = new GrpcRequest(engineConnection -> { StreamObserver<SimulationStepResponse> responseObserver = new StreamObserver<>() { @Override public void onNext(QueryProtos.SimulationStepResponse value) { @@ -155,17 +151,10 @@ public void onNext(QueryProtos.SimulationStepResponse value) { @Override public void onError(Throwable t) { Ecdar.showToast("Could not take next step in simulation\nError: " + t.getMessage()); - - // Release backend connection - backendDriver.addBackendConnection(backendConnection); - connections.remove(backendConnection); - } + } @Override public void onCompleted() { - // Release backend connection - backendDriver.addBackendConnection(backendConnection); - connections.remove(backendConnection); } }; @@ -173,6 +162,7 @@ public void onCompleted() { for (Component c : Ecdar.getProject().getComponents()) { comInfo.addComponents(ComponentProtos.Component.newBuilder().setJson(c.serialize().toString()).build()); } + comInfo.setComponentsHash(comInfo.getComponentsList().hashCode()); var simStepRequest = SimulationStepRequest.newBuilder(); var simInfo = SimulationInfo.newBuilder() @@ -185,13 +175,11 @@ public void onCompleted() { var decision = Decision.newBuilder().setEdge(edge).setSource(source); simStepRequest.setChosenDecision(decision); - backendConnection.getStub().withDeadlineAfter(this.backendDriver.getResponseDeadline(), TimeUnit.MILLISECONDS) + engineConnection.getStub().withDeadlineAfter(20000, TimeUnit.MILLISECONDS) .takeSimulationStep(simStepRequest.build(), responseObserver); - }, BackendHelper.getDefaultBackendInstance()); - - backendDriver.addRequestToExecutionQueue(request); + }); - // increments the number of steps taken during this simulation + request.execute(con); numberOfSteps++; } @@ -297,17 +285,6 @@ public boolean isSimulationRunning() { return false; // ToDo: Implement } - /** - * Close all open backend connection and kill all locally running processes - * - * @throws IOException if any of the sockets do not respond - */ - public void closeAllBackendConnections() throws IOException { - for (BackendConnection con : connections) { - con.close(); - } - } - /** * Removes all states from the trace log after the given state */ diff --git a/src/main/java/ecdar/code_analysis/CodeAnalysis.java b/src/main/java/ecdar/code_analysis/CodeAnalysis.java index d5f9bd74..c3599ceb 100644 --- a/src/main/java/ecdar/code_analysis/CodeAnalysis.java +++ b/src/main/java/ecdar/code_analysis/CodeAnalysis.java @@ -188,5 +188,4 @@ public static void enable() { public static void disable() { ENABLED = false; } - } diff --git a/src/main/java/ecdar/code_analysis/Nearable.java b/src/main/java/ecdar/code_analysis/Nearable.java index fdad43cb..94d3a5a6 100644 --- a/src/main/java/ecdar/code_analysis/Nearable.java +++ b/src/main/java/ecdar/code_analysis/Nearable.java @@ -1,7 +1,5 @@ package ecdar.code_analysis; public interface Nearable { - String generateNearString(); - } diff --git a/src/main/java/ecdar/controllers/BackendOptionsDialogController.java b/src/main/java/ecdar/controllers/BackendOptionsDialogController.java deleted file mode 100644 index 1950f30b..00000000 --- a/src/main/java/ecdar/controllers/BackendOptionsDialogController.java +++ /dev/null @@ -1,496 +0,0 @@ -package ecdar.controllers; - -import com.google.gson.JsonArray; -import com.google.gson.JsonParser; -import com.jfoenix.controls.JFXButton; -import com.jfoenix.controls.JFXRippler; -import ecdar.Ecdar; -import ecdar.backend.BackendInstance; -import ecdar.backend.BackendHelper; -import ecdar.presentations.BackendInstancePresentation; -import javafx.fxml.Initializable; -import javafx.scene.Node; -import javafx.scene.control.ToggleGroup; -import javafx.scene.layout.HBox; -import javafx.scene.layout.Priority; -import javafx.scene.layout.VBox; -import org.apache.commons.lang3.Range; -import org.apache.commons.lang3.SystemUtils; - -import java.io.File; -import java.io.IOException; -import java.net.InetAddress; -import java.net.URISyntaxException; -import java.net.URL; -import java.net.UnknownHostException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.*; - -public class BackendOptionsDialogController implements Initializable { - public VBox backendInstanceList; - public JFXRippler addBackendButton; - public JFXButton closeButton; - public ToggleGroup defaultBackendToggleGroup = new ToggleGroup(); - public JFXButton saveButton; - public JFXButton resetBackendsButton; - - @Override - public void initialize(URL location, ResourceBundle resources) { - initializeBackendInstanceList(); - } - - /** - * Reverts any changes made to the backend options by reloading the options specified in the preference file, - * or to the default, if no backends are present in the preferences file. - */ - public void cancelBackendOptionsChanges() { - initializeBackendInstanceList(); - } - - /** - * Saves the changes made to the backend options to the preferences file and returns true - * if no errors where found in the backend instance definitions, otherwise false. - * - * @return whether the changes could be saved, - * meaning that no errors where found in the changes made to the backend options - */ - public boolean saveChangesToBackendOptions() { - if (this.backendInstanceListIsErrorFree()) { - ArrayList<BackendInstance> backendInstances = new ArrayList<>(); - for (Node backendInstance : backendInstanceList.getChildren()) { - if (backendInstance instanceof BackendInstancePresentation) { - backendInstances.add(((BackendInstancePresentation) backendInstance).getController().updateBackendInstance()); - } - } - - if (backendInstances.size() < 1) { - Ecdar.showToast("Please add an engine instance or press: \"" + resetBackendsButton.getText() + "\""); - return false; - } - - // Close all backend connections to avoid dangling backend connections when port range is changed - try { - Ecdar.getBackendDriver().closeAllBackendConnections(); - Ecdar.getQueryExecutor().closeAllBackendConnections(); - } catch (IOException e) { - e.printStackTrace(); - } - - BackendHelper.updateBackendInstances(backendInstances); - - JsonArray jsonArray = new JsonArray(); - for (BackendInstance bi : backendInstances) { - jsonArray.add(bi.serialize()); - } - - Ecdar.preferences.put("backend_instances", jsonArray.toString()); - - BackendInstance defaultBackend = backendInstances.stream().filter(BackendInstance::isDefault).findFirst().orElse(backendInstances.get(0)); - BackendHelper.setDefaultBackendInstance(defaultBackend); - - String defaultBackendName = (defaultBackend.getName()); - Ecdar.preferences.put("default_backend", defaultBackendName); - - return true; - } else { - return false; - } - } - - /** - * Resets the backends to the default backends present in the 'default_backends.json' file. - */ - public void resetBackendsToDefault() { - updateBackendsInGUI(getPackagedBackends()); - } - - private void initializeBackendInstanceList() { - ArrayList<BackendInstance> backends; - - // Load backends from preferences or get default - var savedBackends = Ecdar.preferences.get("backend_instances", null); - if (savedBackends != null) { - backends = getBackendsFromJsonArray( - JsonParser.parseString(savedBackends).getAsJsonArray()); - } else { - backends = getPackagedBackends(); - } - - // Style add backend button and handle click event - HBox.setHgrow(addBackendButton, Priority.ALWAYS); - addBackendButton.setMaxWidth(Double.MAX_VALUE); - addBackendButton.setOnMouseClicked((event) -> { - BackendInstancePresentation newBackendInstancePresentation = new BackendInstancePresentation(); - addBackendInstancePresentationToList(newBackendInstancePresentation); - }); - - updateBackendsInGUI(backends); - } - - /** - * Clear the backend instance list and add the newly defined backends to it. - * - * @param backends The new list of backends - */ - private void updateBackendsInGUI(ArrayList<BackendInstance> backends) { - backendInstanceList.getChildren().clear(); - - backends.forEach((bi) -> { - BackendInstancePresentation newBackendInstancePresentation = new BackendInstancePresentation(bi); - - // Bind input fields that should not be changed for packaged backends to the locked property of the backend instance - newBackendInstancePresentation.getController().backendName.disableProperty().bind(bi.getLockedProperty()); - newBackendInstancePresentation.getController().pathToBackend.disableProperty().bind(bi.getLockedProperty()); - newBackendInstancePresentation.getController().pickPathToBackend.disableProperty().bind(bi.getLockedProperty()); - newBackendInstancePresentation.getController().isLocal.disableProperty().bind(bi.getLockedProperty()); - addBackendInstancePresentationToList(newBackendInstancePresentation); - }); - - BackendHelper.updateBackendInstances(backends); - } - - /** - * Instantiate backends defined in the given JsonArray. - * - * @param backends The JsonArray containing the backends - * @return An ArrayList of the instantiated backends - */ - private ArrayList<BackendInstance> getBackendsFromJsonArray(JsonArray backends) { - ArrayList<BackendInstance> backendInstances = new ArrayList<>(); - backendInstanceList.getChildren().clear(); - backends.forEach((backend) -> { - BackendInstance newBackendInstance = new BackendInstance(backend.getAsJsonObject()); - backendInstances.add(newBackendInstance); - }); - - return backendInstances; - } - - /** - * Checks a set of paths to the packaged engines, j-Ecdar and Reveaal, and instantiates them - * if one of the related files exists. - * - * @return Backend instances of the packaged engines - */ - private ArrayList<BackendInstance> getPackagedBackends() { - ArrayList<BackendInstance> defaultBackends = new ArrayList<>(); - - // Add Reveaal engine - var reveaal = new BackendInstance(); - reveaal.setName("Reveaal"); - reveaal.setLocal(true); - reveaal.setDefault(true); - - // The engine is thread-safe, a range just adds options for finding an open port - // Only one process will be started - reveaal.setPortStart(5040); - reveaal.setPortEnd(5042); - reveaal.lockInstance(); - reveaal.setIsThreadSafe(true); - - // Load correct Reveaal executable based on OS - List<String> potentialFilesForReveaal = new ArrayList<>(); - if (SystemUtils.IS_OS_WINDOWS) { - potentialFilesForReveaal.add("Reveaal.exe"); - } else { - potentialFilesForReveaal.add("Reveaal"); - } - if (setBackendPathIfFileExists(reveaal, potentialFilesForReveaal)) defaultBackends.add(reveaal); - - // Add jECDAR engine - var jEcdar = new BackendInstance(); - jEcdar.setName("j-Ecdar"); - jEcdar.setLocal(true); - jEcdar.setDefault(false); - jEcdar.setPortStart(5042); - jEcdar.setPortEnd(5050); - jEcdar.lockInstance(); - jEcdar.setIsThreadSafe(false); - - // Load correct j-Ecdar executable based on OS - List<String> potentialFiledForJEcdar = new ArrayList<>(); - if (SystemUtils.IS_OS_WINDOWS) { - potentialFiledForJEcdar.add("j-Ecdar.bat"); - } else { - potentialFiledForJEcdar.add("j-Ecdar"); - } - - if (setBackendPathIfFileExists(jEcdar, potentialFiledForJEcdar)) defaultBackends.add(jEcdar); - - return defaultBackends; - } - - /** - * Sets the path to the backend instance if one of the potential files exists - * - * @param engine The backend instance of the engine to set the path for - * @param potentialFiles List of potential files to use for the engine - * @return True if one of the potentialFiles where found in path, false otherwise. - * This value also signals whether the engine backendLocation is set - */ - private boolean setBackendPathIfFileExists(BackendInstance engine, List<String> potentialFiles) { - engine.setBackendLocation(""); - - try { - // Get directory containing the bin and lib folders for the executing program - String pathToEcdarDirectory = new File(getClass().getProtectionDomain().getCodeSource().getLocation().toURI()).getParentFile().getPath(); - - List<File> files = List.of(Objects.requireNonNull(new File(pathToEcdarDirectory).listFiles())); - for (File f : files) { - if (potentialFiles.contains(f.getName())) { - engine.setBackendLocation(f.getAbsolutePath()); - return true; - } - } - } catch (URISyntaxException e) { - e.printStackTrace(); - Ecdar.showToast("Unable to get URI of parent directory: \"" + getClass().getProtectionDomain().getCodeSource().getLocation() + "\" due to: " + e.getMessage()); - } catch (NullPointerException e) { - e.printStackTrace(); - Ecdar.showToast("Encountered null reference when trying to get path of executing program"); - } - - return !engine.getBackendLocation().equals(""); - } - - /** - * Add the new backend instance presentation to the backend options dialog - * @param newBackendInstancePresentation The presentation of the new backend instance - */ - private void addBackendInstancePresentationToList(BackendInstancePresentation newBackendInstancePresentation) { - backendInstanceList.getChildren().add(newBackendInstancePresentation); - newBackendInstancePresentation.getController().moveBackendInstanceUpRippler.setOnMouseClicked((mouseEvent) -> moveBackendInstance(newBackendInstancePresentation, -1)); - newBackendInstancePresentation.getController().moveBackendInstanceDownRippler.setOnMouseClicked((mouseEvent) -> moveBackendInstance(newBackendInstancePresentation, +1)); - - // Set remove backend action to only fire if the backend is not locked - newBackendInstancePresentation.getController().removeBackendRippler.setOnMouseClicked((mouseEvent) -> { - if (!newBackendInstancePresentation.getController().defaultBackendRadioButton.isSelected()) { - backendInstanceList.getChildren().remove(newBackendInstancePresentation); - } - }); - newBackendInstancePresentation.getController().defaultBackendRadioButton.setToggleGroup(defaultBackendToggleGroup); - } - - /** - * Calculated the location new position of the backend instance, i places further down, in the backend instance list. - * The backend instance presentation is removed at added to the new position. - * Given a negative value, the instance is moved up. This function uses loop-around, meaning that: - * - If the instance is moved down while already at the bottom of the list, it is placed at the top. - * - If the instance is moved up while already at the top of the list, it is placed at the bottom. - * - * @param backendInstancePresentation The backend instance presentation to move - * @param i The number of steps to move the backend instance down - */ - private void moveBackendInstance(BackendInstancePresentation backendInstancePresentation, int i) { - int currentIndex = backendInstanceList.getChildren().indexOf(backendInstancePresentation); - int newIndex = (currentIndex + i) % backendInstanceList.getChildren().size(); - if (newIndex < 0) { - newIndex = backendInstanceList.getChildren().size() - 1; - } - - backendInstanceList.getChildren().remove(backendInstancePresentation); - backendInstanceList.getChildren().add(newIndex, backendInstancePresentation); - } - - /** - * Marks input fields in the backendInstanceList that contains errors and returns whether any errors were found - * - * @return whether any errors were found - */ - private boolean backendInstanceListIsErrorFree() { - boolean error = true; - - for (Node child : backendInstanceList.getChildren()) { - if (child instanceof BackendInstancePresentation) { - BackendInstanceController backendInstanceController = ((BackendInstancePresentation) child).getController(); - error = backendNameIsErrorFree(backendInstanceController) && error; - error = portRangeIsErrorFree(backendInstanceController) && error; - error = backendInstanceLocationIsErrorFree(backendInstanceController) && error; - } - } - - return error; - } - - private boolean backendNameIsErrorFree(BackendInstanceController backendInstanceController) { - String backendName = backendInstanceController.backendName.getText(); - - if (backendName.isBlank()) { - backendInstanceController.backendNameIssue.setText(ValidationErrorMessages.BACKEND_NAME_EMPTY.toString()); - backendInstanceController.backendNameIssue.setVisible(true); - return false; - } - - backendInstanceController.backendNameIssue.setVisible(false); - return true; - } - - private boolean portRangeIsErrorFree(BackendInstanceController backendInstanceController) { - boolean errorFree = true; - int portRangeStart = 0, portRangeEnd = 0; - backendInstanceController.portRangeStartIssue.setText(""); - backendInstanceController.portRangeStartIssue.setVisible(false); - backendInstanceController.portRangeEndIssue.setText(""); - backendInstanceController.portRangeEndIssue.setVisible(false); - backendInstanceController.portRangeIssue.setVisible(false); - - try { - portRangeStart = Integer.parseInt(backendInstanceController.portRangeStart.getText()); - } catch (NumberFormatException numberFormatException) { - backendInstanceController.portRangeStartIssue.setText(ValidationErrorMessages.VALUE_NOT_INTEGER.toString()); - errorFree = false; - } - - try { - portRangeEnd = Integer.parseInt(backendInstanceController.portRangeEnd.getText()); - } catch (NumberFormatException numberFormatException) { - backendInstanceController.portRangeEndIssue.setText(ValidationErrorMessages.VALUE_NOT_INTEGER.toString()); - errorFree = false; - } - - Range<Integer> portRange = Range.between(0, 65535); - - if (!portRange.contains(portRangeStart)) { - if (backendInstanceController.portRangeStartIssue.getText().isBlank()) { - backendInstanceController.portRangeStartIssue.setText(ValidationErrorMessages.PORT_VALUE_NOT_WITHIN_ACCEPTABLE_RANGE.toString()); - } else { - backendInstanceController.portRangeStartIssue.setText(ValidationErrorMessages.PORT_VALUE_NOT_WITHIN_ACCEPTABLE_RANGE_CONCATINATION.toString()); - } - errorFree = false; - } - if (!portRange.contains(portRangeEnd)) { - if (backendInstanceController.portRangeEndIssue.getText().isBlank()) { - backendInstanceController.portRangeEndIssue.setText(ValidationErrorMessages.PORT_VALUE_NOT_WITHIN_ACCEPTABLE_RANGE.toString()); - } else { - backendInstanceController.portRangeEndIssue.setText(ValidationErrorMessages.PORT_VALUE_NOT_WITHIN_ACCEPTABLE_RANGE_CONCATINATION.toString()); - } - errorFree = false; - } - - if (portRangeEnd - portRangeStart < 0) { - backendInstanceController.portRangeIssue.setText(ValidationErrorMessages.PORT_RANGE_MUST_BE_INCREMENTAL.toString()); - errorFree = false; - } - - backendInstanceController.portRangeStartIssue.setVisible(!errorFree); - backendInstanceController.portRangeEndIssue.setVisible(!errorFree); - backendInstanceController.portRangeIssue.setVisible(!errorFree); - - return errorFree; - } - - private boolean backendInstanceLocationIsErrorFree(BackendInstanceController backendInstanceController) { - boolean errorFree = true; - - if (backendInstanceController.isLocal.isSelected()) { - if (backendInstanceController.pathToBackend.getText().isBlank()) { - backendInstanceController.locationIssue.setText(ValidationErrorMessages.FILE_LOCATION_IS_BLANK.toString()); - errorFree = false; - } else { - Path localBackendPath = Paths.get(backendInstanceController.pathToBackend.getText()); - - if (!Files.isExecutable(localBackendPath)) { - backendInstanceController.locationIssue.setText(ValidationErrorMessages.FILE_DOES_NOT_EXIST_OR_NOT_EXECUTABLE.toString()); - errorFree = false; - } - } - } else { - if (backendInstanceController.address.getText().isBlank()) { - backendInstanceController.locationIssue.setText(ValidationErrorMessages.HOST_ADDRESS_IS_BLANK.toString()); - errorFree = false; - } else { - try { - InetAddress address = InetAddress.getByName(backendInstanceController.address.getText()); - boolean reachable = address.isReachable(200); - - if (!reachable) { - backendInstanceController.locationIssue.setText(ValidationErrorMessages.HOST_NOT_REACHABLE.toString()); - errorFree = false; - } - - } catch (UnknownHostException unknownHostException) { - backendInstanceController.locationIssue.setText(ValidationErrorMessages.UNACCEPTABLE_HOST_NAME.toString()); - errorFree = false; - } catch (IOException ioException) { - backendInstanceController.locationIssue.setText(ValidationErrorMessages.IO_EXCEPTION_WITH_HOST.toString()); - errorFree = false; - } - } - } - - backendInstanceController.locationIssue.setVisible(!errorFree); - - return errorFree; - } - - private enum ValidationErrorMessages { - BACKEND_NAME_EMPTY { - @Override - public String toString() { - return "The backend name cannot be empty"; - } - }, - VALUE_NOT_INTEGER { - @Override - public String toString() { - return "Value must be integer"; - } - }, - PORT_RANGE_MUST_BE_INCREMENTAL { - @Override - public String toString() { - return "Start of port range must be greater than end"; - } - }, - PORT_VALUE_NOT_WITHIN_ACCEPTABLE_RANGE { - @Override - public String toString() { - return "Value must be within range 0 - 65535"; - } - }, - PORT_VALUE_NOT_WITHIN_ACCEPTABLE_RANGE_CONCATINATION { - @Override - public String toString() { - return " and within range 0 - 65535"; - } - }, - FILE_LOCATION_IS_BLANK { - @Override - public String toString() { - return "Please specify a file for this backend"; - } - }, - FILE_DOES_NOT_EXIST_OR_NOT_EXECUTABLE { - @Override - public String toString() { - return "The above file does not exists or ECDAR does not have the privileges to execute it"; - } - }, - HOST_ADDRESS_IS_BLANK { - @Override - public String toString() { - return "Please specify an address for the external host"; - } - }, - HOST_NOT_REACHABLE { - @Override - public String toString() { - return "The above address is not reachable. Make sure that the host is correct"; - } - }, - UNACCEPTABLE_HOST_NAME { - @Override - public String toString() { - return "The above address is not an acceptable host name"; - } - }, - IO_EXCEPTION_WITH_HOST { - @Override - public String toString() { - return "An I/O exception was encountered while trying to reach the host"; - } - } - } -} diff --git a/src/main/java/ecdar/controllers/CanvasController.java b/src/main/java/ecdar/controllers/CanvasController.java index 76306a29..7427c70a 100644 --- a/src/main/java/ecdar/controllers/CanvasController.java +++ b/src/main/java/ecdar/controllers/CanvasController.java @@ -2,11 +2,6 @@ import com.jfoenix.controls.JFXRippler; import ecdar.Ecdar; -import ecdar.abstractions.Component; -import ecdar.abstractions.Declarations; -import ecdar.abstractions.HighLevelModelObject; -import ecdar.abstractions.EcdarSystem; -import ecdar.mutation.models.MutationTestPlan; import ecdar.mutation.MutationTestPlanPresentation; import ecdar.presentations.*; import ecdar.utility.helpers.ZoomHelper; @@ -39,8 +34,8 @@ public class CanvasController implements Initializable { public final double DECLARATION_Y_MARGIN = Ecdar.CANVAS_PADDING * 5.5; public ComponentPresentation activeComponentPresentation; - private final ObjectProperty<HighLevelModelObject> activeModel = new SimpleObjectProperty<>(null); - private final HashMap<HighLevelModelObject, Pair<Double, Double>> ModelObjectTranslateMap = new HashMap<>(); + private final ObjectProperty<HighLevelModelPresentation> activeModelPresentation = new SimpleObjectProperty<>(null); + private final HashMap<HighLevelModelPresentation, Pair<Double, Double>> ModelObjectTranslateMap = new HashMap<>(); private DoubleProperty width, height; private BooleanProperty insetShouldShow; @@ -56,21 +51,22 @@ public BooleanProperty getInsetShouldShow() { return insetShouldShow; } - public HighLevelModelObject getActiveModel() { - return activeModel.get(); + public HighLevelModelPresentation getActiveModelPresentation() { + return activeModelPresentation.get(); } /** * Sets the given model as the one to be active, e.g. to be shown on the screen. * @param model the given model */ - public void setActiveModel(final HighLevelModelObject model) { - activeModel.set(model); - Platform.runLater(EcdarController.getActiveCanvasPresentation().getController()::leaveTextAreas); + public void setActiveModelPresentation(final HighLevelModelPresentation model) { + activeModelPresentation.set(model); + Platform.runLater(Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasPresentation().getController()::leaveTextAreas); + Platform.runLater(zoomHelper::zoomToFit); } - public ObjectProperty<HighLevelModelObject> activeComponentProperty() { - return activeModel; + public ObjectProperty<HighLevelModelPresentation> activeModelProperty() { + return activeModelPresentation; } public void leaveTextAreas() { @@ -101,9 +97,11 @@ public void initialize(final URL location, final ResourceBundle resources) { root.widthProperty().addListener((observable, oldValue, newValue) -> width.setValue(newValue)); root.heightProperty().addListener((observable, oldValue, newValue) -> height.setValue(newValue)); - activeModel.addListener((obs, oldModel, newModel) -> onActiveModelChanged(oldModel, newModel)); + activeModelPresentation.addListener((obs, oldModel, newModel) -> { + onActiveModelChanged(oldModel, newModel); + }); - leaveTextAreas = () -> root.requestFocus(); + Platform.runLater(() -> leaveTextAreas = () -> root.requestFocus()); leaveOnEnterPressed = (keyEvent) -> { if (keyEvent.getCode().equals(KeyCode.ENTER) || keyEvent.getCode().equals(KeyCode.ESCAPE)) { leaveTextAreas(); @@ -130,9 +128,9 @@ public void updateOffset(final Boolean shouldHave) { * @param oldObject old object * @param newObject new object */ - private void onActiveModelChanged(final HighLevelModelObject oldObject, final HighLevelModelObject newObject) { + private void onActiveModelChanged(final HighLevelModelPresentation oldObject, final HighLevelModelPresentation newObject) { // If old object is a component or system, add to map in order to remember coordinate - if (oldObject instanceof Component || oldObject instanceof EcdarSystem) { + if (oldObject instanceof ComponentPresentation || oldObject instanceof SystemPresentation) { ModelObjectTranslateMap.put(oldObject, new Pair<>(modelPane.getTranslateX(), modelPane.getTranslateY())); } @@ -142,23 +140,34 @@ private void onActiveModelChanged(final HighLevelModelObject oldObject, final Hi // Remove old object from view modelPane.getChildren().removeIf(node -> node instanceof HighLevelModelPresentation); - if (newObject instanceof Component) { - activeComponentPresentation = new ComponentPresentation((Component) newObject); + if (newObject instanceof ComponentPresentation) { + activeComponentPresentation = (ComponentPresentation) newObject; modelPane.getChildren().add(activeComponentPresentation); - } else if (newObject instanceof Declarations) { + + // To avoid NullPointerException on initial model + Platform.runLater(zoomHelper::resetZoom); + + } else if (newObject instanceof DeclarationsPresentation) { activeComponentPresentation = null; - modelPane.getChildren().add(new DeclarationPresentation((Declarations) newObject)); - } else if (newObject instanceof EcdarSystem) { + modelPane.getChildren().add(newObject); + + // Bind size of Declaration to size of the model pane to ensure alignment and avoid drag + DeclarationsController declarationsController = (DeclarationsController) newObject.getController(); + declarationsController.root.minWidthProperty().bind(modelPane.minWidthProperty()); + declarationsController.root.maxWidthProperty().bind(modelPane.maxWidthProperty()); + declarationsController.root.minHeightProperty().bind(modelPane.minHeightProperty()); + declarationsController.root.maxHeightProperty().bind(modelPane.maxHeightProperty()); + } else if (newObject instanceof SystemPresentation) { activeComponentPresentation = null; - modelPane.getChildren().add(new SystemPresentation((EcdarSystem) newObject)); - } else if (newObject instanceof MutationTestPlan) { + modelPane.getChildren().add(newObject); + } else if (newObject instanceof MutationTestPlanPresentation) { activeComponentPresentation = null; - modelPane.getChildren().add(new MutationTestPlanPresentation((MutationTestPlan) newObject)); + modelPane.getChildren().add(newObject); } else { throw new IllegalStateException("Type of object is not supported."); } - boolean shouldZoomBeActive = newObject instanceof Component || newObject instanceof EcdarSystem; + boolean shouldZoomBeActive = newObject instanceof ComponentPresentation || newObject instanceof SystemPresentation; setZoomAvailable(shouldZoomBeActive); root.requestFocus(); diff --git a/src/main/java/ecdar/controllers/ComponentController.java b/src/main/java/ecdar/controllers/ComponentController.java index a357c1a6..49fd610f 100644 --- a/src/main/java/ecdar/controllers/ComponentController.java +++ b/src/main/java/ecdar/controllers/ComponentController.java @@ -6,26 +6,32 @@ import ecdar.code_analysis.CodeAnalysis; import ecdar.presentations.*; import ecdar.utility.UndoRedoStack; -import ecdar.utility.colors.Color; +import ecdar.utility.colors.EnabledColor; import ecdar.utility.helpers.*; -import ecdar.utility.mouse.MouseTracker; import com.jfoenix.controls.JFXPopup; import com.jfoenix.controls.JFXRippler; +import ecdar.utility.keyboard.NudgeDirection; import javafx.animation.Interpolator; import javafx.animation.Transition; import javafx.application.Platform; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; -import javafx.beans.value.ChangeListener; import javafx.collections.ListChangeListener; import javafx.fxml.FXML; import javafx.fxml.Initializable; +import javafx.geometry.Insets; +import javafx.geometry.Point2D; +import javafx.geometry.Pos; +import javafx.scene.Cursor; import javafx.scene.layout.*; import javafx.scene.Node; import javafx.scene.control.Label; import javafx.scene.input.MouseEvent; import javafx.scene.layout.Pane; import javafx.scene.shape.Circle; +import javafx.scene.shape.Path; +import javafx.scene.shape.Rectangle; +import javafx.scene.shape.Shape; import javafx.util.Duration; import org.fxmisc.richtext.LineNumberFactory; import org.fxmisc.richtext.StyleClassedTextArea; @@ -36,15 +42,20 @@ import java.util.function.Function; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import static ecdar.presentations.ModelPresentation.*; +import static ecdar.utility.helpers.UnoccupiedSpaceFinder.getUnoccupiedSpace; public class ComponentController extends ModelController implements Initializable { + private final List<Consumer<EnabledColor>> updateColorDelegates = new ArrayList<>(); private static final Map<Component, ListChangeListener<Location>> locationListChangeListenerMap = new HashMap<>(); private static final Map<Component, Boolean> errorsAndWarningsInitialized = new HashMap<>(); - private static Location placingLocation = null; private final ObjectProperty<Component> component = new SimpleObjectProperty<>(null); private final Map<DisplayableEdge, EdgePresentation> edgePresentationMap = new HashMap<>(); private final Map<Location, LocationPresentation> locationPresentationMap = new HashMap<>(); + // View elements public StyleClassedTextArea declarationTextArea; public JFXRippler toggleDeclarationButton; public Label x; @@ -56,33 +67,35 @@ public class ComponentController extends ModelController implements Initializabl public VBox outputSignatureContainer; public VBox inputSignatureContainer; - private MouseTracker mouseTracker; private DropDownMenu contextMenu; private DropDownMenu finishEdgeContextMenu; - - public static boolean isPlacingLocation() { - return placingLocation != null; - } - - public static void setPlacingLocation(final Location placingLocation) { - ComponentController.placingLocation = placingLocation; - } + private Point2D dropdownCoordinatesInComponent; @Override public void initialize(final URL location, final ResourceBundle resources) { declarationTextArea.setParagraphGraphicFactory(LineNumberFactory.get(declarationTextArea)); component.addListener((obs, oldComponent, newComponent) -> { + super.initialize(newComponent.getBox()); + + initializeFrame(); + initializeToolbar(); + initializeBackground(); + + // Re-run initialisation on update of width and height property + newComponent.getBox().getWidthProperty().addListener(observable -> initializeFrame()); + newComponent.getBox().getHeightProperty().addListener(observable -> initializeFrame()); + inputSignatureContainer.heightProperty().addListener((change) -> updateMaxHeight()); outputSignatureContainer.heightProperty().addListener((change) -> updateMaxHeight()); - // Bind the declarations of the abstraction the the view + // Bind the declarations of the abstraction to the view declarationTextArea.replaceText(0, declarationTextArea.getLength(), newComponent.getDeclarationsText()); declarationTextArea.textProperty().addListener((observable, oldDeclaration, newDeclaration) -> newComponent.setDeclarationsText(newDeclaration)); // Find the clocks in the decls newComponent.declarationsTextProperty().addListener((observable, oldValue, newValue) -> { - final List<String> clocks = new ArrayList<String>(); + final List<String> clocks = new ArrayList<>(); final String strippedDecls = newValue.replaceAll("[\\r\\n]+", ""); @@ -90,7 +103,7 @@ public void initialize(final URL location, final ResourceBundle resources) { Matcher matcher = pattern.matcher(strippedDecls); while (matcher.find()) { - final String clockStrings[] = matcher.group("CLOCKS").split(","); + final String[] clockStrings = matcher.group("CLOCKS").split(","); for (String clockString : clockStrings) { clocks.add(clockString.replaceAll("\\s", "")); } @@ -99,48 +112,126 @@ public void initialize(final URL location, final ResourceBundle resources) { //TODO this logs the clocks System.out.println(clocks); }); - initializeEdgeHandling(newComponent); - initializeLocationHandling(newComponent); + initializeEdgeHandling(); + initializeLocationHandling(); initializeDeclarations(); - initializeSignature(newComponent); - initializeSignatureListeners(newComponent); - }); + initializeSignature(); + initializeSignatureListeners(); + if (!errorsAndWarningsInitialized.containsKey(newComponent) || !errorsAndWarningsInitialized.get(newComponent)) { + initializeNoIncomingEdgesWarning(); + errorsAndWarningsInitialized.put(newComponent, true); + } + }); - // The root view have been inflated, initialize the mouse tracker on it - mouseTracker = new MouseTracker(root); initializeContextMenu(); - component.addListener((obs, old, component) -> { - if (component == null) return; + declarationTextArea.textProperty().addListener((obs, oldText, newText) -> + declarationTextArea.setStyleSpans(0, UPPAALSyntaxHighlighter.computeHighlighting(newText))); + } - if (!errorsAndWarningsInitialized.containsKey(component) || !errorsAndWarningsInitialized.get(component)) { - initializeNoIncomingEdgesWarning(); - errorsAndWarningsInitialized.put(component, true); - } + private void initializeToolbar() { + final Consumer<EnabledColor> updateColor = (newColor) -> { + // Set the background of the toolbar + toolbar.setBackground(new Background(new BackgroundFill( + newColor.color.getColor(newColor.intensity), + CornerRadii.EMPTY, + Insets.EMPTY + ))); + + // Set the icon color and rippler color of the toggleDeclarationButton + toggleDeclarationButton.setRipplerFill(newColor.getTextColor()); + + toolbar.setPrefHeight(TOOLBAR_HEIGHT); + toggleDeclarationButton.setBackground(Background.EMPTY); + }; + + updateColorDelegates.add(updateColor); + + getComponent().colorProperty().addListener(observable -> updateColor.accept(getComponent().getColor())); + + updateColor.accept(getComponent().getColor()); + + // Set a hover effect for the controller.toggleDeclarationButton + toggleDeclarationButton.setOnMouseEntered(event -> toggleDeclarationButton.setCursor(Cursor.HAND)); + toggleDeclarationButton.setOnMouseExited(event -> toggleDeclarationButton.setCursor(Cursor.DEFAULT)); + toggleDeclarationButton.setOnMousePressed(this::toggleDeclaration); + } + + private void initializeFrame() { + final Shape[] mask = new Shape[1]; + final Rectangle rectangle = new Rectangle(getComponent().getBox().getWidth(), getComponent().getBox().getHeight()); + + final Consumer<EnabledColor> updateColor = (newColor) -> { + // Mask the parent of the frame (will also mask the background) + mask[0] = Path.subtract(rectangle, TOP_LEFT_CORNER); + frame.setClip(mask[0]); + background.setClip(Path.union(mask[0], mask[0])); + background.setOpacity(0.5); + + // Bind the missing lines that we cropped away + topLeftLine.setStartX(CORNER_SIZE); + topLeftLine.setStartY(0); + topLeftLine.setEndX(0); + topLeftLine.setEndY(CORNER_SIZE); + topLeftLine.setStroke(newColor.getStrokeColor()); + topLeftLine.setStrokeWidth(1.25); + StackPane.setAlignment(topLeftLine, Pos.TOP_LEFT); + + // Set the stroke color to two shades darker + frame.setBorder(new Border(new BorderStroke( + newColor.getStrokeColor(), + BorderStrokeStyle.SOLID, + CornerRadii.EMPTY, + new BorderWidths(1), + Insets.EMPTY + ))); + }; + + updateColorDelegates.add(updateColor); + + getComponent().colorProperty().addListener(observable -> { + updateColor.accept(getComponent().getColor()); + }); + + updateColor.accept(getComponent().getColor()); + } + + private void initializeBackground() { + // Bind the background width and height to the values in the model + background.widthProperty().bindBidirectional(getComponent().getBox().getWidthProperty()); + background.heightProperty().bindBidirectional(getComponent().getBox().getHeightProperty()); + + final Consumer<EnabledColor> updateColor = (newColor) -> { + // Set the background color to the lightest possible version of the color and then increase by two + background.setFill(newColor.getLowestIntensity().nextIntensity(2).getPaintColor()); + }; + + updateColorDelegates.add(updateColor); + + getComponent().colorProperty().addListener(observable -> { + updateColor.accept(getComponent().getColor()); }); + + updateColor.accept(getComponent().getColor()); } /*** * Inserts the initial edges of the component to the input/output signature - * @param newComponent The component that should be presented with its signature */ - private void initializeSignature(final Component newComponent) { - newComponent.getOutputStrings().forEach((channel) -> insertSignatureArrow(channel, EdgeStatus.OUTPUT)); - newComponent.getInputStrings().forEach((channel) -> insertSignatureArrow(channel, EdgeStatus.INPUT)); + private void initializeSignature() { + getComponent().getOutputStrings().forEach((channel) -> insertSignatureArrow(channel, EdgeStatus.OUTPUT)); + getComponent().getInputStrings().forEach((channel) -> insertSignatureArrow(channel, EdgeStatus.INPUT)); } - /*** + /** * Initialize the listeners, that listen for changes in the input and output edges of the presented component. * The view is updated whenever an insert (deletions are also a type of insert) is reported - * @param newComponent The component that should be presented with its signature */ - - - private void initializeSignatureListeners(final Component newComponent) { - newComponent.getIsFailingProperty().addListener((observable, oldValue, newValue) -> { - if (newComponent.getIsFailing()) { + private void initializeSignatureListeners() { + getComponent().getIsFailingProperty().addListener((observable, oldValue, newValue) -> { + if (getComponent().getIsFailing()) { for (Node n : inputSignatureContainer.getChildren()) { if (n instanceof SignatureArrow) { for (String label : component.get().getFailingIOStrings()) { @@ -172,7 +263,7 @@ private void initializeSignatureListeners(final Component newComponent) { } } }); - newComponent.getOutputStrings().addListener((ListChangeListener<String>) c -> { + getComponent().getOutputStrings().addListener((ListChangeListener<String>) c -> { // By clearing the container we don't have to fiddle with which elements are removed and added outputSignatureContainer.getChildren().clear(); while (c.next()) { @@ -180,7 +271,7 @@ private void initializeSignatureListeners(final Component newComponent) { } }); - newComponent.getInputStrings().addListener((ListChangeListener<String>) c -> { + getComponent().getInputStrings().addListener((ListChangeListener<String>) c -> { inputSignatureContainer.getChildren().clear(); while (c.next()) { c.getAddedSubList().forEach((channel) -> insertSignatureArrow(channel, EdgeStatus.INPUT)); @@ -188,35 +279,6 @@ private void initializeSignatureListeners(final Component newComponent) { }); } - /*** - * Inserts a new {@link ecdar.presentations.SignatureArrow} in the containers for either input or output signature - * @param channel A String with the channel name that should be shown with the arrow - * @param status An EdgeStatus for the type of arrow to insert - */ - private void insertSignatureArrow(final String channel, final EdgeStatus status) { - SignatureArrow newArrow = new SignatureArrow(channel, status, component.get()); - - if (status == EdgeStatus.INPUT) { - inputSignatureContainer.getChildren().add(newArrow); - } else { - outputSignatureContainer.getChildren().add(newArrow); - } - - } - - /*** - * Updates the component's height to match the input/output signature containers - * if the component is smaller than either of them - */ - private void updateMaxHeight() { - // If input/outputsignature container is taller than the current component height - // we update the component's height to be as tall as the container - double maxHeight = findMaxHeight(); - if (maxHeight > component.get().getBox().getHeight()) { - component.get().getBox().getHeightProperty().set(maxHeight); - } - } - /*** * Finds the max height of the input/output signature container and the component * @return a double of the largest height @@ -283,26 +345,19 @@ private void initializeNoIncomingEdgesWarning() { } }; - final Component component = getComponent(); - checkLocations.accept(component); + checkLocations.accept(getComponent()); // Check location whenever we get new edges - component.getDisplayableEdges().addListener(new ListChangeListener<DisplayableEdge>() { - @Override - public void onChanged(final Change<? extends DisplayableEdge> c) { - while (c.next()) { - checkLocations.accept(component); - } + getComponent().getDisplayableEdges().addListener((ListChangeListener<DisplayableEdge>) c -> { + while (c.next()) { + checkLocations.accept(getComponent()); } }); // Check location whenever we get new locations - component.getLocations().addListener(new ListChangeListener<Location>() { - @Override - public void onChanged(final Change<? extends Location> c) { - while (c.next()) { - checkLocations.accept(component); - } + getComponent().getLocations().addListener((ListChangeListener<Location>) c -> { + while (c.next()) { + checkLocations.accept(getComponent()); } }); } @@ -313,21 +368,12 @@ private void initializeContextMenu() { return; } - contextMenu = new DropDownMenu(root); + contextMenu = new DropDownMenu(modelContainerSubComponent); contextMenu.addClickableListElement("Add Location", event -> { contextMenu.hide(); - final Location newLocation = new Location(); - newLocation.initialize(); - - double x = DropDownMenu.x - LocationPresentation.RADIUS / 2; - newLocation.setX(x); - - double y = DropDownMenu.y - LocationPresentation.RADIUS / 2; - newLocation.setY(y); - newLocation.setColorIntensity(component.getColorIntensity()); - newLocation.setColor(component.getColor()); + final Location newLocation = new Location(component, Location.Type.NORMAL, getComponent().getUniqueLocationId(), dropdownCoordinatesInComponent.getX(), dropdownCoordinatesInComponent.getY()); // Add a new location UndoRedoStack.pushAndPerform(() -> { // Perform @@ -337,13 +383,10 @@ private void initializeContextMenu() { }, "Added location '" + newLocation + "' to component '" + component.getName() + "'", "add-circle"); }); - // Adds the add universal location element to the drop down menu, this element adds an universal location and its required edges + // Adds the add universal location element to the drop-down menu, this element adds a universal location and its required edges contextMenu.addClickableListElement("Add Universal Location", event -> { contextMenu.hide(); - double x = DropDownMenu.x - LocationPresentation.RADIUS / 2; - double y = DropDownMenu.y - LocationPresentation.RADIUS / 2; - - final Location newLocation = new Location(component, Location.Type.UNIVERSAL, x, y); + final Location newLocation = new Location(component, Location.Type.UNIVERSAL, getComponent().getUniqueLocationId(), dropdownCoordinatesInComponent.getX(), dropdownCoordinatesInComponent.getY()); final Edge inputEdge = newLocation.addLeftEdge("*", EdgeStatus.INPUT); inputEdge.setIsLocked(true); @@ -366,10 +409,7 @@ private void initializeContextMenu() { // Adds the add inconsistent location element to the dropdown menu, this element adds an inconsistent location contextMenu.addClickableListElement("Add Inconsistent Location", event -> { contextMenu.hide(); - double x = DropDownMenu.x - LocationPresentation.RADIUS / 2; - double y = DropDownMenu.y - LocationPresentation.RADIUS / 2; - - final Location newLocation = new Location(component, Location.Type.INCONSISTENT, x, y); + final Location newLocation = new Location(component, Location.Type.INCONSISTENT, getComponent().getUniqueLocationId(), dropdownCoordinatesInComponent.getX(), dropdownCoordinatesInComponent.getY()); // Add a new location UndoRedoStack.pushAndPerform(() -> { // Perform @@ -379,24 +419,6 @@ private void initializeContextMenu() { }, "Added inconsistent location '" + newLocation + "' to component '" + component.getName() + "'", "add-circle"); }); - contextMenu.addSpacerElement(); - - contextMenu.addClickableListElement("Contains deadlock?", event -> { - - // Generate the query - final String deadlockQuery = BackendHelper.getExistDeadlockQuery(getComponent()); - - // Add proper comment - final String deadlockComment = "Does " + component.getName() + " contain a deadlock?"; - - // Add new query for this component - final Query query = new Query(deadlockQuery, deadlockComment, QueryState.UNKNOWN); - query.setType(QueryType.REACHABILITY); - Ecdar.getProject().getQueries().add(query); - Ecdar.getQueryExecutor().executeQuery(query); - contextMenu.hide(); - }); - contextMenu.addSpacerElement(); contextMenu.addColorPicker(component, component::dye); }; @@ -405,13 +427,6 @@ private void initializeContextMenu() { initializeDropDownMenu.accept(newComponent); }); - Ecdar.getProject().getComponents().addListener(new ListChangeListener<Component>() { - @Override - public void onChanged(final Change<? extends Component> c) { - initializeDropDownMenu.accept(getComponent()); - } - }); - initializeDropDownMenu.accept(getComponent()); } @@ -429,15 +444,13 @@ private void initializeFinishEdgeContextMenu(final DisplayableEdge unfinishedEdg locationAware.yProperty().set(y); }; - finishEdgeContextMenu = new DropDownMenu(root); + finishEdgeContextMenu = new DropDownMenu(modelContainerSubComponent); finishEdgeContextMenu.addListElement("Finish edge in a:"); finishEdgeContextMenu.addClickableListElement("Location", event -> { finishEdgeContextMenu.hide(); final Location location = new Location(); - location.initialize(); - - location.setColorIntensity(getComponent().getColorIntensity()); + location.initialize(getComponent().getUniqueLocationId()); location.setColor(getComponent().getColor()); if (component.isAnyEdgeWithoutSource()) { @@ -466,10 +479,7 @@ private void initializeFinishEdgeContextMenu(final DisplayableEdge unfinishedEdg finishEdgeContextMenu.addClickableListElement("Universal Location", event -> { finishEdgeContextMenu.hide(); - double x = DropDownMenu.x - LocationPresentation.RADIUS / 2; - double y = DropDownMenu.y - LocationPresentation.RADIUS / 2; - - final Location newLocation = new Location(component, Location.Type.UNIVERSAL, x, y); + final Location newLocation = new Location(component, Location.Type.UNIVERSAL, getComponent().getUniqueLocationId(), dropdownCoordinatesInComponent.getX(), dropdownCoordinatesInComponent.getY()); final Edge inputEdge = newLocation.addLeftEdge("*", EdgeStatus.INPUT); inputEdge.setIsLocked(true); @@ -508,10 +518,7 @@ private void initializeFinishEdgeContextMenu(final DisplayableEdge unfinishedEdg finishEdgeContextMenu.addClickableListElement("Inconsistent Location", event -> { finishEdgeContextMenu.hide(); - double x = DropDownMenu.x - LocationPresentation.RADIUS / 2; - double y = DropDownMenu.y - LocationPresentation.RADIUS / 2; - - final Location newLocation = new Location(component, Location.Type.INCONSISTENT, x, y); + final Location newLocation = new Location(component, Location.Type.INCONSISTENT, getComponent().getUniqueLocationId(), dropdownCoordinatesInComponent.getX(), dropdownCoordinatesInComponent.getY()); if (component.isAnyEdgeWithoutSource()) { unfinishedEdge.setSourceLocation(newLocation); @@ -544,218 +551,15 @@ private void initializeFinishEdgeContextMenu(final DisplayableEdge unfinishedEdg initializeDropDownMenu.accept(getComponent()); } - private void initializeLocationHandling(final Component newComponent) { - final Consumer<Location> handleAddedLocation = (loc) -> { - // Check related to undo/redo stack - if (locationPresentationMap.containsKey(loc)) { - return; - } - - // Create a new presentation, and register it on the map - final LocationPresentation newLocationPresentation = new LocationPresentation(loc, newComponent); - - final ChangeListener<Number> locationPlacementChangedListener = (observable, oldValue, newValue) -> { - final double offset = newLocationPresentation.getController().circle.getRadius() * 2; - boolean hit = false; - ItemDragHelper.DragBounds componentBounds = newLocationPresentation.getController().getDragBounds(); - - //Define the x and y coordinates for the initial and final locations - final double initialLocationX = component.get().getBox().getX() + newLocationPresentation.getController().circle.getRadius() * 2, - initialLocationY = component.get().getBox().getY() + newLocationPresentation.getController().circle.getRadius() * 2, - finalLocationX = component.get().getBox().getX() + component.get().getBox().getWidth() - newLocationPresentation.getController().circle.getRadius() * 2, - finalLocationY = component.get().getBox().getY() + component.get().getBox().getHeight() - newLocationPresentation.getController().circle.getRadius() * 2; - - double latestHitRight = 0, - latestHitDown = 0, - latestHitLeft = 0, - latestHitUp = 0; - - //Check to see if the location is placed on top of the initial location - if (Math.abs(initialLocationX - (newLocationPresentation.getLayoutX())) < offset && - Math.abs(initialLocationY - (newLocationPresentation.getLayoutY())) < offset) { - hit = true; - latestHitRight = initialLocationX; - latestHitDown = initialLocationY; - latestHitLeft = initialLocationX; - latestHitUp = initialLocationY; - } - - //Check to see if the location is placed on top of the final location - else if (Math.abs(finalLocationX - (newLocationPresentation.getLayoutX())) < offset && - Math.abs(finalLocationY - (newLocationPresentation.getLayoutY())) < offset) { - hit = true; - latestHitRight = finalLocationX; - latestHitDown = finalLocationY; - latestHitLeft = finalLocationX; - latestHitUp = finalLocationY; - } - - //Check to see if the location is placed on top of another location - else { - for (Map.Entry<Location, LocationPresentation> entry : locationPresentationMap.entrySet()) { - if (entry.getValue() != newLocationPresentation && - Math.abs(entry.getValue().getLayoutX() - (newLocationPresentation.getLayoutX())) < offset && - Math.abs(entry.getValue().getLayoutY() - (newLocationPresentation.getLayoutY())) < offset) { - hit = true; - latestHitRight = entry.getValue().getLayoutX(); - latestHitDown = entry.getValue().getLayoutY(); - latestHitLeft = entry.getValue().getLayoutX(); - latestHitUp = entry.getValue().getLayoutY(); - break; - } - } - } - - //If the location is not placed on top of any other locations, do not do anything - if (!hit) { - return; - } - hit = false; - - //Find an unoccupied space for the location - for (int i = 1; i < component.get().getBox().getWidth() / offset; i++) { - - //Check to see, if the location can be placed to the right of the existing locations - if (componentBounds.trimX(latestHitRight + offset) == latestHitRight + offset) { - - //Check if the location would be placed on the final location - if (Math.abs(finalLocationX - (latestHitRight + offset)) < offset && - Math.abs(finalLocationY - (newLocationPresentation.getLayoutY())) < offset) { - hit = true; - latestHitRight = finalLocationX; - } else { - for (Map.Entry<Location, LocationPresentation> entry : locationPresentationMap.entrySet()) { - if (entry.getValue() != newLocationPresentation && - Math.abs(entry.getValue().getLayoutX() - (latestHitRight + offset)) < offset && - Math.abs(entry.getValue().getLayoutY() - (newLocationPresentation.getLayoutY())) < offset) { - hit = true; - latestHitRight = entry.getValue().getLayoutX(); - break; - } - } - } - - if (!hit) { - newLocationPresentation.setLayoutX(latestHitRight + offset); - return; - } - } - hit = false; - - //Check to see, if the location can be placed below the existing locations - if (componentBounds.trimY(latestHitDown + offset) == latestHitDown + offset) { - - //Check if the location would be placed on the final location - if (Math.abs(finalLocationX - (newLocationPresentation.getLayoutX())) < offset && - Math.abs(finalLocationY - (latestHitDown + offset)) < offset) { - hit = true; - latestHitDown = finalLocationY; - } else { - for (Map.Entry<Location, LocationPresentation> entry : locationPresentationMap.entrySet()) { - if (entry.getValue() != newLocationPresentation && - Math.abs(entry.getValue().getLayoutX() - (newLocationPresentation.getLayoutX())) < offset && - Math.abs(entry.getValue().getLayoutY() - (latestHitDown + offset)) < offset) { - hit = true; - latestHitDown = entry.getValue().getLayoutY(); - break; - } - } - } - if (!hit) { - newLocationPresentation.setLayoutY(latestHitDown + offset); - return; - } - } - hit = false; - - //Check to see, if the location can be placed to the left of the existing locations - if (componentBounds.trimX(latestHitLeft - offset) == latestHitLeft - offset) { - - //Check if the location would be placed on the initial location - if (Math.abs(initialLocationX - (latestHitLeft - offset)) < offset && - Math.abs(initialLocationY - (newLocationPresentation.getLayoutY())) < offset) { - hit = true; - latestHitLeft = initialLocationX; - } else { - for (Map.Entry<Location, LocationPresentation> entry : locationPresentationMap.entrySet()) { - if (entry.getValue() != newLocationPresentation && - Math.abs(entry.getValue().getLayoutX() - (latestHitLeft - offset)) < offset && - Math.abs(entry.getValue().getLayoutY() - (newLocationPresentation.getLayoutY())) < offset) { - hit = true; - latestHitLeft = entry.getValue().getLayoutX(); - break; - } - } - } - if (!hit) { - newLocationPresentation.setLayoutX(latestHitLeft - offset); - return; - } - } - hit = false; - - //Check to see, if the location can be placed above the existing locations - if (componentBounds.trimY(latestHitUp - offset) == latestHitUp - offset) { - - //Check if the location would be placed on the initial location - if (Math.abs(initialLocationX - (newLocationPresentation.getLayoutX())) < offset && - Math.abs(initialLocationY - (latestHitUp - offset)) < offset) { - hit = true; - latestHitUp = initialLocationY; - } else { - for (Map.Entry<Location, LocationPresentation> entry : locationPresentationMap.entrySet()) { - if (entry.getValue() != newLocationPresentation && - Math.abs(entry.getValue().getLayoutX() - (newLocationPresentation.getLayoutX())) < offset && - Math.abs(entry.getValue().getLayoutY() - (latestHitUp - offset)) < offset) { - hit = true; - latestHitUp = entry.getValue().getLayoutY(); - break; - } - } - } - if (!hit) { - newLocationPresentation.setLayoutY(latestHitUp - offset); - return; - } - } - hit = false; - } - modelContainerLocation.getChildren().remove(newLocationPresentation); - locationPresentationMap.remove(newLocationPresentation.getController().locationProperty().getValue()); - newComponent.getLocations().remove(newLocationPresentation.getController().getLocation()); - newComponent.getDisplayableEdges().removeIf(e -> e.targetLocationProperty().get().equals(newLocationPresentation.getController().getLocation())); - Ecdar.showToast("Please select an empty space for the new location"); - }; - - newLocationPresentation.layoutXProperty().addListener(locationPlacementChangedListener); - newLocationPresentation.layoutYProperty().addListener(locationPlacementChangedListener); - - locationPresentationMap.put(loc, newLocationPresentation); - - // Add it to the view - modelContainerLocation.getChildren().add(newLocationPresentation); - - // Bind the newly created location to the mouse and tell the ui that it is not placed yet - if (loc.getX() == 0) { - newLocationPresentation.setPlaced(false); - BindingHelper.bind(loc, newComponent.getBox().getXProperty(), newComponent.getBox().getYProperty()); - } - }; - + private void initializeLocationHandling() { final ListChangeListener<Location> locationListChangeListener = c -> { if (c.next()) { // Locations are added to the component c.getAddedSubList().forEach((loc) -> { - handleAddedLocation.accept(loc); - - LocationPresentation locationPresentation = locationPresentationMap.get(loc); - - //Ensure that the component is inside the bounds of the component - locationPresentation.setLayoutX(locationPresentation.getController().getDragBounds().trimX(locationPresentation.getLayoutX())); - locationPresentation.setLayoutY(locationPresentation.getController().getDragBounds().trimY(locationPresentation.getLayoutY())); - - //Change the layoutXProperty slightly to invoke listener and ensure distance to existing locations - locationPresentation.setLayoutX(locationPresentation.getLayoutX() + 0.00001); + // Check related to undo/redo stack + if (!locationPresentationMap.containsKey(loc)) { + addLocation(loc); + } }); // Locations are removed from the component @@ -766,18 +570,19 @@ else if (Math.abs(finalLocationX - (newLocationPresentation.getLayoutX())) < off }); } }; - newComponent.getLocations().addListener(locationListChangeListener); - if (!locationListChangeListenerMap.containsKey(newComponent)) { - locationListChangeListenerMap.put(newComponent, locationListChangeListener); + getComponent().getLocations().addListener(locationListChangeListener); + + if (!locationListChangeListenerMap.containsKey(getComponent())) { + locationListChangeListenerMap.put(getComponent(), locationListChangeListener); } - newComponent.getLocations().forEach(handleAddedLocation); + getComponent().getLocations().forEach(this::addLocation); } - private void initializeEdgeHandling(final Component newComponent) { + private void initializeEdgeHandling() { final Consumer<DisplayableEdge> handleAddedEdge = edge -> { - final EdgePresentation edgePresentation = new EdgePresentation(edge, newComponent); + final EdgePresentation edgePresentation = new EdgePresentation(edge, getComponent()); edgePresentationMap.put(edge, edgePresentation); modelContainerEdge.getChildren().add(edgePresentation); @@ -790,28 +595,26 @@ private void initializeEdgeHandling(final Component newComponent) { }; // React on addition of edges to the component - newComponent.getDisplayableEdges().addListener(new ListChangeListener<DisplayableEdge>() { - @Override - public void onChanged(final Change<? extends DisplayableEdge> c) { - if (c.next()) { - // Edges are added to the component - c.getAddedSubList().forEach(handleAddedEdge); - - // Edges are removed from the component - c.getRemoved().forEach(edge -> { - final EdgePresentation edgePresentation = edgePresentationMap.get(edge); - modelContainerEdge.getChildren().remove(edgePresentation); - edgePresentationMap.remove(edge); - }); - } + getComponent().getDisplayableEdges().addListener((ListChangeListener<DisplayableEdge>) c -> { + if (c.next()) { + // Edges are added to the component + c.getAddedSubList().forEach(handleAddedEdge); + + // Edges are removed from the component + c.getRemoved().forEach(edge -> { + final EdgePresentation edgePresentation = edgePresentationMap.get(edge); + modelContainerEdge.getChildren().remove(edgePresentation); + edgePresentationMap.remove(edge); + }); } }); - newComponent.getDisplayableEdges().forEach(handleAddedEdge); + + getComponent().getDisplayableEdges().forEach(handleAddedEdge); } private void initializeDeclarations() { // Initially style the declarations - declarationTextArea.setStyleSpans(0, ComponentPresentation.computeHighlighting(getComponent().getDeclarationsText())); + declarationTextArea.setStyleSpans(0, UPPAALSyntaxHighlighter.computeHighlighting(getComponent().getDeclarationsText())); declarationTextArea.getStyleClass().add("component-declaration"); final Circle circle = new Circle(0); @@ -823,9 +626,81 @@ private void initializeDeclarations() { clip.set(circle); } - public void toggleDeclaration(final MouseEvent mouseEvent) { + /*** + * Inserts a new {@link ecdar.presentations.SignatureArrow} in the containers for either input or output signature + * @param channel A String with the channel name that should be shown with the arrow + * @param status An EdgeStatus for the type of arrow to insert + */ + private void insertSignatureArrow(final String channel, final EdgeStatus status) { + SignatureArrow newArrow = new SignatureArrow(channel, status, getComponent()); + if (status == EdgeStatus.INPUT) { + inputSignatureContainer.getChildren().add(newArrow); + } else { + outputSignatureContainer.getChildren().add(newArrow); + } + } + + /*** + * Handles the addition of a new location + * @param loc The location to add to the component + */ + private void addLocation(Location loc) { + LocationPresentation newLocationPresentation = new LocationPresentation(loc, getComponent()); + Point2D placement = getUnoccupiedSpace(getComponent().getBox(), locationPresentationMap.values().stream().map(l -> new Point2D(l.getLayoutX(), l.getLayoutY())).collect(Collectors.toList()), new Point2D(loc.getX(), loc.getY()), LocationPresentation.RADIUS * 2 + Ecdar.CANVAS_PADDING); + + if (placement == null) { + getComponent().getLocations().remove(loc); + Ecdar.showToast("Please select an empty space for the new location"); + return; + } + + newLocationPresentation.setLayoutX(placement.getX()); + newLocationPresentation.setLayoutY(placement.getY()); + + locationPresentationMap.put(loc, newLocationPresentation); + modelContainerLocation.getChildren().add(newLocationPresentation); + + // Bind the newly created location to the mouse and tell the ui that it is not placed yet + if (loc.getX() == 0) { + newLocationPresentation.setPlaced(false); + BindingHelper.bind(loc, getComponent().getBox().getXProperty(), getComponent().getBox().getYProperty()); + } + } + + /*** + * Updates the component's height to match the input/output signature containers + * if the component is smaller than either of them + */ + private void updateMaxHeight() { + // If input/outputsignature container is taller than the current component height + // we update the component's height to be as tall as the container + double maxHeight = getMaxHeight(); + if (maxHeight > getComponent().getBox().getHeight()) { + getComponent().getBox().getHeightProperty().set(maxHeight); + } + } + + /*** + * Finds the max height of the input/output signature container and the component + * @return a double of the largest height + */ + private double getMaxHeight() { + double inputHeight = inputSignatureContainer.getHeight(); + double outputHeight = outputSignatureContainer.getHeight(); + double componentHeight = getComponent().getBox().getHeight(); + + double maxSignatureHeight = Math.max(outputHeight, inputHeight); + + return Math.max(maxSignatureHeight, componentHeight); + } + + /*** + * Toggle the declaration of the component with a ripple effect originating from the MouseEvent + * @param mouseEvent to use for the origin of the ripple effect + */ + private void toggleDeclaration(final MouseEvent mouseEvent) { final Circle circle = new Circle(0); - circle.setCenterX(component.get().getBox().getWidth() - (toggleDeclarationButton.getWidth() - mouseEvent.getX())); + circle.setCenterX(getComponent().getBox().getWidth() - (toggleDeclarationButton.getWidth() - mouseEvent.getX())); circle.setCenterY(-1 * mouseEvent.getY()); final ObjectProperty<Node> clip = new SimpleObjectProperty<>(circle); @@ -855,6 +730,20 @@ protected void interpolate(final double fraction) { getComponent().declarationOpenProperty().set(!getComponent().isDeclarationOpen()); } + /*** + * Mark the component as selected in the view + */ + public void componentSelected() { + updateColorDelegates.forEach(colorConsumer -> colorConsumer.accept(new EnabledColor(SelectHelper.SELECT_COLOR, SelectHelper.SELECT_COLOR_INTENSITY_NORMAL))); + } + + /*** + * Mark the component as not selected in the view + */ + public void componentDeselected() { + updateColorDelegates.forEach(colorConsumer -> colorConsumer.accept(getComponent().getColor())); + } + public Component getComponent() { return component.get(); } @@ -867,19 +756,56 @@ public ObjectProperty<Component> componentProperty() { return component; } + /** + * Moves all nodes left. + */ + public void moveAllNodesLeft() { + getComponent().getLocations().forEach(loc -> loc.setX(loc.getX() + NudgeDirection.LEFT.getXOffset())); + getComponent().getDisplayableEdges().forEach(edge -> edge.getNails().forEach(nail -> nail.setX(nail.getX() + NudgeDirection.LEFT.getXOffset()))); + } + + /** + * Moves all nodes right. + */ + public void moveAllNodesRight() { + getComponent().getLocations().forEach(loc -> loc.setX(loc.getX() + NudgeDirection.RIGHT.getXOffset())); + getComponent().getDisplayableEdges().forEach(edge -> edge.getNails().forEach(nail -> nail.setX(nail.getX() + NudgeDirection.RIGHT.getXOffset()))); + } + + /** + * Moves all nodes down. + */ + public void moveAllNodesDown() { + getComponent().getLocations().forEach(loc -> loc.setY(loc.getY() + NudgeDirection.DOWN.getYOffset())); + getComponent().getDisplayableEdges().forEach(edge -> edge.getNails().forEach(nail -> nail.setY(nail.getY() + NudgeDirection.DOWN.getYOffset()))); + } + + /** + * Moves all nodes up. + */ + public void moveAllNodesUp() { + getComponent().getLocations().forEach(loc -> loc.setY(loc.getY() + NudgeDirection.UP.getYOffset())); + getComponent().getDisplayableEdges().forEach(edge -> edge.getNails().forEach(nail -> nail.setY(nail.getY() + NudgeDirection.UP.getYOffset()))); + } + + /*** + * Handle the component being pressed based on the mouse button and hotkeys + * @param event to use for handling the action + */ @FXML private void modelContainerPressed(final MouseEvent event) { - EcdarController.getActiveCanvasPresentation().getController().leaveTextAreas(); + Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasPresentation().getController().leaveTextAreas(); final DisplayableEdge unfinishedEdge = getComponent().getUnfinishedEdge(); + dropdownCoordinatesInComponent = new Point2D(event.getX(), event.getY()); + if ((event.isShiftDown() && event.isPrimaryButtonDown()) || event.isMiddleButtonDown()) { final Location location = new Location(); - location.initialize(); + location.initialize(getComponent().getUniqueLocationId()); location.setX(event.getX()); location.setY(event.getY()); - location.setColorIntensity(getComponent().getColorIntensity()); location.setColor(getComponent().getColor()); if (unfinishedEdge != null) { @@ -918,10 +844,10 @@ private void modelContainerPressed(final MouseEvent event) { }); } else if (event.isSecondaryButtonDown()) { if (unfinishedEdge == null) { - contextMenu.show(JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.LEFT, event.getX() * EcdarController.getActiveCanvasZoomFactor().get(), event.getY() * EcdarController.getActiveCanvasZoomFactor().get()); + contextMenu.show(JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.LEFT, event.getX(), event.getY()); } else { initializeFinishEdgeContextMenu(unfinishedEdge); - finishEdgeContextMenu.show(JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.LEFT, event.getX() * EcdarController.getActiveCanvasZoomFactor().get(), event.getY() * EcdarController.getActiveCanvasZoomFactor().get()); + finishEdgeContextMenu.show(JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.LEFT, event.getX(), event.getY()); } } else if (event.isPrimaryButtonDown()) { // We are drawing an edge @@ -950,12 +876,58 @@ private void modelContainerDragged() { contextMenu.hide(); } - public MouseTracker getMouseTracker() { - return mouseTracker; + @Override + public HighLevelModel getModel() { + return getComponent(); } + /** + * Gets the minimum possible height when dragging the anchor. + * The height is based on the y coordinate of locations, nails and the signature arrows + * + * @return the minimum possible height. + */ @Override - public HighLevelModelObject getModel() { - return getComponent(); + double getDragAnchorMinWidth() { + double minWidth = 10 * Ecdar.CANVAS_PADDING; + + for (final Location location : getComponent().getLocations()) { + minWidth = Math.max(minWidth, location.getX() + Ecdar.CANVAS_PADDING * 2); + } + + for (final Edge edge : getComponent().getEdges()) { + for (final Nail nail : edge.getNails()) { + minWidth = Math.max(minWidth, nail.getX() + Ecdar.CANVAS_PADDING); + } + } + + return minWidth; + } + + /** + * Gets the minimum possible height when dragging the anchor. + * The height is based on the y coordinate of locations, nails and the signature arrows + * + * @return the minimum possible height. + */ + @Override + double getDragAnchorMinHeight() { + double minHeight = 10 * Ecdar.CANVAS_PADDING; + + for (final Location location : getComponent().getLocations()) { + minHeight = Math.max(minHeight, location.getY() + Ecdar.CANVAS_PADDING * 2); + } + + for (final Edge edge : getComponent().getEdges()) { + for (final Nail nail : edge.getNails()) { + minHeight = Math.max(minHeight, nail.getY() + Ecdar.CANVAS_PADDING); + } + } + + //Component should not get smaller than the height of the input/output signature containers + minHeight = Math.max(inputSignatureContainer.getHeight(), minHeight); + minHeight = Math.max(outputSignatureContainer.getHeight(), minHeight); + + return minHeight; } } diff --git a/src/main/java/ecdar/controllers/ComponentInstanceController.java b/src/main/java/ecdar/controllers/ComponentInstanceController.java index a706eb95..547ffd78 100644 --- a/src/main/java/ecdar/controllers/ComponentInstanceController.java +++ b/src/main/java/ecdar/controllers/ComponentInstanceController.java @@ -96,7 +96,7 @@ private void initializeDropDownMenu(final EcdarSystem system) { * Listens to an edge to update whether the root has an edge. * @param edge the edge to update with */ - private void handleHasEdge(final EcdarSystemEdge edge) { + private void handleHasEdge(final SystemEdge edge) { edge.getTempNodeProperty().addListener((observable -> updateHasEdge(edge))); edge.getChildProperty().addListener((observable -> updateHasEdge(edge))); edge.getParentProperty().addListener((observable -> updateHasEdge(edge))); @@ -106,7 +106,7 @@ private void handleHasEdge(final EcdarSystemEdge edge) { * Update has edge property to whether the instance is in a given edge. * @param edge the given edge */ - private void updateHasEdge(final EcdarSystemEdge edge) { + private void updateHasEdge(final SystemEdge edge) { hasEdge.set(edge.isInEdge(getInstance())); } @@ -114,7 +114,7 @@ private void updateHasEdge(final EcdarSystemEdge edge) { private void onMouseClicked(final MouseEvent event) { event.consume(); - final EcdarSystemEdge unfinishedEdge = getSystem().getUnfinishedEdge(); + final SystemEdge unfinishedEdge = getSystem().getUnfinishedEdge(); if ((event.isShiftDown() && event.getButton().equals(MouseButton.PRIMARY)) || event.getButton().equals(MouseButton.MIDDLE)) { // If shift click or middle click a component instance, create a new edge @@ -147,11 +147,11 @@ private void onMouseClicked(final MouseEvent event) { } /*** - * Helper method to create a new EcdarSystemEdge and add it to the current system and component instance - * @return The newly created EcdarSystemEdge + * Helper method to create a new SystemEdge and add it to the current system and component instance + * @return The newly created SystemEdge */ - private EcdarSystemEdge createNewSystemEdge() { - final EcdarSystemEdge edge = new EcdarSystemEdge(getInstance()); + private SystemEdge createNewSystemEdge() { + final SystemEdge edge = new SystemEdge(getInstance()); getSystem().addEdge(edge); hasEdge.set(true); diff --git a/src/main/java/ecdar/controllers/ComponentOperatorController.java b/src/main/java/ecdar/controllers/ComponentOperatorController.java index 7da44f1c..f0abc60f 100644 --- a/src/main/java/ecdar/controllers/ComponentOperatorController.java +++ b/src/main/java/ecdar/controllers/ComponentOperatorController.java @@ -1,7 +1,7 @@ package ecdar.controllers; import ecdar.abstractions.ComponentOperator; import ecdar.abstractions.EcdarSystem; -import ecdar.abstractions.EcdarSystemEdge; +import ecdar.abstractions.SystemEdge; import ecdar.presentations.DropDownMenu; import ecdar.presentations.MenuElement; import com.jfoenix.controls.JFXPopup; @@ -83,7 +83,7 @@ private void showContextMenu(MouseEvent mouseEvent) { contextMenu.addMenuElement(new MenuElement("Draw Edge") .setClickable(() -> { - final EcdarSystemEdge edge = new EcdarSystemEdge(operator); + final SystemEdge edge = new SystemEdge(operator); getSystem().addEdge(edge); contextMenu.hide(); @@ -104,7 +104,7 @@ private void showContextMenu(MouseEvent mouseEvent) { * Listens to an edge to update whether the operator has a parent edge. * @param parentEdge the edge to update with */ - private void updateHasParent(final EcdarSystemEdge parentEdge) { + private void updateHasParent(final SystemEdge parentEdge) { // The operator has a parent iff the supposed parent edge has the operator as a child parentEdge.getChildProperty().addListener(((observable, oldValue, newValue) -> hasParent.set(getOperator().equals(newValue)))); } @@ -117,7 +117,7 @@ private void updateHasParent(final EcdarSystemEdge parentEdge) { private void onMouseClicked(final MouseEvent event) { event.consume(); - final EcdarSystemEdge unfinishedEdge = getSystem().getUnfinishedEdge(); + final SystemEdge unfinishedEdge = getSystem().getUnfinishedEdge(); // if primary clicked and there is an unfinished edge, finish it with the system root as target if (unfinishedEdge != null && event.getButton().equals(MouseButton.PRIMARY)) { diff --git a/src/main/java/ecdar/controllers/DeclarationsController.java b/src/main/java/ecdar/controllers/DeclarationsController.java index b4ab05e0..ee474f86 100644 --- a/src/main/java/ecdar/controllers/DeclarationsController.java +++ b/src/main/java/ecdar/controllers/DeclarationsController.java @@ -1,8 +1,8 @@ package ecdar.controllers; -import ecdar.Ecdar; import ecdar.abstractions.Declarations; -import ecdar.presentations.ComponentPresentation; +import ecdar.abstractions.HighLevelModel; +import ecdar.utility.helpers.UPPAALSyntaxHighlighter; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.fxml.Initializable; @@ -17,10 +17,10 @@ /** * Controller for overall declarations. */ -public class DeclarationsController implements Initializable { - public StyleClassedTextArea textArea; +public class DeclarationsController extends HighLevelModelController implements Initializable { public StackPane root; public BorderPane frame; + public StyleClassedTextArea textArea; private final ObjectProperty<Declarations> declarations; @@ -34,21 +34,9 @@ public void setDeclarations(final Declarations declarations) { @Override public void initialize(final URL location, final ResourceBundle resources) { - initializeWidthAndHeight(); initializeText(); } - /** - * Initializes width and height of the text editor field, such that it fills up the whole canvas - */ - private void initializeWidthAndHeight() { - // Fetch width and height of canvas and update - root.minWidthProperty().bind(Ecdar.getPresentation().getController().getEditorPresentation().getController().canvasPane.minWidthProperty()); - root.maxWidthProperty().bind(Ecdar.getPresentation().getController().getEditorPresentation().getController().canvasPane.maxWidthProperty()); - root.minHeightProperty().bind(Ecdar.getPresentation().getController().getEditorPresentation().getController().canvasPane.minHeightProperty()); - root.maxHeightProperty().bind(Ecdar.getPresentation().getController().getEditorPresentation().getController().canvasPane.maxHeightProperty()); - } - /** * Sets up the linenumbers and binds the text in the text area to the declaration object */ @@ -71,6 +59,11 @@ private void initializeText() { * Updates highlighting of the text in the text area. */ public void updateHighlighting() { - textArea.setStyleSpans(0, ComponentPresentation.computeHighlighting(declarations.get().getDeclarationsText())); + textArea.setStyleSpans(0, UPPAALSyntaxHighlighter.computeHighlighting(declarations.get().getDeclarationsText())); + } + + @Override + public HighLevelModel getModel() { + return declarations.get(); } } diff --git a/src/main/java/ecdar/controllers/EcdarController.java b/src/main/java/ecdar/controllers/EcdarController.java index 76f49469..0a135025 100644 --- a/src/main/java/ecdar/controllers/EcdarController.java +++ b/src/main/java/ecdar/controllers/EcdarController.java @@ -6,9 +6,9 @@ import ecdar.Ecdar; import ecdar.abstractions.*; import ecdar.backend.BackendHelper; -import ecdar.backend.BackendInstance; -import ecdar.backend.SimulationHandler; +import ecdar.backend.Engine; import ecdar.code_analysis.CodeAnalysis; +import ecdar.mutation.MutationTestPlanPresentation; import ecdar.mutation.models.MutationTestPlan; import ecdar.presentations.*; import ecdar.utility.UndoRedoStack; @@ -48,7 +48,6 @@ import java.util.stream.Collectors; public class EcdarController implements Initializable { - private SimulationHandler simulationHandler; // View stuff public StackPane root; public BorderPane borderPane; @@ -59,8 +58,6 @@ public class EcdarController implements Initializable { public StackPane modellingHelpDialogContainer; public JFXDialog modellingHelpDialog; public StackPane modalBar; - public JFXTextField queryTextField; - public JFXTextField commentTextField; public ImageView helpInitialImage; public StackPane helpInitialPane; @@ -109,7 +106,7 @@ public class EcdarController implements Initializable { public MenuItem menuBarFileExportAsPngNoBorder; public MenuItem menuBarOptionsCache; public MenuItem menuBarOptionsBackgroundQueries; - public MenuItem menuBarOptionsBackendOptions; + public MenuItem menuBarOptionsEngineOptions; public MenuItem menuBarHelpHelp; public MenuItem menuBarHelpAbout; public MenuItem menuBarHelpTest; @@ -126,8 +123,8 @@ public class EcdarController implements Initializable { public Text queryTextResult; public Text queryTextQuery; - public StackPane backendOptionsDialogContainer; - public BackendOptionsDialogPresentation backendOptionsDialog; + public StackPane engineOptionsDialogContainer; + public EngineOptionsDialogPresentation engineOptionsDialog; public StackPane simulationInitializationDialogContainer; public SimulationInitializationDialogPresentation simulationInitializationDialog; @@ -153,12 +150,7 @@ public enum Mode { public static final ObjectProperty<Mode> currentMode = new SimpleObjectProperty<>(Mode.Editor); private static final EditorPresentation editorPresentation = new EditorPresentation(); - public final ProjectPanePresentation projectPane = new ProjectPanePresentation(); - public final QueryPanePresentation queryPane = new QueryPanePresentation(); - private static final SimulatorPresentation simulatorPresentation = new SimulatorPresentation(); - public final LeftSimPanePresentation leftSimPane = new LeftSimPanePresentation(); - public final RightSimPanePresentation rightSimPane = new RightSimPanePresentation(); @Override public void initialize(final URL location, final ResourceBundle resources) { @@ -167,18 +159,7 @@ public void initialize(final URL location, final ResourceBundle resources) { initializeStatusBar(); initializeMenuBar(); - // Update file coloring when active model changes - editorPresentation.getController().getActiveCanvasPresentation().getController().activeComponentProperty().addListener(observable -> projectPane.getController().updateColorsOnFilePresentations()); - editorPresentation.getController().activeCanvasPresentationProperty().addListener(observable -> projectPane.getController().updateColorsOnFilePresentations()); - - leftSimPane.minWidthProperty().bind(projectPane.minWidthProperty()); - leftSimPane.maxWidthProperty().bind(projectPane.maxWidthProperty()); - rightSimPane.minWidthProperty().bind(queryPane.minWidthProperty()); - rightSimPane.maxWidthProperty().bind(queryPane.maxWidthProperty()); - enterEditorMode(); - - simulationHandler = Ecdar.getSimulationHandler(); } public StackPane getCenter() { @@ -201,22 +182,20 @@ public SimulatorPresentation getSimulatorPresentation() { return simulatorPresentation; } - public static CanvasPresentation getActiveCanvasPresentation() { - return editorPresentation.getController().getActiveCanvasPresentation(); - } - - public static DoubleProperty getActiveCanvasZoomFactor() { - return getActiveCanvasPresentation().getController().zoomHelper.currentZoomFactor; - } - - public static void setActiveCanvasPresentation(CanvasPresentation newActiveCanvasPresentation) { - getActiveCanvasPresentation().setOpacity(0.75); - newActiveCanvasPresentation.setOpacity(1); - editorPresentation.getController().setActiveCanvasPresentation(newActiveCanvasPresentation); + public StackPane getLeftModePane() { + if (currentMode.get().equals(Mode.Editor)) { + return editorPresentation.getController().getLeftPane(); + } else { + return simulatorPresentation.getController().getLeftPane(); + } } - public static void setActiveModelForActiveCanvas(HighLevelModelObject newActiveModel) { - getActiveCanvasPresentation().getController().setActiveModel(newActiveModel); + public StackPane getRightModePane() { + if (currentMode.get().equals(Mode.Editor)) { + return editorPresentation.getController().getRightPane(); + } else { + return simulatorPresentation.getController().getRightPane(); + } } public static void setTemporaryComponentWatermarkVisibility(boolean visibility) { @@ -265,7 +244,29 @@ private void initializeDialogs() { _queryTextQuery = queryTextQuery; initializeDialog(queryDialog, queryDialogContainer); - initializeBackendOptionsDialog(); + initializeDialog(engineOptionsDialog, engineOptionsDialogContainer); + + engineOptionsDialog.getController().resetEnginesButton.setOnMouseClicked(event -> { + engineOptionsDialog.getController().resetEnginesToDefault(); + }); + + engineOptionsDialog.getController().closeButton.setOnMouseClicked(event -> { + engineOptionsDialog.getController().cancelEngineOptionsChanges(); + engineOptionsDialog.close(); + }); + + engineOptionsDialog.getController().saveButton.setOnMouseClicked(event -> { + if (engineOptionsDialog.getController().saveChangesToEngineOptions()) { + engineOptionsDialog.close(); + } + }); + + if (BackendHelper.getEngines().size() < 1) { + Ecdar.showToast("No engines were found. Download j-Ecdar or Reveaal, or add another engine to fix this. No queries can be executed without engines."); + } else { + Engine defaultBackend = BackendHelper.getEngines().stream().filter(Engine::isDefault).findFirst().orElse(BackendHelper.getEngines().get(0)); + BackendHelper.setDefaultEngine(defaultBackend); + } initializeDialog(simulationInitializationDialog, simulationInitializationDialogContainer); @@ -277,37 +278,12 @@ private void initializeDialogs() { simulationInitializationDialog.getController().startButton.setOnMouseClicked(event -> { // ToDo NIELS: Start simulation of selected query - simulationHandler.setComposition(simulationInitializationDialog.getController().simulationComboBox.getSelectionModel().getSelectedItem()); + SimulatorController.simulationHandler.setComposition(simulationInitializationDialog.getController().simulationComboBox.getSelectionModel().getSelectedItem()); currentMode.setValue(Mode.Simulator); simulationInitializationDialog.close(); }); } - private void initializeBackendOptionsDialog() { - initializeDialog(backendOptionsDialog, backendOptionsDialogContainer); - backendOptionsDialog.getController().resetBackendsButton.setOnMouseClicked(event -> { - backendOptionsDialog.getController().resetBackendsToDefault(); - }); - - backendOptionsDialog.getController().closeButton.setOnMouseClicked(event -> { - backendOptionsDialog.getController().cancelBackendOptionsChanges(); - backendOptionsDialog.close(); - }); - - backendOptionsDialog.getController().saveButton.setOnMouseClicked(event -> { - if (backendOptionsDialog.getController().saveChangesToBackendOptions()) { - backendOptionsDialog.close(); - } - }); - - if (BackendHelper.getBackendInstances().size() < 1) { - Ecdar.showToast("No engines were found. Download j-Ecdar or Reveaal, or add another engine to fix this. No queries can be executed without engines."); - } else { - BackendInstance defaultBackend = BackendHelper.getBackendInstances().stream().filter(BackendInstance::isDefault).findFirst().orElse(BackendHelper.getBackendInstances().get(0)); - BackendHelper.setDefaultBackendInstance(defaultBackend); - } - } - private void initializeDialog(JFXDialog dialog, StackPane dialogContainer) { dialog.setDialogContainer(dialogContainer); dialogContainer.opacityProperty().bind(dialog.getChildren().get(0).scaleXProperty()); @@ -321,9 +297,6 @@ private void initializeDialog(JFXDialog dialog, StackPane dialogContainer) { dialogContainer.setMouseTransparent(false); }); - projectPane.getStyleClass().add("responsive-pane-sizing"); - queryPane.getStyleClass().add("responsive-pane-sizing"); - initializeKeybindings(); initializeStatusBar(); } @@ -349,19 +322,6 @@ private void intitializeTemporaryComponentWatermark() { * - Colors */ private void initializeKeybindings() { - //Press ctrl+N or cmd+N to create a new component. The canvas changes to this new component - KeyCodeCombination combination = new KeyCodeCombination(KeyCode.N, KeyCombination.SHORTCUT_DOWN); - Keybind binding = new Keybind(combination, (event) -> { - final Component newComponent = new Component(true); - UndoRedoStack.pushAndPerform(() -> { // Perform - Ecdar.getProject().getComponents().add(newComponent); - setActiveModelForActiveCanvas(newComponent); - }, () -> { // Undo - Ecdar.getProject().getComponents().remove(newComponent); - }, "Created new component: " + newComponent.getName(), "add-circle"); - }); - KeyboardTracker.registerKeybind(KeyboardTracker.CREATE_COMPONENT, binding); - // Keybind for nudging the selected elements KeyboardTracker.registerKeybind(KeyboardTracker.NUDGE_UP, new Keybind(new KeyCodeCombination(KeyCode.UP), (event) -> { event.consume(); @@ -469,10 +429,10 @@ private void initializeOptionsMenu() { menuBarOptionsCache.getGraphic().opacityProperty().bind(new When(isCached).then(1).otherwise(0)); }); - menuBarOptionsBackendOptions.setOnAction(event -> { - backendOptionsDialogContainer.setVisible(true); - backendOptionsDialog.show(backendOptionsDialogContainer); - backendOptionsDialog.setMouseTransparent(false); + menuBarOptionsEngineOptions.setOnAction(event -> { + engineOptionsDialogContainer.setVisible(true); + engineOptionsDialog.show(engineOptionsDialogContainer); + engineOptionsDialog.setMouseTransparent(false); }); menuBarOptionsBackgroundQueries.setOnAction(event -> { @@ -487,33 +447,33 @@ private void initializeOptionsMenu() { private void initializeEditMenu() { menuEditMoveLeft.setAccelerator(new KeyCodeCombination(KeyCode.LEFT, KeyCombination.CONTROL_DOWN)); menuEditMoveLeft.setOnAction(event -> { - final HighLevelModelObject activeModel = getActiveCanvasPresentation().getController().getActiveModel(); - - if (activeModel instanceof Component) ((Component) activeModel).moveAllNodesLeft(); + final HighLevelModelController activeModelController = editorPresentation.getController().getActiveCanvasPresentation().getController().getActiveModelPresentation().getController(); + if (activeModelController instanceof ComponentController) + ((ComponentController) activeModelController).moveAllNodesLeft(); else Ecdar.showToast("This can only be performed on components."); }); menuEditMoveRight.setAccelerator(new KeyCodeCombination(KeyCode.RIGHT, KeyCombination.CONTROL_DOWN)); menuEditMoveRight.setOnAction(event -> { - final HighLevelModelObject activeModel = getActiveCanvasPresentation().getController().getActiveModel(); - - if (activeModel instanceof Component) ((Component) activeModel).moveAllNodesRight(); + final HighLevelModelController activeModelController = editorPresentation.getController().getActiveCanvasPresentation().getController().getActiveModelPresentation().getController(); + if (activeModelController instanceof ComponentController) + ((ComponentController) activeModelController).moveAllNodesRight(); else Ecdar.showToast("This can only be performed on components."); }); menuEditMoveUp.setAccelerator(new KeyCodeCombination(KeyCode.UP, KeyCombination.CONTROL_DOWN)); menuEditMoveUp.setOnAction(event -> { - final HighLevelModelObject activeModel = getActiveCanvasPresentation().getController().getActiveModel(); - - if (activeModel instanceof Component) ((Component) activeModel).moveAllNodesUp(); + final HighLevelModelController activeModelController = editorPresentation.getController().getActiveCanvasPresentation().getController().getActiveModelPresentation().getController(); + if (activeModelController instanceof ComponentController) + ((ComponentController) activeModelController).moveAllNodesUp(); else Ecdar.showToast("This can only be performed on components."); }); menuEditMoveDown.setAccelerator(new KeyCodeCombination(KeyCode.DOWN, KeyCombination.CONTROL_DOWN)); menuEditMoveDown.setOnAction(event -> { - final HighLevelModelObject activeModel = getActiveCanvasPresentation().getController().getActiveModel(); - - if (activeModel instanceof Component) ((Component) activeModel).moveAllNodesDown(); + final HighLevelModelController activeModelController = editorPresentation.getController().getActiveCanvasPresentation().getController().getActiveModelPresentation().getController(); + if (activeModelController instanceof ComponentController) + ((ComponentController) activeModelController).moveAllNodesDown(); else Ecdar.showToast("This can only be performed on components."); }); } @@ -550,13 +510,14 @@ private void initializeViewMenu() { menuBarViewCanvasSplit.getGraphic().setOpacity(1); menuBarViewCanvasSplit.setOnAction(event -> { - final BooleanProperty isSplit = Ecdar.toggleCanvasSplit(); - if (isSplit.get()) { - Platform.runLater(() -> editorPresentation.getController().setCanvasModeToSingular()); - menuBarViewCanvasSplit.setText("Split canvas"); + Ecdar.toggleCanvasSplit(); + }); + + Ecdar.isSplitProperty().addListener((observable, oldValue, newValue) -> { + if (newValue) { + Platform.runLater(editorPresentation.getController()::setCanvasModeToSplit); } else { - Platform.runLater(() -> editorPresentation.getController().setCanvasModeToSplit()); - menuBarViewCanvasSplit.setText("Merge canvases"); + Platform.runLater(editorPresentation.getController()::setCanvasModeToSingular); } }); @@ -568,7 +529,7 @@ private void initializeViewMenu() { return; } - if (!simulationHandler.isSimulationRunning()) { + if (!SimulatorController.getSimulationHandler().isSimulationRunning()) { ArrayList<String> queryOptions = Ecdar.getProject().getQueries().stream().map(Query::getQuery).collect(Collectors.toCollection(ArrayList::new)); if (!simulationInitializationDialog.getController().simulationComboBox.getItems().equals(queryOptions)) { simulationInitializationDialog.getController().simulationComboBox.getItems().setAll(queryOptions); @@ -623,7 +584,7 @@ private void updateScaling(double newScale) { // Text do not scale on the canvas to avoid ugly elements, // this zooms in on the component in order to get the "same font size" - EcdarController.getActiveCanvasPresentation().getController().zoomHelper.setZoomLevel(newCalculatedScale / 13); + editorPresentation.getController().getActiveCanvasPresentation().getController().zoomHelper.setZoomLevel(newCalculatedScale / 13); Ecdar.preferences.put("scale", String.valueOf(newScale)); scaleIcons(root, newCalculatedScale); @@ -655,8 +616,7 @@ private void initializeOpenProjectMenuItem() { final File file = projectPicker.showDialog(root.getScene().getWindow()); if (file != null) { try { - Ecdar.projectDirectory.set(file.getAbsolutePath()); - Ecdar.initializeProjectFolder(); + setProjectDirectory(file.getAbsolutePath()); UndoRedoStack.clear(); addProjectToRecentProjects(file.getAbsolutePath()); } catch (final IOException e) { @@ -676,8 +636,7 @@ private void initializeRecentProjectsMenu() { item.setOnAction(event -> { try { - Ecdar.projectDirectory.set(path); - Ecdar.initializeProjectFolder(); + setProjectDirectory(path); } catch (IOException ex) { Ecdar.showToast("Unable to load project: \"" + path + "\""); } @@ -703,9 +662,15 @@ private void initializeRecentProjectsMenu() { menuBarFileRecentProjects.getItems().add(item); } + private static void setProjectDirectory(String path) throws IOException { + Ecdar.projectDirectory.set(path); + Ecdar.initializeProjectFolder(); + Ecdar.isSplitProperty().set(false); + } + /** * Saves the project to the {@link Ecdar#projectDirectory} path. - * This include making directories, converting project files (components and queries) + * This includes making directories, converting project files (components and queries) * into Json formatted files. */ public void save() { @@ -818,7 +783,7 @@ private void initializeNewMutationTestObjectMenuItem() { UndoRedoStack.pushAndPerform(() -> { // Perform Ecdar.getProject().getTestPlans().add(newPlan); - setActiveModelForActiveCanvas(newPlan); + editorPresentation.getController().setActiveModelPresentationForActiveCanvas(new MutationTestPlanPresentation(newPlan)); }, () -> { // Undo Ecdar.getProject().getTestPlans().remove(newPlan); }, "Created new mutation test plan", ""); @@ -828,15 +793,18 @@ private void initializeNewMutationTestObjectMenuItem() { /** * Creates a new project. */ - private static void createNewProject() { + private void createNewProject() { CodeAnalysis.disable(); CodeAnalysis.clearErrorsAndWarnings(); - Ecdar.projectDirectory.set(null); + try { + setProjectDirectory(null); + } catch (IOException ex) { + Ecdar.showToast("Unable to initialize new project directory"); + } - Ecdar.getProject().reset(); - setActiveModelForActiveCanvas(Ecdar.getProject().getComponents().get(0)); + editorPresentation.getController().setActiveModelPresentationForActiveCanvas(editorPresentation.getController().projectPane.getController().getComponentPresentations().get(0)); UndoRedoStack.clear(); @@ -850,12 +818,12 @@ private void initializeFileExportAsPng() { menuBarFileExportAsPng.setAccelerator(new KeyCodeCombination(KeyCode.L, KeyCombination.SHORTCUT_DOWN)); menuBarFileExportAsPng.setOnAction(event -> { // If there is no active component or system - if (!(getActiveCanvasPresentation().getController().getActiveModel() instanceof Component || getActiveCanvasPresentation().getController().getActiveModel() instanceof EcdarSystem)) { + if (!(editorPresentation.getController().getActiveCanvasPresentation().getController().getActiveModelPresentation() instanceof ComponentPresentation || editorPresentation.getController().getActiveCanvasPresentation().getController().getActiveModelPresentation() instanceof SystemPresentation)) { Ecdar.showToast("No component or system to export."); return; } - CanvasPresentation canvas = getActiveCanvasPresentation(); + CanvasPresentation canvas = editorPresentation.getController().getActiveCanvasPresentation(); // If there was an active component, hide button for toggling declarations final ComponentPresentation presentation = canvas.getController().getActiveComponentPresentation(); @@ -874,7 +842,7 @@ private void initializeFileExportAsPng() { }); menuBarFileExportAsPngNoBorder.setOnAction(event -> { - CanvasPresentation canvas = getActiveCanvasPresentation(); + CanvasPresentation canvas = editorPresentation.getController().getActiveCanvasPresentation(); final ComponentPresentation presentation = canvas.getController().getActiveComponentPresentation(); @@ -898,10 +866,7 @@ private void initializeFileExportAsPng() { */ private void enterEditorMode() { borderPane.setCenter(editorPresentation); - leftPane.getChildren().clear(); - leftPane.getChildren().add(projectPane); - rightPane.getChildren().clear(); - rightPane.getChildren().add(queryPane); + updateSidePanes(editorPresentation.getController()); } /** @@ -910,12 +875,17 @@ private void enterEditorMode() { */ private void enterSimulatorMode() { simulatorPresentation.getController().willShow(); - borderPane.setCenter(simulatorPresentation); + updateSidePanes(simulatorPresentation.getController()); + } + + private void updateSidePanes(ModeController modeController) { leftPane.getChildren().clear(); - leftPane.getChildren().add(leftSimPane); + leftPane.getChildren().add(modeController.getLeftPane()); rightPane.getChildren().clear(); - rightPane.getChildren().add(rightSimPane); + rightPane.getChildren().add(modeController.getRightPane()); + + } /** @@ -953,7 +923,7 @@ private WritableImage scaleAndTakeSnapshot(CanvasPresentation canvas) { * @param image the image */ private void CropAndExportImage(final WritableImage image) { - final String name = getActiveCanvasPresentation().getController().getActiveModel().getName(); + final String name = editorPresentation.getController().getActiveCanvasPresentation().getController().getActiveModelPresentation().getController().getModel().getName(); final FileChooser filePicker = new FileChooser(); filePicker.setTitle("Export png"); @@ -1075,7 +1045,6 @@ private static int getAutoCropRightX(final BufferedImage image) { throw new IllegalArgumentException("Image is all white"); } - private void nudgeSelected(final NudgeDirection direction) { final List<SelectHelper.ItemSelectable> selectedElements = SelectHelper.getSelectedElements(); diff --git a/src/main/java/ecdar/controllers/EdgeController.java b/src/main/java/ecdar/controllers/EdgeController.java index 42bde140..3c65cb0d 100644 --- a/src/main/java/ecdar/controllers/EdgeController.java +++ b/src/main/java/ecdar/controllers/EdgeController.java @@ -8,6 +8,7 @@ import ecdar.utility.Highlightable; import ecdar.utility.UndoRedoStack; import ecdar.utility.colors.Color; +import ecdar.utility.colors.EnabledColor; import ecdar.utility.helpers.*; import ecdar.utility.keyboard.Keybind; import ecdar.utility.keyboard.KeyboardTracker; @@ -442,11 +443,11 @@ public void onChanged(final Change<? extends Link> c) { dropDownMenu.hide(); }); - dropDownMenu.show(JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.LEFT, event.getX() * EcdarController.getActiveCanvasZoomFactor().get(), event.getY() * EcdarController.getActiveCanvasZoomFactor().get()); + dropDownMenu.show(JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.LEFT, event.getX() * Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasZoomFactor().get(), event.getY() * Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasZoomFactor().get()); } else if ((event.isShiftDown() && event.isPrimaryButtonDown()) || event.isMiddleButtonDown()) { - final double nailX = EcdarController.getActiveCanvasPresentation().mouseTracker.xProperty().subtract(getComponent().getBox().getXProperty()).doubleValue(); - final double nailY = EcdarController.getActiveCanvasPresentation().mouseTracker.yProperty().subtract(getComponent().getBox().getYProperty()).doubleValue(); + final double nailX = Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasPresentation().mouseTracker.xProperty().subtract(getComponent().getBox().getXProperty()).doubleValue(); + final double nailY = Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasPresentation().mouseTracker.yProperty().subtract(getComponent().getBox().getYProperty()).doubleValue(); final Nail newNail = new Nail(nailX, nailY); @@ -593,8 +594,8 @@ private void addEdgePropertyRow(final DropDownMenu dropDownMenu, final String ro } private Nail getNewNailBasedOnDropdownPosition() { - final double nailX = DropDownMenu.x / EcdarController.getActiveCanvasZoomFactor().get(); - final double nailY = DropDownMenu.y / EcdarController.getActiveCanvasZoomFactor().get(); + final double nailX = DropDownMenu.x / Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasZoomFactor().get(); + final double nailY = DropDownMenu.y / Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasZoomFactor().get(); return new Nail(nailX, nailY); } @@ -722,24 +723,18 @@ public void edgeDragged(final MouseEvent event) { } @Override - public void color(final Color color, final Color.Intensity intensity) { + public void color(final EnabledColor color) { final DisplayableEdge edge = getEdge(); // Set the color of the edge - edge.setColorIntensity(intensity); edge.setColor(color); } @Override - public Color getColor() { + public EnabledColor getColor() { return getEdge().getColor(); } - @Override - public Color.Intensity getColorIntensity() { - return getEdge().getColorIntensity(); - } - @Override public ItemDragHelper.DragBounds getDragBounds() { return ItemDragHelper.DragBounds.generateLooseDragBounds(); diff --git a/src/main/java/ecdar/controllers/EditorController.java b/src/main/java/ecdar/controllers/EditorController.java index 15557dea..cf12107d 100644 --- a/src/main/java/ecdar/controllers/EditorController.java +++ b/src/main/java/ecdar/controllers/EditorController.java @@ -7,8 +7,7 @@ import ecdar.abstractions.Component; import ecdar.abstractions.DisplayableEdge; import ecdar.abstractions.EdgeStatus; -import ecdar.abstractions.HighLevelModelObject; -import ecdar.presentations.CanvasPresentation; +import ecdar.presentations.*; import ecdar.utility.UndoRedoStack; import ecdar.utility.colors.Color; import ecdar.utility.colors.EnabledColor; @@ -16,7 +15,9 @@ import ecdar.utility.keyboard.Keybind; import ecdar.utility.keyboard.KeyboardTracker; import javafx.application.Platform; +import javafx.beans.property.DoubleProperty; import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleDoubleProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.collections.ObservableList; import javafx.fxml.FXML; @@ -30,10 +31,11 @@ import java.net.URL; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.ResourceBundle; -public class EditorController implements Initializable { +public class EditorController implements ModeController, Initializable { public VBox root; public HBox toolbar; public JFXRippler colorSelected; @@ -45,6 +47,9 @@ public class EditorController implements Initializable { public JFXToggleButton switchEdgeStatusButton; public StackPane canvasPane; + public final ProjectPanePresentation projectPane = new ProjectPanePresentation(); + public final QueryPanePresentation queryPane = new QueryPanePresentation(); + private final ObjectProperty<EdgeStatus> globalEdgeStatus = new SimpleObjectProperty<>(EdgeStatus.INPUT); private final ObjectProperty<CanvasPresentation> activeCanvasPresentation = new SimpleObjectProperty<>(new CanvasPresentation()); @@ -53,6 +58,35 @@ public void initialize(final URL location, final ResourceBundle resources) { initializeCanvasPane(); initializeEdgeStatusHandling(); initializeKeybindings(); + Platform.runLater(this::initializeSidePanes); + + // Update file coloring when active model changes + getActiveCanvasPresentation().getController().activeModelProperty().addListener((observable, oldModel, newModel) -> projectPane.getController().swapHighlightBetweenTwoModelFiles(oldModel, newModel)); + } + + private void initializeSidePanes() { + final DoubleProperty prevX = new SimpleDoubleProperty(); + final DoubleProperty prevWidth = new SimpleDoubleProperty(); + + queryPane.getController().resizeAnchor.setOnMousePressed(event -> { + event.consume(); + + prevX.set(event.getScreenX()); + prevWidth.set(queryPane.getWidth()); + }); + + queryPane.getController().resizeAnchor.setOnMouseDragged(event -> { + double diff = prevX.get() - event.getScreenX(); + + // Set bounds for resizing to be between 280px and half the screen width + final double newWidth = Math.min(Math.max(prevWidth.get() + diff, 280), Ecdar.getPresentation().getWidth() / 2); + + queryPane.setMinWidth(newWidth); + queryPane.setMaxWidth(newWidth); + }); + + projectPane.getStyleClass().add("responsive-pane-sizing"); + queryPane.getStyleClass().add("responsive-pane-sizing"); } public EdgeStatus getGlobalEdgeStatus() { @@ -73,11 +107,15 @@ public void setActiveCanvasPresentation(CanvasPresentation newActiveCanvasPresen activeCanvasPresentation.set(newActiveCanvasPresentation); } - public void setActiveModelForActiveCanvas(HighLevelModelObject newActiveModel) { - EcdarController.setActiveModelForActiveCanvas(newActiveModel); + public void setActiveModelPresentationForActiveCanvas(HighLevelModelPresentation newActiveModelPresentation) { + getActiveCanvasPresentation().getController().setActiveModelPresentation(newActiveModelPresentation); // Change zoom level to fit new active model - Platform.runLater(() -> EcdarController.getActiveCanvasPresentation().getController().zoomHelper.zoomToFit()); + Platform.runLater(() -> getActiveCanvasPresentation().getController().zoomHelper.zoomToFit()); + } + + public DoubleProperty getActiveCanvasZoomFactor() { + return getActiveCanvasPresentation().getController().zoomHelper.currentZoomFactor; } private void initializeCanvasPane() { @@ -92,7 +130,7 @@ private void initializeKeybindings() { final List<Pair<SelectHelper.ItemSelectable, EnabledColor>> previousColor = new ArrayList<>(); SelectHelper.getSelectedElements().forEach(selectable -> { - previousColor.add(new Pair<>(selectable, new EnabledColor(selectable.getColor(), selectable.getColorIntensity()))); + previousColor.add(new Pair<>(selectable, selectable.getColor())); }); changeColorOnSelectedElements(enabledColor, previousColor); SelectHelper.clearSelectedElements(); @@ -159,9 +197,9 @@ public void changeColorOnSelectedElements(final EnabledColor enabledColor, final List<Pair<SelectHelper.ItemSelectable, EnabledColor>> previousColor) { UndoRedoStack.pushAndPerform(() -> { // Perform SelectHelper.getSelectedElements() - .forEach(selectable -> selectable.color(enabledColor.color, enabledColor.intensity)); + .forEach(selectable -> selectable.color(enabledColor)); }, () -> { // Undo - previousColor.forEach(selectableEnabledColorPair -> selectableEnabledColorPair.getKey().color(selectableEnabledColorPair.getValue().color, selectableEnabledColorPair.getValue().intensity)); + previousColor.forEach(selectableEnabledColorPair -> selectableEnabledColorPair.getKey().color(selectableEnabledColorPair.getValue())); }, String.format("Changed the color of %d elements to %s", previousColor.size(), enabledColor.color.name()), "color-lens"); } @@ -171,28 +209,26 @@ public void changeColorOnSelectedElements(final EnabledColor enabledColor, void setCanvasModeToSingular() { canvasPane.getChildren().clear(); CanvasPresentation canvasPresentation = new CanvasPresentation(); - HighLevelModelObject model = activeCanvasPresentation.get().getController().getActiveModel(); - if (model != null) { - canvasPresentation.getController().setActiveModel(activeCanvasPresentation.get().getController().getActiveModel()); - } else { - // If no components where found, the project has not been initialized. The active model will be updated when the project is initialized - canvasPresentation.getController().setActiveModel(Ecdar.getProject().getComponents().stream().findFirst().orElse(null)); + if (activeCanvasPresentation.get().getController().getActiveModelPresentation() != null) { + canvasPresentation.getController().setActiveModelPresentation(activeCanvasPresentation.get().getController().getActiveModelPresentation()); } canvasPane.getChildren().add(canvasPresentation); - activeCanvasPresentation.set(canvasPresentation); + setActiveCanvasPresentation(canvasPresentation); Rectangle clip = new Rectangle(); clip.setArcWidth(1); clip.setArcHeight(1); clip.widthProperty().bind(canvasPane.widthProperty()); clip.heightProperty().bind(canvasPane.heightProperty()); - canvasPresentation.getController().zoomablePane.setClip(clip); + canvasPresentation.getController().zoomablePane.minWidthProperty().bind(canvasPane.widthProperty()); canvasPresentation.getController().zoomablePane.maxWidthProperty().bind(canvasPane.widthProperty()); canvasPresentation.getController().zoomablePane.minHeightProperty().bind(canvasPane.heightProperty()); canvasPresentation.getController().zoomablePane.maxHeightProperty().bind(canvasPane.heightProperty()); + + Platform.runLater(() -> projectPane.getController().setHighlightedForModelFiles(Collections.singletonList(getActiveCanvasPresentation().getController().getActiveModelPresentation()))); } /** @@ -220,12 +256,12 @@ void setCanvasModeToSplit() { canvasGrid.getRowConstraints().add(row1); canvasGrid.getRowConstraints().add(row1); - ObservableList<Component> components = Ecdar.getProject().getComponents(); + ObservableList<ComponentPresentation> components = projectPane.getController().getComponentPresentations(); int currentCompNum = 0, numComponents = components.size(); // Add the canvasPresentation at the top-left CanvasPresentation canvasPresentation = initializeNewCanvasPresentation(); - canvasPresentation.getController().setActiveModel(getActiveCanvasPresentation().getController().getActiveModel()); + canvasPresentation.getController().setActiveModelPresentation(getActiveCanvasPresentation().getController().getActiveModelPresentation()); canvasGrid.add(canvasPresentation, 0, 0); setActiveCanvasPresentation(canvasPresentation); @@ -236,7 +272,7 @@ void setCanvasModeToSplit() { // Update the startIndex for the next canvasPresentation for (int i = 0; i < numComponents; i++) { - if (canvasPresentation.getController().getActiveModel() != null && canvasPresentation.getController().getActiveModel().equals(components.get(i))) { + if (canvasPresentation.getController().getActiveModelPresentation() != null && canvasPresentation.getController().getActiveModelPresentation().getController().getModel().equals(components.get(i))) { currentCompNum = i + 1; } } @@ -248,7 +284,7 @@ void setCanvasModeToSplit() { // Update the startIndex for the next canvasPresentation for (int i = 0; i < numComponents; i++) - if (canvasPresentation.getController().getActiveModel() != null && canvasPresentation.getController().getActiveModel().equals(components.get(i))) { + if (canvasPresentation.getController().getActiveModelPresentation() != null && canvasPresentation.getController().getActiveModelPresentation().getController().getModel().equals(components.get(i))) { currentCompNum = i + 1; } @@ -263,18 +299,18 @@ void setCanvasModeToSplit() { /** * Initialize a new CanvasShellPresentation and set its active component to the next component encountered from the startIndex and return it * - * @param components the list of components for assigning active component of the CanvasPresentation + * @param componentPresentations the list of componentPresentations for assigning active component of the CanvasPresentation * @param startIndex the index to start at when trying to find the component to set as active * @return new CanvasShellPresentation */ - private CanvasPresentation initializeNewCanvasPresentationWithActiveComponent(ObservableList<Component> components, int startIndex) { + private CanvasPresentation initializeNewCanvasPresentationWithActiveComponent(ObservableList<ComponentPresentation> componentPresentations, int startIndex) { CanvasPresentation canvasPresentation = initializeNewCanvasPresentation(); - int numComponents = components.size(); - canvasPresentation.getController().setActiveModel(null); + int numComponents = componentPresentations.size(); + canvasPresentation.getController().setActiveModelPresentation(null); for (int currentCompNum = startIndex; currentCompNum < numComponents; currentCompNum++) { - if (getActiveCanvasPresentation().getController().getActiveModel() != components.get(currentCompNum)) { - canvasPresentation.getController().setActiveModel(components.get(currentCompNum)); + if (getActiveCanvasPresentation().getController().getActiveModelPresentation() != componentPresentations.get(currentCompNum)) { + canvasPresentation.getController().setActiveModelPresentation(componentPresentations.get(currentCompNum)); break; } } @@ -377,4 +413,14 @@ private void deleteSelectedClicked() { SelectHelper.clearSelectedElements(); } + + @Override + public StackPane getLeftPane() { + return projectPane; + } + + @Override + public StackPane getRightPane() { + return queryPane; + } } diff --git a/src/main/java/ecdar/controllers/BackendInstanceController.java b/src/main/java/ecdar/controllers/EngineInstanceController.java similarity index 52% rename from src/main/java/ecdar/controllers/BackendInstanceController.java rename to src/main/java/ecdar/controllers/EngineInstanceController.java index c4f5337b..8daf2668 100644 --- a/src/main/java/ecdar/controllers/BackendInstanceController.java +++ b/src/main/java/ecdar/controllers/EngineInstanceController.java @@ -3,7 +3,7 @@ import com.jfoenix.controls.JFXCheckBox; import com.jfoenix.controls.JFXRippler; import com.jfoenix.controls.JFXTextField; -import ecdar.backend.BackendInstance; +import ecdar.backend.Engine; import javafx.application.Platform; import javafx.beans.property.BooleanProperty; import javafx.fxml.FXML; @@ -22,22 +22,22 @@ import java.net.URL; import java.util.ResourceBundle; -public class BackendInstanceController implements Initializable { - private BackendInstance backendInstance = new BackendInstance(); +public class EngineInstanceController implements Initializable { + private Engine engine = new Engine(); /* Design elements */ - public Label backendNameIssue; - public JFXRippler removeBackendRippler; - public FontIcon removeBackendIcon; + public Label engineNameIssue; + public JFXRippler removeEngineRippler; + public FontIcon removeEngineIcon; public FontIcon expansionIcon; public StackPane content; public JFXCheckBox isLocal; public HBox addressSection; - public HBox pathToBackendSection; - public JFXRippler pickPathToBackend; - public FontIcon pickPathToBackendIcon; - public StackPane moveBackendInstanceUpRippler; - public StackPane moveBackendInstanceDownRippler; + public HBox pathToEngineSection; + public JFXRippler pickPathToEngine; + public FontIcon pickPathToEngineIcon; + public StackPane moveEngineInstanceUpRippler; + public StackPane moveEngineInstanceDownRippler; // Labels for showing potential issues public Label locationIssue; @@ -46,25 +46,24 @@ public class BackendInstanceController implements Initializable { public Label portRangeIssue; /* Input fields */ - public JFXTextField backendName; + public JFXTextField engineName; public JFXTextField address; - public JFXTextField pathToBackend; + public JFXTextField pathToEngine; public JFXTextField portRangeStart; public JFXTextField portRangeEnd; - public RadioButton defaultBackendRadioButton; - public JFXCheckBox threadSafeBackendCheckBox; + public RadioButton defaultEngineRadioButton; + public JFXCheckBox threadSafeEngineCheckBox; @Override public void initialize(URL location, ResourceBundle resources) { Platform.runLater(() -> { this.handleLocalPropertyChanged(); - moveBackendInstanceUpRippler.setCursor(Cursor.HAND); - moveBackendInstanceDownRippler.setCursor(Cursor.HAND); + moveEngineInstanceUpRippler.setCursor(Cursor.HAND); + moveEngineInstanceDownRippler.setCursor(Cursor.HAND); setHGrow(); - colorIconAsDisabledBasedOnProperty(removeBackendIcon, defaultBackendRadioButton.selectedProperty()); - colorIconAsDisabledBasedOnProperty(removeBackendIcon, threadSafeBackendCheckBox.selectedProperty()); - colorIconAsDisabledBasedOnProperty(pickPathToBackendIcon, backendInstance.getLockedProperty()); + colorIconAsDisabledBasedOnProperty(removeEngineIcon, defaultEngineRadioButton.selectedProperty()); + colorIconAsDisabledBasedOnProperty(pickPathToEngineIcon, engine.getLockedProperty()); }); } @@ -74,7 +73,7 @@ public void initialize(URL location, ResourceBundle resources) { * @param property The property to bind to */ private void colorIconAsDisabledBasedOnProperty(FontIcon icon, BooleanProperty property) { - // Disallow the user to pick new backend file location for locked backends + // Disallow the user to pick new engine file location for locked engines property.addListener((observable, oldValue, newValue) -> { if (newValue) { icon.setFill(Color.GREY); @@ -86,22 +85,22 @@ private void colorIconAsDisabledBasedOnProperty(FontIcon icon, BooleanProperty p } /*** - * Sets the backend instance and overrides the current values of the input fields in the GUI. - * @param instance the new BackendInstance + * Sets the engine instance and overrides the current values of the input fields in the GUI. + * @param instance the new Engine */ - public void setBackendInstance(BackendInstance instance) { - this.backendInstance = instance; + public void setEngine(Engine instance) { + this.engine = instance; - this.backendName.setText(instance.getName()); + this.engineName.setText(instance.getName()); this.isLocal.setSelected(instance.isLocal()); - this.defaultBackendRadioButton.setSelected(instance.isDefault()); - this.threadSafeBackendCheckBox.setSelected(instance.isThreadSafe()); + this.defaultEngineRadioButton.setSelected(instance.isDefault()); + this.threadSafeEngineCheckBox.setSelected(instance.isThreadSafe()); // Check if the path or the address should be used if (isLocal.isSelected()) { - this.pathToBackend.setText(instance.getBackendLocation()); + this.pathToEngine.setText(instance.getEngineLocation()); } else { - this.address.setText(instance.getBackendLocation()); + this.address.setText(instance.getIpAddress()); } this.portRangeStart.setText(String.valueOf(instance.getPortStart())); @@ -109,30 +108,30 @@ public void setBackendInstance(BackendInstance instance) { } /** - * Updates the values of the backend instance to the values from the input fields. - * @return The updated backend instance + * Updates the values of the engine instance to the values from the input fields. + * @return The updated engine instance */ - public BackendInstance updateBackendInstance() { - backendInstance.setName(backendName.getText()); - backendInstance.setLocal(isLocal.isSelected()); - backendInstance.setDefault(defaultBackendRadioButton.isSelected()); - backendInstance.setIsThreadSafe(threadSafeBackendCheckBox.isSelected()); - backendInstance.setBackendLocation(isLocal.isSelected() ? pathToBackend.getText() : address.getText()); - backendInstance.setPortStart(Integer.parseInt(portRangeStart.getText())); - backendInstance.setPortEnd(Integer.parseInt(portRangeEnd.getText())); - - return backendInstance; + public Engine updateEngineInstance() { + engine.setName(engineName.getText()); + engine.setLocal(isLocal.isSelected()); + engine.setDefault(defaultEngineRadioButton.isSelected()); + engine.setIsThreadSafe(threadSafeEngineCheckBox.isSelected()); + engine.setEngineLocation(isLocal.isSelected() ? pathToEngine.getText() : address.getText()); + engine.setPortStart(Integer.parseInt(portRangeStart.getText())); + engine.setPortEnd(Integer.parseInt(portRangeEnd.getText())); + + return engine; } private void setHGrow() { - HBox.setHgrow(backendName.getParent().getParent().getParent(), Priority.ALWAYS); - HBox.setHgrow(backendName.getParent(), Priority.ALWAYS); - HBox.setHgrow(backendName, Priority.ALWAYS); + HBox.setHgrow(engineName.getParent().getParent().getParent(), Priority.ALWAYS); + HBox.setHgrow(engineName.getParent(), Priority.ALWAYS); + HBox.setHgrow(engineName, Priority.ALWAYS); HBox.setHgrow(content, Priority.ALWAYS); HBox.setHgrow(addressSection, Priority.ALWAYS); HBox.setHgrow(address, Priority.ALWAYS); - HBox.setHgrow(pathToBackendSection, Priority.ALWAYS); - HBox.setHgrow(pathToBackend, Priority.ALWAYS); + HBox.setHgrow(pathToEngineSection, Priority.ALWAYS); + HBox.setHgrow(pathToEngine, Priority.ALWAYS); HBox.setHgrow(portRangeStart, Priority.ALWAYS); HBox.setHgrow(portRangeEnd, Priority.ALWAYS); } @@ -142,14 +141,14 @@ private void handleLocalPropertyChanged() { address.setDisable(true); addressSection.setVisible(false); addressSection.setManaged(false); - pathToBackendSection.setVisible(true); - pathToBackendSection.setManaged(true); + pathToEngineSection.setVisible(true); + pathToEngineSection.setManaged(true); } else { address.setDisable(false); addressSection.setVisible(true); addressSection.setManaged(true); - pathToBackendSection.setVisible(false); - pathToBackendSection.setManaged(false); + pathToEngineSection.setVisible(false); + pathToEngineSection.setManaged(false); } } @@ -172,23 +171,23 @@ private void expansionClicked() { } @FXML - private void openPathToBackendDialog() { + private void openPathToEngineDialog() { // Dialog title - final FileChooser backendPicker = new FileChooser(); - backendPicker.setTitle("Choose backend"); + final FileChooser enginePicker = new FileChooser(); + enginePicker.setTitle("Choose Engine"); // The initial location for the file choosing dialog - final File jarDir = new File(pathToBackend.getText()).getAbsoluteFile().getParentFile(); + final File jarDir = new File(pathToEngine.getText()).getAbsoluteFile().getParentFile(); // If the file does not exist, we must be running it from a development environment, use a default location if(jarDir.exists()) { - backendPicker.setInitialDirectory(jarDir); + enginePicker.setInitialDirectory(jarDir); } // Prompt the user to find a file (will halt the UI thread) - final File file = backendPicker.showOpenDialog(null); + final File file = enginePicker.showOpenDialog(null); if(file != null) { - pathToBackend.setText(file.getAbsolutePath()); + pathToEngine.setText(file.getAbsolutePath()); } } } diff --git a/src/main/java/ecdar/controllers/EngineOptionsDialogController.java b/src/main/java/ecdar/controllers/EngineOptionsDialogController.java new file mode 100644 index 00000000..5f078ad3 --- /dev/null +++ b/src/main/java/ecdar/controllers/EngineOptionsDialogController.java @@ -0,0 +1,494 @@ +package ecdar.controllers; + +import com.google.gson.JsonArray; +import com.google.gson.JsonParser; +import com.jfoenix.controls.JFXButton; +import com.jfoenix.controls.JFXRippler; +import ecdar.Ecdar; +import ecdar.backend.Engine; +import ecdar.backend.BackendException; +import ecdar.backend.BackendHelper; +import ecdar.presentations.EnginePresentation; +import javafx.fxml.Initializable; +import javafx.scene.Node; +import javafx.scene.control.ToggleGroup; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Priority; +import javafx.scene.layout.VBox; +import org.apache.commons.lang3.Range; +import org.apache.commons.lang3.SystemUtils; + +import java.io.File; +import java.io.IOException; +import java.net.InetAddress; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.UnknownHostException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.*; + +public class EngineOptionsDialogController implements Initializable { + public VBox engineInstanceList; + public JFXRippler addEngineButton; + public JFXButton closeButton; + public ToggleGroup defaultEngineToggleGroup = new ToggleGroup(); + public JFXButton saveButton; + public JFXButton resetEnginesButton; + + @Override + public void initialize(URL location, ResourceBundle resources) { + initializeEngineInstanceList(); + } + + /** + * Reverts any changes made to the engine options by reloading the options specified in the preference file, + * or to the default, if no engines are present in the preferences file. + */ + public void cancelEngineOptionsChanges() { + initializeEngineInstanceList(); + } + + /** + * Saves the changes made to the engine options to the preferences file and returns true + * if no errors where found in the engine instance definitions, otherwise false. + * + * @return whether the changes could be saved, + * meaning that no errors where found in the changes made to the engine options + */ + public boolean saveChangesToEngineOptions() { + if (this.engineInstanceListIsErrorFree()) { + ArrayList<Engine> engines = new ArrayList<>(); + for (Node engine : engineInstanceList.getChildren()) { + if (engine instanceof EnginePresentation) { + engines.add(((EnginePresentation) engine).getController().updateEngineInstance()); + } + } + + if (engines.size() < 1) { + Ecdar.showToast("Please add an engine instance or press: \"" + resetEnginesButton.getText() + "\""); + return false; + } + + // Close all engine connections to avoid dangling engine connections when port range is changed + try { + BackendHelper.clearEngineConnections(); + } catch (BackendException e) { + e.printStackTrace(); + } + + BackendHelper.updateEngineInstances(engines); + + JsonArray jsonArray = new JsonArray(); + for (Engine engine : engines) { + jsonArray.add(engine.serialize()); + } + + Ecdar.preferences.put("engines", jsonArray.toString()); + + Engine defaultEngine = engines.stream().filter(Engine::isDefault).findFirst().orElse(engines.get(0)); + BackendHelper.setDefaultEngine(defaultEngine); + + String defaultEngineName = (defaultEngine.getName()); + Ecdar.preferences.put("default_engine", defaultEngineName); + + return true; + } else { + return false; + } + } + + /** + * Resets the engines to the default engines present in the 'default_engines.json' file. + */ + public void resetEnginesToDefault() { + updateEnginesInGUI(getPackagedEngines()); + } + + private void initializeEngineInstanceList() { + ArrayList<Engine> engines; + + // Load engines from preferences or get default + var savedEngines = Ecdar.preferences.get("engines", null); + if (savedEngines != null) { + engines = getEnginesFromJsonArray( + JsonParser.parseString(savedEngines).getAsJsonArray()); + } else { + engines = getPackagedEngines(); + } + + // Style add engine button and handle click event + HBox.setHgrow(addEngineButton, Priority.ALWAYS); + addEngineButton.setMaxWidth(Double.MAX_VALUE); + addEngineButton.setOnMouseClicked((event) -> { + EnginePresentation newEnginePresentation = new EnginePresentation(); + addEnginePresentationToList(newEnginePresentation); + }); + + updateEnginesInGUI(engines); + } + + /** + * Clear the engine list and add the newly defined engines to it. + * + * @param engines The new list of engines + */ + private void updateEnginesInGUI(ArrayList<Engine> engines) { + engineInstanceList.getChildren().clear(); + + engines.forEach((engine) -> { + EnginePresentation newEnginePresentation = new EnginePresentation(engine); + + // Bind input fields that should not be changed for packaged engines to the locked property of the engine instance + newEnginePresentation.getController().engineName.disableProperty().bind(engine.getLockedProperty()); + newEnginePresentation.getController().pathToEngine.disableProperty().bind(engine.getLockedProperty()); + newEnginePresentation.getController().pickPathToEngine.disableProperty().bind(engine.getLockedProperty()); + newEnginePresentation.getController().isLocal.disableProperty().bind(engine.getLockedProperty()); + addEnginePresentationToList(newEnginePresentation); + }); + + BackendHelper.updateEngineInstances(engines); + } + + /** + * Instantiate enginesArray defined in the given JsonArray. + * + * @param enginesArray The JsonArray containing the enginesArray + * @return An ArrayList of the instantiated enginesArray + */ + private ArrayList<Engine> getEnginesFromJsonArray(JsonArray enginesArray) { + ArrayList<Engine> engines = new ArrayList<>(); + engineInstanceList.getChildren().clear(); + enginesArray.forEach((engine) -> { + Engine newEngine = new Engine(engine.getAsJsonObject()); + engines.add(newEngine); + }); + + return engines; + } + + /** + * Checks a set of paths to the packaged engines, j-Ecdar and Reveaal, and instantiates them + * if one of the related files exists. + * + * @return The packaged engines + */ + private ArrayList<Engine> getPackagedEngines() { + ArrayList<Engine> defaultEngines = new ArrayList<>(); + + // Add Reveaal engine + var reveaal = new Engine(); + reveaal.setName("Reveaal"); + reveaal.setLocal(true); + reveaal.setDefault(true); + reveaal.setPortStart(5040); + reveaal.setPortEnd(5042); + reveaal.lockInstance(); + reveaal.setIsThreadSafe(true); + + // Load correct Reveaal executable based on OS + List<String> potentialFilesForReveaal = new ArrayList<>(); + if (SystemUtils.IS_OS_WINDOWS) { + potentialFilesForReveaal.add("Reveaal.exe"); + } else { + potentialFilesForReveaal.add("Reveaal"); + } + if (setEnginePathIfFileExists(reveaal, potentialFilesForReveaal)) defaultEngines.add(reveaal); + + // Add jECDAR engine + var jEcdar = new Engine(); + jEcdar.setName("j-Ecdar"); + jEcdar.setLocal(true); + jEcdar.setDefault(false); + jEcdar.setPortStart(5042); + jEcdar.setPortEnd(5050); + jEcdar.lockInstance(); + jEcdar.setIsThreadSafe(false); + + // Load correct j-Ecdar executable based on OS + List<String> potentialFiledForJEcdar = new ArrayList<>(); + if (SystemUtils.IS_OS_WINDOWS) { + potentialFiledForJEcdar.add("j-Ecdar.bat"); + } else { + potentialFiledForJEcdar.add("j-Ecdar"); + } + + if (setEnginePathIfFileExists(jEcdar, potentialFiledForJEcdar)) defaultEngines.add(jEcdar); + + return defaultEngines; + } + + /** + * Sets the path to the engine if one of the potential files exists + * + * @param engine The engine to set the path for + * @param potentialFiles List of potential files to use for the engine + * @return True if one of the potentialFiles where found in path, false otherwise. + * This value also signals whether the engine engineLocation is set + */ + private boolean setEnginePathIfFileExists(Engine engine, List<String> potentialFiles) { + engine.setEngineLocation(""); + + try { + // Get directory containing the bin and lib folders for the executing program + String pathToEcdarDirectory = new File(getClass().getProtectionDomain().getCodeSource().getLocation().toURI()).getParentFile().getPath(); + + List<File> files = List.of(Objects.requireNonNull(new File(pathToEcdarDirectory).listFiles())); + for (File f : files) { + if (potentialFiles.contains(f.getName())) { + engine.setEngineLocation(f.getAbsolutePath()); + return true; + } + } + } catch (URISyntaxException e) { + e.printStackTrace(); + Ecdar.showToast("Unable to get URI of parent directory: \"" + getClass().getProtectionDomain().getCodeSource().getLocation() + "\" due to: " + e.getMessage()); + } catch (NullPointerException e) { + e.printStackTrace(); + Ecdar.showToast("Encountered null reference when trying to get path of executing program"); + } + + return !engine.getEngineLocation().equals(""); + } + + /** + * Add the new engine instance presentation to the engine options dialog + * @param newEnginePresentation The presentation of the new engine instance + */ + private void addEnginePresentationToList(EnginePresentation newEnginePresentation) { + newEnginePresentation.getController().defaultEngineRadioButton.setSelected(engineInstanceList.getChildren().isEmpty()); + engineInstanceList.getChildren().add(newEnginePresentation); + newEnginePresentation.getController().moveEngineInstanceUpRippler.setOnMouseClicked((mouseEvent) -> moveEngineInstance(newEnginePresentation, -1)); + newEnginePresentation.getController().moveEngineInstanceDownRippler.setOnMouseClicked((mouseEvent) -> moveEngineInstance(newEnginePresentation, +1)); + + // Set remove engine action to only fire if the engine is not locked + newEnginePresentation.getController().removeEngineRippler.setOnMouseClicked((mouseEvent) -> { + if (!newEnginePresentation.getController().defaultEngineRadioButton.isSelected()) { + engineInstanceList.getChildren().remove(newEnginePresentation); + } + }); + newEnginePresentation.getController().defaultEngineRadioButton.setToggleGroup(defaultEngineToggleGroup); + } + + /** + * Calculated the new position of the engine, 'i' places further down, in the engine list. + * The engine presentation is removed and added to the new position. + * Given a negative value, the instance is moved up. This function uses loop-around, meaning that: + * - If the instance is moved down while already at the bottom of the list, it is placed at the top. + * - If the instance is moved up while already at the top of the list, it is placed at the bottom. + * + * @param enginePresentation The engine presentation to move + * @param i The number of steps to move the engine down + */ + private void moveEngineInstance(EnginePresentation enginePresentation, int i) { + int currentIndex = engineInstanceList.getChildren().indexOf(enginePresentation); + int newIndex = (currentIndex + i) % engineInstanceList.getChildren().size(); + if (newIndex < 0) { + newIndex = engineInstanceList.getChildren().size() - 1; + } + + engineInstanceList.getChildren().remove(enginePresentation); + engineInstanceList.getChildren().add(newIndex, enginePresentation); + } + + /** + * Marks input fields in the engineList that contains errors and returns whether any errors were found + * + * @return whether any errors were found + */ + private boolean engineInstanceListIsErrorFree() { + boolean error = true; + + for (Node child : engineInstanceList.getChildren()) { + if (child instanceof EnginePresentation) { + EngineInstanceController engineInstanceController = ((EnginePresentation) child).getController(); + error = engineNameIsErrorFree(engineInstanceController) && error; + error = portRangeIsErrorFree(engineInstanceController) && error; + error = engineInstanceLocationIsErrorFree(engineInstanceController) && error; + } + } + + return error; + } + + private boolean engineNameIsErrorFree(EngineInstanceController engineInstanceController) { + String engineName = engineInstanceController.engineName.getText(); + + if (engineName.isBlank()) { + engineInstanceController.engineNameIssue.setText(ValidationErrorMessages.ENGINE_NAME_EMPTY.toString()); + engineInstanceController.engineNameIssue.setVisible(true); + return false; + } + + engineInstanceController.engineNameIssue.setVisible(false); + return true; + } + + private boolean portRangeIsErrorFree(EngineInstanceController engineInstanceController) { + boolean errorFree = true; + int portRangeStart = 0, portRangeEnd = 0; + engineInstanceController.portRangeStartIssue.setText(""); + engineInstanceController.portRangeStartIssue.setVisible(false); + engineInstanceController.portRangeEndIssue.setText(""); + engineInstanceController.portRangeEndIssue.setVisible(false); + engineInstanceController.portRangeIssue.setVisible(false); + + try { + portRangeStart = Integer.parseInt(engineInstanceController.portRangeStart.getText()); + } catch (NumberFormatException numberFormatException) { + engineInstanceController.portRangeStartIssue.setText(ValidationErrorMessages.VALUE_NOT_INTEGER.toString()); + errorFree = false; + } + + try { + portRangeEnd = Integer.parseInt(engineInstanceController.portRangeEnd.getText()); + } catch (NumberFormatException numberFormatException) { + engineInstanceController.portRangeEndIssue.setText(ValidationErrorMessages.VALUE_NOT_INTEGER.toString()); + errorFree = false; + } + + Range<Integer> portRange = Range.between(0, 65535); + + if (!portRange.contains(portRangeStart)) { + if (engineInstanceController.portRangeStartIssue.getText().isBlank()) { + engineInstanceController.portRangeStartIssue.setText(ValidationErrorMessages.PORT_VALUE_NOT_WITHIN_ACCEPTABLE_RANGE.toString()); + } else { + engineInstanceController.portRangeStartIssue.setText(ValidationErrorMessages.PORT_VALUE_NOT_WITHIN_ACCEPTABLE_RANGE_CONCATINATION.toString()); + } + errorFree = false; + } + if (!portRange.contains(portRangeEnd)) { + if (engineInstanceController.portRangeEndIssue.getText().isBlank()) { + engineInstanceController.portRangeEndIssue.setText(ValidationErrorMessages.PORT_VALUE_NOT_WITHIN_ACCEPTABLE_RANGE.toString()); + } else { + engineInstanceController.portRangeEndIssue.setText(ValidationErrorMessages.PORT_VALUE_NOT_WITHIN_ACCEPTABLE_RANGE_CONCATINATION.toString()); + } + errorFree = false; + } + + if (portRangeEnd - portRangeStart < 0) { + engineInstanceController.portRangeIssue.setText(ValidationErrorMessages.PORT_RANGE_MUST_BE_INCREMENTAL.toString()); + errorFree = false; + } + + engineInstanceController.portRangeStartIssue.setVisible(!errorFree); + engineInstanceController.portRangeEndIssue.setVisible(!errorFree); + engineInstanceController.portRangeIssue.setVisible(!errorFree); + + return errorFree; + } + + private boolean engineInstanceLocationIsErrorFree(EngineInstanceController engineInstanceController) { + boolean errorFree = true; + + if (engineInstanceController.isLocal.isSelected()) { + if (engineInstanceController.pathToEngine.getText().isBlank()) { + engineInstanceController.locationIssue.setText(ValidationErrorMessages.FILE_LOCATION_IS_BLANK.toString()); + errorFree = false; + } else { + Path localEnginePath = Paths.get(engineInstanceController.pathToEngine.getText()); + + if (!Files.isExecutable(localEnginePath)) { + engineInstanceController.locationIssue.setText(ValidationErrorMessages.FILE_DOES_NOT_EXIST_OR_NOT_EXECUTABLE.toString()); + errorFree = false; + } + } + } else { + if (engineInstanceController.address.getText().isBlank()) { + engineInstanceController.locationIssue.setText(ValidationErrorMessages.HOST_ADDRESS_IS_BLANK.toString()); + errorFree = false; + } else { + try { + InetAddress address = InetAddress.getByName(engineInstanceController.address.getText()); + boolean reachable = address.isReachable(200); + + if (!reachable) { + engineInstanceController.locationIssue.setText(ValidationErrorMessages.HOST_NOT_REACHABLE.toString()); + errorFree = false; + } + + } catch (UnknownHostException unknownHostException) { + engineInstanceController.locationIssue.setText(ValidationErrorMessages.UNACCEPTABLE_HOST_NAME.toString()); + errorFree = false; + } catch (IOException ioException) { + engineInstanceController.locationIssue.setText(ValidationErrorMessages.IO_EXCEPTION_WITH_HOST.toString()); + errorFree = false; + } + } + } + + engineInstanceController.locationIssue.setVisible(!errorFree); + + return errorFree; + } + + private enum ValidationErrorMessages { + ENGINE_NAME_EMPTY { + @Override + public String toString() { + return "The engine name cannot be empty"; + } + }, + VALUE_NOT_INTEGER { + @Override + public String toString() { + return "Value must be integer"; + } + }, + PORT_RANGE_MUST_BE_INCREMENTAL { + @Override + public String toString() { + return "Start of port range must be greater than end"; + } + }, + PORT_VALUE_NOT_WITHIN_ACCEPTABLE_RANGE { + @Override + public String toString() { + return "Value must be within range 0 - 65535"; + } + }, + PORT_VALUE_NOT_WITHIN_ACCEPTABLE_RANGE_CONCATINATION { + @Override + public String toString() { + return " and within range 0 - 65535"; + } + }, + FILE_LOCATION_IS_BLANK { + @Override + public String toString() { + return "Please specify a file for this engine"; + } + }, + FILE_DOES_NOT_EXIST_OR_NOT_EXECUTABLE { + @Override + public String toString() { + return "The above file does not exists or ECDAR does not have the privileges to execute it"; + } + }, + HOST_ADDRESS_IS_BLANK { + @Override + public String toString() { + return "Please specify an address for the external host"; + } + }, + HOST_NOT_REACHABLE { + @Override + public String toString() { + return "The above address is not reachable. Make sure that the host is correct"; + } + }, + UNACCEPTABLE_HOST_NAME { + @Override + public String toString() { + return "The above address is not an acceptable host name"; + } + }, + IO_EXCEPTION_WITH_HOST { + @Override + public String toString() { + return "An I/O exception was encountered while trying to reach the host"; + } + } + } +} diff --git a/src/main/java/ecdar/controllers/FileController.java b/src/main/java/ecdar/controllers/FileController.java index 3c76d1b2..6d51f07e 100644 --- a/src/main/java/ecdar/controllers/FileController.java +++ b/src/main/java/ecdar/controllers/FileController.java @@ -1,9 +1,27 @@ package ecdar.controllers; import com.jfoenix.controls.JFXRippler; +import ecdar.Ecdar; +import ecdar.abstractions.Component; +import ecdar.abstractions.EcdarSystem; +import ecdar.abstractions.HighLevelModel; +import ecdar.mutation.models.MutationTestPlan; +import ecdar.utility.colors.Color; +import ecdar.utility.colors.EnabledColor; +import ecdar.utility.helpers.ImageScaler; +import javafx.application.Platform; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleObjectProperty; import javafx.fxml.Initializable; +import javafx.geometry.Insets; +import javafx.scene.Cursor; +import javafx.scene.control.Label; +import javafx.scene.image.Image; import javafx.scene.image.ImageView; -import javafx.scene.layout.StackPane; +import javafx.scene.layout.*; +import javafx.scene.shape.Circle; +import org.kordamp.ikonli.javafx.FontIcon; import java.net.URL; import java.util.ResourceBundle; @@ -12,12 +30,132 @@ * Controller for a file in the project pane. */ public class FileController implements Initializable { + public AnchorPane root; + public JFXRippler rippler; + public Circle iconBackground; public JFXRippler moreInformation; + public FontIcon moreInformationIcon; + public Label fileName; public ImageView fileImage; public StackPane fileImageStackPane; + private final SimpleObjectProperty<HighLevelModel> model = new SimpleObjectProperty<>(null); + private final BooleanProperty isActive = new SimpleBooleanProperty(false); + private final EnabledColor color = EnabledColor.getDefault().getLowestIntensity(); + @Override public void initialize(URL location, ResourceBundle resources) { + Platform.runLater(() -> { + initializeIcon(); + initializeFileIcons(); + initializeFileName(); + initializeColors(); + initializeRippler(); + initializeMoreInformationButton(); + }); + } + + private void initializeMoreInformationButton() { + if (getModel() instanceof Component || getModel() instanceof EcdarSystem || getModel() instanceof MutationTestPlan) { + moreInformation.setVisible(true); + moreInformation.setMaskType(JFXRippler.RipplerMask.CIRCLE); + moreInformation.setPosition(JFXRippler.RipplerPos.BACK); + moreInformation.setRipplerFill(Color.GREY_BLUE.getColor(Color.Intensity.I500)); + } + } + + private void initializeRippler() { + final Color color = Color.GREY_BLUE; + final Color.Intensity colorIntensity = Color.Intensity.I400; + + rippler.setMaskType(JFXRippler.RipplerMask.RECT); + rippler.setRipplerFill(color.getColor(colorIntensity)); + rippler.setPosition(JFXRippler.RipplerPos.BACK); + } + + private void initializeFileName() { + model.get().nameProperty().addListener((obs, oldName, newName) -> fileName.setText(newName)); + fileName.setText(model.get().getName()); + } + + private void initializeIcon() { + model.get().colorProperty().addListener((obs, oldColor, newColor) -> iconBackground.setFill(newColor.getPaintColor())); + iconBackground.setFill(model.get().getColor().getPaintColor()); + } + + private void initializeColors() { + // Update the background when hovered + root.setOnMouseEntered(event -> { + if (isActive.get()) { + setBackground(color.nextIntensity(2)); + } else { + setBackground(color.nextIntensity()); + } + root.setCursor(Cursor.HAND); + }); + + root.setOnMouseExited(event -> { + if (isActive.get()) { + setBackground(color.nextIntensity()); + } else { + setBackground(color); + } + root.setCursor(Cursor.DEFAULT); + }); + + // Update the background initially + setBackground(color); + } + + /** + * Initialises the icons for the three different file types in Ecdar: Component, System and Declarations + */ + private void initializeFileIcons() { + if (model.get() instanceof Component) { + fileImage.setImage(new Image(Ecdar.class.getResource("component_frame.png").toExternalForm())); + } else if (model.get() instanceof EcdarSystem) { + fileImage.setImage(new Image(Ecdar.class.getResource("system_frame.png").toExternalForm())); + } else if (model.get() instanceof MutationTestPlan) { + fileImage.setImage(new Image(Ecdar.class.getResource("test_frame.png").toExternalForm())); + } else { + fileImage.setImage(new Image(Ecdar.class.getResource("description_frame.png").toExternalForm())); + } + + ImageScaler.fitImageToPane(fileImage, fileImageStackPane); + } + + private void setBackground(EnabledColor newColor) { + root.setBackground(new Background(new BackgroundFill( + newColor.getPaintColor(), + CornerRadii.EMPTY, + Insets.EMPTY + ))); + + root.setBorder(new Border(new BorderStroke( + newColor.getStrokeColor(), + BorderStrokeStyle.SOLID, + CornerRadii.EMPTY, + new BorderWidths(0, 0, 1, 0) + ))); + + moreInformationIcon.setFill(newColor.nextIntensity(5).getPaintColor()); + } + + public void setModel(HighLevelModel newModel) { + model.set(newModel); + } + + public HighLevelModel getModel() { + return model.get(); + } + + public void setIsActive(boolean active) { + isActive.set(active); + if (active) { + setBackground(color.nextIntensity()); + } else { + setBackground(color); + } } } diff --git a/src/main/java/ecdar/controllers/HighLevelModelController.java b/src/main/java/ecdar/controllers/HighLevelModelController.java new file mode 100644 index 00000000..c346a268 --- /dev/null +++ b/src/main/java/ecdar/controllers/HighLevelModelController.java @@ -0,0 +1,7 @@ +package ecdar.controllers; + +import ecdar.abstractions.HighLevelModel; + +abstract public class HighLevelModelController { + public abstract HighLevelModel getModel(); +} diff --git a/src/main/java/ecdar/controllers/LocationController.java b/src/main/java/ecdar/controllers/LocationController.java index 34b62838..d6392b4f 100644 --- a/src/main/java/ecdar/controllers/LocationController.java +++ b/src/main/java/ecdar/controllers/LocationController.java @@ -8,6 +8,7 @@ import ecdar.presentations.*; import ecdar.utility.UndoRedoStack; import ecdar.utility.colors.Color; +import ecdar.utility.colors.EnabledColor; import ecdar.utility.helpers.ItemDragHelper; import ecdar.utility.helpers.SelectHelper; import ecdar.utility.keyboard.Keybind; @@ -36,8 +37,9 @@ import java.util.*; import java.util.function.Consumer; -public class LocationController implements Initializable, SelectHelper.ItemSelectable, Nudgeable { +import static ecdar.presentations.ModelPresentation.TOOLBAR_HEIGHT; +public class LocationController implements Initializable, SelectHelper.ItemSelectable, Nudgeable { private static final Map<Location, Boolean> invalidNameError = new HashMap<>(); private final ObjectProperty<Location> location = new SimpleObjectProperty<>(); @@ -64,7 +66,6 @@ public class LocationController implements Initializable, SelectHelper.ItemSelec public Line prohibitedLocStrikeThrough; private DropDownMenu dropDownMenu; - private boolean dropDownMenuInitialized = false; @Override public void initialize(final URL location, final ResourceBundle resources) { @@ -84,18 +85,15 @@ public void initialize(final URL location, final ResourceBundle resources) { } private void initializeSelectListener() { - SelectHelper.elementsToBeSelected.addListener(new ListChangeListener<Nearable>() { - @Override - public void onChanged(final Change<? extends Nearable> c) { - while (c.next()) { - if (c.getAddedSize() == 0) return; - - for (final Nearable nearable : SelectHelper.elementsToBeSelected) { - if (nearable instanceof Location) { - if (nearable.equals(getLocation())) { - SelectHelper.addToSelection(LocationController.this); - break; - } + SelectHelper.elementsToBeSelected.addListener((ListChangeListener<Nearable>) c -> { + while (c.next()) { + if (c.getAddedSize() == 0) return; + + for (final Nearable nearable : SelectHelper.elementsToBeSelected) { + if (nearable instanceof Location) { + if (nearable.equals(getLocation())) { + SelectHelper.addToSelection(LocationController.this); + break; } } } @@ -108,15 +106,15 @@ public void initializeDropDownMenu() { dropDownMenu.addClickableAndDisableableListElement("Draw Edge", getLocation().getIsLocked(), (event) -> { - final Edge newEdge = new Edge(getLocation(), EcdarController.getGlobalEdgeStatus()); + final Edge newEdge = new Edge(getLocation(), EcdarController.getGlobalEdgeStatus()); - KeyboardTracker.registerKeybind(KeyboardTracker.ABANDON_EDGE, new Keybind(new KeyCodeCombination(KeyCode.ESCAPE), () -> { - getComponent().removeEdge(newEdge); - })); - getComponent().addEdge(newEdge); - dropDownMenu.hide(); - } - ); + KeyboardTracker.registerKeybind(KeyboardTracker.ABANDON_EDGE, new Keybind(new KeyCodeCombination(KeyCode.ESCAPE), () -> { + getComponent().removeEdge(newEdge); + })); + getComponent().addEdge(newEdge); + dropDownMenu.hide(); + } + ); dropDownMenu.addClickableAndDisableableListElement("Add Nickname", getLocation().nicknameProperty().isNotEmpty().or(nicknameTag.textFieldFocusProperty()), @@ -191,8 +189,25 @@ public void initializeDropDownMenu() { dropDownMenu.addSpacerElement(); - dropDownMenu.addColorPicker(getLocation(), (color, intensity) -> { - getLocation().setColorIntensity(intensity); + dropDownMenu.addClickableListElement("Is " + getLocation().getId() + " reachable?", event -> { + dropDownMenu.hide(); + // Generate the query from the engine + final String reachabilityQuery = getComponent().getName() + "." + getLocation().getId(); + + // Add proper comment + final String reachabilityComment = "Is " + getLocation().getMostDescriptiveIdentifier() + " reachable?"; + + // Add new query for this location + final Query query = new Query(reachabilityQuery, reachabilityComment, QueryState.UNKNOWN); + query.setType(QueryType.REACHABILITY); + Ecdar.getProject().getQueries().add(query); + query.execute(); + dropDownMenu.hide(); + }); + + dropDownMenu.addSpacerElement(); + + dropDownMenu.addColorPicker(getLocation(), (color) -> { getLocation().setColor(color); }); @@ -262,17 +277,11 @@ public Location getLocation() { public void setLocation(final Location location) { this.location.set(location); - - if (ComponentController.isPlacingLocation()) { - root.layoutXProperty().bindBidirectional(location.xProperty()); - root.layoutYProperty().bindBidirectional(location.yProperty()); - } else { - root.setLayoutX(location.getX()); - root.setLayoutY(location.getY()); - location.xProperty().bindBidirectional(root.layoutXProperty()); - location.yProperty().bindBidirectional(root.layoutYProperty()); - root.setPlaced(true); - } + root.setLayoutX(location.getX()); + root.setLayoutY(location.getY()); + location.xProperty().bindBidirectional(root.layoutXProperty()); + location.yProperty().bindBidirectional(root.layoutYProperty()); + root.setPlaced(true); } public ObjectProperty<Location> locationProperty() { @@ -303,7 +312,7 @@ private void locationExited() { @FXML private void mouseEntered() { - if(!this.root.isInteractable()) return; + if (!this.root.isInteractable()) return; circle.setCursor(Cursor.HAND); this.root.animateHoverEntered(); @@ -330,7 +339,7 @@ private void mouseEntered() { @FXML private void mouseExited() { final LocationPresentation locationPresentation = this.root; - if(!locationPresentation.isInteractable()) return; + if (!locationPresentation.isInteractable()) return; circle.setCursor(Cursor.DEFAULT); locationPresentation.animateHoverExited(); @@ -409,7 +418,7 @@ private void initializeMouseControls() { } // Otherwise, select the location else { - if(root.isInteractable()) { + if (root.isInteractable()) { if (event.isShortcutDown()) { if (SelectHelper.getSelectedElements().contains(this)) { SelectHelper.deselect(this); @@ -429,7 +438,7 @@ private void initializeMouseControls() { final double minY = Ecdar.CANVAS_PADDING * 4; final double maxY = getComponent().getBox().getHeight() - Ecdar.CANVAS_PADDING * 2; - if(root.getLayoutX() >= minX && root.getLayoutX() <= maxX && root.getLayoutY() >= minY && root.getLayoutY() <= maxY) { + if (root.getLayoutX() >= minX && root.getLayoutX() <= maxX && root.getLayoutY() >= minY && root.getLayoutY() <= maxY) { // Unbind presentation root x and y coordinates (bind the view properly to enable dragging) root.layoutXProperty().unbind(); root.layoutYProperty().unbind(); @@ -440,7 +449,6 @@ private void initializeMouseControls() { // Notify that the location was placed root.setPlaced(true); - ComponentController.setPlacingLocation(null); KeyboardTracker.unregisterKeybind(KeyboardTracker.ABANDON_LOCATION); } else { root.shake(); @@ -449,37 +457,31 @@ private void initializeMouseControls() { }; locationProperty().addListener((obs, oldLocation, newLocation) -> { - if(newLocation == null) return; + if (newLocation == null) return; root.addEventHandler(MouseEvent.MOUSE_CLICKED, mouseClicked::accept); ItemDragHelper.makeDraggable(root, this::getDragBounds); }); } @Override - public void color(final Color color, final Color.Intensity intensity) { + public void color(final EnabledColor color) { final Location location = getLocation(); // Set the color of the location - location.setColorIntensity(intensity); location.setColor(color); } @Override - public Color getColor() { + public EnabledColor getColor() { return getLocation().getColor(); } - @Override - public Color.Intensity getColorIntensity() { - return getLocation().getColorIntensity(); - } - @Override public ItemDragHelper.DragBounds getDragBounds() { final int PADDING = 5; final ObservableDoubleValue minX = new SimpleDoubleProperty(location.get().getRadius() + PADDING); final ObservableDoubleValue maxX = getComponent().getBox().getWidthProperty().subtract(location.get().getRadius() + PADDING); - final ObservableDoubleValue minY = new SimpleDoubleProperty(location.get().getRadius() + PADDING); + final ObservableDoubleValue minY = new SimpleDoubleProperty(location.get().getRadius() + TOOLBAR_HEIGHT + PADDING); final ObservableDoubleValue maxY = getComponent().getBox().getHeightProperty().subtract(location.get().getRadius() + PADDING); return new ItemDragHelper.DragBounds(minX, maxX, minY, maxY); } @@ -507,14 +509,15 @@ public boolean nudge(final NudgeDirection direction) { return oldX != newX || oldY != newY; } - /**' + /** * Method which has the logical expression for the shortcut of adding a new edge, * checks whether the location is locked and the correct buttons are held down + * * @param event the mouse event * @return the result of the boolean expression, false if the location is locked or the incorrect buttons are held down, * true if the correct buttons are down */ - private boolean canCreateEdgeShortcut(MouseEvent event){ + private boolean canCreateEdgeShortcut(MouseEvent event) { return (!getLocation().getIsLocked().get() && ((event.isShiftDown() && event.getButton().equals(MouseButton.PRIMARY)) || event.getButton().equals(MouseButton.MIDDLE))); } diff --git a/src/main/java/ecdar/controllers/MessageCollectionController.java b/src/main/java/ecdar/controllers/MessageCollectionController.java new file mode 100644 index 00000000..c8269bc2 --- /dev/null +++ b/src/main/java/ecdar/controllers/MessageCollectionController.java @@ -0,0 +1,123 @@ +package ecdar.controllers; + +import ecdar.Ecdar; +import ecdar.abstractions.Component; +import ecdar.code_analysis.CodeAnalysis; +import ecdar.presentations.MessagePresentation; +import ecdar.utility.colors.Color; +import ecdar.utility.colors.EnabledColor; +import javafx.beans.InvalidationListener; +import javafx.beans.property.SimpleListProperty; +import javafx.collections.ListChangeListener; +import javafx.collections.ObservableList; +import javafx.event.EventHandler; +import javafx.fxml.Initializable; +import javafx.scene.Cursor; +import javafx.scene.control.Label; +import javafx.scene.input.MouseEvent; +import javafx.scene.layout.VBox; +import javafx.scene.shape.Circle; +import javafx.scene.shape.Line; + +import java.net.URL; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.ResourceBundle; +import java.util.function.Consumer; + +public class MessageCollectionController implements Initializable { + public VBox root; + public Circle indicator; + public Label headline; + public Line line; + public VBox messageBox; + + private Component component; + private final ObservableList<CodeAnalysis.Message> messages = new SimpleListProperty<>(); + private Map<CodeAnalysis.Message, MessagePresentation> messageMessagePresentationMap; + + @Override + public void initialize(URL location, ResourceBundle resources) { + initializeHeadline(component); + initializeLine(); + initializeErrorsListener(); + } + + private void initializeErrorsListener() { + messageMessagePresentationMap = new HashMap<>(); + + final Consumer<CodeAnalysis.Message> addMessage = (message) -> { + final MessagePresentation messagePresentation = new MessagePresentation(message); + messageMessagePresentationMap.put(message, messagePresentation); + messageBox.getChildren().add(messagePresentation); + }; + + messages.forEach(addMessage); + messages.addListener((ListChangeListener<CodeAnalysis.Message>) c -> { + while (c.next()) { + c.getAddedSubList().forEach(addMessage); + + c.getRemoved().forEach(message -> { + messageBox.getChildren().remove(messageMessagePresentationMap.get(message)); + messageMessagePresentationMap.remove(message); + }); + } + }); + } + + private void initializeLine() { + messageBox.getChildren().addListener((InvalidationListener) observable -> line.setEndY(messageBox.getChildren().size() * 23 + 8)); + } + + private void initializeHeadline(final Component component) { + line.setStroke(Color.GREY.getColor(Color.Intensity.I400)); + + // This is an project wide message that is not specific to a component + if (component == null) { + headline.setText("Project"); + return; + } + + headline.setText(component.getName()); + headline.textProperty().bind(component.nameProperty()); + + final EventHandler<MouseEvent> onMouseEntered = event -> { + root.setCursor(Cursor.HAND); + headline.setStyle("-fx-underline: true;"); + }; + + final EventHandler<MouseEvent> onMouseExited = event -> { + root.setCursor(Cursor.DEFAULT); + headline.setStyle("-fx-underline: false;"); + }; + + final EventHandler<MouseEvent> onMousePressed = event -> Ecdar.getPresentation().getController().getEditorPresentation().getController().setActiveModelPresentationForActiveCanvas( + Ecdar.getPresentation().getController().getEditorPresentation().getController(). + projectPane.getController().getComponentPresentations(). + stream().filter(componentPresentation -> componentPresentation.getController().getComponent().equals(component)) + .findFirst().orElse(null)); + + headline.setOnMouseEntered(onMouseEntered); + headline.setOnMouseExited(onMouseExited); + headline.setOnMousePressed(onMousePressed); + indicator.setOnMouseEntered(onMouseEntered); + indicator.setOnMouseExited(onMouseExited); + indicator.setOnMousePressed(onMousePressed); + + final Consumer<EnabledColor> updateColor = (color) -> { + indicator.setFill(color.getPaintColor()); + }; + + updateColor.accept(component.getColor()); + component.colorProperty().addListener((observable, oldColor, newColor) -> updateColor.accept(newColor)); + } + + public void setComponent(Component newComponent) { + component = newComponent; + } + + public void setMessages(List<CodeAnalysis.Message> newMessages) { + if (!newMessages.isEmpty()) messages.setAll(newMessages); + } +} diff --git a/src/main/java/ecdar/controllers/ModeController.java b/src/main/java/ecdar/controllers/ModeController.java new file mode 100644 index 00000000..d1238118 --- /dev/null +++ b/src/main/java/ecdar/controllers/ModeController.java @@ -0,0 +1,8 @@ +package ecdar.controllers; + +import javafx.scene.layout.StackPane; + +interface ModeController { + StackPane getLeftPane(); + StackPane getRightPane(); +} diff --git a/src/main/java/ecdar/controllers/ModelController.java b/src/main/java/ecdar/controllers/ModelController.java index f0b91496..6e378f4a 100644 --- a/src/main/java/ecdar/controllers/ModelController.java +++ b/src/main/java/ecdar/controllers/ModelController.java @@ -1,16 +1,31 @@ package ecdar.controllers; -import ecdar.abstractions.HighLevelModelObject; +import ecdar.Ecdar; +import ecdar.abstractions.Box; +import ecdar.abstractions.HighLevelModel; import com.jfoenix.controls.JFXTextField; +import ecdar.utility.UndoRedoStack; +import ecdar.utility.colors.Color; +import ecdar.utility.colors.EnabledColor; +import ecdar.utility.helpers.StringValidator; +import javafx.application.Platform; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.DoubleProperty; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleDoubleProperty; +import javafx.geometry.Insets; +import javafx.scene.Cursor; import javafx.scene.layout.BorderPane; import javafx.scene.layout.StackPane; import javafx.scene.shape.Line; import javafx.scene.shape.Rectangle; +import static ecdar.presentations.ModelPresentation.CORNER_SIZE; + /** * Controller for a high level model, such as a component or a system. */ -public abstract class ModelController { +public abstract class ModelController extends HighLevelModelController { public StackPane root; public Rectangle background; public BorderPane frame; @@ -21,8 +36,6 @@ public abstract class ModelController { public BorderPane toolbar; public JFXTextField name; - public abstract HighLevelModelObject getModel(); - /** * Hides the border and background. */ @@ -40,4 +53,261 @@ void showBorderAndBackground() { topLeftLine.setVisible(true); background.setVisible(true); } + + abstract double getDragAnchorMinWidth(); + abstract double getDragAnchorMinHeight(); + + /** + * Initializes this. + * @param box the box of the model + */ + void initialize(final Box box) { + initializeName(); + initializeDimensions(box); + initializesBottomDragAnchor(box); + initializesRightDragAnchor(box); + initializesCornerDragAnchor(box); + } + + /** + * Initializes handling of name. + */ + private void initializeName() { + final HighLevelModel model = getModel(); + + final BooleanProperty initialized = new SimpleBooleanProperty(false); + + name.focusedProperty().addListener((observable, oldValue, newValue) -> { + if (newValue && !initialized.get()) { + root.requestFocus(); + initialized.setValue(true); + } + }); + + // Set the text field to the name in the model, and bind the model to the text field + name.setText(model.getName()); + name.textProperty().addListener((obs, oldName, newName) -> { + if (StringValidator.validateComponentName(newName)) { + model.nameProperty().unbind(); + model.setName(newName); + } else { + name.setText(model.getName()); + Ecdar.showToast("Component names cannot contain '.'"); + } + }); + + final Runnable updateColor = () -> { + final EnabledColor color = model.getColor(); + + // Set the text color for the label + name.setStyle("-fx-text-fill: " + color.color.getTextColorRgbaString(color.intensity) + ";"); + name.setFocusColor(color.getTextColor()); + name.setUnFocusColor(javafx.scene.paint.Color.TRANSPARENT); + }; + + model.colorProperty().addListener(observable -> updateColor.run()); + updateColor.run(); + + Platform.runLater(() -> { + // Center the text vertically and add a left padding of CORNER_SIZE + name.setPadding(new Insets(2, 0, 0, CORNER_SIZE)); + name.setOnKeyPressed(Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasPresentation().getController().getLeaveTextAreaKeyHandler()); + }); + } + + /** + * Sets the width and the height of the view to the values in the abstraction. + * @param box The dimensions to set + */ + void initializeDimensions(final Box box) { + // Ensure that the component snaps to the grid + if (box.getX() == 0 && box.getY() == 0) { + box.setX(Ecdar.CANVAS_PADDING * 0.5); + box.setY(Ecdar.CANVAS_PADDING * 0.5); + } + + // Bind the position of the abstraction to the values in the view + root.layoutXProperty().set(box.getX()); + root.layoutYProperty().set(box.getY()); + box.getXProperty().bindBidirectional(root.layoutXProperty()); + box.getYProperty().bindBidirectional(root.layoutYProperty()); + + root.setMinWidth(box.getWidth()); + root.setMaxWidth(box.getWidth()); + root.setMinHeight(box.getHeight()); + root.setMaxHeight(box.getHeight()); + root.minHeightProperty().bindBidirectional(box.getHeightProperty()); + root.maxHeightProperty().bindBidirectional(box.getHeightProperty()); + root.minWidthProperty().bindBidirectional(box.getWidthProperty()); + root.maxWidthProperty().bindBidirectional(box.getWidthProperty()); + } + + /** + * Initializes the right drag anchor. + * @param box the box of the model + */ + private void initializesRightDragAnchor(final Box box) { + final BooleanProperty wasResized = new SimpleBooleanProperty(false); + rightAnchor.setCursor(Cursor.E_RESIZE); + + // Bind the place and size of bottom anchor + rightAnchor.setWidth(5); + rightAnchor.heightProperty().bind(box.getHeightProperty()); + + final DoubleProperty prevX = new SimpleDoubleProperty(); + final DoubleProperty prevWidth = new SimpleDoubleProperty(); + + rightAnchor.setOnMousePressed(event -> { + event.consume(); + + prevX.set(event.getScreenX()); + prevWidth.set(box.getWidth()); + }); + + rightAnchor.setOnMouseDragged(event -> { + double diff = event.getScreenX() - prevX.get(); + diff -= diff % Ecdar.CANVAS_PADDING; + + final double newWidth = prevWidth.get() + diff; + final double minWidth = getDragAnchorMinWidth(); + + // Move the model left or right to account for new height (needed because model is centered in parent) + root.setTranslateX(root.getTranslateX() + (Math.max(newWidth, minWidth) - box.getWidth()) / 2); + box.setWidth(Math.max(newWidth, minWidth)); + wasResized.set(true); + }); + + rightAnchor.setOnMouseReleased(event -> { + if (!wasResized.get()) return; + final double previousWidth = prevWidth.doubleValue(); + final double currentWidth = box.getWidth(); + + // If no difference do not save change + if (previousWidth == currentWidth) return; + + UndoRedoStack.pushAndPerform(() -> { // Perform + box.setWidth(currentWidth); + }, () -> { // Undo + box.setWidth(previousWidth); + }, + "Component width resized", + "settings-overscan" + ); + + wasResized.set(false); + }); + } + + /** + * Initializes the bottom drag anchor. + * @param box the box of the model + */ + private void initializesBottomDragAnchor(final Box box) { + final BooleanProperty wasResized = new SimpleBooleanProperty(false); + bottomAnchor.setCursor(Cursor.S_RESIZE); + + // Bind the place and size of bottom anchor + bottomAnchor.widthProperty().bind(box.getWidthProperty()); + bottomAnchor.setHeight(5); + + final DoubleProperty prevY = new SimpleDoubleProperty(); + final DoubleProperty prevHeight = new SimpleDoubleProperty(); + + bottomAnchor.setOnMousePressed(event -> { + event.consume(); + + prevY.set(event.getScreenY()); + prevHeight.set(box.getHeight()); + }); + + bottomAnchor.setOnMouseDragged(event -> { + double diff = event.getScreenY() - prevY.get(); + final double newHeight = prevHeight.get() + diff; + final double minHeight = getDragAnchorMinHeight(); + + // Move the model up or down to account for new height (needed because model is centered in parent) + root.setTranslateY(root.getTranslateY() + (Math.max(newHeight, minHeight) - box.getHeight()) / 2); + box.setHeight(Math.max(newHeight, minHeight)); + wasResized.set(true); + }); + + bottomAnchor.setOnMouseReleased(event -> { + if (!wasResized.get()) return; + final double previousHeight = prevHeight.doubleValue(); + final double currentHeight = box.getHeight(); + + // If no difference do not save change + if (previousHeight == currentHeight) return; + + UndoRedoStack.pushAndPerform(() -> { // Perform + box.setHeight(currentHeight); + }, () -> { // Undo + box.setHeight(previousHeight); + }, + "Component height resized", + "settings-overscan" + ); + + wasResized.set(false); + }); + } + + private void initializesCornerDragAnchor(final Box box) { + final BooleanProperty wasResized = new SimpleBooleanProperty(false); + cornerAnchor.setCursor(Cursor.SE_RESIZE); + + // Bind the place and size of bottom anchor + cornerAnchor.setWidth(10); + cornerAnchor.setHeight(10); + + final DoubleProperty prevX = new SimpleDoubleProperty(); + final DoubleProperty prevY = new SimpleDoubleProperty(); + final DoubleProperty prevWidth = new SimpleDoubleProperty(); + final DoubleProperty prevHeight = new SimpleDoubleProperty(); + + cornerAnchor.setOnMousePressed(event -> { + event.consume(); + + prevX.set(event.getScreenX()); + prevWidth.set(box.getWidth()); + prevY.set(event.getScreenY()); + prevHeight.set(box.getHeight()); + }); + + cornerAnchor.setOnMouseDragged(event -> { + double xDiff = (event.getScreenX() - prevX.get()) / Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasZoomFactor().get(); // ToDo NIELS: Fix dependency + final double newWidth = Math.max(prevWidth.get() + xDiff, getDragAnchorMinWidth()); + box.setWidth(newWidth); + + double yDiff = (event.getScreenY() - prevY.get()) / Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasZoomFactor().get(); + final double newHeight = Math.max(prevHeight.get() + yDiff, getDragAnchorMinHeight()); + box.setHeight(newHeight); + + wasResized.set(true); + }); + + cornerAnchor.setOnMouseReleased(event -> { + if (!wasResized.get()) return; + final double previousWidth = prevWidth.doubleValue(); + final double currentWidth = box.getWidth(); + final double previousHeight = prevHeight.doubleValue(); + final double currentHeight = box.getHeight(); + + // If no difference do not save change + if (previousWidth == currentWidth && previousHeight == currentHeight) return; + + UndoRedoStack.pushAndPerform(() -> { // Perform + box.setWidth(currentWidth); + box.setHeight(currentHeight); + }, () -> { // Undo + box.setWidth(previousWidth); + box.setHeight(previousHeight); + }, + "Component resized", + "settings-overscan" + ); + + wasResized.set(false); + }); + } } diff --git a/src/main/java/ecdar/controllers/MultiSyncTagController.java b/src/main/java/ecdar/controllers/MultiSyncTagController.java index fd2d5bae..6a7bfb8a 100644 --- a/src/main/java/ecdar/controllers/MultiSyncTagController.java +++ b/src/main/java/ecdar/controllers/MultiSyncTagController.java @@ -14,7 +14,6 @@ import java.util.ResourceBundle; public class MultiSyncTagController implements Initializable { - public VBox syncList; public BorderPane topbar; public BorderPane frame; diff --git a/src/main/java/ecdar/controllers/NailController.java b/src/main/java/ecdar/controllers/NailController.java index e3222c61..3f28556a 100644 --- a/src/main/java/ecdar/controllers/NailController.java +++ b/src/main/java/ecdar/controllers/NailController.java @@ -6,6 +6,7 @@ import ecdar.presentations.*; import ecdar.utility.UndoRedoStack; import ecdar.utility.colors.Color; +import ecdar.utility.colors.EnabledColor; import ecdar.utility.helpers.ItemDragHelper; import ecdar.utility.helpers.SelectHelper; import ecdar.utility.keyboard.NudgeDirection; @@ -244,20 +245,15 @@ public ObjectProperty<DisplayableEdge> edgeProperty() { } @Override - public void color(final Color color, final Color.Intensity intensity) { + public void color(final EnabledColor color) { // Do nothing. A nail cannot be colored, but can be colored as selected } @Override - public Color getColor() { + public EnabledColor getColor() { return getComponent().getColor(); } - @Override - public Color.Intensity getColorIntensity() { - return getComponent().getColorIntensity(); - } - @Override public ItemDragHelper.DragBounds getDragBounds() { final int PADDING = 5; diff --git a/src/main/java/ecdar/controllers/ProcessController.java b/src/main/java/ecdar/controllers/ProcessController.java index dc94965c..aa79a1b1 100755 --- a/src/main/java/ecdar/controllers/ProcessController.java +++ b/src/main/java/ecdar/controllers/ProcessController.java @@ -1,10 +1,13 @@ package ecdar.controllers; import com.jfoenix.controls.JFXRippler; +import ecdar.Ecdar; import ecdar.abstractions.*; import ecdar.presentations.ComponentPresentation; +import ecdar.presentations.ModelPresentation; import ecdar.presentations.SimEdgePresentation; import ecdar.presentations.SimLocationPresentation; +import ecdar.utility.helpers.UPPAALSyntaxHighlighter; import javafx.animation.Interpolator; import javafx.animation.Transition; import javafx.beans.property.ObjectProperty; @@ -18,6 +21,7 @@ import javafx.scene.shape.Circle; import javafx.util.Duration; +import org.checkerframework.checker.units.qual.K; import org.fxmisc.richtext.LineNumberFactory; import org.fxmisc.richtext.StyleClassedTextArea; import org.kordamp.ikonli.javafx.FontIcon; @@ -50,7 +54,7 @@ public class ProcessController extends ModelController implements Initializable @Override public void initialize(final URL location, final ResourceBundle resources) { - component = new SimpleObjectProperty<>(new Component(true)); + component = new SimpleObjectProperty<>(new Component()); // add line numbers to the declaration text area declarationTextArea.setParagraphGraphicFactory(LineNumberFactory.get(declarationTextArea)); initializeValues(); @@ -205,7 +209,7 @@ public void setComponent(final Component component){ }); declarationTextArea.appendText(component.getDeclarationsText()); - declarationTextArea.setStyleSpans(0, ComponentPresentation.computeHighlighting(getComponent().getDeclarationsText())); + declarationTextArea.setStyleSpans(0, UPPAALSyntaxHighlighter.computeHighlighting(getComponent().getDeclarationsText())); declarationTextArea.getStyleClass().add("component-declaration"); } @@ -249,7 +253,7 @@ public Component getComponent(){ } @Override - public HighLevelModelObject getModel() { + public HighLevelModel getModel() { return component.get(); } @@ -260,4 +264,49 @@ public ObservableMap<String, BigDecimal> getVariables() { public ObservableMap<String, BigDecimal> getClocks() { return clocks; } + + /** + * Gets the minimum possible width when dragging the anchor. + * The width is based on the x coordinate of locations, nails and the signature arrows. + * @return the minimum possible width. + */ + @Override + double getDragAnchorMinWidth() { + final Component component = getComponent(); + double minWidth = Ecdar.CANVAS_PADDING * 10; + + for (final Location location : component.getLocations()) { + minWidth = Math.max(minWidth, location.getX() + Ecdar.CANVAS_PADDING * 2); + } + + for (final Edge edge : component.getEdges()) { + for (final Nail nail : edge.getNails()) { + minWidth = Math.max(minWidth, nail.getX() + Ecdar.CANVAS_PADDING); + } + } + return minWidth; + } + + /** + * Gets the minimum possible height when dragging the anchor. + * The height is based on the y coordinate of locations, nails and the signature arrows. + * @return the minimum possible height. + */ + @Override + double getDragAnchorMinHeight() { + final Component component = getComponent(); + double minHeight = Ecdar.CANVAS_PADDING * 10; + + for (final Location location : component.getLocations()) { + minHeight = Math.max(minHeight, location.getY() + Ecdar.CANVAS_PADDING * 2); + } + + for (final Edge edge : component.getEdges()) { + for (final Nail nail : edge.getNails()) { + minHeight = Math.max(minHeight, nail.getY() + Ecdar.CANVAS_PADDING); + } + } + + return minHeight; + } } diff --git a/src/main/java/ecdar/controllers/ProjectPaneController.java b/src/main/java/ecdar/controllers/ProjectPaneController.java index b2e3d4e6..c040b5c9 100644 --- a/src/main/java/ecdar/controllers/ProjectPaneController.java +++ b/src/main/java/ecdar/controllers/ProjectPaneController.java @@ -4,23 +4,31 @@ import ecdar.Ecdar; import ecdar.abstractions.Component; import ecdar.abstractions.EcdarSystem; -import ecdar.abstractions.HighLevelModelObject; +import ecdar.abstractions.HighLevelModel; +import ecdar.abstractions.Project; +import ecdar.mutation.MutationTestPlanPresentation; import ecdar.mutation.models.MutationTestPlan; -import ecdar.presentations.DropDownMenu; -import ecdar.presentations.FilePresentation; +import ecdar.presentations.*; import ecdar.utility.UndoRedoStack; import com.jfoenix.controls.JFXPopup; import com.jfoenix.controls.JFXRippler; import com.jfoenix.controls.JFXTextArea; +import ecdar.utility.colors.EnabledColor; +import ecdar.utility.keyboard.Keybind; +import ecdar.utility.keyboard.KeyboardTracker; import javafx.application.Platform; +import javafx.collections.FXCollections; import javafx.collections.ListChangeListener; +import javafx.collections.ObservableList; import javafx.fxml.FXML; import javafx.fxml.Initializable; import javafx.geometry.Insets; -import javafx.scene.Node; import javafx.scene.control.Label; import javafx.scene.control.ScrollPane; import javafx.scene.image.ImageView; +import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyCodeCombination; +import javafx.scene.input.KeyCombination; import javafx.scene.layout.HBox; import javafx.scene.layout.StackPane; import javafx.scene.layout.VBox; @@ -28,10 +36,7 @@ import org.kordamp.ikonli.material.Material; import java.net.URL; -import java.util.ArrayList; -import java.util.Comparator; -import java.util.HashMap; -import java.util.ResourceBundle; +import java.util.*; public class ProjectPaneController implements Initializable { public StackPane root; @@ -52,65 +57,71 @@ public class ProjectPaneController implements Initializable { public ImageView createSystemImage; public StackPane createSystemPane; - private final HashMap<HighLevelModelObject, FilePresentation> modelPresentationMap = new HashMap<>(); + + public final Project project = new Project(); + private final HashMap<HighLevelModelPresentation, FilePresentation> modelPresentationMap = new HashMap<>(); + private final ObservableList<ComponentPresentation> componentPresentations = FXCollections.observableArrayList(); @Override public void initialize(final URL location, final ResourceBundle resources) { - // Bind global declarations and add mouse event - final FilePresentation globalDclPresentation = new FilePresentation(Ecdar.getProject().getGlobalDeclarations()); - globalDclPresentation.setOnMousePressed(event -> { - event.consume(); - EcdarController.setActiveModelForActiveCanvas(Ecdar.getProject().getGlobalDeclarations()); - updateColorsOnFilePresentations(); + Platform.runLater(() -> { + // Bind global declarations and add mouse event + final DeclarationsPresentation globalDeclarationsPresentation = new DeclarationsPresentation(project.getGlobalDeclarations()); + final FilePresentation globalDclPresentation = new FilePresentation(project.getGlobalDeclarations()); + modelPresentationMap.put(globalDeclarationsPresentation, globalDclPresentation); + globalDclPresentation.setOnMousePressed(event -> { + Ecdar.getPresentation().getController().getEditorPresentation().getController().setActiveModelPresentationForActiveCanvas(globalDeclarationsPresentation); + }); + + filesList.getChildren().add(globalDclPresentation); }); - filesList.getChildren().add(globalDclPresentation); - Ecdar.getProject().getComponents().addListener(new ListChangeListener<Component>() { - @Override - public void onChanged(final Change<? extends Component> c) { - while (c.next()) { - c.getAddedSubList().forEach(o -> handleAddedModel(o)); - c.getRemoved().forEach(o -> handleRemovedModel(o)); + initializeComponentHandling(); + initializeSystemHandling(); + initializeMutationTestPlanHandling(); + initializeCreateComponentKeybinding(); + resetProject(); - // Sort the children alphabetically - sortPresentations(); - } - } + Platform.runLater(() -> { + final var initializedModelPresentation = modelPresentationMap.keySet().stream().filter(mp -> mp instanceof ComponentPresentation).findFirst().orElse(null); + Ecdar.getPresentation().getController().getEditorPresentation().getController().setActiveModelPresentationForActiveCanvas(initializedModelPresentation); }); + } - Ecdar.getProject().getTempComponents().addListener((ListChangeListener<Component>) c -> { - while (c.next()) { - c.getAddedSubList().forEach(this::handleAddedModel); - c.getRemoved().forEach(this::handleRemovedModel); - - generatedComponentsDivider.setVisible(!tempFilesList.getChildren().isEmpty()); + private void initializeSystemHandling() { + project.getSystems().addListener((ListChangeListener<EcdarSystem>) change -> { + while (change.next()) { + change.getAddedSubList().forEach(o -> handleAddedModelPresentation(new SystemPresentation(o))); + change.getRemoved().forEach(o -> handleRemovedModelPresentation(modelPresentationMap.keySet().stream().filter(modelPresentation -> modelPresentation.getController().getModel().equals(o)).findFirst().orElse(null))); // Sort the children alphabetically sortPresentations(); } }); + } - Ecdar.getProject().getComponents().forEach(this::handleAddedModel); + private void initializeComponentHandling() { + project.getComponents().addListener(getComponentListChangeListener()); + project.getTempComponents().addListener(getComponentListChangeListener()); + } - // Listen to added and removed systems - Ecdar.getProject().getSystemsProperty().addListener((ListChangeListener<EcdarSystem>) change -> { - while (change.next()) { - change.getAddedSubList().forEach(this::handleAddedModel); - change.getRemoved().forEach(this::handleRemovedModel); + private ListChangeListener<Component> getComponentListChangeListener() { + return c -> { + while (c.next()) { + c.getAddedSubList().forEach(o -> handleAddedModelPresentation(new ComponentPresentation(o))); + c.getRemoved().forEach(o -> handleRemovedModelPresentation(modelPresentationMap.keySet().stream().filter(modelPresentation -> modelPresentation.getController().getModel().equals(o)).findFirst().orElse(null))); // Sort the children alphabetically sortPresentations(); } - }); - - initializeMutationTestPlanHandling(); + }; } private void initializeMutationTestPlanHandling() { - Ecdar.getProject().getTestPlans().addListener((ListChangeListener<MutationTestPlan>) change -> { + project.getTestPlans().addListener((ListChangeListener<MutationTestPlan>) change -> { while (change.next()) { - change.getAddedSubList().forEach(this::handleAddedModel); - change.getRemoved().forEach(this::handleRemovedModel); + change.getAddedSubList().forEach(o -> handleAddedModelPresentation(new MutationTestPlanPresentation(o))); + change.getRemoved().forEach(o -> handleRemovedModelPresentation(new MutationTestPlanPresentation(o))); // Sort the children alphabetically sortPresentations(); @@ -119,15 +130,20 @@ private void initializeMutationTestPlanHandling() { } private void sortPresentations() { - final ArrayList<HighLevelModelObject> sortedComponentList = new ArrayList<>(modelPresentationMap.keySet()); - sortedComponentList.sort(Comparator.comparing(HighLevelModelObject::getName)); - sortedComponentList.forEach(component -> modelPresentationMap.get(component).toFront()); + Platform.runLater(() -> { + final ArrayList<HighLevelModelPresentation> sortedComponentList = new ArrayList<>(modelPresentationMap.keySet()); + sortedComponentList.sort(Comparator.comparing(o -> o.getController().getModel().getName())); + sortedComponentList.forEach(component -> modelPresentationMap.get(component).toFront()); + + var globalDec = modelPresentationMap.keySet().stream().filter(hp -> hp instanceof DeclarationsPresentation).findFirst().orElse(null); + modelPresentationMap.get(globalDec).toBack(); + }); } private void initializeMoreInformationDropDown(final FilePresentation filePresentation) { final JFXRippler moreInformation = (JFXRippler) filePresentation.lookup("#moreInformation"); final DropDownMenu moreInformationDropDown = new DropDownMenu(moreInformation); - final HighLevelModelObject model = filePresentation.getModel(); + final HighLevelModel model = filePresentation.getController().getModel(); // If component, added toggle for periodic check if (model instanceof Component) { @@ -166,13 +182,13 @@ private void initializeMoreInformationDropDown(final FilePresentation filePresen // Add color picker if (model instanceof Component) { moreInformationDropDown.addColorPicker( - filePresentation.getModel(), - ((Component) filePresentation.getModel())::dye + model, + ((Component) model)::dye ); } else if (model instanceof EcdarSystem) { moreInformationDropDown.addColorPicker( - filePresentation.getModel(), - ((EcdarSystem) filePresentation.getModel())::dye + model, + ((EcdarSystem) model)::dye ); } @@ -180,54 +196,52 @@ private void initializeMoreInformationDropDown(final FilePresentation filePresen if (model instanceof Component) { moreInformationDropDown.addSpacerElement(); - if (!filePresentation.getModel().isTemporary()) { + if (!filePresentation.getController().getModel().isTemporary()) { moreInformationDropDown.addClickableListElement("Delete", event -> { UndoRedoStack.pushAndPerform(() -> { // Perform - Ecdar.getProject().getComponents().remove(model); + project.getComponents().remove(model); }, () -> { // Undo - Ecdar.getProject().getComponents().add((Component) model); + project.addComponent((Component) model); }, "Deleted component " + model.getName(), "delete"); moreInformationDropDown.hide(); }); } else { moreInformationDropDown.addClickableListElement("Delete", event -> { UndoRedoStack.pushAndPerform(() -> { // Perform - Ecdar.getProject().getTempComponents().remove(model); + project.getTempComponents().remove(model); }, () -> { // Undo - Ecdar.getProject().getTempComponents().add((Component) model); + project.getTempComponents().add((Component) model); }, "Deleted component " + model.getName(), "delete"); moreInformationDropDown.hide(); }); - + moreInformationDropDown.addClickableListElement("Add as component", event -> { - if(Ecdar.getProject().getComponents().stream().noneMatch(component -> component.getName().equals(model.getName()))) { + if (project.getComponents().stream().noneMatch(component -> component.getName().equals(model.getName()))) { UndoRedoStack.pushAndPerform(() -> { // Perform - Ecdar.getProject().getTempComponents().remove(model); + project.getTempComponents().remove(model); model.setTemporary(false); - Ecdar.getProject().getComponents().add((Component) model); - EcdarController.setActiveModelForActiveCanvas(model); + project.addComponent((Component) model); }, () -> { // Undo - Ecdar.getProject().getComponents().remove(model); + project.getComponents().remove(model); model.setTemporary(true); - Ecdar.getProject().getTempComponents().add((Component) model); - EcdarController.setActiveModelForActiveCanvas(model); + project.getTempComponents().add((Component) model); }, "Add component " + model.getName(), "add"); moreInformationDropDown.hide(); } else { String originalModelName = model.getName(); + // Get new model number starting from 2 to symbolize second version for (int i = 2; i < 100; i++) { final String newName = originalModelName + " #" + i; - if(Ecdar.getProject().getComponents().stream().noneMatch(component -> component.getName().equals(newName))) { + if (project.getComponents().stream().noneMatch(component -> component.getName().equals(newName))) { UndoRedoStack.pushAndPerform(() -> { // Perform - Ecdar.getProject().getTempComponents().remove(model); + project.getTempComponents().remove(model); model.setTemporary(false); - Ecdar.getProject().getComponents().add((Component) model); - EcdarController.setActiveModelForActiveCanvas(model); + project.addComponent((Component) model); model.setName(newName); }, () -> { // Undo - Ecdar.getProject().getComponents().remove(model); + project.getComponents().remove(model); model.setTemporary(true); - Ecdar.getProject().getTempComponents().add((Component) model); + project.getTempComponents().add((Component) model); model.setName(originalModelName); }, "Add component " + model.getName(), "add"); moreInformationDropDown.hide(); @@ -244,9 +258,9 @@ private void initializeMoreInformationDropDown(final FilePresentation filePresen moreInformationDropDown.addSpacerElement(); moreInformationDropDown.addClickableListElement("Delete", event -> { UndoRedoStack.pushAndPerform(() -> { // Perform - Ecdar.getProject().getSystemsProperty().remove(model); + project.getSystems().remove(model); }, () -> { // Undo - Ecdar.getProject().getSystemsProperty().add((EcdarSystem) model); + project.getSystems().add((EcdarSystem) model); }, "Deleted system " + model.getName(), "delete"); moreInformationDropDown.hide(); }); @@ -265,9 +279,9 @@ private void initializeMoreInformationDropDown(final FilePresentation filePresen // Delete button for test plan moreInformationDropDown.addClickableListElement("Delete", event -> { UndoRedoStack.pushAndPerform(() -> { // Perform - Ecdar.getProject().getTestPlans().remove(model); + project.getTestPlans().remove(model); }, () -> { // Undo - Ecdar.getProject().getTestPlans().add((MutationTestPlan) model); + project.getTestPlans().add((MutationTestPlan) model); }, "Deleted test plan " + model.getName(), "delete"); moreInformationDropDown.hide(); }); @@ -292,83 +306,183 @@ private void initializeTogglePeriodicCheck(DropDownMenu moreInformationDropDown, }); } - private void handleAddedModel(final HighLevelModelObject model) { - final FilePresentation filePresentation = new FilePresentation(model); + private void initializeCreateComponentKeybinding() { + //Press ctrl+N or cmd+N to create a new component. The canvas changes to this new component + KeyCodeCombination combination = new KeyCodeCombination(KeyCode.N, KeyCombination.SHORTCUT_DOWN); + Keybind binding = new Keybind(combination, (event) -> { + final Component newComponent = new Component(getAvailableColor(), getUniqueComponentName()); + UndoRedoStack.pushAndPerform(() -> { // Perform + project.addComponent(newComponent); + }, () -> { // Undo + project.getComponents().remove(newComponent); + }, "Created new component: " + newComponent.getName(), "add-circle"); + + Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasPresentation().getController().setActiveModelPresentation(getComponentPresentations().stream().filter(componentPresentation -> componentPresentation.getController().getComponent().equals(newComponent)).findFirst().orElse(null)); + }); + KeyboardTracker.registerKeybind(KeyboardTracker.CREATE_COMPONENT, binding); + } + + private void handleAddedModelPresentation(final HighLevelModelPresentation modelPresentation) { + final FilePresentation filePresentation = new FilePresentation(modelPresentation.getController().getModel()); initializeMoreInformationDropDown(filePresentation); - // Add the file presentation related to the model to the project pane - if (model.isTemporary()) { + // Add the file presentation related to the modelPresentation to the project pane + if (modelPresentation.getController().getModel().isTemporary()) { tempFilesList.getChildren().add(filePresentation); } else { filesList.getChildren().add(filePresentation); } - modelPresentationMap.put(model, filePresentation); - // Open the component if the presentation is pressed + modelPresentationMap.put(modelPresentation, filePresentation);// ToDo NIELS: Bind these two + if (modelPresentation instanceof ComponentPresentation) { + componentPresentations.add((ComponentPresentation) modelPresentation); + } + + // Open the component if the file is pressed filePresentation.setOnMousePressed(event -> { - event.consume(); - EcdarController.setActiveModelForActiveCanvas(model); - updateColorsOnFilePresentations(); + final var previouslyActiveFile = modelPresentationMap.get(Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasPresentation() + .getController() + .getActiveModelPresentation()); + if (previouslyActiveFile != null) previouslyActiveFile.getController().setIsActive(false); + + Ecdar.getPresentation().getController().getEditorPresentation().getController().setActiveModelPresentationForActiveCanvas(modelPresentation); + Platform.runLater(() -> { + filePresentation.getController().setIsActive(true); + }); }); - model.nameProperty().addListener(obs -> sortPresentations()); + modelPresentation.getController().getModel().nameProperty().addListener(obs -> sortPresentations()); + filePresentation.getController().setIsActive(true); + Platform.runLater(() -> Ecdar.getPresentation().getController().getEditorPresentation().getController().setActiveModelPresentationForActiveCanvas(modelPresentation)); } - private void handleRemovedModel(final HighLevelModelObject model) { - // If we remove the model active on the canvas - if (EcdarController.getActiveCanvasPresentation().getController().getActiveModel() == model) { - if (Ecdar.getProject().getComponents().size() > 0) { + private void handleRemovedModelPresentation(final HighLevelModelPresentation modelPresentation) { + // If we remove the modelPresentation active on the canvas + if (Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasPresentation().getController().getActiveModelPresentation() == modelPresentation) { + if (project.getComponents().size() > 0) { // Find the first available component and show it instead of the removed one - final Component component = Ecdar.getProject().getComponents().get(0); - EcdarController.setActiveModelForActiveCanvas(component); - updateColorsOnFilePresentations(); + final HighLevelModelPresentation newActiveModelPresentation = modelPresentationMap.keySet().iterator().next(); + Ecdar.getPresentation().getController().getEditorPresentation().getController().setActiveModelPresentationForActiveCanvas(newActiveModelPresentation); + modelPresentationMap.get(newActiveModelPresentation).getController().setIsActive(true); } else { // Show no components (since there are none in the project) - EcdarController.setActiveModelForActiveCanvas(null); + Ecdar.getPresentation().getController().getEditorPresentation().getController().setActiveModelPresentationForActiveCanvas(null); } } // Remove the file presentation related to the model from the project pane - if (model.isTemporary()) { - tempFilesList.getChildren().remove(modelPresentationMap.get(model)); + if (modelPresentation.getController().getModel().isTemporary()) { + tempFilesList.getChildren().removeIf(n -> n == modelPresentationMap.get(modelPresentation)); } else { - filesList.getChildren().remove(modelPresentationMap.get(model)); + filesList.getChildren().removeIf(n -> n == modelPresentationMap.get(modelPresentation)); } - modelPresentationMap.remove(model); + + modelPresentationMap.remove(modelPresentation); } /** - * Update the color of all FilePresentations to display currently active components + * Resets components. + * After this, there is only one component. + * Be sure to disable code analysis before call and enable after call. */ - public void updateColorsOnFilePresentations() { - for (Node child : filesList.getChildren()) { - if (child instanceof FilePresentation) { - Platform.runLater(() -> ((FilePresentation) child).updateColors()); + public void resetProject() { + project.clean(); + project.addComponent(new Component(getAvailableColor(), getUniqueComponentName())); + } + + public EnabledColor getAvailableColor() { + ArrayList<EnabledColor> availableColors = new ArrayList<>(EnabledColor.enabledColors); + for (Component comp : project.getComponents()) { + availableColors.removeIf(c -> comp.getColor().equals(c)); + } + + if (availableColors.isEmpty()) { + return EnabledColor.enabledColors.get(new Random().nextInt(EnabledColor.enabledColors.size())); + } + + return availableColors.get(0); + } + + /** + * Gets the name of all components in the project and inserts it into a set + * + * @return the set of all component names + */ + private HashSet<String> getComponentNames() { + final HashSet<String> names = new HashSet<>(); + + for (final Component component : project.getComponents()) { + names.add(component.getName()); + } + + return names; + } + + /** + * Generate a unique name for the component + * + * @return A project unique name + */ + public String getUniqueComponentName() { + for (int counter = 1; ; counter++) { + final String name = Project.COMPONENT + counter; + if (!getComponentNames().contains(name)) { + return name; } } + } - for (Node child : tempFilesList.getChildren()) { - if (child instanceof FilePresentation) { - ((FilePresentation) child).updateColors(); + public String getUniqueSystemName() { + for (int counter = 1; ; counter++) { + final String name = Project.SYSTEM + counter; + if (!getSystemNames().contains(name)) { + return name; } } } + private HashSet<String> getSystemNames() { + final HashSet<String> names = new HashSet<>(); + + for (final EcdarSystem component : project.getSystems()) { + names.add(component.getName()); + } + + return names; + } + + public ObservableList<ComponentPresentation> getComponentPresentations() { + return componentPresentations; + } + + public void setHighlightedForModelFiles(List<HighLevelModelPresentation> currentlyActiveModelPresentations) { + modelPresentationMap.values().forEach(fp -> fp.getController().setIsActive(false)); + + for (HighLevelModelPresentation modelPresentation : currentlyActiveModelPresentations) { + if (modelPresentationMap.containsKey(modelPresentation)) modelPresentationMap.get(modelPresentation).getController().setIsActive(true); + } + } + + public void swapHighlightBetweenTwoModelFiles(final HighLevelModelPresentation oldActive, final HighLevelModelPresentation newActive) { + if (modelPresentationMap.containsKey(oldActive)) modelPresentationMap.get(oldActive) + .getController() + .setIsActive(false); + + if (modelPresentationMap.containsKey(newActive)) modelPresentationMap.get(newActive).getController().setIsActive(true); // newActive is not in the map when opening an existing project + } + /** * Method for creating a new component */ @FXML private void createComponentClicked() { - final Component newComponent = new Component(true); + final Component newComponent = new Component(getAvailableColor(), getUniqueComponentName()); UndoRedoStack.pushAndPerform(() -> { // Perform - Ecdar.getProject().getComponents().add(newComponent); - EcdarController.setActiveModelForActiveCanvas(newComponent); + project.addComponent(newComponent); }, () -> { // Undo - Ecdar.getProject().getComponents().remove(newComponent); + project.getComponents().remove(newComponent); }, "Created new component: " + newComponent.getName(), "add-circle"); - - updateColorsOnFilePresentations(); } /** @@ -376,13 +490,12 @@ private void createComponentClicked() { */ @FXML private void createSystemClicked() { - final EcdarSystem newSystem = new EcdarSystem(); + final EcdarSystem newSystem = new EcdarSystem(getAvailableColor(), getUniqueSystemName()); UndoRedoStack.pushAndPerform(() -> { // Perform - Ecdar.getProject().getSystemsProperty().add(newSystem); - EcdarController.setActiveModelForActiveCanvas(newSystem); + project.getSystems().add(newSystem); }, () -> { // Undo - Ecdar.getProject().getSystemsProperty().remove(newSystem); + project.getSystems().remove(newSystem); }, "Created new system: " + newSystem.getName(), "add-circle"); } diff --git a/src/main/java/ecdar/controllers/QueryController.java b/src/main/java/ecdar/controllers/QueryController.java index 53ec4f68..7602378e 100644 --- a/src/main/java/ecdar/controllers/QueryController.java +++ b/src/main/java/ecdar/controllers/QueryController.java @@ -2,8 +2,8 @@ import com.jfoenix.controls.JFXComboBox; import com.jfoenix.controls.JFXRippler; +import ecdar.backend.Engine; import com.jfoenix.controls.JFXTextField; -import ecdar.backend.BackendInstance; import ecdar.abstractions.Query; import ecdar.abstractions.QueryType; import ecdar.backend.BackendHelper; @@ -27,7 +27,7 @@ public class QueryController implements Initializable { public JFXRippler actionButton; public JFXRippler queryTypeExpand; public Text queryTypeSymbol; - public JFXComboBox<BackendInstance> backendsDropdown; + public JFXComboBox<Engine> enginesDropdown; private Query query; private final Map<QueryType, SimpleBooleanProperty> queryTypeListElementsSelectedState = new HashMap<>(); private final Tooltip noQueryTypeSetTooltip = new Tooltip("Please select a query type beneath the status icon"); @@ -38,12 +38,7 @@ public class QueryController implements Initializable { public void initialize(URL location, ResourceBundle resources) { initializeActionButton(); - queryText.textProperty().addListener(new ChangeListener<String>() { - @Override - public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) { - queryText.setText(StringHelper.ConvertSymbolsToUnicode(newValue)); - } - }); + queryText.textProperty().addListener((observable, oldValue, newValue) -> queryText.setText(StringHelper.ConvertSymbolsToUnicode(newValue))); } public void setQuery(Query query) { @@ -64,29 +59,29 @@ public void setQuery(Query query) { } })); - if (BackendHelper.getBackendInstances().contains(query.getBackend())) { - backendsDropdown.setValue(query.getBackend()); + if (BackendHelper.getEngines().contains(query.getEngine())) { + enginesDropdown.setValue(query.getEngine()); } else { - backendsDropdown.setValue(BackendHelper.getDefaultBackendInstance()); + enginesDropdown.setValue(BackendHelper.getDefaultEngine()); } - backendsDropdown.valueProperty().addListener((observable, oldValue, newValue) -> { + enginesDropdown.valueProperty().addListener((observable, oldValue, newValue) -> { if (newValue != null) { - query.setBackend(newValue); + query.setEngine(newValue); } else { - backendsDropdown.setValue(BackendHelper.getDefaultBackendInstance()); + enginesDropdown.setValue(BackendHelper.getDefaultEngine()); } }); - BackendHelper.addBackendInstanceListener(() -> { + BackendHelper.addEngineInstanceListener(() -> { Platform.runLater(() -> { // The value must be set before the items (https://stackoverflow.com/a/29483445) - if (BackendHelper.getBackendInstances().contains(query.getBackend())) { - backendsDropdown.setValue(query.getBackend()); + if (BackendHelper.getEngines().contains(query.getEngine())) { + enginesDropdown.setValue(query.getEngine()); } else { - backendsDropdown.setValue(BackendHelper.getDefaultBackendInstance()); + enginesDropdown.setValue(BackendHelper.getDefaultEngine()); } - backendsDropdown.setItems(BackendHelper.getBackendInstances()); + enginesDropdown.setItems(BackendHelper.getEngines()); }); }); } diff --git a/src/main/java/ecdar/controllers/QueryPaneController.java b/src/main/java/ecdar/controllers/QueryPaneController.java index 690aad5a..a718b2df 100644 --- a/src/main/java/ecdar/controllers/QueryPaneController.java +++ b/src/main/java/ecdar/controllers/QueryPaneController.java @@ -6,6 +6,7 @@ import ecdar.presentations.QueryPresentation; import com.jfoenix.controls.JFXRippler; import ecdar.utility.colors.Color; +import javafx.application.Platform; import javafx.collections.ListChangeListener; import javafx.fxml.FXML; import javafx.fxml.Initializable; @@ -36,25 +37,26 @@ public class QueryPaneController implements Initializable { @Override public void initialize(final URL location, final ResourceBundle resources) { - Ecdar.getProject().getQueries().addListener((ListChangeListener<Query>) change -> { - while (change.next()) { - for (final Query removeQuery : change.getRemoved()) { - queriesList.getChildren().remove(queryPresentationMap.get(removeQuery)); - queryPresentationMap.remove(removeQuery); - } + Platform.runLater(() -> { + Ecdar.getProject().getQueries().addListener((ListChangeListener<Query>) change -> { + while (change.next()) { + for (final Query removeQuery : change.getRemoved()) { + queriesList.getChildren().remove(queryPresentationMap.get(removeQuery)); + queryPresentationMap.remove(removeQuery); + } - for (final Query newQuery : change.getAddedSubList()) { - final QueryPresentation newQueryPresentation = new QueryPresentation(newQuery); - queryPresentationMap.put(newQuery, newQueryPresentation); - queriesList.getChildren().add(newQueryPresentation); + for (final Query newQuery : change.getAddedSubList()) { + final QueryPresentation newQueryPresentation = new QueryPresentation(newQuery); + queryPresentationMap.put(newQuery, newQueryPresentation); + queriesList.getChildren().add(newQueryPresentation); + } } + }); + for (final Query newQuery : Ecdar.getProject().getQueries()) { + queriesList.getChildren().add(new QueryPresentation(newQuery)); } }); - for (final Query newQuery : Ecdar.getProject().getQueries()) { - queriesList.getChildren().add(new QueryPresentation(newQuery)); - } - initializeResizeAnchor(); } @@ -78,7 +80,7 @@ private void runAllQueriesButtonClicked() { Ecdar.getProject().getQueries().forEach(query -> { if (query.getType() == null) return; query.cancel(); - Ecdar.getQueryExecutor().executeQuery(query); + query.execute(); }); } diff --git a/src/main/java/ecdar/controllers/SimEdgeController.java b/src/main/java/ecdar/controllers/SimEdgeController.java index ee83c513..d2e35a8e 100755 --- a/src/main/java/ecdar/controllers/SimEdgeController.java +++ b/src/main/java/ecdar/controllers/SimEdgeController.java @@ -10,6 +10,7 @@ import ecdar.presentations.SimNailPresentation; import ecdar.utility.Highlightable; import ecdar.utility.colors.Color; +import ecdar.utility.colors.EnabledColor; import ecdar.utility.helpers.BindingHelper; import ecdar.utility.helpers.Circular; import ecdar.utility.helpers.ItemDragHelper; @@ -371,24 +372,18 @@ public ObjectProperty<Component> componentProperty() { * Colors the edge model * * @param color the new color of the edge - * @param intensity the intensity of the edge */ - public void color(final Color color, final Color.Intensity intensity) { + public void color(final EnabledColor color) { final Edge edge = getEdge(); // Set the color of the edge - edge.setColorIntensity(intensity); edge.setColor(color); } - public Color getColor() { + public EnabledColor getColor() { return getEdge().getColor(); } - public Color.Intensity getColorIntensity() { - return getEdge().getColorIntensity(); - } - public ItemDragHelper.DragBounds getDragBounds() { return ItemDragHelper.DragBounds.generateLooseDragBounds(); } diff --git a/src/main/java/ecdar/controllers/SimLocationController.java b/src/main/java/ecdar/controllers/SimLocationController.java index 9921a057..1bd0d1d8 100755 --- a/src/main/java/ecdar/controllers/SimLocationController.java +++ b/src/main/java/ecdar/controllers/SimLocationController.java @@ -1,14 +1,13 @@ package ecdar.controllers; import com.jfoenix.controls.JFXPopup; -import ecdar.Ecdar; import ecdar.abstractions.*; -import ecdar.backend.BackendHelper; import ecdar.backend.SimulationHandler; import ecdar.presentations.DropDownMenu; import ecdar.presentations.SimLocationPresentation; import ecdar.presentations.SimTagPresentation; -import ecdar.utility.colors.Color; +import ecdar.simulation.SimulationState; +import ecdar.utility.colors.EnabledColor; import javafx.beans.property.DoubleProperty; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; @@ -46,6 +45,109 @@ public class SimLocationController implements Initializable { private DropDownMenu dropDownMenu; private SimulationHandler simulationHandler; + public static String getSimLocationReachableQuery(final Location endLocation, final Component component, final String query) { + return getSimLocationReachableQuery(endLocation, component, query, null); + } + + /** + * Generates a reachability query based on the given location and component + * + * @param endLocation The location which should be checked for reachability + * @return A reachability query string + */ + public static String getSimLocationReachableQuery(final Location endLocation, final Component component, final String query, final SimulationState state) { + var stringBuilder = new StringBuilder(); + + // append simulation query + stringBuilder.append(query); + + // append arrow + stringBuilder.append(" -> "); + + // ToDo: append start location here + if (state != null){ + stringBuilder.append(getStartStateString(state)); + stringBuilder.append(";"); + } + + // append end state + stringBuilder.append(getEndStateString(component.getName(), endLocation.getId())); + + // return example: m1||M2->[L1,L4](y<3);[L2, L7](y<2) + System.out.println(stringBuilder); + return stringBuilder.toString(); + } + + private static String getStartStateString(SimulationState state) { + var stringBuilder = new StringBuilder(); + + // append locations + var locations = state.getLocations(); + stringBuilder.append("["); + var appendLocationWithSeparator = false; + for(var componentName : SimulatorController.getSimulationHandler().getComponentsInSimulation()){ + var locationFound = false; + + for(var location:locations){ + if (location.getKey().equals(componentName)){ + if (appendLocationWithSeparator){ + stringBuilder.append("," + location.getValue()); + } + else{ + stringBuilder.append(location.getValue()); + } + locationFound = true; + } + if (locationFound){ + // don't go through more locations, when a location is found for the specific component that we're looking at + break; + } + } + appendLocationWithSeparator = true; + } + stringBuilder.append("]"); + + // append clock values + var clocks = state.getSimulationClocks(); + stringBuilder.append("()"); + + return stringBuilder.toString(); + } + + private static String getEndStateString(String componentName, String endLocationId) { + var stringBuilder = new StringBuilder(); + + stringBuilder.append("["); + var appendLocationWithSeparator = false; + + for (var component : SimulatorController.getSimulationHandler().getComponentsInSimulation()) + { + if (component.equals(componentName)){ + if (appendLocationWithSeparator){ + stringBuilder.append("," + endLocationId); + } + else{ + stringBuilder.append(endLocationId); + } + } + else{ // add underscore to indicate, that we don't care about the end locations in the other components + if (appendLocationWithSeparator){ + stringBuilder.append(",_"); + } + else{ + stringBuilder.append("_"); + } + } + if (!appendLocationWithSeparator) { + appendLocationWithSeparator = true; + } + } + stringBuilder.append("]"); + stringBuilder.append("()"); + + return stringBuilder.toString(); + } + @Override public void initialize(final URL location, final ResourceBundle resources) { this.location.addListener((obsLocation, oldLocation, newLocation) -> { @@ -60,7 +162,7 @@ public void initialize(final URL location, final ResourceBundle resources) { scaleContent.scaleYProperty().bind(scaleContent.scaleXProperty()); initializeMouseControls(); - simulationHandler = Ecdar.getSimulationHandler(); + simulationHandler = SimulatorController.getSimulationHandler(); } private void initializeMouseControls() { @@ -88,7 +190,7 @@ public void initializeDropDownMenu(){ dropDownMenu.addClickableListElement("Is " + getLocation().getId() + " reachable from initial state?", event -> { // Generate the query from the backend - final String reachabilityQuery = BackendHelper.getLocationReachableQuery(getLocation(), getComponent(), simulationHandler.getSimulationQuery()); + final String reachabilityQuery = getSimLocationReachableQuery(getLocation(), getComponent(), simulationHandler.getSimulationQuery()); // Add proper comment final String reachabilityComment = "Is " + getLocation().getMostDescriptiveIdentifier() + " reachable from initial state?"; @@ -98,14 +200,14 @@ public void initializeDropDownMenu(){ query.setType(QueryType.REACHABILITY); // execute query - Ecdar.getQueryExecutor().executeQuery(query); + query.execute(); dropDownMenu.hide(); }); dropDownMenu.addClickableListElement("Is " + getLocation().getId() + " reachable from current locations?", event -> { // Generate the query from the backend - final String reachabilityQuery = BackendHelper.getLocationReachableQuery(getLocation(), getComponent(), simulationHandler.getSimulationQuery(), simulationHandler.getCurrentState()); + final String reachabilityQuery = getSimLocationReachableQuery(getLocation(), getComponent(), simulationHandler.getSimulationQuery(), simulationHandler.getCurrentState()); // Add proper comment final String reachabilityComment = "Is " + getLocation().getMostDescriptiveIdentifier() + " reachable from current locations?"; @@ -115,7 +217,7 @@ public void initializeDropDownMenu(){ query.setType(QueryType.REACHABILITY); // execute query - Ecdar.getQueryExecutor().executeQuery(query); + query.execute(); dropDownMenu.hide(); }); @@ -157,24 +259,18 @@ public ObjectProperty<Component> componentProperty() { /** * Colors the location model * @param color the new color of the location - * @param intensity the intensity of the color */ - public void color(final Color color, final Color.Intensity intensity) { + public void color(final EnabledColor color) { final Location location = getLocation(); // Set the color of the location - location.setColorIntensity(intensity); location.setColor(color); } - public Color getColor() { + public EnabledColor getColor() { return getLocation().getColor(); } - public Color.Intensity getColorIntensity() { - return getLocation().getColorIntensity(); - } - public DoubleProperty xProperty() { return root.layoutXProperty(); } diff --git a/src/main/java/ecdar/controllers/SimNailController.java b/src/main/java/ecdar/controllers/SimNailController.java index 053456d3..ca4546cc 100755 --- a/src/main/java/ecdar/controllers/SimNailController.java +++ b/src/main/java/ecdar/controllers/SimNailController.java @@ -7,6 +7,7 @@ import ecdar.presentations.SimNailPresentation; import ecdar.presentations.SimTagPresentation; import ecdar.utility.colors.Color; +import ecdar.utility.colors.EnabledColor; import javafx.beans.property.DoubleProperty; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; @@ -100,13 +101,10 @@ public ObjectProperty<Edge> edgeProperty() { return edge; } - public Color getColor() { + public EnabledColor getColor() { return getComponent().getColor(); } - public Color.Intensity getColorIntensity() { - return getComponent().getColorIntensity(); - } public DoubleProperty xProperty() { return root.layoutXProperty(); diff --git a/src/main/java/ecdar/controllers/SimulationInitializationDialogController.java b/src/main/java/ecdar/controllers/SimulationInitializationDialogController.java index 6d5c017b..4aa189f6 100644 --- a/src/main/java/ecdar/controllers/SimulationInitializationDialogController.java +++ b/src/main/java/ecdar/controllers/SimulationInitializationDialogController.java @@ -42,6 +42,6 @@ public void setSimulationData(){ } public void initialize(URL location, ResourceBundle resources) { - simulationHandler = Ecdar.getSimulationHandler(); + simulationHandler = SimulatorController.getSimulationHandler(); } } diff --git a/src/main/java/ecdar/controllers/SimulatorController.java b/src/main/java/ecdar/controllers/SimulatorController.java index f2689c0b..231ab789 100644 --- a/src/main/java/ecdar/controllers/SimulatorController.java +++ b/src/main/java/ecdar/controllers/SimulatorController.java @@ -3,6 +3,8 @@ import ecdar.Ecdar; import ecdar.abstractions.*; import ecdar.backend.SimulationHandler; +import ecdar.presentations.LeftSimPanePresentation; +import ecdar.presentations.RightSimPanePresentation; import ecdar.presentations.SimulatorOverviewPresentation; import ecdar.simulation.SimulationState; import javafx.beans.property.DoubleProperty; @@ -17,12 +19,14 @@ import java.util.List; import java.util.ResourceBundle; -public class SimulatorController implements Initializable { +public class SimulatorController implements ModeController, Initializable { public StackPane root; - private SimulationHandler simulationHandler; + public static SimulationHandler simulationHandler = new SimulationHandler(); public SimulatorOverviewPresentation overviewPresentation; public StackPane toolbar; + public final LeftSimPanePresentation leftSimPane = new LeftSimPanePresentation(); + public final RightSimPanePresentation rightSimPane = new RightSimPanePresentation(); private boolean firstTimeInSimulator; private final static DoubleProperty width = new SimpleDoubleProperty(), height = new SimpleDoubleProperty(); @@ -33,7 +37,12 @@ public void initialize(URL location, ResourceBundle resources) { root.widthProperty().addListener((observable, oldValue, newValue) -> width.setValue(newValue)); root.heightProperty().addListener((observable, oldValue, newValue) -> height.setValue(newValue)); firstTimeInSimulator = true; - simulationHandler = Ecdar.getSimulationHandler(); + } + + public static SimulationHandler getSimulationHandler() { return simulationHandler; } + + public static void setSimulationHandler(SimulationHandler simHandler) { + simulationHandler = simHandler; } /** @@ -128,4 +137,14 @@ public static DoubleProperty getHeightProperty() { public static void setSelectedState(SimulationState selectedState) { SimulatorController.selectedState.set(selectedState); } + + @Override + public StackPane getLeftPane() { + return leftSimPane; + } + + @Override + public StackPane getRightPane() { + return rightSimPane; + } } diff --git a/src/main/java/ecdar/controllers/SimulatorOverviewController.java b/src/main/java/ecdar/controllers/SimulatorOverviewController.java index bb3678b7..0f2bf7f8 100644 --- a/src/main/java/ecdar/controllers/SimulatorOverviewController.java +++ b/src/main/java/ecdar/controllers/SimulatorOverviewController.java @@ -1,6 +1,5 @@ package ecdar.controllers; -import ecdar.Ecdar; import ecdar.abstractions.*; import ecdar.backend.SimulationHandler; import ecdar.presentations.ProcessPresentation; @@ -67,7 +66,7 @@ public class SimulatorOverviewController implements Initializable { @Override public void initialize(final URL location, final ResourceBundle resources) { - simulationHandler = Ecdar.getSimulationHandler(); + simulationHandler = SimulatorController.getSimulationHandler(); groupContainer = new Group(); processContainer = new FlowPane(); diff --git a/src/main/java/ecdar/controllers/SystemController.java b/src/main/java/ecdar/controllers/SystemController.java index 5720d06c..49a0f596 100644 --- a/src/main/java/ecdar/controllers/SystemController.java +++ b/src/main/java/ecdar/controllers/SystemController.java @@ -4,6 +4,8 @@ import ecdar.abstractions.*; import ecdar.presentations.*; import ecdar.utility.UndoRedoStack; +import ecdar.utility.colors.Color; +import ecdar.utility.colors.EnabledColor; import ecdar.utility.helpers.SelectHelper; import com.jfoenix.controls.JFXPopup; import javafx.beans.property.ObjectProperty; @@ -11,15 +13,21 @@ import javafx.collections.ListChangeListener; import javafx.fxml.FXML; import javafx.fxml.Initializable; +import javafx.geometry.Insets; +import javafx.geometry.Pos; import javafx.scene.input.MouseEvent; -import javafx.scene.layout.Pane; -import javafx.scene.shape.Circle; -import javafx.scene.shape.Line; +import javafx.scene.layout.*; +import javafx.scene.shape.*; import java.net.URL; import java.util.HashMap; import java.util.Map; import java.util.ResourceBundle; +import java.util.function.BiConsumer; +import java.util.function.Consumer; + +import static ecdar.Ecdar.CANVAS_PADDING; +import static ecdar.presentations.ModelPresentation.*; /** * Controller for a system. @@ -34,21 +42,13 @@ public class SystemController extends ModelController implements Initializable { private final Map<ComponentInstance, ComponentInstancePresentation> componentInstancePresentationMap = new HashMap<>(); private final Map<ComponentOperator, ComponentOperatorPresentation> componentOperatorPresentationMap = new HashMap<>(); - private final Map<EcdarSystemEdge, SystemEdgePresentation> edgePresentationMap = new HashMap<>(); + private final Map<SystemEdge, SystemEdgePresentation> edgePresentationMap = new HashMap<>(); private final ObjectProperty<EcdarSystem> system = new SimpleObjectProperty<>(); private Circle dropDownMenuHelperCircle; private DropDownMenu contextMenu; - public EcdarSystem getSystem() { - return system.get(); - } - - public void setSystem(final EcdarSystem system) { - this.system.setValue(system); - } - @Override public void initialize(final URL location, final ResourceBundle resources) { // Initialize when system is added @@ -60,20 +60,136 @@ public void initialize(final URL location, final ResourceBundle resources) { initializeComponentInstanceHandling(newValue); initializeOperatorHandling(newValue); initializeEdgeHandling(newValue); + + super.initialize(newValue.getBox()); + initializeDimensions(newValue.getBox()); + + // Initialize methods that are sensitive to width and height + final Runnable onUpdateSize = () -> { + initializeToolbar(); + initializeFrame(); + initializeBackground(); + }; + + onUpdateSize.run(); + + // Re-run initialisation on update of width and height property + newValue.getBox().getWidthProperty().addListener(obs -> onUpdateSize.run()); + newValue.getBox().getHeightProperty().addListener(obs -> onUpdateSize.run()); }); } + public void setSystem(final EcdarSystem system) { + this.system.setValue(system); + } + + public EcdarSystem getSystem() { + return system.get(); + } + private void initializeSystemRoot(final EcdarSystem system) { systemRootContainer.getChildren().add(new SystemRootPresentation(system)); } + /** + * Initializes the toolbar. + */ + private void initializeToolbar() { + final Consumer<EnabledColor> updateColor = (newColor) -> { + // Set the background of the toolbar + toolbar.setBackground(new Background(new BackgroundFill( + newColor.getPaintColor(), + CornerRadii.EMPTY, + Insets.EMPTY + ))); + + toolbar.setPrefHeight(TOOLBAR_HEIGHT); + }; + + getSystem().colorProperty().addListener(observable -> updateColor.accept(getSystem().getColor())); + + updateColor.accept(getSystem().getColor()); + } + + /** + * Initializes the frame and handling of it. + * The frame is a rectangle minus two cutouts. + */ + private void initializeFrame() { + final Shape[] mask = new Shape[1]; + final Rectangle rectangle = new Rectangle(getSystem().getBox().getWidth(), getSystem().getBox().getHeight()); + + // Generate top right corner (to subtract) + final Polygon topRightCorner = new Polygon( + getSystem().getBox().getWidth(), 0, + getSystem().getBox().getWidth() - (CORNER_SIZE + 2), 0, + getSystem().getBox().getWidth(), CORNER_SIZE + 2 + ); + + final Consumer<EnabledColor> updateColor = (newColor) -> { + // Mask the parent of the frame (will also mask the background) + mask[0] = Path.subtract(rectangle, TOP_LEFT_CORNER); + mask[0] = Path.subtract(mask[0], topRightCorner); + frame.setClip(mask[0]); + background.setClip(Path.union(mask[0], mask[0])); + background.setOpacity(0.5); + + // Bind the missing lines that we cropped away + topLeftLine.setStartX(CORNER_SIZE); + topLeftLine.setStartY(0); + topLeftLine.setEndX(0); + topLeftLine.setEndY(CORNER_SIZE); + topLeftLine.setStroke(newColor.getStrokeColor()); + topLeftLine.setStrokeWidth(1.25); + StackPane.setAlignment(topLeftLine, Pos.TOP_LEFT); + + topRightLine.setStartX(0); + topRightLine.setStartY(0); + topRightLine.setEndX(CORNER_SIZE); + topRightLine.setEndY(CORNER_SIZE); + topRightLine.setStroke(newColor.getStrokeColor()); + topRightLine.setStrokeWidth(1.25); + StackPane.setAlignment(topRightLine, Pos.TOP_RIGHT); + + // Set the stroke color to two shades darker + frame.setBorder(new Border(new BorderStroke( + newColor.getStrokeColor(), + BorderStrokeStyle.SOLID, + CornerRadii.EMPTY, + new BorderWidths(1), + Insets.EMPTY + ))); + }; + + // Update now, and update on color change + updateColor.accept(getSystem().getColor()); + getSystem().colorProperty().addListener(observable -> updateColor.accept(getSystem().getColor())); + } + + /** + * Initializes the background + */ + private void initializeBackground() { + // Bind the background width and height to the values in the model + background.widthProperty().bindBidirectional(getSystem().getBox().getWidthProperty()); + background.heightProperty().bindBidirectional(getSystem().getBox().getHeightProperty()); + + final Consumer<EnabledColor> updateColor = (newColor) -> { + // Set the background color to the lightest possible version of the color + background.setFill(newColor.setIntensity(2).getPaintColor()); + }; + + getSystem().colorProperty().addListener(observable -> updateColor.accept(getSystem().getColor())); + updateColor.accept(getSystem().getColor()); + } + /** * Handles when tapping on the background of the system view. * @param event mouse event */ @FXML private void modelContainerPressed(final MouseEvent event) { - EcdarController.getActiveCanvasPresentation().getController().leaveTextAreas(); + Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasPresentation().getController().leaveTextAreas(); SelectHelper.clearSelectedElements(); if (event.isSecondaryButtonDown()) { @@ -87,7 +203,7 @@ private void modelContainerPressed(final MouseEvent event) { } @Override - public HighLevelModelObject getModel() { + public HighLevelModel getModel() { return getSystem(); } @@ -260,7 +376,7 @@ private void handleRemovedComponentOperator(final ComponentOperator operator) { */ private void initializeEdgeHandling(final EcdarSystem system) { system.getEdges().forEach(this::handleAddedEdge); - system.getEdges().addListener((ListChangeListener<EcdarSystemEdge>) change -> { + system.getEdges().addListener((ListChangeListener<SystemEdge>) change -> { if (change.next()) { change.getAddedSubList().forEach(this::handleAddedEdge); change.getRemoved().forEach(this::handleRemovedEdge); @@ -272,7 +388,7 @@ private void initializeEdgeHandling(final EcdarSystem system) { * Handles an added edge. * @param edge the edge */ - private void handleAddedEdge(final EcdarSystemEdge edge) { + private void handleAddedEdge(final SystemEdge edge) { final SystemEdgePresentation presentation = new SystemEdgePresentation(edge, getSystem()); edgePresentationMap.put(edge, presentation); edgeContainer.getChildren().add(presentation); @@ -282,7 +398,7 @@ private void handleAddedEdge(final EcdarSystemEdge edge) { * Handles a removed component instance. * @param edge the edge */ - private void handleRemovedEdge(final EcdarSystemEdge edge) { + private void handleRemovedEdge(final SystemEdge edge) { edgeContainer.getChildren().remove(edgePresentationMap.get(edge)); edgePresentationMap.remove(edge); @@ -309,4 +425,44 @@ void showBorderAndBackground() { super.showBorderAndBackground(); topRightLine.setVisible(true); } + + /** + * Gets the minimum allowed width when dragging the anchor. + * It is determined by the position and size of the system nodes. + * @return the minimum allowed width + */ + @Override + double getDragAnchorMinWidth() { + double minWidth = getSystem().getSystemRoot().getX() + SystemRoot.WIDTH + 2 * CANVAS_PADDING; + + for (final ComponentInstance instance : getSystem().getComponentInstances()) { + minWidth = Math.max(minWidth, instance.getBox().getX() + instance.getBox().getWidth() + CANVAS_PADDING); + } + + for (final ComponentOperator operator : getSystem().getComponentOperators()) { + minWidth = Math.max(minWidth, operator.getBox().getX() + operator.getBox().getWidth() + CANVAS_PADDING); + } + + return minWidth; + } + + /** + * Gets the minimum allowed height when dragging the anchor. + * It is determined by the position and size of the system nodes. + * @return the minimum allowed height + */ + @Override + double getDragAnchorMinHeight() { + double minHeight = 10 * CANVAS_PADDING; + + for (final ComponentInstance instance : getSystem().getComponentInstances()) { + minHeight = Math.max(minHeight, instance.getBox().getY() + instance.getBox().getHeight() + CANVAS_PADDING); + } + + for (final ComponentOperator operator : getSystem().getComponentOperators()) { + minHeight = Math.max(minHeight, operator.getBox().getY() + operator.getBox().getHeight() + CANVAS_PADDING); + } + + return minHeight; + } } diff --git a/src/main/java/ecdar/controllers/SystemEdgeController.java b/src/main/java/ecdar/controllers/SystemEdgeController.java index de846446..5901b169 100644 --- a/src/main/java/ecdar/controllers/SystemEdgeController.java +++ b/src/main/java/ecdar/controllers/SystemEdgeController.java @@ -1,7 +1,7 @@ package ecdar.controllers; import ecdar.abstractions.EcdarSystem; -import ecdar.abstractions.EcdarSystemEdge; +import ecdar.abstractions.SystemEdge; import ecdar.presentations.DropDownMenu; import ecdar.utility.helpers.SelectHelper; import ecdar.utility.keyboard.Keybind; @@ -27,7 +27,7 @@ public class SystemEdgeController implements Initializable { public Group root; - private EcdarSystemEdge edge; + private SystemEdge edge; private final ObjectProperty<EcdarSystem> system = new SimpleObjectProperty<>(); private SelectHelper.ItemSelectable selectable; @@ -48,11 +48,11 @@ public void initialize(final URL location, final ResourceBundle resources) { }); } - public EcdarSystemEdge getEdge() { + public SystemEdge getEdge() { return edge; } - public void setEdge(final EcdarSystemEdge edge) { + public void setEdge(final SystemEdge edge) { this.edge = edge; } diff --git a/src/main/java/ecdar/controllers/SystemRootController.java b/src/main/java/ecdar/controllers/SystemRootController.java index c3a81562..38c40053 100644 --- a/src/main/java/ecdar/controllers/SystemRootController.java +++ b/src/main/java/ecdar/controllers/SystemRootController.java @@ -1,6 +1,6 @@ package ecdar.controllers; -import ecdar.abstractions.EcdarSystemEdge; +import ecdar.abstractions.SystemEdge; import ecdar.abstractions.EcdarSystem; import ecdar.abstractions.SystemRoot; import ecdar.presentations.DropDownMenu; @@ -85,7 +85,7 @@ private void initializeDropDownMenu(final EcdarSystem system) { * Listens to an edge to update whether the root has an edge. * @param edge the edge to update with */ - private void handleHasEdge(final EcdarSystemEdge edge) { + private void handleHasEdge(final SystemEdge edge) { edge.getTempNodeProperty().addListener((observable -> updateHasEdge(edge))); edge.getChildProperty().addListener((observable -> updateHasEdge(edge))); edge.getParentProperty().addListener((observable -> updateHasEdge(edge))); @@ -95,7 +95,7 @@ private void handleHasEdge(final EcdarSystemEdge edge) { * Update has edge property to whether the root is in a given edge. * @param edge the given edge */ - private void updateHasEdge(final EcdarSystemEdge edge) { + private void updateHasEdge(final SystemEdge edge) { hasEdge.set(edge.isInEdge(getSystemRoot())); } @@ -103,7 +103,7 @@ private void updateHasEdge(final EcdarSystemEdge edge) { private void onMouseClicked(final MouseEvent event) { event.consume(); - final EcdarSystemEdge unfinishedEdge = getSystem().getUnfinishedEdge(); + final SystemEdge unfinishedEdge = getSystem().getUnfinishedEdge(); if ((event.isShiftDown() && event.getButton().equals(MouseButton.PRIMARY)) || event.getButton().equals(MouseButton.MIDDLE)) { // If shift click or middle click a component instance, create a new edge @@ -131,11 +131,11 @@ private void onMouseClicked(final MouseEvent event) { } /*** - * Helper method to create a new EcdarSystemEdge and add it to the current system and system root - * @return The newly created EcdarSystemEdge + * Helper method to create a new SystemEdge and add it to the current system and system root + * @return The newly created SystemEdge */ - private EcdarSystemEdge createNewSystemEdge() { - final EcdarSystemEdge edge = new EcdarSystemEdge(systemRoot); + private SystemEdge createNewSystemEdge() { + final SystemEdge edge = new SystemEdge(systemRoot); getSystem().addEdge(edge); hasEdge.set(true); handleHasEdge(edge); diff --git a/src/main/java/ecdar/controllers/TracePaneElementController.java b/src/main/java/ecdar/controllers/TracePaneElementController.java index ce5cea76..505a58b7 100755 --- a/src/main/java/ecdar/controllers/TracePaneElementController.java +++ b/src/main/java/ecdar/controllers/TracePaneElementController.java @@ -42,7 +42,7 @@ public class TracePaneElementController implements Initializable { @Override public void initialize(URL location, ResourceBundle resources) { - simulationHandler = Ecdar.getSimulationHandler(); + simulationHandler = SimulatorController.getSimulationHandler(); simulationHandler.getTraceLog().addListener((ListChangeListener<SimulationState>) c -> { while (c.next()) { @@ -64,7 +64,7 @@ public void initialize(URL location, ResourceBundle resources) { /** * Initializes the expand functionality that allows the user to show or hide the trace. - * By default the trace is shown. + * By default, the trace is shown. */ private void initializeTraceExpand() { isTraceExpanded.addListener((obs, oldVal, newVal) -> { diff --git a/src/main/java/ecdar/controllers/TransitionPaneElementController.java b/src/main/java/ecdar/controllers/TransitionPaneElementController.java index 3772f46b..a0872b92 100755 --- a/src/main/java/ecdar/controllers/TransitionPaneElementController.java +++ b/src/main/java/ecdar/controllers/TransitionPaneElementController.java @@ -45,7 +45,7 @@ public class TransitionPaneElementController implements Initializable { @Override public void initialize(URL location, ResourceBundle resources) { - simulationHandler = Ecdar.getSimulationHandler(); + simulationHandler = SimulatorController.getSimulationHandler(); initializeTransitionExpand(); initializeDelayChooser(); } diff --git a/src/main/java/ecdar/issues/ExitStatusCodes.java b/src/main/java/ecdar/issues/ExitStatusCodes.java new file mode 100644 index 00000000..5fc46972 --- /dev/null +++ b/src/main/java/ecdar/issues/ExitStatusCodes.java @@ -0,0 +1,17 @@ +package ecdar.issues; + +/** + * Enum for representing the status of a requested exit + */ +public enum ExitStatusCodes { + SHUTDOWN_SUCCESSFUL(0), + GRACEFUL_SHUTDOWN_FAILED(-1), + CLOSE_ENGINE_CONNECTIONS_FAILED(-2); + + private final int statusCode; + ExitStatusCodes(int statusCode) { this.statusCode = statusCode; } + + public int getStatusCode() { + return statusCode; + } +} \ No newline at end of file diff --git a/src/main/java/ecdar/mutation/ComponentVerificationTransformer.java b/src/main/java/ecdar/mutation/ComponentVerificationTransformer.java new file mode 100644 index 00000000..cba15670 --- /dev/null +++ b/src/main/java/ecdar/mutation/ComponentVerificationTransformer.java @@ -0,0 +1,245 @@ +package ecdar.mutation; + +import com.bpodgursky.jbool_expressions.*; +import com.bpodgursky.jbool_expressions.rules.RuleSet; +import ecdar.abstractions.*; +import ecdar.utility.ExpressionHelper; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +public class ComponentVerificationTransformer { + /** + * Applies demonic completion on this component. + */ + public static void applyDemonicCompletionToComponent(final Component component) { + // Make a universal location + final Location uniLocation = new Location(component, Location.Type.UNIVERSAL, component.getUniqueLocationId(), 0, 0); + component.addLocation(uniLocation); + + final Edge inputEdge = uniLocation.addLeftEdge("*", EdgeStatus.INPUT); + inputEdge.setIsLocked(true); + component.addEdge(inputEdge); + + final Edge outputEdge = uniLocation.addRightEdge("*", EdgeStatus.OUTPUT); + outputEdge.setIsLocked(true); + component.addEdge(outputEdge); + + // Cache input signature, since it could be updated when added edges + final List<String> inputStrings = new ArrayList<>(component.getInputStrings()); + + component.getLocations().forEach(location -> inputStrings.forEach(input -> { + final List<Edge> matchingEdges = getOutgoingInputEdgesFromLocationWithSync(component, location, input); + if (matchingEdges.isEmpty()) return; + + // Extract expression for which edges to create. + // The expression is in DNF + // We create edges to Universal for each child expression in the disjunction. + createDemonicEdgesForComponent(component, location, uniLocation, input, getNegatedEdgeExpressionForComponent(component, matchingEdges)); + })); + } + + /** + * Applies angelic completion on this component. + */ + public static void applyAngelicCompletionForComponent(final Component component) { + // Cache input signature, since it could be updated when added edges + final List<String> inputStrings = new ArrayList<>(component.getInputStrings()); + + component.getLocations().forEach(location -> inputStrings.forEach(input -> { + final List<Edge> matchingEdges = getOutgoingInputEdgesFromLocationWithSync(component, location, input); + if (matchingEdges.isEmpty()) return; + + // Extract expression for which edges to create. + // The expression is in DNF + // We create self loops for each child expression in the disjunction. + createAngelicSelfLoopsForComponent(component, location, input, getNegatedEdgeExpressionForComponent(component, matchingEdges)); + })); + } + + /** + * Creates a clone of another component. + * Copy objects used for verification (e.g. locations, edges and the declarations). + * Does not copy UI elements (sizes and positions). + * Its locations are cloned from the original component. Their ids are the same. + * Does not initialize io listeners, but copies the input and output strings. + * Reachability analysis binding is not initialized. + * @return the clone + */ + public static Component cloneForVerification(final Component component) { + final Component clone = new Component(); + addVerificationObjects(component, clone); + clone.setIncludeInPeriodicCheck(false); + clone.getInputStrings().addAll(component.getInputStrings()); + clone.getOutputStrings().addAll(component.getOutputStrings()); + clone.setName(component.getName()); + + return clone; + } + + /** + * Get the input edges starting from the location with the specified sync. + * If no edges match, this method adds a self loop on the location with the sync. + * @param component containing component + * @param location source location to check outgoing edges + * @param sync desired sync to check for and possibly create self-loop with + * @return list of matching edges (if empty, the self-loop might have been created) + */ + private static List<Edge> getOutgoingInputEdgesFromLocationWithSync(Component component, Location location, String sync) { + // Get outgoing input edges that has the chosen sync + final List<Edge> matchingEdges = component.getListOfEdgesFromDisplayableEdges(component.getOutgoingEdges(location)).stream().filter( + edge -> edge.getStatus().equals(EdgeStatus.INPUT) && + edge.getSync().equals(sync)).collect(Collectors.toList() + ); + + // If no such edges, add a self loop without a guard + if (matchingEdges.isEmpty()) { + final Edge edge = new Edge(location, EdgeStatus.INPUT); + edge.setTargetLocation(location); + edge.addSyncNail(sync); + component.addEdge(edge); + return matchingEdges; + } + + // If an edge has no guard and its target has no invariants, ignore. + // Component is already input-enabled with respect to this location and input. + if (matchingEdges.stream().anyMatch(edge -> edge.getGuard().isEmpty() && + edge.getTargetLocation().getInvariant().isEmpty())) return matchingEdges; + + return matchingEdges; + } + + /** + * Creates edges to a specified Universal location from a location + * in order to finish missing inputs with a demonic completion. + * + * @param location the location to create self loops on + * @param universal the Universal location to create edge to + * @param input the input action to use in the synchronization properties + * @param guardExpression the expression that represents the guards of the edges to create + */ + private static void createDemonicEdgesForComponent(final Component component, final Location location, final Location universal, final String input, final Expression<String> guardExpression) { + final Edge edge; + + switch (guardExpression.getExprType()) { + case Literal.EXPR_TYPE: + // If false, do not create any edges + if (!((Literal<String>) guardExpression).getValue()) break; + + // It should never be true, since that should be handled before calling this method + throw new RuntimeException("Type of expression " + guardExpression + " not accepted"); + case Variable.EXPR_TYPE: + edge = new Edge(location, EdgeStatus.INPUT); + edge.setTargetLocation(universal); + edge.addSyncNail(input); + edge.addGuardNail(((Variable<String>) guardExpression).getValue()); + component.addEdge(edge); + break; + case And.EXPR_TYPE: + edge = new Edge(location, EdgeStatus.INPUT); + edge.setTargetLocation(universal); + edge.addSyncNail(input); + edge.addGuardNail(guardExpression.getChildren().stream() + .map(child -> ((Variable<String>) child).getValue()) + .collect(Collectors.joining("&&"))); + component.addEdge(edge); + break; + case Or.EXPR_TYPE: + guardExpression.getChildren().forEach(child -> createDemonicEdgesForComponent(component, location, universal, input, child)); + break; + default: + throw new RuntimeException("Type of expression " + guardExpression + " not accepted"); + } + } + + /** + * Extracts an expression that represents the negation of a list of edges. + * Multiple edges are resolved as disjunctions. + * There can be conjunction in guards. + * We translate each edge to the conjunction of its guard and the invariant of its target location + * (since it should also be satisfied). + * The result is converted to disjunctive normal form. + * + * @param edges the edges to extract from + * @return the expression that represents the negation + */ + private static Expression<String> getNegatedEdgeExpressionForComponent(final Component component, List<Edge> edges) { + final List<String> clocks = component.getClocks(); + return ExpressionHelper.simplifyNegatedSimpleExpressions( + RuleSet.toDNF(RuleSet.simplify(Not.of(Or.of(edges.stream() + .map(edge -> { + final List<String> clocksToReset = ExpressionHelper.getUpdateSides(edge.getUpdate()) + .keySet().stream().filter(clocks::contains).collect(Collectors.toList()); + return And.of( + ExpressionHelper.parseGuard(edge.getGuard()), + ExpressionHelper.parseInvariantButIgnore(edge.getTargetLocation().getInvariant(), clocksToReset) + ); + } + ).collect(Collectors.toList())) + )))); + } + + /** + * Creates self loops on a location to finish missing inputs with an angelic completion. + * The guard expression should be in DNF without negations. + * + * @param location the location to create self loops on + * @param input the input action to use in the synchronization properties + * @param guardExpression the expression that represents the guards of the self loops + */ + private static void createAngelicSelfLoopsForComponent(final Component component, Location location, final String input, final Expression<String> guardExpression) { + final Edge edge; + + switch (guardExpression.getExprType()) { + case Literal.EXPR_TYPE: + // If false, do not create any loops + if (!((Literal<String>) guardExpression).getValue()) break; + + // It should never be true, since that should be handled before calling this method + throw new RuntimeException("Type of expression " + guardExpression + " not accepted"); + case Variable.EXPR_TYPE: + edge = new Edge(location, EdgeStatus.INPUT); + edge.setTargetLocation(location); + edge.addSyncNail(input); + edge.addGuardNail(((Variable<String>) guardExpression).getValue()); + component.addEdge(edge); + break; + case And.EXPR_TYPE: + edge = new Edge(location, EdgeStatus.INPUT); + edge.setTargetLocation(location); + edge.addSyncNail(input); + edge.addGuardNail(guardExpression.getChildren().stream() + .map(child -> { + if (!child.getExprType().equals(Variable.EXPR_TYPE)) + throw new RuntimeException("Child " + child + " of type " + + child.getExprType() + " in and expression " + + guardExpression + " should be a variable"); + + return ((Variable<String>) child).getValue(); + }) + .collect(Collectors.joining("&&"))); + component.addEdge(edge); + break; + case Or.EXPR_TYPE: + guardExpression.getChildren().forEach(child -> createAngelicSelfLoopsForComponent(component, location, input, child)); + break; + default: + throw new RuntimeException("Type of expression " + guardExpression + " not accepted"); + } + } + + /** + * Adds objects used for verifications from original to clone. + * @param original the component to add from + * @param clone the component to add to + */ + private static void addVerificationObjects(final Component original, final Component clone) { + for (final Location originalLoc : original.getLocations()) { + clone.addLocation(originalLoc.cloneForVerification()); + } + + clone.getListOfEdgesFromDisplayableEdges(original.getDisplayableEdges()).forEach(edge -> clone.addEdge((edge).cloneForVerification(original))); + clone.setDeclarationsText(original.getDeclarationsText()); + } +} diff --git a/src/main/java/ecdar/mutation/ExportHandler.java b/src/main/java/ecdar/mutation/ExportHandler.java index d75dcdfc..9f943a89 100644 --- a/src/main/java/ecdar/mutation/ExportHandler.java +++ b/src/main/java/ecdar/mutation/ExportHandler.java @@ -79,7 +79,7 @@ void start() { // Apply angelic completion if selected if (getPlan().isAngelicWhenExport()) - cases.stream().map(MutationTestCase::getMutant).forEach(Component::applyAngelicCompletion); + cases.stream().map(MutationTestCase::getMutant).forEach(ComponentVerificationTransformer::applyAngelicCompletionForComponent); getPlan().setMutantsText("Mutants: " + cases.size() + " - Execution time: " + MutationTestPlanPresentation.readableFormat(Duration.between(start, Instant.now()))); diff --git a/src/main/java/ecdar/mutation/MutationHandler.java b/src/main/java/ecdar/mutation/MutationHandler.java index 44f091e3..f4a3db16 100644 --- a/src/main/java/ecdar/mutation/MutationHandler.java +++ b/src/main/java/ecdar/mutation/MutationHandler.java @@ -36,9 +36,6 @@ public MutationHandler(final Component testModel, final MutationTestPlan plan, f this.consumer = consumer; } - - /* Properties */ - private Component getTestModel() { return testModel; } @@ -51,9 +48,6 @@ private Consumer<List<MutationTestCase>> getConsumer() { return consumer; } - - /* Other */ - /** * Starts. */ @@ -85,7 +79,7 @@ public void start() { return; } - cases.forEach(testCase -> testCase.getMutant().applyAngelicCompletion()); + cases.forEach(testCase -> ComponentVerificationTransformer.applyAngelicCompletionForComponent(testCase.getMutant())); Platform.runLater(() -> getPlan().setMutantsText("Mutants: " + cases.size() + " - Mutation time: " + MutationTestPlanPresentation.readableFormat(Duration.between(start, Instant.now()))) @@ -94,7 +88,7 @@ public void start() { testModel.setName(MutationTestPlanController.SPEC_NAME); // If chosen, apply demonic completion - if (getPlan().isDemonic()) testModel.applyDemonicCompletion(); + if (getPlan().isDemonic()) ComponentVerificationTransformer.applyDemonicCompletionToComponent(testModel); //Rename universal and inconsistent locations testModel.getLocations().forEach(location -> { @@ -116,4 +110,5 @@ public void start() { Platform.runLater(() -> getConsumer().accept(cases)); }).start(); } + } diff --git a/src/main/java/ecdar/mutation/MutationTestPlanController.java b/src/main/java/ecdar/mutation/MutationTestPlanController.java index b98a5b82..888d8a31 100644 --- a/src/main/java/ecdar/mutation/MutationTestPlanController.java +++ b/src/main/java/ecdar/mutation/MutationTestPlanController.java @@ -6,6 +6,8 @@ import com.jfoenix.controls.JFXTextField; import ecdar.Ecdar; import ecdar.abstractions.Component; +import ecdar.abstractions.HighLevelModel; +import ecdar.controllers.HighLevelModelController; import ecdar.mutation.models.MutationTestCase; import ecdar.mutation.models.MutationTestPlan; import ecdar.mutation.models.TestResult; @@ -30,7 +32,7 @@ /** * Controller for a test plan with model-based mutation testing. */ -public class MutationTestPlanController { +public class MutationTestPlanController extends HighLevelModelController { public final static String SPEC_NAME = "S"; public final static String MUTANT_NAME = "M"; @@ -105,16 +107,10 @@ public class MutationTestPlanController { public Text failedNumber; public Text primaryFailedNumber; - - /* Mutation fields */ - private MutationTestPlan plan; private TestingHandler testingHandler; public final ObservableList<TestResult> resultsToShow = new SimpleListProperty<>(FXCollections.observableArrayList()); - - /* Properties */ - public MutationTestPlan getPlan() { return plan; } @@ -128,8 +124,6 @@ public TestingHandler getTestingHandler() { return testingHandler; } - /* Other methods */ - /** * Triggered when pressed the test button. * Conducts the test. @@ -139,7 +133,7 @@ public void onTestButtonPressed() { // Find test model from test model picker // Clone it, because we want to change its name - final Component testModel = Ecdar.getProject().findComponent(modelPicker.getValue().getText()).cloneForVerification(); + final Component testModel = ComponentVerificationTransformer.cloneForVerification(plan.getTestModel()); new MutationHandler(testModel, getPlan(), cases -> startGeneration(testModel, cases)).start(); } @@ -234,4 +228,9 @@ public void onExportButtonPressed() { getPlan().clearResults(); new ExportHandler(getPlan(), Ecdar.getProject().findComponent(modelPicker.getValue().getText())).start(); } + + @Override + public HighLevelModel getModel() { + return plan; + } } \ No newline at end of file diff --git a/src/main/java/ecdar/mutation/MutationTestPlanPresentation.java b/src/main/java/ecdar/mutation/MutationTestPlanPresentation.java index a50c6f4e..8f299be8 100644 --- a/src/main/java/ecdar/mutation/MutationTestPlanPresentation.java +++ b/src/main/java/ecdar/mutation/MutationTestPlanPresentation.java @@ -6,6 +6,7 @@ import ecdar.Ecdar; import ecdar.abstractions.Component; import ecdar.controllers.EcdarController; +import ecdar.controllers.HighLevelModelController; import ecdar.mutation.models.ExpandableContent; import ecdar.mutation.models.MutationTestPlan; import ecdar.mutation.models.TestResult; @@ -550,19 +551,19 @@ private void initializeModelPicker() { * Initializes width and height of the text editor field, such that it fills up the whole canvas. */ private void initializeWidthAndHeight() { - controller.scrollPane.setPrefWidth(EcdarController.getActiveCanvasPresentation().getController().getWidthProperty().doubleValue()); - EcdarController.getActiveCanvasPresentation().getController().getWidthProperty().addListener((observable, oldValue, newValue) -> + controller.scrollPane.setPrefWidth(Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasPresentation().getController().getWidthProperty().doubleValue()); + Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasPresentation().getController().getWidthProperty().addListener((observable, oldValue, newValue) -> controller.scrollPane.setPrefWidth(newValue.doubleValue())); - updateOffset(EcdarController.getActiveCanvasPresentation().getController().getInsetShouldShow().get()); - EcdarController.getActiveCanvasPresentation().getController().getInsetShouldShow().addListener((observable, oldValue, newValue) -> { + updateOffset(Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasPresentation().getController().getInsetShouldShow().get()); + Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasPresentation().getController().getInsetShouldShow().addListener((observable, oldValue, newValue) -> { updateOffset(newValue); updateHeight(); }); - canvasHeight = EcdarController.getActiveCanvasPresentation().getController().getHeightProperty().doubleValue(); + canvasHeight = Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasPresentation().getController().getHeightProperty().doubleValue(); updateHeight(); - EcdarController.getActiveCanvasPresentation().getController().getHeightProperty().addListener((observable, oldValue, newValue) -> { + Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasPresentation().getController().getHeightProperty().addListener((observable, oldValue, newValue) -> { canvasHeight = newValue.doubleValue(); updateHeight(); }); @@ -589,7 +590,7 @@ private void updateOffset(final boolean shouldHave) { * Updates the height of the view. */ private void updateHeight() { - controller.scrollPane.setPrefHeight(canvasHeight - EcdarController.getActiveCanvasPresentation().getController().DECLARATION_Y_MARGIN - offSet); + controller.scrollPane.setPrefHeight(canvasHeight - Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasPresentation().getController().DECLARATION_Y_MARGIN - offSet); } /** @@ -699,4 +700,8 @@ private void initializeFormatPicker() { getPlan().setFormat(newValue.getText()))); } + @Override + public HighLevelModelController getController() { + return controller; + } } diff --git a/src/main/java/ecdar/mutation/TextFlowBuilder.java b/src/main/java/ecdar/mutation/TextFlowBuilder.java index 74e55aa9..291f1641 100644 --- a/src/main/java/ecdar/mutation/TextFlowBuilder.java +++ b/src/main/java/ecdar/mutation/TextFlowBuilder.java @@ -4,7 +4,6 @@ import ecdar.abstractions.Component; import ecdar.abstractions.Edge; import ecdar.abstractions.Location; -import ecdar.controllers.EcdarController; import ecdar.utility.helpers.SelectHelper; import javafx.application.Platform; import javafx.collections.FXCollections; @@ -94,7 +93,7 @@ public static Text createLocationLink(final String locationId, final String comp } SelectHelper.elementsToBeSelected = FXCollections.observableArrayList(); - EcdarController.setActiveModelForActiveCanvas(component); + Ecdar.getPresentation().getController().getEditorPresentation().getController().setActiveModelPresentationForActiveCanvas(Ecdar.getComponentPresentationOfComponent(component)); // Use a list, since there could be multiple locations (e.i. Universal locations) final List<Location> locations = component.getLocations().filtered(loc -> loc.getId().equals(locationId)); diff --git a/src/main/java/ecdar/mutation/models/MutationTestPlan.java b/src/main/java/ecdar/mutation/models/MutationTestPlan.java index add18dce..96448519 100644 --- a/src/main/java/ecdar/mutation/models/MutationTestPlan.java +++ b/src/main/java/ecdar/mutation/models/MutationTestPlan.java @@ -4,7 +4,7 @@ import com.google.gson.JsonPrimitive; import ecdar.Ecdar; import ecdar.abstractions.Component; -import ecdar.abstractions.HighLevelModelObject; +import ecdar.abstractions.HighLevelModel; import ecdar.mutation.VisibilityHelper; import ecdar.mutation.operators.MutationOperator; import javafx.beans.property.*; @@ -18,7 +18,7 @@ /** * A test plan for conducting model-based mutation testing on a component. */ -public class MutationTestPlan extends HighLevelModelObject { +public class MutationTestPlan extends HighLevelModel { /** * The status of the test plan. * STOPPING: Stopped by the user diff --git a/src/main/java/ecdar/mutation/operators/ChangeActionOperator.java b/src/main/java/ecdar/mutation/operators/ChangeActionOperator.java index 3ab7bf2c..a807a0b3 100644 --- a/src/main/java/ecdar/mutation/operators/ChangeActionOperator.java +++ b/src/main/java/ecdar/mutation/operators/ChangeActionOperator.java @@ -3,6 +3,7 @@ import ecdar.abstractions.Component; import ecdar.abstractions.Edge; import ecdar.abstractions.EdgeStatus; +import ecdar.mutation.ComponentVerificationTransformer; import ecdar.mutation.TextFlowBuilder; import ecdar.mutation.models.MutationTestCase; @@ -25,7 +26,7 @@ MutationTestCase generateTestCase(final Component original, final int edgeIndex, // If action is the action of the original edge, ignore if (originalEdge.getStatus().equals(status) && originalEdge.getSync().equals(sync)) return null; - final Component mutant = original.cloneForVerification(); + final Component mutant = ComponentVerificationTransformer.cloneForVerification(original); // Mutate final Edge mutantEdge = mutant.getEdges().get(edgeIndex); diff --git a/src/main/java/ecdar/mutation/operators/ChangeGuardConstantOperator.java b/src/main/java/ecdar/mutation/operators/ChangeGuardConstantOperator.java index 5c2ec5a6..7d991aec 100644 --- a/src/main/java/ecdar/mutation/operators/ChangeGuardConstantOperator.java +++ b/src/main/java/ecdar/mutation/operators/ChangeGuardConstantOperator.java @@ -2,6 +2,7 @@ import ecdar.abstractions.Component; import ecdar.abstractions.Edge; +import ecdar.mutation.ComponentVerificationTransformer; import ecdar.mutation.TextFlowBuilder; import ecdar.mutation.models.MutationTestCase; @@ -41,7 +42,7 @@ public List<MutationTestCase> generateTestCases(final Component original) { int index = 0; while (matcher.find()) { { - final Component mutant = original.cloneForVerification(); + final Component mutant = ComponentVerificationTransformer.cloneForVerification(original); final Edge mutantEdge = mutant.getEdges().get(edgeIndex); final int newNumber = Integer.parseInt(matcher.group(1)) + 1; @@ -54,7 +55,7 @@ public List<MutationTestCase> generateTestCases(final Component original) { .text(" to ").boldText(mutantEdge.getGuard()).build() )); } { - final Component mutant = original.cloneForVerification(); + final Component mutant = ComponentVerificationTransformer.cloneForVerification(original); final Edge mutantEdge = mutant.getEdges().get(edgeIndex); final int newNumber = Integer.parseInt(matcher.group(1)) -1; diff --git a/src/main/java/ecdar/mutation/operators/ChangeGuardOpOperator.java b/src/main/java/ecdar/mutation/operators/ChangeGuardOpOperator.java index ce12beb4..f2645493 100644 --- a/src/main/java/ecdar/mutation/operators/ChangeGuardOpOperator.java +++ b/src/main/java/ecdar/mutation/operators/ChangeGuardOpOperator.java @@ -2,6 +2,7 @@ import ecdar.abstractions.Component; import ecdar.abstractions.Edge; +import ecdar.mutation.ComponentVerificationTransformer; import ecdar.mutation.MutationTestingException; import ecdar.mutation.TextFlowBuilder; import ecdar.mutation.models.MutationTestCase; @@ -108,7 +109,7 @@ private static boolean containsVar(final String expr, final List<String> vars) { */ private static Component createMutant(final Component original, final String[] originalSimpleGuards, final String newSimpleGuard, final int simpleGuardIndex, final int edgeIndex) { - final Component mutant = original.cloneForVerification(); + final Component mutant = ComponentVerificationTransformer.cloneForVerification(original); final String[] newSimpleGuards = originalSimpleGuards.clone(); newSimpleGuards[simpleGuardIndex] = newSimpleGuard; diff --git a/src/main/java/ecdar/mutation/operators/ChangeInvariantOperator.java b/src/main/java/ecdar/mutation/operators/ChangeInvariantOperator.java index 64038a54..878d192f 100644 --- a/src/main/java/ecdar/mutation/operators/ChangeInvariantOperator.java +++ b/src/main/java/ecdar/mutation/operators/ChangeInvariantOperator.java @@ -2,6 +2,7 @@ import ecdar.abstractions.Component; import ecdar.abstractions.Location; +import ecdar.mutation.ComponentVerificationTransformer; import ecdar.mutation.TextFlowBuilder; import ecdar.mutation.models.MutationTestCase; @@ -39,7 +40,7 @@ public List<MutationTestCase> generateTestCases(final Component original) { final List<String> invariantParts = Arrays.stream(originalLocation.getInvariant() .split("&&")).map(String::trim).collect(Collectors.toList()); for (int partIndex = 0; partIndex < invariantParts.size(); partIndex++) { - final Component mutant = original.cloneForVerification(); + final Component mutant = ComponentVerificationTransformer.cloneForVerification(original); final List<String> newParts = new ArrayList<>(invariantParts); newParts.set(partIndex, newParts.get(partIndex) + " + 1"); diff --git a/src/main/java/ecdar/mutation/operators/ChangeSourceOperator.java b/src/main/java/ecdar/mutation/operators/ChangeSourceOperator.java index cdbac5e4..72d908fc 100644 --- a/src/main/java/ecdar/mutation/operators/ChangeSourceOperator.java +++ b/src/main/java/ecdar/mutation/operators/ChangeSourceOperator.java @@ -3,6 +3,7 @@ import ecdar.abstractions.Component; import ecdar.abstractions.Edge; import ecdar.abstractions.Location; +import ecdar.mutation.ComponentVerificationTransformer; import ecdar.mutation.TextFlowBuilder; import ecdar.mutation.models.MutationTestCase; @@ -46,7 +47,7 @@ public List<MutationTestCase> generateTestCases(final Component original) { if (originalLocation.getType().equals(Location.Type.INCONSISTENT) || originalLocation.getType().equals(Location.Type.UNIVERSAL)) continue; - final Component mutant = original.cloneForVerification(); + final Component mutant = ComponentVerificationTransformer.cloneForVerification(original); // Mutate final Edge mutantEdge = mutant.getEdges().get(edgeIndex); diff --git a/src/main/java/ecdar/mutation/operators/ChangeTargetOperator.java b/src/main/java/ecdar/mutation/operators/ChangeTargetOperator.java index 2b0e3e45..56ddee6a 100644 --- a/src/main/java/ecdar/mutation/operators/ChangeTargetOperator.java +++ b/src/main/java/ecdar/mutation/operators/ChangeTargetOperator.java @@ -3,6 +3,7 @@ import ecdar.abstractions.Component; import ecdar.abstractions.Edge; import ecdar.abstractions.Location; +import ecdar.mutation.ComponentVerificationTransformer; import ecdar.mutation.TextFlowBuilder; import ecdar.mutation.models.MutationTestCase; @@ -39,7 +40,7 @@ public List<MutationTestCase> generateTestCases(final Component original) { // Ignore if location is target in original edge if (originalEdge.getTargetLocation() == originalLocation) continue; - final Component mutant = original.cloneForVerification(); + final Component mutant = ComponentVerificationTransformer.cloneForVerification(original); // Mutate final Edge mutantEdge = mutant.getEdges().get(edgeIndex); diff --git a/src/main/java/ecdar/mutation/operators/ChangeVarUpdateOperator.java b/src/main/java/ecdar/mutation/operators/ChangeVarUpdateOperator.java index 93980f73..80b6707d 100644 --- a/src/main/java/ecdar/mutation/operators/ChangeVarUpdateOperator.java +++ b/src/main/java/ecdar/mutation/operators/ChangeVarUpdateOperator.java @@ -1,15 +1,17 @@ package ecdar.mutation.operators; +import ecdar.Ecdar; import ecdar.abstractions.Component; import ecdar.abstractions.Edge; +import ecdar.mutation.ComponentVerificationTransformer; import ecdar.mutation.TextFlowBuilder; import ecdar.mutation.models.MutationTestCase; import ecdar.utility.ExpressionHelper; import org.apache.commons.lang3.tuple.Triple; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** * Mutation operator that changes the assignment of local variables. @@ -27,7 +29,7 @@ public String getCodeName() { @Override public List<MutationTestCase> generateTestCases(final Component original) { - final List<Triple<String, Integer, Integer>> locals = original.getLocalVariablesWithBounds(); + final List<Triple<String, Integer, Integer>> variablesWithBounds = getLocalComponentVariablesWithBounds(original); final List<MutationTestCase> cases = new ArrayList<>(); @@ -43,12 +45,12 @@ public List<MutationTestCase> generateTestCases(final Component original) { // For each variable final int finalEdgeIndex = edgeIndex; - locals.forEach(local -> { + variablesWithBounds.forEach(local -> { // If variable is not assigned, add it if (sides.get(local.getLeft()) == null) { // For each possible assignment of that variable for (int value = local.getMiddle(); value <= local.getRight(); value++) { - final Component mutant = original.cloneForVerification(); + final Component mutant = ComponentVerificationTransformer.cloneForVerification(original); final Edge mutantEdge = mutant.getEdges().get(finalEdgeIndex); final List<String> newSimpleUpdates = new ArrayList<>(); @@ -73,7 +75,7 @@ public List<MutationTestCase> generateTestCases(final Component original) { // If this is already the original assignment, ignore if (sides.get(local.getLeft()).equals(String.valueOf(value))) continue; - final Component mutant = original.cloneForVerification(); + final Component mutant = ComponentVerificationTransformer.cloneForVerification(original); final Edge mutantEdge = mutant.getEdges().get(finalEdgeIndex); final List<String> newSimpleUpdates = new ArrayList<>(); @@ -102,6 +104,31 @@ public List<MutationTestCase> generateTestCases(final Component original) { return cases; } + /** + * Gets the local variables defined in the declarations text. + * Also gets the lower and upper bounds for these variables. + * + * @return Triples containing (left) name of the variable, (middle) lower bound, (right) upper bound + */ + private List<Triple<String, Integer, Integer>> getLocalComponentVariablesWithBounds(Component component) { + final List<Triple<String, Integer, Integer>> typedefs = Ecdar.getProject().getGlobalDeclarations().getTypedefs(); + + final List<Triple<String, Integer, Integer>> locals = new ArrayList<>(); + + Arrays.stream(component.getDeclarationsText().split(";")).forEach(statement -> { + final Matcher matcher = Pattern.compile("^\\s*(\\w+)\\s+(\\w+)(\\W|$)").matcher(statement); + if (!matcher.find()) return; + + final Optional<Triple<String, Integer, Integer>> typedef = typedefs.stream() + .filter(def -> def.getLeft().equals(matcher.group(1))).findAny(); + if (typedef.isEmpty()) return; + + locals.add(Triple.of(matcher.group(2), typedef.get().getMiddle(), typedef.get().getRight())); + }); + + return locals; + } + @Override public String getDescription() { return "Changes the assignment (or adds, if the variable is not assigned in corresponding edge) of a local variable " + @@ -114,10 +141,9 @@ public String getDescription() { @Override public int getUpperLimit(final Component original) { // Get the sum of valuations of each variable - final int varValueCount = original.getLocalVariablesWithBounds().stream().mapToInt(local -> local.getRight() - local.getMiddle() + 1).sum(); + final int varValueCount = getLocalComponentVariablesWithBounds(original).stream().mapToInt(local -> local.getRight() - local.getMiddle() + 1).sum(); return original.getDisplayableEdges().size() * varValueCount; - } @Override diff --git a/src/main/java/ecdar/mutation/operators/InvertResetOperator.java b/src/main/java/ecdar/mutation/operators/InvertResetOperator.java index 0ecae0a6..c3aecc7f 100644 --- a/src/main/java/ecdar/mutation/operators/InvertResetOperator.java +++ b/src/main/java/ecdar/mutation/operators/InvertResetOperator.java @@ -3,6 +3,7 @@ import com.google.common.collect.Lists; import ecdar.abstractions.Component; import ecdar.abstractions.Edge; +import ecdar.mutation.ComponentVerificationTransformer; import ecdar.mutation.TextFlowBuilder; import ecdar.mutation.models.MutationTestCase; @@ -39,7 +40,7 @@ public List<MutationTestCase> generateTestCases(final Component original) { // For each clock final int finalEdgeIndex = edgeIndex; clocks.forEach(clock -> { - final Component mutant = original.cloneForVerification(); + final Component mutant = ComponentVerificationTransformer.cloneForVerification(original); final Edge mutantEdge = mutant.getEdges().get(finalEdgeIndex); // Mutate diff --git a/src/main/java/ecdar/mutation/operators/SinkLocationOperator.java b/src/main/java/ecdar/mutation/operators/SinkLocationOperator.java index b0862c42..721d2329 100644 --- a/src/main/java/ecdar/mutation/operators/SinkLocationOperator.java +++ b/src/main/java/ecdar/mutation/operators/SinkLocationOperator.java @@ -4,6 +4,7 @@ import ecdar.abstractions.Edge; import ecdar.abstractions.EdgeStatus; import ecdar.abstractions.Location; +import ecdar.mutation.ComponentVerificationTransformer; import ecdar.mutation.TextFlowBuilder; import ecdar.mutation.models.MutationTestCase; @@ -36,7 +37,7 @@ public List<MutationTestCase> generateTestCases(final Component original) { // Ignore if locked (e.g. if edge on the Inconsistent or Universal locations) if (originalEdge.getIsLockedProperty().get()) continue; - final Component mutant = original.cloneForVerification(); + final Component mutant = ComponentVerificationTransformer.cloneForVerification(original); // Mutate final Edge mutantEdge = mutant.getEdges().get(edgeIndex); diff --git a/src/main/java/ecdar/presentations/BackendInstancePresentation.java b/src/main/java/ecdar/presentations/BackendInstancePresentation.java deleted file mode 100644 index 67801874..00000000 --- a/src/main/java/ecdar/presentations/BackendInstancePresentation.java +++ /dev/null @@ -1,34 +0,0 @@ -package ecdar.presentations; - -import com.jfoenix.controls.JFXRippler; -import ecdar.Ecdar; -import ecdar.backend.BackendInstance; -import ecdar.controllers.BackendInstanceController; -import ecdar.utility.colors.Color; -import javafx.application.Platform; -import javafx.scene.Cursor; -import javafx.scene.layout.StackPane; - -public class BackendInstancePresentation extends StackPane { - private final BackendInstanceController controller; - - public BackendInstancePresentation(BackendInstance backendInstance) { - this(); - controller.setBackendInstance(backendInstance); - - // Ensure that the icons are scaled to current font scale - Platform.runLater(() -> Ecdar.getPresentation().getController().scaleIcons(this)); - } - - public BackendInstancePresentation() { - controller = new EcdarFXMLLoader().loadAndGetController("BackendInstancePresentation.fxml", this); - - controller.pickPathToBackend.setCursor(Cursor.HAND); - controller.pickPathToBackend.setRipplerFill(Color.GREY.getColor(Color.Intensity.I500)); - controller.pickPathToBackend.setMaskType(JFXRippler.RipplerMask.CIRCLE); - } - - public BackendInstanceController getController() { - return controller; - } -} diff --git a/src/main/java/ecdar/presentations/BackendOptionsDialogPresentation.java b/src/main/java/ecdar/presentations/BackendOptionsDialogPresentation.java deleted file mode 100644 index c3949083..00000000 --- a/src/main/java/ecdar/presentations/BackendOptionsDialogPresentation.java +++ /dev/null @@ -1,16 +0,0 @@ -package ecdar.presentations; - -import com.jfoenix.controls.JFXDialog; -import ecdar.controllers.BackendOptionsDialogController; - -public class BackendOptionsDialogPresentation extends JFXDialog { - private final BackendOptionsDialogController controller; - - public BackendOptionsDialogPresentation() { - controller = new EcdarFXMLLoader().loadAndGetController("BackendOptionsDialogPresentation.fxml", this); - } - - public BackendOptionsDialogController getController() { - return controller; - } -} diff --git a/src/main/java/ecdar/presentations/CanvasPresentation.java b/src/main/java/ecdar/presentations/CanvasPresentation.java index c8fa7f56..726e85a3 100644 --- a/src/main/java/ecdar/presentations/CanvasPresentation.java +++ b/src/main/java/ecdar/presentations/CanvasPresentation.java @@ -2,10 +2,11 @@ import com.jfoenix.controls.JFXRippler; import com.jfoenix.skins.ValidationPane; +import ecdar.Ecdar; import ecdar.controllers.CanvasController; import ecdar.controllers.EcdarController; import ecdar.utility.colors.Color; -import ecdar.utility.helpers.MouseTrackable; +import ecdar.utility.helpers.LocationAware; import ecdar.utility.helpers.SelectHelper; import ecdar.utility.mouse.MouseTracker; import javafx.application.Platform; @@ -23,7 +24,7 @@ import javafx.scene.layout.*; import javafx.scene.shape.Rectangle; -public class CanvasPresentation extends StackPane implements MouseTrackable { +public class CanvasPresentation extends StackPane implements LocationAware { public MouseTracker mouseTracker; private final DoubleProperty x = new SimpleDoubleProperty(0); @@ -32,11 +33,11 @@ public class CanvasPresentation extends StackPane implements MouseTrackable { private final CanvasController controller; public CanvasPresentation() { - mouseTracker = new MouseTracker(this); controller = new EcdarFXMLLoader().loadAndGetController("CanvasPresentation.fxml", this); + mouseTracker = new MouseTracker(this); mouseTracker.registerOnMousePressedEventHandler(this::startDragSelect); - getController().root.addEventFilter(MouseEvent.MOUSE_PRESSED, event -> EcdarController.setActiveCanvasPresentation(this)); + getController().root.addEventFilter(MouseEvent.MOUSE_PRESSED, event -> Ecdar.getPresentation().getController().getEditorPresentation().getController().setActiveCanvasPresentation(this)); initializeModelDrag(); initializeToolbar(); @@ -121,14 +122,6 @@ private void initializeZoomHelper() { controller.zoomHelper.setCanvas(this); } - /** - * Updates if views should show an inset behind the error view. - * @param shouldShow true iff views should show an inset - */ - public static void showBottomInset(final Boolean shouldShow) { - EcdarController.getActiveCanvasPresentation().getController().updateOffset(shouldShow); - } - @Override public DoubleProperty xProperty() { return x; @@ -149,15 +142,14 @@ public double getY() { return yProperty().get(); } - @Override - public MouseTracker getMouseTracker() { - return mouseTracker; - } - public CanvasController getController() { return controller; } + /*** + * Start drawing selection rectangle for area selection + * @param event used for the origin of the selection rectangle + */ private void startDragSelect(final MouseEvent event) { if(event.isPrimaryButtonDown()) { SelectHelper.clearSelectedElements(); @@ -200,6 +192,12 @@ private void startDragSelect(final MouseEvent event) { } } + /*** + * Initialize the rectangle to use for selection + * @param mouseDownX X-coordinate for the initial mouse press + * @param mouseDownY Y-coordinate for the initial mouse press + * @return the initialized rectangle + */ private Rectangle initializeRectangleForSelectionBox(double mouseDownX, double mouseDownY) { Rectangle selectionRectangle = new Rectangle(); selectionRectangle.setStroke(SelectHelper.SELECT_COLOR.getColor(SelectHelper.SELECT_COLOR_INTENSITY_BORDER)); @@ -216,6 +214,11 @@ private Rectangle initializeRectangleForSelectionBox(double mouseDownX, double m return selectionRectangle; } + /*** + * Traverse the node graph recursively to update the set of nodes that should be selected + * @param currentNode the current 'root' node to traverse + * @param selectionRectangle the rectangle representing the selection area + */ private void updateSelection(Parent currentNode, Rectangle selectionRectangle) { // None of these nodes contain ItemSelectable nodes, so avoiding traversing these sub-trees improves performance if (currentNode instanceof VBox || currentNode instanceof ValidationPane || currentNode instanceof JFXRippler || currentNode instanceof BorderPane) { @@ -245,7 +248,7 @@ private void updateSelection(Parent currentNode, Rectangle selectionRectangle) { }); } - /** + /*** * Returns whether the item is within the selection box. * @param item the node to potentially be selected. * @param itemSelectable the ItemSelectable object related to the item (in order to get width and height for the checks). diff --git a/src/main/java/ecdar/presentations/ComponentInstancePresentation.java b/src/main/java/ecdar/presentations/ComponentInstancePresentation.java index a3a5c509..e15df2d7 100644 --- a/src/main/java/ecdar/presentations/ComponentInstancePresentation.java +++ b/src/main/java/ecdar/presentations/ComponentInstancePresentation.java @@ -5,6 +5,7 @@ import ecdar.controllers.ComponentInstanceController; import ecdar.controllers.EcdarController; import ecdar.utility.colors.Color; +import ecdar.utility.colors.EnabledColor; import ecdar.utility.helpers.ItemDragHelper; import ecdar.utility.helpers.SelectHelper; import javafx.beans.property.BooleanProperty; @@ -24,13 +25,14 @@ import java.util.ArrayList; import java.util.List; import java.util.function.BiConsumer; +import java.util.function.Consumer; /** * Presentation for a component instance. */ public class ComponentInstancePresentation extends StackPane implements SelectHelper.ItemSelectable { private final ComponentInstanceController controller; - private final List<BiConsumer<Color, Color.Intensity>> updateColorDelegates = new ArrayList<>(); + private final List<Consumer<EnabledColor>> updateColorDelegates = new ArrayList<>(); public ComponentInstancePresentation(final ComponentInstance instance, final EcdarSystem system) { controller = new EcdarFXMLLoader().loadAndGetController("ComponentInstancePresentation.fxml", this); @@ -64,28 +66,21 @@ public ComponentInstancePresentation(final ComponentInstance instance, final Ecd */ private void initializeNails() { final Component component = controller.getInstance().getComponent(); - final BiConsumer<Color, Color.Intensity> updateNailColor = (newColor, newIntensity) -> + final Consumer<EnabledColor> updateNailColor = (newColor) -> { - final Color color = newColor; - final Color.Intensity colorIntensity = newIntensity; + controller.inputNailCircle.setFill(newColor.getPaintColor()); + controller.inputNailCircle.setStroke(newColor.getStrokeColor()); - controller.inputNailCircle.setFill(color.getColor(colorIntensity)); - controller.inputNailCircle.setStroke(color.getColor(colorIntensity.next(2))); - - controller.outputNailCircle.setFill(color.getColor(colorIntensity)); - controller.outputNailCircle.setStroke(color.getColor(colorIntensity.next(2))); + controller.outputNailCircle.setFill(newColor.getPaintColor()); + controller.outputNailCircle.setStroke(newColor.getStrokeColor()); }; // When the color of the component updates, update the nail indicator as well controller.getInstance().getComponent().colorProperty().addListener( - (observable) -> updateNailColor.accept(component.getColor(), component.getColorIntensity())); - - // When the color intensity of the component updates, update the nail indicator - controller.getInstance().getComponent().colorIntensityProperty().addListener( - (observable) -> updateNailColor.accept(component.getColor(), component.getColorIntensity())); + (observable) -> updateNailColor.accept(component.getColor())); // Initialize the color of the nail with the current color - updateNailColor.accept(component.getColor(), component.getColorIntensity()); + updateNailColor.accept(component.getColor()); updateColorDelegates.add(updateNailColor); } @@ -107,15 +102,14 @@ private void initializeName() { controller.identifier.textProperty().bindBidirectional(instance.getInstanceIdProperty()); final Runnable updateColor = () -> { - final Color color = instance.getComponent().getColor(); - final Color.Intensity colorIntensity = instance.getComponent().getColorIntensity(); + final EnabledColor color = instance.getComponent().getColor(); // Set the text color for the label - controller.identifier.setStyle("-fx-text-fill: " + color.getTextColorRgbaString(colorIntensity) + ";"); - controller.identifier.setFocusColor(color.getTextColor(colorIntensity)); + controller.identifier.setStyle("-fx-text-fill: " + color.getTextColorRgbaString() + ";"); + controller.identifier.setFocusColor(color.getTextColor()); controller.identifier.setUnFocusColor(javafx.scene.paint.Color.TRANSPARENT); - controller.originalComponentLabel.setStyle("-fx-text-fill: " + color.getTextColorRgbaString(colorIntensity) + ";"); + controller.originalComponentLabel.setStyle("-fx-text-fill: " + color.getTextColorRgbaString() + ";"); }; // Update color and whenever color of the component changes @@ -124,7 +118,7 @@ private void initializeName() { // Center the text vertically and aff a left padding of CORNER_SIZE controller.identifier.setPadding(new Insets(2, 0, 0, Ecdar.CANVAS_PADDING * 4)); - controller.identifier.setOnKeyPressed(EcdarController.getActiveCanvasPresentation().getController().getLeaveTextAreaKeyHandler()); + controller.identifier.setOnKeyPressed(Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasPresentation().getController().getLeaveTextAreaKeyHandler()); controller.originalComponentLabel.setPadding(new Insets(0, 5, 0, 15)); controller.originalComponentLabel.textProperty().bind(instance.getComponent().nameProperty()); @@ -158,10 +152,10 @@ private void initializeDimensions() { private void initializeToolbar() { final Component component = controller.getInstance().getComponent(); - final BiConsumer<Color, Color.Intensity> updateColor = (newColor, newIntensity) -> { + final Consumer<EnabledColor> updateColor = (newColor) -> { // Set the background of the toolbar controller.toolbar.setBackground(new Background(new BackgroundFill( - newColor.getColor(newIntensity), + newColor.getPaintColor(), CornerRadii.EMPTY, Insets.EMPTY ))); @@ -170,8 +164,8 @@ private void initializeToolbar() { }; // Update color now, whenever color of component changes, and when someone uses the color delegates - updateColor.accept(component.getColor(), component.getColorIntensity()); - component.colorProperty().addListener(observable -> updateColor.accept(component.getColor(), component.getColorIntensity())); + updateColor.accept(component.getColor()); + component.colorProperty().addListener(observable -> updateColor.accept(component.getColor())); updateColorDelegates.add(updateColor); } @@ -183,8 +177,8 @@ private void initializeToolbar() { private void initializeSeparator() { final Component component = controller.getInstance().getComponent(); - final BiConsumer<Color, Color.Intensity> updateColor = (newColor, newIntensity) -> { - controller.separatorLine.setStroke(newColor.getColor(newIntensity.next(2))); + final Consumer<EnabledColor> updateColor = (newColor) -> { + controller.separatorLine.setStroke(newColor.getStrokeColor()); }; final Runnable drawLine = () -> { @@ -201,8 +195,8 @@ private void initializeSeparator() { heightProperty().addListener(observable -> drawLine.run()); controller.toolbar.heightProperty().addListener(obs -> drawLine.run()); - updateColor.accept(component.getColor(), component.getColorIntensity()); - component.colorProperty().addListener(observable -> updateColor.accept(component.getColor(), component.getColorIntensity())); + updateColor.accept(component.getColor()); + component.colorProperty().addListener(observable -> updateColor.accept(component.getColor())); updateColorDelegates.add(updateColor); } @@ -222,7 +216,7 @@ private void initializeFrame() { 0, Ecdar.CANVAS_PADDING * 4 + 2 ); - final BiConsumer<Color, Color.Intensity> updateColor = (newColor, newIntensity) -> { + final Consumer<EnabledColor> updateColor = (newColor) -> { // Mask the parent of the frame (will also mask the background) mask[0] = Path.subtract(rectangle, corner1); controller.frame.setClip(mask[0]); @@ -233,13 +227,13 @@ private void initializeFrame() { controller.line1.setStartY(0); controller.line1.setEndX(0); controller.line1.setEndY(Ecdar.CANVAS_PADDING * 4); - controller.line1.setStroke(newColor.getColor(newIntensity.next(2))); + controller.line1.setStroke(newColor.getStrokeColor()); controller.line1.setStrokeWidth(1.25); StackPane.setAlignment(controller.line1, Pos.TOP_LEFT); // Set the stroke color to two shades darker controller.frame.setBorder(new Border(new BorderStroke( - newColor.getColor(newIntensity.next(2)), + newColor.getStrokeColor(), BorderStrokeStyle.SOLID, CornerRadii.EMPTY, new BorderWidths(1), @@ -248,8 +242,8 @@ private void initializeFrame() { }; // Update color now, whenever color of component changes, and when someone uses the color delegates - updateColor.accept(component.getColor(), component.getColorIntensity()); - component.colorProperty().addListener(observable -> updateColor.accept(component.getColor(), component.getColorIntensity())); + updateColor.accept(component.getColor()); + component.colorProperty().addListener(observable -> updateColor.accept(component.getColor())); updateColorDelegates.add(updateColor); } @@ -263,14 +257,14 @@ private void initializeBackground() { controller.background.widthProperty().bind(minWidthProperty()); controller.background.heightProperty().bind(minHeightProperty()); - final BiConsumer<Color, Color.Intensity> updateColor = (newColor, newIntensity) -> { + final Consumer<EnabledColor> updateColor = (newColor) -> { // Set the background color to the lightest possible version of the color - controller.background.setFill(newColor.getColor(newIntensity.next(-20))); + controller.background.setFill(newColor.getLowestIntensity().getPaintColor()); }; // Update color now, whenever color of component changes, and when someone uses the color delegates - updateColor.accept(component.getColor(), component.getColorIntensity()); - component.colorProperty().addListener(observable -> updateColor.accept(component.getColor(), component.getColorIntensity())); + updateColor.accept(component.getColor()); + component.colorProperty().addListener(observable -> updateColor.accept(component.getColor())); updateColorDelegates.add(updateColor); } @@ -301,7 +295,7 @@ private void initializeMouseControls() { */ @Override public void select() { - updateColorDelegates.forEach(colorConsumer -> colorConsumer.accept(SelectHelper.SELECT_COLOR, SelectHelper.SELECT_COLOR_INTENSITY_NORMAL)); + updateColorDelegates.forEach(colorConsumer -> colorConsumer.accept(new EnabledColor(SelectHelper.SELECT_COLOR, SelectHelper.SELECT_COLOR_INTENSITY_NORMAL))); } /** @@ -312,7 +306,7 @@ public void deselect() { updateColorDelegates.forEach(colorConsumer -> { final Component component = controller.getInstance().getComponent(); - colorConsumer.accept(component.getColor(), component.getColorIntensity()); + colorConsumer.accept(component.getColor()); }); } @@ -321,30 +315,20 @@ public void deselect() { * But since, component instances use the color of the corresponding component, * this method does nothing. * @param color not used - * @param intensity not used */ @Override @Deprecated - public void color(final Color color, final Color.Intensity intensity) { } + public void color(final EnabledColor color) { } /** * Gets the color of the corresponding component. * @return the color of the component. */ @Override - public Color getColor() { + public EnabledColor getColor() { return controller.getInstance().getComponent().getColor(); } - /** - * Gets the color intensity of the corresponding component. - * @return the color of the component - */ - @Override - public Color.Intensity getColorIntensity() { - return controller.getInstance().getComponent().getColorIntensity(); - } - /** * Gets the bound that it is valid to drag the instance within. * @return the bounds diff --git a/src/main/java/ecdar/presentations/ComponentOperatorPresentation.java b/src/main/java/ecdar/presentations/ComponentOperatorPresentation.java index a09f861b..c4f53cc0 100644 --- a/src/main/java/ecdar/presentations/ComponentOperatorPresentation.java +++ b/src/main/java/ecdar/presentations/ComponentOperatorPresentation.java @@ -4,6 +4,7 @@ import ecdar.abstractions.EcdarSystem; import ecdar.controllers.ComponentOperatorController; import ecdar.utility.colors.Color; +import ecdar.utility.colors.EnabledColor; import ecdar.utility.helpers.ItemDragHelper; import ecdar.utility.helpers.SelectHelper; import javafx.beans.property.DoubleProperty; @@ -15,13 +16,14 @@ import java.util.ArrayList; import java.util.List; import java.util.function.BiConsumer; +import java.util.function.Consumer; /** * Presentation of a component operator */ public class ComponentOperatorPresentation extends StackPane implements SelectHelper.ItemSelectable { private final ComponentOperatorController controller; - private final List<BiConsumer<Color, Color.Intensity>> updateColorDelegates = new ArrayList<>(); + private final List<Consumer<EnabledColor>> updateColorDelegates = new ArrayList<>(); /** * Constructor for ComponentOperatorPresentation, sets the controller and initializes label, dimensions, frame and mouse controls @@ -53,11 +55,11 @@ private void initializeIdLabel() { idLabel.setTranslateY(-2); // Delegate to style the label based on the color of the location - final BiConsumer<Color, Color.Intensity> updateColor = (newColor, newIntensity) -> idLabel.setTextFill(newColor.getTextColor(newIntensity)); + final Consumer<EnabledColor> updateColor = (newColor) -> idLabel.setTextFill(newColor.getTextColor()); // Update color now and on color change - updateColor.accept(system.getColor(), system.getColorIntensity()); - system.colorProperty().addListener(observable -> updateColor.accept(system.getColor(), system.getColorIntensity())); + updateColor.accept(system.getColor()); + system.colorProperty().addListener(observable -> updateColor.accept(system.getColor())); updateColorDelegates.add(updateColor); } @@ -96,10 +98,10 @@ private void initializeFrame() { controller.frame.getPoints().addAll(1d * Ecdar.CANVAS_PADDING, 1d * Ecdar.CANVAS_PADDING); controller.frame.getPoints().addAll(2d * Ecdar.CANVAS_PADDING, 0d); - final BiConsumer<Color, Color.Intensity> updateColor = (newColor, newIntensity) -> controller.frame.setFill(newColor.getColor(newIntensity)); + final Consumer<EnabledColor> updateColor = (newColor) -> controller.frame.setFill(newColor.getPaintColor()); - updateColor.accept(system.getColor(), system.getColorIntensity()); - system.colorProperty().addListener(observable -> updateColor.accept(system.getColor(), system.getColorIntensity())); + updateColor.accept(system.getColor()); + system.colorProperty().addListener(observable -> updateColor.accept(system.getColor())); updateColorDelegates.add(updateColor); } @@ -128,19 +130,14 @@ private void initializeMouseControls() { * Is meant to set the color, but this feature is not available for operators so this method does nothing */ @Override - public void color(final Color color, final Color.Intensity intensity) { + public void color(final EnabledColor color) { } @Override - public Color getColor() { + public EnabledColor getColor() { return controller.getSystem().getColor(); } - @Override - public Color.Intensity getColorIntensity() { - return controller.getSystem().getColorIntensity(); - } - /** * Gets the bound that it is valid to drag the operator within. * @return the bounds @@ -191,7 +188,7 @@ public double getSelectableHeight() { */ @Override public void select() { - updateColorDelegates.forEach(colorConsumer -> colorConsumer.accept(SelectHelper.SELECT_COLOR, SelectHelper.SELECT_COLOR_INTENSITY_NORMAL)); + updateColorDelegates.forEach(colorConsumer -> colorConsumer.accept(new EnabledColor(SelectHelper.SELECT_COLOR, SelectHelper.SELECT_COLOR_INTENSITY_NORMAL))); } /** @@ -202,7 +199,7 @@ public void deselect() { updateColorDelegates.forEach(colorConsumer -> { final EcdarSystem system = controller.getSystem(); - colorConsumer.accept(system.getColor(), system.getColorIntensity()); + colorConsumer.accept(system.getColor()); }); } diff --git a/src/main/java/ecdar/presentations/ComponentPresentation.java b/src/main/java/ecdar/presentations/ComponentPresentation.java index bb59f463..ff5515f8 100644 --- a/src/main/java/ecdar/presentations/ComponentPresentation.java +++ b/src/main/java/ecdar/presentations/ComponentPresentation.java @@ -1,180 +1,25 @@ package ecdar.presentations; - -import ecdar.Ecdar; import ecdar.abstractions.Component; -import ecdar.abstractions.Edge; -import ecdar.abstractions.Location; -import ecdar.abstractions.Nail; import ecdar.controllers.ComponentController; -import ecdar.controllers.ModelController; -import ecdar.utility.colors.Color; -import ecdar.utility.helpers.MouseTrackable; +import ecdar.utility.helpers.LocationAware; import ecdar.utility.helpers.SelectHelper; -import ecdar.utility.mouse.MouseTracker; import javafx.beans.property.DoubleProperty; -import javafx.geometry.Insets; -import javafx.geometry.Pos; -import javafx.scene.Cursor; -import javafx.scene.layout.*; -import javafx.scene.shape.*; -import org.fxmisc.richtext.model.StyleSpans; -import org.fxmisc.richtext.model.StyleSpansBuilder; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.function.BiConsumer; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -public class ComponentPresentation extends ModelPresentation implements MouseTrackable, SelectHelper.Selectable { - private static final String uppaalKeywords = "clock|chan|urgent|broadcast"; - private static final String cKeywords = "auto|bool|break|case|char|const|continue|default|do|double|else|enum|extern|float|for|goto|if|int|long|register|return|short|signed|sizeof|static|struct|switch|typedef|union|unsigned|void|volatile|while"; - private static final Pattern UPPAAL = Pattern.compile("" - + "(" + uppaalKeywords + ")" - + "|(" + cKeywords + ")" - + "|(//.*|(\"(?:\\\\[^\"]|\\\\\"|.)*?\")|(?s)/\\*.*?\\*/)"); +public class ComponentPresentation extends ModelPresentation implements LocationAware, SelectHelper.Selectable { private final ComponentController controller; - private final List<BiConsumer<Color, Color.Intensity>> updateColorDelegates = new ArrayList<>(); public ComponentPresentation(final Component component) { controller = new EcdarFXMLLoader().loadAndGetController("ComponentPresentation.fxml", this); controller.setComponent(component); - super.initialize(component.getBox()); - - // Initialize methods that is sensitive to width and height - final Runnable onUpdateSize = () -> { - initializeToolbar(); - initializeFrame(); - initializeBackground(); - }; - - onUpdateSize.run(); - - // Re run initialisation on update of width and height property - component.getBox().getWidthProperty().addListener(observable -> onUpdateSize.run()); - component.getBox().getHeightProperty().addListener(observable -> onUpdateSize.run()); - - controller.declarationTextArea.textProperty().addListener((obs, oldText, newText) -> - controller.declarationTextArea.setStyleSpans(0, computeHighlighting(newText))); - } - - public static StyleSpans<Collection<String>> computeHighlighting(final String text) { - final Matcher matcher = UPPAAL.matcher(text); - int lastKwEnd = 0; - final StyleSpansBuilder<Collection<String>> spansBuilder = new StyleSpansBuilder<>(); - while (matcher.find()) { - - spansBuilder.add(Collections.emptyList(), matcher.start() - lastKwEnd); - - if (matcher.group(1) != null) { - spansBuilder.add(Collections.singleton("uppaal-keyword"), matcher.end(1) - matcher.start(1)); - } else if (matcher.group(2) != null) { - spansBuilder.add(Collections.singleton("c-keyword"), matcher.end(2) - matcher.start(2)); - } else if (matcher.group(3) != null) { - spansBuilder.add(Collections.singleton("comment"), matcher.end(3) - matcher.start(3)); - } - - lastKwEnd = matcher.end(); - } - - spansBuilder.add(Collections.emptyList(), text.length() - lastKwEnd); - return spansBuilder.create(); - } - - private void initializeToolbar() { - final Component component = controller.getComponent(); - - final BiConsumer<Color, Color.Intensity> updateColor = (newColor, newIntensity) -> { - // Set the background of the toolbar - controller.toolbar.setBackground(new Background(new BackgroundFill( - newColor.getColor(newIntensity), - CornerRadii.EMPTY, - Insets.EMPTY - ))); - - // Set the icon color and rippler color of the toggleDeclarationButton - controller.toggleDeclarationButton.setRipplerFill(newColor.getTextColor(newIntensity)); - - controller.toolbar.setPrefHeight(Ecdar.CANVAS_PADDING * 2); - controller.toggleDeclarationButton.setBackground(Background.EMPTY); - }; - - updateColorDelegates.add(updateColor); - - controller.getComponent().colorProperty().addListener(observable -> updateColor.accept(component.getColor(), component.getColorIntensity())); - - updateColor.accept(component.getColor(), component.getColorIntensity()); - - // Set a hover effect for the controller.toggleDeclarationButton - controller.toggleDeclarationButton.setOnMouseEntered(event -> controller.toggleDeclarationButton.setCursor(Cursor.HAND)); - controller.toggleDeclarationButton.setOnMouseExited(event -> controller.toggleDeclarationButton.setCursor(Cursor.DEFAULT)); - controller.toggleDeclarationButton.setOnMousePressed(controller::toggleDeclaration); - - } - - private void initializeFrame() { - final Component component = controller.getComponent(); - - final Shape[] mask = new Shape[1]; - final Rectangle rectangle = new Rectangle(component.getBox().getWidth(), component.getBox().getHeight()); - - final BiConsumer<Color, Color.Intensity> updateColor = (newColor, newIntensity) -> { - // Mask the parent of the frame (will also mask the background) - mask[0] = Path.subtract(rectangle, TOP_LEFT_CORNER); - controller.frame.setClip(mask[0]); - controller.background.setClip(Path.union(mask[0], mask[0])); - controller.background.setOpacity(0.5); - - // Bind the missing lines that we cropped away - controller.topLeftLine.setStartX(Ecdar.CANVAS_PADDING * 4); - controller.topLeftLine.setStartY(0); - controller.topLeftLine.setEndX(0); - controller.topLeftLine.setEndY(Ecdar.CANVAS_PADDING * 4); - controller.topLeftLine.setStroke(newColor.getColor(newIntensity.next(2))); - controller.topLeftLine.setStrokeWidth(1.25); - StackPane.setAlignment(controller.topLeftLine, Pos.TOP_LEFT); - - // Set the stroke color to two shades darker - controller.frame.setBorder(new Border(new BorderStroke( - newColor.getColor(newIntensity.next(2)), - BorderStrokeStyle.SOLID, - CornerRadii.EMPTY, - new BorderWidths(1), - Insets.EMPTY - ))); - }; - - updateColorDelegates.add(updateColor); - - component.colorProperty().addListener(observable -> { - updateColor.accept(component.getColor(), component.getColorIntensity()); - }); - - updateColor.accept(component.getColor(), component.getColorIntensity()); + minHeightProperty().bindBidirectional(component.getBox().getHeightProperty()); + maxHeightProperty().bindBidirectional(component.getBox().getHeightProperty()); + minWidthProperty().bindBidirectional(component.getBox().getWidthProperty()); + maxWidthProperty().bindBidirectional(component.getBox().getWidthProperty()); } - private void initializeBackground() { - final Component component = controller.getComponent(); - - // Bind the background width and height to the values in the model - controller.background.widthProperty().bindBidirectional(component.getBox().getWidthProperty()); - controller.background.heightProperty().bindBidirectional(component.getBox().getHeightProperty()); - - final BiConsumer<Color, Color.Intensity> updateColor = (newColor, newIntensity) -> { - // Set the background color to the lightest possible version of the color - controller.background.setFill(newColor.getColor(newIntensity.next(-10).next(2))); - }; - - updateColorDelegates.add(updateColor); - - component.colorProperty().addListener(observable -> { - updateColor.accept(component.getColor(), component.getColorIntensity()); - }); - - updateColor.accept(component.getColor(), component.getColorIntensity()); + public ComponentController getController() { + return controller; } @Override @@ -197,76 +42,13 @@ public double getY() { return yProperty().get(); } - public ComponentController getController() { - return controller; - } - - @Override - public MouseTracker getMouseTracker() { - return controller.getMouseTracker(); - } - @Override public void select() { - updateColorDelegates.forEach(colorConsumer -> colorConsumer.accept(SelectHelper.SELECT_COLOR, SelectHelper.SELECT_COLOR_INTENSITY_NORMAL)); + controller.componentSelected(); } @Override public void deselect() { - updateColorDelegates.forEach(colorConsumer -> { - final Component component = controller.getComponent(); - - colorConsumer.accept(component.getColor(), component.getColorIntensity()); - }); - } - - @Override - ModelController getModelController() { - return getController(); - } - - @Override - double getDragAnchorMinWidth() { - final Component component = controller.getComponent(); - double minWidth = Ecdar.CANVAS_PADDING * 10; - - for (final Location location : component.getLocations()) { - minWidth = Math.max(minWidth, location.getX() + Ecdar.CANVAS_PADDING * 2); - } - - for (final Edge edge : component.getEdges()) { - for (final Nail nail : edge.getNails()) { - minWidth = Math.max(minWidth, nail.getX() + Ecdar.CANVAS_PADDING); - } - } - - return minWidth; - } - - /** - * Gets the minimum possible height when dragging the anchor. - * The height is based on the y coordinate of locations, nails and the signature arrows - * @return the minimum possible height. - */ - @Override - double getDragAnchorMinHeight() { - final Component component = controller.getComponent(); - double minHeight = 100; - - for (final Location location : component.getLocations()) { - minHeight = Math.max(minHeight, location.getY() + 20); - } - - for (final Edge edge : component.getEdges()) { - for (final Nail nail : edge.getNails()) { - minHeight = Math.max(minHeight, nail.getY() + 10); - } - } - - //Component should not get smaller than the height of the input/output signature containers - minHeight = Math.max(controller.inputSignatureContainer.getHeight(), minHeight); - minHeight = Math.max(controller.outputSignatureContainer.getHeight(), minHeight); - - return minHeight; + controller.componentDeselected(); } } diff --git a/src/main/java/ecdar/presentations/DeclarationPresentation.java b/src/main/java/ecdar/presentations/DeclarationsPresentation.java similarity index 60% rename from src/main/java/ecdar/presentations/DeclarationPresentation.java rename to src/main/java/ecdar/presentations/DeclarationsPresentation.java index 60740c4a..99eff0de 100644 --- a/src/main/java/ecdar/presentations/DeclarationPresentation.java +++ b/src/main/java/ecdar/presentations/DeclarationsPresentation.java @@ -2,19 +2,25 @@ import ecdar.abstractions.Declarations; import ecdar.controllers.DeclarationsController; +import ecdar.controllers.HighLevelModelController; /** * Presentation for overall declarations. */ -public class DeclarationPresentation extends HighLevelModelPresentation { +public class DeclarationsPresentation extends HighLevelModelPresentation { private final DeclarationsController controller; - public DeclarationPresentation(final Declarations declarations) { - controller = new EcdarFXMLLoader().loadAndGetController("DeclarationPresentation.fxml", this); + public DeclarationsPresentation(final Declarations declarations) { + controller = new EcdarFXMLLoader().loadAndGetController("DeclarationsPresentation.fxml", this); controller.setDeclarations(declarations); // Listen to changes and controller.textArea.textProperty().addListener((obs, oldText, newText) -> controller.updateHighlighting()); } + + @Override + public HighLevelModelController getController() { + return controller; + } } diff --git a/src/main/java/ecdar/presentations/DropDownMenu.java b/src/main/java/ecdar/presentations/DropDownMenu.java index ce2ebdab..3a389499 100644 --- a/src/main/java/ecdar/presentations/DropDownMenu.java +++ b/src/main/java/ecdar/presentations/DropDownMenu.java @@ -31,7 +31,6 @@ /** * DropDownMenu is a {@link JFXPopup} which is used as the right-click menu and options menu on for instance - * {@link QueryPresentation#actionButton}. * The DropDownMenu includes methods for adding elements to the menu itself. * * Batteries included @@ -266,15 +265,15 @@ public void addSmallSpacerElement() { * @param hasColor The current color * @param consumer A consumer for the color property */ - public void addColorPicker(final HasColor hasColor, final BiConsumer<Color, Color.Intensity> consumer) { + public void addColorPicker(final HasColor hasColor, final Consumer<EnabledColor> consumer) { addListElement("Color"); final FlowPane flowPane = new FlowPane(); flowPane.setStyle("-fx-padding: 0 8 0 8"); for (final EnabledColor color : enabledColors) { - final Circle circle = new Circle(16, color.color.getColor(color.intensity)); - circle.setStroke(color.color.getColor(color.intensity.next(2))); + final Circle circle = new Circle(16, color.getPaintColor()); + circle.setStroke(color.getStrokeColor()); circle.setStrokeWidth(1); final FontIcon icon = new FontIcon(); @@ -310,7 +309,7 @@ public void addColorPicker(final HasColor hasColor, final BiConsumer<Color, Colo // Only color the subject if the user chooses a new color if (hasColor.colorProperty().get().equals(color.color)) return; - consumer.accept(color.color, color.intensity); + consumer.accept(color); }); flowPane.getChildren().add(child); @@ -501,8 +500,6 @@ private void makeSpacerElement(final int height) { } public interface HasColor { - ObjectProperty<Color> colorProperty(); - - ObjectProperty<Color.Intensity> colorIntensityProperty(); + ObjectProperty<EnabledColor> colorProperty(); } } \ No newline at end of file diff --git a/src/main/java/ecdar/presentations/EcdarPresentation.java b/src/main/java/ecdar/presentations/EcdarPresentation.java index b6085699..f69c2213 100644 --- a/src/main/java/ecdar/presentations/EcdarPresentation.java +++ b/src/main/java/ecdar/presentations/EcdarPresentation.java @@ -2,24 +2,24 @@ import com.jfoenix.controls.JFXSnackbarLayout; import ecdar.Ecdar; +import ecdar.abstractions.Component; import ecdar.abstractions.Query; import ecdar.abstractions.Snackbar; import ecdar.controllers.EcdarController; import ecdar.utility.UndoRedoStack; import ecdar.utility.colors.Color; +import ecdar.utility.helpers.ImageScaler; import com.jfoenix.controls.JFXSnackbar; import ecdar.utility.keyboard.Keybind; import ecdar.utility.keyboard.KeyboardTracker; import javafx.animation.*; import javafx.application.Platform; import javafx.beans.property.BooleanProperty; -import javafx.beans.property.DoubleProperty; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleDoubleProperty; import javafx.collections.ListChangeListener; import javafx.geometry.Insets; import javafx.scene.image.Image; -import javafx.scene.image.ImageView; import javafx.scene.input.KeyCode; import javafx.scene.input.KeyCodeCombination; import javafx.scene.input.KeyCombination; @@ -29,9 +29,9 @@ public class EcdarPresentation extends StackPane { private final EcdarController controller; - private final BooleanProperty leftPaneOpen = new SimpleBooleanProperty(false); + private final BooleanProperty leftPaneOpen = new SimpleBooleanProperty(true); private final SimpleDoubleProperty leftPaneAnimationProperty = new SimpleDoubleProperty(0); - private final BooleanProperty rightPaneOpen = new SimpleBooleanProperty(false); + private final BooleanProperty rightPaneOpen = new SimpleBooleanProperty(true); private final SimpleDoubleProperty rightPaneAnimationProperty = new SimpleDoubleProperty(0); private Timeline openLeftPaneAnimation; private Timeline closeLeftPaneAnimation; @@ -66,8 +66,10 @@ public EcdarPresentation() { controller.topPane.minHeightProperty().bind(controller.menuBar.heightProperty()); controller.topPane.maxHeightProperty().bind(controller.menuBar.heightProperty()); - toggleLeftPane(); - toggleRightPane(); + EcdarController.currentMode.addListener(observable -> { + initializeToggleLeftPaneFunctionality(); + initializeToggleRightPaneFunctionality(); + }); Ecdar.getPresentation().controller.scalingProperty.addListener((observable, oldValue, newValue) -> { // If the scaling has changed trigger animations for open panes to update width @@ -80,17 +82,15 @@ public EcdarPresentation() { } }); }); + + // Trigger closing followed by opening of the left pane to ensure correct placement + closeLeftPaneAnimation.setOnFinished((e) -> openLeftPaneAnimation.play()); + closeLeftPaneAnimation.play(); }); initializeHelpImages(); - KeyboardTracker.registerKeybind(KeyboardTracker.ZOOM_IN, new Keybind(new KeyCodeCombination(KeyCode.PLUS, KeyCombination.SHORTCUT_DOWN), () -> EcdarController.getActiveCanvasPresentation().getController().zoomHelper.zoomIn())); - KeyboardTracker.registerKeybind(KeyboardTracker.ZOOM_OUT, new Keybind(new KeyCodeCombination(KeyCode.MINUS, KeyCombination.SHORTCUT_DOWN), () -> EcdarController.getActiveCanvasPresentation().getController().zoomHelper.zoomOut())); - KeyboardTracker.registerKeybind(KeyboardTracker.ZOOM_TO_FIT, new Keybind(new KeyCodeCombination(KeyCode.EQUALS, KeyCombination.SHORTCUT_DOWN), () -> EcdarController.getActiveCanvasPresentation().getController().zoomHelper.zoomToFit())); - KeyboardTracker.registerKeybind(KeyboardTracker.RESET_ZOOM, new Keybind(new KeyCodeCombination(KeyCode.DIGIT0, KeyCombination.SHORTCUT_DOWN), () -> EcdarController.getActiveCanvasPresentation().getController().zoomHelper.resetZoom())); KeyboardTracker.registerKeybind(KeyboardTracker.UNDO, new Keybind(new KeyCodeCombination(KeyCode.Z, KeyCombination.SHORTCUT_DOWN), UndoRedoStack::undo)); KeyboardTracker.registerKeybind(KeyboardTracker.REDO, new Keybind(new KeyCodeCombination(KeyCode.Z, KeyCombination.SHORTCUT_DOWN, KeyCombination.SHIFT_DOWN), UndoRedoStack::redo)); - - initializeResizeQueryPane(); } private void initializeSnackbar() { @@ -116,11 +116,10 @@ private void initializeToggleLeftPaneFunctionality() { initializeCloseLeftPaneAnimation(); // Translate the x coordinate to create the open/close animations - controller.projectPane.translateXProperty().bind(leftPaneAnimationProperty.subtract(controller.projectPane.widthProperty())); - controller.leftSimPane.translateXProperty().bind(leftPaneAnimationProperty.subtract(controller.leftSimPane.widthProperty())); + controller.getLeftModePane().translateXProperty().bind(leftPaneAnimationProperty.subtract(controller.getLeftModePane().widthProperty())); // Whenever the width of the file pane is updated, update the animations - controller.projectPane.widthProperty().addListener((observable) -> { + controller.getLeftModePane().widthProperty().addListener((observable) -> { initializeOpenLeftPaneAnimation(); initializeCloseLeftPaneAnimation(); }); @@ -130,6 +129,7 @@ private void initializeToggleLeftPaneFunctionality() { initializeOpenLeftPaneAnimation(); initializeCloseLeftPaneAnimation(); }); + } private void initializeCloseLeftPaneAnimation() { @@ -137,7 +137,7 @@ private void initializeCloseLeftPaneAnimation() { closeLeftPaneAnimation = new Timeline(); - final KeyValue open = new KeyValue(leftPaneAnimationProperty, controller.projectPane.getWidth(), interpolator); + final KeyValue open = new KeyValue(leftPaneAnimationProperty, controller.getLeftModePane().getWidth(), interpolator); final KeyValue closed = new KeyValue(leftPaneAnimationProperty, 0, interpolator); final KeyFrame kf1 = new KeyFrame(Duration.millis(0), open); @@ -152,7 +152,7 @@ private void initializeOpenLeftPaneAnimation() { openLeftPaneAnimation = new Timeline(); final KeyValue closed = new KeyValue(leftPaneAnimationProperty, 0, interpolator); - final KeyValue open = new KeyValue(leftPaneAnimationProperty, controller.projectPane.getWidth(), interpolator); + final KeyValue open = new KeyValue(leftPaneAnimationProperty, controller.getLeftModePane().getWidth(), interpolator); final KeyFrame kf1 = new KeyFrame(Duration.millis(0), closed); final KeyFrame kf2 = new KeyFrame(Duration.millis(200), open); @@ -165,31 +165,34 @@ private void initializeToggleRightPaneFunctionality() { initializeCloseRightPaneAnimation(); // Translate the x coordinate to create the open/close animations - controller.queryPane.translateXProperty().bind(rightPaneAnimationProperty.multiply(-1).add(controller.queryPane.widthProperty())); - controller.rightSimPane.translateXProperty().bind(rightPaneAnimationProperty.multiply(-1).add(controller.rightSimPane.widthProperty())); + controller.getRightModePane().translateXProperty().bind(rightPaneAnimationProperty.multiply(-1).add(controller.getRightModePane().widthProperty())); // Whenever the width of the query pane is updated, update the animations - controller.queryPane.widthProperty().addListener((observable) -> { + controller.getRightModePane().widthProperty().addListener((observable, oldWidth, newWidth) -> { + Platform.runLater(() -> rightPaneAnimationProperty.set(controller.getRightModePane().getWidth())); + initializeOpenRightPaneAnimation(); initializeCloseRightPaneAnimation(); }); - // When new queries are added, make sure that the query pane is open - Ecdar.getProject().getQueries().addListener((ListChangeListener<Query>) c -> { - if (closeRightPaneAnimation == null) - return; // The query pane is not yet initialized - - while (c.next()) { - c.getAddedSubList().forEach(o -> { - if (!rightPaneOpen.get()) { - // Open the pane - openRightPaneAnimation.play(); - - // Toggle the open state - rightPaneOpen.set(rightPaneOpen.not().get()); - } - }); - } + Platform.runLater(() -> { + // When new queries are added, make sure that the query pane is open + Ecdar.getProject().getQueries().addListener((ListChangeListener<Query>) c -> { + if (closeRightPaneAnimation == null) + return; // The query pane is not yet initialized + + while (c.next()) { + c.getAddedSubList().forEach(o -> { + if (!rightPaneOpen.get()) { + // Open the pane + openRightPaneAnimation.play(); + + // Toggle the open state + rightPaneOpen.set(true); + } + }); + } + }); }); } @@ -198,7 +201,7 @@ private void initializeCloseRightPaneAnimation() { closeRightPaneAnimation = new Timeline(); - final KeyValue open = new KeyValue(rightPaneAnimationProperty, controller.queryPane.getWidth(), interpolator); + final KeyValue open = new KeyValue(rightPaneAnimationProperty, controller.getRightModePane().getWidth(), interpolator); final KeyValue closed = new KeyValue(rightPaneAnimationProperty, 0, interpolator); final KeyFrame kf1 = new KeyFrame(Duration.millis(0), open); @@ -213,7 +216,7 @@ private void initializeOpenRightPaneAnimation() { openRightPaneAnimation = new Timeline(); final KeyValue closed = new KeyValue(rightPaneAnimationProperty, 0, interpolator); - final KeyValue open = new KeyValue(rightPaneAnimationProperty, controller.queryPane.getWidth(), interpolator); + final KeyValue open = new KeyValue(rightPaneAnimationProperty, controller.getRightModePane().getWidth(), interpolator); final KeyFrame kf1 = new KeyFrame(Duration.millis(0), closed); final KeyFrame kf2 = new KeyFrame(Duration.millis(200), open); @@ -246,39 +249,16 @@ private void initializeTopBar() { */ private void initializeHelpImages() { controller.helpInitialImage.setImage(new Image(Ecdar.class.getResource("ic_help_initial.png").toExternalForm())); - fitSizeWhenAvailable(controller.helpInitialImage, controller.helpInitialPane); + ImageScaler.fitImageToPane(controller.helpInitialImage, controller.helpInitialPane); controller.helpUrgentImage.setImage(new Image(Ecdar.class.getResource("ic_help_urgent.png").toExternalForm())); - fitSizeWhenAvailable(controller.helpUrgentImage, controller.helpUrgentPane); + ImageScaler.fitImageToPane(controller.helpUrgentImage, controller.helpUrgentPane); controller.helpInputImage.setImage(new Image(Ecdar.class.getResource("ic_help_input.png").toExternalForm())); - fitSizeWhenAvailable(controller.helpInputImage, controller.helpInputPane); + ImageScaler.fitImageToPane(controller.helpInputImage, controller.helpInputPane); controller.helpOutputImage.setImage(new Image(Ecdar.class.getResource("ic_help_output.png").toExternalForm())); - fitSizeWhenAvailable(controller.helpOutputImage, controller.helpOutputPane); - } - - private void initializeResizeQueryPane() { - final DoubleProperty prevX = new SimpleDoubleProperty(); - final DoubleProperty prevWidth = new SimpleDoubleProperty(); - - controller.queryPane.getController().resizeAnchor.setOnMousePressed(event -> { - event.consume(); - - prevX.set(event.getScreenX()); - prevWidth.set(controller.queryPane.getWidth()); - }); - - controller.queryPane.getController().resizeAnchor.setOnMouseDragged(event -> { - double diff = prevX.get() - event.getScreenX(); - - // Set bounds for resizing to be between 280px and half the screen width - final double newWidth = Math.min(Math.max(prevWidth.get() + diff, 280), controller.root.getWidth() / 2); - - rightPaneAnimationProperty.set(newWidth); - controller.queryPane.setMaxWidth(newWidth); - controller.queryPane.setMinWidth(newWidth); - }); + ImageScaler.fitImageToPane(controller.helpOutputImage, controller.helpOutputPane); } public BooleanProperty toggleLeftPane() { @@ -307,13 +287,6 @@ public BooleanProperty toggleRightPane() { return rightPaneOpen; } - public static void fitSizeWhenAvailable(final ImageView imageView, final StackPane pane) { - pane.widthProperty().addListener((observable, oldValue, newValue) -> - imageView.setFitWidth(pane.getWidth())); - pane.heightProperty().addListener((observable, oldValue, newValue) -> - imageView.setFitHeight(pane.getHeight())); - } - public void showSnackbarMessage(final String message) { JFXSnackbarLayout content = new JFXSnackbarLayout(message); controller.snackbar.enqueue(new JFXSnackbar.SnackbarEvent(content, new Duration(5000))); diff --git a/src/main/java/ecdar/presentations/EditorPresentation.java b/src/main/java/ecdar/presentations/EditorPresentation.java index d17a4ab0..e3abc899 100644 --- a/src/main/java/ecdar/presentations/EditorPresentation.java +++ b/src/main/java/ecdar/presentations/EditorPresentation.java @@ -163,7 +163,7 @@ private void initializeColorSelector() { final List<Pair<SelectHelper.ItemSelectable, EnabledColor>> previousColor = new ArrayList<>(); SelectHelper.getSelectedElements().forEach(selectable -> { - previousColor.add(new Pair<>(selectable, new EnabledColor(selectable.getColor(), selectable.getColorIntensity()))); + previousColor.add(new Pair<>(selectable, selectable.getColor())); }); controller.changeColorOnSelectedElements(color, previousColor); diff --git a/src/main/java/ecdar/presentations/EngineOptionsDialogPresentation.java b/src/main/java/ecdar/presentations/EngineOptionsDialogPresentation.java new file mode 100644 index 00000000..e08de3e4 --- /dev/null +++ b/src/main/java/ecdar/presentations/EngineOptionsDialogPresentation.java @@ -0,0 +1,16 @@ +package ecdar.presentations; + +import com.jfoenix.controls.JFXDialog; +import ecdar.controllers.EngineOptionsDialogController; + +public class EngineOptionsDialogPresentation extends JFXDialog { + private final EngineOptionsDialogController controller; + + public EngineOptionsDialogPresentation() { + controller = new EcdarFXMLLoader().loadAndGetController("EngineOptionsDialogPresentation.fxml", this); + } + + public EngineOptionsDialogController getController() { + return controller; + } +} diff --git a/src/main/java/ecdar/presentations/EnginePresentation.java b/src/main/java/ecdar/presentations/EnginePresentation.java new file mode 100644 index 00000000..4923221c --- /dev/null +++ b/src/main/java/ecdar/presentations/EnginePresentation.java @@ -0,0 +1,34 @@ +package ecdar.presentations; + +import com.jfoenix.controls.JFXRippler; +import ecdar.Ecdar; +import ecdar.backend.Engine; +import ecdar.controllers.EngineInstanceController; +import ecdar.utility.colors.Color; +import javafx.application.Platform; +import javafx.scene.Cursor; +import javafx.scene.layout.StackPane; + +public class EnginePresentation extends StackPane { + private final EngineInstanceController controller; + + public EnginePresentation(Engine engine) { + this(); + controller.setEngine(engine); + + // Ensure that the icons are scaled to current font scale + Platform.runLater(() -> Ecdar.getPresentation().getController().scaleIcons(this)); + } + + public EnginePresentation() { + controller = new EcdarFXMLLoader().loadAndGetController("EnginePresentation.fxml", this); + + controller.pickPathToEngine.setCursor(Cursor.HAND); + controller.pickPathToEngine.setRipplerFill(Color.GREY.getColor(Color.Intensity.I500)); + controller.pickPathToEngine.setMaskType(JFXRippler.RipplerMask.CIRCLE); + } + + public EngineInstanceController getController() { + return controller; + } +} diff --git a/src/main/java/ecdar/presentations/FilePresentation.java b/src/main/java/ecdar/presentations/FilePresentation.java index 700b61b8..de61861f 100644 --- a/src/main/java/ecdar/presentations/FilePresentation.java +++ b/src/main/java/ecdar/presentations/FilePresentation.java @@ -1,206 +1,23 @@ package ecdar.presentations; import ecdar.Ecdar; -import ecdar.abstractions.Component; -import ecdar.abstractions.EcdarSystem; -import ecdar.abstractions.HighLevelModelObject; -import ecdar.controllers.EcdarController; +import ecdar.abstractions.HighLevelModel; import ecdar.controllers.FileController; -import ecdar.mutation.models.MutationTestPlan; -import ecdar.utility.colors.Color; -import com.jfoenix.controls.JFXRippler; import javafx.application.Platform; -import javafx.beans.property.SimpleObjectProperty; -import javafx.geometry.Insets; -import javafx.scene.Cursor; -import javafx.scene.Node; -import javafx.scene.control.Label; -import javafx.scene.image.Image; import javafx.scene.layout.*; -import javafx.scene.shape.Circle; -import org.kordamp.ikonli.javafx.FontIcon; - -import java.util.ArrayList; -import java.util.List; -import java.util.function.BiConsumer; public class FilePresentation extends AnchorPane { - private final SimpleObjectProperty<HighLevelModelObject> model = new SimpleObjectProperty<>(null); - private final FileController controller; - private boolean isActive = false; - public FilePresentation(final HighLevelModelObject model) { + public FilePresentation(final HighLevelModel model) { controller = new EcdarFXMLLoader().loadAndGetController("FilePresentation.fxml", this); - - this.model.set(model); - - initializeIcon(); - initializeFileIcons(); - initializeFileName(); - initializeColors(); - initializeRippler(); - initializeMoreInformationButton(); + controller.setModel(model); // Ensure that the icons are scaled to current font scale Platform.runLater(() -> Ecdar.getPresentation().getController().scaleIcons(this)); } - private void initializeMoreInformationButton() { - if (getModel() instanceof Component || getModel() instanceof EcdarSystem || getModel() instanceof MutationTestPlan) { - controller.moreInformation.setVisible(true); - controller.moreInformation.setMaskType(JFXRippler.RipplerMask.CIRCLE); - controller.moreInformation.setPosition(JFXRippler.RipplerPos.BACK); - controller.moreInformation.setRipplerFill(Color.GREY_BLUE.getColor(Color.Intensity.I500)); - } - } - - private void initializeRippler() { - final JFXRippler rippler = (JFXRippler) lookup("#rippler"); - - final Color color = Color.GREY_BLUE; - final Color.Intensity colorIntensity = Color.Intensity.I400; - - rippler.setMaskType(JFXRippler.RipplerMask.RECT); - rippler.setRipplerFill(color.getColor(colorIntensity)); - rippler.setPosition(JFXRippler.RipplerPos.BACK); - } - - private void initializeFileName() { - final Label label = (Label) lookup("#fileName"); - - model.get().nameProperty().addListener((obs, oldName, newName) -> label.setText(newName)); - label.setText(model.get().getName()); - } - - private void initializeIcon() { - final Circle circle = (Circle) lookup("#iconBackground"); - - model.get().colorProperty().addListener((obs, oldColor, newColor) -> { - circle.setFill(newColor.getColor(model.get().getColorIntensity())); - }); - - circle.setFill(model.get().getColor().getColor(model.get().getColorIntensity())); - } - - private void initializeColors() { - final FontIcon moreInformationIcon = (FontIcon) lookup("#moreInformationIcon"); - - final Color color = Color.GREY_BLUE; - final Color.Intensity colorIntensity = Color.Intensity.I50; - - final BiConsumer<Color, Color.Intensity> setBackground = (newColor, newIntensity) -> { - setBackground(new Background(new BackgroundFill( - newColor.getColor(newIntensity), - CornerRadii.EMPTY, - Insets.EMPTY - ))); - - setBorder(new Border(new BorderStroke( - newColor.getColor(newIntensity.next(2)), - BorderStrokeStyle.SOLID, - CornerRadii.EMPTY, - new BorderWidths(0, 0, 1, 0) - ))); - - moreInformationIcon.setFill(newColor.getColor(newIntensity.next(5))); - }; - - // Update the background when hovered - setOnMouseEntered(event -> { - if(isActive) { - setBackground.accept(color, colorIntensity.next(2)); - } else { - setBackground.accept(color, colorIntensity.next()); - } - setCursor(Cursor.HAND); - }); - - setOnMouseExited(event -> { - if(isActive) { - setBackground.accept(color, colorIntensity.next(1)); - } else { - setBackground.accept(color, colorIntensity); - } - setCursor(Cursor.DEFAULT); - }); - - // Update the background initially - setBackground.accept(color, colorIntensity); - } - - private ArrayList<HighLevelModelObject> getActiveComponents() { - ArrayList<HighLevelModelObject> activeComponents = new ArrayList<>(); - - Node canvasPaneFirstChild = Ecdar.getPresentation().getController().getEditorPresentation().getController().canvasPane.getChildren().get(0); - if(canvasPaneFirstChild instanceof GridPane) { - for (Node child : ((GridPane) canvasPaneFirstChild).getChildren()) { - activeComponents.add(((CanvasPresentation) child).getController().getActiveModel()); - } - } else { - activeComponents.add(EcdarController.getActiveCanvasPresentation().getController().getActiveModel()); - } - - return activeComponents; - } - - /** - * Initialises the icons for the three different file types in Ecdar: Component, System and Declarations - */ - private void initializeFileIcons() { - if(model.get() instanceof Component){ - controller.fileImage.setImage(new Image(Ecdar.class.getResource("component_frame.png").toExternalForm())); - } else if(model.get() instanceof EcdarSystem){ - controller.fileImage.setImage(new Image(Ecdar.class.getResource("system_frame.png").toExternalForm())); - } else if(model.get() instanceof MutationTestPlan){ - controller.fileImage.setImage(new Image(Ecdar.class.getResource("test_frame.png").toExternalForm())); - } else { - controller.fileImage.setImage(new Image(Ecdar.class.getResource("description_frame.png").toExternalForm())); - } - EcdarPresentation.fitSizeWhenAvailable(controller.fileImage, controller.fileImageStackPane); - } - - public HighLevelModelObject getModel() { - return model.get(); - } - - /** - * Updates the color of the FilePresentation based on whether the component is aactive or not - */ - public void updateColors() { - final FontIcon moreInformationIcon = (FontIcon) lookup("#moreInformationIcon"); - - final Color color = Color.GREY_BLUE; - final Color.Intensity colorIntensity = Color.Intensity.I50; - - final BiConsumer<Color, Color.Intensity> setBackground = (newColor, newIntensity) -> { - setBackground(new Background(new BackgroundFill( - newColor.getColor(newIntensity), - CornerRadii.EMPTY, - Insets.EMPTY - ))); - - setBorder(new Border(new BorderStroke( - newColor.getColor(newIntensity.next(2)), - BorderStrokeStyle.SOLID, - CornerRadii.EMPTY, - new BorderWidths(0, 0, 1, 0) - ))); - - moreInformationIcon.setFill(newColor.getColor(newIntensity.next(5))); - }; - - final List<HighLevelModelObject> activeComponents = getActiveComponents(); - setActive(activeComponents.contains(model.get())); - - if (isActive) { - setBackground.accept(color, colorIntensity.next(1)); - } else { - setBackground.accept(color, colorIntensity); - } - } - - public void setActive(boolean active) { - isActive = active; + public FileController getController() { + return controller; } } diff --git a/src/main/java/ecdar/presentations/HighLevelModelPresentation.java b/src/main/java/ecdar/presentations/HighLevelModelPresentation.java index d3e0d5f7..ede058cb 100644 --- a/src/main/java/ecdar/presentations/HighLevelModelPresentation.java +++ b/src/main/java/ecdar/presentations/HighLevelModelPresentation.java @@ -1,9 +1,11 @@ package ecdar.presentations; +import ecdar.controllers.HighLevelModelController; import javafx.scene.layout.StackPane; /** * */ -public class HighLevelModelPresentation extends StackPane { +public abstract class HighLevelModelPresentation extends StackPane { + abstract public HighLevelModelController getController(); } diff --git a/src/main/java/ecdar/presentations/LocationPresentation.java b/src/main/java/ecdar/presentations/LocationPresentation.java index d7cfea10..5fdc833a 100644 --- a/src/main/java/ecdar/presentations/LocationPresentation.java +++ b/src/main/java/ecdar/presentations/LocationPresentation.java @@ -1,13 +1,15 @@ package ecdar.presentations; +import ecdar.Ecdar; import ecdar.abstractions.Component; import ecdar.abstractions.Location; -import ecdar.controllers.EcdarController; import ecdar.controllers.LocationController; import ecdar.utility.colors.Color; +import ecdar.utility.colors.EnabledColor; import ecdar.utility.helpers.BindingHelper; import ecdar.utility.helpers.SelectHelper; import javafx.animation.*; +import javafx.application.Platform; import javafx.beans.binding.DoubleBinding; import javafx.beans.property.*; import javafx.scene.Group; @@ -20,7 +22,6 @@ import java.util.List; import java.util.function.BiConsumer; import java.util.function.Consumer; -import java.util.function.Function; import static javafx.util.Duration.millis; @@ -39,7 +40,7 @@ public class LocationPresentation extends Group implements SelectHelper.Selectab private final Timeline hiddenAreaAnimationExited = new Timeline(); private final Timeline scaleShakeIndicatorBackgroundAnimation = new Timeline(); private final Timeline shakeContentAnimation = new Timeline(); - private final List<BiConsumer<Color, Color.Intensity>> updateColorDelegates = new ArrayList<>(); + private final List<Consumer<EnabledColor>> updateColorDelegates = new ArrayList<>(); private final DoubleProperty animation = new SimpleDoubleProperty(0); private final DoubleBinding reverseAnimation = new SimpleDoubleProperty(1).subtract(animation); private final boolean interactable; @@ -90,33 +91,29 @@ private void initializeIdLabel() { idLabel.widthProperty().addListener((obsWidth, oldWidth, newWidth) -> idLabel.translateXProperty().set(newWidth.doubleValue() / -2)); idLabel.heightProperty().addListener((obsHeight, oldHeight, newHeight) -> idLabel.translateYProperty().set(newHeight.doubleValue() / -2)); - final ObjectProperty<Color> color = location.colorProperty(); - final ObjectProperty<Color.Intensity> colorIntensity = location.colorIntensityProperty(); - final BooleanProperty failing = location.failingProperty(); - // Delegate to style the label based on the color of the location - final BiConsumer<Color, Color.Intensity> updateColor = (newColor, newIntensity) -> { + final Consumer<EnabledColor> updateColor = (newColor) -> { if (location.getFailing()) { - idLabel.setTextFill(Color.RED.getTextColor(newIntensity)); - ds.setColor(Color.RED.getColor(newIntensity)); + idLabel.setTextFill(Color.RED.getTextColor(Color.Intensity.I700)); + ds.setColor(Color.RED.getColor(Color.Intensity.I700)); } else { - idLabel.setTextFill(newColor.getTextColor(newIntensity)); - ds.setColor(newColor.getColor(newIntensity)); + idLabel.setTextFill(newColor.getTextColor()); + ds.setColor(newColor.getPaintColor()); } }; updateColorDelegates.add(updateColor); // Set the initial color - updateColor.accept(color.get(), colorIntensity.get()); + updateColor.accept(location.getColor()); // Update the color of the circle when the color of the location is updated - color.addListener((obs, old, newColor) -> updateColor.accept(newColor, colorIntensity.get())); - failing.addListener((obs, old, newFailing) -> updateColor.accept(color.get(), colorIntensity.get() )); + location.colorProperty().addListener((obs, old, newColor) -> updateColor.accept(newColor)); + location.failingProperty().addListener((obs, old, newFailing) -> updateColor.accept(location.getColor())); // RED if failing, provided color otherwise } private void initializeTags() { - if(!interactable) { + if (!interactable) { controller.nicknameTag.setVisible(false); controller.invariantTag.setVisible(false); return; @@ -129,12 +126,12 @@ private void initializeTags() { // Set the layout from the model (if they are not both 0) final Location loc = controller.getLocation(); - if((loc.getNicknameX() != 0) && (loc.getNicknameY() != 0)) { + if ((loc.getNicknameX() != 0) && (loc.getNicknameY() != 0)) { controller.nicknameTag.setTranslateX(loc.getNicknameX()); controller.nicknameTag.setTranslateY(loc.getNicknameY()); } - if((loc.getInvariantX() != 0) && (loc.getInvariantY() != 0)) { + if ((loc.getInvariantX() != 0) && (loc.getInvariantY() != 0)) { controller.invariantTag.setTranslateX(loc.getInvariantX()); controller.invariantTag.setTranslateY(loc.getInvariantY()); } @@ -152,8 +149,8 @@ private void initializeTags() { final Consumer<Location> updateTags = location -> { // Update the color - controller.nicknameTag.bindToColor(location.colorProperty(), location.colorIntensityProperty(), true); - controller.invariantTag.bindToColor(location.colorProperty(), location.colorIntensityProperty(), false); + controller.nicknameTag.bindToColor(location.colorProperty(), true); + controller.invariantTag.bindToColor(location.colorProperty(), false); // Update the invariant controller.nicknameTag.setAndBindString(location.nicknameProperty()); @@ -167,7 +164,8 @@ private void initializeTags() { final Consumer<String> updateVisibilityFromNickName = (nickname) -> { if (nickname.equals("") && !controller.nicknameTag.textFieldFocusProperty().get()) { controller.nicknameTag.setOpacity(0); - } else {controller.nicknameTag.setOpacity(1); + } else { + controller.nicknameTag.setOpacity(1); } }; @@ -207,8 +205,10 @@ private void initializeTags() { BindingHelper.bind(controller.invariantTagLine, controller.invariantTag); }; - controller.nicknameTag.setOnKeyPressed(EcdarController.getActiveCanvasPresentation().getController().getLeaveTextAreaKeyHandler()); - controller.invariantTag.setOnKeyPressed(EcdarController.getActiveCanvasPresentation().getController().getLeaveTextAreaKeyHandler()); + Platform.runLater(() -> { + controller.nicknameTag.setOnKeyPressed(Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasPresentation().getController().getLeaveTextAreaKeyHandler()); + controller.invariantTag.setOnKeyPressed(Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasPresentation().getController().getLeaveTextAreaKeyHandler()); + }); // Update the tags when the loc updates controller.locationProperty().addListener(observable -> updateTags.accept(loc)); @@ -260,23 +260,21 @@ private void initializeCircle() { final Circle circle = controller.circle; circle.setRadius(RADIUS); - final ObjectProperty<Color> color = location.colorProperty(); - final ObjectProperty<Color.Intensity> colorIntensity = location.colorIntensityProperty(); + final ObjectProperty<EnabledColor> color = location.colorProperty(); // Delegate to style the label based on the color of the location - final BiConsumer<Color, Color.Intensity> updateColor = (newColor, newIntensity) -> { - circle.setFill(newColor.getColor(newIntensity)); - circle.setStroke(newColor.getColor(newIntensity.next(2))); + final Consumer<EnabledColor> updateColor = (newColor) -> { + circle.setFill(newColor.getPaintColor()); + circle.setStroke(newColor.getStrokeColor()); }; updateColorDelegates.add(updateColor); // Set the initial color - updateColor.accept(color.get(), colorIntensity.get()); + updateColor.accept(color.get()); // Update the color of the circle when the color of the location is updated - color.addListener((obs, old, newColor) -> updateColor.accept(newColor, colorIntensity.get())); - colorIntensity.addListener((obs, old, newIntensity) -> updateColor.accept(color.get(), newIntensity)); + color.addListener((obs, old, newColor) -> updateColor.accept(newColor)); } private void initializeLocationShapes() { @@ -314,19 +312,19 @@ protected void interpolate(final double frac) { @Override protected void interpolate(final double frac) { - animation.set(1-frac); + animation.set(1 - frac); } }; boolean isNormalOrProhibited = newUrgency.equals(Location.Urgency.NORMAL) || newUrgency.equals(Location.Urgency.PROHIBITED); - if(!oldUrgency.equals(Location.Urgency.URGENT) && !isNormalOrProhibited) { + if (!oldUrgency.equals(Location.Urgency.URGENT) && !isNormalOrProhibited) { toUrgent.play(); - } else if(isNormalOrProhibited && oldUrgency.equals(Location.Urgency.URGENT)) { + } else if (isNormalOrProhibited && oldUrgency.equals(Location.Urgency.URGENT)) { toNormal.play(); } - if(newUrgency.equals(Location.Urgency.COMMITTED)) { + if (newUrgency.equals(Location.Urgency.COMMITTED)) { committedShape.setVisible(true); notCommittedShape.setVisible(false); } else { @@ -334,13 +332,13 @@ protected void interpolate(final double frac) { notCommittedShape.setVisible(true); } - if(newUrgency.equals(Location.Urgency.PROHIBITED)) { + if (newUrgency.equals(Location.Urgency.PROHIBITED)) { notCommittedShape.setStrokeWidth(4); notCommittedShape.setStroke(Color.RED.getColor(Color.Intensity.A700)); controller.prohibitedLocStrikeThrough.setVisible(true); } else { notCommittedShape.setStrokeWidth(1); - notCommittedShape.setStroke(location.getColor().getColor(location.getColorIntensity().next(2))); + notCommittedShape.setStroke(location.getColor().getStrokeColor()); controller.prohibitedLocStrikeThrough.setVisible(false); } }; @@ -351,48 +349,33 @@ protected void interpolate(final double frac) { updateUrgencies.accept(Location.Urgency.NORMAL, location.getUrgency()); - // Update the colors - final ObjectProperty<Color> color = location.colorProperty(); - final ObjectProperty<Color.Intensity> colorIntensity = location.colorIntensityProperty(); - final BooleanProperty failing = location.failingProperty(); - // Delegate to style the label based on the color of the location - final BiConsumer<Color, Color.Intensity> updateColor = (newColor, newIntensity) -> { - + final Consumer<EnabledColor> updateColor = (newColor) -> { // ToDo NIELS: Fix this consumer, it seems a bit weird if (!location.getUrgency().equals(Location.Urgency.PROHIBITED)) { if (location.getFailing()) { - notCommittedShape.setStroke(Color.RED.getColor(newIntensity)); + notCommittedShape.setFill(Color.RED.getColor(Color.Intensity.I700)); } else { - notCommittedShape.setStroke(newColor.getColor(newIntensity.next(2))); + notCommittedShape.setFill(newColor.getStrokeColor()); } - } - if (location.getFailing()) { - notCommittedShape.setFill(Color.RED.getColor(newIntensity)); - committedShape.setFill(Color.RED.getColor(newIntensity)); - committedShape.setStroke(Color.RED.getColor(newIntensity.next(2))); - } else { - notCommittedShape.setFill(newColor.getColor(newIntensity)); - committedShape.setFill(newColor.getColor(newIntensity)); - committedShape.setStroke(newColor.getColor(newIntensity.next(2))); - } - }; - - final Consumer<Boolean> handleFailingUpdate = (isFailing) -> { - if(isFailing) { - updateColor.accept(Color.RED, colorIntensity.get()); + } else if (location.getFailing()) { + notCommittedShape.setFill(Color.RED.getColor(Color.Intensity.I700)); + committedShape.setFill(Color.RED.getColor(Color.Intensity.I700)); + committedShape.setStroke(Color.RED.getColor(Color.Intensity.I700.next(2))); } else { - updateColor.accept(location.getColor(), colorIntensity.get()); + notCommittedShape.setFill(newColor.getPaintColor()); + committedShape.setFill(newColor.getPaintColor()); + committedShape.setStroke(newColor.getStrokeColor()); } }; updateColorDelegates.add(updateColor); // Set the initial color - updateColor.accept(color.get(), colorIntensity.get()); + updateColor.accept(location.getColor()); // Update the color of the circle when the color of the location is updated - color.addListener((obs, old, newColor) -> updateColor.accept(newColor, colorIntensity.get())); - failing.addListener((obs, old, newFailing) -> updateColor.accept(color.get(), colorIntensity.get())); + location.colorProperty().addListener((obs, old, newColor) -> updateColor.accept(newColor)); + location.failingProperty().addListener(obs -> updateColor.accept(location.getColor())); } private void initializeTypeGraphics() { @@ -550,7 +533,7 @@ public void animateShakeWarning(final boolean start) { @Override public void select() { - updateColorDelegates.forEach(colorConsumer -> colorConsumer.accept(SelectHelper.SELECT_COLOR, SelectHelper.SELECT_COLOR_INTENSITY_NORMAL)); + updateColorDelegates.forEach(colorConsumer -> colorConsumer.accept(new EnabledColor(SelectHelper.SELECT_COLOR, SelectHelper.SELECT_COLOR_INTENSITY_NORMAL))); } @Override @@ -558,7 +541,7 @@ public void deselect() { updateColorDelegates.forEach(colorConsumer -> { final Location location = controller.getLocation(); - colorConsumer.accept(location.getColor(), location.getColorIntensity()); + colorConsumer.accept(location.getColor()); }); } diff --git a/src/main/java/ecdar/presentations/MessageCollectionPresentation.java b/src/main/java/ecdar/presentations/MessageCollectionPresentation.java index 9f69aae2..c9d9eddf 100644 --- a/src/main/java/ecdar/presentations/MessageCollectionPresentation.java +++ b/src/main/java/ecdar/presentations/MessageCollectionPresentation.java @@ -2,121 +2,20 @@ import ecdar.abstractions.Component; import ecdar.code_analysis.CodeAnalysis; -import ecdar.controllers.EcdarController; -import ecdar.utility.colors.Color; -import javafx.beans.InvalidationListener; -import javafx.beans.Observable; -import javafx.collections.ListChangeListener; +import ecdar.controllers.MessageCollectionController; import javafx.collections.ObservableList; -import javafx.event.EventHandler; -import javafx.scene.Cursor; -import javafx.scene.control.Label; -import javafx.scene.input.MouseEvent; import javafx.scene.layout.VBox; -import javafx.scene.shape.Circle; -import javafx.scene.shape.Line; - -import java.util.HashMap; -import java.util.Map; -import java.util.function.BiConsumer; -import java.util.function.Consumer; public class MessageCollectionPresentation extends VBox { - - private final ObservableList<CodeAnalysis.Message> messages; + private final MessageCollectionController controller; public MessageCollectionPresentation(final Component component, final ObservableList<CodeAnalysis.Message> messages) { - this.messages = messages; - - new EcdarFXMLLoader().loadAndGetController("MessageCollectionPresentation.fxml", this); - - initializeHeadline(component); - initializeLine(); - initializeErrorsListener(); - } - - private void initializeErrorsListener() { - final VBox children = (VBox) lookup("#children"); - - final Map<CodeAnalysis.Message, MessagePresentation> messageMessagePresentationMap = new HashMap<>(); - - final Consumer<CodeAnalysis.Message> addMessage = (message) -> { - final MessagePresentation messagePresentation = new MessagePresentation(message); - messageMessagePresentationMap.put(message, messagePresentation); - children.getChildren().add(messagePresentation); - }; - - messages.forEach(addMessage); - messages.addListener(new ListChangeListener<CodeAnalysis.Message>() { - @Override - public void onChanged(final Change<? extends CodeAnalysis.Message> c) { - while (c.next()) { - c.getAddedSubList().forEach(addMessage::accept); - - c.getRemoved().forEach(message -> { - children.getChildren().remove(messageMessagePresentationMap.get(message)); - messageMessagePresentationMap.remove(message); - }); - } - } - }); + controller = new EcdarFXMLLoader().loadAndGetController("MessageCollectionPresentation.fxml", this); + controller.setComponent(component); + controller.setMessages(messages); } - private void initializeLine() { - final VBox children = (VBox) lookup("#children"); - final Line line = (Line) lookup("#line"); - - children.getChildren().addListener(new InvalidationListener() { - @Override - public void invalidated(final Observable observable) { - line.setEndY(children.getChildren().size() * 23 + 8); - } - }); + public MessageCollectionController getController() { + return controller; } - - private void initializeHeadline(final Component component) { - final Label headline = (Label) lookup("#headline"); - final Circle indicator = (Circle) lookup("#indicator"); - final Line line = (Line) lookup("#line"); - - line.setStroke(Color.GREY.getColor(Color.Intensity.I400)); - - // This is an project wide message that is not specific to a component - if(component == null) { - headline.setText("Project"); - return; - } - - headline.setText(component.getName()); - headline.textProperty().bind(component.nameProperty()); - - final EventHandler<MouseEvent> onMouseEntered = event -> { - setCursor(Cursor.HAND); - headline.setStyle("-fx-underline: true;"); - }; - - final EventHandler<MouseEvent> onMouseExited = event -> { - setCursor(Cursor.DEFAULT); - headline.setStyle("-fx-underline: false;"); - }; - - final EventHandler<MouseEvent> onMousePressed = event -> { - EcdarController.setActiveModelForActiveCanvas(component); - }; - - headline.setOnMouseEntered(onMouseEntered); - headline.setOnMouseExited(onMouseExited); - headline.setOnMousePressed(onMousePressed); - indicator.setOnMouseEntered(onMouseEntered); - indicator.setOnMouseExited(onMouseExited); - indicator.setOnMousePressed(onMousePressed); - - final BiConsumer<Color, Color.Intensity> updateColor = (color, intensity) -> { - indicator.setFill(color.getColor(component.getColorIntensity())); - }; - - updateColor.accept(component.getColor(), component.getColorIntensity()); - component.colorProperty().addListener((observable, oldColor, newColor) -> updateColor.accept(newColor, component.getColorIntensity())); - } - } diff --git a/src/main/java/ecdar/presentations/MessagePresentation.java b/src/main/java/ecdar/presentations/MessagePresentation.java index fefd7a2a..6dd49d82 100644 --- a/src/main/java/ecdar/presentations/MessagePresentation.java +++ b/src/main/java/ecdar/presentations/MessagePresentation.java @@ -21,8 +21,7 @@ public class MessagePresentation extends HBox { public MessagePresentation(final CodeAnalysis.Message message) { this.message = message; - - new EcdarFXMLLoader().loadAndGetController("MessagePresentation.fxml", this); + new EcdarFXMLLoader().loadAndGetController("MessagePresentation.fxml", this); // Initialize initializeMessage(); @@ -81,9 +80,9 @@ private void initializeNearLabel() { } if (openComponent[0] != null) { - if (!EcdarController.getActiveCanvasPresentation().getController().getActiveModel().equals(openComponent[0])) { + if (!Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasPresentation().getController().getActiveModelPresentation().getController().getModel().equals(openComponent[0])) { SelectHelper.elementsToBeSelected = FXCollections.observableArrayList(); - EcdarController.setActiveModelForActiveCanvas(openComponent[0]); + Ecdar.getPresentation().getController().getEditorPresentation().getController().setActiveModelPresentationForActiveCanvas(Ecdar.getComponentPresentationOfComponent(openComponent[0])); } SelectHelper.clearSelectedElements(); diff --git a/src/main/java/ecdar/presentations/ModelPresentation.java b/src/main/java/ecdar/presentations/ModelPresentation.java index b487e1b9..4f52014e 100644 --- a/src/main/java/ecdar/presentations/ModelPresentation.java +++ b/src/main/java/ecdar/presentations/ModelPresentation.java @@ -1,21 +1,6 @@ package ecdar.presentations; -import ecdar.Ecdar; -import ecdar.abstractions.Box; -import ecdar.abstractions.HighLevelModelObject; -import ecdar.controllers.EcdarController; -import ecdar.controllers.ModelController; -import ecdar.utility.UndoRedoStack; -import ecdar.utility.colors.Color; -import ecdar.utility.helpers.StringValidator; -import javafx.beans.property.BooleanProperty; -import javafx.beans.property.DoubleProperty; -import javafx.beans.property.SimpleBooleanProperty; -import javafx.beans.property.SimpleDoubleProperty; -import javafx.geometry.Insets; -import javafx.scene.Cursor; import javafx.scene.shape.Polygon; -import javafx.scene.shape.Rectangle; /** * Presentation for high level graphical models such as systems and components @@ -23,269 +8,9 @@ public abstract class ModelPresentation extends HighLevelModelPresentation { public static final int CORNER_SIZE = 40; public static final int TOOLBAR_HEIGHT = CORNER_SIZE / 2; - - static final Polygon TOP_LEFT_CORNER = new Polygon( + public static final Polygon TOP_LEFT_CORNER = new Polygon( 0, 0, CORNER_SIZE + 2, 0, 0, CORNER_SIZE + 2 ); - - abstract ModelController getModelController(); - abstract double getDragAnchorMinWidth(); - abstract double getDragAnchorMinHeight(); - - /** - * Initializes this. - * @param box the box of the model - */ - void initialize(final Box box) { - initializeName(); - initializeDimensions(box); - initializesBottomDragAnchor(box); - initializesRightDragAnchor(box); - initializesCornerDragAnchor(box); - } - - /** - * Initializes handling of name. - */ - private void initializeName() { - final ModelController controller = getModelController(); - final HighLevelModelObject model = controller.getModel(); - - final BooleanProperty initialized = new SimpleBooleanProperty(false); - - controller.name.focusedProperty().addListener((observable, oldValue, newValue) -> { - if (newValue && !initialized.get()) { - controller.root.requestFocus(); - initialized.setValue(true); - } - }); - - // Set the text field to the name in the model, and bind the model to the text field - controller.name.setText(model.getName()); - controller.name.textProperty().addListener((obs, oldName, newName) -> { - if (StringValidator.validateComponentName(newName)) { - model.nameProperty().unbind(); - model.setName(newName); - } else { - controller.name.setText(model.getName()); - Ecdar.showToast("Component names cannot contain '.'"); - } - }); - - final Runnable updateColor = () -> { - final Color color = model.getColor(); - final Color.Intensity colorIntensity = model.getColorIntensity(); - - // Set the text color for the label - controller.name.setStyle("-fx-text-fill: " + color.getTextColorRgbaString(colorIntensity) + ";"); - controller.name.setFocusColor(color.getTextColor(colorIntensity)); - controller.name.setUnFocusColor(javafx.scene.paint.Color.TRANSPARENT); - }; - - model.colorProperty().addListener(observable -> updateColor.run()); - updateColor.run(); - - // Center the text vertically and add a left padding - controller.name.setPadding(new Insets(2, 0, 0, CORNER_SIZE)); - controller.name.setOnKeyPressed(EcdarController.getActiveCanvasPresentation().getController().getLeaveTextAreaKeyHandler()); - } - - /** - * Sets the width and the height of the view to the values in the abstraction. - * @param box The dimensions to set - */ - void initializeDimensions(final Box box) { - // Bind the position of the abstraction to the values in the view - layoutXProperty().set(box.getX()); - layoutYProperty().set(box.getY()); - box.getXProperty().bindBidirectional(layoutXProperty()); - box.getYProperty().bindBidirectional(layoutYProperty()); - - setMinWidth(box.getWidth()); - setMaxWidth(box.getWidth()); - setMinHeight(box.getHeight()); - setMaxHeight(box.getHeight()); - minHeightProperty().bindBidirectional(box.getHeightProperty()); - maxHeightProperty().bindBidirectional(box.getHeightProperty()); - minWidthProperty().bindBidirectional(box.getWidthProperty()); - maxWidthProperty().bindBidirectional(box.getWidthProperty()); - } - - /** - * Initializes the right drag anchor. - * @param box the box of the model - */ - private void initializesRightDragAnchor(final Box box) { - final BooleanProperty wasResized = new SimpleBooleanProperty(false); - - // Right anchor - final Rectangle rightAnchor = getModelController().rightAnchor; - - rightAnchor.setCursor(Cursor.E_RESIZE); - - // Bind the place and size of bottom anchor - rightAnchor.setWidth(5); - rightAnchor.heightProperty().bind(box.getHeightProperty()); - - final DoubleProperty prevX = new SimpleDoubleProperty(); - final DoubleProperty prevWidth = new SimpleDoubleProperty(); - - rightAnchor.setOnMousePressed(event -> { - event.consume(); - - prevX.set(event.getScreenX()); - prevWidth.set(box.getWidth()); - }); - - rightAnchor.setOnMouseDragged(event -> { - double diff = event.getScreenX() - prevX.get(); - final double newWidth = prevWidth.get() + diff; - final double minWidth = getDragAnchorMinWidth(); - - // Move the model left or right to account for new height (needed because model is centered in parent) - setTranslateX(getTranslateX() + (Math.max(newWidth, minWidth) - box.getWidth()) / 2); - box.setWidth(Math.max(newWidth, minWidth)); - wasResized.set(true); - }); - - rightAnchor.setOnMouseReleased(event -> { - if (!wasResized.get()) return; - final double previousWidth = prevWidth.doubleValue(); - final double currentWidth = box.getWidth(); - - // If no difference do not save change - if (previousWidth == currentWidth) return; - - UndoRedoStack.pushAndPerform(() -> { // Perform - box.setWidth(currentWidth); - }, () -> { // Undo - box.setWidth(previousWidth); - }, - "Component width resized", - "settings-overscan" - ); - - wasResized.set(false); - }); - } - - /** - * Initializes the bottom drag anchor. - * @param box the box of the model - */ - private void initializesBottomDragAnchor(final Box box) { - final BooleanProperty wasResized = new SimpleBooleanProperty(false); - // Bottom anchor - final Rectangle bottomAnchor = getModelController().bottomAnchor; - - bottomAnchor.setCursor(Cursor.S_RESIZE); - - // Bind the place and size of bottom anchor - bottomAnchor.widthProperty().bind(box.getWidthProperty()); - bottomAnchor.setHeight(5); - - final DoubleProperty prevY = new SimpleDoubleProperty(); - final DoubleProperty prevHeight = new SimpleDoubleProperty(); - - bottomAnchor.setOnMousePressed(event -> { - event.consume(); - - prevY.set(event.getScreenY()); - prevHeight.set(box.getHeight()); - }); - - bottomAnchor.setOnMouseDragged(event -> { - double diff = event.getScreenY() - prevY.get(); - final double newHeight = prevHeight.get() + diff; - final double minHeight = getDragAnchorMinHeight(); - - // Move the model up or down to account for new height (needed because model is centered in parent) - setTranslateY(getTranslateY() + (Math.max(newHeight, minHeight) - box.getHeight()) / 2); - box.setHeight(Math.max(newHeight, minHeight)); - wasResized.set(true); - }); - - bottomAnchor.setOnMouseReleased(event -> { - if (!wasResized.get()) return; - final double previousHeight = prevHeight.doubleValue(); - final double currentHeight = box.getHeight(); - - // If no difference do not save change - if (previousHeight == currentHeight) return; - - UndoRedoStack.pushAndPerform(() -> { // Perform - box.setHeight(currentHeight); - }, () -> { // Undo - box.setHeight(previousHeight); - }, - "Component height resized", - "settings-overscan" - ); - - wasResized.set(false); - }); - } - - private void initializesCornerDragAnchor(final Box box) { - final BooleanProperty wasResized = new SimpleBooleanProperty(false); - - final Rectangle cornerAnchor = getModelController().cornerAnchor; - cornerAnchor.setCursor(Cursor.SE_RESIZE); - - // Bind the place and size of bottom anchor - cornerAnchor.setWidth(10); - cornerAnchor.setHeight(10); - - final DoubleProperty prevX = new SimpleDoubleProperty(); - final DoubleProperty prevY = new SimpleDoubleProperty(); - final DoubleProperty prevWidth = new SimpleDoubleProperty(); - final DoubleProperty prevHeight = new SimpleDoubleProperty(); - - cornerAnchor.setOnMousePressed(event -> { - event.consume(); - - prevX.set(event.getScreenX()); - prevWidth.set(box.getWidth()); - prevY.set(event.getScreenY()); - prevHeight.set(box.getHeight()); - }); - - cornerAnchor.setOnMouseDragged(event -> { - double xDiff = (event.getScreenX() - prevX.get()) / EcdarController.getActiveCanvasZoomFactor().get(); - final double newWidth = Math.max(prevWidth.get() + xDiff, getDragAnchorMinWidth()); - box.setWidth(newWidth); - - double yDiff = (event.getScreenY() - prevY.get()) / EcdarController.getActiveCanvasZoomFactor().get(); - final double newHeight = Math.max(prevHeight.get() + yDiff, getDragAnchorMinHeight()); - box.setHeight(newHeight); - - wasResized.set(true); - }); - - cornerAnchor.setOnMouseReleased(event -> { - if (!wasResized.get()) return; - final double previousWidth = prevWidth.doubleValue(); - final double currentWidth = box.getWidth(); - final double previousHeight = prevHeight.doubleValue(); - final double currentHeight = box.getHeight(); - - // If no difference do not save change - if (previousWidth == currentWidth && previousHeight == currentHeight) return; - - UndoRedoStack.pushAndPerform(() -> { // Perform - box.setWidth(currentWidth); - box.setHeight(currentHeight); - }, () -> { // Undo - box.setWidth(previousWidth); - box.setHeight(previousHeight); - }, - "Component resized", - "settings-overscan" - ); - - wasResized.set(false); - }); - } } diff --git a/src/main/java/ecdar/presentations/MultiSyncTagPresentation.java b/src/main/java/ecdar/presentations/MultiSyncTagPresentation.java index a2d1fb8d..85b0790b 100644 --- a/src/main/java/ecdar/presentations/MultiSyncTagPresentation.java +++ b/src/main/java/ecdar/presentations/MultiSyncTagPresentation.java @@ -46,7 +46,7 @@ public MultiSyncTagPresentation(GroupedEdge edge, Runnable updateIOStatusOfSubEd Platform.runLater(() -> { // Added to avoid NullPointer exception for location aware and component in getDragBounds method edge.getNails().stream().filter(n -> n.getPropertyType().equals(DisplayableEdge.PropertyType.SYNCHRONIZATION)).findFirst().ifPresent(this::setLocationAware); - setComponent(EcdarController.getActiveCanvasPresentation().getController().activeComponentPresentation.getController().getComponent()); + setComponent(Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasPresentation().getController().activeComponentPresentation.getController().getComponent()); updateTopBorder(); initializeMouseTransparency(); @@ -165,7 +165,7 @@ private void updateTopBorder() { for (Node child : controller.syncList.getChildren()) { ((SyncTextFieldPresentation) child).getController().textField - .setOnKeyPressed(EcdarController.getActiveCanvasPresentation().getController() + .setOnKeyPressed(Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasPresentation().getController() .getLeaveTextAreaKeyHandler((keyEvent) -> { if (keyEvent.getCode().equals(KeyCode.ENTER)) { @@ -181,13 +181,11 @@ private void updateTopBorder() { } private void updateColorAndMouseShape() { - EcdarController.getActiveCanvasPresentation().getController().activeComponentPresentation.getController().getComponent().colorProperty().addListener((observable, oldValue, newValue) -> { - controller.frame.setBackground(new Background(new BackgroundFill(newValue.getColor( - EcdarController.getActiveCanvasPresentation().getController().activeComponentPresentation.getController().getComponent().getColorIntensity()), new CornerRadii(0), Insets.EMPTY))); + Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasPresentation().getController().activeComponentPresentation.getController().getComponent().colorProperty().addListener((observable, oldValue, newValue) -> { + controller.frame.setBackground(new Background(new BackgroundFill(newValue.getPaintColor(), new CornerRadii(0), Insets.EMPTY))); }); - Color color = EcdarController.getActiveCanvasPresentation().getController().activeComponentPresentation.getController().getComponent().getColor().getColor( - EcdarController.getActiveCanvasPresentation().getController().activeComponentPresentation.getController().getComponent().getColorIntensity()); + Color color = Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasPresentation().getController().activeComponentPresentation.getController().getComponent().getColor().getPaintColor(); controller.frame.setBackground(new Background(new BackgroundFill(color, new CornerRadii(0), Insets.EMPTY))); controller.frame.setCursor(Cursor.OPEN_HAND); @@ -216,8 +214,8 @@ private void initializeDragging() { event.consume(); Platform.runLater(() -> { - final double dragDistanceX = (event.getSceneX() - dragOffsetX.get()) / EcdarController.getActiveCanvasZoomFactor().get(); - final double dragDistanceY = (event.getSceneY() - dragOffsetY.get()) / EcdarController.getActiveCanvasZoomFactor().get(); + final double dragDistanceX = (event.getSceneX() - dragOffsetX.get()) / Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasZoomFactor().get(); + final double dragDistanceY = (event.getSceneY() - dragOffsetY.get()) / Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasZoomFactor().get(); double draggableNewX = getDragBounds().trimX(draggablePreviousX.get() + dragDistanceX); double draggableNewY = getDragBounds().trimY(draggablePreviousY.get() + dragDistanceY); diff --git a/src/main/java/ecdar/presentations/NailPresentation.java b/src/main/java/ecdar/presentations/NailPresentation.java index 2aa8ea18..f6ee250e 100644 --- a/src/main/java/ecdar/presentations/NailPresentation.java +++ b/src/main/java/ecdar/presentations/NailPresentation.java @@ -4,6 +4,7 @@ import ecdar.controllers.EdgeController; import ecdar.controllers.NailController; import ecdar.utility.colors.Color; +import ecdar.utility.colors.EnabledColor; import ecdar.utility.helpers.BindingHelper; import ecdar.utility.Highlightable; import ecdar.utility.helpers.SelectHelper; @@ -165,7 +166,7 @@ private void setPropertyTagComponentAndLocationAware(TagPresentation propertyTag BindingHelper.bind(propertyTagLine, propertyTag); // Bind the color of the tag to the color of the component - propertyTag.bindToColor(controller.getComponent().colorProperty(), controller.getComponent().colorIntensityProperty()); + propertyTag.bindToColor(controller.getComponent().colorProperty()); } /** @@ -190,8 +191,6 @@ private void initializeNailCircleColor() { // When the color of the component updates, update the nail indicator as well controller.getComponent().colorProperty().addListener((observable) -> updateNailColor()); - // When the color intensity of the component updates, update the nail indicator - controller.getComponent().colorIntensityProperty().addListener((observable) -> updateNailColor()); // Initialize the color of the nail updateNailColor(); } @@ -201,10 +200,8 @@ private void initializeNailCircleColor() { */ public void onFailingUpdate(boolean isFailing) { final Runnable updateNailColorOnFailingUpdate = () -> { - final Color color = controller.getComponent().getColor(); - final Color.Intensity colorIntensity = controller.getComponent().getColorIntensity(); - controller.nailCircle.setFill(Color.RED.getColor(colorIntensity)); - controller.nailCircle.setStroke(Color.RED.getColor(colorIntensity.next(2))); + controller.nailCircle.setFill(Color.RED.getColor(Color.Intensity.I700)); + controller.nailCircle.setStroke(Color.RED.getColor(Color.Intensity.I700.next(2))); }; if (isFailing) { updateNailColorOnFailingUpdate.run(); @@ -215,22 +212,26 @@ public void onFailingUpdate(boolean isFailing) { private void updateNailColor() { final Runnable updateNailColor = () -> { - final Color color = controller.getComponent().getColor(); - final Color.Intensity colorIntensity = controller.getComponent().getColorIntensity(); - //If edge is failing and is a SYNC + final EnabledColor color = controller.getComponent().getColor(); + if (controller.getEdge().getFailing() && controller.getNail().getPropertyType().equals(Edge.PropertyType.SYNCHRONIZATION)) { - controller.nailCircle.setFill(Color.RED.getColor(colorIntensity)); - controller.nailCircle.setStroke(Color.RED.getColor(colorIntensity.next(2))); + controller.nailCircle.setFill(Color.RED.getColor(Color.Intensity.I700)); + controller.nailCircle.setStroke(Color.RED.getColor(Color.Intensity.I700.next(2))); } //If edge is not NONE - else if (!controller.getNail().getPropertyType().equals(Edge.PropertyType.NONE)) { - controller.nailCircle.setFill(color.getColor(colorIntensity)); - controller.nailCircle.setStroke(color.getColor(colorIntensity.next(2))); + else if(!controller.getNail().getPropertyType().equals(Edge.PropertyType.NONE)) { + controller.nailCircle.setFill(color.getPaintColor()); + controller.nailCircle.setStroke(color.getStrokeColor()); } else { controller.nailCircle.setFill(Color.GREY_BLUE.getColor(Color.Intensity.I800)); controller.nailCircle.setStroke(Color.GREY_BLUE.getColor(Color.Intensity.I900)); } }; + + // When the color of the component updates, update the nail indicator as well + controller.getComponent().colorProperty().addListener((observable) -> updateNailColor.run()); + + // Initialize the color of the nail updateNailColor.run(); } diff --git a/src/main/java/ecdar/presentations/ProcessPresentation.java b/src/main/java/ecdar/presentations/ProcessPresentation.java index 92b4b11d..f4db4c46 100755 --- a/src/main/java/ecdar/presentations/ProcessPresentation.java +++ b/src/main/java/ecdar/presentations/ProcessPresentation.java @@ -8,6 +8,7 @@ import ecdar.controllers.ModelController; import ecdar.controllers.ProcessController; import ecdar.utility.colors.Color; +import ecdar.utility.colors.EnabledColor; import javafx.beans.InvalidationListener; import javafx.geometry.Insets; import javafx.geometry.Pos; @@ -24,6 +25,7 @@ import java.math.BigDecimal; import java.util.List; import java.util.function.BiConsumer; +import java.util.function.Consumer; /** * The presenter of a Process which is shown in {@link SimulatorOverviewPresentation}. <br /> @@ -40,7 +42,7 @@ public class ProcessPresentation extends ModelPresentation { public ProcessPresentation(final Component component){ controller = new EcdarFXMLLoader().loadAndGetController("ProcessPresentation.fxml", this); controller.setComponent(component); - super.initialize(component.getBox()); + // Initialize methods that is sensitive to width and height final Runnable onUpdateSize = () -> { initializeToolbar(); @@ -122,7 +124,7 @@ private void initializeFrame() { final Shape[] mask = new Shape[1]; final Rectangle rectangle = new Rectangle(component.getBox().getWidth(), component.getBox().getHeight()); - final BiConsumer<Color, Color.Intensity> updateColor = (newColor, newIntensity) -> { + final Consumer<EnabledColor> updateColor = (newColor) -> { // Mask the parent of the frame (will also mask the background) mask[0] = Path.subtract(rectangle, TOP_LEFT_CORNER); controller.frame.setClip(mask[0]); @@ -134,13 +136,13 @@ private void initializeFrame() { controller.topLeftLine.setStartY(0); controller.topLeftLine.setEndX(0); controller.topLeftLine.setEndY(CORNER_SIZE); - controller.topLeftLine.setStroke(newColor.getColor(newIntensity.next(2))); + controller.topLeftLine.setStroke(newColor.getStrokeColor()); controller.topLeftLine.setStrokeWidth(1.25); StackPane.setAlignment(controller.topLeftLine, Pos.TOP_LEFT); // Set the stroke color to two shades darker controller.frame.setBorder(new Border(new BorderStroke( - newColor.getColor(newIntensity.next(2)), + newColor.getStrokeColor(), BorderStrokeStyle.SOLID, CornerRadii.EMPTY, new BorderWidths(1), @@ -148,9 +150,9 @@ private void initializeFrame() { ))); }; component.colorProperty().addListener(observable -> { - updateColor.accept(component.getColor(), component.getColorIntensity()); + updateColor.accept(component.getColor()); }); - updateColor.accept(component.getColor(), component.getColorIntensity()); + updateColor.accept(component.getColor()); } /** @@ -163,16 +165,16 @@ private void initializeBackground() { // Bind the background width and height to the values in the model controller.background.widthProperty().bind(component.getBox().getWidthProperty()); controller.background.heightProperty().bind(component.getBox().getHeightProperty()); - controller.background.setFill(component.getColor().getColor(component.getColorIntensity().next(-10).next(2))); - final BiConsumer<Color, Color.Intensity> updateColor = (newColor, newIntensity) -> { + controller.background.setFill(component.getColor().setIntensity(2).getPaintColor()); + final Consumer<EnabledColor> updateColor = (newColor) -> { // Set the background color to the lightest possible version of the color - controller.background.setFill(newColor.getColor(newIntensity.next(-10).next(2))); + controller.background.setFill(newColor.setIntensity(2).getPaintColor()); }; component.colorProperty().addListener(observable -> { - updateColor.accept(component.getColor(), component.getColorIntensity()); + updateColor.accept(component.getColor()); }); - updateColor.accept(component.getColor(), component.getColorIntensity()); + updateColor.accept(component.getColor()); } /** @@ -182,23 +184,23 @@ private void initializeBackground() { private void initializeToolbar() { final Component component = controller.getComponent(); - final BiConsumer<Color, Color.Intensity> updateColor = (newColor, newIntensity) -> { + final Consumer<EnabledColor> updateColor = (newColor) -> { // Set the background of the toolbar controller.toolbar.setBackground(new Background(new BackgroundFill( - newColor.getColor(newIntensity), + newColor.getPaintColor(), CornerRadii.EMPTY, Insets.EMPTY ))); // Set the icon color and rippler color of the toggleDeclarationButton - controller.toggleValuesButton.setRipplerFill(newColor.getTextColor(newIntensity)); + controller.toggleValuesButton.setRipplerFill(newColor.getTextColor()); controller.toolbar.setPrefHeight(TOOLBAR_HEIGHT); controller.toggleValuesButton.setBackground(Background.EMPTY); }; - controller.getComponent().colorProperty().addListener(observable -> updateColor.accept(component.getColor(), component.getColorIntensity())); + controller.getComponent().colorProperty().addListener(observable -> updateColor.accept(component.getColor())); - updateColor.accept(component.getColor(), component.getColorIntensity()); + updateColor.accept(component.getColor()); // Set a hover effect for the controller.toggleDeclarationButton controller.toggleValuesButton.setOnMouseEntered(event -> controller.toggleValuesButton.setCursor(Cursor.HAND)); @@ -222,59 +224,6 @@ public void showActive() { } @Override - ModelController getModelController() { - return controller; - } - - /** - * Gets the minimum possible width when dragging the anchor. - * The width is based on the x coordinate of locations, nails and the signature arrows. <br /> - * This should be removed from {@link ModelPresentation} and made into an interface of its own - * @return the minimum possible width. - */ - @Override - @Deprecated - double getDragAnchorMinWidth() { - final Component component = controller.getComponent(); - double minWidth = Ecdar.CANVAS_PADDING *10; - - for (final Location location : component.getLocations()) { - minWidth = Math.max(minWidth, location.getX() + Ecdar.CANVAS_PADDING * 2); - } - - for (final Edge edge : component.getEdges()) { - for (final Nail nail : edge.getNails()) { - minWidth = Math.max(minWidth, nail.getX() + Ecdar.CANVAS_PADDING); - } - } - return minWidth; - } - - /** - * Gets the minimum possible height when dragging the anchor. - * The height is based on the y coordinate of locations, nails and the signature arrows <br /> - * This should be removed from {@link ModelPresentation} and made into an interface of its own - * @return the minimum possible height. - */ - @Override - @Deprecated - double getDragAnchorMinHeight() { - final Component component = controller.getComponent(); - double minHeight = Ecdar.CANVAS_PADDING * 10; - - for (final Location location : component.getLocations()) { - minHeight = Math.max(minHeight, location.getY() + Ecdar.CANVAS_PADDING * 2); - } - - for (final Edge edge : component.getEdges()) { - for (final Nail nail : edge.getNails()) { - minHeight = Math.max(minHeight, nail.getY() + Ecdar.CANVAS_PADDING); - } - } - - return minHeight; - } - public ProcessController getController() { return controller; } diff --git a/src/main/java/ecdar/presentations/ProjectPanePresentation.java b/src/main/java/ecdar/presentations/ProjectPanePresentation.java index 9d2e8ccc..d152fcaa 100644 --- a/src/main/java/ecdar/presentations/ProjectPanePresentation.java +++ b/src/main/java/ecdar/presentations/ProjectPanePresentation.java @@ -5,13 +5,13 @@ import ecdar.utility.colors.Color; import ecdar.utility.helpers.DropShadowHelper; import com.jfoenix.controls.JFXRippler; +import ecdar.utility.helpers.ImageScaler; import javafx.geometry.Insets; import javafx.scene.control.Tooltip; import javafx.scene.image.Image; import javafx.scene.layout.*; public class ProjectPanePresentation extends StackPane { - private final ProjectPaneController controller; public ProjectPanePresentation() { @@ -101,9 +101,9 @@ private void initializeToolbarButton(final JFXRippler button) { */ private void initializeAddModelIcons() { controller.createComponentImage.setImage(new Image(Ecdar.class.getResource("add_component_frame.png").toExternalForm())); - EcdarPresentation.fitSizeWhenAvailable(controller.createComponentImage, controller.createComponentPane); + ImageScaler.fitImageToPane(controller.createComponentImage, controller.createComponentPane); controller.createSystemImage.setImage(new Image(Ecdar.class.getResource("add_system_frame.png").toExternalForm())); - EcdarPresentation.fitSizeWhenAvailable(controller.createSystemImage, controller.createSystemPane); + ImageScaler.fitImageToPane(controller.createSystemImage, controller.createSystemPane); } /** diff --git a/src/main/java/ecdar/presentations/QueryPresentation.java b/src/main/java/ecdar/presentations/QueryPresentation.java index 852b1704..9c7bd48a 100644 --- a/src/main/java/ecdar/presentations/QueryPresentation.java +++ b/src/main/java/ecdar/presentations/QueryPresentation.java @@ -30,7 +30,6 @@ public class QueryPresentation extends HBox { private final Tooltip tooltip = new Tooltip(); - private Tooltip backendDropdownTooltip; private final QueryController controller; public QueryPresentation(final Query query) { @@ -43,18 +42,19 @@ public QueryPresentation(final Query query) { initializeDetailsButton(); initializeTextFields(); initializeMoreInformationButtonAndQueryTypeSymbol(); - initializeBackendsDropdown(); + initializeEnginesDropdown(); // Ensure that the icons are scaled to current font scale Platform.runLater(() -> Ecdar.getPresentation().getController().scaleIcons(this)); } - private void initializeBackendsDropdown() { - controller.backendsDropdown.setItems(BackendHelper.getBackendInstances()); - backendDropdownTooltip = new Tooltip(); - backendDropdownTooltip.setText("Current backend used for the query"); - JFXTooltip.install(controller.backendsDropdown, backendDropdownTooltip); - controller.backendsDropdown.setValue(BackendHelper.getDefaultBackendInstance()); + private void initializeEnginesDropdown() { + controller.enginesDropdown.setItems(BackendHelper.getEngines()); + + Tooltip enginesDropdownTooltip = new Tooltip(); + enginesDropdownTooltip.setText("Current engine used for the query"); + JFXTooltip.install(controller.enginesDropdown, enginesDropdownTooltip); + controller.enginesDropdown.setValue(BackendHelper.getDefaultEngine()); } private void initializeTextFields() { @@ -69,7 +69,7 @@ private void initializeTextFields() { controller.getQuery().commentProperty().bind(commentTextField.textProperty()); - queryTextField.setOnKeyPressed(EcdarController.getActiveCanvasPresentation().getController().getLeaveTextAreaKeyHandler(keyEvent -> { + queryTextField.setOnKeyPressed(Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasPresentation().getController().getLeaveTextAreaKeyHandler(keyEvent -> { Platform.runLater(() -> { if (keyEvent.getCode().equals(KeyCode.ENTER) && controller.getQuery().getType() != null) { runQuery(); @@ -85,7 +85,7 @@ private void initializeTextFields() { } }); - commentTextField.setOnKeyPressed(EcdarController.getActiveCanvasPresentation().getController().getLeaveTextAreaKeyHandler()); + commentTextField.setOnKeyPressed(Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasPresentation().getController().getLeaveTextAreaKeyHandler()); }); } @@ -332,6 +332,6 @@ private void addQueryTypeListElement(final QueryType type, final DropDownMenu dr } private void runQuery() { - Ecdar.getQueryExecutor().executeQuery(this.controller.getQuery()); + this.controller.getQuery().execute(); } } diff --git a/src/main/java/ecdar/presentations/SignatureArrow.java b/src/main/java/ecdar/presentations/SignatureArrow.java index 51db977e..fb2bfdfb 100644 --- a/src/main/java/ecdar/presentations/SignatureArrow.java +++ b/src/main/java/ecdar/presentations/SignatureArrow.java @@ -1,5 +1,6 @@ package ecdar.presentations; +import ecdar.Ecdar; import ecdar.abstractions.Component; import ecdar.abstractions.EdgeStatus; import ecdar.controllers.EcdarController; @@ -128,14 +129,14 @@ public void unhighlight() { // Color matching SignatureArrow red SignatureArrow matchingSignatureArrow; if (controller.getEdgeStatus().equals(EdgeStatus.INPUT)) { - matchingSignatureArrow = (SignatureArrow) EcdarController.getActiveCanvasPresentation().getController() + matchingSignatureArrow = (SignatureArrow) Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasPresentation().getController() .activeComponentPresentation.getController() .outputSignatureContainer.getChildren() .stream().filter(node -> node instanceof SignatureArrow && ((SignatureArrow) node).controller.getSyncText().equals(controller.getSyncText())) .findFirst().orElse(null); } else { - matchingSignatureArrow = (SignatureArrow) EcdarController.getActiveCanvasPresentation().getController() + matchingSignatureArrow = (SignatureArrow) Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasPresentation().getController() .activeComponentPresentation.getController() .inputSignatureContainer.getChildren() .stream().filter(node -> node instanceof SignatureArrow && ((SignatureArrow) node).controller.getSyncText().equals(controller.getSyncText())) diff --git a/src/main/java/ecdar/presentations/SimEdgePresentation.java b/src/main/java/ecdar/presentations/SimEdgePresentation.java index 1456adc8..8991f145 100644 --- a/src/main/java/ecdar/presentations/SimEdgePresentation.java +++ b/src/main/java/ecdar/presentations/SimEdgePresentation.java @@ -1,9 +1,9 @@ package ecdar.presentations; -import ecdar.Ecdar; import ecdar.abstractions.Component; import ecdar.abstractions.Edge; import ecdar.controllers.SimEdgeController; +import ecdar.controllers.SimulatorController; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.scene.Group; @@ -20,7 +20,7 @@ public class SimEdgePresentation extends Group { public SimEdgePresentation(final Edge edge, final Component component) { controller = new EcdarFXMLLoader().loadAndGetController("SimEdgePresentation.fxml", this); - var simulationHandler = Ecdar.getSimulationHandler(); + var simulationHandler = SimulatorController.getSimulationHandler(); controller.setEdge(edge); this.edge.bind(controller.edgeProperty()); diff --git a/src/main/java/ecdar/presentations/SimLocationPresentation.java b/src/main/java/ecdar/presentations/SimLocationPresentation.java index 082a9bb3..29b51cb1 100755 --- a/src/main/java/ecdar/presentations/SimLocationPresentation.java +++ b/src/main/java/ecdar/presentations/SimLocationPresentation.java @@ -5,6 +5,7 @@ import ecdar.controllers.SimLocationController; import ecdar.utility.Highlightable; import ecdar.utility.colors.Color; +import ecdar.utility.colors.EnabledColor; import ecdar.utility.helpers.BindingHelper; import ecdar.utility.helpers.SelectHelper; import javafx.animation.*; @@ -41,7 +42,7 @@ public class SimLocationPresentation extends Group implements Highlightable { private final Timeline hiddenAreaAnimationExited = new Timeline(); private final Timeline scaleShakeIndicatorBackgroundAnimation = new Timeline(); private final Timeline shakeContentAnimation = new Timeline(); - private final List<BiConsumer<Color, Color.Intensity>> updateColorDelegates = new ArrayList<>(); + private final List<Consumer<EnabledColor>> updateColorDelegates = new ArrayList<>(); private final DoubleProperty animation = new SimpleDoubleProperty(0); private final DoubleBinding reverseAnimation = new SimpleDoubleProperty(1).subtract(animation); private BooleanProperty isPlaced = new SimpleBooleanProperty(true); @@ -87,22 +88,21 @@ private void initializeIdLabel() { idLabel.widthProperty().addListener((obsWidth, oldWidth, newWidth) -> idLabel.translateXProperty().set(newWidth.doubleValue() / -2)); idLabel.heightProperty().addListener((obsHeight, oldHeight, newHeight) -> idLabel.translateYProperty().set(newHeight.doubleValue() / -2)); - final ObjectProperty<Color> color = location.colorProperty(); - final ObjectProperty<Color.Intensity> colorIntensity = location.colorIntensityProperty(); + final ObjectProperty<EnabledColor> color = location.colorProperty(); // Delegate to style the label based on the color of the location - final BiConsumer<Color, Color.Intensity> updateColor = (newColor, newIntensity) -> { - idLabel.setTextFill(newColor.getTextColor(newIntensity)); - ds.setColor(newColor.getColor(newIntensity)); + final Consumer<EnabledColor> updateColor = (newColor) -> { + idLabel.setTextFill(newColor.getTextColor()); + ds.setColor(newColor.getPaintColor()); }; updateColorDelegates.add(updateColor); // Set the initial color - updateColor.accept(color.get(), colorIntensity.get()); + updateColor.accept(color.get()); // Update the color of the circle when the color of the location is updated - color.addListener((obs, old, newColor) -> updateColor.accept(newColor, colorIntensity.get())); + color.addListener((obs, old, newColor) -> updateColor.accept(newColor)); } /** @@ -130,8 +130,8 @@ private void initializeTags() { final Consumer<Location> updateTags = location -> { // Update the color - controller.nicknameTag.bindToColor(location.colorProperty(), location.colorIntensityProperty(), true); - controller.invariantTag.bindToColor(location.colorProperty(), location.colorIntensityProperty(), false); + controller.nicknameTag.bindToColor(location.colorProperty(), true); + controller.invariantTag.bindToColor(location.colorProperty(), false); // Update the invariant controller.nicknameTag.setAndBindString(location.nicknameProperty()); @@ -185,23 +185,21 @@ private void initializeCircle() { final Circle circle = controller.circle; circle.setRadius(RADIUS); - final ObjectProperty<Color> color = location.colorProperty(); - final ObjectProperty<Color.Intensity> colorIntensity = location.colorIntensityProperty(); + final ObjectProperty<EnabledColor> color = location.colorProperty(); // Delegate to style the label based on the color of the location - final BiConsumer<Color, Color.Intensity> updateColor = (newColor, newIntensity) -> { - circle.setFill(newColor.getColor(newIntensity)); - circle.setStroke(newColor.getColor(newIntensity.next(2))); + final Consumer<EnabledColor> updateColor = (newColor) -> { + circle.setFill(newColor.getPaintColor()); + circle.setStroke(newColor.getStrokeColor()); }; updateColorDelegates.add(updateColor); // Set the initial color - updateColor.accept(color.get(), colorIntensity.get()); + updateColor.accept(color.get()); // Update the color of the circle when the color of the location is updated - color.addListener((obs, old, newColor) -> updateColor.accept(newColor, colorIntensity.get())); - colorIntensity.addListener((obs, old, newIntensity) -> updateColor.accept(color.get(), newIntensity)); + color.addListener((obs, old, newColor) -> updateColor.accept(newColor)); } /** @@ -253,22 +251,21 @@ protected void interpolate(final double frac) { updateUrgencies.accept(Location.Urgency.NORMAL, location.getUrgency()); // Update the colors - final ObjectProperty<Color> color = location.colorProperty(); - final ObjectProperty<Color.Intensity> colorIntensity = location.colorIntensityProperty(); + final ObjectProperty<EnabledColor> color = location.colorProperty(); // Delegate to style the label based on the color of the location - final BiConsumer<Color, Color.Intensity> updateColor = (newColor, newIntensity) -> { - notCommittedShape.setFill(newColor.getColor(newIntensity)); - notCommittedShape.setStroke(newColor.getColor(newIntensity.next(2))); + final Consumer<EnabledColor> updateColor = (newColor) -> { + notCommittedShape.setFill(newColor.getPaintColor()); + notCommittedShape.setStroke(newColor.getStrokeColor()); }; updateColorDelegates.add(updateColor); // Set the initial color - updateColor.accept(color.get(), colorIntensity.get()); + updateColor.accept(color.get()); // Update the color of the circle when the color of the location is updated - color.addListener((obs, old, newColor) -> updateColor.accept(newColor, colorIntensity.get())); + color.addListener((obs, old, newColor) -> updateColor.accept(newColor)); } /** @@ -491,15 +488,13 @@ private void initializeLocationShapes(final Path locationShape, final double rad @Override public void highlight() { - updateColorDelegates.forEach(colorConsumer -> colorConsumer.accept(SelectHelper.SELECT_COLOR, SelectHelper.SELECT_COLOR_INTENSITY_NORMAL)); + updateColorDelegates.forEach(colorConsumer -> colorConsumer.accept(new EnabledColor(SelectHelper.SELECT_COLOR, SelectHelper.SELECT_COLOR_INTENSITY_NORMAL))); } @Override public void unhighlight() { updateColorDelegates.forEach(colorConsumer -> { - final Location location = controller.getLocation(); - - colorConsumer.accept(location.getColor(), location.getColorIntensity()); + colorConsumer.accept(controller.getLocation().getColor()); }); } } \ No newline at end of file diff --git a/src/main/java/ecdar/presentations/SimNailPresentation.java b/src/main/java/ecdar/presentations/SimNailPresentation.java index 1baaf392..d6e7fbca 100755 --- a/src/main/java/ecdar/presentations/SimNailPresentation.java +++ b/src/main/java/ecdar/presentations/SimNailPresentation.java @@ -8,6 +8,7 @@ import ecdar.controllers.SimNailController; import ecdar.utility.Highlightable; import ecdar.utility.colors.Color; +import ecdar.utility.colors.EnabledColor; import ecdar.utility.helpers.BindingHelper; import javafx.animation.Interpolator; import javafx.animation.KeyFrame; @@ -88,7 +89,7 @@ private void initializePropertyTag() { BindingHelper.bind(propertyTagLine, propertyTag); // Bind the color of the tag to the color of the component - propertyTag.bindToColor(controller.getComponent().colorProperty(), controller.getComponent().colorIntensityProperty()); + propertyTag.bindToColor(controller.getComponent().colorProperty()); // Updates visibility and placeholder of the tag depending on the type of nail final Consumer<Edge.PropertyType> updatePropertyType = (propertyType) -> { @@ -175,12 +176,11 @@ private void updateSyncLabel() { */ private void initializeNailCircleColor() { final Runnable updateNailColor = () -> { - final Color color = controller.getComponent().getColor(); - final Color.Intensity colorIntensity = controller.getComponent().getColorIntensity(); + final EnabledColor color = controller.getComponent().getColor(); if(!controller.getNail().getPropertyType().equals(Edge.PropertyType.NONE)) { - controller.nailCircle.setFill(color.getColor(colorIntensity)); - controller.nailCircle.setStroke(color.getColor(colorIntensity.next(2))); + controller.nailCircle.setFill(color.getPaintColor()); + controller.nailCircle.setStroke(color.getStrokeColor()); } else { controller.nailCircle.setFill(Color.GREY_BLUE.getColor(Color.Intensity.I800)); controller.nailCircle.setStroke(Color.GREY_BLUE.getColor(Color.Intensity.I900)); @@ -190,9 +190,6 @@ private void initializeNailCircleColor() { // When the color of the component updates, update the nail indicator as well controller.getComponent().colorProperty().addListener((observable) -> updateNailColor.run()); - // When the color intensity of the component updates, update the nail indicator - controller.getComponent().colorIntensityProperty().addListener((observable) -> updateNailColor.run()); - // Initialize the color of the nail updateNailColor.run(); } @@ -253,16 +250,14 @@ public void highlightPurple() { */ @Override public void unhighlight() { - Color color = Color.GREY_BLUE; - Color.Intensity intensity = Color.Intensity.I800; + EnabledColor color = new EnabledColor(Color.GREY_BLUE, Color.Intensity.I800); // Set the color if(!controller.getNail().getPropertyType().equals(Edge.PropertyType.NONE)) { color = controller.getComponent().getColor(); - intensity = controller.getComponent().getColorIntensity(); } - controller.nailCircle.setFill(color.getColor(intensity)); - controller.nailCircle.setStroke(color.getColor(intensity.next(2))); + controller.nailCircle.setFill(color.getPaintColor()); + controller.nailCircle.setStroke(color.getStrokeColor()); } } diff --git a/src/main/java/ecdar/presentations/SimTagPresentation.java b/src/main/java/ecdar/presentations/SimTagPresentation.java index 47fc7d1e..6e322076 100755 --- a/src/main/java/ecdar/presentations/SimTagPresentation.java +++ b/src/main/java/ecdar/presentations/SimTagPresentation.java @@ -3,6 +3,7 @@ import ecdar.Ecdar; import ecdar.abstractions.Component; import ecdar.utility.colors.Color; +import ecdar.utility.colors.EnabledColor; import ecdar.utility.helpers.LocationAware; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; @@ -15,6 +16,7 @@ import javafx.scene.shape.Path; import java.util.function.BiConsumer; +import java.util.function.Consumer; /** * The presentation for the tag shown on a {@link SimEdgePresentation} in the {@link SimulatorOverviewPresentation}<br /> @@ -112,22 +114,21 @@ private void initializeShape() { shape.setStroke(backgroundColor.getColor(backgroundColorIntensity.next(4))); } - public void bindToColor(final ObjectProperty<Color> color, final ObjectProperty<Color.Intensity> intensity) { - bindToColor(color, intensity, false); + public void bindToColor(final ObjectProperty<EnabledColor> color) { + bindToColor(color, false); } - public void bindToColor(final ObjectProperty<Color> color, final ObjectProperty<Color.Intensity> intensity, final boolean doColorBackground) { - final BiConsumer<Color, Color.Intensity> recolor = (newColor, newIntensity) -> { + public void bindToColor(final ObjectProperty<EnabledColor> color, final boolean doColorBackground) { + final Consumer<EnabledColor> recolor = (newColor) -> { if (doColorBackground) { final Path shape = (Path) lookup("#shape"); - shape.setFill(newColor.getColor(newIntensity.next(-1))); - shape.setStroke(newColor.getColor(newIntensity.next(-1).next(2))); + shape.setFill(newColor.nextIntensity(-1).getPaintColor()); + shape.setStroke(newColor.getStrokeColor()); } }; - color.addListener(observable -> recolor.accept(color.get(), intensity.get())); - intensity.addListener(observable -> recolor.accept(color.get(), intensity.get())); - recolor.accept(color.get(), intensity.get()); + color.addListener(observable -> recolor.accept(color.get())); + recolor.accept(color.get()); } /** diff --git a/src/main/java/ecdar/presentations/SyncTextFieldPresentation.java b/src/main/java/ecdar/presentations/SyncTextFieldPresentation.java index a4e7b82c..e957cc89 100644 --- a/src/main/java/ecdar/presentations/SyncTextFieldPresentation.java +++ b/src/main/java/ecdar/presentations/SyncTextFieldPresentation.java @@ -1,5 +1,6 @@ package ecdar.presentations; +import ecdar.Ecdar; import ecdar.abstractions.Edge; import ecdar.controllers.EcdarController; import ecdar.controllers.SyncTextFieldController; @@ -22,7 +23,7 @@ public SyncTextFieldPresentation(String placeholder, Edge edge) { controller.textField.setTranslateY(2); } else { controller.textField.setTranslateY(0); - EcdarController.getActiveCanvasPresentation().getController().leaveTextAreas(); + Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasPresentation().getController().leaveTextAreas(); } }); diff --git a/src/main/java/ecdar/presentations/SystemEdgePresentation.java b/src/main/java/ecdar/presentations/SystemEdgePresentation.java index ffc329a2..1abaf1e0 100644 --- a/src/main/java/ecdar/presentations/SystemEdgePresentation.java +++ b/src/main/java/ecdar/presentations/SystemEdgePresentation.java @@ -1,11 +1,12 @@ package ecdar.presentations; import ecdar.Ecdar; -import ecdar.abstractions.EcdarSystemEdge; +import ecdar.abstractions.SystemEdge; import ecdar.abstractions.EcdarSystem; import ecdar.controllers.EcdarController; import ecdar.controllers.SystemEdgeController; import ecdar.utility.colors.Color; +import ecdar.utility.colors.EnabledColor; import ecdar.utility.helpers.ItemDragHelper; import ecdar.utility.helpers.SelectHelper; import javafx.beans.property.DoubleProperty; @@ -26,7 +27,7 @@ public class SystemEdgePresentation extends Group implements SelectHelper.ItemSe * @param edge system edge to present * @param system system of the system edge */ - public SystemEdgePresentation(final EcdarSystemEdge edge, final EcdarSystem system) { + public SystemEdgePresentation(final SystemEdge edge, final EcdarSystem system) { controller = new EcdarFXMLLoader().loadAndGetController("SystemEdgePresentation.fxml", this); controller.setEdge(edge); controller.setSystem(system); @@ -35,7 +36,7 @@ public SystemEdgePresentation(final EcdarSystemEdge edge, final EcdarSystem syst initializeBinding(edge); } - private void initializeBinding(final EcdarSystemEdge edge) { + private void initializeBinding(final SystemEdge edge) { final Link link = new Link(); links.add(link); @@ -50,8 +51,8 @@ private void initializeBinding(final EcdarSystemEdge edge) { link.startYProperty().bind(edge.getTempNode().getEdgeY()); // Bind to mouse position - link.endXProperty().bind(EcdarController.getActiveCanvasPresentation().mouseTracker.xProperty().subtract(Ecdar.CANVAS_PADDING / 2)); - link.endYProperty().bind(EcdarController.getActiveCanvasPresentation().mouseTracker.xProperty().subtract(Ecdar.CANVAS_PADDING / 2)); + link.endXProperty().bind(Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasPresentation().mouseTracker.xProperty().subtract(Ecdar.CANVAS_PADDING / 2)); + link.endYProperty().bind(Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasPresentation().mouseTracker.xProperty().subtract(Ecdar.CANVAS_PADDING / 2)); } // If edge source and target changes, bind @@ -73,7 +74,7 @@ private void initializeBinding(final EcdarSystemEdge edge) { * Binds the end of the last link to the parent of the edge. * @param edge edge to bind with */ - private void bindFinishedEdge(final EcdarSystemEdge edge) { + private void bindFinishedEdge(final SystemEdge edge) { final Link firstLink = links.get(0); final Link lastLink = links.get(links.size() - 1); @@ -87,10 +88,9 @@ private void bindFinishedEdge(final EcdarSystemEdge edge) { /** * Does nothing, as this cannot change color. * @param color not used - * @param intensity not used */ @Override - public void color(final Color color, final Color.Intensity intensity) { + public void color(final EnabledColor color) { } @@ -99,16 +99,7 @@ public void color(final Color color, final Color.Intensity intensity) { * @return null */ @Override - public Color getColor() { - return null; - } - - /** - * Returns null, as this cannot change color. - * @return null - */ - @Override - public Color.Intensity getColorIntensity() { + public EnabledColor getColor() { return null; } diff --git a/src/main/java/ecdar/presentations/SystemPresentation.java b/src/main/java/ecdar/presentations/SystemPresentation.java index 306b8b12..182d0aa8 100644 --- a/src/main/java/ecdar/presentations/SystemPresentation.java +++ b/src/main/java/ecdar/presentations/SystemPresentation.java @@ -1,19 +1,7 @@ package ecdar.presentations; -import ecdar.Ecdar; import ecdar.abstractions.*; -import ecdar.controllers.ModelController; import ecdar.controllers.SystemController; -import ecdar.utility.colors.Color; -import javafx.geometry.Insets; -import javafx.geometry.Pos; -import javafx.scene.layout.*; -import javafx.scene.shape.Path; -import javafx.scene.shape.Polygon; -import javafx.scene.shape.Rectangle; -import javafx.scene.shape.Shape; - -import java.util.function.BiConsumer; /** * Presentation for a system. @@ -25,166 +13,14 @@ public SystemPresentation(final EcdarSystem system) { controller = new EcdarFXMLLoader().loadAndGetController("SystemPresentation.fxml", this); controller.setSystem(system); - super.initialize(system.getBox()); - - initializeDimensions(system.getBox()); - - // Initialize methods that is sensitive to width and height - final Runnable onUpdateSize = () -> { - initializeToolbar(); - initializeFrame(); - initializeBackground(); - }; - - onUpdateSize.run(); - - // Re-run initialisation on update of width and height property - system.getBox().getWidthProperty().addListener(observable -> onUpdateSize.run()); - system.getBox().getHeightProperty().addListener(observable -> onUpdateSize.run()); - } - - /** - * Initializes the toolbar. - */ - private void initializeToolbar() { - final EcdarSystem system = controller.getSystem(); - - final BiConsumer<Color, Color.Intensity> updateColor = (newColor, newIntensity) -> { - // Set the background of the toolbar - controller.toolbar.setBackground(new Background(new BackgroundFill( - newColor.getColor(newIntensity), - CornerRadii.EMPTY, - Insets.EMPTY - ))); - - controller.toolbar.setPrefHeight(TOOLBAR_HEIGHT); - }; - - system.colorProperty().addListener(observable -> updateColor.accept(system.getColor(), system.getColorIntensity())); - - updateColor.accept(system.getColor(), system.getColorIntensity()); - } - - /** - * Initializes the frame and handling of it. - * The frame is a rectangle minus two cutouts. - */ - private void initializeFrame() { - final EcdarSystem system = controller.getSystem(); - - final Shape[] mask = new Shape[1]; - final Rectangle rectangle = new Rectangle(system.getBox().getWidth(), system.getBox().getHeight()); - - // Generate top right corner (to subtract) - final Polygon topRightCorner = new Polygon( - system.getBox().getWidth(), 0, - system.getBox().getWidth() - (CORNER_SIZE + 2), 0, - system.getBox().getWidth(), CORNER_SIZE + 2 - ); - - final BiConsumer<Color, Color.Intensity> updateColor = (newColor, newIntensity) -> { - // Mask the parent of the frame (will also mask the background) - mask[0] = Path.subtract(rectangle, TOP_LEFT_CORNER); - mask[0] = Path.subtract(mask[0], topRightCorner); - controller.frame.setClip(mask[0]); - controller.background.setClip(Path.union(mask[0], mask[0])); - controller.background.setOpacity(0.5); - - // Bind the missing lines that we cropped away - controller.topLeftLine.setStartX(CORNER_SIZE); - controller.topLeftLine.setStartY(0); - controller.topLeftLine.setEndX(0); - controller.topLeftLine.setEndY(CORNER_SIZE); - controller.topLeftLine.setStroke(newColor.getColor(newIntensity.next(2))); - controller.topLeftLine.setStrokeWidth(1.25); - StackPane.setAlignment(controller.topLeftLine, Pos.TOP_LEFT); - - controller.topRightLine.setStartX(0); - controller.topRightLine.setStartY(0); - controller.topRightLine.setEndX(CORNER_SIZE); - controller.topRightLine.setEndY(CORNER_SIZE); - controller.topRightLine.setStroke(newColor.getColor(newIntensity.next(2))); - controller.topRightLine.setStrokeWidth(1.25); - StackPane.setAlignment(controller.topRightLine, Pos.TOP_RIGHT); - - // Set the stroke color to two shades darker - controller.frame.setBorder(new Border(new BorderStroke( - newColor.getColor(newIntensity.next(2)), - BorderStrokeStyle.SOLID, - CornerRadii.EMPTY, - new BorderWidths(1), - Insets.EMPTY - ))); - }; - - // Update now, and update on color change - updateColor.accept(system.getColor(), system.getColorIntensity()); - system.colorProperty().addListener(observable -> updateColor.accept(system.getColor(), system.getColorIntensity())); - } - - /** - * Initializes the background - */ - private void initializeBackground() { - final EcdarSystem system = controller.getSystem(); - - // Bind the background width and height to the values in the model - controller.background.widthProperty().bindBidirectional(system.getBox().getWidthProperty()); - controller.background.heightProperty().bindBidirectional(system.getBox().getHeightProperty()); - - final BiConsumer<Color, Color.Intensity> updateColor = (newColor, newIntensity) -> { - // Set the background color to the lightest possible version of the color - controller.background.setFill(newColor.getColor(newIntensity.next(-10).next(2))); - }; - - system.colorProperty().addListener(observable -> updateColor.accept(system.getColor(), system.getColorIntensity())); - updateColor.accept(system.getColor(), system.getColorIntensity()); + minHeightProperty().bindBidirectional(system.getBox().getHeightProperty()); + maxHeightProperty().bindBidirectional(system.getBox().getHeightProperty()); + minWidthProperty().bindBidirectional(system.getBox().getWidthProperty()); + maxWidthProperty().bindBidirectional(system.getBox().getWidthProperty()); } @Override - ModelController getModelController() { + public SystemController getController() { return controller; } - - /** - * Gets the minimum allowed width when dragging the anchor. - * It is determined by the position and size of the system nodes. - * @return the minimum allowed width - */ - @Override - double getDragAnchorMinWidth() { - final EcdarSystem system = controller.getSystem(); - double minWidth = system.getSystemRoot().getX() + SystemRoot.WIDTH + Ecdar.CANVAS_PADDING * 2; - - for (final ComponentInstance instance : system.getComponentInstances()) { - minWidth = Math.max(minWidth, instance.getBox().getX() + instance.getBox().getWidth() + Ecdar.CANVAS_PADDING); - } - - for (final ComponentOperator operator : system.getComponentOperators()) { - minWidth = Math.max(minWidth, operator.getBox().getX() + operator.getBox().getWidth() + Ecdar.CANVAS_PADDING); - } - - return minWidth; - } - - /** - * Gets the minimum allowed height when dragging the anchor. - * It is determined by the position and size of the system nodes. - * @return the minimum allowed height - */ - @Override - double getDragAnchorMinHeight() { - final EcdarSystem system = controller.getSystem(); - double minHeight = Ecdar.CANVAS_PADDING * 2; - - for (final ComponentInstance instance : system.getComponentInstances()) { - minHeight = Math.max(minHeight, instance.getBox().getY() + instance.getBox().getHeight() + Ecdar.CANVAS_PADDING); - } - - for (final ComponentOperator operator : system.getComponentOperators()) { - minHeight = Math.max(minHeight, operator.getBox().getY() + operator.getBox().getHeight() + Ecdar.CANVAS_PADDING); - } - - return minHeight; - } } diff --git a/src/main/java/ecdar/presentations/SystemRootPresentation.java b/src/main/java/ecdar/presentations/SystemRootPresentation.java index ac271c7e..b978f410 100644 --- a/src/main/java/ecdar/presentations/SystemRootPresentation.java +++ b/src/main/java/ecdar/presentations/SystemRootPresentation.java @@ -5,6 +5,7 @@ import ecdar.controllers.SystemRootController; import ecdar.utility.Highlightable; import ecdar.utility.colors.Color; +import ecdar.utility.colors.EnabledColor; import ecdar.utility.helpers.ItemDragHelper; import ecdar.utility.helpers.SelectHelper; import javafx.beans.property.BooleanProperty; @@ -19,6 +20,7 @@ */ public class SystemRootPresentation extends StackPane implements Highlightable { private final SystemRootController controller; + private final EnabledColor highlightColor = new EnabledColor(SelectHelper.SELECT_COLOR, SelectHelper.SELECT_COLOR_INTENSITY_NORMAL); public SystemRootPresentation(final EcdarSystem system) { controller = new EcdarFXMLLoader().loadAndGetController("SystemRootPresentation.fxml", this); @@ -104,16 +106,15 @@ private void initializeColor() { * The color will be a bit darker than the color of the system. */ private void dyeFromSystemColor() { - dye(controller.getSystem().getColor(), controller.getSystem().getColorIntensity().next(2)); + dye(controller.getSystem().getColor()); } /** * Dyes the polygon. * @param color the color to dye with - * @param intensity the intensity of the color to use */ - private void dye(final Color color, final Color.Intensity intensity) { - controller.shape.setFill(color.getColor(intensity)); + private void dye(final EnabledColor color) { + controller.shape.setFill(color.getPaintColor()); } /** @@ -133,7 +134,7 @@ private ItemDragHelper.DragBounds getDragBounds() { @Override public void highlight() { - dye(SelectHelper.SELECT_COLOR, SelectHelper.SELECT_COLOR_INTENSITY_NORMAL); + dye(highlightColor); } @Override @@ -143,6 +144,6 @@ public void unhighlight() { @Override public void highlightPurple() { - dye(Color.DEEP_PURPLE, SelectHelper.SELECT_COLOR_INTENSITY_NORMAL); + dye(new EnabledColor(Color.DEEP_PURPLE, SelectHelper.SELECT_COLOR_INTENSITY_NORMAL)); } } diff --git a/src/main/java/ecdar/presentations/TagPresentation.java b/src/main/java/ecdar/presentations/TagPresentation.java index 30786de9..a41d9a5e 100644 --- a/src/main/java/ecdar/presentations/TagPresentation.java +++ b/src/main/java/ecdar/presentations/TagPresentation.java @@ -1,9 +1,11 @@ package ecdar.presentations; +import ecdar.Ecdar; import ecdar.abstractions.Component; import ecdar.controllers.EcdarController; import ecdar.utility.UndoRedoStack; import ecdar.utility.colors.Color; +import ecdar.utility.colors.EnabledColor; import ecdar.utility.helpers.ItemDragHelper; import ecdar.utility.helpers.LocationAware; import com.jfoenix.controls.JFXTextField; @@ -24,6 +26,7 @@ import javafx.scene.shape.Path; import java.util.function.BiConsumer; +import java.util.function.Consumer; import static javafx.scene.paint.Color.TRANSPARENT; @@ -69,7 +72,7 @@ void initializeTextFocusHandler() { textFieldFocusProperty().addListener((observable, oldValue, newValue) -> { if (!hadInitialFocus && newValue) { hadInitialFocus = true; - EcdarController.getActiveCanvasPresentation().getController().leaveTextAreas(); + Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasPresentation().getController().leaveTextAreas(); } }); }); @@ -119,7 +122,9 @@ private void initializeShape() { }); // When enter or escape is pressed release focus - textField.setOnKeyPressed(EcdarController.getActiveCanvasPresentation().getController().getLeaveTextAreaKeyHandler()); + Platform.runLater(() -> { + textField.setOnKeyPressed(Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasPresentation().getController().getLeaveTextAreaKeyHandler()); + }); } private void initializeDragging(JFXTextField textField, Path shape) { @@ -145,8 +150,8 @@ private void initializeDragging(JFXTextField textField, Path shape) { shape.addEventHandler(MouseEvent.MOUSE_DRAGGED, event -> { event.consume(); - final double dragDistanceX = (event.getSceneX() - dragOffsetX.get()) / EcdarController.getActiveCanvasZoomFactor().get(); - final double dragDistanceY = (event.getSceneY() - dragOffsetY.get()) / EcdarController.getActiveCanvasZoomFactor().get(); + final double dragDistanceX = (event.getSceneX() - dragOffsetX.get()) / Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasZoomFactor().get(); + final double dragDistanceY = (event.getSceneY() - dragOffsetY.get()) / Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasZoomFactor().get(); double draggableNewX = getDragBounds().trimX(draggablePreviousX.get() + dragDistanceX); double draggableNewY = getDragBounds().trimY(draggablePreviousY.get() + dragDistanceY); @@ -237,32 +242,30 @@ void initializeLabel() { l3.xProperty().bind(textField.widthProperty()); } - public void bindToColor(final ObjectProperty<Color> color, final ObjectProperty<Color.Intensity> intensity) { - bindToColor(color, intensity, false); + public void bindToColor(final ObjectProperty<EnabledColor> color) { + bindToColor(color, false); } - public void bindToColor(final ObjectProperty<Color> color, final ObjectProperty<Color.Intensity> intensity, final boolean doColorBackground) { - final BiConsumer<Color, Color.Intensity> recolor = (newColor, newIntensity) -> { + public void bindToColor(final ObjectProperty<EnabledColor> color, final boolean doColorBackground) { + final Consumer<EnabledColor> recolor = (newColor) -> { final JFXTextField textField = (JFXTextField) lookup("#textField"); textField.setUnFocusColor(TRANSPARENT); - textField.setFocusColor(newColor.getColor(newIntensity)); + textField.setFocusColor(newColor.getPaintColor()); if (doColorBackground) { final Path shape = (Path) lookup("#shape"); - shape.setFill(newColor.getColor(newIntensity.next(-1))); - shape.setStroke(newColor.getColor(newIntensity.next(-1).next(2))); + shape.setFill(newColor.nextIntensity(-1).getPaintColor()); + shape.setStroke(newColor.nextIntensity(1).getPaintColor()); - textField.setStyle("-fx-prompt-text-fill: rgba(255, 255, 255, 0.6); -fx-text-fill: " + newColor.getTextColorRgbaString(newIntensity) + ";"); - textField.setFocusColor(newColor.getTextColor(newIntensity)); + textField.setStyle("-fx-prompt-text-fill: rgba(255, 255, 255, 0.6); -fx-text-fill: " + newColor.getTextColorRgbaString() + ";"); + textField.setFocusColor(newColor.getTextColor()); } else { textField.setStyle("-fx-prompt-text-fill: rgba(0, 0, 0, 0.6);"); } }; - color.addListener(observable -> recolor.accept(color.get(), intensity.get())); - intensity.addListener(observable -> recolor.accept(color.get(), intensity.get())); - - recolor.accept(color.get(), intensity.get()); + color.addListener(observable -> recolor.accept(color.get())); + recolor.accept(color.get()); } public void setAndBindString(final StringProperty stringProperty) { diff --git a/src/main/java/ecdar/simulation/SimulationState.java b/src/main/java/ecdar/simulation/SimulationState.java index 538a0441..e1c20729 100644 --- a/src/main/java/ecdar/simulation/SimulationState.java +++ b/src/main/java/ecdar/simulation/SimulationState.java @@ -4,6 +4,7 @@ import EcdarProtoBuf.ObjectProtos.State; import ecdar.Ecdar; import ecdar.backend.SimulationHandler; +import ecdar.controllers.SimulatorController; import javafx.collections.ObservableMap; import javafx.util.Pair; @@ -71,6 +72,6 @@ private String getComponentName(String id) { */ public ObservableMap<String, BigDecimal> getSimulationClocks() { // TODO move clocks from SimulationHandler to SimulationState - return Ecdar.getSimulationHandler().getSimulationClocks(); + return SimulatorController.getSimulationHandler().getSimulationClocks(); } } diff --git a/src/main/java/ecdar/utility/colors/Color.java b/src/main/java/ecdar/utility/colors/Color.java index 04643251..e932507a 100644 --- a/src/main/java/ecdar/utility/colors/Color.java +++ b/src/main/java/ecdar/utility/colors/Color.java @@ -368,6 +368,8 @@ public enum Intensity { A400, A700; + public Intensity lowest() { return next(-this.ordinal()); } + public Intensity next() { return next(1); } @@ -376,9 +378,8 @@ public Intensity next(final int levels) { final Intensity[] values = values(); // One of the first 10 elements + final int index = this.ordinal() + levels; if (this.ordinal() <= 9) { - final int index = this.ordinal() + levels; - if (index < 0) { return values[0]; } else if (index > 9) { @@ -389,8 +390,6 @@ public Intensity next(final int levels) { } // One of the last 4 elements else { - final int index = this.ordinal() + levels; - if (index < 10) { return values[10]; } else if (index > 13) { diff --git a/src/main/java/ecdar/utility/colors/EnabledColor.java b/src/main/java/ecdar/utility/colors/EnabledColor.java index 8ef4e647..fc75b523 100644 --- a/src/main/java/ecdar/utility/colors/EnabledColor.java +++ b/src/main/java/ecdar/utility/colors/EnabledColor.java @@ -5,8 +5,7 @@ import java.util.ArrayList; public class EnabledColor { - - public static final ArrayList<EnabledColor> enabledColors = new ArrayList<EnabledColor>() {{ + public static final ArrayList<EnabledColor> enabledColors = new ArrayList<>() {{ add(new EnabledColor(Color.GREY_BLUE, Color.Intensity.I700, KeyCode.DIGIT0)); add(new EnabledColor(Color.DEEP_ORANGE, Color.Intensity.I700, KeyCode.DIGIT1)); add(new EnabledColor(Color.PINK, Color.Intensity.I500, KeyCode.DIGIT2)); @@ -53,6 +52,42 @@ public static EnabledColor fromIdentifier(final String identifier) { return null; } + public static EnabledColor getDefault() { + return enabledColors.get(0); + } + + public javafx.scene.paint.Color getTextColor() { + return color.getTextColor(intensity); + } + + public javafx.scene.paint.Color getPaintColor() { + return color.getColor(intensity); + } + + public javafx.scene.paint.Color getStrokeColor() { + return nextIntensity(2).getPaintColor(); + } + + public String getTextColorRgbaString() { + return color.getTextColorRgbaString(intensity); + } + + public EnabledColor getLowestIntensity() { + return new EnabledColor(color, intensity.lowest()); + } + + public EnabledColor nextIntensity() { + return nextIntensity(1); + } + + public EnabledColor nextIntensity(final int levelIncrement) { + return new EnabledColor(this.color, this.intensity.next(levelIncrement)); + } + + public EnabledColor setIntensity(int i) { + return getLowestIntensity().nextIntensity(i); + } + @Override public boolean equals(final Object obj) { return obj instanceof EnabledColor && ((EnabledColor) obj).color.equals(this.color); diff --git a/src/main/java/ecdar/utility/helpers/BindingHelper.java b/src/main/java/ecdar/utility/helpers/BindingHelper.java index ddfc0462..5c5b747e 100644 --- a/src/main/java/ecdar/utility/helpers/BindingHelper.java +++ b/src/main/java/ecdar/utility/helpers/BindingHelper.java @@ -1,5 +1,6 @@ package ecdar.utility.helpers; +import ecdar.Ecdar; import ecdar.controllers.EcdarController; import ecdar.model_canvas.arrow_heads.ArrowHead; import ecdar.model_canvas.arrow_heads.ChannelReceiverArrowHead; @@ -35,8 +36,8 @@ public static void bind(final Line subject, final SimTagPresentation target) { } public static void bind(final Circular subject, final ObservableDoubleValue x, final ObservableDoubleValue y) { - subject.xProperty().bind(EcdarController.getActiveCanvasPresentation().mouseTracker.xProperty().subtract(x)); - subject.yProperty().bind(EcdarController.getActiveCanvasPresentation().mouseTracker.yProperty().subtract(y)); + subject.xProperty().bind(Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasPresentation().mouseTracker.xProperty().subtract(x)); + subject.yProperty().bind(Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasPresentation().mouseTracker.yProperty().subtract(y)); } public static void bind(final Link lineSubject, final ArrowHead arrowHeadSubject, final Circular source, final Circular target) { @@ -75,7 +76,7 @@ public static void bind(final Link subject, final Circle source, final MouseTrac public static void bind(final Link subject, final Circular source, final ObservableDoubleValue x, final ObservableDoubleValue y) { // Calculate the bindings (so that the line will be based on the circle circumference instead of in its center) - final LineBinding lineBinding = LineBinding.getCircularBindings(source, EcdarController.getActiveCanvasPresentation().mouseTracker, x, y); + final LineBinding lineBinding = LineBinding.getCircularBindings(source, Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasPresentation().mouseTracker, x, y); // Bind the subjects properties accordingly to our calculations subject.startXProperty().bind(lineBinding.startX); @@ -141,7 +142,7 @@ protected double computeValue() { public static void bind(final ArrowHead subject, final Circular source, final ObservableDoubleValue x, final ObservableDoubleValue y) { // Calculate the bindings (so that the line will be based on the circle circumference instead of in its center) - final LineBinding lineBinding = LineBinding.getCircularBindings(source, EcdarController.getActiveCanvasPresentation().mouseTracker, x, y); + final LineBinding lineBinding = LineBinding.getCircularBindings(source, Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasPresentation().mouseTracker, x, y); ObservableDoubleValue startX = lineBinding.startX; ObservableDoubleValue startY = lineBinding.startY; diff --git a/src/main/java/ecdar/utility/helpers/ImageScaler.java b/src/main/java/ecdar/utility/helpers/ImageScaler.java new file mode 100644 index 00000000..428ba723 --- /dev/null +++ b/src/main/java/ecdar/utility/helpers/ImageScaler.java @@ -0,0 +1,16 @@ +package ecdar.utility.helpers; + +import javafx.scene.image.ImageView; +import javafx.scene.layout.StackPane; + +public class ImageScaler { + public static void fitImageToPane(final ImageView imageView, final StackPane pane) { + pane.widthProperty().addListener((observable, oldValue, newValue) -> + imageView.setFitWidth(pane.getWidth())); + pane.heightProperty().addListener((observable, oldValue, newValue) -> + imageView.setFitHeight(pane.getHeight())); + + imageView.setFitWidth(pane.getWidth()); + imageView.setFitHeight(pane.getHeight()); + } +} diff --git a/src/main/java/ecdar/utility/helpers/ItemDragHelper.java b/src/main/java/ecdar/utility/helpers/ItemDragHelper.java index b082ac9b..a103c8b9 100644 --- a/src/main/java/ecdar/utility/helpers/ItemDragHelper.java +++ b/src/main/java/ecdar/utility/helpers/ItemDragHelper.java @@ -1,5 +1,6 @@ package ecdar.utility.helpers; +import ecdar.Ecdar; import ecdar.controllers.EcdarController; import ecdar.controllers.EdgeController; import ecdar.presentations.ComponentOperatorPresentation; @@ -123,8 +124,8 @@ public static void makeDraggable(final Node mouseSubject, final Node draggable, final DragBounds dragBounds = getDragBounds.get(); - final double dragDistanceX = (event.getSceneX() - dragOffsetX.get()) / EcdarController.getActiveCanvasZoomFactor().get(); - final double dragDistanceY = (event.getSceneY() - dragOffsetY.get()) / EcdarController.getActiveCanvasZoomFactor().get(); + final double dragDistanceX = (event.getSceneX() - dragOffsetX.get()) / Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasZoomFactor().get(); + final double dragDistanceY = (event.getSceneY() - dragOffsetY.get()) / Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasZoomFactor().get(); double draggableNewX = dragBounds.trimX(draggablePreviousX.get() + dragDistanceX); double draggableNewY = dragBounds.trimY(draggablePreviousY.get() + dragDistanceY); diff --git a/src/main/java/ecdar/utility/helpers/MouseCircular.java b/src/main/java/ecdar/utility/helpers/MouseCircular.java index d1779988..c36f470f 100644 --- a/src/main/java/ecdar/utility/helpers/MouseCircular.java +++ b/src/main/java/ecdar/utility/helpers/MouseCircular.java @@ -1,5 +1,6 @@ package ecdar.utility.helpers; +import ecdar.Ecdar; import ecdar.controllers.EcdarController; import ecdar.utility.mouse.MouseTracker; import javafx.beans.property.DoubleProperty; @@ -14,7 +15,7 @@ public class MouseCircular implements Circular { private final DoubleProperty originalMouseY = new SimpleDoubleProperty(0d); private final DoubleProperty radius = new SimpleDoubleProperty(10); private final SimpleDoubleProperty scale = new SimpleDoubleProperty(1d); - private final MouseTracker mouseTracker = EcdarController.getActiveCanvasPresentation().mouseTracker; + private final MouseTracker mouseTracker = Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasPresentation().mouseTracker; public MouseCircular(Circular initLocation) { //Set the initial x and y coordinates of the circular @@ -29,10 +30,10 @@ public MouseCircular(Circular initLocation) { mouseTracker.registerOnMouseDraggedEventHandler(event -> updatePosition()); // If the component is dragged while we are drawing an edge, update the coordinates accordingly - EcdarController.getActiveCanvasPresentation().getController().modelPane.translateXProperty().addListener((observable, oldValue, newValue) -> originalX.set( - originalX.get() - (newValue.doubleValue() - oldValue.doubleValue()) / EcdarController.getActiveCanvasZoomFactor().get())); - EcdarController.getActiveCanvasPresentation().getController().modelPane.translateYProperty().addListener((observable, oldValue, newValue) -> originalY.set( - originalY.get() - (newValue.doubleValue() - oldValue.doubleValue()) / EcdarController.getActiveCanvasZoomFactor().get())); + Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasPresentation().getController().modelPane.translateXProperty().addListener((observable, oldValue, newValue) -> originalX.set( + originalX.get() - (newValue.doubleValue() - oldValue.doubleValue()) / Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasZoomFactor().get())); + Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasPresentation().getController().modelPane.translateYProperty().addListener((observable, oldValue, newValue) -> originalY.set( + originalY.get() - (newValue.doubleValue() - oldValue.doubleValue()) / Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasZoomFactor().get())); // ToDo NIELS: When the width or height of the scene is changed, the coordinates should be updated } @@ -41,8 +42,8 @@ private void updatePosition() { final double dragDistanceX = mouseTracker.xProperty().get() - originalMouseX.get(); final double dragDistanceY = mouseTracker.yProperty().get() - originalMouseY.get(); - x.set(originalX.get() + dragDistanceX / EcdarController.getActiveCanvasZoomFactor().get()); - y.set(originalY.get() + dragDistanceY / EcdarController.getActiveCanvasZoomFactor().get()); + x.set(originalX.get() + dragDistanceX / Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasZoomFactor().get()); + y.set(originalY.get() + dragDistanceY / Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasZoomFactor().get()); } @Override diff --git a/src/main/java/ecdar/utility/helpers/MouseTrackable.java b/src/main/java/ecdar/utility/helpers/MouseTrackable.java deleted file mode 100644 index f0ce91cf..00000000 --- a/src/main/java/ecdar/utility/helpers/MouseTrackable.java +++ /dev/null @@ -1,7 +0,0 @@ -package ecdar.utility.helpers; - -import ecdar.utility.mouse.MouseTracker; - -public interface MouseTrackable extends LocationAware { - MouseTracker getMouseTracker(); -} diff --git a/src/main/java/ecdar/utility/helpers/SelectHelper.java b/src/main/java/ecdar/utility/helpers/SelectHelper.java index d4f94ba9..47e23f00 100644 --- a/src/main/java/ecdar/utility/helpers/SelectHelper.java +++ b/src/main/java/ecdar/utility/helpers/SelectHelper.java @@ -1,8 +1,10 @@ package ecdar.utility.helpers; +import ecdar.Ecdar; import ecdar.code_analysis.Nearable; import ecdar.controllers.EcdarController; import ecdar.utility.colors.Color; +import ecdar.utility.colors.EnabledColor; import javafx.collections.FXCollections; import javafx.collections.ObservableList; @@ -18,7 +20,7 @@ public class SelectHelper { public static void select(final ItemSelectable selectable) { - EcdarController.getActiveCanvasPresentation().getController().leaveTextAreas(); + Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasPresentation().getController().leaveTextAreas(); // Check if the element is already selected if (selectedElements.contains(selectable)) return; @@ -79,11 +81,9 @@ public interface Selectable { } public interface ItemSelectable extends Selectable, LocationAware { - void color(Color color, Color.Intensity intensity); + void color(EnabledColor color); - Color getColor(); - - Color.Intensity getColorIntensity(); + EnabledColor getColor(); ItemDragHelper.DragBounds getDragBounds(); diff --git a/src/main/java/ecdar/utility/helpers/UPPAALSyntaxHighlighter.java b/src/main/java/ecdar/utility/helpers/UPPAALSyntaxHighlighter.java new file mode 100644 index 00000000..4cdbabf8 --- /dev/null +++ b/src/main/java/ecdar/utility/helpers/UPPAALSyntaxHighlighter.java @@ -0,0 +1,40 @@ +package ecdar.utility.helpers; + +import org.fxmisc.richtext.model.StyleSpans; +import org.fxmisc.richtext.model.StyleSpansBuilder; + +import java.util.Collection; +import java.util.Collections; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class UPPAALSyntaxHighlighter { + private static final String uppaalKeywords = "clock|chan|urgent|broadcast"; + private static final String cKeywords = "auto|bool|break|case|char|const|continue|default|do|double|else|enum|extern|float|for|goto|if|int|long|register|return|short|signed|sizeof|static|struct|switch|typedef|union|unsigned|void|volatile|while"; + private static final Pattern UPPAAL = Pattern.compile("" + + "(" + uppaalKeywords + ")" + + "|(" + cKeywords + ")" + + "|(//.*|(\"(?:\\\\[^\"]|\\\\\"|.)*?\")|(?s)/\\*.*?\\*/)"); + + public static StyleSpans<Collection<String>> computeHighlighting(final String text) { + final Matcher matcher = UPPAAL.matcher(text); + int lastKwEnd = 0; + final StyleSpansBuilder<Collection<String>> spansBuilder = new StyleSpansBuilder<>(); + while (matcher.find()) { + spansBuilder.add(Collections.emptyList(), matcher.start() - lastKwEnd); + + if (matcher.group(1) != null) { + spansBuilder.add(Collections.singleton("uppaal-keyword"), matcher.end(1) - matcher.start(1)); + } else if (matcher.group(2) != null) { + spansBuilder.add(Collections.singleton("c-keyword"), matcher.end(2) - matcher.start(2)); + } else if (matcher.group(3) != null) { + spansBuilder.add(Collections.singleton("comment"), matcher.end(3) - matcher.start(3)); + } + + lastKwEnd = matcher.end(); + } + + spansBuilder.add(Collections.emptyList(), text.length() - lastKwEnd); + return spansBuilder.create(); + } +} diff --git a/src/main/java/ecdar/utility/helpers/UnoccupiedSpaceFinder.java b/src/main/java/ecdar/utility/helpers/UnoccupiedSpaceFinder.java new file mode 100644 index 00000000..33bf9c72 --- /dev/null +++ b/src/main/java/ecdar/utility/helpers/UnoccupiedSpaceFinder.java @@ -0,0 +1,115 @@ +package ecdar.utility.helpers; + +import ecdar.abstractions.Box; +import javafx.geometry.Point2D; + +import java.util.Collection; + +import static ecdar.presentations.ModelPresentation.TOOLBAR_HEIGHT; + +public class UnoccupiedSpaceFinder { + /** + * Finds an unoccupied space within the provided box, starting from the preferred placement. + * Returns null if no such space could be found given the existing locations + * @param bounds bounds within which to find the unoccupied space + * @param occupiedSpaces array of coordinates that are occupied + * @param preferredPlacement the preferred coordinates for the free space + * @param spacing the spacing between any two points and any point and the edges of the box + * @return a free space or null if unable to find one + */ + public static Point2D getUnoccupiedSpace(final Box bounds, final Collection<Point2D> occupiedSpaces, final Point2D preferredPlacement, final double spacing) { + boolean hit = false; + + double latestHitRight = 0, + latestHitDown = 0, + latestHitLeft = 0, + latestHitUp = 0; + + //Check to see if the location is placed on top of another location + for (Point2D entry : occupiedSpaces) { + if (Math.abs(entry.getX() - (preferredPlacement.getX())) < spacing && Math.abs(entry.getY() - (preferredPlacement.getY())) < spacing) { + hit = true; + latestHitRight = entry.getX(); + latestHitDown = entry.getY(); + latestHitLeft = entry.getX(); + latestHitUp = entry.getY(); + break; + } + } + + //If the location is not placed on top of any other locations, do not do anything + if (!hit) { + return preferredPlacement; + } + hit = false; + + //Find an unoccupied space for the location + for (int i = 1; i < bounds.getWidth() / spacing; i++) { + //Check to see, if the location can be placed to the right of an existing location + if (latestHitRight > spacing && bounds.getWidth() > latestHitRight + spacing) { + for (Point2D entry : occupiedSpaces) { + if (Math.abs(entry.getX() - (latestHitRight + spacing)) < spacing && Math.abs(entry.getY() - (preferredPlacement.getY())) < spacing) { + hit = true; + latestHitRight = entry.getX(); + break; + } + } + + if (!hit) { + return new Point2D(latestHitRight + spacing, preferredPlacement.getY()); + } + } + hit = false; + + //Check to see, if the location can be placed below an existing location + if (latestHitDown > spacing && bounds.getHeight() > latestHitDown + spacing) { + for (Point2D entry : occupiedSpaces) { + if (Math.abs(entry.getX() - (preferredPlacement.getX())) < spacing && Math.abs(entry.getY() - (latestHitDown + spacing)) < spacing) { + hit = true; + latestHitDown = entry.getY(); + break; + } + } + + if (!hit) { + return new Point2D(preferredPlacement.getX(), latestHitDown + spacing); + } + } + hit = false; + + //Check to see, if the location can be placed to the left of the existing location + if (latestHitLeft > spacing && bounds.getWidth() > latestHitLeft - spacing) { + for (Point2D entry : occupiedSpaces) { + if (Math.abs(entry.getX() - (latestHitLeft - spacing)) < spacing && Math.abs(entry.getY() - (preferredPlacement.getY())) < spacing) { + hit = true; + latestHitLeft = entry.getX(); + break; + } + } + + if (!hit) { + return new Point2D(latestHitLeft - spacing, preferredPlacement.getY()); + } + } + hit = false; + + //Check to see, if the location can be placed above an existing location + if (latestHitUp > spacing + TOOLBAR_HEIGHT && bounds.getHeight() > latestHitUp - spacing) { + for (Point2D entry : occupiedSpaces) { + if (Math.abs(entry.getX() - (preferredPlacement.getX())) < spacing && Math.abs(entry.getY() - (latestHitUp - spacing)) < spacing) { + hit = true; + latestHitUp = entry.getY(); + break; + } + } + + if (!hit) { + return new Point2D(preferredPlacement.getX(), latestHitUp - spacing); + } + } + hit = false; + } + + return null; + } +} diff --git a/src/main/java/ecdar/utility/helpers/ZoomHelper.java b/src/main/java/ecdar/utility/helpers/ZoomHelper.java index be0c1f28..b61e12b1 100644 --- a/src/main/java/ecdar/utility/helpers/ZoomHelper.java +++ b/src/main/java/ecdar/utility/helpers/ZoomHelper.java @@ -1,9 +1,8 @@ package ecdar.utility.helpers; import ecdar.Ecdar; -import ecdar.controllers.EcdarController; -import ecdar.presentations.CanvasPresentation; -import ecdar.presentations.ModelPresentation; +import ecdar.controllers.ComponentController; +import ecdar.presentations.*; import javafx.application.Platform; import javafx.beans.property.DoubleProperty; import javafx.beans.property.SimpleDoubleProperty; @@ -14,7 +13,7 @@ public class ZoomHelper { public double maxZoomFactor = 4; private CanvasPresentation canvasPresentation; - private ModelPresentation model; + private HighLevelModelPresentation model; private boolean active = true; /** @@ -24,13 +23,13 @@ public class ZoomHelper { */ public void setCanvas(CanvasPresentation newCanvasPresentation) { canvasPresentation = newCanvasPresentation; - model = canvasPresentation.getController().getActiveComponentPresentation(); + model = canvasPresentation.getController().getActiveModelPresentation(); // Update the model whenever the component is updated - canvasPresentation.getController().activeComponentProperty().addListener((observable) -> { + canvasPresentation.getController().activeModelProperty().addListener((observable) -> { // Run later to ensure that the active component presentation is up-to-date Platform.runLater(() -> { - model = canvasPresentation.getController().getActiveComponentPresentation(); + model = canvasPresentation.getController().getActiveModelPresentation(); }); }); @@ -42,43 +41,43 @@ public Double getZoomLevel() { } public void setZoomLevel(Double zoomLevel) { - if (active && model != null) { - currentZoomFactor.set(zoomLevel); - } + if (!active || model == null) return; + + currentZoomFactor.set(zoomLevel); } /** * Zoom in with a delta of 1.2 */ public void zoomIn() { - if (active) { - double delta = 1.2; - double newScale = currentZoomFactor.get() * delta; + if (!active) return; - //Limit for zooming in - if (newScale > maxZoomFactor) { - return; - } + double delta = 1.2; + double newScale = currentZoomFactor.get() * delta; - currentZoomFactor.set(newScale); + //Limit for zooming in + if (newScale > maxZoomFactor) { + return; } + + currentZoomFactor.set(newScale); } /** * Zoom out with a delta of 1.2 */ public void zoomOut() { - if (active) { - double delta = 1.2; - double newScale = currentZoomFactor.get() / delta; + if (!active) return; - //Limit for zooming out - if (newScale < minZoomFactor) { - return; - } + double delta = 1.2; + double newScale = currentZoomFactor.get() / delta; - currentZoomFactor.set(newScale); + //Limit for zooming out + if (newScale < minZoomFactor) { + return; } + + currentZoomFactor.set(newScale); } /** @@ -86,26 +85,33 @@ public void zoomOut() { */ public void resetZoom() { currentZoomFactor.set(1); + if (canvasPresentation + .getController() + .getActiveModelPresentation() instanceof DeclarationsPresentation) alignDeclaration(); } /** * Zoom in to fit the component on screen */ public void zoomToFit() { - if (active) { - if (EcdarController.getActiveCanvasPresentation().getController().getActiveModel() == null) { - resetZoom(); - return; - } - - double neededWidth = canvasPresentation.getWidth() / (model.getWidth() - + canvasPresentation.getController().getActiveComponentPresentation().getController().inputSignatureContainer.getWidth() - + canvasPresentation.getController().getActiveComponentPresentation().getController().outputSignatureContainer.getWidth()); - double newScale = Math.min(neededWidth, canvasPresentation.getHeight() / model.getHeight() - 0.2); //0.1 for width and 0.2 for height subtracted for margin - - currentZoomFactor.set(newScale); - centerComponent(); + if (!active || model == null) return; + + double neededWidth = getWidthNeededForModel(); + double newScale = Math.min(canvasPresentation.getWidth() / neededWidth, canvasPresentation.getHeight() / model.getMinHeight() - 0.2); // 0.2 subtracted for margin + + currentZoomFactor.set(newScale); + centerComponentOrSystem(); + } + + private double getWidthNeededForModel() { + if (model instanceof ComponentPresentation) { + ComponentController componentController = (ComponentController) model.getController(); + return model.getMinWidth() + + componentController.inputSignatureContainer.getWidth() + + componentController.outputSignatureContainer.getWidth(); } + + return model.getMinWidth(); } /** @@ -115,16 +121,22 @@ public void setActive(boolean activeState) { this.active = activeState; if (!activeState) { // If zoom has been disabled, reset the zoom level - resetZoom(); + Platform.runLater(this::resetZoom); } } - private void centerComponent() { - EcdarController.getActiveCanvasPresentation().getController().modelPane.setTranslateX(0); - EcdarController.getActiveCanvasPresentation().getController().modelPane.setTranslateY(-Ecdar.CANVAS_PADDING * 2); // 0 is slightly below center, this looks better + private void centerComponentOrSystem() { + // 0 is slightly below center, this looks better + canvasPresentation.getController().modelPane.setTranslateY(-Ecdar.CANVAS_PADDING * 2); + canvasPresentation.getController().modelPane.setTranslateX(0); // Center the model within the modelPane to account for resized model model.setTranslateX(0); model.setTranslateY(0); } + + private void alignDeclaration() { + canvasPresentation.getController().modelPane.setTranslateX(0); + canvasPresentation.getController().modelPane.setTranslateY(0); + } } diff --git a/src/main/proto b/src/main/proto index 1e0172b6..e42e35db 160000 --- a/src/main/proto +++ b/src/main/proto @@ -1 +1 @@ -Subproject commit 1e0172b68466459d18efc0dd41eb494f542a488c +Subproject commit e42e35db63efacd7ab42aecd17c9a52b291c7be9 diff --git a/src/main/resources/ecdar/main.css b/src/main/resources/ecdar/main.css index 0d99400f..16c63c23 100644 --- a/src/main/resources/ecdar/main.css +++ b/src/main/resources/ecdar/main.css @@ -259,14 +259,14 @@ -fx-background-insets: 0em 0em 0em 0em; } -.backend-instances-list { +.engine-instances-list { -fx-padding: 5; -fx-border-style: SOLID HIDDEN SOLID HIDDEN; -fx-border-color: -divider-color; -fx-border-width: 2px; } -.backend-instance { +.engine-instance { -fx-border-style: SOLID SOLID SOLID SOLID; -fx-border-color: -divider-color; -fx-border-width: 1px; diff --git a/src/main/resources/ecdar/presentations/DeclarationPresentation.fxml b/src/main/resources/ecdar/presentations/DeclarationsPresentation.fxml similarity index 100% rename from src/main/resources/ecdar/presentations/DeclarationPresentation.fxml rename to src/main/resources/ecdar/presentations/DeclarationsPresentation.fxml diff --git a/src/main/resources/ecdar/presentations/EcdarPresentation.fxml b/src/main/resources/ecdar/presentations/EcdarPresentation.fxml index 4421fd18..cb72110a 100644 --- a/src/main/resources/ecdar/presentations/EcdarPresentation.fxml +++ b/src/main/resources/ecdar/presentations/EcdarPresentation.fxml @@ -202,7 +202,7 @@ <SeparatorMenuItem/> - <MenuItem fx:id="menuBarOptionsBackendOptions" text="Backend options"> + <MenuItem fx:id="menuBarOptionsEngineOptions" text="Engine Options"> <graphic> <FontIcon iconLiteral="gmi-settings-input-component" styleClass="icon-size-medium" fill="black"/> @@ -484,9 +484,9 @@ </JFXDialog> </StackPane> - <!-- Backends Dialog --> - <StackPane fx:id="backendOptionsDialogContainer" style="-fx-background-color: #0000007F;" mouseTransparent="true"> - <BackendOptionsDialogPresentation fx:id="backendOptionsDialog"/> + <!-- Engines Dialog --> + <StackPane fx:id="engineOptionsDialogContainer" style="-fx-background-color: #0000007F;" mouseTransparent="true"> + <EngineOptionsDialogPresentation fx:id="engineOptionsDialog"/> </StackPane> <!-- Simulation initialization Dialog --> diff --git a/src/main/resources/ecdar/presentations/BackendOptionsDialogPresentation.fxml b/src/main/resources/ecdar/presentations/EngineOptionsDialogPresentation.fxml similarity index 80% rename from src/main/resources/ecdar/presentations/BackendOptionsDialogPresentation.fxml rename to src/main/resources/ecdar/presentations/EngineOptionsDialogPresentation.fxml index 3cdcdc00..702d6fc2 100644 --- a/src/main/resources/ecdar/presentations/BackendOptionsDialogPresentation.fxml +++ b/src/main/resources/ecdar/presentations/EngineOptionsDialogPresentation.fxml @@ -10,7 +10,7 @@ <?import javafx.geometry.Insets?> <fx:root xmlns="http://javafx.com/javafx" xmlns:fx="http://javafx.com/fxml" - type="JFXDialog" fx:controller="ecdar.controllers.BackendOptionsDialogController" + type="JFXDialog" fx:controller="ecdar.controllers.EngineOptionsDialogController" prefHeight="400.0" prefWidth="600.0" style="-fx-background-color: #0000007F;"> <VBox> @@ -22,17 +22,17 @@ <Insets topRightBottomLeft="10"/> </padding> <HBox> - <Text styleClass="headline" HBox.hgrow="ALWAYS">Backends</Text> + <Text styleClass="headline" HBox.hgrow="ALWAYS">Engines</Text> <Region HBox.hgrow="ALWAYS"/> - <JFXButton fx:id="resetBackendsButton" text="Reset backends" styleClass="button-danger"/> + <JFXButton fx:id="resetEnginesButton" text="Reset Engines" styleClass="button-danger"/> </HBox> <Region prefHeight="10"/> - <ScrollPane styleClass="backend-instances-list" + <ScrollPane styleClass="engine-instances-list" style="-fx-background-color:transparent;" hbarPolicy="NEVER" fitToWidth="true" fitToHeight="true" maxHeight="600"> - <VBox fx:id="backendInstanceList" prefWidth="600" spacing="5"/> + <VBox fx:id="engineInstanceList" prefWidth="600" spacing="5"/> </ScrollPane> - <JFXRippler fx:id="addBackendButton" alignment="CENTER_RIGHT"> + <JFXRippler fx:id="addEngineButton" alignment="CENTER_RIGHT"> <StackPane minWidth="300" minHeight="40" StackPane.alignment="CENTER"> <FontIcon iconLiteral="gmi-add" styleClass="icon-size-small" fill="black"/> diff --git a/src/main/resources/ecdar/presentations/BackendInstancePresentation.fxml b/src/main/resources/ecdar/presentations/EnginePresentation.fxml similarity index 77% rename from src/main/resources/ecdar/presentations/BackendInstancePresentation.fxml rename to src/main/resources/ecdar/presentations/EnginePresentation.fxml index 61d57bbb..af686165 100644 --- a/src/main/resources/ecdar/presentations/BackendInstancePresentation.fxml +++ b/src/main/resources/ecdar/presentations/EnginePresentation.fxml @@ -10,17 +10,17 @@ <?import javafx.scene.control.Label?> <fx:root xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8.0.76-ea" - type="StackPane" fx:controller="ecdar.controllers.BackendInstanceController" - styleClass="backend-instance"> + type="StackPane" fx:controller="ecdar.controllers.EngineInstanceController" + styleClass="engine-instance"> <HBox spacing="10"> <VBox alignment="CENTER" minWidth="50" maxWidth="50" style="-fx-background-color: -primary-color-darker"> - <JFXRippler fx:id="moveBackendInstanceUpRippler" alignment="CENTER_RIGHT"> + <JFXRippler fx:id="moveEngineInstanceUpRippler" alignment="CENTER_RIGHT"> <StackPane minWidth="40" minHeight="40" StackPane.alignment="CENTER_LEFT"> <FontIcon iconLiteral="gmi-expand-less" styleClass="icon-size-small" fill="white"/> </StackPane> </JFXRippler> - <JFXRippler fx:id="moveBackendInstanceDownRippler" alignment="CENTER_RIGHT"> + <JFXRippler fx:id="moveEngineInstanceDownRippler" alignment="CENTER_RIGHT"> <StackPane minWidth="40" minHeight="40" StackPane.alignment="CENTER_LEFT"> <FontIcon iconLiteral="gmi-expand-more" styleClass="icon-size-small" fill="white"/> @@ -33,8 +33,8 @@ </padding> <HBox alignment="CENTER_LEFT"> <VBox> - <JFXTextField fx:id="backendName" styleClass="subhead" promptText="Backend name"/> - <Label fx:id="backendNameIssue" styleClass="input-violation, sub-caption" visible="false"/> + <JFXTextField fx:id="engineName" styleClass="subhead" promptText="Engine name"/> + <Label fx:id="engineNameIssue" styleClass="input-violation, sub-caption" visible="false"/> </VBox> <JFXRippler alignment="CENTER_RIGHT"> <StackPane minWidth="40" minHeight="40" @@ -43,10 +43,10 @@ <FontIcon fx:id="expansionIcon" iconLiteral="gmi-expand-less" styleClass="icon-size-small" fill="black"/> </StackPane> </JFXRippler> - <JFXRippler fx:id="removeBackendRippler" alignment="CENTER_RIGHT"> + <JFXRippler fx:id="removeEngineRippler" alignment="CENTER_RIGHT"> <StackPane minWidth="40" minHeight="40" StackPane.alignment="TOP_RIGHT"> - <FontIcon fx:id="removeBackendIcon" iconLiteral="gmi-delete" styleClass="icon-size-small" fill="black"/> + <FontIcon fx:id="removeEngineIcon" iconLiteral="gmi-delete" styleClass="icon-size-small" fill="black"/> </StackPane> </JFXRippler> </HBox> @@ -63,14 +63,14 @@ <Text styleClass="subhead">Address: </Text> <JFXTextField fx:id="address" promptText="xxx.xxx.xxx.xxx"/> </HBox> - <HBox fx:id="pathToBackendSection" spacing="10" alignment="CENTER_LEFT"> + <HBox fx:id="pathToEngineSection" spacing="10" alignment="CENTER_LEFT"> <Text styleClass="subhead">Path: </Text> - <JFXTextField fx:id="pathToBackend" promptText="Absolute path"/> - <JFXRippler fx:id="pickPathToBackend"> + <JFXTextField fx:id="pathToEngine" promptText="Absolute path"/> + <JFXRippler fx:id="pickPathToEngine"> <StackPane minWidth="20" minHeight="20" - onMouseClicked="#openPathToBackendDialog" + onMouseClicked="#openPathToEngineDialog" StackPane.alignment="TOP_RIGHT"> - <FontIcon fx:id="pickPathToBackendIcon" iconLiteral="gmi-open-in-new" iconSize="20" fill="black"/> + <FontIcon fx:id="pickPathToEngineIcon" iconLiteral="gmi-open-in-new" iconSize="20" fill="black"/> </StackPane> </JFXRippler> </HBox> @@ -95,8 +95,8 @@ </VBox> </StackPane> <HBox spacing="20"> - <JFXRadioButton fx:id="defaultBackendRadioButton" styleClass="subhead">Default</JFXRadioButton> - <JFXCheckBox fx:id="threadSafeBackendCheckBox" styleClass="subhead">Thread Safe</JFXCheckBox> + <JFXRadioButton fx:id="defaultEngineRadioButton" styleClass="subhead">Default</JFXRadioButton> + <JFXCheckBox fx:id="threadSafeEngineCheckBox" styleClass="subhead">Thread Safe</JFXCheckBox> </HBox> </VBox> </HBox> diff --git a/src/main/resources/ecdar/presentations/FilePresentation.fxml b/src/main/resources/ecdar/presentations/FilePresentation.fxml index 6cc2efa7..31996483 100644 --- a/src/main/resources/ecdar/presentations/FilePresentation.fxml +++ b/src/main/resources/ecdar/presentations/FilePresentation.fxml @@ -8,23 +8,23 @@ <?import javafx.geometry.Insets?> <fx:root xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8.0.76-ea" - type="AnchorPane" + type="AnchorPane" fx:id="root" fx:controller="ecdar.controllers.FileController"> - <JFXRippler id="rippler" AnchorPane.topAnchor="0.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0"> + <JFXRippler fx:id="rippler" AnchorPane.topAnchor="0.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0"> <HBox style="-fx-min-height: 3.7em; -fx-max-height: 3.7em" alignment="CENTER"> <padding> <Insets top="10" bottom="10"/> </padding> <HBox alignment="CENTER_LEFT" HBox.hgrow="ALWAYS"> <StackPane style="-fx-padding: 0em 1em 0em 1em;"> - <Circle id="iconBackground" radius="1.25" styleClass="responsive-circle-radius"/> + <Circle fx:id="iconBackground" radius="1.25" styleClass="responsive-circle-radius"/> <StackPane fx:id="fileImageStackPane" styleClass="responsive-small-image-sizing"> <ImageView fx:id="fileImage"/> </StackPane> </StackPane> <StackPane> - <Label id="fileName" styleClass="body1"/> + <Label fx:id="fileName" styleClass="body1"/> </StackPane> </HBox> @@ -32,7 +32,7 @@ <!-- MORE INFORMATION --> <JFXRippler fx:id="moreInformation" visible="false"> <StackPane styleClass="responsive-icon-stack-pane-sizing"> - <FontIcon id="moreInformationIcon" iconLiteral="gmi-more-vert" fill="white" + <FontIcon fx:id="moreInformationIcon" iconLiteral="gmi-more-vert" fill="white" styleClass="icon-size-medium"/> </StackPane> </JFXRippler> diff --git a/src/main/resources/ecdar/presentations/MessageCollectionPresentation.fxml b/src/main/resources/ecdar/presentations/MessageCollectionPresentation.fxml index 7e0364c0..54d9804e 100644 --- a/src/main/resources/ecdar/presentations/MessageCollectionPresentation.fxml +++ b/src/main/resources/ecdar/presentations/MessageCollectionPresentation.fxml @@ -6,11 +6,12 @@ <?import org.kordamp.ikonli.javafx.FontIcon?> <fx:root xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8.0.76-ea" - type="VBox"> + type="VBox" fx:controller="ecdar.controllers.MessageCollectionController" + fx:id="root"> <HBox> <StackPane> - <Circle id="indicator" radius="16"/> + <Circle fx:id="indicator" radius="16"/> <FontIcon iconLiteral="gmi-description" iconSize="22" fill="white" mouseTransparent="true"/> </StackPane> @@ -18,9 +19,9 @@ </HBox> <HBox translateX="15"> - <Line endY="16" strokeWidth="2" id="line"/> + <Line endY="16" strokeWidth="2" fx:id="line"/> <Region minWidth="24"/> - <VBox id="children"/> + <VBox fx:id="messageBox"/> </HBox> </fx:root> \ No newline at end of file diff --git a/src/main/resources/ecdar/presentations/QueryPresentation.fxml b/src/main/resources/ecdar/presentations/QueryPresentation.fxml index 6b89a210..9bbbef91 100644 --- a/src/main/resources/ecdar/presentations/QueryPresentation.fxml +++ b/src/main/resources/ecdar/presentations/QueryPresentation.fxml @@ -65,7 +65,7 @@ </JFXRippler> </HBox> <StackPane style="-fx-min-width: 6.5em; -fx-max-width: 6.5em;"> - <JFXComboBox fx:id="backendsDropdown" styleClass="caption, responsive-text-area-sizing"/> + <JFXComboBox fx:id="enginesDropdown" styleClass="caption, responsive-text-area-sizing"/> </StackPane> </VBox> <StackPane prefHeight="1" style="-fx-background-color:-grey-300;" AnchorPane.rightAnchor="0" diff --git a/src/test/java/ecdar/abstractions/ComponentTest.java b/src/test/java/ecdar/abstractions/ComponentTest.java index 0946a548..b6f822ad 100644 --- a/src/test/java/ecdar/abstractions/ComponentTest.java +++ b/src/test/java/ecdar/abstractions/ComponentTest.java @@ -1,14 +1,20 @@ package ecdar.abstractions; import ecdar.Ecdar; +import ecdar.mutation.ComponentVerificationTransformer; +import ecdar.utility.colors.EnabledColor; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Assertions; import java.util.List; +import static ecdar.abstractions.Project.LOCATION; + public class ComponentTest { + private int counter = 0; + @BeforeAll static void setup() { Ecdar.setUpForTest(); @@ -16,13 +22,13 @@ static void setup() { @Test public void testCloneSameId() { - final Component original = new Component(false); + final Component original = new Component(EnabledColor.getDefault(), "test_comp"); final Location loc1 = new Location(); original.addLocation(loc1); final String id1 = loc1.getId(); - final Component clone = original.cloneForVerification(); + final Component clone = ComponentVerificationTransformer.cloneForVerification(original); // Clone has a location with the same id Assertions.assertNotNull(clone.findLocation(id1)); @@ -30,16 +36,16 @@ public void testCloneSameId() { @Test public void testCloneChangeTargetOfOriginal() { - final Component original = new Component(false); + final Component original = new Component(EnabledColor.getDefault(), "test_comp"); Ecdar.getProject().getComponents().add(original); final Location loc1 = new Location(); - loc1.initialize(); + loc1.initialize(getUniqueLocationId()); original.addLocation(loc1); final String id1 = loc1.getId(); final Location loc2 = new Location(); - loc2.initialize(); + loc2.initialize(getUniqueLocationId()); original.addLocation(loc2); final String id2 = loc2.getId(); @@ -47,7 +53,7 @@ public void testCloneChangeTargetOfOriginal() { edge1.setTargetLocation(loc1); original.addEdge(edge1); - final Component clone = original.cloneForVerification(); + final Component clone = ComponentVerificationTransformer.cloneForVerification(original); // The two ids should be different Assertions.assertNotEquals(id1, id2); @@ -65,16 +71,16 @@ public void testCloneChangeTargetOfOriginal() { @Test public void testCloneChangeTargetOfClone() { - final Component original = new Component(false); + final Component original = new Component(EnabledColor.getDefault(), "test_comp"); Ecdar.getProject().getComponents().add(original); final Location loc1 = new Location(); - loc1.initialize(); + loc1.initialize(getUniqueLocationId()); original.addLocation(loc1); final String id1 = loc1.getId(); final Location loc2 = new Location(); - loc2.initialize(); + loc2.initialize(getUniqueLocationId()); original.addLocation(loc2); final String id2 = loc2.getId(); @@ -82,7 +88,7 @@ public void testCloneChangeTargetOfClone() { edge1.setTargetLocation(loc1); original.addEdge(edge1); - final Component clone = original.cloneForVerification(); + final Component clone = ComponentVerificationTransformer.cloneForVerification(original); // The two ids should be different Assertions.assertNotEquals(id1, id2); @@ -105,17 +111,17 @@ public void testAngelicCompletion() { // Has no outgoing edges final Location l1 = new Location(); - l1.initialize(); + l1.initialize(getUniqueLocationId()); c.addLocation(l1); // Has outgoing a input edge without guard final Location l2 = new Location(); - l2.initialize(); + l2.initialize(getUniqueLocationId()); c.addLocation(l2); // Has outgoing b input edge with guard x <= 3 final Location l3 = new Location(); - l3.initialize(); + l3.initialize(getUniqueLocationId()); c.addLocation(l3); final Edge e1 = new Edge(l2, EdgeStatus.INPUT); @@ -141,7 +147,7 @@ public void testAngelicCompletion() { Assertions.assertEquals(3, c.getLocations().size()); Assertions.assertEquals(3, c.getEdges().size()); - c.applyAngelicCompletion(); + ComponentVerificationTransformer.applyAngelicCompletionForComponent(c); Assertions.assertEquals(3, c.getLocations().size()); @@ -193,7 +199,7 @@ public void testAngelicCompletionConjunction() { final Component c = new Component(); final Location l1 = new Location(); - l1.initialize(); + l1.initialize(getUniqueLocationId()); c.addLocation(l1); final Edge e1 = new Edge(l1, EdgeStatus.INPUT); @@ -207,7 +213,7 @@ public void testAngelicCompletionConjunction() { Assertions.assertEquals(1, c.getLocations().size()); Assertions.assertEquals(1, c.getEdges().size()); - c.applyAngelicCompletion(); + ComponentVerificationTransformer.applyAngelicCompletionForComponent(c); Assertions.assertEquals(1, c.getLocations().size()); Assertions.assertEquals(3, c.getEdges().size()); @@ -232,7 +238,7 @@ public void testAngelicCompletionDisjunction() { final Component c = new Component(); final Location l1 = new Location(); - l1.initialize(); + l1.initialize(getUniqueLocationId()); c.addLocation(l1); final Edge e1 = new Edge(l1, EdgeStatus.INPUT); @@ -252,7 +258,7 @@ public void testAngelicCompletionDisjunction() { Assertions.assertEquals(1, c.getLocations().size()); Assertions.assertEquals(2, c.getEdges().size()); - c.applyAngelicCompletion(); + ComponentVerificationTransformer.applyAngelicCompletionForComponent(c); Assertions.assertEquals(1, c.getLocations().size()); Assertions.assertEquals(3, c.getEdges().size()); @@ -270,7 +276,7 @@ public void testAngelicCompletionMathInGuard() { final Component c = new Component(); final Location l1 = new Location(); - l1.initialize(); + l1.initialize(getUniqueLocationId()); c.addLocation(l1); final Edge e1 = new Edge(l1, EdgeStatus.INPUT); @@ -284,7 +290,7 @@ public void testAngelicCompletionMathInGuard() { Assertions.assertEquals(1, c.getLocations().size()); Assertions.assertEquals(1, c.getEdges().size()); - c.applyAngelicCompletion(); + ComponentVerificationTransformer.applyAngelicCompletionForComponent(c); Assertions.assertEquals(1, c.getLocations().size()); Assertions.assertEquals(2, c.getEdges().size()); @@ -387,4 +393,9 @@ public void getLocalVariablesBoolAssignment() { Assertions.assertEquals(1, vars.size()); Assertions.assertEquals("sound", vars.get(0)); } + + private String getUniqueLocationId() { + counter++; + return LOCATION + counter; + } } \ No newline at end of file diff --git a/src/test/java/ecdar/backend/QueryHandlerTest.java b/src/test/java/ecdar/backend/QueryHandlerTest.java deleted file mode 100644 index be0fe103..00000000 --- a/src/test/java/ecdar/backend/QueryHandlerTest.java +++ /dev/null @@ -1,20 +0,0 @@ -package ecdar.backend; - -import ecdar.abstractions.Query; -import ecdar.abstractions.QueryState; -import org.junit.jupiter.api.Test; - -import static org.mockito.Mockito.*; - -public class QueryHandlerTest { - @Test - public void testExecuteQuery() { - BackendDriver bd = mock(BackendDriver.class); - QueryHandler qh = new QueryHandler(bd); - - Query q = new Query("refinement: (Administration || Machine || Researcher) \u003c\u003d Spec", "", QueryState.UNKNOWN); - qh.executeQuery(q); - - verify(bd, times(1)).addRequestToExecutionQueue(any()); - } -} diff --git a/src/test/java/ecdar/backend/QueryTest.java b/src/test/java/ecdar/backend/QueryTest.java new file mode 100644 index 00000000..3b9a07ad --- /dev/null +++ b/src/test/java/ecdar/backend/QueryTest.java @@ -0,0 +1,18 @@ +package ecdar.backend; + +import ecdar.abstractions.Query; +import ecdar.abstractions.QueryState; +import org.junit.jupiter.api.Test; + +import static org.mockito.Mockito.*; + +public class QueryTest { + @Test + public void testExecuteQuery() { + Engine e = mock(Engine.class); + Query q = new Query("refinement: (Administration || Machine || Researcher) <= Spec", "", QueryState.UNKNOWN, e); + q.execute(); + + verify(e, times(1)).enqueueQuery(eq(q), any(), any()); + } +} diff --git a/src/test/java/ecdar/mutation/ComponentVerificationTransformerTest.java b/src/test/java/ecdar/mutation/ComponentVerificationTransformerTest.java new file mode 100644 index 00000000..1f0bbf0c --- /dev/null +++ b/src/test/java/ecdar/mutation/ComponentVerificationTransformerTest.java @@ -0,0 +1,26 @@ +package ecdar.mutation; + +import ecdar.abstractions.Component; +import ecdar.abstractions.Location; +import ecdar.abstractions.Project; +import ecdar.utility.colors.EnabledColor; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import static ecdar.mutation.ComponentVerificationTransformer.applyAngelicCompletionForComponent; +import static ecdar.mutation.ComponentVerificationTransformer.applyDemonicCompletionToComponent; + +public class ComponentVerificationTransformerTest { + @Test + public void demonicCompletionAddsUniversalLocationAndMatchAllInputAndOutputEdges() { + final Project project = new Project(); + final Component comp = new Component(EnabledColor.getDefault(), "Comp"); + project.addComponent(comp); + + applyDemonicCompletionToComponent(comp); + + Assertions.assertTrue(comp.getLocations().stream().anyMatch(e -> e.getType().equals(Location.Type.UNIVERSAL))); + Assertions.assertTrue(comp.getListOfEdgesFromDisplayableEdges(comp.getInputEdges()).stream().anyMatch(e -> e.getSync().equals("*"))); + Assertions.assertTrue(comp.getListOfEdgesFromDisplayableEdges(comp.getOutputEdges()).stream().anyMatch(e -> e.getSync().equals("*"))); + } +} diff --git a/src/test/java/ecdar/mutation/operators/ChangeTargetOperatorTest.java b/src/test/java/ecdar/mutation/operators/ChangeTargetOperatorTest.java index fe3a869e..bc543b3b 100644 --- a/src/test/java/ecdar/mutation/operators/ChangeTargetOperatorTest.java +++ b/src/test/java/ecdar/mutation/operators/ChangeTargetOperatorTest.java @@ -5,6 +5,7 @@ import ecdar.abstractions.Edge; import ecdar.abstractions.EdgeStatus; import ecdar.abstractions.Location; +import ecdar.utility.colors.EnabledColor; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Assertions; @@ -18,7 +19,7 @@ static void setup() { @Test public void testNumberOfMutants() { - final Component component = new Component(false); + final Component component = new Component(EnabledColor.getDefault(), "test_comp"); Ecdar.getProject().getComponents().add(component); // 3 locations in addition to the already created initial location diff --git a/src/test/java/ecdar/simulation/ReachabilityTest.java b/src/test/java/ecdar/simulation/ReachabilityTest.java index 06020049..ab1aac1a 100644 --- a/src/test/java/ecdar/simulation/ReachabilityTest.java +++ b/src/test/java/ecdar/simulation/ReachabilityTest.java @@ -3,9 +3,9 @@ import ecdar.Ecdar; import ecdar.abstractions.Component; import ecdar.abstractions.Location; -import ecdar.backend.BackendDriver; -import ecdar.backend.BackendHelper; import ecdar.backend.SimulationHandler; +import ecdar.controllers.SimLocationController; +import ecdar.controllers.SimulatorController; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -31,7 +31,7 @@ void reachabilityQuerySyntaxTestSuccess() { var component = new Component(); component.setName("C1"); - SimulationHandler simulationHandler = new SimulationHandler(new BackendDriver()); + SimulationHandler simulationHandler = new SimulationHandler(); List<String> components = new ArrayList<>(); components.add("C1"); @@ -40,9 +40,9 @@ void reachabilityQuerySyntaxTestSuccess() { simulationHandler.setComponentsInSimulation(components); - Ecdar.setSimulationHandler(simulationHandler); + SimulatorController.setSimulationHandler(simulationHandler); - var result = BackendHelper.getLocationReachableQuery(location, component, "query"); + var result = SimLocationController.getSimLocationReachableQuery(location, component, "query"); assertTrue(result.matches(regex)); } @@ -54,7 +54,7 @@ void reachabilityQueryLocationPosition1TestSuccess() { var component = new Component(); component.setName("C1"); - SimulationHandler simulationHandler = new SimulationHandler(new BackendDriver()); + SimulationHandler simulationHandler = new SimulationHandler(); List<String> components = new ArrayList<>(); components.add("C1"); @@ -65,7 +65,7 @@ void reachabilityQueryLocationPosition1TestSuccess() { Ecdar.setSimulationHandler(simulationHandler); - var result = BackendHelper.getLocationReachableQuery(location, component, "query"); + var result = SimLocationController.getSimLocationReachableQuery(location, component, "query"); int indexOfOpeningBracket = result.indexOf('['); int indexOfComma = 0; @@ -88,7 +88,7 @@ void reachabilityQueryLocationPosition2TestSuccess() { var component = new Component(); component.setName("C2"); - SimulationHandler simulationHandler = new SimulationHandler(new BackendDriver()); + SimulationHandler simulationHandler = new SimulationHandler(); List<String> components = new ArrayList<>(); components.add("C1"); @@ -99,7 +99,7 @@ void reachabilityQueryLocationPosition2TestSuccess() { Ecdar.setSimulationHandler(simulationHandler); - var result = BackendHelper.getLocationReachableQuery(location, component, "query"); + var result = SimLocationController.getSimLocationReachableQuery(location, component, "query"); int indexOfFirstComma = 0; int indexofSecondComma = 0; @@ -128,7 +128,7 @@ void reachabilityQueryLocationPosition3TestSuccess() { var component = new Component(); component.setName("C3"); - SimulationHandler simulationHandler = new SimulationHandler(new BackendDriver()); + SimulationHandler simulationHandler = new SimulationHandler(); List<String> components = new ArrayList<>(); components.add("C1"); @@ -139,7 +139,7 @@ void reachabilityQueryLocationPosition3TestSuccess() { Ecdar.setSimulationHandler(simulationHandler); - var query = BackendHelper.getLocationReachableQuery(location, component, "query"); + var query = SimLocationController.getSimLocationReachableQuery(location, component, "query"); int indexOfLastComma = 0; @@ -164,7 +164,7 @@ void reachabilityQueryNumberOfLocationsTestSuccess() { var component = new Component(); component.setName("C1"); - SimulationHandler simulationHandler = new SimulationHandler(new BackendDriver()); + SimulationHandler simulationHandler = new SimulationHandler(); List<String> components = new ArrayList<>(); components.add("C1"); @@ -176,7 +176,7 @@ void reachabilityQueryNumberOfLocationsTestSuccess() { Ecdar.setSimulationHandler(simulationHandler); - var query = BackendHelper.getLocationReachableQuery(location, component, "query"); + var query = SimLocationController.getSimLocationReachableQuery(location, component, "query"); int commaCount = 0; for (int i = 0; i < query.length(); i++) { if (query.charAt(i) == ',') { @@ -186,6 +186,6 @@ void reachabilityQueryNumberOfLocationsTestSuccess() { int expected = commaCount + 1; - assertEquals(expected, Ecdar.getSimulationHandler().getComponentsInSimulation().size()); + assertEquals(expected, SimulatorController.getSimulationHandler().getComponentsInSimulation().size()); } } diff --git a/src/test/java/ecdar/ui/SidePaneTest.java b/src/test/java/ecdar/ui/SidePaneTest.java index b38a65de..fd05dc52 100644 --- a/src/test/java/ecdar/ui/SidePaneTest.java +++ b/src/test/java/ecdar/ui/SidePaneTest.java @@ -28,7 +28,7 @@ public void activeFilePresentationHasDifferentColorInProjectPane() { ); FilePresentation comp1 = from(lookup("#leftPane").queryAll()).lookup((Predicate<Node>) child -> child instanceof FilePresentation && - ((FilePresentation) child).getModel().getName().equals("Component1")).query(); + ((FilePresentation) child).getController().getModel().getName().equals("Component1")).query(); Assertions.assertEquals(comp1.getBackground().getFills().get(0), baseBackgroundFill); var activeColorIntensity = Color.Intensity.I50.next(1); @@ -39,12 +39,13 @@ public void activeFilePresentationHasDifferentColorInProjectPane() { ); FilePresentation comp2 = from(lookup("#leftPane").queryAll()).lookup((Predicate<Node>) child -> child instanceof FilePresentation && - ((FilePresentation) child).getModel().getName().equals("Component2")).query(); + ((FilePresentation) child).getController().getModel().getName().equals("Component2")).query(); Assertions.assertEquals(comp2.getBackground().getFills().get(0), activeBackgroundFill); } @Test - public void whenDeclarationIsPressedFilePresentationsAreNotActive() throws TimeoutException { + public void whenDeclarationIsPressedFilePresentationsAreNotActive() { + clickOn("#createComponent"); WaitForAsyncUtils.waitForFxEvents(); clickOn("Global Declarations"); @@ -58,7 +59,7 @@ public void whenDeclarationIsPressedFilePresentationsAreNotActive() throws Timeo ); Set<FilePresentation> filePresentations = from(lookup("#leftPane").queryAll()).lookup((Predicate<Node>) child -> child instanceof FilePresentation && - !((FilePresentation) child).getModel().getName().equals("Global Declarations")).queryAll(); + !((FilePresentation) child).getController().getModel().getName().equals("Global Declarations")).queryAll(); for (FilePresentation fp : filePresentations) { Assertions.assertEquals(fp.getBackground().getFills().get(0), baseBackgroundFill); } diff --git a/src/test/java/ecdar/utility/UnoccupiedSpaceFinderTest.java b/src/test/java/ecdar/utility/UnoccupiedSpaceFinderTest.java new file mode 100644 index 00000000..209c2cc1 --- /dev/null +++ b/src/test/java/ecdar/utility/UnoccupiedSpaceFinderTest.java @@ -0,0 +1,64 @@ +package ecdar.utility; + +import ecdar.Ecdar; +import ecdar.abstractions.Box; +import ecdar.presentations.LocationPresentation; +import ecdar.utility.helpers.UnoccupiedSpaceFinder; +import javafx.geometry.Point2D; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; + +public class UnoccupiedSpaceFinderTest { + @Test + public void testUnoccupiedPointIsGivenAndReturned() { + Box box = new Box(); + box.setWidth(300); + box.setHeight(300); + List<Point2D> locations = new ArrayList<>(); + locations.add(new Point2D(100, 40)); + locations.add(new Point2D(40, 100)); + locations.add(new Point2D(100, 100)); + locations.add(new Point2D(150, 150)); + Point2D preferredPlacement = new Point2D(200, 200); + + Point2D freePoint = UnoccupiedSpaceFinder.getUnoccupiedSpace(box, locations, preferredPlacement, LocationPresentation.RADIUS * 2 + Ecdar.CANVAS_PADDING); + + Assertions.assertEquals(preferredPlacement, freePoint); + } + + @Test + public void testPlacementOnOccupiedPointReturnsFreePoint() { + Box box = new Box(); + box.setWidth(300); + box.setHeight(300); + Point2D preferredPlacement = new Point2D(100, 100); + + Point2D freePoint = UnoccupiedSpaceFinder.getUnoccupiedSpace(box, new ArrayList<>(List.of(preferredPlacement)), preferredPlacement, LocationPresentation.RADIUS * 2 + Ecdar.CANVAS_PADDING); + + Assertions.assertNotNull(freePoint); + Assertions.assertNotEquals(preferredPlacement, freePoint); + } + + @Test + public void testPlacementInFullBoxReturnsNull() { + Box box = new Box(); + box.setWidth(100); + box.setHeight(100); + List<Point2D> locations = new ArrayList<>(); + + // Fill box + for (int i = 0; i < box.getWidth(); i += LocationPresentation.RADIUS * 2) { + for (int j = 0; j < box.getHeight(); j += LocationPresentation.RADIUS * 2) { + locations.add(new Point2D(i, j)); + } + } + Point2D preferredPlacement = new Point2D(50, 50); + + Point2D freePoint = UnoccupiedSpaceFinder.getUnoccupiedSpace(box, locations, preferredPlacement, LocationPresentation.RADIUS * 2 + Ecdar.CANVAS_PADDING); + + Assertions.assertNull(freePoint); + } +} From f44bb6f4c5a19fc3979077393cbf543402a59abd Mon Sep 17 00:00:00 2001 From: Niels Vistisen <nielswps@gmail.com> Date: Sat, 15 Apr 2023 12:22:07 +0200 Subject: [PATCH 141/158] WIP: QueryPane resizing refresh increased --- src/main/java/ecdar/presentations/EcdarPresentation.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/ecdar/presentations/EcdarPresentation.java b/src/main/java/ecdar/presentations/EcdarPresentation.java index f69c2213..c58399a4 100644 --- a/src/main/java/ecdar/presentations/EcdarPresentation.java +++ b/src/main/java/ecdar/presentations/EcdarPresentation.java @@ -125,11 +125,11 @@ private void initializeToggleLeftPaneFunctionality() { }); // Whenever the width of the file pane is updated, update the animations + // NOT USED, BUT ALLOWS FOR LEFT PANE RESIZING TO BE ADDED controller.leftPane.widthProperty().addListener((observable) -> { initializeOpenLeftPaneAnimation(); initializeCloseLeftPaneAnimation(); }); - } private void initializeCloseLeftPaneAnimation() { @@ -169,8 +169,7 @@ private void initializeToggleRightPaneFunctionality() { // Whenever the width of the query pane is updated, update the animations controller.getRightModePane().widthProperty().addListener((observable, oldWidth, newWidth) -> { - Platform.runLater(() -> rightPaneAnimationProperty.set(controller.getRightModePane().getWidth())); - + rightPaneAnimationProperty.set(controller.getRightModePane().getWidth()); initializeOpenRightPaneAnimation(); initializeCloseRightPaneAnimation(); }); From 92f6fba878cb14c07a2f6b4a2fbfc23230f3ce6c Mon Sep 17 00:00:00 2001 From: Niels Vistisen <nielswps@gmail.com> Date: Sun, 16 Apr 2023 11:17:01 +0200 Subject: [PATCH 142/158] Refactoring --- src/main/java/ecdar/Ecdar.java | 9 -- .../java/ecdar/backend/SimulationHandler.java | 2 +- .../controllers/LeftSimPaneController.java | 35 +++++-- ...ulationInitializationDialogController.java | 9 +- .../controllers/SimulatorController.java | 5 +- ...ntroller.java => TracePaneController.java} | 85 +++++++++++++++- .../controllers/TransitionController.java | 2 +- ...ler.java => TransitionPaneController.java} | 70 ++++++++++---- .../LeftSimPanePresentation.java | 25 ----- .../TracePaneElementPresentation.java | 96 ------------------- .../presentations/TracePanePresentation.java | 19 ++++ .../TransitionPaneElementPresentation.java | 69 ------------- .../TransitionPanePresentation.java | 19 ++++ .../presentations/TransitionPresentation.java | 2 +- .../LeftSimPanePresentation.fxml | 8 +- ...tation.fxml => TracePanePresentation.fxml} | 2 +- ...n.fxml => TransitionPanePresentation.fxml} | 2 +- src/test/java/ecdar/TestFXBase.java | 32 ------- .../ecdar/simulation/ReachabilityTest.java | 8 -- 19 files changed, 213 insertions(+), 286 deletions(-) rename src/main/java/ecdar/controllers/{TracePaneElementController.java => TracePaneController.java} (67%) rename src/main/java/ecdar/controllers/{TransitionPaneElementController.java => TransitionPaneController.java} (73%) delete mode 100755 src/main/java/ecdar/presentations/TracePaneElementPresentation.java create mode 100755 src/main/java/ecdar/presentations/TracePanePresentation.java delete mode 100755 src/main/java/ecdar/presentations/TransitionPaneElementPresentation.java create mode 100755 src/main/java/ecdar/presentations/TransitionPanePresentation.java rename src/main/resources/ecdar/presentations/{TracePaneElementPresentation.fxml => TracePanePresentation.fxml} (93%) rename src/main/resources/ecdar/presentations/{TransitionPaneElementPresentation.fxml => TransitionPanePresentation.fxml} (93%) delete mode 100644 src/test/java/ecdar/TestFXBase.java diff --git a/src/main/java/ecdar/Ecdar.java b/src/main/java/ecdar/Ecdar.java index 8efe1da7..362407c1 100644 --- a/src/main/java/ecdar/Ecdar.java +++ b/src/main/java/ecdar/Ecdar.java @@ -3,7 +3,6 @@ import ecdar.abstractions.*; import ecdar.backend.BackendException; import ecdar.backend.BackendHelper; -import ecdar.backend.SimulationHandler; import ecdar.code_analysis.CodeAnalysis; import ecdar.issues.ExitStatusCodes; import ecdar.presentations.*; @@ -50,7 +49,6 @@ public class Ecdar extends Application { private static BooleanProperty isUICached = new SimpleBooleanProperty(); public static BooleanProperty shouldRunBackgroundQueries = new SimpleBooleanProperty(true); private static final BooleanProperty isSplit = new SimpleBooleanProperty(false); - private static SimulationHandler simulationHandler; private Stage debugStage; /** @@ -178,13 +176,6 @@ public static void toggleCanvasSplit() { isSplit.set(!isSplit.get()); } - public static SimulationHandler getSimulationHandler() { return simulationHandler; } - - public static void setSimulationHandler(SimulationHandler simHandler) { - simulationHandler = simHandler; - } - - public static double getDpiScale() { if (!autoScalingEnabled.getValue()) return 1; diff --git a/src/main/java/ecdar/backend/SimulationHandler.java b/src/main/java/ecdar/backend/SimulationHandler.java index 1dab3e5d..bc36529d 100644 --- a/src/main/java/ecdar/backend/SimulationHandler.java +++ b/src/main/java/ecdar/backend/SimulationHandler.java @@ -47,7 +47,7 @@ public class SimulationHandler { private List<String> ComponentsInSimulation = new ArrayList<>(); - private EngineConnection con; + private EngineConnection con; // ToDo NIELS: Remove in favor of using open connections through engines /** * Empty constructor that should be used if the system or project has not be initialized yet diff --git a/src/main/java/ecdar/controllers/LeftSimPaneController.java b/src/main/java/ecdar/controllers/LeftSimPaneController.java index 7f68aaaa..a561887e 100755 --- a/src/main/java/ecdar/controllers/LeftSimPaneController.java +++ b/src/main/java/ecdar/controllers/LeftSimPaneController.java @@ -1,11 +1,13 @@ package ecdar.controllers; -import ecdar.presentations.TracePaneElementPresentation; -import ecdar.presentations.TransitionPaneElementPresentation; +import ecdar.presentations.TracePanePresentation; +import ecdar.presentations.TransitionPanePresentation; +import ecdar.utility.colors.Color; import javafx.fxml.Initializable; +import javafx.geometry.Insets; import javafx.scene.control.ScrollPane; -import javafx.scene.layout.StackPane; -import javafx.scene.layout.VBox; +import javafx.scene.layout.*; + import java.net.URL; import java.util.ResourceBundle; @@ -14,11 +16,32 @@ public class LeftSimPaneController implements Initializable { public ScrollPane scrollPane; public VBox scrollPaneVbox; - public TransitionPaneElementPresentation transitionPanePresentation; - public TracePaneElementPresentation tracePanePresentation; + public TransitionPanePresentation transitionPanePresentation; + public TracePanePresentation tracePanePresentation; @Override public void initialize(URL location, ResourceBundle resources) { + initializeBackground(); + initializeRightBorder(); + } + + private void initializeBackground() { + scrollPaneVbox.setBackground(new Background(new BackgroundFill( + Color.GREY.getColor(Color.Intensity.I200), + CornerRadii.EMPTY, + Insets.EMPTY + ))); + } + /** + * Initializes the thin border on the right side of the transition toolbar + */ + private void initializeRightBorder() { + transitionPanePresentation.getController().toolbar.setBorder(new Border(new BorderStroke( + Color.GREY_BLUE.getColor(Color.Intensity.I900), + BorderStrokeStyle.SOLID, + CornerRadii.EMPTY, + new BorderWidths(0, 1, 0, 0) + ))); } } diff --git a/src/main/java/ecdar/controllers/SimulationInitializationDialogController.java b/src/main/java/ecdar/controllers/SimulationInitializationDialogController.java index 4aa189f6..008d7001 100644 --- a/src/main/java/ecdar/controllers/SimulationInitializationDialogController.java +++ b/src/main/java/ecdar/controllers/SimulationInitializationDialogController.java @@ -2,7 +2,6 @@ import com.jfoenix.controls.JFXButton; import com.jfoenix.controls.JFXComboBox; -import ecdar.Ecdar; import ecdar.backend.SimulationHandler; import javafx.fxml.Initializable; @@ -18,6 +17,10 @@ public class SimulationInitializationDialogController implements Initializable { private SimulationHandler simulationHandler; + public void initialize(URL location, ResourceBundle resources) { + simulationHandler = SimulatorController.getSimulationHandler(); + } + /** * Function extracts data from simulation initialization (query and list of components to simulation) * and saves it @@ -40,8 +43,4 @@ public void setSimulationData(){ } simulationHandler.setComponentsInSimulation(listOfComponentsToSimulate); } - - public void initialize(URL location, ResourceBundle resources) { - simulationHandler = SimulatorController.getSimulationHandler(); - } } diff --git a/src/main/java/ecdar/controllers/SimulatorController.java b/src/main/java/ecdar/controllers/SimulatorController.java index 231ab789..94205cde 100644 --- a/src/main/java/ecdar/controllers/SimulatorController.java +++ b/src/main/java/ecdar/controllers/SimulatorController.java @@ -21,16 +21,17 @@ public class SimulatorController implements ModeController, Initializable { public StackPane root; - public static SimulationHandler simulationHandler = new SimulationHandler(); public SimulatorOverviewPresentation overviewPresentation; public StackPane toolbar; public final LeftSimPanePresentation leftSimPane = new LeftSimPanePresentation(); public final RightSimPanePresentation rightSimPane = new RightSimPanePresentation(); + + public static SimulationHandler simulationHandler = new SimulationHandler(); private boolean firstTimeInSimulator; private final static DoubleProperty width = new SimpleDoubleProperty(), height = new SimpleDoubleProperty(); - private static ObjectProperty<SimulationState> selectedState = new SimpleObjectProperty<>(); + private static final ObjectProperty<SimulationState> selectedState = new SimpleObjectProperty<>(); @Override public void initialize(URL location, ResourceBundle resources) { diff --git a/src/main/java/ecdar/controllers/TracePaneElementController.java b/src/main/java/ecdar/controllers/TracePaneController.java similarity index 67% rename from src/main/java/ecdar/controllers/TracePaneElementController.java rename to src/main/java/ecdar/controllers/TracePaneController.java index 505a58b7..10bce97c 100755 --- a/src/main/java/ecdar/controllers/TracePaneElementController.java +++ b/src/main/java/ecdar/controllers/TracePaneController.java @@ -6,12 +6,15 @@ import ecdar.backend.SimulationHandler; import ecdar.simulation.SimulationState; import ecdar.presentations.TransitionPresentation; +import ecdar.utility.colors.Color; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleIntegerProperty; import javafx.collections.ListChangeListener; import javafx.event.EventHandler; import javafx.fxml.FXML; import javafx.fxml.Initializable; +import javafx.geometry.Insets; +import javafx.scene.Cursor; import javafx.scene.control.Label; import javafx.scene.layout.*; import org.kordamp.ikonli.javafx.FontIcon; @@ -20,11 +23,12 @@ import java.util.LinkedHashMap; import java.util.Map; import java.util.ResourceBundle; +import java.util.function.BiConsumer; /** * The controller class for the trace pane element that can be inserted into a simulator pane */ -public class TracePaneElementController implements Initializable { +public class TracePaneController implements Initializable { public VBox root; public HBox toolbar; public Label traceTitle; @@ -35,15 +39,20 @@ public class TracePaneElementController implements Initializable { public Label summaryTitleLabel; public Label summarySubtitleLabel; - private SimpleBooleanProperty isTraceExpanded = new SimpleBooleanProperty(false); - private Map<SimulationState, TransitionPresentation> transitionPresentationMap = new LinkedHashMap<>(); - private SimpleIntegerProperty numberOfSteps = new SimpleIntegerProperty(0); + private final SimpleBooleanProperty isTraceExpanded = new SimpleBooleanProperty(false); + private final SimpleIntegerProperty numberOfSteps = new SimpleIntegerProperty(0); + private final Map<SimulationState, TransitionPresentation> transitionPresentationMap = new LinkedHashMap<>(); + private SimulationHandler simulationHandler; @Override public void initialize(URL location, ResourceBundle resources) { simulationHandler = SimulatorController.getSimulationHandler(); + initializeToolbar(); + initializeSummaryView(); + initializeTraceExpand(); + simulationHandler.getTraceLog().addListener((ListChangeListener<SimulationState>) c -> { while (c.next()) { for (final SimulationState state : c.getAddedSubList()) { @@ -58,8 +67,74 @@ public void initialize(URL location, ResourceBundle resources) { numberOfSteps.set(transitionPresentationMap.size()); }); + } - initializeTraceExpand(); + /** + * Initializes the toolbar that contains the trace pane element's title and buttons + * Sets the color of the bar and title label. Also sets the look of the rippler effect + */ + private void initializeToolbar() { + final Color color = Color.GREY_BLUE; + final Color.Intensity colorIntensity = Color.Intensity.I800; + + toolbar.setBackground(new Background(new BackgroundFill( + color.getColor(colorIntensity), + CornerRadii.EMPTY, + Insets.EMPTY))); + traceTitle.setTextFill(color.getTextColor(colorIntensity)); + + expandTrace.setMaskType(JFXRippler.RipplerMask.CIRCLE); + expandTrace.setRipplerFill(color.getTextColor(colorIntensity)); + } + + /** + * Initializes the summary view to be updated when steps are taken in the trace. + * Also changes the color and cursor when mouse enters and exits the summary view. + */ + private void initializeSummaryView() { + getNumberOfStepsProperty().addListener( + (observable, oldValue, newValue) -> updateSummaryTitle(newValue.intValue())); + + final Color color = Color.GREY_BLUE; + final Color.Intensity colorIntensity = Color.Intensity.I50; + + final BiConsumer<Color, Color.Intensity> setBackground = (newColor, newIntensity) -> { + traceSummary.setBackground(new Background(new BackgroundFill( + newColor.getColor(newIntensity), + CornerRadii.EMPTY, + Insets.EMPTY + ))); + + traceSummary.setBorder(new Border(new BorderStroke( + newColor.getColor(newIntensity.next(2)), + BorderStrokeStyle.SOLID, + CornerRadii.EMPTY, + new BorderWidths(0, 0, 1, 0) + ))); + }; + + // Update the background when hovered + traceSummary.setOnMouseEntered(event -> { + setBackground.accept(color, colorIntensity.next()); + root.setCursor(Cursor.HAND); + }); + + // Update the background when the mouse exits + traceSummary.setOnMouseExited(event -> { + setBackground.accept(color, colorIntensity); + root.setCursor(Cursor.DEFAULT); + }); + + // Update the background initially + setBackground.accept(color, colorIntensity); + } + + /** + * Updates the text of the summary title label with the current number of steps in the trace + * @param steps The number of steps in the trace + */ + private void updateSummaryTitle(int steps) { + summaryTitleLabel.setText(steps + " number of steps in trace"); } /** diff --git a/src/main/java/ecdar/controllers/TransitionController.java b/src/main/java/ecdar/controllers/TransitionController.java index 7bacda2e..48f12a1f 100755 --- a/src/main/java/ecdar/controllers/TransitionController.java +++ b/src/main/java/ecdar/controllers/TransitionController.java @@ -12,7 +12,7 @@ /** * The controller class for the transition view element. - * It represents a single transition and may be used by classes like {@see TransitionPaneElementController} + * It represents a single transition and may be used by classes like {@see TransitionPaneController} * to show a list of transitions */ public class TransitionController implements Initializable { diff --git a/src/main/java/ecdar/controllers/TransitionPaneElementController.java b/src/main/java/ecdar/controllers/TransitionPaneController.java similarity index 73% rename from src/main/java/ecdar/controllers/TransitionPaneElementController.java rename to src/main/java/ecdar/controllers/TransitionPaneController.java index a0872b92..b2855a79 100755 --- a/src/main/java/ecdar/controllers/TransitionPaneElementController.java +++ b/src/main/java/ecdar/controllers/TransitionPaneController.java @@ -2,20 +2,21 @@ import com.jfoenix.controls.JFXRippler; import com.jfoenix.controls.JFXTextField; -import ecdar.Ecdar; import ecdar.abstractions.Edge; import ecdar.backend.SimulationHandler; import ecdar.simulation.Transition; import ecdar.presentations.TransitionPresentation; +import ecdar.utility.colors.EnabledColor; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.event.EventHandler; import javafx.fxml.FXML; import javafx.fxml.Initializable; +import javafx.geometry.Insets; import javafx.scene.control.Label; import javafx.scene.control.Tooltip; -import javafx.scene.layout.HBox; -import javafx.scene.layout.VBox; +import javafx.scene.input.MouseEvent; +import javafx.scene.layout.*; import org.kordamp.ikonli.javafx.FontIcon; import java.math.BigDecimal; @@ -27,7 +28,7 @@ /** * The controller class for the transition pane element that can be inserted into the simulator panes */ -public class TransitionPaneElementController implements Initializable { +public class TransitionPaneController implements Initializable { public VBox root; public VBox transitionList; public HBox toolbar; @@ -38,18 +39,40 @@ public class TransitionPaneElementController implements Initializable { public VBox delayChooser; public JFXTextField delayTextField; - private SimpleBooleanProperty isTransitionExpanded = new SimpleBooleanProperty(false); - private Map<Transition, TransitionPresentation> transitionPresentationMap = new HashMap<>(); - private SimpleObjectProperty<BigDecimal> delay = new SimpleObjectProperty<>(BigDecimal.ZERO); + private final SimpleBooleanProperty isTransitionExpanded = new SimpleBooleanProperty(false); + private final Map<Transition, TransitionPresentation> transitionPresentationMap = new HashMap<>(); + private final SimpleObjectProperty<BigDecimal> delay = new SimpleObjectProperty<>(BigDecimal.ZERO); private SimulationHandler simulationHandler; @Override public void initialize(URL location, ResourceBundle resources) { simulationHandler = SimulatorController.getSimulationHandler(); + initializeToolbar(); initializeTransitionExpand(); initializeDelayChooser(); } + /** + * Initializes the toolbar for the transition pane element. + * Sets the background of the toolbar and changes the title color. + * Also changes the look of the rippler effect. + */ + private void initializeToolbar() { + // Set the background of the toolbar + toolbar.setBackground(new Background(new BackgroundFill( + EnabledColor.getDefault().getStrokeColor(), + CornerRadii.EMPTY, + Insets.EMPTY))); + // Set the font color of elements in the toolbar + toolbarTitle.setTextFill(EnabledColor.getDefault().getTextColor()); + + refreshRippler.setMaskType(JFXRippler.RipplerMask.CIRCLE); + refreshRippler.setRipplerFill(EnabledColor.getDefault().getTextColor()); + + expandTransition.setMaskType(JFXRippler.RipplerMask.CIRCLE); + expandTransition.setRipplerFill(EnabledColor.getDefault().getTextColor()); + } + /** * Sets up listeners for the delay chooser. * Listens for changes in text property and updates the textfield with a sanitized value (e.g. no letters in delay). @@ -57,6 +80,19 @@ public void initialize(URL location, ResourceBundle resources) { * Adds tooltip for the textfield. */ private void initializeDelayChooser() { + delayChooser.setBackground(new Background(new BackgroundFill( + EnabledColor.getDefault().getLowestIntensity().getPaintColor(), + CornerRadii.EMPTY, + Insets.EMPTY + ))); + + delayChooser.setBorder(new Border(new BorderStroke( + EnabledColor.getDefault().getLowestIntensity().nextIntensity(2).getPaintColor(), + BorderStrokeStyle.SOLID, + CornerRadii.EMPTY, + new BorderWidths(0, 0, 1, 0) + ))); + delayTextField.textProperty().addListener(((observable, oldValue, newValue) -> { delayTextChanged(oldValue, newValue); })); @@ -74,7 +110,7 @@ private void initializeDelayChooser() { /** * Initializes the expand functionality that allows the user to show or hide the transitions. - * By default the transitions are shown. + * By default, the transitions are shown. */ private void initializeTransitionExpand() { isTransitionExpanded.addListener((obs, oldVal, newVal) -> { @@ -132,10 +168,8 @@ private void insertTransition(Transition transition) { // mouseEntered.handle(event); // }); - EventHandler mouseExited = transitionPresentation.getOnMouseExited(); - transitionPresentation.setOnMouseExited(event -> { - mouseExited.handle(event); - }); + EventHandler<? super MouseEvent> mouseExited = transitionPresentation.getOnMouseExited(); + transitionPresentation.setOnMouseExited(mouseExited); transitionPresentationMap.put(transition, transitionPresentation); @@ -152,13 +186,13 @@ private void insertTransition(Transition transition) { * @return A string representing the transition */ private String transitionString(Transition transition) { - String title = transition.getLabel(); + StringBuilder title = new StringBuilder(transition.getLabel()); if(transition.getEdges() != null) { for (Edge edge : transition.getEdges()) { - title += " " + edge.getId(); + title.append(" ").append(edge.getId()); } } - return title; + return title.toString(); } /** @@ -166,11 +200,7 @@ private String transitionString(Transition transition) { */ @FXML private void expandTransitions() { - if(isTransitionExpanded.get()) { - isTransitionExpanded.set(false); - } else { - isTransitionExpanded.set(true); - } + isTransitionExpanded.set(!isTransitionExpanded.get()); } /** diff --git a/src/main/java/ecdar/presentations/LeftSimPanePresentation.java b/src/main/java/ecdar/presentations/LeftSimPanePresentation.java index adc693ed..de6e195e 100755 --- a/src/main/java/ecdar/presentations/LeftSimPanePresentation.java +++ b/src/main/java/ecdar/presentations/LeftSimPanePresentation.java @@ -1,8 +1,6 @@ package ecdar.presentations; import ecdar.controllers.LeftSimPaneController; -import ecdar.utility.colors.Color; -import javafx.geometry.Insets; import javafx.scene.layout.*; public class LeftSimPanePresentation extends StackPane { @@ -10,28 +8,5 @@ public class LeftSimPanePresentation extends StackPane { public LeftSimPanePresentation() { controller = new EcdarFXMLLoader().loadAndGetController("LeftSimPanePresentation.fxml", this); - - initializeBackground(); - initializeRightBorder(); - } - - private void initializeBackground() { - controller.scrollPaneVbox.setBackground(new Background(new BackgroundFill( - Color.GREY.getColor(Color.Intensity.I200), - CornerRadii.EMPTY, - Insets.EMPTY - ))); - } - - /** - * Initializes the thin border on the right side of the transition toolbar - */ - private void initializeRightBorder() { - controller.transitionPanePresentation.getController().toolbar.setBorder(new Border(new BorderStroke( - Color.GREY_BLUE.getColor(Color.Intensity.I900), - BorderStrokeStyle.SOLID, - CornerRadii.EMPTY, - new BorderWidths(0, 1, 0, 0) - ))); } } diff --git a/src/main/java/ecdar/presentations/TracePaneElementPresentation.java b/src/main/java/ecdar/presentations/TracePaneElementPresentation.java deleted file mode 100755 index 0e8be627..00000000 --- a/src/main/java/ecdar/presentations/TracePaneElementPresentation.java +++ /dev/null @@ -1,96 +0,0 @@ -package ecdar.presentations; - -import com.jfoenix.controls.JFXRippler; -import ecdar.controllers.TracePaneElementController; -import ecdar.utility.colors.Color; -import javafx.geometry.Insets; -import javafx.scene.Cursor; -import javafx.scene.layout.*; - -import java.util.function.BiConsumer; - -/** - * The presentation class for the trace element that can be inserted into the simulator panes - */ -public class TracePaneElementPresentation extends VBox { - final private TracePaneElementController controller; - - public TracePaneElementPresentation() { - controller = new EcdarFXMLLoader().loadAndGetController("TracePaneElementPresentation.fxml", this); - - initializeToolbar(); - initializeSummaryView(); - } - - /** - * Initializes the tool bar that contains the trace pane element's title and buttons - * Sets the color of the bar and title label. Also sets the look of the rippler effect - */ - private void initializeToolbar() { - final Color color = Color.GREY_BLUE; - final Color.Intensity colorIntensity = Color.Intensity.I800; - - controller.toolbar.setBackground(new Background(new BackgroundFill( - color.getColor(colorIntensity), - CornerRadii.EMPTY, - Insets.EMPTY))); - controller.traceTitle.setTextFill(color.getTextColor(colorIntensity)); - - controller.expandTrace.setMaskType(JFXRippler.RipplerMask.CIRCLE); - controller.expandTrace.setRipplerFill(color.getTextColor(colorIntensity)); - } - - /** - * Initializes the summary view so it is update when steps are taken in the trace. - * Also changes the color and cursor when mouse enters and exits the summary view. - */ - private void initializeSummaryView() { - controller.getNumberOfStepsProperty().addListener( - (observable, oldValue, newValue) -> updateSummaryTitle(newValue.intValue())); - - final Color color = Color.GREY_BLUE; - final Color.Intensity colorIntensity = Color.Intensity.I50; - - final BiConsumer<Color, Color.Intensity> setBackground = (newColor, newIntensity) -> { - controller.traceSummary.setBackground(new Background(new BackgroundFill( - newColor.getColor(newIntensity), - CornerRadii.EMPTY, - Insets.EMPTY - ))); - - controller.traceSummary.setBorder(new Border(new BorderStroke( - newColor.getColor(newIntensity.next(2)), - BorderStrokeStyle.SOLID, - CornerRadii.EMPTY, - new BorderWidths(0, 0, 1, 0) - ))); - }; - - // Update the background when hovered - controller.traceSummary.setOnMouseEntered(event -> { - setBackground.accept(color, colorIntensity.next()); - setCursor(Cursor.HAND); - }); - - // Update the background when the mouse exits - controller.traceSummary.setOnMouseExited(event -> { - setBackground.accept(color, colorIntensity); - setCursor(Cursor.DEFAULT); - }); - - // Update the background initially - setBackground.accept(color, colorIntensity); - } - - /** - * Updates the text of the summary title label with the current number of steps in the trace - * @param steps The number of steps in the trace - */ - private void updateSummaryTitle(int steps) { - controller.summaryTitleLabel.setText(steps + " number of steps in trace"); - } - - public TracePaneElementController getController() { - return controller; - } -} diff --git a/src/main/java/ecdar/presentations/TracePanePresentation.java b/src/main/java/ecdar/presentations/TracePanePresentation.java new file mode 100755 index 00000000..605263c4 --- /dev/null +++ b/src/main/java/ecdar/presentations/TracePanePresentation.java @@ -0,0 +1,19 @@ +package ecdar.presentations; + +import ecdar.controllers.TracePaneController; +import javafx.scene.layout.*; + +/** + * The presentation class for the trace element that can be inserted into the simulator panes + */ +public class TracePanePresentation extends VBox { + final private TracePaneController controller; + + public TracePanePresentation() { + controller = new EcdarFXMLLoader().loadAndGetController("TracePanePresentation.fxml", this); + } + + public TracePaneController getController() { + return controller; + } +} diff --git a/src/main/java/ecdar/presentations/TransitionPaneElementPresentation.java b/src/main/java/ecdar/presentations/TransitionPaneElementPresentation.java deleted file mode 100755 index 8453af64..00000000 --- a/src/main/java/ecdar/presentations/TransitionPaneElementPresentation.java +++ /dev/null @@ -1,69 +0,0 @@ -package ecdar.presentations; - -import com.jfoenix.controls.JFXRippler; -import ecdar.controllers.TransitionPaneElementController; -import ecdar.utility.colors.Color; -import javafx.geometry.Insets; -import javafx.scene.layout.*; - -/** - * The presentation class for the transition pane element that can be inserted into the simulator panes - */ -public class TransitionPaneElementPresentation extends VBox { - final private TransitionPaneElementController controller; - - public TransitionPaneElementPresentation() { - controller = new EcdarFXMLLoader().loadAndGetController("TransitionPaneElementPresentation.fxml", this); - - initializeToolbar(); - initializeDelayChooser(); - } - - /** - * Initializes the toolbar for the transition pane element. - * Sets the background of the toolbar and changes the title color. - * Also changes the look of the rippler effect. - */ - private void initializeToolbar() { - final Color color = Color.GREY_BLUE; - final Color.Intensity colorIntensity = Color.Intensity.I800; - - // Set the background of the toolbar - controller.toolbar.setBackground(new Background(new BackgroundFill( - color.getColor(colorIntensity), - CornerRadii.EMPTY, - Insets.EMPTY))); - // Set the font color of elements in the toolbar - controller.toolbarTitle.setTextFill(color.getTextColor(colorIntensity)); - - controller.refreshRippler.setMaskType(JFXRippler.RipplerMask.CIRCLE); - controller.refreshRippler.setRipplerFill(color.getTextColor(colorIntensity)); - - controller.expandTransition.setMaskType(JFXRippler.RipplerMask.CIRCLE); - controller.expandTransition.setRipplerFill(color.getTextColor(colorIntensity)); - } - - /** - * Sets the background color of the delay chooser - */ - private void initializeDelayChooser() { - final Color color = Color.GREY_BLUE; - final Color.Intensity colorIntensity = Color.Intensity.I50; - controller.delayChooser.setBackground(new Background(new BackgroundFill( - color.getColor(colorIntensity), - CornerRadii.EMPTY, - Insets.EMPTY - ))); - - controller.delayChooser.setBorder(new Border(new BorderStroke( - color.getColor(colorIntensity.next(2)), - BorderStrokeStyle.SOLID, - CornerRadii.EMPTY, - new BorderWidths(0, 0, 1, 0) - ))); - } - - public TransitionPaneElementController getController() { - return controller; - } -} diff --git a/src/main/java/ecdar/presentations/TransitionPanePresentation.java b/src/main/java/ecdar/presentations/TransitionPanePresentation.java new file mode 100755 index 00000000..989ebbf7 --- /dev/null +++ b/src/main/java/ecdar/presentations/TransitionPanePresentation.java @@ -0,0 +1,19 @@ +package ecdar.presentations; + +import ecdar.controllers.TransitionPaneController; +import javafx.scene.layout.*; + +/** + * The presentation class for the transition pane element that can be inserted into the simulator panes + */ +public class TransitionPanePresentation extends VBox { + final private TransitionPaneController controller; + + public TransitionPanePresentation() { + controller = new EcdarFXMLLoader().loadAndGetController("TransitionPanePresentation.fxml", this); + } + + public TransitionPaneController getController() { + return controller; + } +} diff --git a/src/main/java/ecdar/presentations/TransitionPresentation.java b/src/main/java/ecdar/presentations/TransitionPresentation.java index 15fb47e6..547307ad 100755 --- a/src/main/java/ecdar/presentations/TransitionPresentation.java +++ b/src/main/java/ecdar/presentations/TransitionPresentation.java @@ -14,7 +14,7 @@ /** * The presentation class for a transition view element. - * It represents a single transition and may be used by classes like {@see TransitionPaneElementController} + * It represents a single transition and may be used by classes like {@see TransitionPaneController} * to show a list of transitions */ public class TransitionPresentation extends AnchorPane { diff --git a/src/main/resources/ecdar/presentations/LeftSimPanePresentation.fxml b/src/main/resources/ecdar/presentations/LeftSimPanePresentation.fxml index e80df29e..88a15f23 100755 --- a/src/main/resources/ecdar/presentations/LeftSimPanePresentation.fxml +++ b/src/main/resources/ecdar/presentations/LeftSimPanePresentation.fxml @@ -2,8 +2,8 @@ <?import javafx.scene.control.*?> <?import javafx.scene.layout.*?> -<?import ecdar.presentations.TracePaneElementPresentation?> -<?import ecdar.presentations.TransitionPaneElementPresentation?> +<?import ecdar.presentations.TracePanePresentation?> +<?import ecdar.presentations.TransitionPanePresentation?> <fx:root xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8.0.76-ea" @@ -19,8 +19,8 @@ AnchorPane.rightAnchor="0" styleClass="edge-to-edge"> <VBox fx:id="scrollPaneVbox"> - <TransitionPaneElementPresentation fx:id="transitionPanePresentation"/> - <TracePaneElementPresentation fx:id="tracePanePresentation"/> + <TransitionPanePresentation fx:id="transitionPanePresentation"/> + <TracePanePresentation fx:id="tracePanePresentation"/> </VBox> </ScrollPane> </AnchorPane> diff --git a/src/main/resources/ecdar/presentations/TracePaneElementPresentation.fxml b/src/main/resources/ecdar/presentations/TracePanePresentation.fxml similarity index 93% rename from src/main/resources/ecdar/presentations/TracePaneElementPresentation.fxml rename to src/main/resources/ecdar/presentations/TracePanePresentation.fxml index 0a233345..1f80fecf 100755 --- a/src/main/resources/ecdar/presentations/TracePaneElementPresentation.fxml +++ b/src/main/resources/ecdar/presentations/TracePanePresentation.fxml @@ -9,7 +9,7 @@ <fx:root xmlns="http://javafx.com/javafx" xmlns:fx="http://javafx.com/fxml" type="VBox" fx:id="root" AnchorPane.leftAnchor="0" AnchorPane.rightAnchor="0" - fx:controller="ecdar.controllers.TracePaneElementController"> + fx:controller="ecdar.controllers.TracePaneController"> <HBox fx:id="toolbar" alignment="CENTER"> <padding> <Insets topRightBottomLeft="10"/> diff --git a/src/main/resources/ecdar/presentations/TransitionPaneElementPresentation.fxml b/src/main/resources/ecdar/presentations/TransitionPanePresentation.fxml similarity index 93% rename from src/main/resources/ecdar/presentations/TransitionPaneElementPresentation.fxml rename to src/main/resources/ecdar/presentations/TransitionPanePresentation.fxml index 8047bcb3..7852d35e 100755 --- a/src/main/resources/ecdar/presentations/TransitionPaneElementPresentation.fxml +++ b/src/main/resources/ecdar/presentations/TransitionPanePresentation.fxml @@ -9,7 +9,7 @@ <?import javafx.geometry.Insets?> <fx:root xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8.0.76-ea" - fx:controller="ecdar.controllers.TransitionPaneElementController" + fx:controller="ecdar.controllers.TransitionPaneController" fx:id="root" type="VBox" AnchorPane.leftAnchor="0" AnchorPane.rightAnchor="0"> <HBox fx:id="toolbar" alignment="CENTER"> diff --git a/src/test/java/ecdar/TestFXBase.java b/src/test/java/ecdar/TestFXBase.java deleted file mode 100644 index 843527ee..00000000 --- a/src/test/java/ecdar/TestFXBase.java +++ /dev/null @@ -1,32 +0,0 @@ -package ecdar; - -import javafx.scene.input.KeyCode; -import javafx.scene.input.MouseButton; -import javafx.stage.Stage; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.testfx.api.FxRobot; -import org.testfx.api.FxToolkit; -import org.testfx.framework.junit.ApplicationTest; - -import java.util.concurrent.TimeoutException; - -public class TestFXBase extends ApplicationTest { - @BeforeAll - static void setUp() throws Exception { - ApplicationTest.launch(Ecdar.class); - } - - @Override - public void start(Stage stage) { - stage.show(); - } - - @AfterAll - public static void afterEachTest() throws TimeoutException { - FxToolkit.hideStage(); - FxRobot robot = new FxRobot(); - robot.release(new KeyCode[]{}); - robot.release(new MouseButton[]{}); - } -} diff --git a/src/test/java/ecdar/simulation/ReachabilityTest.java b/src/test/java/ecdar/simulation/ReachabilityTest.java index ab1aac1a..62243a35 100644 --- a/src/test/java/ecdar/simulation/ReachabilityTest.java +++ b/src/test/java/ecdar/simulation/ReachabilityTest.java @@ -63,8 +63,6 @@ void reachabilityQueryLocationPosition1TestSuccess() { simulationHandler.setComponentsInSimulation(components); - Ecdar.setSimulationHandler(simulationHandler); - var result = SimLocationController.getSimLocationReachableQuery(location, component, "query"); int indexOfOpeningBracket = result.indexOf('['); @@ -97,8 +95,6 @@ void reachabilityQueryLocationPosition2TestSuccess() { simulationHandler.setComponentsInSimulation(components); - Ecdar.setSimulationHandler(simulationHandler); - var result = SimLocationController.getSimLocationReachableQuery(location, component, "query"); int indexOfFirstComma = 0; @@ -137,8 +133,6 @@ void reachabilityQueryLocationPosition3TestSuccess() { simulationHandler.setComponentsInSimulation(components); - Ecdar.setSimulationHandler(simulationHandler); - var query = SimLocationController.getSimLocationReachableQuery(location, component, "query"); int indexOfLastComma = 0; @@ -174,8 +168,6 @@ void reachabilityQueryNumberOfLocationsTestSuccess() { simulationHandler.setComponentsInSimulation(components); - Ecdar.setSimulationHandler(simulationHandler); - var query = SimLocationController.getSimLocationReachableQuery(location, component, "query"); int commaCount = 0; for (int i = 0; i < query.length(); i++) { From 93338f963dca7cd209bcbd178fc6293391e15a07 Mon Sep 17 00:00:00 2001 From: Niels Vistisen <nielswps@gmail.com> Date: Sun, 16 Apr 2023 12:03:34 +0200 Subject: [PATCH 143/158] WIP: Simulation RPC requests moved to Engine to utilize connection pool --- src/main/java/ecdar/backend/Engine.java | 91 ++++++++++- .../java/ecdar/backend/SimulationHandler.java | 149 ++++++------------ 2 files changed, 133 insertions(+), 107 deletions(-) diff --git a/src/main/java/ecdar/backend/Engine.java b/src/main/java/ecdar/backend/Engine.java index 936178e9..e5c11598 100644 --- a/src/main/java/ecdar/backend/Engine.java +++ b/src/main/java/ecdar/backend/Engine.java @@ -1,9 +1,13 @@ package ecdar.backend; +import EcdarProtoBuf.ComponentProtos; +import EcdarProtoBuf.ObjectProtos; import EcdarProtoBuf.QueryProtos; import com.google.gson.JsonObject; import ecdar.Ecdar; +import ecdar.abstractions.Component; import ecdar.abstractions.Query; +import ecdar.simulation.SimulationState; import ecdar.utility.serialize.Serializable; import io.grpc.stub.StreamObserver; import javafx.beans.property.SimpleBooleanProperty; @@ -137,9 +141,9 @@ public ArrayList<EngineConnection> getStartedConnections() { /** * Enqueue query for execution with consumers for success and error * - * @param query the query to enqueue for execution - * @param successConsumer consumer for returned QueryResponse - * @param errorConsumer consumer for any throwable that might result from the execution + * @param query to enqueue for execution + * @param successConsumer for returned QueryResponse + * @param errorConsumer for any throwable that might result from the execution */ public void enqueueQuery(Query query, Consumer<QueryProtos.QueryResponse> successConsumer, Consumer<Throwable> errorConsumer) { GrpcRequest request = new GrpcRequest(engineConnection -> { @@ -178,6 +182,86 @@ public void onCompleted() { requestQueue.add(request); } + /** + * Enqueue request of initial simulation step with consumers for success and error + * + * @param composition of the current simulated query + * @param stepConsumer for the resulting step + * @param errorConsumer for potential errors + */ + public void enqueueInitialSimulationStepRequest(String composition, Consumer<QueryProtos.SimulationStepResponse> stepConsumer, Consumer<Throwable> errorConsumer) { + GrpcRequest request = new GrpcRequest(engineConnection -> { + StreamObserver<QueryProtos.SimulationStepResponse> responseObserver = getSimulationResponseObserver(stepConsumer, errorConsumer); + + var comInfo = ComponentProtos.ComponentsInfo.newBuilder(); + for (Component c : Ecdar.getProject().getComponents()) { + comInfo.addComponents(ComponentProtos.Component.newBuilder().setJson(c.serialize().toString()).build()); + } + + comInfo.setComponentsHash(comInfo.getComponentsList().hashCode()); + var simStartRequest = QueryProtos.SimulationStartRequest.newBuilder(); + var simInfo = QueryProtos.SimulationInfo.newBuilder() + .setComponentComposition(composition) + .setComponentsInfo(comInfo); + simStartRequest.setSimulationInfo(simInfo); + engineConnection.getStub().withDeadlineAfter(responseDeadline, TimeUnit.MILLISECONDS) + .startSimulation(simStartRequest.build(), responseObserver); + }); + + requestQueue.add(request); + } + + /** + * Enqueue request of initial simulation step with consumers for success and error + * + * @param composition of the current simulated query + * @param stepConsumer for the resulting step + * @param errorConsumer for potential errors + */ + public void enqueueSimulationStepRequest(String composition, SimulationState state, String edgeId, String componentName, int componentId, Consumer<QueryProtos.SimulationStepResponse> stepConsumer, Consumer<Throwable> errorConsumer) { + GrpcRequest request = new GrpcRequest(engineConnection -> { + StreamObserver<QueryProtos.SimulationStepResponse> responseObserver = getSimulationResponseObserver(stepConsumer, errorConsumer); + + var comInfo = ComponentProtos.ComponentsInfo.newBuilder(); + for (Component c : Ecdar.getProject().getComponents()) { + comInfo.addComponents(ComponentProtos.Component.newBuilder().setJson(c.serialize().toString()).build()); + } + + comInfo.setComponentsHash(comInfo.getComponentsList().hashCode()); + var simStepRequest = QueryProtos.SimulationStepRequest.newBuilder(); + var simInfo = QueryProtos.SimulationInfo.newBuilder() + .setComponentComposition(composition) + .setComponentsInfo(comInfo); + simStepRequest.setSimulationInfo(simInfo); + var specComp = ObjectProtos.SpecificComponent.newBuilder().setComponentName(componentName).setComponentIndex(componentId); + var edge = EcdarProtoBuf.ObjectProtos.Edge.newBuilder().setId(edgeId).setSpecificComponent(specComp); + var decision = ObjectProtos.Decision.newBuilder().setEdge(edge).setSource(state.getState()); + simStepRequest.setChosenDecision(decision); + + engineConnection.getStub().withDeadlineAfter(responseDeadline, TimeUnit.MILLISECONDS) + .takeSimulationStep(simStepRequest.build(), responseObserver); + }); + + requestQueue.add(request); + } + + private static StreamObserver<QueryProtos.SimulationStepResponse> getSimulationResponseObserver(Consumer<QueryProtos.SimulationStepResponse> stepConsumer, Consumer<Throwable> errorConsumer) { + return new StreamObserver<>() { + @Override + public void onNext(QueryProtos.SimulationStepResponse value) { + stepConsumer.accept(value); + } + + @Override + public void onError(Throwable t) { + errorConsumer.accept(t); + } + + @Override + public void onCompleted() {} + }; + } + /** * Signal that the EngineConnection can be used not in use and available for queries * @@ -214,6 +298,7 @@ private EngineConnection getConnection() throws BackendException.NoAvailableEngi if (newConnection != null) { startedConnections.add(newConnection); + setConnectionAsAvailable(newConnection); } } diff --git a/src/main/java/ecdar/backend/SimulationHandler.java b/src/main/java/ecdar/backend/SimulationHandler.java index bc36529d..23172535 100644 --- a/src/main/java/ecdar/backend/SimulationHandler.java +++ b/src/main/java/ecdar/backend/SimulationHandler.java @@ -47,14 +47,10 @@ public class SimulationHandler { private List<String> ComponentsInSimulation = new ArrayList<>(); - private EngineConnection con; // ToDo NIELS: Remove in favor of using open connections through engines - /** * Empty constructor that should be used if the system or project has not be initialized yet */ - public SimulationHandler() { - - } + public SimulationHandler() {} public void clearComponentsInSimulation() { ComponentsInSimulation.clear(); @@ -74,11 +70,6 @@ private void initializeSimulation() { this.selectedEdge.set(null); this.traceLog.clear(); this.system = getSystem(); - - if (con == null) { - EngineConnectionStarter ecs = new EngineConnectionStarter(BackendHelper.getDefaultEngine()); - con = ecs.tryStartNewConnection(); - } } /** @@ -87,102 +78,48 @@ private void initializeSimulation() { public void initialStep() { initializeSimulation(); - GrpcRequest request = new GrpcRequest(engineConnection -> { - StreamObserver<SimulationStepResponse> responseObserver = new StreamObserver<>() { - @Override - public void onNext(QueryProtos.SimulationStepResponse value) { + BackendHelper.getDefaultEngine().enqueueInitialSimulationStepRequest( + composition, + (response) -> { // ToDo: This is temp solution to compile but should be fixed to handle ambiguity - currentState.set(new SimulationState(value.getNewDecisionPoints(0))); + currentState.set(new SimulationState(response.getNewDecisionPoints(0))); Platform.runLater(() -> traceLog.add(currentState.get())); - } - - @Override - public void onError(Throwable t) { - Ecdar.showToast("Could not start simulation:\n" + t.getMessage()); - } + }, + (error) -> Ecdar.showToast("Could not start simulation:\n" + error.getMessage())); - @Override - public void onCompleted() { - } - }; - - var comInfo = ComponentProtos.ComponentsInfo.newBuilder(); - for (Component c : Ecdar.getProject().getComponents()) { - comInfo.addComponents(ComponentProtos.Component.newBuilder().setJson(c.serialize().toString()).build()); - } - - comInfo.setComponentsHash(comInfo.getComponentsList().hashCode()); - var simStartRequest = QueryProtos.SimulationStartRequest.newBuilder(); - var simInfo = QueryProtos.SimulationInfo.newBuilder() - .setComponentComposition(composition) - .setComponentsInfo(comInfo); - simStartRequest.setSimulationInfo(simInfo); - engineConnection.getStub().withDeadlineAfter(20000, TimeUnit.MILLISECONDS) - .startSimulation(simStartRequest.build(), responseObserver); - }); - - request.execute(con); numberOfSteps++; } - - /** - * Resets the simulation to the initial location - */ - public void resetToInitialLocation() { - initialStep(); - } /** * Take a step in the simulation. */ public void nextStep() { // removes invalid states from the log when stepping forward after previewing a previous state - removeStatesFromLog(currentState.get()); - - GrpcRequest request = new GrpcRequest(engineConnection -> { - StreamObserver<SimulationStepResponse> responseObserver = new StreamObserver<>() { - @Override - public void onNext(QueryProtos.SimulationStepResponse value) { - // TODO this is temp solution to compile but should be fixed to handle ambiguity - currentState.set(new SimulationState(value.getNewDecisionPoints(0))); + removeStatesFromLog(currentState.get()); + + BackendHelper.getDefaultEngine().enqueueSimulationStepRequest( + composition, + currentState.get(), + selectedEdge.get().getId(), + getComponentName(selectedEdge.get()), + getComponentIndex(selectedEdge.get()), + (response) -> { + // ToDo: This is temp solution to compile but should be fixed to handle ambiguity + currentState.set(new SimulationState(response.getNewDecisionPoints(0))); Platform.runLater(() -> traceLog.add(currentState.get())); - } - - @Override - public void onError(Throwable t) { - Ecdar.showToast("Could not take next step in simulation\nError: " + t.getMessage()); - } - - @Override - public void onCompleted() { - } - }; - - var comInfo = ComponentProtos.ComponentsInfo.newBuilder(); - for (Component c : Ecdar.getProject().getComponents()) { - comInfo.addComponents(ComponentProtos.Component.newBuilder().setJson(c.serialize().toString()).build()); - } + }, + (error) -> Ecdar.showToast("Could not take next step in simulation\nError: " + error.getMessage())); - comInfo.setComponentsHash(comInfo.getComponentsList().hashCode()); - var simStepRequest = SimulationStepRequest.newBuilder(); - var simInfo = SimulationInfo.newBuilder() - .setComponentComposition(composition) - .setComponentsInfo(comInfo); - simStepRequest.setSimulationInfo(simInfo); - var source = currentState.get().getState(); - var specComp = ObjectProtos.SpecificComponent.newBuilder().setComponentName(getComponentName(selectedEdge.get())).setComponentIndex(getComponentIndex(selectedEdge.get())); - var edge = EcdarProtoBuf.ObjectProtos.Edge.newBuilder().setId(selectedEdge.get().getId()).setSpecificComponent(specComp); - var decision = Decision.newBuilder().setEdge(edge).setSource(source); - simStepRequest.setChosenDecision(decision); - - engineConnection.getStub().withDeadlineAfter(20000, TimeUnit.MILLISECONDS) - .takeSimulationStep(simStepRequest.build(), responseObserver); - }); - - request.execute(con); numberOfSteps++; } + /** + * Resets the simulation to the initial location + */ + public void resetToInitialLocation() { + initialStep(); + } + private String getComponentName(Edge edge) { var components = Ecdar.getProject().getComponents(); for (var component : components) { @@ -196,7 +133,7 @@ private String getComponentName(Edge edge) { throw new RuntimeException("Could not find component name for edge with id " + edge.getId()); } - private int getComponentIndex (Edge edge) { + private int getComponentIndex(Edge edge) { for (int i = 0; i < Ecdar.getProject().getComponents().size(); i++) { if (Ecdar.getProject().getComponents().get(i).getEdges().stream().anyMatch(p -> Objects.equals(p.getId(), edge.getId()))) { return i; @@ -226,8 +163,8 @@ public ObservableList<SimulationState> getTraceLog() { /** * All the available transitions in this state - * @return * + * @return * @return an {@link ObservableList} of all the currently available transitions in this state */ public ArrayList<Pair<String, String>> getAvailableTransitions() { @@ -277,9 +214,13 @@ public EcdarSystem getSystem() { return system; } - public String getComposition() { return composition;} + public String getComposition() { + return composition; + } - public void setComposition(String composition) {this.composition = composition;} + public void setComposition(String composition) { + this.composition = composition; + } public boolean isSimulationRunning() { return false; // ToDo: Implement @@ -306,39 +247,39 @@ public void setSimulationQuery(String query) { simulationQuery = query; } - public String getSimulationQuery(){ + public String getSimulationQuery() { return simulationQuery; } /** * Set list of components used in the simulation */ - public void setSimulationComponents(ArrayList<Component> components){ + public void setSimulationComponents(ArrayList<Component> components) { simulationComponents = components; } /** * Get list of components used in the simulation */ - public ArrayList<Component> getSimulationComponents(){ + public ArrayList<Component> getSimulationComponents() { return simulationComponents; } /** * Highlights the edges from the reachability response */ - public void highlightReachabilityEdges(ArrayList<String> ids){ + public void highlightReachabilityEdges(ArrayList<String> ids) { //unhighlight all edges - for(var comp : simulationComponents){ - for(var edge : comp.getEdges()){ + for (var comp : simulationComponents) { + for (var edge : comp.getEdges()) { edge.setIsHighlightedForReachability(false); } } //highlight the edges from the reachability response - for(var comp : simulationComponents){ - for(var edge : comp.getEdges()){ - for(var id : ids){ - if(edge.getId().equals(id)){ + for (var comp : simulationComponents) { + for (var edge : comp.getEdges()) { + for (var id : ids) { + if (edge.getId().equals(id)) { edge.setIsHighlightedForReachability(true); } } From 1667bdbb473a3996bf493873ec579c8e82a9b809 Mon Sep 17 00:00:00 2001 From: Niels Vistisen <nielswps@gmail.com> Date: Sun, 16 Apr 2023 12:16:36 +0200 Subject: [PATCH 144/158] ToDos cleaned up --- src/main/java/ecdar/abstractions/Query.java | 2 +- src/main/java/ecdar/backend/Engine.java | 1 - src/main/java/ecdar/controllers/EcdarController.java | 2 -- src/main/java/ecdar/controllers/ModelController.java | 5 ++--- src/main/java/ecdar/controllers/ProjectPaneController.java | 2 +- .../java/ecdar/mutation/TestCaseGenerationHandler.java | 1 - .../java/ecdar/presentations/LocationPresentation.java | 2 +- src/main/java/ecdar/presentations/SimTagPresentation.java | 7 ++----- src/main/java/ecdar/utility/helpers/MouseCircular.java | 2 -- .../ecdar/presentations/RightSimPanePresentation.fxml | 2 +- 10 files changed, 8 insertions(+), 18 deletions(-) diff --git a/src/main/java/ecdar/abstractions/Query.java b/src/main/java/ecdar/abstractions/Query.java index 175aa5d9..face9e71 100644 --- a/src/main/java/ecdar/abstractions/Query.java +++ b/src/main/java/ecdar/abstractions/Query.java @@ -359,7 +359,7 @@ private void addGeneratedComponent(Component newComponent) { Platform.runLater(() -> { newComponent.setTemporary(true); - ObservableList<Component> listOfGeneratedComponents = Ecdar.getProject().getTempComponents(); // ToDo NIELS: Refactor + ObservableList<Component> listOfGeneratedComponents = Ecdar.getProject().getTempComponents(); Component matchedComponent = null; for (Component currentGeneratedComponent : listOfGeneratedComponents) { diff --git a/src/main/java/ecdar/backend/Engine.java b/src/main/java/ecdar/backend/Engine.java index e5c11598..66756ff4 100644 --- a/src/main/java/ecdar/backend/Engine.java +++ b/src/main/java/ecdar/backend/Engine.java @@ -43,7 +43,6 @@ public class Engine implements Serializable { private final ArrayList<EngineConnection> startedConnections = new ArrayList<>(); private final BlockingQueue<GrpcRequest> requestQueue = new ArrayBlockingQueue<>(200); // Magic number - // ToDo NIELS: Refactor to resize queue on port range change private final BlockingQueue<EngineConnection> availableConnections = new ArrayBlockingQueue<>(200); // Magic number private final EngineConnectionStarter connectionStarter = new EngineConnectionStarter(this); diff --git a/src/main/java/ecdar/controllers/EcdarController.java b/src/main/java/ecdar/controllers/EcdarController.java index 0a135025..61e05dec 100644 --- a/src/main/java/ecdar/controllers/EcdarController.java +++ b/src/main/java/ecdar/controllers/EcdarController.java @@ -276,8 +276,6 @@ private void initializeDialogs() { }); simulationInitializationDialog.getController().startButton.setOnMouseClicked(event -> { - - // ToDo NIELS: Start simulation of selected query SimulatorController.simulationHandler.setComposition(simulationInitializationDialog.getController().simulationComboBox.getSelectionModel().getSelectedItem()); currentMode.setValue(Mode.Simulator); simulationInitializationDialog.close(); diff --git a/src/main/java/ecdar/controllers/ModelController.java b/src/main/java/ecdar/controllers/ModelController.java index 6e378f4a..c56b2ba2 100644 --- a/src/main/java/ecdar/controllers/ModelController.java +++ b/src/main/java/ecdar/controllers/ModelController.java @@ -5,7 +5,6 @@ import ecdar.abstractions.HighLevelModel; import com.jfoenix.controls.JFXTextField; import ecdar.utility.UndoRedoStack; -import ecdar.utility.colors.Color; import ecdar.utility.colors.EnabledColor; import ecdar.utility.helpers.StringValidator; import javafx.application.Platform; @@ -275,11 +274,11 @@ private void initializesCornerDragAnchor(final Box box) { }); cornerAnchor.setOnMouseDragged(event -> { - double xDiff = (event.getScreenX() - prevX.get()) / Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasZoomFactor().get(); // ToDo NIELS: Fix dependency + double xDiff = (event.getScreenX() - prevX.get()); final double newWidth = Math.max(prevWidth.get() + xDiff, getDragAnchorMinWidth()); box.setWidth(newWidth); - double yDiff = (event.getScreenY() - prevY.get()) / Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasZoomFactor().get(); + double yDiff = (event.getScreenY() - prevY.get()); final double newHeight = Math.max(prevHeight.get() + yDiff, getDragAnchorMinHeight()); box.setHeight(newHeight); diff --git a/src/main/java/ecdar/controllers/ProjectPaneController.java b/src/main/java/ecdar/controllers/ProjectPaneController.java index c040b5c9..4295543e 100644 --- a/src/main/java/ecdar/controllers/ProjectPaneController.java +++ b/src/main/java/ecdar/controllers/ProjectPaneController.java @@ -333,7 +333,7 @@ private void handleAddedModelPresentation(final HighLevelModelPresentation model filesList.getChildren().add(filePresentation); } - modelPresentationMap.put(modelPresentation, filePresentation);// ToDo NIELS: Bind these two + modelPresentationMap.put(modelPresentation, filePresentation); if (modelPresentation instanceof ComponentPresentation) { componentPresentations.add((ComponentPresentation) modelPresentation); } diff --git a/src/main/java/ecdar/mutation/TestCaseGenerationHandler.java b/src/main/java/ecdar/mutation/TestCaseGenerationHandler.java index 8f408cbd..b0dcbdb9 100644 --- a/src/main/java/ecdar/mutation/TestCaseGenerationHandler.java +++ b/src/main/java/ecdar/mutation/TestCaseGenerationHandler.java @@ -222,7 +222,6 @@ private void generateTestCase(final MutationTestCase testCase, final int tries) * @throws BackendException if the backend encounters an error */ private Process startProcessToFetchStrategy(final String modelPath) throws BackendException { - // ToDo NIELS: not implemented after switch to thread pool // Run backend to check refinement and to fetch strategy if non-refinement // return ((jECDARDriver) BackendDriverManager.getInstance(BackendHelper.BackendNames.jEcdar)).getJEcdarProcessForRefinementCheckAndStrategyIfNonRefinement(modelPath, queryFilePath); return null; diff --git a/src/main/java/ecdar/presentations/LocationPresentation.java b/src/main/java/ecdar/presentations/LocationPresentation.java index 5fdc833a..ce6aee49 100644 --- a/src/main/java/ecdar/presentations/LocationPresentation.java +++ b/src/main/java/ecdar/presentations/LocationPresentation.java @@ -350,7 +350,7 @@ protected void interpolate(final double frac) { updateUrgencies.accept(Location.Urgency.NORMAL, location.getUrgency()); // Delegate to style the label based on the color of the location - final Consumer<EnabledColor> updateColor = (newColor) -> { // ToDo NIELS: Fix this consumer, it seems a bit weird + final Consumer<EnabledColor> updateColor = (newColor) -> { if (!location.getUrgency().equals(Location.Urgency.PROHIBITED)) { if (location.getFailing()) { notCommittedShape.setFill(Color.RED.getColor(Color.Intensity.I700)); diff --git a/src/main/java/ecdar/presentations/SimTagPresentation.java b/src/main/java/ecdar/presentations/SimTagPresentation.java index 6e322076..e1ca8d61 100755 --- a/src/main/java/ecdar/presentations/SimTagPresentation.java +++ b/src/main/java/ecdar/presentations/SimTagPresentation.java @@ -15,7 +15,6 @@ import javafx.scene.shape.MoveTo; import javafx.scene.shape.Path; -import java.util.function.BiConsumer; import java.util.function.Consumer; /** @@ -34,8 +33,6 @@ public class SimTagPresentation extends StackPane { private LineTo l2; private LineTo l3; - private final static double TAG_HEIGHT = 16; // ToDo NIELS: This should be changed to follow the same value as TagPresentation - /** * Constructs the {@link SimTagPresentation} */ @@ -82,7 +79,7 @@ private void initializeLabel() { if (getWidth() >= 1000) { setWidth(newWidth); - setHeight(TAG_HEIGHT); + setHeight(TagPresentation.TAG_HEIGHT); shape.setTranslateY(-1); } @@ -98,7 +95,7 @@ private void initializeLabel() { */ private void initializeShape() { final int WIDTH = 5000; - final double HEIGHT = TAG_HEIGHT; + final double HEIGHT = TagPresentation.TAG_HEIGHT; final Path shape = (Path) lookup("#shape"); diff --git a/src/main/java/ecdar/utility/helpers/MouseCircular.java b/src/main/java/ecdar/utility/helpers/MouseCircular.java index c36f470f..ae13cd51 100644 --- a/src/main/java/ecdar/utility/helpers/MouseCircular.java +++ b/src/main/java/ecdar/utility/helpers/MouseCircular.java @@ -34,8 +34,6 @@ public MouseCircular(Circular initLocation) { originalX.get() - (newValue.doubleValue() - oldValue.doubleValue()) / Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasZoomFactor().get())); Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasPresentation().getController().modelPane.translateYProperty().addListener((observable, oldValue, newValue) -> originalY.set( originalY.get() - (newValue.doubleValue() - oldValue.doubleValue()) / Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasZoomFactor().get())); - - // ToDo NIELS: When the width or height of the scene is changed, the coordinates should be updated } private void updatePosition() { diff --git a/src/main/resources/ecdar/presentations/RightSimPanePresentation.fxml b/src/main/resources/ecdar/presentations/RightSimPanePresentation.fxml index 6f3d588c..21eec468 100755 --- a/src/main/resources/ecdar/presentations/RightSimPanePresentation.fxml +++ b/src/main/resources/ecdar/presentations/RightSimPanePresentation.fxml @@ -17,7 +17,7 @@ AnchorPane.rightAnchor="0" styleClass="edge-to-edge"> <VBox fx:id="scrollPaneVbox" > - <!-- ToDo NIELS: Previously contained <QueryPaneElementPresentation fx:id="queryPaneElement"/>, i.e. queries identical to the editor, should now contain the queries, but maybe in a different format --> + <!-- Waiting on feature to be specified --> </VBox> </ScrollPane> </AnchorPane> From 3fe0b9f0b89928a6b387967f4588655f91a234db Mon Sep 17 00:00:00 2001 From: "Niels F. S. Vistisen" <42961494+Nielswps@users.noreply.github.com> Date: Thu, 20 Apr 2023 09:00:25 +0200 Subject: [PATCH 145/158] Default engine loading FIX (#153) * Engine access modifiers updated and loading of default status of engines FIXED * Unused default_engine preference variable removed --- src/main/java/ecdar/backend/Engine.java | 23 ++++++------------- .../EngineOptionsDialogController.java | 6 +---- 2 files changed, 8 insertions(+), 21 deletions(-) diff --git a/src/main/java/ecdar/backend/Engine.java b/src/main/java/ecdar/backend/Engine.java index 61d039f3..f94ef3dd 100644 --- a/src/main/java/ecdar/backend/Engine.java +++ b/src/main/java/ecdar/backend/Engine.java @@ -23,16 +23,16 @@ public class Engine implements Serializable { private static final String PORT_RANGE_START = "portRangeStart"; private static final String PORT_RANGE_END = "portRangeEnd"; private static final String LOCKED = "locked"; - private final int responseDeadline = 20000; - private final int rerunRequestDelay = 200; - private final int numberOfRetriesPerQuery = 5; + private static final int responseDeadline = 20000; + private static final int rerunRequestDelay = 200; + private static final int numberOfRetriesPerQuery = 5; private String name; private boolean isLocal; private boolean isDefault; private int portStart; private int portEnd; - private SimpleBooleanProperty locked = new SimpleBooleanProperty(false); + private final SimpleBooleanProperty locked = new SimpleBooleanProperty(false); /** * This is either a path to the engines executable or an IP address at which the engine is running */ @@ -123,7 +123,7 @@ public SimpleBooleanProperty getLockedProperty() { return locked; } - public ArrayList<EngineConnection> getStartedConnections() { + protected ArrayList<EngineConnection> getStartedConnections() { return startedConnections; } @@ -171,19 +171,10 @@ public void onCompleted() { * * @param connection to make available */ - public void setConnectionAsAvailable(EngineConnection connection) { + private void setConnectionAsAvailable(EngineConnection connection) { if (!availableConnections.contains(connection)) availableConnections.add(connection); } - /** - * Clears all queued queries, stops all active engines, and closes all open engine connections - */ - public void clear() throws BackendException { - BackendHelper.stopQueries(); - requestQueue.clear(); - closeConnections(); - } - /** * Filters the list of open {@link EngineConnection}s to the specified {@link Engine} and returns the * first match or attempts to start a new connection if none is found. @@ -257,7 +248,7 @@ public void onCompleted() { * @throws BackendException if one or more connections throw an exception on {@link EngineConnection#close()} * (use getSuppressed() to see all thrown exceptions) */ - public void closeConnections() throws BackendException { + protected void closeConnections() throws BackendException { // Create a list for storing all terminated connection List<CompletableFuture<EngineConnection>> closeFutures = new ArrayList<>(); BackendException exceptions = new BackendException("Exceptions were thrown while attempting to close engine connections on " + getName()); diff --git a/src/main/java/ecdar/controllers/EngineOptionsDialogController.java b/src/main/java/ecdar/controllers/EngineOptionsDialogController.java index fafd6592..c1c1360c 100644 --- a/src/main/java/ecdar/controllers/EngineOptionsDialogController.java +++ b/src/main/java/ecdar/controllers/EngineOptionsDialogController.java @@ -90,9 +90,6 @@ public boolean saveChangesToEngineOptions() { Engine defaultEngine = engines.stream().filter(Engine::isDefault).findFirst().orElse(engines.get(0)); BackendHelper.setDefaultEngine(defaultEngine); - String defaultEngineName = (defaultEngine.getName()); - Ecdar.preferences.put("default_engine", defaultEngineName); - return true; } else { return false; @@ -100,7 +97,7 @@ public boolean saveChangesToEngineOptions() { } /** - * Resets the engines to the default engines present in the 'default_engines.json' file. + * Resets the engines to those packaged with the system. */ public void resetEnginesToDefault() { updateEnginesInGUI(getPackagedEngines()); @@ -255,7 +252,6 @@ private boolean setEnginePathIfFileExists(Engine engine, List<String> potentialF * @param newEnginePresentation The presentation of the new engine instance */ private void addEnginePresentationToList(EnginePresentation newEnginePresentation) { - newEnginePresentation.getController().defaultEngineRadioButton.setSelected(engineInstanceList.getChildren().isEmpty()); engineInstanceList.getChildren().add(newEnginePresentation); newEnginePresentation.getController().moveEngineInstanceUpRippler.setOnMouseClicked((mouseEvent) -> moveEngineInstance(newEnginePresentation, -1)); newEnginePresentation.getController().moveEngineInstanceDownRippler.setOnMouseClicked((mouseEvent) -> moveEngineInstance(newEnginePresentation, +1)); From b52a69a7917ff2204b9233f405a145b8bf39469e Mon Sep 17 00:00:00 2001 From: "Niels F. S. Vistisen" <42961494+Nielswps@users.noreply.github.com> Date: Fri, 12 May 2023 19:16:34 +0200 Subject: [PATCH 146/158] Feature/visitor pattern (#20) * Visitor pattern implemented for gRPC request construction * New proto (#19) * WIP: Update to new protobuf * WIP: Updated proto to futureproof1 branch an moved simulation classes to separate package * WIP: Compilable * WIP: Full refactor/re-implementation started * WIP: Complete refactor * WIP: Continued work to re-implement simulation with new proto * WIP: Initial simulation step FIXED for new proto * WIP: Simulation steps FIXED * WIP: Query test fixed and dublicate consumers merged * WIP: Minor clean up --- .../java/ecdar/abstractions/Decision.java | 58 +++ .../ecdar/abstractions/DisplayableEdge.java | 4 +- .../java/ecdar/abstractions/GroupedEdge.java | 4 +- src/main/java/ecdar/abstractions/Query.java | 236 ++++------ .../ecdar/abstractions/RequestSource.java | 10 + .../java/ecdar/abstractions/Simulation.java | 77 ++++ src/main/java/ecdar/abstractions/State.java | 63 +++ .../java/ecdar/abstractions/Transition.java | 13 +- src/main/java/ecdar/backend/Engine.java | 134 +----- .../ecdar/backend/GrpcRequestFactory.java | 116 +++++ .../java/ecdar/backend/SimulationHandler.java | 289 ------------ .../controllers/ComponentController.java | 4 +- .../ecdar/controllers/DecisionController.java | 34 ++ .../ecdar/controllers/EcdarController.java | 24 +- .../ecdar/controllers/EdgeController.java | 3 +- .../controllers/LeftSimPaneController.java | 13 +- .../ecdar/controllers/ModeController.java | 2 +- .../ecdar/controllers/ProcessController.java | 5 +- .../controllers/RightSimPaneController.java | 100 ++++- .../ecdar/controllers/SimEdgeController.java | 6 +- .../controllers/SimLocationController.java | 81 ++-- .../ecdar/controllers/SimNailController.java | 2 - .../controllers/SimulationController.java | 416 ++++++++++++++++++ ...ulationInitializationDialogController.java | 29 -- .../controllers/SimulatorController.java | 151 ------- .../SimulatorOverviewController.java | 332 -------------- ...onController.java => StateController.java} | 18 +- ...ntroller.java => StatePaneController.java} | 58 ++- .../controllers/TracePaneController.java | 198 +++------ .../presentations/DecisionPresentation.java | 18 + .../ecdar/presentations/EcdarFXMLLoader.java | 2 +- .../presentations/EcdarPresentation.java | 6 +- .../LeftSimPanePresentation.java | 6 +- .../presentations/ProcessPresentation.java | 9 +- .../RightSimPanePresentation.java | 37 +- .../presentations/SimEdgePresentation.java | 17 +- .../SimLocationPresentation.java | 4 +- .../presentations/SimNailPresentation.java | 2 +- .../presentations/SimTagPresentation.java | 2 +- ...ationInitializationDialogPresentation.java | 1 + .../presentations/SimulationPresentation.java | 21 + .../SimulatorOverviewPresentation.java | 20 - .../presentations/SimulatorPresentation.java | 20 - .../presentations/StatePanePresentation.java | 19 + ...esentation.java => StatePresentation.java} | 20 +- .../ecdar/presentations/TagPresentation.java | 2 +- .../TransitionPanePresentation.java | 19 - .../ecdar/simulation/SimulationState.java | 77 ---- .../java/ecdar/simulation/Transition.java | 17 - .../ecdar/utility/helpers/BindingHelper.java | 1 - src/main/proto | 2 +- .../presentations/DecisionPresentation.fxml | 21 + .../presentations/EcdarPresentation.fxml | 5 +- .../LeftSimPanePresentation.fxml | 4 +- .../RightSimPanePresentation.fxml | 4 +- ...ationInitializationDialogPresentation.fxml | 2 +- .../presentations/SimulationPresentation.fxml | 30 ++ .../SimulatorOverviewPresentation.fxml | 13 - .../presentations/SimulatorPresentation.fxml | 19 - ...tation.fxml => StatePanePresentation.fxml} | 2 +- ...esentation.fxml => StatePresentation.fxml} | 6 +- .../presentations/TracePanePresentation.fxml | 4 +- src/test/java/ecdar/backend/QueryTest.java | 4 +- .../ecdar/simulation/ReachabilityTest.java | 28 +- .../java/ecdar/simulation/SimulationTest.java | 3 - 65 files changed, 1350 insertions(+), 1597 deletions(-) create mode 100644 src/main/java/ecdar/abstractions/Decision.java create mode 100644 src/main/java/ecdar/abstractions/RequestSource.java create mode 100644 src/main/java/ecdar/abstractions/Simulation.java create mode 100644 src/main/java/ecdar/abstractions/State.java create mode 100644 src/main/java/ecdar/backend/GrpcRequestFactory.java delete mode 100644 src/main/java/ecdar/backend/SimulationHandler.java create mode 100644 src/main/java/ecdar/controllers/DecisionController.java create mode 100644 src/main/java/ecdar/controllers/SimulationController.java delete mode 100644 src/main/java/ecdar/controllers/SimulatorController.java delete mode 100644 src/main/java/ecdar/controllers/SimulatorOverviewController.java rename src/main/java/ecdar/controllers/{TransitionController.java => StateController.java} (63%) rename src/main/java/ecdar/controllers/{TransitionPaneController.java => StatePaneController.java} (78%) create mode 100644 src/main/java/ecdar/presentations/DecisionPresentation.java create mode 100755 src/main/java/ecdar/presentations/SimulationPresentation.java delete mode 100755 src/main/java/ecdar/presentations/SimulatorOverviewPresentation.java delete mode 100755 src/main/java/ecdar/presentations/SimulatorPresentation.java create mode 100755 src/main/java/ecdar/presentations/StatePanePresentation.java rename src/main/java/ecdar/presentations/{TransitionPresentation.java => StatePresentation.java} (84%) delete mode 100755 src/main/java/ecdar/presentations/TransitionPanePresentation.java delete mode 100644 src/main/java/ecdar/simulation/SimulationState.java delete mode 100644 src/main/java/ecdar/simulation/Transition.java create mode 100755 src/main/resources/ecdar/presentations/DecisionPresentation.fxml create mode 100755 src/main/resources/ecdar/presentations/SimulationPresentation.fxml delete mode 100755 src/main/resources/ecdar/presentations/SimulatorOverviewPresentation.fxml delete mode 100755 src/main/resources/ecdar/presentations/SimulatorPresentation.fxml rename src/main/resources/ecdar/presentations/{TransitionPanePresentation.fxml => StatePanePresentation.fxml} (93%) rename src/main/resources/ecdar/presentations/{TransitionPresentation.fxml => StatePresentation.fxml} (65%) diff --git a/src/main/java/ecdar/abstractions/Decision.java b/src/main/java/ecdar/abstractions/Decision.java new file mode 100644 index 00000000..3aed1099 --- /dev/null +++ b/src/main/java/ecdar/abstractions/Decision.java @@ -0,0 +1,58 @@ +package ecdar.abstractions; + +import EcdarProtoBuf.ObjectProtos; +import EcdarProtoBuf.QueryProtos; +import ecdar.backend.GrpcRequest; +import ecdar.backend.GrpcRequestFactory; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; +import java.util.stream.Collectors; + +public class Decision implements RequestSource<QueryProtos.SimulationStepResponse> { + public final String composition; + public final State source; + public final State target; + public final List<String> edgeIds; + public final String action; + public ObjectProtos.Decision protoDecision; + + public Decision(String composition, State source, State target, List<String> edgeIds, String action) { + this.composition = composition; + this.source = source; + this.target = target; + this.edgeIds = edgeIds; + this.action = action; + this.protoDecision = null; + } + + /** + * This constructed is used to represent the decision submitted for the initial state + * ToDo NIELS: refactor to use a factory pattern for constructing new decisions + */ + public Decision(String composition) { + this(composition, null, null, new ArrayList<>(), null); + } + + public Decision(String composition, ObjectProtos.Decision protoDecision) { + this(composition, + new State(protoDecision.getSource()), + new State(protoDecision.getDestination()), + protoDecision.getEdgesList().stream().map(ObjectProtos.Edge::getId).collect(Collectors.toList()), + protoDecision.getAction()); + + this.protoDecision = protoDecision; // Save the proto decision for requesting simulation step + } + + public boolean isInitial() { + return source == null; + } + + @Override + public GrpcRequest accept(GrpcRequestFactory requestFactory, + Consumer<QueryProtos.SimulationStepResponse> successConsumer, + Consumer<Throwable> errorConsumer) { + return requestFactory.create(this, successConsumer, errorConsumer); + } +} diff --git a/src/main/java/ecdar/abstractions/DisplayableEdge.java b/src/main/java/ecdar/abstractions/DisplayableEdge.java index 52ced7b5..121e64f6 100644 --- a/src/main/java/ecdar/abstractions/DisplayableEdge.java +++ b/src/main/java/ecdar/abstractions/DisplayableEdge.java @@ -1,7 +1,6 @@ package ecdar.abstractions; import ecdar.code_analysis.Nearable; -import ecdar.utility.colors.Color; import ecdar.utility.colors.EnabledColor; import ecdar.utility.helpers.Circular; import ecdar.utility.helpers.MouseCircular; @@ -9,7 +8,6 @@ import javafx.beans.property.*; import javafx.collections.FXCollections; import javafx.collections.ObservableList; -import javafx.fxml.FXMLLoader; import java.util.List; import java.util.UUID; @@ -123,7 +121,7 @@ public ObjectProperty<EnabledColor> colorProperty() { public void setIsHighlighted(final boolean highlight){ this.isHighlighted.set(highlight);} - public boolean getIsHighlighted(){ return this.isHighlighted.get(); } + public boolean isHighlighted(){ return this.isHighlighted.get(); } public boolean getIsHighlightedForReachability(){ return this.isHighlightedForReachability.get(); } public BooleanProperty isHighlightedProperty() { return this.isHighlighted; } diff --git a/src/main/java/ecdar/abstractions/GroupedEdge.java b/src/main/java/ecdar/abstractions/GroupedEdge.java index 420762be..10935628 100644 --- a/src/main/java/ecdar/abstractions/GroupedEdge.java +++ b/src/main/java/ecdar/abstractions/GroupedEdge.java @@ -54,7 +54,7 @@ private void initializeFromEdge(Edge edge) { setGuard(edge.getGuard()); setUpdate(edge.getUpdate()); setColor(edge.getColor()); - setIsHighlighted(edge.getIsHighlighted()); + setIsHighlighted(edge.isHighlighted()); setIsLocked(edge.getIsLockedProperty().getValue()); setStatus(edge.getStatus()); } @@ -115,7 +115,7 @@ public Edge getBaseSubEdge() { edge.guardProperty().bind(this.guardProperty()); edge.updateProperty().bind(this.updateProperty()); edge.colorProperty().bind(this.colorProperty()); - edge.setIsHighlighted(this.getIsHighlighted()); + edge.setIsHighlighted(this.isHighlighted()); edge.getIsLockedProperty().bind(this.getIsLockedProperty()); edge.setGroup(this.getId()); edge.makeSyncNailBetweenLocations(); diff --git a/src/main/java/ecdar/abstractions/Query.java b/src/main/java/ecdar/abstractions/Query.java index face9e71..9dc4dc36 100644 --- a/src/main/java/ecdar/abstractions/Query.java +++ b/src/main/java/ecdar/abstractions/Query.java @@ -5,7 +5,7 @@ import ecdar.Ecdar; import ecdar.backend.*; import ecdar.controllers.EcdarController; -import ecdar.controllers.SimulatorController; +import ecdar.controllers.SimulationController; import ecdar.utility.UndoRedoStack; import ecdar.utility.helpers.StringValidator; import EcdarProtoBuf.ObjectProtos; @@ -22,7 +22,7 @@ import java.util.function.BiConsumer; import java.util.function.Consumer; -public class Query implements Serializable { +public class Query implements RequestSource<QueryProtos.QueryResponse>, Serializable { private static final String QUERY = "query"; private static final String COMMENT = "comment"; private static final String IS_PERIODIC = "isPeriodic"; @@ -74,38 +74,38 @@ public class Query implements Serializable { }; private final BiConsumer<ObjectProtos.State, List<String>> stateActionConsumer = (state, actions) -> { - + // ToDo: Color all IO strings red for (Component c : Ecdar.getProject().getComponents()) { c.removeFailingLocations(); c.removeFailingEdges(); } - for (ObjectProtos.Location location : state.getLocationTuple().getLocationsList()) { - Component c = Ecdar.getProject().findComponent(location.getSpecificComponent().getComponentName()); - - if (c == null) { - throw new NullPointerException("Could not find the specific component: " + location.getSpecificComponent().getComponentName()); - } - - if (location.getId().isEmpty()) { - if(c.getName().equals(location.getSpecificComponent().getComponentName())){ - c.setFailingIOStrings(actions); - c.setIsFailing(true); - } - } else { - Location l = c.findLocation(location.getId()); - if (l == null) { - throw new NullPointerException("Could not find location: " + location.getId()); - } - - c.addFailingLocation(l.getId()); - for (Edge edge : c.getEdges()) { - if (actions.get(0).equals(edge.getSync()) && edge.getSourceLocation() == l) { - c.addFailingEdge(edge); - } - } - } - } +// for (ObjectProtos.LeafLocation location : ) { +// Component c = Ecdar.getProject().findComponent(location.getSpecificComponent().getComponentName()); +// +// if (c == null) { +// throw new NullPointerException("Could not find the specific component: " + location.getSpecificComponent().getComponentName()); +// } +// +// if (location.getId().isEmpty()) { +// if (c.getName().equals(location.getSpecificComponent().getComponentName())) { +// c.setFailingIOStrings(actions); +// c.setIsFailing(true); +// } +// } else { +// Location l = c.findLocation(location.getId()); +// if (l == null) { +// throw new NullPointerException("Could not find location: " + location.getId()); +// } +// +// c.addFailingLocation(l.getId()); +// for (Edge edge : c.getEdges()) { +// if (actions.get(0).equals(edge.getSync()) && edge.getSourceLocation() == l) { +// c.addFailingEdge(edge); +// } +// } +// } +// } }; public Query(final String query, final String comment, final QueryState queryState, final Engine engine) { @@ -219,114 +219,65 @@ public void execute() throws NoSuchElementException { setQueryState(QueryState.RUNNING); errors().set(""); - getEngine().enqueueQuery(this, this::handleQueryResponse, this::handleQueryBackendError); + getEngine().enqueueRequest(this, this::handleQueryResponse, this::handleQueryBackendError); } private void handleQueryResponse(QueryProtos.QueryResponse value) { if (getQueryState() == QueryState.UNKNOWN) return; - switch (value.getResultCase()) { - case REFINEMENT: - if (value.getRefinement().getSuccess()) { - setQueryState(QueryState.SUCCESSFUL); - getSuccessConsumer().accept(true); - } else { - setQueryState(QueryState.ERROR); - getFailureConsumer().accept(new BackendException.QueryErrorException(value.getRefinement().getReason())); - getSuccessConsumer().accept(false); - getStateActionConsumer().accept(value.getRefinement().getState(), - value.getRefinement().getActionList()); - } - break; - case CONSISTENCY: - if (value.getConsistency().getSuccess()) { - setQueryState(QueryState.SUCCESSFUL); - getSuccessConsumer().accept(true); - } else { - setQueryState(QueryState.ERROR); - getFailureConsumer().accept(new BackendException.QueryErrorException(value.getConsistency().getReason())); - getSuccessConsumer().accept(false); - getStateActionConsumer().accept(value.getConsistency().getState(), - value.getConsistency().getActionList()); - } - break; + if (value.hasSuccess()) { + setQueryState(QueryState.SUCCESSFUL); + getSuccessConsumer().accept(true); - case DETERMINISM: - if (value.getDeterminism().getSuccess()) { - setQueryState(QueryState.SUCCESSFUL); - getSuccessConsumer().accept(true); - } else { - setQueryState(QueryState.ERROR); - getFailureConsumer().accept(new BackendException.QueryErrorException(value.getDeterminism().getReason())); - getSuccessConsumer().accept(false); - getStateActionConsumer().accept(value.getDeterminism().getState(), - value.getDeterminism().getActionList()); + switch (value.getResultCase()) { + case COMPONENT: + JsonObject returnedComponent = (JsonObject) JsonParser.parseString(value.getComponent().getJson()); + addGeneratedComponent(new Component(returnedComponent)); + break; + case REACHABILITY: + // Highlight edges in path + ArrayList<String> edgeIds = new ArrayList<>(); + for (var decision : value.getReachabilityPath().getPath().getDecisionsList()) { + for (var edge : decision.getEdgesList()) { + edgeIds.add(edge.getId()); + } + } - } - break; +// highlightReachabilityEdges(edgeIds); // ToDo NIELS: Refactor + break; + } + } else { + setQueryState(QueryState.ERROR); + getFailureConsumer().accept(new BackendException.QueryErrorException(value.getError().getError())); + getSuccessConsumer().accept(false); - case IMPLEMENTATION: - if (value.getImplementation().getSuccess()) { - setQueryState(QueryState.SUCCESSFUL); - getSuccessConsumer().accept(true); - } else { - setQueryState(QueryState.ERROR); - getFailureConsumer().accept(new BackendException.QueryErrorException(value.getImplementation().getReason())); - getSuccessConsumer().accept(false); - //ToDo: These errors are not implemented in the Reveaal backend. - getStateActionConsumer().accept(value.getImplementation().getState(), + switch (value.getResultCase()) { + case REFINEMENT: + getStateActionConsumer().accept(value.getRefinement().getRefinementState().getState().getState(), new ArrayList<>()); - } - break; - - case REACHABILITY: - if (value.getReachability().getSuccess()) { - setQueryState(QueryState.SUCCESSFUL); - Ecdar.showToast("Reachability check was successful and the location can be reached."); + break; - //create list of edge id's - ArrayList<String> edgeIds = new ArrayList<>(); - for(var pathsList : value.getReachability().getComponentPathsList()){ - for(var id : pathsList.getEdgeIdsList().toArray()) { - edgeIds.add(id.toString()); - } - } - //highlight the edges - SimulatorController.getSimulationHandler().highlightReachabilityEdges(edgeIds); - getSuccessConsumer().accept(true); - } - else if(!value.getReachability().getSuccess()){ - Ecdar.showToast("Reachability check was successful but the location cannot be reached."); - getSuccessConsumer().accept(true); - } else { - setQueryState(QueryState.ERROR); - Ecdar.showToast("Error from backend: Reachability check was unsuccessful!"); - getFailureConsumer().accept(new BackendException.QueryErrorException(value.getReachability().getReason())); - getSuccessConsumer().accept(false); - //ToDo: These errors are not implemented in the Reveaal backend. - getStateActionConsumer().accept(value.getReachability().getState(), + case CONSISTENCY: + getStateActionConsumer().accept(value.getConsistency().getFailureState(), new ArrayList<>()); - } - break; + break; - case COMPONENT: - setQueryState(QueryState.SUCCESSFUL); - getSuccessConsumer().accept(true); - JsonObject returnedComponent = (JsonObject) JsonParser.parseString(value.getComponent().getComponent().getJson()); - addGeneratedComponent(new Component(returnedComponent)); - break; + case DETERMINISM: + getStateActionConsumer().accept(value.getDeterminism().getFailureState().getState(), + new ArrayList<>()); + break; - case ERROR: - setQueryState(QueryState.ERROR); - Ecdar.showToast(value.getError()); - getFailureConsumer().accept(new BackendException.QueryErrorException(value.getError())); - getSuccessConsumer().accept(false); - break; + case IMPLEMENTATION: + getStateActionConsumer().accept(value.getImplementation().getFailureState().getState(), + new ArrayList<>()); + break; - case RESULT_NOT_SET: - setQueryState(QueryState.ERROR); - getSuccessConsumer().accept(false); - break; + case REACHABILITY: + // ToDo: Reachability failure state not implemented + getStateActionConsumer().accept(value.getConsistency().getFailureState(), + new ArrayList<>()); + break; + } } } @@ -335,7 +286,7 @@ private void handleQueryBackendError(Throwable t) { if (getQueryState() == QueryState.UNKNOWN) return; // Due to limit information provided by the engines, we can only show the following for unreachable locations - if(getType() == QueryType.REACHABILITY){ + if (getType() == QueryType.REACHABILITY) { Ecdar.showToast("Timeout (no response from backend): The reachability query failed. This might be due to the fact that the location is not reachable."); } @@ -404,6 +355,26 @@ public BiConsumer<ObjectProtos.State, List<String>> getStateActionConsumer() { return stateActionConsumer; } + public void cancel() { + if (getQueryState().equals(QueryState.RUNNING)) { + forcedCancel = true; + setQueryState(QueryState.UNKNOWN); + } + } + + public void addError(String e) { + errors.set(errors.getValue() + e + "\n"); + } + + public String getCurrentErrors() { + return errors.getValue(); + } + + @Override + public GrpcRequest accept(GrpcRequestFactory requestFactory, Consumer<QueryProtos.QueryResponse> successConsumer, Consumer<Throwable> errorConsumer) { + return requestFactory.create(this, this::handleQueryResponse, this::handleQueryBackendError); + } + @Override public JsonObject serialize() { final JsonObject result = new JsonObject(); @@ -434,25 +405,10 @@ public void deserialize(final JsonObject json) { setIsPeriodic(json.getAsJsonPrimitive(IS_PERIODIC).getAsBoolean()); } - if(json.has(ENGINE)) { + if (json.has(ENGINE)) { setEngine(BackendHelper.getEngineByName(json.getAsJsonPrimitive(ENGINE).getAsString())); } else { setEngine(BackendHelper.getDefaultEngine()); } } - - public void cancel() { - if (getQueryState().equals(QueryState.RUNNING)) { - forcedCancel = true; - setQueryState(QueryState.UNKNOWN); - } - } - - public void addError(String e) { - errors.set(errors.getValue() + e + "\n"); - } - - public String getCurrentErrors() { - return errors.getValue(); - } } diff --git a/src/main/java/ecdar/abstractions/RequestSource.java b/src/main/java/ecdar/abstractions/RequestSource.java new file mode 100644 index 00000000..ebfb7720 --- /dev/null +++ b/src/main/java/ecdar/abstractions/RequestSource.java @@ -0,0 +1,10 @@ +package ecdar.abstractions; + +import ecdar.backend.GrpcRequest; +import ecdar.backend.GrpcRequestFactory; + +import java.util.function.Consumer; + +public interface RequestSource <T> { + GrpcRequest accept(GrpcRequestFactory requestFactory, Consumer<T> successConsumer, Consumer<Throwable> errorConsumer); +} diff --git a/src/main/java/ecdar/abstractions/Simulation.java b/src/main/java/ecdar/abstractions/Simulation.java new file mode 100644 index 00000000..8f862f47 --- /dev/null +++ b/src/main/java/ecdar/abstractions/Simulation.java @@ -0,0 +1,77 @@ +package ecdar.abstractions; + +import EcdarProtoBuf.ObjectProtos; +import ecdar.Ecdar; +import ecdar.presentations.DecisionPresentation; +import ecdar.presentations.StatePresentation; +import javafx.application.Platform; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.collections.ObservableMap; + +import java.math.BigDecimal; +import java.util.List; +import java.util.function.Consumer; +import java.util.regex.MatchResult; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +public class Simulation { + public final String composition; + public final ObservableList<Component> simulatedComponents = FXCollections.observableArrayList(); + public final ObjectProperty<State> initialState = new SimpleObjectProperty<>(); + public final ObservableMap<String, BigDecimal> variables = FXCollections.observableHashMap(); + public final ObservableMap<String, BigDecimal> clocks = FXCollections.observableHashMap(); + + public final ObjectProperty<State> currentState = new SimpleObjectProperty<>(); + + public final ObservableList<DecisionPresentation> availableDecisions = FXCollections.observableArrayList(); + public final ObservableList<StatePresentation> traceLog = FXCollections.observableArrayList(); + + public Simulation(String composition, State initialState) { + this.composition = composition; + setSimulatedComponents(composition); + + this.initialState.set(initialState); + this.currentState.set(initialState); + +// addStateToTraceLog(initialState); + } + + private void setSimulatedComponents(String composition) { + // Match all referenced components by ignoring operators. + Pattern pattern = Pattern.compile("([\\w]*)", Pattern.CASE_INSENSITIVE); + Matcher matcher = pattern.matcher(composition); + + // Get all components referenced in the composition + List<String> componentsToSimulateByName = matcher.results().map(MatchResult::group).collect(Collectors.toList()); + List<Component> componentsToSimulate = Ecdar.getProject().getComponents().stream() + .filter(component -> componentsToSimulateByName.contains(component.getName())) + .collect(Collectors.toList()); + + + simulatedComponents.addAll(componentsToSimulate); + } + + public void addDecisionsFromProto(List<ObjectProtos.Decision> protoDecisions) { + Platform.runLater(() -> { + protoDecisions.forEach(protoDecision -> { + availableDecisions.add(new DecisionPresentation(new Decision(composition, protoDecision))); + }); + }); + } + + public void addStateToTraceLog(State state, Consumer<StatePresentation> onTraceLogStatePressed) { + StatePresentation statePresentation = new StatePresentation(state); + + statePresentation.setOnMouseReleased(event -> { + event.consume(); + onTraceLogStatePressed.accept(statePresentation); + }); + + traceLog.add(statePresentation); + } +} diff --git a/src/main/java/ecdar/abstractions/State.java b/src/main/java/ecdar/abstractions/State.java new file mode 100644 index 00000000..f7212deb --- /dev/null +++ b/src/main/java/ecdar/abstractions/State.java @@ -0,0 +1,63 @@ +package ecdar.abstractions; + +import EcdarProtoBuf.ObjectProtos; +import javafx.collections.FXCollections; +import javafx.collections.ObservableMap; + +import java.math.BigDecimal; +import java.util.Map; +import java.util.function.Consumer; + +public class State { + // locations and edges are saved as key-value pair where key is component name and value = id + private final ObjectProtos.LocationTree locationTree; + private final ObjectProtos.State protoState; + public final ObservableMap<String, BigDecimal> clocks = FXCollections.observableHashMap(); + + public State(ObjectProtos.State state) { + this.locationTree = state.getLocationTree(); // ToDo NIELS: Ensure that the source is indeed the same for all decisions + this.protoState = state; + } + + /** + * All the clocks connected to the current simulation. + * + * @return a {@link Map} where the component name (String) is the key, and the location name is the value (String) + */ + public ObjectProtos.LocationTree getLocationTree() { + return locationTree; + } + + public void consumeLeafLocations(Consumer<ObjectProtos.LeafLocation> consumer) { + consumeLeafLocations(locationTree, consumer); + } + + private void consumeLeafLocations(ObjectProtos.LocationTree tree, Consumer<ObjectProtos.LeafLocation> consumer) { + switch (tree.getNodeTypeCase()) { + case LEAF_LOCATION: + consumer.accept(tree.getLeafLocation()); + + case BINARY_LOCATION_OP: { + consumeLeafLocations(tree.getBinaryLocationOp().getLeft(), consumer); + consumeLeafLocations(tree.getBinaryLocationOp().getRight(), consumer); + } + + case SPECIAL_LOCATION: // ToDo: Implement visualization of inconsistent and universal locations + + case NODETYPE_NOT_SET: // Will never happen + } + } + + /** + * All the clocks connected to the current simulation. + * + * @return a {@link Map} where the name (String) is the key, and a {@link BigDecimal} is the clock value + */ + public ObservableMap<String, BigDecimal> getSimulationClocks() { + return this.clocks; + } + + public ObjectProtos.State getProtoState() { + return protoState; + } +} diff --git a/src/main/java/ecdar/abstractions/Transition.java b/src/main/java/ecdar/abstractions/Transition.java index c79d4f21..0db558d1 100644 --- a/src/main/java/ecdar/abstractions/Transition.java +++ b/src/main/java/ecdar/abstractions/Transition.java @@ -1,12 +1,17 @@ package ecdar.abstractions; -import EcdarProtoBuf.ObjectProtos; +import ecdar.abstractions.Edge; + import java.util.ArrayList; public class Transition { - public final ArrayList<Edge> edges = new ArrayList<>(); + public String getLabel() { + // ToDo: Implement + return "Transition label"; + } - public Transition(ObjectProtos protoBufTransition) { - // ToDo: Construct transition instance based on protoBuf input + public ArrayList<Edge> getEdges() { + // ToDo: Implement + return new ArrayList<>(); } } diff --git a/src/main/java/ecdar/backend/Engine.java b/src/main/java/ecdar/backend/Engine.java index 790c72ab..96538f4f 100644 --- a/src/main/java/ecdar/backend/Engine.java +++ b/src/main/java/ecdar/backend/Engine.java @@ -1,15 +1,9 @@ package ecdar.backend; -import EcdarProtoBuf.ComponentProtos; -import EcdarProtoBuf.ObjectProtos; -import EcdarProtoBuf.QueryProtos; import com.google.gson.JsonObject; import ecdar.Ecdar; -import ecdar.abstractions.Component; -import ecdar.abstractions.Query; -import ecdar.simulation.SimulationState; +import ecdar.abstractions.RequestSource; import ecdar.utility.serialize.Serializable; -import io.grpc.stub.StreamObserver; import javafx.beans.property.SimpleBooleanProperty; import java.util.*; @@ -25,7 +19,7 @@ public class Engine implements Serializable { private static final String PORT_RANGE_END = "portRangeEnd"; private static final String LOCKED = "locked"; private static final String IS_THREAD_SAFE = "isThreadSafe"; - private static final int responseDeadline = 20000; + private static final int rerunRequestDelay = 200; private static final int numberOfRetriesPerQuery = 5; @@ -138,129 +132,19 @@ protected ArrayList<EngineConnection> getStartedConnections() { } /** - * Enqueue query for execution with consumers for success and error - * - * @param query to enqueue for execution - * @param successConsumer for returned QueryResponse - * @param errorConsumer for any throwable that might result from the execution - */ - public void enqueueQuery(Query query, Consumer<QueryProtos.QueryResponse> successConsumer, Consumer<Throwable> errorConsumer) { - GrpcRequest request = new GrpcRequest(engineConnection -> { - var componentsInfoBuilder = BackendHelper.getComponentsInfoBuilder(query.getQuery()); - - StreamObserver<QueryProtos.QueryResponse> responseObserver = new StreamObserver<>() { - @Override - public void onNext(QueryProtos.QueryResponse value) { - successConsumer.accept(value); - } - - @Override - public void onError(Throwable t) { - errorConsumer.accept(t); - setConnectionAsAvailable(engineConnection); - } - - @Override - public void onCompleted() { - // Release engine connection - setConnectionAsAvailable(engineConnection); - } - }; - - var queryBuilder = QueryProtos.QueryRequest.newBuilder() - .setUserId(1) - .setQueryId(UUID.randomUUID().hashCode()) - .setSettings(QueryProtos.QueryRequest.Settings.newBuilder().setDisableClockReduction(true)) - .setQuery(query.getType().getQueryName() + ": " + query.getQuery()) - .setComponentsInfo(componentsInfoBuilder); - - engineConnection.getStub().withDeadlineAfter(responseDeadline, TimeUnit.MILLISECONDS) - .sendQuery(queryBuilder.build(), responseObserver); - }); - - requestQueue.add(request); - } - - /** - * Enqueue request of initial simulation step with consumers for success and error - * - * @param composition of the current simulated query - * @param stepConsumer for the resulting step - * @param errorConsumer for potential errors - */ - public void enqueueInitialSimulationStepRequest(String composition, Consumer<QueryProtos.SimulationStepResponse> stepConsumer, Consumer<Throwable> errorConsumer) { - GrpcRequest request = new GrpcRequest(engineConnection -> { - StreamObserver<QueryProtos.SimulationStepResponse> responseObserver = getSimulationResponseObserver(stepConsumer, errorConsumer); - - var comInfo = ComponentProtos.ComponentsInfo.newBuilder(); - for (Component c : Ecdar.getProject().getComponents()) { - comInfo.addComponents(ComponentProtos.Component.newBuilder().setJson(c.serialize().toString()).build()); - } - - comInfo.setComponentsHash(comInfo.getComponentsList().hashCode()); - var simStartRequest = QueryProtos.SimulationStartRequest.newBuilder(); - var simInfo = QueryProtos.SimulationInfo.newBuilder() - .setComponentComposition(composition) - .setComponentsInfo(comInfo); - simStartRequest.setSimulationInfo(simInfo); - engineConnection.getStub().withDeadlineAfter(responseDeadline, TimeUnit.MILLISECONDS) - .startSimulation(simStartRequest.build(), responseObserver); - }); - - requestQueue.add(request); - } - - /** - * Enqueue request of initial simulation step with consumers for success and error + * Enqueue request for execution based on request source with consumers for success and error * - * @param composition of the current simulated query - * @param stepConsumer for the resulting step - * @param errorConsumer for potential errors + * @param requestSource RequestSource instance to construct the request from + * @param successConsumer for returned gRPC response + * @param errorConsumer for any throwable received from the engine */ - public void enqueueSimulationStepRequest(String composition, SimulationState state, String edgeId, String componentName, int componentId, Consumer<QueryProtos.SimulationStepResponse> stepConsumer, Consumer<Throwable> errorConsumer) { - GrpcRequest request = new GrpcRequest(engineConnection -> { - StreamObserver<QueryProtos.SimulationStepResponse> responseObserver = getSimulationResponseObserver(stepConsumer, errorConsumer); - - var comInfo = ComponentProtos.ComponentsInfo.newBuilder(); - for (Component c : Ecdar.getProject().getComponents()) { - comInfo.addComponents(ComponentProtos.Component.newBuilder().setJson(c.serialize().toString()).build()); - } - - comInfo.setComponentsHash(comInfo.getComponentsList().hashCode()); - var simStepRequest = QueryProtos.SimulationStepRequest.newBuilder(); - var simInfo = QueryProtos.SimulationInfo.newBuilder() - .setComponentComposition(composition) - .setComponentsInfo(comInfo); - simStepRequest.setSimulationInfo(simInfo); - var specComp = ObjectProtos.SpecificComponent.newBuilder().setComponentName(componentName).setComponentIndex(componentId); - var edge = EcdarProtoBuf.ObjectProtos.Edge.newBuilder().setId(edgeId).setSpecificComponent(specComp); - var decision = ObjectProtos.Decision.newBuilder().setEdge(edge).setSource(state.getState()); - simStepRequest.setChosenDecision(decision); - - engineConnection.getStub().withDeadlineAfter(responseDeadline, TimeUnit.MILLISECONDS) - .takeSimulationStep(simStepRequest.build(), responseObserver); - }); + public <T> void enqueueRequest(RequestSource<T> requestSource, Consumer<T> successConsumer, Consumer<Throwable> errorConsumer) { + GrpcRequestFactory factory = new GrpcRequestFactory(() -> {}, this::setConnectionAsAvailable); + GrpcRequest request = requestSource.accept(factory, successConsumer, errorConsumer); requestQueue.add(request); } - private static StreamObserver<QueryProtos.SimulationStepResponse> getSimulationResponseObserver(Consumer<QueryProtos.SimulationStepResponse> stepConsumer, Consumer<Throwable> errorConsumer) { - return new StreamObserver<>() { - @Override - public void onNext(QueryProtos.SimulationStepResponse value) { - stepConsumer.accept(value); - } - - @Override - public void onError(Throwable t) { - errorConsumer.accept(t); - } - - @Override - public void onCompleted() {} - }; - } - /** * Signal that the EngineConnection can be used not in use and available for queries * diff --git a/src/main/java/ecdar/backend/GrpcRequestFactory.java b/src/main/java/ecdar/backend/GrpcRequestFactory.java new file mode 100644 index 00000000..e90dd78e --- /dev/null +++ b/src/main/java/ecdar/backend/GrpcRequestFactory.java @@ -0,0 +1,116 @@ +package ecdar.backend; + +import EcdarProtoBuf.ComponentProtos; +import EcdarProtoBuf.QueryProtos; +import ecdar.Ecdar; +import ecdar.abstractions.Component; +import ecdar.abstractions.Query; +import ecdar.abstractions.Decision; +import io.grpc.stub.StreamObserver; + +import java.util.UUID; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; + +public class GrpcRequestFactory { + private final Runnable onNext; + private final Consumer<EngineConnection> onFinished; + private static final int responseDeadline = 20000; + + public GrpcRequestFactory(Runnable onNext, Consumer<EngineConnection> onFinished) { + this.onNext = onNext; + this.onFinished = onFinished; + } + + public GrpcRequest create(Query query, Consumer<QueryProtos.QueryResponse> queryResponseConsumer, Consumer<Throwable> errorConsumer) { + return new GrpcRequest(engineConnection -> { + StreamObserver<QueryProtos.QueryResponse> responseObserver = getResponseStreamObserver(engineConnection, queryResponseConsumer, errorConsumer); + + var componentsInfoBuilder = BackendHelper.getComponentsInfoBuilder(query.getQuery()); + + var queryBuilder = QueryProtos.QueryRequest.newBuilder() + .setUserId(1) + .setQueryId(UUID.randomUUID().hashCode()) + .setSettings(QueryProtos.QueryRequest.Settings.newBuilder().setDisableClockReduction(true)) + .setQuery(query.getType().getQueryName() + ": " + query.getQuery()) + .setComponentsInfo(componentsInfoBuilder); + + engineConnection.getStub().withDeadlineAfter(responseDeadline, TimeUnit.MILLISECONDS) + .sendQuery(queryBuilder.build(), responseObserver); + }); + } + + public GrpcRequest create(Decision decision, Consumer<QueryProtos.SimulationStepResponse> simulationStepResponseConsumer, Consumer<Throwable> errorConsumer) { + if (decision.isInitial()) { + return createInitialSimulationStepRequest(decision, simulationStepResponseConsumer, errorConsumer); + } + + return createSimulationStepRequest(decision, simulationStepResponseConsumer, errorConsumer); + } + + private GrpcRequest createInitialSimulationStepRequest(Decision step, Consumer<QueryProtos.SimulationStepResponse> simulationStepResponseConsumer, Consumer<Throwable> errorConsumer) { + return new GrpcRequest(engineConnection -> { + StreamObserver<QueryProtos.SimulationStepResponse> responseObserver = getResponseStreamObserver(engineConnection, simulationStepResponseConsumer, errorConsumer); + ComponentProtos.ComponentsInfo.Builder comInfo = getComponentInfoBuilder(); + + var simStartRequest = QueryProtos.SimulationStartRequest.newBuilder(); + var simInfo = QueryProtos.SimulationInfo.newBuilder() + .setComponentComposition(step.composition) + .setComponentsInfo(comInfo); + simStartRequest.setSimulationInfo(simInfo); + engineConnection.getStub().withDeadlineAfter(responseDeadline, TimeUnit.MILLISECONDS) + .startSimulation(simStartRequest.build(), responseObserver); + }); + } + + private GrpcRequest createSimulationStepRequest(Decision decision, Consumer<QueryProtos.SimulationStepResponse> simulationStepResponseConsumer, Consumer<Throwable> errorConsumer) { + return new GrpcRequest(engineConnection -> { + StreamObserver<QueryProtos.SimulationStepResponse> responseObserver = getResponseStreamObserver(engineConnection, simulationStepResponseConsumer, errorConsumer); + + ComponentProtos.ComponentsInfo.Builder comInfo = getComponentInfoBuilder(); + + var simStepRequest = QueryProtos.SimulationStepRequest.newBuilder(); + var simInfo = QueryProtos.SimulationInfo.newBuilder() + .setComponentComposition(decision.composition) + .setComponentsInfo(comInfo); + simStepRequest.setSimulationInfo(simInfo); + + // ToDo NIELS: Handle below (might not be necessary) +// var specComp = ObjectProtos.ComponentInstance.newBuilder().setComponentName(decision.componentName).setComponentIndex(decision.componentId); + + simStepRequest.setChosenDecision(decision.protoDecision); + engineConnection.getStub().withDeadlineAfter(responseDeadline, TimeUnit.MILLISECONDS) + .takeSimulationStep(simStepRequest.build(), responseObserver); + }); + } + + private <T> StreamObserver<T> getResponseStreamObserver(EngineConnection connection, Consumer<T> queryResponseConsumer, Consumer<Throwable> errorConsumer) { + return new StreamObserver<>() { + @Override + public void onNext(T value) { + onNext.run(); + queryResponseConsumer.accept(value); + } + + @Override + public void onError(Throwable t) { + onFinished.accept(connection); + errorConsumer.accept(t); + } + + @Override + public void onCompleted() { + onFinished.accept(connection); + } + }; + } + + private ComponentProtos.ComponentsInfo.Builder getComponentInfoBuilder() { + var comInfo = ComponentProtos.ComponentsInfo.newBuilder(); + for (Component c : Ecdar.getProject().getComponents()) { + comInfo.addComponents(ComponentProtos.Component.newBuilder().setJson(c.serialize().toString()).build()); + } + comInfo.setComponentsHash(comInfo.getComponentsList().hashCode()); + return comInfo; + } +} diff --git a/src/main/java/ecdar/backend/SimulationHandler.java b/src/main/java/ecdar/backend/SimulationHandler.java deleted file mode 100644 index 23172535..00000000 --- a/src/main/java/ecdar/backend/SimulationHandler.java +++ /dev/null @@ -1,289 +0,0 @@ -package ecdar.backend; - -import EcdarProtoBuf.ComponentProtos; -import EcdarProtoBuf.ObjectProtos; -import EcdarProtoBuf.QueryProtos; -import EcdarProtoBuf.ObjectProtos.Decision; -import ecdar.Ecdar; -import ecdar.abstractions.*; -import ecdar.simulation.SimulationState; -import io.grpc.stub.StreamObserver; -import javafx.application.Platform; -import javafx.beans.property.ObjectProperty; -import javafx.beans.property.SimpleObjectProperty; -import javafx.collections.FXCollections; -import javafx.collections.ObservableList; -import javafx.collections.ObservableMap; -import javafx.util.Pair; - -import java.math.BigDecimal; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.concurrent.TimeUnit; - -import EcdarProtoBuf.QueryProtos.SimulationInfo; -import EcdarProtoBuf.QueryProtos.SimulationStepRequest; -import EcdarProtoBuf.QueryProtos.SimulationStepResponse; - -/** - * Handles state changes, updates of values / clocks, and keeps track of all the transitions that - * have been taken throughout a simulation. - */ -public class SimulationHandler { - public static final String QUERY_PREFIX = "Query: "; - private String composition; - public ObjectProperty<SimulationState> currentState = new SimpleObjectProperty<>(); - public ObjectProperty<SimulationState> initialState = new SimpleObjectProperty<>(); - public ObjectProperty<Edge> selectedEdge = new SimpleObjectProperty<>(); - private EcdarSystem system; - private int numberOfSteps; - private String simulationQuery; - private ArrayList<Component> simulationComponents = new ArrayList<>(); - private final ObservableMap<String, BigDecimal> simulationVariables = FXCollections.observableHashMap(); - private final ObservableMap<String, BigDecimal> simulationClocks = FXCollections.observableHashMap(); - public ObservableList<SimulationState> traceLog = FXCollections.observableArrayList(); - - private List<String> ComponentsInSimulation = new ArrayList<>(); - - /** - * Empty constructor that should be used if the system or project has not be initialized yet - */ - public SimulationHandler() {} - - public void clearComponentsInSimulation() { - ComponentsInSimulation.clear(); - } - - /** - * Initializes the values and properties in the {@link SimulationHandler}. - * Can also be used as a reset of the simulation. - * THIS METHOD DOES NOT RESET THE ENGINE, - */ - private void initializeSimulation() { - // Initialization - this.numberOfSteps = 0; - this.simulationVariables.clear(); - this.simulationClocks.clear(); - this.currentState.set(null); - this.selectedEdge.set(null); - this.traceLog.clear(); - this.system = getSystem(); - } - - /** - * Reloads the whole simulation sets the initial transitions, states, etc - */ - public void initialStep() { - initializeSimulation(); - - BackendHelper.getDefaultEngine().enqueueInitialSimulationStepRequest( - composition, - (response) -> { - // ToDo: This is temp solution to compile but should be fixed to handle ambiguity - currentState.set(new SimulationState(response.getNewDecisionPoints(0))); - Platform.runLater(() -> traceLog.add(currentState.get())); - }, - (error) -> Ecdar.showToast("Could not start simulation:\n" + error.getMessage())); - - numberOfSteps++; - } - - /** - * Take a step in the simulation. - */ - public void nextStep() { - // removes invalid states from the log when stepping forward after previewing a previous state - removeStatesFromLog(currentState.get()); - - BackendHelper.getDefaultEngine().enqueueSimulationStepRequest( - composition, - currentState.get(), - selectedEdge.get().getId(), - getComponentName(selectedEdge.get()), - getComponentIndex(selectedEdge.get()), - (response) -> { - // ToDo: This is temp solution to compile but should be fixed to handle ambiguity - currentState.set(new SimulationState(response.getNewDecisionPoints(0))); - Platform.runLater(() -> traceLog.add(currentState.get())); - }, - (error) -> Ecdar.showToast("Could not take next step in simulation\nError: " + error.getMessage())); - - numberOfSteps++; - } - - /** - * Resets the simulation to the initial location - */ - public void resetToInitialLocation() { - initialStep(); - } - - private String getComponentName(Edge edge) { - var components = Ecdar.getProject().getComponents(); - for (var component : components) { - for (var e : component.getEdges()) { - if (e.getId().equals(edge.getId())) { - return component.getName(); - } - } - } - - throw new RuntimeException("Could not find component name for edge with id " + edge.getId()); - } - - private int getComponentIndex(Edge edge) { - for (int i = 0; i < Ecdar.getProject().getComponents().size(); i++) { - if (Ecdar.getProject().getComponents().get(i).getEdges().stream().anyMatch(p -> Objects.equals(p.getId(), edge.getId()))) { - return i; - } - } - - throw new IllegalArgumentException("Edge does not belong to any component"); - } - - /** - * The number of total steps taken in the current simulation - * - * @return the number of steps - */ - public int getNumberOfSteps() { - return numberOfSteps; - } - - /** - * All the transitions taken in this simulation - * - * @return an {@link ObservableList} of all the transitions taken in this simulation so far - */ - public ObservableList<SimulationState> getTraceLog() { - return traceLog; - } - - /** - * All the available transitions in this state - * - * @return - * @return an {@link ObservableList} of all the currently available transitions in this state - */ - public ArrayList<Pair<String, String>> getAvailableTransitions() { - return currentState.get().getEnabledEdges(); - } - - /** - * All the variables connected to the current simulation. - * This does not return any clocks, if you need please use {@link SimulationHandler#getSimulationClocks()} instead - * - * @return a {@link Map} where the name (String) is the key, and a {@link BigDecimal} is the value - */ - public ObservableMap<String, BigDecimal> getSimulationVariables() { - return simulationVariables; - } - - /** - * All the clocks connected to the current simulation. - * - * @return a {@link Map} where the name (String) is the key, and a {@link BigDecimal} is the clock value - * @see SimulationHandler#getSimulationVariables() - */ - public ObservableMap<String, BigDecimal> getSimulationClocks() { - return simulationClocks; - } - - public SimulationState getCurrentState() { - return currentState.get(); - } - - /** - * The initial state of the current simulation - * - * @return the initial {@link SimulationState} of this simulation - */ - public SimulationState getInitialState() { - // ToDo: Implement - return initialState.get(); - } - - public ObjectProperty<SimulationState> initialStateProperty() { - return initialState; - } - - - public EcdarSystem getSystem() { - return system; - } - - public String getComposition() { - return composition; - } - - public void setComposition(String composition) { - this.composition = composition; - } - - public boolean isSimulationRunning() { - return false; // ToDo: Implement - } - - /** - * Removes all states from the trace log after the given state - */ - private void removeStatesFromLog(SimulationState state) { - while (traceLog.get(traceLog.size() - 1) != state) { - traceLog.remove(traceLog.size() - 1); - } - } - - public void setComponentsInSimulation(List<String> value) { - ComponentsInSimulation = value; - } - - public List<String> getComponentsInSimulation() { - return ComponentsInSimulation; - } - - public void setSimulationQuery(String query) { - simulationQuery = query; - } - - public String getSimulationQuery() { - return simulationQuery; - } - - /** - * Set list of components used in the simulation - */ - public void setSimulationComponents(ArrayList<Component> components) { - simulationComponents = components; - } - - /** - * Get list of components used in the simulation - */ - public ArrayList<Component> getSimulationComponents() { - return simulationComponents; - } - - /** - * Highlights the edges from the reachability response - */ - public void highlightReachabilityEdges(ArrayList<String> ids) { - //unhighlight all edges - for (var comp : simulationComponents) { - for (var edge : comp.getEdges()) { - edge.setIsHighlightedForReachability(false); - } - } - //highlight the edges from the reachability response - for (var comp : simulationComponents) { - for (var edge : comp.getEdges()) { - for (var id : ids) { - if (edge.getId().equals(id)) { - edge.setIsHighlightedForReachability(true); - } - } - } - } - } -} \ No newline at end of file diff --git a/src/main/java/ecdar/controllers/ComponentController.java b/src/main/java/ecdar/controllers/ComponentController.java index 49fd610f..39702255 100644 --- a/src/main/java/ecdar/controllers/ComponentController.java +++ b/src/main/java/ecdar/controllers/ComponentController.java @@ -553,7 +553,7 @@ private void initializeFinishEdgeContextMenu(final DisplayableEdge unfinishedEdg private void initializeLocationHandling() { final ListChangeListener<Location> locationListChangeListener = c -> { - if (c.next()) { + while (c.next()) { // Locations are added to the component c.getAddedSubList().forEach((loc) -> { // Check related to undo/redo stack @@ -596,7 +596,7 @@ private void initializeEdgeHandling() { // React on addition of edges to the component getComponent().getDisplayableEdges().addListener((ListChangeListener<DisplayableEdge>) c -> { - if (c.next()) { + while (c.next()) { // Edges are added to the component c.getAddedSubList().forEach(handleAddedEdge); diff --git a/src/main/java/ecdar/controllers/DecisionController.java b/src/main/java/ecdar/controllers/DecisionController.java new file mode 100644 index 00000000..0fe01e0e --- /dev/null +++ b/src/main/java/ecdar/controllers/DecisionController.java @@ -0,0 +1,34 @@ +package ecdar.controllers; + +import com.jfoenix.controls.JFXRippler; +import ecdar.abstractions.Decision; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.fxml.Initializable; +import javafx.scene.control.Label; + +import java.net.URL; +import java.util.ResourceBundle; + +public class DecisionController implements Initializable { + public JFXRippler rippler; + public Label decisionDescription; + + private final ObjectProperty<Decision> decision = new SimpleObjectProperty<>(); + + @Override + public void initialize(URL location, ResourceBundle resources) { + decision.addListener((observable, oldValue, newValue) -> { + // ToDo NIELS: Add all relevant information to the description + decisionDescription.setText(newValue.composition + ": " + newValue.action); + }); + } + + public Decision getDecision() { + return decision.get(); + } + + public void setDecision(Decision decision) { + this.decision.set(decision); + } +} diff --git a/src/main/java/ecdar/controllers/EcdarController.java b/src/main/java/ecdar/controllers/EcdarController.java index 61e05dec..da0f7918 100644 --- a/src/main/java/ecdar/controllers/EcdarController.java +++ b/src/main/java/ecdar/controllers/EcdarController.java @@ -11,6 +11,8 @@ import ecdar.mutation.MutationTestPlanPresentation; import ecdar.mutation.models.MutationTestPlan; import ecdar.presentations.*; +import ecdar.presentations.SimulationInitializationDialogPresentation; +import ecdar.presentations.SimulationPresentation; import ecdar.utility.UndoRedoStack; import ecdar.utility.colors.Color; import ecdar.utility.helpers.SelectHelper; @@ -150,7 +152,7 @@ public enum Mode { public static final ObjectProperty<Mode> currentMode = new SimpleObjectProperty<>(Mode.Editor); private static final EditorPresentation editorPresentation = new EditorPresentation(); - private static final SimulatorPresentation simulatorPresentation = new SimulatorPresentation(); + private SimulationPresentation simulationPresentation; @Override public void initialize(final URL location, final ResourceBundle resources) { @@ -166,7 +168,7 @@ public StackPane getCenter() { if (currentMode.get().equals(Mode.Editor)) { return editorPresentation.getController().canvasPane; } else { - return simulatorPresentation; + return simulationPresentation; } } @@ -178,15 +180,11 @@ public EditorPresentation getEditorPresentation() { return editorPresentation; } - public SimulatorPresentation getSimulatorPresentation() { - return simulatorPresentation; - } - public StackPane getLeftModePane() { if (currentMode.get().equals(Mode.Editor)) { return editorPresentation.getController().getLeftPane(); } else { - return simulatorPresentation.getController().getLeftPane(); + return simulationPresentation.getController().getLeftPane(); } } @@ -194,7 +192,7 @@ public StackPane getRightModePane() { if (currentMode.get().equals(Mode.Editor)) { return editorPresentation.getController().getRightPane(); } else { - return simulatorPresentation.getController().getRightPane(); + return simulationPresentation.getController().getRightPane(); } } @@ -276,7 +274,6 @@ private void initializeDialogs() { }); simulationInitializationDialog.getController().startButton.setOnMouseClicked(event -> { - SimulatorController.simulationHandler.setComposition(simulationInitializationDialog.getController().simulationComboBox.getSelectionModel().getSelectedItem()); currentMode.setValue(Mode.Simulator); simulationInitializationDialog.close(); }); @@ -527,7 +524,7 @@ private void initializeViewMenu() { return; } - if (!SimulatorController.getSimulationHandler().isSimulationRunning()) { + if (true) { // ToDo NIELS: Add check for whether the simulation is running ArrayList<String> queryOptions = Ecdar.getProject().getQueries().stream().map(Query::getQuery).collect(Collectors.toCollection(ArrayList::new)); if (!simulationInitializationDialog.getController().simulationComboBox.getItems().equals(queryOptions)) { simulationInitializationDialog.getController().simulationComboBox.getItems().setAll(queryOptions); @@ -872,9 +869,10 @@ private void enterEditorMode() { * Only enter if the mode is not already Simulator */ private void enterSimulatorMode() { - simulatorPresentation.getController().willShow(); - borderPane.setCenter(simulatorPresentation); - updateSidePanes(simulatorPresentation.getController()); + simulationPresentation = new SimulationPresentation(simulationInitializationDialog.getController().simulationComboBox.getSelectionModel().getSelectedItem()); + + borderPane.setCenter(simulationPresentation); + updateSidePanes(simulationPresentation.getController()); } private void updateSidePanes(ModeController modeController) { diff --git a/src/main/java/ecdar/controllers/EdgeController.java b/src/main/java/ecdar/controllers/EdgeController.java index 3c65cb0d..16bb0e63 100644 --- a/src/main/java/ecdar/controllers/EdgeController.java +++ b/src/main/java/ecdar/controllers/EdgeController.java @@ -7,7 +7,6 @@ import ecdar.presentations.*; import ecdar.utility.Highlightable; import ecdar.utility.UndoRedoStack; -import ecdar.utility.colors.Color; import ecdar.utility.colors.EnabledColor; import ecdar.utility.helpers.*; import ecdar.utility.keyboard.Keybind; @@ -66,7 +65,7 @@ public void initialize(final URL location, final ResourceBundle resources) { // When an edge updates highlight property, // we want to update the view to reflect current highlight property edge.get().isHighlightedProperty().addListener(v -> { - if (edge.get().getIsHighlighted()) { + if (edge.get().isHighlighted()) { this.highlight(); } else { this.unhighlight(); diff --git a/src/main/java/ecdar/controllers/LeftSimPaneController.java b/src/main/java/ecdar/controllers/LeftSimPaneController.java index a561887e..a5c64d0d 100755 --- a/src/main/java/ecdar/controllers/LeftSimPaneController.java +++ b/src/main/java/ecdar/controllers/LeftSimPaneController.java @@ -1,8 +1,10 @@ package ecdar.controllers; +import ecdar.presentations.StatePresentation; import ecdar.presentations.TracePanePresentation; -import ecdar.presentations.TransitionPanePresentation; +import ecdar.presentations.StatePanePresentation; import ecdar.utility.colors.Color; +import javafx.collections.ObservableList; import javafx.fxml.Initializable; import javafx.geometry.Insets; import javafx.scene.control.ScrollPane; @@ -16,7 +18,7 @@ public class LeftSimPaneController implements Initializable { public ScrollPane scrollPane; public VBox scrollPaneVbox; - public TransitionPanePresentation transitionPanePresentation; + public StatePanePresentation statePanePresentation; public TracePanePresentation tracePanePresentation; @Override @@ -37,11 +39,16 @@ private void initializeBackground() { * Initializes the thin border on the right side of the transition toolbar */ private void initializeRightBorder() { - transitionPanePresentation.getController().toolbar.setBorder(new Border(new BorderStroke( + statePanePresentation.getController().toolbar.setBorder(new Border(new BorderStroke( Color.GREY_BLUE.getColor(Color.Intensity.I900), BorderStrokeStyle.SOLID, CornerRadii.EMPTY, new BorderWidths(0, 1, 0, 0) ))); } + + public void setTraceLog(ObservableList<StatePresentation> traceLog) { + statePanePresentation.getController().setTraceLog(traceLog); + tracePanePresentation.getController().setTraceLog(traceLog); + } } diff --git a/src/main/java/ecdar/controllers/ModeController.java b/src/main/java/ecdar/controllers/ModeController.java index d1238118..54be9495 100644 --- a/src/main/java/ecdar/controllers/ModeController.java +++ b/src/main/java/ecdar/controllers/ModeController.java @@ -2,7 +2,7 @@ import javafx.scene.layout.StackPane; -interface ModeController { +public interface ModeController { StackPane getLeftPane(); StackPane getRightPane(); } diff --git a/src/main/java/ecdar/controllers/ProcessController.java b/src/main/java/ecdar/controllers/ProcessController.java index aa79a1b1..49d7d124 100755 --- a/src/main/java/ecdar/controllers/ProcessController.java +++ b/src/main/java/ecdar/controllers/ProcessController.java @@ -3,8 +3,6 @@ import com.jfoenix.controls.JFXRippler; import ecdar.Ecdar; import ecdar.abstractions.*; -import ecdar.presentations.ComponentPresentation; -import ecdar.presentations.ModelPresentation; import ecdar.presentations.SimEdgePresentation; import ecdar.presentations.SimLocationPresentation; import ecdar.utility.helpers.UPPAALSyntaxHighlighter; @@ -21,7 +19,6 @@ import javafx.scene.shape.Circle; import javafx.util.Duration; -import org.checkerframework.checker.units.qual.K; import org.fxmisc.richtext.LineNumberFactory; import org.fxmisc.richtext.StyleClassedTextArea; import org.kordamp.ikonli.javafx.FontIcon; @@ -31,7 +28,7 @@ import java.util.*; /** - * The controller for the process shown in the {@link SimulatorOverviewController} + * The controller for a process shown in the {@link SimulationController} */ public class ProcessController extends ModelController implements Initializable { public StackPane componentPane; diff --git a/src/main/java/ecdar/controllers/RightSimPaneController.java b/src/main/java/ecdar/controllers/RightSimPaneController.java index 5b86fc1b..06a9eca5 100755 --- a/src/main/java/ecdar/controllers/RightSimPaneController.java +++ b/src/main/java/ecdar/controllers/RightSimPaneController.java @@ -1,20 +1,116 @@ package ecdar.controllers; +import ecdar.abstractions.Edge; +import ecdar.abstractions.Decision; +import ecdar.presentations.DecisionPresentation; +import ecdar.utility.colors.Color; +import javafx.application.Platform; +import javafx.collections.FXCollections; +import javafx.collections.ListChangeListener; +import javafx.collections.ObservableList; import javafx.fxml.Initializable; +import javafx.geometry.Insets; import javafx.scene.layout.*; import java.net.URL; +import java.util.List; import java.util.ResourceBundle; +import java.util.function.Consumer; +import java.util.stream.Collectors; /** * Controller class for the right pane in the simulator */ public class RightSimPaneController implements Initializable { public StackPane root; - public VBox scrollPaneVbox; -// public QueryPaneElementPresentation queryPaneElement; + public VBox availableDecisionsVBox; + + private Consumer<Decision> onDecisionSelected; + private ObservableList<DecisionPresentation> availableDecisions = FXCollections.observableArrayList(); @Override public void initialize(URL location, ResourceBundle resources) { + initializeBackground(); + initializeLeftBorder(); + } + + /** + * Sets the background color of the ScrollPane Vbox + */ + private void initializeBackground() { + availableDecisionsVBox.setBackground(new Background(new BackgroundFill( + Color.GREY.getColor(Color.Intensity.I200), + CornerRadii.EMPTY, + Insets.EMPTY + ))); + } + + /** + * Initializes the thin border on the left side of the querypane toolbar + */ + private void initializeLeftBorder() { + root.setBorder(new Border(new BorderStroke( + Color.GREY_BLUE.getColor(Color.Intensity.I900), + BorderStrokeStyle.SOLID, + CornerRadii.EMPTY, + new BorderWidths(0, 0, 0, 1) + ))); + } + + private void initializeDecisionHandling() { + availableDecisions.addListener((ListChangeListener<DecisionPresentation>) c -> { + while (c.next()) { + c.getAddedSubList().forEach(decisionPresentation -> { + // Request next step when decision is clicked + decisionPresentation.setOnMouseClicked(event -> { + onDecisionSelected.accept(decisionPresentation.getController().getDecision()); + }); + + Platform.runLater(() -> { + availableDecisionsVBox.getChildren().add(decisionPresentation); + }); + }); + + c.getRemoved().forEach(decisionPresentation -> Platform.runLater(() -> availableDecisionsVBox.getChildren().remove(decisionPresentation))); + } + }); + + availableDecisions.forEach(d -> { + // Request next step when decision is clicked + d.setOnMouseClicked(event -> { + onDecisionSelected.accept(d.getController().getDecision()); + }); + + Platform.runLater(() -> { + availableDecisionsVBox.getChildren().add(d); + }); + }); } + + public void setOnDecisionSelected(Consumer<Decision> decisionSelected) { + onDecisionSelected = decisionSelected; + } + + public void setDecisionsList(ObservableList<DecisionPresentation> decisions) { + availableDecisions = decisions; + initializeDecisionHandling(); + } + + protected List<Decision> getDecisions() { + return availableDecisions.stream() + .map(decisionPresentation -> decisionPresentation.getController().getDecision()) + .collect(Collectors.toList()); + } + +// /** +// * Get all enable edges in this state +// * +// * @return list of pairs of the component instance connected to each edge +// */ +// public List<Edge> getEnabledEdges() { +// return getDecisions().stream() +// .map(decision -> decision.edgeIds) +// .flatMap(List::stream) +// .collect(Collectors.toList()); +// } } diff --git a/src/main/java/ecdar/controllers/SimEdgeController.java b/src/main/java/ecdar/controllers/SimEdgeController.java index d2e35a8e..64ae1cc7 100755 --- a/src/main/java/ecdar/controllers/SimEdgeController.java +++ b/src/main/java/ecdar/controllers/SimEdgeController.java @@ -8,8 +8,8 @@ import ecdar.presentations.Link; import ecdar.presentations.NailPresentation; import ecdar.presentations.SimNailPresentation; +import ecdar.presentations.SimulationPresentation; import ecdar.utility.Highlightable; -import ecdar.utility.colors.Color; import ecdar.utility.colors.EnabledColor; import ecdar.utility.helpers.BindingHelper; import ecdar.utility.helpers.Circular; @@ -37,7 +37,7 @@ import java.util.function.Consumer; /** - * The controller for the edge shown in the {@link ecdar.presentations.SimulatorOverviewPresentation} + * The controller for the edge shown in the {@link SimulationPresentation} */ public class SimEdgeController implements Initializable, Highlightable { private final ObservableList<Link> links = FXCollections.observableArrayList(); @@ -69,7 +69,7 @@ public void initialize(final URL location, final ResourceBundle resources) { // When an edge updates highlight property, // we want to update the view to reflect current highlight property edge.get().isHighlightedProperty().addListener(v -> { - if (edge.get().getIsHighlighted()) { + if (edge.get().isHighlighted()) { this.highlight(); } else { this.unhighlight(); diff --git a/src/main/java/ecdar/controllers/SimLocationController.java b/src/main/java/ecdar/controllers/SimLocationController.java index 1bd0d1d8..01f0804b 100755 --- a/src/main/java/ecdar/controllers/SimLocationController.java +++ b/src/main/java/ecdar/controllers/SimLocationController.java @@ -1,12 +1,14 @@ package ecdar.controllers; +import EcdarProtoBuf.ObjectProtos; import com.jfoenix.controls.JFXPopup; +import ecdar.Ecdar; import ecdar.abstractions.*; -import ecdar.backend.SimulationHandler; import ecdar.presentations.DropDownMenu; import ecdar.presentations.SimLocationPresentation; import ecdar.presentations.SimTagPresentation; -import ecdar.simulation.SimulationState; +import ecdar.abstractions.State; +import ecdar.presentations.SimulationPresentation; import ecdar.utility.colors.EnabledColor; import javafx.beans.property.DoubleProperty; import javafx.beans.property.ObjectProperty; @@ -19,13 +21,16 @@ import javafx.scene.shape.Path; import javafx.scene.input.MouseButton; import javafx.scene.input.MouseEvent; +import javafx.util.Pair; + +import java.util.ArrayList; import java.util.function.Consumer; import java.net.URL; import java.util.ResourceBundle; /** - * The controller of a location shown in the {@link ecdar.presentations.SimulatorOverviewPresentation} + * The controller of a location shown in the {@link SimulationPresentation} */ public class SimLocationController implements Initializable { private final ObjectProperty<Location> location = new SimpleObjectProperty<>(); @@ -43,7 +48,6 @@ public class SimLocationController implements Initializable { public Line nameTagLine; public Line invariantTagLine; private DropDownMenu dropDownMenu; - private SimulationHandler simulationHandler; public static String getSimLocationReachableQuery(final Location endLocation, final Component component, final String query) { return getSimLocationReachableQuery(endLocation, component, query, null); @@ -55,7 +59,7 @@ public static String getSimLocationReachableQuery(final Location endLocation, fi * @param endLocation The location which should be checked for reachability * @return A reachability query string */ - public static String getSimLocationReachableQuery(final Location endLocation, final Component component, final String query, final SimulationState state) { + public static String getSimLocationReachableQuery(final Location endLocation, final Component component, final String query, final State state) { var stringBuilder = new StringBuilder(); // append simulation query @@ -66,7 +70,7 @@ public static String getSimLocationReachableQuery(final Location endLocation, fi // ToDo: append start location here if (state != null){ - stringBuilder.append(getStartStateString(state)); + stringBuilder.append(getInitialStateString(state)); stringBuilder.append(";"); } @@ -78,23 +82,35 @@ public static String getSimLocationReachableQuery(final Location endLocation, fi return stringBuilder.toString(); } - private static String getStartStateString(SimulationState state) { - var stringBuilder = new StringBuilder(); + /** + * ToDo NIELS: Determine if this is actually what it does + * Returns a string representation of an array of initial location IDs for all components in the simulation + * + * @param state + * @return + */ + private static String getInitialStateString(State state) { + var initialStateStringBuilder = new StringBuilder(); + var locations = new ArrayList<Pair<ObjectProtos.ComponentInstance, String>>(); + + state.consumeLeafLocations((leafLocation -> locations.add(new Pair<>(leafLocation.getComponentInstance(), leafLocation.getId())))); // append locations - var locations = state.getLocations(); - stringBuilder.append("["); + initialStateStringBuilder.append("["); var appendLocationWithSeparator = false; - for(var componentName : SimulatorController.getSimulationHandler().getComponentsInSimulation()){ + + // ToDO NIELS: Determine how to process this, if it is indeed the initial locations + for(var component : SimulationController.getSimulatedComponents()){ var locationFound = false; - for(var location:locations){ - if (location.getKey().equals(componentName)){ + for(var location : locations){ + if (location.getKey().getComponentName().equals(component.getName())){ if (appendLocationWithSeparator){ - stringBuilder.append("," + location.getValue()); + initialStateStringBuilder.append(",") + .append(location.getValue()); } else{ - stringBuilder.append(location.getValue()); + initialStateStringBuilder.append(location.getValue()); } locationFound = true; } @@ -105,13 +121,13 @@ private static String getStartStateString(SimulationState state) { } appendLocationWithSeparator = true; } - stringBuilder.append("]"); + initialStateStringBuilder.append("]"); - // append clock values + // ToDo: append clock values var clocks = state.getSimulationClocks(); - stringBuilder.append("()"); + initialStateStringBuilder.append("()"); - return stringBuilder.toString(); + return initialStateStringBuilder.toString(); } private static String getEndStateString(String componentName, String endLocationId) { @@ -120,11 +136,12 @@ private static String getEndStateString(String componentName, String endLocation stringBuilder.append("["); var appendLocationWithSeparator = false; - for (var component : SimulatorController.getSimulationHandler().getComponentsInSimulation()) + for (var component : SimulationController.getSimulatedComponents()) { - if (component.equals(componentName)){ + if (component.getName().equals(componentName)){ if (appendLocationWithSeparator){ - stringBuilder.append("," + endLocationId); + stringBuilder.append(",") + .append(endLocationId); } else{ stringBuilder.append(endLocationId); @@ -161,8 +178,6 @@ public void initialize(final URL location, final ResourceBundle resources) { // Scale x and y 1:1 (based on the x-scale) scaleContent.scaleYProperty().bind(scaleContent.scaleXProperty()); initializeMouseControls(); - - simulationHandler = SimulatorController.getSimulationHandler(); } private void initializeMouseControls() { @@ -188,9 +203,20 @@ private void initializeMouseControls() { public void initializeDropDownMenu(){ dropDownMenu = new DropDownMenu(root); + String composition; + State currentState; + + try { + composition = SimulationController.getComposition(); + currentState = SimulationController.getCurrentState(); + } catch (NullPointerException e) { + Ecdar.showToast("Unable to inittialize dropdown due to null simulation"); + return; + } + dropDownMenu.addClickableListElement("Is " + getLocation().getId() + " reachable from initial state?", event -> { // Generate the query from the backend - final String reachabilityQuery = getSimLocationReachableQuery(getLocation(), getComponent(), simulationHandler.getSimulationQuery()); + final String reachabilityQuery = getSimLocationReachableQuery(getLocation(), getComponent(), composition); // Add proper comment final String reachabilityComment = "Is " + getLocation().getMostDescriptiveIdentifier() + " reachable from initial state?"; @@ -206,8 +232,8 @@ public void initializeDropDownMenu(){ }); dropDownMenu.addClickableListElement("Is " + getLocation().getId() + " reachable from current locations?", event -> { - // Generate the query from the backend - final String reachabilityQuery = getSimLocationReachableQuery(getLocation(), getComponent(), simulationHandler.getSimulationQuery(), simulationHandler.getCurrentState()); + // Generate the query from the backend ToDo NIELS: Remove static methods + final String reachabilityQuery = getSimLocationReachableQuery(getLocation(), getComponent(), composition, currentState); // Add proper comment final String reachabilityComment = "Is " + getLocation().getMostDescriptiveIdentifier() + " reachable from current locations?"; @@ -222,6 +248,7 @@ public void initializeDropDownMenu(){ dropDownMenu.hide(); }); } + public Location getLocation() { return location.get(); } diff --git a/src/main/java/ecdar/controllers/SimNailController.java b/src/main/java/ecdar/controllers/SimNailController.java index ca4546cc..bff7bad1 100755 --- a/src/main/java/ecdar/controllers/SimNailController.java +++ b/src/main/java/ecdar/controllers/SimNailController.java @@ -6,7 +6,6 @@ import ecdar.abstractions.Nail; import ecdar.presentations.SimNailPresentation; import ecdar.presentations.SimTagPresentation; -import ecdar.utility.colors.Color; import ecdar.utility.colors.EnabledColor; import javafx.beans.property.DoubleProperty; import javafx.beans.property.ObjectProperty; @@ -105,7 +104,6 @@ public EnabledColor getColor() { return getComponent().getColor(); } - public DoubleProperty xProperty() { return root.layoutXProperty(); } diff --git a/src/main/java/ecdar/controllers/SimulationController.java b/src/main/java/ecdar/controllers/SimulationController.java new file mode 100644 index 00000000..935b3bd6 --- /dev/null +++ b/src/main/java/ecdar/controllers/SimulationController.java @@ -0,0 +1,416 @@ +package ecdar.controllers; + +import EcdarProtoBuf.ObjectProtos; +import EcdarProtoBuf.QueryProtos; +import ecdar.Ecdar; +import ecdar.backend.BackendHelper; +import ecdar.presentations.*; +import javafx.application.Platform; +import javafx.collections.FXCollections; +import javafx.collections.ListChangeListener; + +import ecdar.abstractions.*; +import ecdar.abstractions.State; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.collections.ObservableMap; +import javafx.fxml.Initializable; +import javafx.geometry.Insets; +import javafx.scene.Group; +import javafx.scene.control.ScrollPane; +import javafx.scene.layout.FlowPane; +import javafx.scene.layout.StackPane; + +import java.net.URL; +import java.util.*; +import java.util.function.Consumer; +import java.util.stream.Collectors; + +public class SimulationController implements ModeController, Initializable { + public StackPane root; + public StackPane toolbar; + public ScrollPane scrollPane; + public Group groupContainer; + public FlowPane processContainer; + + public final LeftSimPanePresentation leftSimPane = new LeftSimPanePresentation(); + public final RightSimPanePresentation rightSimPane = new RightSimPanePresentation(this::nextStep); + + private static final ObjectProperty<Simulation> activeSimulation = new SimpleObjectProperty<>(null); + private static final ObjectProperty<State> selectedState = new SimpleObjectProperty<>(); + + /** + * The amount that is going be zoomed in/out for each press on + or - + */ + private final double SCALE_DELTA = 1.1; + + /** + * The max that the user can zoom in + */ + private static final double MAX_ZOOM_IN = 1.6; + + /** + * The max that the user can zoom in + */ + private static final double MAX_ZOOM_OUT = 0.5; + + /** + * Offset such that the view does not overlap with the scroll bar on the right-hand side. + */ + private static final int SCROLLPANE_OFFSET = 20; + + /** + * Is true if a reset of the zoom have been requested, false if not. + */ + private boolean resetZoom = false; + private boolean isMaxZoomInReached = false; + private boolean isMaxZoomOutReached = false; + + private final ObservableMap<String, ProcessPresentation> componentNameProcessPresentationMap = FXCollections.observableHashMap(); + + // ToDo NIELS: Remove static + public static State getCurrentState() throws NullPointerException { + return activeSimulation.get().currentState.get(); + } + + public static List<Component> getSimulatedComponents() { + return activeSimulation.get().simulatedComponents; + } + + @Override + public void initialize(final URL location, final ResourceBundle resources) { + //In case that the processContainer gets moved around we have to keep in into place. + initializeProcessContainer(); + initializeWindowResizing(); + initializeZoom(); + } + + /** + * Initializes the {@link #processContainer} with its correct styling, and placement on the view. + * It also adds a {@link ListChangeListener} on the list of simulated components where it adds the + * {@link Component}<code>s</code> which are needed to the <code>processContainer</code>. + */ + private void initializeProcessContainer() { + processContainer.translateXProperty().addListener((observable, oldValue, newValue) -> { + processContainer.setTranslateX(0); + }); + //Sets the space between the processes + processContainer.setHgap(10); + processContainer.setVgap(10); + + // padding to the scrollpane + processContainer.setPadding(new Insets(5)); + } + + /** + * Initializes the zoom functionality in {@link #processContainer} + */ + private void initializeZoom() { + processContainer.scaleXProperty().addListener((observable, oldValue, newValue) -> { + if (newValue.doubleValue() > MAX_ZOOM_IN) isMaxZoomInReached = true; + if (newValue.doubleValue() < MAX_ZOOM_OUT) isMaxZoomOutReached = true; + + handleWidthOnScale(oldValue, newValue); + }); + } + + /** + * Initializes listener for change of width in {@link #scrollPane} which also affects {@link #processContainer} <br /> + * This does also take the zooming into account when doing the resizing. + */ + private void initializeWindowResizing() { + scrollPane.widthProperty().addListener((observable, oldValue, newValue) -> { + final double width = (newValue.doubleValue()) * (1 + (1 - processContainer.getScaleX())); + if (processContainer.getScaleX() > 1) { //Zoomed in + processContainer.setMinWidth(width); + processContainer.setMaxWidth(width); + } else if (processContainer.getScaleX() < 1) { //Zoomed out + processContainer.setMinWidth(width); + processContainer.setMaxWidth(width); + final double deltaWidth = newValue.doubleValue() - groupContainer.layoutBoundsProperty().get().getWidth(); + processContainer.setMinWidth(processContainer.getWidth() + (deltaWidth - SCROLLPANE_OFFSET) * (1 + (1 - processContainer.getScaleX()))); + processContainer.setMaxWidth(processContainer.getWidth() + (deltaWidth - SCROLLPANE_OFFSET) * (1 + (1 - processContainer.getScaleX()))); + } else { // Reset + processContainer.setMinWidth(newValue.doubleValue() - SCROLLPANE_OFFSET); + processContainer.setMaxWidth(newValue.doubleValue() - SCROLLPANE_OFFSET); + } + }); + } + + /** + * Clears the {@link #processContainer} and the {@link #componentNameProcessPresentationMap}. + */ + private void clearOverview() { + processContainer.getChildren().clear(); + componentNameProcessPresentationMap.clear(); + } + + /** + * Handles the scaling of the width of the {@link #processContainer} + * + * @param oldValue the width of {@link #scrollPane} before the change + * @param newValue the width of {@link #scrollPane} after the change + */ + private void handleWidthOnScale(final Number oldValue, final Number newValue) { + if (resetZoom) { //Zoom reset + resetZoom = false; + processContainer.setMinWidth(scrollPane.getWidth() - SCROLLPANE_OFFSET); + processContainer.setMaxWidth(scrollPane.getWidth() - SCROLLPANE_OFFSET); + } else if (oldValue.doubleValue() > newValue.doubleValue()) { //Zoom in + resetZoom = false; + processContainer.setMinWidth(Math.round(processContainer.getWidth() * SCALE_DELTA)); + processContainer.setMaxWidth(Math.round(processContainer.getWidth() * SCALE_DELTA)); + } else { // Zoom out + resetZoom = false; + processContainer.setMinWidth(Math.round(processContainer.getWidth() * (1 / SCALE_DELTA))); + processContainer.setMaxWidth(Math.round(processContainer.getWidth() * (1 / SCALE_DELTA))); + } + } + + /** + * Unhighlights all processes + */ + public void unhighlightProcesses() { + for (final ProcessPresentation presentation : getProcessPresentations()) { + presentation.getController().unhighlightProcess(); + presentation.showActive(); + } + } + + private List<ProcessPresentation> getProcessPresentations() { + return new ArrayList<>(componentNameProcessPresentationMap.values()); + } + + /** + * Finds the processes for the input locations in the input {@link State} and highlights the locations. + * + * @param state The state with the locations to highlight + */ + public void highlightProcessState(final State state) { + Consumer<ObjectProtos.LeafLocation> leafLocationConsumer = + (leaf) -> componentNameProcessPresentationMap.get(leaf.getComponentInstance().getComponentName()) + .getController().highlightLocation(leaf.getId()); + + Platform.runLater(() -> state.consumeLeafLocations(leafLocationConsumer)); + } + + public void highlightAvailableEdgesFromDecisions(List<DecisionPresentation> availableDecisions) { + List<String> edges = availableDecisions.stream() + .map(decisionPresentation -> decisionPresentation.getController().getDecision().edgeIds) + .flatMap(List::stream) + .collect(Collectors.toList()); + + // Remove previous highlighting of edges + componentNameProcessPresentationMap.values().forEach(p -> p.getController() + .getComponent().getEdges() + .forEach(e -> e.setIsHighlighted(edges.contains(e.getId())))); + } + + /** + * Reloads the whole simulation sets the initial transitions, states, etc + */ + public void initialStep(String composition) { + BackendHelper.getDefaultEngine().enqueueRequest(new Decision(composition), + (response) -> Platform.runLater(() -> initializeActiveSimulation(response, composition)), + (error) -> Ecdar.showToast("Could not start simulation:\n" + error.getMessage())); + } + + private void initializeActiveSimulation(QueryProtos.SimulationStepResponse response, String composition) { + State newState = createStateFromResponse(response); + + Simulation newSimulation = new Simulation(composition, newState); + activeSimulation.set(newSimulation); + + newSimulation.simulatedComponents.forEach(component -> { + var processPresentation = new ProcessPresentation(component); + componentNameProcessPresentationMap.put(component.getName(), processPresentation); + processContainer.getChildren().addAll(processPresentation); + }); + + leftSimPane.getController().setTraceLog(newSimulation.traceLog); + rightSimPane.getController().setDecisionsList(newSimulation.availableDecisions); + + // Update highlighting when state changes + newSimulation.currentState.addListener(observable -> { + updateSimulationVariablesAndClocks(); + updateHighlighting(); + highlightTraceStates(); + }); + + updateSimulationState(response.getNewDecisionPointsList()); + } + + private void updateSimulationState(List<ObjectProtos.Decision> availableDecisions) { + Platform.runLater(() -> { + getActiveSimulation().addStateToTraceLog(getActiveSimulation().currentState.get(), this::previewStep); + + updateSimulationVariablesAndClocks(); + updateHighlighting(); + highlightTraceStates(); + + getActiveSimulation().availableDecisions.clear(); + if (availableDecisions.isEmpty()) { + // If no edges are available in any of the returned decisions + Ecdar.showToast("No available decisions."); + } else { + getActiveSimulation().addDecisionsFromProto(availableDecisions); + } + }); + } + + /** + * Setup listeners for displaying clock and variable values on the {@link ProcessPresentation} + */ + private void updateSimulationVariablesAndClocks() { + getActiveSimulation().variables.forEach((var, val) -> { + if (var.equals("t(0)")) {// As t(0) does not belong to any process + final String[] spittedString = var.split("\\."); + // If the process containing the var is not there we just skip it + if (spittedString.length > 0 && componentNameProcessPresentationMap.size() > 0) { + componentNameProcessPresentationMap.entrySet().stream() + .filter(processPair -> processPair.getKey().equals(spittedString[0])) + .findFirst().get().getValue().getController().getVariables() + .put(spittedString[1], val); + } + } + }); + + if (componentNameProcessPresentationMap.size() == 0) return; + getActiveSimulation().clocks.forEach((clock, val) -> { + if (!clock.equals("t(0)")) {// As t(0) does not belong to any process + final String[] spittedString = clock.split("\\."); + // If the process containing the clock is not there we just skip it + if (spittedString.length > 0 && componentNameProcessPresentationMap.size() > 0) { + componentNameProcessPresentationMap.entrySet().stream() + .filter(processPair -> processPair.getKey().equals(spittedString[0])) + .findFirst().get().getValue().getController().getClocks() + .put(spittedString[1], val); + } + } + }); + } + + /** + * Initializer method to set up listeners that handle highlighting when selected/current state/transition changes + */ + private void updateHighlighting() { + highlightAvailableEdgesFromDecisions(getActiveSimulation().availableDecisions); + + Platform.runLater(() -> { + unhighlightProcesses(); + highlightProcessState(getActiveSimulation().currentState.get()); + }); + } + + /** + * Initializes the fading of states in the trace list when a state is previewed + */ + private void highlightTraceStates() { + var traceListStates = leftSimPane.getController().tracePanePresentation.getController().traceList.getChildren(); + + var activeStatePresentation = traceListStates.stream() + .filter(n -> ((StatePresentation) n) + .getController().getState() + .equals(getActiveSimulation().currentState.get())) + .findFirst().orElse(null); + + if (activeStatePresentation == null || traceListStates.get(traceListStates.size() - 1).equals(activeStatePresentation)) + return; + + traceListStates.forEach(trace -> trace.setOpacity(1)); + int i = traceListStates.size() - 1; + while (traceListStates.get(i) != activeStatePresentation) { + traceListStates.get(i).setOpacity(0.4); + i--; + } + } + + private Simulation getActiveSimulation() { + return activeSimulation.get(); + } + + /** + * Take a step in the simulation. + */ + public void nextStep(Decision decision) { + // removes invalid states from the log when stepping forward after previewing a previous state + removeStatesFromLog(getActiveSimulation().traceLog.filtered(statePresentation -> statePresentation.getController().getState() == getActiveSimulation().currentState.get()).stream().findFirst().orElse(null)); + + BackendHelper.getDefaultEngine().enqueueRequest(decision, + (response) -> { + // ToDo: This is temp solution to compile but should be fixed to handle ambiguity + State newState = createStateFromResponse(response); + getActiveSimulation().currentState.set(newState); + updateSimulationState(response.getNewDecisionPointsList()); + }, + + (error) -> Ecdar.showToast("Could not take next step in simulation\nError: " + error.getMessage())); + } + + /** + * Removes all states from the trace log after the given state + */ + private void removeStatesFromLog(StatePresentation statePresentation) { + if (statePresentation == null) return; + + var newLastStateIndex = getActiveSimulation().traceLog.indexOf(statePresentation); + getActiveSimulation().traceLog.remove(newLastStateIndex + 1, getActiveSimulation().traceLog.size()); + } + + private void previewStep(final StatePresentation statePresentation) throws NullPointerException { + getActiveSimulation().currentState.set(statePresentation.getController().getState()); + } + + /** + * Resets the current simulation, and prepares for a new simulation by clearing the + * {@link SimulationController#processContainer} and adding the processes of the new simulation. + */ + public void resetSimulation(String queryToSimulate) { + clearOverview(); + initialStep(queryToSimulate); + } + + private State createStateFromResponse(QueryProtos.SimulationStepResponse response) { + return new State(response.getNewDecisionPoints(0).getSource()); // ToDo NIELS: Each source is only a subset, we should combine them to the full state + } + + /** + * Highlights the edges from the reachability response + */ + public void highlightReachabilityEdges(ArrayList<String> ids) throws NullPointerException { + //unhighlight all edges + for (var comp : getActiveSimulation().simulatedComponents) { + for (var edge : comp.getEdges()) { + edge.setIsHighlightedForReachability(false); + } + } + //highlight the edges from the reachability response + for (var comp : getActiveSimulation().simulatedComponents) { + for (var edge : comp.getEdges()) { + for (var id : ids) { + if (edge.getId().equals(id)) { + edge.setIsHighlightedForReachability(true); + } + } + } + } + } + + public static void setSelectedState(State selectedState) { + SimulationController.selectedState.set(selectedState); + } + + public static String getComposition() { + return activeSimulation.get().composition; + } + + @Override + public StackPane getLeftPane() { + return leftSimPane; + } + + @Override + public StackPane getRightPane() { + return rightSimPane; + } +} diff --git a/src/main/java/ecdar/controllers/SimulationInitializationDialogController.java b/src/main/java/ecdar/controllers/SimulationInitializationDialogController.java index 008d7001..a3d0d905 100644 --- a/src/main/java/ecdar/controllers/SimulationInitializationDialogController.java +++ b/src/main/java/ecdar/controllers/SimulationInitializationDialogController.java @@ -2,45 +2,16 @@ import com.jfoenix.controls.JFXButton; import com.jfoenix.controls.JFXComboBox; -import ecdar.backend.SimulationHandler; import javafx.fxml.Initializable; import java.net.URL; import java.util.*; -import java.util.regex.Matcher; -import java.util.regex.Pattern; public class SimulationInitializationDialogController implements Initializable { public JFXComboBox<String> simulationComboBox; public JFXButton cancelButton; public JFXButton startButton; - private SimulationHandler simulationHandler; - public void initialize(URL location, ResourceBundle resources) { - simulationHandler = SimulatorController.getSimulationHandler(); - } - - /** - * Function extracts data from simulation initialization (query and list of components to simulation) - * and saves it - */ - public void setSimulationData(){ - // set simulation query - simulationHandler.setSimulationQuery(simulationComboBox.getSelectionModel().getSelectedItem()); - - // set list of components involved in simulation - simulationHandler.clearComponentsInSimulation(); - // pattern filters out all components by ignoring operators. - Pattern pattern = Pattern.compile("([\\w]*)", Pattern.CASE_INSENSITIVE); - Matcher matcher = pattern.matcher(simulationHandler.getSimulationQuery()); - List<String> listOfComponentsToSimulate = new ArrayList<>(); - //Adds all found components to list. - while(matcher.find()){ - if(matcher.group().length() != 0) { - listOfComponentsToSimulate.add(matcher.group()); - } - } - simulationHandler.setComponentsInSimulation(listOfComponentsToSimulate); } } diff --git a/src/main/java/ecdar/controllers/SimulatorController.java b/src/main/java/ecdar/controllers/SimulatorController.java deleted file mode 100644 index 94205cde..00000000 --- a/src/main/java/ecdar/controllers/SimulatorController.java +++ /dev/null @@ -1,151 +0,0 @@ -package ecdar.controllers; - -import ecdar.Ecdar; -import ecdar.abstractions.*; -import ecdar.backend.SimulationHandler; -import ecdar.presentations.LeftSimPanePresentation; -import ecdar.presentations.RightSimPanePresentation; -import ecdar.presentations.SimulatorOverviewPresentation; -import ecdar.simulation.SimulationState; -import javafx.beans.property.DoubleProperty; -import javafx.beans.property.ObjectProperty; -import javafx.beans.property.SimpleDoubleProperty; -import javafx.beans.property.SimpleObjectProperty; -import javafx.fxml.Initializable; -import javafx.scene.layout.StackPane; - -import java.net.URL; -import java.util.ArrayList; -import java.util.List; -import java.util.ResourceBundle; - -public class SimulatorController implements ModeController, Initializable { - public StackPane root; - public SimulatorOverviewPresentation overviewPresentation; - public StackPane toolbar; - - public final LeftSimPanePresentation leftSimPane = new LeftSimPanePresentation(); - public final RightSimPanePresentation rightSimPane = new RightSimPanePresentation(); - - public static SimulationHandler simulationHandler = new SimulationHandler(); - private boolean firstTimeInSimulator; - private final static DoubleProperty width = new SimpleDoubleProperty(), - height = new SimpleDoubleProperty(); - private static final ObjectProperty<SimulationState> selectedState = new SimpleObjectProperty<>(); - - @Override - public void initialize(URL location, ResourceBundle resources) { - root.widthProperty().addListener((observable, oldValue, newValue) -> width.setValue(newValue)); - root.heightProperty().addListener((observable, oldValue, newValue) -> height.setValue(newValue)); - firstTimeInSimulator = true; - } - - public static SimulationHandler getSimulationHandler() { return simulationHandler; } - - public static void setSimulationHandler(SimulationHandler simHandler) { - simulationHandler = simHandler; - } - - /** - * Prepares the simulator to be shown.<br /> - * It also prepares the processes to be shown in the {@link SimulatorOverviewPresentation} by: <br /> - * - Building the system if it has been updated or have never been created.<br /> - * - Adding the components which are going to be used in the simulation to - */ - public void willShow() { - // If the user left a trace, continue from that trace - boolean shouldSimulationBeReset = simulationHandler.traceLog.size() < 2; - - // If the composition is not the same as previous simulation, reset the simulation - if (!(overviewPresentation.getController().getComponentObservableList().hashCode() == - findComponentsInCurrentSimulation(simulationHandler.getComponentsInSimulation()).hashCode())) { - shouldSimulationBeReset = true; - } - - if (shouldSimulationBeReset || firstTimeInSimulator || simulationHandler.currentState.get() == null) { - resetSimulation(); - simulationHandler.initialStep(); - } - - overviewPresentation.getController().addProcessesToGroup(); - - // If the simulation continues, highligt the current state and available edges - if (simulationHandler.currentState.get() != null && !shouldSimulationBeReset) { - overviewPresentation.getController().highlightProcessState(simulationHandler.currentState.get()); - overviewPresentation.getController().highlightAvailableEdges(simulationHandler.currentState.get()); - } - - } - - /** - * Resets the current simulation, and prepares for a new simulation by clearing the - * {@link SimulatorOverviewController#processContainer} and adding the processes of the new simulation. - */ - private void resetSimulation() { - List<Component> listOfComponentsForSimulation = findComponentsInCurrentSimulation(simulationHandler.getComponentsInSimulation()); - overviewPresentation.getController().clearOverview(); - overviewPresentation.getController().getComponentObservableList().clear(); - overviewPresentation.getController().getComponentObservableList().addAll(listOfComponentsForSimulation); - firstTimeInSimulator = false; - } - - /** - * Finds the components that are used in the current simulation by looking at the components found in - * Ecdar.getProject.getComponents() and compares them to the components found in the queryComponents list - * - * @return all the components used in the current simulation - */ - private List<Component> findComponentsInCurrentSimulation(List<String> queryComponents) { - //Show components from the system - List<Component> components = Ecdar.getProject().getComponents(); - - //Matches query components against with existing components and adds them to simulation - List<Component> SelectedComponents = new ArrayList<>(); - for(Component comp : components) { - for(String componentInQuery : queryComponents) { - if((comp.getName().equals(componentInQuery))) { - Component temp = new Component(comp.serialize()); - SelectedComponents.add(temp); - } - } - } - simulationHandler.setSimulationComponents((ArrayList<Component>) SelectedComponents); - return SelectedComponents; - } - - /** - * Resets the simulation and prepares the view for showing the new simulation to the user - */ - public void resetCurrentSimulation() { - overviewPresentation.getController().removeProcessesFromGroup(); - resetSimulation(); - simulationHandler.resetToInitialLocation(); - overviewPresentation.getController().addProcessesToGroup(); - } - - public void willHide() { - overviewPresentation.getController().removeProcessesFromGroup(); - } - - public static DoubleProperty getWidthProperty() { - return width; - } - - public static DoubleProperty getHeightProperty() { - return height; - } - - public static void setSelectedState(SimulationState selectedState) { - SimulatorController.selectedState.set(selectedState); - } - - @Override - public StackPane getLeftPane() { - return leftSimPane; - } - - @Override - public StackPane getRightPane() { - return rightSimPane; - } -} diff --git a/src/main/java/ecdar/controllers/SimulatorOverviewController.java b/src/main/java/ecdar/controllers/SimulatorOverviewController.java deleted file mode 100644 index 0f2bf7f8..00000000 --- a/src/main/java/ecdar/controllers/SimulatorOverviewController.java +++ /dev/null @@ -1,332 +0,0 @@ -package ecdar.controllers; - -import ecdar.abstractions.*; -import ecdar.backend.SimulationHandler; -import ecdar.presentations.ProcessPresentation; -import ecdar.simulation.SimulationState; -import ecdar.simulation.Transition; -import javafx.collections.FXCollections; -import javafx.collections.ListChangeListener; -import javafx.collections.ObservableList; -import javafx.beans.InvalidationListener; -import javafx.collections.*; -import javafx.fxml.Initializable; -import javafx.geometry.Insets; -import javafx.scene.Group; -import javafx.scene.control.ScrollPane; -import javafx.scene.layout.*; -import javafx.util.Pair; - -import java.net.URL; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Map; -import java.util.ResourceBundle; - -/** - * The controller of the middle part of the simulator. - * It is here where processes of a simulation will be shown. - */ -public class SimulatorOverviewController implements Initializable { - public AnchorPane root; - public ScrollPane scrollPane; - public FlowPane processContainer; - public Group groupContainer; - - /** - * The amount that is going be zoomed in/out for each press on + or - - */ - private final double SCALE_DELTA = 1.1; - - /** - * The max that the user can zoom in - */ - private static final double MAX_ZOOM_IN = 1.6; - - /** - * The max that the user can zoom in - */ - private static final double MAX_ZOOM_OUT = 0.5; - - /** - * Offset such that the view does not overlap with the scroll bar on the right-hand side. - */ - private static final int SCROLLPANE_OFFSET = 20; - - private final ObservableList<Component> componentArrayList = FXCollections.observableArrayList(); - private final ObservableMap<String, ProcessPresentation> processPresentations = FXCollections.observableHashMap(); - - /** - * Is true if a reset of the zoom have been requested, false if not. - */ - private boolean resetZoom = false; - private boolean isMaxZoomInReached = false; - private boolean isMaxZoomOutReached = false; - private SimulationHandler simulationHandler; - - @Override - public void initialize(final URL location, final ResourceBundle resources) { - simulationHandler = SimulatorController.getSimulationHandler(); - - groupContainer = new Group(); - processContainer = new FlowPane(); - //In case that the processContainer gets moved around we have to keep in into place. - initializeProcessContainer(); - - initializeWindowResizing(); - initializeZoom(); - initializeSimulationVariables(); - initializeHighlighting(); - // Add the processes and group to the view - addProcessesToGroup(); - scrollPane.setContent(groupContainer); - } - - /** - * Initializes the {@link #processContainer} with its correct styling, and placement on the view. - * It also adds a {@link ListChangeListener} on {@link #componentArrayList} where it adds the - * {@link Component}<code>s</code> which are needed to the <code>processContainer</code>. - */ - private void initializeProcessContainer() { - processContainer.translateXProperty().addListener((observable, oldValue, newValue) -> { - processContainer.setTranslateX(0); - }); - //Sets the space between the processes - processContainer.setHgap(10); - processContainer.setVgap(10); - - // padding to the scrollpane - processContainer.setPadding(new Insets(5)); - - componentArrayList.addListener((ListChangeListener<Component>) c -> { - final Map<String, ProcessPresentation> processes = new HashMap<>(); - while (c.next()) { - if (c.wasRemoved()) { - clearOverview(); - } else { - c.getAddedSubList().forEach(o -> processes.put(o.getName(), new ProcessPresentation(o))); - } - } - // Highlight the current state when the processes change - highlightProcessState(simulationHandler.currentState.get()); - processContainer.getChildren().addAll(processes.values()); - processPresentations.putAll(processes); - }); - - final Map<String, ProcessPresentation> processes = new HashMap<>(); - componentArrayList.forEach(o -> processes.put(o.getName(), new ProcessPresentation(o))); - - processContainer.getChildren().addAll(processes.values()); - processPresentations.putAll(processes); - } - - /** - * Clears the {@link #processContainer} and the {@link #processPresentations}. - */ - void clearOverview() { - processContainer.getChildren().clear(); - processPresentations.clear(); - } - - /** - * Setup listeners for displaying clock and variable values on the {@link ProcessPresentation} - */ - private void initializeSimulationVariables() { - simulationHandler.getSimulationVariables().addListener((InvalidationListener) obs -> { - simulationHandler.getSimulationVariables().forEach((s, bigDecimal) -> { - if (!s.equals("t(0)")) {// As t(0) does not belong to any process - final String[] spittedString = s.split("\\."); - // If the process containing the var is not there we just skip it - if (spittedString.length > 0 && processPresentations.size() > 0) { - processPresentations.get(spittedString[0]).getController().getVariables().put(spittedString[1], bigDecimal); - } - } - }); - }); - simulationHandler.getSimulationClocks().addListener((InvalidationListener) obs -> { - if (processPresentations.size() == 0) return; - simulationHandler.getSimulationClocks().forEach((s, bigDecimal) -> { - if (!s.equals("t(0)")) {// As t(0) does not belong to any process - final String[] spittedString = s.split("\\."); - // If the process containing the clock is not there we just skip it - if (spittedString.length > 0 && processPresentations.size() > 0) { - processPresentations.get(spittedString[0]).getController().getClocks().put(spittedString[1], bigDecimal); - } - } - }); - }); - } - - /** - * Removes {@link #processContainer} from the {@link #groupContainer}. <br /> - * In this way the {@link Component}<code>s</code> in the <code>processContainer</code> will then again be resizable, - * as the class {@link Group} makes its children not resizeable. - * - * @see Group - */ - void removeProcessesFromGroup() { - groupContainer.getChildren().removeAll(processContainer); - } - - /** - * Adds the {@link #processContainer} to the {@link #groupContainer}. <br /> - * This method is usually needed to called if {@link #removeProcessesFromGroup()} have been called, or - * if the <code>processContainer</code> just need to be added to the <code>groupContainer</code>.<br /> - * This method makes sure that the <code>processContainer</code> will be added to the <code>groupContainer</code> - * which is needed to show the {@link ProcessPresentation}<code>s</code> in the {@link #scrollPane}. - * If the <code>processContainer</code> is already contained in the <code>groupContainer</code> - * the method does nothing but return. - * - * @see #removeProcessesFromGroup() - */ - void addProcessesToGroup() { - if (groupContainer.getChildren().contains(processContainer)) return; - groupContainer.getChildren().add(processContainer); - } - - /** - * Initializes the zoom functionality in {@link #processContainer} - */ - private void initializeZoom() { - processContainer.scaleXProperty().addListener((observable, oldValue, newValue) -> { - if (newValue.doubleValue() > MAX_ZOOM_IN) isMaxZoomInReached = true; - if (newValue.doubleValue() < MAX_ZOOM_OUT) isMaxZoomOutReached = true; - - handleWidthOnScale(oldValue, newValue); - }); - } - - /** - * Initializes listener for change of width in {@link #scrollPane} which also affects {@link #processContainer} <br /> - * This does also take the zooming into account when doing the resizing. - */ - private void initializeWindowResizing() { - scrollPane.widthProperty().addListener((observable, oldValue, newValue) -> { - final double width = (newValue.doubleValue()) * (1 + (1 - processContainer.getScaleX())); - if (processContainer.getScaleX() > 1) { //Zoomed in - processContainer.setMinWidth(width); - processContainer.setMaxWidth(width); - } else if (processContainer.getScaleX() < 1) { //Zoomed out - processContainer.setMinWidth(width); - processContainer.setMaxWidth(width); - final double deltaWidth = newValue.doubleValue() - groupContainer.layoutBoundsProperty().get().getWidth(); - processContainer.setMinWidth(processContainer.getWidth() + (deltaWidth - SCROLLPANE_OFFSET) * (1 + (1 - processContainer.getScaleX()))); - processContainer.setMaxWidth(processContainer.getWidth() + (deltaWidth - SCROLLPANE_OFFSET) * (1 + (1 - processContainer.getScaleX()))); - } else { // Reset - processContainer.setMinWidth(newValue.doubleValue() - SCROLLPANE_OFFSET); - processContainer.setMaxWidth(newValue.doubleValue() - SCROLLPANE_OFFSET); - } - }); - } - - /** - * Handles the scaling of the width of the {@link #processContainer} - * - * @param oldValue the width of {@link #scrollPane} before the change - * @param newValue the width of {@link #scrollPane} after the change - */ - private void handleWidthOnScale(final Number oldValue, final Number newValue) { - if (resetZoom) { //Zoom reset - resetZoom = false; - processContainer.setMinWidth(scrollPane.getWidth() - SCROLLPANE_OFFSET); - processContainer.setMaxWidth(scrollPane.getWidth() - SCROLLPANE_OFFSET); - } else if (oldValue.doubleValue() > newValue.doubleValue()) { //Zoom in - resetZoom = false; - processContainer.setMinWidth(Math.round(processContainer.getWidth() * SCALE_DELTA)); - processContainer.setMaxWidth(Math.round(processContainer.getWidth() * SCALE_DELTA)); - } else { // Zoom out - resetZoom = false; - processContainer.setMinWidth(Math.round(processContainer.getWidth() * (1 / SCALE_DELTA))); - processContainer.setMaxWidth(Math.round(processContainer.getWidth() * (1 / SCALE_DELTA))); - } - } - - /** - * Initializer method to setup listeners that handle highlighting when selected/current state/transition changes - */ - private void initializeHighlighting() { - simulationHandler.selectedEdge.addListener((observable, oldEdge, newEdge) -> { - unhighlightProcesses(); - }); - - simulationHandler.currentState.addListener((observable, oldState, newState) -> { - if (newState == null) { - return; - } - unhighlightProcesses(); - highlightProcessState(newState); - highlightAvailableEdges(newState); - }); - } - - /** - * Highlights all the processes involved in the transition. - * Finds the processes involved in the transition (processes with edges in the transition) and highlights their edges - * Also fades processes that are not active in the selected transition - * - * @param transition The transition for which we highlight the involved processes - */ - public void highlightProcessTransition(final Transition transition) { - final var edges = transition.getEdges(); - - // List of all processes to show as inactive if they are not involved in a transition - // Processes are removed from this list, if they have an edge in the transition - final ArrayList<ProcessPresentation> processesToHide = new ArrayList<>(processPresentations.values()); - for (final ProcessPresentation processPresentation : processPresentations.values()) { - // Find the processes that have edges involved in this transition - processPresentation.getController().highlightEdges(edges); - processesToHide.remove(processPresentation); - } - - processesToHide.forEach(ProcessPresentation::showInactive); - } - - /** - * Unhighlights all processes - */ - public void unhighlightProcesses() { - for (final ProcessPresentation presentation : processPresentations.values()) { - presentation.getController().unhighlightProcess(); - presentation.showActive(); - } - } - - /** - * Finds the processes for the input locations in the input {@link SimulationState} and highlights the locations. - * - * @param state The state with the locations to highlight - */ - public void highlightProcessState(final SimulationState state) { - if (state == null) return; - - for (var loc : state.getLocations()) { - processPresentations.values().stream() - .filter(p -> p.getController().getComponent().getName().equals(loc.getKey())) - .forEach(p -> p.getController().highlightLocation(loc.getValue())); - } - } - - public ObservableList<Component> getComponentObservableList() { - return componentArrayList; - } - - public void highlightAvailableEdges(SimulationState state) { - // unhighlight all edges - for (Pair<String, String> edge : state.getEnabledEdges()) { - processPresentations.values().stream() - .forEach(p -> p.getController().getComponent().getEdges().stream() - .forEach(e -> e.setIsHighlighted(false))); - } - - // highlight available edges in the given state - for (Pair<String, String> edge : state.getEnabledEdges()) { - processPresentations.values().stream() - .forEach(p -> p.getController().getComponent().getEdges().stream() - .forEach(e -> { - if (e.getId().equals(edge.getValue())) { - e.setIsHighlighted(true); - } - })); - } - } -} diff --git a/src/main/java/ecdar/controllers/TransitionController.java b/src/main/java/ecdar/controllers/StateController.java similarity index 63% rename from src/main/java/ecdar/controllers/TransitionController.java rename to src/main/java/ecdar/controllers/StateController.java index 48f12a1f..3bb01100 100755 --- a/src/main/java/ecdar/controllers/TransitionController.java +++ b/src/main/java/ecdar/controllers/StateController.java @@ -1,7 +1,7 @@ package ecdar.controllers; import com.jfoenix.controls.JFXRippler; -import ecdar.simulation.Transition; +import ecdar.abstractions.State; import javafx.beans.property.SimpleObjectProperty; import javafx.fxml.Initializable; import javafx.scene.control.Label; @@ -12,17 +12,17 @@ /** * The controller class for the transition view element. - * It represents a single transition and may be used by classes like {@see TransitionPaneController} + * It represents a single transition and may be used by classes like {@see StatePaneController} * to show a list of transitions */ -public class TransitionController implements Initializable { +public class StateController implements Initializable { public AnchorPane root; public Label titleLabel; public JFXRippler rippler; // The transition that the view represents - private SimpleObjectProperty<Transition> transition = new SimpleObjectProperty<>(); - private SimpleObjectProperty<String> title = new SimpleObjectProperty<>(); + private final SimpleObjectProperty<State> state = new SimpleObjectProperty<>(); + private final SimpleObjectProperty<String> title = new SimpleObjectProperty<>(); @Override public void initialize(URL location, ResourceBundle resources) { @@ -35,11 +35,11 @@ public void setTitle(String title) { this.title.set(title); } - public void setTransition(Transition transition) { - this.transition.set(transition); + public void setState(State state) { + this.state.set(state); } - public Transition getTransition() { - return transition.get(); + public State getState() { + return state.get(); } } diff --git a/src/main/java/ecdar/controllers/TransitionPaneController.java b/src/main/java/ecdar/controllers/StatePaneController.java similarity index 78% rename from src/main/java/ecdar/controllers/TransitionPaneController.java rename to src/main/java/ecdar/controllers/StatePaneController.java index b2855a79..7ebb723f 100755 --- a/src/main/java/ecdar/controllers/TransitionPaneController.java +++ b/src/main/java/ecdar/controllers/StatePaneController.java @@ -3,12 +3,13 @@ import com.jfoenix.controls.JFXRippler; import com.jfoenix.controls.JFXTextField; import ecdar.abstractions.Edge; -import ecdar.backend.SimulationHandler; -import ecdar.simulation.Transition; -import ecdar.presentations.TransitionPresentation; +import ecdar.abstractions.Transition; +import ecdar.presentations.StatePresentation; import ecdar.utility.colors.EnabledColor; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleObjectProperty; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; import javafx.event.EventHandler; import javafx.fxml.FXML; import javafx.fxml.Initializable; @@ -21,14 +22,12 @@ import java.math.BigDecimal; import java.net.URL; -import java.util.HashMap; -import java.util.Map; import java.util.ResourceBundle; /** * The controller class for the transition pane element that can be inserted into the simulator panes */ -public class TransitionPaneController implements Initializable { +public class StatePaneController implements Initializable { public VBox root; public VBox transitionList; public HBox toolbar; @@ -40,13 +39,12 @@ public class TransitionPaneController implements Initializable { public JFXTextField delayTextField; private final SimpleBooleanProperty isTransitionExpanded = new SimpleBooleanProperty(false); - private final Map<Transition, TransitionPresentation> transitionPresentationMap = new HashMap<>(); private final SimpleObjectProperty<BigDecimal> delay = new SimpleObjectProperty<>(BigDecimal.ZERO); - private SimulationHandler simulationHandler; + + private ObservableList<StatePresentation> statePresentations = FXCollections.observableArrayList(); @Override public void initialize(URL location, ResourceBundle resources) { - simulationHandler = SimulatorController.getSimulationHandler(); initializeToolbar(); initializeTransitionExpand(); initializeDelayChooser(); @@ -133,6 +131,10 @@ private void initializeTransitionExpand() { isTransitionExpanded.set(true); } + protected void setTraceLog(ObservableList<StatePresentation> traceLog) { + statePresentations = traceLog; + } + /** * Removes all the transition view elements as to hide the transitions from the user */ @@ -141,42 +143,35 @@ private void hideTransitions() { } /** - * Shows the available transitions by inserting a {@link TransitionPresentation} for each transition + * Shows the available transitions by inserting a {@link StatePresentation} for each transition */ private void showTransitions() { - transitionPresentationMap.forEach((transition, presentation) -> { - insertTransition(transition); - }); + transitionList.getChildren().addAll(statePresentations); } /** - * Instantiates a TransitionPresentation for a Transition and adds it to the view - * @param transition The transition that should be inserted into the view + * Instantiates a StatePresentation for a Transition and adds it to the view + * @param statePresentation The state presentation that should be inserted into the view */ - private void insertTransition(Transition transition) { - final TransitionPresentation transitionPresentation = new TransitionPresentation(); - String title = transitionString(transition); - transitionPresentation.getController().setTitle(title); - transitionPresentation.getController().setTransition(transition); + private void insertState(StatePresentation statePresentation) { + String title = "Not yet implemented"; // ToDo NIELS: Re-implement - transitionString(statePresentation); // Update the selected transition when mouse entered. // Add the event to existing mouseEntered events - // e.g. TransitionPresentation already has mouseEntered functionality and we want to keep it - EventHandler mouseEntered = transitionPresentation.getOnMouseEntered(); - // transitionPresentation.setOnMouseEntered(event -> { - // SimulatorController.setSelectedTransition(transitionPresentation.getController().getTransition()); + // e.g. StatePresentation already has mouseEntered functionality and we want to keep it + EventHandler mouseEntered = statePresentation.getOnMouseEntered(); + // statePresentation.setOnMouseEntered(event -> { + // SimulationController.setSelectedTransition(statePresentation.getController().getTransition()); // mouseEntered.handle(event); // }); - EventHandler<? super MouseEvent> mouseExited = transitionPresentation.getOnMouseExited(); - transitionPresentation.setOnMouseExited(mouseExited); - - transitionPresentationMap.put(transition, transitionPresentation); + EventHandler<? super MouseEvent> mouseExited = statePresentation.getOnMouseExited(); + statePresentation.setOnMouseExited(mouseExited); // Only insert the presentation into the view if the transitions are expanded // Avoids inserting duplicate elements in the view (it's still added to the map) if(isTransitionExpanded.get()) { - transitionList.getChildren().add(transitionPresentation); + transitionList.getChildren().add(statePresentation); } } @@ -208,8 +203,8 @@ private void expandTransitions() { */ @FXML private void restartSimulation() { - simulationHandler.resetToInitialLocation(); - } + return; + } // ToDo NIELS: Use simulation controller /** * Sanitizes the input that the user inserts into the delay textfield. @@ -244,5 +239,4 @@ private void delayTextChanged(String oldValue, String newValue) { } } - } diff --git a/src/main/java/ecdar/controllers/TracePaneController.java b/src/main/java/ecdar/controllers/TracePaneController.java index 10bce97c..ec5178f6 100755 --- a/src/main/java/ecdar/controllers/TracePaneController.java +++ b/src/main/java/ecdar/controllers/TracePaneController.java @@ -1,15 +1,15 @@ package ecdar.controllers; import com.jfoenix.controls.JFXRippler; -import ecdar.Ecdar; -import ecdar.abstractions.Location; -import ecdar.backend.SimulationHandler; -import ecdar.simulation.SimulationState; -import ecdar.presentations.TransitionPresentation; +import ecdar.abstractions.State; +import ecdar.presentations.StatePresentation; import ecdar.utility.colors.Color; +import javafx.application.Platform; +import javafx.beans.property.IntegerProperty; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleIntegerProperty; import javafx.collections.ListChangeListener; +import javafx.collections.ObservableList; import javafx.event.EventHandler; import javafx.fxml.FXML; import javafx.fxml.Initializable; @@ -20,8 +20,6 @@ import org.kordamp.ikonli.javafx.FontIcon; import java.net.URL; -import java.util.LinkedHashMap; -import java.util.Map; import java.util.ResourceBundle; import java.util.function.BiConsumer; @@ -40,33 +38,12 @@ public class TracePaneController implements Initializable { public Label summarySubtitleLabel; private final SimpleBooleanProperty isTraceExpanded = new SimpleBooleanProperty(false); - private final SimpleIntegerProperty numberOfSteps = new SimpleIntegerProperty(0); - private final Map<SimulationState, TransitionPresentation> transitionPresentationMap = new LinkedHashMap<>(); - - private SimulationHandler simulationHandler; + private ObservableList<StatePresentation> traceLog; @Override public void initialize(URL location, ResourceBundle resources) { - simulationHandler = SimulatorController.getSimulationHandler(); - initializeToolbar(); initializeSummaryView(); - initializeTraceExpand(); - - simulationHandler.getTraceLog().addListener((ListChangeListener<SimulationState>) c -> { - while (c.next()) { - for (final SimulationState state : c.getAddedSubList()) { - if (state != null) insertTraceState(state, true); - } - - for (final SimulationState state : c.getRemoved()) { - traceList.getChildren().remove(transitionPresentationMap.get(state)); - transitionPresentationMap.remove(state); - } - } - - numberOfSteps.set(transitionPresentationMap.size()); - }); } /** @@ -92,9 +69,6 @@ private void initializeToolbar() { * Also changes the color and cursor when mouse enters and exits the summary view. */ private void initializeSummaryView() { - getNumberOfStepsProperty().addListener( - (observable, oldValue, newValue) -> updateSummaryTitle(newValue.intValue())); - final Color color = Color.GREY_BLUE; final Color.Intensity colorIntensity = Color.Intensity.I50; @@ -129,34 +103,39 @@ private void initializeSummaryView() { setBackground.accept(color, colorIntensity); } - /** - * Updates the text of the summary title label with the current number of steps in the trace - * @param steps The number of steps in the trace - */ - private void updateSummaryTitle(int steps) { - summaryTitleLabel.setText(steps + " number of steps in trace"); - } + protected void setTraceLog(ObservableList<StatePresentation> traceLog) { + this.traceLog = traceLog; + + this.traceLog.addListener((ListChangeListener<StatePresentation>) c -> { + updateSummaryTitle(traceLog.size()); + + if (!isTraceExpanded.get()) return; + while (c.next()) { + c.getRemoved().forEach(statePresentation -> traceList.getChildren().remove(statePresentation)); + c.getAddedSubList().forEach(statePresentation -> traceList.getChildren().add(statePresentation)); + } + }); - /** - * Initializes the expand functionality that allows the user to show or hide the trace. - * By default, the trace is shown. - */ - private void initializeTraceExpand() { isTraceExpanded.addListener((obs, oldVal, newVal) -> { if (newVal) { - showTrace(); + Platform.runLater(this::showTrace); expandTraceIcon.setIconLiteral("gmi-expand-less"); expandTraceIcon.setIconSize(24); } else { - hideTrace(); + Platform.runLater(this::hideTrace); expandTraceIcon.setIconLiteral("gmi-expand-more"); expandTraceIcon.setIconSize(24); } }); - - isTraceExpanded.set(true); } + /** + * Updates the text of the summary title label with the current number of steps in the trace + * @param steps The number of steps in the trace + */ + private void updateSummaryTitle(int steps) { + summaryTitleLabel.setText(steps + " number of steps in trace"); + } /** * Removes all the trace view elements as to hide the trace from the user @@ -168,112 +147,81 @@ private void hideTrace() { } /** - * Shows the trace by inserting a {@link TransitionPresentation} for each trace state + * Shows the trace by inserting a {@link StatePresentation} for each trace state * Also hides the summary view, since it should only be visible when the trace is hidden */ private void showTrace() { - transitionPresentationMap.forEach((state, presentation) -> { - insertTraceState(state, false); - }); root.getChildren().remove(traceSummary); - } - - private void previewStep(final SimulationState state) { - traceList.getChildren().forEach(trace -> trace.setOpacity(1)); - int i = traceList.getChildren().size() - 1; - while (traceList.getChildren().get(i) != transitionPresentationMap.get(state)) { - traceList.getChildren().get(i).setOpacity(0.4); - i--; - } - simulationHandler.currentState.set(state); + this.traceLog.forEach(this::insertStateInTrace); } /** - * Instantiates a {@link TransitionPresentation} for a {@link SimulationState} and adds it to the view + * Instantiates a {@link StatePresentation} for a {@link State} and adds it to the view * - * @param state The state the should be inserted into the trace log - * @param shouldAnimate A boolean that indicates whether the trace should fade in when added to the view + * @param statePresentation The statePresentation the should be inserted into the trace log */ - private void insertTraceState(final SimulationState state, final boolean shouldAnimate) { - final TransitionPresentation transitionPresentation = new TransitionPresentation(); - transitionPresentationMap.put(state, transitionPresentation); - - transitionPresentation.setOnMouseReleased(event -> { - event.consume(); - if (simulationHandler == null) return; - previewStep(state); - }); - - EventHandler mouseEntered = transitionPresentation.getOnMouseEntered(); - transitionPresentation.setOnMouseEntered(event -> { - SimulatorController.setSelectedState(state); + private void insertStateInTrace(final StatePresentation statePresentation) { + EventHandler mouseEntered = statePresentation.getOnMouseEntered(); + statePresentation.setOnMouseEntered(event -> { + SimulationController.setSelectedState(statePresentation.getController().getState()); mouseEntered.handle(event); }); - EventHandler mouseExited = transitionPresentation.getOnMouseExited(); - transitionPresentation.setOnMouseExited(event -> { - SimulatorController.setSelectedState(null); + EventHandler mouseExited = statePresentation.getOnMouseExited(); + statePresentation.setOnMouseExited(event -> { + SimulationController.setSelectedState(null); mouseExited.handle(event); }); - String title = traceString(state); - transitionPresentation.getController().setTitle(title); + String title = traceString(statePresentation.getController().getState()); + statePresentation.getController().setTitle(title); - // Only insert the presentation into the view if the trace is expanded & state is not null - if (isTraceExpanded.get() && state != null) { - traceList.getChildren().add(transitionPresentation); - if (shouldAnimate) { - transitionPresentation.playFadeAnimation(); - } + // Only insert the presentation into the view if the trace is expanded & statePresentation is not null + if (isTraceExpanded.get()) { + traceList.getChildren().add(statePresentation); } } /** * A helper method that returns a string representing a state in the trace log * - * @param state The SimulationState to represent + * @param state The State to represent * @return A string representing the state */ - private String traceString(SimulationState state) { - StringBuilder title = new StringBuilder("("); - int length = state.getLocations().size(); - for (int i = 0; i < length; i++) { - Location loc = Ecdar.getProject() - .findComponent(state.getLocations().get(i).getKey()) - .findLocation(state.getLocations().get(i).getValue()); - String locationName = loc.getId(); - if (i == length - 1) { - title.append(locationName); - } else { - title.append(locationName).append(", "); - } - } - title.append(")\n"); - - StringBuilder clocks = new StringBuilder(); - for (var constraint : state.getState().getFederation().getDisjunction().getConjunctions(0).getConstraintsList()) { - var x = constraint.getX().getClockName(); - var y = constraint.getY().getClockName(); - var c = constraint.getC(); - var strict = constraint.getStrict(); - clocks.append(x).append(" - ").append(y).append(strict ? " < " : " <= ").append(c).append("\n"); - } - return title.toString() + clocks.toString(); + private String traceString(State state) { + // ToDo: Generate string from state +// StringBuilder title = new StringBuilder("("); +// int length = state.getLocationTree().size(); +// for (int i = 0; i < length; i++) { +// Location loc = Ecdar.getProject() +// .findComponent(state.getLocationTree().get(i).getKey()) +// .findLocation(state.getLocationTree().get(i).getValue()); +// String locationName = loc.getId(); +// if (i == length - 1) { +// title.append(locationName); +// } else { +// title.append(locationName).append(", "); +// } +// } +// title.append(")\n"); +// +// StringBuilder clocks = new StringBuilder(); +// for (var constraint : state.getState().getFederation().getDisjunction().getConjunctions(0).getConstraintsList()) { +// var x = constraint.getX().getClockName(); +// var y = constraint.getY().getClockName(); +// var c = constraint.getC(); +// var strict = constraint.getStrict(); +// clocks.append(x).append(" - ").append(y).append(strict ? " < " : " <= ").append(c).append("\n"); +// } +// return title.toString() + clocks.toString(); + return "Not implemented: " + state.toString(); } /** * Method to be called when clicking on the expand rippler in the trace toolbar */ @FXML - private void expandTrace() { - if (isTraceExpanded.get()) { - isTraceExpanded.set(false); - } else { - isTraceExpanded.set(true); - } - } - - public SimpleIntegerProperty getNumberOfStepsProperty() { - return numberOfSteps; + private void toggleTraceExpand() { + isTraceExpanded.set(!isTraceExpanded.get()); } } diff --git a/src/main/java/ecdar/presentations/DecisionPresentation.java b/src/main/java/ecdar/presentations/DecisionPresentation.java new file mode 100644 index 00000000..376248a7 --- /dev/null +++ b/src/main/java/ecdar/presentations/DecisionPresentation.java @@ -0,0 +1,18 @@ +package ecdar.presentations; + +import ecdar.abstractions.Decision; +import ecdar.controllers.DecisionController; +import javafx.scene.layout.AnchorPane; + +public class DecisionPresentation extends AnchorPane { + private final DecisionController controller; + + public DecisionPresentation(Decision decision) { + controller = new EcdarFXMLLoader().loadAndGetController("DecisionPresentation.fxml", this); + controller.setDecision(decision); + } + + public DecisionController getController() { + return controller; + } +} diff --git a/src/main/java/ecdar/presentations/EcdarFXMLLoader.java b/src/main/java/ecdar/presentations/EcdarFXMLLoader.java index 80280e8f..548b3901 100644 --- a/src/main/java/ecdar/presentations/EcdarFXMLLoader.java +++ b/src/main/java/ecdar/presentations/EcdarFXMLLoader.java @@ -11,7 +11,7 @@ */ public class EcdarFXMLLoader extends FXMLLoader { /** - * Loads an object hierarchy from from an XML document. + * Loads an object hierarchy from an XML document. * @param resourceName the name of the resource used to resolve relative path attribute values * @param <T> type of controller to get * @param root the root of the object hierarchy diff --git a/src/main/java/ecdar/presentations/EcdarPresentation.java b/src/main/java/ecdar/presentations/EcdarPresentation.java index c58399a4..c5d9ca4d 100644 --- a/src/main/java/ecdar/presentations/EcdarPresentation.java +++ b/src/main/java/ecdar/presentations/EcdarPresentation.java @@ -67,8 +67,10 @@ public EcdarPresentation() { controller.topPane.maxHeightProperty().bind(controller.menuBar.heightProperty()); EcdarController.currentMode.addListener(observable -> { - initializeToggleLeftPaneFunctionality(); - initializeToggleRightPaneFunctionality(); + Platform.runLater(() -> { + initializeToggleLeftPaneFunctionality(); + initializeToggleRightPaneFunctionality(); + }); }); Ecdar.getPresentation().controller.scalingProperty.addListener((observable, oldValue, newValue) -> { diff --git a/src/main/java/ecdar/presentations/LeftSimPanePresentation.java b/src/main/java/ecdar/presentations/LeftSimPanePresentation.java index de6e195e..728d73af 100755 --- a/src/main/java/ecdar/presentations/LeftSimPanePresentation.java +++ b/src/main/java/ecdar/presentations/LeftSimPanePresentation.java @@ -4,9 +4,13 @@ import javafx.scene.layout.*; public class LeftSimPanePresentation extends StackPane { - private LeftSimPaneController controller; + private final LeftSimPaneController controller; public LeftSimPanePresentation() { controller = new EcdarFXMLLoader().loadAndGetController("LeftSimPanePresentation.fxml", this); } + + public LeftSimPaneController getController() { + return controller; + } } diff --git a/src/main/java/ecdar/presentations/ProcessPresentation.java b/src/main/java/ecdar/presentations/ProcessPresentation.java index f4db4c46..48576fd7 100755 --- a/src/main/java/ecdar/presentations/ProcessPresentation.java +++ b/src/main/java/ecdar/presentations/ProcessPresentation.java @@ -1,13 +1,7 @@ package ecdar.presentations; -import ecdar.Ecdar; import ecdar.abstractions.Component; -import ecdar.abstractions.Edge; -import ecdar.abstractions.Location; -import ecdar.abstractions.Nail; -import ecdar.controllers.ModelController; import ecdar.controllers.ProcessController; -import ecdar.utility.colors.Color; import ecdar.utility.colors.EnabledColor; import javafx.beans.InvalidationListener; import javafx.geometry.Insets; @@ -24,11 +18,10 @@ import java.math.BigDecimal; import java.util.List; -import java.util.function.BiConsumer; import java.util.function.Consumer; /** - * The presenter of a Process which is shown in {@link SimulatorOverviewPresentation}. <br /> + * The presentation of a Process which is shown in {@link SimulationPresentation}. <br /> * This class have some of the same functionality as {@link ComponentPresentation} and could be refactored * into a base class. */ diff --git a/src/main/java/ecdar/presentations/RightSimPanePresentation.java b/src/main/java/ecdar/presentations/RightSimPanePresentation.java index 2345664d..b9eca0ba 100755 --- a/src/main/java/ecdar/presentations/RightSimPanePresentation.java +++ b/src/main/java/ecdar/presentations/RightSimPanePresentation.java @@ -1,44 +1,23 @@ package ecdar.presentations; +import ecdar.abstractions.Decision; import ecdar.controllers.RightSimPaneController; -import ecdar.utility.colors.Color; -import javafx.geometry.Insets; import javafx.scene.layout.*; +import java.util.function.Consumer; + /** * Presentation class for the right pane in the simulator */ public class RightSimPanePresentation extends StackPane { - private RightSimPaneController controller; + private final RightSimPaneController controller; - public RightSimPanePresentation() { + public RightSimPanePresentation(Consumer<Decision> onDecisionSelected) { controller = new EcdarFXMLLoader().loadAndGetController("RightSimPanePresentation.fxml", this); - - initializeBackground(); - initializeLeftBorder(); + controller.setOnDecisionSelected(onDecisionSelected); } - /** - * Sets the background color of the ScrollPane Vbox - */ - private void initializeBackground() { - controller.scrollPaneVbox.setBackground(new Background(new BackgroundFill( - Color.GREY.getColor(Color.Intensity.I200), - CornerRadii.EMPTY, - Insets.EMPTY - ))); + public RightSimPaneController getController() { + return controller; } - - /** - * Initializes the thin border on the left side of the querypane toolbar - */ - private void initializeLeftBorder() { - setBorder(new Border(new BorderStroke( - Color.GREY_BLUE.getColor(Color.Intensity.I900), - BorderStrokeStyle.SOLID, - CornerRadii.EMPTY, - new BorderWidths(0, 0, 0, 1) - ))); - } - } diff --git a/src/main/java/ecdar/presentations/SimEdgePresentation.java b/src/main/java/ecdar/presentations/SimEdgePresentation.java index 8991f145..6cf47d9c 100644 --- a/src/main/java/ecdar/presentations/SimEdgePresentation.java +++ b/src/main/java/ecdar/presentations/SimEdgePresentation.java @@ -3,14 +3,13 @@ import ecdar.abstractions.Component; import ecdar.abstractions.Edge; import ecdar.controllers.SimEdgeController; -import ecdar.controllers.SimulatorController; +import ecdar.controllers.SimulationController; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.scene.Group; -import javafx.util.Pair; /** - * The presentation class for the edges shown in the {@link SimulatorOverviewPresentation} + * The presentation class for the edges shown in the {@link SimulationPresentation} */ public class SimEdgePresentation extends Group { private final SimEdgeController controller; @@ -20,7 +19,6 @@ public class SimEdgePresentation extends Group { public SimEdgePresentation(final Edge edge, final Component component) { controller = new EcdarFXMLLoader().loadAndGetController("SimEdgePresentation.fxml", this); - var simulationHandler = SimulatorController.getSimulationHandler(); controller.setEdge(edge); this.edge.bind(controller.edgeProperty()); @@ -30,18 +28,11 @@ public SimEdgePresentation(final Edge edge, final Component component) { // when hovering mouse the cursor should change to hand this.setOnMouseEntered(event -> { - if (simulationHandler.currentState.get().getEnabledEdges().contains(new Pair<>(component.getName(), edge.getId()))) + // ToDo NIELS: Handle change of functionality and utilization of ComponentInstances + if (controller.getEdge().isHighlighted()) this.getScene().setCursor(javafx.scene.Cursor.HAND); }); this.setOnMouseExited(event -> this.getScene().setCursor(javafx.scene.Cursor.DEFAULT)); - - // when clicking the edge, the edge should be selected and the simulation should take next step (if the edge is enabled) - this.setOnMouseClicked(event -> { - if (simulationHandler.currentState.get().getEnabledEdges().contains(new Pair<>(component.getName(), edge.getId()))) { - simulationHandler.selectedEdge.set(edge); - simulationHandler.nextStep(); - } - }); } public SimEdgeController getController() { diff --git a/src/main/java/ecdar/presentations/SimLocationPresentation.java b/src/main/java/ecdar/presentations/SimLocationPresentation.java index 29b51cb1..7c127d56 100755 --- a/src/main/java/ecdar/presentations/SimLocationPresentation.java +++ b/src/main/java/ecdar/presentations/SimLocationPresentation.java @@ -3,8 +3,8 @@ import ecdar.abstractions.Component; import ecdar.abstractions.Location; import ecdar.controllers.SimLocationController; +import ecdar.controllers.SimulationController; import ecdar.utility.Highlightable; -import ecdar.utility.colors.Color; import ecdar.utility.colors.EnabledColor; import ecdar.utility.helpers.BindingHelper; import ecdar.utility.helpers.SelectHelper; @@ -27,7 +27,7 @@ import static ecdar.presentations.LocationPresentation.RADIUS; /** - * Presentation for a location in the {@link SimulatorOverviewPresentation}. + * Presentation for a location in the {@link SimulationPresentation}. * This class should be refactored such that the shared code between this class * and {@link LocationPresentation} is placed in a base class. */ diff --git a/src/main/java/ecdar/presentations/SimNailPresentation.java b/src/main/java/ecdar/presentations/SimNailPresentation.java index d6e7fbca..14bd2bfd 100755 --- a/src/main/java/ecdar/presentations/SimNailPresentation.java +++ b/src/main/java/ecdar/presentations/SimNailPresentation.java @@ -23,7 +23,7 @@ import static javafx.util.Duration.millis; /** - * The presentation for the nail shown on a {@link SimEdgePresentation} in the {@link SimulatorOverviewPresentation}<br /> + * The presentation for the nail shown on a {@link SimEdgePresentation} in the {@link SimulationPresentation}<br /> * This class should be refactored such that code which are duplicated from {@link NailPresentation} * have its own base class. */ diff --git a/src/main/java/ecdar/presentations/SimTagPresentation.java b/src/main/java/ecdar/presentations/SimTagPresentation.java index e1ca8d61..705144bc 100755 --- a/src/main/java/ecdar/presentations/SimTagPresentation.java +++ b/src/main/java/ecdar/presentations/SimTagPresentation.java @@ -18,7 +18,7 @@ import java.util.function.Consumer; /** - * The presentation for the tag shown on a {@link SimEdgePresentation} in the {@link SimulatorOverviewPresentation}<br /> + * The presentation for the tag shown on a {@link SimEdgePresentation} in the {@link SimulationPresentation}<br /> * This class should be refactored such that code which are duplicated from {@link TagPresentation} * have its own base class. */ diff --git a/src/main/java/ecdar/presentations/SimulationInitializationDialogPresentation.java b/src/main/java/ecdar/presentations/SimulationInitializationDialogPresentation.java index e9c9664a..dc2b5ba3 100644 --- a/src/main/java/ecdar/presentations/SimulationInitializationDialogPresentation.java +++ b/src/main/java/ecdar/presentations/SimulationInitializationDialogPresentation.java @@ -1,6 +1,7 @@ package ecdar.presentations; import com.jfoenix.controls.JFXDialog; +import ecdar.presentations.EcdarFXMLLoader; import ecdar.controllers.SimulationInitializationDialogController; public class SimulationInitializationDialogPresentation extends JFXDialog { diff --git a/src/main/java/ecdar/presentations/SimulationPresentation.java b/src/main/java/ecdar/presentations/SimulationPresentation.java new file mode 100755 index 00000000..c9ef5f8b --- /dev/null +++ b/src/main/java/ecdar/presentations/SimulationPresentation.java @@ -0,0 +1,21 @@ +package ecdar.presentations; + +import ecdar.controllers.SimulationController; +import javafx.scene.layout.StackPane; + +public class SimulationPresentation extends StackPane { + private final SimulationController controller; + + public SimulationPresentation(String composition) { + controller = new EcdarFXMLLoader().loadAndGetController("SimulationPresentation.fxml", this); + controller.resetSimulation(composition); + } + + /** + * The way to get the associated/linked controller of this presenter + * @return the controller linked to this presenter + */ + public SimulationController getController() { + return controller; + } +} diff --git a/src/main/java/ecdar/presentations/SimulatorOverviewPresentation.java b/src/main/java/ecdar/presentations/SimulatorOverviewPresentation.java deleted file mode 100755 index 15a34e54..00000000 --- a/src/main/java/ecdar/presentations/SimulatorOverviewPresentation.java +++ /dev/null @@ -1,20 +0,0 @@ -package ecdar.presentations; - -import ecdar.controllers.SimulatorOverviewController; -import javafx.scene.layout.AnchorPane; - -/** - * The presenter of the middle part of the simulator. - * It is here where processes of a simulation will be shown. - */ -public class SimulatorOverviewPresentation extends AnchorPane { - private final SimulatorOverviewController controller; - - public SimulatorOverviewPresentation() { - controller = new EcdarFXMLLoader().loadAndGetController("SimulatorOverviewPresentation.fxml", this); - } - - public SimulatorOverviewController getController() { - return controller; - } -} diff --git a/src/main/java/ecdar/presentations/SimulatorPresentation.java b/src/main/java/ecdar/presentations/SimulatorPresentation.java deleted file mode 100755 index 255b7dd1..00000000 --- a/src/main/java/ecdar/presentations/SimulatorPresentation.java +++ /dev/null @@ -1,20 +0,0 @@ -package ecdar.presentations; - -import ecdar.controllers.SimulatorController; -import javafx.scene.layout.StackPane; - -public class SimulatorPresentation extends StackPane { - private final SimulatorController controller; - - public SimulatorPresentation() { - controller = new EcdarFXMLLoader().loadAndGetController("SimulatorPresentation.fxml", this); - } - - /** - * The way to get the associated/linked controller of this presenter - * @return the controller linked to this presenter - */ - public SimulatorController getController() { - return controller; - } -} diff --git a/src/main/java/ecdar/presentations/StatePanePresentation.java b/src/main/java/ecdar/presentations/StatePanePresentation.java new file mode 100755 index 00000000..50734290 --- /dev/null +++ b/src/main/java/ecdar/presentations/StatePanePresentation.java @@ -0,0 +1,19 @@ +package ecdar.presentations; + +import ecdar.controllers.StatePaneController; +import javafx.scene.layout.*; + +/** + * The presentation class for the transition pane element that can be inserted into the simulator panes + */ +public class StatePanePresentation extends VBox { + final private StatePaneController controller; + + public StatePanePresentation() { + controller = new EcdarFXMLLoader().loadAndGetController("StatePanePresentation.fxml", this); + } + + public StatePaneController getController() { + return controller; + } +} diff --git a/src/main/java/ecdar/presentations/TransitionPresentation.java b/src/main/java/ecdar/presentations/StatePresentation.java similarity index 84% rename from src/main/java/ecdar/presentations/TransitionPresentation.java rename to src/main/java/ecdar/presentations/StatePresentation.java index 547307ad..42bbdd68 100755 --- a/src/main/java/ecdar/presentations/TransitionPresentation.java +++ b/src/main/java/ecdar/presentations/StatePresentation.java @@ -1,7 +1,8 @@ package ecdar.presentations; import com.jfoenix.controls.JFXRippler; -import ecdar.controllers.TransitionController; +import ecdar.abstractions.State; +import ecdar.controllers.StateController; import ecdar.utility.colors.Color; import javafx.animation.FadeTransition; import javafx.animation.Interpolator; @@ -14,21 +15,26 @@ /** * The presentation class for a transition view element. - * It represents a single transition and may be used by classes like {@see TransitionPaneController} + * It represents a single transition and may be used by classes like {@see StatePaneController} * to show a list of transitions */ -public class TransitionPresentation extends AnchorPane { - private TransitionController controller; +public class StatePresentation extends AnchorPane { + private final StateController controller; private FadeTransition transition; - public TransitionPresentation() { - controller = new EcdarFXMLLoader().loadAndGetController("TransitionPresentation.fxml", this); + public StatePresentation() { + controller = new EcdarFXMLLoader().loadAndGetController("StatePresentation.fxml", this); initializeRippler(); initializeColors(); initializeFadeAnimation(); } + public StatePresentation(State state) { + this(); + controller.setState(state); + } + /** * Initializes the rippler. * Sets the color, mask and position of the rippler effect. @@ -92,7 +98,7 @@ public void playFadeAnimation() { this.transition.play(); } - public TransitionController getController() { + public StateController getController() { return controller; } } diff --git a/src/main/java/ecdar/presentations/TagPresentation.java b/src/main/java/ecdar/presentations/TagPresentation.java index a41d9a5e..663954b9 100644 --- a/src/main/java/ecdar/presentations/TagPresentation.java +++ b/src/main/java/ecdar/presentations/TagPresentation.java @@ -41,7 +41,7 @@ public class TagPresentation extends StackPane { LineTo l3; boolean hadInitialFocus = false; - static double TAG_HEIGHT = 16; + public static double TAG_HEIGHT = 16; public TagPresentation() { new EcdarFXMLLoader().loadAndGetController("TagPresentation.fxml", this); diff --git a/src/main/java/ecdar/presentations/TransitionPanePresentation.java b/src/main/java/ecdar/presentations/TransitionPanePresentation.java deleted file mode 100755 index 989ebbf7..00000000 --- a/src/main/java/ecdar/presentations/TransitionPanePresentation.java +++ /dev/null @@ -1,19 +0,0 @@ -package ecdar.presentations; - -import ecdar.controllers.TransitionPaneController; -import javafx.scene.layout.*; - -/** - * The presentation class for the transition pane element that can be inserted into the simulator panes - */ -public class TransitionPanePresentation extends VBox { - final private TransitionPaneController controller; - - public TransitionPanePresentation() { - controller = new EcdarFXMLLoader().loadAndGetController("TransitionPanePresentation.fxml", this); - } - - public TransitionPaneController getController() { - return controller; - } -} diff --git a/src/main/java/ecdar/simulation/SimulationState.java b/src/main/java/ecdar/simulation/SimulationState.java deleted file mode 100644 index e1c20729..00000000 --- a/src/main/java/ecdar/simulation/SimulationState.java +++ /dev/null @@ -1,77 +0,0 @@ -package ecdar.simulation; - -import EcdarProtoBuf.ObjectProtos; -import EcdarProtoBuf.ObjectProtos.State; -import ecdar.Ecdar; -import ecdar.backend.SimulationHandler; -import ecdar.controllers.SimulatorController; -import javafx.collections.ObservableMap; -import javafx.util.Pair; - -import java.math.BigDecimal; -import java.util.ArrayList; -import java.util.Map; - -public class SimulationState { - // locations and edges are saved as key-value pair where key is component name and value = id - private final ArrayList<Pair<String, String>> locations; - private final ArrayList<Pair<String, String>> edges; - private final State state; - - public SimulationState(ObjectProtos.DecisionPoint decisionPoint) { - locations = new ArrayList<>(); - for (ObjectProtos.Location location : decisionPoint.getSource().getLocationTuple().getLocationsList()) { - locations.add(new Pair<>(location.getSpecificComponent().getComponentName(), location.getId())); - } - - edges = new ArrayList<>(); - if (decisionPoint.getEdgesList().isEmpty()) { - Ecdar.showToast("No available transitions."); - } - for (ObjectProtos.Edge edge : decisionPoint.getEdgesList()) { - edges.add(new Pair<>(getComponentName(edge.getId()), edge.getId())); - } - state = decisionPoint.getSource(); - } - - /** - * All the clocks connected to the current simulation. - * - * @return a {@link Map} where the component name (String) is the key, and the location name is the value (String) - * @see SimulationHandler#getSimulationVariables() - */ - public ArrayList<Pair<String, String>> getLocations() { - return locations; - } - - public ArrayList<Pair<String, String>> getEnabledEdges() { - return edges; - } - - public State getState() { - return state; - } - - private String getComponentName(String id) { - var components = Ecdar.getProject().getComponents(); - for (var component : components) { - for (var edge : component.getEdges()) { - if (edge.getId().equals(id)) { - return component.getName(); - } - } - } - throw new RuntimeException("Could not find component name for edge with id " + id); - } - - /** - * All the clocks connected to the current simulation. - * - * @return a {@link Map} where the name (String) is the key, and a {@link BigDecimal} is the clock value - * @see SimulationHandler#getSimulationVariables() - */ - public ObservableMap<String, BigDecimal> getSimulationClocks() { - // TODO move clocks from SimulationHandler to SimulationState - return SimulatorController.getSimulationHandler().getSimulationClocks(); - } -} diff --git a/src/main/java/ecdar/simulation/Transition.java b/src/main/java/ecdar/simulation/Transition.java deleted file mode 100644 index da5bac14..00000000 --- a/src/main/java/ecdar/simulation/Transition.java +++ /dev/null @@ -1,17 +0,0 @@ -package ecdar.simulation; - -import ecdar.abstractions.Edge; - -import java.util.ArrayList; - -public class Transition { - public String getLabel() { - // ToDo: Implement - return "Transition label"; - } - - public ArrayList<Edge> getEdges() { - // ToDo: Implement - return new ArrayList<>(); - } -} diff --git a/src/main/java/ecdar/utility/helpers/BindingHelper.java b/src/main/java/ecdar/utility/helpers/BindingHelper.java index 5c5b747e..a8068ff3 100644 --- a/src/main/java/ecdar/utility/helpers/BindingHelper.java +++ b/src/main/java/ecdar/utility/helpers/BindingHelper.java @@ -1,7 +1,6 @@ package ecdar.utility.helpers; import ecdar.Ecdar; -import ecdar.controllers.EcdarController; import ecdar.model_canvas.arrow_heads.ArrowHead; import ecdar.model_canvas.arrow_heads.ChannelReceiverArrowHead; import ecdar.model_canvas.arrow_heads.ChannelSenderArrowHead; diff --git a/src/main/proto b/src/main/proto index e42e35db..ae8364e6 160000 --- a/src/main/proto +++ b/src/main/proto @@ -1 +1 @@ -Subproject commit e42e35db63efacd7ab42aecd17c9a52b291c7be9 +Subproject commit ae8364e6c8942a5cfde24adec779ca87d4431e4a diff --git a/src/main/resources/ecdar/presentations/DecisionPresentation.fxml b/src/main/resources/ecdar/presentations/DecisionPresentation.fxml new file mode 100755 index 00000000..ae902014 --- /dev/null +++ b/src/main/resources/ecdar/presentations/DecisionPresentation.fxml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<?import javafx.scene.control.*?> +<?import javafx.scene.layout.*?> +<?import javafx.geometry.Insets?> + +<?import com.jfoenix.controls.JFXRippler?> +<fx:root xmlns="http://javafx.com/javafx" + xmlns:fx="http://javafx.com/fxml" + fx:controller="ecdar.controllers.DecisionController" + type="AnchorPane"> + <JFXRippler fx:id="rippler" AnchorPane.leftAnchor="0" AnchorPane.rightAnchor="0"> + <AnchorPane minHeight="48"> + <padding> + <Insets top="10" right="20" bottom="10" left="20"/> + </padding> + <Label fx:id="decisionDescription" styleClass="body1" wrapText="true" + AnchorPane.topAnchor="0" AnchorPane.bottomAnchor="0" AnchorPane.leftAnchor="0" AnchorPane.rightAnchor="0"/> + </AnchorPane> + </JFXRippler> +</fx:root> diff --git a/src/main/resources/ecdar/presentations/EcdarPresentation.fxml b/src/main/resources/ecdar/presentations/EcdarPresentation.fxml index cb72110a..e6ebecc6 100644 --- a/src/main/resources/ecdar/presentations/EcdarPresentation.fxml +++ b/src/main/resources/ecdar/presentations/EcdarPresentation.fxml @@ -1,5 +1,8 @@ <?xml version="1.0" encoding="UTF-8"?> +<?import ecdar.Ecdar?> +<?import ecdar.presentations.*?> +<?import ecdar.simulation.presentations.*?> <?import com.jfoenix.controls.*?> <?import javafx.scene.control.*?> <?import javafx.scene.image.ImageView?> @@ -7,8 +10,6 @@ <?import javafx.scene.shape.Rectangle?> <?import javafx.scene.text.Text?> <?import org.kordamp.ikonli.javafx.*?> -<?import ecdar.presentations.*?> -<?import ecdar.Ecdar?> <?import javafx.scene.text.TextFlow?> <?import javafx.geometry.Insets?> <fx:root xmlns:fx="http://javafx.com/fxml/1" diff --git a/src/main/resources/ecdar/presentations/LeftSimPanePresentation.fxml b/src/main/resources/ecdar/presentations/LeftSimPanePresentation.fxml index 88a15f23..d996f819 100755 --- a/src/main/resources/ecdar/presentations/LeftSimPanePresentation.fxml +++ b/src/main/resources/ecdar/presentations/LeftSimPanePresentation.fxml @@ -3,7 +3,7 @@ <?import javafx.scene.control.*?> <?import javafx.scene.layout.*?> <?import ecdar.presentations.TracePanePresentation?> -<?import ecdar.presentations.TransitionPanePresentation?> +<?import ecdar.presentations.StatePanePresentation?> <fx:root xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8.0.76-ea" @@ -19,7 +19,7 @@ AnchorPane.rightAnchor="0" styleClass="edge-to-edge"> <VBox fx:id="scrollPaneVbox"> - <TransitionPanePresentation fx:id="transitionPanePresentation"/> + <StatePanePresentation fx:id="statePanePresentation"/> <TracePanePresentation fx:id="tracePanePresentation"/> </VBox> </ScrollPane> diff --git a/src/main/resources/ecdar/presentations/RightSimPanePresentation.fxml b/src/main/resources/ecdar/presentations/RightSimPanePresentation.fxml index 21eec468..06064369 100755 --- a/src/main/resources/ecdar/presentations/RightSimPanePresentation.fxml +++ b/src/main/resources/ecdar/presentations/RightSimPanePresentation.fxml @@ -16,9 +16,7 @@ AnchorPane.leftAnchor="0" AnchorPane.rightAnchor="0" styleClass="edge-to-edge"> - <VBox fx:id="scrollPaneVbox" > - <!-- Waiting on feature to be specified --> - </VBox> + <VBox fx:id="availableDecisionsVBox"/> </ScrollPane> </AnchorPane> </fx:root> diff --git a/src/main/resources/ecdar/presentations/SimulationInitializationDialogPresentation.fxml b/src/main/resources/ecdar/presentations/SimulationInitializationDialogPresentation.fxml index eb97bdd1..332e082d 100644 --- a/src/main/resources/ecdar/presentations/SimulationInitializationDialogPresentation.fxml +++ b/src/main/resources/ecdar/presentations/SimulationInitializationDialogPresentation.fxml @@ -36,7 +36,7 @@ </padding> <Region HBox.hgrow="ALWAYS"/> <JFXButton fx:id="cancelButton" text="Cancel"/> - <JFXButton fx:id="startButton" onMousePressed="#setSimulationData" text="Start Simulation"/> + <JFXButton fx:id="startButton" text="Start Simulation"/> </HBox> </VBox> </VBox> diff --git a/src/main/resources/ecdar/presentations/SimulationPresentation.fxml b/src/main/resources/ecdar/presentations/SimulationPresentation.fxml new file mode 100755 index 00000000..d3ab4bab --- /dev/null +++ b/src/main/resources/ecdar/presentations/SimulationPresentation.fxml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<?import javafx.scene.layout.*?> + +<?import javafx.scene.control.ScrollPane?> +<?import javafx.scene.Group?> +<fx:root xmlns:fx="http://javafx.com/fxml/1" + xmlns="http://javafx.com/javafx/8.0.76-ea" + type="StackPane" + fx:id="root" + fx:controller="ecdar.controllers.SimulationController"> + <AnchorPane translateY="55" style="-fx-focus-traversable: false; -fx-focus-color: lightgray"> + <ScrollPane fx:id="scrollPane" fitToHeight="true" fitToWidth="true" AnchorPane.topAnchor="0" + AnchorPane.bottomAnchor="55" AnchorPane.leftAnchor="0" AnchorPane.rightAnchor="0"> + <Group fx:id="groupContainer"> + <FlowPane fx:id="processContainer"> + <!-- ProcessPresentations go here --> + </FlowPane> + </Group> + </ScrollPane> + </AnchorPane> + <StackPane fx:id="toolbar" + StackPane.alignment="TOP_CENTER" + minHeight="56" maxHeight="56"> + <HBox style="-fx-padding: 0 16 0 16;"> + <!-- Content of the toolbar --> + <Region HBox.hgrow="ALWAYS"/> + </HBox> + </StackPane> +</fx:root> diff --git a/src/main/resources/ecdar/presentations/SimulatorOverviewPresentation.fxml b/src/main/resources/ecdar/presentations/SimulatorOverviewPresentation.fxml deleted file mode 100755 index f854ef33..00000000 --- a/src/main/resources/ecdar/presentations/SimulatorOverviewPresentation.fxml +++ /dev/null @@ -1,13 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<?import javafx.scene.layout.AnchorPane?> -<?import javafx.scene.control.ScrollPane?> -<fx:root xmlns:fx="http://javafx.com/fxml/1" - xmlns="http://javafx.com/javafx/8.0.76-ea" - type="AnchorPane" - fx:id="root" - fx:controller="ecdar.controllers.SimulatorOverviewController" - translateY="55" style="-fx-focus-traversable: false; -fx-focus-color: lightgray"> - <ScrollPane fx:id="scrollPane" fitToHeight="true" fitToWidth="true" AnchorPane.topAnchor="0" - AnchorPane.bottomAnchor="55" AnchorPane.leftAnchor="0" AnchorPane.rightAnchor="0"> - </ScrollPane> -</fx:root> \ No newline at end of file diff --git a/src/main/resources/ecdar/presentations/SimulatorPresentation.fxml b/src/main/resources/ecdar/presentations/SimulatorPresentation.fxml deleted file mode 100755 index 7524a380..00000000 --- a/src/main/resources/ecdar/presentations/SimulatorPresentation.fxml +++ /dev/null @@ -1,19 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> - -<?import ecdar.presentations.SimulatorOverviewPresentation?> -<?import javafx.scene.layout.*?> -<fx:root xmlns:fx="http://javafx.com/fxml/1" - xmlns="http://javafx.com/javafx/8.0.76-ea" - type="StackPane" - fx:id="root" - fx:controller="ecdar.controllers.SimulatorController"> - <SimulatorOverviewPresentation fx:id="overviewPresentation"/> - <StackPane fx:id="toolbar" - StackPane.alignment="TOP_CENTER" - minHeight="56" maxHeight="56"> - <HBox style="-fx-padding: 0 16 0 16;"> - <!-- Content of the toolbar --> - <Region HBox.hgrow="ALWAYS"/> - </HBox> - </StackPane> -</fx:root> diff --git a/src/main/resources/ecdar/presentations/TransitionPanePresentation.fxml b/src/main/resources/ecdar/presentations/StatePanePresentation.fxml similarity index 93% rename from src/main/resources/ecdar/presentations/TransitionPanePresentation.fxml rename to src/main/resources/ecdar/presentations/StatePanePresentation.fxml index 7852d35e..7ca0861c 100755 --- a/src/main/resources/ecdar/presentations/TransitionPanePresentation.fxml +++ b/src/main/resources/ecdar/presentations/StatePanePresentation.fxml @@ -9,7 +9,7 @@ <?import javafx.geometry.Insets?> <fx:root xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8.0.76-ea" - fx:controller="ecdar.controllers.TransitionPaneController" + fx:controller="ecdar.controllers.StatePaneController" fx:id="root" type="VBox" AnchorPane.leftAnchor="0" AnchorPane.rightAnchor="0"> <HBox fx:id="toolbar" alignment="CENTER"> diff --git a/src/main/resources/ecdar/presentations/TransitionPresentation.fxml b/src/main/resources/ecdar/presentations/StatePresentation.fxml similarity index 65% rename from src/main/resources/ecdar/presentations/TransitionPresentation.fxml rename to src/main/resources/ecdar/presentations/StatePresentation.fxml index 900aa8d4..94e02afa 100755 --- a/src/main/resources/ecdar/presentations/TransitionPresentation.fxml +++ b/src/main/resources/ecdar/presentations/StatePresentation.fxml @@ -7,15 +7,15 @@ <?import com.jfoenix.controls.JFXRippler?> <fx:root xmlns="http://javafx.com/javafx" xmlns:fx="http://javafx.com/fxml" - fx:controller="ecdar.controllers.TransitionController" + fx:controller="ecdar.controllers.StateController" type="AnchorPane"> <JFXRippler fx:id="rippler" AnchorPane.leftAnchor="0" AnchorPane.rightAnchor="0"> <AnchorPane minHeight="48"> <padding> <Insets top="10" right="20" bottom="10" left="20"/> </padding> - <Label alignment="CENTER_LEFT" fx:id="titleLabel" styleClass="body1" wrapText="true" - AnchorPane.topAnchor="0" AnchorPane.bottomAnchor="0" AnchorPane.leftAnchor="0" AnchorPane.rightAnchor="0"/> + <Label fx:id="titleLabel" styleClass="body1" wrapText="true" + AnchorPane.topAnchor="0" AnchorPane.bottomAnchor="0" AnchorPane.leftAnchor="0" AnchorPane.rightAnchor="0"/> </AnchorPane> </JFXRippler> diff --git a/src/main/resources/ecdar/presentations/TracePanePresentation.fxml b/src/main/resources/ecdar/presentations/TracePanePresentation.fxml index 1f80fecf..f891e9c0 100755 --- a/src/main/resources/ecdar/presentations/TracePanePresentation.fxml +++ b/src/main/resources/ecdar/presentations/TracePanePresentation.fxml @@ -15,7 +15,7 @@ <Insets topRightBottomLeft="10"/> </padding> <JFXRippler fx:id="expandTrace"> - <StackPane styleClass="responsive-icon-stack-pane-sizing" onMouseClicked="#expandTrace"> + <StackPane styleClass="responsive-icon-stack-pane-sizing" onMouseClicked="#toggleTraceExpand"> <FontIcon fx:id="expandTraceIcon" iconLiteral="gmi-add" styleClass="icon-size-medium" fill="white"/> </StackPane> @@ -32,7 +32,7 @@ <!-- Trace goes here --> </VBox> - <AnchorPane fx:id="traceSummary" minHeight="56" maxHeight="56" onMouseClicked="#expandTrace"> + <AnchorPane fx:id="traceSummary" minHeight="56" maxHeight="56" onMouseClicked="#toggleTraceExpand"> <VBox AnchorPane.leftAnchor="8" AnchorPane.topAnchor="0" AnchorPane.bottomAnchor="0" diff --git a/src/test/java/ecdar/backend/QueryTest.java b/src/test/java/ecdar/backend/QueryTest.java index 3b9a07ad..4f9bfbcd 100644 --- a/src/test/java/ecdar/backend/QueryTest.java +++ b/src/test/java/ecdar/backend/QueryTest.java @@ -8,11 +8,11 @@ public class QueryTest { @Test - public void testExecuteQuery() { + public void testExecutionOfQuery() { Engine e = mock(Engine.class); Query q = new Query("refinement: (Administration || Machine || Researcher) <= Spec", "", QueryState.UNKNOWN, e); q.execute(); - verify(e, times(1)).enqueueQuery(eq(q), any(), any()); + verify(e, times(1)).enqueueRequest(eq(q), any(), any()); } } diff --git a/src/test/java/ecdar/simulation/ReachabilityTest.java b/src/test/java/ecdar/simulation/ReachabilityTest.java index 62243a35..19e63b4d 100644 --- a/src/test/java/ecdar/simulation/ReachabilityTest.java +++ b/src/test/java/ecdar/simulation/ReachabilityTest.java @@ -3,9 +3,8 @@ import ecdar.Ecdar; import ecdar.abstractions.Component; import ecdar.abstractions.Location; -import ecdar.backend.SimulationHandler; import ecdar.controllers.SimLocationController; -import ecdar.controllers.SimulatorController; +import ecdar.controllers.SimulationController; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -15,6 +14,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; +// ToDo NIELS: Fix tests public class ReachabilityTest { @BeforeAll @@ -31,17 +31,11 @@ void reachabilityQuerySyntaxTestSuccess() { var component = new Component(); component.setName("C1"); - SimulationHandler simulationHandler = new SimulationHandler(); - List<String> components = new ArrayList<>(); components.add("C1"); components.add("C2"); components.add("C3"); - simulationHandler.setComponentsInSimulation(components); - - SimulatorController.setSimulationHandler(simulationHandler); - var result = SimLocationController.getSimLocationReachableQuery(location, component, "query"); assertTrue(result.matches(regex)); @@ -54,15 +48,11 @@ void reachabilityQueryLocationPosition1TestSuccess() { var component = new Component(); component.setName("C1"); - SimulationHandler simulationHandler = new SimulationHandler(); - List<String> components = new ArrayList<>(); components.add("C1"); components.add("C2"); components.add("C3"); - simulationHandler.setComponentsInSimulation(components); - var result = SimLocationController.getSimLocationReachableQuery(location, component, "query"); int indexOfOpeningBracket = result.indexOf('['); @@ -86,15 +76,11 @@ void reachabilityQueryLocationPosition2TestSuccess() { var component = new Component(); component.setName("C2"); - SimulationHandler simulationHandler = new SimulationHandler(); - List<String> components = new ArrayList<>(); components.add("C1"); components.add("C2"); components.add("C3"); - simulationHandler.setComponentsInSimulation(components); - var result = SimLocationController.getSimLocationReachableQuery(location, component, "query"); int indexOfFirstComma = 0; @@ -124,15 +110,11 @@ void reachabilityQueryLocationPosition3TestSuccess() { var component = new Component(); component.setName("C3"); - SimulationHandler simulationHandler = new SimulationHandler(); - List<String> components = new ArrayList<>(); components.add("C1"); components.add("C2"); components.add("C3"); - simulationHandler.setComponentsInSimulation(components); - var query = SimLocationController.getSimLocationReachableQuery(location, component, "query"); int indexOfLastComma = 0; @@ -158,16 +140,12 @@ void reachabilityQueryNumberOfLocationsTestSuccess() { var component = new Component(); component.setName("C1"); - SimulationHandler simulationHandler = new SimulationHandler(); - List<String> components = new ArrayList<>(); components.add("C1"); components.add("C2"); components.add("C3"); components.add("C4"); - simulationHandler.setComponentsInSimulation(components); - var query = SimLocationController.getSimLocationReachableQuery(location, component, "query"); int commaCount = 0; for (int i = 0; i < query.length(); i++) { @@ -178,6 +156,6 @@ void reachabilityQueryNumberOfLocationsTestSuccess() { int expected = commaCount + 1; - assertEquals(expected, SimulatorController.getSimulationHandler().getComponentsInSimulation().size()); +// assertEquals(expected, SimulationController.getSimulationHandler().getComponentsInSimulation().size()); } } diff --git a/src/test/java/ecdar/simulation/SimulationTest.java b/src/test/java/ecdar/simulation/SimulationTest.java index 8cd51066..d0b385bb 100644 --- a/src/test/java/ecdar/simulation/SimulationTest.java +++ b/src/test/java/ecdar/simulation/SimulationTest.java @@ -3,9 +3,6 @@ import EcdarProtoBuf.EcdarBackendGrpc; import EcdarProtoBuf.ObjectProtos; import EcdarProtoBuf.QueryProtos; -import EcdarProtoBuf.ObjectProtos.DecisionPoint; -import EcdarProtoBuf.ObjectProtos.LocationTuple; -import EcdarProtoBuf.ObjectProtos.SpecificComponent; import ecdar.abstractions.Component; import ecdar.abstractions.Location; import io.grpc.BindableService; From d464c828a81dc4bc85c0784a1a859c668140cfe1 Mon Sep 17 00:00:00 2001 From: Niels Vistisen <nielswps@gmail.com> Date: Sun, 14 May 2023 11:46:30 +0200 Subject: [PATCH 147/158] WIP: Rough tracelog state design draft --- .../java/ecdar/abstractions/Simulation.java | 2 - src/main/java/ecdar/abstractions/State.java | 9 --- .../ecdar/controllers/StateController.java | 22 ++++-- .../controllers/TracePaneController.java | 78 +++++++++++-------- .../presentations/QueryPresentation.fxml | 2 - .../presentations/StatePresentation.fxml | 22 ++++-- 6 files changed, 75 insertions(+), 60 deletions(-) diff --git a/src/main/java/ecdar/abstractions/Simulation.java b/src/main/java/ecdar/abstractions/Simulation.java index 8f862f47..92773c5e 100644 --- a/src/main/java/ecdar/abstractions/Simulation.java +++ b/src/main/java/ecdar/abstractions/Simulation.java @@ -37,8 +37,6 @@ public Simulation(String composition, State initialState) { this.initialState.set(initialState); this.currentState.set(initialState); - -// addStateToTraceLog(initialState); } private void setSimulatedComponents(String composition) { diff --git a/src/main/java/ecdar/abstractions/State.java b/src/main/java/ecdar/abstractions/State.java index f7212deb..2dc97a31 100644 --- a/src/main/java/ecdar/abstractions/State.java +++ b/src/main/java/ecdar/abstractions/State.java @@ -19,15 +19,6 @@ public State(ObjectProtos.State state) { this.protoState = state; } - /** - * All the clocks connected to the current simulation. - * - * @return a {@link Map} where the component name (String) is the key, and the location name is the value (String) - */ - public ObjectProtos.LocationTree getLocationTree() { - return locationTree; - } - public void consumeLeafLocations(Consumer<ObjectProtos.LeafLocation> consumer) { consumeLeafLocations(locationTree, consumer); } diff --git a/src/main/java/ecdar/controllers/StateController.java b/src/main/java/ecdar/controllers/StateController.java index 3bb01100..326a4774 100755 --- a/src/main/java/ecdar/controllers/StateController.java +++ b/src/main/java/ecdar/controllers/StateController.java @@ -17,22 +17,32 @@ */ public class StateController implements Initializable { public AnchorPane root; - public Label titleLabel; + public Label locationsLabel; + public Label clocksLabel; public JFXRippler rippler; // The transition that the view represents private final SimpleObjectProperty<State> state = new SimpleObjectProperty<>(); - private final SimpleObjectProperty<String> title = new SimpleObjectProperty<>(); + private final SimpleObjectProperty<String> locationsString = new SimpleObjectProperty<>(); + private final SimpleObjectProperty<String> clocksString = new SimpleObjectProperty<>(); @Override public void initialize(URL location, ResourceBundle resources) { - title.addListener(((observable, oldValue, newValue) -> { - titleLabel.setText(newValue); + locationsString.addListener(((observable, oldValue, newValue) -> { + locationsLabel.setText(newValue); })); + + clocksString.addListener(((observable, oldValue, newValue) -> { + clocksLabel.setText(newValue); + })); + } + + public void setLocationsString(String locationsString) { + this.locationsString.set(locationsString); } - public void setTitle(String title) { - this.title.set(title); + public void setClocksString(String clocksString) { + this.clocksString.set(clocksString); } public void setState(State state) { diff --git a/src/main/java/ecdar/controllers/TracePaneController.java b/src/main/java/ecdar/controllers/TracePaneController.java index ec5178f6..cace2a18 100755 --- a/src/main/java/ecdar/controllers/TracePaneController.java +++ b/src/main/java/ecdar/controllers/TracePaneController.java @@ -1,13 +1,12 @@ package ecdar.controllers; +import EcdarProtoBuf.ObjectProtos; import com.jfoenix.controls.JFXRippler; import ecdar.abstractions.State; import ecdar.presentations.StatePresentation; import ecdar.utility.colors.Color; import javafx.application.Platform; -import javafx.beans.property.IntegerProperty; import javafx.beans.property.SimpleBooleanProperty; -import javafx.beans.property.SimpleIntegerProperty; import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; import javafx.event.EventHandler; @@ -20,6 +19,7 @@ import org.kordamp.ikonli.javafx.FontIcon; import java.net.URL; +import java.util.ArrayList; import java.util.ResourceBundle; import java.util.function.BiConsumer; @@ -112,7 +112,7 @@ protected void setTraceLog(ObservableList<StatePresentation> traceLog) { if (!isTraceExpanded.get()) return; while (c.next()) { c.getRemoved().forEach(statePresentation -> traceList.getChildren().remove(statePresentation)); - c.getAddedSubList().forEach(statePresentation -> traceList.getChildren().add(statePresentation)); + c.getAddedSubList().forEach(this::insertStateInTrace); } }); @@ -173,8 +173,8 @@ private void insertStateInTrace(final StatePresentation statePresentation) { mouseExited.handle(event); }); - String title = traceString(statePresentation.getController().getState()); - statePresentation.getController().setTitle(title); + statePresentation.getController().setLocationsString(getStateLocationsString(statePresentation.getController().getState())); + statePresentation.getController().setClocksString(getStateClockConstraintsString(statePresentation.getController().getState())); // Only insert the presentation into the view if the trace is expanded & statePresentation is not null if (isTraceExpanded.get()) { @@ -183,38 +183,48 @@ private void insertStateInTrace(final StatePresentation statePresentation) { } /** - * A helper method that returns a string representing a state in the trace log + * A helper method that returns a string representing the locations of a state in the trace log * * @param state The State to represent - * @return A string representing the state + * @return A string representing the locations */ - private String traceString(State state) { - // ToDo: Generate string from state -// StringBuilder title = new StringBuilder("("); -// int length = state.getLocationTree().size(); -// for (int i = 0; i < length; i++) { -// Location loc = Ecdar.getProject() -// .findComponent(state.getLocationTree().get(i).getKey()) -// .findLocation(state.getLocationTree().get(i).getValue()); -// String locationName = loc.getId(); -// if (i == length - 1) { -// title.append(locationName); -// } else { -// title.append(locationName).append(", "); -// } -// } -// title.append(")\n"); -// -// StringBuilder clocks = new StringBuilder(); -// for (var constraint : state.getState().getFederation().getDisjunction().getConjunctions(0).getConstraintsList()) { -// var x = constraint.getX().getClockName(); -// var y = constraint.getY().getClockName(); -// var c = constraint.getC(); -// var strict = constraint.getStrict(); -// clocks.append(x).append(" - ").append(y).append(strict ? " < " : " <= ").append(c).append("\n"); -// } -// return title.toString() + clocks.toString(); - return "Not implemented: " + state.toString(); + private String getStateLocationsString(State state) { + StringBuilder locationsString = new StringBuilder("("); + + var leafLocations = new ArrayList<ObjectProtos.LeafLocation>(); + state.consumeLeafLocations(leafLocations::add); + + int length = leafLocations.size(); + for (int i = 0; i < length; i++) { + locationsString.append(leafLocations.get(i).getComponentInstance().getComponentName()); + locationsString.append(leafLocations.get(i).getId()); + + if (i != length - 1) { + locationsString.append(", "); + } + } + locationsString.append(")\n"); + + return locationsString.toString(); + } + + /** + * A helper method that returns a string representing the clock constraints of a state in the trace log + * + * @param state The State to represent + * @return A string representing the clock constraints + */ + private String getStateClockConstraintsString(State state) { + StringBuilder clocksString = new StringBuilder(); + for (var constraint : state.getProtoState().getZone().getConjunctions(0).getConstraintsList()) { + var x = constraint.getX().getComponentClock().getClockName(); + var y = constraint.getY().getComponentClock().getClockName(); + var c = constraint.getC(); + var strict = constraint.getStrict(); + clocksString.append(x).append(" - ").append(y).append(strict ? " < " : " <= ").append(c).append("\n"); + } + + return clocksString.toString(); } /** diff --git a/src/main/resources/ecdar/presentations/QueryPresentation.fxml b/src/main/resources/ecdar/presentations/QueryPresentation.fxml index 9bbbef91..57aa8c01 100644 --- a/src/main/resources/ecdar/presentations/QueryPresentation.fxml +++ b/src/main/resources/ecdar/presentations/QueryPresentation.fxml @@ -2,8 +2,6 @@ <?import com.jfoenix.controls.*?> <?import javafx.scene.layout.*?> <?import org.kordamp.ikonli.javafx.FontIcon?> -<?import javafx.scene.control.TitledPane?> -<?import javafx.scene.control.Separator?> <?import javafx.scene.text.Text?> <?import javafx.geometry.Insets?> <fx:root xmlns:fx="http://javafx.com/fxml/1" diff --git a/src/main/resources/ecdar/presentations/StatePresentation.fxml b/src/main/resources/ecdar/presentations/StatePresentation.fxml index 94e02afa..f91e3fc1 100755 --- a/src/main/resources/ecdar/presentations/StatePresentation.fxml +++ b/src/main/resources/ecdar/presentations/StatePresentation.fxml @@ -5,18 +5,26 @@ <?import javafx.geometry.Insets?> <?import com.jfoenix.controls.JFXRippler?> +<?import javafx.scene.text.Text?> <fx:root xmlns="http://javafx.com/javafx" xmlns:fx="http://javafx.com/fxml" fx:controller="ecdar.controllers.StateController" type="AnchorPane"> - <JFXRippler fx:id="rippler" AnchorPane.leftAnchor="0" AnchorPane.rightAnchor="0"> - <AnchorPane minHeight="48"> + <JFXRippler fx:id="rippler"> + <VBox spacing="10"> <padding> - <Insets top="10" right="20" bottom="10" left="20"/> + <Insets topRightBottomLeft="10"/> </padding> - <Label fx:id="titleLabel" styleClass="body1" wrapText="true" - AnchorPane.topAnchor="0" AnchorPane.bottomAnchor="0" AnchorPane.leftAnchor="0" AnchorPane.rightAnchor="0"/> - </AnchorPane> - + <Text text="Locations" styleClass="subhead" style="-fx-stroke-width: 1.2em"/> + <HBox> + <Region minWidth="10" maxWidth="10"/> + <Label fx:id="locationsLabel" styleClass="body1" wrapText="true"/> + </HBox> + <Text text="Clocks" styleClass="subhead" style="-fx-stroke-width: 1.2em"/> + <HBox> + <Region minWidth="10" maxWidth="10"/> + <Label fx:id="clocksLabel" styleClass="body1" wrapText="true"/> + </HBox> + </VBox> </JFXRippler> </fx:root> From e502b7689c39add3d666fc56d0a77438f814aeab Mon Sep 17 00:00:00 2001 From: Niels Vistisen <nielswps@gmail.com> Date: Wed, 17 May 2023 13:12:49 +0200 Subject: [PATCH 148/158] WIP: Decision design draft implemented and refactoring --- src/main/java/ecdar/abstractions/State.java | 46 ++++++++++++++++- .../ecdar/controllers/DecisionController.java | 7 +-- .../ecdar/controllers/StateController.java | 25 +++------- .../controllers/TracePaneController.java | 50 ------------------- .../presentations/DecisionPresentation.fxml | 20 ++++++-- .../presentations/StatePresentation.fxml | 6 ++- 6 files changed, 74 insertions(+), 80 deletions(-) diff --git a/src/main/java/ecdar/abstractions/State.java b/src/main/java/ecdar/abstractions/State.java index 2dc97a31..2ddae96d 100644 --- a/src/main/java/ecdar/abstractions/State.java +++ b/src/main/java/ecdar/abstractions/State.java @@ -5,6 +5,7 @@ import javafx.collections.ObservableMap; import java.math.BigDecimal; +import java.util.ArrayList; import java.util.Map; import java.util.function.Consumer; @@ -15,7 +16,7 @@ public class State { public final ObservableMap<String, BigDecimal> clocks = FXCollections.observableHashMap(); public State(ObjectProtos.State state) { - this.locationTree = state.getLocationTree(); // ToDo NIELS: Ensure that the source is indeed the same for all decisions + this.locationTree = state.getLocationTree(); this.protoState = state; } @@ -51,4 +52,47 @@ public ObservableMap<String, BigDecimal> getSimulationClocks() { public ObjectProtos.State getProtoState() { return protoState; } + + /** + * A helper method that returns a string representing the clock constraints of a state in the trace log + * + * @return A string representing the clock constraints + */ + public String getStateClockConstraintsString() { + StringBuilder clocksString = new StringBuilder(); + for (var constraint : getProtoState().getZone().getConjunctions(0).getConstraintsList()) { + var x = constraint.getX().getComponentClock().getClockName(); + var y = constraint.getY().getComponentClock().getClockName(); + var c = constraint.getC(); + var strict = constraint.getStrict(); + clocksString.append(x).append(" - ").append(y).append(strict ? " < " : " <= ").append(c).append("\n"); + } + + return clocksString.toString(); + } + + /** + * A helper method that returns a string representing the locations of a state in the trace log + * + * @return A string representing the locations + */ + public String getStateLocationsString() { + StringBuilder locationsString = new StringBuilder(); + + var leafLocations = new ArrayList<ObjectProtos.LeafLocation>(); + consumeLeafLocations(leafLocations::add); + + int length = leafLocations.size(); + for (int i = 0; i < length; i++) { + locationsString.append(leafLocations.get(i).getComponentInstance().getComponentName()); + locationsString.append('.'); + locationsString.append(leafLocations.get(i).getId()); + + if (i != length - 1) { + locationsString.append("\n"); + } + } + + return locationsString.toString(); + } } diff --git a/src/main/java/ecdar/controllers/DecisionController.java b/src/main/java/ecdar/controllers/DecisionController.java index 0fe01e0e..4aab902c 100644 --- a/src/main/java/ecdar/controllers/DecisionController.java +++ b/src/main/java/ecdar/controllers/DecisionController.java @@ -12,15 +12,16 @@ public class DecisionController implements Initializable { public JFXRippler rippler; - public Label decisionDescription; + public Label action; + public Label clockConstraints; private final ObjectProperty<Decision> decision = new SimpleObjectProperty<>(); @Override public void initialize(URL location, ResourceBundle resources) { decision.addListener((observable, oldValue, newValue) -> { - // ToDo NIELS: Add all relevant information to the description - decisionDescription.setText(newValue.composition + ": " + newValue.action); + action.setText(newValue.action); + clockConstraints.setText(newValue.source.getStateClockConstraintsString()); }); } diff --git a/src/main/java/ecdar/controllers/StateController.java b/src/main/java/ecdar/controllers/StateController.java index 326a4774..4ad268f3 100755 --- a/src/main/java/ecdar/controllers/StateController.java +++ b/src/main/java/ecdar/controllers/StateController.java @@ -17,32 +17,19 @@ */ public class StateController implements Initializable { public AnchorPane root; - public Label locationsLabel; - public Label clocksLabel; + public Label locations; + public Label clockConstraints; public JFXRippler rippler; // The transition that the view represents private final SimpleObjectProperty<State> state = new SimpleObjectProperty<>(); - private final SimpleObjectProperty<String> locationsString = new SimpleObjectProperty<>(); - private final SimpleObjectProperty<String> clocksString = new SimpleObjectProperty<>(); @Override public void initialize(URL location, ResourceBundle resources) { - locationsString.addListener(((observable, oldValue, newValue) -> { - locationsLabel.setText(newValue); - })); - - clocksString.addListener(((observable, oldValue, newValue) -> { - clocksLabel.setText(newValue); - })); - } - - public void setLocationsString(String locationsString) { - this.locationsString.set(locationsString); - } - - public void setClocksString(String clocksString) { - this.clocksString.set(clocksString); + state.addListener((observable, oldValue, newValue) -> { + locations.setText(newValue.getStateLocationsString()); + clockConstraints.setText(newValue.getStateClockConstraintsString()); + }); } public void setState(State state) { diff --git a/src/main/java/ecdar/controllers/TracePaneController.java b/src/main/java/ecdar/controllers/TracePaneController.java index cace2a18..018f9799 100755 --- a/src/main/java/ecdar/controllers/TracePaneController.java +++ b/src/main/java/ecdar/controllers/TracePaneController.java @@ -1,6 +1,5 @@ package ecdar.controllers; -import EcdarProtoBuf.ObjectProtos; import com.jfoenix.controls.JFXRippler; import ecdar.abstractions.State; import ecdar.presentations.StatePresentation; @@ -19,7 +18,6 @@ import org.kordamp.ikonli.javafx.FontIcon; import java.net.URL; -import java.util.ArrayList; import java.util.ResourceBundle; import java.util.function.BiConsumer; @@ -173,60 +171,12 @@ private void insertStateInTrace(final StatePresentation statePresentation) { mouseExited.handle(event); }); - statePresentation.getController().setLocationsString(getStateLocationsString(statePresentation.getController().getState())); - statePresentation.getController().setClocksString(getStateClockConstraintsString(statePresentation.getController().getState())); - // Only insert the presentation into the view if the trace is expanded & statePresentation is not null if (isTraceExpanded.get()) { traceList.getChildren().add(statePresentation); } } - /** - * A helper method that returns a string representing the locations of a state in the trace log - * - * @param state The State to represent - * @return A string representing the locations - */ - private String getStateLocationsString(State state) { - StringBuilder locationsString = new StringBuilder("("); - - var leafLocations = new ArrayList<ObjectProtos.LeafLocation>(); - state.consumeLeafLocations(leafLocations::add); - - int length = leafLocations.size(); - for (int i = 0; i < length; i++) { - locationsString.append(leafLocations.get(i).getComponentInstance().getComponentName()); - locationsString.append(leafLocations.get(i).getId()); - - if (i != length - 1) { - locationsString.append(", "); - } - } - locationsString.append(")\n"); - - return locationsString.toString(); - } - - /** - * A helper method that returns a string representing the clock constraints of a state in the trace log - * - * @param state The State to represent - * @return A string representing the clock constraints - */ - private String getStateClockConstraintsString(State state) { - StringBuilder clocksString = new StringBuilder(); - for (var constraint : state.getProtoState().getZone().getConjunctions(0).getConstraintsList()) { - var x = constraint.getX().getComponentClock().getClockName(); - var y = constraint.getY().getComponentClock().getClockName(); - var c = constraint.getC(); - var strict = constraint.getStrict(); - clocksString.append(x).append(" - ").append(y).append(strict ? " < " : " <= ").append(c).append("\n"); - } - - return clocksString.toString(); - } - /** * Method to be called when clicking on the expand rippler in the trace toolbar */ diff --git a/src/main/resources/ecdar/presentations/DecisionPresentation.fxml b/src/main/resources/ecdar/presentations/DecisionPresentation.fxml index ae902014..2c330c84 100755 --- a/src/main/resources/ecdar/presentations/DecisionPresentation.fxml +++ b/src/main/resources/ecdar/presentations/DecisionPresentation.fxml @@ -5,17 +5,27 @@ <?import javafx.geometry.Insets?> <?import com.jfoenix.controls.JFXRippler?> +<?import javafx.scene.text.Text?> <fx:root xmlns="http://javafx.com/javafx" xmlns:fx="http://javafx.com/fxml" fx:controller="ecdar.controllers.DecisionController" type="AnchorPane"> <JFXRippler fx:id="rippler" AnchorPane.leftAnchor="0" AnchorPane.rightAnchor="0"> - <AnchorPane minHeight="48"> + <VBox spacing="10"> <padding> - <Insets top="10" right="20" bottom="10" left="20"/> + <Insets topRightBottomLeft="10"/> </padding> - <Label fx:id="decisionDescription" styleClass="body1" wrapText="true" - AnchorPane.topAnchor="0" AnchorPane.bottomAnchor="0" AnchorPane.leftAnchor="0" AnchorPane.rightAnchor="0"/> - </AnchorPane> + <HBox alignment="BASELINE_LEFT"> + <Text text="Action: " styleClass="subhead" style="-fx-stroke-width: 1.2em"/> + <Label fx:id="action" styleClass="body1" wrapText="true"/> + </HBox> + <Text text="Clock Constraints" styleClass="subhead" style="-fx-stroke-width: 1.2em"/> + <HBox> + <Region minWidth="10" maxWidth="10"/> + <Label fx:id="clockConstraints" styleClass="body1" wrapText="true"/> + </HBox> + </VBox> </JFXRippler> + <StackPane prefHeight="1" style="-fx-background-color:-grey-300;" AnchorPane.rightAnchor="0" + AnchorPane.bottomAnchor="0" AnchorPane.leftAnchor="0" minHeight="1" maxHeight="1"/> </fx:root> diff --git a/src/main/resources/ecdar/presentations/StatePresentation.fxml b/src/main/resources/ecdar/presentations/StatePresentation.fxml index f91e3fc1..5bba2f2a 100755 --- a/src/main/resources/ecdar/presentations/StatePresentation.fxml +++ b/src/main/resources/ecdar/presentations/StatePresentation.fxml @@ -18,13 +18,15 @@ <Text text="Locations" styleClass="subhead" style="-fx-stroke-width: 1.2em"/> <HBox> <Region minWidth="10" maxWidth="10"/> - <Label fx:id="locationsLabel" styleClass="body1" wrapText="true"/> + <Label fx:id="locations" styleClass="body1" wrapText="true"/> </HBox> <Text text="Clocks" styleClass="subhead" style="-fx-stroke-width: 1.2em"/> <HBox> <Region minWidth="10" maxWidth="10"/> - <Label fx:id="clocksLabel" styleClass="body1" wrapText="true"/> + <Label fx:id="clockConstraints" styleClass="body1" wrapText="true"/> </HBox> </VBox> </JFXRippler> + <StackPane prefHeight="1" style="-fx-background-color:-grey-300;" AnchorPane.rightAnchor="0" + AnchorPane.bottomAnchor="0" AnchorPane.leftAnchor="0" minHeight="1" maxHeight="1"/> </fx:root> From b9d7f5c0fd7e009f83a9d71a1fe6487c13b48be0 Mon Sep 17 00:00:00 2001 From: Niels Vistisen <nielswps@gmail.com> Date: Wed, 17 May 2023 13:15:55 +0200 Subject: [PATCH 149/158] WIP: minWidth removed from right sim pane --- .../ecdar/presentations/RightSimPanePresentation.fxml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/resources/ecdar/presentations/RightSimPanePresentation.fxml b/src/main/resources/ecdar/presentations/RightSimPanePresentation.fxml index 06064369..d505da16 100755 --- a/src/main/resources/ecdar/presentations/RightSimPanePresentation.fxml +++ b/src/main/resources/ecdar/presentations/RightSimPanePresentation.fxml @@ -6,8 +6,7 @@ xmlns="http://javafx.com/javafx/8.0.76-ea" type="StackPane" fx:id="root" - fx:controller="ecdar.controllers.RightSimPaneController" - minWidth="400"> + fx:controller="ecdar.controllers.RightSimPaneController"> <AnchorPane> <ScrollPane fitToHeight="true" fitToWidth="true" From 2a1610406bee772de49e9ca261fed29974084a94 Mon Sep 17 00:00:00 2001 From: Niels Vistisen <nielswps@gmail.com> Date: Thu, 18 May 2023 08:27:35 +0200 Subject: [PATCH 150/158] Highlighting of simulation extracted into helper class --- src/main/java/ecdar/abstractions/State.java | 1 - .../controllers/SimulationController.java | 283 +++++++----------- .../helpers/SimulationHighlighter.java | 143 +++++++++ .../presentations/StatePresentation.fxml | 2 +- 4 files changed, 251 insertions(+), 178 deletions(-) create mode 100644 src/main/java/ecdar/utility/helpers/SimulationHighlighter.java diff --git a/src/main/java/ecdar/abstractions/State.java b/src/main/java/ecdar/abstractions/State.java index 2ddae96d..ee4bfa37 100644 --- a/src/main/java/ecdar/abstractions/State.java +++ b/src/main/java/ecdar/abstractions/State.java @@ -10,7 +10,6 @@ import java.util.function.Consumer; public class State { - // locations and edges are saved as key-value pair where key is component name and value = id private final ObjectProtos.LocationTree locationTree; private final ObjectProtos.State protoState; public final ObservableMap<String, BigDecimal> clocks = FXCollections.observableHashMap(); diff --git a/src/main/java/ecdar/controllers/SimulationController.java b/src/main/java/ecdar/controllers/SimulationController.java index 935b3bd6..b57dc2cf 100644 --- a/src/main/java/ecdar/controllers/SimulationController.java +++ b/src/main/java/ecdar/controllers/SimulationController.java @@ -5,6 +5,7 @@ import ecdar.Ecdar; import ecdar.backend.BackendHelper; import ecdar.presentations.*; +import ecdar.utility.helpers.SimulationHighlighter; import javafx.application.Platform; import javafx.collections.FXCollections; import javafx.collections.ListChangeListener; @@ -14,17 +15,18 @@ import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.collections.ObservableMap; +import javafx.event.EventHandler; import javafx.fxml.Initializable; import javafx.geometry.Insets; import javafx.scene.Group; +import javafx.scene.Node; import javafx.scene.control.ScrollPane; +import javafx.scene.input.MouseEvent; import javafx.scene.layout.FlowPane; import javafx.scene.layout.StackPane; import java.net.URL; import java.util.*; -import java.util.function.Consumer; -import java.util.stream.Collectors; public class SimulationController implements ModeController, Initializable { public StackPane root; @@ -39,6 +41,8 @@ public class SimulationController implements ModeController, Initializable { private static final ObjectProperty<Simulation> activeSimulation = new SimpleObjectProperty<>(null); private static final ObjectProperty<State> selectedState = new SimpleObjectProperty<>(); + private SimulationHighlighter highlighter; + /** * The amount that is going be zoomed in/out for each press on + or - */ @@ -66,16 +70,16 @@ public class SimulationController implements ModeController, Initializable { private boolean isMaxZoomInReached = false; private boolean isMaxZoomOutReached = false; - private final ObservableMap<String, ProcessPresentation> componentNameProcessPresentationMap = FXCollections.observableHashMap(); - - // ToDo NIELS: Remove static - public static State getCurrentState() throws NullPointerException { - return activeSimulation.get().currentState.get(); - } + private EventHandler<MouseEvent> decisionOnMouseEnter = event -> { + // ToDo: Change highlighted state + highlighter.highlightProcessState(((DecisionPresentation)event.getTarget()).getController().getDecision().target); + }; + private EventHandler<MouseEvent> decisionOnMouseExit = event -> { + // ToDo: Change highlighted state + highlighter.highlightProcessState(getActiveSimulation().currentState.get()); + }; - public static List<Component> getSimulatedComponents() { - return activeSimulation.get().simulatedComponents; - } + private final ObservableMap<String, ProcessPresentation> componentNameProcessPresentationMap = FXCollections.observableHashMap(); @Override public void initialize(final URL location, final ResourceBundle resources) { @@ -83,6 +87,7 @@ public void initialize(final URL location, final ResourceBundle resources) { initializeProcessContainer(); initializeWindowResizing(); initializeZoom(); + highlighter = new SimulationHighlighter(componentNameProcessPresentationMap, activeSimulation); } /** @@ -137,73 +142,55 @@ private void initializeWindowResizing() { }); } - /** - * Clears the {@link #processContainer} and the {@link #componentNameProcessPresentationMap}. - */ - private void clearOverview() { - processContainer.getChildren().clear(); - componentNameProcessPresentationMap.clear(); - } + private void initializeActiveSimulation(QueryProtos.SimulationStepResponse response, String composition) { + State newState = createStateFromResponse(response); - /** - * Handles the scaling of the width of the {@link #processContainer} - * - * @param oldValue the width of {@link #scrollPane} before the change - * @param newValue the width of {@link #scrollPane} after the change - */ - private void handleWidthOnScale(final Number oldValue, final Number newValue) { - if (resetZoom) { //Zoom reset - resetZoom = false; - processContainer.setMinWidth(scrollPane.getWidth() - SCROLLPANE_OFFSET); - processContainer.setMaxWidth(scrollPane.getWidth() - SCROLLPANE_OFFSET); - } else if (oldValue.doubleValue() > newValue.doubleValue()) { //Zoom in - resetZoom = false; - processContainer.setMinWidth(Math.round(processContainer.getWidth() * SCALE_DELTA)); - processContainer.setMaxWidth(Math.round(processContainer.getWidth() * SCALE_DELTA)); - } else { // Zoom out - resetZoom = false; - processContainer.setMinWidth(Math.round(processContainer.getWidth() * (1 / SCALE_DELTA))); - processContainer.setMaxWidth(Math.round(processContainer.getWidth() * (1 / SCALE_DELTA))); - } - } + Simulation newSimulation = new Simulation(composition, newState); + activeSimulation.set(newSimulation); - /** - * Unhighlights all processes - */ - public void unhighlightProcesses() { - for (final ProcessPresentation presentation : getProcessPresentations()) { - presentation.getController().unhighlightProcess(); - presentation.showActive(); - } - } + newSimulation.simulatedComponents.forEach(component -> { + var processPresentation = new ProcessPresentation(component); + componentNameProcessPresentationMap.put(component.getName(), processPresentation); + processContainer.getChildren().addAll(processPresentation); + }); - private List<ProcessPresentation> getProcessPresentations() { - return new ArrayList<>(componentNameProcessPresentationMap.values()); + leftSimPane.getController().setTraceLog(newSimulation.traceLog); + rightSimPane.getController().setDecisionsList(newSimulation.availableDecisions); + + updateSimulationState(response.getNewDecisionPointsList()); + + // Update highlighting when state changes + newSimulation.currentState.addListener(observable -> { + updateSimulationVariablesAndClocks(); + highlighter.updateHighlighting(); + highlighter.highlightTraceStates(leftSimPane.getController().tracePanePresentation.getController().traceList.getChildren()); + }); + + rightSimPane.getController().availableDecisionsVBox.getChildren().addListener((ListChangeListener<? super Node>) c -> { + while (c.next()) { + c.getAddedSubList().forEach(decisionPresentation -> { + decisionPresentation.setOnMouseEntered(decisionOnMouseEnter); + decisionPresentation.setOnMouseExited(decisionOnMouseExit); + }); + } + }); } - /** - * Finds the processes for the input locations in the input {@link State} and highlights the locations. - * - * @param state The state with the locations to highlight - */ - public void highlightProcessState(final State state) { - Consumer<ObjectProtos.LeafLocation> leafLocationConsumer = - (leaf) -> componentNameProcessPresentationMap.get(leaf.getComponentInstance().getComponentName()) - .getController().highlightLocation(leaf.getId()); + // ToDo NIELS: Remove static + public static State getCurrentState() throws NullPointerException { + return activeSimulation.get().currentState.get(); + } - Platform.runLater(() -> state.consumeLeafLocations(leafLocationConsumer)); + public static List<Component> getSimulatedComponents() { + return activeSimulation.get().simulatedComponents; } - public void highlightAvailableEdgesFromDecisions(List<DecisionPresentation> availableDecisions) { - List<String> edges = availableDecisions.stream() - .map(decisionPresentation -> decisionPresentation.getController().getDecision().edgeIds) - .flatMap(List::stream) - .collect(Collectors.toList()); + private Simulation getActiveSimulation() { + return activeSimulation.get(); + } - // Remove previous highlighting of edges - componentNameProcessPresentationMap.values().forEach(p -> p.getController() - .getComponent().getEdges() - .forEach(e -> e.setIsHighlighted(edges.contains(e.getId())))); + private List<ProcessPresentation> getProcessPresentations() { + return new ArrayList<>(componentNameProcessPresentationMap.values()); } /** @@ -215,38 +202,40 @@ public void initialStep(String composition) { (error) -> Ecdar.showToast("Could not start simulation:\n" + error.getMessage())); } - private void initializeActiveSimulation(QueryProtos.SimulationStepResponse response, String composition) { - State newState = createStateFromResponse(response); - - Simulation newSimulation = new Simulation(composition, newState); - activeSimulation.set(newSimulation); + /** + * Take a step in the simulation. + */ + public void nextStep(Decision decision) { + // removes invalid states from the log when stepping forward after previewing a previous state + removeStatesFromLog(getActiveSimulation().traceLog.filtered(statePresentation -> statePresentation.getController().getState() == getActiveSimulation().currentState.get()).stream().findFirst().orElse(null)); - newSimulation.simulatedComponents.forEach(component -> { - var processPresentation = new ProcessPresentation(component); - componentNameProcessPresentationMap.put(component.getName(), processPresentation); - processContainer.getChildren().addAll(processPresentation); - }); + BackendHelper.getDefaultEngine().enqueueRequest(decision, + (response) -> { + // ToDo: This is temp solution to compile but should be fixed to handle ambiguity + State newState = createStateFromResponse(response); + getActiveSimulation().currentState.set(newState); + updateSimulationState(response.getNewDecisionPointsList()); + }, - leftSimPane.getController().setTraceLog(newSimulation.traceLog); - rightSimPane.getController().setDecisionsList(newSimulation.availableDecisions); + (error) -> Ecdar.showToast("Could not take next step in simulation\nError: " + error.getMessage())); + } - // Update highlighting when state changes - newSimulation.currentState.addListener(observable -> { - updateSimulationVariablesAndClocks(); - updateHighlighting(); - highlightTraceStates(); - }); + /** + * Resets the current simulation, and prepares for a new simulation by clearing the + * {@link SimulationController#processContainer} and adding the processes of the new simulation. + */ + public void resetSimulation(String queryToSimulate) { + clearOverview(); + initialStep(queryToSimulate); + } - updateSimulationState(response.getNewDecisionPointsList()); + private void previewState(final StatePresentation statePresentation) throws NullPointerException { + getActiveSimulation().currentState.set(statePresentation.getController().getState()); } private void updateSimulationState(List<ObjectProtos.Decision> availableDecisions) { Platform.runLater(() -> { - getActiveSimulation().addStateToTraceLog(getActiveSimulation().currentState.get(), this::previewStep); - - updateSimulationVariablesAndClocks(); - updateHighlighting(); - highlightTraceStates(); + getActiveSimulation().addStateToTraceLog(getActiveSimulation().currentState.get(), this::previewState); getActiveSimulation().availableDecisions.clear(); if (availableDecisions.isEmpty()) { @@ -255,6 +244,10 @@ private void updateSimulationState(List<ObjectProtos.Decision> availableDecision } else { getActiveSimulation().addDecisionsFromProto(availableDecisions); } + + updateSimulationVariablesAndClocks(); + highlighter.updateHighlighting(); + highlighter.highlightTraceStates(leftSimPane.getController().tracePanePresentation.getController().traceList.getChildren()); }); } @@ -291,62 +284,35 @@ private void updateSimulationVariablesAndClocks() { } /** - * Initializer method to set up listeners that handle highlighting when selected/current state/transition changes + * Clears the {@link #processContainer} and the {@link #componentNameProcessPresentationMap}. */ - private void updateHighlighting() { - highlightAvailableEdgesFromDecisions(getActiveSimulation().availableDecisions); - - Platform.runLater(() -> { - unhighlightProcesses(); - highlightProcessState(getActiveSimulation().currentState.get()); - }); + private void clearOverview() { + processContainer.getChildren().clear(); + componentNameProcessPresentationMap.clear(); } /** - * Initializes the fading of states in the trace list when a state is previewed + * Handles the scaling of the width of the {@link #processContainer} + * + * @param oldValue the width of {@link #scrollPane} before the change + * @param newValue the width of {@link #scrollPane} after the change */ - private void highlightTraceStates() { - var traceListStates = leftSimPane.getController().tracePanePresentation.getController().traceList.getChildren(); - - var activeStatePresentation = traceListStates.stream() - .filter(n -> ((StatePresentation) n) - .getController().getState() - .equals(getActiveSimulation().currentState.get())) - .findFirst().orElse(null); - - if (activeStatePresentation == null || traceListStates.get(traceListStates.size() - 1).equals(activeStatePresentation)) - return; - - traceListStates.forEach(trace -> trace.setOpacity(1)); - int i = traceListStates.size() - 1; - while (traceListStates.get(i) != activeStatePresentation) { - traceListStates.get(i).setOpacity(0.4); - i--; + private void handleWidthOnScale(final Number oldValue, final Number newValue) { + if (resetZoom) { //Zoom reset + resetZoom = false; + processContainer.setMinWidth(scrollPane.getWidth() - SCROLLPANE_OFFSET); + processContainer.setMaxWidth(scrollPane.getWidth() - SCROLLPANE_OFFSET); + } else if (oldValue.doubleValue() > newValue.doubleValue()) { //Zoom in + resetZoom = false; + processContainer.setMinWidth(Math.round(processContainer.getWidth() * SCALE_DELTA)); + processContainer.setMaxWidth(Math.round(processContainer.getWidth() * SCALE_DELTA)); + } else { // Zoom out + resetZoom = false; + processContainer.setMinWidth(Math.round(processContainer.getWidth() * (1 / SCALE_DELTA))); + processContainer.setMaxWidth(Math.round(processContainer.getWidth() * (1 / SCALE_DELTA))); } } - private Simulation getActiveSimulation() { - return activeSimulation.get(); - } - - /** - * Take a step in the simulation. - */ - public void nextStep(Decision decision) { - // removes invalid states from the log when stepping forward after previewing a previous state - removeStatesFromLog(getActiveSimulation().traceLog.filtered(statePresentation -> statePresentation.getController().getState() == getActiveSimulation().currentState.get()).stream().findFirst().orElse(null)); - - BackendHelper.getDefaultEngine().enqueueRequest(decision, - (response) -> { - // ToDo: This is temp solution to compile but should be fixed to handle ambiguity - State newState = createStateFromResponse(response); - getActiveSimulation().currentState.set(newState); - updateSimulationState(response.getNewDecisionPointsList()); - }, - - (error) -> Ecdar.showToast("Could not take next step in simulation\nError: " + error.getMessage())); - } - /** * Removes all states from the trace log after the given state */ @@ -357,45 +323,10 @@ private void removeStatesFromLog(StatePresentation statePresentation) { getActiveSimulation().traceLog.remove(newLastStateIndex + 1, getActiveSimulation().traceLog.size()); } - private void previewStep(final StatePresentation statePresentation) throws NullPointerException { - getActiveSimulation().currentState.set(statePresentation.getController().getState()); - } - - /** - * Resets the current simulation, and prepares for a new simulation by clearing the - * {@link SimulationController#processContainer} and adding the processes of the new simulation. - */ - public void resetSimulation(String queryToSimulate) { - clearOverview(); - initialStep(queryToSimulate); - } - private State createStateFromResponse(QueryProtos.SimulationStepResponse response) { return new State(response.getNewDecisionPoints(0).getSource()); // ToDo NIELS: Each source is only a subset, we should combine them to the full state } - /** - * Highlights the edges from the reachability response - */ - public void highlightReachabilityEdges(ArrayList<String> ids) throws NullPointerException { - //unhighlight all edges - for (var comp : getActiveSimulation().simulatedComponents) { - for (var edge : comp.getEdges()) { - edge.setIsHighlightedForReachability(false); - } - } - //highlight the edges from the reachability response - for (var comp : getActiveSimulation().simulatedComponents) { - for (var edge : comp.getEdges()) { - for (var id : ids) { - if (edge.getId().equals(id)) { - edge.setIsHighlightedForReachability(true); - } - } - } - } - } - public static void setSelectedState(State selectedState) { SimulationController.selectedState.set(selectedState); } diff --git a/src/main/java/ecdar/utility/helpers/SimulationHighlighter.java b/src/main/java/ecdar/utility/helpers/SimulationHighlighter.java new file mode 100644 index 00000000..ddb9b10e --- /dev/null +++ b/src/main/java/ecdar/utility/helpers/SimulationHighlighter.java @@ -0,0 +1,143 @@ +package ecdar.utility.helpers; + +import EcdarProtoBuf.ObjectProtos; +import ecdar.abstractions.Simulation; +import ecdar.abstractions.State; +import ecdar.presentations.DecisionPresentation; +import ecdar.presentations.ProcessPresentation; +import ecdar.presentations.StatePresentation; +import javafx.application.Platform; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.collections.ObservableMap; +import javafx.scene.Node; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; +import java.util.stream.Collectors; + +public class SimulationHighlighter { + // ToDo NIELS: Clean up by removing extraneous methods + private final ObservableMap<String, ProcessPresentation> componentNameProcessPresentationMap; + private final ObjectProperty<Simulation> simulation = new SimpleObjectProperty<>(); + private ArrayList<String> temporarilyHighlightedLocations = new ArrayList<>(); + + public SimulationHighlighter(ObservableMap<String, ProcessPresentation> componentNameProcessPresentationMap, ObjectProperty<Simulation> simulation) { + this.componentNameProcessPresentationMap = componentNameProcessPresentationMap; + this.simulation.bind(simulation); + } + + private Simulation getActiveSimulation() { + return simulation.get(); + } + + private List<ProcessPresentation> getProcessPresentations() { + return new ArrayList<>(componentNameProcessPresentationMap.values()); + } + + /** + * Initializer method to set up listeners that handle highlighting when selected/current state/transition changes + */ + public void updateHighlighting() { + highlightAvailableEdgesFromDecisions(getActiveSimulation().availableDecisions); + + Platform.runLater(() -> { + unhighlightProcesses(); + highlightProcessState(getActiveSimulation().currentState.get()); + }); + } + + /** + * Unhighlights all processes + */ + public void unhighlightProcesses() { + for (final ProcessPresentation presentation : getProcessPresentations()) { + presentation.getController().unhighlightProcess(); + presentation.showActive(); + } + } + + /** + * Initializes the fading of states in the trace list when a state is previewed + */ + public void highlightTraceStates(List<Node> traceListStates) { + var activeStatePresentation = traceListStates.stream() + .filter(n -> ((StatePresentation) n) + .getController().getState() + .equals(getActiveSimulation().currentState.get())) + .findFirst().orElse(null); + + if (activeStatePresentation == null || traceListStates.get(traceListStates.size() - 1).equals(activeStatePresentation)) + return; + + traceListStates.forEach(trace -> trace.setOpacity(1)); + int i = traceListStates.size() - 1; + while (traceListStates.get(i) != activeStatePresentation) { + traceListStates.get(i).setOpacity(0.4); + i--; + } + } + + /** + * Highlights the edges from the reachability response + */ + public void highlightReachabilityEdges(ArrayList<String> ids) throws NullPointerException { + //unhighlight all edges + for (var comp : getActiveSimulation().simulatedComponents) { + for (var edge : comp.getEdges()) { + edge.setIsHighlightedForReachability(false); + } + } + //highlight the edges from the reachability response + for (var comp : getActiveSimulation().simulatedComponents) { + for (var edge : comp.getEdges()) { + for (var id : ids) { + if (edge.getId().equals(id)) { + edge.setIsHighlightedForReachability(true); + } + } + } + } + } + + /** + * Finds the processes for the input locations in the input {@link State} and highlights the locations. + * + * @param state The state with the locations to highlight + */ + public void highlightProcessState(final State state) { + Consumer<ObjectProtos.LeafLocation> leafLocationConsumer = + (leaf) -> componentNameProcessPresentationMap.get(leaf.getComponentInstance().getComponentName()) + .getController().highlightLocation(leaf.getId()); + + Platform.runLater(() -> state.consumeLeafLocations(leafLocationConsumer)); + } + + /** + * Highlights the edges that are part of at least one of the possible decisions + * + * @param availableDecisions list of decision presentations to extract the edges from + */ + public void highlightAvailableEdgesFromDecisions(List<DecisionPresentation> availableDecisions) { + List<String> edges = availableDecisions.stream() + .map(decisionPresentation -> decisionPresentation.getController().getDecision().edgeIds) + .flatMap(List::stream) + .collect(Collectors.toList()); + + // Remove previous highlighting of edges + componentNameProcessPresentationMap.values().forEach(p -> p.getController() + .getComponent().getEdges() + .forEach(e -> e.setIsHighlighted(edges.contains(e.getId())))); + } + + public void highlightPreview(final State state) { + Consumer<ObjectProtos.LeafLocation> leafLocationConsumer = + (leaf) -> { + componentNameProcessPresentationMap.get(leaf.getComponentInstance().getComponentName()).getController().highlightLocation(leaf.getId()); + temporarilyHighlightedLocations.add(leaf.getId()); + }; + + Platform.runLater(() -> state.consumeLeafLocations(leafLocationConsumer)); + } +} diff --git a/src/main/resources/ecdar/presentations/StatePresentation.fxml b/src/main/resources/ecdar/presentations/StatePresentation.fxml index 5bba2f2a..39cf9957 100755 --- a/src/main/resources/ecdar/presentations/StatePresentation.fxml +++ b/src/main/resources/ecdar/presentations/StatePresentation.fxml @@ -20,7 +20,7 @@ <Region minWidth="10" maxWidth="10"/> <Label fx:id="locations" styleClass="body1" wrapText="true"/> </HBox> - <Text text="Clocks" styleClass="subhead" style="-fx-stroke-width: 1.2em"/> + <Text text="Clock Constraints" styleClass="subhead" style="-fx-stroke-width: 1.2em"/> <HBox> <Region minWidth="10" maxWidth="10"/> <Label fx:id="clockConstraints" styleClass="body1" wrapText="true"/> From 99d4ba9643d95a1388a56592b9ac826f33ed8d51 Mon Sep 17 00:00:00 2001 From: Niels Vistisen <nielswps@gmail.com> Date: Thu, 25 May 2023 13:52:25 +0200 Subject: [PATCH 151/158] Decision edges highlighting ADDED and tracelog fading FIXED --- .../controllers/SimulationController.java | 32 +++++++++++-------- .../controllers/TracePaneController.java | 7 ++-- .../helpers/SimulationHighlighter.java | 32 +++++++++++-------- 3 files changed, 41 insertions(+), 30 deletions(-) diff --git a/src/main/java/ecdar/controllers/SimulationController.java b/src/main/java/ecdar/controllers/SimulationController.java index b57dc2cf..f68e9149 100644 --- a/src/main/java/ecdar/controllers/SimulationController.java +++ b/src/main/java/ecdar/controllers/SimulationController.java @@ -70,20 +70,14 @@ public class SimulationController implements ModeController, Initializable { private boolean isMaxZoomInReached = false; private boolean isMaxZoomOutReached = false; - private EventHandler<MouseEvent> decisionOnMouseEnter = event -> { - // ToDo: Change highlighted state - highlighter.highlightProcessState(((DecisionPresentation)event.getTarget()).getController().getDecision().target); - }; - private EventHandler<MouseEvent> decisionOnMouseExit = event -> { - // ToDo: Change highlighted state - highlighter.highlightProcessState(getActiveSimulation().currentState.get()); - }; + private final EventHandler<MouseEvent> decisionOnMouseEnter = event -> highlighter.highlightDecisionEdges(((DecisionPresentation)event.getTarget())); + private final EventHandler<MouseEvent> decisionOnMouseExit = event -> highlighter.unhighlightEdges(); private final ObservableMap<String, ProcessPresentation> componentNameProcessPresentationMap = FXCollections.observableHashMap(); @Override public void initialize(final URL location, final ResourceBundle resources) { - //In case that the processContainer gets moved around we have to keep in into place. + //In case that the processContainer gets moved around we have to keep it in place. initializeProcessContainer(); initializeWindowResizing(); initializeZoom(); @@ -142,6 +136,12 @@ private void initializeWindowResizing() { }); } + /** + * Initializes the simulation based on the received proto response and current composition + * + * @param response ProtoBuf response received from a step request + * @param composition current composition being simulated + */ private void initializeActiveSimulation(QueryProtos.SimulationStepResponse response, String composition) { State newState = createStateFromResponse(response); @@ -163,7 +163,7 @@ private void initializeActiveSimulation(QueryProtos.SimulationStepResponse respo newSimulation.currentState.addListener(observable -> { updateSimulationVariablesAndClocks(); highlighter.updateHighlighting(); - highlighter.highlightTraceStates(leftSimPane.getController().tracePanePresentation.getController().traceList.getChildren()); + highlighter.fadeSucceedingTraceStates(leftSimPane.getController().tracePanePresentation.getController().traceList.getChildren()); }); rightSimPane.getController().availableDecisionsVBox.getChildren().addListener((ListChangeListener<? super Node>) c -> { @@ -195,6 +195,8 @@ private List<ProcessPresentation> getProcessPresentations() { /** * Reloads the whole simulation sets the initial transitions, states, etc + * + * @param composition current composition being simulated */ public void initialStep(String composition) { BackendHelper.getDefaultEngine().enqueueRequest(new Decision(composition), @@ -204,6 +206,8 @@ public void initialStep(String composition) { /** * Take a step in the simulation. + * + * @param decision basis of the step to take */ public void nextStep(Decision decision) { // removes invalid states from the log when stepping forward after previewing a previous state @@ -216,7 +220,6 @@ public void nextStep(Decision decision) { getActiveSimulation().currentState.set(newState); updateSimulationState(response.getNewDecisionPointsList()); }, - (error) -> Ecdar.showToast("Could not take next step in simulation\nError: " + error.getMessage())); } @@ -231,6 +234,7 @@ public void resetSimulation(String queryToSimulate) { private void previewState(final StatePresentation statePresentation) throws NullPointerException { getActiveSimulation().currentState.set(statePresentation.getController().getState()); + highlighter.fadeSucceedingTraceStates(leftSimPane.getController().tracePanePresentation.getController().traceList.getChildren()); } private void updateSimulationState(List<ObjectProtos.Decision> availableDecisions) { @@ -247,7 +251,7 @@ private void updateSimulationState(List<ObjectProtos.Decision> availableDecision updateSimulationVariablesAndClocks(); highlighter.updateHighlighting(); - highlighter.highlightTraceStates(leftSimPane.getController().tracePanePresentation.getController().traceList.getChildren()); + highlighter.fadeSucceedingTraceStates(leftSimPane.getController().tracePanePresentation.getController().traceList.getChildren()); }); } @@ -314,7 +318,7 @@ private void handleWidthOnScale(final Number oldValue, final Number newValue) { } /** - * Removes all states from the trace log after the given state + * Removes all states from the trace log, succeeding the given state */ private void removeStatesFromLog(StatePresentation statePresentation) { if (statePresentation == null) return; @@ -328,7 +332,7 @@ private State createStateFromResponse(QueryProtos.SimulationStepResponse respons } public static void setSelectedState(State selectedState) { - SimulationController.selectedState.set(selectedState); + SimulationController.selectedState.set(selectedState); // ToDo NIELS: Handle selection } public static String getComposition() { diff --git a/src/main/java/ecdar/controllers/TracePaneController.java b/src/main/java/ecdar/controllers/TracePaneController.java index 018f9799..294c5788 100755 --- a/src/main/java/ecdar/controllers/TracePaneController.java +++ b/src/main/java/ecdar/controllers/TracePaneController.java @@ -14,6 +14,7 @@ import javafx.geometry.Insets; import javafx.scene.Cursor; import javafx.scene.control.Label; +import javafx.scene.input.MouseEvent; import javafx.scene.layout.*; import org.kordamp.ikonli.javafx.FontIcon; @@ -156,16 +157,16 @@ private void showTrace() { /** * Instantiates a {@link StatePresentation} for a {@link State} and adds it to the view * - * @param statePresentation The statePresentation the should be inserted into the trace log + * @param statePresentation The statePresentation to insert into the trace log */ private void insertStateInTrace(final StatePresentation statePresentation) { - EventHandler mouseEntered = statePresentation.getOnMouseEntered(); + EventHandler<? super MouseEvent> mouseEntered = statePresentation.getOnMouseEntered(); statePresentation.setOnMouseEntered(event -> { SimulationController.setSelectedState(statePresentation.getController().getState()); mouseEntered.handle(event); }); - EventHandler mouseExited = statePresentation.getOnMouseExited(); + EventHandler<? super MouseEvent> mouseExited = statePresentation.getOnMouseExited(); statePresentation.setOnMouseExited(event -> { SimulationController.setSelectedState(null); mouseExited.handle(event); diff --git a/src/main/java/ecdar/utility/helpers/SimulationHighlighter.java b/src/main/java/ecdar/utility/helpers/SimulationHighlighter.java index ddb9b10e..1a1bf243 100644 --- a/src/main/java/ecdar/utility/helpers/SimulationHighlighter.java +++ b/src/main/java/ecdar/utility/helpers/SimulationHighlighter.java @@ -40,8 +40,6 @@ private List<ProcessPresentation> getProcessPresentations() { * Initializer method to set up listeners that handle highlighting when selected/current state/transition changes */ public void updateHighlighting() { - highlightAvailableEdgesFromDecisions(getActiveSimulation().availableDecisions); - Platform.runLater(() -> { unhighlightProcesses(); highlightProcessState(getActiveSimulation().currentState.get()); @@ -61,15 +59,14 @@ public void unhighlightProcesses() { /** * Initializes the fading of states in the trace list when a state is previewed */ - public void highlightTraceStates(List<Node> traceListStates) { + public void fadeSucceedingTraceStates(List<Node> traceListStates) { var activeStatePresentation = traceListStates.stream() .filter(n -> ((StatePresentation) n) .getController().getState() .equals(getActiveSimulation().currentState.get())) .findFirst().orElse(null); - if (activeStatePresentation == null || traceListStates.get(traceListStates.size() - 1).equals(activeStatePresentation)) - return; + if (activeStatePresentation == null) return; traceListStates.forEach(trace -> trace.setOpacity(1)); int i = traceListStates.size() - 1; @@ -115,20 +112,29 @@ public void highlightProcessState(final State state) { } /** - * Highlights the edges that are part of at least one of the possible decisions + * Highlights the edges involved in the action of a decision * - * @param availableDecisions list of decision presentations to extract the edges from + * @param decision decision presentations to extract the action from */ - public void highlightAvailableEdgesFromDecisions(List<DecisionPresentation> availableDecisions) { - List<String> edges = availableDecisions.stream() - .map(decisionPresentation -> decisionPresentation.getController().getDecision().edgeIds) - .flatMap(List::stream) + public void highlightDecisionEdges(DecisionPresentation decision) { + List<String> involvedEdgeIds = decision.getController() + .getDecision() + .protoDecision.getEdgesList() + .stream().map(ObjectProtos.Edge::getId) .collect(Collectors.toList()); - // Remove previous highlighting of edges componentNameProcessPresentationMap.values().forEach(p -> p.getController() .getComponent().getEdges() - .forEach(e -> e.setIsHighlighted(edges.contains(e.getId())))); + .forEach(e -> e.setIsHighlighted(involvedEdgeIds.contains(e.getId())))); + } + + /** + * Removes highlighting from all edges + */ + public void unhighlightEdges() { + componentNameProcessPresentationMap.values().forEach(p -> p.getController() + .getComponent().getEdges() + .forEach(e -> e.setIsHighlighted(false))); } public void highlightPreview(final State state) { From 29bdd7d9f77679ddcaccfadde9e1b36f8dfae663 Mon Sep 17 00:00:00 2001 From: Niels Vistisen <nielswps@gmail.com> Date: Thu, 25 May 2023 15:37:29 +0200 Subject: [PATCH 152/158] Constraint formatting improved and combination logic initiated --- .../ecdar/abstractions/ClockConstraint.java | 31 ++++++ src/main/java/ecdar/abstractions/State.java | 12 +-- .../utility/helpers/ConstraintsHandler.java | 99 +++++++++++++++++++ .../helpers/SimulationHighlighter.java | 2 +- 4 files changed, 133 insertions(+), 11 deletions(-) create mode 100644 src/main/java/ecdar/abstractions/ClockConstraint.java create mode 100644 src/main/java/ecdar/utility/helpers/ConstraintsHandler.java diff --git a/src/main/java/ecdar/abstractions/ClockConstraint.java b/src/main/java/ecdar/abstractions/ClockConstraint.java new file mode 100644 index 00000000..c4944fe9 --- /dev/null +++ b/src/main/java/ecdar/abstractions/ClockConstraint.java @@ -0,0 +1,31 @@ +package ecdar.abstractions; + +import javafx.util.Pair; + +public class ClockConstraint { + public final Pair<String, String> clocks; + public final char comparator; + public final int constant; + public final boolean isStrict; + + public ClockConstraint(String left_clock, String right_clock, int constant, char comparator, boolean isStrict) { + this.clocks = new Pair<>(left_clock, right_clock); + this.constant = constant; + this.comparator = comparator; + this.isStrict = isStrict; + } + + @Override + public String toString() { + if (clocks.getValue() != null) { + return clocks.getKey() + " - " + + clocks.getValue() + " " + + comparator + (isStrict ? "= " : " ") + + constant; + } else { + return clocks.getKey() + " " + + comparator + (isStrict ? "= " : " ") + + constant; + } + } +} \ No newline at end of file diff --git a/src/main/java/ecdar/abstractions/State.java b/src/main/java/ecdar/abstractions/State.java index ee4bfa37..67d16ebc 100644 --- a/src/main/java/ecdar/abstractions/State.java +++ b/src/main/java/ecdar/abstractions/State.java @@ -1,6 +1,7 @@ package ecdar.abstractions; import EcdarProtoBuf.ObjectProtos; +import ecdar.utility.helpers.ConstraintsHandler; import javafx.collections.FXCollections; import javafx.collections.ObservableMap; @@ -58,16 +59,7 @@ public ObjectProtos.State getProtoState() { * @return A string representing the clock constraints */ public String getStateClockConstraintsString() { - StringBuilder clocksString = new StringBuilder(); - for (var constraint : getProtoState().getZone().getConjunctions(0).getConstraintsList()) { - var x = constraint.getX().getComponentClock().getClockName(); - var y = constraint.getY().getComponentClock().getClockName(); - var c = constraint.getC(); - var strict = constraint.getStrict(); - clocksString.append(x).append(" - ").append(y).append(strict ? " < " : " <= ").append(c).append("\n"); - } - - return clocksString.toString(); + return ConstraintsHandler.getStateClockConstraintsString(getProtoState()); } /** diff --git a/src/main/java/ecdar/utility/helpers/ConstraintsHandler.java b/src/main/java/ecdar/utility/helpers/ConstraintsHandler.java new file mode 100644 index 00000000..d64772f5 --- /dev/null +++ b/src/main/java/ecdar/utility/helpers/ConstraintsHandler.java @@ -0,0 +1,99 @@ +package ecdar.utility.helpers; + +import EcdarProtoBuf.ObjectProtos; +import ecdar.abstractions.ClockConstraint; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +public class ConstraintsHandler { + public static String getStateClockConstraintsString(ObjectProtos.State protoState) { + List<ClockConstraint> refinedConstraints = new ArrayList<>(); + for (var constraint : protoState.getZone().getConjunctions(0).getConstraintsList()) { + refinedConstraints.add(getClockConstraintLine(constraint)); + } + +// ToDo NIELS: Check for combinable constraints +// StringBuilder clockConstraintsString = new StringBuilder(); +// List<Integer> mergedConstraints = new ArrayList<>(); +// +// for (int i = 0; i < refinedConstraints.size(); i++) { +// if (mergedConstraints.contains(i)) continue; // 'i' has already been merged +// +// for (int j = 1; j < refinedConstraints.size(); j++) { +// if (i == j || mergedConstraints.contains(j)) continue; // 'j' has already been merged +// +// ClockConstraint c1 = refinedConstraints.get(i); +// ClockConstraint c2 = refinedConstraints.get(j); +// +// if (constraintClocksMatch(c1, c2)) { +// clockConstraintsString.append(getMergedConstraintString(c1, c2)).append("\n"); +// mergedConstraints.add(i); +// mergedConstraints.add(j); +// } +// } +// } +// +// for (int i = 0; i < refinedConstraints.size(); i++) { +// if (!mergedConstraints.contains(i)) { +// clockConstraintsString.append(refinedConstraints.get(i).toString()).append("\n"); +// } +// } +// +// // Remove trailing newline +// clockConstraintsString.setLength(clockConstraintsString.length() - 1); + + return refinedConstraints.stream().map(ClockConstraint::toString).collect(Collectors.joining("\n")); + } + + private static String getMergedConstraintString(ClockConstraint c1, ClockConstraint c2) { + StringBuilder mergedConstraint = new StringBuilder(); + + var smallestConstraint = c1.constant < c2.constant ? c1 : c2; + var largestConstraint = c1.equals(smallestConstraint) ? c2 : c1; + + // Left constant + mergedConstraint.append(smallestConstraint.constant).append(" ").append(smallestConstraint.comparator).append(smallestConstraint.isStrict ? "= " : " "); + + // Clocks + mergedConstraint.append(smallestConstraint.clocks.getKey()); + if (smallestConstraint.clocks.getValue() == null) { + mergedConstraint.append(" - ").append(smallestConstraint.clocks.getValue()); + } + mergedConstraint.append(" "); + + // Right constant + mergedConstraint.append(largestConstraint.comparator).append(largestConstraint.isStrict ? "= " : " ").append(largestConstraint.constant); + + return mergedConstraint.toString(); + } + + private static boolean constraintClocksMatch(ClockConstraint c1, ClockConstraint c2) { + return (Objects.equals(c1.clocks.getValue(), c2.clocks.getValue()) && Objects.equals(c1.clocks.getKey(), c2.clocks.getKey())) || + (Objects.equals(c1.clocks.getValue(), c2.clocks.getKey()) && Objects.equals(c1.clocks.getKey(), c2.clocks.getValue())); + } + + private static ClockConstraint getClockConstraintLine(ObjectProtos.Constraint constraint) { + var clock1 = constraint.getX().getComponentClock().getClockName(); + var clock2 = constraint.getY().getComponentClock().getClockName(); + var constant = constraint.getC(); + var strict = constraint.getStrict(); + + if (clock1.equals("")) { + // The first clock is the zero-clock + return new ClockConstraint(clock2, null, Math.abs(constant), '>', strict); + } else if (clock2.equals("")) { + // The second clock is the zero-clock + return new ClockConstraint(clock1, null, Math.abs(constant), '<', strict); + } else { + // None of the clocks are the zero-clock + if (constant >= 0) { + return new ClockConstraint(clock1, clock2, Math.abs(constant), '<', strict); + } else { + return new ClockConstraint(clock2, clock1, Math.abs(constant), '>', strict); + } + } + } +} diff --git a/src/main/java/ecdar/utility/helpers/SimulationHighlighter.java b/src/main/java/ecdar/utility/helpers/SimulationHighlighter.java index 1a1bf243..673b428b 100644 --- a/src/main/java/ecdar/utility/helpers/SimulationHighlighter.java +++ b/src/main/java/ecdar/utility/helpers/SimulationHighlighter.java @@ -77,7 +77,7 @@ public void fadeSucceedingTraceStates(List<Node> traceListStates) { } /** - * Highlights the edges from the reachability response + * Highlights the edges from the reachability response ToDO NIELS: Trigger reachability highlighting */ public void highlightReachabilityEdges(ArrayList<String> ids) throws NullPointerException { //unhighlight all edges From 7ea8b2f3db5657a8dfaded8714f69ce936768cc3 Mon Sep 17 00:00:00 2001 From: Niels Vistisen <nielswps@gmail.com> Date: Tue, 30 May 2023 08:49:45 +0200 Subject: [PATCH 153/158] WIP: Working clocke constraint merging and string generation --- .../utility/helpers/ConstraintsHandler.java | 138 +++++++++++------- 1 file changed, 89 insertions(+), 49 deletions(-) diff --git a/src/main/java/ecdar/utility/helpers/ConstraintsHandler.java b/src/main/java/ecdar/utility/helpers/ConstraintsHandler.java index d64772f5..bda47f7b 100644 --- a/src/main/java/ecdar/utility/helpers/ConstraintsHandler.java +++ b/src/main/java/ecdar/utility/helpers/ConstraintsHandler.java @@ -6,76 +6,116 @@ import java.util.ArrayList; import java.util.List; import java.util.Objects; -import java.util.stream.Collectors; public class ConstraintsHandler { + /** + * Generates a string representation of the clock constraints of the provided state + * + * @param protoState state containing the clock constraints to represent in the string + * @return a prettified string representation of the state's clock constraints + */ public static String getStateClockConstraintsString(ObjectProtos.State protoState) { List<ClockConstraint> refinedConstraints = new ArrayList<>(); for (var constraint : protoState.getZone().getConjunctions(0).getConstraintsList()) { - refinedConstraints.add(getClockConstraintLine(constraint)); + refinedConstraints.add(generateClockConstraint(constraint)); } -// ToDo NIELS: Check for combinable constraints -// StringBuilder clockConstraintsString = new StringBuilder(); -// List<Integer> mergedConstraints = new ArrayList<>(); -// -// for (int i = 0; i < refinedConstraints.size(); i++) { -// if (mergedConstraints.contains(i)) continue; // 'i' has already been merged -// -// for (int j = 1; j < refinedConstraints.size(); j++) { -// if (i == j || mergedConstraints.contains(j)) continue; // 'j' has already been merged -// -// ClockConstraint c1 = refinedConstraints.get(i); -// ClockConstraint c2 = refinedConstraints.get(j); -// -// if (constraintClocksMatch(c1, c2)) { -// clockConstraintsString.append(getMergedConstraintString(c1, c2)).append("\n"); -// mergedConstraints.add(i); -// mergedConstraints.add(j); -// } -// } -// } -// -// for (int i = 0; i < refinedConstraints.size(); i++) { -// if (!mergedConstraints.contains(i)) { -// clockConstraintsString.append(refinedConstraints.get(i).toString()).append("\n"); -// } -// } -// -// // Remove trailing newline -// clockConstraintsString.setLength(clockConstraintsString.length() - 1); - - return refinedConstraints.stream().map(ClockConstraint::toString).collect(Collectors.joining("\n")); + StringBuilder clockConstraintsString = new StringBuilder(); + List<Integer> mergedConstraints = new ArrayList<>(); + + for (int i = 0; i < refinedConstraints.size(); i++) { + if (mergedConstraints.contains(i)) continue; // 'i' has already been merged + + for (int j = 1; j < refinedConstraints.size(); j++) { + if (i == j || mergedConstraints.contains(j)) continue; // 'j' has already been merged + + ClockConstraint c1 = refinedConstraints.get(i); + ClockConstraint c2 = refinedConstraints.get(j); + + if (constraintClocksMatch(c1, c2)) { + clockConstraintsString.append(getMergedConstraintString(c1, c2)).append("\n"); + mergedConstraints.add(i); + mergedConstraints.add(j); + } + } + } + + for (int i = 0; i < refinedConstraints.size(); i++) { + if (!mergedConstraints.contains(i)) { + clockConstraintsString.append(refinedConstraints.get(i).toString()).append("\n"); + } + } + + // Remove trailing newline + clockConstraintsString.setLength(clockConstraintsString.length() - 1); + + return clockConstraintsString.toString(); } + /** + * Merges two clock constraints into a combined string. + * If the two constraints are incompatible, this returns the two individual string representations split by `\n` + * + * @param c1 first clock constraint + * @param c2 second clock constraint + * @return string representation of the merged clock constraint if compatible, or the two separate representations otherwise + */ private static String getMergedConstraintString(ClockConstraint c1, ClockConstraint c2) { StringBuilder mergedConstraint = new StringBuilder(); - var smallestConstraint = c1.constant < c2.constant ? c1 : c2; - var largestConstraint = c1.equals(smallestConstraint) ? c2 : c1; + if ((c1.comparator == '<' && c2.comparator == '>') || + (c1.comparator == '>' && c2.comparator == '<')) { + var smallestConstraint = c1.comparator == '>' ? c1 : c2; + var largestConstraint = c1.equals(smallestConstraint) ? c2 : c1; - // Left constant - mergedConstraint.append(smallestConstraint.constant).append(" ").append(smallestConstraint.comparator).append(smallestConstraint.isStrict ? "= " : " "); + // Lower bound + mergedConstraint.append(smallestConstraint.constant) + .append(" <") + .append(smallestConstraint.isStrict ? "= " : " "); - // Clocks - mergedConstraint.append(smallestConstraint.clocks.getKey()); - if (smallestConstraint.clocks.getValue() == null) { - mergedConstraint.append(" - ").append(smallestConstraint.clocks.getValue()); - } - mergedConstraint.append(" "); + // Clock relation + mergedConstraint.append(smallestConstraint.clocks.getKey()); + if (smallestConstraint.clocks.getValue() != null) { + mergedConstraint.append(" - ").append(smallestConstraint.clocks.getValue()); + } + mergedConstraint.append(" "); + + // Upper bound + mergedConstraint.append("<") + .append(largestConstraint.isStrict ? "= " : " ") + .append(largestConstraint.constant); - // Right constant - mergedConstraint.append(largestConstraint.comparator).append(largestConstraint.isStrict ? "= " : " ").append(largestConstraint.constant); + return mergedConstraint.toString(); + } else if (Objects.equals(c1.clocks.getValue(), c2.clocks.getKey()) && + Objects.equals(c1.clocks.getKey(), c2.clocks.getValue())) { + if (c1.constant == 0 && c2.constant == 0) { + return mergedConstraint.append(c1.clocks.getKey()) + .append(" = ") + .append(c1.clocks.getValue()).toString(); + } + } - return mergedConstraint.toString(); + return mergedConstraint.append(c1).append("\n").append(c2).toString(); } + /** + * Checks if the tro constraints can be merged + * + * @param c1 first clock constraint + * @param c2 second clock constraint + * @return true if the two clock constraints are compatible, false otherwise + */ private static boolean constraintClocksMatch(ClockConstraint c1, ClockConstraint c2) { - return (Objects.equals(c1.clocks.getValue(), c2.clocks.getValue()) && Objects.equals(c1.clocks.getKey(), c2.clocks.getKey())) || - (Objects.equals(c1.clocks.getValue(), c2.clocks.getKey()) && Objects.equals(c1.clocks.getKey(), c2.clocks.getValue())); + return (Objects.equals(c1.clocks.getValue(), c2.clocks.getValue()) && Objects.equals(c1.clocks.getKey(), c2.clocks.getKey())) || (Objects.equals(c1.clocks.getValue(), c2.clocks.getKey()) && Objects.equals(c1.clocks.getKey(), c2.clocks.getValue())); } - private static ClockConstraint getClockConstraintLine(ObjectProtos.Constraint constraint) { + /** + * Generates a ClockConstraint object from a ProtoBuf constraint + * + * @param constraint the ProtoBuf constraint to generate from + * @return the generated ClockConstraint + */ + private static ClockConstraint generateClockConstraint(ObjectProtos.Constraint constraint) { var clock1 = constraint.getX().getComponentClock().getClockName(); var clock2 = constraint.getY().getComponentClock().getClockName(); var constant = constraint.getC(); From 484afd1c2663bd7baa2a506809ca737371ca5d39 Mon Sep 17 00:00:00 2001 From: Niels Vistisen <nielswps@gmail.com> Date: Tue, 30 May 2023 12:31:34 +0200 Subject: [PATCH 154/158] WIP: Decisions are now updated on preview, decoupling of protobuf from simulation abstractions, tracelog reversed, and clean-up --- .../ecdar/abstractions/ClockConstraint.java | 4 +- .../java/ecdar/abstractions/Decision.java | 24 +-- .../java/ecdar/abstractions/Simulation.java | 12 -- src/main/java/ecdar/abstractions/State.java | 93 ++---------- src/main/java/ecdar/backend/StateFactory.java | 141 ++++++++++++++++++ .../ecdar/controllers/DecisionController.java | 6 +- .../controllers/RightSimPaneController.java | 46 ++---- .../controllers/SimLocationController.java | 83 +++++------ .../controllers/SimulationController.java | 52 ++++--- .../ecdar/controllers/StateController.java | 42 +++++- .../controllers/TracePaneController.java | 2 +- ...dler.java => ClockConstraintsHandler.java} | 58 ++----- .../helpers/SimulationHighlighter.java | 30 ++-- .../presentations/StatePresentation.fxml | 2 +- 14 files changed, 308 insertions(+), 287 deletions(-) create mode 100644 src/main/java/ecdar/backend/StateFactory.java rename src/main/java/ecdar/utility/helpers/{ConstraintsHandler.java => ClockConstraintsHandler.java} (60%) diff --git a/src/main/java/ecdar/abstractions/ClockConstraint.java b/src/main/java/ecdar/abstractions/ClockConstraint.java index c4944fe9..7dff0fcd 100644 --- a/src/main/java/ecdar/abstractions/ClockConstraint.java +++ b/src/main/java/ecdar/abstractions/ClockConstraint.java @@ -8,8 +8,8 @@ public class ClockConstraint { public final int constant; public final boolean isStrict; - public ClockConstraint(String left_clock, String right_clock, int constant, char comparator, boolean isStrict) { - this.clocks = new Pair<>(left_clock, right_clock); + public ClockConstraint(String leftClock, String rightClock, int constant, char comparator, boolean isStrict) { + this.clocks = new Pair<>(leftClock, rightClock); this.constant = constant; this.comparator = comparator; this.isStrict = isStrict; diff --git a/src/main/java/ecdar/abstractions/Decision.java b/src/main/java/ecdar/abstractions/Decision.java index 3aed1099..e8254e68 100644 --- a/src/main/java/ecdar/abstractions/Decision.java +++ b/src/main/java/ecdar/abstractions/Decision.java @@ -12,19 +12,17 @@ public class Decision implements RequestSource<QueryProtos.SimulationStepResponse> { public final String composition; - public final State source; - public final State target; public final List<String> edgeIds; public final String action; + public final List<ClockConstraint> clockConstraints; public ObjectProtos.Decision protoDecision; - public Decision(String composition, State source, State target, List<String> edgeIds, String action) { + public Decision(String composition, List<String> edgeIds, String action, List<ClockConstraint> clockConstraints, ObjectProtos.Decision protoDecision) { this.composition = composition; - this.source = source; - this.target = target; this.edgeIds = edgeIds; this.action = action; - this.protoDecision = null; + this.clockConstraints = clockConstraints; + this.protoDecision = protoDecision; } /** @@ -32,21 +30,11 @@ public Decision(String composition, State source, State target, List<String> edg * ToDo NIELS: refactor to use a factory pattern for constructing new decisions */ public Decision(String composition) { - this(composition, null, null, new ArrayList<>(), null); - } - - public Decision(String composition, ObjectProtos.Decision protoDecision) { - this(composition, - new State(protoDecision.getSource()), - new State(protoDecision.getDestination()), - protoDecision.getEdgesList().stream().map(ObjectProtos.Edge::getId).collect(Collectors.toList()), - protoDecision.getAction()); - - this.protoDecision = protoDecision; // Save the proto decision for requesting simulation step + this(composition, new ArrayList<>(), null, null, null); } public boolean isInitial() { - return source == null; + return protoDecision == null; } @Override diff --git a/src/main/java/ecdar/abstractions/Simulation.java b/src/main/java/ecdar/abstractions/Simulation.java index 92773c5e..1fc45674 100644 --- a/src/main/java/ecdar/abstractions/Simulation.java +++ b/src/main/java/ecdar/abstractions/Simulation.java @@ -1,10 +1,7 @@ package ecdar.abstractions; -import EcdarProtoBuf.ObjectProtos; import ecdar.Ecdar; -import ecdar.presentations.DecisionPresentation; import ecdar.presentations.StatePresentation; -import javafx.application.Platform; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.collections.FXCollections; @@ -28,7 +25,6 @@ public class Simulation { public final ObjectProperty<State> currentState = new SimpleObjectProperty<>(); - public final ObservableList<DecisionPresentation> availableDecisions = FXCollections.observableArrayList(); public final ObservableList<StatePresentation> traceLog = FXCollections.observableArrayList(); public Simulation(String composition, State initialState) { @@ -54,14 +50,6 @@ private void setSimulatedComponents(String composition) { simulatedComponents.addAll(componentsToSimulate); } - public void addDecisionsFromProto(List<ObjectProtos.Decision> protoDecisions) { - Platform.runLater(() -> { - protoDecisions.forEach(protoDecision -> { - availableDecisions.add(new DecisionPresentation(new Decision(composition, protoDecision))); - }); - }); - } - public void addStateToTraceLog(State state, Consumer<StatePresentation> onTraceLogStatePressed) { StatePresentation statePresentation = new StatePresentation(state); diff --git a/src/main/java/ecdar/abstractions/State.java b/src/main/java/ecdar/abstractions/State.java index 67d16ebc..56c785e7 100644 --- a/src/main/java/ecdar/abstractions/State.java +++ b/src/main/java/ecdar/abstractions/State.java @@ -1,89 +1,28 @@ package ecdar.abstractions; -import EcdarProtoBuf.ObjectProtos; -import ecdar.utility.helpers.ConstraintsHandler; -import javafx.collections.FXCollections; -import javafx.collections.ObservableMap; - -import java.math.BigDecimal; -import java.util.ArrayList; -import java.util.Map; -import java.util.function.Consumer; +import java.util.HashMap; +import java.util.List; public class State { - private final ObjectProtos.LocationTree locationTree; - private final ObjectProtos.State protoState; - public final ObservableMap<String, BigDecimal> clocks = FXCollections.observableHashMap(); - - public State(ObjectProtos.State state) { - this.locationTree = state.getLocationTree(); - this.protoState = state; - } - - public void consumeLeafLocations(Consumer<ObjectProtos.LeafLocation> consumer) { - consumeLeafLocations(locationTree, consumer); + private final HashMap<String, String> componentLocationMap; + private final List<ClockConstraint> clockConstraints; + private final List<Decision> decisions; + + public State(HashMap<String, String> componentLocationMap, List<ClockConstraint> clockConstraints, List<Decision> decisions) { + this.componentLocationMap = componentLocationMap; + this.clockConstraints = clockConstraints; + this.decisions = decisions; } - private void consumeLeafLocations(ObjectProtos.LocationTree tree, Consumer<ObjectProtos.LeafLocation> consumer) { - switch (tree.getNodeTypeCase()) { - case LEAF_LOCATION: - consumer.accept(tree.getLeafLocation()); - - case BINARY_LOCATION_OP: { - consumeLeafLocations(tree.getBinaryLocationOp().getLeft(), consumer); - consumeLeafLocations(tree.getBinaryLocationOp().getRight(), consumer); - } - - case SPECIAL_LOCATION: // ToDo: Implement visualization of inconsistent and universal locations - - case NODETYPE_NOT_SET: // Will never happen - } + public HashMap<String, String> getComponentLocationMap() { + return componentLocationMap; } - /** - * All the clocks connected to the current simulation. - * - * @return a {@link Map} where the name (String) is the key, and a {@link BigDecimal} is the clock value - */ - public ObservableMap<String, BigDecimal> getSimulationClocks() { - return this.clocks; + public List<ClockConstraint> getClockConstraints() { + return clockConstraints; } - public ObjectProtos.State getProtoState() { - return protoState; - } - - /** - * A helper method that returns a string representing the clock constraints of a state in the trace log - * - * @return A string representing the clock constraints - */ - public String getStateClockConstraintsString() { - return ConstraintsHandler.getStateClockConstraintsString(getProtoState()); - } - - /** - * A helper method that returns a string representing the locations of a state in the trace log - * - * @return A string representing the locations - */ - public String getStateLocationsString() { - StringBuilder locationsString = new StringBuilder(); - - var leafLocations = new ArrayList<ObjectProtos.LeafLocation>(); - consumeLeafLocations(leafLocations::add); - - int length = leafLocations.size(); - for (int i = 0; i < length; i++) { - locationsString.append(leafLocations.get(i).getComponentInstance().getComponentName()); - locationsString.append('.'); - locationsString.append(leafLocations.get(i).getId()); - - if (i != length - 1) { - locationsString.append("\n"); - } - } - - return locationsString.toString(); + public List<Decision> getDecisions() { + return decisions; } } diff --git a/src/main/java/ecdar/backend/StateFactory.java b/src/main/java/ecdar/backend/StateFactory.java new file mode 100644 index 00000000..949904d6 --- /dev/null +++ b/src/main/java/ecdar/backend/StateFactory.java @@ -0,0 +1,141 @@ +package ecdar.backend; + +import EcdarProtoBuf.ObjectProtos; +import EcdarProtoBuf.QueryProtos; +import ecdar.abstractions.ClockConstraint; +import ecdar.abstractions.Decision; +import ecdar.abstractions.State; + +import java.util.*; +import java.util.stream.Collectors; + +public class StateFactory { + /** + * Create a state instance from the composition and simulation step response + * + * @param composition of the client requesting the state + * @param response the simulation step response to generate the state from + * @return the generated state + */ + public State createState(String composition, QueryProtos.SimulationStepResponse response) { + ObjectProtos.State sourceState = response.getNewDecisionPoints(0).getSource(); + + HashMap<String, String> componentLocationsMap = loadLocations(sourceState.getLocationTree()); + + // ToDo: Handle all conjunctions + List<ClockConstraint> clockConstraints = loadClockConstraints(sourceState.getZone().getConjunctions(0).getConstraintsList()); + List<Decision> decisions = loadDecisions(composition, response.getNewDecisionPointsList()); + + return new State(componentLocationsMap, clockConstraints, decisions); + } + + /** + * Create the state instance representing the initial state + * + * @param composition of the client requesting the state + * @param response the simulation step response to generate the state from + * @return the generated initial state + */ + public State createInitialState(String composition, QueryProtos.SimulationStepResponse response) { + ObjectProtos.State sourceState = response.getNewDecisionPoints(0).getSource(); + + HashMap<String, String> componentLocationsMap = loadLocations(sourceState.getLocationTree()); + List<Decision> decisions = loadDecisions(composition, response.getNewDecisionPointsList()); + + // ToDO: Clock constraints are currently not specified for the initial state. + // Should account for the initial locations' invariant. + List<ClockConstraint> clockConstraints = new ArrayList<>(); + + return new State(componentLocationsMap, clockConstraints, decisions); + } + + private HashMap<String, String> loadLocations(ObjectProtos.LocationTree rootLocationNode) { + HashMap<String, String> componentLocationsMap = new HashMap<>(); + + Queue<ObjectProtos.LocationTree> locationNodes = new ArrayDeque<>(); + locationNodes.add(rootLocationNode); + + ObjectProtos.LocationTree currentNode; + while ((currentNode = locationNodes.poll()) != null) { + switch (currentNode.getNodeTypeCase()) { + case LEAF_LOCATION: + componentLocationsMap.put( + currentNode.getLeafLocation().getComponentInstance().getComponentName(), + currentNode.getLeafLocation().getId()); + + case BINARY_LOCATION_OP: { + locationNodes.add(currentNode.getBinaryLocationOp().getLeft()); + locationNodes.add(currentNode.getBinaryLocationOp().getRight()); + } + + case SPECIAL_LOCATION: // ToDo: Implement visualization of inconsistent and universal locations + + case NODETYPE_NOT_SET: // Will never happen + } + } + + return componentLocationsMap; + } + + private List<Decision> loadDecisions(String composition, List<ObjectProtos.Decision> decisionPointsList) { + List<Decision> decisions = new ArrayList<>(); + + for (ObjectProtos.Decision protoDecision : decisionPointsList) { + List<String> edgeIds = protoDecision.getEdgesList().stream() + .map(ObjectProtos.Edge::getId) + .collect(Collectors.toList()); + + List<ClockConstraint> clockConstraints = protoDecision.getSource().getZone().getConjunctions(0) + .getConstraintsList().stream() + .map(this::generateClockConstraint) + .collect(Collectors.toList()); + + decisions.add(new Decision( + composition, + edgeIds, + protoDecision.getAction(), + clockConstraints, + protoDecision)); + } + + return decisions; + } + + private List<ClockConstraint> loadClockConstraints(List<ObjectProtos.Constraint> constraintsList) { + List<ClockConstraint> clockConstraints = new ArrayList<>(); + + for (var constraint : constraintsList) { + clockConstraints.add(generateClockConstraint(constraint)); + } + + return clockConstraints; + } + + /** + * Generates a ClockConstraint object from a ProtoBuf constraint + * + * @param constraint the ProtoBuf constraint to generate from + * @return the generated ClockConstraint + */ + private ClockConstraint generateClockConstraint(ObjectProtos.Constraint constraint) { + var clock1 = constraint.getX().getComponentClock().getClockName(); + var clock2 = constraint.getY().getComponentClock().getClockName(); + var constant = constraint.getC(); + var strict = constraint.getStrict(); + + if (clock1.equals("")) { + // The first clock is the zero-clock + return new ClockConstraint(clock2, null, Math.abs(constant), '>', strict); + } else if (clock2.equals("")) { + // The second clock is the zero-clock + return new ClockConstraint(clock1, null, Math.abs(constant), '<', strict); + } else { + // None of the clocks are the zero-clock + if (constant >= 0) { + return new ClockConstraint(clock1, clock2, Math.abs(constant), '<', strict); + } else { + return new ClockConstraint(clock2, clock1, Math.abs(constant), '>', strict); + } + } + } +} diff --git a/src/main/java/ecdar/controllers/DecisionController.java b/src/main/java/ecdar/controllers/DecisionController.java index 4aab902c..d08aeebf 100644 --- a/src/main/java/ecdar/controllers/DecisionController.java +++ b/src/main/java/ecdar/controllers/DecisionController.java @@ -2,6 +2,7 @@ import com.jfoenix.controls.JFXRippler; import ecdar.abstractions.Decision; +import ecdar.utility.helpers.ClockConstraintsHandler; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.fxml.Initializable; @@ -16,12 +17,15 @@ public class DecisionController implements Initializable { public Label clockConstraints; private final ObjectProperty<Decision> decision = new SimpleObjectProperty<>(); + private final ClockConstraintsHandler clockConstraintsHandler = new ClockConstraintsHandler(); @Override public void initialize(URL location, ResourceBundle resources) { decision.addListener((observable, oldValue, newValue) -> { action.setText(newValue.action); - clockConstraints.setText(newValue.source.getStateClockConstraintsString()); + clockConstraints.setText( + clockConstraintsHandler.getStateClockConstraintsString(newValue.clockConstraints) + ); }); } diff --git a/src/main/java/ecdar/controllers/RightSimPaneController.java b/src/main/java/ecdar/controllers/RightSimPaneController.java index 06a9eca5..b3b79643 100755 --- a/src/main/java/ecdar/controllers/RightSimPaneController.java +++ b/src/main/java/ecdar/controllers/RightSimPaneController.java @@ -26,12 +26,13 @@ public class RightSimPaneController implements Initializable { public VBox availableDecisionsVBox; private Consumer<Decision> onDecisionSelected; - private ObservableList<DecisionPresentation> availableDecisions = FXCollections.observableArrayList(); + private final ObservableList<DecisionPresentation> availableDecisions = FXCollections.observableArrayList(); @Override public void initialize(URL location, ResourceBundle resources) { initializeBackground(); initializeLeftBorder(); + initializeDecisionHandling(); } /** @@ -60,40 +61,23 @@ private void initializeLeftBorder() { private void initializeDecisionHandling() { availableDecisions.addListener((ListChangeListener<DecisionPresentation>) c -> { while (c.next()) { + c.getRemoved().forEach(decisionPresentation -> Platform.runLater(() -> availableDecisionsVBox.getChildren().remove(decisionPresentation))); + c.getAddedSubList().forEach(decisionPresentation -> { // Request next step when decision is clicked - decisionPresentation.setOnMouseClicked(event -> { - onDecisionSelected.accept(decisionPresentation.getController().getDecision()); - }); - - Platform.runLater(() -> { - availableDecisionsVBox.getChildren().add(decisionPresentation); - }); + decisionPresentation.setOnMouseClicked(event -> onDecisionSelected.accept(decisionPresentation.getController().getDecision())); + Platform.runLater(() -> availableDecisionsVBox.getChildren().add(decisionPresentation)); }); - - c.getRemoved().forEach(decisionPresentation -> Platform.runLater(() -> availableDecisionsVBox.getChildren().remove(decisionPresentation))); } }); - - availableDecisions.forEach(d -> { - // Request next step when decision is clicked - d.setOnMouseClicked(event -> { - onDecisionSelected.accept(d.getController().getDecision()); - }); - - Platform.runLater(() -> { - availableDecisionsVBox.getChildren().add(d); - }); - }); } public void setOnDecisionSelected(Consumer<Decision> decisionSelected) { onDecisionSelected = decisionSelected; } - public void setDecisionsList(ObservableList<DecisionPresentation> decisions) { - availableDecisions = decisions; - initializeDecisionHandling(); + public void setDecisionsList(List<DecisionPresentation> decisions) { + availableDecisions.setAll(decisions); } protected List<Decision> getDecisions() { @@ -101,16 +85,4 @@ protected List<Decision> getDecisions() { .map(decisionPresentation -> decisionPresentation.getController().getDecision()) .collect(Collectors.toList()); } - -// /** -// * Get all enable edges in this state -// * -// * @return list of pairs of the component instance connected to each edge -// */ -// public List<Edge> getEnabledEdges() { -// return getDecisions().stream() -// .map(decision -> decision.edgeIds) -// .flatMap(List::stream) -// .collect(Collectors.toList()); -// } -} +} \ No newline at end of file diff --git a/src/main/java/ecdar/controllers/SimLocationController.java b/src/main/java/ecdar/controllers/SimLocationController.java index 01f0804b..2f4bffd1 100755 --- a/src/main/java/ecdar/controllers/SimLocationController.java +++ b/src/main/java/ecdar/controllers/SimLocationController.java @@ -1,6 +1,5 @@ package ecdar.controllers; -import EcdarProtoBuf.ObjectProtos; import com.jfoenix.controls.JFXPopup; import ecdar.Ecdar; import ecdar.abstractions.*; @@ -21,9 +20,7 @@ import javafx.scene.shape.Path; import javafx.scene.input.MouseButton; import javafx.scene.input.MouseEvent; -import javafx.util.Pair; -import java.util.ArrayList; import java.util.function.Consumer; import java.net.URL; @@ -54,11 +51,11 @@ public static String getSimLocationReachableQuery(final Location endLocation, fi } /** - * Generates a reachability query based on the given location and component - * - * @param endLocation The location which should be checked for reachability - * @return A reachability query string - */ + * Generates a reachability query based on the given location and component + * + * @param endLocation The location which should be checked for reachability + * @return A reachability query string + */ public static String getSimLocationReachableQuery(final Location endLocation, final Component component, final String query, final State state) { var stringBuilder = new StringBuilder(); @@ -69,7 +66,7 @@ public static String getSimLocationReachableQuery(final Location endLocation, fi stringBuilder.append(" -> "); // ToDo: append start location here - if (state != null){ + if (state != null) { stringBuilder.append(getInitialStateString(state)); stringBuilder.append(";"); } @@ -83,7 +80,6 @@ public static String getSimLocationReachableQuery(final Location endLocation, fi } /** - * ToDo NIELS: Determine if this is actually what it does * Returns a string representation of an array of initial location IDs for all components in the simulation * * @param state @@ -91,40 +87,35 @@ public static String getSimLocationReachableQuery(final Location endLocation, fi */ private static String getInitialStateString(State state) { var initialStateStringBuilder = new StringBuilder(); - var locations = new ArrayList<Pair<ObjectProtos.ComponentInstance, String>>(); - - state.consumeLeafLocations((leafLocation -> locations.add(new Pair<>(leafLocation.getComponentInstance(), leafLocation.getId())))); // append locations initialStateStringBuilder.append("["); var appendLocationWithSeparator = false; // ToDO NIELS: Determine how to process this, if it is indeed the initial locations - for(var component : SimulationController.getSimulatedComponents()){ - var locationFound = false; - - for(var location : locations){ - if (location.getKey().getComponentName().equals(component.getName())){ - if (appendLocationWithSeparator){ - initialStateStringBuilder.append(",") - .append(location.getValue()); - } - else{ - initialStateStringBuilder.append(location.getValue()); - } - locationFound = true; - } - if (locationFound){ - // don't go through more locations, when a location is found for the specific component that we're looking at - break; - } - } + for (var component : SimulationController.getSimulatedComponents()) { + boolean appendSeparator = appendLocationWithSeparator; + state.getComponentLocationMap().entrySet().stream() + // Should only match one component + .filter((componentLocationPair) -> componentLocationPair.getKey().equals(component.getName())) + .forEach((entry) -> { + String compName = entry.getKey(), locId = entry.getValue(); + + if (compName.equals(component.getName())) { + if (appendSeparator) { + initialStateStringBuilder.append(",") + .append(locId); + } else { + initialStateStringBuilder.append(locId); + } + } + }); + appendLocationWithSeparator = true; } initialStateStringBuilder.append("]"); - // ToDo: append clock values - var clocks = state.getSimulationClocks(); + // ToDo: Add clocks initialStateStringBuilder.append("()"); return initialStateStringBuilder.toString(); @@ -136,22 +127,18 @@ private static String getEndStateString(String componentName, String endLocation stringBuilder.append("["); var appendLocationWithSeparator = false; - for (var component : SimulationController.getSimulatedComponents()) - { - if (component.getName().equals(componentName)){ - if (appendLocationWithSeparator){ + for (var component : SimulationController.getSimulatedComponents()) { + if (component.getName().equals(componentName)) { + if (appendLocationWithSeparator) { stringBuilder.append(",") .append(endLocationId); - } - else{ + } else { stringBuilder.append(endLocationId); } - } - else{ // add underscore to indicate, that we don't care about the end locations in the other components - if (appendLocationWithSeparator){ + } else { // add underscore to indicate, that we don't care about the end locations in the other components + if (appendLocationWithSeparator) { stringBuilder.append(",_"); - } - else{ + } else { stringBuilder.append("_"); } } @@ -191,7 +178,7 @@ private void initializeMouseControls() { }; locationProperty().addListener((obs, oldLocation, newLocation) -> { - if(newLocation == null) return; + if (newLocation == null) return; root.addEventHandler(MouseEvent.MOUSE_CLICKED, mouseClicked::accept); }); } @@ -200,7 +187,7 @@ private void initializeMouseControls() { * Creates the dropdown when right clicking a location. * When reachability is chosen a request will be send to the backend to see if the location can be reached */ - public void initializeDropDownMenu(){ + public void initializeDropDownMenu() { dropDownMenu = new DropDownMenu(root); String composition; @@ -257,6 +244,7 @@ public Location getLocation() { * Set/places the given location on the view. * This have to be done before adding the {@link SimLocationPresentation} to the view as nothing * would then be displayed. + * * @param location the location */ public void setLocation(final Location location) { @@ -285,6 +273,7 @@ public ObjectProperty<Component> componentProperty() { /** * Colors the location model + * * @param color the new color of the location */ public void color(final EnabledColor color) { diff --git a/src/main/java/ecdar/controllers/SimulationController.java b/src/main/java/ecdar/controllers/SimulationController.java index f68e9149..56273bef 100644 --- a/src/main/java/ecdar/controllers/SimulationController.java +++ b/src/main/java/ecdar/controllers/SimulationController.java @@ -4,6 +4,7 @@ import EcdarProtoBuf.QueryProtos; import ecdar.Ecdar; import ecdar.backend.BackendHelper; +import ecdar.backend.StateFactory; import ecdar.presentations.*; import ecdar.utility.helpers.SimulationHighlighter; import javafx.application.Platform; @@ -27,6 +28,7 @@ import java.net.URL; import java.util.*; +import java.util.stream.Collectors; public class SimulationController implements ModeController, Initializable { public StackPane root; @@ -74,6 +76,7 @@ public class SimulationController implements ModeController, Initializable { private final EventHandler<MouseEvent> decisionOnMouseExit = event -> highlighter.unhighlightEdges(); private final ObservableMap<String, ProcessPresentation> componentNameProcessPresentationMap = FXCollections.observableHashMap(); + private final StateFactory stateFactory = new StateFactory(); @Override public void initialize(final URL location, final ResourceBundle resources) { @@ -143,9 +146,9 @@ private void initializeWindowResizing() { * @param composition current composition being simulated */ private void initializeActiveSimulation(QueryProtos.SimulationStepResponse response, String composition) { - State newState = createStateFromResponse(response); + State initialState = this.stateFactory.createInitialState(composition, response); - Simulation newSimulation = new Simulation(composition, newState); + Simulation newSimulation = new Simulation(composition, initialState); activeSimulation.set(newSimulation); newSimulation.simulatedComponents.forEach(component -> { @@ -155,15 +158,25 @@ private void initializeActiveSimulation(QueryProtos.SimulationStepResponse respo }); leftSimPane.getController().setTraceLog(newSimulation.traceLog); - rightSimPane.getController().setDecisionsList(newSimulation.availableDecisions); + rightSimPane.getController().setDecisionsList( + initialState.getDecisions().stream() + .map(DecisionPresentation::new) + .collect(Collectors.toList()) + ); - updateSimulationState(response.getNewDecisionPointsList()); + updateSimulationState(); - // Update highlighting when state changes - newSimulation.currentState.addListener(observable -> { + // Update highlighting and decisions when state changes + newSimulation.currentState.addListener((observable, oldState, newState) -> { updateSimulationVariablesAndClocks(); highlighter.updateHighlighting(); highlighter.fadeSucceedingTraceStates(leftSimPane.getController().tracePanePresentation.getController().traceList.getChildren()); + + rightSimPane.getController().setDecisionsList( + newState.getDecisions().stream() + .map(DecisionPresentation::new) + .collect(Collectors.toList()) + ); }); rightSimPane.getController().availableDecisionsVBox.getChildren().addListener((ListChangeListener<? super Node>) c -> { @@ -176,7 +189,6 @@ private void initializeActiveSimulation(QueryProtos.SimulationStepResponse respo }); } - // ToDo NIELS: Remove static public static State getCurrentState() throws NullPointerException { return activeSimulation.get().currentState.get(); } @@ -215,10 +227,8 @@ public void nextStep(Decision decision) { BackendHelper.getDefaultEngine().enqueueRequest(decision, (response) -> { - // ToDo: This is temp solution to compile but should be fixed to handle ambiguity - State newState = createStateFromResponse(response); - getActiveSimulation().currentState.set(newState); - updateSimulationState(response.getNewDecisionPointsList()); + getActiveSimulation().currentState.set(stateFactory.createState(getComposition(), response)); + updateSimulationState(); }, (error) -> Ecdar.showToast("Could not take next step in simulation\nError: " + error.getMessage())); } @@ -232,22 +242,14 @@ public void resetSimulation(String queryToSimulate) { initialStep(queryToSimulate); } - private void previewState(final StatePresentation statePresentation) throws NullPointerException { - getActiveSimulation().currentState.set(statePresentation.getController().getState()); + private void previewState(final State state) throws NullPointerException { + getActiveSimulation().currentState.set(state); highlighter.fadeSucceedingTraceStates(leftSimPane.getController().tracePanePresentation.getController().traceList.getChildren()); } - private void updateSimulationState(List<ObjectProtos.Decision> availableDecisions) { + private void updateSimulationState() { Platform.runLater(() -> { - getActiveSimulation().addStateToTraceLog(getActiveSimulation().currentState.get(), this::previewState); - - getActiveSimulation().availableDecisions.clear(); - if (availableDecisions.isEmpty()) { - // If no edges are available in any of the returned decisions - Ecdar.showToast("No available decisions."); - } else { - getActiveSimulation().addDecisionsFromProto(availableDecisions); - } + getActiveSimulation().addStateToTraceLog(getActiveSimulation().currentState.get(), (statePresentation) -> this.previewState(statePresentation.getController().getState())); updateSimulationVariablesAndClocks(); highlighter.updateHighlighting(); @@ -327,10 +329,6 @@ private void removeStatesFromLog(StatePresentation statePresentation) { getActiveSimulation().traceLog.remove(newLastStateIndex + 1, getActiveSimulation().traceLog.size()); } - private State createStateFromResponse(QueryProtos.SimulationStepResponse response) { - return new State(response.getNewDecisionPoints(0).getSource()); // ToDo NIELS: Each source is only a subset, we should combine them to the full state - } - public static void setSelectedState(State selectedState) { SimulationController.selectedState.set(selectedState); // ToDo NIELS: Handle selection } diff --git a/src/main/java/ecdar/controllers/StateController.java b/src/main/java/ecdar/controllers/StateController.java index 4ad268f3..28769099 100755 --- a/src/main/java/ecdar/controllers/StateController.java +++ b/src/main/java/ecdar/controllers/StateController.java @@ -2,12 +2,15 @@ import com.jfoenix.controls.JFXRippler; import ecdar.abstractions.State; +import ecdar.utility.helpers.ClockConstraintsHandler; import javafx.beans.property.SimpleObjectProperty; import javafx.fxml.Initializable; import javafx.scene.control.Label; import javafx.scene.layout.AnchorPane; +import javafx.scene.text.Text; import java.net.URL; +import java.util.Map; import java.util.ResourceBundle; /** @@ -18,17 +21,22 @@ public class StateController implements Initializable { public AnchorPane root; public Label locations; + public Text clockConstraintsHeader; public Label clockConstraints; public JFXRippler rippler; // The transition that the view represents private final SimpleObjectProperty<State> state = new SimpleObjectProperty<>(); + private final ClockConstraintsHandler clockConstraintsHandler = new ClockConstraintsHandler(); + @Override public void initialize(URL location, ResourceBundle resources) { state.addListener((observable, oldValue, newValue) -> { - locations.setText(newValue.getStateLocationsString()); - clockConstraints.setText(newValue.getStateClockConstraintsString()); + locations.setText(getStateLocationsString(newValue)); + + setClockConstraintsSectionVisibility(!newValue.getClockConstraints().isEmpty()); + clockConstraints.setText(clockConstraintsHandler.getStateClockConstraintsString(newValue.getClockConstraints())); }); } @@ -39,4 +47,34 @@ public void setState(State state) { public State getState() { return state.get(); } + + private void setClockConstraintsSectionVisibility(boolean visibility) { + clockConstraintsHeader.setVisible(visibility); + clockConstraintsHeader.setManaged(visibility); + clockConstraints.setVisible(visibility); + clockConstraints.setManaged(visibility); + } + + /** + * A helper method that returns a string representing the locations of a state in the trace log + * + * @return A string representing the locations + */ + private String getStateLocationsString(State state) { + StringBuilder locationsString = new StringBuilder(); + + int i = 0; + for (Map.Entry<String, String> componentLocation : state.getComponentLocationMap().entrySet()) { + locationsString.append(componentLocation.getKey()); + locationsString.append('.'); + locationsString.append(componentLocation.getValue()); + + i++; + if (i < state.getComponentLocationMap().size()) { + locationsString.append(System.lineSeparator()); + } + } + + return locationsString.toString(); + } } diff --git a/src/main/java/ecdar/controllers/TracePaneController.java b/src/main/java/ecdar/controllers/TracePaneController.java index 294c5788..d8b06ae5 100755 --- a/src/main/java/ecdar/controllers/TracePaneController.java +++ b/src/main/java/ecdar/controllers/TracePaneController.java @@ -174,7 +174,7 @@ private void insertStateInTrace(final StatePresentation statePresentation) { // Only insert the presentation into the view if the trace is expanded & statePresentation is not null if (isTraceExpanded.get()) { - traceList.getChildren().add(statePresentation); + traceList.getChildren().add(0, statePresentation); } } diff --git a/src/main/java/ecdar/utility/helpers/ConstraintsHandler.java b/src/main/java/ecdar/utility/helpers/ClockConstraintsHandler.java similarity index 60% rename from src/main/java/ecdar/utility/helpers/ConstraintsHandler.java rename to src/main/java/ecdar/utility/helpers/ClockConstraintsHandler.java index bda47f7b..b865bb6f 100644 --- a/src/main/java/ecdar/utility/helpers/ConstraintsHandler.java +++ b/src/main/java/ecdar/utility/helpers/ClockConstraintsHandler.java @@ -1,36 +1,32 @@ package ecdar.utility.helpers; -import EcdarProtoBuf.ObjectProtos; import ecdar.abstractions.ClockConstraint; import java.util.ArrayList; import java.util.List; import java.util.Objects; -public class ConstraintsHandler { +public class ClockConstraintsHandler { /** * Generates a string representation of the clock constraints of the provided state * - * @param protoState state containing the clock constraints to represent in the string - * @return a prettified string representation of the state's clock constraints + * @param clockConstraints list of the clock constraints represent in the string + * @return a prettified string representation of the clock constraints */ - public static String getStateClockConstraintsString(ObjectProtos.State protoState) { - List<ClockConstraint> refinedConstraints = new ArrayList<>(); - for (var constraint : protoState.getZone().getConjunctions(0).getConstraintsList()) { - refinedConstraints.add(generateClockConstraint(constraint)); - } + public String getStateClockConstraintsString(List<ClockConstraint> clockConstraints) { + if (clockConstraints.isEmpty()) return ""; StringBuilder clockConstraintsString = new StringBuilder(); List<Integer> mergedConstraints = new ArrayList<>(); - for (int i = 0; i < refinedConstraints.size(); i++) { + for (int i = 0; i < clockConstraints.size(); i++) { if (mergedConstraints.contains(i)) continue; // 'i' has already been merged - for (int j = 1; j < refinedConstraints.size(); j++) { + for (int j = 1; j < clockConstraints.size(); j++) { if (i == j || mergedConstraints.contains(j)) continue; // 'j' has already been merged - ClockConstraint c1 = refinedConstraints.get(i); - ClockConstraint c2 = refinedConstraints.get(j); + ClockConstraint c1 = clockConstraints.get(i); + ClockConstraint c2 = clockConstraints.get(j); if (constraintClocksMatch(c1, c2)) { clockConstraintsString.append(getMergedConstraintString(c1, c2)).append("\n"); @@ -40,9 +36,9 @@ public static String getStateClockConstraintsString(ObjectProtos.State protoStat } } - for (int i = 0; i < refinedConstraints.size(); i++) { + for (int i = 0; i < clockConstraints.size(); i++) { if (!mergedConstraints.contains(i)) { - clockConstraintsString.append(refinedConstraints.get(i).toString()).append("\n"); + clockConstraintsString.append(clockConstraints.get(i).toString()).append("\n"); } } @@ -60,7 +56,7 @@ public static String getStateClockConstraintsString(ObjectProtos.State protoStat * @param c2 second clock constraint * @return string representation of the merged clock constraint if compatible, or the two separate representations otherwise */ - private static String getMergedConstraintString(ClockConstraint c1, ClockConstraint c2) { + private String getMergedConstraintString(ClockConstraint c1, ClockConstraint c2) { StringBuilder mergedConstraint = new StringBuilder(); if ((c1.comparator == '<' && c2.comparator == '>') || @@ -105,35 +101,7 @@ private static String getMergedConstraintString(ClockConstraint c1, ClockConstra * @param c2 second clock constraint * @return true if the two clock constraints are compatible, false otherwise */ - private static boolean constraintClocksMatch(ClockConstraint c1, ClockConstraint c2) { + private boolean constraintClocksMatch(ClockConstraint c1, ClockConstraint c2) { return (Objects.equals(c1.clocks.getValue(), c2.clocks.getValue()) && Objects.equals(c1.clocks.getKey(), c2.clocks.getKey())) || (Objects.equals(c1.clocks.getValue(), c2.clocks.getKey()) && Objects.equals(c1.clocks.getKey(), c2.clocks.getValue())); } - - /** - * Generates a ClockConstraint object from a ProtoBuf constraint - * - * @param constraint the ProtoBuf constraint to generate from - * @return the generated ClockConstraint - */ - private static ClockConstraint generateClockConstraint(ObjectProtos.Constraint constraint) { - var clock1 = constraint.getX().getComponentClock().getClockName(); - var clock2 = constraint.getY().getComponentClock().getClockName(); - var constant = constraint.getC(); - var strict = constraint.getStrict(); - - if (clock1.equals("")) { - // The first clock is the zero-clock - return new ClockConstraint(clock2, null, Math.abs(constant), '>', strict); - } else if (clock2.equals("")) { - // The second clock is the zero-clock - return new ClockConstraint(clock1, null, Math.abs(constant), '<', strict); - } else { - // None of the clocks are the zero-clock - if (constant >= 0) { - return new ClockConstraint(clock1, clock2, Math.abs(constant), '<', strict); - } else { - return new ClockConstraint(clock2, clock1, Math.abs(constant), '>', strict); - } - } - } } diff --git a/src/main/java/ecdar/utility/helpers/SimulationHighlighter.java b/src/main/java/ecdar/utility/helpers/SimulationHighlighter.java index 673b428b..72f09d6d 100644 --- a/src/main/java/ecdar/utility/helpers/SimulationHighlighter.java +++ b/src/main/java/ecdar/utility/helpers/SimulationHighlighter.java @@ -14,11 +14,9 @@ import java.util.ArrayList; import java.util.List; -import java.util.function.Consumer; import java.util.stream.Collectors; public class SimulationHighlighter { - // ToDo NIELS: Clean up by removing extraneous methods private final ObservableMap<String, ProcessPresentation> componentNameProcessPresentationMap; private final ObjectProperty<Simulation> simulation = new SimpleObjectProperty<>(); private ArrayList<String> temporarilyHighlightedLocations = new ArrayList<>(); @@ -69,10 +67,11 @@ public void fadeSucceedingTraceStates(List<Node> traceListStates) { if (activeStatePresentation == null) return; traceListStates.forEach(trace -> trace.setOpacity(1)); - int i = traceListStates.size() - 1; - while (traceListStates.get(i) != activeStatePresentation) { + + int i = 0; + while (i < traceListStates.size() && traceListStates.get(i) != activeStatePresentation) { traceListStates.get(i).setOpacity(0.4); - i--; + i++; } } @@ -104,11 +103,10 @@ public void highlightReachabilityEdges(ArrayList<String> ids) throws NullPointer * @param state The state with the locations to highlight */ public void highlightProcessState(final State state) { - Consumer<ObjectProtos.LeafLocation> leafLocationConsumer = - (leaf) -> componentNameProcessPresentationMap.get(leaf.getComponentInstance().getComponentName()) - .getController().highlightLocation(leaf.getId()); - - Platform.runLater(() -> state.consumeLeafLocations(leafLocationConsumer)); + Platform.runLater(() -> state.getComponentLocationMap().forEach((comp, loc) -> { + componentNameProcessPresentationMap.get(comp) + .getController().highlightLocation(loc); + })); } /** @@ -137,13 +135,11 @@ public void unhighlightEdges() { .forEach(e -> e.setIsHighlighted(false))); } - public void highlightPreview(final State state) { - Consumer<ObjectProtos.LeafLocation> leafLocationConsumer = - (leaf) -> { - componentNameProcessPresentationMap.get(leaf.getComponentInstance().getComponentName()).getController().highlightLocation(leaf.getId()); - temporarilyHighlightedLocations.add(leaf.getId()); - }; - Platform.runLater(() -> state.consumeLeafLocations(leafLocationConsumer)); + public void highlightPreview(final State state) { + Platform.runLater(() -> state.getComponentLocationMap().forEach((comp, loc) -> { + componentNameProcessPresentationMap.get(comp).getController().highlightLocation(loc); + temporarilyHighlightedLocations.add(loc); + })); } } diff --git a/src/main/resources/ecdar/presentations/StatePresentation.fxml b/src/main/resources/ecdar/presentations/StatePresentation.fxml index 39cf9957..4b9a3481 100755 --- a/src/main/resources/ecdar/presentations/StatePresentation.fxml +++ b/src/main/resources/ecdar/presentations/StatePresentation.fxml @@ -20,7 +20,7 @@ <Region minWidth="10" maxWidth="10"/> <Label fx:id="locations" styleClass="body1" wrapText="true"/> </HBox> - <Text text="Clock Constraints" styleClass="subhead" style="-fx-stroke-width: 1.2em"/> + <Text fx:id="clockConstraintsHeader" text="Clock Constraints" styleClass="subhead" style="-fx-stroke-width: 1.2em"/> <HBox> <Region minWidth="10" maxWidth="10"/> <Label fx:id="clockConstraints" styleClass="body1" wrapText="true"/> From e3e3b7bbcff9760f1fbc5224afe07e464f173b61 Mon Sep 17 00:00:00 2001 From: Niels Vistisen <nielswps@gmail.com> Date: Sun, 11 Jun 2023 12:24:52 +0200 Subject: [PATCH 155/158] WIP: Unused import removed --- src/main/java/ecdar/controllers/SimulationController.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/ecdar/controllers/SimulationController.java b/src/main/java/ecdar/controllers/SimulationController.java index 56273bef..d05259ab 100644 --- a/src/main/java/ecdar/controllers/SimulationController.java +++ b/src/main/java/ecdar/controllers/SimulationController.java @@ -1,6 +1,5 @@ package ecdar.controllers; -import EcdarProtoBuf.ObjectProtos; import EcdarProtoBuf.QueryProtos; import ecdar.Ecdar; import ecdar.backend.BackendHelper; From 0eebe8e80e4b47c18ced1d63818550401c1e4f6e Mon Sep 17 00:00:00 2001 From: "Niels F. S. Vistisen" <42961494+Nielswps@users.noreply.github.com> Date: Tue, 13 Jun 2023 13:30:47 +0200 Subject: [PATCH 156/158] Proto updated to use state-from-step and simulation test update to use locationTree (currenly failing) (#21) --- .../java/ecdar/abstractions/Decision.java | 1 - src/main/java/ecdar/backend/StateFactory.java | 4 +- src/main/proto | 2 +- .../java/ecdar/simulation/SimulationTest.java | 44 ++++++++++++------- 4 files changed, 30 insertions(+), 21 deletions(-) diff --git a/src/main/java/ecdar/abstractions/Decision.java b/src/main/java/ecdar/abstractions/Decision.java index e8254e68..3d659732 100644 --- a/src/main/java/ecdar/abstractions/Decision.java +++ b/src/main/java/ecdar/abstractions/Decision.java @@ -8,7 +8,6 @@ import java.util.ArrayList; import java.util.List; import java.util.function.Consumer; -import java.util.stream.Collectors; public class Decision implements RequestSource<QueryProtos.SimulationStepResponse> { public final String composition; diff --git a/src/main/java/ecdar/backend/StateFactory.java b/src/main/java/ecdar/backend/StateFactory.java index 949904d6..9527f2e1 100644 --- a/src/main/java/ecdar/backend/StateFactory.java +++ b/src/main/java/ecdar/backend/StateFactory.java @@ -18,7 +18,7 @@ public class StateFactory { * @return the generated state */ public State createState(String composition, QueryProtos.SimulationStepResponse response) { - ObjectProtos.State sourceState = response.getNewDecisionPoints(0).getSource(); + ObjectProtos.State sourceState = response.getFullState(); HashMap<String, String> componentLocationsMap = loadLocations(sourceState.getLocationTree()); @@ -37,7 +37,7 @@ public State createState(String composition, QueryProtos.SimulationStepResponse * @return the generated initial state */ public State createInitialState(String composition, QueryProtos.SimulationStepResponse response) { - ObjectProtos.State sourceState = response.getNewDecisionPoints(0).getSource(); + ObjectProtos.State sourceState = response.getFullState(); HashMap<String, String> componentLocationsMap = loadLocations(sourceState.getLocationTree()); List<Decision> decisions = loadDecisions(composition, response.getNewDecisionPointsList()); diff --git a/src/main/proto b/src/main/proto index ae8364e6..70e8d5a2 160000 --- a/src/main/proto +++ b/src/main/proto @@ -1 +1 @@ -Subproject commit ae8364e6c8942a5cfde24adec779ca87d4431e4a +Subproject commit 70e8d5a2dca183687cc0827d4ade7441fbdb4ddf diff --git a/src/test/java/ecdar/simulation/SimulationTest.java b/src/test/java/ecdar/simulation/SimulationTest.java index d0b385bb..fe13d070 100644 --- a/src/test/java/ecdar/simulation/SimulationTest.java +++ b/src/test/java/ecdar/simulation/SimulationTest.java @@ -15,6 +15,7 @@ import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; @@ -35,19 +36,23 @@ public void testGetInitialStateHighlightsTheInitialLocation() { public void startSimulation(QueryProtos.SimulationStartRequest request, StreamObserver<QueryProtos.SimulationStepResponse> responseObserver) { try { - ObjectProtos.LocationTuple locations = LocationTuple.newBuilder().addAllLocations(components.stream() - .map(c -> ObjectProtos.Location.newBuilder() - .setSpecificComponent(SpecificComponent.newBuilder() - .setComponentName(c.getName())) - .setId(c.getInitialLocation().getId()) - .build()) - .collect(Collectors.toList())) - .build(); + var leafLocations = components.stream() + .map(c -> ObjectProtos.LeafLocation.newBuilder() + .setComponentInstance(ObjectProtos.ComponentInstance.newBuilder() + .setComponentName(c.getName())) + .setId(c.getInitialLocation().getId()) + .build()) + .collect(Collectors.toList()); + + var locationTreeBuilder = ObjectProtos.LocationTree.newBuilder(); + leafLocations.stream().map(locationTreeBuilder::mergeLeafLocation); + + var locationTree = locationTreeBuilder.build(); - ObjectProtos.State state = ObjectProtos.State.newBuilder().setLocationTuple(locations).build(); - DecisionPoint decisionPoint = DecisionPoint.newBuilder().setSource(state).build(); + ObjectProtos.State state = ObjectProtos.State.newBuilder().setLocationTree(locationTree).build(); + ObjectProtos.Decision decision = ObjectProtos.Decision.newBuilder().setSource(state).build(); QueryProtos.SimulationStepResponse response = QueryProtos.SimulationStepResponse.newBuilder() - .addNewDecisionPoints(decisionPoint) + .addNewDecisionPoints(decision) .build(); responseObserver.onNext(response); responseObserver.onCompleted(); @@ -73,18 +78,23 @@ public void takeSimulationStep(EcdarProtoBuf.QueryProtos.SimulationStepRequest r QueryProtos.SimulationStartRequest request = QueryProtos.SimulationStartRequest.newBuilder().build(); - var expectedResponse = new ObjectProtos.Location[components.size()]; + var expectedResponse = new ObjectProtos.LocationTree[components.size()]; for (int i = 0; i < components.size(); i++) { Component comp = components.get(i); - expectedResponse[i] = ObjectProtos.Location.newBuilder() - .setSpecificComponent(SpecificComponent.newBuilder().setComponentName(comp.getName())) - .setId(comp.getInitialLocation().getId()).build(); + expectedResponse[i] = ObjectProtos.LocationTree.newBuilder() + .setLeafLocation(ObjectProtos.LeafLocation.newBuilder().setId(comp.getInitialLocation().getId()) + .setComponentInstance(ObjectProtos.ComponentInstance + .newBuilder() + .setComponentName(comp.getName()).build() + ) + ) + .build(); } - var result = stub.startSimulation(request).getNewDecisionPoints(0).getSource().getLocationTuple().getLocationsList().toArray(); + var result = stub.startSimulation(request).getFullState().getLocationTree(); - Assertions.assertArrayEquals(expectedResponse, result); + Assertions.assertTrue(Arrays.asList(expectedResponse).contains(result)); } catch (IOException e) { Assertions.fail("Exception encountered: " + e.getMessage()); } From 820fd2bd59ad3ffd48f721e247467fe2d22305cb Mon Sep 17 00:00:00 2001 From: Niels Vistisen <nielswps@gmail.com> Date: Tue, 27 Jun 2023 20:19:43 +0200 Subject: [PATCH 157/158] WIP: Concrete sim delay REMOVED, restart sim confirm dialog ADDED, and several minor simulator issues FIXED --- .../controllers/LeftSimPaneController.java | 11 +-- .../controllers/SimulationController.java | 44 +++++++++++- .../controllers/StatePaneController.java | 8 --- .../controllers/TracePaneController.java | 71 +++++++++++++------ .../LeftSimPanePresentation.fxml | 7 +- .../presentations/TracePanePresentation.fxml | 27 +++++-- 6 files changed, 115 insertions(+), 53 deletions(-) diff --git a/src/main/java/ecdar/controllers/LeftSimPaneController.java b/src/main/java/ecdar/controllers/LeftSimPaneController.java index a5c64d0d..2461cd52 100755 --- a/src/main/java/ecdar/controllers/LeftSimPaneController.java +++ b/src/main/java/ecdar/controllers/LeftSimPaneController.java @@ -2,12 +2,10 @@ import ecdar.presentations.StatePresentation; import ecdar.presentations.TracePanePresentation; -import ecdar.presentations.StatePanePresentation; import ecdar.utility.colors.Color; import javafx.collections.ObservableList; import javafx.fxml.Initializable; import javafx.geometry.Insets; -import javafx.scene.control.ScrollPane; import javafx.scene.layout.*; import java.net.URL; @@ -15,10 +13,8 @@ public class LeftSimPaneController implements Initializable { public StackPane root; - public ScrollPane scrollPane; - public VBox scrollPaneVbox; + public VBox content; - public StatePanePresentation statePanePresentation; public TracePanePresentation tracePanePresentation; @Override @@ -28,7 +24,7 @@ public void initialize(URL location, ResourceBundle resources) { } private void initializeBackground() { - scrollPaneVbox.setBackground(new Background(new BackgroundFill( + content.setBackground(new Background(new BackgroundFill( Color.GREY.getColor(Color.Intensity.I200), CornerRadii.EMPTY, Insets.EMPTY @@ -39,7 +35,7 @@ private void initializeBackground() { * Initializes the thin border on the right side of the transition toolbar */ private void initializeRightBorder() { - statePanePresentation.getController().toolbar.setBorder(new Border(new BorderStroke( + tracePanePresentation.getController().toolbar.setBorder(new Border(new BorderStroke( Color.GREY_BLUE.getColor(Color.Intensity.I900), BorderStrokeStyle.SOLID, CornerRadii.EMPTY, @@ -48,7 +44,6 @@ private void initializeRightBorder() { } public void setTraceLog(ObservableList<StatePresentation> traceLog) { - statePanePresentation.getController().setTraceLog(traceLog); tracePanePresentation.getController().setTraceLog(traceLog); } } diff --git a/src/main/java/ecdar/controllers/SimulationController.java b/src/main/java/ecdar/controllers/SimulationController.java index d05259ab..f9ef26c9 100644 --- a/src/main/java/ecdar/controllers/SimulationController.java +++ b/src/main/java/ecdar/controllers/SimulationController.java @@ -1,6 +1,9 @@ package ecdar.controllers; import EcdarProtoBuf.QueryProtos; +import com.jfoenix.controls.JFXButton; +import com.jfoenix.controls.JFXDialog; +import com.jfoenix.controls.JFXDialogLayout; import ecdar.Ecdar; import ecdar.backend.BackendHelper; import ecdar.backend.StateFactory; @@ -20,6 +23,7 @@ import javafx.geometry.Insets; import javafx.scene.Group; import javafx.scene.Node; +import javafx.scene.control.Label; import javafx.scene.control.ScrollPane; import javafx.scene.input.MouseEvent; import javafx.scene.layout.FlowPane; @@ -71,21 +75,48 @@ public class SimulationController implements ModeController, Initializable { private boolean isMaxZoomInReached = false; private boolean isMaxZoomOutReached = false; - private final EventHandler<MouseEvent> decisionOnMouseEnter = event -> highlighter.highlightDecisionEdges(((DecisionPresentation)event.getTarget())); + private final EventHandler<MouseEvent> decisionOnMouseEnter = event -> highlighter.highlightDecisionEdges(((DecisionPresentation) event.getTarget())); private final EventHandler<MouseEvent> decisionOnMouseExit = event -> highlighter.unhighlightEdges(); private final ObservableMap<String, ProcessPresentation> componentNameProcessPresentationMap = FXCollections.observableHashMap(); private final StateFactory stateFactory = new StateFactory(); + private JFXDialog restartSimConfirmDialog; + @Override public void initialize(final URL location, final ResourceBundle resources) { //In case that the processContainer gets moved around we have to keep it in place. initializeProcessContainer(); initializeWindowResizing(); initializeZoom(); + initializeRestartSimulationConfirmationDialog(); + highlighter = new SimulationHighlighter(componentNameProcessPresentationMap, activeSimulation); } + private void initializeRestartSimulationConfirmationDialog() { + JFXDialogLayout layout = new JFXDialogLayout(); + layout.setHeading(new Label("Restart Simulation")); + + Label body = new Label("Restarting the simulation will remove all steps in the current trace.\n\nAre you sure you want to restart the simulation?"); + body.getStyleClass().add("body1"); + layout.setBody(body); + + restartSimConfirmDialog = new JFXDialog(new StackPane(), layout, JFXDialog.DialogTransition.CENTER); + JFXButton confirm = new JFXButton("Yes"); + confirm.setOnAction((event) -> { + restartSimConfirmDialog.close(); + resetSimulation(getActiveSimulation().composition); + }); + + JFXButton cancel = new JFXButton("No"); + cancel.setOnAction((event) -> { + restartSimConfirmDialog.close(); + }); + + layout.setActions(cancel, confirm); + } + /** * Initializes the {@link #processContainer} with its correct styling, and placement on the view. * It also adds a {@link ListChangeListener} on the list of simulated components where it adds the @@ -141,7 +172,7 @@ private void initializeWindowResizing() { /** * Initializes the simulation based on the received proto response and current composition * - * @param response ProtoBuf response received from a step request + * @param response ProtoBuf response received from a step request * @param composition current composition being simulated */ private void initializeActiveSimulation(QueryProtos.SimulationStepResponse response, String composition) { @@ -157,6 +188,13 @@ private void initializeActiveSimulation(QueryProtos.SimulationStepResponse respo }); leftSimPane.getController().setTraceLog(newSimulation.traceLog); + Platform.runLater(() -> leftSimPane.getController() + .tracePanePresentation.getController() + .restartSimulation.setOnMouseClicked((event) -> { + event.consume(); + restartSimConfirmDialog.show(Ecdar.getPresentation()); + })); + rightSimPane.getController().setDecisionsList( initialState.getDecisions().stream() .map(DecisionPresentation::new) @@ -329,7 +367,7 @@ private void removeStatesFromLog(StatePresentation statePresentation) { } public static void setSelectedState(State selectedState) { - SimulationController.selectedState.set(selectedState); // ToDo NIELS: Handle selection + SimulationController.selectedState.set(selectedState); } public static String getComposition() { diff --git a/src/main/java/ecdar/controllers/StatePaneController.java b/src/main/java/ecdar/controllers/StatePaneController.java index 7ebb723f..f4ac90c5 100755 --- a/src/main/java/ecdar/controllers/StatePaneController.java +++ b/src/main/java/ecdar/controllers/StatePaneController.java @@ -198,14 +198,6 @@ private void expandTransitions() { isTransitionExpanded.set(!isTransitionExpanded.get()); } - /** - * Restart simulation to the initial step. - */ - @FXML - private void restartSimulation() { - return; - } // ToDo NIELS: Use simulation controller - /** * Sanitizes the input that the user inserts into the delay textfield. * Checks if the text can be converted into a BigDecimal otherwise show the previous value. diff --git a/src/main/java/ecdar/controllers/TracePaneController.java b/src/main/java/ecdar/controllers/TracePaneController.java index d8b06ae5..1c2e33c6 100755 --- a/src/main/java/ecdar/controllers/TracePaneController.java +++ b/src/main/java/ecdar/controllers/TracePaneController.java @@ -1,11 +1,13 @@ package ecdar.controllers; import com.jfoenix.controls.JFXRippler; +import com.jfoenix.controls.JFXTooltip; import ecdar.abstractions.State; import ecdar.presentations.StatePresentation; import ecdar.utility.colors.Color; import javafx.application.Platform; import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.value.ChangeListener; import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; import javafx.event.EventHandler; @@ -30,6 +32,8 @@ public class TracePaneController implements Initializable { public HBox toolbar; public Label traceTitle; public JFXRippler expandTrace; + public JFXRippler restartSimulation; + public VBox traceSection; public VBox traceList; public FontIcon expandTraceIcon; public AnchorPane traceSummary; @@ -39,10 +43,38 @@ public class TracePaneController implements Initializable { private final SimpleBooleanProperty isTraceExpanded = new SimpleBooleanProperty(false); private ObservableList<StatePresentation> traceLog; + /** + * Keep reference to the traceLogListener, such that it can be removed and added to future logs + */ + private final ListChangeListener<StatePresentation> traceLogListener = c -> { + updateSummaryTitle(traceLog.size()); + + if (!isTraceExpanded.get()) return; + while (c.next()) { + c.getRemoved().forEach(statePresentation -> traceList.getChildren().remove(statePresentation)); + c.getAddedSubList().forEach(this::insertStateInTrace); + } + }; + + @Override public void initialize(URL location, ResourceBundle resources) { initializeToolbar(); initializeSummaryView(); + + isTraceExpanded.addListener((obs, oldVal, newVal) -> { + if (newVal) { + Platform.runLater(this::showTrace); + expandTraceIcon.setIconLiteral("gmi-expand-less"); + } else { + Platform.runLater(this::hideTrace); + expandTraceIcon.setIconLiteral("gmi-expand-more"); + } + + traceSection.setManaged(newVal); + }); + + Platform.runLater(this::toggleTraceExpand); } /** @@ -61,6 +93,8 @@ private void initializeToolbar() { expandTrace.setMaskType(JFXRippler.RipplerMask.CIRCLE); expandTrace.setRipplerFill(color.getTextColor(colorIntensity)); + + JFXTooltip.install(restartSimulation, new JFXTooltip("Restart Simulation")); } /** @@ -103,33 +137,22 @@ private void initializeSummaryView() { } protected void setTraceLog(ObservableList<StatePresentation> traceLog) { - this.traceLog = traceLog; - - this.traceLog.addListener((ListChangeListener<StatePresentation>) c -> { - updateSummaryTitle(traceLog.size()); + if (this.traceLog != null) { + // Remove all information from previous simulation run + this.traceLog.removeListener(traceLogListener); + this.traceLog.clear(); + }; - if (!isTraceExpanded.get()) return; - while (c.next()) { - c.getRemoved().forEach(statePresentation -> traceList.getChildren().remove(statePresentation)); - c.getAddedSubList().forEach(this::insertStateInTrace); - } - }); + this.traceList.getChildren().clear(); + this.traceLog = traceLog; - isTraceExpanded.addListener((obs, oldVal, newVal) -> { - if (newVal) { - Platform.runLater(this::showTrace); - expandTraceIcon.setIconLiteral("gmi-expand-less"); - expandTraceIcon.setIconSize(24); - } else { - Platform.runLater(this::hideTrace); - expandTraceIcon.setIconLiteral("gmi-expand-more"); - expandTraceIcon.setIconSize(24); - } - }); + // Add listener to new log + this.traceLog.addListener(traceLogListener); } /** * Updates the text of the summary title label with the current number of steps in the trace + * * @param steps The number of steps in the trace */ private void updateSummaryTitle(int steps) { @@ -151,7 +174,8 @@ private void hideTrace() { */ private void showTrace() { root.getChildren().remove(traceSummary); - this.traceLog.forEach(this::insertStateInTrace); + // After initialization, the log will be null + if (this.traceLog != null) this.traceLog.forEach(this::insertStateInTrace); } /** @@ -160,6 +184,7 @@ private void showTrace() { * @param statePresentation The statePresentation to insert into the trace log */ private void insertStateInTrace(final StatePresentation statePresentation) { + // Install mouse event listeners on the state EventHandler<? super MouseEvent> mouseEntered = statePresentation.getOnMouseEntered(); statePresentation.setOnMouseEntered(event -> { SimulationController.setSelectedState(statePresentation.getController().getState()); @@ -172,7 +197,7 @@ private void insertStateInTrace(final StatePresentation statePresentation) { mouseExited.handle(event); }); - // Only insert the presentation into the view if the trace is expanded & statePresentation is not null + // Only insert the presentation into the view if the trace is expanded if (isTraceExpanded.get()) { traceList.getChildren().add(0, statePresentation); } diff --git a/src/main/resources/ecdar/presentations/LeftSimPanePresentation.fxml b/src/main/resources/ecdar/presentations/LeftSimPanePresentation.fxml index d996f819..7035a34a 100755 --- a/src/main/resources/ecdar/presentations/LeftSimPanePresentation.fxml +++ b/src/main/resources/ecdar/presentations/LeftSimPanePresentation.fxml @@ -3,7 +3,6 @@ <?import javafx.scene.control.*?> <?import javafx.scene.layout.*?> <?import ecdar.presentations.TracePanePresentation?> -<?import ecdar.presentations.StatePanePresentation?> <fx:root xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8.0.76-ea" @@ -11,15 +10,13 @@ fx:id="root" fx:controller="ecdar.controllers.LeftSimPaneController"> <AnchorPane> - <ScrollPane fx:id="scrollPane" - fitToHeight="true" fitToWidth="true" + <ScrollPane fitToHeight="true" fitToWidth="true" AnchorPane.topAnchor="0" AnchorPane.bottomAnchor="0" AnchorPane.leftAnchor="0" AnchorPane.rightAnchor="0" styleClass="edge-to-edge"> - <VBox fx:id="scrollPaneVbox"> - <StatePanePresentation fx:id="statePanePresentation"/> + <VBox fx:id="content"> <TracePanePresentation fx:id="tracePanePresentation"/> </VBox> </ScrollPane> diff --git a/src/main/resources/ecdar/presentations/TracePanePresentation.fxml b/src/main/resources/ecdar/presentations/TracePanePresentation.fxml index f891e9c0..929586eb 100755 --- a/src/main/resources/ecdar/presentations/TracePanePresentation.fxml +++ b/src/main/resources/ecdar/presentations/TracePanePresentation.fxml @@ -16,8 +16,7 @@ </padding> <JFXRippler fx:id="expandTrace"> <StackPane styleClass="responsive-icon-stack-pane-sizing" onMouseClicked="#toggleTraceExpand"> - <FontIcon fx:id="expandTraceIcon" iconLiteral="gmi-add" styleClass="icon-size-medium" - fill="white"/> + <FontIcon fx:id="expandTraceIcon" iconLiteral="gmi-expand-more" styleClass="icon-size-medium" fill="white"/> </StackPane> </JFXRippler> @@ -26,11 +25,13 @@ <Label fx:id="traceTitle" styleClass="title" text="Trace"/> <Region HBox.hgrow="ALWAYS"/> - </HBox> - <VBox fx:id="traceList"> - <!-- Trace goes here --> - </VBox> + <JFXRippler fx:id="restartSimulation"> + <StackPane styleClass="responsive-icon-stack-pane-sizing"> + <FontIcon iconLiteral="gmi-refresh" styleClass="icon-size-medium" fill="white"/> + </StackPane> + </JFXRippler> + </HBox> <AnchorPane fx:id="traceSummary" minHeight="56" maxHeight="56" onMouseClicked="#toggleTraceExpand"> <VBox AnchorPane.leftAnchor="8" @@ -46,6 +47,20 @@ styleClass="caption"/> </VBox> </AnchorPane> + + <VBox fx:id="traceSection" managed="false"> + <ScrollPane fitToHeight="true" fitToWidth="true" + AnchorPane.topAnchor="0" + AnchorPane.bottomAnchor="0" + AnchorPane.leftAnchor="0" + AnchorPane.rightAnchor="0" + styleClass="edge-to-edge"> + <VBox fx:id="traceList"> + <!-- Trace goes here --> + </VBox> + </ScrollPane> + </VBox> + <padding> <Insets bottom="25"/> </padding> From 2dd722aaf32d3041c2dd0cdadb565f5a176834b4 Mon Sep 17 00:00:00 2001 From: Niels Vistisen <nielswps@gmail.com> Date: Tue, 27 Jun 2023 20:23:17 +0200 Subject: [PATCH 158/158] WIP: Unused state pane files REMOVED --- .../ecdar/controllers/StateController.java | 5 +- .../controllers/StatePaneController.java | 234 ------------------ .../presentations/StatePanePresentation.java | 19 -- .../presentations/StatePresentation.java | 5 +- .../presentations/StatePanePresentation.fxml | 51 ---- 5 files changed, 4 insertions(+), 310 deletions(-) delete mode 100755 src/main/java/ecdar/controllers/StatePaneController.java delete mode 100755 src/main/java/ecdar/presentations/StatePanePresentation.java delete mode 100755 src/main/resources/ecdar/presentations/StatePanePresentation.fxml diff --git a/src/main/java/ecdar/controllers/StateController.java b/src/main/java/ecdar/controllers/StateController.java index 28769099..0310d44c 100755 --- a/src/main/java/ecdar/controllers/StateController.java +++ b/src/main/java/ecdar/controllers/StateController.java @@ -14,9 +14,8 @@ import java.util.ResourceBundle; /** - * The controller class for the transition view element. - * It represents a single transition and may be used by classes like {@see StatePaneController} - * to show a list of transitions + * The controller class for the state view element. + * It represents a single state within the trace of the simulation */ public class StateController implements Initializable { public AnchorPane root; diff --git a/src/main/java/ecdar/controllers/StatePaneController.java b/src/main/java/ecdar/controllers/StatePaneController.java deleted file mode 100755 index f4ac90c5..00000000 --- a/src/main/java/ecdar/controllers/StatePaneController.java +++ /dev/null @@ -1,234 +0,0 @@ -package ecdar.controllers; - -import com.jfoenix.controls.JFXRippler; -import com.jfoenix.controls.JFXTextField; -import ecdar.abstractions.Edge; -import ecdar.abstractions.Transition; -import ecdar.presentations.StatePresentation; -import ecdar.utility.colors.EnabledColor; -import javafx.beans.property.SimpleBooleanProperty; -import javafx.beans.property.SimpleObjectProperty; -import javafx.collections.FXCollections; -import javafx.collections.ObservableList; -import javafx.event.EventHandler; -import javafx.fxml.FXML; -import javafx.fxml.Initializable; -import javafx.geometry.Insets; -import javafx.scene.control.Label; -import javafx.scene.control.Tooltip; -import javafx.scene.input.MouseEvent; -import javafx.scene.layout.*; -import org.kordamp.ikonli.javafx.FontIcon; - -import java.math.BigDecimal; -import java.net.URL; -import java.util.ResourceBundle; - -/** - * The controller class for the transition pane element that can be inserted into the simulator panes - */ -public class StatePaneController implements Initializable { - public VBox root; - public VBox transitionList; - public HBox toolbar; - public Label toolbarTitle; - public JFXRippler refreshRippler; - public JFXRippler expandTransition; - public FontIcon expandTransitionIcon; - public VBox delayChooser; - public JFXTextField delayTextField; - - private final SimpleBooleanProperty isTransitionExpanded = new SimpleBooleanProperty(false); - private final SimpleObjectProperty<BigDecimal> delay = new SimpleObjectProperty<>(BigDecimal.ZERO); - - private ObservableList<StatePresentation> statePresentations = FXCollections.observableArrayList(); - - @Override - public void initialize(URL location, ResourceBundle resources) { - initializeToolbar(); - initializeTransitionExpand(); - initializeDelayChooser(); - } - - /** - * Initializes the toolbar for the transition pane element. - * Sets the background of the toolbar and changes the title color. - * Also changes the look of the rippler effect. - */ - private void initializeToolbar() { - // Set the background of the toolbar - toolbar.setBackground(new Background(new BackgroundFill( - EnabledColor.getDefault().getStrokeColor(), - CornerRadii.EMPTY, - Insets.EMPTY))); - // Set the font color of elements in the toolbar - toolbarTitle.setTextFill(EnabledColor.getDefault().getTextColor()); - - refreshRippler.setMaskType(JFXRippler.RipplerMask.CIRCLE); - refreshRippler.setRipplerFill(EnabledColor.getDefault().getTextColor()); - - expandTransition.setMaskType(JFXRippler.RipplerMask.CIRCLE); - expandTransition.setRipplerFill(EnabledColor.getDefault().getTextColor()); - } - - /** - * Sets up listeners for the delay chooser. - * Listens for changes in text property and updates the textfield with a sanitized value (e.g. no letters in delay). - * Also listens for changes in focus, so there's always a value in the textfield, even if the user deleted the text. - * Adds tooltip for the textfield. - */ - private void initializeDelayChooser() { - delayChooser.setBackground(new Background(new BackgroundFill( - EnabledColor.getDefault().getLowestIntensity().getPaintColor(), - CornerRadii.EMPTY, - Insets.EMPTY - ))); - - delayChooser.setBorder(new Border(new BorderStroke( - EnabledColor.getDefault().getLowestIntensity().nextIntensity(2).getPaintColor(), - BorderStrokeStyle.SOLID, - CornerRadii.EMPTY, - new BorderWidths(0, 0, 1, 0) - ))); - - delayTextField.textProperty().addListener(((observable, oldValue, newValue) -> { - delayTextChanged(oldValue, newValue); - })); - - delayTextField.focusedProperty().addListener((observable, oldValue, newValue) -> { - // If the textfield loses focus and the user didn't enter anything - // show the value 0.0 - if(!newValue && delay.get().equals(BigDecimal.ZERO)) { - delayTextField.setText("0.0"); - } - }); - - Tooltip.install(delayTextField, new Tooltip("Enter delay to use for next transition")); - } - - /** - * Initializes the expand functionality that allows the user to show or hide the transitions. - * By default, the transitions are shown. - */ - private void initializeTransitionExpand() { - isTransitionExpanded.addListener((obs, oldVal, newVal) -> { - if(newVal) { - if(!root.getChildren().contains(delayChooser)) { - // Add the delay chooser just below the toolbar - root.getChildren().add(1, delayChooser); - } - showTransitions(); - expandTransitionIcon.setIconLiteral("gmi-expand-less"); - expandTransitionIcon.setIconSize(24); - } else { - root.getChildren().remove(delayChooser); - hideTransitions(); - expandTransitionIcon.setIconLiteral("gmi-expand-more"); - expandTransitionIcon.setIconSize(24); - } - }); - - isTransitionExpanded.set(true); - } - - protected void setTraceLog(ObservableList<StatePresentation> traceLog) { - statePresentations = traceLog; - } - - /** - * Removes all the transition view elements as to hide the transitions from the user - */ - private void hideTransitions() { - transitionList.getChildren().clear(); - } - - /** - * Shows the available transitions by inserting a {@link StatePresentation} for each transition - */ - private void showTransitions() { - transitionList.getChildren().addAll(statePresentations); - } - - /** - * Instantiates a StatePresentation for a Transition and adds it to the view - * @param statePresentation The state presentation that should be inserted into the view - */ - private void insertState(StatePresentation statePresentation) { - String title = "Not yet implemented"; // ToDo NIELS: Re-implement - transitionString(statePresentation); - - // Update the selected transition when mouse entered. - // Add the event to existing mouseEntered events - // e.g. StatePresentation already has mouseEntered functionality and we want to keep it - EventHandler mouseEntered = statePresentation.getOnMouseEntered(); - // statePresentation.setOnMouseEntered(event -> { - // SimulationController.setSelectedTransition(statePresentation.getController().getTransition()); - // mouseEntered.handle(event); - // }); - - EventHandler<? super MouseEvent> mouseExited = statePresentation.getOnMouseExited(); - statePresentation.setOnMouseExited(mouseExited); - - // Only insert the presentation into the view if the transitions are expanded - // Avoids inserting duplicate elements in the view (it's still added to the map) - if(isTransitionExpanded.get()) { - transitionList.getChildren().add(statePresentation); - } - } - - /** - * A helper method that returns a string representing a transition in the transition chooser - * @param transition The {@link Transition} to represent - * @return A string representing the transition - */ - private String transitionString(Transition transition) { - StringBuilder title = new StringBuilder(transition.getLabel()); - if(transition.getEdges() != null) { - for (Edge edge : transition.getEdges()) { - title.append(" ").append(edge.getId()); - } - } - return title.toString(); - } - - /** - * Method to be called when clicking on the expand rippler in the transition toolbar - */ - @FXML - private void expandTransitions() { - isTransitionExpanded.set(!isTransitionExpanded.get()); - } - - /** - * Sanitizes the input that the user inserts into the delay textfield. - * Checks if the text can be converted into a BigDecimal otherwise show the previous value. - * For example avoids users entering letters. - * @param oldValue The old value to show if the newvalue cannot be converted to a BigDecimal - * @param newValue The new value to show in the textfield - */ - @FXML - private void delayTextChanged(String oldValue, String newValue) { - // If the value is empty (the user deleted the value), assume that the value is 0.0 but do not update the text - if(newValue.isEmpty()) { - this.delay.set(BigDecimal.ZERO); - } else { - // Try to convert the new value into a BigDecimal - // Note that we don't setText here, as the new value is already shown in the textfield - try { - BigDecimal bd = new BigDecimal(newValue); - - // Checking the string for "-" instead of whether bd is negative is due to the case of -0.0 - // So checking the string is just simpler - if(newValue.contains("-")) { - throw new NumberFormatException(); - } - - this.delay.set(bd); - - } catch (NumberFormatException ex) { - // If the conversion was not possible, show the old value - this.delayTextField.setText(oldValue); - } - } - - } -} diff --git a/src/main/java/ecdar/presentations/StatePanePresentation.java b/src/main/java/ecdar/presentations/StatePanePresentation.java deleted file mode 100755 index 50734290..00000000 --- a/src/main/java/ecdar/presentations/StatePanePresentation.java +++ /dev/null @@ -1,19 +0,0 @@ -package ecdar.presentations; - -import ecdar.controllers.StatePaneController; -import javafx.scene.layout.*; - -/** - * The presentation class for the transition pane element that can be inserted into the simulator panes - */ -public class StatePanePresentation extends VBox { - final private StatePaneController controller; - - public StatePanePresentation() { - controller = new EcdarFXMLLoader().loadAndGetController("StatePanePresentation.fxml", this); - } - - public StatePaneController getController() { - return controller; - } -} diff --git a/src/main/java/ecdar/presentations/StatePresentation.java b/src/main/java/ecdar/presentations/StatePresentation.java index 42bbdd68..79877fd9 100755 --- a/src/main/java/ecdar/presentations/StatePresentation.java +++ b/src/main/java/ecdar/presentations/StatePresentation.java @@ -14,9 +14,8 @@ import java.util.function.BiConsumer; /** - * The presentation class for a transition view element. - * It represents a single transition and may be used by classes like {@see StatePaneController} - * to show a list of transitions + * The presentation class for a state view element. + * It represents a single state within the trace of the simulation */ public class StatePresentation extends AnchorPane { private final StateController controller; diff --git a/src/main/resources/ecdar/presentations/StatePanePresentation.fxml b/src/main/resources/ecdar/presentations/StatePanePresentation.fxml deleted file mode 100755 index 7ca0861c..00000000 --- a/src/main/resources/ecdar/presentations/StatePanePresentation.fxml +++ /dev/null @@ -1,51 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> - -<?import javafx.scene.control.*?> -<?import javafx.scene.layout.*?> - -<?import com.jfoenix.controls.JFXRippler?> -<?import org.kordamp.ikonli.javafx.FontIcon?> -<?import com.jfoenix.controls.JFXTextField?> -<?import javafx.geometry.Insets?> -<fx:root xmlns:fx="http://javafx.com/fxml/1" - xmlns="http://javafx.com/javafx/8.0.76-ea" - fx:controller="ecdar.controllers.StatePaneController" - fx:id="root" type="VBox" - AnchorPane.leftAnchor="0" AnchorPane.rightAnchor="0"> - <HBox fx:id="toolbar" alignment="CENTER"> - <padding> - <Insets topRightBottomLeft="10"/> - </padding> - <JFXRippler fx:id="expandTransition"> - <StackPane styleClass="responsive-icon-stack-pane-sizing" onMouseClicked="#expandTransitions"> - <FontIcon fx:id="expandTransitionIcon" iconLiteral="gmi-add" styleClass="icon-size-medium" - fill="white"/> - </StackPane> - </JFXRippler> - - <Region minWidth="24" maxWidth="24"/> - - <Label fx:id="toolbarTitle" styleClass="title" text="Transitions"/> - - <Region HBox.hgrow="ALWAYS"/> - - <JFXRippler fx:id="refreshRippler"> - <StackPane styleClass="responsive-icon-stack-pane-sizing" onMouseClicked="#restartSimulation"> - <FontIcon iconLiteral="gmi-refresh" styleClass="icon-size-medium" fill="white"/> - </StackPane> - </JFXRippler> - </HBox> - <VBox fx:id="delayChooser"> - <padding> - <Insets top="10" right="20" bottom="10" left="20"/> - </padding> - <Label text="Delay" styleClass="subhead"/> - <JFXTextField fx:id="delayTextField" - text="0.0" - styleClass="subhead"/> - </VBox> - - <VBox fx:id="transitionList"> - <!-- Transitions goes here --> - </VBox> -</fx:root>