mirror of
https://github.com/sparrowwallet/sparrow.git
synced 2024-12-25 05:06:45 +00:00
receive pane
This commit is contained in:
parent
cabd62166a
commit
3db7fc1e99
22 changed files with 557 additions and 193 deletions
|
@ -32,7 +32,7 @@ dependencies {
|
||||||
implementation('com.google.code.gson:gson:2.8.6')
|
implementation('com.google.code.gson:gson:2.8.6')
|
||||||
implementation('org.fxmisc.richtext:richtextfx:0.10.4')
|
implementation('org.fxmisc.richtext:richtextfx:0.10.4')
|
||||||
implementation('no.tornado:tornadofx-controls:1.0.4')
|
implementation('no.tornado:tornadofx-controls:1.0.4')
|
||||||
implementation('org.apache.commons:commons-compress:1.20')
|
implementation('com.google.zxing:javase:3.4.0')
|
||||||
implementation('org.controlsfx:controlsfx:11.0.1' ) {
|
implementation('org.controlsfx:controlsfx:11.0.1' ) {
|
||||||
exclude group: 'org.openjfx', module: 'javafx-base'
|
exclude group: 'org.openjfx', module: 'javafx-base'
|
||||||
exclude group: 'org.openjfx', module: 'javafx-graphics'
|
exclude group: 'org.openjfx', module: 'javafx-graphics'
|
||||||
|
|
2
drongo
2
drongo
|
@ -1 +1 @@
|
||||||
Subproject commit eabcf4e8f48ae18ff8d21436a2ab5e5153719944
|
Subproject commit 7871413573e67ed7539cf03d6deadd1a2c4abafa
|
113
src/main/java/com/sparrowwallet/sparrow/BaseController.java
Normal file
113
src/main/java/com/sparrowwallet/sparrow/BaseController.java
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
package com.sparrowwallet.sparrow;
|
||||||
|
|
||||||
|
import com.sparrowwallet.drongo.protocol.Script;
|
||||||
|
import com.sparrowwallet.drongo.protocol.ScriptChunk;
|
||||||
|
import com.sparrowwallet.sparrow.transaction.ScriptContextMenu;
|
||||||
|
import javafx.geometry.Point2D;
|
||||||
|
import javafx.scene.control.Label;
|
||||||
|
import javafx.stage.Popup;
|
||||||
|
import org.fxmisc.richtext.CodeArea;
|
||||||
|
import org.fxmisc.richtext.event.MouseOverTextEvent;
|
||||||
|
import org.fxmisc.richtext.model.TwoDimensional;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
|
||||||
|
import static com.sparrowwallet.drongo.protocol.ScriptType.*;
|
||||||
|
import static com.sparrowwallet.drongo.protocol.ScriptType.P2WSH;
|
||||||
|
import static org.fxmisc.richtext.model.TwoDimensional.Bias.Backward;
|
||||||
|
|
||||||
|
public abstract class BaseController {
|
||||||
|
protected void appendScript(CodeArea codeArea, Script script) {
|
||||||
|
appendScript(codeArea, script, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void appendScript(CodeArea codeArea, Script script, Script redeemScript, Script witnessScript) {
|
||||||
|
if(P2PKH.isScriptType(script)) {
|
||||||
|
codeArea.append(script.getChunks().get(0).toString(), "script-opcode");
|
||||||
|
codeArea.append(" ", "");
|
||||||
|
codeArea.append(script.getChunks().get(1).toString(), "script-opcode");
|
||||||
|
codeArea.append(" ", "");
|
||||||
|
codeArea.append("<pkh>", "script-hash");
|
||||||
|
codeArea.append(" ", "");
|
||||||
|
codeArea.append(script.getChunks().get(3).toString(), "script-opcode");
|
||||||
|
codeArea.append(" ", "");
|
||||||
|
codeArea.append(script.getChunks().get(4).toString(), "script-opcode");
|
||||||
|
} else if(P2SH.isScriptType(script)) {
|
||||||
|
codeArea.append(script.getChunks().get(0).toString(), "script-opcode");
|
||||||
|
codeArea.append(" ", "");
|
||||||
|
codeArea.append("<sh>", "script-hash");
|
||||||
|
codeArea.append(" ", "");
|
||||||
|
codeArea.append(script.getChunks().get(2).toString(), "script-opcode");
|
||||||
|
} else if(P2WPKH.isScriptType(script)) {
|
||||||
|
codeArea.append(script.getChunks().get(0).toString(), "script-opcode");
|
||||||
|
codeArea.append(" ", "");
|
||||||
|
codeArea.append("<wpkh>", "script-hash");
|
||||||
|
} else if(P2WSH.isScriptType(script)) {
|
||||||
|
codeArea.append(script.getChunks().get(0).toString(), "script-opcode");
|
||||||
|
codeArea.append(" ", "");
|
||||||
|
codeArea.append("<wsh>", "script-hash");
|
||||||
|
} else {
|
||||||
|
int signatureCount = 1;
|
||||||
|
int pubKeyCount = 1;
|
||||||
|
for (int i = 0; i < script.getChunks().size(); i++) {
|
||||||
|
ScriptChunk chunk = script.getChunks().get(i);
|
||||||
|
if(chunk.isOpCode()) {
|
||||||
|
codeArea.append(chunk.toString(), "script-opcode");
|
||||||
|
} else if(chunk.isSignature()) {
|
||||||
|
codeArea.append("<signature" + signatureCount++ + ">", "script-signature");
|
||||||
|
} else if(chunk.isPubKey()) {
|
||||||
|
codeArea.append("<pubkey" + pubKeyCount++ + ">", "script-pubkey");
|
||||||
|
} else if(chunk.isScript()) {
|
||||||
|
Script nestedScript = chunk.getScript();
|
||||||
|
if (nestedScript.equals(redeemScript)) {
|
||||||
|
codeArea.append("<RedeemScript>", "script-redeem");
|
||||||
|
} else if (nestedScript.equals(witnessScript)) {
|
||||||
|
codeArea.append("<WitnessScript>", "script-redeem");
|
||||||
|
} else {
|
||||||
|
codeArea.append("(", "script-nest");
|
||||||
|
appendScript(codeArea, nestedScript);
|
||||||
|
codeArea.append(")", "script-nest");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
codeArea.append(chunk.toString(), "script-other");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(i < script.getChunks().size() - 1) {
|
||||||
|
codeArea.append(" ", "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addScriptPopup(codeArea, script);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void addScriptPopup(CodeArea area, Script script) {
|
||||||
|
ScriptContextMenu contextMenu = new ScriptContextMenu(area, script);
|
||||||
|
area.setContextMenu(contextMenu);
|
||||||
|
|
||||||
|
Popup popup = new Popup();
|
||||||
|
Label popupMsg = new Label();
|
||||||
|
popupMsg.getStyleClass().add("tooltip");
|
||||||
|
popup.getContent().add(popupMsg);
|
||||||
|
|
||||||
|
area.setMouseOverTextDelay(Duration.ofMillis(150));
|
||||||
|
area.addEventHandler(MouseOverTextEvent.MOUSE_OVER_TEXT_BEGIN, e -> {
|
||||||
|
TwoDimensional.Position position = area.getParagraph(0).getStyleSpans().offsetToPosition(e.getCharacterIndex(), Backward);
|
||||||
|
if(position.getMajor() % 2 == 0) {
|
||||||
|
ScriptChunk hoverChunk = script.getChunks().get(position.getMajor()/2);
|
||||||
|
if(!hoverChunk.isOpCode()) {
|
||||||
|
Point2D pos = e.getScreenPosition();
|
||||||
|
popupMsg.setText(describeScriptChunk(hoverChunk));
|
||||||
|
popup.show(area, pos.getX(), pos.getY() + 10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
area.addEventHandler(MouseOverTextEvent.MOUSE_OVER_TEXT_END, e -> {
|
||||||
|
popup.hide();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String describeScriptChunk(ScriptChunk chunk) {
|
||||||
|
return chunk.toString();
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,12 +3,11 @@ package com.sparrowwallet.sparrow.control;
|
||||||
import com.sparrowwallet.drongo.Utils;
|
import com.sparrowwallet.drongo.Utils;
|
||||||
import com.sparrowwallet.drongo.address.Address;
|
import com.sparrowwallet.drongo.address.Address;
|
||||||
import com.sparrowwallet.drongo.protocol.Transaction;
|
import com.sparrowwallet.drongo.protocol.Transaction;
|
||||||
import com.sparrowwallet.drongo.wallet.Wallet;
|
|
||||||
import com.sparrowwallet.sparrow.EventManager;
|
import com.sparrowwallet.sparrow.EventManager;
|
||||||
import com.sparrowwallet.sparrow.event.ReceiveActionEvent;
|
import com.sparrowwallet.sparrow.event.ReceiveActionEvent;
|
||||||
|
import com.sparrowwallet.sparrow.wallet.Entry;
|
||||||
|
import com.sparrowwallet.sparrow.wallet.NodeEntry;
|
||||||
import javafx.beans.property.ReadOnlyObjectWrapper;
|
import javafx.beans.property.ReadOnlyObjectWrapper;
|
||||||
import javafx.beans.property.SimpleStringProperty;
|
|
||||||
import javafx.beans.property.StringProperty;
|
|
||||||
import javafx.event.Event;
|
import javafx.event.Event;
|
||||||
import javafx.geometry.Pos;
|
import javafx.geometry.Pos;
|
||||||
import javafx.scene.control.*;
|
import javafx.scene.control.*;
|
||||||
|
@ -24,26 +23,24 @@ import org.controlsfx.glyphfont.Glyph;
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
public class AddressTreeTable extends TreeTableView<AddressTreeTable.Data> {
|
public class AddressTreeTable extends TreeTableView<Entry> {
|
||||||
public void initialize(Wallet.Node rootNode) {
|
public void initialize(NodeEntry rootEntry) {
|
||||||
getStyleClass().add("address-treetable");
|
getStyleClass().add("address-treetable");
|
||||||
|
|
||||||
String address = null;
|
String address = null;
|
||||||
Data rootData = new Data(rootNode);
|
TreeItem<Entry> rootItem = new TreeItem<>(rootEntry);
|
||||||
TreeItem<Data> rootItem = new TreeItem<>(rootData);
|
for(Entry childEntry : rootEntry.getChildren()) {
|
||||||
for(Wallet.Node childNode : rootNode.getChildren()) {
|
TreeItem<Entry> childItem = new TreeItem<>(childEntry);
|
||||||
Data childData = new Data(childNode);
|
|
||||||
TreeItem<Data> childItem = new TreeItem<>(childData);
|
|
||||||
rootItem.getChildren().add(childItem);
|
rootItem.getChildren().add(childItem);
|
||||||
address = childNode.getAddress().toString();
|
address = rootEntry.getNode().getAddress().toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
rootItem.setExpanded(true);
|
rootItem.setExpanded(true);
|
||||||
setRoot(rootItem);
|
setRoot(rootItem);
|
||||||
setShowRoot(false);
|
setShowRoot(false);
|
||||||
|
|
||||||
TreeTableColumn<Data, Data> addressCol = new TreeTableColumn<>("Address / Outpoints");
|
TreeTableColumn<Entry, Entry> addressCol = new TreeTableColumn<>("Address / Outpoints");
|
||||||
addressCol.setCellValueFactory((TreeTableColumn.CellDataFeatures<Data, Data> param) -> {
|
addressCol.setCellValueFactory((TreeTableColumn.CellDataFeatures<Entry, Entry> param) -> {
|
||||||
return new ReadOnlyObjectWrapper<>(param.getValue().getValue());
|
return new ReadOnlyObjectWrapper<>(param.getValue().getValue());
|
||||||
});
|
});
|
||||||
addressCol.setCellFactory(p -> new DataCell());
|
addressCol.setCellFactory(p -> new DataCell());
|
||||||
|
@ -54,23 +51,26 @@ public class AddressTreeTable extends TreeTableView<AddressTreeTable.Data> {
|
||||||
addressCol.setMinWidth(TextUtils.computeTextWidth(Font.font("Courier"), address, 0.0));
|
addressCol.setMinWidth(TextUtils.computeTextWidth(Font.font("Courier"), address, 0.0));
|
||||||
}
|
}
|
||||||
|
|
||||||
TreeTableColumn<Data, String> labelCol = new TreeTableColumn<>("Label");
|
TreeTableColumn<Entry, String> labelCol = new TreeTableColumn<>("Label");
|
||||||
labelCol.setCellValueFactory((TreeTableColumn.CellDataFeatures<Data, String> param) -> {
|
labelCol.setCellValueFactory((TreeTableColumn.CellDataFeatures<Entry, String> param) -> {
|
||||||
return param.getValue().getValue().getLabelProperty();
|
return param.getValue().getValue().labelProperty();
|
||||||
});
|
});
|
||||||
labelCol.setCellFactory(p -> new LabelCell());
|
labelCol.setCellFactory(p -> new LabelCell());
|
||||||
labelCol.setSortable(false);
|
labelCol.setSortable(false);
|
||||||
getColumns().add(labelCol);
|
getColumns().add(labelCol);
|
||||||
|
|
||||||
TreeTableColumn<Data, Long> amountCol = new TreeTableColumn<>("Amount");
|
TreeTableColumn<Entry, Long> amountCol = new TreeTableColumn<>("Amount");
|
||||||
amountCol.setCellValueFactory((TreeTableColumn.CellDataFeatures<Data, Long> param) -> {
|
amountCol.setCellValueFactory((TreeTableColumn.CellDataFeatures<Entry, Long> param) -> {
|
||||||
return new ReadOnlyObjectWrapper<>(param.getValue().getValue().getAmount());
|
return new ReadOnlyObjectWrapper<>(param.getValue().getValue().getAmount());
|
||||||
});
|
});
|
||||||
amountCol.setCellFactory(p -> new AmountCell());
|
amountCol.setCellFactory(p -> new AmountCell());
|
||||||
amountCol.setSortable(false);
|
amountCol.setSortable(false);
|
||||||
getColumns().add(amountCol);
|
getColumns().add(amountCol);
|
||||||
|
|
||||||
TreeTableColumn<Data, Void> actionCol = new TreeTableColumn<>("Actions");
|
TreeTableColumn<Entry, Entry> actionCol = new TreeTableColumn<>("Actions");
|
||||||
|
actionCol.setCellValueFactory((TreeTableColumn.CellDataFeatures<Entry, Entry> param) -> {
|
||||||
|
return new ReadOnlyObjectWrapper<>(param.getValue().getValue());
|
||||||
|
});
|
||||||
actionCol.setCellFactory(p -> new ActionCell());
|
actionCol.setCellFactory(p -> new ActionCell());
|
||||||
actionCol.setSortable(false);
|
actionCol.setSortable(false);
|
||||||
getColumns().add(actionCol);
|
getColumns().add(actionCol);
|
||||||
|
@ -79,53 +79,23 @@ public class AddressTreeTable extends TreeTableView<AddressTreeTable.Data> {
|
||||||
setColumnResizePolicy(TreeTableView.CONSTRAINED_RESIZE_POLICY);
|
setColumnResizePolicy(TreeTableView.CONSTRAINED_RESIZE_POLICY);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Data {
|
private static class DataCell extends TreeTableCell<Entry, Entry> {
|
||||||
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() {
|
public DataCell() {
|
||||||
super();
|
super();
|
||||||
getStyleClass().add("data-cell");
|
getStyleClass().add("address-cell");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void updateItem(Data data, boolean empty) {
|
protected void updateItem(Entry entry, boolean empty) {
|
||||||
super.updateItem(data, empty);
|
super.updateItem(entry, empty);
|
||||||
|
|
||||||
if(empty) {
|
if(empty) {
|
||||||
setText(null);
|
setText(null);
|
||||||
setGraphic(null);
|
setGraphic(null);
|
||||||
} else {
|
} else {
|
||||||
if(data.getWalletNode() != null) {
|
if(entry instanceof NodeEntry) {
|
||||||
Address address = data.getWalletNode().getAddress();
|
NodeEntry nodeEntry = (NodeEntry)entry;
|
||||||
|
Address address = nodeEntry.getNode().getAddress();
|
||||||
setText(address.toString());
|
setText(address.toString());
|
||||||
setContextMenu(new AddressContextMenu(address));
|
setContextMenu(new AddressContextMenu(address));
|
||||||
} else {
|
} else {
|
||||||
|
@ -157,7 +127,7 @@ public class AddressTreeTable extends TreeTableView<AddressTreeTable.Data> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class LabelCell extends TextFieldTreeTableCell<Data, String> {
|
private static class LabelCell extends TextFieldTreeTableCell<Entry, String> {
|
||||||
public LabelCell() {
|
public LabelCell() {
|
||||||
super(new DefaultStringConverter());
|
super(new DefaultStringConverter());
|
||||||
getStyleClass().add("label-cell");
|
getStyleClass().add("label-cell");
|
||||||
|
@ -183,10 +153,10 @@ public class AddressTreeTable extends TreeTableView<AddressTreeTable.Data> {
|
||||||
// intercept the loss of focus. The default commitEdit(...) method
|
// intercept the loss of focus. The default commitEdit(...) method
|
||||||
// simply bails if we are not editing...
|
// simply bails if we are not editing...
|
||||||
if (!isEditing() && !label.equals(getItem())) {
|
if (!isEditing() && !label.equals(getItem())) {
|
||||||
TreeTableView<Data> treeTable = getTreeTableView();
|
TreeTableView<Entry> treeTable = getTreeTableView();
|
||||||
if(treeTable != null) {
|
if(treeTable != null) {
|
||||||
TreeTableColumn<Data, String> column = getTableColumn();
|
TreeTableColumn<Entry, String> column = getTableColumn();
|
||||||
TreeTableColumn.CellEditEvent<Data, String> event = new TreeTableColumn.CellEditEvent<>(
|
TreeTableColumn.CellEditEvent<Entry, String> event = new TreeTableColumn.CellEditEvent<>(
|
||||||
treeTable, new TreeTablePosition<>(treeTable, getIndex(), column),
|
treeTable, new TreeTablePosition<>(treeTable, getIndex(), column),
|
||||||
TreeTableColumn.editCommitEvent(), label
|
TreeTableColumn.editCommitEvent(), label
|
||||||
);
|
);
|
||||||
|
@ -231,7 +201,7 @@ public class AddressTreeTable extends TreeTableView<AddressTreeTable.Data> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class AmountCell extends TreeTableCell<Data, Long> {
|
private static class AmountCell extends TreeTableCell<Entry, Long> {
|
||||||
public AmountCell() {
|
public AmountCell() {
|
||||||
super();
|
super();
|
||||||
getStyleClass().add("amount-cell");
|
getStyleClass().add("amount-cell");
|
||||||
|
@ -260,8 +230,9 @@ public class AddressTreeTable extends TreeTableView<AddressTreeTable.Data> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class ActionCell extends TreeTableCell<Data, Void> {
|
private static class ActionCell extends TreeTableCell<Entry, Entry> {
|
||||||
private final HBox actionBox;
|
private final HBox actionBox;
|
||||||
|
private final Button receiveButton;
|
||||||
|
|
||||||
public ActionCell() {
|
public ActionCell() {
|
||||||
super();
|
super();
|
||||||
|
@ -271,23 +242,27 @@ public class AddressTreeTable extends TreeTableView<AddressTreeTable.Data> {
|
||||||
actionBox.setSpacing(8);
|
actionBox.setSpacing(8);
|
||||||
actionBox.setAlignment(Pos.CENTER);
|
actionBox.setAlignment(Pos.CENTER);
|
||||||
|
|
||||||
Button receiveButton = new Button("");
|
receiveButton = new Button("");
|
||||||
Glyph receiveGlyph = new Glyph("FontAwesome", FontAwesome.Glyph.ARROW_DOWN);
|
Glyph receiveGlyph = new Glyph("FontAwesome", FontAwesome.Glyph.ARROW_DOWN);
|
||||||
receiveGlyph.setFontSize(12);
|
receiveGlyph.setFontSize(12);
|
||||||
receiveButton.setGraphic(receiveGlyph);
|
receiveButton.setGraphic(receiveGlyph);
|
||||||
receiveButton.setOnAction(event -> {
|
receiveButton.setOnAction(event -> {
|
||||||
EventManager.get().post(new ReceiveActionEvent(getTreeTableView().getTreeItem(getIndex()).getValue().getWalletNode()));
|
NodeEntry nodeEntry = (NodeEntry)getTreeTableView().getTreeItem(getIndex()).getValue();
|
||||||
|
EventManager.get().post(new ReceiveActionEvent(nodeEntry));
|
||||||
});
|
});
|
||||||
|
|
||||||
actionBox.getChildren().add(receiveButton);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void updateItem(Void item, boolean empty) {
|
protected void updateItem(Entry entry, boolean empty) {
|
||||||
super.updateItem(item, empty);
|
super.updateItem(entry, empty);
|
||||||
if (empty) {
|
if (empty) {
|
||||||
setGraphic(null);
|
setGraphic(null);
|
||||||
} else {
|
} else {
|
||||||
|
actionBox.getChildren().remove(0, actionBox.getChildren().size());
|
||||||
|
if(entry instanceof NodeEntry) {
|
||||||
|
actionBox.getChildren().add(receiveButton);
|
||||||
|
}
|
||||||
|
|
||||||
setGraphic(actionBox);
|
setGraphic(actionBox);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,64 @@
|
||||||
|
package com.sparrowwallet.sparrow.control;
|
||||||
|
|
||||||
|
import javafx.animation.FadeTransition;
|
||||||
|
import javafx.beans.InvalidationListener;
|
||||||
|
import javafx.beans.Observable;
|
||||||
|
import javafx.beans.property.ObjectProperty;
|
||||||
|
import javafx.scene.Cursor;
|
||||||
|
import javafx.scene.Node;
|
||||||
|
import javafx.scene.input.Clipboard;
|
||||||
|
import javafx.scene.input.ClipboardContent;
|
||||||
|
import javafx.scene.layout.Region;
|
||||||
|
import javafx.scene.layout.StackPane;
|
||||||
|
import javafx.util.Duration;
|
||||||
|
import org.controlsfx.control.textfield.CustomTextField;
|
||||||
|
|
||||||
|
public class CopyableTextField extends CustomTextField {
|
||||||
|
private static final Duration FADE_DURATION = Duration.millis(350);
|
||||||
|
|
||||||
|
public CopyableTextField() {
|
||||||
|
super();
|
||||||
|
getStyleClass().add("copyable-text-field");
|
||||||
|
setupCopyButtonField(super.rightProperty());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupCopyButtonField(ObjectProperty<Node> rightProperty) {
|
||||||
|
Region copyButton = new Region();
|
||||||
|
copyButton.getStyleClass().addAll("graphic"); //$NON-NLS-1$
|
||||||
|
StackPane copyButtonPane = new StackPane(copyButton);
|
||||||
|
copyButtonPane.getStyleClass().addAll("copy-button"); //$NON-NLS-1$
|
||||||
|
copyButtonPane.setOpacity(0.0);
|
||||||
|
copyButtonPane.setCursor(Cursor.DEFAULT);
|
||||||
|
copyButtonPane.setOnMouseReleased(e -> {
|
||||||
|
ClipboardContent content = new ClipboardContent();
|
||||||
|
content.putString(getText());
|
||||||
|
Clipboard.getSystemClipboard().setContent(content);
|
||||||
|
});
|
||||||
|
|
||||||
|
rightProperty.set(copyButtonPane);
|
||||||
|
|
||||||
|
final FadeTransition fader = new FadeTransition(FADE_DURATION, copyButtonPane);
|
||||||
|
fader.setCycleCount(1);
|
||||||
|
|
||||||
|
textProperty().addListener(new InvalidationListener() {
|
||||||
|
@Override
|
||||||
|
public void invalidated(Observable arg0) {
|
||||||
|
String text = getText();
|
||||||
|
boolean isTextEmpty = text == null || text.isEmpty();
|
||||||
|
boolean isButtonVisible = fader.getNode().getOpacity() > 0;
|
||||||
|
|
||||||
|
if (isTextEmpty && isButtonVisible) {
|
||||||
|
setButtonVisible(false);
|
||||||
|
} else if (!isTextEmpty && !isButtonVisible) {
|
||||||
|
setButtonVisible(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setButtonVisible( boolean visible ) {
|
||||||
|
fader.setFromValue(visible? 0.0: 1.0);
|
||||||
|
fader.setToValue(visible? 1.0: 0.0);
|
||||||
|
fader.play();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,15 +1,15 @@
|
||||||
package com.sparrowwallet.sparrow.event;
|
package com.sparrowwallet.sparrow.event;
|
||||||
|
|
||||||
import com.sparrowwallet.drongo.wallet.Wallet;
|
import com.sparrowwallet.sparrow.wallet.NodeEntry;
|
||||||
|
|
||||||
public class ReceiveActionEvent {
|
public class ReceiveActionEvent {
|
||||||
private Wallet.Node receiveNode;
|
private NodeEntry receiveEntry;
|
||||||
|
|
||||||
public ReceiveActionEvent(Wallet.Node receiveNode) {
|
public ReceiveActionEvent(NodeEntry receiveEntry) {
|
||||||
this.receiveNode = receiveNode;
|
this.receiveEntry = receiveEntry;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Wallet.Node getReceiveNode() {
|
public NodeEntry getReceiveEntry() {
|
||||||
return receiveNode;
|
return receiveEntry;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,6 @@ import com.sparrowwallet.drongo.wallet.WalletModel;
|
||||||
import javafx.concurrent.ScheduledService;
|
import javafx.concurrent.ScheduledService;
|
||||||
import javafx.concurrent.Service;
|
import javafx.concurrent.Service;
|
||||||
import javafx.concurrent.Task;
|
import javafx.concurrent.Task;
|
||||||
import org.apache.commons.compress.compressors.lz4.FramedLZ4CompressorInputStream;
|
|
||||||
import org.controlsfx.tools.Platform;
|
import org.controlsfx.tools.Platform;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
|
@ -20,6 +19,7 @@ import java.nio.file.attribute.PosixFilePermissions;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.zip.GZIPInputStream;
|
||||||
import java.util.zip.ZipEntry;
|
import java.util.zip.ZipEntry;
|
||||||
import java.util.zip.ZipInputStream;
|
import java.util.zip.ZipInputStream;
|
||||||
|
|
||||||
|
@ -130,7 +130,7 @@ public class Hwi {
|
||||||
File tempExec = tempExecPath.toFile();
|
File tempExec = tempExecPath.toFile();
|
||||||
//tempExec.deleteOnExit();
|
//tempExec.deleteOnExit();
|
||||||
OutputStream tempExecStream = new BufferedOutputStream(new FileOutputStream(tempExec));
|
OutputStream tempExecStream = new BufferedOutputStream(new FileOutputStream(tempExec));
|
||||||
ByteStreams.copy(new FramedLZ4CompressorInputStream(inputStream), tempExecStream);
|
ByteStreams.copy(new GZIPInputStream(inputStream), tempExecStream);
|
||||||
inputStream.close();
|
inputStream.close();
|
||||||
tempExecStream.flush();
|
tempExecStream.flush();
|
||||||
tempExecStream.close();
|
tempExecStream.close();
|
||||||
|
|
|
@ -1,25 +1,17 @@
|
||||||
package com.sparrowwallet.sparrow.transaction;
|
package com.sparrowwallet.sparrow.transaction;
|
||||||
|
|
||||||
import com.sparrowwallet.drongo.address.Address;
|
import com.sparrowwallet.drongo.address.Address;
|
||||||
import com.sparrowwallet.drongo.protocol.*;
|
import com.sparrowwallet.drongo.protocol.NonStandardScriptException;
|
||||||
|
import com.sparrowwallet.drongo.protocol.TransactionOutput;
|
||||||
|
import com.sparrowwallet.sparrow.BaseController;
|
||||||
import javafx.collections.FXCollections;
|
import javafx.collections.FXCollections;
|
||||||
import javafx.collections.ObservableList;
|
import javafx.collections.ObservableList;
|
||||||
import javafx.geometry.Point2D;
|
|
||||||
import javafx.scene.chart.PieChart;
|
import javafx.scene.chart.PieChart;
|
||||||
import javafx.scene.control.Label;
|
|
||||||
import javafx.scene.control.Tooltip;
|
import javafx.scene.control.Tooltip;
|
||||||
import javafx.stage.Popup;
|
|
||||||
import org.fxmisc.richtext.CodeArea;
|
|
||||||
import org.fxmisc.richtext.event.MouseOverTextEvent;
|
|
||||||
import org.fxmisc.richtext.model.TwoDimensional;
|
|
||||||
|
|
||||||
import java.time.Duration;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import static org.fxmisc.richtext.model.TwoDimensional.Bias.Backward;
|
public abstract class TransactionFormController extends BaseController {
|
||||||
import static com.sparrowwallet.drongo.protocol.ScriptType.*;
|
|
||||||
|
|
||||||
public abstract class TransactionFormController {
|
|
||||||
protected void addPieData(PieChart pie, List<TransactionOutput> outputs) {
|
protected void addPieData(PieChart pie, List<TransactionOutput> outputs) {
|
||||||
ObservableList<PieChart.Data> outputsPieData = FXCollections.observableArrayList();
|
ObservableList<PieChart.Data> outputsPieData = FXCollections.observableArrayList();
|
||||||
|
|
||||||
|
@ -52,98 +44,4 @@ public abstract class TransactionFormController {
|
||||||
data.pieValueProperty().addListener((observable, oldValue, newValue) -> tooltip.setText(newValue + "%"));
|
data.pieValueProperty().addListener((observable, oldValue, newValue) -> tooltip.setText(newValue + "%"));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void appendScript(CodeArea codeArea, Script script) {
|
|
||||||
appendScript(codeArea, script, null, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void appendScript(CodeArea codeArea, Script script, Script redeemScript, Script witnessScript) {
|
|
||||||
if(P2PKH.isScriptType(script)) {
|
|
||||||
codeArea.append(script.getChunks().get(0).toString(), "script-opcode");
|
|
||||||
codeArea.append(" ", "");
|
|
||||||
codeArea.append(script.getChunks().get(1).toString(), "script-opcode");
|
|
||||||
codeArea.append(" ", "");
|
|
||||||
codeArea.append("<pkh>", "script-hash");
|
|
||||||
codeArea.append(" ", "");
|
|
||||||
codeArea.append(script.getChunks().get(3).toString(), "script-opcode");
|
|
||||||
codeArea.append(" ", "");
|
|
||||||
codeArea.append(script.getChunks().get(4).toString(), "script-opcode");
|
|
||||||
} else if(P2SH.isScriptType(script)) {
|
|
||||||
codeArea.append(script.getChunks().get(0).toString(), "script-opcode");
|
|
||||||
codeArea.append(" ", "");
|
|
||||||
codeArea.append("<sh>", "script-hash");
|
|
||||||
codeArea.append(" ", "");
|
|
||||||
codeArea.append(script.getChunks().get(2).toString(), "script-opcode");
|
|
||||||
} else if(P2WPKH.isScriptType(script)) {
|
|
||||||
codeArea.append(script.getChunks().get(0).toString(), "script-opcode");
|
|
||||||
codeArea.append(" ", "");
|
|
||||||
codeArea.append("<wpkh>", "script-hash");
|
|
||||||
} else if(P2WSH.isScriptType(script)) {
|
|
||||||
codeArea.append(script.getChunks().get(0).toString(), "script-opcode");
|
|
||||||
codeArea.append(" ", "");
|
|
||||||
codeArea.append("<wsh>", "script-hash");
|
|
||||||
} else {
|
|
||||||
int signatureCount = 1;
|
|
||||||
int pubKeyCount = 1;
|
|
||||||
for (int i = 0; i < script.getChunks().size(); i++) {
|
|
||||||
ScriptChunk chunk = script.getChunks().get(i);
|
|
||||||
if(chunk.isOpCode()) {
|
|
||||||
codeArea.append(chunk.toString(), "script-opcode");
|
|
||||||
} else if(chunk.isSignature()) {
|
|
||||||
codeArea.append("<signature" + signatureCount++ + ">", "script-signature");
|
|
||||||
} else if(chunk.isPubKey()) {
|
|
||||||
codeArea.append("<pubkey" + pubKeyCount++ + ">", "script-pubkey");
|
|
||||||
} else if(chunk.isScript()) {
|
|
||||||
Script nestedScript = chunk.getScript();
|
|
||||||
if (nestedScript.equals(redeemScript)) {
|
|
||||||
codeArea.append("<RedeemScript>", "script-redeem");
|
|
||||||
} else if (nestedScript.equals(witnessScript)) {
|
|
||||||
codeArea.append("<WitnessScript>", "script-redeem");
|
|
||||||
} else {
|
|
||||||
codeArea.append("(", "script-nest");
|
|
||||||
appendScript(codeArea, nestedScript);
|
|
||||||
codeArea.append(")", "script-nest");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
codeArea.append(chunk.toString(), "script-other");
|
|
||||||
}
|
|
||||||
|
|
||||||
if(i < script.getChunks().size() - 1) {
|
|
||||||
codeArea.append(" ", "");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
addScriptPopup(codeArea, script);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void addScriptPopup(CodeArea area, Script script) {
|
|
||||||
ScriptContextMenu contextMenu = new ScriptContextMenu(area, script);
|
|
||||||
area.setContextMenu(contextMenu);
|
|
||||||
|
|
||||||
Popup popup = new Popup();
|
|
||||||
Label popupMsg = new Label();
|
|
||||||
popupMsg.getStyleClass().add("tooltip");
|
|
||||||
popup.getContent().add(popupMsg);
|
|
||||||
|
|
||||||
area.setMouseOverTextDelay(Duration.ofMillis(150));
|
|
||||||
area.addEventHandler(MouseOverTextEvent.MOUSE_OVER_TEXT_BEGIN, e -> {
|
|
||||||
TwoDimensional.Position position = area.getParagraph(0).getStyleSpans().offsetToPosition(e.getCharacterIndex(), Backward);
|
|
||||||
if(position.getMajor() % 2 == 0) {
|
|
||||||
ScriptChunk hoverChunk = script.getChunks().get(position.getMajor()/2);
|
|
||||||
if(!hoverChunk.isOpCode()) {
|
|
||||||
Point2D pos = e.getScreenPosition();
|
|
||||||
popupMsg.setText(describeScriptChunk(hoverChunk));
|
|
||||||
popup.show(area, pos.getX(), pos.getY() + 10);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
area.addEventHandler(MouseOverTextEvent.MOUSE_OVER_TEXT_END, e -> {
|
|
||||||
popup.hide();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
protected String describeScriptChunk(ScriptChunk chunk) {
|
|
||||||
return chunk.toString();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,7 +26,7 @@ public class AddressesController extends WalletFormController implements Initial
|
||||||
public void initializeView() {
|
public void initializeView() {
|
||||||
Wallet wallet = walletForm.getWallet();
|
Wallet wallet = walletForm.getWallet();
|
||||||
|
|
||||||
receiveTable.initialize(wallet.getNodes(KeyPurpose.RECEIVE));
|
receiveTable.initialize(getWalletForm().getNodeEntry(KeyPurpose.RECEIVE));
|
||||||
changeTable.initialize(wallet.getNodes(KeyPurpose.CHANGE));
|
changeTable.initialize(getWalletForm().getNodeEntry(KeyPurpose.CHANGE));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
33
src/main/java/com/sparrowwallet/sparrow/wallet/Entry.java
Normal file
33
src/main/java/com/sparrowwallet/sparrow/wallet/Entry.java
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
package com.sparrowwallet.sparrow.wallet;
|
||||||
|
|
||||||
|
import javafx.beans.property.SimpleStringProperty;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public abstract class Entry {
|
||||||
|
private final SimpleStringProperty labelProperty;
|
||||||
|
private final List<Entry> children = new ArrayList<>();
|
||||||
|
|
||||||
|
public Entry(String label) {
|
||||||
|
this.labelProperty = new SimpleStringProperty(label);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Entry(SimpleStringProperty labelProperty) {
|
||||||
|
this.labelProperty = labelProperty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLabel() {
|
||||||
|
return labelProperty.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public SimpleStringProperty labelProperty() {
|
||||||
|
return labelProperty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Entry> getChildren() {
|
||||||
|
return children;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract Long getAmount();
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
package com.sparrowwallet.sparrow.wallet;
|
||||||
|
|
||||||
|
import com.sparrowwallet.drongo.wallet.Wallet;
|
||||||
|
|
||||||
|
public class NodeEntry extends Entry {
|
||||||
|
private final Wallet.Node node;
|
||||||
|
|
||||||
|
public NodeEntry(Wallet.Node node) {
|
||||||
|
super(node.getLabel());
|
||||||
|
this.node = node;
|
||||||
|
|
||||||
|
labelProperty().addListener((observable, oldValue, newValue) -> node.setLabel(newValue));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Long getAmount() {
|
||||||
|
//TODO: Iterate through TransactionEntries to calculate amount
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Wallet.Node getNode() {
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,104 @@
|
||||||
|
package com.sparrowwallet.sparrow.wallet;
|
||||||
|
|
||||||
|
import com.google.zxing.BarcodeFormat;
|
||||||
|
import com.google.zxing.client.j2se.MatrixToImageConfig;
|
||||||
|
import com.google.zxing.client.j2se.MatrixToImageWriter;
|
||||||
|
import com.google.zxing.common.BitMatrix;
|
||||||
|
import com.google.zxing.qrcode.QRCodeWriter;
|
||||||
|
import com.sparrowwallet.drongo.KeyPurpose;
|
||||||
|
import com.sparrowwallet.sparrow.EventManager;
|
||||||
|
import com.sparrowwallet.sparrow.control.CopyableLabel;
|
||||||
|
import com.sparrowwallet.sparrow.control.CopyableTextField;
|
||||||
|
import javafx.beans.value.ChangeListener;
|
||||||
|
import javafx.beans.value.ObservableValue;
|
||||||
|
import javafx.event.ActionEvent;
|
||||||
|
import javafx.fxml.FXML;
|
||||||
|
import javafx.fxml.Initializable;
|
||||||
|
import javafx.scene.control.TextField;
|
||||||
|
import javafx.scene.image.Image;
|
||||||
|
import javafx.scene.image.ImageView;
|
||||||
|
import org.fxmisc.richtext.CodeArea;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.ResourceBundle;
|
||||||
|
|
||||||
|
public class ReceiveController extends WalletFormController implements Initializable {
|
||||||
|
@FXML
|
||||||
|
private CopyableTextField address;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private TextField label;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private CopyableLabel derivationPath;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private CopyableLabel lastUsed;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private ImageView qrCode;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private CodeArea scriptPubKeyArea;
|
||||||
|
|
||||||
|
private NodeEntry currentEntry;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initialize(URL location, ResourceBundle resources) {
|
||||||
|
EventManager.get().register(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initializeView() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNodeEntry(NodeEntry nodeEntry) {
|
||||||
|
if(currentEntry != null) {
|
||||||
|
label.textProperty().unbindBidirectional(currentEntry.labelProperty());
|
||||||
|
}
|
||||||
|
|
||||||
|
this.currentEntry = nodeEntry;
|
||||||
|
|
||||||
|
address.setText(nodeEntry.getNode().getAddress().toString());
|
||||||
|
|
||||||
|
label.textProperty().bindBidirectional(nodeEntry.labelProperty());
|
||||||
|
|
||||||
|
derivationPath.setText(nodeEntry.getNode().getDerivationPath());
|
||||||
|
|
||||||
|
//TODO: Find last used block height if available (red flag?)
|
||||||
|
lastUsed.setText("Unknown");
|
||||||
|
|
||||||
|
Image qrImage = getQrCode(nodeEntry.getNode().getAddress().toString());
|
||||||
|
if(qrImage != null) {
|
||||||
|
qrCode.setImage(qrImage);
|
||||||
|
}
|
||||||
|
|
||||||
|
scriptPubKeyArea.clear();
|
||||||
|
appendScript(scriptPubKeyArea, nodeEntry.getNode().getOutputScript(), null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Image getQrCode(String address) {
|
||||||
|
try {
|
||||||
|
QRCodeWriter qrCodeWriter = new QRCodeWriter();
|
||||||
|
BitMatrix qrMatrix = qrCodeWriter.encode(address, BarcodeFormat.QR_CODE, 150, 150);
|
||||||
|
|
||||||
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
|
MatrixToImageWriter.writeToStream(qrMatrix, "PNG", baos, new MatrixToImageConfig());
|
||||||
|
|
||||||
|
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
|
||||||
|
return new Image(bais);
|
||||||
|
} catch(Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void getNewAddress(ActionEvent event) {
|
||||||
|
NodeEntry freshEntry = getWalletForm().getFreshNodeEntry(KeyPurpose.RECEIVE, currentEntry);
|
||||||
|
setNodeEntry(freshEntry);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,18 +1,22 @@
|
||||||
package com.sparrowwallet.sparrow.wallet;
|
package com.sparrowwallet.sparrow.wallet;
|
||||||
|
|
||||||
import com.sparrowwallet.drongo.crypto.ECKey;
|
import com.sparrowwallet.drongo.KeyPurpose;
|
||||||
import com.sparrowwallet.drongo.crypto.Pbkdf2KeyDeriver;
|
|
||||||
import com.sparrowwallet.drongo.wallet.Wallet;
|
import com.sparrowwallet.drongo.wallet.Wallet;
|
||||||
import com.sparrowwallet.sparrow.io.Storage;
|
import com.sparrowwallet.sparrow.io.Storage;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
public class WalletForm {
|
public class WalletForm {
|
||||||
private final Storage storage;
|
private final Storage storage;
|
||||||
private Wallet oldWallet;
|
private Wallet oldWallet;
|
||||||
private Wallet wallet;
|
private Wallet wallet;
|
||||||
|
|
||||||
|
private final List<NodeEntry> accountEntries = new ArrayList<>();
|
||||||
|
|
||||||
public WalletForm(Storage storage, Wallet currentWallet) {
|
public WalletForm(Storage storage, Wallet currentWallet) {
|
||||||
this.storage = storage;
|
this.storage = storage;
|
||||||
this.oldWallet = currentWallet;
|
this.oldWallet = currentWallet;
|
||||||
|
@ -39,4 +43,39 @@ public class WalletForm {
|
||||||
storage.storeWallet(wallet);
|
storage.storeWallet(wallet);
|
||||||
oldWallet = wallet.copy();
|
oldWallet = wallet.copy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public NodeEntry getNodeEntry(KeyPurpose keyPurpose) {
|
||||||
|
NodeEntry purposeEntry;
|
||||||
|
Optional<NodeEntry> optionalPurposeEntry = accountEntries.stream().filter(entry -> entry.getNode().getKeyPurpose().equals(keyPurpose)).findFirst();
|
||||||
|
if(optionalPurposeEntry.isPresent()) {
|
||||||
|
purposeEntry = optionalPurposeEntry.get();
|
||||||
|
} else {
|
||||||
|
Wallet.Node purposeNode = getWallet().getNode(keyPurpose);
|
||||||
|
purposeEntry = new NodeEntry(purposeNode);
|
||||||
|
for(Wallet.Node childNode : purposeNode.getChildren()) {
|
||||||
|
NodeEntry childEntry = new NodeEntry(childNode);
|
||||||
|
purposeEntry.getChildren().add(childEntry);
|
||||||
|
}
|
||||||
|
|
||||||
|
accountEntries.add(purposeEntry);
|
||||||
|
}
|
||||||
|
|
||||||
|
return purposeEntry;
|
||||||
|
}
|
||||||
|
|
||||||
|
public NodeEntry getFreshNodeEntry(KeyPurpose keyPurpose, NodeEntry currentEntry) {
|
||||||
|
NodeEntry rootEntry = getNodeEntry(keyPurpose);
|
||||||
|
Wallet.Node freshNode = getWallet().getFreshNode(keyPurpose, currentEntry == null ? null : currentEntry.getNode());
|
||||||
|
|
||||||
|
for(Entry childEntry : rootEntry.getChildren()) {
|
||||||
|
NodeEntry nodeEntry = (NodeEntry)childEntry;
|
||||||
|
if(nodeEntry.getNode().equals(freshNode)) {
|
||||||
|
return nodeEntry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NodeEntry freshEntry = new NodeEntry(freshNode);
|
||||||
|
rootEntry.getChildren().add(freshEntry);
|
||||||
|
return freshEntry;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package com.sparrowwallet.sparrow.wallet;
|
package com.sparrowwallet.sparrow.wallet;
|
||||||
|
|
||||||
public abstract class WalletFormController {
|
import com.sparrowwallet.sparrow.BaseController;
|
||||||
|
|
||||||
|
public abstract class WalletFormController extends BaseController {
|
||||||
public WalletForm walletForm;
|
public WalletForm walletForm;
|
||||||
|
|
||||||
public WalletForm getWalletForm() {
|
public WalletForm getWalletForm() {
|
||||||
|
|
|
@ -10,6 +10,7 @@ open module com.sparrowwallet.sparrow {
|
||||||
requires com.google.common;
|
requires com.google.common;
|
||||||
requires flowless;
|
requires flowless;
|
||||||
requires com.google.gson;
|
requires com.google.gson;
|
||||||
requires org.apache.commons.compress;
|
requires com.google.zxing;
|
||||||
|
requires com.google.zxing.javase;
|
||||||
requires javafx.swing;
|
requires javafx.swing;
|
||||||
}
|
}
|
|
@ -98,3 +98,22 @@
|
||||||
.titled-description-pane .hyperlink:hover:visited {
|
.titled-description-pane .hyperlink:hover:visited {
|
||||||
-fx-underline: true;
|
-fx-underline: true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.copyable-text-field .copy-button {
|
||||||
|
-fx-padding: 0 3 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.copyable-text-field .copy-button > .graphic {
|
||||||
|
-fx-background-color: #949494;
|
||||||
|
-fx-scale-shape: false;
|
||||||
|
-fx-padding: 4.5 4.5 4.5 4.5; /* Graphic is 9x9 px */
|
||||||
|
-fx-shape: "M 6.429688 7.875 L 6.429688 8.578125 C 6.429688 8.8125 6.210938 9 5.945312 9 L 0.480469 9 C 0.214844 9 0 8.8125 0 8.578125 L 0 2.109375 C 0 1.875 0.214844 1.6875 0.480469 1.6875 L 1.929688 1.6875 L 1.929688 6.890625 C 1.929688 7.433594 2.433594 7.875 3.054688 7.875 Z M 6.429688 1.828125 L 6.429688 0 L 3.054688 0 C 2.789062 0 2.570312 0.1875 2.570312 0.421875 L 2.570312 6.890625 C 2.570312 7.125 2.789062 7.3125 3.054688 7.3125 L 8.519531 7.3125 C 8.785156 7.3125 9 7.125 9 6.890625 L 9 2.25 L 6.910156 2.25 C 6.644531 2.25 6.429688 2.058594 6.429688 1.828125 Z M 8.859375 1.28125 L 7.535156 0.125 C 7.445312 0.0429688 7.320312 0 7.191406 0 L 7.070312 0 L 7.070312 1.6875 L 9 1.6875 L 9 1.582031 C 9 1.46875 8.949219 1.363281 8.859375 1.28125 Z M 8.859375 1.28125 ";
|
||||||
|
}
|
||||||
|
|
||||||
|
.copyable-text-field .copy-button:hover > .graphic {
|
||||||
|
-fx-background-color: #0184bc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.copyable-text-field .copy-button:pressed > .graphic {
|
||||||
|
-fx-background-color: #116a8d;
|
||||||
|
}
|
|
@ -17,7 +17,7 @@
|
||||||
<?import com.sparrowwallet.sparrow.control.CoinLabel?>
|
<?import com.sparrowwallet.sparrow.control.CoinLabel?>
|
||||||
<?import com.sparrowwallet.sparrow.control.AddressLabel?>
|
<?import com.sparrowwallet.sparrow.control.AddressLabel?>
|
||||||
|
|
||||||
<GridPane hgap="10.0" vgap="10.0" stylesheets="@input.css, @script.css, @../general.css" xmlns="http://javafx.com/javafx/10.0.2-internal" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.sparrowwallet.sparrow.transaction.InputController">
|
<GridPane hgap="10.0" vgap="10.0" stylesheets="@input.css, @../script.css, @../general.css" xmlns="http://javafx.com/javafx/10.0.2-internal" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.sparrowwallet.sparrow.transaction.InputController">
|
||||||
<padding>
|
<padding>
|
||||||
<Insets left="25.0" right="25.0" top="25.0" />
|
<Insets left="25.0" right="25.0" top="25.0" />
|
||||||
</padding>
|
</padding>
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
<?import com.sparrowwallet.sparrow.control.CoinLabel?>
|
<?import com.sparrowwallet.sparrow.control.CoinLabel?>
|
||||||
<?import com.sparrowwallet.sparrow.control.AddressLabel?>
|
<?import com.sparrowwallet.sparrow.control.AddressLabel?>
|
||||||
|
|
||||||
<GridPane hgap="10.0" vgap="10.0" stylesheets="@output.css, @script.css, @../general.css" xmlns="http://javafx.com/javafx/10.0.2-internal" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.sparrowwallet.sparrow.transaction.OutputController">
|
<GridPane hgap="10.0" vgap="10.0" stylesheets="@output.css, @../script.css, @../general.css" xmlns="http://javafx.com/javafx/10.0.2-internal" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.sparrowwallet.sparrow.transaction.OutputController">
|
||||||
<padding>
|
<padding>
|
||||||
<Insets left="25.0" right="25.0" top="25.0" />
|
<Insets left="25.0" right="25.0" top="25.0" />
|
||||||
</padding>
|
</padding>
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
-fx-padding: 10 0 10 0;
|
-fx-padding: 10 0 10 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.data-cell {
|
.address-cell {
|
||||||
-fx-font-family: Courier;
|
-fx-font-family: Courier;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
.address-text-field {
|
||||||
|
-fx-font-family: Courier;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qr-code {
|
||||||
|
-fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.8), 10, 0, 0, 0);
|
||||||
|
-fx-padding: 20;
|
||||||
|
}
|
|
@ -0,0 +1,83 @@
|
||||||
|
<?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 tornadofx.control.Form?>
|
||||||
|
<?import tornadofx.control.Fieldset?>
|
||||||
|
<?import tornadofx.control.Field?>
|
||||||
|
<?import javafx.scene.image.ImageView?>
|
||||||
|
<?import org.fxmisc.flowless.VirtualizedScrollPane?>
|
||||||
|
<?import org.fxmisc.richtext.CodeArea?>
|
||||||
|
<?import com.sparrowwallet.sparrow.control.CopyableLabel?>
|
||||||
|
<?import org.controlsfx.glyphfont.Glyph?>
|
||||||
|
<?import com.sparrowwallet.sparrow.control.CopyableTextField?>
|
||||||
|
|
||||||
|
<BorderPane stylesheets="@receive.css, @wallet.css, @../script.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.ReceiveController">
|
||||||
|
<center>
|
||||||
|
|
||||||
|
<GridPane hgap="10.0" vgap="10.0">
|
||||||
|
<padding>
|
||||||
|
<Insets left="25.0" right="25.0" top="25.0" />
|
||||||
|
</padding>
|
||||||
|
<columnConstraints>
|
||||||
|
<ColumnConstraints percentWidth="70" />
|
||||||
|
<ColumnConstraints percentWidth="30" />
|
||||||
|
</columnConstraints>
|
||||||
|
<rowConstraints>
|
||||||
|
<RowConstraints />
|
||||||
|
</rowConstraints>
|
||||||
|
<Form GridPane.columnIndex="0" GridPane.rowIndex="0">
|
||||||
|
<Fieldset inputGrow="SOMETIMES" text="Receive">
|
||||||
|
<Field text="Address:">
|
||||||
|
<CopyableTextField fx:id="address" styleClass="address-text-field" editable="false" prefWidth="350"/>
|
||||||
|
</Field>
|
||||||
|
<Field text="Label:">
|
||||||
|
<TextField fx:id="label" />
|
||||||
|
</Field>
|
||||||
|
<Field text="Derivation:">
|
||||||
|
<CopyableLabel fx:id="derivationPath" />
|
||||||
|
</Field>
|
||||||
|
<Field text="Last Used:">
|
||||||
|
<CopyableLabel fx:id="lastUsed" />
|
||||||
|
</Field>
|
||||||
|
</Fieldset>
|
||||||
|
</Form>
|
||||||
|
|
||||||
|
<AnchorPane GridPane.columnIndex="1" GridPane.rowIndex="0">
|
||||||
|
<ImageView fx:id="qrCode" styleClass="qr-code" AnchorPane.rightAnchor="5"/>
|
||||||
|
</AnchorPane>
|
||||||
|
|
||||||
|
<Separator styleClass="form-separator" GridPane.columnIndex="0" GridPane.columnSpan="2" GridPane.rowIndex="1" />
|
||||||
|
|
||||||
|
<Form GridPane.columnIndex="0" GridPane.columnSpan="2" GridPane.rowIndex="2">
|
||||||
|
<Fieldset inputGrow="SOMETIMES" text="Required Script">
|
||||||
|
<Field text="ScriptPubKey:">
|
||||||
|
<VirtualizedScrollPane>
|
||||||
|
<content>
|
||||||
|
<CodeArea fx:id="scriptPubKeyArea" editable="false" wrapText="true" prefHeight="42" maxHeight="42" styleClass="uneditable-codearea" />
|
||||||
|
</content>
|
||||||
|
</VirtualizedScrollPane>
|
||||||
|
</Field>
|
||||||
|
</Fieldset>
|
||||||
|
</Form>
|
||||||
|
|
||||||
|
</GridPane>
|
||||||
|
</center>
|
||||||
|
<bottom>
|
||||||
|
<AnchorPane>
|
||||||
|
<padding>
|
||||||
|
<Insets left="25.0" right="25.0" bottom="25.0" />
|
||||||
|
</padding>
|
||||||
|
<Button fx:id="nextAddress" text="Get Next Address" defaultButton="true" AnchorPane.rightAnchor="10" onAction="#getNewAddress">
|
||||||
|
<graphic>
|
||||||
|
<Glyph fontFamily="FontAwesome" icon="ARROW_DOWN" fontSize="12" />
|
||||||
|
</graphic>
|
||||||
|
</Button>
|
||||||
|
</AnchorPane>
|
||||||
|
</bottom>
|
||||||
|
</BorderPane>
|
Loading…
Reference in a new issue