forked from j62/ctbrec
1
0
Fork 0

Add new settings tab implementation

This commit is contained in:
0xboobface 2020-06-27 18:40:41 +02:00
parent c16baf94e5
commit ec1a0826e0
17 changed files with 933 additions and 24 deletions

View File

@ -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;
@ -190,6 +191,7 @@ public class CamrecApplication extends Application {
tabPane.getTabs().add(recordingsTab);
settingsTab = new SettingsTab(sites, recorder);
tabPane.getTabs().add(settingsTab);
tabPane.getTabs().add(new SettingsTab2(sites, recorder));
tabPane.getTabs().add(new NewsTab());
tabPane.getTabs().add(new DonateTabFx());
tabPane.getTabs().add(new HelpTab());
@ -205,6 +207,7 @@ public class CamrecApplication extends Application {
primaryStage.getScene().getStylesheets().add("/ctbrec/ui/ThumbCell.css");
primaryStage.getScene().getStylesheets().add("/ctbrec/ui/controls/SearchBox.css");
primaryStage.getScene().getStylesheets().add("/ctbrec/ui/controls/Popover.css");
primaryStage.getScene().getStylesheets().add("/ctbrec/ui/settings/api/Preferences.css");
primaryStage.getScene().widthProperty().addListener((observable, oldVal, newVal) -> Config.getInstance().getSettings().windowWidth = newVal.intValue());
primaryStage.getScene().heightProperty()
.addListener((observable, oldVal, newVal) -> Config.getInstance().getSettings().windowHeight = newVal.intValue());

View File

@ -1,7 +1,8 @@
package ctbrec.ui.controls;
package ctbrec.ui.controls.autocomplete;
import javafx.collections.ObservableList;
import java.util.Optional;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.control.TextField;
@ -10,12 +11,12 @@ import javafx.scene.input.KeyEvent;
public class AutoFillTextField extends TextField {
private ObservableList<String> suggestions;
private EventHandler<ActionEvent> handler;
private Suggester suggester;
public AutoFillTextField(ObservableList<String> suggestions) {
this.suggestions = suggestions;
addEventHandler(KeyEvent.KEY_RELEASED, (evt) -> {
public AutoFillTextField(Suggester suggester) {
this.suggester = suggester;
addEventHandler(KeyEvent.KEY_RELEASED, evt -> {
if (evt.getCode().isLetterKey() || evt.getCode().isDigitKey()) {
autocomplete(false);
} else if (evt.getCode() == KeyCode.ENTER) {
@ -38,16 +39,19 @@ public class AutoFillTextField extends TextField {
if(oldtext.isEmpty()) {
return;
}
for (String sug : suggestions) {
boolean startsWith = sug.toLowerCase().startsWith(oldtext.toLowerCase());
boolean textMatch = fulltextSearch && sug.toLowerCase().contains(oldtext.toLowerCase());
if(startsWith || textMatch) {
setText(sug);
Optional<String> match = null;
if(fulltextSearch) {
match = suggester.fulltext(oldtext);
} else {
match = suggester.startsWith(oldtext);
}
if(match.isPresent()) {
setText(match.get());
int pos = oldtext.length();
positionCaret(pos);
selectRange(pos, sug.length());
break;
}
selectRange(pos, match.get().length());
}
}

View File

@ -0,0 +1,37 @@
package ctbrec.ui.controls.autocomplete;
import java.util.Optional;
import javafx.collections.ObservableList;
public class ObservableListSuggester implements Suggester {
private ObservableList<?> suggestions;
public ObservableListSuggester(ObservableList<?> suggestions) {
this.suggestions = suggestions;
}
@Override
public Optional<String> startsWith(String search) {
for (Object sug : suggestions) {
boolean startsWith = sug.toString().toLowerCase().startsWith(search.toLowerCase());
if (startsWith) {
return Optional.of(sug.toString());
}
}
return Optional.empty();
}
@Override
public Optional<String> fulltext(String search) {
for (Object sug : suggestions) {
boolean startsWith = sug.toString().toLowerCase().contains(search.toLowerCase());
if (startsWith) {
return Optional.of(sug.toString());
}
}
return Optional.empty();
}
}

View File

@ -0,0 +1,9 @@
package ctbrec.ui.controls.autocomplete;
import java.util.Optional;
public interface Suggester {
public Optional<String> startsWith(String search);
public Optional<String> fulltext(String search);
}

View File

@ -0,0 +1,5 @@
package ctbrec.ui.controls.autocomplete;
public class Suggestion {
}

View File

@ -0,0 +1,106 @@
package ctbrec.ui.settings;
import java.io.IOException;
import java.lang.reflect.Field;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ctbrec.Config;
import ctbrec.Settings;
import ctbrec.ui.settings.api.Preferences;
import ctbrec.ui.settings.api.PreferencesStorage;
import javafx.scene.Node;
import javafx.scene.control.CheckBox;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
public class CtbrecPreferencesStorage implements PreferencesStorage {
private static final Logger LOG = LoggerFactory.getLogger(CtbrecPreferencesStorage.class);
public static final String PATTERN_NOT_A_DIGIT = "[^\\d]";
public static final String COULDNT_SAVE_MSG = "Couldn't save config setting";
private Config config;
private Settings settings;
public CtbrecPreferencesStorage(Config config) {
this.config = config;
this.settings = config.getSettings();
}
@Override
public void save(Preferences preferences) throws IOException {
config.save();
}
@Override
public void load(Preferences preferences) {
throw new RuntimeException("not implemented");
}
@Override
public Node createGui(String key) throws Exception {
Field field = Settings.class.getField(key);
Class<?> t = field.getType();
Object value = field.get(settings);
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);
} else {
return new Label("Unsupported Type for key " + key + ": " + t);
}
}
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);
field.set(settings, newV);
config.save();
}));
return ctrl;
}
private Node createIntegerProperty(String fieldName, Integer value) {
TextField ctrl = new TextField(value.toString());
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.set(settings, Integer.parseInt(ctrl.getText()));
config.save();
}
}));
return ctrl;
}
private Node createBooleanProperty(String fieldName, Boolean value) {
CheckBox ctrl = new CheckBox();
ctrl.setSelected(value);
ctrl.selectedProperty().addListener((obs, oldV, newV) -> saveValue(() -> {
Field field = Settings.class.getField(fieldName);
field.set(settings, newV);
config.save();
}));
return ctrl;
}
private void saveValue(Exec exe) {
try {
exe.run();
} catch (Exception e) {
LOG.error(COULDNT_SAVE_MSG, e);
}
}
@FunctionalInterface
private interface Exec {
public void run() throws Exception;
}
}

