aboutsummaryrefslogtreecommitdiffstats
path: root/src/jcgp/gui
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
parent95b4a9421923cec63b6e0e8f58972d146100bd0f (diff)
Refactored Mutable, commented a little bit of the GUI package
Diffstat (limited to 'src/jcgp/gui')
-rw-r--r--src/jcgp/gui/GUI.java247
-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.java5
-rw-r--r--src/jcgp/gui/population/GUIOutput.java2
-rw-r--r--src/jcgp/gui/settings/parameters/GUIParameter.java2
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();