From 83775c805a15d4e8f39f843d14545fb0162faf41 Mon Sep 17 00:00:00 2001 From: 0xboobface <0xboobface@gmail.com> Date: Sun, 5 Jul 2020 14:02:15 +0200 Subject: [PATCH] Add all settings to the new settings panel --- .../java/ctbrec/ui/CamrecApplication.java | 3 +- .../ui/controls/AbstractFileSelectionBox.java | 17 +- .../ctbrec/ui/controls/range/RangeSlider.java | 21 +- .../controls/range/RangeSliderBehavior.java | 4 +- .../ui/controls/range/RangeSliderSkin.java | 8 +- .../ui/settings/ActionSettingsPanel.java | 41 ++- .../ui/settings/CtbrecPreferencesStorage.java | 223 +++++++++++++--- .../java/ctbrec/ui/settings/IgnoreList.java | 162 ++++++++++++ .../ctbrec/ui/settings/IgnoreListDialog.java | 96 ------- .../java/ctbrec/ui/settings/SettingsTab.java | 41 ++- .../java/ctbrec/ui/settings/SettingsTab2.java | 243 ++++++++++++++++-- .../api/ExclusiveSelectionProperty.java | 23 ++ .../ui/settings/api/GigabytesConverter.java | 19 ++ .../ui/settings/api/HighlightingSupport.java | 6 +- .../ctbrec/ui/settings/api/Preferences.java | 2 +- .../ui/settings/api/SelectionSetting.java | 15 -- .../java/ctbrec/ui/settings/api/Setting.java | 51 ++-- .../settings/api/SimpleDirectoryProperty.java | 11 + .../ui/settings/api/SimpleFileProperty.java | 11 + .../ui/settings/api/SimpleRangeProperty.java | 42 +++ .../ui/settings/api/ValueConverter.java | 7 + 21 files changed, 806 insertions(+), 240 deletions(-) create mode 100644 client/src/main/java/ctbrec/ui/settings/IgnoreList.java delete mode 100644 client/src/main/java/ctbrec/ui/settings/IgnoreListDialog.java create mode 100644 client/src/main/java/ctbrec/ui/settings/api/ExclusiveSelectionProperty.java create mode 100644 client/src/main/java/ctbrec/ui/settings/api/GigabytesConverter.java delete mode 100644 client/src/main/java/ctbrec/ui/settings/api/SelectionSetting.java create mode 100644 client/src/main/java/ctbrec/ui/settings/api/SimpleDirectoryProperty.java create mode 100644 client/src/main/java/ctbrec/ui/settings/api/SimpleFileProperty.java create mode 100644 client/src/main/java/ctbrec/ui/settings/api/SimpleRangeProperty.java create mode 100644 client/src/main/java/ctbrec/ui/settings/api/ValueConverter.java diff --git a/client/src/main/java/ctbrec/ui/CamrecApplication.java b/client/src/main/java/ctbrec/ui/CamrecApplication.java index ae221c38..c0881658 100644 --- a/client/src/main/java/ctbrec/ui/CamrecApplication.java +++ b/client/src/main/java/ctbrec/ui/CamrecApplication.java @@ -54,6 +54,7 @@ import ctbrec.sites.stripchat.Stripchat; import ctbrec.ui.controls.Dialogs; import ctbrec.ui.news.NewsTab; import ctbrec.ui.settings.SettingsTab; +import ctbrec.ui.settings.SettingsTab2; import ctbrec.ui.tabs.DonateTabFx; import ctbrec.ui.tabs.HelpTab; import ctbrec.ui.tabs.RecordedModelsTab; @@ -189,7 +190,7 @@ public class CamrecApplication extends Application { recordingsTab = new RecordingsTab("Recordings", recorder, config, sites); tabPane.getTabs().add(recordingsTab); settingsTab = new SettingsTab(sites, recorder); - //tabPane.getTabs().add(new SettingsTab2(sites, recorder)); + tabPane.getTabs().add(new SettingsTab2(sites, recorder)); tabPane.getTabs().add(settingsTab); tabPane.getTabs().add(new NewsTab()); tabPane.getTabs().add(new DonateTabFx()); diff --git a/client/src/main/java/ctbrec/ui/controls/AbstractFileSelectionBox.java b/client/src/main/java/ctbrec/ui/controls/AbstractFileSelectionBox.java index 5fc0ce2c..0f3820b8 100644 --- a/client/src/main/java/ctbrec/ui/controls/AbstractFileSelectionBox.java +++ b/client/src/main/java/ctbrec/ui/controls/AbstractFileSelectionBox.java @@ -46,9 +46,10 @@ public abstract class AbstractFileSelectionBox extends HBox { } }); Node browse = createBrowseButton(); - getChildren().addAll(fileInput, browse); - fileInput.disableProperty().bind(disableProperty()); browse.disableProperty().bind(disableProperty()); + fileInput.disableProperty().bind(disableProperty()); + fileInput.textProperty().bindBidirectional(fileProperty); + getChildren().addAll(fileInput, browse); HBox.setHgrow(fileInput, Priority.ALWAYS); disabledProperty().addListener((obs, oldV, newV) -> { @@ -70,10 +71,12 @@ public abstract class AbstractFileSelectionBox extends HBox { private ChangeListener textListener() { return (obs, o, n) -> { String input = fileInput.getText(); - if (StringUtil.isBlank(input) && allowEmptyValue) { - fileProperty.set(""); - hideValidationHints(); - return; + if (StringUtil.isBlank(input)) { + if (allowEmptyValue) { + fileProperty.set(""); + hideValidationHints(); + return; + } } else { File program = new File(input); setFile(program); @@ -122,6 +125,8 @@ public abstract class AbstractFileSelectionBox extends HBox { private Button createBrowseButton() { Button button = new Button("Select"); button.setOnAction(e -> choose()); + button.prefHeightProperty().bind(this.heightProperty()); + button.prefWidthProperty().set(70); return button; } diff --git a/client/src/main/java/ctbrec/ui/controls/range/RangeSlider.java b/client/src/main/java/ctbrec/ui/controls/range/RangeSlider.java index 78d98b21..b1c27951 100644 --- a/client/src/main/java/ctbrec/ui/controls/range/RangeSlider.java +++ b/client/src/main/java/ctbrec/ui/controls/range/RangeSlider.java @@ -1,7 +1,7 @@ package ctbrec.ui.controls.range; -import javafx.beans.property.DoubleProperty; -import javafx.beans.property.SimpleDoubleProperty; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; import javafx.geometry.Orientation; import javafx.scene.control.Control; import javafx.scene.control.Skin; @@ -11,16 +11,17 @@ public class RangeSlider extends Control { private static final String DEFAULT_STYLE_CLASS = "rangeslider"; private Range range; - private DoubleProperty low; - private DoubleProperty high; + private ObjectProperty low; + private ObjectProperty high; private boolean showTickMarks = false; private boolean showTickLabels = false; private Orientation orientation = Orientation.HORIZONTAL; + @SuppressWarnings({ "unchecked", "rawtypes" }) public RangeSlider(Range range) { this.range = range; - low = new SimpleDoubleProperty(getMinimum().doubleValue()); - high = new SimpleDoubleProperty(getMaximum().doubleValue()); + low = new SimpleObjectProperty(getMinimum()); + high = new SimpleObjectProperty(getMaximum()); getStyleClass().setAll(DEFAULT_STYLE_CLASS); } @@ -34,20 +35,20 @@ public class RangeSlider extends Control { return RangeSlider.class.getResource("rangeslider.css").toExternalForm(); } - public DoubleProperty getLow() { + public ObjectProperty getLow() { return low; } public void setLow(T newPosition) { - low.set(newPosition.doubleValue()); + low.set(newPosition); } - public DoubleProperty getHigh() { + public ObjectProperty getHigh() { return high; } public void setHigh(T newPosition) { - this.high.set(newPosition.doubleValue()); + this.high.set(newPosition); } public T getMinimum() { diff --git a/client/src/main/java/ctbrec/ui/controls/range/RangeSliderBehavior.java b/client/src/main/java/ctbrec/ui/controls/range/RangeSliderBehavior.java index 2a38c596..b2496f65 100644 --- a/client/src/main/java/ctbrec/ui/controls/range/RangeSliderBehavior.java +++ b/client/src/main/java/ctbrec/ui/controls/range/RangeSliderBehavior.java @@ -70,11 +70,11 @@ public class RangeSliderBehavior extends BehaviorBase> { t.low.setOnMousePressed(me -> { preDragThumbPoint = t.low.localToParent(me.getX(), me.getY()); - preDragPos = (getSkinnable().getLow().doubleValue() - getSkinnable().getMinimum().doubleValue()) / (getMaxMinusMinNoZero()); + preDragPos = (getSkinnable().getLow().get().doubleValue() - getSkinnable().getMinimum().doubleValue()) / (getMaxMinusMinNoZero()); }); t.low.setOnMouseDragged(me -> { Point2D cur = t.low.localToParent(me.getX(), me.getY()); @@ -52,7 +52,7 @@ public class RangeSliderSkin extends SkinBase> { t.high.setOnMousePressed(me -> { preDragThumbPoint = t.high.localToParent(me.getX(), me.getY()); - preDragPos = (getSkinnable().getHigh().doubleValue() - getSkinnable().getMinimum().doubleValue()) / (getMaxMinusMinNoZero()); + preDragPos = (getSkinnable().getHigh().get().doubleValue() - getSkinnable().getMinimum().doubleValue()) / (getMaxMinusMinNoZero()); }); t.high.setOnMouseDragged(me -> { boolean orientation = getSkinnable().getOrientation() == Orientation.HORIZONTAL; @@ -160,8 +160,8 @@ public class RangeSliderSkin extends SkinBase> { private void positionThumbs() { RangeSlider s = getSkinnable(); - double lxl = trackStart + (trackLength * ((s.getLow().doubleValue() - s.getMinimum().doubleValue()) / (getMaxMinusMinNoZero())) - thumbWidth / 2D); - double lxh = trackStart + (trackLength * ((s.getHigh().doubleValue() - s.getMinimum().doubleValue()) / (getMaxMinusMinNoZero())) - thumbWidth / 2D); + double lxl = trackStart + (trackLength * ((s.getLow().get().doubleValue() - s.getMinimum().doubleValue()) / (getMaxMinusMinNoZero())) - thumbWidth / 2D); + double lxh = trackStart + (trackLength * ((s.getHigh().get().doubleValue() - s.getMinimum().doubleValue()) / (getMaxMinusMinNoZero())) - thumbWidth / 2D); double ly = lowThumbPos; if (thumbRange != null) { diff --git a/client/src/main/java/ctbrec/ui/settings/ActionSettingsPanel.java b/client/src/main/java/ctbrec/ui/settings/ActionSettingsPanel.java index 80f88e62..53ae77b9 100644 --- a/client/src/main/java/ctbrec/ui/settings/ActionSettingsPanel.java +++ b/client/src/main/java/ctbrec/ui/settings/ActionSettingsPanel.java @@ -50,9 +50,7 @@ import javafx.scene.control.ScrollPane; import javafx.scene.control.SelectionMode; import javafx.scene.control.Separator; import javafx.scene.control.TextField; -import javafx.scene.control.TitledPane; import javafx.scene.image.Image; -import javafx.scene.layout.BorderPane; import javafx.scene.layout.GridPane; import javafx.scene.layout.HBox; import javafx.scene.layout.Pane; @@ -61,8 +59,8 @@ import javafx.stage.Modality; import javafx.stage.Stage; import javafx.stage.Window; -public class ActionSettingsPanel extends TitledPane { - private static final transient Logger LOG = LoggerFactory.getLogger(ActionSettingsPanel.class); +public class ActionSettingsPanel extends Pane { + private static final Logger LOG = LoggerFactory.getLogger(ActionSettingsPanel.class); private ListView actionTable; private TextField name = new TextField(); @@ -80,11 +78,8 @@ public class ActionSettingsPanel extends TitledPane { private Recorder recorder; - public ActionSettingsPanel(SettingsTab settingsTab, Recorder recorder) { + public ActionSettingsPanel(Recorder recorder) { this.recorder = recorder; - setText("Events & Actions"); - setExpanded(true); - setCollapsible(false); createGui(); loadEventHandlers(); } @@ -94,15 +89,22 @@ public class ActionSettingsPanel extends TitledPane { } private void createGui() { - BorderPane mainLayout = new BorderPane(); - setContent(mainLayout); + GridPane grid = new GridPane(); + grid.setHgap(10); + grid.setVgap(10); + grid.setPadding(new Insets(20, 150, 10, 10)); + getChildren().add(grid); + + Label headline = new Label("Events & Actions"); + headline.getStyleClass().add("settings-group-label"); + grid.add(headline, 0, 0); actionTable = createActionTable(); ScrollPane scrollPane = new ScrollPane(actionTable); scrollPane.setFitToHeight(true); scrollPane.setFitToWidth(true); scrollPane.setStyle("-fx-background-color: -fx-background"); - mainLayout.setCenter(scrollPane); + grid.add(scrollPane, 0, 1); Button add = new Button("Add"); add.setOnAction(this::add); @@ -110,15 +112,10 @@ public class ActionSettingsPanel extends TitledPane { delete.setOnAction(this::delete); delete.setDisable(true); HBox buttons = new HBox(5, add, delete); - mainLayout.setBottom(buttons); - BorderPane.setMargin(buttons, new Insets(5, 0, 0, 0)); + buttons.setStyle("-fx-background-color: -fx-background"); // workaround so that the buttons don't shrink + grid.add(buttons, 0, 2); - actionTable.getSelectionModel().getSelectedItems().addListener(new ListChangeListener() { - @Override - public void onChanged(Change change) { - delete.setDisable(change.getList().isEmpty()); - } - }); + actionTable.getSelectionModel().getSelectedItems().addListener((ListChangeListener) change -> delete.setDisable(change.getList().isEmpty())); } private void add(ActionEvent evt) { @@ -235,9 +232,7 @@ public class ActionSettingsPanel extends TitledPane { event.getItems().clear(); event.getItems().add(Event.Type.MODEL_STATUS_CHANGED); event.getItems().add(Event.Type.RECORDING_STATUS_CHANGED); - event.setOnAction(evt -> { - modelState.setVisible(event.getSelectionModel().getSelectedItem() == Event.Type.MODEL_STATUS_CHANGED); - }); + event.setOnAction(evt -> modelState.setVisible(event.getSelectionModel().getSelectedItem() == Event.Type.MODEL_STATUS_CHANGED)); event.getSelectionModel().select(Event.Type.MODEL_STATUS_CHANGED); layout.add(event, 1, row++); @@ -254,7 +249,7 @@ public class ActionSettingsPanel extends TitledPane { Label l = new Label("Models"); layout.add(l, 0, row); - modelSelectionPane = new ListSelectionPane(recorder.getModels(), Collections.emptyList()); + modelSelectionPane = new ListSelectionPane<>(recorder.getModels(), Collections.emptyList()); layout.add(modelSelectionPane, 1, row++); GridPane.setValignment(l, VPos.TOP); GridPane.setHgrow(modelSelectionPane, Priority.ALWAYS); diff --git a/client/src/main/java/ctbrec/ui/settings/CtbrecPreferencesStorage.java b/client/src/main/java/ctbrec/ui/settings/CtbrecPreferencesStorage.java index 27a8034d..b3bb8180 100644 --- a/client/src/main/java/ctbrec/ui/settings/CtbrecPreferencesStorage.java +++ b/client/src/main/java/ctbrec/ui/settings/CtbrecPreferencesStorage.java @@ -2,7 +2,8 @@ package ctbrec.ui.settings; import java.io.IOException; import java.lang.reflect.Field; -import java.util.Arrays; +import java.util.List; +import java.util.Objects; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -10,16 +11,33 @@ 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.SelectionSetting; import ctbrec.ui.settings.api.Setting; -import javafx.collections.FXCollections; +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 { @@ -37,7 +55,7 @@ public class CtbrecPreferencesStorage implements PreferencesStorage { @Override public void save(Preferences preferences) throws IOException { - config.save(); + throw new RuntimeException("not implemented"); } @Override @@ -47,69 +65,206 @@ public class CtbrecPreferencesStorage implements PreferencesStorage { @Override public Node createGui(Setting setting) throws Exception { - String key = setting.getKey(); - Field field = Settings.class.getField(key); - Class t = field.getType(); - Object value = field.get(settings); - if(setting instanceof SelectionSetting) { - return createComboBox(key, ((SelectionSetting) setting).getOptions()); - } else if (t == String.class) { - return createStringProperty(key, (String) value); - } else if (t == int.class || t == Integer.class) { - return createIntegerProperty(key, (Integer) value); - } else if (t == boolean.class || t == Boolean.class) { - return createBooleanProperty(key, (Boolean) value); + 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 " + key + ": " + t); + return new Label("Unsupported Type for key " + setting.getKey() + ": " + setting.getProperty()); } } - private Node createStringProperty(String fieldName, String value) { - TextField ctrl = new TextField(value); - ctrl.textProperty().addListener((obs, oldV, newV) -> saveValue(() -> { - Field field = Settings.class.getField(fieldName); + 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; } - private Node createIntegerProperty(String fieldName, Integer value) { - TextField ctrl = new TextField(value.toString()); + @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(fieldName); + 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; } - private Node createBooleanProperty(String fieldName, Boolean value) { + @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.setSelected(value); ctrl.selectedProperty().addListener((obs, oldV, newV) -> saveValue(() -> { - Field field = Settings.class.getField(fieldName); + Field field = Settings.class.getField(setting.getKey()); field.set(settings, newV); config.save(); })); + BooleanProperty prop = (BooleanProperty) setting.getProperty(); + ctrl.selectedProperty().bindBidirectional(prop); return ctrl; } - private Node createComboBox(String key, Object[] options) throws NoSuchFieldException, IllegalAccessException { - @SuppressWarnings({ "rawtypes", "unchecked" }) - ComboBox comboBox = new ComboBox(FXCollections.observableList(Arrays.asList(options))); - Field field = Settings.class.getField(key); + @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())) { - comboBox.getSelectionModel().select(value); + 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(() -> { - field.set(settings, newV); + if (setting.getConverter() != null) { + field.set(settings, setting.getConverter().convertFrom(newV)); + } else { + field.set(settings, newV); + } config.save(); })); return comboBox; diff --git a/client/src/main/java/ctbrec/ui/settings/IgnoreList.java b/client/src/main/java/ctbrec/ui/settings/IgnoreList.java new file mode 100644 index 00000000..943ed10c --- /dev/null +++ b/client/src/main/java/ctbrec/ui/settings/IgnoreList.java @@ -0,0 +1,162 @@ +package ctbrec.ui.settings; + +import static javafx.scene.control.ButtonType.*; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.lang.reflect.Type; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.util.Collections; +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.squareup.moshi.JsonAdapter; +import com.squareup.moshi.Moshi; +import com.squareup.moshi.Types; + +import ctbrec.Config; +import ctbrec.Model; +import ctbrec.io.ModelJsonAdapter; +import ctbrec.sites.Site; +import ctbrec.ui.AutosizeAlert; +import ctbrec.ui.controls.Dialogs; +import javafx.geometry.Insets; +import javafx.scene.control.Alert.AlertType; +import javafx.scene.control.Button; +import javafx.scene.control.ButtonType; +import javafx.scene.control.Label; +import javafx.scene.control.ListView; +import javafx.scene.control.SelectionMode; +import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyEvent; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Pane; +import javafx.stage.FileChooser; + +public class IgnoreList extends Pane { + + private static final Logger LOG = LoggerFactory.getLogger(IgnoreList.class); + + private ListView ignoreListView; + + private List sites; + + public IgnoreList(List sites) { + this.sites = sites; + createGui(); + loadIgnoredModels(); + } + + private void createGui() { + GridPane grid = new GridPane(); + grid.setHgap(10); + grid.setVgap(10); + grid.setPadding(new Insets(20, 150, 10, 10)); + + Label headline = new Label("Ignore List"); + headline.getStyleClass().add("settings-group-label"); + grid.add(headline, 0, 0); + + ignoreListView = new ListView<>(); + ignoreListView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE); + ignoreListView.addEventHandler(KeyEvent.KEY_PRESSED, event -> { + if (event.getCode() == KeyCode.DELETE) { + removeSelectedModels(); + } + }); + grid.add(ignoreListView, 0, 1); + + Button remove = new Button("Remove"); + remove.setOnAction(evt -> removeSelectedModels()); + Button exportIgnoreList = new Button("Export"); + exportIgnoreList.setOnAction(e -> exportIgnoreList()); + Button importIgnoreList = new Button("Import"); + importIgnoreList.setOnAction(e -> importIgnoreList()); + HBox buttons = new HBox(10, remove, exportIgnoreList, importIgnoreList); + grid.add(buttons, 0, 2); + buttons.setStyle("-fx-background-color: -fx-background"); // workaround so that the buttons don't shrink + + getChildren().add(grid); + } + + private void removeSelectedModels() { + List selectedModels = ignoreListView.getSelectionModel().getSelectedItems(); + if (selectedModels.isEmpty()) { + return; + } else { + Config.getInstance().getSettings().modelsIgnored.removeAll(selectedModels); + ignoreListView.getItems().removeAll(selectedModels); + LOG.debug(Config.getInstance().getSettings().modelsIgnored.toString()); + try { + Config.getInstance().save(); + } catch (IOException e) { + LOG.warn("Couldn't save config", e); + } + } + } + + private void loadIgnoredModels() { + List ignored = Config.getInstance().getSettings().modelsIgnored; + ignoreListView.getItems().clear(); + ignoreListView.getItems().addAll(ignored); + Collections.sort(ignoreListView.getItems()); + } + + public void refresh() { + loadIgnoredModels(); + } + + private void exportIgnoreList() { + FileChooser chooser = new FileChooser(); + chooser.setTitle("Export ignore list"); + chooser.setInitialFileName("ctbrec-ignorelist.json"); + File file = chooser.showSaveDialog(null); + if (file != null) { + Moshi moshi = new Moshi.Builder().add(Model.class, new ModelJsonAdapter(sites)).build(); + Type modelListType = Types.newParameterizedType(List.class, Model.class); + JsonAdapter> adapter = moshi.adapter(modelListType); + adapter = adapter.indent(" "); + try (FileOutputStream out = new FileOutputStream(file)) { + String json = adapter.toJson(Config.getInstance().getSettings().modelsIgnored); + out.write(json.getBytes(StandardCharsets.UTF_8)); + } catch (IOException e) { + Dialogs.showError(getScene(), "Couldn't export ignore list", e.getLocalizedMessage(), e); + } + } + } + + private void importIgnoreList() { + FileChooser chooser = new FileChooser(); + chooser.setTitle("Import ignore list"); + File file = chooser.showOpenDialog(null); + if (file != null) { + Moshi moshi = new Moshi.Builder().add(Model.class, new ModelJsonAdapter(sites)).build(); + Type modelListType = Types.newParameterizedType(List.class, Model.class); + JsonAdapter> adapter = moshi.adapter(modelListType); + try { + byte[] fileContent = Files.readAllBytes(file.toPath()); + List ignoredModels = adapter.fromJson(new String(fileContent, StandardCharsets.UTF_8)); + boolean confirmed = true; + if (!Config.getInstance().getSettings().modelsIgnored.isEmpty()) { + String msg = "This will replace the existing ignore list! Continue?"; + AutosizeAlert confirm = new AutosizeAlert(AlertType.CONFIRMATION, msg, getScene(), YES, NO); + confirm.setTitle("Import ignore list"); + confirm.setHeaderText("Overwrite ignore list"); + confirm.showAndWait(); + confirmed = confirm.getResult() == ButtonType.YES; + } + if (confirmed) { + Config.getInstance().getSettings().modelsIgnored = ignoredModels; + refresh(); + } + } catch (IOException e) { + Dialogs.showError(getScene(), "Couldn't import ignore list", e.getLocalizedMessage(), e); + } + } + } +} diff --git a/client/src/main/java/ctbrec/ui/settings/IgnoreListDialog.java b/client/src/main/java/ctbrec/ui/settings/IgnoreListDialog.java deleted file mode 100644 index 762743cc..00000000 --- a/client/src/main/java/ctbrec/ui/settings/IgnoreListDialog.java +++ /dev/null @@ -1,96 +0,0 @@ -package ctbrec.ui.settings; - -import java.io.IOException; -import java.io.InputStream; -import java.util.Collections; -import java.util.List; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import ctbrec.Config; -import ctbrec.Model; -import ctbrec.ui.controls.Dialogs; -import javafx.geometry.Insets; -import javafx.scene.Scene; -import javafx.scene.control.Button; -import javafx.scene.control.ButtonType; -import javafx.scene.control.Dialog; -import javafx.scene.control.ListView; -import javafx.scene.control.SelectionMode; -import javafx.scene.image.Image; -import javafx.scene.input.KeyCode; -import javafx.scene.input.KeyEvent; -import javafx.scene.layout.GridPane; -import javafx.stage.Modality; -import javafx.stage.Stage; - -public class IgnoreListDialog extends Dialog { - - private static final Logger LOG = LoggerFactory.getLogger(IgnoreListDialog.class); - - private Scene parent; - private ListView ignoreList; - - public IgnoreListDialog(Scene parent) { - this.parent = parent; - createGui(); - loadIgnoredModels(); - } - - private void createGui() { - setTitle("Ignore List"); - getDialogPane().getButtonTypes().addAll(ButtonType.OK); - initModality(Modality.APPLICATION_MODAL); - setResizable(true); - InputStream icon = Dialogs.class.getResourceAsStream("/icon.png"); - Stage stage = (Stage) getDialogPane().getScene().getWindow(); - stage.getIcons().add(new Image(icon)); - if (parent != null) { - stage.getScene().getStylesheets().addAll(parent.getStylesheets()); - } - - GridPane grid = new GridPane(); - grid.setHgap(10); - grid.setVgap(10); - grid.setPadding(new Insets(20, 150, 10, 10)); - - ignoreList = new ListView<>(); - ignoreList.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE); - ignoreList.addEventHandler(KeyEvent.KEY_PRESSED, event -> { - if (event.getCode() == KeyCode.DELETE) { - removeSelectedModels(); - } - }); - grid.add(ignoreList, 0, 0); - - Button remove = new Button("Remove"); - remove.setOnAction(evt -> removeSelectedModels()); - grid.add(remove, 0, 1); - - getDialogPane().setContent(grid); - setResizable(true); - } - - private void removeSelectedModels() { - List selectedModels = ignoreList.getSelectionModel().getSelectedItems(); - if (selectedModels.isEmpty()) { - return; - } else { - Config.getInstance().getSettings().modelsIgnored.removeAll(selectedModels); - ignoreList.getItems().removeAll(selectedModels); - LOG.debug(Config.getInstance().getSettings().modelsIgnored.toString()); - try { - Config.getInstance().save(); - } catch (IOException e) { - LOG.warn("Couldn't save config", e); - } - } - } - - private void loadIgnoredModels() { - List ignored = Config.getInstance().getSettings().modelsIgnored; - ignoreList.getItems().addAll(ignored); - Collections.sort(ignoreList.getItems()); - } -} diff --git a/client/src/main/java/ctbrec/ui/settings/SettingsTab.java b/client/src/main/java/ctbrec/ui/settings/SettingsTab.java index 8c10e24a..dd226278 100644 --- a/client/src/main/java/ctbrec/ui/settings/SettingsTab.java +++ b/client/src/main/java/ctbrec/ui/settings/SettingsTab.java @@ -38,6 +38,7 @@ 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.ValueConverter; import ctbrec.ui.sites.ConfigUI; import ctbrec.ui.tabs.TabSelectionListener; import javafx.collections.FXCollections; @@ -159,7 +160,7 @@ public class SettingsTab extends Tab implements TabSelectionListener { //right side rightSide.getChildren().add(siteConfigAccordion); - ActionSettingsPanel actions = new ActionSettingsPanel(this, recorder); + ActionSettingsPanel actions = new ActionSettingsPanel(recorder); rightSide.getChildren().add(actions); proxySettingsPane = new ProxySettingsPane(this); rightSide.getChildren().add(createIgnoreListPanel()); @@ -561,7 +562,7 @@ public class SettingsTab extends Tab implements TabSelectionListener { private Node createIgnoreListPanel() { GridPane layout = createGridLayout(); Button editIgnoreList = new Button("Edit"); - editIgnoreList.setOnAction(e -> new IgnoreListDialog(editIgnoreList.getScene()).showAndWait()); + //editIgnoreList.setOnAction(e -> new IgnoreList(editIgnoreList.getScene()).showAndWait()); layout.add(editIgnoreList, 0, 0); Button exportIgnoreList = new Button("Export"); exportIgnoreList.setOnAction(e -> exportIgnoreList()); @@ -850,7 +851,7 @@ public class SettingsTab extends Tab implements TabSelectionListener { public void saveConfig() { if(proxySettingsPane != null) { - proxySettingsPane.saveConfig(); + //proxySettingsPane.saveConfig(); } try { Config.getInstance().save(); @@ -877,5 +878,39 @@ public class SettingsTab extends Tab implements TabSelectionListener { public String toString() { return label; } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + value; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + SplitAfterOption other = (SplitAfterOption) obj; + return value == other.value; + } + + public static ValueConverter converter() { + return new ValueConverter() { + @Override + public Integer convertFrom(Object splitAfterOption) { + return ((SplitAfterOption) splitAfterOption).getValue(); + } + + @Override + public SplitAfterOption convertTo(Object integer) { + return new SplitAfterOption(integer.toString(), (Integer) integer); + } + }; + } } } diff --git a/client/src/main/java/ctbrec/ui/settings/SettingsTab2.java b/client/src/main/java/ctbrec/ui/settings/SettingsTab2.java index 32304faa..9e28912a 100644 --- a/client/src/main/java/ctbrec/ui/settings/SettingsTab2.java +++ b/client/src/main/java/ctbrec/ui/settings/SettingsTab2.java @@ -1,91 +1,284 @@ package ctbrec.ui.settings; +import static ctbrec.Settings.DirectoryStructure.*; import static ctbrec.Settings.ProxyType.*; +import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; +import java.util.stream.Collectors; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import ctbrec.Config; +import ctbrec.Hmac; +import ctbrec.Settings; +import ctbrec.Settings.DirectoryStructure; +import ctbrec.Settings.ProxyType; import ctbrec.recorder.Recorder; import ctbrec.sites.Site; import ctbrec.ui.SiteUiFactory; +import ctbrec.ui.controls.range.DiscreteRange; +import ctbrec.ui.settings.SettingsTab.SplitAfterOption; import ctbrec.ui.settings.api.Category; +import ctbrec.ui.settings.api.ExclusiveSelectionProperty; +import ctbrec.ui.settings.api.GigabytesConverter; import ctbrec.ui.settings.api.Group; import ctbrec.ui.settings.api.Preferences; 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 ctbrec.ui.tabs.TabSelectionListener; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleIntegerProperty; +import javafx.beans.property.SimpleListProperty; +import javafx.beans.property.SimpleLongProperty; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.value.ObservableValue; +import javafx.collections.FXCollections; import javafx.scene.control.Tab; +import javafx.scene.control.TextInputDialog; public class SettingsTab2 extends Tab implements TabSelectionListener { + private static final Logger LOG = LoggerFactory.getLogger(SettingsTab2.class); + private List sites; private Recorder recorder; private Preferences prefs; private boolean initialized = false; + private Config config; + private Settings settings; + + private SimpleStringProperty httpUserAgent; + private SimpleStringProperty httpUserAgentMobile; + private SimpleIntegerProperty overviewUpdateIntervalInSecs; + private SimpleBooleanProperty updateThumbnails; + private SimpleBooleanProperty determineResolution; + private SimpleBooleanProperty chooseStreamQuality; + private SimpleBooleanProperty livePreviews; + private SimpleListProperty startTab; + private SimpleFileProperty mediaPlayer; + private SimpleStringProperty mediaPlayerParams; + private SimpleIntegerProperty maximumResolutionPlayer; + private SimpleBooleanProperty showPlayerStarting; + private SimpleBooleanProperty singlePlayer; + private SimpleListProperty proxyType; + private SimpleStringProperty proxyHost; + private SimpleStringProperty proxyPort; + private SimpleStringProperty proxyUser; + private SimpleStringProperty proxyPassword; + private SimpleDirectoryProperty recordingsDir; + private SimpleListProperty directoryStructure; + private SimpleListProperty splitAfter; + private SimpleRangeProperty resolutionRange; + private List labels = Arrays.asList(0, 240, 360, 480, 600, 720, 960, 1080, 1440, 2160, 4320, 8640); + private List values = Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11); + private DiscreteRange rangeValues = new DiscreteRange<>(values, labels); + private SimpleIntegerProperty concurrentRecordings; + private SimpleIntegerProperty onlineCheckIntervalInSecs; + private SimpleLongProperty leaveSpaceOnDevice; + private SimpleIntegerProperty minimumLengthInSecs; + private SimpleStringProperty ffmpegParameters; + private SimpleStringProperty fileExtension; + private SimpleStringProperty server; + private SimpleIntegerProperty port; + private SimpleStringProperty path; + private SimpleBooleanProperty requireAuthentication; + private SimpleBooleanProperty transportLayerSecurity; + private ExclusiveSelectionProperty recordLocal; + private SimpleFileProperty postProcessing; + private SimpleIntegerProperty postProcessingThreads; + private SimpleBooleanProperty removeRecordingAfterPp; + private IgnoreList ignoreList; public SettingsTab2(List sites, Recorder recorder) { this.sites = sites; this.recorder = recorder; setText("Settings"); setClosable(false); + config = Config.getInstance(); + settings = config.getSettings(); + } + private void initializeProperties() { + httpUserAgent = new SimpleStringProperty(null, "httpUserAgent", settings.httpUserAgent); + httpUserAgentMobile = new SimpleStringProperty(null, "httpUserAgentMobile", settings.httpUserAgentMobile); + overviewUpdateIntervalInSecs = new SimpleIntegerProperty(null, "overviewUpdateIntervalInSecs", settings.overviewUpdateIntervalInSecs); + updateThumbnails = new SimpleBooleanProperty(null, "updateThumbnails", settings.updateThumbnails); + determineResolution = new SimpleBooleanProperty(null, "determineResolution", settings.determineResolution); + chooseStreamQuality = new SimpleBooleanProperty(null, "chooseStreamQuality", settings.chooseStreamQuality); + livePreviews = new SimpleBooleanProperty(null, "livePreviews", settings.livePreviews); + startTab = new SimpleListProperty<>(null, "startTab", FXCollections.observableList(getTabNames())); + mediaPlayer = new SimpleFileProperty(null, "mediaPlayer", settings.mediaPlayer); + mediaPlayerParams = new SimpleStringProperty(null, "mediaPlayerParams", settings.mediaPlayerParams); + maximumResolutionPlayer = new SimpleIntegerProperty(null, "maximumResolutionPlayer", settings.maximumResolutionPlayer); + showPlayerStarting = new SimpleBooleanProperty(null, "showPlayerStarting", settings.showPlayerStarting); + singlePlayer = new SimpleBooleanProperty(null, "singlePlayer", settings.singlePlayer); + proxyType = new SimpleListProperty<>(null, "proxyType", FXCollections.observableList(List.of(DIRECT, HTTP, SOCKS4, SOCKS5))); + proxyHost = new SimpleStringProperty(null, "proxyHost", settings.proxyHost); + proxyPort = new SimpleStringProperty(null, "proxyPort", settings.proxyPort); + proxyUser = new SimpleStringProperty(null, "proxyUser", settings.proxyUser); + proxyPassword = new SimpleStringProperty(null, "proxyPassword", settings.proxyPassword); + recordingsDir = new SimpleDirectoryProperty(null, "recordingsDir", settings.recordingsDir); + directoryStructure = new SimpleListProperty<>(null, "recordingsDirStructure", FXCollections.observableList(List.of(FLAT, ONE_PER_MODEL, ONE_PER_RECORDING))); + splitAfter = new SimpleListProperty<>(null, "splitRecordings", FXCollections.observableList(getSplitOptions())); + resolutionRange = new SimpleRangeProperty<>(rangeValues, "minimumResolution", "maximumResolution", settings.minimumResolution, settings.maximumResolution); + concurrentRecordings = new SimpleIntegerProperty(null, "concurrentRecordings", settings.concurrentRecordings); + onlineCheckIntervalInSecs = new SimpleIntegerProperty(null, "onlineCheckIntervalInSecs", settings.onlineCheckIntervalInSecs); + leaveSpaceOnDevice = new SimpleLongProperty(null, "minimumSpaceLeftInBytes", (long) new GigabytesConverter().convertTo(settings.minimumSpaceLeftInBytes)); + minimumLengthInSecs = new SimpleIntegerProperty(null, "minimumLengthInSeconds", settings.minimumLengthInSeconds); + ffmpegParameters = new SimpleStringProperty(null, "ffmpegMergedDownloadArgs", settings.ffmpegMergedDownloadArgs); + fileExtension = new SimpleStringProperty(null, "ffmpegFileSuffix", settings.ffmpegFileSuffix); + server = new SimpleStringProperty(null, "httpServer", settings.httpServer); + port = new SimpleIntegerProperty(null, "httpPort", settings.httpPort); + path = new SimpleStringProperty(null, "servletContext", settings.servletContext); + requireAuthentication = new SimpleBooleanProperty(null, "requireAuthentication", settings.requireAuthentication); + requireAuthentication.addListener(this::requireAuthenticationChanged); + transportLayerSecurity = new SimpleBooleanProperty(null, "transportLayerSecurity", settings.transportLayerSecurity); + recordLocal = new ExclusiveSelectionProperty(null, "localRecording", settings.localRecording, "Local", "Remote"); + postProcessing = new SimpleFileProperty(null, "postProcessing", settings.postProcessing); + postProcessingThreads = new SimpleIntegerProperty(null, "postProcessingThreads", settings.postProcessingThreads); + removeRecordingAfterPp = new SimpleBooleanProperty(null, "removeRecordingAfterPostProcessing", settings.removeRecordingAfterPostProcessing); } private void createGui() { + ignoreList = new IgnoreList(sites); List siteCategories = new ArrayList<>(); for (Site site : sites) { siteCategories.add(Category.of(site.getName(), SiteUiFactory.getUi(site).getConfigUI().createConfigPanel())); } - prefs = Preferences.of(new CtbrecPreferencesStorage(Config.getInstance()), + prefs = Preferences.of(new CtbrecPreferencesStorage(config), Category.of("General", Group.of("General", - Setting.of("User-Agent", "httpUserAgent"), - Setting.of("User-Agent mobile", "httpUserAgentMobile"), - Setting.of("Update overview interval (seconds)", "overviewUpdateIntervalInSecs", "Update the thumbnail overviews every x seconds"), - Setting.of("Update thumbnails", "updateThumbnails", "The overviews will still be updated, but the thumbnails won't be changed. This is useful for less powerful systems."), - Setting.of("Display stream resolution in overview", "determineResolution"), - Setting.of("Manually select stream quality", "chooseStreamQuality", "Opens a dialog to select the video resolution before recording"), - Setting.of("Enable live previews (experimental)", "livePreviews"), - Setting.of("Start Tab", "startTab", getTabNames()), - Setting.of("Colors (Base / Accent)", null, new ColorSettingsPane(Config.getInstance())) + Setting.of("User-Agent", httpUserAgent), + Setting.of("User-Agent mobile", httpUserAgentMobile), + Setting.of("Update overview interval (seconds)", overviewUpdateIntervalInSecs, "Update the thumbnail overviews every x seconds"), + Setting.of("Update thumbnails", updateThumbnails, "The overviews will still be updated, but the thumbnails won't be changed. This is useful for less powerful systems."), + Setting.of("Display stream resolution in overview", determineResolution), + Setting.of("Enable live previews (experimental)", livePreviews), + Setting.of("Start Tab", startTab), + Setting.of("Colors (Base / Accent)", new ColorSettingsPane(Config.getInstance())) ), Group.of("Player", - Setting.of("Player", "mediaPlayer"), - Setting.of("Start parameters", "mediaPlayerParams"), - Setting.of("Maximum resolution (0 = unlimited)", "maximumResolutionPlayer", "video height, e.g. 720 or 1080"), - Setting.of("Show \"Player Starting\" Message", "showPlayerStarting"), - Setting.of("Start only one player at a time", "singlePlayer") + Setting.of("Player", mediaPlayer), + Setting.of("Start parameters", mediaPlayerParams), + Setting.of("Maximum resolution (0 = unlimited)", maximumResolutionPlayer, "video height, e.g. 720 or 1080"), + Setting.of("Show \"Player Starting\" Message", showPlayerStarting), + Setting.of("Start only one player at a time", singlePlayer) ) ), + Category.of("Recorder", + Group.of("Settings", + Setting.of("Recordings Directory", recordingsDir), + Setting.of("Directory Structure", directoryStructure), + Setting.of("Split recordings after (minutes)", splitAfter).converter(SplitAfterOption.converter()), + Setting.of("Manually select stream quality", chooseStreamQuality, "Opens a dialog to select the video resolution before recording"), + Setting.of("Restrict Resolution", resolutionRange, "Only record streams with resolution within the given range"), + Setting.of("Concurrent Recordings (0 = unlimited)", concurrentRecordings), + Setting.of("Check online state every (seconds)", onlineCheckIntervalInSecs, "Check every x seconds, if a model came online"), + Setting.of("Leave space on device (GiB)", leaveSpaceOnDevice, "Stop recording, if the free space on the device gets below this threshold").converter(new GigabytesConverter()), + Setting.of("FFmpeg parameters", ffmpegParameters, "FFmpeg parameters to use when merging stream segments"), + Setting.of("File Extension", fileExtension, "File extension to use for recordings") + ), + Group.of("Location", + Setting.of("Record Location", recordLocal), + Setting.of("Server", server), + Setting.of("Port", port), + Setting.of("Path", path, "Leave empty, if you didn't change the servletContext in the server config"), + Setting.of("Require authentication", requireAuthentication), + Setting.of("Use Secure Communication (TLS)", transportLayerSecurity) + ) + ), + Category.of("Post-Processing", + Group.of("Post-Processing", + Setting.of("Post-Processing", postProcessing), + Setting.of("Threads", postProcessingThreads), + Setting.of("Delete recordings shorter than (secs)", minimumLengthInSecs, "Delete recordings, which are shorter than x seconds. 0 to disable"), + Setting.of("Remove recording after post-processing", removeRecordingAfterPp) + ) + ), + Category.of("Events & Actions", new ActionSettingsPanel(recorder)), + Category.of("Ignore List", ignoreList), Category.of("Sites", siteCategories.toArray(new Category[0])), Category.of("Proxy", Group.of("Proxy", - Setting.of("Type", "proxyType", DIRECT, HTTP, SOCKS4, SOCKS5), - Setting.of("Host", "proxyHost"), - Setting.of("Port", "proxyPort"), - Setting.of("Username", "proxyUser"), - Setting.of("Password", "proxyPassword") + Setting.of("Type", proxyType), + Setting.of("Host", proxyHost), + Setting.of("Port", proxyPort), + Setting.of("Username", proxyUser), + Setting.of("Password", proxyPassword) ) ) ); setContent(prefs.getView()); } - private Object[] getTabNames() { - List tabNames = new ArrayList<>(); - for (Tab tab : getTabPane().getTabs()) { - tabNames.add(tab.getText()); + private List getTabNames() { + return getTabPane().getTabs().stream() + .map(Tab::getText) + .collect(Collectors.toList()); + } + + private List getSplitOptions() { + List splitOptions = new ArrayList<>(); + splitOptions.add(new SplitAfterOption("disabled", 0)); + if (Config.isDevMode()) { + splitOptions.add(new SplitAfterOption("1 min", 1 * 60)); + splitOptions.add(new SplitAfterOption("3 min", 3 * 60)); + } + splitOptions.add(new SplitAfterOption("5 min", 5 * 60)); + splitOptions.add(new SplitAfterOption("10 min", 10 * 60)); + splitOptions.add(new SplitAfterOption("15 min", 15 * 60)); + splitOptions.add(new SplitAfterOption("20 min", 20 * 60)); + splitOptions.add(new SplitAfterOption("30 min", 30 * 60)); + splitOptions.add(new SplitAfterOption("60 min", 60 * 60)); + return splitOptions; + } + + private void requireAuthenticationChanged(ObservableValue obs, Boolean oldV, Boolean newV) { // NOSONAR + boolean requiresAuthentication = newV; + Config.getInstance().getSettings().requireAuthentication = requiresAuthentication; + if (requiresAuthentication) { + byte[] key = Config.getInstance().getSettings().key; + if (key == null) { + key = Hmac.generateKey(); + Config.getInstance().getSettings().key = key; + saveConfig(); + } + TextInputDialog keyDialog = new TextInputDialog(); + keyDialog.setResizable(true); + keyDialog.setTitle("Server Authentication"); + keyDialog.setHeaderText("A key has been generated"); + keyDialog.setContentText("Add this setting to your server's config.json:\n"); + keyDialog.getEditor().setText("\"key\": " + Arrays.toString(key)); + keyDialog.getEditor().setEditable(false); + keyDialog.setWidth(800); + keyDialog.setHeight(200); + keyDialog.show(); + } + } + + public void saveConfig() { + try { + Config.getInstance().save(); + } catch (IOException e) { + LOG.error("Couldn't save config", e); } - return tabNames.toArray(new String[0]); } @Override public void selected() { if (!initialized) { + initializeProperties(); createGui(); initialized = true; } + ignoreList.refresh(); } @Override diff --git a/client/src/main/java/ctbrec/ui/settings/api/ExclusiveSelectionProperty.java b/client/src/main/java/ctbrec/ui/settings/api/ExclusiveSelectionProperty.java new file mode 100644 index 00000000..fd367819 --- /dev/null +++ b/client/src/main/java/ctbrec/ui/settings/api/ExclusiveSelectionProperty.java @@ -0,0 +1,23 @@ +package ctbrec.ui.settings.api; + +import javafx.beans.property.SimpleBooleanProperty; + +public class ExclusiveSelectionProperty extends SimpleBooleanProperty { + + private String optionA; + private String optionB; + + public ExclusiveSelectionProperty(Object bean, String name, boolean value, String optionA, String optionB) { + super(bean, name, value); + this.optionA = optionA; + this.optionB = optionB; + } + + public String getOptionA() { + return optionA; + } + + public String getOptionB() { + return optionB; + } +} diff --git a/client/src/main/java/ctbrec/ui/settings/api/GigabytesConverter.java b/client/src/main/java/ctbrec/ui/settings/api/GigabytesConverter.java new file mode 100644 index 00000000..f007e20d --- /dev/null +++ b/client/src/main/java/ctbrec/ui/settings/api/GigabytesConverter.java @@ -0,0 +1,19 @@ +package ctbrec.ui.settings.api; + +public class GigabytesConverter implements ValueConverter { + + private static final int ONE_GIB_IN_BYTES = 1024 * 1024 * 1024; + + @Override + public Object convertTo(Object a) { + long input = (long) a; + return input / ONE_GIB_IN_BYTES; + } + + @Override + public Object convertFrom(Object b) { + long spaceLeftInGiB = (long) b; + return spaceLeftInGiB * ONE_GIB_IN_BYTES; + } + +} diff --git a/client/src/main/java/ctbrec/ui/settings/api/HighlightingSupport.java b/client/src/main/java/ctbrec/ui/settings/api/HighlightingSupport.java index 5776f54a..ab14d273 100644 --- a/client/src/main/java/ctbrec/ui/settings/api/HighlightingSupport.java +++ b/client/src/main/java/ctbrec/ui/settings/api/HighlightingSupport.java @@ -15,12 +15,12 @@ class HighlightingSupport { private HighlightingSupport() {} - static void highlightMatchess(Category cat, String filter) { + static void highlightMatches(Category cat, String filter) { Node node = cat.getGuiOrElse(Label::new); highlightMatchess(node, filter); if(cat.hasSubCategories()) { for (Category sub : cat.getSubCategories()) { - highlightMatchess(sub, filter); + highlightMatches(sub, filter); } } } @@ -59,7 +59,7 @@ class HighlightingSupport { contains |= ofNullable(((Control) labeledNode).getTooltip()).map(Tooltip::getText).orElse("").toLowerCase().contains(filter); } if (labeledNode instanceof TextInputControl) { - contains |= ((TextInputControl) labeledNode).getText().toLowerCase().contains(filter); + contains |= ofNullable(((TextInputControl) labeledNode).getText()).orElse("").toLowerCase().contains(filter); } } return contains; diff --git a/client/src/main/java/ctbrec/ui/settings/api/Preferences.java b/client/src/main/java/ctbrec/ui/settings/api/Preferences.java index b1520564..539817fa 100644 --- a/client/src/main/java/ctbrec/ui/settings/api/Preferences.java +++ b/client/src/main/java/ctbrec/ui/settings/api/Preferences.java @@ -102,7 +102,7 @@ public class Preferences { for (Category category : categories) { if (q.length() > 2) { - HighlightingSupport.highlightMatchess(category, q); + HighlightingSupport.highlightMatches(category, q); } else { HighlightingSupport.removeHighlights(category); } diff --git a/client/src/main/java/ctbrec/ui/settings/api/SelectionSetting.java b/client/src/main/java/ctbrec/ui/settings/api/SelectionSetting.java deleted file mode 100644 index 4dec3327..00000000 --- a/client/src/main/java/ctbrec/ui/settings/api/SelectionSetting.java +++ /dev/null @@ -1,15 +0,0 @@ -package ctbrec.ui.settings.api; - -public class SelectionSetting extends Setting { - - private Object[] options; - - public SelectionSetting(String name, String key, Object[] options) { - super(name, key); - this.options = options; - } - - public Object[] getOptions() { - return options; - } -} diff --git a/client/src/main/java/ctbrec/ui/settings/api/Setting.java b/client/src/main/java/ctbrec/ui/settings/api/Setting.java index 63f842b9..89f90298 100644 --- a/client/src/main/java/ctbrec/ui/settings/api/Setting.java +++ b/client/src/main/java/ctbrec/ui/settings/api/Setting.java @@ -11,35 +11,35 @@ import javafx.scene.control.Tooltip; public class Setting { private String name; - private String key; private String tooltip; private Property property; private Node gui; private PreferencesStorage preferencesStorage; private boolean needsRestart = false; + private ValueConverter converter; - protected Setting(String name, String key) { + protected Setting(String name, Property property) { this.name = name; - this.key = key; + this.property = property; } - public static Setting of(String name, String key) { - return new Setting(name, key); + protected Setting(String name, Node gui) { + this.name = name; + this.gui = gui; } - public static Setting of(String name, String key, String tooltip) { - Setting setting = new Setting(name, key); + public static Setting of(String name, Property property) { + return new Setting(name, property); + } + + public static Setting of(String name, Property property, String tooltip) { + Setting setting = new Setting(name, property); setting.tooltip = tooltip; return setting; } - public static Setting of(String name, String key, Object...options) { - return new SelectionSetting(name, key, options); - } - - public static Setting of(String name, String key, Node gui) { - Setting setting = new Setting(name, key); - setting.gui = gui; + public static Setting of(String name, Node gui) { + Setting setting = new Setting(name, gui); return setting; } @@ -48,7 +48,15 @@ public class Setting { } public String getKey() { - return key; + if (getProperty() == null) { + return ""; + } else { + String key = getProperty().getName(); + if (StringUtil.isBlank(key)) { + throw new IllegalStateException("Name for property of setting [" + name + "] is null"); + } + return key; + } } public String getTooltip() { @@ -65,7 +73,7 @@ public class Setting { } @SuppressWarnings("rawtypes") - Property getProperty() { + public Property getProperty() { return property; } @@ -87,7 +95,16 @@ public class Setting { public boolean contains(String filter) { boolean contains = name.toLowerCase().contains(filter) || ofNullable(tooltip).orElse("").toLowerCase().contains(filter) - || property != null && property.getValue().toString().toLowerCase().contains(filter); + || ofNullable(property).map(Property::getValue).map(Object::toString).orElse("").toLowerCase().contains(filter); return contains; } + + public Setting converter(ValueConverter converter) { + this.converter = converter; + return this; + } + + public ValueConverter getConverter() { + return converter; + } } diff --git a/client/src/main/java/ctbrec/ui/settings/api/SimpleDirectoryProperty.java b/client/src/main/java/ctbrec/ui/settings/api/SimpleDirectoryProperty.java new file mode 100644 index 00000000..0fd61c3b --- /dev/null +++ b/client/src/main/java/ctbrec/ui/settings/api/SimpleDirectoryProperty.java @@ -0,0 +1,11 @@ +package ctbrec.ui.settings.api; + +import javafx.beans.property.SimpleStringProperty; + +public class SimpleDirectoryProperty extends SimpleStringProperty { + + public SimpleDirectoryProperty(Object bean, String name, String initialValue) { + super(bean, name, initialValue); + } + +} diff --git a/client/src/main/java/ctbrec/ui/settings/api/SimpleFileProperty.java b/client/src/main/java/ctbrec/ui/settings/api/SimpleFileProperty.java new file mode 100644 index 00000000..837ffd3e --- /dev/null +++ b/client/src/main/java/ctbrec/ui/settings/api/SimpleFileProperty.java @@ -0,0 +1,11 @@ +package ctbrec.ui.settings.api; + +import javafx.beans.property.SimpleStringProperty; + +public class SimpleFileProperty extends SimpleStringProperty { + + public SimpleFileProperty(Object bean, String name, String initialValue) { + super(bean, name, initialValue); + } + +} diff --git a/client/src/main/java/ctbrec/ui/settings/api/SimpleRangeProperty.java b/client/src/main/java/ctbrec/ui/settings/api/SimpleRangeProperty.java new file mode 100644 index 00000000..4f99c929 --- /dev/null +++ b/client/src/main/java/ctbrec/ui/settings/api/SimpleRangeProperty.java @@ -0,0 +1,42 @@ +package ctbrec.ui.settings.api; + +import ctbrec.ui.controls.range.Range; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; + +public class SimpleRangeProperty extends SimpleObjectProperty { + + private Range range; + private SimpleObjectProperty lowProperty; + private SimpleObjectProperty highProperty; + private String lowKey; + private String highKey; + + public SimpleRangeProperty(Range range, String lowKey, String highKey, T low, T high) { + this.range = range; + this.lowKey = lowKey; + this.highKey = highKey; + lowProperty = new SimpleObjectProperty<>(low); + highProperty = new SimpleObjectProperty<>(high); + } + + public Range getRange() { + return range; + } + + public ObjectProperty lowProperty() { + return lowProperty; + } + + public ObjectProperty highProperty() { + return highProperty; + } + + public String getLowKey() { + return lowKey; + } + + public String getHighKey() { + return highKey; + } +} diff --git a/client/src/main/java/ctbrec/ui/settings/api/ValueConverter.java b/client/src/main/java/ctbrec/ui/settings/api/ValueConverter.java new file mode 100644 index 00000000..86764ffc --- /dev/null +++ b/client/src/main/java/ctbrec/ui/settings/api/ValueConverter.java @@ -0,0 +1,7 @@ +package ctbrec.ui.settings.api; + +public interface ValueConverter { + + Object convertTo(Object a); + Object convertFrom(Object b); +}