From abf65b1cc23cbece4cebed4f976afbb69321ba09 Mon Sep 17 00:00:00 2001 From: 0xb00bface <0xboobface@gmail.com> Date: Sat, 11 Sep 2021 17:43:44 +0200 Subject: [PATCH] Make columns of RecordingsTable configurable --- .../table/SettingTableViewStateStore.java | 109 +++++++++++++ .../table/StatePersistingTableView.java | 147 ++++++++++++++++++ .../controls/table/TableViewStateStore.java | 21 +++ .../table/TableViewStateStoreException.java | 9 ++ .../java/ctbrec/ui/tabs/RecordingsTab.java | 67 +------- common/src/main/java/ctbrec/Settings.java | 9 +- 6 files changed, 297 insertions(+), 65 deletions(-) create mode 100644 client/src/main/java/ctbrec/ui/controls/table/SettingTableViewStateStore.java create mode 100644 client/src/main/java/ctbrec/ui/controls/table/StatePersistingTableView.java create mode 100644 client/src/main/java/ctbrec/ui/controls/table/TableViewStateStore.java create mode 100644 client/src/main/java/ctbrec/ui/controls/table/TableViewStateStoreException.java diff --git a/client/src/main/java/ctbrec/ui/controls/table/SettingTableViewStateStore.java b/client/src/main/java/ctbrec/ui/controls/table/SettingTableViewStateStore.java new file mode 100644 index 00000000..1c59195c --- /dev/null +++ b/client/src/main/java/ctbrec/ui/controls/table/SettingTableViewStateStore.java @@ -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 loadColumnOrder() { + return loadSetting(columnOrderSetting); + } + + @Override + public Map loadColumnWidths() { + return loadSetting(columnWidthSetting); + } + + @Override + public Map loadColumnVisibility() { + return loadSetting(columnVisibilitySetting); + } + + @Override + public String loadSortColumn() { + return loadSetting(sortColumnSetting); + } + + @Override + public SortType loadSortType() { + return SortType.valueOf(loadSetting(sortTypeSetting)); + } + + @SuppressWarnings("unchecked") + private 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 columnIds) throws IOException { + setSetting(columnOrderSetting, columnIds); + save(); + } + + @Override + public void saveColumnWidths(Map columnIdsToWidth) throws IOException { + setSetting(columnWidthSetting, columnIdsToWidth); + save(); + } + + @Override + public void saveColumnVisibility(Map 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; + } +} diff --git a/client/src/main/java/ctbrec/ui/controls/table/StatePersistingTableView.java b/client/src/main/java/ctbrec/ui/controls/table/StatePersistingTableView.java new file mode 100644 index 00000000..86f2bfa6 --- /dev/null +++ b/client/src/main/java/ctbrec/ui/controls/table/StatePersistingTableView.java @@ -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 extends TableView { + + 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>) 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>) c -> saveSorting()); + getColumns().forEach(tc -> tc.sortTypeProperty().addListener((obs, oldV, newV) -> saveSorting())); + } + + protected void restoreColumnVisibility() { + Map visibility = stateStore.loadColumnVisibility(); + for (TableColumn tc : getColumns()) { + tc.setVisible(visibility.getOrDefault(tc.getId(), true)); + } + } + + protected void restoreColumnWidths() { + Map widths = stateStore.loadColumnWidths(); + for (TableColumn tc : getColumns()) { + tc.setPrefWidth(widths.getOrDefault(tc.getId(), tc.getWidth())); + } + } + + protected void restoreColumnOrder() { + List order = stateStore.loadColumnOrder(); + ObservableList> 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 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 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 col = getSortOrder().get(0); + saveSetting(() -> stateStore.saveSorting(col.getId(), col.getSortType())); + } else { + saveSetting(() -> stateStore.saveSorting(null, stateStore.loadSortType())); + } + } + + protected void saveColumnVisibility() { + saveSetting(() -> { + Map columnIdToVisible = getColumns().stream().collect(Collectors.toMap(TableColumn::getId, TableColumn::isVisible)); + stateStore.saveColumnVisibility(columnIdToVisible); + }); + } + + protected void saveColumnWidths() { + saveSetting(() -> { + Map columnIdToWidth = getColumns().stream().collect(Collectors.toMap(TableColumn::getId, TableColumn::getWidth)); + stateStore.saveColumnWidths(columnIdToWidth); + }); + } + + protected void saveColumnOrder() { + saveSetting(() -> { + List 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 + } +} diff --git a/client/src/main/java/ctbrec/ui/controls/table/TableViewStateStore.java b/client/src/main/java/ctbrec/ui/controls/table/TableViewStateStore.java new file mode 100644 index 00000000..44a9b8e5 --- /dev/null +++ b/client/src/main/java/ctbrec/ui/controls/table/TableViewStateStore.java @@ -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 loadColumnOrder(); + Map loadColumnWidths(); + Map loadColumnVisibility(); + String loadSortColumn(); + SortType loadSortType(); + + void saveColumnOrder(List columnIds) throws IOException; + void saveColumnWidths(Map columnIdsToWidth) throws IOException; + void saveColumnVisibility(Map columnIdsToVisibility) throws IOException; + void saveSorting(String columnId, SortType sortType) throws IOException; + String getName(); +} diff --git a/client/src/main/java/ctbrec/ui/controls/table/TableViewStateStoreException.java b/client/src/main/java/ctbrec/ui/controls/table/TableViewStateStoreException.java new file mode 100644 index 00000000..52504e30 --- /dev/null +++ b/client/src/main/java/ctbrec/ui/controls/table/TableViewStateStoreException.java @@ -0,0 +1,9 @@ +package ctbrec.ui.controls.table; + +public class TableViewStateStoreException extends RuntimeException { + + public TableViewStateStoreException(Exception e) { + super(e); + } + +} diff --git a/client/src/main/java/ctbrec/ui/tabs/RecordingsTab.java b/client/src/main/java/ctbrec/ui/tabs/RecordingsTab.java index 57b0dee9..9bbac023 100644 --- a/client/src/main/java/ctbrec/ui/tabs/RecordingsTab.java +++ b/client/src/main/java/ctbrec/ui/tabs/RecordingsTab.java @@ -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 table = new TableView<>(); + SettingTableViewStateStore tableStateStore = new SettingTableViewStateStore(Config.getInstance(), "recordingsTable"); + StatePersistingTableView table = new StatePersistingTableView<>(tableStateStore); ObservableList 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 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 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> 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 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(); } } diff --git a/common/src/main/java/ctbrec/Settings.java b/common/src/main/java/ctbrec/Settings.java index 38bb0cf7..337b09fd 100644 --- a/common/src/main/java/ctbrec/Settings.java +++ b/common/src/main/java/ctbrec/Settings.java @@ -148,12 +148,13 @@ public class Settings { public List recordedModelsDisabledTableColumns = new ArrayList<>(); public String recordedModelsSortColumn = ""; public String recordedModelsSortType = ""; - public double[] recordingsColumnWidths = new double[0]; - public String[] recordingsColumnIds = new String[0]; + public List recordingsTableColumnOrder = new ArrayList<>(); + public Map recordingsTableColumnVisibility = new HashMap<>(); + public Map 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 recordLater = new ArrayList<>(); public boolean recordSingleFile = false; public boolean removeRecordingAfterPostProcessing = false;