Add font setting to settings tab

This commit is contained in:
0xb00bface 2021-08-26 17:03:25 +02:00
parent ffb1997e24
commit b1f3390a69
4 changed files with 158 additions and 108 deletions

View File

@ -255,6 +255,7 @@ public class CamrecApplication extends Application {
loadStyleSheet(primaryStage, "color.css");
}
loadStyleSheet(primaryStage, "style.css");
loadStyleSheet(primaryStage, "font.css");
primaryStage.getScene().getStylesheets().add("/ctbrec/ui/settings/ColorSettingsPane.css");
primaryStage.getScene().getStylesheets().add("/ctbrec/ui/controls/SearchBox.css");
primaryStage.getScene().getStylesheets().add("/ctbrec/ui/controls/Popover.css");

View File

@ -0,0 +1,103 @@
package ctbrec.ui.settings;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ctbrec.Config;
import ctbrec.ui.controls.Dialogs;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.control.Button;
import javafx.scene.control.ComboBox;
import javafx.scene.control.ListCell;
import javafx.scene.layout.HBox;
import javafx.scene.text.Font;
public class FontSettingsPane extends HBox {
private static final Logger LOG = LoggerFactory.getLogger(FontSettingsPane.class);
private ComboBox<String> fontFaceCombo;
private ComboBox<Integer> fontSizeCombo;
private SettingsTab settingsTab;
private Config config;
public FontSettingsPane(SettingsTab settingsTab, Config config) {
this.settingsTab = settingsTab;
this.config = config;
setSpacing(5);
getChildren().addAll(buildFontFaceCombo(), buildFontSizeCombo(), buildFontResetButton());
}
private ComboBox<String> buildFontFaceCombo() {
ObservableList<String> lst = FXCollections.observableList(javafx.scene.text.Font.getFamilies());
fontFaceCombo = new ComboBox<>(lst);
fontFaceCombo.getSelectionModel().select(0);
fontFaceCombo.setCellFactory((listview -> new ListCell<String>() {
@Override
protected void updateItem(String family, boolean empty) {
super.updateItem(family, empty);
if (empty) {
setText(null);
} else {
setFont(Font.font(family));
setText(family);
}
}
}));
fontFaceCombo.getSelectionModel().select(config.getSettings().fontFamily);
fontFaceCombo.setOnAction(evt -> saveFontConfig());
return fontFaceCombo;
}
private ComboBox<Integer> buildFontSizeCombo() {
ObservableList<Integer> lst = FXCollections
.observableList(List.of(4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 22, 24, 26, 28, 32, 48, 64, 72, 80, 96, 128));
fontSizeCombo = new ComboBox<>(lst);
fontSizeCombo.setOnAction(evt -> saveFontConfig());
int size = config.getSettings().fontSize;
int selectedIndex = Math.max(0, lst.indexOf(size));
fontSizeCombo.getSelectionModel().select(selectedIndex);
return fontSizeCombo;
}
private void saveFontConfig() {
Font font = Font.font(fontFaceCombo.getSelectionModel().getSelectedItem());
int size = fontSizeCombo.getSelectionModel().getSelectedItem();
String css = ".root {\n -fx-font: " + size + " '" + font.getFamily() + "';\n}";
config.getSettings().fontFamily = font.getFamily();
config.getSettings().fontSize = size;
try {
config.save();
Files.writeString(getFontCssFile().toPath(), css);
settingsTab.showRestartRequired();
} catch (IOException e) {
LOG.error("Couldn't write font file", e);
Dialogs.showError(getScene(), "Error saving configuration", "The font stylesheet file couldn't be written", e);
}
}
private File getFontCssFile() {
return new File(Config.getInstance().getConfigDir(), "font.css");
}
private Button buildFontResetButton() {
var button = new Button("Reset");
button.setOnAction(evt -> {
try {
Files.delete(getFontCssFile().toPath());
settingsTab.showRestartRequired();
} catch (IOException e) {
LOG.error("Couldn't delete font file", e);
Dialogs.showError(getScene(), "Error resetting font configuration", "The font stylesheet file couldn't be deleted", e);
}
});
return button;
}
}