View File

@ -0,0 +1,101 @@
package ctbrec.ui.settings;
import java.io.IOException;
import java.io.InputStream;
import ctbrec.Config;
import ctbrec.Settings;
import ctbrec.ui.controls.Dialogs;
import javafx.application.Platform;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.ButtonType;
import javafx.scene.control.Dialog;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.control.Tooltip;
import javafx.scene.image.Image;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Priority;
import javafx.stage.Modality;
import javafx.stage.Stage;
public class PostProcessingSettingsDialog extends Dialog<Exception> {
private Scene parent;
private Config config;
private Settings settings;
private TextField playerParams;
private TextField maxResolution;
public PostProcessingSettingsDialog(Scene parent, Config config) {
this.parent = parent;
this.config = config;
this.settings = config.getSettings();
initGui();
}
private void initGui() {
setTitle("Player Settings");
setHeaderText("Player Settings");
getDialogPane().getButtonTypes().addAll(ButtonType.OK, ButtonType.CANCEL);
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));
grid.add(new Label("Start parameters"), 0, 0);
playerParams = new TextField(settings.mediaPlayerParams);
grid.add(playerParams, 1, 0);
getDialogPane().setContent(grid);
GridPane.setFillWidth(playerParams, true);
GridPane.setHgrow(playerParams, Priority.ALWAYS);
Label l = new Label("Maximum resolution (0 = unlimited)");
grid.add(l, 0, 1);
maxResolution = new TextField(Integer.toString(settings.maximumResolutionPlayer));
Tooltip tt = new Tooltip("video height, e.g. 720 or 1080");
l.setTooltip(tt);
maxResolution.setTooltip(tt);
grid.add(maxResolution, 1, 1);
getDialogPane().setContent(grid);
GridPane.setFillWidth(maxResolution, true);
GridPane.setHgrow(maxResolution, Priority.ALWAYS);
Platform.runLater(playerParams::requestFocus);
setResultConverter(dialogButton -> {
try {
if (dialogButton == ButtonType.OK) {
saveSettings();
}
return null;
} catch (IOException e) {
return e;
}
});
}
public void saveSettings() throws IOException {
settings.mediaPlayerParams = playerParams.getText();
String res = maxResolution.getText();
if (res.matches("\\d+")) {
int newRes = Integer.parseInt(maxResolution.getText());
if (newRes != Config.getInstance().getSettings().maximumResolutionPlayer) {
settings.maximumResolutionPlayer = newRes;
}
}
config.save();
}
}

