From c35a6806df01481c1b169cd0fc47660ea1cc10fb Mon Sep 17 00:00:00 2001 From: Eduardo Pedroni Date: Wed, 14 May 2014 01:32:51 +0100 Subject: Refactored Mutable, commented a little bit of the GUI package --- src/jcgp/backend/modules/mutator/PointMutator.java | 42 +--- .../modules/mutator/ProbabilisticMutator.java | 4 +- src/jcgp/backend/parsers/ChromosomeParser.java | 2 +- src/jcgp/backend/population/Chromosome.java | 26 +-- src/jcgp/backend/population/Mutable.java | 22 +- src/jcgp/backend/population/Node.java | 41 +++- src/jcgp/backend/population/Output.java | 51 +++-- src/jcgp/backend/tests/ChromosomeTests.java | 6 +- src/jcgp/backend/tests/OutputTests.java | 4 +- src/jcgp/gui/GUI.java | 247 ++++++++++++++++++--- src/jcgp/gui/console/ConsolePane.java | 123 ++++++++++ src/jcgp/gui/console/GUIConsole.java | 123 ---------- src/jcgp/gui/population/GUIGene.java | 5 - src/jcgp/gui/population/GUIOutput.java | 2 +- src/jcgp/gui/settings/parameters/GUIParameter.java | 2 - 15 files changed, 455 insertions(+), 245 deletions(-) create mode 100644 src/jcgp/gui/console/ConsolePane.java delete mode 100644 src/jcgp/gui/console/GUIConsole.java (limited to 'src/jcgp') diff --git a/src/jcgp/backend/modules/mutator/PointMutator.java b/src/jcgp/backend/modules/mutator/PointMutator.java index 5aba0d1..6ba3e10 100644 --- a/src/jcgp/backend/modules/mutator/PointMutator.java +++ b/src/jcgp/backend/modules/mutator/PointMutator.java @@ -4,8 +4,6 @@ import jcgp.backend.parameters.BooleanParameter; import jcgp.backend.parameters.IntegerParameter; import jcgp.backend.population.Chromosome; import jcgp.backend.population.Mutable; -import jcgp.backend.population.Node; -import jcgp.backend.population.Output; import jcgp.backend.resources.Resources; /** @@ -40,43 +38,13 @@ public abstract class PointMutator extends Mutator { // for however many genes must be mutated for (int i = 0; i < genesMutated.get(); i++) { + // choose a random mutable + Mutable mutable = chromosome.getRandomMutable(); - Mutable m = chromosome.getRandomMutable(); + if (report.get()) getResources().report("[Mutator] Mutation " + i + " selected " + mutable); - if (report.get()) getResources().report("[Mutator] Mutation " + i + " selected " + m + ", "); - - // outputs and nodes are mutated differently - if (m instanceof Output) { - if (report.get()) getResources().report("changed source from " + ((Output) m).getSource() + " "); - - // outputs are easy, simply set to a different random connection, any will do - m.setConnection(0, chromosome.getRandomConnection()); - - if (report.get()) getResources().reportln("to " + ((Output) m).getSource()); - } else if (m instanceof Node) { - /* nodes are more complicated, first we must decide whether to mutate the function - * or a connection - * we do this by generating a random int between 0 and 1 + arity - */ - int geneType = getResources().getRandomInt(1 + getResources().arity()); - - // if the int is less than 1, mutate function, else mutate connections - if (geneType < 1) { - if (report.get()) getResources().report("changed function from " + ((Node) m).getFunction() + " "); - - ((Node) m).setFunction(getResources().getRandomFunction()); - - if (report.get()) getResources().reportln("to " + ((Node) m).getFunction()); - } else { - // if we decided to mutate connection, subtract 1 from geneType so it fits into the arity range - geneType--; - if (report.get()) getResources().report("changed connection " + geneType + " from " + ((Node) m).getConnection(geneType) + " "); - - m.setConnection(geneType, chromosome.getRandomConnection(((Node) m).getColumn())); - - if (report.get()) getResources().reportln("to " + ((Node) m).getConnection(geneType)); - } - } + // mutate a random gene + mutable.mutate(); } } diff --git a/src/jcgp/backend/modules/mutator/ProbabilisticMutator.java b/src/jcgp/backend/modules/mutator/ProbabilisticMutator.java index 9273558..1ea1373 100644 --- a/src/jcgp/backend/modules/mutator/ProbabilisticMutator.java +++ b/src/jcgp/backend/modules/mutator/ProbabilisticMutator.java @@ -93,13 +93,13 @@ public class ProbabilisticMutator extends Mutator { if (report.get()) getResources().report("[Mutator] Mutating " + out + ", changed source from " + out.getSource()); - out.setConnection(0, chromosome.getRandomConnection()); + out.setSource(chromosome.getRandomConnection()); if (report.get()) getResources().reportln("to " + out.getSource()); } } - if (report.get()) getResources().reportln("[Mutator] Mutation finished "); + if (report.get()) getResources().reportln("[Mutator] Mutation finished"); } diff --git a/src/jcgp/backend/parsers/ChromosomeParser.java b/src/jcgp/backend/parsers/ChromosomeParser.java index 6106dbd..b892182 100644 --- a/src/jcgp/backend/parsers/ChromosomeParser.java +++ b/src/jcgp/backend/parsers/ChromosomeParser.java @@ -117,7 +117,7 @@ public abstract class ChromosomeParser { newConnection = chromosome.getNode((gene - resources.inputs()) % resources.rows(), (gene - resources.inputs()) / resources.rows()); } - chromosome.getOutput(o).setConnection(0, newConnection); + chromosome.getOutput(o).setSource(newConnection); } in.close(); diff --git a/src/jcgp/backend/population/Chromosome.java b/src/jcgp/backend/population/Chromosome.java index 673bb26..e28032c 100644 --- a/src/jcgp/backend/population/Chromosome.java +++ b/src/jcgp/backend/population/Chromosome.java @@ -149,7 +149,7 @@ public class Chromosome implements Comparable { // set random outputs for (Output output : outputs) { - output.setConnection(0, getRandomConnection()); + output.setSource(getRandomConnection()); } } @@ -198,9 +198,9 @@ public class Chromosome implements Comparable { for (int o = 0; o < outputs.length; o++) { copyOutput = clone.getOutput(o).getSource(); if (copyOutput instanceof Input) { - outputs[o].setConnection(0, inputs[((Input) copyOutput).getIndex()]); + outputs[o].setSource(inputs[((Input) copyOutput).getIndex()]); } else if (copyOutput instanceof Node) { - outputs[o].setConnection(0, nodes[((Node) copyOutput).getRow()][((Node) copyOutput).getColumn()]); + outputs[o].setSource(nodes[((Node) copyOutput).getRow()][((Node) copyOutput).getColumn()]); } else { // something bad happened System.out.println("Warning: Connection of subtype " + copyOutput.getClass().toString() + " is not explicitly handled by copy constructor."); @@ -211,6 +211,16 @@ public class Chromosome implements Comparable { this.fitness = clone.getFitness(); } + /** + * Returns a reference to the indexed input. + * + * @param index the input index. + * @return the input reference. + */ + public Input getInput(int index) { + return inputs[index]; + } + /** * Returns a reference to any node, addressed by row and column. * @@ -232,16 +242,6 @@ public class Chromosome implements Comparable { return outputs[index]; } - /** - * Returns a reference to the indexed input. - * - * @param index the input index. - * @return the input reference. - */ - public Input getInput(int index) { - return inputs[index]; - } - /** * @return the fitness of the chromosome. */ diff --git a/src/jcgp/backend/population/Mutable.java b/src/jcgp/backend/population/Mutable.java index 3ce7065..a5cbe37 100644 --- a/src/jcgp/backend/population/Mutable.java +++ b/src/jcgp/backend/population/Mutable.java @@ -8,8 +8,8 @@ package jcgp.backend.population; *

