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/jcgp/gui/population | |
parent | 1e7d7a9b09ca9d24b56cf33a168953ca033d7c53 (diff) |
Diffstat (limited to 'src/jcgp/gui/population')
-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 |
9 files changed, 1095 insertions, 447 deletions
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(); } } |