Make columns of RecordingsTable configurable

This commit is contained in:
0xb00bface 2021-09-11 17:43:44 +02:00
parent fd51527f75
commit abf65b1cc2
6 changed files with 297 additions and 65 deletions

View File

@ -0,0 +1,109 @@
package ctbrec.ui.controls.table;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.List;
import java.util.Map;
import ctbrec.Config;
import ctbrec.Settings;
import javafx.scene.control.TableColumn.SortType;
public class SettingTableViewStateStore implements TableViewStateStore {
private final Config config;
private final String settingPrefix;
private final String columnOrderSetting;
private final String columnWidthSetting;
private final String columnVisibilitySetting;
private final String sortColumnSetting;
private final String sortTypeSetting;
public SettingTableViewStateStore(Config config, String settingPrefix) {
this.config = config;
this.settingPrefix = settingPrefix;
columnOrderSetting = settingPrefix + "ColumnOrder";
columnWidthSetting = settingPrefix + "ColumnWidth";
columnVisibilitySetting = settingPrefix + "ColumnVisibility";
sortColumnSetting = settingPrefix + "SortColumn";
sortTypeSetting = settingPrefix + "SortType";
}
@Override
public List<String> loadColumnOrder() {
return loadSetting(columnOrderSetting);
}
@Override
public Map<String, Double> loadColumnWidths() {
return loadSetting(columnWidthSetting);
}
@Override
public Map<String, Boolean> loadColumnVisibility() {
return loadSetting(columnVisibilitySetting);
}
@Override
public String loadSortColumn() {
return loadSetting(sortColumnSetting);
}
@Override
public SortType loadSortType() {
return SortType.valueOf(loadSetting(sortTypeSetting));
}
@SuppressWarnings("unchecked")
private <T> T loadSetting(String name) {
try {
Field field = Settings.class.getDeclaredField(name);
return (T) field.get(config.getSettings());
} catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
throw new TableViewStateStoreException(e);
}
}
private void setSetting(String name, Object value) {
try {
Field field = Settings.class.getDeclaredField(name);
field.set(config.getSettings(), value); // NOSONAR
} catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
throw new TableViewStateStoreException(e);
}
}
@Override
public void saveColumnOrder(List<String> columnIds) throws IOException {
setSetting(columnOrderSetting, columnIds);
save();
}
@Override
public void saveColumnWidths(Map<String, Double> columnIdsToWidth) throws IOException {
setSetting(columnWidthSetting, columnIdsToWidth);
save();
}
@Override
public void saveColumnVisibility(Map<String, Boolean> columnIdsToVisibility) throws IOException {
setSetting(columnVisibilitySetting, columnIdsToVisibility);
save();
}
@Override
public void saveSorting(String columnId, SortType sortType) throws IOException {
setSetting(sortColumnSetting, columnId);
setSetting(sortTypeSetting, sortType.name());
save();
}
private void save() throws IOException {
config.save();
}
@Override
public String getName() {
return settingPrefix;
}
}

View File

