store treetable column sort on adjustment, and restore on wallet load

This commit is contained in:
Craig Raw 2025-01-28 12:53:10 +02:00
parent f9199b65f0
commit 3dfd8210a8
17 changed files with 123 additions and 53 deletions

2
drongo

@ -1 +1 @@
Subproject commit 1805aeb3740a9b90cff321219f0e60c4a1ccd3a6
Subproject commit b2c362d5a71b8c46f8990a6b6b9b96f81ee24c90

View file

@ -1,7 +1,5 @@
package com.sparrowwallet.sparrow.control;
import com.sparrowwallet.drongo.KeyPurpose;
import com.sparrowwallet.drongo.wallet.TableType;
import com.sparrowwallet.drongo.wallet.WalletNode;
import com.sparrowwallet.sparrow.AppServices;
import com.sparrowwallet.sparrow.EventManager;
@ -79,7 +77,7 @@ public class AddressTreeTable extends CoinTreeTable {
getColumns().forEach(col -> col.setContextMenu(contextMenu));
setEditable(true);
setupColumnWidths(rootEntry.getNode().getIndex() == KeyPurpose.RECEIVE.getPathIndex().num() ? TableType.RECEIVE_ADDRESSES : TableType.CHANGE_ADDRESSES);
setupColumnWidths();
addressCol.setSortType(TreeTableColumn.SortType.ASCENDING);
getSortOrder().add(addressCol);

View file

@ -1,6 +1,7 @@
package com.sparrowwallet.sparrow.control;
import com.sparrowwallet.drongo.BitcoinUnit;
import com.sparrowwallet.drongo.wallet.SortDirection;
import com.sparrowwallet.drongo.wallet.TableType;
import com.sparrowwallet.drongo.wallet.Wallet;
import com.sparrowwallet.drongo.wallet.WalletTable;
@ -8,7 +9,7 @@ import com.sparrowwallet.sparrow.CurrencyRate;
import com.sparrowwallet.sparrow.UnitFormat;
import com.sparrowwallet.sparrow.AppServices;
import com.sparrowwallet.sparrow.EventManager;
import com.sparrowwallet.sparrow.event.WalletTableColumnsResizedEvent;
import com.sparrowwallet.sparrow.event.WalletTableChangedEvent;
import com.sparrowwallet.sparrow.event.WalletAddressesChangedEvent;
import com.sparrowwallet.sparrow.event.WalletDataChangedEvent;
import com.sparrowwallet.sparrow.event.WalletHistoryStatusEvent;
@ -19,6 +20,7 @@ import com.sparrowwallet.sparrow.wallet.Entry;
import io.reactivex.Observable;
import io.reactivex.subjects.PublishSubject;
import javafx.application.Platform;
import javafx.collections.ListChangeListener;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.*;
@ -31,13 +33,22 @@ import java.util.Optional;
import java.util.concurrent.TimeUnit;
public class CoinTreeTable extends TreeTableView<Entry> {
private TableType tableType;
private BitcoinUnit bitcoinUnit;
private UnitFormat unitFormat;
private CurrencyRate currencyRate;
protected static final double STANDARD_WIDTH = 100.0;
private final PublishSubject<WalletTableColumnsResizedEvent> columnResizeSubject = PublishSubject.create();
private final Observable<WalletTableColumnsResizedEvent> columnResizeEvents = columnResizeSubject.debounce(1, TimeUnit.SECONDS);
private final PublishSubject<WalletTableChangedEvent> walletTableSubject = PublishSubject.create();
private final Observable<WalletTableChangedEvent> walletTableEvents = walletTableSubject.debounce(1, TimeUnit.SECONDS);
public TableType getTableType() {
return tableType;
}
public void setTableType(TableType tableType) {
this.tableType = tableType;
}
public BitcoinUnit getBitcoinUnit() {
return bitcoinUnit;
@ -143,17 +154,63 @@ public class CoinTreeTable extends TreeTableView<Entry> {
return stackPane;
}
public void setSortColumn(int columnIndex, TreeTableColumn.SortType sortType) {
if(columnIndex >= 0 && columnIndex < getColumns().size() && getSortOrder().isEmpty() && !getRoot().getChildren().isEmpty()) {
TreeTableColumn<Entry, ?> column = getColumns().get(columnIndex);
column.setSortType(sortType == null ? TreeTableColumn.SortType.DESCENDING : sortType);
protected void setupColumnSort(int defaultColumnIndex, TreeTableColumn.SortType defaultSortType) {
WalletTable.Sort columnSort = getSavedColumnSort();
if(columnSort == null) {
columnSort = new WalletTable.Sort(defaultColumnIndex, getSortDirection(defaultSortType));
}
setSortColumn(columnSort);
getSortOrder().addListener((ListChangeListener<? super TreeTableColumn<Entry, ?>>) c -> {
if(c.next()) {
walletTableChanged();
}
});
for(TreeTableColumn<Entry, ?> column : getColumns()) {
column.sortTypeProperty().addListener((_, _, _) -> walletTableChanged());
}
}
protected void resetSortColumn() {
setSortColumn(getColumnSort());
}
protected void setSortColumn(WalletTable.Sort sort) {
if(sort.sortColumn() >= 0 && sort.sortColumn() < getColumns().size() && getSortOrder().isEmpty() && !getRoot().getChildren().isEmpty()) {
TreeTableColumn<Entry, ?> column = getColumns().get(sort.sortColumn());
column.setSortType(sort.sortDirection() == SortDirection.DESCENDING ? TreeTableColumn.SortType.DESCENDING : TreeTableColumn.SortType.ASCENDING);
getSortOrder().add(column);
}
}
private WalletTable.Sort getColumnSort() {
if(getSortOrder().isEmpty() || !getColumns().contains(getSortOrder().getFirst())) {
return new WalletTable.Sort(tableType == TableType.UTXOS ? getColumns().size() - 1 : 0, SortDirection.DESCENDING);
}
return new WalletTable.Sort(getColumns().indexOf(getSortOrder().getFirst()), getSortDirection(getSortOrder().getFirst().getSortType()));
}
private SortDirection getSortDirection(TreeTableColumn.SortType sortType) {
return sortType == TreeTableColumn.SortType.ASCENDING ? SortDirection.ASCENDING : SortDirection.DESCENDING;
}
private WalletTable.Sort getSavedColumnSort() {
if(getRoot() != null && getRoot().getValue() != null && getRoot().getValue().getWallet() != null) {
Wallet wallet = getRoot().getValue().getWallet();
WalletTable walletTable = wallet.getWalletTable(tableType);
if(walletTable != null) {
return walletTable.getSort();
}
}
return null;
}
@SuppressWarnings("deprecation")
protected void setupColumnWidths(TableType tableType) {
Double[] savedWidths = getSavedColumnWidths(tableType);
protected void setupColumnWidths() {
Double[] savedWidths = getSavedColumnWidths();
for(int i = 0; i < getColumns().size(); i++) {
TreeTableColumn<Entry, ?> column = getColumns().get(i);
column.setPrefWidth(savedWidths != null && getColumns().size() == savedWidths.length ? savedWidths[i] : STANDARD_WIDTH);
@ -162,18 +219,27 @@ public class CoinTreeTable extends TreeTableView<Entry> {
//TODO: Replace with TreeTableView.CONSTRAINED_RESIZE_POLICY_FLEX_LAST_COLUMN when JavaFX 20+ has headless support
setColumnResizePolicy(TreeTableView.CONSTRAINED_RESIZE_POLICY);
getColumns().getLast().widthProperty().addListener((_, _, _) -> {
if(getRoot() != null && getRoot().getValue() != null && getRoot().getValue().getWallet() != null) {
Double[] widths = getColumns().stream().map(TableColumnBase::getWidth).toArray(Double[]::new);
WalletTable walletTable = new WalletTable(tableType, widths);
columnResizeSubject.onNext(new WalletTableColumnsResizedEvent(getRoot().getValue().getWallet(), walletTable));
}
getColumns().getLast().widthProperty().addListener((_, _, _) -> walletTableChanged());
//Ignore initial resizes during layout
walletTableEvents.skip(3, TimeUnit.SECONDS).subscribe(event -> {
event.getWallet().getWalletTables().put(event.getTableType(), event.getWalletTable());
EventManager.get().post(event);
});
columnResizeEvents.skip(3, TimeUnit.SECONDS).subscribe(event -> EventManager.get().post(event));
}
private Double[] getSavedColumnWidths(TableType tableType) {
private void walletTableChanged() {
if(getRoot() != null && getRoot().getValue() != null && getRoot().getValue().getWallet() != null) {
WalletTable walletTable = new WalletTable(tableType, getColumnWidths(), getColumnSort());
walletTableSubject.onNext(new WalletTableChangedEvent(getRoot().getValue().getWallet(), walletTable));
}
}
private Double[] getColumnWidths() {
return getColumns().stream().map(TableColumnBase::getWidth).toArray(Double[]::new);
}
private Double[] getSavedColumnWidths() {
if(getRoot() != null && getRoot().getValue() != null && getRoot().getValue().getWallet() != null) {
Wallet wallet = getRoot().getValue().getWallet();
WalletTable walletTable = wallet.getWalletTable(tableType);

View file

@ -806,7 +806,7 @@ public class EntryCell extends TreeTableCell<Entry, Entry> implements Confirmati
cell.getStyleClass().remove("utxo-row");
cell.getStyleClass().remove("unconfirmed-row");
cell.getStyleClass().remove("summary-row");
cell.getStyleClass().remove("address-cell");
boolean addressCell = cell.getStyleClass().remove("address-cell");
cell.getStyleClass().remove("hashindex-row");
cell.getStyleClass().remove("confirming");
cell.getStyleClass().remove("negative-amount");
@ -835,7 +835,7 @@ public class EntryCell extends TreeTableCell<Entry, Entry> implements Confirmati
if(!utxoEntry.isSpendable()) {
cell.getStyleClass().add("unspendable");
}
if(OsType.getCurrent() == OsType.MACOS && utxoEntry.getHashIndex().getHeight() > 0) {
if(OsType.getCurrent() == OsType.MACOS && utxoEntry.getHashIndex().getHeight() > 0 && !addressCell) {
cell.getStyleClass().add("number-field");
}
} else if(entry instanceof HashIndexEntry hashIndexEntry) {

View file

@ -87,6 +87,7 @@ public class SearchWalletDialog extends Dialog<Entry> {
form.getChildren().add(fieldset);
results = new CoinTreeTable();
results.setTableType(TableType.SEARCH_WALLET);
results.setShowRoot(false);
results.setPrefWidth(showWallet || showAccount ? 950 : 850);
results.setUnitFormat(walletForms.iterator().next().getWallet());
@ -174,7 +175,7 @@ public class SearchWalletDialog extends Dialog<Entry> {
results.setRoot(rootItem);
setResizable(true);
results.setupColumnWidths(TableType.SEARCH_WALLET);
results.setupColumnWidths();
AppServices.moveToActiveWindowScreen(this);

View file

@ -49,8 +49,8 @@ public class TransactionsTreeTable extends CoinTreeTable {
setPlaceholder(getDefaultPlaceholder(rootEntry.getWallet()));
setEditable(true);
setupColumnWidths(TableType.TRANSACTIONS);
setSortColumn(0, TreeTableColumn.SortType.DESCENDING);
setupColumnWidths();
setupColumnSort(0, TreeTableColumn.SortType.DESCENDING);
}
public void updateAll(WalletTransactionsEntry rootEntry) {
@ -60,13 +60,13 @@ public class TransactionsTreeTable extends CoinTreeTable {
setRoot(rootItem);
rootItem.setExpanded(true);
setSortColumn(0, TreeTableColumn.SortType.DESCENDING);
resetSortColumn();
}
public void updateHistory() {
//Transaction entries should have already been updated using WalletTransactionsEntry.updateHistory, so only a resort required
sort();
setSortColumn(0, TreeTableColumn.SortType.DESCENDING);
resetSortColumn();
}
public void updateLabel(Entry entry) {

View file

@ -82,8 +82,8 @@ public class UtxosTreeTable extends CoinTreeTable {
setPlaceholder(getDefaultPlaceholder(rootEntry.getWallet()));
setEditable(true);
setupColumnWidths(TableType.UTXOS);
setSortColumn(getColumns().size() - 1, TreeTableColumn.SortType.DESCENDING);
setupColumnWidths();
setupColumnSort(getColumns().size() - 1, TreeTableColumn.SortType.DESCENDING);
getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
}
@ -95,14 +95,14 @@ public class UtxosTreeTable extends CoinTreeTable {
setRoot(rootItem);
rootItem.setExpanded(true);
setSortColumn(getColumns().size() - 1, TreeTableColumn.SortType.DESCENDING);
resetSortColumn();
}
public void updateHistory() {
//Utxo entries should have already been updated, so only a resort required
if(!getRoot().getChildren().isEmpty()) {
sort();
setSortColumn(getColumns().size() - 1, TreeTableColumn.SortType.DESCENDING);
resetSortColumn();
}
}

View file

@ -55,6 +55,7 @@ public class WalletSummaryDialog extends Dialog<Void> {
HBox hBox = new HBox(40);
CoinTreeTable table = new CoinTreeTable();
table.setTableType(TableType.WALLET_SUMMARY);
TreeTableColumn<Entry, String> nameColumn = new TreeTableColumn<>("Wallet");
nameColumn.setCellValueFactory((TreeTableColumn.CellDataFeatures<Entry, String> param) -> {
@ -102,7 +103,7 @@ public class WalletSummaryDialog extends Dialog<Void> {
table.setRoot(rootItem);
rootItem.setExpanded(true);
table.setupColumnWidths(TableType.WALLET_SUMMARY);
table.setupColumnWidths();
table.setPrefWidth(450);
VBox vBox = new VBox();

View file

@ -4,10 +4,10 @@ import com.sparrowwallet.drongo.wallet.TableType;
import com.sparrowwallet.drongo.wallet.Wallet;
import com.sparrowwallet.drongo.wallet.WalletTable;
public class WalletTableColumnsResizedEvent extends WalletChangedEvent {
public class WalletTableChangedEvent extends WalletChangedEvent {
private final WalletTable walletTable;
public WalletTableColumnsResizedEvent(Wallet wallet, WalletTable walletTable) {
public WalletTableChangedEvent(Wallet wallet, WalletTable walletTable) {
super(wallet);
this.walletTable = walletTable;
}

View file

@ -774,7 +774,7 @@ public class DbPersistence implements Persistence {
}
@Subscribe
public void walletTableColumnsResized(WalletTableColumnsResizedEvent event) {
public void walletTableChanged(WalletTableChangedEvent event) {
if(persistsFor(event.getWallet()) && event.getTableType() != null && event.getWallet().getWalletTable(event.getTableType()) != null) {
updateExecutor.execute(() -> dirtyPersistablesMap.computeIfAbsent(event.getWallet(), key -> new DirtyPersistables()).walletTable = event.getWalletTable());
}

View file

@ -12,20 +12,20 @@ import java.util.HashMap;
import java.util.Map;
public interface WalletTableDao {
@SqlQuery("select id, type, widths from walletTable where wallet = ?")
@SqlQuery("select id, type, widths, sortColumn, sortDirection from walletTable where wallet = ?")
@RegisterRowMapper(WalletTableMapper.class)
Map<TableType, WalletTable> getForWalletId(Long id);
@SqlQuery("select id, type, widths from walletTable where type = ?")
@SqlQuery("select id, type, widths, sortColumn, sortDirection from walletTable where type = ?")
@RegisterRowMapper(WalletTableMapper.class)
Map<TableType, WalletTable> getForTypeId(int tableTypeId);
@SqlUpdate("insert into walletTable (type, widths, wallet) values (?, ?, ?)")
@SqlUpdate("insert into walletTable (type, widths, sortColumn, sortDirection, wallet) values (?, ?, ?, ?, ?)")
@GetGeneratedKeys("id")
long insertWalletTable(int tableType, Double[] widths, long wallet);
long insertWalletTable(int tableType, Double[] widths, int sortColumn, int sortDirection, long wallet);
@SqlUpdate("update walletTable set type = ?, widths = ?, wallet = ? where id = ?")
void updateWalletTable(int tableType, Double[] widths, long wallet, long id);
@SqlUpdate("update walletTable set type = ?, widths = ?, sortColumn = ?, sortDirection = ?, wallet = ? where id = ?")
void updateWalletTable(int tableType, Double[] widths, int sortColumn, int sortDirection, long wallet, long id);
default void addWalletTables(Wallet wallet) {
Map<TableType, WalletTable> walletTables = new HashMap<>(wallet.getWalletTables());
@ -39,11 +39,13 @@ public interface WalletTableDao {
Map<TableType, WalletTable> existing = getForTypeId(tableType.ordinal());
if(existing.isEmpty() && walletTable.getId() == null) {
long id = insertWalletTable(walletTable.getTableType().ordinal(), walletTable.getWidths(), wallet.getId());
long id = insertWalletTable(walletTable.getTableType().ordinal(), walletTable.getWidths(),
walletTable.getSortColumn(), walletTable.getSortDirection().ordinal(), wallet.getId());
walletTable.setId(id);
} else {
Long existingId = existing.get(tableType) != null ? existing.get(tableType).getId() : walletTable.getId();
updateWalletTable(walletTable.getTableType().ordinal(), walletTable.getWidths(), wallet.getId(), existingId);
updateWalletTable(walletTable.getTableType().ordinal(), walletTable.getWidths(),
walletTable.getSortColumn(), walletTable.getSortDirection().ordinal(), wallet.getId(), existingId);
walletTable.setId(existingId);
}
}

View file

@ -1,5 +1,6 @@
package com.sparrowwallet.sparrow.io.db;
import com.sparrowwallet.drongo.wallet.SortDirection;
import com.sparrowwallet.drongo.wallet.TableType;
import com.sparrowwallet.drongo.wallet.WalletTable;
import org.jdbi.v3.core.mapper.RowMapper;
@ -16,8 +17,10 @@ public class WalletTableMapper implements RowMapper<Map.Entry<TableType, WalletT
TableType tableType = TableType.values()[rs.getInt("type")];
Object[] objWidths = (Object[])rs.getArray("widths").getArray();
Double[] widths = Arrays.copyOf(objWidths, objWidths.length, Double[].class);
int sortColumn = rs.getInt("sortColumn");
SortDirection sortDirection = SortDirection.values()[rs.getInt("sortDirection")];
WalletTable walletTable = new WalletTable(tableType, widths);
WalletTable walletTable = new WalletTable(tableType, widths, sortColumn, sortDirection);
walletTable.setId(rs.getLong("id"));
return new Map.Entry<>() {

View file

@ -594,9 +594,8 @@ public class WalletForm {
}
@Subscribe
public void walletTableColumnsResized(WalletTableColumnsResizedEvent event) {
public void walletTableChanged(WalletTableChangedEvent event) {
if(event.getWallet() == wallet && event.getTableType() != null) {
wallet.getWalletTables().put(event.getTableType(), event.getWalletTable());
Platform.runLater(() -> EventManager.get().post(new WalletDataChangedEvent(wallet)));
}
}

View file

@ -1,2 +1,2 @@
create table walletTable (id identity not null, type integer not null, widths double precision array not null, wallet bigint not null);
create table walletTable (id identity not null, type integer not null, widths double precision array not null, sortColumn integer not null, sortDirection integer not null, wallet bigint not null);
alter table keystore add column deviceRegistration varbinary(32) after externalPaymentCode;

View file

@ -45,7 +45,7 @@
</HBox>
</top>
<center>
<AddressTreeTable fx:id="receiveTable" />
<AddressTreeTable fx:id="receiveTable" tableType="RECEIVE_ADDRESSES" />
</center>
</BorderPane>
<BorderPane GridPane.columnIndex="0" GridPane.rowIndex="1">
@ -63,7 +63,7 @@
</HBox>
</top>
<center>
<AddressTreeTable fx:id="changeTable" />
<AddressTreeTable fx:id="changeTable" tableType="CHANGE_ADDRESSES" />
</center>
</BorderPane>
</GridPane>

View file

@ -64,7 +64,7 @@
</GridPane>
<MasterDetailPane fx:id="transactionsMasterDetail" detailSide="BOTTOM" VBox.vgrow="ALWAYS">
<masterNode>
<TransactionsTreeTable fx:id="transactionsTable" />
<TransactionsTreeTable fx:id="transactionsTable" tableType="TRANSACTIONS" />
</masterNode>
<detailNode>
<TextArea fx:id="loadingLog" styleClass="readonly,fixed-width" />

View file

@ -64,7 +64,7 @@
</GridPane>
<BorderPane VBox.vgrow="ALWAYS">
<center>
<UtxosTreeTable fx:id="utxosTable" />
<UtxosTreeTable fx:id="utxosTable" tableType="UTXOS" />
</center>
<bottom>
<HBox>