Part 3 recap: the Swing control panel & a state machine
The GUI half: a multi-window Swing control panel with a scrollable log and a menu/button set whose enabled state is a small state machine, run off the EDT so the background controller threads never freeze the UI. Then the capstone checkpoint.
Part 3: a control panel for the controller
Part 3 wraps everything in a Swing GUI (Module 11): a window with a scrollable status log and controls (buttons + a File menu) that start, suspend, resume, open a file, and exit the greenhouse run. The interesting engineering is not the widgets — it is the state machine that decides which controls are enabled, and keeping the GUI responsive while the controller's threads run.
### The panel as a state machine The panel is in exactly one of three states, and each state fixes which controls are usable:
| State | Run | Open | Suspend | Resume | |---|---|---|---|---| | Stopped | enabled | enabled | disabled | disabled | | Running | disabled | disabled | enabled | disabled | | Suspended | disabled | disabled | disabled | enabled |
Every transition calls setEnabled(true/false) on the affected buttons, so the UI can never offer an illegal action (you cannot *Suspend* a stopped controller, cannot *Run* a running one). A single updateButtons(State s) method centralises the rule:
private void updateButtons(State s) {
runButton.setEnabled(s == State.STOPPED);
openButton.setEnabled(s == State.STOPPED);
suspendButton.setEnabled(s == State.RUNNING);
resumeButton.setEnabled(s == State.SUSPENDED);
}
### Wiring a button (anonymous inner class)
Buttons fire ActionEvents to registered ActionListeners. Because a listener is an inner class, it can reach the panel's fields and call updateButtons directly (Module 2.5 / 11.2):
runButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
startController(); // kick off the controller threads
updateButtons(State.RUNNING);
}
});
### Keep work OFF the Event Dispatch Thread Swing is single-threaded: all UI updates happen on the Event Dispatch Thread (EDT). Two rules follow (Module 11.4):
- Never run long work on the EDT —
startController()must launch the controller on a *background* thread (orSwingWorker), or the click handler would block and the window would freeze. - Never touch Swing from a background thread — when a controller thread wants to append a line to the log text area, it must hop back onto the EDT with
SwingUtilities.invokeLater:
SwingUtilities.invokeLater(() -> log.append(line + "\n"));
This is where the whole course converges: Module 10's threads do the work, Module 11's EDT rule keeps the UI live, Module 2's inner classes wire the callbacks, and the controller from Lesson 1 is what the panel drives.