From 72f96333f0dd0bad05f3b5d10cf343cf21aa7e39 Mon Sep 17 00:00:00 2001 From: Eduardo Pedroni Date: Wed, 27 May 2015 13:24:23 +0200 Subject: Brought back the old GUI, refactorings are now in dev and will be merged when complete --- src/jcgp/gui/GUI.java | 10 +- src/jcgp/gui/constants/Constants.java | 102 ++---- src/jcgp/gui/constants/Position.java | 103 ------ src/jcgp/gui/handlers/InputHandlers.java | 56 --- src/jcgp/gui/handlers/NodeHandlers.java | 164 --------- src/jcgp/gui/handlers/OutputHandlers.java | 84 ----- src/jcgp/gui/handlers/Target.java | 70 ---- src/jcgp/gui/population/ChromosomePane.java | 195 ++++++---- src/jcgp/gui/population/FunctionSelector.java | 4 +- src/jcgp/gui/population/GUIConnection.java | 32 -- src/jcgp/gui/population/GUIGene.java | 195 +++------- src/jcgp/gui/population/GUIInput.java | 241 +++++++++++-- src/jcgp/gui/population/GUIMutable.java | 20 -- src/jcgp/gui/population/GUINode.java | 494 ++++++++++++++++++++++---- src/jcgp/gui/population/GUIOutput.java | 343 +++++++++++++++--- src/jcgp/gui/population/PopulationPane.java | 18 +- 16 files changed, 1124 insertions(+), 1007 deletions(-) delete mode 100644 src/jcgp/gui/constants/Position.java delete mode 100644 src/jcgp/gui/handlers/InputHandlers.java delete mode 100644 src/jcgp/gui/handlers/NodeHandlers.java delete mode 100644 src/jcgp/gui/handlers/OutputHandlers.java delete mode 100644 src/jcgp/gui/handlers/Target.java delete mode 100644 src/jcgp/gui/population/GUIConnection.java delete mode 100644 src/jcgp/gui/population/GUIMutable.java (limited to 'src') 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. *
* 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. - *
- * 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}. - *

- * 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 mouseEnteredHandler = new EventHandler() { - @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 mouseExitedHandler = new EventHandler() { - @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}. - *

- * 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 mouseEnteredHandler = new EventHandler() { - @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 mouseExitedHandler = new EventHandler() { - @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 socketDragDetected = new EventHandler() { - @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 socketMousePressedHandler = new EventHandler() { - @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 socketMouseDraggedHandler = new EventHandler() { - @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 socketMouseReleasedHandler = new EventHandler() { - @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 dragEnteredHandler = new EventHandler() { - @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 dragExitedHandler = new EventHandler() { - @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}. - *

- * 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 mouseEnteredHandler = new EventHandler() { - @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 mouseExitedHandler = new EventHandler() { - @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 mouseClickHandler = new EventHandler() { - @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 connectionLines; + private ArrayList relock = new ArrayList(); + + 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 connectionLines = new ArrayList(); - - 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(); + 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() { @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() { @@ -58,7 +58,7 @@ public class FunctionSelector extends VBox { l.addEventFilter(MouseEvent.MOUSE_CLICKED, new EventHandler() { @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}. - *
- * 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. - *

- * 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. - *

- * 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. - *
- * 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. - *
- * 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. - *
- * 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() { + @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() { + @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() { + @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() { + @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() { + @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}. - *
- * 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() { + @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() { + @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() { + @Override + public void handle(MouseEvent event) { + // user exits the connection socket + connectionNumber.setVisible(false); + } + }); + + s.addEventFilter(MouseEvent.MOUSE_PRESSED, new EventHandler() { + @Override + public void handle(MouseEvent event) { + // mouse was pressed on the socket + setState(GUIGeneState.SOURCE); + connectionIndex = index; + } + }); + + s.addEventFilter(MouseEvent.MOUSE_DRAGGED, new EventHandler() { + @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() { + @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() { + @Override + public void handle(MouseEvent event) { + gui.bringFunctionSelector(event, (GUINode) event.getSource()); + } + }); - // add elements + addEventFilter(MouseDragEvent.MOUSE_DRAG_ENTERED, new EventHandler() { + @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() { + @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() { + @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() { + @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() { + @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() { + @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() { + @Override + public void handle(MouseEvent event) { + // user is hovering over connection socket + connectionLabel.setVisible(true); + } + }); + + socket.addEventFilter(MouseEvent.MOUSE_EXITED, new EventHandler() { + @Override + public void handle(MouseEvent event) { + // user exits the connection socket + connectionLabel.setVisible(false); + } + }); + + socket.addEventFilter(MouseEvent.MOUSE_PRESSED, new EventHandler() { + @Override + public void handle(MouseEvent event) { + // mouse was pressed on the socket + setState(GUIGeneState.SOURCE); + } + }); + + socket.addEventFilter(MouseEvent.MOUSE_DRAGGED, new EventHandler() { + @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() { + @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() { + @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() { + @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() { + @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() { + @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() { + @Override + public void handle(MouseEvent event) { + setLocked(!isLocked()); + } + }); + + addEventFilter(MouseEvent.MOUSE_EXITED, new EventHandler() { + @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 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(); } } -- cgit v1.2.3