diff options
| author | Eduardo Pedroni <e.pedroni91@gmail.com> | 2015-05-27 13:24:23 +0200 | 
|---|---|---|
| committer | Eduardo Pedroni <e.pedroni91@gmail.com> | 2015-05-27 13:24:23 +0200 | 
| commit | 72f96333f0dd0bad05f3b5d10cf343cf21aa7e39 (patch) | |
| tree | 1f138ac604b3d5f703301912d2c3863785a40792 /src | |
| parent | 1e7d7a9b09ca9d24b56cf33a168953ca033d7c53 (diff) | |
Diffstat (limited to 'src')
| -rw-r--r-- | src/jcgp/gui/GUI.java | 10 | ||||
| -rw-r--r-- | src/jcgp/gui/constants/Constants.java | 102 | ||||
| -rw-r--r-- | src/jcgp/gui/constants/Position.java | 103 | ||||
| -rw-r--r-- | src/jcgp/gui/handlers/InputHandlers.java | 56 | ||||
| -rw-r--r-- | src/jcgp/gui/handlers/NodeHandlers.java | 164 | ||||
| -rw-r--r-- | src/jcgp/gui/handlers/OutputHandlers.java | 84 | ||||
| -rw-r--r-- | src/jcgp/gui/handlers/Target.java | 70 | ||||
| -rw-r--r-- | src/jcgp/gui/population/ChromosomePane.java | 195 | ||||
| -rw-r--r-- | src/jcgp/gui/population/FunctionSelector.java | 4 | ||||
| -rw-r--r-- | src/jcgp/gui/population/GUIConnection.java | 32 | ||||
| -rw-r--r-- | src/jcgp/gui/population/GUIGene.java | 195 | ||||
| -rw-r--r-- | src/jcgp/gui/population/GUIInput.java | 241 | ||||
| -rw-r--r-- | src/jcgp/gui/population/GUIMutable.java | 20 | ||||
| -rw-r--r-- | src/jcgp/gui/population/GUINode.java | 494 | ||||
| -rw-r--r-- | src/jcgp/gui/population/GUIOutput.java | 343 | ||||
| -rw-r--r-- | src/jcgp/gui/population/PopulationPane.java | 18 | 
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();  		}  	} | 
