{"id":"gui-basics-nesting","slug":"nesting-layouts","title":"Layouts inside layouts","text":"Each layout so far was the root of its window, holding controls. But a layout is a node too, so it can be a child of another layout. Real windows are built like that: a BorderPane for the frame, with an HBox of buttons at the bottom, a VBox down one side, a GridPane in the middle.\n\n### A scoreboard, nested\n\n```java\nimport javafx.application.Application;\nimport javafx.geometry.Insets;\nimport javafx.geometry.Pos;\nimport javafx.scene.Scene;\nimport javafx.scene.control.Button;\nimport javafx.scene.control.Label;\nimport javafx.scene.layout.BorderPane;\nimport javafx.scene.layout.HBox;\nimport javafx.scene.layout.VBox;\nimport javafx.stage.Stage;\n\npublic class Main extends Application {\n\n    @Override\n    public void start(Stage stage) {\n        HBox score = new HBox(20, new Label(\"Harbor Rovers\"), new Label(\"2 : 1\"), new Label(\"Mill Town\"));\n        score.setPadding(new Insets(10));\n        score.setAlignment(Pos.CENTER);\n\n        VBox goals = new VBox(4, new Label(\"12' Okafor\"), new Label(\"40' Lindqvist\"), new Label(\"77' Mbeki\"));\n        goals.setPadding(new Insets(10));\n\n        HBox buttons = new HBox(8, new Button(\"Previous match\"), new Button(\"Next match\"));\n        buttons.setPadding(new Insets(10));\n        buttons.setAlignment(Pos.CENTER_RIGHT);\n\n        BorderPane board = new BorderPane();\n        board.setTop(score);\n        board.setCenter(goals);\n        board.setBottom(buttons);\n\n        stage.setScene(new Scene(board));\n        stage.setTitle(\"Scoreboard\");\n        stage.show();\n    }\n\n    public static void main(String[] args) {\n        launch(Main.class);\n    }\n}\n```\n\n```window\n{\"events\":[],\"window\":{\"windows\":[{\"title\":\"Scoreboard\",\"scene\":{\"root\":{\"n\":1,\"type\":\"BorderPane\",\"top\":{\"n\":2,\"type\":\"HBox\",\"padding\":[10,10,10,10],\"spacing\":20,\"alignment\":\"CENTER\",\"children\":[{\"n\":3,\"type\":\"Label\",\"text\":\"Harbor Rovers\"},{\"n\":4,\"type\":\"Label\",\"text\":\"2 : 1\"},{\"n\":5,\"type\":\"Label\",\"text\":\"Mill Town\"}]},\"center\":{\"n\":6,\"type\":\"VBox\",\"padding\":[10,10,10,10],\"spacing\":4,\"children\":[{\"n\":7,\"type\":\"Label\",\"text\":\"12' Okafor\"},{\"n\":8,\"type\":\"Label\",\"text\":\"40' Lindqvist\"},{\"n\":9,\"type\":\"Label\",\"text\":\"77' Mbeki\"}]},\"bottom\":{\"n\":10,\"type\":\"HBox\",\"padding\":[10,10,10,10],\"spacing\":8,\"alignment\":\"CENTER_RIGHT\",\"children\":[{\"n\":11,\"type\":\"Button\",\"text\":\"Previous match\"},{\"n\":12,\"type\":\"Button\",\"text\":\"Next match\"}]}}}}],\"problems\":[]},\"outline\":\"Window \\\"Scoreboard\\\"\\n  BorderPane\\n    top: HBox spacing 20, padding 10, alignment CENTER\\n      Label \\\"Harbor Rovers\\\"\\n      Label \\\"2 : 1\\\"\\n      Label \\\"Mill Town\\\"\\n    center: VBox spacing 4, padding 10\\n      Label \\\"12' Okafor\\\"\\n      Label \\\"40' Lindqvist\\\"\\n      Label \\\"77' Mbeki\\\"\\n    bottom: HBox spacing 8, padding 10, alignment CENTER_RIGHT\\n      Button \\\"Previous match\\\"\\n      Button \\\"Next match\\\"\"}\n```\n\nLook at the window as text: the HBox is in the top region, and its three labels are indented under it, one level deeper. Each layout looks after its own children only. The BorderPane decides where the HBox goes, and the HBox decides where its labels go inside it.\n\n### Building parts in helper methods\n\nA window like this makes `start` long, and the parts get mixed up. Methods help, as they did in every program before: a method can build one part of the window and return it. Its return type is the class of the part (`HBox`, `VBox`, `GridPane`), or a more general one, such as `Parent` or `Node`. The same scoreboard:\n\n```java\nimport javafx.application.Application;\nimport javafx.geometry.Insets;\nimport javafx.geometry.Pos;\nimport javafx.scene.Scene;\nimport javafx.scene.control.Button;\nimport javafx.scene.control.Label;\nimport javafx.scene.layout.BorderPane;\nimport javafx.scene.layout.HBox;\nimport javafx.scene.layout.VBox;\nimport javafx.stage.Stage;\n\npublic class Main extends Application {\n\n    @Override\n    public void start(Stage stage) {\n        String[] scorers = {\"12' Okafor\", \"40' Lindqvist\", \"77' Mbeki\"};\n        BorderPane board = new BorderPane();\n        board.setTop(score(\"Harbor Rovers\", 2, 1, \"Mill Town\"));\n        board.setCenter(goals(scorers));\n        board.setBottom(buttons());\n\n        stage.setScene(new Scene(board));\n        stage.setTitle(\"Scoreboard\");\n        stage.show();\n    }\n\n    private HBox score(String home, int homeGoals, int awayGoals, String away) {\n        HBox score = new HBox(20, new Label(home), new Label(homeGoals + \" : \" + awayGoals), new Label(away));\n        score.setPadding(new Insets(10));\n        score.setAlignment(Pos.CENTER);\n        return score;\n    }\n\n    private VBox goals(String[] scorers) {\n        VBox goals = new VBox(4);\n        goals.setPadding(new Insets(10));\n        for (String scorer : scorers) {\n            goals.getChildren().add(new Label(scorer));\n        }\n        return goals;\n    }\n\n    private HBox buttons() {\n        HBox buttons = new HBox(8, new Button(\"Previous match\"), new Button(\"Next match\"));\n        buttons.setPadding(new Insets(10));\n        buttons.setAlignment(Pos.CENTER_RIGHT);\n        return buttons;\n    }\n\n    public static void main(String[] args) {\n        launch(Main.class);\n    }\n}\n```\n\n```window\n{\"events\":[],\"window\":{\"windows\":[{\"title\":\"Scoreboard\",\"scene\":{\"root\":{\"n\":1,\"type\":\"BorderPane\",\"top\":{\"n\":2,\"type\":\"HBox\",\"padding\":[10,10,10,10],\"spacing\":20,\"alignment\":\"CENTER\",\"children\":[{\"n\":3,\"type\":\"Label\",\"text\":\"Harbor Rovers\"},{\"n\":4,\"type\":\"Label\",\"text\":\"2 : 1\"},{\"n\":5,\"type\":\"Label\",\"text\":\"Mill Town\"}]},\"center\":{\"n\":6,\"type\":\"VBox\",\"padding\":[10,10,10,10],\"spacing\":4,\"children\":[{\"n\":7,\"type\":\"Label\",\"text\":\"12' Okafor\"},{\"n\":8,\"type\":\"Label\",\"text\":\"40' Lindqvist\"},{\"n\":9,\"type\":\"Label\",\"text\":\"77' Mbeki\"}]},\"bottom\":{\"n\":10,\"type\":\"HBox\",\"padding\":[10,10,10,10],\"spacing\":8,\"alignment\":\"CENTER_RIGHT\",\"children\":[{\"n\":11,\"type\":\"Button\",\"text\":\"Previous match\"},{\"n\":12,\"type\":\"Button\",\"text\":\"Next match\"}]}}}}],\"problems\":[]},\"outline\":\"Window \\\"Scoreboard\\\"\\n  BorderPane\\n    top: HBox spacing 20, padding 10, alignment CENTER\\n      Label \\\"Harbor Rovers\\\"\\n      Label \\\"2 : 1\\\"\\n      Label \\\"Mill Town\\\"\\n    center: VBox spacing 4, padding 10\\n      Label \\\"12' Okafor\\\"\\n      Label \\\"40' Lindqvist\\\"\\n      Label \\\"77' Mbeki\\\"\\n    bottom: HBox spacing 8, padding 10, alignment CENTER_RIGHT\\n      Button \\\"Previous match\\\"\\n      Button \\\"Next match\\\"\"}\n```\n\n- `start` now reads like a summary of the window: a score on top, the goals in the middle, buttons at the bottom.\n- The methods aren't `static`: they are methods of the Main object that JavaFX created, as `start` is.\n- `score` takes the teams and the goals as parameters, so the same method could build the line of any match.\n- `goals` takes an array of the scorers and makes a label for each, so it works for a match of any number of goals.\n\nEach call of a helper method makes new nodes. That's what you want: since a node can be in one place at a time, a method that returned the same Label object on every call would move it from place to place, and the window would show it only once.\n\n### A form in a grid\n\nA form is a grid: labels in the first column, fields in the second. With the form in the center of a BorderPane and the buttons in an HBox at the bottom, it's the shape of many windows. The **Your turn** below builds one.\n","task":"The library's form for a new card. Build it with two helper methods, `form()` and `buttons()`, which `start` already uses. The window's title is `New library card`, and its root is a BorderPane with:\n\n- top: the label `Apply for a library card`, in the font `Font.font(\"Serif\", 18)`\n- center: the GridPane that `form()` returns, with the gaps 8 between both columns and rows and the padding 10 on every side. It holds the labels `Name`, `Email` and `Branch` in column 0, rows 0 to 2, and next to each label, in column 1, an empty TextField.\n- bottom: the HBox that `buttons()` returns, with the spacing 8, the padding 10 on every side and the alignment `CENTER_RIGHT`. It holds the buttons `Cancel` and `Apply`.\n\nThe window it should show is drawn below.\n","kind":"code","seed":"import javafx.application.Application;\nimport javafx.geometry.Insets;\nimport javafx.geometry.Pos;\nimport javafx.scene.Scene;\nimport javafx.scene.control.Button;\nimport javafx.scene.control.Label;\nimport javafx.scene.control.TextField;\nimport javafx.scene.layout.BorderPane;\nimport javafx.scene.layout.GridPane;\nimport javafx.scene.layout.HBox;\nimport javafx.scene.text.Font;\nimport javafx.stage.Stage;\n\npublic class Main extends Application {\n\n    @Override\n    public void start(Stage stage) {\n        Label header = new Label(\"Apply for a library card\");\n        header.setFont(Font.font(\"Serif\", 18));\n\n        BorderPane root = new BorderPane();\n        root.setTop(header);\n        root.setCenter(form());\n        root.setBottom(buttons());\n\n        stage.setScene(new Scene(root));\n        stage.setTitle(\"New library card\");\n        stage.show();\n    }\n\n    private GridPane form() {\n        GridPane form = new GridPane();\n        return form;\n    }\n\n    private HBox buttons() {\n        HBox buttons = new HBox();\n        return buttons;\n    }\n\n    public static void main(String[] args) {\n        launch(Main.class);\n    }\n}\n","solution":"import javafx.application.Application;\nimport javafx.geometry.Insets;\nimport javafx.geometry.Pos;\nimport javafx.scene.Scene;\nimport javafx.scene.control.Button;\nimport javafx.scene.control.Label;\nimport javafx.scene.control.TextField;\nimport javafx.scene.layout.BorderPane;\nimport javafx.scene.layout.GridPane;\nimport javafx.scene.layout.HBox;\nimport javafx.scene.text.Font;\nimport javafx.stage.Stage;\n\npublic class Main extends Application {\n\n    @Override\n    public void start(Stage stage) {\n        Label header = new Label(\"Apply for a library card\");\n        header.setFont(Font.font(\"Serif\", 18));\n\n        BorderPane root = new BorderPane();\n        root.setTop(header);\n        root.setCenter(form());\n        root.setBottom(buttons());\n\n        stage.setScene(new Scene(root));\n        stage.setTitle(\"New library card\");\n        stage.show();\n    }\n\n    private GridPane form() {\n        GridPane form = new GridPane();\n        form.setHgap(8);\n        form.setVgap(8);\n        form.setPadding(new Insets(10));\n        String[] names = {\"Name\", \"Email\", \"Branch\"};\n        for (int row = 0; row < names.length; row++) {\n            form.add(new Label(names[row]), 0, row);\n            form.add(new TextField(), 1, row);\n        }\n        return form;\n    }\n\n    private HBox buttons() {\n        HBox buttons = new HBox(8, new Button(\"Cancel\"), new Button(\"Apply\"));\n        buttons.setPadding(new Insets(10));\n        buttons.setAlignment(Pos.CENTER_RIGHT);\n        return buttons;\n    }\n\n    public static void main(String[] args) {\n        launch(Main.class);\n    }\n}\n","hints":["`add(node, column, row)` takes the column first: each label goes into column 0 and its field into column 1 of the same row, one row per name. A loop over the three names saves writing each row.","In `form()`: `form.add(new Label(\"Name\"), 0, 0);` and `form.add(new TextField(), 1, 0);`, and the same for rows 1 and 2 (or a loop over the three names). Then `setHgap(8)`, `setVgap(8)` and `setPadding(new Insets(10))`.","In `buttons()`: `new HBox(8, new Button(\"Cancel\"), new Button(\"Apply\"))` gives the spacing and the buttons at once. Add `setPadding(new Insets(10))` and `setAlignment(Pos.CENTER_RIGHT)`."],"tests":[{"name":"Test 1","stdin":"","expect":"","hidden":false,"events":[],"window":{"windows":[{"title":"New library card","scene":{"root":{"n":1,"type":"BorderPane","top":{"n":2,"type":"Label","text":"Apply for a library card","font":{"family":"Serif","size":18}},"center":{"n":3,"type":"GridPane","padding":[10,10,10,10],"hgap":8,"vgap":8,"children":[{"n":4,"type":"Label","column":0,"row":0,"text":"Name"},{"n":5,"type":"TextField","column":1,"row":0,"text":""},{"n":6,"type":"Label","column":0,"row":1,"text":"Email"},{"n":7,"type":"TextField","column":1,"row":1,"text":""},{"n":8,"type":"Label","column":0,"row":2,"text":"Branch"},{"n":9,"type":"TextField","column":1,"row":2,"text":""}]},"bottom":{"n":10,"type":"HBox","padding":[10,10,10,10],"spacing":8,"alignment":"CENTER_RIGHT","children":[{"n":11,"type":"Button","text":"Cancel"},{"n":12,"type":"Button","text":"Apply"}]}}}}],"problems":[]},"outline":"Window \"New library card\"\n  BorderPane\n    top: Label \"Apply for a library card\", font Serif 18\n    center: GridPane hgap 8, vgap 8, padding 10\n      (0, 0) Label \"Name\"\n      (1, 0) TextField \"\"\n      (0, 1) Label \"Email\"\n      (1, 1) TextField \"\"\n      (0, 2) Label \"Branch\"\n      (1, 2) TextField \"\"\n    bottom: HBox spacing 8, padding 10, alignment CENTER_RIGHT\n      Button \"Cancel\"\n      Button \"Apply\""},{"name":"Test 2","stdin":"","expect":"","hidden":true,"events":["type TextField 1 \"Grace Hopper\"","type TextField 2 \"grace@example.com\"","type TextField 3 \"Riverside\""],"window":{"windows":[{"title":"New library card","scene":{"root":{"n":1,"type":"BorderPane","top":{"n":2,"type":"Label","text":"Apply for a library card","font":{"family":"Serif","size":18}},"center":{"n":3,"type":"GridPane","padding":[10,10,10,10],"hgap":8,"vgap":8,"children":[{"n":4,"type":"Label","column":0,"row":0,"text":"Name"},{"n":5,"type":"TextField","column":1,"row":0,"text":"Grace Hopper"},{"n":6,"type":"Label","column":0,"row":1,"text":"Email"},{"n":7,"type":"TextField","column":1,"row":1,"text":"grace@example.com"},{"n":8,"type":"Label","column":0,"row":2,"text":"Branch"},{"n":9,"type":"TextField","column":1,"row":2,"text":"Riverside"}]},"bottom":{"n":10,"type":"HBox","padding":[10,10,10,10],"spacing":8,"alignment":"CENTER_RIGHT","children":[{"n":11,"type":"Button","text":"Cancel"},{"n":12,"type":"Button","text":"Apply"}]}}}}],"problems":[]},"outline":"Window \"New library card\"\n  BorderPane\n    top: Label \"Apply for a library card\", font Serif 18\n    center: GridPane hgap 8, vgap 8, padding 10\n      (0, 0) Label \"Name\"\n      (1, 0) TextField \"Grace Hopper\"\n      (0, 1) Label \"Email\"\n      (1, 1) TextField \"grace@example.com\"\n      (0, 2) Label \"Branch\"\n      (1, 2) TextField \"Riverside\"\n    bottom: HBox spacing 8, padding 10, alignment CENTER_RIGHT\n      Button \"Cancel\"\n      Button \"Apply\""}],"require":[],"forbid":[],"more":[{"task":"Step 1's bus stop gets a board with all its departures. First the data. The file `departures.txt` has a line for each bus, such as `7:15;4;Harbor Square`: the time, the line and where it goes, with semicolons between them. In `BusTime.java`, write the class `BusTime`:\n\n- a constructor `BusTime(String clock, String route, String towards)`\n- the methods `getClock()`, `getRoute()` and `getTowards()`\n- a static method `fromText(String text)`, which returns a new BusTime made from a line of the file\n- `toString()`, which returns the departure like `7:15 line 4 to Harbor Square`\n\n`Main.java` is ready: it shows a VBox with a label for each of three departures, made with your class. The check runs:\n\n```java\nBusTime bus = BusTime.fromText(\"7:15;4;Harbor Square\");\nSystem.out.println(bus.getRoute() + \" leaves at \" + bus.getClock());\nSystem.out.println(bus);\n```\n\nand expects:\n\n```\n4 leaves at 7:15\n7:15 line 4 to Harbor Square\n```\n","kind":"code","seed":"// ==== BusTime.java ====\npublic class BusTime {\n\n}\n\n// ==== Main.java ====\nimport javafx.application.Application;\nimport javafx.scene.Scene;\nimport javafx.scene.control.Label;\nimport javafx.scene.layout.VBox;\nimport javafx.stage.Stage;\n\npublic class Main extends Application {\n\n    @Override\n    public void start(Stage stage) {\n        String[] lines = {\"7:15;4;Harbor Square\", \"7:22;11;Airport\", \"7:30;4;Harbor Square\"};\n        VBox board = new VBox(4);\n        for (String text : lines) {\n            BusTime bus = BusTime.fromText(text);\n            board.getChildren().add(new Label(bus.toString()));\n        }\n\n        stage.setScene(new Scene(board));\n        stage.setTitle(\"Stop 14 departures\");\n        stage.show();\n    }\n\n    public static void main(String[] args) {\n        launch(Main.class);\n    }\n}\n","solution":"// ==== BusTime.java ====\npublic class BusTime {\n    private String clock;\n    private String route;\n    private String towards;\n\n    public BusTime(String clock, String route, String towards) {\n        this.clock = clock;\n        this.route = route;\n        this.towards = towards;\n    }\n\n    public static BusTime fromText(String text) {\n        String[] parts = text.split(\";\");\n        return new BusTime(parts[0], parts[1], parts[2]);\n    }\n\n    public String getClock() {\n        return this.clock;\n    }\n\n    public String getRoute() {\n        return this.route;\n    }\n\n    public String getTowards() {\n        return this.towards;\n    }\n\n    @Override\n    public String toString() {\n        return this.clock + \" line \" + this.route + \" to \" + this.towards;\n    }\n}\n\n// ==== Main.java ====\nimport javafx.application.Application;\nimport javafx.scene.Scene;\nimport javafx.scene.control.Label;\nimport javafx.scene.layout.VBox;\nimport javafx.stage.Stage;\n\npublic class Main extends Application {\n\n    @Override\n    public void start(Stage stage) {\n        String[] lines = {\"7:15;4;Harbor Square\", \"7:22;11;Airport\", \"7:30;4;Harbor Square\"};\n        VBox board = new VBox(4);\n        for (String text : lines) {\n            BusTime bus = BusTime.fromText(text);\n            board.getChildren().add(new Label(bus.toString()));\n        }\n\n        stage.setScene(new Scene(board));\n        stage.setTitle(\"Stop 14 departures\");\n        stage.show();\n    }\n\n    public static void main(String[] args) {\n        launch(Main.class);\n    }\n}\n","hints":["Three private String instance variables, a constructor that stores them, and a getter for each. `toString` puts them together: `this.clock + \" line \" + this.route + \" to \" + this.towards`.","In `fromText`, `text.split(\";\")` gives an array of the three parts, in the file's order: time, line, destination. Return `new BusTime(parts[0], parts[1], parts[2])`. The method is `public static BusTime fromText(String text)`."],"tests":[{"name":"Test 1","stdin":"","call":"BusTime bus = BusTime.fromText(\"7:15;4;Harbor Square\");\nSystem.out.println(bus.getRoute() + \" leaves at \" + bus.getClock());\nSystem.out.println(bus);\n","expect":"4 leaves at 7:15\n7:15 line 4 to Harbor Square","hidden":false},{"name":"Test 2","stdin":"","call":"BusTime late = BusTime.fromText(\"23:40;N2;Old Town\");\nSystem.out.println(late.getTowards() + \"|\" + late.getRoute() + \"|\" + late.getClock());\nSystem.out.println(late);\nBusTime own = new BusTime(\"6:05\", \"11\", \"Airport\");\nSystem.out.println(own + \" / \" + own.getTowards());\n","expect":"Old Town|N2|23:40\n23:40 line N2 to Old Town\n6:05 line 11 to Airport / Airport","hidden":true}],"require":[],"forbid":[]},{"task":"Now the board itself, with `BusTime` from the last challenge ready in `BusTime.java`. Build the window: the title is `Stop 14 departures`, and the root is a BorderPane with:\n\n- top: the label `Departures from stop 14`\n- center: a GridPane with the gaps 12 between columns and 4 between rows, and the padding 10 on every side. Row 0 holds the labels `Time`, `Line` and `To`, in columns 0 to 2. Below it, a row for each departure of `departures.txt`, in the file's order: labels with its time, its line and its destination, in the same columns.\n\nWrite a helper method `addRow(GridPane grid, int row, String first, String second, String third)` (it returns nothing) that adds the three labels of one row, and use it for every row, the first one too. For the file shown below, the window should look like the drawing under it.\n","kind":"code","seed":"// ==== Main.java ====\nimport java.nio.file.Files;\nimport java.nio.file.Paths;\nimport java.util.List;\nimport javafx.application.Application;\nimport javafx.geometry.Insets;\nimport javafx.scene.Scene;\nimport javafx.scene.control.Label;\nimport javafx.scene.layout.BorderPane;\nimport javafx.scene.layout.GridPane;\nimport javafx.stage.Stage;\n\npublic class Main extends Application {\n\n    @Override\n    public void start(Stage stage) throws Exception {\n        List<String> lines = Files.readAllLines(Paths.get(\"departures.txt\"));\n        BorderPane board = new BorderPane();\n\n        stage.setScene(new Scene(board));\n        stage.setTitle(\"Stop 14 departures\");\n        stage.show();\n    }\n\n    public static void main(String[] args) {\n        launch(Main.class);\n    }\n}\n\n// ==== BusTime.java ====\npublic class BusTime {\n    private String clock;\n    private String route;\n    private String towards;\n\n    public BusTime(String clock, String route, String towards) {\n        this.clock = clock;\n        this.route = route;\n        this.towards = towards;\n    }\n\n    public static BusTime fromText(String text) {\n        String[] parts = text.split(\";\");\n        return new BusTime(parts[0], parts[1], parts[2]);\n    }\n\n    public String getClock() {\n        return this.clock;\n    }\n\n    public String getRoute() {\n        return this.route;\n    }\n\n    public String getTowards() {\n        return this.towards;\n    }\n\n    @Override\n    public String toString() {\n        return this.clock + \" line \" + this.route + \" to \" + this.towards;\n    }\n}\n","solution":"// ==== Main.java ====\nimport java.nio.file.Files;\nimport java.nio.file.Paths;\nimport java.util.List;\nimport javafx.application.Application;\nimport javafx.geometry.Insets;\nimport javafx.scene.Scene;\nimport javafx.scene.control.Label;\nimport javafx.scene.layout.BorderPane;\nimport javafx.scene.layout.GridPane;\nimport javafx.stage.Stage;\n\npublic class Main extends Application {\n\n    @Override\n    public void start(Stage stage) throws Exception {\n        List<String> lines = Files.readAllLines(Paths.get(\"departures.txt\"));\n        BorderPane board = new BorderPane();\n        board.setTop(new Label(\"Departures from stop 14\"));\n\n        GridPane grid = new GridPane();\n        grid.setHgap(12);\n        grid.setVgap(4);\n        grid.setPadding(new Insets(10));\n        addRow(grid, 0, \"Time\", \"Line\", \"To\");\n        for (int i = 0; i < lines.size(); i++) {\n            BusTime bus = BusTime.fromText(lines.get(i));\n            addRow(grid, i + 1, bus.getClock(), bus.getRoute(), bus.getTowards());\n        }\n        board.setCenter(grid);\n\n        stage.setScene(new Scene(board));\n        stage.setTitle(\"Stop 14 departures\");\n        stage.show();\n    }\n\n    private void addRow(GridPane grid, int row, String first, String second, String third) {\n        grid.add(new Label(first), 0, row);\n        grid.add(new Label(second), 1, row);\n        grid.add(new Label(third), 2, row);\n    }\n\n    public static void main(String[] args) {\n        launch(Main.class);\n    }\n}\n\n// ==== BusTime.java ====\npublic class BusTime {\n    private String clock;\n    private String route;\n    private String towards;\n\n    public BusTime(String clock, String route, String towards) {\n        this.clock = clock;\n        this.route = route;\n        this.towards = towards;\n    }\n\n    public static BusTime fromText(String text) {\n        String[] parts = text.split(\";\");\n        return new BusTime(parts[0], parts[1], parts[2]);\n    }\n\n    public String getClock() {\n        return this.clock;\n    }\n\n    public String getRoute() {\n        return this.route;\n    }\n\n    public String getTowards() {\n        return this.towards;\n    }\n\n    @Override\n    public String toString() {\n        return this.clock + \" line \" + this.route + \" to \" + this.towards;\n    }\n}\n","hints":["`addRow` puts its three texts into the columns 0, 1 and 2 of the given row: `grid.add(new Label(first), 0, row);` and so on. The header is `addRow(grid, 0, \"Time\", \"Line\", \"To\");`.","The departure on line `i` of the file (counting from 0) goes into row `i + 1`, below the header. Make it with `BusTime.fromText(lines.get(i))`, and pass its time, line and destination to `addRow`."],"tests":[{"name":"Test 1","stdin":"","files":{"departures.txt":"7:15;4;Harbor Square\n7:22;11;Airport\n7:30;4;Harbor Square\n"},"expect":"","hidden":false,"events":[],"window":{"windows":[{"title":"Stop 14 departures","scene":{"root":{"n":1,"type":"BorderPane","top":{"n":2,"type":"Label","text":"Departures from stop 14"},"center":{"n":3,"type":"GridPane","padding":[10,10,10,10],"hgap":12,"vgap":4,"children":[{"n":4,"type":"Label","column":0,"row":0,"text":"Time"},{"n":5,"type":"Label","column":1,"row":0,"text":"Line"},{"n":6,"type":"Label","column":2,"row":0,"text":"To"},{"n":7,"type":"Label","column":0,"row":1,"text":"7:15"},{"n":8,"type":"Label","column":1,"row":1,"text":"4"},{"n":9,"type":"Label","column":2,"row":1,"text":"Harbor Square"},{"n":10,"type":"Label","column":0,"row":2,"text":"7:22"},{"n":11,"type":"Label","column":1,"row":2,"text":"11"},{"n":12,"type":"Label","column":2,"row":2,"text":"Airport"},{"n":13,"type":"Label","column":0,"row":3,"text":"7:30"},{"n":14,"type":"Label","column":1,"row":3,"text":"4"},{"n":15,"type":"Label","column":2,"row":3,"text":"Harbor Square"}]}}}}],"problems":[]},"outline":"Window \"Stop 14 departures\"\n  BorderPane\n    top: Label \"Departures from stop 14\"\n    center: GridPane hgap 12, vgap 4, padding 10\n      (0, 0) Label \"Time\"\n      (1, 0) Label \"Line\"\n      (2, 0) Label \"To\"\n      (0, 1) Label \"7:15\"\n      (1, 1) Label \"4\"\n      (2, 1) Label \"Harbor Square\"\n      (0, 2) Label \"7:22\"\n      (1, 2) Label \"11\"\n      (2, 2) Label \"Airport\"\n      (0, 3) Label \"7:30\"\n      (1, 3) Label \"4\"\n      (2, 3) Label \"Harbor Square\""},{"name":"Test 2","stdin":"","files":{"departures.txt":"23:10;N2;Old Town\n23:40;N2;Old Town\n0:05;N7;Mill Street\n0:30;N2;Old Town\n"},"expect":"","hidden":true,"events":[],"window":{"windows":[{"title":"Stop 14 departures","scene":{"root":{"n":1,"type":"BorderPane","top":{"n":2,"type":"Label","text":"Departures from stop 14"},"center":{"n":3,"type":"GridPane","padding":[10,10,10,10],"hgap":12,"vgap":4,"children":[{"n":4,"type":"Label","column":0,"row":0,"text":"Time"},{"n":5,"type":"Label","column":1,"row":0,"text":"Line"},{"n":6,"type":"Label","column":2,"row":0,"text":"To"},{"n":7,"type":"Label","column":0,"row":1,"text":"23:10"},{"n":8,"type":"Label","column":1,"row":1,"text":"N2"},{"n":9,"type":"Label","column":2,"row":1,"text":"Old Town"},{"n":10,"type":"Label","column":0,"row":2,"text":"23:40"},{"n":11,"type":"Label","column":1,"row":2,"text":"N2"},{"n":12,"type":"Label","column":2,"row":2,"text":"Old Town"},{"n":13,"type":"Label","column":0,"row":3,"text":"0:05"},{"n":14,"type":"Label","column":1,"row":3,"text":"N7"},{"n":15,"type":"Label","column":2,"row":3,"text":"Mill Street"},{"n":16,"type":"Label","column":0,"row":4,"text":"0:30"},{"n":17,"type":"Label","column":1,"row":4,"text":"N2"},{"n":18,"type":"Label","column":2,"row":4,"text":"Old Town"}]}}}}],"problems":[]},"outline":"Window \"Stop 14 departures\"\n  BorderPane\n    top: Label \"Departures from stop 14\"\n    center: GridPane hgap 12, vgap 4, padding 10\n      (0, 0) Label \"Time\"\n      (1, 0) Label \"Line\"\n      (2, 0) Label \"To\"\n      (0, 1) Label \"23:10\"\n      (1, 1) Label \"N2\"\n      (2, 1) Label \"Old Town\"\n      (0, 2) Label \"23:40\"\n      (1, 2) Label \"N2\"\n      (2, 2) Label \"Old Town\"\n      (0, 3) Label \"0:05\"\n      (1, 3) Label \"N7\"\n      (2, 3) Label \"Mill Street\"\n      (0, 4) Label \"0:30\"\n      (1, 4) Label \"N2\"\n      (2, 4) Label \"Old Town\""},{"name":"Test 3","stdin":"","files":{"departures.txt":"12:00;9;Lighthouse\n"},"expect":"","hidden":true,"events":[],"window":{"windows":[{"title":"Stop 14 departures","scene":{"root":{"n":1,"type":"BorderPane","top":{"n":2,"type":"Label","text":"Departures from stop 14"},"center":{"n":3,"type":"GridPane","padding":[10,10,10,10],"hgap":12,"vgap":4,"children":[{"n":4,"type":"Label","column":0,"row":0,"text":"Time"},{"n":5,"type":"Label","column":1,"row":0,"text":"Line"},{"n":6,"type":"Label","column":2,"row":0,"text":"To"},{"n":7,"type":"Label","column":0,"row":1,"text":"12:00"},{"n":8,"type":"Label","column":1,"row":1,"text":"9"},{"n":9,"type":"Label","column":2,"row":1,"text":"Lighthouse"}]}}}}],"problems":[]},"outline":"Window \"Stop 14 departures\"\n  BorderPane\n    top: Label \"Departures from stop 14\"\n    center: GridPane hgap 12, vgap 4, padding 10\n      (0, 0) Label \"Time\"\n      (1, 0) Label \"Line\"\n      (2, 0) Label \"To\"\n      (0, 1) Label \"12:00\"\n      (1, 1) Label \"9\"\n      (2, 1) Label \"Lighthouse\""}],"require":[{"pattern":"void\\s+addRow\\s*\\(\\s*GridPane\\s+\\w+\\s*,\\s*int\\s+\\w+\\s*,\\s*String\\s+\\w+\\s*,\\s*String\\s+\\w+\\s*,\\s*String\\s+\\w+\\s*\\)","message":"Write the helper method `void addRow(GridPane grid, int row, String first, String second, String third)`, which returns nothing."}],"forbid":[]}]}