View File

@ -0,0 +1,66 @@
package ctbrec.ui.settings;
import java.util.ArrayList;
import java.util.List;
import ctbrec.Config;
import ctbrec.recorder.Recorder;
import ctbrec.sites.Site;
import ctbrec.ui.SiteUiFactory;
import ctbrec.ui.settings.api.Category;
import ctbrec.ui.settings.api.Group;
import ctbrec.ui.settings.api.Preferences;
import ctbrec.ui.settings.api.Setting;
import ctbrec.ui.tabs.TabSelectionListener;
import javafx.scene.control.Tab;
public class SettingsTab2 extends Tab implements TabSelectionListener {
private List<Site> sites;
private Recorder recorder;
public SettingsTab2(List<Site> sites, Recorder recorder) {
this.sites = sites;
this.recorder = recorder;
setText("Settings");
createGui();
setClosable(false);
}
private void createGui() {
List<Category> siteCategories = new ArrayList<>();
for (Site site : sites) {
siteCategories.add(Category.of(site.getName(), SiteUiFactory.getUi(site).getConfigUI().createConfigPanel()));
}
Preferences prefs = Preferences.of(new CtbrecPreferencesStorage(Config.getInstance()),
Category.of("General",
Group.of("General",
Setting.of("Player", "mediaPlayer"),
Setting.of("Start parameters", "mediaPlayerParams"),
Setting.of("Maximum resolution (0 = unlimited)", "maximumResolutionPlayer", "video height, e.g. 720 or 1080")),
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"))
),
Category.of("Sites", siteCategories.toArray(new Category[0]))
);
setContent(prefs.getView());
}
@Override
public void selected() {
// TODO Auto-generated method stub
}
@Override
public void deselected() {
// TODO Auto-generated method stub
}
}

View File

@ -0,0 +1,153 @@
package ctbrec.ui.settings.api;
import static java.util.Optional.*;
import java.util.function.Supplier;
import ctbrec.StringUtil;
import javafx.scene.Node;
import javafx.scene.control.Control;
import javafx.scene.control.Label;
import javafx.scene.control.Tooltip;
import javafx.scene.layout.Pane;
public class Category {
protected String name;
protected Group[] groups;
protected Category[] subCategories;
protected Node gui;
private Category(String name, Group... groups) {
this.name = name;
this.groups = groups;
}
public Category(String name, Category[] subCategories) {
this.name = name;
this.subCategories = subCategories;
groups = new Group[0];
}
public Category(String name, Node gui) {
this.name = name;
this.gui = gui;
groups = new Group[0];
}
public static Category of(String name, Setting... settings) {
return new Category(name, Group.of(settings));
}
public static Category of(String name, Group... groups) {
return new Category(name, groups);
}
public static Category of(String name, Category... subCategories) {
return new Category(name, subCategories);
}
public static Category of(String name, Node gui) {
return new Category(name, gui);
}
String getName() {
return name;
}
Group[] getGroups() {
return groups;
}
Category[] getSubCategories() {
return subCategories;
}
boolean hasGroups() {
return groups != null && groups.length > 0 && !groups[0].isDefault();
}
boolean hasSubCategories() {
return subCategories != null && subCategories.length > 0;
}
Node getGuiOrElse(Supplier<Node> guiFactory) {
if (gui == null) {
gui = guiFactory.get();
}
return gui;
}
@Override
public String toString() {
return name;
}
public boolean contains(String filter) {
if (StringUtil.isBlank(filter)) {
return true;
}
String q = filter.toLowerCase().trim();
if(hasGroups() || hasSubCategories()) {
return name.toLowerCase().contains(q)
| groupsContains(q)
| subCategoriesContains(q);
} else {
return name.toLowerCase().contains(q)
| guiContains(q);
}
}
private boolean subCategoriesContains(String filter) {
boolean contains = false;
if (subCategories != null) {
for (Category category : subCategories) {
if (category.contains(filter)) {
contains = true;
}
}
}
return contains;
}
private boolean groupsContains(String filter) {
boolean contains = false;
if (groups != null) {
for (Group group : groups) {
if (group.contains(filter)) {
contains = true;
}
}
}
return contains;
}
private boolean guiContains(String filter) {
if (gui != null) {
return nodeContains(gui, filter);
}
return false;
}
private boolean nodeContains(Node node, String filter) {
boolean contains = false;
if (node instanceof Pane) {
Pane pane = (Pane) node;
for (Node child : pane.getChildren()) {
contains |= nodeContains(child, filter);
}
}
if (node instanceof Label) {
Label lbl = (Label) node;
contains |= lbl.getText().toLowerCase().contains(filter);
contains |= ofNullable(lbl.getTooltip()).map(Tooltip::getText).orElse("").toLowerCase().contains(filter);
}
if (node instanceof Control) {
contains |= ofNullable(((Control) node).getTooltip()).map(Tooltip::getText).orElse("").toLowerCase().contains(filter);
}
contains |= node.toString().toLowerCase().contains(filter);
return contains;
}
}

