14 changed files with 419 additions and 19 deletions
@ -1 +1 @@ |
|||||
Subproject commit de70f44535916f93d18f66190a217d93f94e1240 |
Subproject commit eabcf4e8f48ae18ff8d21436a2ab5e5153719944 |
@ -0,0 +1,295 @@ |
|||||
|
package com.sparrowwallet.sparrow.control; |
||||
|
|
||||
|
import com.sparrowwallet.drongo.Utils; |
||||
|
import com.sparrowwallet.drongo.address.Address; |
||||
|
import com.sparrowwallet.drongo.protocol.Transaction; |
||||
|
import com.sparrowwallet.drongo.wallet.Wallet; |
||||
|
import com.sparrowwallet.sparrow.EventManager; |
||||
|
import com.sparrowwallet.sparrow.event.ReceiveActionEvent; |
||||
|
import javafx.beans.property.ReadOnlyObjectWrapper; |
||||
|
import javafx.beans.property.SimpleStringProperty; |
||||
|
import javafx.beans.property.StringProperty; |
||||
|
import javafx.event.Event; |
||||
|
import javafx.geometry.Pos; |
||||
|
import javafx.scene.control.*; |
||||
|
import javafx.scene.control.cell.TextFieldTreeTableCell; |
||||
|
import javafx.scene.input.Clipboard; |
||||
|
import javafx.scene.input.ClipboardContent; |
||||
|
import javafx.scene.layout.HBox; |
||||
|
import javafx.scene.text.Font; |
||||
|
import javafx.util.converter.DefaultStringConverter; |
||||
|
import org.controlsfx.glyphfont.FontAwesome; |
||||
|
import org.controlsfx.glyphfont.Glyph; |
||||
|
|
||||
|
import java.lang.reflect.Field; |
||||
|
import java.util.Locale; |
||||
|
|
||||
|
public class AddressTreeTable extends TreeTableView<AddressTreeTable.Data> { |
||||
|
public void initialize(Wallet.Node rootNode) { |
||||
|
getStyleClass().add("address-treetable"); |
||||
|
|
||||
|
String address = null; |
||||
|
Data rootData = new Data(rootNode); |
||||
|
TreeItem<Data> rootItem = new TreeItem<>(rootData); |
||||
|
for(Wallet.Node childNode : rootNode.getChildren()) { |
||||
|
Data childData = new Data(childNode); |
||||
|
TreeItem<Data> childItem = new TreeItem<>(childData); |
||||
|
rootItem.getChildren().add(childItem); |
||||
|
address = childNode.getAddress().toString(); |
||||
|
} |
||||
|
|
||||
|
rootItem.setExpanded(true); |
||||
|
setRoot(rootItem); |
||||
|
setShowRoot(false); |
||||
|
|
||||
|
TreeTableColumn<Data, Data> addressCol = new TreeTableColumn<>("Address / Outpoints"); |
||||
|
addressCol.setCellValueFactory((TreeTableColumn.CellDataFeatures<Data, Data> param) -> { |
||||
|
return new ReadOnlyObjectWrapper<>(param.getValue().getValue()); |
||||
|
}); |
||||
|
addressCol.setCellFactory(p -> new DataCell()); |
||||
|
addressCol.setSortable(false); |
||||
|
getColumns().add(addressCol); |
||||
|
|
||||
|
if(address != null) { |
||||
|
addressCol.setMinWidth(TextUtils.computeTextWidth(Font.font("Courier"), address, 0.0)); |
||||
|
} |
||||
|
|
||||
|
TreeTableColumn<Data, String> labelCol = new TreeTableColumn<>("Label"); |
||||
|
labelCol.setCellValueFactory((TreeTableColumn.CellDataFeatures<Data, String> param) -> { |
||||
|
return param.getValue().getValue().getLabelProperty(); |
||||
|
}); |
||||
|
labelCol.setCellFactory(p -> new LabelCell()); |
||||
|
labelCol.setSortable(false); |
||||
|
getColumns().add(labelCol); |
||||
|
|
||||
|
TreeTableColumn<Data, Long> amountCol = new TreeTableColumn<>("Amount"); |
||||
|
amountCol.setCellValueFactory((TreeTableColumn.CellDataFeatures<Data, Long> param) -> { |
||||
|
return new ReadOnlyObjectWrapper<>(param.getValue().getValue().getAmount()); |
||||
|
}); |
||||
|
amountCol.setCellFactory(p -> new AmountCell()); |
||||
|
amountCol.setSortable(false); |
||||
|
getColumns().add(amountCol); |
||||
|
|
||||
|
TreeTableColumn<Data, Void> actionCol = new TreeTableColumn<>("Actions"); |
||||
|
actionCol.setCellFactory(p -> new ActionCell()); |
||||
|
actionCol.setSortable(false); |
||||
|
getColumns().add(actionCol); |
||||
|
|
||||
|
setEditable(true); |
||||
|
setColumnResizePolicy(TreeTableView.CONSTRAINED_RESIZE_POLICY); |
||||
|
} |
||||
|
|
||||
|
public static class Data { |
||||
|
private final Wallet.Node walletNode; |
||||
|
private final SimpleStringProperty labelProperty; |
||||
|
private final Long amount; |
||||
|
|
||||
|
public Data(Wallet.Node walletNode) { |
||||
|
this.walletNode = walletNode; |
||||
|
|
||||
|
labelProperty = new SimpleStringProperty(walletNode.getLabel()); |
||||
|
labelProperty.addListener((observable, oldValue, newValue) -> walletNode.setLabel(newValue)); |
||||
|
|
||||
|
amount = walletNode.getAmount(); |
||||
|
} |
||||
|
|
||||
|
public Wallet.Node getWalletNode() { |
||||
|
return walletNode; |
||||
|
} |
||||
|
|
||||
|
public String getLabel() { |
||||
|
return labelProperty.get(); |
||||
|
} |
||||
|
|
||||
|
public StringProperty getLabelProperty() { |
||||
|
return labelProperty; |
||||
|
} |
||||
|
|
||||
|
public Long getAmount() { |
||||
|
return amount; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static class DataCell extends TreeTableCell<Data, Data> { |
||||
|
public DataCell() { |
||||
|
super(); |
||||
|
getStyleClass().add("data-cell"); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected void updateItem(Data data, boolean empty) { |
||||
|
super.updateItem(data, empty); |
||||
|
|
||||
|
if(empty) { |
||||
|
setText(null); |
||||
|
setGraphic(null); |
||||
|
} else { |
||||
|
if(data.getWalletNode() != null) { |
||||
|
Address address = data.getWalletNode().getAddress(); |
||||
|
setText(address.toString()); |
||||
|
setContextMenu(new AddressContextMenu(address)); |
||||
|
} else { |
||||
|
//TODO: Add transaction outpoint
|
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static class AddressContextMenu extends ContextMenu { |
||||
|
public AddressContextMenu(Address address) { |
||||
|
MenuItem copyAddress = new MenuItem("Copy Address"); |
||||
|
copyAddress.setOnAction(AE -> { |
||||
|
hide(); |
||||
|
ClipboardContent content = new ClipboardContent(); |
||||
|
content.putString(address.toString()); |
||||
|
Clipboard.getSystemClipboard().setContent(content); |
||||
|
}); |
||||
|
|
||||
|
MenuItem copyHex = new MenuItem("Copy Script Output Bytes"); |
||||
|
copyHex.setOnAction(AE -> { |
||||
|
hide(); |
||||
|
ClipboardContent content = new ClipboardContent(); |
||||
|
content.putString(Utils.bytesToHex(address.getOutputScriptData())); |
||||
|
Clipboard.getSystemClipboard().setContent(content); |
||||
|
}); |
||||
|
|
||||
|
getItems().addAll(copyAddress, copyHex); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static class LabelCell extends TextFieldTreeTableCell<Data, String> { |
||||
|
public LabelCell() { |
||||
|
super(new DefaultStringConverter()); |
||||
|
getStyleClass().add("label-cell"); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void updateItem(String label, boolean empty) { |
||||
|
super.updateItem(label, empty); |
||||
|
|
||||
|
if(empty) { |
||||
|
setText(null); |
||||
|
setGraphic(null); |
||||
|
} else { |
||||
|
setText(label); |
||||
|
setContextMenu(new LabelContextMenu(label)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void commitEdit(String label) { |
||||
|
// This block is necessary to support commit on losing focus, because
|
||||
|
// the baked-in mechanism sets our editing state to false before we can
|
||||
|
// intercept the loss of focus. The default commitEdit(...) method
|
||||
|
// simply bails if we are not editing...
|
||||
|
if (!isEditing() && !label.equals(getItem())) { |
||||
|
TreeTableView<Data> treeTable = getTreeTableView(); |
||||
|
if(treeTable != null) { |
||||
|
TreeTableColumn<Data, String> column = getTableColumn(); |
||||
|
TreeTableColumn.CellEditEvent<Data, String> event = new TreeTableColumn.CellEditEvent<>( |
||||
|
treeTable, new TreeTablePosition<>(treeTable, getIndex(), column), |
||||
|
TreeTableColumn.editCommitEvent(), label |
||||
|
); |
||||
|
Event.fireEvent(column, event); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
super.commitEdit(label); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void startEdit() { |
||||
|
super.startEdit(); |
||||
|
|
||||
|
try { |
||||
|
Field f = getClass().getSuperclass().getDeclaredField("textField"); |
||||
|
f.setAccessible(true); |
||||
|
TextField textField = (TextField)f.get(this); |
||||
|
textField.focusedProperty().addListener((obs, wasFocused, isNowFocused) -> { |
||||
|
if (!isNowFocused) { |
||||
|
commitEdit(getConverter().fromString(textField.getText())); |
||||
|
setText(getConverter().fromString(textField.getText())); |
||||
|
} |
||||
|
}); |
||||
|
} catch (Exception e) { |
||||
|
e.printStackTrace(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static class LabelContextMenu extends ContextMenu { |
||||
|
public LabelContextMenu(String label) { |
||||
|
MenuItem copyLabel = new MenuItem("Copy Label"); |
||||
|
copyLabel.setOnAction(AE -> { |
||||
|
hide(); |
||||
|
ClipboardContent content = new ClipboardContent(); |
||||
|
content.putString(label); |
||||
|
Clipboard.getSystemClipboard().setContent(content); |
||||
|
}); |
||||
|
|
||||
|
getItems().add(copyLabel); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static class AmountCell extends TreeTableCell<Data, Long> { |
||||
|
public AmountCell() { |
||||
|
super(); |
||||
|
getStyleClass().add("amount-cell"); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected void updateItem(Long amount, boolean empty) { |
||||
|
super.updateItem(amount, empty); |
||||
|
|
||||
|
if(empty || amount == null) { |
||||
|
setText(null); |
||||
|
setGraphic(null); |
||||
|
} else { |
||||
|
String satsValue = String.format(Locale.ENGLISH, "%,d", amount) + " sats"; |
||||
|
String btcValue = CoinLabel.BTC_FORMAT.format(amount.doubleValue() / Transaction.SATOSHIS_PER_BITCOIN) + " BTC"; |
||||
|
Tooltip tooltip = new Tooltip(); |
||||
|
if(amount > CoinLabel.MAX_SATS_SHOWN) { |
||||
|
tooltip.setText(satsValue); |
||||
|
setText(btcValue); |
||||
|
} else { |
||||
|
tooltip.setText(btcValue); |
||||
|
setText(satsValue); |
||||
|
} |
||||
|
setTooltip(tooltip); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static class ActionCell extends TreeTableCell<Data, Void> { |
||||
|
private final HBox actionBox; |
||||
|
|
||||
|
public ActionCell() { |
||||
|
super(); |
||||
|
getStyleClass().add("action-cell"); |
||||
|
|
||||
|
actionBox = new HBox(); |
||||
|
actionBox.setSpacing(8); |
||||
|
actionBox.setAlignment(Pos.CENTER); |
||||
|
|
||||
|
Button receiveButton = new Button(""); |
||||
|
Glyph receiveGlyph = new Glyph("FontAwesome", FontAwesome.Glyph.ARROW_DOWN); |
||||
|
receiveGlyph.setFontSize(12); |
||||
|
receiveButton.setGraphic(receiveGlyph); |
||||
|
receiveButton.setOnAction(event -> { |
||||
|
EventManager.get().post(new ReceiveActionEvent(getTreeTableView().getTreeItem(getIndex()).getValue().getWalletNode())); |
||||
|
}); |
||||
|
|
||||
|
actionBox.getChildren().add(receiveButton); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected void updateItem(Void item, boolean empty) { |
||||
|
super.updateItem(item, empty); |
||||
|
if (empty) { |
||||
|
setGraphic(null); |
||||
|
} else { |
||||
|
setGraphic(actionBox); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,15 @@ |
|||||
|
package com.sparrowwallet.sparrow.event; |
||||
|
|
||||
|
import com.sparrowwallet.drongo.wallet.Wallet; |
||||
|
|
||||
|
public class ReceiveActionEvent { |
||||
|
private Wallet.Node receiveNode; |
||||
|
|
||||
|
public ReceiveActionEvent(Wallet.Node receiveNode) { |
||||
|
this.receiveNode = receiveNode; |
||||
|
} |
||||
|
|
||||
|
public Wallet.Node getReceiveNode() { |
||||
|
return receiveNode; |
||||
|
} |
||||
|
} |
@ -0,0 +1,32 @@ |
|||||
|
package com.sparrowwallet.sparrow.wallet; |
||||
|
|
||||
|
import com.sparrowwallet.drongo.KeyPurpose; |
||||
|
import com.sparrowwallet.drongo.wallet.Wallet; |
||||
|
import com.sparrowwallet.sparrow.EventManager; |
||||
|
import com.sparrowwallet.sparrow.control.AddressTreeTable; |
||||
|
import javafx.fxml.FXML; |
||||
|
import javafx.fxml.Initializable; |
||||
|
|
||||
|
import java.net.URL; |
||||
|
import java.util.ResourceBundle; |
||||
|
|
||||
|
public class AddressesController extends WalletFormController implements Initializable { |
||||
|
@FXML |
||||
|
private AddressTreeTable receiveTable; |
||||
|
|
||||
|
@FXML |
||||
|
private AddressTreeTable changeTable; |
||||
|
|
||||
|
@Override |
||||
|
public void initialize(URL location, ResourceBundle resources) { |
||||
|
EventManager.get().register(this); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void initializeView() { |
||||
|
Wallet wallet = walletForm.getWallet(); |
||||
|
|
||||
|
receiveTable.initialize(wallet.getNodes(KeyPurpose.RECEIVE)); |
||||
|
changeTable.initialize(wallet.getNodes(KeyPurpose.CHANGE)); |
||||
|
} |
||||
|
} |
@ -0,0 +1,19 @@ |
|||||
|
.address-treetable-label { |
||||
|
-fx-font-weight: bold; |
||||
|
-fx-font-size: 1.2em; |
||||
|
-fx-padding: 10 0 10 0; |
||||
|
} |
||||
|
|
||||
|
.data-cell { |
||||
|
-fx-font-family: Courier; |
||||
|
} |
||||
|
|
||||
|
.label-cell .text-field { |
||||
|
-fx-padding: 0; |
||||
|
} |
||||
|
|
||||
|
.action-cell .button { |
||||
|
-fx-padding: 0; |
||||
|
-fx-pref-height: 18; |
||||
|
-fx-pref-width: 18; |
||||
|
} |
@ -0,0 +1,38 @@ |
|||||
|
<?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 com.sparrowwallet.sparrow.control.AddressTreeTable?> |
||||
|
<GridPane hgap="10.0" vgap="10.0" stylesheets="@addresses.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.AddressesController"> |
||||
|
<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="address-treetable-label" text="Receiving Addresses:"/> |
||||
|
</top> |
||||
|
<center> |
||||
|
<AddressTreeTable fx:id="receiveTable" /> |
||||
|
</center> |
||||
|
</BorderPane> |
||||
|
<BorderPane GridPane.columnIndex="0" GridPane.rowIndex="1"> |
||||
|
<top> |
||||
|
<Label styleClass="address-treetable-label" text="Change Addresses:"/> |
||||
|
</top> |
||||
|
<center> |
||||
|
<AddressTreeTable fx:id="changeTable" /> |
||||
|
</center> |
||||
|
</BorderPane> |
||||
|
</GridPane> |
Loading…
Reference in new issue