/* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3 as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.cirqwizard.fx.settings; import javafx.beans.property.IntegerProperty; import javafx.beans.property.SimpleIntegerProperty; import javafx.collections.FXCollections; import javafx.fxml.FXML; import javafx.fxml.Initializable; import javafx.geometry.Pos; import javafx.scene.Node; import javafx.scene.control.*; import javafx.scene.layout.GridPane; import org.cirqwizard.fx.MainApplication; import org.cirqwizard.fx.ScreenController; import org.cirqwizard.fx.SettingsDependentScreenController; import org.cirqwizard.fx.controls.RealNumberTextField; import org.cirqwizard.logging.LoggerFactory; import org.cirqwizard.serial.SerialInterfaceFactory; import org.cirqwizard.settings.*; import java.beans.IntrospectionException; import java.beans.PropertyDescriptor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.ParameterizedType; import java.net.URL; import java.util.List; import java.util.ResourceBundle; public class SettingsEditor extends ScreenController implements Initializable { @FXML private ListView<SettingsGroup> groups; @FXML private GridPane settingsPane; @Override protected String getFxmlName() { return "SettingsEditor.fxml"; } @Override protected String getName() { return "Settings"; } @Override public void initialize(URL location, ResourceBundle resources) { groups.getSelectionModel().selectedItemProperty().addListener((v, oldV, newV) -> refreshSettingsPane()); } @Override public void refresh() { groups.setItems(FXCollections.observableArrayList(SettingsFactory.getAllGroups())); groups.getSelectionModel().select(0); } private void refreshSettingsPane() { renderSettings(settingsPane, groups.getSelectionModel().getSelectedItem(), getMainApplication(), null); } public static void renderSettings(GridPane rootPane, SettingsGroup group, MainApplication mainApplication, SettingsDependentScreenController listener) { try { rootPane.getChildren().clear(); if (group == null) return; String preferenceGroupName = null; GridPane container = rootPane; IntegerProperty rootRow = new SimpleIntegerProperty(0); IntegerProperty row = null; for (Field f : group.getClass().getDeclaredFields()) { if (!f.isAnnotationPresent(PersistentPreference.class)) continue; if (f.isAnnotationPresent(PreferenceGroup.class)) { String name = f.getAnnotation(PreferenceGroup.class).name(); if (!name.equals(preferenceGroupName)) { container = new GridPane(); container.setHgap(10); container.setVgap(10); rootPane.add(container, 0, rootRow.get(), 3, 1); rootRow.setValue(rootRow.get() + 1); container.getStyleClass().add("settings-group"); Label header = new Label(name); header.getStyleClass().add("settings-group-header"); container.add(header, 0, 0, 2, 1); preferenceGroupName = name; row = new SimpleIntegerProperty(1); } } else { container = rootPane; row = rootRow; } Class argumentClass = (Class) ((ParameterizedType)f.getGenericType()).getActualTypeArguments()[0]; UserPreference p = (UserPreference) new PropertyDescriptor(f.getName(), group.getClass()).getReadMethod().invoke(group); if (listener == null || p.showInPopOver()) { if (p.getType() == PreferenceType.TOOL_TABLE) { Node editor = getEditor(argumentClass, p, group, mainApplication, listener); container.add(editor, 0, row.get(), 3, 1); } else { container.add(new Label(p.getUserName()), 0, row.get()); container.add(getEditor(argumentClass, p, group, mainApplication, listener), 1, row.get()); container.add(new Label(p.getUnits()), 2, row.get()); } row.setValue(row.get() + 1); } } } catch (IllegalAccessException | IntrospectionException | InvocationTargetException e) { LoggerFactory.logException("Error accessing settings group", e); } } private static void addInvalidationListenerToTextField(TextField textField, SettingsDependentScreenController listener) { textField.setOnAction((event) -> listener.settingsInvalidated()); } private static Node getEditor(Class clazz, UserPreference p, SettingsGroup group, MainApplication mainApplication, SettingsDependentScreenController listener) { Node editor; if (p.getType() == PreferenceType.TOOL_TABLE) { editor = new SettingsToolTable().getView(); } else if (Integer.class.equals(clazz)) { if (p.getType() == PreferenceType.INTEGER || p.getType() == PreferenceType.PERCENT) { editor = new TextField(p.getValue() == null ? null : String.valueOf(p.getValue())); ((TextField)editor).textProperty().addListener((v, oldV, newV) -> { p.setValue(Integer.valueOf(newV)); group.save(); }); ((TextField)editor).setAlignment(Pos.CENTER_RIGHT); if (p.triggersInvalidation() && listener != null) addInvalidationListenerToTextField((TextField) editor, listener); } else { RealNumberTextField rnEditor = new RealNumberTextField(); editor = rnEditor; rnEditor.setIntegerValue(p.getValue() == null ? null : (Integer) p.getValue()); rnEditor.realNumberIntegerProperty().addListener((v, oldV, newV) -> { p.setValue(newV); group.save(); }); if (p.triggersInvalidation() && listener != null) addInvalidationListenerToTextField(rnEditor, listener); rnEditor.setAlignment(Pos.CENTER_RIGHT); } } else if (p.getType() == null && String.class.equals(clazz)) { editor = new TextField(p.getValue() == null ? null : (String) p.getValue()); ((TextField)editor).textProperty().addListener((v, oldV, newV) -> { p.setValue(newV); group.save(); }); if (p.triggersInvalidation() && listener != null) addInvalidationListenerToTextField((TextField) editor, listener); ((TextField)editor).setAlignment(Pos.CENTER_RIGHT); } else if (Boolean.class.equals(clazz)) { editor = new CheckBox(); ((CheckBox)editor).setSelected(p.getValue() == null ? false : (Boolean)p.getValue()); ((CheckBox)editor).selectedProperty().addListener((v, oldV, newV) -> { p.setValue(newV); group.save(); }); } else { List items = p.getItems(); if (p.getType() == PreferenceType.SERIAL_PORT) items = SerialInterfaceFactory.getSerialInterfaces(mainApplication.getSerialInterface()); editor = new ComboBox(FXCollections.observableArrayList(items)); ((ComboBox)editor).getSelectionModel().select(p.getValue()); ((ComboBox)editor).getSelectionModel().selectedItemProperty().addListener((v, oldV, newV) -> { p.setValue(newV); group.save(); }); if (p.getType() == PreferenceType.SERIAL_PORT) { ((ComboBox)editor).getSelectionModel().selectedItemProperty().addListener((v, oldV, newV) -> { mainApplication.connectSerialPort((String) newV); group.save(); }); } ((Control)editor).setPrefWidth(150); } if (editor instanceof TextField) ((Control)editor).setPrefWidth(75); return editor; } public void resetToDefaults() { Alert alert = new Alert(Alert.AlertType.CONFIRMATION, "", ButtonType.YES, ButtonType.NO); alert.setTitle("Reset confirmation"); alert.setHeaderText("Are you sure you want to reset all settings to default?"); alert.showAndWait().filter(response -> response == ButtonType.YES).ifPresent(response -> { SettingsFactory.resetAll(); refresh(); }); } }