View File

@ -0,0 +1,51 @@
package ctbrec.ui.settings.api;
import java.util.Objects;
public class Group {
public static final String DEFAULT = "default";
private String name;
private Setting[] settings;
private Group(String name, Setting...settings) {
this.name = name;
this.settings = settings;
}
public static Group of(Setting...settings) {
return new Group(DEFAULT, settings);
}
public static Group of(String name, Setting...settings) {
return new Group(name, settings);
}
String getName() {
return name;
}
Setting[] getSettings() {
return settings;
}
boolean isDefault() {
return Objects.equals(name, DEFAULT);
}
public boolean contains(String filter) {
return name.toLowerCase().contains(filter) | settingsContain(filter);
}
private boolean settingsContain(String filter) {
boolean contains = false;
if (settings != null) {
for (Setting setting : settings) {
if (setting.contains(filter)) {
contains = true;
}
}
}
return contains;
}
}

View File

@ -0,0 +1,89 @@
package ctbrec.ui.settings.api;
import static java.util.Optional.*;
import javafx.scene.Node;
import javafx.scene.control.Control;
import javafx.scene.control.Label;
import javafx.scene.control.TextInputControl;
import javafx.scene.control.Tooltip;
import javafx.scene.layout.Pane;
class HighlightingSupport {
private static final String CSS_HIGHLIGHT_CLASS = "setting-highlighted";
private HighlightingSupport() {}
static void highlightMatchess(Category cat, String filter) {
Node node = cat.getGuiOrElse(Label::new);
highlightMatchess(node, filter);
if(cat.hasSubCategories()) {
for (Category sub : cat.getSubCategories()) {
highlightMatchess(sub, filter);
}
}
}
static void highlightMatchess(Node node, String filter) {
boolean contains = false;
if (node instanceof Pane) {
Pane pane = (Pane) node;
for (Node child : pane.getChildren()) {
highlightMatchess(child, filter);
}
}
if (node instanceof Label) {
Label lbl = (Label) node;
contains |= lbl.getText().toLowerCase().contains(filter);
contains |= ofNullable(lbl.getTooltip()).map(Tooltip::getText).orElse("").toLowerCase().contains(filter);
contains |= labelControlContains(lbl, filter);
if (contains) {
if (!node.getStyleClass().contains(CSS_HIGHLIGHT_CLASS)) {
node.getStyleClass().add(CSS_HIGHLIGHT_CLASS);
}
} else {
node.getStyleClass().remove(CSS_HIGHLIGHT_CLASS);
}
}
}
private static boolean labelControlContains(Label lbl, String filter) {
boolean contains = false;
if (lbl.labelForProperty().get() != null) {
Node labeledNode = lbl.labelForProperty().get();
contains |= labeledNode.toString().toLowerCase().contains(filter);
if (labeledNode instanceof Control) {
contains |= ofNullable(((Control) labeledNode).getTooltip()).map(Tooltip::getText).orElse("").toLowerCase().contains(filter);
}
if (labeledNode instanceof TextInputControl) {
contains |= ((TextInputControl) labeledNode).getText().toLowerCase().contains(filter);
}
}
return contains;
}
static void removeHighlights(Category cat) {
Node node = cat.getGuiOrElse(Label::new);
removeHighlights(node);
if(cat.hasSubCategories()) {
for (Category sub : cat.getSubCategories()) {
removeHighlights(sub);
}
}
}
static void removeHighlights(Node gui) {
if (gui != null) {
if (gui instanceof Pane) {
Pane p = (Pane) gui;
for (Node n : p.getChildren()) {
removeHighlights(n);
}
}
gui.getStyleClass().remove(CSS_HIGHLIGHT_CLASS);
}
}
}

