aboutsummaryrefslogtreecommitdiffstats
path: root/src/jcgp/gui/population
diff options
context:
space:
mode:
Diffstat (limited to 'src/jcgp/gui/population')
-rw-r--r--src/jcgp/gui/population/ChromosomePane.java97
-rw-r--r--src/jcgp/gui/population/GUIGene.java56
-rw-r--r--src/jcgp/gui/population/GUIInput.java193
-rw-r--r--src/jcgp/gui/population/GUINode.java417
-rw-r--r--src/jcgp/gui/population/GUIOutput.java277
5 files changed, 1040 insertions, 0 deletions
diff --git a/src/jcgp/gui/population/ChromosomePane.java b/src/jcgp/gui/population/ChromosomePane.java
new file mode 100644
index 0000000..546314e
--- /dev/null
+++ b/src/jcgp/gui/population/ChromosomePane.java
@@ -0,0 +1,97 @@
+package jcgp.gui.population;
+
+import java.util.ArrayList;
+
+import javafx.scene.control.ScrollPane;
+import javafx.scene.layout.Pane;
+import javafx.scene.shape.Line;
+import jcgp.CGP.Resources;
+import jcgp.population.Chromosome;
+
+
+public class ChromosomePane extends ScrollPane {
+
+ private GUINode[][] guiNodes;
+ private GUIInput[] guiInputs;
+ private GUIOutput[] guiOutputs;
+
+ private Pane content;
+
+ private ArrayList<Line> connectionLines;
+
+ public ChromosomePane(Chromosome chromosome, Resources resources) {
+ super();
+ connectionLines = new ArrayList<Line>();
+
+ content = new Pane();
+ content.setId("content pane for genes");
+
+ // generate the GUIGenes
+ // inputs
+ guiInputs = new GUIInput[(int) resources.get("inputs")];
+ for (int i = 0; i < guiInputs.length; i++) {
+ // make the GUI elements
+ guiInputs[i] = new GUIInput(this, chromosome.getInput(i));
+ content.getChildren().addAll(guiInputs[i]);
+ }
+ // nodes
+ guiNodes = new GUINode[(int) resources.get("rows")][(int) resources.get("columns")];
+ double angle, xPos, yPos;
+ for (int r = 0; r < guiNodes.length; r++) {
+ for (int c = 0; c < guiNodes[r].length; c++) {
+ // make the connection lines
+ Line lines[] = new Line[(int) resources.get("arity")];
+ for (int l = 0; l < lines.length; l++) {
+ angle = ((((double) (l + 1)) / ((double) (lines.length + 1))) * GUIGene.THETA) - (GUIGene.THETA / 2);
+ xPos = (-Math.cos(angle) * GUIGene.NODE_RADIUS) + (((c + 1) * (2 * GUIGene.NODE_RADIUS + GUIGene.SPACING)) + GUIGene.NODE_RADIUS);
+ yPos = (Math.sin(angle) * GUIGene.NODE_RADIUS) + ((r * (2 * GUIGene.NODE_RADIUS + GUIGene.SPACING)) + GUIGene.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 elements
+ guiNodes[r][c] = new GUINode(this, chromosome.getNode(r, c), resources, lines);
+ }
+ content.getChildren().addAll(guiNodes[r]);
+ }
+
+ // outputs
+ guiOutputs = new GUIOutput[(int) resources.get("outputs")];
+ for (int i = 0; i < guiOutputs.length; i++) {
+ xPos = (((int) resources.get("columns") + 1) * (2 * GUIGene.NODE_RADIUS + GUIGene.SPACING));
+ yPos = (chromosome.getOutput(i).getIndex() * (2 * GUIGene.NODE_RADIUS + GUIGene.SPACING)) + GUIGene.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 elements
+ guiOutputs[i] = new GUIOutput(this, chromosome.getOutput(i), resources, line);
+ content.getChildren().addAll(guiOutputs[i]);
+ }
+
+ content.getChildren().addAll(connectionLines);
+ setPrefWidth(620);
+ setContent(content);
+ }
+
+
+ public GUINode getGuiNode(int row, int column) {
+ return guiNodes[row][column];
+ }
+
+ public GUIInput getGuiInput(int index) {
+ return guiInputs[index];
+ }
+
+ public GUIOutput getGuiOutput(int index) {
+ return guiOutputs[index];
+ }
+
+ public Pane getContentPane() {
+ return content;
+ }
+
+}
diff --git a/src/jcgp/gui/population/GUIGene.java b/src/jcgp/gui/population/GUIGene.java
new file mode 100644
index 0000000..ef44110
--- /dev/null
+++ b/src/jcgp/gui/population/GUIGene.java
@@ -0,0 +1,56 @@
+package jcgp.gui.population;
+
+import javafx.beans.property.SimpleObjectProperty;
+import javafx.scene.Group;
+import javafx.scene.shape.Circle;
+import javafx.scene.text.Text;
+import jcgp.population.Gene;
+
+enum GUIGeneState {
+ NEUTRAL,
+ HOVER,
+ INDIRECT_HOVER,
+ ACTIVE_HOVER,
+ SOURCE,
+ TARGET,
+ NO_CHANGE_TARGET,
+ FORBIDDEN_TARGET
+}
+
+public abstract class GUIGene extends Group {
+
+ public static final double NODE_RADIUS = 30;
+ public static final double SPACING = 15;
+
+ public static final double THETA = Math.PI / 1.4;
+ public static final double SOCKET_RADIUS = Math.sqrt(NODE_RADIUS) / 1.8;
+
+ public static final double NODE_TEXT = 0;
+
+ protected Text text;
+ protected Circle mainCircle;
+
+ protected SimpleObjectProperty<GUIGeneState> stateProperty = new SimpleObjectProperty<GUIGeneState>(GUIGeneState.NEUTRAL);
+
+ protected ChromosomePane parent;
+
+ public SimpleObjectProperty<GUIGeneState> stateProperty() {
+ return stateProperty;
+ }
+
+ public void setState(GUIGeneState newState) {
+ stateProperty.set(newState);
+ }
+
+ public void showText(boolean value) {
+ text.setVisible(value);
+ }
+
+ public abstract Gene getGene();
+
+ public abstract void setConnections(GUIGeneState newState);
+
+ public abstract void resetState();
+
+
+}
diff --git a/src/jcgp/gui/population/GUIInput.java b/src/jcgp/gui/population/GUIInput.java
new file mode 100644
index 0000000..6f7a523
--- /dev/null
+++ b/src/jcgp/gui/population/GUIInput.java
@@ -0,0 +1,193 @@
+package jcgp.gui.population;
+
+import javafx.beans.value.ChangeListener;
+import javafx.beans.value.ObservableValue;
+import javafx.event.EventHandler;
+import javafx.geometry.VPos;
+import javafx.scene.input.MouseDragEvent;
+import javafx.scene.input.MouseEvent;
+import javafx.scene.paint.Paint;
+import javafx.scene.shape.Circle;
+import javafx.scene.text.Font;
+import javafx.scene.text.Text;
+import javafx.scene.text.TextAlignment;
+import jcgp.GUI;
+import jcgp.population.Input;
+import jcgp.population.Node;
+import jcgp.population.Output;
+
+public class GUIInput extends GUIGene {
+
+ private Circle outputSocket;
+ private Input input;
+
+ public GUIInput(ChromosomePane parentRef, final Input input) {
+
+ this.parent = parentRef;
+ this.input = input;
+
+ relocate(NODE_RADIUS,
+ (input.getIndex() * (2 * NODE_RADIUS + SPACING)) + NODE_RADIUS);
+
+ mainCircle = new Circle(NODE_RADIUS, Paint.valueOf("white"));
+ mainCircle.setStroke(Paint.valueOf("black"));
+
+ text = new Text("I: " + input.getIndex());
+ text.setFont(Font.font("Arial", 12));
+ text.setTextOrigin(VPos.CENTER);
+ text.setTextAlignment(TextAlignment.CENTER);
+ text.setWrappingWidth(NODE_RADIUS * 2);
+ text.setX(-NODE_RADIUS);
+ text.setVisible(true);
+
+ outputSocket = new Circle(NODE_RADIUS, 0, SOCKET_RADIUS, Paint.valueOf("white"));
+ outputSocket.setId(String.valueOf(0));
+ outputSocket.setStroke(Paint.valueOf("black"));
+
+ getChildren().addAll(mainCircle, text, outputSocket);
+
+ /*
+ * 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
+ if (event.getGestureSource() instanceof GUINode) {
+ Node source = ((GUINode) event.getGestureSource()).getGene();
+ for (int i = 0; i < (int) GUI.resources.get("arity"); i++) {
+ if (input == source.getConnection(i)) {
+ stateProperty.set(GUIGeneState.NO_CHANGE_TARGET);
+ return;
+ }
+ }
+ } else if (event.getGestureSource() instanceof GUIOutput) {
+ Output source = ((GUIOutput) event.getGestureSource()).getGene();
+ if (((GUIGene) event.getSource()).getGene() == source.getSource()) {
+ ((GUIGene) event.getSource()).setState(GUIGeneState.NO_CHANGE_TARGET);
+ }
+ }
+ stateProperty.set(GUIGeneState.TARGET);
+ }
+ });
+
+ addEventFilter(MouseDragEvent.MOUSE_DRAG_OVER, new EventHandler<MouseDragEvent>() {
+ @Override
+ public void handle(MouseDragEvent event) {
+ // the user is dragging over this node, react appropriately
+ // this happens even if we are the source of the drag
+
+ }
+ });
+
+ 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 (stateProperty.get() == GUIGeneState.NO_CHANGE_TARGET) {
+ stateProperty.set(GUIGeneState.INDIRECT_HOVER);
+ } else {
+ stateProperty.set(GUIGeneState.NEUTRAL);
+ }
+ }
+
+ });
+
+ addEventFilter(MouseDragEvent.MOUSE_DRAG_RELEASED, new EventHandler<MouseDragEvent>() {
+ @Override
+ public void handle(MouseDragEvent event) {
+ // set states to reflect the new situation
+ ((GUIGene) event.getGestureSource()).setState(GUIGeneState.NEUTRAL);
+ ((GUIGene) event.getGestureSource()).setConnections(GUIGeneState.NEUTRAL);
+ stateProperty.set(GUIGeneState.HOVER);
+ // the user released the drag gesture on this node, react appropriately
+ if (event.getGestureSource() instanceof GUINode) {
+ ((GUINode) event.getGestureSource()).setChangingConnection(input);
+ } else if (event.getGestureSource() instanceof GUIOutput) {
+ ((GUIOutput) event.getGestureSource()).getGene().setConnection(0, input);
+ }
+ }
+ });
+
+ 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 (stateProperty.get() == GUIGeneState.NEUTRAL) {
+ stateProperty.set(GUIGeneState.INDIRECT_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 (stateProperty.get() == GUIGeneState.INDIRECT_HOVER) {
+ stateProperty.set(GUIGeneState.NEUTRAL);
+ setConnections(GUIGeneState.NEUTRAL);
+ }
+ }
+ });
+
+ stateProperty.addListener(new ChangeListener<GUIGeneState>() {
+ @Override
+ public void changed(ObservableValue<? extends GUIGeneState> observable, GUIGeneState oldValue, GUIGeneState newValue) {
+
+ switch (newValue) {
+ case ACTIVE_HOVER:
+ mainCircle.setFill(Paint.valueOf(GUI.SOFT_HIGHLIGHT_COLOUR));
+ break;
+ case FORBIDDEN_TARGET:
+ mainCircle.setFill(Paint.valueOf(GUI.BAD_SELECTION_COLOUR));
+ break;
+ case HOVER:
+ mainCircle.setFill(Paint.valueOf(GUI.HARD_HIGHLIGHT_COLOUR));
+ break;
+ case INDIRECT_HOVER:
+ mainCircle.setFill(Paint.valueOf(GUI.SOFT_HIGHLIGHT_COLOUR));
+ break;
+ case NEUTRAL:
+ mainCircle.setFill(Paint.valueOf(GUI.NEUTRAL_COLOUR));
+ break;
+ case NO_CHANGE_TARGET:
+ mainCircle.setFill(Paint.valueOf(GUI.NEUTRAL_SELECTION_COLOUR));
+ break;
+ case SOURCE:
+ mainCircle.setFill(Paint.valueOf(GUI.HARD_HIGHLIGHT_COLOUR));
+ break;
+ case TARGET:
+ mainCircle.setFill(Paint.valueOf(GUI.GOOD_SELECTION_COLOUR));
+ break;
+ default:
+ break;
+
+ }
+ }
+ });
+
+ }
+
+ @Override
+ public Input getGene() {
+ return input;
+ }
+
+ /**
+ * Set all connections to a given state.
+ *
+ * @param newState the state to set connections to.
+ */
+ @Override
+ public void setConnections(GUIGeneState newState) {
+ // nothing
+ }
+
+ @Override
+ public void resetState() {
+ stateProperty.set(GUIGeneState.NEUTRAL);
+
+ }
+}
diff --git a/src/jcgp/gui/population/GUINode.java b/src/jcgp/gui/population/GUINode.java
new file mode 100644
index 0000000..4350e52
--- /dev/null
+++ b/src/jcgp/gui/population/GUINode.java
@@ -0,0 +1,417 @@
+package jcgp.gui.population;
+
+import javafx.beans.value.ChangeListener;
+import javafx.beans.value.ObservableValue;
+import javafx.event.EventHandler;
+import javafx.geometry.VPos;
+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 javafx.scene.text.Font;
+import javafx.scene.text.Text;
+import javafx.scene.text.TextAlignment;
+import jcgp.CGP.Resources;
+import jcgp.GUI;
+import jcgp.function.Function;
+import jcgp.population.Connection;
+import jcgp.population.Input;
+import jcgp.population.Node;
+import jcgp.population.Output;
+
+public class GUINode extends GUIGene {
+
+ private Circle[] sockets;
+ private Circle output;
+
+ private Line[] lines;
+
+ private Label connectionNumber;
+
+ private Node node;
+
+ private int connectionIndex;
+
+ public GUINode(ChromosomePane parentRef, final Node node, Resources resources, Line[] connectionLines) {
+
+ // store references
+ this.parent = parentRef;
+ this.node = node;
+ this.lines = connectionLines;
+
+ // move the GUIGene to the right position
+ relocate(((node.getColumn() + 1) * (2 * NODE_RADIUS + SPACING)) + NODE_RADIUS,
+ (node.getRow() * (2 * NODE_RADIUS + SPACING)) + NODE_RADIUS);
+
+ // set the line ends correctly
+ updateLines();
+
+ connectionNumber = new Label();
+ connectionNumber.setStyle("-fx-background-color:rgb(255, 255, 255); -fx-border-color:rgba(0, 0, 0, 0.5); ");
+ connectionNumber.setVisible(false);
+
+ output = new Circle(NODE_RADIUS, 0, SOCKET_RADIUS, Paint.valueOf("white"));
+ output.setStroke(Paint.valueOf("black"));
+
+ mainCircle = new Circle(NODE_RADIUS, Paint.valueOf("white"));
+ mainCircle.setStroke(Paint.valueOf("black"));
+
+ text = new Text(node.getFunction().getName());
+ this.node.functionProperty().addListener(new ChangeListener<Function>() {
+ @Override
+ public void changed(ObservableValue<? extends Function> observable,
+ Function oldValue, Function newValue) {
+ text.setText(newValue.getName());
+ }
+ });
+ text.setFont(Font.font("Arial", 12));
+ text.setTextOrigin(VPos.CENTER);
+ text.setTextAlignment(TextAlignment.CENTER);
+ text.setWrappingWidth(NODE_RADIUS * 2);
+ text.setX(-NODE_RADIUS);
+ text.setVisible(true);
+
+ sockets = new Circle[(int) resources.get("arity")];
+ double angle, xPos, yPos;
+ for (int l = 0; l < sockets.length; l++) {
+ angle = ((((double) (l + 1)) / ((double) ((int) resources.get("arity") + 1))) * THETA) - (THETA / 2);
+ xPos = -Math.cos(angle) * NODE_RADIUS;
+ yPos = Math.sin(angle) * NODE_RADIUS;
+
+ sockets[l] = new Circle(xPos, yPos, GUIGene.SOCKET_RADIUS, Paint.valueOf("white"));
+ sockets[l].setId(String.valueOf(l));
+ sockets[l].setStroke(Paint.valueOf("black"));
+
+ final Circle s = sockets[l];
+ final int index = l;
+
+ /*
+ * Mouse event handlers on sockets
+ *
+ */
+ s.addEventFilter(MouseDragEvent.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(MouseDragEvent.MOUSE_PRESSED, new EventHandler<MouseEvent>() {
+ @Override
+ public void handle(MouseEvent event) {
+ // mouse was pressed on the socket
+ stateProperty.set(GUIGeneState.SOURCE);
+ connectionIndex = index;
+ }
+ });
+
+ s.addEventFilter(MouseDragEvent.MOUSE_DRAGGED, new EventHandler<MouseEvent>() {
+ @Override
+ public void handle(MouseEvent event) {
+ lines[connectionIndex].setEndX(event.getX() + ((Circle) event.getSource()).getParent().getLayoutX());
+ lines[connectionIndex].setEndY(event.getY() + ((Circle) event.getSource()).getParent().getLayoutY());
+ }
+ });
+
+ s.addEventFilter(MouseDragEvent.MOUSE_RELEASED, new EventHandler<MouseEvent>() {
+ @Override
+ public void handle(MouseEvent event) {
+ if (event.isDragDetect()) {
+ // mouse was released before dragging out of the socket
+ updateLine(index);
+ stateProperty.set(GUIGeneState.HOVER);
+ }
+ }
+ });
+ }
+
+ /*
+ * 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
+ if (isAllowed((GUIGene) event.getGestureSource(), (GUIGene) event.getSource())) {
+ if (event.getGestureSource() instanceof GUINode) {
+ Node source = ((GUINode) event.getGestureSource()).getGene();
+ for (int i = 0; i < lines.length; i++) {
+ if (node == source.getConnection(i)) {
+ stateProperty.set(GUIGeneState.NO_CHANGE_TARGET);
+ return;
+ }
+ }
+ } else if (event.getGestureSource() instanceof GUIOutput) {
+ Output source = ((GUIOutput) event.getGestureSource()).getGene();
+ if (((GUIGene) event.getSource()).getGene() == source.getSource()) {
+ ((GUIGene) event.getSource()).setState(GUIGeneState.NO_CHANGE_TARGET);
+ }
+ }
+ stateProperty.set(GUIGeneState.TARGET);
+ } else {
+ stateProperty.set(GUIGeneState.FORBIDDEN_TARGET);
+ }
+ }
+ });
+
+ addEventFilter(MouseDragEvent.MOUSE_DRAG_OVER, new EventHandler<MouseDragEvent>() {
+ @Override
+ public void handle(MouseDragEvent event) {
+ // the user is dragging over this node, react appropriately
+ // this happens even if we are the source of the drag
+
+ }
+ });
+
+ 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()) {
+ stateProperty.set(GUIGeneState.SOURCE);
+ } else {
+ if (stateProperty.get() == GUIGeneState.NO_CHANGE_TARGET) {
+ stateProperty.set(GUIGeneState.INDIRECT_HOVER);
+ } else {
+ stateProperty.set(GUIGeneState.NEUTRAL);
+ }
+ }
+ }
+ }
+ });
+
+ addEventFilter(MouseDragEvent.MOUSE_DRAG_RELEASED, new EventHandler<MouseDragEvent>() {
+ @Override
+ public void handle(MouseDragEvent event) {
+ // set states to reflect the new situation
+ ((GUIGene) event.getGestureSource()).setState(GUIGeneState.NEUTRAL);
+ ((GUIGene) event.getGestureSource()).setConnections(GUIGeneState.NEUTRAL);
+ stateProperty.set(GUIGeneState.HOVER);
+ // the user released the drag gesture on this node, react appropriately
+ if (isAllowed((GUIGene) event.getGestureSource(), (GUIGene) event.getSource())) {
+ if (event.getGestureSource() instanceof GUINode) {
+ ((GUINode) event.getGestureSource()).setChangingConnection(node);
+ } else if (event.getGestureSource() instanceof GUIOutput) {
+ ((GUIOutput) event.getGestureSource()).getGene().setConnection(0, node);
+ }
+ }
+ if (event.getGestureSource() instanceof GUINode) {
+ ((GUINode) event.getGestureSource()).updateLines();
+ } else if (event.getGestureSource() instanceof GUIOutput) {
+ ((GUIOutput) event.getGestureSource()).updateLine();
+ }
+
+ }
+ });
+
+
+ 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 (stateProperty.get() == GUIGeneState.NEUTRAL) {
+ stateProperty.set(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 (stateProperty.get() == GUIGeneState.HOVER) {
+ stateProperty.set(GUIGeneState.NEUTRAL);
+ setConnections(GUIGeneState.NEUTRAL);
+ }
+ }
+ });
+
+ getChildren().addAll(mainCircle, text);
+ getChildren().addAll(sockets);
+ getChildren().addAll(output, connectionNumber);
+
+ stateProperty.addListener(new ChangeListener<GUIGeneState>() {
+ @Override
+ public void changed(ObservableValue<? extends GUIGeneState> observable, GUIGeneState oldValue, GUIGeneState newValue) {
+
+ switch (newValue) {
+ case ACTIVE_HOVER:
+ mainCircle.setFill(Paint.valueOf(GUI.SOFT_HIGHLIGHT_COLOUR));
+ showLines(true);
+ setConnections(GUIGeneState.ACTIVE_HOVER);
+ break;
+ case FORBIDDEN_TARGET:
+ mainCircle.setFill(Paint.valueOf(GUI.BAD_SELECTION_COLOUR));
+ break;
+ case HOVER:
+ mainCircle.setFill(Paint.valueOf(GUI.HARD_HIGHLIGHT_COLOUR));
+ showLines(true);
+ setConnections(GUIGeneState.INDIRECT_HOVER);
+ break;
+ case INDIRECT_HOVER:
+ mainCircle.setFill(Paint.valueOf(GUI.SOFT_HIGHLIGHT_COLOUR));
+ break;
+ case NEUTRAL:
+ mainCircle.setFill(Paint.valueOf(GUI.NEUTRAL_COLOUR));
+ showLines(false);
+ if (oldValue == GUIGeneState.ACTIVE_HOVER) {
+ setConnections(GUIGeneState.NEUTRAL);
+ }
+ break;
+ case NO_CHANGE_TARGET:
+ mainCircle.setFill(Paint.valueOf(GUI.NEUTRAL_SELECTION_COLOUR));
+ break;
+ case SOURCE:
+ mainCircle.setFill(Paint.valueOf(GUI.HARD_HIGHLIGHT_COLOUR));
+ break;
+ case TARGET:
+ mainCircle.setFill(Paint.valueOf(GUI.GOOD_SELECTION_COLOUR));
+ break;
+ default:
+ break;
+
+ }
+ }
+ });
+
+ for (int c = 0; c < lines.length; c++) {
+ final int i = c;
+ node.connections().get(c).addListener(new ChangeListener<Connection>() {
+ @Override
+ public void changed(ObservableValue<? extends Connection> observable,
+ Connection oldValue, Connection newValue) {
+ updateLine(i);
+ }
+ });
+ }
+
+ }
+
+
+ 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).getGene(), s = ((GUINode) source).getGene();
+ if (s.getColumn() - t.getColumn() > 0 && s.getColumn() - t.getColumn() <= (int) GUI.resources.get("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.");
+ }
+
+
+ /**
+ * Place the end of the specified line on the output of the associated connection.
+ *
+ * @param index the line to be updated.
+ */
+ 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 * NODE_RADIUS + SPACING)) + 2 * NODE_RADIUS);
+ lines[index].setEndY((row * (2 * NODE_RADIUS + SPACING)) + NODE_RADIUS);
+ } else if (node.getConnection(index) instanceof Input) {
+ int inputIndex = ((Input) node.getConnection(index)).getIndex();
+ lines[index].setEndX(2 * NODE_RADIUS);
+ lines[index].setEndY(inputIndex * (2 * NODE_RADIUS + SPACING) + NODE_RADIUS);
+ }
+ }
+
+ /**
+ * Updates the end of all lines to match the associated connections.
+ */
+ public void updateLines() {
+ for (int c = 0; c < lines.length; c++) {
+ updateLine(c);
+ }
+ }
+
+ /**
+ * Toggle visibility of all connection lines.
+ *
+ * @param value whether to show the lines or not.
+ */
+ private void showLines(boolean value) {
+ for (int i = 0; i < lines.length; i++) {
+ lines[i].setVisible(value);
+ }
+ }
+
+ @Override
+ public Node getGene() {
+ return node;
+ }
+
+
+ @Override
+ public void setConnections(GUIGeneState newState) {
+ for (int i = 0; i < lines.length; i++) {
+ if (node.getConnection(i) instanceof Node) {
+ parent.getGuiNode(((Node) node.getConnection(i)).getRow(), ((Node) node.getConnection(i)).getColumn()).setState(newState);
+ } else if (node.getConnection(i) instanceof Input) {
+ parent.getGuiInput(((Input) node.getConnection(i)).getIndex()).setState(newState);
+ }
+ }
+ }
+
+ /**
+ * This method sets the connection currently being changed to the
+ * specified value.
+ */
+ public void setChangingConnection(Connection newConnection) {
+ node.setConnection(connectionIndex, newConnection);
+ }
+
+
+ @Override
+ public void resetState() {
+ stateProperty.set(GUIGeneState.NEUTRAL);
+ setConnections(GUIGeneState.NEUTRAL);
+ }
+
+}
diff --git a/src/jcgp/gui/population/GUIOutput.java b/src/jcgp/gui/population/GUIOutput.java
new file mode 100644
index 0000000..602a022
--- /dev/null
+++ b/src/jcgp/gui/population/GUIOutput.java
@@ -0,0 +1,277 @@
+package jcgp.gui.population;
+
+import javafx.beans.value.ChangeListener;
+import javafx.beans.value.ObservableValue;
+import javafx.event.EventHandler;
+import javafx.geometry.VPos;
+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 javafx.scene.text.Font;
+import javafx.scene.text.Text;
+import javafx.scene.text.TextAlignment;
+import jcgp.GUI;
+import jcgp.CGP.Resources;
+import jcgp.population.Connection;
+import jcgp.population.Input;
+import jcgp.population.Node;
+import jcgp.population.Output;
+
+public class GUIOutput extends GUIGene {
+
+ private Circle socket;
+
+ private Label connectionLabel;
+
+ private Line sourceLine;
+
+ private Output output;
+
+ public GUIOutput(ChromosomePane parentRef, final Output output, Resources resources, Line line) {
+
+ this.parent = parentRef;
+ this.output = output;
+ this.sourceLine = line;
+
+ relocate((((int) resources.get("columns") + 1) * (2 * NODE_RADIUS + SPACING)) + NODE_RADIUS,
+ (output.getIndex() * (2 * NODE_RADIUS + SPACING)) + NODE_RADIUS);
+
+ // set the line ends correctly
+ updateLine();
+
+ mainCircle = new Circle(NODE_RADIUS, Paint.valueOf("white"));
+ mainCircle.setStroke(Paint.valueOf("black"));
+
+ text = new Text("O: " + output.getIndex());
+ text.setFont(Font.font("Arial", 12));
+ text.setTextOrigin(VPos.CENTER);
+ text.setTextAlignment(TextAlignment.CENTER);
+ text.setWrappingWidth(NODE_RADIUS * 2);
+ text.setX(-NODE_RADIUS);
+ text.setVisible(true);
+
+ socket = new Circle(-NODE_RADIUS, 0, SOCKET_RADIUS, Paint.valueOf("white"));
+ socket.setId(String.valueOf(0));
+ socket.setStroke(Paint.valueOf("black"));
+
+ 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(MouseDragEvent.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(MouseDragEvent.MOUSE_PRESSED, new EventHandler<MouseEvent>() {
+ @Override
+ public void handle(MouseEvent event) {
+ // mouse was pressed on the socket
+ stateProperty.set(GUIGeneState.SOURCE);
+ }
+ });
+
+ socket.addEventFilter(MouseDragEvent.MOUSE_DRAGGED, new EventHandler<MouseEvent>() {
+ @Override
+ public void handle(MouseEvent event) {
+ sourceLine.setEndX(event.getX() + ((Circle) event.getSource()).getParent().getLayoutX());
+ sourceLine.setEndY(event.getY() + ((Circle) event.getSource()).getParent().getLayoutY());
+ }
+ });
+
+ socket.addEventFilter(MouseDragEvent.MOUSE_RELEASED, new EventHandler<MouseEvent>() {
+ @Override
+ public void handle(MouseEvent event) {
+ if (event.isDragDetect()) {
+ // mouse was released before dragging out of the socket
+ updateLine();
+ stateProperty.set(GUIGeneState.HOVER);
+ }
+ }
+ });
+
+
+ /*
+ * 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
+ stateProperty.set(GUIGeneState.FORBIDDEN_TARGET);
+ }
+ });
+
+ addEventFilter(MouseDragEvent.MOUSE_DRAG_OVER, new EventHandler<MouseDragEvent>() {
+ @Override
+ public void handle(MouseDragEvent event) {
+ // the user is dragging over this node, react appropriately
+ // this happens even if we are the source of the drag
+
+ }
+ });
+
+ 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()) {
+ stateProperty.set(GUIGeneState.SOURCE);
+ } else {
+ if (stateProperty.get() == GUIGeneState.NO_CHANGE_TARGET) {
+ stateProperty.set(GUIGeneState.INDIRECT_HOVER);
+ } else {
+ stateProperty.set(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) event.getGestureSource()).setState(GUIGeneState.NEUTRAL);
+ ((GUIGene) event.getGestureSource()).setConnections(GUIGeneState.NEUTRAL);
+ stateProperty.set(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 (stateProperty.get() == GUIGeneState.NEUTRAL) {
+ stateProperty.set(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 (stateProperty.get() == GUIGeneState.HOVER) {
+ stateProperty.set(GUIGeneState.NEUTRAL);
+ setConnections(GUIGeneState.NEUTRAL);
+ }
+ }
+ });
+
+
+ getChildren().addAll(mainCircle, text, socket, connectionLabel);
+
+ stateProperty.addListener(new ChangeListener<GUIGeneState>() {
+ @Override
+ public void changed(ObservableValue<? extends GUIGeneState> observable, GUIGeneState oldValue, GUIGeneState newValue) {
+
+
+ switch (newValue) {
+ case ACTIVE_HOVER:
+ break;
+ case FORBIDDEN_TARGET:
+ mainCircle.setFill(Paint.valueOf(GUI.BAD_SELECTION_COLOUR));
+ break;
+ case HOVER:
+ mainCircle.setFill(Paint.valueOf(GUI.HARD_HIGHLIGHT_COLOUR));
+ sourceLine.setVisible(true);
+ setConnections(GUIGeneState.ACTIVE_HOVER);
+ break;
+ case INDIRECT_HOVER:
+ mainCircle.setFill(Paint.valueOf(GUI.SOFT_HIGHLIGHT_COLOUR));
+ break;
+ case NEUTRAL:
+ mainCircle.setFill(Paint.valueOf(GUI.NEUTRAL_COLOUR));
+ sourceLine.setVisible(false);
+ break;
+ case NO_CHANGE_TARGET:
+ mainCircle.setFill(Paint.valueOf(GUI.NEUTRAL_SELECTION_COLOUR));
+ break;
+ case SOURCE:
+ mainCircle.setFill(Paint.valueOf(GUI.HARD_HIGHLIGHT_COLOUR));
+ break;
+ case TARGET:
+ mainCircle.setFill(Paint.valueOf(GUI.GOOD_SELECTION_COLOUR));
+ break;
+ default:
+ break;
+
+ }
+ }
+ });
+
+ output.sourceProperty().addListener(new ChangeListener<Connection>() {
+ @Override
+ public void changed(ObservableValue<? extends Connection> observable,
+ Connection oldValue, Connection newValue) {
+ updateLine();
+ }
+ });
+
+ }
+
+ public void updateLine() {
+ if (output.getSource() instanceof Node) {
+ int row = ((Node) output.getSource()).getRow(),
+ column = ((Node) output.getSource()).getColumn();
+ sourceLine.setEndX(((column + 1) * (2 * NODE_RADIUS + SPACING)) + 2 * NODE_RADIUS);
+ sourceLine.setEndY((row * (2 * NODE_RADIUS + SPACING)) + NODE_RADIUS);
+ } else if (output.getSource() instanceof Input) {
+ int inputIndex = ((Input) output.getSource()).getIndex();
+ sourceLine.setEndX(2 * NODE_RADIUS);
+ sourceLine.setEndY(inputIndex * (2 * NODE_RADIUS + SPACING) + NODE_RADIUS);
+ }
+ }
+
+ @Override
+ public Output getGene() {
+ return output;
+ }
+
+ @Override
+ public void setConnections(GUIGeneState newState) {
+ if (output.getSource() instanceof Node) {
+ parent.getGuiNode(((Node) output.getSource()).getRow(), ((Node) output.getSource()).getColumn()).setState(newState);
+ } else if (output.getSource() instanceof Input) {
+ parent.getGuiInput(((Input) output.getSource()).getIndex()).setState(newState);
+ }
+ }
+
+ @Override
+ public void resetState() {
+ stateProperty.set(GUIGeneState.NEUTRAL);
+ setConnections(GUIGeneState.NEUTRAL);
+ }
+}