Add all settings to the new settings panel

This commit is contained in:
0xboobface 2020-07-05 14:02:15 +02:00
parent e7349b2118
commit 83775c805a
21 changed files with 806 additions and 240 deletions

View File

@ -54,6 +54,7 @@ import ctbrec.sites.stripchat.Stripchat;
import ctbrec.ui.controls.Dialogs; import ctbrec.ui.controls.Dialogs;
import ctbrec.ui.news.NewsTab; import ctbrec.ui.news.NewsTab;
import ctbrec.ui.settings.SettingsTab; import ctbrec.ui.settings.SettingsTab;
import ctbrec.ui.settings.SettingsTab2;
import ctbrec.ui.tabs.DonateTabFx; import ctbrec.ui.tabs.DonateTabFx;
import ctbrec.ui.tabs.HelpTab; import ctbrec.ui.tabs.HelpTab;
import ctbrec.ui.tabs.RecordedModelsTab; import ctbrec.ui.tabs.RecordedModelsTab;
@ -189,7 +190,7 @@ public class CamrecApplication extends Application {
recordingsTab = new RecordingsTab("Recordings", recorder, config, sites); recordingsTab = new RecordingsTab("Recordings", recorder, config, sites);
tabPane.getTabs().add(recordingsTab); tabPane.getTabs().add(recordingsTab);
settingsTab = new SettingsTab(sites, recorder); 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(settingsTab);
tabPane.getTabs().add(new NewsTab()); tabPane.getTabs().add(new NewsTab());
tabPane.getTabs().add(new DonateTabFx()); tabPane.getTabs().add(new DonateTabFx());

View File

@ -46,9 +46,10 @@ public abstract class AbstractFileSelectionBox extends HBox {
} }
}); });
Node browse = createBrowseButton(); Node browse = createBrowseButton();
getChildren().addAll(fileInput, browse);
fileInput.disableProperty().bind(disableProperty());
browse.disableProperty().bind(disableProperty()); browse.disableProperty().bind(disableProperty());
fileInput.disableProperty().bind(disableProperty());
fileInput.textProperty().bindBidirectional(fileProperty);
getChildren().addAll(fileInput, browse);
HBox.setHgrow(fileInput, Priority.ALWAYS); HBox.setHgrow(fileInput, Priority.ALWAYS);
disabledProperty().addListener((obs, oldV, newV) -> { disabledProperty().addListener((obs, oldV, newV) -> {
@ -70,10 +71,12 @@ public abstract class AbstractFileSelectionBox extends HBox {
private ChangeListener<? super String> textListener() { private ChangeListener<? super String> textListener() {
return (obs, o, n) -> { return (obs, o, n) -> {
String input = fileInput.getText(); String input = fileInput.getText();
if (StringUtil.isBlank(input) && allowEmptyValue) { if (StringUtil.isBlank(input)) {
fileProperty.set(""); if (allowEmptyValue) {
hideValidationHints(); fileProperty.set("");
return; hideValidationHints();
return;
}
} else { } else {
File program = new File(input); File program = new File(input);
setFile(program); setFile(program);
@ -122,6 +125,8 @@ public abstract class AbstractFileSelectionBox extends HBox {
private Button createBrowseButton() { private Button createBrowseButton() {
Button button = new Button("Select"); Button button = new Button("Select");
button.setOnAction(e -> choose()); button.setOnAction(e -> choose());
button.prefHeightProperty().bind(this.heightProperty());
button.prefWidthProperty().set(70);
return button; return button;
} }

View File

@ -1,7 +1,7 @@
package ctbrec.ui.controls.range; package ctbrec.ui.controls.range;
import javafx.beans.property.DoubleProperty; import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleDoubleProperty; import javafx.beans.property.SimpleObjectProperty;
import javafx.geometry.Orientation; import javafx.geometry.Orientation;
import javafx.scene.control.Control; import javafx.scene.control.Control;
import javafx.scene.control.Skin; import javafx.scene.control.Skin;
@ -11,16 +11,17 @@ public class RangeSlider<T extends Number> extends Control {
private static final String DEFAULT_STYLE_CLASS = "rangeslider"; private static final String DEFAULT_STYLE_CLASS = "rangeslider";
private Range<T> range; private Range<T> range;
private DoubleProperty low; private ObjectProperty<T> low;
private DoubleProperty high; private ObjectProperty<T> high;
private boolean showTickMarks = false; private boolean showTickMarks = false;
private boolean showTickLabels = false; private boolean showTickLabels = false;
private Orientation orientation = Orientation.HORIZONTAL; private Orientation orientation = Orientation.HORIZONTAL;
@SuppressWarnings({ "unchecked", "rawtypes" })
public RangeSlider(Range<T> range) { public RangeSlider(Range<T> range) {
this.range = range; this.range = range;
low = new SimpleDoubleProperty(getMinimum().doubleValue()); low = new SimpleObjectProperty(getMinimum());
high = new SimpleDoubleProperty(getMaximum().doubleValue()); high = new SimpleObjectProperty(getMaximum());
getStyleClass().setAll(DEFAULT_STYLE_CLASS); getStyleClass().setAll(DEFAULT_STYLE_CLASS);
} }
@ -34,20 +35,20 @@ public class RangeSlider<T extends Number> extends Control {
return RangeSlider.class.getResource("rangeslider.css").toExternalForm(); return RangeSlider.class.getResource("rangeslider.css").toExternalForm();
} }
public DoubleProperty getLow() { public ObjectProperty<T> getLow() {
return low; return low;
} }
public void setLow(T newPosition) { public void setLow(T newPosition) {
low.set(newPosition.doubleValue()); low.set(newPosition);
} }
public DoubleProperty getHigh() { public ObjectProperty<T> getHigh() {
return high; return high;
} }
public void setHigh(T newPosition) { public void setHigh(T newPosition) {
this.high.set(newPosition.doubleValue()); this.high.set(newPosition);
} }
public T getMinimum() { public T getMinimum() {

View File

@ -70,11 +70,11 @@ public class RangeSliderBehavior<T extends Number> extends BehaviorBase<RangeSli
} }
private T getLow() { private T getLow() {
return rangeSlider.getRange().getTicks().get(rangeSlider.getLow().intValue()); return rangeSlider.getRange().getTicks().get(rangeSlider.getLow().get().intValue());
} }
private T getHigh() { private T getHigh() {
return rangeSlider.getRange().getTicks().get(rangeSlider.getHigh().intValue()); return rangeSlider.getRange().getTicks().get(rangeSlider.getHigh().get().intValue());
} }
/** /**

View File

@ -42,7 +42,7 @@ public class RangeSliderSkin extends SkinBase<RangeSlider<?>> {
t.low.setOnMousePressed(me -> { t.low.setOnMousePressed(me -> {
preDragThumbPoint = t.low.localToParent(me.getX(), me.getY()); 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 -> { t.low.setOnMouseDragged(me -> {
Point2D cur = t.low.localToParent(me.getX(), me.getY()); Point2D cur = t.low.localToParent(me.getX(), me.getY());
@ -52,7 +52,7 @@ public class RangeSliderSkin extends SkinBase<RangeSlider<?>> {
t.high.setOnMousePressed(me -> { t.high.setOnMousePressed(me -> {
preDragThumbPoint = t.high.localToParent(me.getX(), me.getY()); 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 -> { t.high.setOnMouseDragged(me -> {
boolean orientation = getSkinnable().getOrientation() == Orientation.HORIZONTAL; boolean orientation = getSkinnable().getOrientation() == Orientation.HORIZONTAL;
@ -160,8 +160,8 @@ public class RangeSliderSkin extends SkinBase<RangeSlider<?>> {
private void positionThumbs() { private void positionThumbs() {
RangeSlider<?> s = getSkinnable(); RangeSlider<?> s = getSkinnable();
double lxl = trackStart + (trackLength * ((s.getLow().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().doubleValue() - s.getMinimum().doubleValue()) / (getMaxMinusMinNoZero())) - thumbWidth / 2D); double lxh = trackStart + (trackLength * ((s.getHigh().get().doubleValue() - s.getMinimum().doubleValue()) / (getMaxMinusMinNoZero())) - thumbWidth / 2D);
double ly = lowThumbPos; double ly = lowThumbPos;
if (thumbRange != null) { if (thumbRange != null) {

View File

@ -50,9 +50,7 @@ import javafx.scene.control.ScrollPane;
import javafx.scene.control.SelectionMode; import javafx.scene.control.SelectionMode;
import javafx.scene.control.Separator; import javafx.scene.control.Separator;
import javafx.scene.control.TextField; import javafx.scene.control.TextField;
import javafx.scene.control.TitledPane;
import javafx.scene.image.Image; import javafx.scene.image.Image;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.GridPane; import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox; import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane; import javafx.scene.layout.Pane;
@ -61,8 +59,8 @@ import javafx.stage.Modality;
import javafx.stage.Stage; import javafx.stage.Stage;
import javafx.stage.Window; import javafx.stage.Window;
public class ActionSettingsPanel extends TitledPane { public class ActionSettingsPanel extends Pane {
private static final transient Logger LOG = LoggerFactory.getLogger(ActionSettingsPanel.class); private static final Logger LOG = LoggerFactory.getLogger(ActionSettingsPanel.class);
private ListView<EventHandlerConfiguration> actionTable; private ListView<EventHandlerConfiguration> actionTable;
private TextField name = new TextField(); private TextField name = new TextField();
@ -80,11 +78,8 @@ public class ActionSettingsPanel extends TitledPane {
private Recorder recorder; private Recorder recorder;
public ActionSettingsPanel(SettingsTab settingsTab, Recorder recorder) { public ActionSettingsPanel(Recorder recorder) {
this.recorder = recorder; this.recorder = recorder;
setText("Events & Actions");
setExpanded(true);
setCollapsible(false);
createGui(); createGui();
loadEventHandlers(); loadEventHandlers();
} }
@ -94,15 +89,22 @@ public class ActionSettingsPanel extends TitledPane {
} }
private void createGui() { private void createGui() {
BorderPane mainLayout = new BorderPane(); GridPane grid = new GridPane();
setContent(mainLayout); 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(); actionTable = createActionTable();
ScrollPane scrollPane = new ScrollPane(actionTable); ScrollPane scrollPane = new ScrollPane(actionTable);
scrollPane.setFitToHeight(true); scrollPane.setFitToHeight(true);
scrollPane.setFitToWidth(true); scrollPane.setFitToWidth(true);
scrollPane.setStyle("-fx-background-color: -fx-background"); scrollPane.setStyle("-fx-background-color: -fx-background");
mainLayout.setCenter(scrollPane); grid.add(scrollPane, 0, 1);
Button add = new Button("Add"); Button add = new Button("Add");
add.setOnAction(this::add); add.setOnAction(this::add);
@ -110,15 +112,10 @@ public class ActionSettingsPanel extends TitledPane {
delete.setOnAction(this::delete); delete.setOnAction(this::delete);
delete.setDisable(true); delete.setDisable(true);
HBox buttons = new HBox(5, add, delete); HBox buttons = new HBox(5, add, delete);
mainLayout.setBottom(buttons); buttons.setStyle("-fx-background-color: -fx-background"); // workaround so that the buttons don't shrink
BorderPane.setMargin(buttons, new Insets(5, 0, 0, 0)); grid.add(buttons, 0, 2);
actionTable.getSelectionModel().getSelectedItems().addListener(new ListChangeListener<EventHandlerConfiguration>() { actionTable.getSelectionModel().getSelectedItems().addListener((ListChangeListener<EventHandlerConfiguration>) change -> delete.setDisable(change.getList().isEmpty()));
@Override
public void onChanged(Change<? extends EventHandlerConfiguration> change) {
delete.setDisable(change.getList().isEmpty());
}
});
} }
private void add(ActionEvent evt) { private void add(ActionEvent evt) {
@ -235,9 +232,7 @@ public class ActionSettingsPanel extends TitledPane {
event.getItems().clear(); event.getItems().clear();
event.getItems().add(Event.Type.MODEL_STATUS_CHANGED); event.getItems().add(Event.Type.MODEL_STATUS_CHANGED);
event.getItems().add(Event.Type.RECORDING_STATUS_CHANGED); event.getItems().add(Event.Type.RECORDING_STATUS_CHANGED);
event.setOnAction(evt -> { event.setOnAction(evt -> modelState.setVisible(event.getSelectionModel().getSelectedItem() == Event.Type.MODEL_STATUS_CHANGED));
modelState.setVisible(event.getSelectionModel().getSelectedItem() == Event.Type.MODEL_STATUS_CHANGED);
});
event.getSelectionModel().select(Event.Type.MODEL_STATUS_CHANGED); event.getSelectionModel().select(Event.Type.MODEL_STATUS_CHANGED);
layout.add(event, 1, row++); layout.add(event, 1, row++);
@ -254,7 +249,7 @@ public class ActionSettingsPanel extends TitledPane {
Label l = new Label("Models"); Label l = new Label("Models");
layout.add(l, 0, row); layout.add(l, 0, row);
modelSelectionPane = new ListSelectionPane<Model>(recorder.getModels(), Collections.emptyList()); modelSelectionPane = new ListSelectionPane<>(recorder.getModels(), Collections.emptyList());
layout.add(modelSelectionPane, 1, row++); layout.add(modelSelectionPane, 1, row++);
GridPane.setValignment(l, VPos.TOP); GridPane.setValignment(l, VPos.TOP);
GridPane.setHgrow(modelSelectionPane, Priority.ALWAYS); GridPane.setHgrow(modelSelectionPane, Priority.ALWAYS);

View File

@ -2,7 +2,8 @@ package ctbrec.ui.settings;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.Arrays; import java.util.List;
import java.util.Objects;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -10,16 +11,33 @@ import org.slf4j.LoggerFactory;
import ctbrec.Config; import ctbrec.Config;
import ctbrec.Settings; import ctbrec.Settings;
import ctbrec.StringUtil; 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.Preferences;
import ctbrec.ui.settings.api.PreferencesStorage; import ctbrec.ui.settings.api.PreferencesStorage;
import ctbrec.ui.settings.api.SelectionSetting;
import ctbrec.ui.settings.api.Setting; 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.Node;
import javafx.scene.control.CheckBox; import javafx.scene.control.CheckBox;
import javafx.scene.control.ComboBox; import javafx.scene.control.ComboBox;
import javafx.scene.control.Label; import javafx.scene.control.Label;
import javafx.scene.control.RadioButton;
import javafx.scene.control.TextField; 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 { public class CtbrecPreferencesStorage implements PreferencesStorage {
@ -37,7 +55,7 @@ public class CtbrecPreferencesStorage implements PreferencesStorage {
@Override @Override
public void save(Preferences preferences) throws IOException { public void save(Preferences preferences) throws IOException {
config.save(); throw new RuntimeException("not implemented");
} }
@Override @Override
@ -47,69 +65,206 @@ public class CtbrecPreferencesStorage implements PreferencesStorage {
@Override @Override
public Node createGui(Setting setting) throws Exception { public Node createGui(Setting setting) throws Exception {
String key = setting.getKey(); Property<?> prop = setting.getProperty();
Field field = Settings.class.getField(key); if (prop instanceof ExclusiveSelectionProperty) {
Class<?> t = field.getType(); return createRadioGroup(setting);
Object value = field.get(settings); } else if (prop instanceof SimpleRangeProperty) {
if(setting instanceof SelectionSetting) { return createRangeSlider(setting);
return createComboBox(key, ((SelectionSetting) setting).getOptions()); } else if (prop instanceof SimpleDirectoryProperty) {
} else if (t == String.class) { return createDirectorySelector(setting);
return createStringProperty(key, (String) value); } else if (prop instanceof SimpleFileProperty) {
} else if (t == int.class || t == Integer.class) { return createFileSelector(setting);
return createIntegerProperty(key, (Integer) value); } else if (prop instanceof IntegerProperty) {
} else if (t == boolean.class || t == Boolean.class) { return createIntegerProperty(setting);
return createBooleanProperty(key, (Boolean) value); } 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 { } 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) { private Node createRadioGroup(Setting setting) {
TextField ctrl = new TextField(value); ExclusiveSelectionProperty prop = (ExclusiveSelectionProperty) setting.getProperty();
ctrl.textProperty().addListener((obs, oldV, newV) -> saveValue(() -> { ToggleGroup toggleGroup = new ToggleGroup();
Field field = Settings.class.getField(fieldName); 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); field.set(settings, newV);
config.save(); 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; return ctrl;
} }
private Node createIntegerProperty(String fieldName, Integer value) { @SuppressWarnings("unchecked")
TextField ctrl = new TextField(value.toString()); private Node createIntegerProperty(Setting setting) {
TextField ctrl = new TextField();
ctrl.textProperty().addListener((obs, oldV, newV) -> saveValue(() -> { ctrl.textProperty().addListener((obs, oldV, newV) -> saveValue(() -> {
if (!newV.matches("\\d*")) { if (!newV.matches("\\d*")) {
ctrl.setText(newV.replaceAll(PATTERN_NOT_A_DIGIT, "")); ctrl.setText(newV.replaceAll(PATTERN_NOT_A_DIGIT, ""));
} }
if (!ctrl.getText().isEmpty()) { if (!ctrl.getText().isEmpty()) {
Field field = Settings.class.getField(fieldName); Field field = Settings.class.getField(setting.getKey());
field.set(settings, Integer.parseInt(ctrl.getText())); field.set(settings, Integer.parseInt(ctrl.getText()));
config.save(); config.save();
} }
})); }));
Property<Number> prop = setting.getProperty();
ctrl.textProperty().bindBidirectional(prop, new NumberStringConverter());
return ctrl; 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<Number> prop = setting.getProperty();
ctrl.textProperty().bindBidirectional(prop, new NumberStringConverter());
return ctrl;
}
private Node createBooleanProperty(Setting setting) {
CheckBox ctrl = new CheckBox(); CheckBox ctrl = new CheckBox();
ctrl.setSelected(value);
ctrl.selectedProperty().addListener((obs, oldV, newV) -> saveValue(() -> { ctrl.selectedProperty().addListener((obs, oldV, newV) -> saveValue(() -> {
Field field = Settings.class.getField(fieldName); Field field = Settings.class.getField(setting.getKey());
field.set(settings, newV); field.set(settings, newV);
config.save(); config.save();
})); }));
BooleanProperty prop = (BooleanProperty) setting.getProperty();
ctrl.selectedProperty().bindBidirectional(prop);
return ctrl; return ctrl;
} }
private Node createComboBox(String key, Object[] options) throws NoSuchFieldException, IllegalAccessException { @SuppressWarnings({ "rawtypes", "unchecked" })
@SuppressWarnings({ "rawtypes", "unchecked" }) private Node createComboBox(Setting setting) throws NoSuchFieldException, IllegalAccessException {
ComboBox<Object> comboBox = new ComboBox(FXCollections.observableList(Arrays.asList(options))); ListProperty<?> listProp = (ListProperty<?>) setting.getProperty();
Field field = Settings.class.getField(key); ComboBox<Object> comboBox = new ComboBox(listProp);
Field field = Settings.class.getField(setting.getKey());
Object value = field.get(settings); Object value = field.get(settings);
if(StringUtil.isNotBlank(value.toString())) { if (StringUtil.isNotBlank(value.toString())) {
comboBox.getSelectionModel().select(value); if (setting.getConverter() != null) {
comboBox.getSelectionModel().select(setting.getConverter().convertTo(value));
} else {
comboBox.getSelectionModel().select(value);
}
} }
comboBox.valueProperty().addListener((obs, oldV, newV) -> saveValue(() -> { 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(); config.save();
})); }));
return comboBox; return comboBox;

View File

@ -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<Model> ignoreListView;
private List<Site> sites;
public IgnoreList(List<Site> 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<Model> 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<Model> 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<List<Model>> 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<List<Model>> adapter = moshi.adapter(modelListType);
try {
byte[] fileContent = Files.readAllBytes(file.toPath());
List<Model> 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);
}
}
}
}

View File

@ -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<Void> {
private static final Logger LOG = LoggerFactory.getLogger(IgnoreListDialog.class);
private Scene parent;
private ListView<Model> 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<Model> 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<Model> ignored = Config.getInstance().getSettings().modelsIgnored;
ignoreList.getItems().addAll(ignored);
Collections.sort(ignoreList.getItems());
}
}

View File

@ -38,6 +38,7 @@ import ctbrec.ui.controls.DirectorySelectionBox;
import ctbrec.ui.controls.ProgramSelectionBox; import ctbrec.ui.controls.ProgramSelectionBox;
import ctbrec.ui.controls.range.DiscreteRange; import ctbrec.ui.controls.range.DiscreteRange;
import ctbrec.ui.controls.range.RangeSlider; import ctbrec.ui.controls.range.RangeSlider;
import ctbrec.ui.settings.api.ValueConverter;
import ctbrec.ui.sites.ConfigUI; import ctbrec.ui.sites.ConfigUI;
import ctbrec.ui.tabs.TabSelectionListener; import ctbrec.ui.tabs.TabSelectionListener;
import javafx.collections.FXCollections; import javafx.collections.FXCollections;
@ -159,7 +160,7 @@ public class SettingsTab extends Tab implements TabSelectionListener {
//right side //right side
rightSide.getChildren().add(siteConfigAccordion); rightSide.getChildren().add(siteConfigAccordion);
ActionSettingsPanel actions = new ActionSettingsPanel(this, recorder); ActionSettingsPanel actions = new ActionSettingsPanel(recorder);
rightSide.getChildren().add(actions); rightSide.getChildren().add(actions);
proxySettingsPane = new ProxySettingsPane(this); proxySettingsPane = new ProxySettingsPane(this);
rightSide.getChildren().add(createIgnoreListPanel()); rightSide.getChildren().add(createIgnoreListPanel());
@ -561,7 +562,7 @@ public class SettingsTab extends Tab implements TabSelectionListener {
private Node createIgnoreListPanel() { private Node createIgnoreListPanel() {
GridPane layout = createGridLayout(); GridPane layout = createGridLayout();
Button editIgnoreList = new Button("Edit"); 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); layout.add(editIgnoreList, 0, 0);
Button exportIgnoreList = new Button("Export"); Button exportIgnoreList = new Button("Export");
exportIgnoreList.setOnAction(e -> exportIgnoreList()); exportIgnoreList.setOnAction(e -> exportIgnoreList());
@ -850,7 +851,7 @@ public class SettingsTab extends Tab implements TabSelectionListener {
public void saveConfig() { public void saveConfig() {
if(proxySettingsPane != null) { if(proxySettingsPane != null) {
proxySettingsPane.saveConfig(); //proxySettingsPane.saveConfig();
} }
try { try {
Config.getInstance().save(); Config.getInstance().save();
@ -877,5 +878,39 @@ public class SettingsTab extends Tab implements TabSelectionListener {
public String toString() { public String toString() {
return label; 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);
}
};
}
} }
} }

View File

@ -1,91 +1,284 @@
package ctbrec.ui.settings; package ctbrec.ui.settings;
import static ctbrec.Settings.DirectoryStructure.*;
import static ctbrec.Settings.ProxyType.*; import static ctbrec.Settings.ProxyType.*;
import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ctbrec.Config; import ctbrec.Config;
import ctbrec.Hmac;
import ctbrec.Settings;
import ctbrec.Settings.DirectoryStructure;
import ctbrec.Settings.ProxyType;
import ctbrec.recorder.Recorder; import ctbrec.recorder.Recorder;
import ctbrec.sites.Site; import ctbrec.sites.Site;
import ctbrec.ui.SiteUiFactory; 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.Category;
import ctbrec.ui.settings.api.ExclusiveSelectionProperty;
import ctbrec.ui.settings.api.GigabytesConverter;
import ctbrec.ui.settings.api.Group; import ctbrec.ui.settings.api.Group;
import ctbrec.ui.settings.api.Preferences; import ctbrec.ui.settings.api.Preferences;
import ctbrec.ui.settings.api.Setting; 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 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.Tab;
import javafx.scene.control.TextInputDialog;
public class SettingsTab2 extends Tab implements TabSelectionListener { public class SettingsTab2 extends Tab implements TabSelectionListener {
private static final Logger LOG = LoggerFactory.getLogger(SettingsTab2.class);
private List<Site> sites; private List<Site> sites;
private Recorder recorder; private Recorder recorder;
private Preferences prefs; private Preferences prefs;
private boolean initialized = false; 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<String> startTab;
private SimpleFileProperty mediaPlayer;
private SimpleStringProperty mediaPlayerParams;
private SimpleIntegerProperty maximumResolutionPlayer;
private SimpleBooleanProperty showPlayerStarting;
private SimpleBooleanProperty singlePlayer;
private SimpleListProperty<ProxyType> proxyType;
private SimpleStringProperty proxyHost;
private SimpleStringProperty proxyPort;
private SimpleStringProperty proxyUser;
private SimpleStringProperty proxyPassword;
private SimpleDirectoryProperty recordingsDir;
private SimpleListProperty<DirectoryStructure> directoryStructure;
private SimpleListProperty<SplitAfterOption> splitAfter;
private SimpleRangeProperty<Integer> resolutionRange;
private List<Integer> labels = Arrays.asList(0, 240, 360, 480, 600, 720, 960, 1080, 1440, 2160, 4320, 8640);
private List<Integer> values = Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11);
private DiscreteRange<Integer> 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<Site> sites, Recorder recorder) { public SettingsTab2(List<Site> sites, Recorder recorder) {
this.sites = sites; this.sites = sites;
this.recorder = recorder; this.recorder = recorder;
setText("Settings"); setText("Settings");
setClosable(false); 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() { private void createGui() {
ignoreList = new IgnoreList(sites);
List<Category> siteCategories = new ArrayList<>(); List<Category> siteCategories = new ArrayList<>();
for (Site site : sites) { for (Site site : sites) {
siteCategories.add(Category.of(site.getName(), SiteUiFactory.getUi(site).getConfigUI().createConfigPanel())); 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", Category.of("General",
Group.of("General", Group.of("General",
Setting.of("User-Agent", "httpUserAgent"), Setting.of("User-Agent", httpUserAgent),
Setting.of("User-Agent mobile", "httpUserAgentMobile"), Setting.of("User-Agent mobile", httpUserAgentMobile),
Setting.of("Update overview interval (seconds)", "overviewUpdateIntervalInSecs", "Update the thumbnail overviews every x seconds"), 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("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("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("Enable live previews (experimental)", "livePreviews"), Setting.of("Start Tab", startTab),
Setting.of("Start Tab", "startTab", getTabNames()), Setting.of("Colors (Base / Accent)", new ColorSettingsPane(Config.getInstance()))
Setting.of("Colors (Base / Accent)", null, new ColorSettingsPane(Config.getInstance()))
), ),
Group.of("Player", Group.of("Player",
Setting.of("Player", "mediaPlayer"), Setting.of("Player", mediaPlayer),
Setting.of("Start parameters", "mediaPlayerParams"), Setting.of("Start parameters", mediaPlayerParams),
Setting.of("Maximum resolution (0 = unlimited)", "maximumResolutionPlayer", "video height, e.g. 720 or 1080"), Setting.of("Maximum resolution (0 = unlimited)", maximumResolutionPlayer, "video height, e.g. 720 or 1080"),
Setting.of("Show \"Player Starting\" Message", "showPlayerStarting"), Setting.of("Show \"Player Starting\" Message", showPlayerStarting),
Setting.of("Start only one player at a time", "singlePlayer") 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("Sites", siteCategories.toArray(new Category[0])),
Category.of("Proxy", Category.of("Proxy",
Group.of("Proxy", Group.of("Proxy",
Setting.of("Type", "proxyType", DIRECT, HTTP, SOCKS4, SOCKS5), Setting.of("Type", proxyType),
Setting.of("Host", "proxyHost"), Setting.of("Host", proxyHost),
Setting.of("Port", "proxyPort"), Setting.of("Port", proxyPort),
Setting.of("Username", "proxyUser"), Setting.of("Username", proxyUser),
Setting.of("Password", "proxyPassword") Setting.of("Password", proxyPassword)
) )
) )
); );
setContent(prefs.getView()); setContent(prefs.getView());
} }
private Object[] getTabNames() { private List<String> getTabNames() {
List<String> tabNames = new ArrayList<>(); return getTabPane().getTabs().stream()
for (Tab tab : getTabPane().getTabs()) { .map(Tab::getText)
tabNames.add(tab.getText()); .collect(Collectors.toList());
}
private List<SplitAfterOption> getSplitOptions() {
List<SplitAfterOption> 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 @Override
public void selected() { public void selected() {
if (!initialized) { if (!initialized) {
initializeProperties();
createGui(); createGui();
initialized = true; initialized = true;
} }
ignoreList.refresh();
} }
@Override @Override

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -15,12 +15,12 @@ class HighlightingSupport {
private HighlightingSupport() {} private HighlightingSupport() {}
static void highlightMatchess(Category cat, String filter) { static void highlightMatches(Category cat, String filter) {
Node node = cat.getGuiOrElse(Label::new); Node node = cat.getGuiOrElse(Label::new);
highlightMatchess(node, filter); highlightMatchess(node, filter);
if(cat.hasSubCategories()) { if(cat.hasSubCategories()) {
for (Category sub : cat.getSubCategories()) { 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); contains |= ofNullable(((Control) labeledNode).getTooltip()).map(Tooltip::getText).orElse("").toLowerCase().contains(filter);
} }
if (labeledNode instanceof TextInputControl) { if (labeledNode instanceof TextInputControl) {
contains |= ((TextInputControl) labeledNode).getText().toLowerCase().contains(filter); contains |= ofNullable(((TextInputControl) labeledNode).getText()).orElse("").toLowerCase().contains(filter);
} }
} }
return contains; return contains;

View File

@ -102,7 +102,7 @@ public class Preferences {
for (Category category : categories) { for (Category category : categories) {
if (q.length() > 2) { if (q.length() > 2) {
HighlightingSupport.highlightMatchess(category, q); HighlightingSupport.highlightMatches(category, q);
} else { } else {
HighlightingSupport.removeHighlights(category); HighlightingSupport.removeHighlights(category);
} }

View File

@ -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;
}
}

View File

@ -11,35 +11,35 @@ import javafx.scene.control.Tooltip;
public class Setting { public class Setting {
private String name; private String name;
private String key;
private String tooltip; private String tooltip;
private Property<?> property; private Property<?> property;
private Node gui; private Node gui;
private PreferencesStorage preferencesStorage; private PreferencesStorage preferencesStorage;
private boolean needsRestart = false; private boolean needsRestart = false;
private ValueConverter converter;
protected Setting(String name, String key) { protected Setting(String name, Property<?> property) {
this.name = name; this.name = name;
this.key = key; this.property = property;
} }
public static Setting of(String name, String key) { protected Setting(String name, Node gui) {
return new Setting(name, key); this.name = name;
this.gui = gui;
} }
public static Setting of(String name, String key, String tooltip) { public static Setting of(String name, Property<?> property) {
Setting setting = new Setting(name, key); return new Setting(name, property);
}
public static Setting of(String name, Property<?> property, String tooltip) {
Setting setting = new Setting(name, property);
setting.tooltip = tooltip; setting.tooltip = tooltip;
return setting; return setting;
} }
public static Setting of(String name, String key, Object...options) { public static Setting of(String name, Node gui) {
return new SelectionSetting(name, key, options); Setting setting = new Setting(name, gui);
}
public static Setting of(String name, String key, Node gui) {
Setting setting = new Setting(name, key);
setting.gui = gui;
return setting; return setting;
} }
@ -48,7 +48,15 @@ public class Setting {
} }
public String getKey() { 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() { public String getTooltip() {
@ -65,7 +73,7 @@ public class Setting {
} }
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
Property getProperty() { public Property getProperty() {
return property; return property;
} }
@ -87,7 +95,16 @@ public class Setting {
public boolean contains(String filter) { public boolean contains(String filter) {
boolean contains = name.toLowerCase().contains(filter) boolean contains = name.toLowerCase().contains(filter)
|| ofNullable(tooltip).orElse("").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; return contains;
} }
public Setting converter(ValueConverter converter) {
this.converter = converter;
return this;
}
public ValueConverter getConverter() {
return converter;
}
} }

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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<T> extends SimpleObjectProperty<T> {
private Range<T> range;
private SimpleObjectProperty<T> lowProperty;
private SimpleObjectProperty<T> highProperty;
private String lowKey;
private String highKey;
public SimpleRangeProperty(Range<T> 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<T> getRange() {
return range;
}
public ObjectProperty<T> lowProperty() {
return lowProperty;
}
public ObjectProperty<T> highProperty() {
return highProperty;
}
public String getLowKey() {
return lowKey;
}
public String getHighKey() {
return highKey;
}
}

View File

@ -0,0 +1,7 @@
package ctbrec.ui.settings.api;
public interface ValueConverter {
Object convertTo(Object a);
Object convertFrom(Object b);
}