Exact picture seems to work
This commit is contained in:
@@ -4,5 +4,4 @@ package ch.gtache.fro;
|
||||
* {@link Provider} of {@link Bird}
|
||||
*/
|
||||
public interface BirdProvider extends Provider<Bird> {
|
||||
|
||||
}
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
package ch.gtache.fro;
|
||||
|
||||
import ch.gtache.fro.practice.BirdPracticeParameters;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.time.Duration;
|
||||
import java.util.Collection;
|
||||
|
||||
public interface Configuration {
|
||||
|
||||
/**
|
||||
* Returns the root folder where the data is stored
|
||||
*
|
||||
* @return The root folder
|
||||
*/
|
||||
Path rootFolder();
|
||||
|
||||
/**
|
||||
* Sets the root folder where the data is stored
|
||||
*
|
||||
* @param rootFolder The root folder
|
||||
*/
|
||||
void setRootFolder(final Path rootFolder);
|
||||
|
||||
/**
|
||||
* Returns the duration to wait between two fetches
|
||||
*
|
||||
* @return The duration
|
||||
*/
|
||||
Duration waitBetweenFetch();
|
||||
|
||||
/**
|
||||
* Sets the duration to wait between two fetches
|
||||
*
|
||||
* @param waitBetweenFetch The duration
|
||||
*/
|
||||
void setWaitBetweenFetch(final Duration waitBetweenFetch);
|
||||
|
||||
/**
|
||||
* Returns the bird practice parameters
|
||||
*
|
||||
* @return The parameters
|
||||
*/
|
||||
Collection<BirdPracticeParameters> birdPracticeParameters();
|
||||
|
||||
/**
|
||||
* Returns the bird practice parameters for a specific bird
|
||||
*
|
||||
* @param bird The bird
|
||||
* @return The parameters
|
||||
*/
|
||||
BirdPracticeParameters birdPracticeParameters(final Bird bird);
|
||||
|
||||
/**
|
||||
* Sets the bird practice parameters
|
||||
*
|
||||
* @param birdPracticeParameters The parameters
|
||||
*/
|
||||
default void setBirdPracticeParameters(final Collection<? extends BirdPracticeParameters> birdPracticeParameters) {
|
||||
for (final var birdPracticeParameter : birdPracticeParameters) {
|
||||
setBirdPracticeParameters(birdPracticeParameter);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the bird practice parameters for a specific bird
|
||||
*
|
||||
* @param birdPracticeParameters The parameters
|
||||
*/
|
||||
void setBirdPracticeParameters(final BirdPracticeParameters birdPracticeParameters);
|
||||
}
|
||||
35
api/src/main/java/ch/gtache/fro/FetcherConfiguration.java
Normal file
35
api/src/main/java/ch/gtache/fro/FetcherConfiguration.java
Normal file
@@ -0,0 +1,35 @@
|
||||
package ch.gtache.fro;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.time.Duration;
|
||||
|
||||
public interface FetcherConfiguration {
|
||||
|
||||
/**
|
||||
* Returns the root folder where the data is stored
|
||||
*
|
||||
* @return The root folder
|
||||
*/
|
||||
Path rootFolder();
|
||||
|
||||
/**
|
||||
* Sets the root folder where the data is stored
|
||||
*
|
||||
* @param rootFolder The root folder
|
||||
*/
|
||||
void setRootFolder(final Path rootFolder);
|
||||
|
||||
/**
|
||||
* Returns the duration to wait between two fetches
|
||||
*
|
||||
* @return The duration
|
||||
*/
|
||||
Duration waitBetweenFetch();
|
||||
|
||||
/**
|
||||
* Sets the duration to wait between two fetches
|
||||
*
|
||||
* @param waitBetweenFetch The duration
|
||||
*/
|
||||
void setWaitBetweenFetch(final Duration waitBetweenFetch);
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package ch.gtache.fro.practice;
|
||||
|
||||
import ch.gtache.fro.Bird;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
public interface PracticeConfiguration {
|
||||
|
||||
/**
|
||||
* Sets the number of unsuccessful guesses before a question is considered as failed
|
||||
*
|
||||
* @return The number of unsuccessful guesses
|
||||
*/
|
||||
int guessesNumber();
|
||||
|
||||
/**
|
||||
* Sets the number of unsuccessful guesses before a question is considered as failed
|
||||
*
|
||||
* @param guessNumber The number of unsuccessful guesses
|
||||
*/
|
||||
void setGuessesNumber(final int guessNumber);
|
||||
|
||||
/**
|
||||
* Returns the saved question rates
|
||||
*
|
||||
* @return The question rates
|
||||
*/
|
||||
Map<QuestionType, Double> questionRates();
|
||||
|
||||
/**
|
||||
* Sets the question rates
|
||||
*
|
||||
* @param questionRates The question rates
|
||||
*/
|
||||
void setQuestionRates(final Map<QuestionType, Double> questionRates);
|
||||
|
||||
/**
|
||||
* Returns the bird practice parameters
|
||||
*
|
||||
* @return The parameters
|
||||
*/
|
||||
Collection<BirdPracticeParameters> birdPracticeParameters();
|
||||
|
||||
/**
|
||||
* Returns the bird practice parameters for a specific bird
|
||||
*
|
||||
* @param bird The bird
|
||||
* @return The parameters
|
||||
*/
|
||||
BirdPracticeParameters birdPracticeParameters(final Bird bird);
|
||||
|
||||
/**
|
||||
* Sets the bird practice parameters
|
||||
*
|
||||
* @param birdPracticeParameters The parameters
|
||||
*/
|
||||
void setBirdPracticeParameters(final Collection<? extends BirdPracticeParameters> birdPracticeParameters);
|
||||
|
||||
/**
|
||||
* Sets the bird practice parameters for a specific bird
|
||||
*
|
||||
* @param birdPracticeParameters The parameters
|
||||
*/
|
||||
void setBirdPracticeParameters(final BirdPracticeParameters birdPracticeParameters);
|
||||
}
|
||||
@@ -17,23 +17,30 @@ public interface PracticeParameters {
|
||||
Map<Bird, BirdPracticeParameters> birdParameters();
|
||||
|
||||
/**
|
||||
* Returns the practice type
|
||||
* Returns the number of unsuccessful guesses before a question is considered as failed
|
||||
*
|
||||
* @return The practice type
|
||||
* @return The number of unsuccessful guesses
|
||||
*/
|
||||
PracticeType practiceType();
|
||||
int guessesNumber();
|
||||
|
||||
/**
|
||||
* Returns the question type rates (chance of each type)
|
||||
*
|
||||
* @return The question types
|
||||
*/
|
||||
Map<QuestionType, Double> questionTypeRates();
|
||||
|
||||
/**
|
||||
* Returns the number of questions in this practice
|
||||
*
|
||||
* @return The number of questions
|
||||
*/
|
||||
int questionsCount();
|
||||
int questionsNumber();
|
||||
|
||||
/**
|
||||
* Returns the number of propositions for a multichoice type
|
||||
*
|
||||
* @return The number of propositions
|
||||
*/
|
||||
int propositionsCount();
|
||||
int propositionsNumber();
|
||||
}
|
||||
|
||||
@@ -13,4 +13,11 @@ public interface PracticeQuestion {
|
||||
* @return The bird
|
||||
*/
|
||||
Bird bird();
|
||||
|
||||
/**
|
||||
* Returns the type of this question
|
||||
*
|
||||
* @return The type
|
||||
*/
|
||||
QuestionType questionType();
|
||||
}
|
||||
|
||||
@@ -14,10 +14,37 @@ public interface PracticeResult {
|
||||
*/
|
||||
List<PracticeQuestion> correctQuestions();
|
||||
|
||||
/**
|
||||
* Returns the total number of correct questions
|
||||
*
|
||||
* @return The total number of correct questions
|
||||
*/
|
||||
default int correctQuestionsCount() {
|
||||
return correctQuestions().size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the failed questions
|
||||
*
|
||||
* @return The failed questions
|
||||
*/
|
||||
List<PracticeQuestion> failedQuestions();
|
||||
|
||||
/**
|
||||
* Returns the total number of failed questions
|
||||
*
|
||||
* @return The total number of failed questions
|
||||
*/
|
||||
default int failedQuestionsCount() {
|
||||
return failedQuestions().size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the total number of questions
|
||||
*
|
||||
* @return The total number of questions
|
||||
*/
|
||||
default int totalQuestions() {
|
||||
return correctQuestions().size() + failedQuestions().size();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,4 +41,8 @@ public interface PracticeRun {
|
||||
* @return The failed questions
|
||||
*/
|
||||
List<PracticeQuestion> failedQuestions();
|
||||
|
||||
default boolean isFinished() {
|
||||
return currentQuestionIndex() == parameters().questionsNumber();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
package ch.gtache.fro.practice;
|
||||
|
||||
import ch.gtache.fro.Translator;
|
||||
|
||||
/**
|
||||
* Translates a {@link PracticeType} to a string
|
||||
*/
|
||||
public interface PracticeTypeTranslator extends Translator<PracticeType> {
|
||||
|
||||
}
|
||||
@@ -3,7 +3,7 @@ package ch.gtache.fro.practice;
|
||||
/**
|
||||
* Represents the type of practice
|
||||
*/
|
||||
public enum PracticeType {
|
||||
public enum QuestionType {
|
||||
/**
|
||||
* See an image, must write the bird name
|
||||
*/
|
||||
@@ -0,0 +1,10 @@
|
||||
package ch.gtache.fro.practice;
|
||||
|
||||
import ch.gtache.fro.Translator;
|
||||
|
||||
/**
|
||||
* Translates a {@link QuestionType} to a string
|
||||
*/
|
||||
public interface QuestionTypeTranslator extends Translator<QuestionType> {
|
||||
|
||||
}
|
||||
@@ -2,9 +2,9 @@ package ch.gtache.fro.chants.oiseaux.fr;
|
||||
|
||||
import ch.gtache.fro.Bird;
|
||||
import ch.gtache.fro.BirdProvider;
|
||||
import ch.gtache.fro.Configuration;
|
||||
import ch.gtache.fro.FetchException;
|
||||
import ch.gtache.fro.Fetcher;
|
||||
import ch.gtache.fro.FetcherConfiguration;
|
||||
import ch.gtache.fro.selenium.AbstractSeleniumFetcher;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -21,7 +21,7 @@ public class ChantOiseauxFrFetcher extends AbstractSeleniumFetcher {
|
||||
* @param configuration The configuration
|
||||
* @throws NullPointerException If any parameter is null
|
||||
*/
|
||||
protected ChantOiseauxFrFetcher(final BirdProvider birdProvider, final Configuration configuration) {
|
||||
protected ChantOiseauxFrFetcher(final BirdProvider birdProvider, final FetcherConfiguration configuration) {
|
||||
super(birdProvider, configuration);
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,10 @@
|
||||
|
||||
<artifactId>fro-core</artifactId>
|
||||
|
||||
<properties>
|
||||
<jackson.version>2.20.0</jackson.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>ch.gtache.fro</groupId>
|
||||
@@ -20,6 +24,11 @@
|
||||
<groupId>org.apache.logging.log4j</groupId>
|
||||
<artifactId>log4j-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
<version>${jackson.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.dagger</groupId>
|
||||
<artifactId>dagger</artifactId>
|
||||
|
||||
@@ -8,8 +8,10 @@ import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
@@ -22,7 +24,8 @@ public abstract class AbstractFetcher implements Fetcher {
|
||||
private static final Pattern FILENAME_SPLIT = Pattern.compile("__");
|
||||
|
||||
private final BirdProvider birdProvider;
|
||||
private final Configuration configuration;
|
||||
private final FetcherConfiguration configuration;
|
||||
private final Set<Bird> fetched;
|
||||
|
||||
/**
|
||||
* Instantiates the fetcher
|
||||
@@ -31,9 +34,10 @@ public abstract class AbstractFetcher implements Fetcher {
|
||||
* @param configuration The configuration
|
||||
* @throws NullPointerException If any parameter is null
|
||||
*/
|
||||
protected AbstractFetcher(final BirdProvider birdProvider, final Configuration configuration) {
|
||||
protected AbstractFetcher(final BirdProvider birdProvider, final FetcherConfiguration configuration) {
|
||||
this.birdProvider = Objects.requireNonNull(birdProvider);
|
||||
this.configuration = Objects.requireNonNull(configuration);
|
||||
this.fetched = new HashSet<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -61,6 +65,7 @@ public abstract class AbstractFetcher implements Fetcher {
|
||||
@Override
|
||||
public FilledBird fetch(final Bird bird) throws FetchException {
|
||||
if (!isAlreadyFetched(bird)) {
|
||||
fetched.add(bird);
|
||||
logger.info("Fetching {} ({})", bird, name());
|
||||
download(bird);
|
||||
}
|
||||
@@ -169,6 +174,9 @@ public abstract class AbstractFetcher implements Fetcher {
|
||||
}
|
||||
|
||||
private boolean isAlreadyFetched(final Bird bird) {
|
||||
if (fetched.contains(bird)) {
|
||||
return true;
|
||||
}
|
||||
final var path = getCachePath(bird);
|
||||
if (Files.isDirectory(path)) {
|
||||
try (final var found = Files.find(path, Integer.MAX_VALUE, (p, bfa) -> bfa.isRegularFile())) {
|
||||
|
||||
23
core/src/main/java/ch/gtache/fro/impl/BirdImpl.java
Normal file
23
core/src/main/java/ch/gtache/fro/impl/BirdImpl.java
Normal file
@@ -0,0 +1,23 @@
|
||||
package ch.gtache.fro.impl;
|
||||
|
||||
import ch.gtache.fro.Bird;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Implementation of {@link Bird}
|
||||
*
|
||||
* @param name The bird name
|
||||
*/
|
||||
public record BirdImpl(String name) implements Bird {
|
||||
|
||||
/**
|
||||
* Instantiates the bird
|
||||
*
|
||||
* @param name The bird name
|
||||
* @throws NullPointerException If any parameter is null
|
||||
*/
|
||||
public BirdImpl {
|
||||
Objects.requireNonNull(name);
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ import java.util.Locale;
|
||||
*/
|
||||
public class CommonBirdsProvider implements BirdProvider {
|
||||
|
||||
|
||||
/**
|
||||
* Instantiates the provider
|
||||
*/
|
||||
|
||||
@@ -4,6 +4,8 @@ import ch.gtache.fro.Bird;
|
||||
import ch.gtache.fro.BirdTranslator;
|
||||
import jakarta.inject.Inject;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Implementation of {@link BirdTranslator} for {@link CommonBirds}
|
||||
*/
|
||||
@@ -14,6 +16,15 @@ public class CommonBirdsTranslatorImpl extends AbstractBundleTranslator<Bird> im
|
||||
super("ch.gtache.fro.impl.BirdBundle");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String translate(final Bird object, final Locale locale) {
|
||||
if (object instanceof CommonBirds) {
|
||||
return super.translate(object, locale);
|
||||
} else {
|
||||
return object.name();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getKey(final Bird object) {
|
||||
return "bird." + object.name() + ".label";
|
||||
|
||||
@@ -1,157 +0,0 @@
|
||||
package ch.gtache.fro.impl;
|
||||
|
||||
import ch.gtache.fro.Bird;
|
||||
import ch.gtache.fro.BirdProvider;
|
||||
import ch.gtache.fro.Configuration;
|
||||
import ch.gtache.fro.PictureType;
|
||||
import ch.gtache.fro.ProvisionException;
|
||||
import ch.gtache.fro.SoundType;
|
||||
import ch.gtache.fro.practice.BirdPracticeParameters;
|
||||
import ch.gtache.fro.practice.impl.BirdPracticeParametersImpl;
|
||||
import jakarta.inject.Inject;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.time.Duration;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.prefs.Preferences;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
/**
|
||||
* Implementation of {@link Configuration}
|
||||
*/
|
||||
public class ConfigurationImpl implements Configuration {
|
||||
|
||||
private static final Logger logger = LogManager.getLogger(ConfigurationImpl.class);
|
||||
|
||||
private static final Path DEFAULT_ROOT_FOLDER = Path.of(System.getProperty("user.home"), ".fro", "birds");
|
||||
private static final Duration DEFAULT_WAIT_BETWEEN_FETCH = Duration.ofMillis(3000);
|
||||
private static final String ROOT_FOLDER_KEY = "rootFolder";
|
||||
private static final String WAIT_BETWEEN_FETCH_KEY = "waitBetweenFetch";
|
||||
|
||||
|
||||
private final BirdProvider birdProvider;
|
||||
private final Preferences preferences;
|
||||
private Path rootFolder;
|
||||
private Duration waitBetweenFetch;
|
||||
private final Map<Bird, BirdPracticeParameters> birdPracticeParameters;
|
||||
|
||||
@Inject
|
||||
ConfigurationImpl(final BirdProvider birdProvider) {
|
||||
this.birdProvider = requireNonNull(birdProvider);
|
||||
this.rootFolder = DEFAULT_ROOT_FOLDER;
|
||||
this.waitBetweenFetch = DEFAULT_WAIT_BETWEEN_FETCH;
|
||||
this.preferences = Preferences.userNodeForPackage(this.getClass());
|
||||
this.birdPracticeParameters = new HashMap<>();
|
||||
load();
|
||||
}
|
||||
|
||||
private void load() {
|
||||
this.rootFolder = Path.of(preferences.get(ROOT_FOLDER_KEY, DEFAULT_ROOT_FOLDER.toString()));
|
||||
this.waitBetweenFetch = Duration.ofMillis(preferences.getLong(WAIT_BETWEEN_FETCH_KEY, DEFAULT_WAIT_BETWEEN_FETCH.toMillis()));
|
||||
try {
|
||||
final var allBirds = birdProvider.getAllObjects();
|
||||
final var practiceParameters = allBirds.stream().map(this::loadPracticeParameters).collect(Collectors.toMap(BirdPracticeParameters::bird, e -> e));
|
||||
birdPracticeParameters.putAll(practiceParameters);
|
||||
} catch (final ProvisionException e) {
|
||||
logger.error("Failed to get all birds", e);
|
||||
}
|
||||
logger.info("Loaded configuration: rootFolder={}, waitBetweenFetch={}", rootFolder, waitBetweenFetch);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Path rootFolder() {
|
||||
return rootFolder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRootFolder(final Path rootFolder) {
|
||||
logger.info("Setting root folder to {}", rootFolder);
|
||||
this.rootFolder = requireNonNull(rootFolder);
|
||||
preferences.put(ROOT_FOLDER_KEY, rootFolder.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Duration waitBetweenFetch() {
|
||||
return waitBetweenFetch;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setWaitBetweenFetch(final Duration waitBetweenFetch) {
|
||||
logger.info("Setting wait between fetch to {}", waitBetweenFetch);
|
||||
this.waitBetweenFetch = requireNonNull(waitBetweenFetch);
|
||||
preferences.putLong(WAIT_BETWEEN_FETCH_KEY, waitBetweenFetch.toMillis());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<BirdPracticeParameters> birdPracticeParameters() {
|
||||
return birdPracticeParameters.values();
|
||||
}
|
||||
|
||||
@Override
|
||||
public BirdPracticeParameters birdPracticeParameters(final Bird bird) {
|
||||
return birdPracticeParameters.get(bird);
|
||||
}
|
||||
|
||||
private BirdPracticeParameters loadPracticeParameters(final Bird bird) {
|
||||
final var parameters = getAllParameters(bird);
|
||||
final var birdParameters = parameters.stream().collect(Collectors.toMap(e -> e, e -> preferences.get(e, getDefaultValue(e))));
|
||||
return convertParameters(bird, birdParameters);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBirdPracticeParameters(final BirdPracticeParameters birdPracticeParameters) {
|
||||
this.birdPracticeParameters.put(birdPracticeParameters.bird(), birdPracticeParameters);
|
||||
final var parameters = convertParameters(birdPracticeParameters);
|
||||
parameters.forEach(preferences::put);
|
||||
}
|
||||
|
||||
private static Map<String, String> convertParameters(final BirdPracticeParameters parameters) {
|
||||
final var enabled = parameters.enabled();
|
||||
final var enabledFetchers = String.join(";", parameters.enabledFetchers());
|
||||
final var enabledPictureTypes = parameters.enabledPictureTypes().stream().map(PictureType::name).collect(Collectors.joining(";"));
|
||||
final var enabledSoundTypes = parameters.enabledSoundTypes().stream().map(SoundType::name).collect(Collectors.joining(";"));
|
||||
final var name = parameters.bird().name();
|
||||
return Map.of(
|
||||
name + "_enabled", String.valueOf(enabled),
|
||||
name + "_enabledFetchers", enabledFetchers,
|
||||
name + "_enabledPictureTypes", enabledPictureTypes,
|
||||
name + "_enabledSoundTypes", enabledSoundTypes
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
private static BirdPracticeParameters convertParameters(final Bird bird, final Map<String, String> parameters) {
|
||||
final var enabled = Boolean.parseBoolean(parameters.get(bird.name() + "_enabled"));
|
||||
final var enabledFetchers = Arrays.stream(parameters.get(bird.name() + "_enabledFetchers").split(";")).filter(e -> !e.isBlank()).collect(Collectors.toSet());
|
||||
final var enabledPictureTypes = Arrays.stream(parameters.get(bird.name() + "_enabledPictureTypes").split(";")).filter(e -> !e.isBlank()).map(PictureType::valueOf).collect(Collectors.toSet());
|
||||
final var enabledSoundTypes = Arrays.stream(parameters.get(bird.name() + "_enabledSoundTypes").split(";")).filter(e -> !e.isBlank()).map(SoundType::valueOf).collect(Collectors.toSet());
|
||||
return new BirdPracticeParametersImpl(bird, enabled, enabledFetchers, enabledPictureTypes, enabledSoundTypes);
|
||||
}
|
||||
|
||||
private static String getDefaultValue(final String parameter) {
|
||||
final var type = parameter.split("_")[1];
|
||||
return switch (type) {
|
||||
case "enabled" -> "true";
|
||||
case "enabledFetchers", "enabledPictureTypes", "enabledSoundTypes" -> "all";
|
||||
default -> "";
|
||||
};
|
||||
}
|
||||
|
||||
private static Set<String> getAllParameters(final Bird bird) {
|
||||
final var name = bird.name();
|
||||
return Set.of(
|
||||
name + "_enabled",
|
||||
name + "_enabledFetchers",
|
||||
name + "_enabledPictureTypes",
|
||||
name + "_enabledSoundTypes"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package ch.gtache.fro.impl;
|
||||
|
||||
import ch.gtache.fro.FetcherConfiguration;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.time.Duration;
|
||||
import java.util.prefs.Preferences;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
/**
|
||||
* Implementation of {@link FetcherConfiguration}
|
||||
*/
|
||||
public class FetcherConfigurationImpl implements FetcherConfiguration {
|
||||
private static final Logger logger = LogManager.getLogger(FetcherConfigurationImpl.class);
|
||||
|
||||
private static final Path DEFAULT_ROOT_FOLDER = Path.of(System.getProperty("user.home"), ".fro", "birds");
|
||||
private static final Duration DEFAULT_WAIT_BETWEEN_FETCH = Duration.ofMillis(3000);
|
||||
private static final String ROOT_FOLDER_KEY = "rootFolder";
|
||||
private static final String WAIT_BETWEEN_FETCH_KEY = "waitBetweenFetch";
|
||||
|
||||
private final Preferences preferences;
|
||||
private Path rootFolder;
|
||||
private Duration waitBetweenFetch;
|
||||
|
||||
/**
|
||||
* Instantiates the configuration
|
||||
*
|
||||
* @param fetcherName The name of the fetcher
|
||||
* @throws NullPointerException If any parameter is null
|
||||
*/
|
||||
public FetcherConfigurationImpl(final String fetcherName) {
|
||||
this.preferences = Preferences.userNodeForPackage(this.getClass()).node(fetcherName);
|
||||
load();
|
||||
}
|
||||
|
||||
private void load() {
|
||||
this.rootFolder = Path.of(preferences.get(ROOT_FOLDER_KEY, DEFAULT_ROOT_FOLDER.toString()));
|
||||
this.waitBetweenFetch = Duration.ofMillis(preferences.getLong(WAIT_BETWEEN_FETCH_KEY, DEFAULT_WAIT_BETWEEN_FETCH.toMillis()));
|
||||
logger.info("Loaded configuration: rootFolder={}, waitBetweenFetch={}", rootFolder, waitBetweenFetch);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Path rootFolder() {
|
||||
return rootFolder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRootFolder(final Path rootFolder) {
|
||||
logger.info("Setting root folder to {}", rootFolder);
|
||||
this.rootFolder = requireNonNull(rootFolder);
|
||||
preferences.put(ROOT_FOLDER_KEY, rootFolder.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Duration waitBetweenFetch() {
|
||||
return waitBetweenFetch;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setWaitBetweenFetch(final Duration waitBetweenFetch) {
|
||||
logger.info("Setting wait between fetch to {}", waitBetweenFetch);
|
||||
this.waitBetweenFetch = requireNonNull(waitBetweenFetch);
|
||||
preferences.putLong(WAIT_BETWEEN_FETCH_KEY, waitBetweenFetch.toMillis());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,213 @@
|
||||
package ch.gtache.fro.impl;
|
||||
|
||||
import ch.gtache.fro.Bird;
|
||||
import ch.gtache.fro.BirdProvider;
|
||||
import ch.gtache.fro.Fetcher;
|
||||
import ch.gtache.fro.FetcherProvider;
|
||||
import ch.gtache.fro.PictureType;
|
||||
import ch.gtache.fro.ProvisionException;
|
||||
import ch.gtache.fro.SoundType;
|
||||
import ch.gtache.fro.practice.BirdPracticeParameters;
|
||||
import ch.gtache.fro.practice.PracticeConfiguration;
|
||||
import ch.gtache.fro.practice.QuestionType;
|
||||
import ch.gtache.fro.practice.impl.BirdPracticeParametersImpl;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import jakarta.inject.Inject;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.EnumMap;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.prefs.Preferences;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
/**
|
||||
* Implementation of {@link PracticeConfiguration}
|
||||
*/
|
||||
public class PracticeConfigurationImpl implements PracticeConfiguration {
|
||||
|
||||
private static final Logger logger = LogManager.getLogger(PracticeConfigurationImpl.class);
|
||||
|
||||
private static final int DEFAULT_GUESS_NUMBER = 1;
|
||||
|
||||
private static final String QUESTION_RATES_KEY = "questionRates";
|
||||
private static final String BIRD_PRACTICE_PARAMETERS_KEY = "birdPracticeParameters";
|
||||
private static final String GUESSES_NUMBER_KEY = "guessNumber";
|
||||
|
||||
private final BirdProvider birdProvider;
|
||||
private final FetcherProvider fetcherProvider;
|
||||
private final ObjectMapper objectMapper;
|
||||
private final Preferences preferences;
|
||||
private final Map<Bird, BirdPracticeParameters> birdPracticeParameters;
|
||||
private final Map<QuestionType, Double> questionRates;
|
||||
private int guessesNumber;
|
||||
|
||||
@Inject
|
||||
PracticeConfigurationImpl(final BirdProvider birdProvider, final FetcherProvider fetcherProvider, final ObjectMapper objectMapper) {
|
||||
this.birdProvider = requireNonNull(birdProvider);
|
||||
this.fetcherProvider = requireNonNull(fetcherProvider);
|
||||
this.objectMapper = requireNonNull(objectMapper);
|
||||
this.preferences = Preferences.userNodeForPackage(this.getClass());
|
||||
this.birdPracticeParameters = new HashMap<>();
|
||||
this.questionRates = new EnumMap<>(QuestionType.class);
|
||||
this.guessesNumber = DEFAULT_GUESS_NUMBER;
|
||||
load();
|
||||
}
|
||||
|
||||
private void load() {
|
||||
loadBirdPracticeParameters();
|
||||
loadQuestionRates();
|
||||
this.guessesNumber = preferences.getInt(GUESSES_NUMBER_KEY, DEFAULT_GUESS_NUMBER);
|
||||
}
|
||||
|
||||
private void loadQuestionRates() {
|
||||
final var json = preferences.get(QUESTION_RATES_KEY, "{}");
|
||||
try {
|
||||
final var deserializedQuestionRates = objectMapper.readValue(json, new QuestionRatesMapTypeReference());
|
||||
questionRates.putAll(deserializedQuestionRates);
|
||||
} catch (final JsonProcessingException e) {
|
||||
logger.error("Failed to deserialize question rates", e);
|
||||
}
|
||||
Arrays.stream(QuestionType.values()).forEach(qt -> {
|
||||
if (!questionRates.containsKey(qt)) {
|
||||
questionRates.put(qt, qt == QuestionType.PICTURE_EXACT ? 1.0 : 0.0);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void loadBirdPracticeParameters() {
|
||||
final var practiceParameters = preferences.get(BIRD_PRACTICE_PARAMETERS_KEY, "[]");
|
||||
try {
|
||||
final var deserializedPracticeParameters = deserializeBirdPracticeParameters(objectMapper.readValue(practiceParameters, new SerializedBirdPracticeParametersTypeReference()));
|
||||
birdPracticeParameters.putAll(deserializedPracticeParameters);
|
||||
} catch (final JsonProcessingException e) {
|
||||
logger.error("Failed to deserialize bird parameters", e);
|
||||
}
|
||||
try {
|
||||
final var allBirds = birdProvider.getAllObjects();
|
||||
allBirds.forEach(b -> {
|
||||
if (!birdPracticeParameters.containsKey(b)) {
|
||||
birdPracticeParameters.put(b, getDefaultParameters(b));
|
||||
}
|
||||
});
|
||||
} catch (final ProvisionException e) {
|
||||
logger.error("Failed to get all birds", e);
|
||||
}
|
||||
}
|
||||
|
||||
private Map<Bird, BirdPracticeParameters> deserializeBirdPracticeParameters(final List<SerializedBirdPracticeParameters> parameters) {
|
||||
return parameters.stream().collect(Collectors.toMap(p -> {
|
||||
try {
|
||||
return birdProvider.getObject(p.bird());
|
||||
} catch (final ProvisionException e) {
|
||||
logger.error("Failed to get bird {}", p.bird(), e);
|
||||
return null;
|
||||
}
|
||||
}, this::deserializeBirdPracticeParameters));
|
||||
}
|
||||
|
||||
private BirdPracticeParameters deserializeBirdPracticeParameters(final SerializedBirdPracticeParameters parameters) {
|
||||
try {
|
||||
final var bird = birdProvider.getObject(parameters.bird());
|
||||
return new BirdPracticeParametersImpl(bird, parameters.enabled(), parameters.enabledFetchers(), parameters.enabledPictureTypes(), parameters.enabledSoundTypes());
|
||||
} catch (final ProvisionException e) {
|
||||
logger.error("Failed to get bird {}", parameters.bird(), e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private List<SerializedBirdPracticeParameters> serializeBirdPracticeParameters() {
|
||||
return birdPracticeParameters.values().stream().map(PracticeConfigurationImpl::serializeBirdPracticeParameters).toList();
|
||||
}
|
||||
|
||||
private static SerializedBirdPracticeParameters serializeBirdPracticeParameters(final BirdPracticeParameters parameters) {
|
||||
return new SerializedBirdPracticeParameters(parameters.bird().name(), parameters.enabled(), parameters.enabledFetchers(), parameters.enabledPictureTypes(), parameters.enabledSoundTypes());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int guessesNumber() {
|
||||
return guessesNumber;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setGuessesNumber(final int guessNumber) {
|
||||
this.guessesNumber = guessNumber;
|
||||
preferences.putInt(GUESSES_NUMBER_KEY, guessNumber);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<QuestionType, Double> questionRates() {
|
||||
return new EnumMap<>(questionRates);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setQuestionRates(final Map<QuestionType, Double> questionRates) {
|
||||
this.questionRates.clear();
|
||||
this.questionRates.putAll(questionRates);
|
||||
saveQuestionRates();
|
||||
}
|
||||
|
||||
private void saveQuestionRates() {
|
||||
final String json;
|
||||
try {
|
||||
json = objectMapper.writeValueAsString(questionRates);
|
||||
preferences.put(QUESTION_RATES_KEY, json);
|
||||
} catch (final JsonProcessingException e) {
|
||||
logger.error("Failed to serialize question rates", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<BirdPracticeParameters> birdPracticeParameters() {
|
||||
return birdPracticeParameters.values();
|
||||
}
|
||||
|
||||
@Override
|
||||
public BirdPracticeParameters birdPracticeParameters(final Bird bird) {
|
||||
return birdPracticeParameters.get(bird);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBirdPracticeParameters(final Collection<? extends BirdPracticeParameters> birdPracticeParameters) {
|
||||
this.birdPracticeParameters.clear();
|
||||
this.birdPracticeParameters.putAll(birdPracticeParameters.stream().collect(Collectors.toMap(BirdPracticeParameters::bird, Function.identity())));
|
||||
saveBirdPracticeParameters();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBirdPracticeParameters(final BirdPracticeParameters birdPracticeParameters) {
|
||||
this.birdPracticeParameters.put(birdPracticeParameters.bird(), birdPracticeParameters);
|
||||
saveBirdPracticeParameters();
|
||||
}
|
||||
|
||||
private void saveBirdPracticeParameters() {
|
||||
final var parameters = serializeBirdPracticeParameters();
|
||||
try {
|
||||
final var json = objectMapper.writeValueAsString(parameters);
|
||||
preferences.put(BIRD_PRACTICE_PARAMETERS_KEY, json);
|
||||
} catch (final JsonProcessingException e) {
|
||||
logger.error("Failed to serialize bird parameters", e);
|
||||
}
|
||||
}
|
||||
|
||||
private BirdPracticeParameters getDefaultParameters(final Bird bird) {
|
||||
final var pictureTypes = Set.copyOf(Arrays.asList(PictureType.values()));
|
||||
final var soundTypes = Set.copyOf(Arrays.asList(SoundType.values()));
|
||||
try {
|
||||
final var fetcherNames = fetcherProvider.getAllObjects().stream().map(Fetcher::name).collect(Collectors.toSet());
|
||||
return new BirdPracticeParametersImpl(bird, true, fetcherNames, pictureTypes, soundTypes);
|
||||
} catch (final ProvisionException e) {
|
||||
logger.error("Failed to get fetcher names", e);
|
||||
return new BirdPracticeParametersImpl(bird, true, Set.of(), pictureTypes, soundTypes);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package ch.gtache.fro.impl;
|
||||
|
||||
import ch.gtache.fro.practice.QuestionType;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
class QuestionRatesMapTypeReference extends TypeReference<Map<QuestionType, Double>> {
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package ch.gtache.fro.impl;
|
||||
|
||||
import ch.gtache.fro.PictureType;
|
||||
import ch.gtache.fro.SoundType;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
record SerializedBirdPracticeParameters(String bird, boolean enabled, Set<String> enabledFetchers,
|
||||
Set<PictureType> enabledPictureTypes,
|
||||
Set<SoundType> enabledSoundTypes) {
|
||||
|
||||
SerializedBirdPracticeParameters(final String bird) {
|
||||
this(bird, false, Set.of(), Set.of(), Set.of());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package ch.gtache.fro.impl;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
class SerializedBirdPracticeParametersTypeReference extends TypeReference<List<SerializedBirdPracticeParameters>> {
|
||||
}
|
||||
@@ -2,19 +2,21 @@ package ch.gtache.fro.modules.impl;
|
||||
|
||||
import ch.gtache.fro.BirdProvider;
|
||||
import ch.gtache.fro.BirdTranslator;
|
||||
import ch.gtache.fro.Configuration;
|
||||
import ch.gtache.fro.FetcherProvider;
|
||||
import ch.gtache.fro.PictureTypeTranslator;
|
||||
import ch.gtache.fro.SoundTypeTranslator;
|
||||
import ch.gtache.fro.impl.CommonBirdsProvider;
|
||||
import ch.gtache.fro.impl.CommonBirdsTranslatorImpl;
|
||||
import ch.gtache.fro.impl.ConfigurationImpl;
|
||||
import ch.gtache.fro.impl.FetcherProviderImpl;
|
||||
import ch.gtache.fro.impl.PictureTypeTranslatorImpl;
|
||||
import ch.gtache.fro.impl.PracticeConfigurationImpl;
|
||||
import ch.gtache.fro.impl.SoundTypeTranslatorImpl;
|
||||
import ch.gtache.fro.modules.practice.impl.CorePracticeModule;
|
||||
import ch.gtache.fro.practice.PracticeConfiguration;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import dagger.Binds;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
|
||||
/**
|
||||
* Dagger module for the core package
|
||||
@@ -30,7 +32,7 @@ public abstract class CoreModule {
|
||||
abstract BirdProvider bindsBirdProvider(final CommonBirdsProvider commonBirdsProvider);
|
||||
|
||||
@Binds
|
||||
abstract Configuration bindsConfiguration(final ConfigurationImpl configurationImpl);
|
||||
abstract PracticeConfiguration bindsConfiguration(final PracticeConfigurationImpl configurationImpl);
|
||||
|
||||
@Binds
|
||||
abstract FetcherProvider bindsFetcherProvider(final FetcherProviderImpl fetcherProviderImpl);
|
||||
@@ -43,4 +45,9 @@ public abstract class CoreModule {
|
||||
|
||||
@Binds
|
||||
abstract SoundTypeTranslator bindsSoundTypeTranslator(final SoundTypeTranslatorImpl soundTypeTranslatorImpl);
|
||||
|
||||
@Provides
|
||||
static ObjectMapper providesObjectMapper() {
|
||||
return new ObjectMapper();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,12 +2,15 @@ package ch.gtache.fro.modules.practice.impl;
|
||||
|
||||
import ch.gtache.fro.practice.PracticeQuestionGenerator;
|
||||
import ch.gtache.fro.practice.PracticeRunner;
|
||||
import ch.gtache.fro.practice.PracticeTypeTranslator;
|
||||
import ch.gtache.fro.practice.QuestionTypeTranslator;
|
||||
import ch.gtache.fro.practice.impl.PracticeQuestionGeneratorImpl;
|
||||
import ch.gtache.fro.practice.impl.PracticeRunnerImpl;
|
||||
import ch.gtache.fro.practice.impl.PracticeTypeTranslatorImpl;
|
||||
import ch.gtache.fro.practice.impl.QuestionTypeTranslatorImpl;
|
||||
import dagger.Binds;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* Dagger module for the core practice package
|
||||
@@ -26,5 +29,10 @@ public abstract class CorePracticeModule {
|
||||
abstract PracticeRunner bindsPracticeRunner(final PracticeRunnerImpl practiceRunnerImpl);
|
||||
|
||||
@Binds
|
||||
abstract PracticeTypeTranslator bindsPracticeTypeTranslator(final PracticeTypeTranslatorImpl practiceTypeTranslatorImpl);
|
||||
abstract QuestionTypeTranslator bindsQuestionTypeTranslator(final QuestionTypeTranslatorImpl questionTypeTranslator);
|
||||
|
||||
@Provides
|
||||
static Random providesRandom() {
|
||||
return new Random();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package ch.gtache.fro.practice.impl;
|
||||
import ch.gtache.fro.Bird;
|
||||
import ch.gtache.fro.Picture;
|
||||
import ch.gtache.fro.practice.PictureMultichoicePracticeQuestion;
|
||||
import ch.gtache.fro.practice.QuestionType;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -28,4 +29,9 @@ public record PictureMultichoicePracticeQuestionImpl(List<Bird> choices,
|
||||
choices = List.copyOf(choices);
|
||||
requireNonNull(picture);
|
||||
}
|
||||
|
||||
@Override
|
||||
public QuestionType questionType() {
|
||||
return QuestionType.PICTURE_MULTICHOICE;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package ch.gtache.fro.practice.impl;
|
||||
|
||||
import ch.gtache.fro.Picture;
|
||||
import ch.gtache.fro.practice.PicturePracticeQuestion;
|
||||
import ch.gtache.fro.practice.QuestionType;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
@@ -21,4 +22,9 @@ public record PicturePracticeQuestionImpl(Picture picture) implements PicturePra
|
||||
public PicturePracticeQuestionImpl {
|
||||
Objects.requireNonNull(picture);
|
||||
}
|
||||
|
||||
@Override
|
||||
public QuestionType questionType() {
|
||||
return QuestionType.PICTURE_EXACT;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,39 +3,44 @@ package ch.gtache.fro.practice.impl;
|
||||
import ch.gtache.fro.Bird;
|
||||
import ch.gtache.fro.practice.BirdPracticeParameters;
|
||||
import ch.gtache.fro.practice.PracticeParameters;
|
||||
import ch.gtache.fro.practice.PracticeType;
|
||||
import ch.gtache.fro.practice.QuestionType;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
/**
|
||||
* Implementation of {@link PracticeParameters}
|
||||
*
|
||||
* @param birdParameters The bird parameters
|
||||
* @param practiceType The practice type
|
||||
* @param questionsCount The number of questions
|
||||
* @param propositionsCount The number of propositions
|
||||
* @param birdParameters The bird parameters
|
||||
* @param questionTypeRates The practice type rates
|
||||
* @param guessesNumber The number of unsuccessful guesses before a question is considered as failed
|
||||
* @param questionsNumber The number of questions
|
||||
* @param propositionsNumber The number of propositions
|
||||
*/
|
||||
public record PracticeParametersImpl(Map<Bird, BirdPracticeParameters> birdParameters, PracticeType practiceType,
|
||||
int questionsCount, int propositionsCount) implements PracticeParameters {
|
||||
public record PracticeParametersImpl(Map<Bird, BirdPracticeParameters> birdParameters,
|
||||
Map<QuestionType, Double> questionTypeRates,
|
||||
int guessesNumber, int questionsNumber,
|
||||
int propositionsNumber) implements PracticeParameters {
|
||||
|
||||
/**
|
||||
* Instantiates the parameters
|
||||
*
|
||||
* @param birdParameters The bird parameters
|
||||
* @param practiceType The practice type
|
||||
* @param questionsCount The number of questions
|
||||
* @param propositionsCount The number of propositions
|
||||
* @param birdParameters The bird parameters
|
||||
* @param questionTypeRates The practice type rates
|
||||
* @param guessesNumber The number of unsuccessful guesses before a question is considered as failed
|
||||
* @param questionsNumber The number of questions
|
||||
* @param propositionsNumber The number of propositions
|
||||
* @throws NullPointerException If any parameter is null
|
||||
* @throws IllegalArgumentException If questionsCount is less than 1
|
||||
*/
|
||||
public PracticeParametersImpl {
|
||||
birdParameters = Map.copyOf(birdParameters);
|
||||
requireNonNull(practiceType);
|
||||
if (questionsCount <= 0) {
|
||||
questionTypeRates = Map.copyOf(questionTypeRates);
|
||||
if (guessesNumber <= 0) {
|
||||
throw new IllegalArgumentException("guessesNumber must be > 0");
|
||||
}
|
||||
if (questionsNumber <= 0) {
|
||||
throw new IllegalArgumentException("questionsCount must be > 0");
|
||||
}
|
||||
}
|
||||
@@ -43,17 +48,18 @@ public record PracticeParametersImpl(Map<Bird, BirdPracticeParameters> birdParam
|
||||
/**
|
||||
* Instantiates the parameters
|
||||
*
|
||||
* @param birdParameters The bird parameters
|
||||
* @param practiceType The practice type
|
||||
* @param questionsCount The number of questions
|
||||
* @param propositionsCount The number of propositions
|
||||
* @param birdParameters The bird parameters
|
||||
* @param questionTypeRates The practice type rates
|
||||
* @param guessesNumber The number of unsuccessful guesses before a question is considered as failed
|
||||
* @param questionsNumber The number of questions
|
||||
* @param propositionsNumber The number of propositions
|
||||
* @throws NullPointerException If any parameter is null
|
||||
* @throws IllegalArgumentException If questionsCount is less than 1
|
||||
*/
|
||||
public PracticeParametersImpl(final Collection<? extends BirdPracticeParameters> birdParameters,
|
||||
final PracticeType practiceType, final int questionsCount,
|
||||
final int propositionsCount) {
|
||||
final Map<QuestionType, Double> questionTypeRates, final int guessesNumber,
|
||||
final int questionsNumber, final int propositionsNumber) {
|
||||
this(birdParameters.stream().collect(Collectors.toMap(BirdPracticeParameters::bird, e -> e)),
|
||||
practiceType, questionsCount, propositionsCount);
|
||||
questionTypeRates, guessesNumber, questionsNumber, propositionsNumber);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import ch.gtache.fro.practice.BirdPracticeParameters;
|
||||
import ch.gtache.fro.practice.PracticeParameters;
|
||||
import ch.gtache.fro.practice.PracticeQuestion;
|
||||
import ch.gtache.fro.practice.PracticeQuestionGenerator;
|
||||
import ch.gtache.fro.practice.QuestionType;
|
||||
import jakarta.inject.Inject;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
@@ -17,6 +18,7 @@ import org.apache.logging.log4j.Logger;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@@ -48,15 +50,15 @@ public class PracticeQuestionGeneratorImpl implements PracticeQuestionGenerator
|
||||
public PracticeQuestion generate(final PracticeParameters parameters) {
|
||||
final var enabledBirds = parameters.birdParameters().values().stream()
|
||||
.filter(p -> p.enabled() && !p.enabledFetchers().isEmpty()).toList();
|
||||
final var practiceType = parameters.practiceType();
|
||||
return switch (practiceType) {
|
||||
final var questionType = getQuestionType(parameters.questionTypeRates());
|
||||
return switch (questionType) {
|
||||
case PICTURE_EXACT -> {
|
||||
final var image = getPicture(enabledBirds);
|
||||
yield new PicturePracticeQuestionImpl(image);
|
||||
}
|
||||
case PICTURE_MULTICHOICE -> {
|
||||
final var image = getPicture(enabledBirds);
|
||||
final var propositions = getPropositions(enabledBirds, parameters.propositionsCount());
|
||||
final var propositions = getPropositions(enabledBirds, parameters.propositionsNumber());
|
||||
yield new PictureMultichoicePracticeQuestionImpl(propositions, image);
|
||||
}
|
||||
case SOUND_EXACT -> {
|
||||
@@ -65,13 +67,26 @@ public class PracticeQuestionGeneratorImpl implements PracticeQuestionGenerator
|
||||
}
|
||||
case SOUND_MULTICHOICE -> {
|
||||
final var sound = getSound(enabledBirds);
|
||||
final var propositions = getPropositions(enabledBirds, parameters.propositionsCount());
|
||||
final var propositions = getPropositions(enabledBirds, parameters.propositionsNumber());
|
||||
yield new SoundMultichoicePracticeQuestionImpl(propositions, sound);
|
||||
}
|
||||
case SOUND_MULTICHOICE_PICTURE -> throw new UnsupportedOperationException("Not implemented yet");
|
||||
};
|
||||
}
|
||||
|
||||
private QuestionType getQuestionType(final Map<QuestionType, Double> map) {
|
||||
final var total = map.values().stream().mapToDouble(Double::doubleValue).sum();
|
||||
final var randomCount = random.nextDouble() * total;
|
||||
var sum = 0.0;
|
||||
for (final var entry : map.entrySet()) {
|
||||
sum += entry.getValue();
|
||||
if (sum >= randomCount) {
|
||||
return entry.getKey();
|
||||
}
|
||||
}
|
||||
throw new IllegalStateException("Should never happen");
|
||||
}
|
||||
|
||||
private Sound getSound(final Collection<? extends BirdPracticeParameters> enabledBirds) {
|
||||
final var sounds = enabledBirds.stream().flatMap(p ->
|
||||
p.enabledFetchers().stream().flatMap(f -> {
|
||||
|
||||
@@ -38,7 +38,7 @@ public class PracticeRunnerImpl implements PracticeRunner {
|
||||
|
||||
@Override
|
||||
public PracticeRun step(final PracticeRun run, final Bird answer) {
|
||||
if (run.currentQuestionIndex() < run.parameters().questionsCount()) {
|
||||
if (run.currentQuestionIndex() < run.parameters().questionsNumber()) {
|
||||
final var question = questionGenerator.generate(run.parameters());
|
||||
final var currentQuestion = run.currentQuestion();
|
||||
if (currentQuestion.bird() == answer) {
|
||||
@@ -57,7 +57,7 @@ public class PracticeRunnerImpl implements PracticeRunner {
|
||||
|
||||
@Override
|
||||
public PracticeResult finish(final PracticeRun run) {
|
||||
if (run.currentQuestionIndex() == run.parameters().questionsCount()) {
|
||||
if (run.currentQuestionIndex() == run.parameters().questionsNumber()) {
|
||||
return new PracticeResultImpl(run.correctQuestions(), run.failedQuestions());
|
||||
} else {
|
||||
throw new IllegalStateException("The practice run " + run + " is not finished");
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
package ch.gtache.fro.practice.impl;
|
||||
|
||||
import ch.gtache.fro.impl.AbstractBundleTranslator;
|
||||
import ch.gtache.fro.practice.PracticeType;
|
||||
import ch.gtache.fro.practice.PracticeTypeTranslator;
|
||||
import jakarta.inject.Inject;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Implementation of {@link PracticeTypeTranslator}
|
||||
*/
|
||||
public class PracticeTypeTranslatorImpl extends AbstractBundleTranslator<PracticeType>
|
||||
implements PracticeTypeTranslator {
|
||||
|
||||
@Inject
|
||||
PracticeTypeTranslatorImpl() {
|
||||
super("ch.gtache.fro.practice.impl.PracticeTypeBundle");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getKey(final PracticeType object) {
|
||||
return "practice.type." + object.name().toLowerCase(Locale.ROOT) + ".label";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package ch.gtache.fro.practice.impl;
|
||||
|
||||
import ch.gtache.fro.impl.AbstractBundleTranslator;
|
||||
import ch.gtache.fro.practice.QuestionType;
|
||||
import ch.gtache.fro.practice.QuestionTypeTranslator;
|
||||
import jakarta.inject.Inject;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Implementation of {@link QuestionTypeTranslator}
|
||||
*/
|
||||
public class QuestionTypeTranslatorImpl extends AbstractBundleTranslator<QuestionType>
|
||||
implements QuestionTypeTranslator {
|
||||
|
||||
@Inject
|
||||
QuestionTypeTranslatorImpl() {
|
||||
super("ch.gtache.fro.practice.impl.QuestionTypeBundle");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getKey(final QuestionType object) {
|
||||
return "question.type." + object.name().toLowerCase(Locale.ROOT) + ".label";
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package ch.gtache.fro.practice.impl;
|
||||
|
||||
import ch.gtache.fro.Picture;
|
||||
import ch.gtache.fro.Sound;
|
||||
import ch.gtache.fro.practice.QuestionType;
|
||||
import ch.gtache.fro.practice.SoundMultichoicePicturePracticeQuestion;
|
||||
|
||||
import java.util.List;
|
||||
@@ -28,4 +29,9 @@ public record SoundMultichoicePicturePracticeQuestionImpl(List<Picture> pictureC
|
||||
pictureChoices = List.copyOf(pictureChoices);
|
||||
requireNonNull(sound);
|
||||
}
|
||||
|
||||
@Override
|
||||
public QuestionType questionType() {
|
||||
return QuestionType.SOUND_MULTICHOICE_PICTURE;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package ch.gtache.fro.practice.impl;
|
||||
|
||||
import ch.gtache.fro.Bird;
|
||||
import ch.gtache.fro.Sound;
|
||||
import ch.gtache.fro.practice.QuestionType;
|
||||
import ch.gtache.fro.practice.SoundMultichoicePracticeQuestion;
|
||||
|
||||
import java.util.List;
|
||||
@@ -28,4 +29,9 @@ public record SoundMultichoicePracticeQuestionImpl(List<Bird> choices,
|
||||
choices = List.copyOf(choices);
|
||||
requireNonNull(sound);
|
||||
}
|
||||
|
||||
@Override
|
||||
public QuestionType questionType() {
|
||||
return QuestionType.SOUND_MULTICHOICE;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package ch.gtache.fro.practice.impl;
|
||||
|
||||
import ch.gtache.fro.Sound;
|
||||
import ch.gtache.fro.practice.QuestionType;
|
||||
import ch.gtache.fro.practice.SoundPracticeQuestion;
|
||||
|
||||
import java.util.Objects;
|
||||
@@ -21,4 +22,9 @@ public record SoundPracticeQuestionImpl(Sound sound) implements SoundPracticeQue
|
||||
public SoundPracticeQuestionImpl {
|
||||
Objects.requireNonNull(sound);
|
||||
}
|
||||
|
||||
@Override
|
||||
public QuestionType questionType() {
|
||||
return QuestionType.SOUND_EXACT;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,14 +3,18 @@
|
||||
*/
|
||||
module ch.gtache.fro.core {
|
||||
requires transitive ch.gtache.fro.api;
|
||||
requires java.prefs;
|
||||
requires transitive com.fasterxml.jackson.core;
|
||||
requires transitive dagger;
|
||||
requires org.apache.logging.log4j;
|
||||
requires dagger;
|
||||
requires java.prefs;
|
||||
requires jakarta.inject;
|
||||
requires com.fasterxml.jackson.databind;
|
||||
|
||||
exports ch.gtache.fro.impl;
|
||||
exports ch.gtache.fro.practice.impl;
|
||||
|
||||
exports ch.gtache.fro.modules.impl;
|
||||
exports ch.gtache.fro.modules.practice.impl;
|
||||
|
||||
opens ch.gtache.fro.impl to com.fasterxml.jackson.databind;
|
||||
}
|
||||
428
core/src/main/resources/ch/gtache/fro/impl/BirdBundle.properties
Normal file
428
core/src/main/resources/ch/gtache/fro/impl/BirdBundle.properties
Normal file
@@ -0,0 +1,428 @@
|
||||
bird.ACCENTEUR_ALPIN.label=Alpine Accentor
|
||||
bird.ACCENTEUR_MOUCHET.label=Dunnock
|
||||
bird.AGROBATE_ROUX.label=Rufous-tailed Scrub Robin
|
||||
bird.AIGLE_BOTTE.label=Booted Eagle
|
||||
bird.AIGLE_CRIARD.label=Greater Spotted Eagle
|
||||
bird.AIGLE_POMARIN.label=Lesser Spotted Eagle
|
||||
bird.AIGLE_ROYAL.label=Golden Eagle
|
||||
bird.AIGRETTE_GARZETTE.label=Little Egret
|
||||
bird.ALOUETTE_CALANDRE.label=Calandra Lark
|
||||
bird.ALOUETTE_CALANDRELLE.label=Greater Short-toed Lark
|
||||
bird.ALOUETTE_DES_CHAMPS.label=Eurasian Skylark
|
||||
bird.ALOUETTE_HAUSSECOL.label=Horned Lark
|
||||
bird.ALOUETTE_LEUCOPTERE.label=White-winged Lark
|
||||
bird.ALOUETTE_LULU.label=Woodlark
|
||||
bird.ALOUETTE_MONTICOLE.label=Bimaculated Lark
|
||||
bird.ALOUETTE_PISPOLETTE.label=Mediterranean Short-toed Lark
|
||||
bird.AUTOUR_DES_PALOMBES.label=Eurasian Goshawk
|
||||
bird.AVOCETTE_ELEGANTE.label=Pied Avocet
|
||||
bird.BALBUZARD_PECHEUR.label=Osprey
|
||||
bird.BARGE_A_QUEUE_NOIRE.label=Black-tailed Godwit
|
||||
bird.BARGE_ROUSSE.label=Bar-tailed Godwit
|
||||
bird.BEC_CROISE_DES_SAPINS.label=Red Crossbill
|
||||
bird.BECASSE_DES_BOIS.label=Eurasian Woodcock
|
||||
bird.BECASSEAU_COCORLI.label=Curlew Sandpiper
|
||||
bird.BECASSEAU_DE_BONAPARTE.label=White-rumped Sandpiper
|
||||
bird.BECASSEAU_DE_TEMMINCK.label=Temminck's Stint
|
||||
bird.BECASSEAU_FALCINELLE.label=Broad-billed Sandpiper
|
||||
bird.BECASSEAU_MAUBECHE.label=Red Knot
|
||||
bird.BECASSEAU_MINUTE.label=Little Stint
|
||||
bird.BECASSEAU_ROUSSET.label=Buff-breasted Sandpiper
|
||||
bird.BECASSEAU_SANDERLING.label=Sanderling
|
||||
bird.BECASSEAU_TACHETE.label=Pectoral Sandpiper
|
||||
bird.BECASSEAU_VARIABLE.label=Dunlin
|
||||
bird.BECASSEAU_VIOLET.label=Purple Sandpiper
|
||||
bird.BECASSINE_DES_MARAIS.label=Common Snipe
|
||||
bird.BECASSINE_DOUBLE.label=Great Snipe
|
||||
bird.BECASSINE_SOURDE.label=Jack Snipe
|
||||
bird.BERGERONNETTE_CITRINE.label=Citrine Wagtail
|
||||
bird.BERGERONNETTE_DES_RUISSEAUX.label=Grey Wagtail
|
||||
bird.BERGERONNETTE_GRISE.label=White Wagtail
|
||||
bird.BERGERONNETTE_PRINTANIERE.label=Western Yellow Wagtail
|
||||
bird.BERNACHE_A_COU_ROUX.label=Red-breasted Goose
|
||||
bird.BERNACHE_CRAVANT.label=Brant Goose
|
||||
bird.BERNACHE_DU_CANADA.label=Canada Goose
|
||||
bird.BERNACHE_NONNETTE.label=Barnacle Goose
|
||||
bird.BIHOREAU_GRIS.label=Black-crowned Night Heron
|
||||
bird.BLONGIOS_NAIN.label=Little Bittern
|
||||
bird.BONDREE_APIVORE.label=European Honey Buzzard
|
||||
bird.BOUSCARLE_DE_CETTI.label=Cetti's Warbler
|
||||
bird.BOUVREUIL_PIVOINE.label=Eurasian Bullfinch
|
||||
bird.BRUANT_A_CALOTTE_BLANCHE.label=Pine Bunting
|
||||
bird.BRUANT_CHANTEUR.label=Song Sparrow
|
||||
bird.BRUANT_DES_NEIGES.label=Snow Bunting
|
||||
bird.BRUANT_DES_ROSEAUX.label=Common Reed Bunting
|
||||
bird.BRUANT_FOU.label=Rock Bunting
|
||||
bird.BRUANT_JAUNE.label=Yellowhammer
|
||||
bird.BRUANT_LAPON.label=Lapland Longspur
|
||||
bird.BRUANT_MELANOCEPHALE.label=Black-headed Bunting
|
||||
bird.BRUANT_NAIN.label=Little Bunting
|
||||
bird.BRUANT_ORTOLAN.label=Ortolan Bunting
|
||||
bird.BRUANT_PROYER.label=Corn Bunting
|
||||
bird.BRUANT_RUSTIQUE.label=Rustic Bunting
|
||||
bird.BRUANT_ZIZI.label=Cirl Bunting
|
||||
bird.BUSARD_CENDRE.label=Montagu's Harrier
|
||||
bird.BUSARD_DES_ROSEAUX.label=Western Marsh Harrier
|
||||
bird.BUSARD_PALE.label=Pallid Harrier
|
||||
bird.BUSARD_SAINT_MARTIN.label=Hen Harrier
|
||||
bird.BUSE_FEROCE.label=Long-legged Buzzard
|
||||
bird.BUSE_PATTUE.label=Rough-legged Buzzard
|
||||
bird.BUSE_VARIABLE.label=Common Buzzard
|
||||
bird.BUTOR_ETOILE.label=Eurasian Bittern
|
||||
bird.CAILLE_DES_BLES.label=Common Quail
|
||||
bird.CANARD_CAROLIN.label=Wood Duck
|
||||
bird.CANARD_CHIPEAU.label=Gadwall
|
||||
bird.CANARD_COLVERT.label=Mallard
|
||||
bird.CANARD_MANDARIN.label=Mandarin Duck
|
||||
bird.CANARD_PILET.label=Northern Pintail
|
||||
bird.CANARD_SIFFLEUR.label=Eurasian Wigeon
|
||||
bird.CANARD_SOUCHET.label=Northern Shoveler
|
||||
bird.CASSENOIX_MOUCHETE.label=Northern Nutcracker
|
||||
bird.CHARDONNERET_ELEGANT.label=European Goldfinch
|
||||
bird.CHEVALIER_ABOYEUR.label=Common Greenshank
|
||||
bird.CHEVALIER_ARLEQUIN.label=Spotted Redshank
|
||||
bird.CHEVALIER_BARGETTE.label=Terek Sandpiper
|
||||
bird.CHEVALIER_CULBLANC.label=Green Sandpiper
|
||||
bird.CHEVALIER_GAMBETTE.label=Common Redshank
|
||||
bird.CHEVALIER_GRIVELE.label=Spotted Sandpiper
|
||||
bird.CHEVALIER_GUIGNETTE.label=Common Sandpiper
|
||||
bird.CHEVALIER_STAGNATILE.label=Marsh Sandpiper
|
||||
bird.CHEVALIER_SYLVAIN.label=Wood Sandpiper
|
||||
bird.CHEVECHE_D_ATHENA.label=Little Owl
|
||||
bird.CHEVECHETTE_D_EUROPE.label=Eurasian Pygmy Owl
|
||||
bird.CHOCARD_A_BEC_JAUNE.label=Alpine Chough
|
||||
bird.CHOUCAS_DES_TOURS.label=Western Jackdaw
|
||||
bird.CHOUETTE_DE_TENGMALM.label=Boreal Owl
|
||||
bird.CHOUETTE_EPERVIERE.label=Northern Hawk-Owl
|
||||
bird.CHOUETTE_HULOTTE.label=Tawny Owl
|
||||
bird.CIGOGNE_BLANCHE.label=White Stork
|
||||
bird.CIGOGNE_NOIRE.label=Black Stork
|
||||
bird.CINCLE_PLONGEUR.label=White-throated Dipper
|
||||
bird.CIRCAETE_JEAN_LE_BLANC.label=Short-toed Snake Eagle
|
||||
bird.CISTICOLE_DES_JONCS.label=Zitting Cisticola
|
||||
bird.COCHEVIS_HUPPE.label=Crested Lark
|
||||
bird.COMBATTANT_VARIE.label=Ruff
|
||||
bird.CORBEAU_FREUX.label=Rook
|
||||
bird.CORMORAN_HUPPE.label=European Shag
|
||||
bird.CORMORAN_PYGMEE.label=Pygmy Cormorant
|
||||
bird.CORNEILLE_MANTELEE.label=Hooded Crow
|
||||
bird.CORNEILLE_NOIRE.label=Carrion Crow
|
||||
bird.COUCOU_GEAI.label=Great Spotted Cuckoo
|
||||
bird.COUCOU_GRIS.label=Common Cuckoo
|
||||
bird.COURLIS_A_BEC_GRELE.label=Slender-billed Curlew
|
||||
bird.COURLIS_CENDRE.label=Eurasian Curlew
|
||||
bird.COURLIS_CORLIEU.label=Eurasian Whimbrel
|
||||
bird.COURVITE_ISABELLE.label=Cream-colored Courser
|
||||
bird.CRABIER_CHEVELU.label=Squacco Heron
|
||||
bird.CRAVE_A_BEC_ROUGE.label=Red-billed Chough
|
||||
bird.CYGNE_CHANTEUR.label=Whooper Swan
|
||||
bird.CYGNE_DE_BEWICK.label=Tundra Swan
|
||||
bird.CYGNE_TUBERCULE.label=Mute Swan
|
||||
bird.DURBEC_DES_SAPINS.label=Pine Grosbeak
|
||||
bird.ECHASSE_BLANCHE.label=Black-winged Stilt
|
||||
bird.EFFRAIE_DES_CLOCHERS.label=Western Barn Owl
|
||||
bird.EIDER_A_DUVET.label=Common Eider
|
||||
bird.ELANION_BLANC.label=Black-winged Kite
|
||||
bird.ENGOULEVENT_D_EUROPE.label=European Nightjar
|
||||
bird.EPERVIER_D_EUROPE.label=Eurasian Sparrowhawk
|
||||
bird.ERISMATURE_A_TETE_BLANCHE.label=White-headed Duck
|
||||
bird.ERISMATURE_ROUSSE.label=Ruddy Duck
|
||||
bird.ETOURNEAU_ROSELIN.label=Rosy Starling
|
||||
bird.ETOURNEAU_SANSONNET.label=Common Starling
|
||||
bird.ETOURNEAU_UNICOLORE.label=Spotless Starling
|
||||
bird.FAISAN_DE_COLCHIDE.label=Common Pheasant
|
||||
bird.FAUCON_CRECERELLE.label=Common Kestrel
|
||||
bird.FAUCON_CRECERELLETTE.label=Lesser Kestrel
|
||||
bird.FAUCON_D_ELEONORE.label=Eleonora's Falcon
|
||||
bird.FAUCON_EMERILLON.label=Merlin
|
||||
bird.FAUCON_GERFAUT.label=Gyrfalcon
|
||||
bird.FAUCON_HOBEREAU.label=Eurasian Hobby
|
||||
bird.FAUCON_KOBEZ.label=Red-footed Falcon
|
||||
bird.FAUCON_PELERIN.label=Peregrine Falcon
|
||||
bird.FAUCON_SACRE.label=Saker Falcon
|
||||
bird.FAUVETTE_A_LUNETTES.label=Spectacled Warbler
|
||||
bird.FAUVETTE_A_TETE_NOIRE.label=Eurasian Blackcap
|
||||
bird.FAUVETTE_BABILLARDE.label=Lesser Whitethroat
|
||||
bird.FAUVETTE_DES_JARDINS.label=Garden Warbler
|
||||
bird.FAUVETTE_EPERVIERE.label=Barred Warbler
|
||||
bird.FAUVETTE_GRISETTE.label=Common Whitethroat
|
||||
bird.FAUVETTE_MELANOCEPHALE.label=Sardinian Warbler
|
||||
bird.FAUVETTE_ORPHEE.label=Western Orphean Warbler
|
||||
bird.FAUVETTE_PASSERINETTE.label=Western Subalpine Warbler
|
||||
bird.FAUVETTE_PITCHOU.label=Dartford Warbler
|
||||
bird.FLAMANT_ROSE.label=Greater Flamingo
|
||||
bird.FOU_DE_BASSAN.label=Northern Gannet
|
||||
bird.FOULQUE_MACROULE.label=Eurasian Coot
|
||||
bird.FULIGULE_A_BEC_CERCLE.label=Ring-necked Duck
|
||||
bird.FULIGULE_A_TETE_NOIRE.label=Lesser Scaup
|
||||
bird.FULIGULE_MILOUIN.label=Common Pochard
|
||||
bird.FULIGULE_MILOUINAN.label=Greater Scaup
|
||||
bird.FULIGULE_MORILLON.label=Tufted Duck
|
||||
bird.FULIGULE_NYROCA.label=Ferruginous Duck
|
||||
bird.GALLINULE_POULE_D_EAU.label=Common Moorhen
|
||||
bird.GARROT_A_OEIL_D_OR.label=Common Goldeneye
|
||||
bird.GEAI_DES_CHENES.label=Eurasian Jay
|
||||
bird.GELINOTTE_DES_BOIS.label=Hazel Grouse
|
||||
bird.GLAREOLE_A_AILES_NOIRES.label=Black-winged Pratincole
|
||||
bird.GLAREOLE_A_COLLIER.label=Collared Pratincole
|
||||
bird.GOBEMOUCHE_A_COLLIER.label=Collared Flycatcher
|
||||
bird.GOBEMOUCHE_A_DEMI_COLLIER.label=Semicollared Flycatcher
|
||||
bird.GOBEMOUCHE_GRIS.label=Spotted Flycatcher
|
||||
bird.GOBEMOUCHE_NAIN.label=Red-breasted Flycatcher
|
||||
bird.GOBEMOUCHE_NOIR.label=European Pied Flycatcher
|
||||
bird.GOELAND_A_AILES_BLANCHES.label=Iceland Gull
|
||||
bird.GOELAND_ARGENTE.label=European Herring Gull
|
||||
bird.GOELAND_BOURGMESTRE.label=Glaucous Gull
|
||||
bird.GOELAND_BRUN.label=Lesser Black-backed Gull
|
||||
bird.GOELAND_CENDRE.label=Common Gull
|
||||
bird.GOELAND_D_AUDOUIN.label=Audouin's Gull
|
||||
bird.GOELAND_LEUCOPHEE.label=Yellow-legged Gull
|
||||
bird.GOELAND_MARIN.label=Great Black-backed Gull
|
||||
bird.GOELAND_PONTIQUE.label=Caspian Gull
|
||||
bird.GOELAND_RAILLEUR.label=Slender-billed Gull
|
||||
bird.GORGEBLEUE_A_MIROIR.label=Bluethroat
|
||||
bird.GRAND_CORBEAU.label=Northern Raven
|
||||
bird.GRAND_CORMORAN.label=Great Cormorant
|
||||
bird.GRAND_GRAVELOT.label=Common Ringed Plover
|
||||
bird.GRAND_LABBE.label=Great Skua
|
||||
bird.GRAND_TETRAS.label=Western Capercaillie
|
||||
bird.GRAND_DUC_D_EUROPE.label=Eurasian Eagle-Owl
|
||||
bird.GRANDE_AIGRETTE.label=Great Egret
|
||||
bird.GRAVELOT_A_COLLIER_INTERROMPU.label=Kentish Plover
|
||||
bird.GRAVELOT_KILDIR.label=Killdeer
|
||||
bird.GREBE_A_COU_NOIR.label=Black-necked Grebe
|
||||
bird.GREBE_CASTAGNEUX.label=Little Grebe
|
||||
bird.GREBE_ESCLAVON.label=Horned Grebe
|
||||
bird.GREBE_HUPPE.label=Great Crested Grebe
|
||||
bird.GREBE_JOUGRIS.label=Red-necked Grebe
|
||||
bird.GRIMPEREAU_DES_BOIS.label=Eurasian Treecreeper
|
||||
bird.GRIMPEREAU_DES_JARDINS.label=Short-toed Treecreeper
|
||||
bird.GRIVE_DE_SIBERIE.label=Siberian Thrush
|
||||
bird.GRIVE_DRAINE.label=Mistle Thrush
|
||||
bird.GRIVE_LITORNE.label=Fieldfare
|
||||
bird.GRIVE_MAUVIS.label=Redwing
|
||||
bird.GRIVE_MUSICIENNE.label=Song Thrush
|
||||
bird.GROSBEC_CASSE_NOYAUX.label=Hawfinch
|
||||
bird.GRUE_CENDREE.label=Common Crane
|
||||
bird.GUEPIER_D_EUROPE.label=European Bee-eater
|
||||
bird.GUEPIER_DE_PERSE.label=Blue-cheeked Bee-eater
|
||||
bird.GUIFETTE_LEUCOPTERE.label=White-winged Tern
|
||||
bird.GUIFETTE_MOUSTAC.label=Whiskered Tern
|
||||
bird.GUIFETTE_NOIRE.label=Black Tern
|
||||
bird.GUILLEMOT_A_LONG_BEC.label=Long-billed Murrelet
|
||||
bird.GUILLEMOT_DE_TROIL.label=Common Murre
|
||||
bird.GYPAETE_BARBU.label=Bearded Vulture
|
||||
bird.HARELDE_BOREALE.label=Long-tailed Duck
|
||||
bird.HARLE_BIEVRE.label=Common Merganser
|
||||
bird.HARLE_HUPPE.label=Red-breasted Merganser
|
||||
bird.HARLE_PIETTE.label=Smew
|
||||
bird.HERON_CENDRE.label=Grey Heron
|
||||
bird.HERON_GARDE_BOEUFS.label=Western Cattle Egret
|
||||
bird.HERON_POURPRE.label=Purple Heron
|
||||
bird.HIBOU_DES_MARAIS.label=Short-eared Owl
|
||||
bird.HIBOU_MOYEN_DUC.label=Long-eared Owl
|
||||
bird.HIRONDELLE_DE_FENETRE.label=Western House Martin
|
||||
bird.HIRONDELLE_DE_RIVAGE.label=Sand Martin
|
||||
bird.HIRONDELLE_DE_ROCHERS.label=Eurasian Crag Martin
|
||||
bird.HIRONDELLE_ROUSSELINE.label=European Red-rumped Swallow
|
||||
bird.HIRONDELLE_RUSTIQUE.label=Barn Swallow
|
||||
bird.HUITRIER_PIE.label=Eurasian Oystercatcher
|
||||
bird.HUPPE_FASCIEE.label=Eurasian Hoopoe
|
||||
bird.HYPOLAIS_BOTTEE.label=Booted Warbler
|
||||
bird.HYPOLAIS_ICTERINE.label=Icterine Warbler
|
||||
bird.HYPOLAIS_POLYGLOTTE.label=Melodious Warbler
|
||||
bird.IBIS_CHAUVE.label=Northern Bald Ibis
|
||||
bird.IBIS_FALCINELLE.label=Glossy Ibis
|
||||
bird.IBIS_SACRE.label=African Sacred Ibis
|
||||
bird.IRANIE_A_GORGE_BLANCHE.label=White-throated Robin
|
||||
bird.JASEUR_BOREAL.label=Bohemian Waxwing
|
||||
bird.LABBE_A_LONGUE_QUEUE.label=Long-tailed Jaeger
|
||||
bird.LABBE_PARASITE.label=Parasitic Jaeger
|
||||
bird.LABBE_POMARIN.label=Pomarine Jaeger
|
||||
bird.LAGOPEDE_ALPIN.label=Rock Ptarmigan
|
||||
bird.LINOTTE_A_BEC_JAUNE.label=Twite
|
||||
bird.LINOTTE_MELODIEUSE.label=Common Linnet
|
||||
bird.LOCUSTELLE_FLUVIATILE.label=River Warbler
|
||||
bird.LOCUSTELLE_LUSCINIOIDE.label=Savi's Warbler
|
||||
bird.LOCUSTELLE_TACHETEE.label=Common Grasshopper Warbler
|
||||
bird.LORIOT_D_EUROPE.label=Eurasian Golden Oriole
|
||||
bird.LUSCINIOLE_A_MOUSTACHES.label=Moustached Warbler
|
||||
bird.MACREUSE_BRUNE.label=Velvet Scoter
|
||||
bird.MACREUSE_NOIRE.label=Common Scoter
|
||||
bird.MAROUETTE_DE_BAILLON.label=Baillon's Crake
|
||||
bird.MAROUETTE_PONCTUEE.label=Spotted Crake
|
||||
bird.MAROUETTE_POUSSIN.label=Little Crake
|
||||
bird.MARTIN_PECHEUR_D_EUROPE.label=Common Kingfisher
|
||||
bird.MARTINET_A_VENTRE_BLANC.label=Alpine Swift
|
||||
bird.MARTINET_NOIR.label=Common Swift
|
||||
bird.MARTINET_PALE.label=Pallid Swift
|
||||
bird.MERLE_A_PLASTRON.label=Ring Ouzel
|
||||
bird.MERLE_NOIR.label=Common Blackbird
|
||||
bird.MESANGE_ALPESTRE.label=???
|
||||
bird.MESANGE_A_LONGUE_QUEUE.label=Long-tailed Tit
|
||||
bird.MESANGE_BLEUE.label=Eurasian Blue Tit
|
||||
bird.MESANGE_BOREALE.label=Willow Tit
|
||||
bird.MESANGE_CHARBONNIERE.label=Great Tit
|
||||
bird.MESANGE_DES_SAULES.label=???
|
||||
bird.MESANGE_HUPPEE.label=Crested Tit
|
||||
bird.MESANGE_NOIRE.label=Coal Tit
|
||||
bird.MESANGE_NONNETTE.label=Marsh Tit
|
||||
bird.MILAN_NOIR.label=Black Kite
|
||||
bird.MILAN_ROYAL.label=Red Kite
|
||||
bird.MOINEAU_CISALPIN.label=Italian Sparrow
|
||||
bird.MOINEAU_DOMESTIQUE.label=House Sparrow
|
||||
bird.MOINEAU_ESPAGNOL.label=Spanish Sparrow
|
||||
bird.MOINEAU_FRIQUET.label=Eurasian Tree Sparrow
|
||||
bird.MOINEAU_SOULCIE.label=Rock Sparrow
|
||||
bird.MONTICOLE_BLEU.label=Blue Rock Thrush
|
||||
bird.MONTICOLE_DE_ROCHE.label=Common Rock Thrush
|
||||
bird.MOUETTE_ATRICILLE.label=Laughing Gull
|
||||
bird.MOUETTE_BLANCHE.label=Ivory Gull
|
||||
bird.MOUETTE_DE_FRANKLIN.label=Franklin's Gull
|
||||
bird.MOUETTE_DE_SABINE.label=Sabine's Gull
|
||||
bird.MOUETTE_MELANOCEPHALE.label=Mediterranean Gull
|
||||
bird.MOUETTE_PYGMEE.label=Little Gull
|
||||
bird.MOUETTE_RIEUSE.label=Black-headed Gull
|
||||
bird.MOUETTE_TRIDACTYLE.label=Black-legged Kittiwake
|
||||
bird.NETTE_ROUSSE.label=Red-crested Pochard
|
||||
bird.NIVEROLLE_ALPINE.label=White-winged Snowfinch
|
||||
bird.OCEANITE_CULBLANC.label=Leach's Storm Petrel
|
||||
bird.OCEANITE_DE_CASTRO.label=Band-rumped Storm Petrel
|
||||
bird.OCEANITE_TEMPETE.label=European Storm Petrel
|
||||
bird.OEDICNEME_CRIARD.label=Eurasian Stone-curlew
|
||||
bird.OIE_A_BEC_COURT.label=Pink-footed Goose
|
||||
bird.OIE_CENDREE.label=Greylag Goose
|
||||
bird.OIE_DES_MOISSONS.label=Taiga Bean Goose
|
||||
bird.OIE_DES_NEIGES.label=Snow Goose
|
||||
bird.OIE_NAINE.label=Lesser White-fronted Goose
|
||||
bird.OIE_RIEUSE.label=Greater White-fronted Goose
|
||||
bird.OUETTE_D_EGYPTE.label=Egyptian Goose
|
||||
bird.OUTARDE_BARBUE.label=Great Bustard
|
||||
bird.OUTARDE_CANEPETIERE.label=Little Bustard
|
||||
bird.OUTARDE_DE_MACQUEEN.label=Asian Houbara
|
||||
bird.OUTARDE_HOUBARA.label=African Houbara
|
||||
bird.PANURE_A_MOUSTACHES.label=Bearded Reedling
|
||||
bird.PELICAN_BLANC.label=Great White Pelican
|
||||
bird.PELICAN_FRISE.label=Dalmatian Pelican
|
||||
bird.PELICAN_GRIS.label=Pink-backed Pelican
|
||||
bird.PERDRIX_BARTAVELLE.label=Rock Partridge
|
||||
bird.PERDRIX_GRISE.label=Grey Partridge
|
||||
bird.PERDRIX_ROUGE.label=Red-legged Partridge
|
||||
bird.PETIT_GRAVELOT.label=Little Ringed Plover
|
||||
bird.PETIT_DUC_SCOPS.label=Eurasian Scops Owl
|
||||
bird.PHALAROPE_A_BEC_ETROIT.label=Red-necked Phalarope
|
||||
bird.PHALAROPE_A_BEC_LARGE.label=Red Phalarope
|
||||
bird.PHRAGMITE_AQUATIQUE.label=Aquatic Warbler
|
||||
bird.PHRAGMITE_DES_JONCS.label=Sedge Warbler
|
||||
bird.PIC_A_DOS_BLANC.label=White-backed Woodpecker
|
||||
bird.PIC_CENDRE.label=Grey-headed Woodpecker
|
||||
bird.PIC_EPEICHE.label=Great Spotted Woodpecker
|
||||
bird.PIC_EPEICHETTE.label=Lesser Spotted Woodpecker
|
||||
bird.PIC_MAR.label=Middle Spotted Woodpecker
|
||||
bird.PIC_NOIR.label=Black Woodpecker
|
||||
bird.PIC_TRIDACTYLE.label=Eurasian Three-toed Woodpecker
|
||||
bird.PIC_VERT.label=European Green Woodpecker
|
||||
bird.PIE_BAVARDE.label=Eurasian Magpie
|
||||
bird.PIE_GRIECHE_A_POITRINE_ROSE.label=Lesser Grey Shrike
|
||||
bird.PIE_GRIECHE_A_TETE_ROUSSE.label=Woodchat Shrike
|
||||
bird.PIE_GRIECHE_ECORCHEUR.label=Red-backed Shrike
|
||||
bird.PIE_GRIECHE_GRISE.label=Great Grey Shrike
|
||||
bird.PIE_GRIECHE_ISABELLE.label=Isabelline Shrike
|
||||
bird.PIGEON_BISET_DOMESTIQUE.label=Rock Dove
|
||||
bird.PIGEON_COLOMBIN.label=Stock Dove
|
||||
bird.PIGEON_RAMIER.label=Common Wood Pigeon
|
||||
bird.PINSON_DES_ARBRES.label=Eurasian Chaffinch
|
||||
bird.PINSON_DU_NORD.label=Brambling
|
||||
bird.PIPIT_A_DOS_OLIVE.label=Olive-backed Pipit
|
||||
bird.PIPIT_A_GORGE_ROUSSE.label=Red-throated Pipit
|
||||
bird.PIPIT_DE_RICHARD.label=Richard's Pipit
|
||||
bird.PIPIT_DES_ARBRES.label=Tree Pipit
|
||||
bird.PIPIT_FARLOUSE.label=Meadow Pipit
|
||||
bird.PIPIT_ROUSSELINE.label=Tawny Pipit
|
||||
bird.PIPIT_SPIONCELLE.label=Water Pipit
|
||||
bird.PLONGEON_A_BEC_BLANC.label=Yellow-billed Loon
|
||||
bird.PLONGEON_ARCTIQUE.label=Black-throated Loon
|
||||
bird.PLONGEON_CATMARIN.label=Red-throated Loon
|
||||
bird.PLONGEON_DU_PACIFIQUE.label=Pacific Loon
|
||||
bird.PLONGEON_IMBRIN.label=Common Loon
|
||||
bird.PLUVIER_ARGENTE.label=Grey Plover
|
||||
bird.PLUVIER_DORE.label=European Golden Plover
|
||||
bird.PLUVIER_FAUVE.label=Pacific Golden Plover
|
||||
bird.PLUVIER_GUIGNARD.label=Eurasian Dotterel
|
||||
bird.POUILLOT_A_GRANDS_SOURCILS.label=Yellow-browed Warbler
|
||||
bird.POUILLOT_BRUN.label=Dusky Warbler
|
||||
bird.POUILLOT_DE_BONELLI.label=Western Bonelli's Warbler
|
||||
bird.POUILLOT_DE_HUME.label=Hume's Leaf Warbler
|
||||
bird.POUILLOT_DE_PALLAS.label=Pallas's Leaf Warbler
|
||||
bird.POUILLOT_DE_SCHWARZ.label=Radde's Warbler
|
||||
bird.POUILLOT_DE_SIBERIE.label=Common Chiffchaff (tristis)
|
||||
bird.POUILLOT_FITIS.label=Willow Warbler
|
||||
bird.POUILLOT_IBERIQUE.label=Iberian Chiffchaff
|
||||
bird.POUILLOT_SIFFLEUR.label=Wood Warbler
|
||||
bird.POUILLOT_VELOCE.label=Common Chiffchaff
|
||||
bird.POUILLOT_VERDATRE.label=Greenish Warbler
|
||||
bird.PUFFIN_CENDRE.label=Cory's Shearwater
|
||||
bird.PUFFIN_DE_SCOPOLI.label=Scopoli's Shearwater
|
||||
bird.PUFFIN_DES_ANGLAIS.label=Manx Shearwater
|
||||
bird.PUFFIN_FULIGINEUX.label=Sooty Shearwater
|
||||
bird.PUFFIN_YELKOUAN.label=Yelkouan Shearwater
|
||||
bird.PYGARGUE_A_QUEUE_BLANCHE.label=White-tailed Eagle
|
||||
bird.RALE_D_EAU.label=Water Rail
|
||||
bird.RALE_DES_GENETS.label=Corn Crake
|
||||
bird.REMIZ_PENDULINE.label=Eurasian Penduline Tit
|
||||
bird.ROITELET_A_TRIPLE_BANDEAU.label=Common Firecrest
|
||||
bird.ROITELET_HUPPE.label=Goldcrest
|
||||
bird.ROLLIER_D_EUROPE.label=European Roller
|
||||
bird.ROSELIN_CRAMOISI.label=Common Rosefinch
|
||||
bird.ROSELIN_GITHAGINE.label=Trumpeter Finch
|
||||
bird.ROSSIGNOL_PHILOMELE.label=Common Nightingale
|
||||
bird.ROSSIGNOL_PROGNE.label=Thrush Nightingale
|
||||
bird.ROUGEGORGE_FAMILIER.label=European Robin
|
||||
bird.ROUGEQUEUE_A_FRONT_BLANC.label=Common Redstart
|
||||
bird.ROUGEQUEUE_NOIR.label=Black Redstart
|
||||
bird.ROUSSEROLLE_DES_BUISSONS.label=Blyth's Reed Warbler
|
||||
bird.ROUSSEROLLE_EFFARVATTE.label=Common Reed Warbler
|
||||
bird.ROUSSEROLLE_ISABELLE.label=Paddyfield Warbler
|
||||
bird.ROUSSEROLLE_TURDOIDE.label=Great Reed Warbler
|
||||
bird.ROUSSEROLLE_VERDEROLLE.label=Marsh Warbler
|
||||
bird.SARCELLE_A_AILES_BLEUES.label=Blue-winged Teal
|
||||
bird.SARCELLE_D_ETE.label=Garganey
|
||||
bird.SARCELLE_D_HIVER.label=Eurasian Teal
|
||||
bird.SARCELLE_MARBREE.label=Marbled Duck
|
||||
bird.SERIN_CINI.label=European Serin
|
||||
bird.SITTELLE_TORCHEPOT.label=Eurasian Nuthatch
|
||||
bird.SIZERIN_FLAMME.label=Redpoll
|
||||
bird.SPATULE_BLANCHE.label=Eurasian Spoonbill
|
||||
bird.STERNE_ARCTIQUE.label=Arctic Tern
|
||||
bird.STERNE_CASPIENNE.label=Caspian Tern
|
||||
bird.STERNE_CAUGEK.label=Sandwich Tern
|
||||
bird.STERNE_DE_DOUGALL.label=Roseate Tern
|
||||
bird.STERNE_HANSEL.label=Gull-billed Tern
|
||||
bird.STERNE_NAINE.label=Little Tern
|
||||
bird.STERNE_PIERREGARIN.label=Common Tern
|
||||
bird.STERNE_VOYAGEUSE.label=Lesser Crested Tern
|
||||
bird.SYRRHAPTE_PARADOXAL.label=Pallas's Sandgrouse
|
||||
bird.TADORNE_CASARCA.label=Ruddy Shelduck
|
||||
bird.TADORNE_DE_BELON.label=Common Shelduck
|
||||
bird.TALEVE_SULTANE.label=Western Swamphen
|
||||
bird.TALEVE_VIOLACEE.label=Purple Gallinule
|
||||
bird.TARIER_DES_PRES.label=Whinchat
|
||||
bird.TARIER_PATRE.label=European Stonechat
|
||||
bird.TARIN_DES_AULNES.label=Eurasian Siskin
|
||||
bird.TETRAS_LYRE.label=Black Grouse
|
||||
bird.TICHODROME_ECHELETTE.label=Wallcreeper
|
||||
bird.TORCOL_FOURMILIER.label=Eurasian Wryneck
|
||||
bird.TOURNEPIERRE_A_COLLIER.label=Ruddy Turnstone
|
||||
bird.TOURTERELLE_DES_BOIS.label=European Turtle Dove
|
||||
bird.TOURTERELLE_TURQUE.label=Eurasian Collared Dove
|
||||
bird.TRAQUET_DU_DESERT.label=Desert Wheatear
|
||||
bird.TRAQUET_MOTTEUX.label=Northern Wheatear
|
||||
bird.TRAQUET_OREILLARD.label=Western Black-eared Wheatear
|
||||
bird.TROGLODYTE_MIGNON.label=Eurasian Wren
|
||||
bird.VANNEAU_HUPPE.label=Northern Lapwing
|
||||
bird.VANNEAU_SOCIABLE.label=Sociable Lapwing
|
||||
bird.VAUTOUR_FAUVE.label=Griffon Vulture
|
||||
bird.VAUTOUR_MOINE.label=Cinereous Vulture
|
||||
bird.VAUTOUR_PERCNOPTERE.label=Egyptian Vulture
|
||||
bird.VENTURON_MONTAGNARD.label=Citril Finch
|
||||
bird.VERDIER_D_EUROPE.label=European Greenfinch
|
||||
@@ -424,3 +424,5 @@ bird.VAUTOUR_MOINE.label=Vautour moine
|
||||
bird.VAUTOUR_PERCNOPTERE.label=Percnoptère d'Égypte
|
||||
bird.VENTURON_MONTAGNARD.label=Venturon montagnard
|
||||
bird.VERDIER_D_EUROPE.label=Verdier d'Europe
|
||||
bird.MESANGE_DES_SAULES.label=Mésange des saules
|
||||
bird.MESANGE_ALPESTRE.label=Mésange alpestre
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
practice.type.picture_exact.label=Image
|
||||
practice.type.picture_multichoice.label=Image (multiple choice)
|
||||
practice.type.sound_exact.label=Sound
|
||||
practice.type.sound_multichoice.label=Sound (multiple choice)
|
||||
practice.type.sound_multichoice_picture.label=Sound (multiple picture choice)
|
||||
@@ -1,5 +0,0 @@
|
||||
practice.type.picture_exact.label=Image
|
||||
practice.type.picture_multichoice.label=Image (multiple choice)
|
||||
practice.type.sound_exact.label=Sound
|
||||
practice.type.sound_multichoice.label=Sound (multiple choice)
|
||||
practice.type.sound_multichoice_picture.label=Sound (multiple picture choice)
|
||||
@@ -1,5 +0,0 @@
|
||||
practice.type.picture_exact.label=Image
|
||||
practice.type.picture_multichoice.label=Image (choix multiples)
|
||||
practice.type.sound_exact.label=Son
|
||||
practice.type.sound_multichoice.label=Son (choix multiples)
|
||||
practice.type.sound_multichoice_picture.label=Son (choix multiple d'images)
|
||||
@@ -0,0 +1,5 @@
|
||||
question.type.picture_exact.label=Image
|
||||
question.type.picture_multichoice.label=Image (multiple choice)
|
||||
question.type.sound_exact.label=Sound
|
||||
question.type.sound_multichoice.label=Sound (multiple choice)
|
||||
question.type.sound_multichoice_picture.label=Sound (multiple picture choice)
|
||||
@@ -0,0 +1,5 @@
|
||||
question.type.picture_exact.label=Image
|
||||
question.type.picture_multichoice.label=Image (multiple choice)
|
||||
question.type.sound_exact.label=Sound
|
||||
question.type.sound_multichoice.label=Sound (multiple choice)
|
||||
question.type.sound_multichoice_picture.label=Sound (multiple picture choice)
|
||||
@@ -0,0 +1,5 @@
|
||||
question.type.picture_exact.label=Image
|
||||
question.type.picture_multichoice.label=Image (choix multiples)
|
||||
question.type.sound_exact.label=Son
|
||||
question.type.sound_multichoice.label=Son (choix multiples)
|
||||
question.type.sound_multichoice_picture.label=Son (choix multiple d'images)
|
||||
@@ -5,6 +5,16 @@ package ch.gtache.fro.gui;
|
||||
*/
|
||||
public interface SettingsController extends Controller {
|
||||
|
||||
/**
|
||||
* Imports settings from a file
|
||||
*/
|
||||
void importSettings();
|
||||
|
||||
/**
|
||||
* Exports settings to a file
|
||||
*/
|
||||
void exportSettings();
|
||||
|
||||
@Override
|
||||
SettingsModel model();
|
||||
}
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
package ch.gtache.fro.practice.gui;
|
||||
|
||||
import ch.gtache.fro.gui.Model;
|
||||
|
||||
/**
|
||||
* Model for the practice view - exact guess
|
||||
*/
|
||||
public interface PracticeExactModel extends Model {
|
||||
public interface PracticeExactModel extends PracticeQuestionModel {
|
||||
|
||||
/**
|
||||
* Returns the user's current guess
|
||||
@@ -19,5 +17,5 @@ public interface PracticeExactModel extends Model {
|
||||
*
|
||||
* @param guess The guess
|
||||
*/
|
||||
void guess(String guess);
|
||||
void setGuess(String guess);
|
||||
}
|
||||
|
||||
@@ -11,4 +11,9 @@ public interface PracticeGuessController extends Controller {
|
||||
* Confirms the current guess
|
||||
*/
|
||||
void confirm();
|
||||
|
||||
/**
|
||||
* Proceeds to the next question/show the result page
|
||||
*/
|
||||
void next();
|
||||
}
|
||||
|
||||
@@ -1,11 +1,24 @@
|
||||
package ch.gtache.fro.practice.gui;
|
||||
|
||||
import ch.gtache.fro.gui.Model;
|
||||
import ch.gtache.fro.practice.PracticeRun;
|
||||
|
||||
/**
|
||||
* Model for the practice view
|
||||
*/
|
||||
public interface PracticeModel extends Model {
|
||||
|
||||
/**
|
||||
* Returns the current practice run
|
||||
*
|
||||
* @return The practice run
|
||||
*/
|
||||
PracticeRun practiceRun();
|
||||
|
||||
/**
|
||||
* Sets the practice run
|
||||
*
|
||||
* @param practiceRun The practice run
|
||||
*/
|
||||
void setPracticeRun(PracticeRun practiceRun);
|
||||
}
|
||||
|
||||
@@ -29,5 +29,5 @@ public interface PracticeMultichoiceModel extends Model {
|
||||
*
|
||||
* @param selected The selected bird
|
||||
*/
|
||||
void selected(Bird selected);
|
||||
void setSelected(Bird selected);
|
||||
}
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
package ch.gtache.fro.practice.gui;
|
||||
|
||||
import ch.gtache.fro.Picture;
|
||||
import ch.gtache.fro.gui.Model;
|
||||
|
||||
/**
|
||||
* Represents a model for a practice view with a picture
|
||||
*/
|
||||
public interface PracticePictureModel extends Model {
|
||||
public interface PracticePictureModel extends PracticeQuestionModel {
|
||||
|
||||
/**
|
||||
* Returns the picture to guess
|
||||
@@ -20,5 +19,5 @@ public interface PracticePictureModel extends Model {
|
||||
*
|
||||
* @param picture The picture
|
||||
*/
|
||||
void picture(Picture picture);
|
||||
void setPicture(Picture picture);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
package ch.gtache.fro.practice.gui;
|
||||
|
||||
import ch.gtache.fro.Bird;
|
||||
import ch.gtache.fro.gui.Model;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Model for the practice questions views
|
||||
*/
|
||||
public interface PracticeQuestionModel extends Model {
|
||||
|
||||
/**
|
||||
* Returns whether the view is currently showing the question result
|
||||
*
|
||||
* @return The result
|
||||
*/
|
||||
boolean isShowingResult();
|
||||
|
||||
/**
|
||||
* Returns the list of guesses made
|
||||
*
|
||||
* @return The guesses
|
||||
*/
|
||||
List<Bird> guesses();
|
||||
|
||||
/**
|
||||
* Returns the number of guesses made
|
||||
*
|
||||
* @return The guess count
|
||||
*/
|
||||
default int guessesCount() {
|
||||
return guesses().size();
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,11 @@ import ch.gtache.fro.gui.Controller;
|
||||
*/
|
||||
public interface PracticeResultController extends Controller {
|
||||
|
||||
/**
|
||||
* Closes the practice result view
|
||||
*/
|
||||
void confirm();
|
||||
|
||||
@Override
|
||||
PracticeResultModel model();
|
||||
}
|
||||
|
||||
@@ -1,9 +1,52 @@
|
||||
package ch.gtache.fro.practice.gui;
|
||||
|
||||
import ch.gtache.fro.gui.Model;
|
||||
import ch.gtache.fro.practice.PracticeResult;
|
||||
|
||||
/**
|
||||
* Model for the practice result view
|
||||
*/
|
||||
public interface PracticeResultModel extends Model {
|
||||
|
||||
/**
|
||||
* Gets the practice result
|
||||
*
|
||||
* @return the practice result
|
||||
*/
|
||||
PracticeResult result();
|
||||
|
||||
/**
|
||||
* Sets the practice result
|
||||
*
|
||||
* @param result the practice result
|
||||
*/
|
||||
void setResult(PracticeResult result);
|
||||
|
||||
/**
|
||||
* The text for the success label
|
||||
*
|
||||
* @return The text
|
||||
*/
|
||||
String successLabel();
|
||||
|
||||
/**
|
||||
* The text for the failure label
|
||||
*
|
||||
* @return The text
|
||||
*/
|
||||
String failureLabel();
|
||||
|
||||
/**
|
||||
* The text for the label of successful guesses
|
||||
*
|
||||
* @return The text
|
||||
*/
|
||||
String successListLabel();
|
||||
|
||||
/**
|
||||
* The text for the label of failed guesses
|
||||
*
|
||||
* @return The text
|
||||
*/
|
||||
String failureListLabel();
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
package ch.gtache.fro.practice.gui;
|
||||
|
||||
import ch.gtache.fro.gui.Model;
|
||||
import ch.gtache.fro.practice.PracticeType;
|
||||
import ch.gtache.fro.practice.PracticeParameters;
|
||||
import ch.gtache.fro.practice.QuestionType;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -15,21 +16,49 @@ public interface PracticeSettingsModel extends Model {
|
||||
*
|
||||
* @return The list of practice types
|
||||
*/
|
||||
List<PracticeType> practiceTypes();
|
||||
List<QuestionType> questionTypes();
|
||||
|
||||
/**
|
||||
* Returns the currently selected practice type
|
||||
*
|
||||
* @return The practice type
|
||||
*/
|
||||
PracticeType practiceType();
|
||||
QuestionType questionType();
|
||||
|
||||
/**
|
||||
* Sets the practice type
|
||||
*
|
||||
* @param practiceType The practice type
|
||||
* @param questionType The practice type
|
||||
*/
|
||||
void practiceType(PracticeType practiceType);
|
||||
void setQuestionType(QuestionType questionType);
|
||||
|
||||
/**
|
||||
* Returns the number of unsuccessful guesses before a question is considered as failed
|
||||
*
|
||||
* @return The number of unsuccessful guesses
|
||||
*/
|
||||
int guessesNumber();
|
||||
|
||||
/**
|
||||
* Sets the number of unsuccessful guesses before a question is considered as failed
|
||||
*
|
||||
* @param guessesNumber The number of unsuccessful guesses
|
||||
*/
|
||||
void setGuessesNumber(int guessesNumber);
|
||||
|
||||
/**
|
||||
* Returns the number of questions
|
||||
*
|
||||
* @return The number of questions
|
||||
*/
|
||||
int questionsNumber();
|
||||
|
||||
/**
|
||||
* Sets the number of questions
|
||||
*
|
||||
* @param questionsNumber The number of questions
|
||||
*/
|
||||
void setQuestionsNumber(int questionsNumber);
|
||||
|
||||
/**
|
||||
* Returns true if the practice type has suggestions
|
||||
@@ -50,5 +79,12 @@ public interface PracticeSettingsModel extends Model {
|
||||
*
|
||||
* @param suggestionsNumber The number of suggestions
|
||||
*/
|
||||
void suggestionsNumber(int suggestionsNumber);
|
||||
void setSuggestionsNumber(int suggestionsNumber);
|
||||
|
||||
/**
|
||||
* Returns the current practice parameters
|
||||
*
|
||||
* @return The practice parameters
|
||||
*/
|
||||
PracticeParameters practiceParameters();
|
||||
}
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
package ch.gtache.fro.practice.gui;
|
||||
|
||||
import ch.gtache.fro.Sound;
|
||||
import ch.gtache.fro.gui.Model;
|
||||
|
||||
/**
|
||||
* Represents a model for a practice view with a sound
|
||||
*/
|
||||
public interface PracticeSoundModel extends Model {
|
||||
public interface PracticeSoundModel extends PracticeQuestionModel {
|
||||
|
||||
/**
|
||||
* Returns the sound to guess
|
||||
@@ -20,5 +19,5 @@ public interface PracticeSoundModel extends Model {
|
||||
*
|
||||
* @param sound The sound
|
||||
*/
|
||||
void sound(Sound sound);
|
||||
void setSound(Sound sound);
|
||||
}
|
||||
|
||||
@@ -28,5 +28,5 @@ public interface PracticeSoundMultichoicePictureModel extends PracticeSoundModel
|
||||
*
|
||||
* @param selected The selected picture
|
||||
*/
|
||||
void selected(Picture selected);
|
||||
void setSelected(Picture selected);
|
||||
}
|
||||
@@ -3,9 +3,8 @@
|
||||
*/
|
||||
module ch.gtache.fro.gui.core {
|
||||
requires transitive ch.gtache.fro.gui.api;
|
||||
requires ch.gtache.fro.core;
|
||||
requires jakarta.inject;
|
||||
requires dagger;
|
||||
requires transitive jakarta.inject;
|
||||
requires transitive ch.gtache.fro.core;
|
||||
|
||||
exports ch.gtache.fro.gui.impl;
|
||||
exports ch.gtache.fro.modules.gui.impl;
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
fetch.button.all.label=Télécharger tous
|
||||
fetch.button.fetch.label=Télécharger
|
||||
fetch.button.all.label=Télécharger tous
|
||||
fetch.button.fetch.label=Télécharger
|
||||
fetch.providers.label=Sources
|
||||
@@ -1,3 +1,3 @@
|
||||
main.tab.fetch.label=Téléchargement
|
||||
main.tab.practice.label=Entraînement
|
||||
main.tab.settings.label=Paramètres
|
||||
main.tab.fetch.label=Téléchargement
|
||||
main.tab.practice.label=Entraînement
|
||||
main.tab.settings.label=Paramètres
|
||||
@@ -1,5 +1,5 @@
|
||||
settings.table.column.bird=Oiseau
|
||||
settings.table.column.enabled=Activé
|
||||
settings.table.column.enabled=Activé
|
||||
settings.table.column.fetchers=Fournisseurs
|
||||
settings.table.column.pictures=Images
|
||||
settings.table.column.sounds=Sons
|
||||
@@ -1 +1,2 @@
|
||||
practice.picture.exact.next.button.label=Next
|
||||
practice.picture.exact.validate.button.label=Confirm
|
||||
@@ -1 +1,2 @@
|
||||
practice.picture.exact.next.button.label=Next
|
||||
practice.picture.exact.validate.button.label=Confirm
|
||||
@@ -1 +1,2 @@
|
||||
practice.picture.exact.next.button.label=Suivant
|
||||
practice.picture.exact.validate.button.label=Confirmer
|
||||
@@ -1 +1,2 @@
|
||||
practice.picture.multichoice.next.button.label=Next
|
||||
practice.picture.multichoice.validate.button.label=Confirm
|
||||
@@ -1 +1,2 @@
|
||||
practice.picture.multichoice.next.button.label=Next
|
||||
practice.picture.multichoice.validate.button.label=Confirm
|
||||
@@ -1 +1,2 @@
|
||||
practice.picture.multichoice.next.button.label=Suivant
|
||||
practice.picture.multichoice.validate.button.label=Confirmer
|
||||
@@ -1,3 +1,4 @@
|
||||
practice.result.confirm.button.label=Confirm
|
||||
practice.result.failure.label=Failed guesses
|
||||
practice.result.label=Practice result
|
||||
practice.result.success.label=Correct guesses
|
||||
@@ -1,3 +1,4 @@
|
||||
practice.result.confirm.button.label=Confirm
|
||||
practice.result.failure.label=Failed guesses
|
||||
practice.result.label=Practice result
|
||||
practice.result.success.label=Correct guesses
|
||||
@@ -1,3 +1,4 @@
|
||||
practice.result.failure.label=Choix incorrects
|
||||
practice.result.confirm.button.label=Confirmer
|
||||
practice.result.failure.label=Réponses incorrectes
|
||||
practice.result.label=Résultat
|
||||
practice.result.success.label=Choix corrects
|
||||
practice.result.success.label=Réponses correctes
|
||||
@@ -1,3 +1,5 @@
|
||||
practice.settings.guesses.number.label=Number of available guesses
|
||||
practice.settings.questions.number.label=Number of questions
|
||||
practice.settings.start.button.label=Start
|
||||
practice.settings.suggestions.number.label=Number of suggestions
|
||||
practice.settings.type.label=Practice type
|
||||
@@ -1,3 +1,5 @@
|
||||
practice.settings.guesses.number.label=Number of available guesses
|
||||
practice.settings.questions.number.label=Number of questions
|
||||
practice.settings.start.button.label=Start
|
||||
practice.settings.suggestions.number.label=Number of suggestions
|
||||
practice.settings.type.label=Practice type
|
||||
@@ -1,3 +1,5 @@
|
||||
practice.settings.start.button.label=Démarrer
|
||||
practice.settings.guesses.number.label=Nombre d'essais disponibles
|
||||
practice.settings.questions.number.label=Nombre de questions
|
||||
practice.settings.start.button.label=Démarrer
|
||||
practice.settings.suggestions.number.label=Nombre de suggestions
|
||||
practice.settings.type.label=Type d'entraînement
|
||||
practice.settings.type.label=Type d'entraînement
|
||||
@@ -1 +1,2 @@
|
||||
practice.sound.exact.next.button.label=Next
|
||||
practice.sound.exact.validate.button.label=Confirm
|
||||
@@ -1 +1,2 @@
|
||||
practice.sound.exact.next.button.label=Next
|
||||
practice.sound.exact.validate.button.label=Confirm
|
||||
@@ -1 +1,2 @@
|
||||
practice.sound.exact.next.button.label=Suivant
|
||||
practice.sound.exact.validate.button.label=Confirmer
|
||||
@@ -1 +1,2 @@
|
||||
practice.sound.multichoice.next.button.label=Next
|
||||
practice.sound.multichoice.validate.button.label=Confirm
|
||||
@@ -1 +1,2 @@
|
||||
practice.sound.multichoice.next.button.label=Next
|
||||
practice.sound.multichoice.validate.button.label=Confirm
|
||||
@@ -1 +1,2 @@
|
||||
practice.sound.multichoice.next.button.label=Suivant
|
||||
practice.sound.multichoice.validate.button.label=Confirmer
|
||||
@@ -1 +1,2 @@
|
||||
practice.sound.multichoice.picture.next.button.label=Next
|
||||
practice.sound.multichoice.picture.validate.button.label=Confirm
|
||||
@@ -1 +1,2 @@
|
||||
practice.sound.multichoice.picture.next.button.label=Next
|
||||
practice.sound.multichoice.picture.validate.button.label=Confirm
|
||||
@@ -1 +1,2 @@
|
||||
practice.sound.multichoice.picture.next.button.label=Suivant
|
||||
practice.sound.multichoice.picture.validate.button.label=Confirmer
|
||||
@@ -1,8 +1,14 @@
|
||||
package ch.gtache.fro.gui.fx;
|
||||
|
||||
import ch.gtache.fro.Bird;
|
||||
import ch.gtache.fro.Fetcher;
|
||||
import ch.gtache.fro.gui.FetchModel;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Singleton;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
|
||||
/**
|
||||
* FX implementation of {@link FetchModel}
|
||||
@@ -10,8 +16,16 @@ import jakarta.inject.Singleton;
|
||||
@Singleton
|
||||
public final class FXFetchModel implements FetchModel {
|
||||
|
||||
private final ObservableList<Bird> birds;
|
||||
private final ObjectProperty<Bird> selectedBird;
|
||||
private final ObservableList<Fetcher> fetchers;
|
||||
private final ObjectProperty<Fetcher> selectedFetcher;
|
||||
|
||||
@Inject
|
||||
FXFetchModel() {
|
||||
|
||||
this.birds = FXCollections.observableArrayList();
|
||||
this.selectedBird = new SimpleObjectProperty<>();
|
||||
this.fetchers = FXCollections.observableArrayList();
|
||||
this.selectedFetcher = new SimpleObjectProperty<>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package ch.gtache.fro.gui.fx;
|
||||
|
||||
import ch.gtache.fro.Bird;
|
||||
import ch.gtache.fro.gui.SettingsController;
|
||||
import ch.gtache.fro.practice.BirdPracticeParameters;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Singleton;
|
||||
import javafx.fxml.FXML;
|
||||
@@ -18,13 +18,23 @@ public final class FXSettingsController implements SettingsController {
|
||||
private final FXSettingsModel model;
|
||||
|
||||
@FXML
|
||||
private TableView<Bird> table;
|
||||
private TableView<BirdPracticeParameters> table;
|
||||
|
||||
@Inject
|
||||
FXSettingsController(final FXSettingsModel model) {
|
||||
this.model = Objects.requireNonNull(model);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void importSettings() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exportSettings() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public FXSettingsModel model() {
|
||||
return model;
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
package ch.gtache.fro.gui.fx;
|
||||
|
||||
import ch.gtache.fro.gui.SettingsModel;
|
||||
import ch.gtache.fro.practice.BirdPracticeParameters;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Singleton;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
|
||||
/**
|
||||
* FX implementation of {@link SettingsModel}
|
||||
@@ -10,7 +13,10 @@ import jakarta.inject.Singleton;
|
||||
@Singleton
|
||||
public final class FXSettingsModel implements SettingsModel {
|
||||
|
||||
private final ObservableList<BirdPracticeParameters> birdParameters;
|
||||
|
||||
@Inject
|
||||
FXSettingsModel() {
|
||||
this.birdParameters = FXCollections.observableArrayList();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
package ch.gtache.fro.practice.gui.fx;
|
||||
|
||||
import ch.gtache.fro.Bird;
|
||||
import ch.gtache.fro.practice.PracticeRun;
|
||||
import ch.gtache.fro.practice.gui.PracticeQuestionModel;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.ReadOnlyStringProperty;
|
||||
import javafx.beans.property.ReadOnlyStringWrapper;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
|
||||
/**
|
||||
* Base FX model for practice question views
|
||||
*/
|
||||
public abstract class AbstractFXPracticeQuestionModel implements PracticeQuestionModel {
|
||||
private final ObjectProperty<PracticeRun> practiceRun;
|
||||
private final ReadOnlyStringWrapper progress;
|
||||
private final BooleanProperty showingResult;
|
||||
private final ObservableList<Bird> guesses;
|
||||
|
||||
/**
|
||||
* Instantiates the model
|
||||
*/
|
||||
protected AbstractFXPracticeQuestionModel() {
|
||||
this.practiceRun = new SimpleObjectProperty<>();
|
||||
this.progress = new ReadOnlyStringWrapper();
|
||||
this.showingResult = new SimpleBooleanProperty(false);
|
||||
this.guesses = FXCollections.observableArrayList();
|
||||
this.progress.bind(practiceRun.map(r -> r.currentQuestionIndex() + 1 + "/" + r.parameters().questionsNumber()));
|
||||
}
|
||||
|
||||
ObjectProperty<PracticeRun> practiceRunProperty() {
|
||||
return practiceRun;
|
||||
}
|
||||
|
||||
ReadOnlyStringProperty progressProperty() {
|
||||
return progress.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isShowingResult() {
|
||||
return showingResult.get();
|
||||
}
|
||||
|
||||
BooleanProperty showingResultProperty() {
|
||||
return showingResult;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObservableList<Bird> guesses() {
|
||||
return guesses;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package ch.gtache.fro.practice.gui.fx;
|
||||
|
||||
import ch.gtache.fro.practice.gui.PracticeExactModel;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
|
||||
/**
|
||||
* Base FX model for practice question views with exact answer
|
||||
*/
|
||||
public abstract class AbstractFXPraticeQuestionExactModel extends AbstractFXPracticeQuestionModel implements PracticeExactModel {
|
||||
|
||||
private final StringProperty guess;
|
||||
|
||||
protected AbstractFXPraticeQuestionExactModel() {
|
||||
this.guess = new SimpleStringProperty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String guess() {
|
||||
return guess.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setGuess(String guess) {
|
||||
this.guess.set(guess);
|
||||
}
|
||||
|
||||
StringProperty guessProperty() {
|
||||
return guess;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package ch.gtache.fro.practice.gui.fx;
|
||||
|
||||
import ch.gtache.fro.Bird;
|
||||
import ch.gtache.fro.practice.gui.PracticeMultichoiceModel;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
|
||||
/**
|
||||
* Base FX model for practice question views with multichoice answers
|
||||
*/
|
||||
public abstract class AbstractFXPraticeQuestionMultichoiceModel extends AbstractFXPracticeQuestionModel implements PracticeMultichoiceModel {
|
||||
|
||||
private final ObservableList<Bird> suggestions;
|
||||
private final ObjectProperty<Bird> selected;
|
||||
|
||||
/**
|
||||
* Instantiates the model
|
||||
*/
|
||||
protected AbstractFXPraticeQuestionMultichoiceModel() {
|
||||
this.suggestions = FXCollections.observableArrayList();
|
||||
this.selected = new SimpleObjectProperty<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObservableList<Bird> suggestions() {
|
||||
return suggestions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bird selected() {
|
||||
return selected.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSelected(final Bird selected) {
|
||||
this.selected.set(selected);
|
||||
}
|
||||
|
||||
ObjectProperty<Bird> selectedProperty() {
|
||||
return selected;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package ch.gtache.fro.practice.gui.fx;
|
||||
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Singleton;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Binds the models.
|
||||
*/
|
||||
@Singleton
|
||||
public class FXPracticeBinder {
|
||||
|
||||
private final FXPracticeModel model;
|
||||
private final FXPracticePictureExactModel pictureExactModel;
|
||||
private final FXPracticePictureMultichoiceModel practicePictureMultichoiceModel;
|
||||
private final FXPracticeSoundExactModel soundExactModel;
|
||||
private final FXPracticeSoundMultichoiceModel soundMultichoiceModel;
|
||||
private final FXPracticeSoundMultichoicePictureModel soundMultichoicePictureModel;
|
||||
private final FXPracticeSettingsModel settingsModel;
|
||||
private final FXPracticeResultModel resultModel;
|
||||
|
||||
@Inject
|
||||
FXPracticeBinder(final FXPracticeModel model, final FXPracticePictureExactModel pictureExactModel,
|
||||
final FXPracticePictureMultichoiceModel practicePictureMultichoiceModel,
|
||||
final FXPracticeSoundExactModel soundExactModel,
|
||||
final FXPracticeSoundMultichoiceModel soundMultichoiceModel,
|
||||
final FXPracticeSoundMultichoicePictureModel soundMultichoicePictureModel,
|
||||
final FXPracticeSettingsModel settingsModel,
|
||||
final FXPracticeResultModel resultModel) {
|
||||
this.model = Objects.requireNonNull(model);
|
||||
this.pictureExactModel = Objects.requireNonNull(pictureExactModel);
|
||||
this.practicePictureMultichoiceModel = Objects.requireNonNull(practicePictureMultichoiceModel);
|
||||
this.soundExactModel = Objects.requireNonNull(soundExactModel);
|
||||
this.soundMultichoiceModel = Objects.requireNonNull(soundMultichoiceModel);
|
||||
this.soundMultichoicePictureModel = Objects.requireNonNull(soundMultichoicePictureModel);
|
||||
this.settingsModel = Objects.requireNonNull(settingsModel);
|
||||
this.resultModel = Objects.requireNonNull(resultModel);
|
||||
}
|
||||
|
||||
void createBindings() {
|
||||
model.practiceRunProperty().bindBidirectional(pictureExactModel.practiceRunProperty());
|
||||
model.practiceRunProperty().bindBidirectional(practicePictureMultichoiceModel.practiceRunProperty());
|
||||
model.practiceRunProperty().bindBidirectional(soundExactModel.practiceRunProperty());
|
||||
model.practiceRunProperty().bindBidirectional(soundMultichoiceModel.practiceRunProperty());
|
||||
model.practiceRunProperty().bindBidirectional(soundMultichoicePictureModel.practiceRunProperty());
|
||||
model.practiceRunProperty().bindBidirectional(settingsModel.practiceRunProperty());
|
||||
model.practiceResultProperty().bindBidirectional(resultModel.resultProperty());
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,13 @@
|
||||
package ch.gtache.fro.practice.gui.fx;
|
||||
|
||||
import ch.gtache.fro.practice.PracticeRunner;
|
||||
import ch.gtache.fro.practice.gui.PracticeController;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Singleton;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.layout.Pane;
|
||||
|
||||
import java.util.Objects;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
/**
|
||||
* FX implementation of {@link PracticeController}
|
||||
@@ -30,10 +31,14 @@ public final class FXPracticeController implements PracticeController {
|
||||
private Pane practiceSoundMultichoicePicture;
|
||||
|
||||
private final FXPracticeModel model;
|
||||
private final FXPracticeBinder binder;
|
||||
private final PracticeRunner runner;
|
||||
|
||||
@Inject
|
||||
FXPracticeController(final FXPracticeModel model) {
|
||||
this.model = Objects.requireNonNull(model);
|
||||
FXPracticeController(final FXPracticeModel model, final FXPracticeBinder binder, final PracticeRunner runner) {
|
||||
this.model = requireNonNull(model);
|
||||
this.binder = requireNonNull(binder);
|
||||
this.runner = requireNonNull(runner);
|
||||
}
|
||||
|
||||
@FXML
|
||||
@@ -47,6 +52,31 @@ public final class FXPracticeController implements PracticeController {
|
||||
}
|
||||
});
|
||||
model.setSelectedPane(practiceSettings);
|
||||
binder.createBindings();
|
||||
|
||||
model.practiceRunProperty().addListener((_, _, newValue) -> {
|
||||
if (newValue == null) {
|
||||
model.setSelectedPane(practiceSettings);
|
||||
} else if (newValue.isFinished()) {
|
||||
final var result = runner.finish(newValue);
|
||||
model.practiceResultProperty().set(result);
|
||||
model.setSelectedPane(practiceResult);
|
||||
} else {
|
||||
final var pane = switch (newValue.currentQuestion().questionType()) {
|
||||
case PICTURE_EXACT -> practicePictureExact;
|
||||
case PICTURE_MULTICHOICE -> practicePictureMultichoice;
|
||||
case SOUND_EXACT -> practiceSoundExact;
|
||||
case SOUND_MULTICHOICE -> practiceSoundMultichoice;
|
||||
case SOUND_MULTICHOICE_PICTURE -> practiceSoundMultichoicePicture;
|
||||
};
|
||||
model.setSelectedPane(pane);
|
||||
}
|
||||
});
|
||||
model.practiceResultProperty().addListener((_, _, newValue) -> {
|
||||
if (newValue == null) {
|
||||
model.setPracticeRun(null);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package ch.gtache.fro.practice.gui.fx;
|
||||
|
||||
import ch.gtache.fro.practice.PracticeResult;
|
||||
import ch.gtache.fro.practice.PracticeRun;
|
||||
import ch.gtache.fro.practice.gui.PracticeModel;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Singleton;
|
||||
@@ -17,11 +19,15 @@ public final class FXPracticeModel implements PracticeModel {
|
||||
|
||||
private final ObservableList<Pane> panes;
|
||||
private final ObjectProperty<Pane> selectedPane;
|
||||
private final ObjectProperty<PracticeRun> practiceRun;
|
||||
private final ObjectProperty<PracticeResult> practiceResult;
|
||||
|
||||
@Inject
|
||||
FXPracticeModel() {
|
||||
this.panes = FXCollections.observableArrayList();
|
||||
this.selectedPane = new SimpleObjectProperty<>();
|
||||
this.practiceRun = new SimpleObjectProperty<>();
|
||||
this.practiceResult = new SimpleObjectProperty<>();
|
||||
}
|
||||
|
||||
ObservableList<Pane> panes() {
|
||||
@@ -39,4 +45,22 @@ public final class FXPracticeModel implements PracticeModel {
|
||||
ObjectProperty<Pane> selectedPaneProperty() {
|
||||
return selectedPane;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PracticeRun practiceRun() {
|
||||
return practiceRun.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPracticeRun(final PracticeRun practiceRun) {
|
||||
this.practiceRun.set(practiceRun);
|
||||
}
|
||||
|
||||
ObjectProperty<PracticeRun> practiceRunProperty() {
|
||||
return practiceRun;
|
||||
}
|
||||
|
||||
ObjectProperty<PracticeResult> practiceResultProperty() {
|
||||
return practiceResult;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,25 @@
|
||||
package ch.gtache.fro.practice.gui.fx;
|
||||
|
||||
import ch.gtache.fro.Bird;
|
||||
import ch.gtache.fro.BirdTranslator;
|
||||
import ch.gtache.fro.PictureType;
|
||||
import ch.gtache.fro.practice.PracticeRunner;
|
||||
import ch.gtache.fro.practice.gui.PracticePictureExactController;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Singleton;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.controlsfx.control.PrefixSelectionComboBox;
|
||||
import org.controlsfx.control.textfield.CustomTextField;
|
||||
|
||||
import java.util.Objects;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
/**
|
||||
* FX implementation of {@link PracticePictureExactController}
|
||||
@@ -18,25 +27,84 @@ import java.util.Objects;
|
||||
@Singleton
|
||||
public final class FXPracticePictureExactController implements PracticePictureExactController {
|
||||
|
||||
private static final Logger logger = LogManager.getLogger(FXPracticePictureExactController.class);
|
||||
|
||||
private final FXPracticePictureExactModel model;
|
||||
private final PracticeRunner runner;
|
||||
private final UserInputNormalizer normalizer;
|
||||
private final BirdTranslator translator;
|
||||
|
||||
@FXML
|
||||
private ImageView pictureView;
|
||||
@FXML
|
||||
private CustomTextField nameField;
|
||||
private CustomTextField inputField;
|
||||
@FXML
|
||||
private PrefixSelectionComboBox<PictureType> typeCombobox;
|
||||
@FXML
|
||||
private Button validateButton;
|
||||
private Button confirmButton;
|
||||
@FXML
|
||||
private Label progressLabel;
|
||||
@FXML
|
||||
private VBox guessesBox;
|
||||
@FXML
|
||||
private Label answerLabel;
|
||||
|
||||
@Inject
|
||||
FXPracticePictureExactController(final FXPracticePictureExactModel model) {
|
||||
this.model = Objects.requireNonNull(model);
|
||||
FXPracticePictureExactController(final FXPracticePictureExactModel model, final PracticeRunner runner,
|
||||
final UserInputNormalizer normalizer, final BirdTranslator translator) {
|
||||
this.model = requireNonNull(model);
|
||||
this.runner = requireNonNull(runner);
|
||||
this.normalizer = requireNonNull(normalizer);
|
||||
this.translator = requireNonNull(translator);
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void initialize() {
|
||||
inputField.textProperty().bindBidirectional(model.guessProperty());
|
||||
inputField.disableProperty().bind(model.showingResultProperty());
|
||||
confirmButton.disableProperty().bind(model.guessProperty().isEmpty().and(model.showingResultProperty().not()));
|
||||
answerLabel.visibleProperty().bind(model.showingResultProperty());
|
||||
answerLabel.textProperty().bind(model.pictureProperty().map(p -> translator.translateAsync(p.bird()).join()));
|
||||
progressLabel.textProperty().bind(model.progressProperty());
|
||||
pictureView.imageProperty().bind(model.imageProperty());
|
||||
pictureView.fitHeightProperty().bind(model.imageProperty().map(Image::getHeight));
|
||||
pictureView.fitWidthProperty().bind(model.imageProperty().map(Image::getWidth));
|
||||
model.guesses().addListener((ListChangeListener<Bird>) _ -> buildGuessesBox());
|
||||
}
|
||||
|
||||
private void buildGuessesBox() {
|
||||
final var labels = model.guesses().stream().map(this::getLabel).toList();
|
||||
guessesBox.getChildren().setAll(labels);
|
||||
}
|
||||
|
||||
private Label getLabel(final Bird bird) {
|
||||
final String text;
|
||||
final var birdName = translator.translateAsync(bird).join();
|
||||
if (bird.equals(model.picture().bird())) {
|
||||
text = "✓ " + birdName;
|
||||
} else {
|
||||
text = "✗ " + birdName;
|
||||
}
|
||||
return new Label(text);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void confirm() {
|
||||
throw new UnsupportedOperationException();
|
||||
final var bird = normalizer.getBird(model.guess());
|
||||
model.setGuess("");
|
||||
model.guesses().add(bird);
|
||||
if (bird.equals(model.picture().bird()) ||
|
||||
model.guessesCount() >= model.practiceRunProperty().get().parameters().guessesNumber()) {
|
||||
model.showingResultProperty().set(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void next() {
|
||||
final var newRun = runner.step(model.practiceRunProperty().get(), model.guesses().getLast());
|
||||
model.guesses().clear();
|
||||
model.showingResultProperty().set(false);
|
||||
model.practiceRunProperty().set(newRun);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -46,11 +114,19 @@ public final class FXPracticePictureExactController implements PracticePictureEx
|
||||
|
||||
@FXML
|
||||
private void enterPressed() {
|
||||
confirm();
|
||||
confirmOrNext();
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void validatePressed() {
|
||||
confirm();
|
||||
private void confirmPressed() {
|
||||
confirmOrNext();
|
||||
}
|
||||
|
||||
private void confirmOrNext() {
|
||||
if (model.isShowingResult()) {
|
||||
next();
|
||||
} else {
|
||||
confirm();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,47 +1,38 @@
|
||||
package ch.gtache.fro.practice.gui.fx;
|
||||
|
||||
import ch.gtache.fro.Picture;
|
||||
import ch.gtache.fro.practice.PicturePracticeQuestion;
|
||||
import ch.gtache.fro.practice.gui.PracticePictureExactModel;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Singleton;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||
import javafx.beans.property.ReadOnlyObjectWrapper;
|
||||
import javafx.scene.image.Image;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
|
||||
|
||||
/**
|
||||
* FX implementation of {@link PracticePictureExactModel}
|
||||
*/
|
||||
@Singleton
|
||||
public final class FXPracticePictureExactModel implements PracticePictureExactModel {
|
||||
public final class FXPracticePictureExactModel extends AbstractFXPraticeQuestionExactModel implements PracticePictureExactModel {
|
||||
|
||||
private final StringProperty guess;
|
||||
private final ObjectProperty<Picture> picture;
|
||||
private final ReadOnlyObjectWrapper<Picture> picture;
|
||||
private final ReadOnlyObjectWrapper<Image> image;
|
||||
|
||||
@Inject
|
||||
FXPracticePictureExactModel() {
|
||||
this.guess = new SimpleStringProperty();
|
||||
this.picture = new SimpleObjectProperty<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String guess() {
|
||||
return guess.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void guess(final String guess) {
|
||||
this.guess.set(guess);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the property for the user guess
|
||||
*
|
||||
* @return the property for the user guess
|
||||
*/
|
||||
StringProperty guessProperty() {
|
||||
return guess;
|
||||
this.picture = new ReadOnlyObjectWrapper<>();
|
||||
this.image = new ReadOnlyObjectWrapper<>();
|
||||
this.picture.bind(practiceRunProperty().map(r -> ((PicturePracticeQuestion) r.currentQuestion()).picture()));
|
||||
this.image.bind(picture.map(p -> {
|
||||
try {
|
||||
return new Image(p.path().toUri().toURL().toString(), 800, 600, true, true);
|
||||
} catch (final MalformedURLException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -50,16 +41,15 @@ public final class FXPracticePictureExactModel implements PracticePictureExactMo
|
||||
}
|
||||
|
||||
@Override
|
||||
public void picture(final Picture picture) {
|
||||
this.picture.set(picture);
|
||||
public void setPicture(final Picture picture) {
|
||||
throw new IllegalArgumentException("Picture cannot be set");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the property for the picture
|
||||
*
|
||||
* @return the property for the picture
|
||||
*/
|
||||
ObjectProperty<Picture> pictureProperty() {
|
||||
return picture;
|
||||
ReadOnlyObjectProperty<Picture> pictureProperty() {
|
||||
return picture.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
ReadOnlyObjectProperty<Image> imageProperty() {
|
||||
return image.getReadOnlyProperty();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
package ch.gtache.fro.practice.gui.fx;
|
||||
|
||||
import ch.gtache.fro.practice.PracticeRunner;
|
||||
import ch.gtache.fro.practice.gui.PracticePictureMultichoiceController;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Singleton;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.GridPane;
|
||||
|
||||
import java.util.Objects;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
|
||||
/**
|
||||
@@ -17,15 +21,30 @@ import java.util.Objects;
|
||||
public final class FXPracticePictureMultichoiceController implements PracticePictureMultichoiceController {
|
||||
|
||||
private final FXPracticePictureMultichoiceModel model;
|
||||
private final PracticeRunner runner;
|
||||
|
||||
@FXML
|
||||
private ImageView pictureView;
|
||||
@FXML
|
||||
private GridPane grid;
|
||||
@FXML
|
||||
private Button validateButton;
|
||||
private Button confirmButton;
|
||||
@FXML
|
||||
private Label progressLabel;
|
||||
|
||||
@Inject
|
||||
FXPracticePictureMultichoiceController(final FXPracticePictureMultichoiceModel model) {
|
||||
this.model = Objects.requireNonNull(model);
|
||||
FXPracticePictureMultichoiceController(final FXPracticePictureMultichoiceModel model, final PracticeRunner runner) {
|
||||
this.model = requireNonNull(model);
|
||||
this.runner = requireNonNull(runner);
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void initialize() {
|
||||
confirmButton.disableProperty().bind(model.selectedProperty().isNull());
|
||||
progressLabel.textProperty().bind(model.progressProperty());
|
||||
pictureView.imageProperty().bind(model.imageProperty());
|
||||
pictureView.fitHeightProperty().bind(model.imageProperty().map(Image::getHeight));
|
||||
pictureView.fitWidthProperty().bind(model.imageProperty().map(Image::getWidth));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -35,11 +54,18 @@ public final class FXPracticePictureMultichoiceController implements PracticePic
|
||||
|
||||
@Override
|
||||
public void confirm() {
|
||||
final var newRun = runner.step(model.practiceRunProperty().get(), model.selected());
|
||||
model.setSelected(null);
|
||||
model.practiceRunProperty().set(newRun);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void next() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void validatePressed() {
|
||||
private void confirmPressed() {
|
||||
confirm();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,55 +1,38 @@
|
||||
package ch.gtache.fro.practice.gui.fx;
|
||||
|
||||
import ch.gtache.fro.Bird;
|
||||
import ch.gtache.fro.Picture;
|
||||
import ch.gtache.fro.practice.PicturePracticeQuestion;
|
||||
import ch.gtache.fro.practice.gui.PracticePictureMultichoiceModel;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Singleton;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||
import javafx.beans.property.ReadOnlyObjectWrapper;
|
||||
import javafx.scene.image.Image;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
|
||||
|
||||
/**
|
||||
* FX implementation of {@link PracticePictureMultichoiceModel}
|
||||
*/
|
||||
@Singleton
|
||||
public final class FXPracticePictureMultichoiceModel implements PracticePictureMultichoiceModel {
|
||||
public final class FXPracticePictureMultichoiceModel extends AbstractFXPraticeQuestionMultichoiceModel implements PracticePictureMultichoiceModel {
|
||||
|
||||
private final ObjectProperty<Picture> picture;
|
||||
private final ObservableList<Bird> suggestions;
|
||||
private final ObjectProperty<Bird> selected;
|
||||
private final ReadOnlyObjectWrapper<Picture> picture;
|
||||
private final ReadOnlyObjectWrapper<Image> image;
|
||||
|
||||
@Inject
|
||||
FXPracticePictureMultichoiceModel() {
|
||||
this.picture = new SimpleObjectProperty<>();
|
||||
this.suggestions = FXCollections.observableArrayList();
|
||||
this.selected = new SimpleObjectProperty<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObservableList<Bird> suggestions() {
|
||||
return suggestions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bird selected() {
|
||||
return selected.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void selected(final Bird selected) {
|
||||
this.selected.set(selected);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the property for the selected bird
|
||||
*
|
||||
* @return the property for the selected bird
|
||||
*/
|
||||
ObjectProperty<Bird> selectedProperty() {
|
||||
return selected;
|
||||
this.picture = new ReadOnlyObjectWrapper<>();
|
||||
this.image = new ReadOnlyObjectWrapper<>();
|
||||
this.picture.bind(practiceRunProperty().map(r -> ((PicturePracticeQuestion) r.currentQuestion()).picture()));
|
||||
this.image.bind(picture.map(p -> {
|
||||
try {
|
||||
return new Image(p.path().toUri().toURL().toString(), 800, 600, true, true);
|
||||
} catch (final MalformedURLException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -58,16 +41,15 @@ public final class FXPracticePictureMultichoiceModel implements PracticePictureM
|
||||
}
|
||||
|
||||
@Override
|
||||
public void picture(final Picture picture) {
|
||||
this.picture.set(picture);
|
||||
public void setPicture(final Picture picture) {
|
||||
throw new IllegalArgumentException("Picture cannot be set");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the property for the picture
|
||||
*
|
||||
* @return the property for the picture
|
||||
*/
|
||||
ObjectProperty<Picture> pictureProperty() {
|
||||
return picture;
|
||||
ReadOnlyObjectProperty<Picture> pictureProperty() {
|
||||
return picture.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
ReadOnlyObjectProperty<Image> imageProperty() {
|
||||
return image.getReadOnlyProperty();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import ch.gtache.fro.practice.gui.PracticeResultController;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Singleton;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
|
||||
import java.util.Objects;
|
||||
@@ -23,12 +24,32 @@ public final class FXPracticeResultController implements PracticeResultControlle
|
||||
private Label successListLabel;
|
||||
@FXML
|
||||
private Label failureListLabel;
|
||||
@FXML
|
||||
private Button confirmButton;
|
||||
|
||||
@Inject
|
||||
FXPracticeResultController(final FXPracticeResultModel model) {
|
||||
this.model = Objects.requireNonNull(model);
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void initialize() {
|
||||
successNumberLabel.textProperty().bind(model.successLabelProperty());
|
||||
failureNumberLabel.textProperty().bind(model.failureLabelProperty());
|
||||
successListLabel.textProperty().bind(model.successListLabelProperty());
|
||||
failureListLabel.textProperty().bind(model.failureListLabelProperty());
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void confirmPressed() {
|
||||
confirm();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void confirm() {
|
||||
model.setResult(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FXPracticeResultModel model() {
|
||||
return model;
|
||||
|
||||
@@ -1,8 +1,16 @@
|
||||
package ch.gtache.fro.practice.gui.fx;
|
||||
|
||||
import ch.gtache.fro.BirdTranslator;
|
||||
import ch.gtache.fro.practice.PracticeResult;
|
||||
import ch.gtache.fro.practice.gui.PracticeResultModel;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Singleton;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.ReadOnlyStringProperty;
|
||||
import javafx.beans.property.ReadOnlyStringWrapper;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* FX implementation of {@link PracticeResultModel}
|
||||
@@ -10,9 +18,72 @@ import jakarta.inject.Singleton;
|
||||
@Singleton
|
||||
public final class FXPracticeResultModel implements PracticeResultModel {
|
||||
|
||||
@Inject
|
||||
FXPracticeResultModel() {
|
||||
private final ObjectProperty<PracticeResult> practiceResult;
|
||||
private final ReadOnlyStringWrapper successLabel;
|
||||
private final ReadOnlyStringWrapper failureLabel;
|
||||
private final ReadOnlyStringWrapper successListLabel;
|
||||
private final ReadOnlyStringWrapper failureListLabel;
|
||||
|
||||
@Inject
|
||||
FXPracticeResultModel(final BirdTranslator translator) {
|
||||
this.practiceResult = new SimpleObjectProperty<>();
|
||||
this.successLabel = new ReadOnlyStringWrapper();
|
||||
this.failureLabel = new ReadOnlyStringWrapper();
|
||||
this.successListLabel = new ReadOnlyStringWrapper();
|
||||
this.failureListLabel = new ReadOnlyStringWrapper();
|
||||
successLabel.bind(resultProperty().map(r -> r.correctQuestionsCount() + "/" + r.totalQuestions()));
|
||||
failureLabel.bind(resultProperty().map(r -> r.failedQuestionsCount() + "/" + r.totalQuestions()));
|
||||
successListLabel.bind(resultProperty().map(r -> r.correctQuestions().stream().map(pq -> translator.translateAsync(pq.bird()).join()).collect(Collectors.joining(", "))));
|
||||
failureListLabel.bind(resultProperty().map(r -> r.failedQuestions().stream().map(pq -> translator.translateAsync(pq.bird()).join()).collect(Collectors.joining(", "))));
|
||||
}
|
||||
|
||||
@Override
|
||||
public PracticeResult result() {
|
||||
return practiceResult.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setResult(final PracticeResult result) {
|
||||
this.practiceResult.set(result);
|
||||
}
|
||||
|
||||
ObjectProperty<PracticeResult> resultProperty() {
|
||||
return practiceResult;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String successLabel() {
|
||||
return successLabel.get();
|
||||
}
|
||||
|
||||
ReadOnlyStringProperty successLabelProperty() {
|
||||
return successLabel.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String failureLabel() {
|
||||
return failureLabel.get();
|
||||
}
|
||||
|
||||
ReadOnlyStringProperty failureLabelProperty() {
|
||||
return failureLabel.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String successListLabel() {
|
||||
return successListLabel.get();
|
||||
}
|
||||
|
||||
ReadOnlyStringProperty successListLabelProperty() {
|
||||
return successListLabel.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String failureListLabel() {
|
||||
return failureListLabel.get();
|
||||
}
|
||||
|
||||
ReadOnlyStringProperty failureListLabelProperty() {
|
||||
return failureListLabel.getReadOnlyProperty();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
package ch.gtache.fro.practice.gui.fx;
|
||||
|
||||
import ch.gtache.fro.practice.PracticeType;
|
||||
import ch.gtache.fro.practice.PracticeRunner;
|
||||
import ch.gtache.fro.practice.QuestionType;
|
||||
import ch.gtache.fro.practice.gui.PracticeSettingsController;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Singleton;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.Spinner;
|
||||
@@ -19,34 +21,53 @@ import static java.util.Objects.requireNonNull;
|
||||
public final class FXPracticeSettingsController implements PracticeSettingsController {
|
||||
|
||||
private final FXPracticeSettingsModel model;
|
||||
private final PracticeTypeConverter practiceTypeConverter;
|
||||
private final QuestionTypeConverter questionTypeConverter;
|
||||
private final PracticeRunner runner;
|
||||
@FXML
|
||||
private PrefixSelectionComboBox<PracticeType> practiceTypeCombobox;
|
||||
private PrefixSelectionComboBox<QuestionType> questionTypeCombobox;
|
||||
@FXML
|
||||
private Spinner<Integer> questionsNumberSpinner;
|
||||
@FXML
|
||||
private Label suggestionsNumberLabel;
|
||||
@FXML
|
||||
private Spinner<Integer> suggestionsNumberSpinner;
|
||||
@FXML
|
||||
private Spinner<Integer> guessesNumberSpinner;
|
||||
|
||||
private final ObjectProperty<Integer> guessesNumberProperty;
|
||||
private final ObjectProperty<Integer> questionsNumberProperty;
|
||||
private final ObjectProperty<Integer> suggestionsNumberProperty;
|
||||
|
||||
@Inject
|
||||
FXPracticeSettingsController(final FXPracticeSettingsModel model, final PracticeTypeConverter practiceTypeConverter) {
|
||||
FXPracticeSettingsController(final FXPracticeSettingsModel model, final QuestionTypeConverter questionTypeConverter,
|
||||
final PracticeRunner runner) {
|
||||
this.model = requireNonNull(model);
|
||||
this.practiceTypeConverter = requireNonNull(practiceTypeConverter);
|
||||
this.questionTypeConverter = requireNonNull(questionTypeConverter);
|
||||
this.runner = requireNonNull(runner);
|
||||
this.guessesNumberProperty = model.guessesNumberProperty().asObject();
|
||||
this.questionsNumberProperty = model.questionsNumberProperty().asObject();
|
||||
this.suggestionsNumberProperty = model.suggestionsNumberProperty().asObject();
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void initialize() {
|
||||
practiceTypeCombobox.setItems(model.practiceTypes());
|
||||
practiceTypeCombobox.valueProperty().bindBidirectional(model.practiceTypeProperty());
|
||||
practiceTypeCombobox.setConverter(practiceTypeConverter);
|
||||
questionTypeCombobox.setItems(model.questionTypes());
|
||||
questionTypeCombobox.valueProperty().bindBidirectional(model.questionTypeProperty());
|
||||
questionTypeCombobox.setConverter(questionTypeConverter);
|
||||
guessesNumberSpinner.setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(1, Integer.MAX_VALUE, model.guessesNumber()));
|
||||
guessesNumberSpinner.getValueFactory().valueProperty().bindBidirectional(guessesNumberProperty);
|
||||
questionsNumberSpinner.setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(1, Integer.MAX_VALUE, model.suggestionsNumber()));
|
||||
questionsNumberSpinner.getValueFactory().valueProperty().bindBidirectional(questionsNumberProperty);
|
||||
suggestionsNumberLabel.visibleProperty().bind(model.hasSuggestionsProperty());
|
||||
suggestionsNumberSpinner.visibleProperty().bind(model.hasSuggestionsProperty());
|
||||
suggestionsNumberSpinner.setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(1, 8, model.suggestionsNumber()));
|
||||
suggestionsNumberSpinner.getValueFactory().valueProperty().bindBidirectional(model.suggestionsNumberProperty().asObject());
|
||||
suggestionsNumberSpinner.getValueFactory().valueProperty().bindBidirectional(suggestionsNumberProperty);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startPractice() {
|
||||
throw new UnsupportedOperationException();
|
||||
final var run = runner.start(model.practiceParameters());
|
||||
model.practiceRunProperty().set(run);
|
||||
}
|
||||
|
||||
@FXML
|
||||
|
||||
@@ -1,53 +1,88 @@
|
||||
package ch.gtache.fro.practice.gui.fx;
|
||||
|
||||
import ch.gtache.fro.practice.PracticeType;
|
||||
import ch.gtache.fro.practice.PracticeConfiguration;
|
||||
import ch.gtache.fro.practice.PracticeParameters;
|
||||
import ch.gtache.fro.practice.PracticeRun;
|
||||
import ch.gtache.fro.practice.QuestionType;
|
||||
import ch.gtache.fro.practice.gui.PracticeSettingsModel;
|
||||
import ch.gtache.fro.practice.impl.PracticeParametersImpl;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Singleton;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.IntegerProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.ReadOnlyBooleanProperty;
|
||||
import javafx.beans.property.ReadOnlyBooleanWrapper;
|
||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||
import javafx.beans.property.ReadOnlyObjectWrapper;
|
||||
import javafx.beans.property.SimpleIntegerProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* FX implementation of {@link PracticeSettingsModel}
|
||||
*/
|
||||
@Singleton
|
||||
public final class FXPracticeSettingsModel implements PracticeSettingsModel {
|
||||
|
||||
private static final int DEFAULT_QUESTIONS_NUMBER = 20;
|
||||
private static final int DEFAULT_SUGGESTIONS_NUMBER = 4;
|
||||
|
||||
private final ObservableList<PracticeType> practiceTypes;
|
||||
private final ObjectProperty<PracticeType> practiceType;
|
||||
private final ObservableList<QuestionType> questionTypes;
|
||||
private final ObjectProperty<QuestionType> questionType;
|
||||
private final ReadOnlyBooleanWrapper hasSuggestions;
|
||||
private final IntegerProperty guessesNumber;
|
||||
private final IntegerProperty questionsNumber;
|
||||
private final IntegerProperty suggestionsNumber;
|
||||
private final ReadOnlyObjectWrapper<PracticeParameters> practiceParameters;
|
||||
private final ObjectProperty<PracticeRun> practiceRun;
|
||||
|
||||
@Inject
|
||||
FXPracticeSettingsModel() {
|
||||
this.practiceTypes = FXCollections.observableArrayList(PracticeType.values());
|
||||
this.practiceType = new SimpleObjectProperty<>(PracticeType.PICTURE_EXACT);
|
||||
FXPracticeSettingsModel(final PracticeConfiguration configuration) {
|
||||
this.questionTypes = FXCollections.observableArrayList(QuestionType.values());
|
||||
this.questionType = new SimpleObjectProperty<>(QuestionType.PICTURE_EXACT);
|
||||
this.hasSuggestions = new ReadOnlyBooleanWrapper(true);
|
||||
hasSuggestions.bind(practiceType.isEqualTo(PracticeType.PICTURE_EXACT).or(practiceType.isEqualTo(PracticeType.SOUND_EXACT)));
|
||||
this.guessesNumber = new SimpleIntegerProperty(configuration.guessesNumber());
|
||||
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));
|
||||
hasSuggestions.bind(questionType.isNotEqualTo(QuestionType.PICTURE_EXACT).and(questionType.isNotEqualTo(QuestionType.SOUND_EXACT)));
|
||||
guessesNumber.addListener((_, _, newValue) -> configuration.setGuessesNumber(newValue.intValue()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObservableList<PracticeType> practiceTypes() {
|
||||
return practiceTypes;
|
||||
public ObservableList<QuestionType> questionTypes() {
|
||||
return questionTypes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PracticeType practiceType() {
|
||||
return practiceType.get();
|
||||
public QuestionType questionType() {
|
||||
return questionType.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void practiceType(final PracticeType practiceType) {
|
||||
this.practiceType.set(practiceType);
|
||||
public void setQuestionType(final QuestionType questionType) {
|
||||
this.questionType.set(questionType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int guessesNumber() {
|
||||
return guessesNumber.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setGuessesNumber(final int guessesNumber) {
|
||||
this.guessesNumber.set(guessesNumber);
|
||||
}
|
||||
|
||||
IntegerProperty guessesNumberProperty() {
|
||||
return guessesNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -55,8 +90,8 @@ public final class FXPracticeSettingsModel implements PracticeSettingsModel {
|
||||
*
|
||||
* @return The practice type property
|
||||
*/
|
||||
ObjectProperty<PracticeType> practiceTypeProperty() {
|
||||
return practiceType;
|
||||
ObjectProperty<QuestionType> questionTypeProperty() {
|
||||
return questionType;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -73,16 +108,39 @@ public final class FXPracticeSettingsModel implements PracticeSettingsModel {
|
||||
return hasSuggestions.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int questionsNumber() {
|
||||
return questionsNumber.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setQuestionsNumber(final int questionsNumber) {
|
||||
this.questionsNumber.set(questionsNumber);
|
||||
}
|
||||
|
||||
IntegerProperty questionsNumberProperty() {
|
||||
return questionsNumber;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int suggestionsNumber() {
|
||||
return suggestionsNumber.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void suggestionsNumber(final int suggestionsNumber) {
|
||||
public void setSuggestionsNumber(final int suggestionsNumber) {
|
||||
this.suggestionsNumber.set(suggestionsNumber);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PracticeParameters practiceParameters() {
|
||||
return practiceParameters.get();
|
||||
}
|
||||
|
||||
ReadOnlyObjectProperty<PracticeParameters> practiceParametersProperty() {
|
||||
return practiceParameters.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the suggestions number property
|
||||
*
|
||||
@@ -91,4 +149,8 @@ public final class FXPracticeSettingsModel implements PracticeSettingsModel {
|
||||
IntegerProperty suggestionsNumberProperty() {
|
||||
return suggestionsNumber;
|
||||
}
|
||||
|
||||
ObjectProperty<PracticeRun> practiceRunProperty() {
|
||||
return practiceRun;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
package ch.gtache.fro.practice.gui.fx;
|
||||
|
||||
import ch.gtache.fro.SoundType;
|
||||
import ch.gtache.fro.practice.PracticeRunner;
|
||||
import ch.gtache.fro.practice.gui.PracticeSoundExactController;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Singleton;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.media.MediaView;
|
||||
import org.controlsfx.control.PrefixSelectionComboBox;
|
||||
|
||||
import java.util.Objects;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
|
||||
/**
|
||||
@@ -20,19 +23,35 @@ import java.util.Objects;
|
||||
public final class FXPracticeSoundExactController implements PracticeSoundExactController {
|
||||
|
||||
private final FXPracticeSoundExactModel model;
|
||||
private final UserInputNormalizer normalizer;
|
||||
private final PracticeRunner runner;
|
||||
|
||||
@FXML
|
||||
private Button validateButton;
|
||||
private Button confirmButton;
|
||||
@FXML
|
||||
private TextField inputField;
|
||||
@FXML
|
||||
private MediaView mediaView;
|
||||
@FXML
|
||||
private PrefixSelectionComboBox<SoundType> typeCombobox;
|
||||
@FXML
|
||||
private Label progressLabel;
|
||||
@FXML
|
||||
private VBox guessesBox;
|
||||
|
||||
@Inject
|
||||
FXPracticeSoundExactController(final FXPracticeSoundExactModel model) {
|
||||
this.model = Objects.requireNonNull(model);
|
||||
FXPracticeSoundExactController(final FXPracticeSoundExactModel model, final PracticeRunner runner,
|
||||
final UserInputNormalizer normalizer) {
|
||||
this.model = requireNonNull(model);
|
||||
this.normalizer = requireNonNull(normalizer);
|
||||
this.runner = requireNonNull(runner);
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void initialize() {
|
||||
inputField.textProperty().bindBidirectional(model.guessProperty());
|
||||
confirmButton.disableProperty().bind(model.guessProperty().isEmpty());
|
||||
progressLabel.textProperty().bind(model.progressProperty());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -42,11 +61,19 @@ public final class FXPracticeSoundExactController implements PracticeSoundExactC
|
||||
|
||||
@Override
|
||||
public void confirm() {
|
||||
final var bird = normalizer.getBird(model.guess());
|
||||
model.setGuess("");
|
||||
final var newRun = runner.step(model.practiceRunProperty().get(), bird);
|
||||
model.practiceRunProperty().set(newRun);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void next() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void validatePressed() {
|
||||
private void confirmButton() {
|
||||
confirm();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,47 +1,26 @@
|
||||
package ch.gtache.fro.practice.gui.fx;
|
||||
|
||||
import ch.gtache.fro.Sound;
|
||||
import ch.gtache.fro.practice.SoundPracticeQuestion;
|
||||
import ch.gtache.fro.practice.gui.PracticeSoundExactModel;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Singleton;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||
import javafx.beans.property.ReadOnlyObjectWrapper;
|
||||
|
||||
|
||||
/**
|
||||
* FX implementation of {@link PracticeSoundExactModel}
|
||||
*/
|
||||
@Singleton
|
||||
public final class FXPracticeSoundExactModel implements PracticeSoundExactModel {
|
||||
public final class FXPracticeSoundExactModel extends AbstractFXPraticeQuestionExactModel implements PracticeSoundExactModel {
|
||||
|
||||
private final StringProperty guess;
|
||||
private final ObjectProperty<Sound> sound;
|
||||
private final ReadOnlyObjectWrapper<Sound> sound;
|
||||
|
||||
@Inject
|
||||
FXPracticeSoundExactModel() {
|
||||
this.guess = new SimpleStringProperty();
|
||||
this.sound = new SimpleObjectProperty<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String guess() {
|
||||
return guess.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void guess(final String guess) {
|
||||
this.guess.set(guess);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the property for the user guess
|
||||
*
|
||||
* @return the property for the user guess
|
||||
*/
|
||||
StringProperty guessProperty() {
|
||||
return guess;
|
||||
this.sound = new ReadOnlyObjectWrapper<>();
|
||||
this.sound.bind(practiceRunProperty().map(r -> ((SoundPracticeQuestion) r.currentQuestion()).sound()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -50,16 +29,11 @@ public final class FXPracticeSoundExactModel implements PracticeSoundExactModel
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sound(final Sound sound) {
|
||||
this.sound.set(sound);
|
||||
public void setSound(final Sound sound) {
|
||||
throw new IllegalArgumentException("Sound cannot be set");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the property for the sound
|
||||
*
|
||||
* @return the property for the sound
|
||||
*/
|
||||
ObjectProperty<Sound> soundProperty() {
|
||||
return sound;
|
||||
ReadOnlyObjectProperty<Sound> soundProperty() {
|
||||
return sound.getReadOnlyProperty();
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user