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();  		}  	} | 