View File

@ -0,0 +1,8 @@
.settings-group-label {
-fx-font-size: 1.6em;
}
.setting-highlighted {
-fx-border-color: -fx-accent;
-fx-border-width: 3;
}

View File

@ -0,0 +1,182 @@
package ctbrec.ui.settings.api;
import static java.util.Optional.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ctbrec.ui.controls.SearchBox;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Insets;
import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.control.Tooltip;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
public class Preferences {
private static final Logger LOG = LoggerFactory.getLogger(Preferences.class);
private Category[] categories;
private TreeView<Category> categoryTree;
private Preferences(PreferencesStorage preferencesStorage, Category...categories) {
this.categories = categories;
for (Category category : categories) {
assignPreferencesStorage(category, preferencesStorage);
}
}
private void assignPreferencesStorage(Category cat, PreferencesStorage preferencesStorage) {
if(cat.hasSubCategories()) {
for (Category sub : cat.getSubCategories()) {
assignPreferencesStorage(sub, preferencesStorage);
}
} else {
for (Group group : cat.getGroups()) {
for (Setting setting : group.getSettings()) {
setting.setPreferencesStorage(preferencesStorage);
}
}
}
}
public static Preferences of(PreferencesStorage preferencesStorage, Category... categories) {
return new Preferences(preferencesStorage, categories);
}
public void save() {
throw new RuntimeException("save not implemented");
}
Category[] getCategories() {
return categories;
}
public Node getView() {
SearchBox search = new SearchBox(true);
search.textProperty().addListener(this::filterTree);
TreeItem<Category> categoryTreeItems = createCategoryTree(categories, new TreeItem<>(), null);
categoryTree = new TreeView<>(categoryTreeItems);
categoryTree.showRootProperty().set(false);
VBox leftSide = new VBox(search, categoryTree);
VBox.setVgrow(categoryTree, Priority.ALWAYS);
VBox.setMargin(search, new Insets(2));
VBox.setMargin(categoryTree, new Insets(2));
BorderPane main = new BorderPane();
main.setLeft(leftSide);
main.setCenter(new Label("Center"));
BorderPane.setMargin(leftSide, new Insets(2));
categoryTree.getSelectionModel().selectedItemProperty().addListener((obs, oldV, newV) -> {
if (newV != null) {
Category cat = newV.getValue();
Node gui = cat.getGuiOrElse(() -> createGui(cat));
BorderPane.setMargin(gui, new Insets(10));
main.setCenter(gui);
}
});
return main;
}
private void filterTree(ObservableValue<? extends String> obs, String oldV, String newV) {
String q = ofNullable(newV).orElse("").toLowerCase().trim();
TreeItem<Category> filteredCategoryTree = createCategoryTree(categories, new TreeItem<>(), q);
categoryTree.setRoot(filteredCategoryTree);
expandAll(categoryTree.getRoot());
TreeItem<Category> parent = categoryTree.getRoot();
while (!parent.getChildren().isEmpty()) {
parent = parent.getChildren().get(0);
categoryTree.getSelectionModel().select(parent);
}
for (Category category : categories) {
if (q.length() > 2) {
HighlightingSupport.highlightMatchess(category, q);
} else {
HighlightingSupport.removeHighlights(category);
}
}
}
private void expandAll(TreeItem<Category> treeItem) {
treeItem.setExpanded(true);
for (TreeItem<Category> child : treeItem.getChildren()) {
expandAll(child);
}
}
private Node createGui(Category cat) {
try {
if (cat.hasSubCategories()) {
return new Label(cat.getName());
} else if(cat.hasGroups()) {
return createPaneWithGroups(cat);
} else {
return createGrid(cat.getGroups()[0].getSettings());
}
} catch(Exception e) {
LOG.error("Error creating the GUI", e);
return new Label(e.getLocalizedMessage());
}
}
private Node createPaneWithGroups(Category cat) throws Exception {
VBox pane = new VBox();
for (Group grp : cat.getGroups()) {
Label groupLabel = new Label(grp.getName());
groupLabel.getStyleClass().add("settings-group-label");
VBox.setMargin(groupLabel, new Insets(20, 0, 10, 20));
pane.getChildren().add(groupLabel);
Node parameterGrid = createGrid(grp.getSettings());
pane.getChildren().add(parameterGrid);
VBox.setMargin(parameterGrid, new Insets(0, 0, 0, 40));
}
return pane;
}
private Node createGrid(Setting[] settings) throws Exception {
GridPane pane = new GridPane();
int row = 0;
for (Setting setting : settings) {
Node node = setting.getGui();
Label label = new Label(setting.getName());
label.labelForProperty().set(node);
if (setting.getTooltip() != null) {
label.setTooltip(new Tooltip(setting.getTooltip()));
}
pane.addRow(row++, label, node);
GridPane.setMargin(node, new Insets(5, 0, 5, 10));
GridPane.setHgrow(node, Priority.ALWAYS);
}
return pane;
}
/**
* Creates a tree of the given categories. Filters out categories, which don't match the filter
* @param filter may be null
*/
private TreeItem<Category> createCategoryTree(Category[] categories, TreeItem<Category> parent, String filter) {
for (Category category : categories) {
TreeItem<Category> child = new TreeItem<>(category);
if (category.hasSubCategories()) {
createCategoryTree(category.getSubCategories(), child, filter);
if (!child.getChildren().isEmpty()) {
parent.getChildren().add(child);
}
} else if(category.contains(filter)) {
parent.getChildren().add(child);
}
}
return parent;
}
}

