diff options
Diffstat (limited to 'src/jcgp/gui')
-rw-r--r-- | src/jcgp/gui/GUI.java | 247 | ||||
-rw-r--r-- | src/jcgp/gui/console/ConsolePane.java (renamed from src/jcgp/gui/console/GUIConsole.java) | 4 | ||||
-rw-r--r-- | src/jcgp/gui/population/GUIGene.java | 5 | ||||
-rw-r--r-- | src/jcgp/gui/population/GUIOutput.java | 2 | ||||
-rw-r--r-- | src/jcgp/gui/settings/parameters/GUIParameter.java | 2 |
5 files changed, 223 insertions, 37 deletions
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). + * <br><br> + * This class declares the main method used when running the GUI. + * In addition, all main GUI panes are declared and instantiated here. + * <br><br> + * 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. + * <br><br> + * 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. + * <br> + * 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<Void> jcgpService; + private Runnable consoleFlush; - private Service<Void> 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<Void> () { + /* + * 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<Void> () { @Override protected Task<Void> createTask() { Task<Void> t = new Task<Void>() { @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<WindowEvent>() { @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/GUIConsole.java b/src/jcgp/gui/console/ConsolePane.java index b3d037c..c4cfc21 100644 --- a/src/jcgp/gui/console/GUIConsole.java +++ b/src/jcgp/gui/console/ConsolePane.java @@ -26,7 +26,7 @@ import jcgp.gui.GUI; * @author Eduardo Pedroni * */ -public class GUIConsole extends AnchorPane implements Console { +public class ConsolePane extends AnchorPane implements Console { private TextArea textArea = new TextArea("Welcome to JCGP!\n"); private StringBuffer printBuffer = new StringBuffer(); @@ -34,7 +34,7 @@ public class GUIConsole extends AnchorPane implements Console { /** * Creates a new instance of this class. */ - public GUIConsole() { + public ConsolePane() { super(); textArea.setEditable(false); 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<T> 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(); |