@ -0,0 +1,147 @@
package ctbrec.ui.controls.table;
import java.time.Duration;
import java.time.Instant;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ctbrec.StringUtil;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
public class StatePersistingTableView<T> extends TableView<T> {
private static final Logger LOG = LoggerFactory.getLogger(StatePersistingTableView.class);
private Instant initialized;
private TableViewStateStore stateStore;
public StatePersistingTableView(TableViewStateStore stateStore) {
super();
this.stateStore = stateStore;
setTableMenuButtonVisible(true);
initialized = Instant.now();
}
public void restoreState() {
restoreColumnOrder();
restoreColumnWidths();
restoreColumnVisibility();
restoreSorting();
addStateListeners();
}
private void addStateListeners() {
// column order
getColumns().addListener((ListChangeListener<? super TableColumn<T, ?>>) c -> saveColumnOrder());
// column visibility
getColumns().forEach(tc -> tc.visibleProperty().addListener((obs, oldV, newV) -> saveColumnVisibility()));
// column width
getColumns().forEach(tc -> tc.widthProperty().addListener((obs, oldV, newV) -> saveColumnWidths()));
// sort order
getSortOrder().addListener((ListChangeListener<? super TableColumn<T, ?>>) c -> saveSorting());
getColumns().forEach(tc -> tc.sortTypeProperty().addListener((obs, oldV, newV) -> saveSorting()));
}
protected void restoreColumnVisibility() {
Map<String, Boolean> visibility = stateStore.loadColumnVisibility();
for (TableColumn<T, ?> tc : getColumns()) {
tc.setVisible(visibility.getOrDefault(tc.getId(), true));
}
}
protected void restoreColumnWidths() {
Map<String, Double> widths = stateStore.loadColumnWidths();
for (TableColumn<T, ?> tc : getColumns()) {
tc.setPrefWidth(widths.getOrDefault(tc.getId(), tc.getWidth()));
}
}
protected void restoreColumnOrder() {
List<String> order = stateStore.loadColumnOrder();
ObservableList<TableColumn<T, ?>> tableColumns = getColumns();
for (var i = 0; i < order.size(); i++) {
for (var j = 0; j < getColumns().size(); j++) {
if (Objects.equals(order.get(i), tableColumns.get(j).getId())) {
TableColumn<T, ?> col = tableColumns.get(j);
tableColumns.remove(j);
tableColumns.add(Math.min(i, tableColumns.size()), col);
}
}
}
}
protected void restoreSorting() {
String sortCol = stateStore.loadSortColumn();
if (StringUtil.isNotBlank(sortCol)) {
for (TableColumn<T, ?> col : getColumns()) {
if (Objects.equals(sortCol, col.getId())) {
col.setSortType(stateStore.loadSortType());
getSortOrder().clear();
getSortOrder().add(col);
break;
}
}
}
}
public void saveState() {
saveColumnOrder();
saveColumnWidths();
saveColumnVisibility();
saveSorting();
}
protected void saveSorting() {
if (!getSortOrder().isEmpty()) {
TableColumn<T, ?> col = getSortOrder().get(0);
saveSetting(() -> stateStore.saveSorting(col.getId(), col.getSortType()));
} else {
saveSetting(() -> stateStore.saveSorting(null, stateStore.loadSortType()));
}
}
protected void saveColumnVisibility() {
saveSetting(() -> {
Map<String, Boolean> columnIdToVisible = getColumns().stream().collect(Collectors.toMap(TableColumn::getId, TableColumn::isVisible));
stateStore.saveColumnVisibility(columnIdToVisible);
});
}
protected void saveColumnWidths() {
saveSetting(() -> {
Map<String, Double> columnIdToWidth = getColumns().stream().collect(Collectors.toMap(TableColumn::getId, TableColumn::getWidth));
stateStore.saveColumnWidths(columnIdToWidth);
});
}
protected void saveColumnOrder() {
saveSetting(() -> {
List<String> tableIds = getColumns().stream().map(TableColumn::getId).collect(Collectors.toList());
stateStore.saveColumnOrder(tableIds);
});
}
private void saveSetting(ThrowingRunnable r) {
if (Duration.between(initialized, Instant.now()).getSeconds() > 1) {
try {
r.run();
} catch (Exception e) {
LOG.error("Couldn't safe table view state with prefix {}", stateStore.getName(), e);
}
}
}
@FunctionalInterface
private interface ThrowingRunnable {
void run() throws Exception; // NOSONAR
}
}

View File

@ -0,0 +1,21 @@
package ctbrec.ui.controls.table;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import javafx.scene.control.TableColumn.SortType;
public interface TableViewStateStore {
List<String> loadColumnOrder();
Map<String, Double> loadColumnWidths();
Map<String, Boolean> loadColumnVisibility();
String loadSortColumn();
SortType loadSortType();
void saveColumnOrder(List<String> columnIds) throws IOException;
void saveColumnWidths(Map<String, Double> columnIdsToWidth) throws IOException;
void saveColumnVisibility(Map<String, Boolean> columnIdsToVisibility) throws IOException;
void saveSorting(String columnId, SortType sortType) throws IOException;
String getName();
}

View File

@ -0,0 +1,9 @@
package ctbrec.ui.controls.table;
public class TableViewStateStoreException extends RuntimeException {
public TableViewStateStoreException(Exception e) {
super(e);
}
}

View File