View File

@ -0,0 +1,13 @@
package ctbrec.ui.settings.api;
import java.io.IOException;
import javafx.scene.Node;
public interface PreferencesStorage {
void save(Preferences preferences) throws IOException;
void load(Preferences preferences);
Node createGui(String key) throws Exception;
}

View File

@ -0,0 +1,73 @@
package ctbrec.ui.settings.api;
import static java.util.Optional.*;
import ctbrec.StringUtil;
import javafx.beans.property.Property;
import javafx.scene.Node;
import javafx.scene.control.Control;
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 Setting(String name, String key) {
this.name = name;
this.key = key;
}
public static Setting of(String name, String key) {
return new Setting(name, key);
}
public static Setting of(String name, String key, String tooltip) {
Setting setting = new Setting(name, key);
setting.tooltip = tooltip;
return setting;
}
String getName() {
return name;
}
String getKey() {
return key;
}
String getTooltip() {
return tooltip;
}
@SuppressWarnings("rawtypes")
Property getProperty() {
return property;
}
Node getGui() throws Exception {
if (gui == null) {
gui = preferencesStorage.createGui(key);
if (gui instanceof Control && StringUtil.isNotBlank(tooltip)) {
Control control = (Control) gui;
control.setTooltip(new Tooltip(tooltip));
}
}
return gui;
}
public void setPreferencesStorage(PreferencesStorage preferencesStorage) {
this.preferencesStorage = preferencesStorage;
}
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);
return contains;
}
}

View File

