Implements database, adds profiles

This commit is contained in:
2025-10-01 22:23:00 +02:00
parent b2571c191f
commit d2da811868
86 changed files with 17323 additions and 483 deletions

View File

@@ -12,7 +12,7 @@
<artifactId>gui-fx</artifactId>
<properties>
<javafx.version>24.0.2</javafx.version>
<javafx.version>25</javafx.version>
<controlsfx.version>11.2.2</controlsfx.version>
</properties>

View File

@@ -5,6 +5,7 @@ import ch.gtache.fro.practice.BirdPracticeParameters;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
import javafx.fxml.FXML;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import java.util.Objects;
@@ -19,12 +20,27 @@ public final class FXSettingsController implements SettingsController {
@FXML
private TableView<BirdPracticeParameters> table;
@FXML
private TableColumn<BirdPracticeParameters, Boolean> enabledColumn;
@FXML
private TableColumn<BirdPracticeParameters, String> birdColumn;
@FXML
private TableColumn fetchersColumn;
@FXML
private TableColumn picturesColumn;
@FXML
private TableColumn soundsColumn;
@Inject
FXSettingsController(final FXSettingsModel model) {
this.model = Objects.requireNonNull(model);
}
@FXML
private void initialize() {
table.setItems(model.birdPracticeParameters());
}
@Override
public void importSettings() {
throw new UnsupportedOperationException();

View File

@@ -2,6 +2,9 @@ package ch.gtache.fro.gui.fx;
import ch.gtache.fro.gui.SettingsModel;
import ch.gtache.fro.practice.BirdPracticeParameters;
import ch.gtache.fro.practice.GroupedBirdPracticeParameters;
import ch.gtache.fro.practice.GroupedBirdPracticeParametersManager;
import ch.gtache.fro.practice.PracticeProfileProvider;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
import javafx.collections.FXCollections;
@@ -16,7 +19,18 @@ public final class FXSettingsModel implements SettingsModel {
private final ObservableList<BirdPracticeParameters> birdParameters;
@Inject
FXSettingsModel() {
FXSettingsModel(final PracticeProfileProvider profileProvider,
final GroupedBirdPracticeParametersManager birdPracticeParametersManager) {
this.birdParameters = FXCollections.observableArrayList();
}
@Override
public ObservableList<BirdPracticeParameters> birdPracticeParameters() {
return birdParameters;
}
@Override
public GroupedBirdPracticeParameters configuration() {
return null;
}
}

View File

@@ -1,5 +1,6 @@
package ch.gtache.fro.practice.gui.fx;
import ch.gtache.fro.practice.PracticeProfile;
import ch.gtache.fro.practice.PracticeRunner;
import ch.gtache.fro.practice.QuestionType;
import ch.gtache.fro.practice.gui.PracticeSettingsController;
@@ -23,6 +24,9 @@ public final class FXPracticeSettingsController implements PracticeSettingsContr
private final FXPracticeSettingsModel model;
private final QuestionTypeConverter questionTypeConverter;
private final PracticeRunner runner;
@FXML
private PrefixSelectionComboBox<PracticeProfile> profileCombobox;
@FXML
private PrefixSelectionComboBox<QuestionType> questionTypeCombobox;
@FXML
@@ -51,6 +55,9 @@ public final class FXPracticeSettingsController implements PracticeSettingsContr
@FXML
private void initialize() {
profileCombobox.setItems(model.availableProfiles());
profileCombobox.valueProperty().bindBidirectional(model.profileProperty());
profileCombobox.setConverter(new PracticeProfileConverter());
questionTypeCombobox.setItems(model.questionTypes());
questionTypeCombobox.valueProperty().bindBidirectional(model.questionTypeProperty());
questionTypeCombobox.setConverter(questionTypeConverter);

View File

@@ -1,7 +1,10 @@
package ch.gtache.fro.practice.gui.fx;
import ch.gtache.fro.practice.PracticeConfiguration;
import ch.gtache.fro.practice.GroupedBirdPracticeParameters;
import ch.gtache.fro.practice.GroupedBirdPracticeParametersManager;
import ch.gtache.fro.practice.PracticeParameters;
import ch.gtache.fro.practice.PracticeProfile;
import ch.gtache.fro.practice.PracticeProfileProvider;
import ch.gtache.fro.practice.PracticeRun;
import ch.gtache.fro.practice.QuestionType;
import ch.gtache.fro.practice.gui.PracticeSettingsModel;
@@ -28,9 +31,13 @@ import java.util.Map;
@Singleton
public final class FXPracticeSettingsModel implements PracticeSettingsModel {
private static final int DEFAULT_GUESSES_NUMBER = 1;
private static final int DEFAULT_QUESTIONS_NUMBER = 20;
private static final int DEFAULT_SUGGESTIONS_NUMBER = 4;
private final ObservableList<PracticeProfile> availableProfiles;
private final ObjectProperty<PracticeProfile> profile;
private final ReadOnlyObjectWrapper<GroupedBirdPracticeParameters> birdPracticeParameters;
private final ObservableList<QuestionType> questionTypes;
private final ObjectProperty<QuestionType> questionType;
private final ReadOnlyBooleanWrapper hasSuggestions;
@@ -41,19 +48,51 @@ public final class FXPracticeSettingsModel implements PracticeSettingsModel {
private final ObjectProperty<PracticeRun> practiceRun;
@Inject
FXPracticeSettingsModel(final PracticeConfiguration configuration) {
FXPracticeSettingsModel(final PracticeProfileProvider profileProvider,
final GroupedBirdPracticeParametersManager birdParametersManager) {
this.availableProfiles = FXCollections.observableArrayList(profileProvider.getAllObjectsAsync().join());
this.profile = new SimpleObjectProperty<>(availableProfiles.getFirst());
this.birdPracticeParameters = new ReadOnlyObjectWrapper<>(birdParametersManager.getObjectAsync(profile()).join());
this.questionTypes = FXCollections.observableArrayList(QuestionType.values());
this.questionType = new SimpleObjectProperty<>(QuestionType.PICTURE_EXACT);
this.hasSuggestions = new ReadOnlyBooleanWrapper(true);
this.guessesNumber = new SimpleIntegerProperty(configuration.guessesNumber());
this.guessesNumber = new SimpleIntegerProperty(DEFAULT_GUESSES_NUMBER);
this.questionsNumber = new SimpleIntegerProperty(DEFAULT_QUESTIONS_NUMBER);
this.suggestionsNumber = new SimpleIntegerProperty(DEFAULT_SUGGESTIONS_NUMBER);
this.practiceParameters = new ReadOnlyObjectWrapper<>();
this.practiceRun = new SimpleObjectProperty<>();
practiceParameters.bind(Bindings.createObjectBinding(() -> new PracticeParametersImpl(configuration.birdPracticeParameters(), Map.of(questionType(), 1.0),
guessesNumber(), questionsNumber(), suggestionsNumber()), questionType, guessesNumber, questionsNumber, suggestionsNumber));
birdPracticeParameters.bind(Bindings.createObjectBinding(() -> birdParametersManager.getObjectAsync(profile()).join(), profile));
practiceParameters.bind(Bindings.createObjectBinding(() -> new PracticeParametersImpl(birdPracticeParameters(), Map.of(questionType(), 1.0),
guessesNumber(), questionsNumber(), suggestionsNumber()), birdPracticeParameters, questionType, guessesNumber, questionsNumber, suggestionsNumber));
hasSuggestions.bind(questionType.isNotEqualTo(QuestionType.PICTURE_EXACT).and(questionType.isNotEqualTo(QuestionType.SOUND_EXACT)));
guessesNumber.addListener((_, _, newValue) -> configuration.setGuessesNumber(newValue.intValue()));
}
@Override
public ObservableList<PracticeProfile> availableProfiles() {
return availableProfiles;
}
@Override
public PracticeProfile profile() {
return profile.get();
}
@Override
public void setProfile(final PracticeProfile profile) {
this.profile.set(profile);
}
ObjectProperty<PracticeProfile> profileProperty() {
return profile;
}
@Override
public GroupedBirdPracticeParameters birdPracticeParameters() {
return birdPracticeParameters.get();
}
ReadOnlyObjectProperty<GroupedBirdPracticeParameters> birdPracticeParametersProperty() {
return birdPracticeParameters.getReadOnlyProperty();
}
@Override

View File

@@ -0,0 +1,25 @@
package ch.gtache.fro.practice.gui.fx;
import ch.gtache.fro.practice.PracticeProfile;
import jakarta.inject.Inject;
import javafx.util.StringConverter;
/**
* {@link StringConverter} for {@link PracticeProfile}
*/
public class PracticeProfileConverter extends StringConverter<PracticeProfile> {
@Inject
PracticeProfileConverter() {
}
@Override
public String toString(final PracticeProfile object) {
return object.name();
}
@Override
public PracticeProfile fromString(final String string) {
return null;
}
}

View File

@@ -8,6 +8,9 @@ import javafx.util.StringConverter;
import java.util.Objects;
/**
* {@link StringConverter} for {@link QuestionType}
*/
public class QuestionTypeConverter extends StringConverter<QuestionType> {
private final QuestionTypeTranslator translator;

View File

@@ -5,7 +5,7 @@ import ch.gtache.fro.BirdProvider;
import ch.gtache.fro.BirdTranslator;
import ch.gtache.fro.ProvisionException;
import ch.gtache.fro.TranslationException;
import ch.gtache.fro.impl.BirdImpl;
import ch.gtache.fro.impl.MinimalBirdImpl;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
@@ -42,7 +42,7 @@ public class UserInputNormalizer {
Bird getBird(final String input) {
final var normalized = normalize(input);
return birdMap.getOrDefault(normalized, new BirdImpl(input));
return birdMap.getOrDefault(normalized, new MinimalBirdImpl(input));
}
private static String normalize(final CharSequence input) {

View File

@@ -12,15 +12,15 @@
AnchorPane.rightAnchor="10.0" AnchorPane.topAnchor="10.0">
<columns>
<TableColumn maxWidth="1.7976931348623157E308" minWidth="-1.0" prefWidth="75.0"
text="%settings.table.column.enabled"/>
text="%settings.table.column.bird" fx:id="birdColumn"/>
<TableColumn maxWidth="1.7976931348623157E308" minWidth="-1.0" prefWidth="75.0"
text="%settings.table.column.bird"/>
text="%settings.table.column.enabled" fx:id="enabledColumn"/>
<TableColumn maxWidth="1.7976931348623157E308" minWidth="-1.0" prefWidth="75.0"
text="%settings.table.column.fetchers"/>
text="%settings.table.column.fetchers" fx:id="fetchersColumn"/>
<TableColumn maxWidth="1.7976931348623157E308" minWidth="-1.0" prefWidth="75.0"
text="%settings.table.column.pictures"/>
text="%settings.table.column.pictures" fx:id="picturesColumn"/>
<TableColumn maxWidth="1.7976931348623157E308" minWidth="-1.0" prefWidth="75.0"
text="%settings.table.column.sounds"/>
text="%settings.table.column.sounds" fx:id="soundsColumn"/>
</columns>
<columnResizePolicy>
<TableView fx:constant="CONSTRAINED_RESIZE_POLICY"/>

View File

@@ -4,36 +4,38 @@
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.layout.*?>
<?import org.controlsfx.control.PrefixSelectionComboBox?>
<?import org.controlsfx.control.textfield.CustomTextField?>
<GridPane hgap="10.0" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" vgap="10.0" xmlns="http://javafx.com/javafx/24.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="ch.gtache.fro.practice.gui.fx.FXPracticePictureExactController">
<GridPane hgap="10.0" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" vgap="10.0"
xmlns="http://javafx.com/javafx/24.0.1" xmlns:fx="http://javafx.com/fxml/1"
fx:controller="ch.gtache.fro.practice.gui.fx.FXPracticePictureExactController">
<columnConstraints>
<ColumnConstraints hgrow="ALWAYS" />
<ColumnConstraints hgrow="SOMETIMES" />
<ColumnConstraints hgrow="ALWAYS"/>
<ColumnConstraints hgrow="SOMETIMES"/>
</columnConstraints>
<rowConstraints>
<RowConstraints vgrow="ALWAYS" />
<RowConstraints vgrow="ALWAYS" />
<RowConstraints vgrow="ALWAYS" />
<RowConstraints vgrow="SOMETIMES" />
<RowConstraints vgrow="SOMETIMES" />
<RowConstraints vgrow="SOMETIMES" />
<RowConstraints vgrow="ALWAYS"/>
<RowConstraints vgrow="ALWAYS"/>
<RowConstraints vgrow="ALWAYS"/>
<RowConstraints vgrow="SOMETIMES"/>
<RowConstraints vgrow="SOMETIMES"/>
<RowConstraints vgrow="SOMETIMES"/>
</rowConstraints>
<children>
<ImageView fx:id="pictureView" fitHeight="800.0" fitWidth="800.0" pickOnBounds="true" preserveRatio="true" GridPane.columnSpan="2147483647" GridPane.rowIndex="1" />
<CustomTextField fx:id="inputField" onAction="#enterPressed" GridPane.rowIndex="3" />
<PrefixSelectionComboBox fx:id="typeCombobox" managed="false" visible="false" GridPane.columnIndex="1" GridPane.rowIndex="3" />
<Button fx:id="confirmButton" mnemonicParsing="false" onAction="#confirmPressed" text="%practice.picture.exact.validate.button.label" GridPane.columnSpan="2147483647" GridPane.halignment="CENTER" GridPane.rowIndex="5" />
<Label fx:id="progressLabel" text="Label" GridPane.columnSpan="2147483647" GridPane.halignment="CENTER" />
<VBox fx:id="guessesBox" GridPane.columnSpan="2147483647" GridPane.halignment="CENTER" GridPane.rowIndex="4" />
<Label fx:id="answerLabel" GridPane.rowIndex="2" />
<ImageView fx:id="pictureView" fitHeight="800.0" fitWidth="800.0" pickOnBounds="true" preserveRatio="true"
GridPane.columnSpan="2147483647" GridPane.rowIndex="1"/>
<CustomTextField fx:id="inputField" onAction="#enterPressed" GridPane.rowIndex="3"/>
<PrefixSelectionComboBox fx:id="typeCombobox" managed="false" visible="false" GridPane.columnIndex="1"
GridPane.rowIndex="3"/>
<Button fx:id="confirmButton" mnemonicParsing="false" onAction="#confirmPressed"
text="%practice.picture.exact.validate.button.label" GridPane.columnSpan="2147483647"
GridPane.halignment="CENTER" GridPane.rowIndex="5"/>
<Label fx:id="progressLabel" text="Label" GridPane.columnSpan="2147483647" GridPane.halignment="CENTER"/>
<VBox fx:id="guessesBox" GridPane.columnSpan="2147483647" GridPane.halignment="CENTER" GridPane.rowIndex="4"/>
<Label fx:id="answerLabel" GridPane.rowIndex="2" GridPane.columnSpan="2147483647" GridPane.halignment="CENTER"/>
</children>
<padding>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0"/>
</padding>
</GridPane>

View File

@@ -15,6 +15,7 @@
<ColumnConstraints hgrow="SOMETIMES" />
</columnConstraints>
<rowConstraints>
<RowConstraints vgrow="SOMETIMES" />
<RowConstraints vgrow="SOMETIMES" />
<RowConstraints vgrow="SOMETIMES" />
<RowConstraints vgrow="SOMETIMES" />
@@ -22,15 +23,17 @@
<RowConstraints vgrow="SOMETIMES" />
</rowConstraints>
<children>
<Label text="%practice.settings.type.label" />
<PrefixSelectionComboBox fx:id="questionTypeCombobox" GridPane.columnIndex="1" />
<Label fx:id="suggestionsNumberLabel" text="%practice.settings.suggestions.number.label" GridPane.rowIndex="3" />
<Spinner fx:id="suggestionsNumberSpinner" editable="true" GridPane.columnIndex="1" GridPane.rowIndex="3" />
<Button mnemonicParsing="false" onAction="#startPressed" text="%practice.settings.start.button.label" GridPane.columnSpan="2147483647" GridPane.halignment="CENTER" GridPane.rowIndex="4" />
<Label text="%practice.settings.questions.number.label" GridPane.rowIndex="1" />
<Spinner fx:id="questionsNumberSpinner" GridPane.columnIndex="1" GridPane.rowIndex="1" />
<Label text="%practice.settings.guesses.number.label" GridPane.rowIndex="2" />
<Spinner fx:id="guessesNumberSpinner" GridPane.columnIndex="1" GridPane.rowIndex="2" />
<Label text="%practice.settings.type.label" GridPane.rowIndex="1" />
<PrefixSelectionComboBox fx:id="questionTypeCombobox" GridPane.columnIndex="1" GridPane.rowIndex="1" />
<Label fx:id="suggestionsNumberLabel" text="%practice.settings.suggestions.number.label" GridPane.rowIndex="4" />
<Spinner fx:id="suggestionsNumberSpinner" editable="true" GridPane.columnIndex="1" GridPane.rowIndex="4" />
<Button mnemonicParsing="false" onAction="#startPressed" text="%practice.settings.start.button.label" GridPane.columnSpan="2147483647" GridPane.halignment="CENTER" GridPane.rowIndex="5" />
<Label text="%practice.settings.questions.number.label" GridPane.rowIndex="2" />
<Spinner fx:id="questionsNumberSpinner" GridPane.columnIndex="1" GridPane.rowIndex="2" />
<Label text="%practice.settings.guesses.number.label" GridPane.rowIndex="3" />
<Spinner fx:id="guessesNumberSpinner" GridPane.columnIndex="1" GridPane.rowIndex="3" />
<Label text="%practice.settings.profile.label" />
<PrefixSelectionComboBox fx:id="profileCombobox" GridPane.columnIndex="1" />
</children>
<padding>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />