aboutsummaryrefslogtreecommitdiffstats
path: root/src/jcgp/gui
diff options
context:
space:
mode:
Diffstat (limited to 'src/jcgp/gui')
-rw-r--r--src/jcgp/gui/GUI.java10
-rw-r--r--src/jcgp/gui/constants/Constants.java102
-rw-r--r--src/jcgp/gui/constants/Position.java103
-rw-r--r--src/jcgp/gui/handlers/InputHandlers.java56
-rw-r--r--src/jcgp/gui/handlers/NodeHandlers.java164
-rw-r--r--src/jcgp/gui/handlers/OutputHandlers.java84
-rw-r--r--src/jcgp/gui/handlers/Target.java70
-rw-r--r--src/jcgp/gui/population/ChromosomePane.java195
-rw-r--r--src/jcgp/gui/population/FunctionSelector.java4
-rw-r--r--src/jcgp/gui/population/GUIConnection.java32
-rw-r--r--src/jcgp/gui/population/GUIGene.java195
-rw-r--r--src/jcgp/gui/population/GUIInput.java241
-rw-r--r--src/jcgp/gui/population/GUIMutable.java20
-rw-r--r--src/jcgp/gui/population/GUINode.java494
-rw-r--r--src/jcgp/gui/population/GUIOutput.java343
-rw-r--r--src/jcgp/gui/population/PopulationPane.java18
16 files changed, 1124 insertions, 1007 deletions
diff --git a/src/jcgp/gui/GUI.java b/src/jcgp/gui/GUI.java
index 79678bc..d99bfbc 100644
--- a/src/jcgp/gui/GUI.java
+++ b/src/jcgp/gui/GUI.java
@@ -13,7 +13,6 @@ import javafx.stage.Stage;
import javafx.stage.WindowEvent;
import jcgp.JCGP;
import jcgp.backend.modules.problem.TestCaseProblem.TestCase;
-import jcgp.backend.resources.Resources;
import jcgp.gui.console.ConsolePane;
import jcgp.gui.dragresize.HorizontalDragResize;
import jcgp.gui.dragresize.VerticalDragResize;
@@ -40,7 +39,7 @@ import jcgp.gui.settings.SettingsPane;
* until it is interrupted by the main JavaFX thread.
* <br>
* This service also handles flushing the console in a thread safe way. This
- * is done by synchronising the {@code nextGeneration()} and {@code flush()}
+ * is done by synchronizing the {@code nextGeneration()} and {@code flush()}
* method calls on a lock object.
*
* @author Eduardo Pedroni
@@ -70,8 +69,6 @@ public class GUI extends Application {
*/
private final JCGP jcgp;
- public static Resources resources;
-
/**
* Start JCGP with the user interface.
@@ -90,7 +87,6 @@ public class GUI extends Application {
*/
public GUI() {
jcgp = new JCGP();
- resources = jcgp.getResources();
functionSelector = new FunctionSelector(jcgp.getResources().getFunctionSet());
/*
@@ -331,10 +327,12 @@ public class GUI extends Application {
if (settingsPane.isResetRequired()) {
reset();
}
+ populationPane.unlockOutputs();
jcgp.nextGeneration();
console.flush();
populationPane.updateGenes();
+ populationPane.relockOutputs();
settingsPane.revalidateParameters();
settingsPane.updateControls(false, jcgp.isFinished());
}
@@ -378,11 +376,13 @@ public class GUI extends Application {
*/
private void runningMode(boolean value) {
if (value) {
+ populationPane.unlockOutputs();
if (settingsPane.isResetRequired()) {
reset();
}
} else {
populationPane.updateGenes();
+ populationPane.relockOutputs();
settingsPane.revalidateParameters();
}
populationPane.setDisable(value);
diff --git a/src/jcgp/gui/constants/Constants.java b/src/jcgp/gui/constants/Constants.java
index 509d982..350f8b1 100644
--- a/src/jcgp/gui/constants/Constants.java
+++ b/src/jcgp/gui/constants/Constants.java
@@ -1,107 +1,55 @@
package jcgp.gui.constants;
-import javafx.scene.paint.Paint;
-
/**
* Holds the constants used in the GUI.
*
* @author Eduardo Pedroni
*
*/
-public final class Constants {
+public abstract class Constants {
+ /* Colours */
/**
- * Private constructor to prevent instantiation.
- */
- private Constants(){}
-
- /*---------------------------------------------------------------------------------------------------
- * Colour Strings
- *-------------------------------------------------------------------------------------------------*/
- /**
- * A {@code String} containing the colour used for representing neutrality.
+ * A string containing the hexadecimal colour used for representing neutrality.
*/
public static final String NEUTRAL_COLOUR = "#FFFFFF";
/**
- * A {@code String} containing the hexadecimal colour used for representing a hard highlight.
- * A "hard" select, for instance, happens when an output path is locked on the chromosome pane.
+ * A string containing the hexadecimal colour used for representing a hard highlight.
+ * A "hard" select, for instance, happens when an output path is locked on the chromosome
+ * pane.
*/
public static final String HARD_HIGHLIGHT_COLOUR = "#5496FF";
/**
- * A {@code String} containing the hexadecimal colour used for a medium highlight.
- * One example of such a selection is the colour applied to a node when it is hovered over.
+ * A string containing the hexadecimal colour used for a medium highlight.
+ * One example of such a selection is the colour applied to a node
+ * when it is hovered over.
*/
public static final String MEDIUM_HIGHLIGHT_COLOUR = "#75BAFF";
/**
- * A {@code String} containing the hexadecimal colour used for a soft highlight.
+ * A string containing the hexadecimal colour used for a soft highlight.
* When hovering over a node, its connections are soft-selected.
*/
public static final String SOFT_HIGHLIGHT_COLOUR = "#C7DFFF";
/**
- * A {@code String} containing the hexadecimal colour used for representing a good selection.
+ * A string containing the hexadecimal colour used for representing a good selection.
* Ideally a shade of green, used for instance when a manual connection is valid.
*/
public static final String GOOD_SELECTION_COLOUR = "#38C25B";
/**
- * A {@code String} containing the hexadecimal colour used for representing a neutral selection.
- * Ideally a shade of yellow, used for instance when a manual connection is already the current connection.
+ * A string containing the hexadecimal colour used for representing a neutral selection.
+ * Ideally a shade of yellow, used for instance when a manual connection is already the
+ * current connection.
*/
public static final String NEUTRAL_SELECTION_COLOUR = "#FFEF73";
/**
- * A {@code String} containing the hexadecimal colour used for representing a bad selection.
+ * A string containing the hexadecimal colour used for representing a bad selection.
* Ideally a shade of red, use for instance when a manual connection is not valid.
*/
public static final String BAD_SELECTION_COLOUR = "#FF5C5C";
- /**
- * A {@code String} containing the hexadecimal colour used for the gene sockets.
- */
- public static final String SOCKET_COLOUR = "#FFFFFF";
- /*---------------------------------------------------------------------------------------------------
- * Colour Paints
- *-------------------------------------------------------------------------------------------------*/
- /**
- * A {@code Paint} containing the colour used for representing neutrality.
- */
- public static final Paint NEUTRAL_PAINT = Paint.valueOf(NEUTRAL_COLOUR);
- /**
- * A {@code Paint} containing the colour used for representing a hard highlight.
- * A "hard" select, for instance, happens when an output path is locked on the chromosome pane.
- */
- public static final Paint HARD_HIGHLIGHT_PAINT = Paint.valueOf(HARD_HIGHLIGHT_COLOUR);
- /**
- * A {@code Paint} containing the colour used for a medium highlight.
- * One example of such a selection is the colour applied to a node when it is hovered over.
- */
- public static final Paint MEDIUM_HIGHLIGHT_PAINT = Paint.valueOf(MEDIUM_HIGHLIGHT_COLOUR);
- /**
- * A {@code Paint} containing the colour used for a soft highlight.
- * When hovering over a node, its connections are soft-selected.
- */
- public static final Paint SOFT_HIGHLIGHT_PAINT = Paint.valueOf(SOFT_HIGHLIGHT_COLOUR);
- /**
- * A {@code Paint} containing the colour used for representing a good selection.
- * Ideally a shade of green, used for instance when a manual connection is valid.
- */
- public static final Paint GOOD_SELECTION_PAINT = Paint.valueOf(GOOD_SELECTION_COLOUR);
- /**
- * A {@code Paint} containing the colour used for representing a neutral selection.
- * Ideally a shade of yellow, used for instance when a manual connection is already the current connection.
- */
- public static final Paint NEUTRAL_SELECTION_PAINT = Paint.valueOf(NEUTRAL_SELECTION_COLOUR);
- /**
- * A {@code Paint} containing the colour used for representing a bad selection.
- * Ideally a shade of red, use for instance when a manual connection is not valid.
- */
- public static final Paint BAD_SELECTION_PAINT = Paint.valueOf(BAD_SELECTION_COLOUR);
- /**
- * A {@code Paint} containing the colour used for the gene sockets.
- */
- public static final Paint SOCKET_PAINT = Paint.valueOf(SOCKET_COLOUR);
- /*---------------------------------------------------------------------------------------------------
- * Sizes and distances
- *-------------------------------------------------------------------------------------------------*/
+
+ /* Sizes and distances */
/**
* The width or height of the area that can be clicked on
* to drag-resize a pane.
@@ -126,15 +74,13 @@ public final class Constants {
*/
public static final double SPACING = 15;
/**
- * The margin between the genes and the edge of the chromosome pane.
- */
- public static final double CHROMOSOME_PANE_MARGIN = 10;
- /**
- * The angle across which the node's sockets are evenly distributed.
+ * The angle across which the node's sockets are evently distributed.
*/
public static final double THETA = Math.PI / 1.4;
/**
- * The radius of the connection sockets, calculated as a function of NODE_RADIUS.
+ * The radius of the connection sockets, calculated as a function of
+ * NODE_RADIUS.
+ *
*/
public static final double SOCKET_RADIUS = Math.sqrt(NODE_RADIUS) / 1.8;
/**
@@ -142,10 +88,10 @@ public final class Constants {
*/
public static final double NODE_TEXT = NODE_RADIUS / 2.5;
- /*---------------------------------------------------------------------------------------------------
- * CSS Styles
+
+ /* CSS Styles
* TODO extract to stylesheet?
- *-------------------------------------------------------------------------------------------------*/
+ */
/**
* The basic style of text boxes used in parameters.
*/
diff --git a/src/jcgp/gui/constants/Position.java b/src/jcgp/gui/constants/Position.java
deleted file mode 100644
index 6d4e02b..0000000
--- a/src/jcgp/gui/constants/Position.java
+++ /dev/null
@@ -1,103 +0,0 @@
-package jcgp.gui.constants;
-
-import javafx.scene.shape.Circle;
-import javafx.scene.shape.Line;
-import jcgp.gui.GUI;
-import jcgp.gui.population.GUIGene;
-import jcgp.gui.population.GUIInput;
-import jcgp.gui.population.GUINode;
-import jcgp.gui.population.GUIOutput;
-
-/**
- * Abstracts the task of positioning GUI components.
- * <br>
- * Do not instantiate this class; instead, use the {@code public static} methods provided.
- *
- * @author Eduardo Pedroni
- *
- */
-public final class Position {
-
- /**
- * Private constructor to prevent instantiation.
- */
- private Position() {}
-
- /**
- * Sets the X and Y layouts of the specified input to the appropriate values, according to its index.
- *
- * @param input the {@code GUIInput} instance to relocate.
- */
- public static void place(GUIInput input) {
- // inputs are the first column, so we only worry about the margin and their index
- input.relocate(Constants.CHROMOSOME_PANE_MARGIN,
- input.getInput().getIndex() * (2 * Constants.NODE_RADIUS + Constants.SPACING) + Constants.CHROMOSOME_PANE_MARGIN);
- }
-
- /**
- * Sets the X and Y layouts of the specified node to the appropriate values, according to its row and column values.
- * This also connects the start of every line with its respective socket. Therefore, this method should be called at least
- * once when the {@code GUINode} is instantiated.
- *
- * @param node the {@code GUINode} instance to relocate.
- */
- public static void place(GUINode node) {
- // calculate x and y offsets, in relation to the layout origin
- double xOffset = (node.getNode().getColumn() + 1) * (2 * Constants.NODE_RADIUS + Constants.SPACING) + Constants.CHROMOSOME_PANE_MARGIN;
- double yOffset = node.getNode().getRow() * (2 * Constants.NODE_RADIUS + Constants.SPACING) + Constants.CHROMOSOME_PANE_MARGIN;
-
- // move node
- node.relocate(xOffset, yOffset);
-
- // use the offset and the socket positions to connect the lines
- for (int i = 0; i < GUI.resources.arity(); i++) {
- node.getLines()[i].setStartX(node.getSocket(i).getCenterX() + xOffset + Constants.NODE_RADIUS + Constants.SOCKET_RADIUS);
- node.getLines()[i].setStartY(node.getSocket(i).getCenterY() + yOffset + Constants.NODE_RADIUS);
- }
- }
-
- /**
- * Sets the X and Y layouts of the specified output to the appropriate values, according to its index.
- * This also connects the start of the output's single line to its single input socket.Therefore,
- * this method should be called at least once when the {@code GUIOutput} is instantiated.
- *
- * @param output the {@code GUIOutput} instance to relocate.
- */
- public static void place(GUIOutput output) {
- // the output's position is a function of the number of columns and its own index
- output.relocate(((GUI.resources.columns() + 1) * (2 * Constants.NODE_RADIUS + Constants.SPACING)) + Constants.CHROMOSOME_PANE_MARGIN,
- output.getOutput().getIndex() * (2 * Constants.NODE_RADIUS + Constants.SPACING) + Constants.CHROMOSOME_PANE_MARGIN);
- output.getLines()[0].setStartX(output.getLayoutX() - Constants.NODE_RADIUS);
- output.getLines()[0].setStartY(output.getLayoutY());
- }
-
- /**
- * Connects the end of a specified line to the specified gene.
- *
- * @param line the line to connect.
- * @param target the target gene to connect to.
- */
- public static void connect(Line line, GUIGene target) {
- // set line ends based on the layout position of the target
- line.setEndX(target.getLayoutX() + Constants.NODE_RADIUS);
- line.setEndY(target.getLayoutY());
- }
-
- /**
- * Relocates the given socket to the appropriate position given the
- * socket's index.
- *
- * @param index the socket index.
- * @param socket the {@code Circle} instance to relocate.
- */
- public static void placeSocket(int index, Circle socket) {
- // calculate the angle with respect to the x-axis
- double angle = (((index + 1) / ((double) (GUI.resources.arity() + 1))) * Constants.THETA) - (Constants.THETA / 2);
- // convert to cartesian form
- double xPos = -Math.cos(angle) * Constants.NODE_RADIUS;
- double yPos = Math.sin(angle) * Constants.NODE_RADIUS;
- // set centre
- socket.setCenterX(xPos);
- socket.setCenterY(yPos);
- }
-}
diff --git a/src/jcgp/gui/handlers/InputHandlers.java b/src/jcgp/gui/handlers/InputHandlers.java
deleted file mode 100644
index cc677eb..0000000
--- a/src/jcgp/gui/handlers/InputHandlers.java
+++ /dev/null
@@ -1,56 +0,0 @@
-package jcgp.gui.handlers;
-
-import javafx.event.EventHandler;
-import javafx.scene.input.MouseEvent;
-import jcgp.gui.population.GUIGene.GUIGeneState;
-import jcgp.gui.population.GUIInput;
-
-/**
- * Holds the handlers that define the behaviour of {@code GUIInput}.
- * <br><br>
- * The handlers are instantiated here statically and added to {@code GUIInput}
- * instances using {@code InputHandlers.addHandlers(...)}. This guarantees that
- * all inputs behave the same way without instantiating a new set of handlers for
- * each input instance.
- *
- * @author Eduardo Pedroni
- *
- */
-public final class InputHandlers {
-
- /**
- * Private constructor to prevent instantiation.
- */
- private InputHandlers() {}
-
- /**
- * Inputs don't do much; set state to hover when mouse enters.
- */
- private static EventHandler<MouseEvent> mouseEnteredHandler = new EventHandler<MouseEvent>() {
- @Override
- public void handle(MouseEvent event) {
- ((GUIInput) event.getSource()).setState(GUIGeneState.HOVER);
- }
- };
-
- /**
- * Inputs don't do much; set state to neutral when mouse exits.
- */
- private static EventHandler<MouseEvent> mouseExitedHandler = new EventHandler<MouseEvent>() {
- @Override
- public void handle(MouseEvent event) {
- ((GUIInput) event.getSource()).setState(GUIGeneState.NEUTRAL);
- }
- };
-
- /**
- * Adds all handlers to the specified input.
- *
- * @param input the {@code GUIInput} to which the handlers will be added.
- */
- public static void addHandlers(GUIInput input) {
- input.addEventHandler(MouseEvent.MOUSE_ENTERED, mouseEnteredHandler);
- input.addEventHandler(MouseEvent.MOUSE_EXITED, mouseExitedHandler);
- }
-
-}
diff --git a/src/jcgp/gui/handlers/NodeHandlers.java b/src/jcgp/gui/handlers/NodeHandlers.java
deleted file mode 100644
index b413a62..0000000
--- a/src/jcgp/gui/handlers/NodeHandlers.java
+++ /dev/null
@@ -1,164 +0,0 @@
-package jcgp.gui.handlers;
-
-import javafx.event.EventHandler;
-import javafx.scene.input.MouseDragEvent;
-import javafx.scene.input.MouseEvent;
-import javafx.scene.shape.Circle;
-import javafx.scene.shape.Line;
-import jcgp.backend.population.Gene;
-import jcgp.gui.GUI;
-import jcgp.gui.constants.Position;
-import jcgp.gui.population.ChromosomePane;
-import jcgp.gui.population.GUIConnection;
-import jcgp.gui.population.GUIGene;
-import jcgp.gui.population.GUIGene.GUIGeneState;
-import jcgp.gui.population.GUINode;
-
-/**
- * Holds the handlers that define the behaviour of {@code GUINode}.
- * <br><br>
- * The handlers are instantiated here statically and added to {@code GUINode}
- * instances using {@code NodeHandlers.addHandlers(...)}. This guarantees that
- * all nodes behave the same way without instantiating a new set of handlers for
- * each node instance.
- *
- * @author Eduardo Pedroni
- *
- */
-public final class NodeHandlers {
-
- /**
- * Private constructor to prevent instantiation.
- */
- private NodeHandlers() {}
-
- /**
- * Set the node to {@code GUIGeneState.HOVER} state, and set its immediate connections to {@code GUIGeneState.EXTENDED_HOVER}.
- */
- private static EventHandler<MouseEvent> mouseEnteredHandler = new EventHandler<MouseEvent>() {
- @Override
- public void handle(MouseEvent event) {
- // acquire the source, we can safely cast it to GUINode
- GUINode source = (GUINode) event.getSource();
-
- source.setState(GUIGeneState.HOVER);
- for (int i = 0; i < GUI.resources.arity(); i++) {
- ((GUIGene) ((Gene) source.getNode().getConnection(i)).getGUIObject()).setState(GUIGeneState.EXTENDED_HOVER);
- }
- }
- };
-
- /**
- * Set the node and its immediate connections to {@code GUIGeneState.NEUTRAL} state.
- */
- private static EventHandler<MouseEvent> mouseExitedHandler = new EventHandler<MouseEvent>() {
- @Override
- public void handle(MouseEvent event) {
- // acquire the source, we can safely cast it to GUINode
- GUINode source = (GUINode) event.getSource();
-
- if (Target.getSourceMutable() != source) {
- source.setState(GUIGeneState.NEUTRAL);
- for (int i = 0; i < GUI.resources.arity(); i++) {
- ((GUIGene) ((Gene) source.getNode().getConnection(i)).getGUIObject()).setState(GUIGeneState.NEUTRAL);
- }
- }
- }
- };
-
- private static EventHandler<MouseEvent> socketDragDetected = new EventHandler<MouseEvent>() {
- @Override
- public void handle(MouseEvent event) {
- // it's safe to assume that the source is the socket
- ((GUINode) ((Circle) event.getSource()).getParent()).startFullDrag();
- }
- };
-
- private static EventHandler<MouseEvent> socketMousePressedHandler = new EventHandler<MouseEvent>() {
- @Override
- public void handle(MouseEvent event) {
- // it's safe to assume that the source is the socket
- Target.start((Circle) event.getSource());
- }
- };
-
- private static EventHandler<MouseEvent> socketMouseDraggedHandler = new EventHandler<MouseEvent>() {
- @Override
- public void handle(MouseEvent event) {
- // this can only happen after a press, so we know Target is up-to-date
- if (!Target.isProspecting()) {
- GUINode node = (GUINode) Target.getSourceMutable();
- Line line = Target.getConnectionLine();
- line.setEndX(event.getX() + node.getLayoutX());
- line.setEndY(event.getY() + node.getLayoutY());
- }
-
- }
- };
-
- private static EventHandler<MouseEvent> socketMouseReleasedHandler = new EventHandler<MouseEvent>() {
- @Override
- public void handle(MouseEvent event) {
-
- GUINode node = (GUINode) ((Circle) event.getSource()).getParent();
- int connectionId = Integer.valueOf(((Circle) event.getSource()).getId());
-
- Position.connect(node.getLines()[connectionId], (GUIGene) ((Gene) node.getNode().getConnection(connectionId)).getGUIObject());
- }
- };
-
- private static EventHandler<MouseDragEvent> dragEnteredHandler = new EventHandler<MouseDragEvent>() {
- @Override
- public void handle(MouseDragEvent event) {
- // acquire the source, we can safely cast it to GUINode
- GUINode source = (GUINode) event.getSource();
- if (Target.getCurrentConnection() == source) {
- source.setState(GUIGeneState.NEUTRAL_TARGET);
- // we are now prospecting
- Target.setProspecting(true);
- Position.connect(Target.getConnectionLine(), source);
- } else if (ChromosomePane.isAllowed(Target.getSourceMutable(), (GUIConnection) source)) {
- source.setState(GUIGeneState.GOOD_TARGET);
- // we are now prospecting
- Target.setProspecting(true);
- Position.connect(Target.getConnectionLine(), source);
- } else {
- source.setState(GUIGeneState.BAD_TARGET);
- }
- }
- };
-
- private static EventHandler<MouseDragEvent> dragExitedHandler = new EventHandler<MouseDragEvent>() {
- @Override
- public void handle(MouseDragEvent event) {
- // acquire the source, we can safely cast it to GUINode
- GUINode source = (GUINode) event.getSource();
- source.setState(GUIGeneState.NEUTRAL);
-
- // no longer prospecting
- Target.setProspecting(false);
- }
- };
-
- /**
- * Adds all handlers to the specified node.
- *
- * @param node the {@code GUINode} to which the handlers will be added.
- */
- public static void addHandlers(GUINode node) {
- node.addEventHandler(MouseEvent.MOUSE_ENTERED, mouseEnteredHandler);
- node.addEventHandler(MouseEvent.MOUSE_EXITED, mouseExitedHandler);
-
- node.addEventHandler(MouseDragEvent.MOUSE_DRAG_ENTERED, dragEnteredHandler);
- node.addEventHandler(MouseDragEvent.MOUSE_DRAG_EXITED, dragExitedHandler);
-
- Circle[] sockets = node.getSockets();
- for (int s = 0; s < sockets.length; s++) {
-
- sockets[s].addEventFilter(MouseEvent.DRAG_DETECTED, socketDragDetected);
- sockets[s].addEventHandler(MouseEvent.MOUSE_PRESSED, socketMousePressedHandler);
- sockets[s].addEventHandler(MouseEvent.MOUSE_DRAGGED, socketMouseDraggedHandler);
- sockets[s].addEventHandler(MouseEvent.MOUSE_RELEASED, socketMouseReleasedHandler);
- }
- }
-}
diff --git a/src/jcgp/gui/handlers/OutputHandlers.java b/src/jcgp/gui/handlers/OutputHandlers.java
deleted file mode 100644
index b89d746..0000000
--- a/src/jcgp/gui/handlers/OutputHandlers.java
+++ /dev/null
@@ -1,84 +0,0 @@
-package jcgp.gui.handlers;
-
-import javafx.event.EventHandler;
-import javafx.scene.input.MouseEvent;
-import jcgp.backend.population.Gene;
-import jcgp.gui.population.GUIConnection;
-import jcgp.gui.population.GUIGene.GUIGeneState;
-import jcgp.gui.population.GUIOutput;
-
-/**
- * Holds the handlers that define the behaviour of {@code GUIOutput}.
- * <br><br>
- * The handlers are instantiated here statically and added to {@code GUIOutput}
- * instances using {@code OutputHandlers.addHandlers(...)}. This guarantees that
- * all outputs behave the same way without instantiating a new set of handlers for
- * each output instance.
- *
- * @author Eduardo Pedroni
- *
- */
-public final class OutputHandlers {
-
- /**
- * Private constructor to prevent instantiation.
- */
- private OutputHandlers() {}
-
- /**
- * Set the output to {@code GUIGeneState.HOVER} state, and recursively set its active genes
- * to {@code GUIGeneState.ACTIVE_HOVER}.
- */
- private static EventHandler<MouseEvent> mouseEnteredHandler = new EventHandler<MouseEvent>() {
- @Override
- public void handle(MouseEvent event) {
- // acquire the source, we can safely cast it to GUIOutput
- GUIOutput source = (GUIOutput) event.getSource();
-
- source.setState(GUIGeneState.HOVER);
- ((GUIConnection) ((Gene) source.getOutput().getSource()).getGUIObject()).setStateRecursively(GUIGeneState.ACTIVE_HOVER);
- }
- };
-
- /**
- * Set the output and all of its active genes to {@code GUIGeneState.NEUTRAL} state.
- */
- private static EventHandler<MouseEvent> mouseExitedHandler = new EventHandler<MouseEvent>() {
- @Override
- public void handle(MouseEvent event) {
- // acquire the source, we can safely cast it to GUIOutput
- GUIOutput source = (GUIOutput) event.getSource();
-
- source.setState(GUIGeneState.NEUTRAL);
- ((GUIConnection) ((Gene) source.getOutput().getSource()).getGUIObject()).setStateRecursively(GUIGeneState.NEUTRAL);
- }
- };
-
- /**
- * If the output is locked, unlock it and all of its associated genes recursively.
- * If it is unlocked, lock it and its active genes.
- */
- private static EventHandler<MouseEvent> mouseClickHandler = new EventHandler<MouseEvent>() {
- @Override
- public void handle(MouseEvent event) {
- // acquire the source, we can safely cast it to GUIOutput
- GUIOutput source = (GUIOutput) event.getSource();
-
- boolean lock = !source.isLocked();
- source.setLock(lock);
- ((GUIConnection) ((Gene) source.getOutput().getSource()).getGUIObject()).setLockRecursively(lock);
- }
- };
-
- /**
- * Adds all handlers to the specified output.
- *
- * @param output the {@code GUIOutput} to which the handlers will be added.
- */
- public static void addHandlers(GUIOutput output) {
- output.addEventHandler(MouseEvent.MOUSE_ENTERED, mouseEnteredHandler);
- output.addEventHandler(MouseEvent.MOUSE_EXITED, mouseExitedHandler);
- output.addEventHandler(MouseEvent.MOUSE_CLICKED, mouseClickHandler);
- }
-
-}
diff --git a/src/jcgp/gui/handlers/Target.java b/src/jcgp/gui/handlers/Target.java
deleted file mode 100644
index b050663..0000000
--- a/src/jcgp/gui/handlers/Target.java
+++ /dev/null
@@ -1,70 +0,0 @@
-package jcgp.gui.handlers;
-
-import javafx.scene.shape.Circle;
-import javafx.scene.shape.Line;
-import jcgp.gui.population.GUIConnection;
-import jcgp.gui.population.GUIMutable;
-
-/**
- * @author Eduardo Pedroni
- *
- */
-public final class Target {
-
- /**
- * Private constructor to prevent instantiation.
- */
- private Target() {}
-
- private static GUIConnection targetConnection;
- private static GUIMutable sourceMutable;
- private static int connectionIndex;
- private static Line connectionLine;
- private static Circle sourceSocket;
- private static boolean prospecting = false;
-
- public static void start(Circle newSocket) {
- // store new socket
- sourceSocket = newSocket;
- // derive the rest of the information from it
- connectionIndex = Integer.valueOf(newSocket.getId());
- sourceMutable = (GUIMutable) newSocket.getParent();
- connectionLine = sourceMutable.getLines()[connectionIndex];
- }
-
- public static GUIMutable getSourceMutable() {
- return sourceMutable;
- }
-
- public static int getConnectionIndex() {
- return connectionIndex;
- }
-
- public static Line getConnectionLine() {
- return connectionLine;
- }
-
- public static Circle getSourceSocket() {
- return sourceSocket;
- }
-
- public static GUIConnection getTarget() {
- return targetConnection;
- }
-
- public static GUIConnection getCurrentConnection() {
- return sourceMutable.getConnections()[connectionIndex];
- }
-
- public static void setProspecting(boolean value) {
- prospecting = value;
- }
-
- public static boolean isProspecting() {
- return prospecting;
- }
-
- public static void setTarget(GUIConnection newTarget) {
- targetConnection = newTarget;
- }
-}
diff --git a/src/jcgp/gui/population/ChromosomePane.java b/src/jcgp/gui/population/ChromosomePane.java
index a87a054..3546011 100644
--- a/src/jcgp/gui/population/ChromosomePane.java
+++ b/src/jcgp/gui/population/ChromosomePane.java
@@ -6,13 +6,17 @@ import javafx.scene.control.ScrollPane;
import javafx.scene.layout.Pane;
import javafx.scene.shape.Line;
import jcgp.backend.population.Chromosome;
+import jcgp.backend.population.Connection;
+import jcgp.backend.population.Input;
import jcgp.backend.population.Node;
+import jcgp.backend.resources.Resources;
import jcgp.gui.GUI;
+import jcgp.gui.constants.Constants;
/**
* This extension of {@code ScrollPane} contains a series of
* nodes, inputs and outputs spread across a grid. It also contains
- * all of the connection lines laid over the nodes, inputs and outputs.
+ * all of the connection lines overlaid over the nodes, inputs and outputs.
*
*
* @author Eduardo Pedroni
@@ -20,121 +24,164 @@ import jcgp.gui.GUI;
*/
public class ChromosomePane extends ScrollPane {
- private GUIInput[] guiInputs;
private GUINode[][] guiNodes;
+ private GUIInput[] guiInputs;
private GUIOutput[] guiOutputs;
-
+
private Pane content;
-
+
+ private ArrayList<Line> connectionLines;
+ private ArrayList<GUIOutput> relock = new ArrayList<GUIOutput>();
+
+ private int rows, columns;
+
+ private Object[] testInputs;
+
private boolean target = false;
-
- public ChromosomePane(Chromosome chromosome) {
+ private PopulationPane parent;
+
+ public ChromosomePane(Chromosome chromosome, GUI gui, PopulationPane parent) {
super();
-
- ArrayList<Line> connectionLines = new ArrayList<Line>();
-
- int rows = GUI.resources.rows();
- int columns = GUI.resources.columns();
-
+
+ final Resources resources = gui.getExperiment().getResources();
+ this.parent = parent;
+
+ rows = resources.rows();
+ columns = resources.columns();
+
+ connectionLines = new ArrayList<Line>();
+
content = new Pane();
content.setId("content pane for genes");
-
- /*
- * inputs
- */
- guiInputs = new GUIInput[GUI.resources.inputs()];
+
+ // generate the GUIGenes
+ // inputs
+ guiInputs = new GUIInput[resources.inputs()];
for (int i = 0; i < guiInputs.length; i++) {
- guiInputs[i] = new GUIInput(chromosome.getInput(i));
+ // make the GUI elements
+ guiInputs[i] = new GUIInput(this, chromosome.getInput(i));
+ content.getChildren().addAll(guiInputs[i]);
}
- // add inputs to content pane
- content.getChildren().addAll(guiInputs);
-
- /*
- * nodes
- */
+ // nodes
guiNodes = new GUINode[rows][columns];
- for (int c = 0; c < columns; c++) {
- for (int r = 0; r < rows; r++) {
+ double angle, xPos, yPos;
+ for (int r = 0; r < rows; r++) {
+ for (int c = 0; c < columns; c++) {
// make the connection lines
- Line lines[] = new Line[GUI.resources.arity()];
+ Line lines[] = new Line[resources.arity()];
for (int l = 0; l < lines.length; l++) {
- lines[l] = new Line();
+ angle = ((((double) (l + 1)) / ((double) (lines.length + 1))) * Constants.THETA) - (Constants.THETA / 2);
+ xPos = (-Math.cos(angle) * Constants.NODE_RADIUS) + (((c + 1) * (2 * Constants.NODE_RADIUS + Constants.SPACING)) + Constants.NODE_RADIUS);
+ yPos = (Math.sin(angle) * Constants.NODE_RADIUS) + ((r * (2 * Constants.NODE_RADIUS + Constants.SPACING)) + Constants.NODE_RADIUS);
+
+ lines[l] = new Line(xPos, yPos, xPos, yPos);
lines[l].setMouseTransparent(true);
lines[l].setVisible(false);
connectionLines.add(lines[l]);
}
- // make the GUI element
- guiNodes[r][c] = new GUINode(chromosome.getNode(r, c), lines);
- // add node to content pane
- content.getChildren().add(guiNodes[r][c]);
+ // make the GUI elements
+ guiNodes[r][c] = new GUINode(this, chromosome.getNode(r, c), lines, gui);
}
+ content.getChildren().addAll(guiNodes[r]);
}
-
- /*
- * outputs
- */
- guiOutputs = new GUIOutput[GUI.resources.outputs()];
+ // outputs
+ guiOutputs = new GUIOutput[resources.outputs()];
for (int i = 0; i < guiOutputs.length; i++) {
- // make the connection line
- Line line = new Line();
- line.setVisible(false);
+ xPos = ((resources.columns() + 1) * (2 * Constants.NODE_RADIUS + Constants.SPACING));
+ yPos = (chromosome.getOutput(i).getIndex() * (2 * Constants.NODE_RADIUS + Constants.SPACING)) + Constants.NODE_RADIUS;
+ // make the line
+ Line line = new Line(xPos, yPos, xPos, yPos);
line.setMouseTransparent(true);
+ line.setVisible(false);
connectionLines.add(line);
- // make the GUI element
- guiOutputs[i] = new GUIOutput(chromosome.getOutput(i), line);
+ // make the GUI elements
+ guiOutputs[i] = new GUIOutput(this, chromosome.getOutput(i), line, gui);
+ content.getChildren().addAll(guiOutputs[i]);
}
- // add outputs to content pane
- content.getChildren().addAll(guiOutputs);
- // add lines to the pane on top of genes
content.getChildren().addAll(connectionLines);
-
- setPrefWidth(620);
+ setPrefWidth(620);
setContent(content);
}
-
+
+ protected GUIGene getGuiGene(Connection gene) {
+ if (gene instanceof Input) {
+ return guiInputs[((Input) gene).getIndex()];
+ } else if (gene instanceof Node) {
+ return guiNodes[((Node) gene).getRow()][((Node) gene).getColumn()];
+ } else {
+ // something bad happened!
+ throw new ClassCastException();
+ }
+ }
+
protected boolean isTarget() {
return target;
}
-
+
protected void setTarget(boolean newValue) {
target = newValue;
}
-
+
public void updateGenes(Chromosome chr) {
- for (int r = 0; r < GUI.resources.rows(); r++) {
- for (int c = 0; c < GUI.resources.columns(); c++) {
+ for (int r = 0; r < rows; r++) {
+ for (int c = 0; c < columns; c++) {
guiNodes[r][c].setNode(chr.getNode(r, c));
+ guiNodes[r][c].updateLines();
+ guiNodes[r][c].updateText();
}
}
for (int i = 0; i < guiOutputs.length; i++) {
guiOutputs[i].setOutput(chr.getOutput(i));
+ guiOutputs[i].updateLines();
+ }
+ if (isEvaluating()) {
+ setInputs(testInputs);
+ }
+ }
+
+ public void unlockOutputs() {
+ relock.clear();
+ for (int i = 0; i < guiOutputs.length; i++) {
+ if (guiOutputs[i].isLocked()) {
+ guiOutputs[i].unlock();
+ relock.add(guiOutputs[i]);
+ }
+ }
+ }
+
+ public void relockOutputs() {
+ for (int i = 0; i < relock.size(); i++) {
+ relock.get(i).lock();
+ }
+ }
+
+ public void setInputs(Object[] values) {
+ testInputs = values;
+ for (int i = 0; i < guiInputs.length; i++) {
+ guiInputs[i].setValue(values[i]);
}
+ updateValues();
}
- public static boolean isAllowed(GUIMutable source, GUIConnection target) {
- if (source instanceof GUINode) {
- // if the source is a node, all inputs and some nodes are valid
- if (target instanceof GUIInput) {
- return true;
- } else if (target instanceof GUINode) {
- // target and source are nodes, let's look at levels back
- Node t = ((GUINode) target).getNode(), s = ((GUINode) source).getNode();
- if (s.getColumn() - t.getColumn() > 0 && s.getColumn() - t.getColumn() <= GUI.resources.levelsBack()) {
- return true;
- }
- }
- return false;
- } else if (source instanceof GUIOutput) {
- // if the source is an output, any node or input is valid
- if (target instanceof GUINode || target instanceof GUIInput) {
- return true;
- } else {
- // this should never happen...
- return false;
+ public void updateValues() {
+ for (int i = 0; i < guiInputs.length; i++) {
+ guiInputs[i].updateText();
+ }
+ for (int c = 0; c < columns; c++) {
+ for (int r = 0; r < rows; r++) {
+ guiNodes[r][c].updateText();
}
}
- // if the source was neither node nor output, something bad is happening
- throw new ClassCastException("Source was neither GUINode nor GUIOutput.");
+ for (int o = 0; o < guiOutputs.length; o++) {
+ guiOutputs[o].updateText();
+ }
+ }
+
+ /**
+ * @return the evaluating attribute.
+ */
+ public boolean isEvaluating() {
+ return parent.isEvaluating();
}
}
diff --git a/src/jcgp/gui/population/FunctionSelector.java b/src/jcgp/gui/population/FunctionSelector.java
index 14614e5..ac7a2c2 100644
--- a/src/jcgp/gui/population/FunctionSelector.java
+++ b/src/jcgp/gui/population/FunctionSelector.java
@@ -46,7 +46,7 @@ public class FunctionSelector extends VBox {
l.addEventFilter(MouseEvent.MOUSE_ENTERED, new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent event) {
- ((Label) event.getSource()).setStyle("-fx-background-color: " + Constants.SOFT_HIGHLIGHT_PAINT + "; -fx-border-color: #B0B0B0; -fx-border-width: 0 0 1 0; -fx-padding: 2");
+ ((Label) event.getSource()).setStyle("-fx-background-color: " + Constants.SOFT_HIGHLIGHT_COLOUR + "; -fx-border-color: #B0B0B0; -fx-border-width: 0 0 1 0; -fx-padding: 2");
}
});
l.addEventFilter(MouseEvent.MOUSE_EXITED, new EventHandler<MouseEvent>() {
@@ -58,7 +58,7 @@ public class FunctionSelector extends VBox {
l.addEventFilter(MouseEvent.MOUSE_CLICKED, new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent event) {
- //target.setFunction(fs.getAllowedFunction(index));
+ target.setFunction(fs.getAllowedFunction(index));
dismiss();
}
});
diff --git a/src/jcgp/gui/population/GUIConnection.java b/src/jcgp/gui/population/GUIConnection.java
deleted file mode 100644
index dc7fcc8..0000000
--- a/src/jcgp/gui/population/GUIConnection.java
+++ /dev/null
@@ -1,32 +0,0 @@
-package jcgp.gui.population;
-
-import jcgp.gui.population.GUIGene.GUIGeneState;
-
-/**
- * A loose equivalent to {@link jcgp.backend.population.Connection}.
- * <br>
- * This defines behaviour that all GUI representations of connections
- * should be capable of.
- *
- * @author Eduardo Pedroni
- *
- */
-public interface GUIConnection {
-
- /**
- * Set the connection's state, but also recursively propagate that state
- * all the way back to the inputs.
- *
- * @param state the state to set.
- */
- public void setStateRecursively(GUIGeneState state);
-
- /**
- * Add or remove a lock, but also recursively propagate that change
- * all the way back to the inputs.
- *
- * @param value true to lock, false to unlock.
- */
- public void setLockRecursively(boolean value);
-
-}
diff --git a/src/jcgp/gui/population/GUIGene.java b/src/jcgp/gui/population/GUIGene.java
index 5e6107f..bae7647 100644
--- a/src/jcgp/gui/population/GUIGene.java
+++ b/src/jcgp/gui/population/GUIGene.java
@@ -7,181 +7,76 @@ import javafx.scene.shape.Circle;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
import javafx.scene.text.TextAlignment;
+import jcgp.backend.population.Connection;
import jcgp.gui.constants.Constants;
-/**
- * Defines the general behaviour of the visual representation of each chromosome gene.
- * <br><br>
- * In practice, this is subclass of {@code javafx.scene.Group} containing a {@code Circle}
- * object and a {@code Text} object. Subclasses may add further elements to the group, for
- * instance to display connection input and output sockets.
- * <br><br>
- * Genes also contain a locked property. When locked, some gene states behave slightly
- * differently. This is used so genes remain highlighted even in the neutral state. The
- * gene lock is in fact recursive; a gene can be locked multiple times and only unlocking
- * it as many times will actually revert it back to its unlocked state. This allows multiple
- * pathways to lock the same gene independently without affecting each other; the gene remains
- * locked until no pathways are locking it.
- *
- * @author Eduardo Pedroni
- *
- */
public abstract class GUIGene extends Group {
- /**
- * This {@code enum} type defines a finite list of all states
- * a gene can take. Each state represents a particular steady
- * situation, and has its own GUI appearance associated with it:
- * a combination of connection line visibility, gene background colour
- * and other visual characteristics.
- *
- * @author Eduardo Pedroni
- *
- */
public enum GUIGeneState {
- /**
- * No user interaction at all.
- */
NEUTRAL,
- /**
- * User is simply hovering over the node.
- */
HOVER,
- /**
- * User is hovering over a node connected to this one.
- */
- EXTENDED_HOVER,
- /**
- * User is hovering over an output connected to this gene.
- */
+ INDIRECT_HOVER,
ACTIVE_HOVER,
-
- GOOD_TARGET,
-
- NEUTRAL_TARGET,
-
- BAD_TARGET
+ LOCKED_HOVER,
+ SOURCE,
+ VALID_TARGET,
+ NO_CHANGE_TARGET,
+ INVALID_TARGET
}
+
+ protected Text text = new Text();
+ protected Circle mainCircle = new Circle(Constants.NODE_RADIUS, Paint.valueOf("white"));
+
+ private GUIGeneState state = GUIGeneState.NEUTRAL;
- private GUIGeneState currentState = GUIGeneState.NEUTRAL;
-
- private Text text;
- private Circle mainCircle;
+ protected ChromosomePane parent;
- /**
- * Recursive lock; lock == 0 means unlocked, lock > 0 means locked.
- * Accessing using {@code setLock(...)}.
- */
- private int lock = 0;
+ protected int locked = 0;
- /**
- * Initialises the {@code Text} and {@code Circle} objects so that all genes are standardised.
- */
- protected GUIGene() {
- text = new Text();
+ public GUIGene() {
text.setFont(Font.font("Arial", 12));
text.setTextOrigin(VPos.CENTER);
text.setTextAlignment(TextAlignment.CENTER);
text.setWrappingWidth(Constants.NODE_RADIUS * 2);
text.setX(-Constants.NODE_RADIUS);
-
- mainCircle = new Circle(Constants.NODE_RADIUS, Constants.NEUTRAL_PAINT);
- mainCircle.setStroke(Paint.valueOf("black"));
+ text.setVisible(true);
- getChildren().addAll(mainCircle, text);
+ mainCircle.setStroke(Paint.valueOf("black"));
}
-
- /**
- * Sets the gene's text field.
- *
- * @param newText the text string to be displayed.
- */
- public void setText(String newText) {
- text.setText(newText);
+
+ public void setState(GUIGeneState newState) {
+ state = newState;
}
-
- /**
- * @return the gene's current state.
- */
+
public GUIGeneState getState() {
- return currentState;
+ return state;
}
- /**
- * Gene states are standardised: all gene subclasses behave the same way in each state.
- * <br>
- * This design choice was made for the sake of consistency. Rather than controlling the
- * appearance of the genes with logic in the state transition method AND the mouse handlers,
- * the states are now consistent across all types of gene. The mouse handlers implement
- * whatever logic is necessary to determine the gene's new state given a certain user input,
- * but the states themselves are the same for all genes.
- * <br>
- * The transition logic for each type of gene is defined in its respective handler class:
- * {@code InputHandlers}, {@code NodeHandlers} and {@code OutputHandlers}.
- *
- * @param newState the gene's new state.
- */
- public final void setState(GUIGeneState newState) {
- switch (newState) {
- case NEUTRAL:
- mainCircle.setFill(isLocked() ? Constants.HARD_HIGHLIGHT_PAINT : Constants.NEUTRAL_PAINT);
- setLinesVisible(isLocked());
- break;
- case HOVER:
- mainCircle.setFill(Constants.MEDIUM_HIGHLIGHT_PAINT);
- setLinesVisible(true);
- break;
- case EXTENDED_HOVER:
- mainCircle.setFill(Constants.SOFT_HIGHLIGHT_PAINT);
- setLinesVisible(isLocked());
- break;
- case ACTIVE_HOVER:
- mainCircle.setFill(Constants.SOFT_HIGHLIGHT_PAINT);
- setLinesVisible(true);
- break;
- case GOOD_TARGET:
- mainCircle.setFill(Constants.GOOD_SELECTION_PAINT);
- break;
- case NEUTRAL_TARGET:
- mainCircle.setFill(Constants.NEUTRAL_SELECTION_PAINT);
- break;
- case BAD_TARGET:
- mainCircle.setFill(Constants.BAD_SELECTION_PAINT);
- break;
- }
- currentState = newState;
- }
-
- /**
- * For the sake of practicality, all {@code GUIGene} instances must implement this
- * method. It sets the visibility of all of the gene's lines, if it has any.
- *
- * @param value the visibility value.
- */
- protected abstract void setLinesVisible(boolean value);
-
- /**
- * @return true if the gene is locked, false otherwise.
- */
public boolean isLocked() {
- return lock > 0;
+ return locked > 0;
}
- /**
- * Locks or unlocks the gene once. Locked genes
- * behave slightly differently in some states.
- * <br>
- * Unlocking an already unlocked gene does nothing.
- *
- * @param value true to lock, false to unlock;
- */
- public void setLock(boolean value) {
- if (value) {
- lock++;
- } else if (lock > 0) {
- lock--;
- } else {
- lock = 0;
- }
+ public int getLocks() {
+ return locked;
}
+
+ protected abstract void setLocked(boolean value);
+
+ public abstract void addLocks(int value);
+
+ public abstract void removeLocks(int value);
+
+ public abstract void updateLines();
+
+ public abstract void setChangingConnection(Connection newConnection);
+
+ public abstract Connection getChangingConnection();
+
+ public abstract void setConnectionStates(GUIGeneState newState);
+
+ public abstract void resetState();
+
+ public abstract void setConnectionLine(GUIGene gene);
+
+ public abstract void updateText();
}
diff --git a/src/jcgp/gui/population/GUIInput.java b/src/jcgp/gui/population/GUIInput.java
index 3db7416..8b55a58 100644
--- a/src/jcgp/gui/population/GUIInput.java
+++ b/src/jcgp/gui/population/GUIInput.java
@@ -1,70 +1,233 @@
package jcgp.gui.population;
+import javafx.event.EventHandler;
+import javafx.scene.input.MouseDragEvent;
+import javafx.scene.input.MouseEvent;
import javafx.scene.paint.Paint;
import javafx.scene.shape.Circle;
+import jcgp.backend.population.Connection;
import jcgp.backend.population.Input;
import jcgp.gui.constants.Constants;
-import jcgp.gui.constants.Position;
-import jcgp.gui.handlers.InputHandlers;
-/**
- * The GUI counterpart of {@link jcgp.backend.population.Input}. This is a
- * subclass of {@code GUIGene} which represents a chromosome input.
- *
- * @author Eduardo Pedroni
- */
-public class GUIInput extends GUIGene implements GUIConnection {
+public class GUIInput extends GUIGene {
private Input input;
-
- /**
- * Instantiate {@code GUIInput} given an {@code Input}.
- *
- * @param input the associated backend input.
- */
- public GUIInput(final Input input) {
+
+ public GUIInput(ChromosomePane parentRef, final Input input) {
super();
- // store the input, associate itself with it
+
+ this.parent = parentRef;
this.input = input;
- input.setGUIObject(this);
- // inputs only have a single output socket
+ relocate(Constants.NODE_RADIUS,
+ (input.getIndex() * (2 * Constants.NODE_RADIUS + Constants.SPACING)) + Constants.NODE_RADIUS);
+
+ updateText();
+
Circle outputSocket = new Circle(Constants.NODE_RADIUS, 0, Constants.SOCKET_RADIUS, Paint.valueOf("white"));
- outputSocket.setStroke(Paint.valueOf("black"));
outputSocket.setId(String.valueOf(0));
- getChildren().add(outputSocket);
+ outputSocket.setStroke(Paint.valueOf("black"));
+
+ getChildren().addAll(mainCircle, text, outputSocket);
- // relocate to the right position, add mouse handlers
- Position.place(this);
- InputHandlers.addHandlers(this);
- }
+ /*
+ * Mouse event handlers on whole gene
+ */
+ addEventFilter(MouseDragEvent.MOUSE_DRAG_ENTERED, new EventHandler<MouseDragEvent>() {
+ @Override
+ public void handle(MouseDragEvent event) {
+ // the drag has entered this node, react appropriately
+ // this happens even if we are the source of the drag
+ ((GUIGene) event.getGestureSource()).setConnectionLine((GUIGene) event.getSource());
+ Connection source = ((GUIGene) event.getGestureSource()).getChangingConnection();
+ if (input == source) {
+ setState(GUIGeneState.NO_CHANGE_TARGET);
+ } else {
+ setState(GUIGeneState.VALID_TARGET);
+ }
+ }
+ });
- /**
- * @return the {@code Input} instance associated with this object.
- */
- public Input getInput() {
- return input;
+ addEventFilter(MouseDragEvent.MOUSE_DRAG_EXITED, new EventHandler<MouseDragEvent>() {
+ @Override
+ public void handle(MouseDragEvent event) {
+ // the drag has exited this node, react appropriately
+ // this happens even if we are the source of the drag
+ parent.setTarget(false);
+ if (event.isPrimaryButtonDown()) {
+ if (getState() == GUIGeneState.NO_CHANGE_TARGET) {
+ setState(GUIGeneState.INDIRECT_HOVER);
+ } else {
+ setState(GUIGeneState.NEUTRAL);
+ ((GUIGene) event.getGestureSource()).setConnectionStates(GUIGeneState.INDIRECT_HOVER);
+ }
+ }
+ }
+
+ });
+
+ addEventFilter(MouseDragEvent.MOUSE_DRAG_RELEASED, new EventHandler<MouseDragEvent>() {
+ @Override
+ public void handle(MouseDragEvent event) {
+ GUIGene source = ((GUIGene) event.getGestureSource());
+ // set states to reflect the new situation
+ if (source.isLocked()) {
+ source.setState(GUIGeneState.HOVER);
+ source.setConnectionStates(GUIGeneState.HOVER);
+ } else {
+ source.setState(GUIGeneState.NEUTRAL);
+ source.setConnectionStates(GUIGeneState.NEUTRAL);
+ }
+
+ // the user released the drag gesture on this node, react appropriately
+ if (source.isLocked()) {
+ // remove locks from the old connection, add the to the new
+ // note that the old connection may still have locks after this
+ parent.getGuiGene(source.getChangingConnection()).removeLocks(source.getLocks());
+ source.setChangingConnection(input);
+ addLocks(source.getLocks());
+ } else {
+ source.setChangingConnection(input);
+ }
+
+ source.updateLines();
+ setState(GUIGeneState.HOVER);
+ }
+ });
+
+ addEventFilter(MouseEvent.MOUSE_ENTERED, new EventHandler<MouseEvent>() {
+ @Override
+ public void handle(MouseEvent event) {
+ // cursor has entered this node without dragging, or it is dragging and this is the source
+ if (getState() == GUIGeneState.NEUTRAL) {
+ setState(GUIGeneState.HOVER);
+ }
+ }
+ });
+
+ addEventFilter(MouseEvent.MOUSE_EXITED, new EventHandler<MouseEvent>() {
+ @Override
+ public void handle(MouseEvent event) {
+ // cursor has left this node without dragging, or it is dragging and this is the source
+ if (getState() == GUIGeneState.HOVER) {
+ setState(GUIGeneState.NEUTRAL);
+ setConnectionStates(GUIGeneState.NEUTRAL);
+ }
+ }
+ });
}
+ @Override
+ public void setState(GUIGeneState newState) {
+ super.setState(newState);
+
+ switch (newState) {
+ case ACTIVE_HOVER:
+ if (locked > 0) {
+ setState(GUIGeneState.LOCKED_HOVER);
+ } else {
+ mainCircle.setFill(Paint.valueOf(Constants.SOFT_HIGHLIGHT_COLOUR));
+ }
+ break;
+ case INVALID_TARGET:
+ mainCircle.setFill(Paint.valueOf(Constants.BAD_SELECTION_COLOUR));
+ break;
+ case LOCKED_HOVER:
+ mainCircle.setFill(Paint.valueOf(Constants.SOFT_HIGHLIGHT_COLOUR));
+ break;
+ case HOVER:
+ mainCircle.setFill(Paint.valueOf(Constants.MEDIUM_HIGHLIGHT_COLOUR));
+ break;
+ case INDIRECT_HOVER:
+ mainCircle.setFill(Paint.valueOf(Constants.SOFT_HIGHLIGHT_COLOUR));
+ break;
+ case NEUTRAL:
+ if (locked > 0) {
+ setState(GUIGeneState.HOVER);
+ } else {
+ mainCircle.setFill(Paint.valueOf(Constants.NEUTRAL_COLOUR));
+ }
+ break;
+ case NO_CHANGE_TARGET:
+ parent.setTarget(true);
+ mainCircle.setFill(Paint.valueOf(Constants.NEUTRAL_SELECTION_COLOUR));
+ break;
+ case SOURCE:
+ mainCircle.setFill(Paint.valueOf(Constants.MEDIUM_HIGHLIGHT_COLOUR));
+ break;
+ case VALID_TARGET:
+ parent.setTarget(true);
+ mainCircle.setFill(Paint.valueOf(Constants.GOOD_SELECTION_COLOUR));
+ break;
+ default:
+ break;
+ }
+
+ }
+
/**
- * Associates this instance with a new input.
+ * Set all connections to a given state.
*
- * @param input the new input.
+ * @param newState the state to set connections to.
*/
- void setInput(Input input) {
- this.input = input;
+ @Override
+ public void setConnectionStates(GUIGeneState newState) {
+ // nothing
+ }
+
+ @Override
+ public void resetState() {
+ setState(GUIGeneState.NEUTRAL);
}
@Override
- public void setStateRecursively(GUIGeneState state) {
- setState(state);
+ protected void setLocked(boolean value) {
+ locked += value ? 1 : -1;
+ setState(locked > 0 ? GUIGeneState.HOVER : GUIGeneState.ACTIVE_HOVER);
}
@Override
- protected void setLinesVisible(boolean value) {}
+ public void setChangingConnection(Connection newConnection) {
+ // do nothing
+ }
+
+ @Override
+ public Connection getChangingConnection() {
+ return null;
+ }
@Override
- public void setLockRecursively(boolean value) {
- setLock(value);
+ public void addLocks(int value) {
+ locked += value;
+ setState(locked > 0 ? GUIGeneState.HOVER : GUIGeneState.ACTIVE_HOVER);
}
+
+ @Override
+ public void updateLines() {
+ // nothing
+ }
+
+ @Override
+ public void removeLocks(int value) {
+ locked -= value;
+ setState(locked > 0 ? GUIGeneState.HOVER : GUIGeneState.NEUTRAL);
+ }
+
+ @Override
+ public void setConnectionLine(GUIGene gene) {
+ // nothing
+ }
+
+ public void setValue(Object newValue) {
+ input.setValue(newValue);
+ }
+
+ @Override
+ public void updateText() {
+ if (parent.isEvaluating()) {
+ text.setText("I: " + input.getIndex() + "\n" + input.getValue().toString());
+ } else {
+ text.setText("I: " + input.getIndex());
+ }
+ }
}
diff --git a/src/jcgp/gui/population/GUIMutable.java b/src/jcgp/gui/population/GUIMutable.java
deleted file mode 100644
index fa996e2..0000000
--- a/src/jcgp/gui/population/GUIMutable.java
+++ /dev/null
@@ -1,20 +0,0 @@
-package jcgp.gui.population;
-
-import javafx.scene.shape.Line;
-
-/**
- * A loose equivalent to {@link jcgp.backend.population.Mutable}.
- * <br>
- * This defines behaviour that all GUI representations of mutables
- * should be capable of.
- *
- * @author Eduardo Pedroni
- *
- */
-public interface GUIMutable {
-
- public Line[] getLines();
-
- public GUIConnection[] getConnections();
-
-}
diff --git a/src/jcgp/gui/population/GUINode.java b/src/jcgp/gui/population/GUINode.java
index 1a32426..4d420ea 100644
--- a/src/jcgp/gui/population/GUINode.java
+++ b/src/jcgp/gui/population/GUINode.java
@@ -1,134 +1,472 @@
package jcgp.gui.population;
+import javafx.event.EventHandler;
+import javafx.scene.control.Label;
+import javafx.scene.input.MouseDragEvent;
+import javafx.scene.input.MouseEvent;
import javafx.scene.paint.Paint;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Line;
-import jcgp.backend.population.Gene;
+import jcgp.backend.function.Function;
+import jcgp.backend.population.Connection;
+import jcgp.backend.population.Input;
import jcgp.backend.population.Node;
+import jcgp.backend.resources.Resources;
import jcgp.gui.GUI;
import jcgp.gui.constants.Constants;
-import jcgp.gui.constants.Position;
-import jcgp.gui.handlers.NodeHandlers;
-/**
- * The GUI counterpart of {@link jcgp.backend.population.Node}. This is a
- * subclass of {@code GUIGene} which represents a chromosome node.
- *
- * @author Eduardo Pedroni
- */
-public class GUINode extends GUIGene implements GUIMutable, GUIConnection {
+public class GUINode extends GUIGene {
- private Node node;
private Line[] lines;
- private Circle[] sockets;
+ private Node node;
+ private Resources resources;
+ private int connectionIndex = 0;
- /**
- * Instantiate {@code GUINode} given a {@code Node} and the lines needed
- * to show its connections.
- *
- * @param node the associated backend node.
- * @param lines the lines used to display connections.
- */
- public GUINode(Node node, Line[] lines) {
+
+ public GUINode(ChromosomePane parentRef, final Node node, Line[] connectionLines, final GUI gui) {
super();
- // store references, associate with node
+
+ // store references
+ this.parent = parentRef;
this.node = node;
- this.lines = lines;
- node.setGUIObject(this);
+ this.lines = connectionLines;
+ this.resources = gui.getExperiment().getResources();
+
+ // move the GUIGene to the right position
+ relocate(((node.getColumn() + 1) * (2 * Constants.NODE_RADIUS + Constants.SPACING)) + Constants.NODE_RADIUS,
+ (node.getRow() * (2 * Constants.NODE_RADIUS + Constants.SPACING)) + Constants.NODE_RADIUS);
+
+ // set the line ends correctly
+ updateLines();
- // create the output socket
- Circle output = new Circle(Constants.NODE_RADIUS, 0, Constants.SOCKET_RADIUS, Constants.SOCKET_PAINT);
+ final Label connectionNumber = new Label();
+ connectionNumber.setStyle("-fx-background-color:rgb(255, 255, 255); -fx-border-color:rgba(0, 0, 0, 0.5); ");
+ connectionNumber.setVisible(false);
+
+ Circle output = new Circle(Constants.NODE_RADIUS, 0, Constants.SOCKET_RADIUS, Paint.valueOf("white"));
output.setStroke(Paint.valueOf("black"));
-
- // create input sockets
- sockets = new Circle[GUI.resources.arity()];
+
+ updateText();
+
+ Circle[] sockets = new Circle[resources.arity()];
+ double angle, xPos, yPos;
for (int l = 0; l < sockets.length; l++) {
- sockets[l] = new Circle(Constants.SOCKET_RADIUS, Constants.SOCKET_PAINT);
- sockets[l].setStroke(Paint.valueOf("black"));
+ angle = (((l + 1) / ((double) (resources.arity() + 1))) * Constants.THETA) - (Constants.THETA / 2);
+ xPos = -Math.cos(angle) * Constants.NODE_RADIUS;
+ yPos = Math.sin(angle) * Constants.NODE_RADIUS;
+
+ sockets[l] = new Circle(xPos, yPos, Constants.SOCKET_RADIUS, Paint.valueOf("white"));
sockets[l].setId(String.valueOf(l));
- // relocate them
- Position.placeSocket(l, sockets[l]);
- Position.connect(lines[l], (GUIGene) ((Gene) node.getConnection(l)).getGUIObject());
+ sockets[l].setStroke(Paint.valueOf("black"));
+
+ final Circle s = sockets[l];
+ final int index = l;
+
+ /*
+ * Mouse event handlers on sockets
+ *
+ */
+ s.addEventFilter(MouseEvent.DRAG_DETECTED, new EventHandler<MouseEvent>() {
+ @Override
+ public void handle(MouseEvent event) {
+ // the mouse has been dragged out of the socket, this means a full drag is in progress
+ startFullDrag();
+ }
+ });
+
+ s.addEventFilter(MouseEvent.MOUSE_ENTERED, new EventHandler<MouseEvent>() {
+ @Override
+ public void handle(MouseEvent event) {
+ // user is hovering over connection socket
+ connectionNumber.setText("C: " + s.getId());
+ connectionNumber.relocate(s.getCenterX() + 5, s.getCenterY() - 10);
+ connectionNumber.setVisible(true);
+ }
+ });
+
+ s.addEventFilter(MouseEvent.MOUSE_EXITED, new EventHandler<MouseEvent>() {
+ @Override
+ public void handle(MouseEvent event) {
+ // user exits the connection socket
+ connectionNumber.setVisible(false);
+ }
+ });
+
+ s.addEventFilter(MouseEvent.MOUSE_PRESSED, new EventHandler<MouseEvent>() {
+ @Override
+ public void handle(MouseEvent event) {
+ // mouse was pressed on the socket
+ setState(GUIGeneState.SOURCE);
+ connectionIndex = index;
+ }
+ });
+
+ s.addEventFilter(MouseEvent.MOUSE_DRAGGED, new EventHandler<MouseEvent>() {
+ @Override
+ public void handle(MouseEvent event) {
+ if (!parent.isTarget()) {
+ lines[connectionIndex].setEndX(event.getX() + ((Circle) event.getSource()).getParent().getLayoutX());
+ lines[connectionIndex].setEndY(event.getY() + ((Circle) event.getSource()).getParent().getLayoutY());
+ }
+ }
+ });
+
+ s.addEventFilter(MouseEvent.MOUSE_RELEASED, new EventHandler<MouseEvent>() {
+ @Override
+ public void handle(MouseEvent event) {
+ if (event.isStillSincePress()) {
+ // mouse was released before dragging out of the socket
+ updateLine(index);
+ setState(GUIGeneState.HOVER);
+ } else if (getState() == GUIGeneState.SOURCE) {
+ // no connection has been made, fallback
+ resetState();
+ updateLines();
+ }
+ }
+ });
}
+
+ /*
+ * Mouse event handlers on whole gene
+ */
+ addEventFilter(MouseEvent.MOUSE_CLICKED, new EventHandler<MouseEvent>() {
+ @Override
+ public void handle(MouseEvent event) {
+ gui.bringFunctionSelector(event, (GUINode) event.getSource());
+ }
+ });
- // add elements
+ addEventFilter(MouseDragEvent.MOUSE_DRAG_ENTERED, new EventHandler<MouseDragEvent>() {
+ @Override
+ public void handle(MouseDragEvent event) {
+ // the drag has entered this node, react appropriately
+ // this happens even if we are the source of the drag
+ if (isAllowed((GUIGene) event.getGestureSource(), (GUIGene) event.getSource())) {
+ ((GUIGene) event.getGestureSource()).setConnectionLine((GUIGene) event.getSource());
+
+ Connection source = ((GUIGene) event.getGestureSource()).getChangingConnection();
+ if (node == source) {
+ setState(GUIGeneState.NO_CHANGE_TARGET);
+ } else {
+ setState(GUIGeneState.VALID_TARGET);
+ }
+ } else {
+ setState(GUIGeneState.INVALID_TARGET);
+ }
+ }
+ });
+
+ addEventFilter(MouseDragEvent.MOUSE_DRAG_EXITED, new EventHandler<MouseDragEvent>() {
+ @Override
+ public void handle(MouseDragEvent event) {
+ // the drag has exited this node, react appropriately
+ // this happens even if we are the source of the drag
+ parent.setTarget(false);
+ if (event.isPrimaryButtonDown()) {
+ if (event.getGestureSource() == event.getSource()) {
+ setState(GUIGeneState.SOURCE);
+ } else {
+ if (getState() == GUIGeneState.NO_CHANGE_TARGET) {
+ setState(GUIGeneState.INDIRECT_HOVER);
+ } else {
+ setState(GUIGeneState.NEUTRAL);
+ ((GUIGene) event.getGestureSource()).setConnectionStates(GUIGeneState.INDIRECT_HOVER);
+ }
+ }
+ }
+ }
+ });
+
+ addEventFilter(MouseDragEvent.MOUSE_DRAG_RELEASED, new EventHandler<MouseDragEvent>() {
+ @Override
+ public void handle(MouseDragEvent event) {
+ GUIGene source = ((GUIGene) event.getGestureSource());
+ // set states to reflect the new situation
+ if (source.isLocked()) {
+ source.setState(GUIGeneState.HOVER);
+ source.setConnectionStates(GUIGeneState.HOVER);
+ } else {
+ source.setState(GUIGeneState.NEUTRAL);
+ source.setConnectionStates(GUIGeneState.NEUTRAL);
+ }
+
+ // the user released the drag gesture on this node, react appropriately
+ if (isAllowed((GUIGene) event.getGestureSource(), (GUIGene) event.getSource())) {
+ if (source.isLocked()) {
+ // remove locks from the old connection, add the to setConnethe new
+ // note that the old connection may still have locks after this
+ parent.getGuiGene(source.getChangingConnection()).removeLocks(source.getLocks());
+ addLocks(source.getLocks());
+ } else {
+ if (source instanceof GUIOutput) {
+ source.resetState();
+ }
+ }
+ source.setChangingConnection(node);
+
+ }
+ source.updateLines();
+ setState(GUIGeneState.HOVER);
+ }
+ });
+
+ addEventFilter(MouseEvent.MOUSE_ENTERED, new EventHandler<MouseEvent>() {
+ @Override
+ public void handle(MouseEvent event) {
+ // cursor has entered this node without dragging, or it is dragging and this is the source
+ if (getState() == GUIGeneState.NEUTRAL) {
+ setState(GUIGeneState.HOVER);
+ } else if (locked > 0) {
+ setConnectionStates(GUIGeneState.LOCKED_HOVER);
+ }
+ }
+ });
+
+ addEventFilter(MouseEvent.MOUSE_EXITED, new EventHandler<MouseEvent>() {
+ @Override
+ public void handle(MouseEvent event) {
+ // cursor has left this node without dragging, or it is dragging and this is the source
+ if (getState() == GUIGeneState.HOVER && locked <= 0) {
+ setState(GUIGeneState.NEUTRAL);
+ setConnectionStates(GUIGeneState.NEUTRAL);
+ } else if (locked > 0) {
+ if (getState() == GUIGeneState.SOURCE || getState() == GUIGeneState.INVALID_TARGET) {
+ setConnectionStates(GUIGeneState.INDIRECT_HOVER);
+ } else {
+ setConnectionStates(GUIGeneState.HOVER);
+ }
+
+ }
+ }
+ });
+
+ getChildren().addAll(mainCircle, text);
getChildren().addAll(sockets);
- getChildren().add(output);
-
- // relocate node, add handlers
- Position.place(this);
- NodeHandlers.addHandlers(this);
+ getChildren().addAll(output, connectionNumber);
+
+ }
+
+ @Override
+ public void setState(GUIGeneState newState) {
+ switch (newState) {
+ case ACTIVE_HOVER:
+ if (locked > 0) {
+ setState(GUIGeneState.LOCKED_HOVER);
+ } else {
+ mainCircle.setFill(Paint.valueOf(Constants.SOFT_HIGHLIGHT_COLOUR));
+ showLines(true);
+ }
+ setConnectionStates(GUIGeneState.ACTIVE_HOVER);
+ break;
+ case LOCKED_HOVER:
+ mainCircle.setFill(Paint.valueOf(Constants.SOFT_HIGHLIGHT_COLOUR));
+ break;
+ case INVALID_TARGET:
+ mainCircle.setFill(Paint.valueOf(Constants.BAD_SELECTION_COLOUR));
+ break;
+ case HOVER:
+ mainCircle.setFill(Paint.valueOf(Constants.MEDIUM_HIGHLIGHT_COLOUR));
+ showLines(true);
+ if (locked <= 0) {
+ setConnectionStates(GUIGeneState.INDIRECT_HOVER);
+ } else {
+ setConnectionStates(GUIGeneState.HOVER);
+ }
+ break;
+ case INDIRECT_HOVER:
+ mainCircle.setFill(Paint.valueOf(Constants.SOFT_HIGHLIGHT_COLOUR));
+ break;
+ case NEUTRAL:
+ if (locked > 0) {
+ setState(GUIGeneState.HOVER);
+ } else {
+ mainCircle.setFill(Paint.valueOf(Constants.NEUTRAL_COLOUR));
+ showLines(false);
+ if (getState() == GUIGeneState.ACTIVE_HOVER) {
+ setConnectionStates(GUIGeneState.NEUTRAL);
+ }
+ }
+ break;
+ case NO_CHANGE_TARGET:
+ parent.setTarget(true);
+ mainCircle.setFill(Paint.valueOf(Constants.NEUTRAL_SELECTION_COLOUR));
+ break;
+ case SOURCE:
+ mainCircle.setFill(Paint.valueOf(Constants.HARD_HIGHLIGHT_COLOUR));
+ break;
+ case VALID_TARGET:
+ parent.setTarget(true);
+ mainCircle.setFill(Paint.valueOf(Constants.GOOD_SELECTION_COLOUR));
+ break;
+ default:
+ break;
+ }
+
+ super.setState(newState);
}
- /**
- * @return the {@code Node} instance associated with this object.
- */
+ @Override
+ public Connection getChangingConnection() {
+ return node.getConnection(connectionIndex);
+ }
+
+ private boolean isAllowed(GUIGene source, GUIGene target) {
+ if (source instanceof GUINode) {
+ // if the source is a node, all inputs and some nodes are valid
+ if (target instanceof GUIInput) {
+ return true;
+ } else if (target instanceof GUINode) {
+ // target and source are nodes, let's look at levels back
+ Node t = ((GUINode) target).getNode(), s = ((GUINode) source).getNode();
+ if (s.getColumn() - t.getColumn() > 0 && s.getColumn() - t.getColumn() <= resources.levelsBack()) {
+ return true;
+ }
+ return false;
+ } else if (target instanceof GUIOutput) {
+ return false;
+ } else {
+ throw new ClassCastException("Target was neither GUINode nor GUIInput nor GUIOutput.");
+ }
+ } else if (source instanceof GUIOutput) {
+ // if the source is an output, any node or input is valid
+ if (target instanceof GUINode || target instanceof GUIInput) {
+ return true;
+ } else if (target instanceof GUIOutput) {
+ return false;
+ } else {
+ throw new ClassCastException("Target was neither GUINode nor GUIInput nor GUIOutput.");
+ }
+ }
+ // if the source was neither node nor output, something bad is happening
+ throw new ClassCastException("Source was neither GUINode nor GUIOutput.");
+ }
+
+
public Node getNode() {
return node;
}
/**
- * Associates this instance with a new node.
+ * Place the end of the specified line on the output of the associated connection.
*
- * @param node the new node.
+ * @param index the line to be updated.
*/
- void setNode(Node node) {
- this.node = node;
+ public void updateLine(int index) {
+ if (node.getConnection(index) instanceof Node) {
+ int row = ((Node) node.getConnection(index)).getRow(),
+ column = ((Node) node.getConnection(index)).getColumn();
+ lines[index].setEndX(((column + 1) * (2 * Constants.NODE_RADIUS + Constants.SPACING)) + 2 * Constants.NODE_RADIUS);
+ lines[index].setEndY((row * (2 * Constants.NODE_RADIUS + Constants.SPACING)) + Constants.NODE_RADIUS);
+ } else if (node.getConnection(index) instanceof Input) {
+ int inputIndex = ((Input) node.getConnection(index)).getIndex();
+ lines[index].setEndX(2 * Constants.NODE_RADIUS);
+ lines[index].setEndY(inputIndex * (2 * Constants.NODE_RADIUS + Constants.SPACING) + Constants.NODE_RADIUS);
+ }
}
-
+
+ /**
+ * Updates the end of all lines to match the associated connections.
+ */
@Override
- public Line[] getLines() {
- return lines;
+ public void updateLines() {
+ for (int c = 0; c < lines.length; c++) {
+ updateLine(c);
+ }
}
-
+
/**
- * Returns one of this object's connection sockets. They are
- * indexed in the same order as lines and the connections
- * they represent.
+ * Toggle visibility of all connection lines.
*
- * @param index the socket to return.
- * @return the indexed socket object.
+ * @param value whether to show the lines or not.
*/
- public Circle getSocket(int index) {
- return sockets[index];
+ private void showLines(boolean value) {
+ for (int i = 0; i < lines.length; i++) {
+ lines[i].setVisible(value);
+ }
}
-
- /**
- * @return the entire {@code Socket} array.
- */
- public Circle[] getSockets() {
- return sockets;
+
+ @Override
+ public void setConnectionStates(GUIGeneState newState) {
+ for (int i = 0; i < lines.length; i++) {
+ parent.getGuiGene(node.getConnection(i)).setState(newState);
+ }
+ }
+
+ @Override
+ public void setChangingConnection(Connection newConnection) {
+ node.setConnection(connectionIndex, newConnection);
+ if (parent.isEvaluating()) {
+ parent.updateValues();
+ }
}
+
@Override
- public void setStateRecursively(GUIGeneState state) {
- setState(state);
- for (int i = 0; i < GUI.resources.arity(); i++) {
- ((GUIConnection) ((Gene) node.getConnection(i)).getGUIObject()).setStateRecursively(state);
+ public void resetState() {
+ if (locked > 0) {
+ setState(GUIGeneState.HOVER);
+ } else {
+ setState(GUIGeneState.NEUTRAL);
+ setConnectionStates(GUIGeneState.NEUTRAL);
}
+
}
@Override
- protected void setLinesVisible(boolean value) {
+ protected void setLocked(boolean value) {
+ locked += value ? 1 : -1;
+ setState(locked > 0 ? GUIGeneState.HOVER : GUIGeneState.ACTIVE_HOVER);
+
for (int i = 0; i < lines.length; i++) {
- lines[i].setVisible(value);
+ parent.getGuiGene(node.getConnection(i)).setLocked(value);
+ }
+ }
+
+ @Override
+ public void addLocks(int value) {
+ locked += value;
+ setState(locked > 0 ? GUIGeneState.HOVER : GUIGeneState.ACTIVE_HOVER);
+
+ for (int i = 0; i < lines.length; i++) {
+ parent.getGuiGene(node.getConnection(i)).addLocks(value);
}
}
@Override
- public void setLockRecursively(boolean value) {
- setLock(value);
- for (int i = 0; i < GUI.resources.arity(); i++) {
- ((GUIConnection) ((Gene) node.getConnection(i)).getGUIObject()).setLockRecursively(value);
+ public void removeLocks(int value) {
+ locked -= value;
+ setState(locked > 0 ? GUIGeneState.HOVER : GUIGeneState.NEUTRAL);
+
+ for (int i = 0; i < lines.length; i++) {
+ parent.getGuiGene(node.getConnection(i)).removeLocks(value);
}
}
@Override
- public GUIConnection[] getConnections() {
- GUIConnection[] connections = new GUIConnection[GUI.resources.arity()];
- for (int c = 0; c < connections.length; c++) {
- connections[c] = (GUIConnection) ((Gene) node.getConnection(c)).getGUIObject();
+ public void setConnectionLine(GUIGene gene) {
+ lines[connectionIndex].setEndX(gene.getLayoutX() + Constants.NODE_RADIUS);
+ lines[connectionIndex].setEndY(gene.getLayoutY());
+ }
+
+ public void updateText() {
+ if (parent.isEvaluating()) {
+ text.setText(node.getFunction() + "\n" + node.getValue().toString());
+ } else {
+ text.setText(node.getFunction().toString());
}
- return connections;
+ }
+
+ public void setFunction(Function function) {
+ node.setFunction(function);
+ if (parent.isEvaluating()) {
+ parent.updateValues();
+ } else {
+ updateText();
+ }
+ }
+
+ public void setNode(Node newNode) {
+ node = newNode;
}
}
diff --git a/src/jcgp/gui/population/GUIOutput.java b/src/jcgp/gui/population/GUIOutput.java
index f023d00..d715138 100644
--- a/src/jcgp/gui/population/GUIOutput.java
+++ b/src/jcgp/gui/population/GUIOutput.java
@@ -1,79 +1,324 @@
package jcgp.gui.population;
+import javafx.event.EventHandler;
+import javafx.scene.control.Label;
+import javafx.scene.input.MouseDragEvent;
+import javafx.scene.input.MouseEvent;
import javafx.scene.paint.Paint;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Line;
-import jcgp.backend.population.Gene;
+import jcgp.backend.population.Connection;
+import jcgp.backend.population.Input;
+import jcgp.backend.population.Node;
import jcgp.backend.population.Output;
+import jcgp.gui.GUI;
import jcgp.gui.constants.Constants;
-import jcgp.gui.constants.Position;
-import jcgp.gui.handlers.OutputHandlers;
-/**
- * The GUI counterpart of {@link jcgp.backend.population.Output}. This is a
- * subclass of {@code GUIGene} which represents a chromosome output.
- *
- * @author Eduardo Pedroni
- */
-public class GUIOutput extends GUIGene implements GUIMutable {
+public class GUIOutput extends GUIGene {
+ private Line sourceLine;
private Output output;
- private Line line;
-
- /**
- * Instantiate {@code GUIOutput} given an {@code Output} and the line needed
- * to show its connection.
- *
- * @param output the associated backend output.
- * @param line the line used to display connection.
- */
- public GUIOutput(final Output output, Line line) {
+
+ public GUIOutput(ChromosomePane parentRef, final Output output, Line line, GUI gui) {
super();
- // store references, associate with backend object
+
+ this.parent = parentRef;
this.output = output;
- this.line = line;
- output.setGUIObject(this);
+ this.sourceLine = line;
- // create input socket
- Circle socket = new Circle(-Constants.NODE_RADIUS, 0, Constants.SOCKET_RADIUS, Constants.SOCKET_PAINT);
- socket.setStroke(Paint.valueOf("black"));
+ relocate(((gui.getExperiment().getResources().columns() + 1) * (2 * Constants.NODE_RADIUS + Constants.SPACING)) + Constants.NODE_RADIUS,
+ (output.getIndex() * (2 * Constants.NODE_RADIUS + Constants.SPACING)) + Constants.NODE_RADIUS);
+
+ // set the line ends correctly
+ updateLines();
+ updateText();
+
+ Circle socket = new Circle(-Constants.NODE_RADIUS, 0, Constants.SOCKET_RADIUS, Paint.valueOf("white"));
socket.setId(String.valueOf(0));
- Position.connect(line, (GUIGene) ((Gene) output.getSource()).getGUIObject());
- getChildren().add(socket);
+ socket.setStroke(Paint.valueOf("black"));
+
+ final Label connectionLabel = new Label("S");
+ connectionLabel.setStyle("-fx-background-color:rgb(255, 255, 255); -fx-border-color:rgba(0, 0, 0, 0.5); ");
+ connectionLabel.relocate(socket.getCenterX() + 5, socket.getCenterY() - 10);
+ connectionLabel.setVisible(false);
+
+ /*
+ * Mouse event handlers on sockets
+ *
+ */
+ socket.addEventFilter(MouseEvent.DRAG_DETECTED, new EventHandler<MouseEvent>() {
+ @Override
+ public void handle(MouseEvent event) {
+ // the mouse has been dragged out of the socket, this means a full drag is in progress
+ startFullDrag();
+ }
+ });
+
+ socket.addEventFilter(MouseEvent.MOUSE_ENTERED, new EventHandler<MouseEvent>() {
+ @Override
+ public void handle(MouseEvent event) {
+ // user is hovering over connection socket
+ connectionLabel.setVisible(true);
+ }
+ });
+
+ socket.addEventFilter(MouseEvent.MOUSE_EXITED, new EventHandler<MouseEvent>() {
+ @Override
+ public void handle(MouseEvent event) {
+ // user exits the connection socket
+ connectionLabel.setVisible(false);
+ }
+ });
+
+ socket.addEventFilter(MouseEvent.MOUSE_PRESSED, new EventHandler<MouseEvent>() {
+ @Override
+ public void handle(MouseEvent event) {
+ // mouse was pressed on the socket
+ setState(GUIGeneState.SOURCE);
+ }
+ });
+
+ socket.addEventFilter(MouseEvent.MOUSE_DRAGGED, new EventHandler<MouseEvent>() {
+ @Override
+ public void handle(MouseEvent event) {
+ if (!parent.isTarget()) {
+ sourceLine.setEndX(event.getX() + ((Circle) event.getSource()).getParent().getLayoutX());
+ sourceLine.setEndY(event.getY() + ((Circle) event.getSource()).getParent().getLayoutY());
+ }
+
+ }
+ });
+
+ socket.addEventFilter(MouseEvent.MOUSE_RELEASED, new EventHandler<MouseEvent>() {
+ @Override
+ public void handle(MouseEvent event) {
+ if (event.isStillSincePress()) {
+ // mouse was released before dragging out of the socket
+ updateLines();
+ setState(GUIGeneState.HOVER);
+ } else if (getState() == GUIGeneState.SOURCE) {
+ // no connection has been made, fallback
+ resetState();
+ updateLines();
+ }
+
+ }
+ });
+
- // relocate output, add handlers
- Position.place(this);
- OutputHandlers.addHandlers(this);
+ /*
+ * Mouse event handlers on whole gene
+ */
+ addEventFilter(MouseDragEvent.MOUSE_DRAG_ENTERED, new EventHandler<MouseDragEvent>() {
+ @Override
+ public void handle(MouseDragEvent event) {
+ // the drag has entered this node, react appropriately
+ setState(GUIGeneState.INVALID_TARGET);
+ }
+ });
+
+ addEventFilter(MouseDragEvent.MOUSE_DRAG_EXITED, new EventHandler<MouseDragEvent>() {
+ @Override
+ public void handle(MouseDragEvent event) {
+ // the drag has exited this node, react appropriately
+ // this happens even if we are the source of the drag
+ if (event.isPrimaryButtonDown()) {
+ if (event.getGestureSource() == event.getSource()) {
+ setState(GUIGeneState.SOURCE);
+ } else {
+ setState(isLocked() ? GUIGeneState.HOVER : GUIGeneState.NEUTRAL);
+ }
+ }
+ }
+ });
+
+ addEventFilter(MouseDragEvent.MOUSE_DRAG_RELEASED, new EventHandler<MouseDragEvent>() {
+ @Override
+ public void handle(MouseDragEvent event) {
+ // making a connection to an output is illegal
+ // set states to reflect the new situation
+ GUIGene source = ((GUIGene) event.getGestureSource());
+
+ if (source.isLocked()) {
+ source.setState(GUIGeneState.HOVER);
+ source.setConnectionStates(GUIGeneState.HOVER);
+ } else {
+ source.setState(GUIGeneState.NEUTRAL);
+ source.setConnectionStates(GUIGeneState.NEUTRAL);
+ }
+
+ source.updateLines();
+ setState(GUIGeneState.HOVER);
+ }
+ });
+
+
+ addEventFilter(MouseEvent.MOUSE_ENTERED, new EventHandler<MouseEvent>() {
+ @Override
+ public void handle(MouseEvent event) {
+ // cursor has entered this node without dragging, or it is dragging and this is the source
+ if (getState() == GUIGeneState.NEUTRAL) {
+ setState(GUIGeneState.HOVER);
+ }
+ }
+ });
+
+ addEventFilter(MouseEvent.MOUSE_CLICKED, new EventHandler<MouseEvent>() {
+ @Override
+ public void handle(MouseEvent event) {
+ setLocked(!isLocked());
+ }
+ });
+
+ addEventFilter(MouseEvent.MOUSE_EXITED, new EventHandler<MouseEvent>() {
+ @Override
+ public void handle(MouseEvent event) {
+ // cursor has left this node without dragging, or it is dragging and this is the source
+ if (getState() == GUIGeneState.HOVER && !isLocked()) {
+ setState(GUIGeneState.NEUTRAL);
+ setConnectionStates(GUIGeneState.NEUTRAL);
+ }
+ }
+ });
+
+
+ getChildren().addAll(mainCircle, text, socket, connectionLabel);
+
}
- /**
- * @return the {@code Output} instance associated with this object.
- */
- public Output getOutput() {
- return output;
+ @Override
+ public void setState(GUIGeneState newState) {
+ super.setState(newState);
+
+ switch (newState) {
+ case ACTIVE_HOVER:
+ break;
+ case INVALID_TARGET:
+ mainCircle.setFill(Paint.valueOf(Constants.BAD_SELECTION_COLOUR));
+ break;
+ case HOVER:
+ mainCircle.setFill(Paint.valueOf(Constants.MEDIUM_HIGHLIGHT_COLOUR));
+ sourceLine.setVisible(true);
+ if (!isLocked()) {
+ setConnectionStates(GUIGeneState.ACTIVE_HOVER);
+ }
+ break;
+ case INDIRECT_HOVER:
+ mainCircle.setFill(Paint.valueOf(Constants.SOFT_HIGHLIGHT_COLOUR));
+ break;
+ case NEUTRAL:
+ mainCircle.setFill(Paint.valueOf(Constants.NEUTRAL_COLOUR));
+ sourceLine.setVisible(false);
+ break;
+ case NO_CHANGE_TARGET:
+ mainCircle.setFill(Paint.valueOf(Constants.NEUTRAL_SELECTION_COLOUR));
+ break;
+ case SOURCE:
+ mainCircle.setFill(Paint.valueOf(Constants.HARD_HIGHLIGHT_COLOUR));
+ setConnectionStates(GUIGeneState.NEUTRAL);
+ setConnectionStates(GUIGeneState.INDIRECT_HOVER);
+ break;
+ case VALID_TARGET:
+ mainCircle.setFill(Paint.valueOf(Constants.GOOD_SELECTION_COLOUR));
+ break;
+ default:
+ break;
+ }
}
- /**
- * Associates this instance with a new output.
- *
- * @param output the new output.
- */
- void setOutput(Output output) {
- this.output = output;
+ @Override
+ public void updateLines() {
+ if (output.getSource() instanceof Node) {
+ int row = ((Node) output.getSource()).getRow(),
+ column = ((Node) output.getSource()).getColumn();
+ sourceLine.setEndX(((column + 1) * (2 * Constants.NODE_RADIUS + Constants.SPACING)) + 2 * Constants.NODE_RADIUS);
+ sourceLine.setEndY((row * (2 * Constants.NODE_RADIUS + Constants.SPACING)) + Constants.NODE_RADIUS);
+ } else if (output.getSource() instanceof Input) {
+ int inputIndex = ((Input) output.getSource()).getIndex();
+ sourceLine.setEndX(2 * Constants.NODE_RADIUS);
+ sourceLine.setEndY(inputIndex * (2 * Constants.NODE_RADIUS + Constants.SPACING) + Constants.NODE_RADIUS);
+ }
+ }
+
+ @Override
+ public void setConnectionStates(GUIGeneState newState) {
+ parent.getGuiGene(output.getSource()).setState(newState);
}
@Override
- public Line[] getLines() {
- return new Line[] {line};
+ public void resetState() {
+ if (locked > 0) {
+ setState(GUIGeneState.HOVER);
+ setConnectionStates(GUIGeneState.HOVER);
+ } else {
+ setState(GUIGeneState.NEUTRAL);
+ setConnectionStates(GUIGeneState.NEUTRAL);
+ }
}
@Override
- protected void setLinesVisible(boolean value) {
- line.setVisible(value);
+ protected void setLocked(boolean value) {
+ locked += value ? 1 : -1;
+ setConnectionStates(value ? GUIGeneState.HOVER : GUIGeneState.ACTIVE_HOVER);
+
+ parent.getGuiGene(output.getSource()).setLocked(value);
+
}
@Override
- public GUIConnection[] getConnections() {
- return new GUIConnection[] {(GUIConnection) output.getGUIObject()};
+ public void setChangingConnection(Connection newConnection) {
+ output.setSource(newConnection);
+ updateText();
}
+
+ @Override
+ public Connection getChangingConnection() {
+ return output.getSource();
+ }
+
+ @Override
+ public void addLocks(int value) {
+ locked += value;
+ }
+
+ @Override
+ public void removeLocks(int value) {
+ locked -= value;
+ }
+
+ @Override
+ public void setConnectionLine(GUIGene gene) {
+ sourceLine.setEndX(gene.getLayoutX() + Constants.NODE_RADIUS);
+ sourceLine.setEndY(gene.getLayoutY());
+ }
+
+ public void unlock() {
+ if (isLocked()) {
+ setLocked(false);
+ setState(GUIGeneState.NEUTRAL);
+ setConnectionStates(GUIGeneState.NEUTRAL);
+ }
+ }
+
+ public void lock() {
+ if (!isLocked()) {
+ setState(GUIGeneState.HOVER);
+ setLocked(true);
+ }
+ }
+
+ @Override
+ public void updateText() {
+ if (parent.isEvaluating()) {
+ text.setText("O: " + output.getIndex() + "\n" + output.getSource().getValue().toString());
+ } else {
+ text.setText("O: " + output.getIndex());
+ }
+
+ }
+
+ public void setOutput(Output newOutput) {
+ output = newOutput;
+ }
+
}
diff --git a/src/jcgp/gui/population/PopulationPane.java b/src/jcgp/gui/population/PopulationPane.java
index 51b5ba4..4b1b7f8 100644
--- a/src/jcgp/gui/population/PopulationPane.java
+++ b/src/jcgp/gui/population/PopulationPane.java
@@ -27,7 +27,7 @@ public class PopulationPane extends TabPane {
Tab tab;
ChromosomePane cp;
for (int i = 0; i < jcgp.getResources().populationSize(); i++) {
- cp = new ChromosomePane(jcgp.getPopulation().get(i));
+ cp = new ChromosomePane(jcgp.getPopulation().get(i), gui, this);
tab = new Tab("Chr " + i);
tab.setContent(cp);
getTabs().add(tab);
@@ -43,13 +43,25 @@ public class PopulationPane extends TabPane {
}
}
+ public void unlockOutputs() {
+ for (int i = 0; i < getTabs().size(); i++) {
+ ((ChromosomePane) getTabs().get(i).getContent()).unlockOutputs();
+ }
+ }
+
+ public void relockOutputs() {
+ for (int i = 0; i < getTabs().size(); i++) {
+ ((ChromosomePane) getTabs().get(i).getContent()).relockOutputs();
+ }
+ }
+
public void evaluateTestCase(TestCase<Object> testCase) {
if (gui.getExperiment().getProblem() instanceof TestCaseProblem && testCase != null) {
currentTestCase = testCase;
if (testCase.getInputs().length == gui.getExperiment().getResources().inputs()) {
evaluating = true;
for (int i = 0; i < getTabs().size(); i++) {
- //((ChromosomePane) getTabs().get(i).getContent()).setInputs(testCase.getInputs());
+ ((ChromosomePane) getTabs().get(i).getContent()).setInputs(testCase.getInputs());
}
} else {
throw new IllegalArgumentException("Test case has " + testCase.getInputs().length
@@ -61,7 +73,7 @@ public class PopulationPane extends TabPane {
public void hideValues() {
evaluating = false;
for (int i = 0; i < getTabs().size(); i++) {
- //((ChromosomePane) getTabs().get(i).getContent()).updateValues();
+ ((ChromosomePane) getTabs().get(i).getContent()).updateValues();
}
}