* This interface provides a way to deal with mutable elements * generically without having to specify whether they are nodes - * or outputs. In this way a random mutable element can be picked and - * dealt with more easily, facilitating mutations. + * or outputs. When mutating a mutable, {@code mutate()} is guaranteed + * to perform a fair mutation. * * @author Eduardo Pedroni * @@ -17,14 +17,16 @@ package jcgp.backend.population; public interface Mutable { /** - * This method sets the indexed connection to the specified new connection. - * Implementing classes may choose to ignore the given index (such as in the - * case of outputs, which only have one connection). - * - * @param index the connection index to set. - * @param newConnection the chromosome element to connect to. + * This method performs an arbitrary mutation on the {@code Mutable}. + *

+ * In the case of nodes, this chooses to mutate a function or connection + * fairly, and carries out the required mutation by using the node's own + * reference to chromosome. + *

+ * In the case of outputs, this simply picks a random connection to serve + * as the source - any connection is allowed. */ - public void setConnection(int index, Connection newConnection); + public void mutate(); /** * Asserts if the specified element is a copy of the elements @@ -48,6 +50,6 @@ public interface Mutable { * @param element the mutable element to compare to. * @return true if {@code element} is a copy of this element. */ - boolean copyOf(Mutable element); + public boolean copyOf(Mutable element); } diff --git a/src/jcgp/backend/population/Node.java b/src/jcgp/backend/population/Node.java index 3bcf3da..7712c50 100644 --- a/src/jcgp/backend/population/Node.java +++ b/src/jcgp/backend/population/Node.java @@ -3,6 +3,7 @@ package jcgp.backend.population; import java.util.ArrayList; import jcgp.backend.function.Function; +import jcgp.backend.resources.Resources; /** * Nodes make up the main part of the chromosome, @@ -16,6 +17,10 @@ import jcgp.backend.function.Function; * (determined by the maximum arity of the function set) * and must be reinstantiated if the experiment arity * changes. + *

+ * When mutating a node, it is easiest to use {@code mutate()}. + * Alternatively, you may also perform a specific mutation using + * {@code setConnection(...)} and {@code setFunction(...)}. * * @author Eduardo Pedroni * @@ -122,6 +127,24 @@ public class Node implements Mutable, Connection { } } } + + /** + * This method sets the indexed connection to the specified new connection. + * If the given connection is null or disrespects levels back, it is discarded + * and no connections are changed. + * + * @param index the connection index to set. + * @param newConnection the {@code Connection} to connect to. + */ + public void setConnection(int index, Connection newConnection) { + // connection must not be null + if (newConnection != null) { + //if () { + connections[index] = newConnection; + chromosome.recomputeActiveNodes(); + //} + } + } @Override public boolean copyOf(Mutable element) { @@ -174,11 +197,19 @@ public class Node implements Mutable, Connection { } @Override - public void setConnection(int index, Connection newConnection) { - // connection must not be null - if (newConnection != null) { - connections[index] = newConnection; - chromosome.recomputeActiveNodes(); + public void mutate() { + Resources resources = chromosome.getResources(); + + // choose to mutate the function or a connection + int geneType = resources.getRandomInt(1 + resources.arity()); + + // if the int is less than 1, mutate function, else mutate connections + if (geneType < 1) { + setFunction(resources.getRandomFunction()); + } else { + // if we decided to mutate connection, subtract 1 from geneType so it fits into the arity range + geneType--; + setConnection(geneType, chromosome.getRandomConnection(column)); } } diff --git a/src/jcgp/backend/population/Output.java b/src/jcgp/backend/population/Output.java index 938741b..a346d4a 100644 --- a/src/jcgp/backend/population/Output.java +++ b/src/jcgp/backend/population/Output.java @@ -8,6 +8,10 @@ import java.util.ArrayList; * returns the value of its single connection, but it * may not be connected to - it terminates a chromosome * active connection path. + *

+ * When mutating an output, it is easiest to use {@code mutate()}. + * Alternatively, you may also perform a specific mutation using + * {@code setSource(...)}. * * @author Eduardo Pedroni * @@ -37,34 +41,47 @@ public class Output implements Mutable { return source.getValue(); } + /** + * @return this output's index. + */ public int getIndex() { return index; } + + /** + * This method sets the output source to the specified connection. + * + * @param newConnection the {@code Connection} to connect to. + */ + public void setSource(Connection newConnection) { + source = newConnection; + // trigger active path recomputation + chromosome.recomputeActiveNodes(); + } + /** + * @return the source of this output's value. + */ public Connection getSource() { return source; } + /** + * Calls {@code getActive(...)} on this output's + * source. This kicks off a recursive process whereby + * all nodes connected to this output are added to the + * specified list of nodes. This is used to create a + * list of all active nodes. + * + * @param activeNodes the list to add all active nodes to. + */ public void getActiveNodes(ArrayList activeNodes) { + // do not add if the source is an input if (source instanceof Node) { ((Node) source).getActive(activeNodes); } } - /** - * When mutating an output, the index parameter - * is simply ignored and the output source is - * set. - * - * @see jcgp.backend.population.Mutable#setConnection(int, jcgp.backend.population.Connection) - */ - @Override - public void setConnection(int index, Connection newConnection) { - source = newConnection; - // trigger active path recomputation - chromosome.recomputeActiveNodes(); - } - @Override public boolean copyOf(Mutable m) { // both cannot be the same instance @@ -93,6 +110,12 @@ public class Output implements Mutable { return false; } + @Override + public void mutate() { + // simply change output to a new, random connection + setSource(chromosome.getRandomConnection()); + } + @Override public String toString() { return "Output " + index; diff --git a/src/jcgp/backend/tests/ChromosomeTests.java b/src/jcgp/backend/tests/ChromosomeTests.java index b5c1da7..7cbf2e2 100644 --- a/src/jcgp/backend/tests/ChromosomeTests.java +++ b/src/jcgp/backend/tests/ChromosomeTests.java @@ -136,7 +136,7 @@ public class ChromosomeTests { } // mutate an output in clone, check that the same node in chromosome produces a different output - clone.getOutput(1).setConnection(resources.getRandomInt(resources.arity()), clone.getInput(2)); + clone.getOutput(1).setSource(clone.getInput(2)); assertTrue("Mutation affected nodes in both chromosomes.", clone.getOutput(1).calculate() != chromosome.getOutput(1).calculate()); @@ -332,8 +332,8 @@ public class ChromosomeTests { c.getNode(1, 1).initialise(resources.getFunction(0), c.getNode(0, 0), c.getInput(1)); c.getNode(1, 2).initialise(resources.getFunction(0), c.getNode(1, 1), c.getInput(2)); - c.getOutput(0).setConnection(0, c.getNode(0, 0)); - c.getOutput(1).setConnection(0, c.getNode(1, 2)); + c.getOutput(0).setSource(c.getNode(0, 0)); + c.getOutput(1).setSource(c.getNode(1, 2)); return c; } diff --git a/src/jcgp/backend/tests/OutputTests.java b/src/jcgp/backend/tests/OutputTests.java index 8cc10a8..c5aa6b4 100644 --- a/src/jcgp/backend/tests/OutputTests.java +++ b/src/jcgp/backend/tests/OutputTests.java @@ -47,7 +47,7 @@ public class OutputTests { @Test public void evaluationsTest() { // set source connection, check that the appropriate value is returned - output.setConnection(0, new Connection() { + output.setSource(new Connection() { @Override public Object getValue() { @@ -70,7 +70,7 @@ public class OutputTests { return 0; } }; - output.setConnection(0, newConn); + output.setSource(newConn); assertTrue("Incorrect connection returned.", output.getSource() == newConn); } diff --git a/src/jcgp/gui/GUI.java b/src/jcgp/gui/GUI.java index 437a739..2f09dea 100644 --- a/src/jcgp/gui/GUI.java +++ b/src/jcgp/gui/GUI.java @@ -13,7 +13,7 @@ import javafx.stage.Stage; import javafx.stage.WindowEvent; import jcgp.JCGP; import jcgp.backend.modules.problem.TestCaseProblem.TestCase; -import jcgp.gui.console.GUIConsole; +import jcgp.gui.console.ConsolePane; import jcgp.gui.dragresize.HorizontalDragResize; import jcgp.gui.dragresize.VerticalDragResize; import jcgp.gui.population.FunctionSelector; @@ -22,8 +22,25 @@ import jcgp.gui.population.PopulationPane; import jcgp.gui.settings.SettingsPane; /** - * Main class for the graphical user interface (GUI) - * + * Main class for the graphical user interface (GUI). + *

+ * This class declares the main method used when running the GUI. + * In addition, all main GUI panes are declared and instantiated here. + *

+ * The user interface is divided into 3 main components: the node grid + * ({@link PopulationPane}), the control pane ({@link SettingsPane}) and + * the console ({@link ConsolePane}). Click on any of the links in + * brackets to see more information about each interface component. + *

+ * This class also contains the instance of JCGP responsible for + * running the experiments in GUI mode. JCGP's execution must be delegated + * to a separate thread so that the GUI remains unblocked. This is done using + * a JavaFX {@code Service} which calls {@code nextGeneration()} in a loop + * until it is interrupted by the main JavaFX thread. + *
+ * This service also handles flushing the console in a thread safe way. This + * is done by synchronizing the {@code nextGeneration()} and {@code flush()} + * method calls on a lock object. * * @author Eduardo Pedroni * @@ -31,72 +48,213 @@ import jcgp.gui.settings.SettingsPane; public class GUI extends Application { /* Colours */ + /** + * A string containing the hexadecimal colour used for representing neutrality. + */ public static final String NEUTRAL_COLOUR = "#FFFFFF"; + /** + * A string containing the hexadecimal colour used for representing a hard highlight. + * A "hard" select, for instance, happens when an output path is locked on the chromosome + * pane. + */ public static final String HARD_HIGHLIGHT_COLOUR = "#5496FF"; + /** + * A string containing the hexadecimal colour used for a medium highlight. + * One example of such a selection is the colour applied to a node + * when it is hovered over. + */ public static final String MEDIUM_HIGHLIGHT_COLOUR = "#75BAFF"; + /** + * A string containing the hexadecimal colour used for a soft highlight. + * When hovering over a node, its connections are soft-selected. + */ public static final String SOFT_HIGHLIGHT_COLOUR = "#C7DFFF"; + /** + * A string containing the hexadecimal colour used for representing a good selection. + * Ideally a shade of green, used for instance when a manual connection is valid. + */ public static final String GOOD_SELECTION_COLOUR = "#38C25B"; + /** + * A string containing the hexadecimal colour used for representing a neutral selection. + * Ideally a shade of yellow, used for instance when a manual connection is already the + * current connection. + */ public static final String NEUTRAL_SELECTION_COLOUR = "#FFEF73"; + /** + * A string containing the hexadecimal colour used for representing a bad selection. + * Ideally a shade of red, use for instance when a manual connection is not valid. + */ public static final String BAD_SELECTION_COLOUR = "#FF5C5C"; /* Sizes and distances */ + /** + * The width or height of the area that can be clicked on + * to drag-resize a pane. + */ public static final double RESIZE_MARGIN = 5.0; + /** + * The minimum width of the settings pane, to prevent it + * from being resized beyond visibility. + */ public static final double SETTINGS_MIN_WIDTH = 200; + /** + * The minimum width of the console pane, to prevent it + * from being resized beyond visibility. + */ public static final double CONSOLE_MIN_HEIGHT = 100; - private final JCGP jcgp; - + /* + * Actual GUI elements + */ private Stage stage; - - private final FunctionSelector functionSelector; - private PopulationPane populationPane; - - private GUIConsole console; - + private ConsolePane console; private SettingsPane settingsPane; + private final FunctionSelector functionSelector; + /* + * Flow control objects + */ private boolean running = false; - private final Object printLock = new Object(); + private Service jcgpService; + private Runnable consoleFlush; - private Service cgpService; + /* + * The experiment itself + */ + private final JCGP jcgp; - private Runnable consoleFlush; + /** + * Start JCGP with the user interface. + * + * @param args no arguments are used. + */ public static void main(String[] args) { + // not much to do, simply launch the JavaFX application launch(); } + /** + * Makes a new instance of GUI. This initialises the JCGP experiment and + * instantiates the function selector. It also creates the console flush task + * and the service responsible for running the JCGP experiment. + */ public GUI() { jcgp = new JCGP(); functionSelector = new FunctionSelector(jcgp.getResources().getFunctionSet()); + /* + * This task flushes the console in a thread-safe way. + * The problem is that this task is executed using Platform.runLater() + * to ensure that the flush itself happens on the JavaFX thread. However, + * runLater() is not guaranteed to run anytime soon. If the time taken for + * jcgp to perform a single generation is shorter than the time taken for + * this task to be executed by the platform, consoleFlush tasks will be + * scheduled faster than they can be executed and the console will eventually + * freeze. + * + * This is addressed by synchronizing the flushes with each nextGeneration() call. + */ consoleFlush = new Runnable() { @Override public void run() { + /* + * Try to acquire printlock - wait here until jcgpService relinquishes it + * by calling wait(). This means that it is finished with the current generation + * and will wait for the console to be flushed to move on. + * It might be the case that the service has already released the lock by waiting + * on it; it makes no difference. In that case this will acquire the lock + * immediately and proceed to flush the console. + */ synchronized(printLock) { + /* + * The lock is acquired, at this point we are certain that jcgpService + * cannot execute; it is currently waiting to be notified about the lock. + * No additional consoleFlush tasks can be scheduled with runLater() because + * the service is waiting. We can now take our time to flush the console. + */ console.flush(); + /* + * Once the console finishes flushing, we notify jcgpService to perform the + * next generation. + */ printLock.notifyAll(); } } }; - cgpService = new Service () { + /* + * This service runs on a separate thread and performs + * the experiment, including console prints, in a thread-safe + * way. It is synchronized with consoleFlush. + */ + jcgpService = new Service () { @Override protected Task createTask() { Task t = new Task() { @Override protected Void call() throws Exception { + /* + * Only execute if the experiment isn't finished + * and the service hasn't been cancelled. + */ while (!isCancelled() && !jcgp.isFinished()) { + /* + * Attempt to acquire the printlock. + * Successfully doing so means no printing + * is currently taking place and we are free + * to schedule a print task. + * This lock acquisition should never block. It should + * not be possible to execute this statement without + * having been notified by consoleFlush. + */ synchronized (printLock) { + /* + * Lock has been acquired, schedule a print + * task ahead of time. The actual print messages + * haven't been send to the console yet, that happens + * during nextGeneration(), but since we have the lock + * consoleFlush() will sit and wait for us to release it + * whenever we are finished queueing prints. + */ Platform.runLater(consoleFlush); + /* + * Perform the actual generation. Here zero or more + * strings might be sent to the console buffer. + */ jcgp.nextGeneration(); + /* + * The generation is complete, relinquish the lock. + * By this point chances are the platform is already trying + * to execute the consoleFlush task that we scheduled. If it + * hasn't already started though, it doesn't matter; we will + * wait for a notification on the lock, which will only come + * when printing is complete. + */ printLock.wait(); + /* + * We have been notified. This means all buffered messages have + * been successfully flushed to the actual console control and + * we are now ready to perform another generation (or break out + * of the loop if the loop conditions are no longer met). + */ } + /* + * We no longer own the lock, but neither does consoleFlush. + * The synchrony cycle has returned to its initial state, and we + * are free to acquire the lock again. + */ } + /* + * Something happened to break the while loop - + * either the experiment finished or the user pressed + * pause. + */ if (jcgp.isFinished()) { + // the experiment has finished, switch to pause mode Platform.runLater(new Runnable() { @Override public void run() { @@ -114,60 +272,95 @@ public class GUI extends Application { @Override public void start(Stage primaryStage) throws Exception { - console = new GUIConsole(); + /* + * This method gets called when the application launches. Once it + * returns, the application falls into the main loop which handles + * events, so all elements must be constructed here. + */ + + // make the console and set it so it is used for JCGP prints + console = new ConsolePane(); jcgp.setConsole(console); + + // store reference to the stage stage = primaryStage; + /* - * Instantiate the various GUI elements here. - * - * + * The experiment layer contains all of the experiment-related panes. + * The only element that sits higher than this is the function selector. */ - BorderPane leftFrame = new BorderPane(); BorderPane experimentLayer = new BorderPane(); + /* + * The left frame encapsulates the population pane and the console. + * It goes into the center position of the experiment layer, next to the settings pane. + */ + BorderPane leftFrame = new BorderPane(); + /* + * The population pane is a TabPane containing a tab for each chromosome. + */ populationPane = new PopulationPane(this); + /* + * The settings pane is a big class containing the entire control pane + */ settingsPane = new SettingsPane(this); - settingsPane.maxWidthProperty().bind(experimentLayer.widthProperty()); - - console.maxHeightProperty().bind(experimentLayer.heightProperty()); + // make control pane and console resizable HorizontalDragResize.makeDragResizable(settingsPane); VerticalDragResize.makeDragResizable(console); - + // prevent resizables from growing larger than the experiment layer + settingsPane.maxWidthProperty().bind(experimentLayer.widthProperty()); + console.maxHeightProperty().bind(experimentLayer.heightProperty()); + + // put console and population pane in the main frame leftFrame.setCenter(populationPane); leftFrame.setBottom(console); + // set the main frame and the control pane in the experiment layer experimentLayer.setCenter(leftFrame); experimentLayer.setRight(settingsPane); + /* + * Now we deal with the stage. + */ primaryStage.setTitle("JCGP"); + // this pane holds the entire scene, that is its sole job. Pane sceneParent = new Pane(); + // the experiment layer should fill the entire scene parent experimentLayer.prefHeightProperty().bind(sceneParent.heightProperty()); experimentLayer.prefWidthProperty().bind(sceneParent.widthProperty()); + // the function selector goes over the experiment layer so it doesn't get covered by other panes sceneParent.getChildren().addAll(experimentLayer, functionSelector); + // set the scene, minimum sizes, show primaryStage.setScene(new Scene(sceneParent)); primaryStage.setMinWidth(800); primaryStage.setMinHeight(600); primaryStage.show(); + // when the main stage closes, close the test case table as well primaryStage.setOnCloseRequest(new EventHandler() { @Override public void handle(WindowEvent event) { - settingsPane.getTestCaseTable().close(); + if (settingsPane.getTestCaseTable() != null) { + settingsPane.getTestCaseTable().close(); + } } }); } + /** + * + */ public void runPause() { if (!jcgp.isFinished() && settingsPane.areParametersValid()) { if (!running) { runningMode(true); - cgpService.restart(); + jcgpService.restart(); } else { - cgpService.cancel(); + jcgpService.cancel(); runningMode(false); } } diff --git a/src/jcgp/gui/console/ConsolePane.java b/src/jcgp/gui/console/ConsolePane.java new file mode 100644 index 0000000..c4cfc21 --- /dev/null +++ b/src/jcgp/gui/console/ConsolePane.java @@ -0,0 +1,123 @@ +package jcgp.gui.console; + +import javafx.event.ActionEvent; +import javafx.event.Event; +import javafx.event.EventDispatchChain; +import javafx.event.EventDispatcher; +import javafx.event.EventHandler; +import javafx.scene.control.ContextMenu; +import javafx.scene.control.MenuItem; +import javafx.scene.control.SeparatorMenuItem; +import javafx.scene.control.TextArea; +import javafx.scene.input.MouseButton; +import javafx.scene.input.MouseEvent; +import javafx.scene.layout.AnchorPane; +import jcgp.backend.resources.Console; +import jcgp.gui.GUI; + +/** + * Console pane used by the GUI to display CGP output messages. + * This class realises {@code Console}. It consists of a JavaFX + * {@code TextArea} and a {@code StringBuffer}. The buffer is filled + * as print messages are queued. Calling {@code flush()} writes the + * contents of the buffer to the {@code TextArea} and empties the buffer. + * + * @see Console + * @author Eduardo Pedroni + * + */ +public class ConsolePane extends AnchorPane implements Console { + + private TextArea textArea = new TextArea("Welcome to JCGP!\n"); + private StringBuffer printBuffer = new StringBuffer(); + + /** + * Creates a new instance of this class. + */ + public ConsolePane() { + super(); + textArea.setEditable(false); + + /* + * This nasty hack is needed because the default TextArea ContextMenu is not + * in the public API, making it impossible to override it with a custom one. + * This has not been fixed as of 8/4/2014. + * + * The following code modifies the EventDispatcher to consume the right mouse + * button click, preventing the default menu from appearing. It propagates the mouse + * click further so other elements will respond appropriately. + * + * TODO this should be refactored once the API is updated. + */ + final EventDispatcher initial = textArea.getEventDispatcher(); + textArea.setEventDispatcher(new EventDispatcher() { + @Override + public Event dispatchEvent(Event event, EventDispatchChain tail) { + if (event instanceof MouseEvent) { + MouseEvent mouseEvent = (MouseEvent)event; + if (mouseEvent.getButton() == MouseButton.SECONDARY || + (mouseEvent.getButton() == MouseButton.PRIMARY && mouseEvent.isControlDown())) { + event.consume(); + } + } + return initial.dispatchEvent(event, tail); + } + }); + + // make the new context menu including the clear option + MenuItem copySelected = new MenuItem("Copy"); + copySelected.setOnAction(new EventHandler() { + @Override + public void handle(ActionEvent event) { + textArea.copy(); + } + }); + MenuItem selectAll = new MenuItem("Select all"); + selectAll.setOnAction(new EventHandler() { + @Override + public void handle(ActionEvent event) { + textArea.selectAll(); + } + }); + MenuItem clearConsole = new MenuItem("Clear"); + clearConsole.setOnAction(new EventHandler() { + @Override + public void handle(ActionEvent event) { + textArea.setText(""); + } + }); + + textArea.setContextMenu(new ContextMenu(copySelected, + selectAll, + new SeparatorMenuItem(), + clearConsole)); + + // anchor the text area so it resizes automatically + AnchorPane.setTopAnchor(textArea, GUI.RESIZE_MARGIN); + AnchorPane.setBottomAnchor(textArea, 0.0); + AnchorPane.setRightAnchor(textArea, 0.0); + AnchorPane.setLeftAnchor(textArea, 0.0); + + setMinHeight(GUI.CONSOLE_MIN_HEIGHT); + setPrefHeight(GUI.CONSOLE_MIN_HEIGHT); + + getChildren().add(textArea); + } + + @Override + public void println(String s) { + printBuffer.append(s + "\n"); + } + + @Override + public void print(String s) { + printBuffer.append(s); + } + + @Override + public void flush() { + textArea.appendText(printBuffer.toString()); + printBuffer = new StringBuffer(); + } + +} diff --git a/src/jcgp/gui/console/GUIConsole.java b/src/jcgp/gui/console/GUIConsole.java deleted file mode 100644 index b3d037c..0000000 --- a/src/jcgp/gui/console/GUIConsole.java +++ /dev/null @@ -1,123 +0,0 @@ -package jcgp.gui.console; - -import javafx.event.ActionEvent; -import javafx.event.Event; -import javafx.event.EventDispatchChain; -import javafx.event.EventDispatcher; -import javafx.event.EventHandler; -import javafx.scene.control.ContextMenu; -import javafx.scene.control.MenuItem; -import javafx.scene.control.SeparatorMenuItem; -import javafx.scene.control.TextArea; -import javafx.scene.input.MouseButton; -import javafx.scene.input.MouseEvent; -import javafx.scene.layout.AnchorPane; -import jcgp.backend.resources.Console; -import jcgp.gui.GUI; - -/** - * Console pane used by the GUI to display CGP output messages. - * This class realises {@code Console}. It consists of a JavaFX - * {@code TextArea} and a {@code StringBuffer}. The buffer is filled - * as print messages are queued. Calling {@code flush()} writes the - * contents of the buffer to the {@code TextArea} and empties the buffer. - * - * @see Console - * @author Eduardo Pedroni - * - */ -public class GUIConsole extends AnchorPane implements Console { - - private TextArea textArea = new TextArea("Welcome to JCGP!\n"); - private StringBuffer printBuffer = new StringBuffer(); - - /** - * Creates a new instance of this class. - */ - public GUIConsole() { - super(); - textArea.setEditable(false); - - /* - * This nasty hack is needed because the default TextArea ContextMenu is not - * in the public API, making it impossible to override it with a custom one. - * This has not been fixed as of 8/4/2014. - * - * The following code modifies the EventDispatcher to consume the right mouse - * button click, preventing the default menu from appearing. It propagates the mouse - * click further so other elements will respond appropriately. - * - * TODO this should be refactored once the API is updated. - */ - final EventDispatcher initial = textArea.getEventDispatcher(); - textArea.setEventDispatcher(new EventDispatcher() { - @Override - public Event dispatchEvent(Event event, EventDispatchChain tail) { - if (event instanceof MouseEvent) { - MouseEvent mouseEvent = (MouseEvent)event; - if (mouseEvent.getButton() == MouseButton.SECONDARY || - (mouseEvent.getButton() == MouseButton.PRIMARY && mouseEvent.isControlDown())) { - event.consume(); - } - } - return initial.dispatchEvent(event, tail); - } - }); - - // make the new context menu including the clear option - MenuItem copySelected = new MenuItem("Copy"); - copySelected.setOnAction(new EventHandler() { - @Override - public void handle(ActionEvent event) { - textArea.copy(); - } - }); - MenuItem selectAll = new MenuItem("Select all"); - selectAll.setOnAction(new EventHandler() { - @Override - public void handle(ActionEvent event) { - textArea.selectAll(); - } - }); - MenuItem clearConsole = new MenuItem("Clear"); - clearConsole.setOnAction(new EventHandler() { - @Override - public void handle(ActionEvent event) { - textArea.setText(""); - } - }); - - textArea.setContextMenu(new ContextMenu(copySelected, - selectAll, - new SeparatorMenuItem(), - clearConsole)); - - // anchor the text area so it resizes automatically - AnchorPane.setTopAnchor(textArea, GUI.RESIZE_MARGIN); - AnchorPane.setBottomAnchor(textArea, 0.0); - AnchorPane.setRightAnchor(textArea, 0.0); - AnchorPane.setLeftAnchor(textArea, 0.0); - - setMinHeight(GUI.CONSOLE_MIN_HEIGHT); - setPrefHeight(GUI.CONSOLE_MIN_HEIGHT); - - getChildren().add(textArea); - } - - @Override - public void println(String s) { - printBuffer.append(s + "\n"); - } - - @Override - public void print(String s) { - printBuffer.append(s); - } - - @Override - public void flush() { - textArea.appendText(printBuffer.toString()); - printBuffer = new StringBuffer(); - } - -} diff --git a/src/jcgp/gui/population/GUIGene.java b/src/jcgp/gui/population/GUIGene.java index 0eea045..3d9cffb 100644 --- a/src/jcgp/gui/population/GUIGene.java +++ b/src/jcgp/gui/population/GUIGene.java @@ -71,11 +71,6 @@ public abstract class GUIGene extends Group { public abstract void addLocks(int value); - /** - * - * - * @param value - */ public abstract void removeLocks(int value); public abstract void updateLines(); diff --git a/src/jcgp/gui/population/GUIOutput.java b/src/jcgp/gui/population/GUIOutput.java index 5a76298..364458b 100644 --- a/src/jcgp/gui/population/GUIOutput.java +++ b/src/jcgp/gui/population/GUIOutput.java @@ -266,7 +266,7 @@ public class GUIOutput extends GUIGene { @Override public void setChangingConnection(Connection newConnection) { - output.setConnection(0, newConnection); + output.setSource(newConnection); updateText(); } diff --git a/src/jcgp/gui/settings/parameters/GUIParameter.java b/src/jcgp/gui/settings/parameters/GUIParameter.java index f896fa3..9188aec 100644 --- a/src/jcgp/gui/settings/parameters/GUIParameter.java +++ b/src/jcgp/gui/settings/parameters/GUIParameter.java @@ -231,8 +231,6 @@ public abstract class GUIParameter extends HBox { * (INVALID_PARAMETER_STYLE, WARNING_PARAMETER_STYLE, VALID_PARAMETER_STYLE) * provide a way to keep the GUI consistent. * - * TODO update this if the strings are externalised - * * @see ParameterStatus */ protected abstract void setValidityStyle(); -- cgit v1.2.3