Craig Raw
5 years ago
19 changed files with 420 additions and 29 deletions
@ -0,0 +1,41 @@ |
|||
package com.sparrowwallet.sparrow.control; |
|||
|
|||
import com.sparrowwallet.drongo.address.Address; |
|||
import com.sparrowwallet.sparrow.wallet.Entry; |
|||
import com.sparrowwallet.sparrow.wallet.UtxoEntry; |
|||
import javafx.geometry.Pos; |
|||
import javafx.scene.control.ContentDisplay; |
|||
import javafx.scene.control.Tooltip; |
|||
import javafx.scene.control.TreeTableCell; |
|||
|
|||
public class AddressCell extends TreeTableCell<Entry, Entry> { |
|||
public AddressCell() { |
|||
super(); |
|||
setAlignment(Pos.CENTER_LEFT); |
|||
setContentDisplay(ContentDisplay.RIGHT); |
|||
} |
|||
|
|||
@Override |
|||
protected void updateItem(Entry entry, boolean empty) { |
|||
super.updateItem(entry, empty); |
|||
|
|||
EntryCell.applyRowStyles(this, entry); |
|||
getStyleClass().add("address-cell"); |
|||
|
|||
if (empty) { |
|||
setText(null); |
|||
setGraphic(null); |
|||
} else { |
|||
if(entry instanceof UtxoEntry) { |
|||
UtxoEntry utxoEntry = (UtxoEntry)entry; |
|||
Address address = utxoEntry.getAddress(); |
|||
setText(address.toString()); |
|||
setContextMenu(new EntryCell.AddressContextMenu(address, utxoEntry.getOutputDescriptor())); |
|||
Tooltip tooltip = new Tooltip(); |
|||
tooltip.setText(utxoEntry.getNode().getDerivationPath()); |
|||
setTooltip(tooltip); |
|||
} |
|||
setGraphic(null); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,68 @@ |
|||
package com.sparrowwallet.sparrow.control; |
|||
|
|||
import com.sparrowwallet.drongo.wallet.BlockTransactionHashIndex; |
|||
import com.sparrowwallet.sparrow.wallet.Entry; |
|||
import com.sparrowwallet.sparrow.wallet.UtxoEntry; |
|||
import javafx.geometry.Pos; |
|||
import javafx.scene.control.*; |
|||
import javafx.scene.input.Clipboard; |
|||
import javafx.scene.input.ClipboardContent; |
|||
|
|||
import java.text.DateFormat; |
|||
import java.text.SimpleDateFormat; |
|||
|
|||
public class DateCell extends TreeTableCell<Entry, Entry> { |
|||
private static final DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm"); |
|||
|
|||
public DateCell() { |
|||
super(); |
|||
setAlignment(Pos.CENTER_LEFT); |
|||
setContentDisplay(ContentDisplay.RIGHT); |
|||
getStyleClass().add("date-cell"); |
|||
} |
|||
|
|||
@Override |
|||
protected void updateItem(Entry entry, boolean empty) { |
|||
super.updateItem(entry, empty); |
|||
|
|||
EntryCell.applyRowStyles(this, entry); |
|||
|
|||
if (empty) { |
|||
setText(null); |
|||
setGraphic(null); |
|||
} else { |
|||
if(entry instanceof UtxoEntry) { |
|||
UtxoEntry utxoEntry = (UtxoEntry)entry; |
|||
String date = DATE_FORMAT.format(utxoEntry.getHashIndex().getDate()); |
|||
setText(date); |
|||
setContextMenu(new DateContextMenu(date, utxoEntry.getHashIndex())); |
|||
Tooltip tooltip = new Tooltip(); |
|||
tooltip.setText(Integer.toString(utxoEntry.getHashIndex().getHeight())); |
|||
setTooltip(tooltip); |
|||
} |
|||
setGraphic(null); |
|||
} |
|||
} |
|||
|
|||
private static class DateContextMenu extends ContextMenu { |
|||
public DateContextMenu(String date, BlockTransactionHashIndex reference) { |
|||
MenuItem copyDate = new MenuItem("Copy Date"); |
|||
copyDate.setOnAction(AE -> { |
|||
hide(); |
|||
ClipboardContent content = new ClipboardContent(); |
|||
content.putString(date); |
|||
Clipboard.getSystemClipboard().setContent(content); |
|||
}); |
|||
|
|||
MenuItem copyHeight = new MenuItem("Copy Block Height"); |
|||
copyHeight.setOnAction(AE -> { |
|||
hide(); |
|||
ClipboardContent content = new ClipboardContent(); |
|||
content.putString(Integer.toString(reference.getHeight())); |
|||
Clipboard.getSystemClipboard().setContent(content); |
|||
}); |
|||
|
|||
getItems().addAll(copyDate, copyHeight); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,95 @@ |
|||
package com.sparrowwallet.sparrow.control; |
|||
|
|||
import com.sparrowwallet.drongo.wallet.WalletNode; |
|||
import com.sparrowwallet.sparrow.wallet.*; |
|||
import javafx.beans.property.ReadOnlyObjectWrapper; |
|||
import javafx.scene.control.Label; |
|||
import javafx.scene.control.TreeTableColumn; |
|||
import javafx.scene.control.TreeTableView; |
|||
|
|||
import java.util.List; |
|||
|
|||
public class UtxosTreeTable extends TreeTableView<Entry> { |
|||
public void initialize(WalletUtxosEntry rootEntry) { |
|||
getStyleClass().add("utxos-treetable"); |
|||
|
|||
updateAll(rootEntry); |
|||
setShowRoot(false); |
|||
|
|||
TreeTableColumn<Entry, Entry> dateCol = new TreeTableColumn<>("Date"); |
|||
dateCol.setCellValueFactory((TreeTableColumn.CellDataFeatures<Entry, Entry> param) -> { |
|||
return new ReadOnlyObjectWrapper<>(param.getValue().getValue()); |
|||
}); |
|||
dateCol.setCellFactory(p -> new DateCell()); |
|||
dateCol.setSortable(true); |
|||
getColumns().add(dateCol); |
|||
|
|||
TreeTableColumn<Entry, Entry> outputCol = new TreeTableColumn<>("Output"); |
|||
outputCol.setCellValueFactory((TreeTableColumn.CellDataFeatures<Entry, Entry> param) -> { |
|||
return new ReadOnlyObjectWrapper<>(param.getValue().getValue()); |
|||
}); |
|||
outputCol.setCellFactory(p -> new EntryCell()); |
|||
outputCol.setSortable(true); |
|||
outputCol.setComparator((o1, o2) -> { |
|||
UtxoEntry entry1 = (UtxoEntry)o1; |
|||
UtxoEntry entry2 = (UtxoEntry)o2; |
|||
return entry1.getDescription().compareTo(entry2.getDescription()); |
|||
}); |
|||
getColumns().add(outputCol); |
|||
|
|||
TreeTableColumn<Entry, Entry> addressCol = new TreeTableColumn<>("Address"); |
|||
addressCol.setCellValueFactory((TreeTableColumn.CellDataFeatures<Entry, Entry> param) -> { |
|||
return new ReadOnlyObjectWrapper<>(param.getValue().getValue()); |
|||
}); |
|||
addressCol.setCellFactory(p -> new AddressCell()); |
|||
addressCol.setSortable(true); |
|||
addressCol.setComparator((o1, o2) -> { |
|||
UtxoEntry entry1 = (UtxoEntry)o1; |
|||
UtxoEntry entry2 = (UtxoEntry)o2; |
|||
return entry1.getAddress().toString().compareTo(entry2.getAddress().toString()); |
|||
}); |
|||
getColumns().add(addressCol); |
|||
|
|||
TreeTableColumn<Entry, String> labelCol = new TreeTableColumn<>("Label"); |
|||
labelCol.setCellValueFactory((TreeTableColumn.CellDataFeatures<Entry, String> param) -> { |
|||
return param.getValue().getValue().labelProperty(); |
|||
}); |
|||
labelCol.setCellFactory(p -> new LabelCell()); |
|||
labelCol.setSortable(true); |
|||
getColumns().add(labelCol); |
|||
|
|||
TreeTableColumn<Entry, Number> amountCol = new TreeTableColumn<>("Value"); |
|||
amountCol.setCellValueFactory((TreeTableColumn.CellDataFeatures<Entry, Number> param) -> { |
|||
return new ReadOnlyObjectWrapper<>(param.getValue().getValue().getValue()); |
|||
}); |
|||
amountCol.setCellFactory(p -> new AmountCell()); |
|||
amountCol.setSortable(true); |
|||
getColumns().add(amountCol); |
|||
setTreeColumn(amountCol); |
|||
|
|||
setPlaceholder(new Label("No unspent outputs")); |
|||
setEditable(true); |
|||
setColumnResizePolicy(TreeTableView.CONSTRAINED_RESIZE_POLICY); |
|||
dateCol.setSortType(TreeTableColumn.SortType.DESCENDING); |
|||
getSortOrder().add(dateCol); |
|||
} |
|||
|
|||
public void updateAll(WalletUtxosEntry rootEntry) { |
|||
RecursiveTreeItem<Entry> rootItem = new RecursiveTreeItem<>(rootEntry, Entry::getChildren); |
|||
setRoot(rootItem); |
|||
rootItem.setExpanded(true); |
|||
|
|||
if(getColumns().size() > 0 && getSortOrder().isEmpty()) { |
|||
TreeTableColumn<Entry, ?> dateCol = getColumns().get(0); |
|||
getSortOrder().add(dateCol); |
|||
dateCol.setSortType(TreeTableColumn.SortType.DESCENDING); |
|||
} |
|||
} |
|||
|
|||
public void updateHistory(List<WalletNode> updatedNodes) { |
|||
//Recalculate from scratch and update accordingly
|
|||
WalletUtxosEntry rootEntry = (WalletUtxosEntry)getRoot().getValue(); |
|||
rootEntry.updateUtxos(); |
|||
sort(); |
|||
} |
|||
} |
@ -1,5 +1,5 @@ |
|||
package com.sparrowwallet.sparrow.wallet; |
|||
|
|||
public enum Function { |
|||
TRANSACTIONS, SEND, RECEIVE, ADDRESSES, POLICIES, SETTINGS; |
|||
TRANSACTIONS, SEND, RECEIVE, ADDRESSES, UTXOS, SETTINGS; |
|||
} |
|||
|
@ -0,0 +1,44 @@ |
|||
package com.sparrowwallet.sparrow.wallet; |
|||
|
|||
import com.sparrowwallet.drongo.address.Address; |
|||
import com.sparrowwallet.drongo.wallet.BlockTransactionHashIndex; |
|||
import com.sparrowwallet.drongo.wallet.Wallet; |
|||
import com.sparrowwallet.drongo.wallet.WalletNode; |
|||
import javafx.collections.FXCollections; |
|||
import javafx.collections.ObservableList; |
|||
|
|||
public class UtxoEntry extends HashIndexEntry { |
|||
private final WalletNode node; |
|||
|
|||
public UtxoEntry(Wallet wallet, BlockTransactionHashIndex hashIndex, Type type, WalletNode node) { |
|||
super(wallet, hashIndex, type, node.getKeyPurpose()); |
|||
this.node = node; |
|||
} |
|||
|
|||
@Override |
|||
public ObservableList<Entry> getChildren() { |
|||
return FXCollections.emptyObservableList(); |
|||
} |
|||
|
|||
@Override |
|||
public String getDescription() { |
|||
return getHashIndex().getHash().toString().substring(0, 8) + "...:" + getHashIndex().getIndex(); |
|||
} |
|||
|
|||
@Override |
|||
public boolean isSpent() { |
|||
return false; |
|||
} |
|||
|
|||
public Address getAddress() { |
|||
return getWallet().getAddress(node); |
|||
} |
|||
|
|||
public WalletNode getNode() { |
|||
return node; |
|||
} |
|||
|
|||
public String getOutputDescriptor() { |
|||
return getWallet().getOutputDescriptor(node); |
|||
} |
|||
} |
@ -0,0 +1,42 @@ |
|||
package com.sparrowwallet.sparrow.wallet; |
|||
|
|||
import com.google.common.eventbus.Subscribe; |
|||
import com.sparrowwallet.sparrow.EventManager; |
|||
import com.sparrowwallet.sparrow.control.UtxosTreeTable; |
|||
import com.sparrowwallet.sparrow.event.WalletHistoryChangedEvent; |
|||
import com.sparrowwallet.sparrow.event.WalletNodesChangedEvent; |
|||
import javafx.fxml.FXML; |
|||
import javafx.fxml.Initializable; |
|||
|
|||
import java.net.URL; |
|||
import java.util.ResourceBundle; |
|||
|
|||
public class UtxosController extends WalletFormController implements Initializable { |
|||
|
|||
@FXML |
|||
private UtxosTreeTable utxosTable; |
|||
|
|||
@Override |
|||
public void initialize(URL location, ResourceBundle resources) { |
|||
EventManager.get().register(this); |
|||
} |
|||
|
|||
@Override |
|||
public void initializeView() { |
|||
utxosTable.initialize(getWalletForm().getWalletUtxosEntry()); |
|||
} |
|||
|
|||
@Subscribe |
|||
public void walletNodesChanged(WalletNodesChangedEvent event) { |
|||
if(event.getWallet().equals(walletForm.getWallet())) { |
|||
utxosTable.updateAll(getWalletForm().getWalletUtxosEntry()); |
|||
} |
|||
} |
|||
|
|||
@Subscribe |
|||
public void walletHistoryChanged(WalletHistoryChangedEvent event) { |
|||
if(event.getWallet().equals(walletForm.getWallet())) { |
|||
utxosTable.updateHistory(event.getHistoryChangedNodes()); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,57 @@ |
|||
package com.sparrowwallet.sparrow.wallet; |
|||
|
|||
import com.sparrowwallet.drongo.KeyPurpose; |
|||
import com.sparrowwallet.drongo.wallet.BlockTransactionHashIndex; |
|||
import com.sparrowwallet.drongo.wallet.Wallet; |
|||
import com.sparrowwallet.drongo.wallet.WalletNode; |
|||
|
|||
import java.util.*; |
|||
import java.util.stream.Collectors; |
|||
|
|||
public class WalletUtxosEntry extends Entry { |
|||
private final Wallet wallet; |
|||
|
|||
public WalletUtxosEntry(Wallet wallet) { |
|||
super(wallet.getName(), getWalletUtxos(wallet).entrySet().stream().map(entry -> new UtxoEntry(wallet, entry.getKey(), HashIndexEntry.Type.OUTPUT, entry.getValue())).collect(Collectors.toList())); |
|||
this.wallet = wallet; |
|||
} |
|||
|
|||
public Wallet getWallet() { |
|||
return wallet; |
|||
} |
|||
|
|||
@Override |
|||
public Long getValue() { |
|||
return 0L; |
|||
} |
|||
|
|||
public void updateUtxos() { |
|||
List<Entry> current = getWalletUtxos(wallet).entrySet().stream().map(entry -> new UtxoEntry(wallet, entry.getKey(), HashIndexEntry.Type.OUTPUT, entry.getValue())).collect(Collectors.toList()); |
|||
List<Entry> previous = new ArrayList<>(getChildren()); |
|||
|
|||
List<Entry> entriesAdded = new ArrayList<>(current); |
|||
entriesAdded.removeAll(previous); |
|||
getChildren().addAll(entriesAdded); |
|||
|
|||
List<Entry> entriesRemoved = new ArrayList<>(previous); |
|||
entriesRemoved.removeAll(current); |
|||
getChildren().removeAll(entriesRemoved); |
|||
} |
|||
|
|||
private static Map<BlockTransactionHashIndex, WalletNode> getWalletUtxos(Wallet wallet) { |
|||
Map<BlockTransactionHashIndex, WalletNode> walletUtxos = new TreeMap<>(); |
|||
|
|||
getWalletUtxos(wallet, walletUtxos, wallet.getNode(KeyPurpose.RECEIVE)); |
|||
getWalletUtxos(wallet, walletUtxos, wallet.getNode(KeyPurpose.CHANGE)); |
|||
|
|||
return walletUtxos; |
|||
} |
|||
|
|||
private static void getWalletUtxos(Wallet wallet, Map<BlockTransactionHashIndex, WalletNode> walletUtxos, WalletNode purposeNode) { |
|||
for(WalletNode addressNode : purposeNode.getChildren()) { |
|||
for(BlockTransactionHashIndex utxo : addressNode.getUnspentTransactionOutputs()) { |
|||
walletUtxos.put(utxo, addressNode); |
|||
} |
|||
} |
|||
} |
|||
} |
@ -1 +1,5 @@ |
|||
|
|||
.addresses-treetable-label { |
|||
-fx-font-weight: bold; |
|||
-fx-font-size: 1.2em; |
|||
-fx-padding: 10 0 10 0; |
|||
} |
|||
|
@ -0,0 +1,5 @@ |
|||
.utxos-treetable-label { |
|||
-fx-font-weight: bold; |
|||
-fx-font-size: 1.2em; |
|||
-fx-padding: 10 0 10 0; |
|||
} |
@ -0,0 +1,36 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
|
|||
<?import java.lang.*?> |
|||
<?import java.util.*?> |
|||
<?import javafx.scene.*?> |
|||
<?import javafx.scene.control.*?> |
|||
<?import javafx.scene.layout.*?> |
|||
|
|||
<?import javafx.geometry.Insets?> |
|||
<?import javafx.scene.chart.BarChart?> |
|||
<?import com.sparrowwallet.sparrow.control.UtxosTreeTable?> |
|||
<GridPane hgap="10.0" vgap="10.0" stylesheets="@utxos.css, @wallet.css, @../general.css" styleClass="wallet-pane" xmlns="http://javafx.com/javafx/10.0.2-internal" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.sparrowwallet.sparrow.wallet.UtxosController"> |
|||
<padding> |
|||
<Insets left="25.0" right="25.0" top="15.0" bottom="25.0" /> |
|||
</padding> |
|||
<columnConstraints> |
|||
<ColumnConstraints percentWidth="100.0" /> |
|||
</columnConstraints> |
|||
<rowConstraints> |
|||
<RowConstraints percentHeight="50" /> |
|||
<RowConstraints percentHeight="50" /> |
|||
</rowConstraints> |
|||
<BorderPane GridPane.columnIndex="0" GridPane.rowIndex="0"> |
|||
<top> |
|||
<Label styleClass="utxos-treetable-label" text="Unspent Transaction Outputs:"/> |
|||
</top> |
|||
<center> |
|||
<UtxosTreeTable fx:id="utxosTable" /> |
|||
</center> |
|||
</BorderPane> |
|||
<BorderPane GridPane.columnIndex="0" GridPane.rowIndex="1"> |
|||
<center> |
|||
<!-- <BarChart fx:id="changeTable" /> --> |
|||
</center> |
|||
</BorderPane> |
|||
</GridPane> |
Loading…
Reference in new issue