receive and address pane improvements

This commit is contained in:
Craig Raw 2020-05-28 11:42:42 +02:00
parent 3db7fc1e99
commit c115f6e729
13 changed files with 189 additions and 31 deletions

2
drongo

@ -1 +1 @@
Subproject commit 7871413573e67ed7539cf03d6deadd1a2c4abafa Subproject commit 11978e1f48851cd964f1c5f52a29a8e2ea432432

View file

@ -5,8 +5,10 @@ import com.sparrowwallet.drongo.address.Address;
import com.sparrowwallet.drongo.protocol.Transaction; import com.sparrowwallet.drongo.protocol.Transaction;
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.event.ReceiveToEvent;
import com.sparrowwallet.sparrow.wallet.Entry; import com.sparrowwallet.sparrow.wallet.Entry;
import com.sparrowwallet.sparrow.wallet.NodeEntry; import com.sparrowwallet.sparrow.wallet.NodeEntry;
import javafx.application.Platform;
import javafx.beans.property.ReadOnlyObjectWrapper; import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.event.Event; import javafx.event.Event;
import javafx.geometry.Pos; import javafx.geometry.Pos;
@ -27,16 +29,11 @@ public class AddressTreeTable extends TreeTableView<Entry> {
public void initialize(NodeEntry rootEntry) { public void initialize(NodeEntry rootEntry) {
getStyleClass().add("address-treetable"); getStyleClass().add("address-treetable");
String address = null; String address = rootEntry.getNode().getAddress().toString();
TreeItem<Entry> rootItem = new TreeItem<>(rootEntry); RecursiveTreeItem<Entry> rootItem = new RecursiveTreeItem<>(rootEntry, Entry::getChildren);
for(Entry childEntry : rootEntry.getChildren()) { setRoot(rootItem);
TreeItem<Entry> childItem = new TreeItem<>(childEntry);
rootItem.getChildren().add(childItem);
address = rootEntry.getNode().getAddress().toString();
}
rootItem.setExpanded(true); rootItem.setExpanded(true);
setRoot(rootItem);
setShowRoot(false); setShowRoot(false);
TreeTableColumn<Entry, Entry> addressCol = new TreeTableColumn<>("Address / Outpoints"); TreeTableColumn<Entry, Entry> addressCol = new TreeTableColumn<>("Address / Outpoints");
@ -249,6 +246,7 @@ public class AddressTreeTable extends TreeTableView<Entry> {
receiveButton.setOnAction(event -> { receiveButton.setOnAction(event -> {
NodeEntry nodeEntry = (NodeEntry)getTreeTableView().getTreeItem(getIndex()).getValue(); NodeEntry nodeEntry = (NodeEntry)getTreeTableView().getTreeItem(getIndex()).getValue();
EventManager.get().post(new ReceiveActionEvent(nodeEntry)); EventManager.get().post(new ReceiveActionEvent(nodeEntry));
Platform.runLater(() -> EventManager.get().post(new ReceiveToEvent(nodeEntry)));
}); });
} }

View file

@ -0,0 +1,72 @@
package com.sparrowwallet.sparrow.control;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.scene.Node;
import javafx.scene.control.TreeItem;
import javafx.util.Callback;
import java.util.List;
import java.util.stream.Collectors;
public class RecursiveTreeItem<T> extends TreeItem<T> {
private final Callback<T, ObservableList<T>> childrenFactory;
private final Callback<T, Node> graphicsFactory;
public RecursiveTreeItem(Callback<T, ObservableList<T>> childrenFactory){
this(null, childrenFactory);
}
public RecursiveTreeItem(final T value, Callback<T, ObservableList<T>> childrenFactory){
this(value, (item) -> null, childrenFactory);
}
public RecursiveTreeItem(final T value, Callback<T, Node> graphicsFactory, Callback<T, ObservableList<T>> childrenFactory){
super(value, graphicsFactory.call(value));
this.graphicsFactory = graphicsFactory;
this.childrenFactory = childrenFactory;
if(value != null) {
addChildrenListener(value);
}
valueProperty().addListener((obs, oldValue, newValue)->{
if(newValue != null){
addChildrenListener(newValue);
}
});
this.setExpanded(true);
}
private void addChildrenListener(T value){
final ObservableList<T> children = childrenFactory.call(value);
children.forEach(child -> RecursiveTreeItem.this.getChildren().add(
new RecursiveTreeItem<>(child, this.graphicsFactory, childrenFactory)));
children.addListener((ListChangeListener<T>) change -> {
while(change.next()){
if(change.wasAdded()){
change.getAddedSubList().forEach(t-> RecursiveTreeItem.this.getChildren().add(
new RecursiveTreeItem<>(t, this.graphicsFactory, childrenFactory)));
}
if(change.wasRemoved()){
change.getRemoved().forEach(t->{
final List<TreeItem<T>> itemsToRemove = RecursiveTreeItem.this
.getChildren()
.stream()
.filter(treeItem -> treeItem.getValue().equals(t))
.collect(Collectors.toList());
RecursiveTreeItem.this.getChildren().removeAll(itemsToRemove);
});
}
}
});
}
}

View file

@ -3,7 +3,7 @@ package com.sparrowwallet.sparrow.event;
import com.sparrowwallet.sparrow.wallet.NodeEntry; import com.sparrowwallet.sparrow.wallet.NodeEntry;
public class ReceiveActionEvent { public class ReceiveActionEvent {
private NodeEntry receiveEntry; private final NodeEntry receiveEntry;
public ReceiveActionEvent(NodeEntry receiveEntry) { public ReceiveActionEvent(NodeEntry receiveEntry) {
this.receiveEntry = receiveEntry; this.receiveEntry = receiveEntry;

View file

@ -0,0 +1,15 @@
package com.sparrowwallet.sparrow.event;
import com.sparrowwallet.sparrow.wallet.NodeEntry;
public class ReceiveToEvent {
private final NodeEntry receiveEntry;
public ReceiveToEvent(NodeEntry receiveEntry) {
this.receiveEntry = receiveEntry;
}
public NodeEntry getReceiveEntry() {
return receiveEntry;
}
}

View file

@ -18,6 +18,8 @@ import java.nio.charset.StandardCharsets;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.util.Arrays; import java.util.Arrays;
import java.util.Base64; import java.util.Base64;
import java.util.Iterator;
import java.util.TreeSet;
import java.util.zip.*; import java.util.zip.*;
import static com.sparrowwallet.drongo.crypto.Argon2KeyDeriver.SPRW1_PARAMETERS; import static com.sparrowwallet.drongo.crypto.Argon2KeyDeriver.SPRW1_PARAMETERS;
@ -49,14 +51,16 @@ public class Storage {
return getGson(true); return getGson(true);
} }
private static Gson getGson(boolean includeKeystoreSerializer) { private static Gson getGson(boolean includeWalletSerializers) {
GsonBuilder gsonBuilder = new GsonBuilder(); GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(ExtendedKey.class, new ExtendedPublicKeySerializer()); gsonBuilder.registerTypeAdapter(ExtendedKey.class, new ExtendedPublicKeySerializer());
gsonBuilder.registerTypeAdapter(ExtendedKey.class, new ExtendedPublicKeyDeserializer()); gsonBuilder.registerTypeAdapter(ExtendedKey.class, new ExtendedPublicKeyDeserializer());
gsonBuilder.registerTypeAdapter(byte[].class, new ByteArraySerializer()); gsonBuilder.registerTypeAdapter(byte[].class, new ByteArraySerializer());
gsonBuilder.registerTypeAdapter(byte[].class, new ByteArrayDeserializer()); gsonBuilder.registerTypeAdapter(byte[].class, new ByteArrayDeserializer());
if(includeKeystoreSerializer) { if(includeWalletSerializers) {
gsonBuilder.registerTypeAdapter(Keystore.class, new KeystoreSerializer()); gsonBuilder.registerTypeAdapter(Keystore.class, new KeystoreSerializer());
gsonBuilder.registerTypeAdapter(Wallet.Node.class, new NodeSerializer());
gsonBuilder.registerTypeAdapter(Wallet.Node.class, new NodeDeserializer());
} }
return gsonBuilder.setPrettyPrinting().disableHtmlEscaping().create(); return gsonBuilder.setPrettyPrinting().disableHtmlEscaping().create();
@ -266,7 +270,6 @@ public class Storage {
private static class KeystoreSerializer implements JsonSerializer<Keystore> { private static class KeystoreSerializer implements JsonSerializer<Keystore> {
@Override @Override
public JsonElement serialize(Keystore keystore, Type typeOfSrc, JsonSerializationContext context) { public JsonElement serialize(Keystore keystore, Type typeOfSrc, JsonSerializationContext context) {
JsonObject jsonObject = (JsonObject)getGson(false).toJsonTree(keystore); JsonObject jsonObject = (JsonObject)getGson(false).toJsonTree(keystore);
if(keystore.hasSeed()) { if(keystore.hasSeed()) {
jsonObject.remove("extendedPublicKey"); jsonObject.remove("extendedPublicKey");
@ -277,6 +280,42 @@ public class Storage {
} }
} }
private static class NodeSerializer implements JsonSerializer<Wallet.Node> {
@Override
public JsonElement serialize(Wallet.Node node, Type typeOfSrc, JsonSerializationContext context) {
JsonObject jsonObject = (JsonObject)getGson(false).toJsonTree(node);
JsonArray children = jsonObject.getAsJsonArray("children");
Iterator<JsonElement> iter = children.iterator();
while(iter.hasNext()) {
JsonObject childObject = (JsonObject)iter.next();
if(childObject.get("label") == null) {
iter.remove();
}
if(childObject.get("children") != null && childObject.getAsJsonArray("children").size() == 0) {
childObject.remove("children");
}
}
return jsonObject;
}
}
private static class NodeDeserializer implements JsonDeserializer<Wallet.Node> {
@Override
public Wallet.Node deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
Wallet.Node node = getGson(false).fromJson(json, typeOfT);
for(Wallet.Node childNode : node.getChildren()) {
if(childNode.getChildren() == null) {
childNode.setChildren(new TreeSet<>());
}
}
return node;
}
}
public static class WalletAndKey { public static class WalletAndKey {
public final Wallet wallet; public final Wallet wallet;
public final Key key; public final Key key;

View file

@ -1,20 +1,23 @@
package com.sparrowwallet.sparrow.wallet; package com.sparrowwallet.sparrow.wallet;
import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import java.util.ArrayList;
import java.util.List; import java.util.List;
public abstract class Entry { public abstract class Entry {
private final SimpleStringProperty labelProperty; private final SimpleStringProperty labelProperty;
private final List<Entry> children = new ArrayList<>(); private final ObservableList<Entry> children;
public Entry(String label) { public Entry(String label, List<Entry> entries) {
this.labelProperty = new SimpleStringProperty(label); this.labelProperty = new SimpleStringProperty(label);
this.children = FXCollections.observableList(entries);
} }
public Entry(SimpleStringProperty labelProperty) { public Entry(SimpleStringProperty labelProperty, ObservableList<Entry> children) {
this.labelProperty = labelProperty; this.labelProperty = labelProperty;
this.children = children;
} }
public String getLabel() { public String getLabel() {
@ -25,7 +28,7 @@ public abstract class Entry {
return labelProperty; return labelProperty;
} }
public List<Entry> getChildren() { public ObservableList<Entry> getChildren() {
return children; return children;
} }

View file

@ -2,11 +2,13 @@ package com.sparrowwallet.sparrow.wallet;
import com.sparrowwallet.drongo.wallet.Wallet; import com.sparrowwallet.drongo.wallet.Wallet;
import java.util.stream.Collectors;
public class NodeEntry extends Entry { public class NodeEntry extends Entry {
private final Wallet.Node node; private final Wallet.Node node;
public NodeEntry(Wallet.Node node) { public NodeEntry(Wallet.Node node) {
super(node.getLabel()); super(node.getLabel(), node.getChildren().stream().map(NodeEntry::new).collect(Collectors.toList()));
this.node = node; this.node = node;
labelProperty().addListener((observable, oldValue, newValue) -> node.setLabel(newValue)); labelProperty().addListener((observable, oldValue, newValue) -> node.setLabel(newValue));

View file

@ -1,5 +1,6 @@
package com.sparrowwallet.sparrow.wallet; package com.sparrowwallet.sparrow.wallet;
import com.google.common.eventbus.Subscribe;
import com.google.zxing.BarcodeFormat; import com.google.zxing.BarcodeFormat;
import com.google.zxing.client.j2se.MatrixToImageConfig; import com.google.zxing.client.j2se.MatrixToImageConfig;
import com.google.zxing.client.j2se.MatrixToImageWriter; import com.google.zxing.client.j2se.MatrixToImageWriter;
@ -9,8 +10,7 @@ import com.sparrowwallet.drongo.KeyPurpose;
import com.sparrowwallet.sparrow.EventManager; import com.sparrowwallet.sparrow.EventManager;
import com.sparrowwallet.sparrow.control.CopyableLabel; import com.sparrowwallet.sparrow.control.CopyableLabel;
import com.sparrowwallet.sparrow.control.CopyableTextField; import com.sparrowwallet.sparrow.control.CopyableTextField;
import javafx.beans.value.ChangeListener; import com.sparrowwallet.sparrow.event.ReceiveToEvent;
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent; import javafx.event.ActionEvent;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.fxml.Initializable; import javafx.fxml.Initializable;
@ -43,6 +43,9 @@ public class ReceiveController extends WalletFormController implements Initializ
@FXML @FXML
private CodeArea scriptPubKeyArea; private CodeArea scriptPubKeyArea;
@FXML
private CodeArea outputDescriptor;
private NodeEntry currentEntry; private NodeEntry currentEntry;
@Override @Override
@ -78,6 +81,9 @@ public class ReceiveController extends WalletFormController implements Initializ
scriptPubKeyArea.clear(); scriptPubKeyArea.clear();
appendScript(scriptPubKeyArea, nodeEntry.getNode().getOutputScript(), null, null); appendScript(scriptPubKeyArea, nodeEntry.getNode().getOutputScript(), null, null);
outputDescriptor.clear();
outputDescriptor.appendText(nodeEntry.getNode().getOutputDescriptor());
} }
private Image getQrCode(String address) { private Image getQrCode(String address) {
@ -101,4 +107,9 @@ public class ReceiveController extends WalletFormController implements Initializ
NodeEntry freshEntry = getWalletForm().getFreshNodeEntry(KeyPurpose.RECEIVE, currentEntry); NodeEntry freshEntry = getWalletForm().getFreshNodeEntry(KeyPurpose.RECEIVE, currentEntry);
setNodeEntry(freshEntry); setNodeEntry(freshEntry);
} }
@Subscribe
public void receiveTo(ReceiveToEvent event) {
setNodeEntry(event.getReceiveEntry());
}
} }

View file

@ -3,6 +3,7 @@ package com.sparrowwallet.sparrow.wallet;
import com.google.common.eventbus.Subscribe; import com.google.common.eventbus.Subscribe;
import com.sparrowwallet.sparrow.AppController; import com.sparrowwallet.sparrow.AppController;
import com.sparrowwallet.sparrow.EventManager; import com.sparrowwallet.sparrow.EventManager;
import com.sparrowwallet.sparrow.event.ReceiveActionEvent;
import com.sparrowwallet.sparrow.event.WalletChangedEvent; import com.sparrowwallet.sparrow.event.WalletChangedEvent;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.fxml.FXML; import javafx.fxml.FXML;
@ -97,4 +98,9 @@ public class WalletController extends WalletFormController implements Initializa
public void walletChanged(WalletChangedEvent event) { public void walletChanged(WalletChangedEvent event) {
configure(walletForm.getWallet().isValid()); configure(walletForm.getWallet().isValid());
} }
@Subscribe
public void receiveAction(ReceiveActionEvent event) {
selectFunction(Function.RECEIVE);
}
} }

View file

@ -52,11 +52,6 @@ public class WalletForm {
} else { } else {
Wallet.Node purposeNode = getWallet().getNode(keyPurpose); Wallet.Node purposeNode = getWallet().getNode(keyPurpose);
purposeEntry = new NodeEntry(purposeNode); purposeEntry = new NodeEntry(purposeNode);
for(Wallet.Node childNode : purposeNode.getChildren()) {
NodeEntry childEntry = new NodeEntry(childNode);
purposeEntry.getChildren().add(childEntry);
}
accountEntries.add(purposeEntry); accountEntries.add(purposeEntry);
} }

View file

@ -5,4 +5,9 @@
.qr-code { .qr-code {
-fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.8), 10, 0, 0, 0); -fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.8), 10, 0, 0, 0);
-fx-padding: 20; -fx-padding: 20;
}
.receive-form .form .fieldset:horizontal .label-container {
-fx-pref-width: 90px;
-fx-pref-height: 25px;
} }

View file

@ -20,13 +20,13 @@
<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"> <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> <center>
<GridPane hgap="10.0" vgap="10.0"> <GridPane styleClass="receive-form" hgap="10.0" vgap="10.0">
<padding> <padding>
<Insets left="25.0" right="25.0" top="25.0" /> <Insets left="25.0" right="25.0" top="25.0" />
</padding> </padding>
<columnConstraints> <columnConstraints>
<ColumnConstraints percentWidth="70" /> <ColumnConstraints percentWidth="80" />
<ColumnConstraints percentWidth="30" /> <ColumnConstraints percentWidth="20" />
</columnConstraints> </columnConstraints>
<rowConstraints> <rowConstraints>
<RowConstraints /> <RowConstraints />
@ -34,7 +34,7 @@
<Form GridPane.columnIndex="0" GridPane.rowIndex="0"> <Form GridPane.columnIndex="0" GridPane.rowIndex="0">
<Fieldset inputGrow="SOMETIMES" text="Receive"> <Fieldset inputGrow="SOMETIMES" text="Receive">
<Field text="Address:"> <Field text="Address:">
<CopyableTextField fx:id="address" styleClass="address-text-field" editable="false" prefWidth="350"/> <CopyableTextField fx:id="address" styleClass="address-text-field" editable="false"/>
</Field> </Field>
<Field text="Label:"> <Field text="Label:">
<TextField fx:id="label" /> <TextField fx:id="label" />
@ -56,7 +56,7 @@
<Form GridPane.columnIndex="0" GridPane.columnSpan="2" GridPane.rowIndex="2"> <Form GridPane.columnIndex="0" GridPane.columnSpan="2" GridPane.rowIndex="2">
<Fieldset inputGrow="SOMETIMES" text="Required Script"> <Fieldset inputGrow="SOMETIMES" text="Required Script">
<Field text="ScriptPubKey:"> <Field text="ScriptPubKey">
<VirtualizedScrollPane> <VirtualizedScrollPane>
<content> <content>
<CodeArea fx:id="scriptPubKeyArea" editable="false" wrapText="true" prefHeight="42" maxHeight="42" styleClass="uneditable-codearea" /> <CodeArea fx:id="scriptPubKeyArea" editable="false" wrapText="true" prefHeight="42" maxHeight="42" styleClass="uneditable-codearea" />
@ -66,6 +66,18 @@
</Fieldset> </Fieldset>
</Form> </Form>
<Form GridPane.columnIndex="0" GridPane.columnSpan="2" GridPane.rowIndex="3">
<Fieldset inputGrow="SOMETIMES" text="Output Descriptor">
<Field text="Descriptor:">
<VirtualizedScrollPane>
<content>
<CodeArea fx:id="outputDescriptor" editable="false" wrapText="true" prefHeight="42" maxHeight="42" styleClass="uneditable-codearea" />
</content>
</VirtualizedScrollPane>
</Field>
</Fieldset>
</Form>
</GridPane> </GridPane>
</center> </center>
<bottom> <bottom>