262 lines
8.7 KiB
Java
262 lines
8.7 KiB
Java
package ctbrec.ui.settings.api;
|
|
|
|
import static java.util.Optional.*;
|
|
|
|
import java.io.IOException;
|
|
import java.util.Objects;
|
|
import java.util.Optional;
|
|
import java.util.function.Consumer;
|
|
|
|
import org.slf4j.Logger;
|
|
import org.slf4j.LoggerFactory;
|
|
|
|
import ctbrec.ui.controls.SearchBox;
|
|
import javafx.beans.value.ObservableValue;
|
|
import javafx.geometry.Insets;
|
|
import javafx.geometry.VPos;
|
|
import javafx.scene.Node;
|
|
import javafx.scene.control.Label;
|
|
import javafx.scene.control.Tooltip;
|
|
import javafx.scene.control.TreeItem;
|
|
import javafx.scene.control.TreeView;
|
|
import javafx.scene.layout.BorderPane;
|
|
import javafx.scene.layout.GridPane;
|
|
import javafx.scene.layout.Priority;
|
|
import javafx.scene.layout.Region;
|
|
import javafx.scene.layout.VBox;
|
|
|
|
public class Preferences {
|
|
|
|
private static final Logger LOG = LoggerFactory.getLogger(Preferences.class);
|
|
|
|
private Category[] categories;
|
|
|
|
private TreeView<Category> categoryTree;
|
|
|
|
private PreferencesStorage preferencesStorage;
|
|
|
|
private Runnable restartRequiredCallback = () -> {};
|
|
|
|
private Preferences(PreferencesStorage preferencesStorage, Category...categories) {
|
|
this.preferencesStorage = preferencesStorage;
|
|
this.categories = categories;
|
|
for (Category category : categories) {
|
|
assignPreferencesStorage(category, preferencesStorage);
|
|
}
|
|
}
|
|
|
|
private void assignPreferencesStorage(Category cat, PreferencesStorage preferencesStorage) {
|
|
if(cat.hasSubCategories()) {
|
|
for (Category sub : cat.getSubCategories()) {
|
|
assignPreferencesStorage(sub, preferencesStorage);
|
|
}
|
|
} else {
|
|
for (Group group : cat.getGroups()) {
|
|
for (Setting setting : group.getSettings()) {
|
|
setting.setPreferencesStorage(preferencesStorage);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public static Preferences of(PreferencesStorage preferencesStorage, Category... categories) {
|
|
return new Preferences(preferencesStorage, categories);
|
|
}
|
|
|
|
public void save() throws IOException {
|
|
preferencesStorage.save(this);
|
|
}
|
|
|
|
Category[] getCategories() {
|
|
return categories;
|
|
}
|
|
|
|
public Region getView(boolean withNavigation) {
|
|
var search = new SearchBox(true);
|
|
search.textProperty().addListener(this::filterTree);
|
|
TreeItem<Category> categoryTreeItems = createCategoryTree(categories, new TreeItem<>(), null);
|
|
categoryTree = new TreeView<>(categoryTreeItems);
|
|
categoryTree.showRootProperty().set(false);
|
|
var leftSide = new VBox(search, categoryTree);
|
|
VBox.setVgrow(categoryTree, Priority.ALWAYS);
|
|
VBox.setMargin(search, new Insets(2));
|
|
VBox.setMargin(categoryTree, new Insets(2));
|
|
|
|
var main = new BorderPane();
|
|
if (withNavigation) {
|
|
main.setLeft(leftSide);
|
|
}
|
|
main.setCenter(new Label("Center"));
|
|
BorderPane.setMargin(leftSide, new Insets(2));
|
|
|
|
categoryTree.getSelectionModel().selectedItemProperty().addListener((obs, oldV, newV) -> {
|
|
if (newV != null) {
|
|
Category cat = newV.getValue();
|
|
Node gui = cat.getGuiOrElse(() -> createGui(cat));
|
|
BorderPane.setMargin(gui, new Insets(10));
|
|
main.setCenter(gui);
|
|
}
|
|
});
|
|
categoryTree.getSelectionModel().select(0);
|
|
return main;
|
|
}
|
|
|
|
public Region getView() {
|
|
return getView(true);
|
|
}
|
|
|
|
private void filterTree(ObservableValue<? extends String> obs, String oldV, String newV) {
|
|
String q = ofNullable(newV).orElse("").toLowerCase().trim();
|
|
TreeItem<Category> filteredCategoryTree = createCategoryTree(categories, new TreeItem<>(), q);
|
|
categoryTree.setRoot(filteredCategoryTree);
|
|
expandAll(categoryTree.getRoot());
|
|
|
|
TreeItem<Category> parent = categoryTree.getRoot();
|
|
while (!parent.getChildren().isEmpty()) {
|
|
parent = parent.getChildren().get(0);
|
|
categoryTree.getSelectionModel().select(parent);
|
|
}
|
|
|
|
for (Category category : categories) {
|
|
if (q.length() > 2) {
|
|
HighlightingSupport.highlightMatches(category, q);
|
|
} else {
|
|
HighlightingSupport.removeHighlights(category);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void expandAll(TreeItem<Category> treeItem) {
|
|
treeItem.setExpanded(true);
|
|
for (TreeItem<Category> child : treeItem.getChildren()) {
|
|
expandAll(child);
|
|
}
|
|
}
|
|
|
|
private Node createGui(Category cat) {
|
|
try {
|
|
if (cat.hasSubCategories()) {
|
|
return new Label(cat.getName());
|
|
} else if(cat.hasGroups()) {
|
|
return createPaneWithGroups(cat);
|
|
} else {
|
|
return createGrid(cat.getGroups()[0].getSettings());
|
|
}
|
|
} catch(Exception e) {
|
|
LOG.error("Error creating the GUI", e);
|
|
return new Label(e.getLocalizedMessage());
|
|
}
|
|
}
|
|
|
|
private Node createPaneWithGroups(Category cat) throws Exception {
|
|
var pane = new VBox();
|
|
for (Group grp : cat.getGroups()) {
|
|
var groupLabel = new Label(grp.getName());
|
|
groupLabel.getStyleClass().add("settings-group-label");
|
|
VBox.setMargin(groupLabel, new Insets(20, 0, 10, 20));
|
|
pane.getChildren().add(groupLabel);
|
|
Node parameterGrid = createGrid(grp.getSettings());
|
|
pane.getChildren().add(parameterGrid);
|
|
VBox.setMargin(parameterGrid, new Insets(0, 0, 0, 40));
|
|
}
|
|
return pane;
|
|
}
|
|
|
|
private Node createGrid(Setting[] settings) throws Exception {
|
|
var pane = new GridPane();
|
|
pane.setHgap(2);
|
|
pane.vgapProperty().bind(pane.hgapProperty());
|
|
var row = 0;
|
|
for (Setting setting : settings) {
|
|
var node = setting.getGui();
|
|
var label = new Label(setting.getName());
|
|
label.setMinHeight(34);
|
|
label.labelForProperty().set(node);
|
|
label.setTooltip(new Tooltip(setting.getName()));
|
|
pane.addRow(row++, label, node);
|
|
GridPane.setVgrow(label, Priority.ALWAYS);
|
|
GridPane.setValignment(label, VPos.CENTER);
|
|
GridPane.setMargin(node, new Insets(5, 0, 5, 10));
|
|
GridPane.setValignment(node, VPos.CENTER);
|
|
GridPane.setHgrow(node, Priority.ALWAYS);
|
|
}
|
|
return pane;
|
|
}
|
|
|
|
/**
|
|
* Creates a tree of the given categories. Filters out categories, which don't match the filter
|
|
* @param filter may be null
|
|
*/
|
|
private TreeItem<Category> createCategoryTree(Category[] categories, TreeItem<Category> parent, String filter) {
|
|
for (Category category : categories) {
|
|
TreeItem<Category> child = new TreeItem<>(category);
|
|
if (category.hasSubCategories()) {
|
|
createCategoryTree(category.getSubCategories(), child, filter);
|
|
if (!child.getChildren().isEmpty()) {
|
|
parent.getChildren().add(child);
|
|
}
|
|
} else if(category.contains(filter)) {
|
|
parent.getChildren().add(child);
|
|
}
|
|
}
|
|
return parent;
|
|
}
|
|
|
|
public void expandTree() {
|
|
expandAll(categoryTree.getRoot());
|
|
}
|
|
|
|
public void traverse(Consumer<Setting> visitor) {
|
|
for (Category category : categories) {
|
|
visit(category, visitor);
|
|
}
|
|
}
|
|
|
|
private void visit(Category cat, Consumer<Setting> visitor) {
|
|
for (Group group : cat.getGroups()) {
|
|
for (Setting setting : group.getSettings()) {
|
|
visitor.accept(setting);
|
|
}
|
|
}
|
|
if (cat.hasSubCategories()) {
|
|
for (Category subcat : cat.getSubCategories()) {
|
|
visit(subcat, visitor);
|
|
}
|
|
}
|
|
}
|
|
|
|
public Optional<Setting> getSetting(String key) {
|
|
var search = new SettingSearchVisitor(key);
|
|
traverse(search);
|
|
return search.getResult();
|
|
}
|
|
|
|
private class SettingSearchVisitor implements Consumer<Setting> {
|
|
Optional<Setting> result = Optional.empty();
|
|
private String key;
|
|
|
|
public SettingSearchVisitor(String key) {
|
|
this.key = key;
|
|
}
|
|
|
|
@Override
|
|
public void accept(Setting s) {
|
|
if (Objects.equals(key, ofNullable(s.getKey()).orElse(""))) {
|
|
result = Optional.of(s);
|
|
}
|
|
}
|
|
|
|
public Optional<Setting> getResult() {
|
|
return result;
|
|
}
|
|
}
|
|
|
|
public void onRestartRequired(Runnable callback) {
|
|
this.restartRequiredCallback = callback;
|
|
}
|
|
|
|
public Runnable getRestartRequiredCallback() {
|
|
return restartRequiredCallback;
|
|
}
|
|
}
|