@ -55,6 +55,8 @@ import ctbrec.ui.controls.CustomMouseBehaviorContextMenu;
import ctbrec.ui.controls.DateTimeCellFactory;
import ctbrec.ui.controls.Dialogs;
import ctbrec.ui.controls.Toast;
import ctbrec.ui.controls.table.SettingTableViewStateStore;
import ctbrec.ui.controls.table.StatePersistingTableView;
import ctbrec.ui.menu.ModelMenuContributor;
import ctbrec.ui.tabs.recorded.ModelName;
import javafx.application.Platform;
@ -80,8 +82,6 @@ import javafx.scene.control.SelectionMode;
import javafx.scene.control.Tab;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableColumn.SortType;
import javafx.scene.control.TableView;
import javafx.scene.control.Tooltip;
import javafx.scene.input.ContextMenuEvent;
import javafx.scene.input.KeyCode;
@ -109,7 +109,8 @@ public class RecordingsTab extends Tab implements TabSelectionListener, Shutdown
FlowPane grid = new FlowPane();
ScrollPane scrollPane = new ScrollPane();
TableView<JavaFxRecording> table = new TableView<>();
SettingTableViewStateStore tableStateStore = new SettingTableViewStateStore(Config.getInstance(), "recordingsTable");
StatePersistingTableView<JavaFxRecording> table = new StatePersistingTableView<>(tableStateStore);
ObservableList<JavaFxRecording> observableRecordings = FXCollections.observableArrayList();
ContextMenu popup;
ProgressBar spaceLeft;
@ -216,7 +217,7 @@ public class RecordingsTab extends Tab implements TabSelectionListener, Shutdown
root.setCenter(scrollPane);
setContent(root);
restoreState();
table.restoreState();
}
public boolean isDownloadRunning() {
@ -855,62 +856,6 @@ public class RecordingsTab extends Tab implements TabSelectionListener, Shutdown
@Override
public void onShutdown() {
if (!table.getSortOrder().isEmpty()) {
TableColumn<JavaFxRecording, ?> col = table.getSortOrder().get(0);
Config.getInstance().getSettings().recordingsSortColumn = col.getText();
Config.getInstance().getSettings().recordingsSortType = col.getSortType().toString();
}
int columns = table.getColumns().size();
var columnWidths = new double[columns];
var columnIds = new String[columns];
for (var i = 0; i < columnWidths.length; i++) {
columnWidths[i] = table.getColumns().get(i).getWidth();
columnIds[i] = table.getColumns().get(i).getId();
}
Config.getInstance().getSettings().recordingsColumnWidths = columnWidths;
Config.getInstance().getSettings().recordingsColumnIds = columnIds;
}
private void restoreState() {
restoreColumnOrder();
restoreColumnWidths();
restoreSorting();
}
private void restoreSorting() {
String sortCol = Config.getInstance().getSettings().recordingsSortColumn;
if (StringUtil.isNotBlank(sortCol)) {
for (TableColumn<JavaFxRecording, ?> col : table.getColumns()) {
if (Objects.equals(sortCol, col.getText())) {
col.setSortType(SortType.valueOf(Config.getInstance().getSettings().recordingsSortType));
table.getSortOrder().clear();
table.getSortOrder().add(col);
break;
}
}
}
}
private void restoreColumnOrder() {
String[] columnIds = Config.getInstance().getSettings().recordingsColumnIds;
ObservableList<TableColumn<JavaFxRecording, ?>> columns = table.getColumns();
for (var i = 0; i < columnIds.length; i++) {
for (var j = 0; j < table.getColumns().size(); j++) {
if (Objects.equals(columnIds[i], columns.get(j).getId())) {
TableColumn<JavaFxRecording, ?> col = columns.get(j);
columns.remove(j); // NOSONAR
columns.add(i, col);
}
}
}
}
private void restoreColumnWidths() {
double[] columnWidths = Config.getInstance().getSettings().recordingsColumnWidths;
if (columnWidths != null && columnWidths.length == table.getColumns().size()) {
for (var i = 0; i < columnWidths.length; i++) {
table.getColumns().get(i).setPrefWidth(columnWidths[i]);
}
}
table.saveState();
}
}

View File

@ -148,12 +148,13 @@ public class Settings {
public List<String> recordedModelsDisabledTableColumns = new ArrayList<>();
public String recordedModelsSortColumn = "";
public String recordedModelsSortType = "";
public double[] recordingsColumnWidths = new double[0];
public String[] recordingsColumnIds = new String[0];
public List<String> recordingsTableColumnOrder = new ArrayList<>();
public Map<String, Boolean> recordingsTableColumnVisibility = new HashMap<>();
public Map<String, Double> recordingsTableColumnWidth = new HashMap<>();
public String recordingsTableSortColumn = "";
public String recordingsTableSortType = "";
public String recordingsDir = System.getProperty("user.home") + File.separator + "ctbrec";
public DirectoryStructure recordingsDirStructure = DirectoryStructure.FLAT;
public String recordingsSortColumn = "";
public String recordingsSortType = "";
public List<Model> recordLater = new ArrayList<>();
public boolean recordSingleFile = false;
public boolean removeRecordingAfterPostProcessing = false;