ctbrec-5.3.2-experimental/client/src/main/java/ctbrec/ui/settings/CtbrecPreferencesStorage.java

286 lines
12 KiB
Java

package ctbrec.ui.settings;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.List;
import java.util.Objects;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ctbrec.Config;
import ctbrec.Settings;
import ctbrec.StringUtil;
import ctbrec.ui.controls.DirectorySelectionBox;
import ctbrec.ui.controls.ProgramSelectionBox;
import ctbrec.ui.controls.range.DiscreteRange;
import ctbrec.ui.controls.range.RangeSlider;
import ctbrec.ui.settings.api.ExclusiveSelectionProperty;
import ctbrec.ui.settings.api.Preferences;
import ctbrec.ui.settings.api.PreferencesStorage;
import ctbrec.ui.settings.api.Setting;
import ctbrec.ui.settings.api.SimpleDirectoryProperty;
import ctbrec.ui.settings.api.SimpleFileProperty;
import ctbrec.ui.settings.api.SimpleRangeProperty;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ListProperty;
import javafx.beans.property.LongProperty;
import javafx.beans.property.Property;
import javafx.beans.property.StringProperty;
import javafx.geometry.Insets;
import javafx.scene.Node;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.control.RadioButton;
import javafx.scene.control.TextField;
import javafx.scene.control.ToggleGroup;
import javafx.scene.layout.HBox;
import javafx.util.converter.NumberStringConverter;
public class CtbrecPreferencesStorage implements PreferencesStorage {
private static final Logger LOG = LoggerFactory.getLogger(CtbrecPreferencesStorage.class);
public static final String PATTERN_NOT_A_DIGIT = "[^\\d]";
public static final String COULDNT_SAVE_MSG = "Couldn't save config setting";
private Config config;
private Settings settings;
public CtbrecPreferencesStorage(Config config) {
this.config = config;
this.settings = config.getSettings();
}
@Override
public void save(Preferences preferences) throws IOException {
throw new RuntimeException("not implemented");
}
@Override
public void load(Preferences preferences) {
throw new RuntimeException("not implemented");
}
@Override
public Node createGui(Setting setting) throws Exception {
Property<?> prop = setting.getProperty();
if (prop instanceof ExclusiveSelectionProperty) {
return createRadioGroup(setting);
} else if (prop instanceof SimpleRangeProperty) {
return createRangeSlider(setting);
} else if (prop instanceof SimpleDirectoryProperty) {
return createDirectorySelector(setting);
} else if (prop instanceof SimpleFileProperty) {
return createFileSelector(setting);
} else if (prop instanceof IntegerProperty) {
return createIntegerProperty(setting);
} else if (prop instanceof LongProperty) {
return createLongProperty(setting);
} else if (prop instanceof BooleanProperty) {
return createBooleanProperty(setting);
} else if (prop instanceof ListProperty) {
return createComboBox(setting);
} else if (prop instanceof StringProperty) {
return createStringProperty(setting);
} else {
return new Label("Unsupported Type for key " + setting.getKey() + ": " + setting.getProperty());
}
}
private Node createRadioGroup(Setting setting) {
ExclusiveSelectionProperty prop = (ExclusiveSelectionProperty) setting.getProperty();
ToggleGroup toggleGroup = new ToggleGroup();
RadioButton optionA = new RadioButton(prop.getOptionA());
optionA.setSelected(prop.getValue());
optionA.setToggleGroup(toggleGroup);
RadioButton optionB = new RadioButton(prop.getOptionB());
optionB.setSelected(!optionA.isSelected());
optionB.setToggleGroup(toggleGroup);
optionA.selectedProperty().bindBidirectional(prop);
prop.addListener((obs, oldV, newV) -> saveValue(() -> {
Field field = Settings.class.getField(setting.getKey());
field.set(settings, newV);
config.save();
}));
HBox row = new HBox();
row.getChildren().addAll(optionA, optionB);
HBox.setMargin(optionA, new Insets(5));
HBox.setMargin(optionB, new Insets(5));
return row;
}
@SuppressWarnings("unchecked")
private Node createRangeSlider(Setting setting) {
SimpleRangeProperty<Integer> rangeProperty = (SimpleRangeProperty<Integer>) setting.getProperty();
DiscreteRange<Integer> range = (DiscreteRange<Integer>) rangeProperty.getRange();
List<Integer> labels = (List<Integer>) range.getLabels();
List<Integer> values = range.getTicks();
RangeSlider<Integer> resolutionRange = new RangeSlider<>(rangeProperty.getRange());
resolutionRange.setShowTickMarks(true);
resolutionRange.setShowTickLabels(true);
int lowValue = getRangeSliderValue(values, labels, Config.getInstance().getSettings().minimumResolution);
resolutionRange.setLow(lowValue >= 0 ? lowValue : values.get(0));
int highValue = getRangeSliderValue(values, labels, Config.getInstance().getSettings().maximumResolution);
resolutionRange.setHigh(highValue >= 0 ? highValue : values.get(values.size() - 1));
resolutionRange.getLow().addListener((obs, o, n) -> saveValue(() -> {
int newV = labels.get(n.intValue());
Field field = Settings.class.getField(rangeProperty.getLowKey());
field.set(settings, newV);
config.save();
}));
resolutionRange.getHigh().addListener((obs, o, n) -> saveValue(() -> {
int newV = labels.get(n.intValue());
Field field = Settings.class.getField(rangeProperty.getHighKey());
field.set(settings, newV);
config.save();
}));
return resolutionRange;
}
private int getRangeSliderValue(List<Integer> values, List<Integer> labels, int value) {
for (int i = 0; i < labels.size(); i++) {
int label = labels.get(i).intValue();
if(label == value) {
return values.get(i);
}
}
return -1;
}
private Node createFileSelector(Setting setting) {
ProgramSelectionBox programSelector = new ProgramSelectionBox("");
programSelector.fileProperty().addListener((obs, o, n) -> saveValue(() -> {
String path = n;
Field field = Settings.class.getField(setting.getKey());
String oldValue = (String) field.get(settings);
if (!Objects.equals(path, oldValue)) {
field.set(settings, path);
config.save();
}
}));
StringProperty property = (StringProperty) setting.getProperty();
programSelector.fileProperty().bindBidirectional(property);
return programSelector;
}
private Node createDirectorySelector(Setting setting) {
DirectorySelectionBox directorySelector = new DirectorySelectionBox("");
directorySelector.prefWidth(400);
directorySelector.fileProperty().addListener((obs, o, n) -> saveValue(() -> {
String path = n;
Field field = Settings.class.getField(setting.getKey());
String oldValue = (String) field.get(settings);
if (!Objects.equals(path, oldValue)) {
field.set(settings, path);
config.save();
}
}));
StringProperty property = (StringProperty) setting.getProperty();
directorySelector.fileProperty().bindBidirectional(property);
return directorySelector;
}
private Node createStringProperty(Setting setting) {
TextField ctrl = new TextField();
ctrl.textProperty().addListener((obs, oldV, newV) -> saveValue(() -> {
Field field = Settings.class.getField(setting.getKey());
field.set(settings, newV);
config.save();
}));
StringProperty prop = (StringProperty) setting.getProperty();
ctrl.textProperty().bindBidirectional(prop);
return ctrl;
}
@SuppressWarnings("unchecked")
private Node createIntegerProperty(Setting setting) {
TextField ctrl = new TextField();
ctrl.textProperty().addListener((obs, oldV, newV) -> saveValue(() -> {
if (!newV.matches("\\d*")) {
ctrl.setText(newV.replaceAll(PATTERN_NOT_A_DIGIT, ""));
}
if (!ctrl.getText().isEmpty()) {
Field field = Settings.class.getField(setting.getKey());
field.set(settings, Integer.parseInt(ctrl.getText()));
config.save();
}
}));
Property<Number> prop = setting.getProperty();
ctrl.textProperty().bindBidirectional(prop, new NumberStringConverter());
return ctrl;
}
@SuppressWarnings("unchecked")
private Node createLongProperty(Setting setting) {
TextField ctrl = new TextField();
ctrl.textProperty().addListener((obs, oldV, newV) -> saveValue(() -> {
if (!newV.matches("\\d*")) {
ctrl.setText(newV.replaceAll(PATTERN_NOT_A_DIGIT, ""));
}
if (!ctrl.getText().isEmpty()) {
long value = Long.parseLong(ctrl.getText());
if (setting.getConverter() != null) {
value = (long) setting.getConverter().convertFrom(value);
}
Field field = Settings.class.getField(setting.getKey());
field.set(settings, value);
config.save();
}
}));
Property<Number> prop = setting.getProperty();
ctrl.textProperty().bindBidirectional(prop, new NumberStringConverter());
return ctrl;
}
private Node createBooleanProperty(Setting setting) {
CheckBox ctrl = new CheckBox();
ctrl.selectedProperty().addListener((obs, oldV, newV) -> saveValue(() -> {
Field field = Settings.class.getField(setting.getKey());
field.set(settings, newV);
config.save();
}));
BooleanProperty prop = (BooleanProperty) setting.getProperty();
ctrl.selectedProperty().bindBidirectional(prop);
return ctrl;
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private Node createComboBox(Setting setting) throws NoSuchFieldException, IllegalAccessException {
ListProperty<?> listProp = (ListProperty<?>) setting.getProperty();
ComboBox<Object> comboBox = new ComboBox(listProp);
Field field = Settings.class.getField(setting.getKey());
Object value = field.get(settings);
if (StringUtil.isNotBlank(value.toString())) {
if (setting.getConverter() != null) {
comboBox.getSelectionModel().select(setting.getConverter().convertTo(value));
} else {
comboBox.getSelectionModel().select(value);
}
}
comboBox.valueProperty().addListener((obs, oldV, newV) -> saveValue(() -> {
if (setting.getConverter() != null) {
field.set(settings, setting.getConverter().convertFrom(newV));
} else {
field.set(settings, newV);
}
config.save();
}));
return comboBox;
}
private void saveValue(Exec exe) {
try {
exe.run();
} catch (Exception e) {
LOG.error(COULDNT_SAVE_MSG, e);
}
}
@FunctionalInterface
private interface Exec {
public void run() throws Exception;
}
}