Add setting to customize the date time format in the GUI

This commit is contained in:
0xb00bface 2021-12-24 15:38:29 +01:00
parent a6e3fcc6e3
commit 38b898f405
11 changed files with 122 additions and 71 deletions

View File

@ -252,7 +252,7 @@ public class CamrecApplication extends Application {
tabPane.getTabs().add(new RecentlyWatchedTab(recorder, sites)); tabPane.getTabs().add(new RecentlyWatchedTab(recorder, sites));
} }
tabPane.getTabs().add(new SettingsTab(sites, recorder)); tabPane.getTabs().add(new SettingsTab(sites, recorder));
tabPane.getTabs().add(new NewsTab()); tabPane.getTabs().add(new NewsTab(config));
tabPane.getTabs().add(new DonateTabFx()); tabPane.getTabs().add(new DonateTabFx());
tabPane.getTabs().add(new HelpTab()); tabPane.getTabs().add(new HelpTab());
tabPane.getTabs().add(new LoggingTab()); tabPane.getTabs().add(new LoggingTab());

View File

@ -1,16 +1,22 @@
package ctbrec.ui.controls; package ctbrec.ui.controls;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import javafx.scene.control.TableCell; import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn; import javafx.scene.control.TableColumn;
import javafx.util.Callback; import javafx.util.Callback;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
public class DateTimeCellFactory<T> implements Callback<TableColumn<T, Instant>, TableCell<T, Instant>> { public class DateTimeCellFactory<T> implements Callback<TableColumn<T, Instant>, TableCell<T, Instant>> {
private final DateTimeFormatter formatter;
public DateTimeCellFactory (DateTimeFormatter formatter) {
this.formatter = formatter;
}
@Override @Override
public TableCell<T, Instant> call(TableColumn<T, Instant> param) { public TableCell<T, Instant> call(TableColumn<T, Instant> param) {
return new TableCell<T, Instant>() { return new TableCell<T, Instant>() {
@ -20,7 +26,6 @@ public class DateTimeCellFactory<T> implements Callback<TableColumn<T, Instant>,
setText(""); setText("");
} else { } else {
var dateTime = LocalDateTime.ofInstant(item, ZoneId.systemDefault()); var dateTime = LocalDateTime.ofInstant(item, ZoneId.systemDefault());
var formatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT);
String formattedDateTime = formatter.format(dateTime); String formattedDateTime = formatter.format(dateTime);
setText(item.equals(Instant.EPOCH) ? "" : formattedDateTime); setText(item.equals(Instant.EPOCH) ? "" : formattedDateTime);
} }

View File

@ -5,6 +5,7 @@ import static ctbrec.io.HttpConstants.*;
import java.io.IOException; import java.io.IOException;
import java.util.Objects; import java.util.Objects;
import ctbrec.Config;
import org.json.JSONObject; import org.json.JSONObject;
import com.squareup.moshi.JsonAdapter; import com.squareup.moshi.JsonAdapter;
@ -26,10 +27,11 @@ import okhttp3.Request;
public class NewsTab extends Tab implements TabSelectionListener { public class NewsTab extends Tab implements TabSelectionListener {
private static final String ACCESS_TOKEN = "a2804d73a89951a22e0f8483a6fcec8943afd88b7ba17c459c095aa9e6f94fd0"; private static final String ACCESS_TOKEN = "a2804d73a89951a22e0f8483a6fcec8943afd88b7ba17c459c095aa9e6f94fd0";
private static final String URL = "https://mastodon.cloud/api/v1/accounts/480960/statuses?limit=20&exclude_replies=true"; private static final String URL = "https://mastodon.cloud/api/v1/accounts/480960/statuses?limit=20&exclude_replies=true";
private final Config config;
private final VBox layout = new VBox();
private VBox layout = new VBox(); public NewsTab(Config config) {
this.config = config;
public NewsTab() {
setText("News"); setText("News");
layout.setMaxWidth(800); layout.setMaxWidth(800);
layout.setAlignment(Pos.CENTER); layout.setAlignment(Pos.CENTER);
@ -46,11 +48,11 @@ public class NewsTab extends Tab implements TabSelectionListener {
var request = new Request.Builder() var request = new Request.Builder()
.url(URL) .url(URL)
.header("Authorization", "Bearer " + ACCESS_TOKEN) .header("Authorization", "Bearer " + ACCESS_TOKEN)
.header(USER_AGENT, "ctbrec " + CamrecApplication.getVersion().toString()) .header(USER_AGENT, "ctbrec " + CamrecApplication.getVersion())
.build(); .build();
try (var response = CamrecApplication.httpClient.execute(request)) { try (var response = CamrecApplication.httpClient.execute(request)) {
if (response.isSuccessful()) { if (response.isSuccessful()) {
var body = response.body().string(); var body = Objects.requireNonNull(response.body(), "HTTP response body is null").string();
if (body.startsWith("[")) { if (body.startsWith("[")) {
onSuccess(body); onSuccess(body);
} else if (body.startsWith("{")) { } else if (body.startsWith("{")) {
@ -79,12 +81,12 @@ public class NewsTab extends Tab implements TabSelectionListener {
private void onSuccess(String body) throws IOException { private void onSuccess(String body) throws IOException {
var moshi = new Moshi.Builder().build(); var moshi = new Moshi.Builder().build();
JsonAdapter<Status[]> statusListAdapter = moshi.adapter(Status[].class); JsonAdapter<Status[]> statusListAdapter = moshi.adapter(Status[].class);
Status[] statusArray = statusListAdapter.fromJson(body); Status[] statusArray = Objects.requireNonNull(statusListAdapter.fromJson(body));
Platform.runLater(() -> { Platform.runLater(() -> {
layout.getChildren().clear(); layout.getChildren().clear();
for (Status status : statusArray) { for (Status status : statusArray) {
if (status.getInReplyToId() == null && !Objects.equals("direct", status.getVisibility())) { if (status.getInReplyToId() == null && !Objects.equals("direct", status.getVisibility())) {
var stp = new StatusPane(status); var stp = new StatusPane(status, config.getDateTimeFormatter());
layout.getChildren().add(stp); layout.getChildren().add(stp);
VBox.setMargin(stp, new Insets(10)); VBox.setMargin(stp, new Insets(10));
} }

View File

@ -1,10 +1,5 @@
package ctbrec.ui.news; package ctbrec.ui.news;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import java.util.Set;
import ctbrec.io.HtmlParser; import ctbrec.io.HtmlParser;
import ctbrec.ui.DesktopIntegration; import ctbrec.ui.DesktopIntegration;
import javafx.collections.ObservableList; import javafx.collections.ObservableList;
@ -12,20 +7,20 @@ import javafx.geometry.Insets;
import javafx.geometry.Orientation; import javafx.geometry.Orientation;
import javafx.geometry.Pos; import javafx.geometry.Pos;
import javafx.scene.Node; import javafx.scene.Node;
import javafx.scene.control.Button; import javafx.scene.control.*;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollBar;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.TextArea;
import javafx.scene.layout.StackPane; import javafx.scene.layout.StackPane;
import javafx.scene.shape.Rectangle; import javafx.scene.shape.Rectangle;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Set;
public class StatusPane extends StackPane { public class StatusPane extends StackPane {
TextArea content; TextArea content;
Button reply; Button reply;
public StatusPane(Status status) { public StatusPane(Status status, DateTimeFormatter formatter) {
String text = HtmlParser.getText("<div>" + status.getContent() + "</div>", "div"); String text = HtmlParser.getText("<div>" + status.getContent() + "</div>", "div");
content = new TextArea(text); content = new TextArea(text);
@ -35,7 +30,7 @@ public class StatusPane extends StackPane {
getChildren().add(content); getChildren().add(content);
ZonedDateTime createdAt = status.getCreationTime(); ZonedDateTime createdAt = status.getCreationTime();
String creationTime = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT, FormatStyle.SHORT).format(createdAt); String creationTime = formatter.format(createdAt);
var time = new Label(creationTime); var time = new Label(creationTime);
time.setStyle("-fx-background-color: -fx-base"); time.setStyle("-fx-background-color: -fx-base");
time.setOpacity(.7); time.setOpacity(.7);
@ -63,13 +58,10 @@ public class StatusPane extends StackPane {
protected void layoutChildren() { protected void layoutChildren() {
ObservableList<Node> childrenUnmodifiable = content.getChildrenUnmodifiable(); ObservableList<Node> childrenUnmodifiable = content.getChildrenUnmodifiable();
for (Node node : childrenUnmodifiable) { for (Node node : childrenUnmodifiable) {
if (node instanceof ScrollPane) { if (node instanceof ScrollPane scrollPane) {
var scrollPane = (ScrollPane) node;
Set<Node> nodes = scrollPane.lookupAll(".scroll-bar"); Set<Node> nodes = scrollPane.lookupAll(".scroll-bar");
for (final Node child : nodes) { for (final Node child : nodes) {
if (child instanceof ScrollBar) { if (child instanceof ScrollBar sb && sb.getOrientation() == Orientation.VERTICAL) {
ScrollBar sb = (ScrollBar) child;
if (sb.getOrientation() == Orientation.VERTICAL) {
if (sb.isVisible()) { if (sb.isVisible()) {
StackPane.setMargin(reply, new Insets(5, 22, 5, 5)); StackPane.setMargin(reply, new Insets(5, 22, 5, 5));
} else { } else {
@ -79,7 +71,6 @@ public class StatusPane extends StackPane {
} }
} }
} }
}
super.layoutChildren(); super.layoutChildren();
} }
} }

View File

@ -119,6 +119,7 @@ public class SettingsTab extends Tab implements TabSelectionListener {
private LocalTimeProperty timeoutRecordingStartingAt; private LocalTimeProperty timeoutRecordingStartingAt;
private LocalTimeProperty timeoutRecordingEndingAt; private LocalTimeProperty timeoutRecordingEndingAt;
private SimpleLongProperty recordUntilDefaultDurationInMinutes; private SimpleLongProperty recordUntilDefaultDurationInMinutes;
private SimpleStringProperty dateTimeFormat;
public SettingsTab(List<Site> sites, Recorder recorder) { public SettingsTab(List<Site> sites, Recorder recorder) {
this.sites = sites; this.sites = sites;
@ -189,6 +190,7 @@ public class SettingsTab extends Tab implements TabSelectionListener {
timeoutRecordingStartingAt = new LocalTimeProperty(null, "timeoutRecordingStartingAt", settings.timeoutRecordingStartingAt); timeoutRecordingStartingAt = new LocalTimeProperty(null, "timeoutRecordingStartingAt", settings.timeoutRecordingStartingAt);
timeoutRecordingEndingAt = new LocalTimeProperty(null, "timeoutRecordingEndingAt", settings.timeoutRecordingEndingAt); timeoutRecordingEndingAt = new LocalTimeProperty(null, "timeoutRecordingEndingAt", settings.timeoutRecordingEndingAt);
recordUntilDefaultDurationInMinutes = new SimpleLongProperty(null, "recordUntilDefaultDurationInMinutes", settings.recordUntilDefaultDurationInMinutes); recordUntilDefaultDurationInMinutes = new SimpleLongProperty(null, "recordUntilDefaultDurationInMinutes", settings.recordUntilDefaultDurationInMinutes);
dateTimeFormat = new SimpleStringProperty(null, "dateTimeFormat", settings.dateTimeFormat);
} }
private void createGui() { private void createGui() {
@ -229,6 +231,7 @@ public class SettingsTab extends Tab implements TabSelectionListener {
Group.of("Look & Feel", Group.of("Look & Feel",
Setting.of("Colors (Base / Accent)", new ColorSettingsPane(Config.getInstance())).needsRestart(), Setting.of("Colors (Base / Accent)", new ColorSettingsPane(Config.getInstance())).needsRestart(),
Setting.of("Font", new FontSettingsPane(this, config)).needsRestart(), Setting.of("Font", new FontSettingsPane(this, config)).needsRestart(),
Setting.of("Date format (empty = system default)", dateTimeFormat, DATE_FORMATTER_TOOLTIP).needsRestart(),
Setting.of("Display stream resolution in overview", determineResolution), Setting.of("Display stream resolution in overview", determineResolution),
Setting.of("Total model count in title", totalModelCountInTitle, "Show the total number of models in the title bar"), Setting.of("Total model count in title", totalModelCountInTitle, "Show the total number of models in the title bar"),
Setting.of("Show grid lines in tables", showGridLinesInTables, "Show grid lines in tables").needsRestart(), Setting.of("Show grid lines in tables", showGridLinesInTables, "Show grid lines in tables").needsRestart(),
@ -588,5 +591,54 @@ public class SettingsTab extends Tab implements TabSelectionListener {
} }
} }
private static final String DATE_FORMATTER_TOOLTIP = """
Leave empty for system default
Symbol Meaning Presentation Examples
------ ------- ------------ -------
G era text AD; Anno Domini; A
u year year 2004; 04
y year-of-era year 2004; 04
D day-of-year number 189
M/L month-of-year number/text 7; 07; Jul; July; J
d day-of-month number 10
Q/q quarter-of-year number/text 3; 03; Q3; 3rd quarter
Y week-based-year year 1996; 96
w week-of-week-based-year number 27
W week-of-month number 4
E day-of-week text Tue; Tuesday; T
e/c localized day-of-week number/text 2; 02; Tue; Tuesday; T
F week-of-month number 3
a am-pm-of-day text PM
h clock-hour-of-am-pm (1-12) number 12
K hour-of-am-pm (0-11) number 0
k clock-hour-of-am-pm (1-24) number 0
H hour-of-day (0-23) number 0
m minute-of-hour number 30
s second-of-minute number 55
S fraction-of-second fraction 978
A milli-of-day number 1234
n nano-of-second number 987654321
N nano-of-day number 1234000000
V time-zone ID zone-id America/Los_Angeles; Z; -08:30
z time-zone name zone-name Pacific Standard Time; PST
O localized zone-offset offset-O GMT+8; GMT+08:00; UTC-08:00;
X zone-offset 'Z' for zero offset-X Z; -08; -0830; -08:30; -083015; -08:30:15;
x zone-offset offset-x +0000; -08; -0830; -08:30; -083015; -08:30:15;
Z zone-offset offset-Z +0000; -0800; -08:00;
p pad next pad modifier 1
' escape for text delimiter
'' single quote literal '
[ optional section start
] optional section end
# reserved for future use
{ reserved for future use
} reserved for future use
""";
} }

View File

@ -147,8 +147,8 @@ public class RecentlyWatchedTab extends Tab implements ShutdownListener {
TableColumn<PlayerStartedEvent, Instant> timestamp = createTableColumn("Timestamp", 150, idx); TableColumn<PlayerStartedEvent, Instant> timestamp = createTableColumn("Timestamp", 150, idx);
timestamp.setId("timestamp"); timestamp.setId("timestamp");
timestamp.setCellValueFactory(cdf -> new SimpleObjectProperty<Instant>(cdf.getValue().getTimestamp())); timestamp.setCellValueFactory(cdf -> new SimpleObjectProperty<>(cdf.getValue().getTimestamp()));
timestamp.setCellFactory(new DateTimeCellFactory<>()); timestamp.setCellFactory(new DateTimeCellFactory<>(config.getDateTimeFormatter()));
timestamp.setEditable(false); timestamp.setEditable(false);
timestamp.setSortType(SortType.DESCENDING); timestamp.setSortType(SortType.DESCENDING);
table.getColumns().add(timestamp); table.getColumns().add(timestamp);

View File

@ -128,7 +128,7 @@ public class RecordingsTab extends Tab implements TabSelectionListener, Shutdown
var instant = cdf.getValue().getStartDate(); var instant = cdf.getValue().getStartDate();
return new SimpleObjectProperty<>(instant); return new SimpleObjectProperty<>(instant);
}); });
date.setCellFactory(new DateTimeCellFactory<>()); date.setCellFactory(new DateTimeCellFactory<>(config.getDateTimeFormatter()));
date.setPrefWidth(200); date.setPrefWidth(200);
TableColumn<JavaFxRecording, String> status = new TableColumn<>("Status"); TableColumn<JavaFxRecording, String> status = new TableColumn<>("Status");
status.setId("status"); status.setId("status");

View File

@ -315,7 +315,7 @@ public abstract class AbstractRecordedModelsTab extends Tab implements TabSelect
protected void addAddedTimestampColumn(int columnIdx) { protected void addAddedTimestampColumn(int columnIdx) {
TableColumn<JavaFxModel, Instant> tc = addTableColumn("addedTimestamp", "added at", columnIdx, 400); TableColumn<JavaFxModel, Instant> tc = addTableColumn("addedTimestamp", "added at", columnIdx, 400);
tc.setCellFactory(new DateTimeCellFactory<>()); tc.setCellFactory(new DateTimeCellFactory<>(config.getDateTimeFormatter()));
tc.setCellValueFactory(param -> new SimpleObjectProperty<>(param.getValue().getAddedTimestamp())); tc.setCellValueFactory(param -> new SimpleObjectProperty<>(param.getValue().getAddedTimestamp()));
tc.setPrefWidth(150); tc.setPrefWidth(150);
tc.setEditable(false); tc.setEditable(false);

View File

@ -124,7 +124,7 @@ public class RecordedModelsTab extends AbstractRecordedModelsTab implements TabS
table.getColumns().add(priority); table.getColumns().add(priority);
TableColumn<JavaFxModel, Instant> lastSeen = new TableColumn<>("last seen"); TableColumn<JavaFxModel, Instant> lastSeen = new TableColumn<>("last seen");
lastSeen.setCellValueFactory(cdf -> cdf.getValue().lastSeenProperty()); lastSeen.setCellValueFactory(cdf -> cdf.getValue().lastSeenProperty());
lastSeen.setCellFactory(new DateTimeCellFactory<>()); lastSeen.setCellFactory(new DateTimeCellFactory<>(config.getDateTimeFormatter()));
lastSeen.setPrefWidth(150); lastSeen.setPrefWidth(150);
lastSeen.setEditable(false); lastSeen.setEditable(false);
lastSeen.setId("lastSeen"); lastSeen.setId("lastSeen");
@ -134,7 +134,7 @@ public class RecordedModelsTab extends AbstractRecordedModelsTab implements TabS
table.getColumns().add(lastSeen); table.getColumns().add(lastSeen);
TableColumn<JavaFxModel, Instant> lastRecorded = new TableColumn<>("last recorded"); TableColumn<JavaFxModel, Instant> lastRecorded = new TableColumn<>("last recorded");
lastRecorded.setCellValueFactory(cdf -> cdf.getValue().lastRecordedProperty()); lastRecorded.setCellValueFactory(cdf -> cdf.getValue().lastRecordedProperty());
lastRecorded.setCellFactory(new DateTimeCellFactory<>()); lastRecorded.setCellFactory(new DateTimeCellFactory<>(config.getDateTimeFormatter()));
lastRecorded.setPrefWidth(150); lastRecorded.setPrefWidth(150);
lastRecorded.setEditable(false); lastRecorded.setEditable(false);
lastRecorded.setId("lastRecorded"); lastRecorded.setId("lastRecorded");

View File

@ -1,7 +1,18 @@
package ctbrec; package ctbrec;
import static java.nio.charset.StandardCharsets.*; import com.squareup.moshi.JsonAdapter;
import static java.nio.file.StandardOpenOption.*; import com.squareup.moshi.Moshi;
import ctbrec.Settings.SplitStrategy;
import ctbrec.io.*;
import ctbrec.recorder.postprocessing.DeleteTooShort;
import ctbrec.recorder.postprocessing.PostProcessor;
import ctbrec.recorder.postprocessing.RemoveKeepFile;
import ctbrec.recorder.postprocessing.Script;
import ctbrec.sites.Site;
import ctbrec.sites.cam4.Cam4Model;
import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
@ -13,34 +24,13 @@ import java.time.LocalDateTime;
import java.time.LocalTime; import java.time.LocalTime;
import java.time.ZoneId; import java.time.ZoneId;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.util.Date; import java.time.format.FormatStyle;
import java.util.Iterator; import java.util.*;
import java.util.List;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.apache.commons.io.FileUtils; import static java.nio.charset.StandardCharsets.UTF_8;
import org.slf4j.Logger; import static java.nio.file.StandardOpenOption.*;
import org.slf4j.LoggerFactory;
import com.squareup.moshi.JsonAdapter;
import com.squareup.moshi.Moshi;
import ctbrec.Settings.SplitStrategy;
import ctbrec.io.FileJsonAdapter;
import ctbrec.io.LocalTimeJsonAdapter;
import ctbrec.io.ModelJsonAdapter;
import ctbrec.io.PostProcessorJsonAdapter;
import ctbrec.io.UuidJSonAdapter;
import ctbrec.recorder.postprocessing.DeleteTooShort;
import ctbrec.recorder.postprocessing.PostProcessor;
import ctbrec.recorder.postprocessing.RemoveKeepFile;
import ctbrec.recorder.postprocessing.Script;
import ctbrec.sites.Site;
import ctbrec.sites.cam4.Cam4Model;
// TODO don't use singleton pattern // TODO don't use singleton pattern
public class Config { public class Config {
@ -305,6 +295,15 @@ public class Config {
return context; return context;
} }
public DateTimeFormatter getDateTimeFormatter() {
var dtf = getSettings().dateTimeFormat;
if (StringUtil.isNotBlank(dtf)) {
return DateTimeFormatter.ofPattern(dtf);
} else {
return DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT);
}
}
public String getModelNotes(Model m) { public String getModelNotes(Model m) {
return Config.getInstance().getSettings().modelNotes.getOrDefault(m.getUrl(), ""); return Config.getInstance().getSettings().modelNotes.getOrDefault(m.getUrl(), "");
} }

View File

@ -2,6 +2,7 @@ package ctbrec;
import java.io.File; import java.io.File;
import java.time.LocalTime; import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
@ -66,6 +67,7 @@ public class Settings {
public int concurrentRecordings = 0; public int concurrentRecordings = 0;
public boolean confirmationForDangerousActions = false; public boolean confirmationForDangerousActions = false;
public String contactsheetTimestampLook = "font=sans-serif:fontcolor=white:fontsize=60:box=1:boxcolor=black@0.5:boxborderw=5"; public String contactsheetTimestampLook = "font=sans-serif:fontcolor=white:fontsize=60:box=1:boxcolor=black@0.5:boxborderw=5";
public String dateTimeFormat = "";
public int defaultPriority = 50; public int defaultPriority = 50;
public boolean determineResolution = false; public boolean determineResolution = false;
public List<String> disabledSites = new ArrayList<>(); public List<String> disabledSites = new ArrayList<>();