ctbrec-5.3.2-experimental/client/src/main/java/ctbrec/ui/controls/table/StatePersistingTableView.java

148 lines
5.1 KiB
Java

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(), tc.isVisible()));
}
}
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
}
}