diff --git a/src/main/java/es/kauron/jstudy/controller/Controller.java b/src/main/java/es/kauron/jstudy/controller/Controller.java index f3e4e4a..7e0168d 100644 --- a/src/main/java/es/kauron/jstudy/controller/Controller.java +++ b/src/main/java/es/kauron/jstudy/controller/Controller.java @@ -1,6 +1,7 @@ package es.kauron.jstudy.controller; import es.kauron.jstudy.Main; +import es.kauron.jstudy.model.AnsweredItem; import es.kauron.jstudy.model.AppPrefs; import es.kauron.jstudy.model.TestItem; import javafx.application.Platform; @@ -356,7 +357,7 @@ public class Controller implements Initializable { FXMLLoader loader = new FXMLLoader(Main.class.getResource("view/test.fxml")); Parent root = loader.load(); - ((TestController) loader.getController()).setList(new ArrayList<>(list)); + ((TestController) loader.getController()).setData(new ArrayList<>(list), this); theTest = new Tab("Test: " + tabPane.getSelectionModel().getSelectedItem().getText(), root); tabPane.getTabs().add(theTest); @@ -367,6 +368,20 @@ public class Controller implements Initializable { } } + void createStatsTab(List answers) { + try { + FXMLLoader loader = new FXMLLoader(Main.class.getResource("view/stats.fxml")); + Parent root = loader.load(); + ((StatsController) loader.getController()).setData(answers); + Matcher m = Pattern.compile("^Test: (.*)$").matcher(tabPane.getSelectionModel().getSelectedItem().getText()); + m.find(); + tabPane.getTabs().add(new Tab("Stats: " + m.group(1), root)); + tabPane.getSelectionModel().selectLast(); + } catch (IOException e) { + e.printStackTrace(); + } + } + @FXML protected void onAboutAction(ActionEvent event) { if (Desktop.isDesktopSupported()) { diff --git a/src/main/java/es/kauron/jstudy/controller/StatsController.java b/src/main/java/es/kauron/jstudy/controller/StatsController.java new file mode 100644 index 0000000..bd24a6b --- /dev/null +++ b/src/main/java/es/kauron/jstudy/controller/StatsController.java @@ -0,0 +1,42 @@ +package es.kauron.jstudy.controller; + +import es.kauron.jstudy.model.AnsweredItem; +import javafx.beans.binding.Bindings; +import javafx.collections.FXCollections; +import javafx.fxml.FXML; +import javafx.fxml.Initializable; +import javafx.scene.control.TableColumn; +import javafx.scene.control.TableRow; +import javafx.scene.control.TableView; + +import java.net.URL; +import java.util.List; +import java.util.ResourceBundle; + +public class StatsController implements Initializable { + @FXML + protected TableView table; + + @FXML + protected TableColumn numCol, questionCol, answerCol, userAnswerCol, timeCol; + + @Override + public void initialize(URL location, ResourceBundle resources) { + numCol.setCellValueFactory(e -> e.getValue().indexProperty.asString()); + questionCol.setCellValueFactory(e -> e.getValue().item.questionProperty()); + answerCol.setCellValueFactory(e -> e.getValue().item.answerProperty()); + userAnswerCol.setCellValueFactory(e -> e.getValue().answerProperty); + timeCol.setCellValueFactory(e -> Bindings.format("%.3f", e.getValue().timeProperty.divide(1e9))); + } + + public void setData(List answers) { + table.setItems(FXCollections.observableArrayList(answers)); + // Set wrong answers in bold + table.setRowFactory(param -> { + TableRow row = new TableRow<>(); + row.itemProperty().addListener((obj, o, n) -> + row.setStyle(n == null || n.isRight() ? "" : "-fx-font-weight: bold")); + return row; + }); + } +} diff --git a/src/main/java/es/kauron/jstudy/controller/TestController.java b/src/main/java/es/kauron/jstudy/controller/TestController.java index 956df7b..34523de 100644 --- a/src/main/java/es/kauron/jstudy/controller/TestController.java +++ b/src/main/java/es/kauron/jstudy/controller/TestController.java @@ -1,5 +1,6 @@ package es.kauron.jstudy.controller; +import es.kauron.jstudy.model.AnsweredItem; import es.kauron.jstudy.model.AppPrefs; import es.kauron.jstudy.model.TestItem; import es.kauron.jstudy.util.Clock; @@ -12,6 +13,7 @@ import javafx.scene.control.*; import javafx.scene.layout.Pane; import java.net.URL; +import java.util.ArrayList; import java.util.List; import java.util.ResourceBundle; @@ -36,6 +38,9 @@ public class TestController implements Initializable { private final IntegerProperty errors = new SimpleIntegerProperty(0); private final ObjectProperty item = new SimpleObjectProperty<>(); private final IntegerProperty done = new SimpleIntegerProperty(0); + private final List answers = new ArrayList<>(); + private long timeQuestionStarted = 0; + private Controller controller; // Time accounting private final Clock clock = new Clock(); @@ -51,7 +56,8 @@ public class TestController implements Initializable { }); } - void setList(List list) { + void setData(List list, Controller controller) { + this.controller = controller; this.list = list; int total = list.size(); progressLabel.textProperty().bind(Bindings.format( @@ -72,13 +78,18 @@ public class TestController implements Initializable { @FXML private void onNextAction(ActionEvent event) { + // Do not accept empty responses + if (answer.getText().trim().isEmpty()) return; + // Record the answer + long timeElapsed = System.nanoTime() - timeQuestionStarted; + AnsweredItem ai = new AnsweredItem(item.get(), answers.size() + 1, answer.getText().trim(), timeElapsed); + answers.add(ai); prevAnswer.setText(answer.getText()); - boolean right = item.get().checkAnswer(answer.getText()); - correctAnswer.setVisible(!right); - correctLabel.setVisible(!right); + correctAnswer.setVisible(!ai.isRight()); + correctLabel.setVisible(!ai.isRight()); - if (!right) { + if (!ai.isRight()) { errors.set(errors.get() + 1); prevAnswer.setStyle("-fx-text-fill: #C40000;"); } else { @@ -86,10 +97,10 @@ public class TestController implements Initializable { } // Remove the question from the pool if the question was correctly answered or there is no repetition - if (right || !AppPrefs.repeatWrong.get()) { + if (ai.isRight() || !AppPrefs.repeatWrong.get()) { if (!correctingError.get()) done.set(done.get() + 1); - correctingError.set(!right && AppPrefs.repeatImmediately.get()); + correctingError.set(!ai.isRight() && AppPrefs.repeatImmediately.get()); list.remove(item.get()); if (list.size() == 0) { onEndAction(null); @@ -105,6 +116,7 @@ public class TestController implements Initializable { } answer.setText(""); answer.requestFocus(); + timeQuestionStarted = System.nanoTime(); } private void chooseQuestion() { @@ -121,6 +133,7 @@ public class TestController implements Initializable { chooseQuestion(); answer.setText(""); answer.requestFocus(); + timeQuestionStarted = System.nanoTime(); } @FXML @@ -130,6 +143,7 @@ public class TestController implements Initializable { pauseCheckBox.setSelected(true); pauseCheckBox.setDisable(true); clock.stop(); + controller.createStatsTab(answers); } void stopTimer() { diff --git a/src/main/java/es/kauron/jstudy/model/AnsweredItem.java b/src/main/java/es/kauron/jstudy/model/AnsweredItem.java new file mode 100644 index 0000000..502f973 --- /dev/null +++ b/src/main/java/es/kauron/jstudy/model/AnsweredItem.java @@ -0,0 +1,24 @@ +package es.kauron.jstudy.model; + +import javafx.beans.property.ReadOnlyIntegerProperty; +import javafx.beans.property.ReadOnlyIntegerWrapper; +import javafx.beans.property.ReadOnlyLongWrapper; +import javafx.beans.property.ReadOnlyStringWrapper; + +public class AnsweredItem { + public final ReadOnlyStringWrapper answerProperty; + public final ReadOnlyLongWrapper timeProperty; + public final ReadOnlyIntegerProperty indexProperty; + public final TestItem item; + + public AnsweredItem(TestItem item, int index, String answer, long timeNano) { + this.item = item; + this.indexProperty = new ReadOnlyIntegerWrapper(index); + this.answerProperty = new ReadOnlyStringWrapper(answer); + this.timeProperty = new ReadOnlyLongWrapper(timeNano); + } + + public boolean isRight() { + return item.getAnswer().equals(answerProperty.get()); + } +} diff --git a/src/main/java/es/kauron/jstudy/model/TestItem.java b/src/main/java/es/kauron/jstudy/model/TestItem.java index c93e7ff..344af3d 100644 --- a/src/main/java/es/kauron/jstudy/model/TestItem.java +++ b/src/main/java/es/kauron/jstudy/model/TestItem.java @@ -49,10 +49,6 @@ public class TestItem { return !question.get().isEmpty() && !answer.get().isEmpty(); } - public boolean checkAnswer(String answer) { - return getAnswer().equals(answer); - } - public static void saveTo(File file, List data) { try (OutputStreamWriter writer = new OutputStreamWriter( new FileOutputStream(file), StandardCharsets.UTF_8.newEncoder())) { diff --git a/src/main/resources/es/kauron/jstudy/view/stats.fxml b/src/main/resources/es/kauron/jstudy/view/stats.fxml new file mode 100644 index 0000000..a4d5d73 --- /dev/null +++ b/src/main/resources/es/kauron/jstudy/view/stats.fxml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + +