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 } }