diff --git a/drongo b/drongo index 378ab611..1805aeb3 160000 --- a/drongo +++ b/drongo @@ -1 +1 @@ -Subproject commit 378ab611f5bc7fc06e73d8c86003b4b7ea79751d +Subproject commit 1805aeb3740a9b90cff321219f0e60c4a1ccd3a6 diff --git a/src/main/java/com/sparrowwallet/sparrow/control/AddressTreeTable.java b/src/main/java/com/sparrowwallet/sparrow/control/AddressTreeTable.java index d3437cb6..e0093cd0 100644 --- a/src/main/java/com/sparrowwallet/sparrow/control/AddressTreeTable.java +++ b/src/main/java/com/sparrowwallet/sparrow/control/AddressTreeTable.java @@ -1,5 +1,7 @@ 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; @@ -77,7 +79,7 @@ public class AddressTreeTable extends CoinTreeTable { getColumns().forEach(col -> col.setContextMenu(contextMenu)); setEditable(true); - setEqualPreferredColumnWidths(); + setupColumnWidths(rootEntry.getNode().getIndex() == KeyPurpose.RECEIVE.getPathIndex().num() ? TableType.RECEIVE_ADDRESSES : TableType.CHANGE_ADDRESSES); addressCol.setSortType(TreeTableColumn.SortType.ASCENDING); getSortOrder().add(addressCol); diff --git a/src/main/java/com/sparrowwallet/sparrow/control/CoinTreeTable.java b/src/main/java/com/sparrowwallet/sparrow/control/CoinTreeTable.java index 6f1ba858..b4d605cc 100644 --- a/src/main/java/com/sparrowwallet/sparrow/control/CoinTreeTable.java +++ b/src/main/java/com/sparrowwallet/sparrow/control/CoinTreeTable.java @@ -1,11 +1,14 @@ package com.sparrowwallet.sparrow.control; import com.sparrowwallet.drongo.BitcoinUnit; +import com.sparrowwallet.drongo.wallet.TableType; import com.sparrowwallet.drongo.wallet.Wallet; +import com.sparrowwallet.drongo.wallet.WalletTable; 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.WalletAddressesChangedEvent; import com.sparrowwallet.sparrow.event.WalletDataChangedEvent; import com.sparrowwallet.sparrow.event.WalletHistoryStatusEvent; @@ -13,19 +16,19 @@ import com.sparrowwallet.sparrow.io.Config; import com.sparrowwallet.sparrow.io.Storage; import com.sparrowwallet.sparrow.net.ServerType; import com.sparrowwallet.sparrow.wallet.Entry; +import io.reactivex.Observable; +import io.reactivex.subjects.PublishSubject; import javafx.application.Platform; import javafx.geometry.Pos; import javafx.scene.Node; -import javafx.scene.control.Hyperlink; -import javafx.scene.control.Label; -import javafx.scene.control.TreeTableColumn; -import javafx.scene.control.TreeTableView; +import javafx.scene.control.*; import javafx.scene.layout.StackPane; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Optional; +import java.util.concurrent.TimeUnit; public class CoinTreeTable extends TreeTableView { private BitcoinUnit bitcoinUnit; @@ -33,6 +36,9 @@ public class CoinTreeTable extends TreeTableView { private CurrencyRate currencyRate; protected static final double STANDARD_WIDTH = 100.0; + private final PublishSubject columnResizeSubject = PublishSubject.create(); + private final Observable columnResizeEvents = columnResizeSubject.debounce(1, TimeUnit.SECONDS); + public BitcoinUnit getBitcoinUnit() { return bitcoinUnit; } @@ -146,10 +152,36 @@ public class CoinTreeTable extends TreeTableView { } @SuppressWarnings("deprecation") - protected void setEqualPreferredColumnWidths() { - for(TreeTableColumn column : getColumns()) { - column.setPrefWidth(STANDARD_WIDTH); + protected void setupColumnWidths(TableType tableType) { + Double[] savedWidths = getSavedColumnWidths(tableType); + for(int i = 0; i < getColumns().size(); i++) { + TreeTableColumn column = getColumns().get(i); + column.setPrefWidth(savedWidths != null && getColumns().size() == savedWidths.length ? savedWidths[i] : STANDARD_WIDTH); } + + //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)); + } + }); + + columnResizeEvents.skip(3, TimeUnit.SECONDS).subscribe(event -> EventManager.get().post(event)); + } + + private Double[] getSavedColumnWidths(TableType tableType) { + 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.getWidths(); + } + } + + return null; } } diff --git a/src/main/java/com/sparrowwallet/sparrow/control/SearchWalletDialog.java b/src/main/java/com/sparrowwallet/sparrow/control/SearchWalletDialog.java index 89dcafff..efbffdda 100644 --- a/src/main/java/com/sparrowwallet/sparrow/control/SearchWalletDialog.java +++ b/src/main/java/com/sparrowwallet/sparrow/control/SearchWalletDialog.java @@ -6,6 +6,7 @@ import com.sparrowwallet.drongo.KeyPurpose; import com.sparrowwallet.drongo.Utils; import com.sparrowwallet.drongo.address.Address; import com.sparrowwallet.drongo.address.InvalidAddressException; +import com.sparrowwallet.drongo.wallet.TableType; import com.sparrowwallet.drongo.wallet.Wallet; import com.sparrowwallet.sparrow.AppServices; import com.sparrowwallet.sparrow.UnitFormat; @@ -89,7 +90,6 @@ public class SearchWalletDialog extends Dialog { results.setShowRoot(false); results.setPrefWidth(showWallet || showAccount ? 950 : 850); results.setUnitFormat(walletForms.iterator().next().getWallet()); - results.setEqualPreferredColumnWidths(); results.setPlaceholder(new Label("No results")); results.setEditable(true); @@ -169,7 +169,12 @@ public class SearchWalletDialog extends Dialog { searchWallets(newValue); }); + SearchWalletEntry rootEntry = new SearchWalletEntry(walletForms.getFirst().getWallet(), Collections.emptyList()); + RecursiveTreeItem rootItem = new RecursiveTreeItem<>(rootEntry, Entry::getChildren); + results.setRoot(rootItem); + setResizable(true); + results.setupColumnWidths(TableType.SEARCH_WALLET); AppServices.moveToActiveWindowScreen(this); diff --git a/src/main/java/com/sparrowwallet/sparrow/control/TransactionsTreeTable.java b/src/main/java/com/sparrowwallet/sparrow/control/TransactionsTreeTable.java index dfc9fe71..36f80aa7 100644 --- a/src/main/java/com/sparrowwallet/sparrow/control/TransactionsTreeTable.java +++ b/src/main/java/com/sparrowwallet/sparrow/control/TransactionsTreeTable.java @@ -1,11 +1,11 @@ package com.sparrowwallet.sparrow.control; +import com.sparrowwallet.drongo.wallet.TableType; import com.sparrowwallet.sparrow.wallet.Entry; import com.sparrowwallet.sparrow.wallet.TransactionEntry; import com.sparrowwallet.sparrow.wallet.WalletTransactionsEntry; import javafx.beans.property.ReadOnlyObjectWrapper; import javafx.scene.control.TreeTableColumn; -import javafx.scene.control.TreeTableView; public class TransactionsTreeTable extends CoinTreeTable { public void initialize(WalletTransactionsEntry rootEntry) { @@ -49,7 +49,7 @@ public class TransactionsTreeTable extends CoinTreeTable { setPlaceholder(getDefaultPlaceholder(rootEntry.getWallet())); setEditable(true); - setEqualPreferredColumnWidths(); + setupColumnWidths(TableType.TRANSACTIONS); setSortColumn(0, TreeTableColumn.SortType.DESCENDING); } diff --git a/src/main/java/com/sparrowwallet/sparrow/control/UtxosTreeTable.java b/src/main/java/com/sparrowwallet/sparrow/control/UtxosTreeTable.java index 918deb82..3a0e6cc8 100644 --- a/src/main/java/com/sparrowwallet/sparrow/control/UtxosTreeTable.java +++ b/src/main/java/com/sparrowwallet/sparrow/control/UtxosTreeTable.java @@ -1,10 +1,10 @@ package com.sparrowwallet.sparrow.control; +import com.sparrowwallet.drongo.wallet.TableType; import com.sparrowwallet.sparrow.wallet.*; import javafx.beans.property.ReadOnlyObjectWrapper; import javafx.scene.control.SelectionMode; import javafx.scene.control.TreeTableColumn; -import javafx.scene.control.TreeTableView; import java.util.Comparator; @@ -82,7 +82,7 @@ public class UtxosTreeTable extends CoinTreeTable { setPlaceholder(getDefaultPlaceholder(rootEntry.getWallet())); setEditable(true); - setEqualPreferredColumnWidths(); + setupColumnWidths(TableType.UTXOS); setSortColumn(getColumns().size() - 1, TreeTableColumn.SortType.DESCENDING); getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE); diff --git a/src/main/java/com/sparrowwallet/sparrow/control/WalletSummaryDialog.java b/src/main/java/com/sparrowwallet/sparrow/control/WalletSummaryDialog.java index f93b9612..abc46826 100644 --- a/src/main/java/com/sparrowwallet/sparrow/control/WalletSummaryDialog.java +++ b/src/main/java/com/sparrowwallet/sparrow/control/WalletSummaryDialog.java @@ -1,5 +1,6 @@ package com.sparrowwallet.sparrow.control; +import com.sparrowwallet.drongo.wallet.TableType; import com.sparrowwallet.drongo.wallet.Wallet; import com.sparrowwallet.sparrow.AppServices; import com.sparrowwallet.sparrow.CurrencyRate; @@ -101,7 +102,7 @@ public class WalletSummaryDialog extends Dialog { table.setRoot(rootItem); rootItem.setExpanded(true); - table.setEqualPreferredColumnWidths(); + table.setupColumnWidths(TableType.WALLET_SUMMARY); table.setPrefWidth(450); VBox vBox = new VBox(); diff --git a/src/main/java/com/sparrowwallet/sparrow/event/WalletTableColumnsResizedEvent.java b/src/main/java/com/sparrowwallet/sparrow/event/WalletTableColumnsResizedEvent.java new file mode 100644 index 00000000..294ee215 --- /dev/null +++ b/src/main/java/com/sparrowwallet/sparrow/event/WalletTableColumnsResizedEvent.java @@ -0,0 +1,22 @@ +package com.sparrowwallet.sparrow.event; + +import com.sparrowwallet.drongo.wallet.TableType; +import com.sparrowwallet.drongo.wallet.Wallet; +import com.sparrowwallet.drongo.wallet.WalletTable; + +public class WalletTableColumnsResizedEvent extends WalletChangedEvent { + private final WalletTable walletTable; + + public WalletTableColumnsResizedEvent(Wallet wallet, WalletTable walletTable) { + super(wallet); + this.walletTable = walletTable; + } + + public WalletTable getWalletTable() { + return walletTable; + } + + public TableType getTableType() { + return walletTable == null ? null : walletTable.getTableType(); + } +} diff --git a/src/main/java/com/sparrowwallet/sparrow/io/db/DbPersistence.java b/src/main/java/com/sparrowwallet/sparrow/io/db/DbPersistence.java index 6eb42151..5649a763 100644 --- a/src/main/java/com/sparrowwallet/sparrow/io/db/DbPersistence.java +++ b/src/main/java/com/sparrowwallet/sparrow/io/db/DbPersistence.java @@ -323,6 +323,11 @@ public class DbPersistence implements Persistence { walletConfigDao.addOrUpdate(wallet, wallet.getWalletConfig()); } + if(dirtyPersistables.walletTable != null) { + WalletTableDao walletTableDao = handle.attach(WalletTableDao.class); + walletTableDao.addOrUpdate(wallet, dirtyPersistables.walletTable.getTableType(), dirtyPersistables.walletTable); + } + if(dirtyPersistables.mixConfig) { MixConfigDao mixConfigDao = handle.attach(MixConfigDao.class); mixConfigDao.addOrUpdate(wallet, wallet.getMixConfig()); @@ -768,6 +773,13 @@ public class DbPersistence implements Persistence { } } + @Subscribe + public void walletTableColumnsResized(WalletTableColumnsResizedEvent 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()); + } + } + @Subscribe public void walletMixConfigChanged(WalletMixConfigChangedEvent event) { if(persistsFor(event.getWallet()) && event.getWallet().getMixConfig() != null) { @@ -824,6 +836,7 @@ public class DbPersistence implements Persistence { public final List labelEntries = new ArrayList<>(); public final List utxoStatuses = new ArrayList<>(); public boolean walletConfig; + public WalletTable walletTable = null; public boolean mixConfig; public final Map changedUtxoMixes = new HashMap<>(); public final Map removedUtxoMixes = new HashMap<>(); @@ -845,6 +858,7 @@ public class DbPersistence implements Persistence { "\nUTXO labels:" + labelEntries.stream().filter(entry -> entry instanceof HashIndexEntry).map(entry -> ((HashIndexEntry)entry).getHashIndex().toString()).collect(Collectors.toList()) + "\nUTXO statuses:" + utxoStatuses + "\nWallet config:" + walletConfig + + "\nWallet table:" + walletTable + "\nMix config:" + mixConfig + "\nUTXO mixes changed:" + changedUtxoMixes + "\nUTXO mixes removed:" + removedUtxoMixes + diff --git a/src/main/java/com/sparrowwallet/sparrow/io/db/WalletConfigMapper.java b/src/main/java/com/sparrowwallet/sparrow/io/db/WalletConfigMapper.java index d172495c..3ceae711 100644 --- a/src/main/java/com/sparrowwallet/sparrow/io/db/WalletConfigMapper.java +++ b/src/main/java/com/sparrowwallet/sparrow/io/db/WalletConfigMapper.java @@ -7,7 +7,7 @@ import org.jdbi.v3.core.statement.StatementContext; import java.sql.ResultSet; import java.sql.SQLException; -public class WalletConfigMapper implements RowMapper { +public class WalletConfigMapper implements RowMapper { @Override public WalletConfig map(ResultSet rs, StatementContext ctx) throws SQLException { byte[] iconData = rs.getBytes("iconData"); diff --git a/src/main/java/com/sparrowwallet/sparrow/io/db/WalletDao.java b/src/main/java/com/sparrowwallet/sparrow/io/db/WalletDao.java index 82a20882..7175c640 100644 --- a/src/main/java/com/sparrowwallet/sparrow/io/db/WalletDao.java +++ b/src/main/java/com/sparrowwallet/sparrow/io/db/WalletDao.java @@ -33,6 +33,9 @@ public interface WalletDao { @CreateSqlObject WalletConfigDao createWalletConfigDao(); + @CreateSqlObject + WalletTableDao createWalletTableDao(); + @CreateSqlObject MixConfigDao createMixConfigDao(); @@ -119,6 +122,10 @@ public interface WalletDao { wallet.getDetachedLabels().putAll(detachedLabels); wallet.setWalletConfig(createWalletConfigDao().getForWalletId(wallet.getId())); + + Map walletTables = createWalletTableDao().getForWalletId(wallet.getId()); + wallet.getWalletTables().putAll(walletTables); + wallet.setMixConfig(createMixConfigDao().getForWalletId(wallet.getId())); Map utxoMixes = createUtxoMixDataDao().getForWalletId(wallet.getId()); @@ -138,6 +145,7 @@ public interface WalletDao { createBlockTransactionDao().addBlockTransactions(wallet); createDetachedLabelDao().clearAndAddAll(wallet); createWalletConfigDao().addWalletConfig(wallet); + createWalletTableDao().addWalletTables(wallet); createMixConfigDao().addMixConfig(wallet); createUtxoMixDataDao().addUtxoMixData(wallet); } finally { diff --git a/src/main/java/com/sparrowwallet/sparrow/io/db/WalletTableDao.java b/src/main/java/com/sparrowwallet/sparrow/io/db/WalletTableDao.java new file mode 100644 index 00000000..e3eb4577 --- /dev/null +++ b/src/main/java/com/sparrowwallet/sparrow/io/db/WalletTableDao.java @@ -0,0 +1,50 @@ +package com.sparrowwallet.sparrow.io.db; + +import com.sparrowwallet.drongo.wallet.TableType; +import com.sparrowwallet.drongo.wallet.Wallet; +import com.sparrowwallet.drongo.wallet.WalletTable; +import org.jdbi.v3.sqlobject.config.RegisterRowMapper; +import org.jdbi.v3.sqlobject.statement.GetGeneratedKeys; +import org.jdbi.v3.sqlobject.statement.SqlQuery; +import org.jdbi.v3.sqlobject.statement.SqlUpdate; + +import java.util.HashMap; +import java.util.Map; + +public interface WalletTableDao { + @SqlQuery("select id, type, widths from walletTable where wallet = ?") + @RegisterRowMapper(WalletTableMapper.class) + Map getForWalletId(Long id); + + @SqlQuery("select id, type, widths from walletTable where type = ?") + @RegisterRowMapper(WalletTableMapper.class) + Map getForTypeId(int tableTypeId); + + @SqlUpdate("insert into walletTable (type, widths, wallet) values (?, ?, ?)") + @GetGeneratedKeys("id") + long insertWalletTable(int tableType, Double[] widths, long wallet); + + @SqlUpdate("update walletTable set type = ?, widths = ?, wallet = ? where id = ?") + void updateWalletTable(int tableType, Double[] widths, long wallet, long id); + + default void addWalletTables(Wallet wallet) { + Map walletTables = new HashMap<>(wallet.getWalletTables()); + for(Map.Entry tableEntry : walletTables.entrySet()) { + tableEntry.getValue().setId(null); + addOrUpdate(wallet, tableEntry.getKey(), tableEntry.getValue()); + } + } + + default void addOrUpdate(Wallet wallet, TableType tableType, WalletTable walletTable) { + Map existing = getForTypeId(tableType.ordinal()); + + if(existing.isEmpty() && walletTable.getId() == null) { + long id = insertWalletTable(walletTable.getTableType().ordinal(), walletTable.getWidths(), 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); + walletTable.setId(existingId); + } + } +} diff --git a/src/main/java/com/sparrowwallet/sparrow/io/db/WalletTableMapper.java b/src/main/java/com/sparrowwallet/sparrow/io/db/WalletTableMapper.java new file mode 100644 index 00000000..c307a391 --- /dev/null +++ b/src/main/java/com/sparrowwallet/sparrow/io/db/WalletTableMapper.java @@ -0,0 +1,40 @@ +package com.sparrowwallet.sparrow.io.db; + +import com.sparrowwallet.drongo.wallet.TableType; +import com.sparrowwallet.drongo.wallet.WalletTable; +import org.jdbi.v3.core.mapper.RowMapper; +import org.jdbi.v3.core.statement.StatementContext; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Arrays; +import java.util.Map; + +public class WalletTableMapper implements RowMapper> { + @Override + public Map.Entry map(ResultSet rs, StatementContext ctx) throws SQLException { + TableType tableType = TableType.values()[rs.getInt("type")]; + Object[] objWidths = (Object[])rs.getArray("widths").getArray(); + Double[] widths = Arrays.copyOf(objWidths, objWidths.length, Double[].class); + + WalletTable walletTable = new WalletTable(tableType, widths); + walletTable.setId(rs.getLong("id")); + + return new Map.Entry<>() { + @Override + public TableType getKey() { + return tableType; + } + + @Override + public WalletTable getValue() { + return walletTable; + } + + @Override + public WalletTable setValue(WalletTable value) { + return null; + } + }; + } +} diff --git a/src/main/java/com/sparrowwallet/sparrow/wallet/WalletForm.java b/src/main/java/com/sparrowwallet/sparrow/wallet/WalletForm.java index 7940724b..8e4fb3b6 100644 --- a/src/main/java/com/sparrowwallet/sparrow/wallet/WalletForm.java +++ b/src/main/java/com/sparrowwallet/sparrow/wallet/WalletForm.java @@ -593,6 +593,14 @@ public class WalletForm { } } + @Subscribe + public void walletTableColumnsResized(WalletTableColumnsResizedEvent event) { + if(event.getWallet() == wallet && event.getTableType() != null) { + wallet.getWalletTables().put(event.getTableType(), event.getWalletTable()); + Platform.runLater(() -> EventManager.get().post(new WalletDataChangedEvent(wallet))); + } + } + @Subscribe public void walletMixConfigChanged(WalletMixConfigChangedEvent event) { if(event.getWallet() == wallet) { diff --git a/src/main/resources/com/sparrowwallet/sparrow/sql/V9__KeystoreRegistration.sql b/src/main/resources/com/sparrowwallet/sparrow/sql/V9__KeystoreRegistration.sql deleted file mode 100644 index b896178c..00000000 --- a/src/main/resources/com/sparrowwallet/sparrow/sql/V9__KeystoreRegistration.sql +++ /dev/null @@ -1 +0,0 @@ -alter table keystore add column deviceRegistration varbinary(32) after externalPaymentCode; \ No newline at end of file diff --git a/src/main/resources/com/sparrowwallet/sparrow/sql/V9__WalletTable.sql b/src/main/resources/com/sparrowwallet/sparrow/sql/V9__WalletTable.sql new file mode 100644 index 00000000..3590afa3 --- /dev/null +++ b/src/main/resources/com/sparrowwallet/sparrow/sql/V9__WalletTable.sql @@ -0,0 +1,2 @@ +create table walletTable (id identity not null, type integer not null, widths double precision array not null, wallet bigint not null); +alter table keystore add column deviceRegistration varbinary(32) after externalPaymentCode; \ No newline at end of file