@ -29,11 +29,9 @@ public class MyFreeCamsConfigUI extends AbstractConfigUI {
GridPane layout = SettingsTab.createGridLayout();
Settings settings = Config.getInstance().getSettings();
Label l = new Label("Active");
layout.add(l, 0, row);
CheckBox enabled = new CheckBox();
enabled.setSelected(!settings.disabledSites.contains(myFreeCams.getName()));
enabled.setOnAction((e) -> {
enabled.setOnAction(e -> {
if(enabled.isSelected()) {
settings.disabledSites.remove(myFreeCams.getName());
} else {
@ -42,10 +40,13 @@ public class MyFreeCamsConfigUI extends AbstractConfigUI {
save();
});
GridPane.setMargin(enabled, new Insets(0, 0, 0, SettingsTab.CHECKBOX_MARGIN));
Label l = new Label("Active");
l.labelForProperty().set(enabled);
layout.add(l, 0, row);
layout.add(enabled, 1, row++);
layout.add(new Label("MyFreeCams User"), 0, row);
TextField username = new TextField(Config.getInstance().getSettings().mfcUsername);
username.setId("mfcUsername");
username.setPrefWidth(300);
username.textProperty().addListener((ob, o, n) -> {
if(!n.equals(Config.getInstance().getSettings().mfcUsername)) {
@ -57,9 +58,11 @@ public class MyFreeCamsConfigUI extends AbstractConfigUI {
GridPane.setFillWidth(username, true);
GridPane.setHgrow(username, Priority.ALWAYS);
GridPane.setColumnSpan(username, 2);
l = new Label("MyFreeCams User");
l.labelForProperty().set(username);
layout.add(l, 0, row);
layout.add(username, 1, row++);
layout.add(new Label("MyFreeCams Password"), 0, row);
PasswordField password = new PasswordField();
password.setText(Config.getInstance().getSettings().mfcPassword);
password.textProperty().addListener((ob, o, n) -> {
@ -72,13 +75,15 @@ public class MyFreeCamsConfigUI extends AbstractConfigUI {
GridPane.setFillWidth(password, true);
GridPane.setHgrow(password, Priority.ALWAYS);
GridPane.setColumnSpan(password, 2);
l = new Label("MyFreeCams Password");
l.labelForProperty().set(password);
layout.add(l, 0, row);
layout.add(password, 1, row++);
Button createAccount = new Button("Create new Account");
createAccount.setOnAction(e -> DesktopIntegration.open(myFreeCams.getAffiliateLink()));
layout.add(createAccount, 1, row++);
layout.add(new Label("MyFreeCams Base URL"), 0, row);
TextField baseUrl = new TextField();
baseUrl.setText(Config.getInstance().getSettings().mfcBaseUrl);
baseUrl.textProperty().addListener((ob, o, n) -> {
@ -88,6 +93,9 @@ public class MyFreeCamsConfigUI extends AbstractConfigUI {
GridPane.setFillWidth(baseUrl, true);
GridPane.setHgrow(baseUrl, Priority.ALWAYS);
GridPane.setColumnSpan(baseUrl, 2);
l = new Label("MyFreeCams Base URL");
l.labelForProperty().set(baseUrl);
layout.add(l, 0, row);
layout.add(baseUrl, 1, row);
GridPane.setColumnSpan(createAccount, 2);

View File

@ -39,10 +39,11 @@ import ctbrec.ui.action.PauseAction;
import ctbrec.ui.action.PlayAction;
import ctbrec.ui.action.ResumeAction;
import ctbrec.ui.action.StopRecordingAction;
import ctbrec.ui.controls.AutoFillTextField;
import ctbrec.ui.controls.DateTimeCellFactory;
import ctbrec.ui.controls.Dialogs;
import ctbrec.ui.controls.SearchBox;
import ctbrec.ui.controls.autocomplete.AutoFillTextField;
import ctbrec.ui.controls.autocomplete.ObservableListSuggester;
import javafx.application.Platform;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringPropertyBase;
@ -254,7 +255,7 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
modelLabel.setPadding(new Insets(5, 0, 0, 0));
ObservableList<String> suggestions = FXCollections.observableArrayList();
sites.forEach(site -> suggestions.add(site.getClass().getSimpleName()));
model = new AutoFillTextField(suggestions);
model = new AutoFillTextField(new ObservableListSuggester(suggestions));
model.setPrefWidth(600);
model.setPromptText("e.g. MyFreeCams:ModelName or an URL like https://chaturbate.com/modelname/");
model.onActionHandler(this::addModel);