View File

@ -50,14 +50,11 @@ import javafx.beans.property.SimpleLongProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.Button;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
import javafx.scene.control.Tab;
import javafx.scene.control.TextInputDialog;
import javafx.scene.layout.Background;
@ -71,7 +68,6 @@ import javafx.scene.layout.GridPane;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.util.Duration;
public class SettingsTab extends Tab implements TabSelectionListener {
@ -172,13 +168,16 @@ public class SettingsTab extends Tab implements TabSelectionListener {
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)));
directoryStructure = new SimpleListProperty<>(null, "recordingsDirStructure",
FXCollections.observableList(List.of(FLAT, ONE_PER_MODEL, ONE_PER_RECORDING)));
splitAfter = new SimpleListProperty<>(null, "splitRecordingsAfterSecs", FXCollections.observableList(getSplitAfterSecsOptions()));
splitBiggerThan = new SimpleListProperty<>(null, "splitRecordingsBiggerThanBytes", FXCollections.observableList(getSplitBiggerThanOptions()));
resolutionRange = new SimpleRangeProperty<>(rangeValues, "minimumResolution", "maximumResolution", settings.minimumResolution, settings.maximumResolution);
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));
leaveSpaceOnDevice = new SimpleLongProperty(null, "minimumSpaceLeftInBytes",
(long) new GigabytesConverter().convertTo(settings.minimumSpaceLeftInBytes));
ffmpegParameters = new SimpleStringProperty(null, "ffmpegMergedDownloadArgs", settings.ffmpegMergedDownloadArgs);
logFFmpegOutput = new SimpleBooleanProperty(null, "logFFmpegOutput", settings.logFFmpegOutput);
loghlsdlOutput = new SimpleBooleanProperty(null, "loghlsdlOutput", settings.loghlsdlOutput);
@ -215,94 +214,62 @@ public class SettingsTab extends Tab implements TabSelectionListener {
}
var storage = new CtbrecPreferencesStorage(config);
var prefs = Preferences.of(storage,
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").needsRestart(),
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("Enable recently watched tab", recentlyWatched).needsRestart(),
Setting.of("Add models from clipboard", monitorClipboard, "Monitor clipboard for model URLs and automatically add them to the recorder").needsRestart(),
Setting.of("Minimize to tray", minimizeToTray, "Removes the app from the task bar, if minimized"),
Setting.of("Fast scroll speed", fastScrollSpeed, "Makes the thumbnail overviews scroll faster with the mouse wheel").needsRestart(),
Setting.of("Show confirmation dialogs", confirmationDialogs, "Show confirmation dialogs for irreversible actions"),
Setting.of("Total model count in title", totalModelCountInTitle, "Show the total number of models in the title bar"),
Setting.of("Start Tab", startTab),
Setting.of("Colors (Base / Accent)", new ColorSettingsPane(Config.getInstance())).needsRestart()
//,
//Setting.of("Font", buildFontFaceCombo()).needsRestart()
),
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)
)
),
var prefs = Preferences.of(storage, 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").needsRestart(),
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("Enable recently watched tab", recentlyWatched).needsRestart(),
Setting.of("Add models from clipboard", monitorClipboard, "Monitor clipboard for model URLs and automatically add them to the recorder")
.needsRestart(),
Setting.of("Minimize to tray", minimizeToTray, "Removes the app from the task bar, if minimized"),
Setting.of("Fast scroll speed", fastScrollSpeed, "Makes the thumbnail overviews scroll faster with the mouse wheel").needsRestart(),
Setting.of("Show confirmation dialogs", confirmationDialogs, "Show confirmation dialogs for irreversible actions"),
Setting.of("Total model count in title", totalModelCountInTitle, "Show the total number of models in the title bar"),
Setting.of("Start Tab", startTab), Setting.of("Colors (Base / Accent)", new ColorSettingsPane(Config.getInstance())).needsRestart(),
Setting.of("Font", new FontSettingsPane(this, config)).needsRestart()),
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))),
Category.of("Recorder",
Group.of("Settings",
Setting.of("Recordings Directory", recordingsDir),
Setting.of("Directory Structure", directoryStructure),
Group.of("Settings", Setting.of("Recordings Directory", recordingsDir), Setting.of("Directory Structure", directoryStructure),
Setting.of("Split recordings after", splitAfter).converter(SplitAfterOption.converter()).onChange(this::splitValuesChanged),
Setting.of("Split recordings bigger than", splitBiggerThan).converter(SplitBiggerThanOption.converter()).onChange(this::splitValuesChanged),
Setting.of("Split recordings bigger than", splitBiggerThan).converter(SplitBiggerThanOption.converter())
.onChange(this::splitValuesChanged),
Setting.of("Restrict Resolution", resolutionRange, "Only record streams with resolution within the given range"),
Setting.of("Concurrent Recordings (0 = unlimited)", concurrentRecordings),
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("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"),
Setting.of("Check online state every (seconds)", onlineCheckIntervalInSecs, "Check every x seconds, if a model came online"),
Setting.of("Skip online check for paused models", onlineCheckSkipsPausedModels, "Skip online check for paused models")
),
Group.of("Location",
Setting.of("Record Location", recordLocal).needsRestart(),
Setting.of("Server", server),
Setting.of("Port", port),
Setting.of("Skip online check for paused models", onlineCheckSkipsPausedModels, "Skip online check for paused models")),
Group.of("Location", Setting.of("Record Location", recordLocal).needsRestart(), 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("Download Filename", downloadFilename, "File name pattern for downloads"),
Setting.of("", variablesHelpButton),
Setting.of("Download Filename", downloadFilename, "File name pattern for downloads"), Setting.of("", variablesHelpButton),
Setting.of("Require authentication", requireAuthentication),
Setting.of("Use Secure Communication (TLS)", transportLayerSecurity)
)
),
Setting.of("Use Secure Communication (TLS)", transportLayerSecurity))),
Category.of("Post-Processing",
Group.of("Post-Processing",
Setting.of("Threads", postProcessingThreads),
Setting.of("Steps", postProcessingStepPanel),
Setting.of("", createHelpButton("Post-Processing Help", "http://localhost:5689/docs/PostProcessing.md"))
)
),
Category.of("Events & Actions", new ActionSettingsPanel(recorder)),
Category.of("Ignore List", ignoreList),
Group.of("Post-Processing", Setting.of("Threads", postProcessingThreads), Setting.of("Steps", postProcessingStepPanel),
Setting.of("", createHelpButton("Post-Processing Help", "http://localhost:5689/docs/PostProcessing.md")))),
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).needsRestart(),
Setting.of("Host", proxyHost).needsRestart(),
Setting.of("Port", proxyPort).needsRestart(),
Setting.of("Username", proxyUser).needsRestart(),
Setting.of("Password", proxyPassword).needsRestart()
)
),
Group.of("Proxy", Setting.of("Type", proxyType).needsRestart(), Setting.of("Host", proxyHost).needsRestart(),
Setting.of("Port", proxyPort).needsRestart(), Setting.of("Username", proxyUser).needsRestart(),
Setting.of("Password", proxyPassword).needsRestart())),
Category.of("Advanced / Devtools",
Group.of("Networking",
Setting.of("Playlist request timeout (ms)", playlistRequestTimeout, "Timeout in ms for playlist requests")
),
Group.of("Logging",
Setting.of("Log FFmpeg output", logFFmpegOutput, "Log FFmpeg output to files in the system's temp directory"),
Setting.of("Log missed segments", logMissedSegments, "Write a log files in the system's temp directory to analyze missed segments")
),
Group.of("Networking", Setting.of("Playlist request timeout (ms)", playlistRequestTimeout, "Timeout in ms for playlist requests")),
Group.of("Logging", Setting.of("Log FFmpeg output", logFFmpegOutput, "Log FFmpeg output to files in the system's temp directory"),
Setting.of("Log missed segments", logMissedSegments,
"Write a log files in the system's temp directory to analyze missed segments")),
Group.of("hlsdl (experimental)",
Setting.of("Use hlsdl (if possible)", useHlsdl, "Use hlsdl to record the live streams. Some features might not work correctly."),
Setting.of("Use hlsdl (if possible)", useHlsdl,
"Use hlsdl to record the live streams. Some features might not work correctly."),
Setting.of("hlsdl executable", hlsdlExecutable, "Path to the hlsdl executable"),
Setting.of("Log hlsdl output", loghlsdlOutput, "Log hlsdl output to files in the system's temp directory")
)
)
);
Setting.of("Log hlsdl output", loghlsdlOutput, "Log hlsdl output to files in the system's temp directory"))));
Region preferencesView = prefs.getView();
prefs.onRestartRequired(this::showRestartRequired);
storage.setPreferences(prefs);
@ -313,7 +280,8 @@ public class SettingsTab extends Tab implements TabSelectionListener {
restartNotification.setVisible(false);
restartNotification.setOpacity(0);
restartNotification.setStyle("-fx-font-size: 28; -fx-padding: .3em");
restartNotification.setBorder(new Border(new BorderStroke(Color.web(settings.colorAccent), BorderStrokeStyle.SOLID, new CornerRadii(5), new BorderWidths(2))));
restartNotification
.setBorder(new Border(new BorderStroke(Color.web(settings.colorAccent), BorderStrokeStyle.SOLID, new CornerRadii(5), new BorderWidths(2))));
restartNotification.setBackground(new Background(new BackgroundFill(Color.web(settings.colorBase), new CornerRadii(5), Insets.EMPTY)));
stackPane.getChildren().add(restartNotification);
StackPane.setAlignment(restartNotification, Pos.TOP_RIGHT);
@ -387,9 +355,7 @@ public class SettingsTab extends Tab implements TabSelectionListener {
}
private List<String> getTabNames() {
return getTabPane().getTabs().stream()
.map(Tab::getText)
.collect(Collectors.toList());
return getTabPane().getTabs().stream().map(Tab::getText).collect(Collectors.toList());
}
private List<SplitAfterOption> getSplitAfterSecsOptions() {
@ -503,6 +469,7 @@ public class SettingsTab extends Tab implements TabSelectionListener {
}
private static final Duration ANIMATION_DURATION = new Duration(500);
private Transition changeOpacity(Node node, double opacity) {
var transition = new FadeTransition(ANIMATION_DURATION, node);
transition.setFromValue(node.getOpacity());
@ -618,28 +585,5 @@ public class SettingsTab extends Tab implements TabSelectionListener {
}
}
private ComboBox<String> buildFontFaceCombo() {
ObservableList<String> lst = FXCollections.observableList(javafx.scene.text.Font.getFamilies());
ComboBox<String> cb = new ComboBox<>(lst);
cb.getSelectionModel().select(0);
cb.setCellFactory((listview -> new ListCell<String>() {
@Override
protected void updateItem(String family, boolean empty) {
super.updateItem(family, empty);
if (empty) {
setText(null);
} else {
setFont(Font.font(family));
setText(family);
}
}
}));
cb.setOnAction(evt -> {
// TODO write a css file with the font setting
// System.out.println(evt);
// Font font = Font.font(cb.getSelectionModel().getSelectedItem());
// SettingsTab.this.getTabPane().getScene().getStylesheets().add(".root {-fx-font: 24 '" + font.getFamily() + "';}");
});
return cb;
}
}

View File

@ -75,6 +75,8 @@ public class Settings {
public String ffmpegFileSuffix = "ts";
public String flirt4freePassword;
public String flirt4freeUsername;
public String fontFamily = "Sans-Serif";
public int fontSize = 14;
public boolean generatePlaylist = true;
public String hlsdlExecutable = "hlsdl";
public int httpPort = 8080;