aboutsummaryrefslogtreecommitdiffstats
path: root/src/jcgp/gui/GUI.java
diff options
context:
space:
mode:
authorEduardo Pedroni <ep625@york.ac.uk>2014-05-14 01:32:51 +0100
committerEduardo Pedroni <ep625@york.ac.uk>2014-05-14 01:32:51 +0100
commitc35a6806df01481c1b169cd0fc47660ea1cc10fb (patch)
tree7a9a90d88a9b962bcc091533997df798ac454423 /src/jcgp/gui/GUI.java
parent95b4a9421923cec63b6e0e8f58972d146100bd0f (diff)
Refactored Mutable, commented a little bit of the GUI package
Diffstat (limited to 'src/jcgp/gui/GUI.java')
-rw-r--r--src/jcgp/gui/GUI.java247
1 files changed, 220 insertions, 27 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);
}
}