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 rangeProperty = (SimpleRangeProperty) setting.getProperty(); DiscreteRange range = (DiscreteRange) rangeProperty.getRange(); List labels = (List) range.getLabels(); List values = range.getTicks(); RangeSlider 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 values, List 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 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 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 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; } }