signatures progress bar and sign buttons

This commit is contained in:
Craig Raw
2020-07-20 17:05:08 +02:00
parent 6ef333ae2a
commit fca55779bf
8 changed files with 333 additions and 7 deletions
@@ -0,0 +1,150 @@
package com.sparrowwallet.sparrow.control;
import com.sparrowwallet.drongo.wallet.Keystore;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.geometry.Orientation;
import javafx.geometry.Pos;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressBar;
import javafx.scene.layout.StackPane;
import javafx.util.Duration;
import org.controlsfx.control.SegmentedBar;
public class SignaturesProgressBar extends SegmentedBar<SignaturesProgressBar.SignatureProgressSegment> {
public SignaturesProgressBar() {
setOrientation(Orientation.HORIZONTAL);
setSegmentViewFactory(SignatureProgressSegmentView::new);
setInfoNodeFactory(segment -> segment.getKeystore() == null ? null : new SignatureProgressSegmentLabel(segment.getKeystore().getLabel()));
}
public void initialize(ObservableList<Keystore> signedKeystores, int threshold) {
getStyleClass().add("signatures-progress-bar");
getSegments().clear();
int numSegments = Math.max(threshold, signedKeystores.size());
double segmentSize = 100d / numSegments;
for(int i = 0; i < numSegments; i++) {
if(i < signedKeystores.size()) {
getSegments().add(new SignatureProgressSegment(segmentSize, i, signedKeystores.get(i)));
} else {
getSegments().add(new SignatureProgressSegment(segmentSize, i, null));
}
}
signedKeystores.addListener((ListChangeListener<Keystore>) c -> {
int numSegments1 = Math.max(threshold, c.getList().size());
double newSegmentSize = 100d / numSegments1;
for(int i = 0; i < numSegments1; i++) {
SignatureProgressSegment segment = null;
if(i < getSegments().size()) {
segment = getSegments().get(i);
}
Keystore signedKeystore = null;
if(i < signedKeystores.size()) {
signedKeystore = signedKeystores.get(i);
}
if(segment != null) {
//Animate new signature if changed
segment.setKeystore(signedKeystore);
} else {
//Add extra (unnecessary) signature
for(SignaturesProgressBar.SignatureProgressSegment existingSegment : getSegments()) {
existingSegment.setValue(newSegmentSize);
}
SignaturesProgressBar.SignatureProgressSegment newSegment = new SignatureProgressSegment(newSegmentSize, i, null);
getSegments().add(newSegment);
newSegment.setKeystore(signedKeystore);
}
}
});
}
public static class SignatureProgressSegment extends SegmentedBar.Segment {
private final SimpleObjectProperty<Keystore> keystoreProperty;
private final int index;
public SignatureProgressSegment(double value, int index, Keystore keystore) {
super(value);
this.index = index;
this.keystoreProperty = new SimpleObjectProperty<>(this, "keystore", null);
keystoreProperty.addListener((observable, oldValue, newValue) -> {
setText(newValue == null ? "No keystore" : newValue.getLabel());
});
setKeystore(keystore);
}
public int getIndex() {
return index;
}
public Keystore getKeystore() {
return keystoreProperty.get();
}
public SimpleObjectProperty<Keystore> keystoreProperty() {
return keystoreProperty;
}
public void setKeystore(Keystore keystore) {
keystoreProperty.set(keystore);
}
}
public static class SignatureProgressSegmentView extends StackPane {
private final ProgressBar progressBar;
private final Label label;
public SignatureProgressSegmentView(SignatureProgressSegment segment) {
getStyleClass().add("signature-progress-segment");
getStyleClass().add("segment" + segment.getIndex());
label = new Label();
label.textProperty().bind(segment.textProperty());
label.getStyleClass().add("signature-progress-segment-label");
StackPane.setAlignment(label, Pos.CENTER);
progressBar = new ProgressBar(segment.getKeystore() == null ? 0.0 : 1.0);
progressBar.setPrefWidth(Double.MAX_VALUE);
progressBar.setPrefHeight(30);
setPrefHeight(50);
getChildren().addAll(progressBar, label);
segment.keystoreProperty().addListener((observable, oldValue, newValue) -> {
if(oldValue == null && newValue != null) {
Timeline timeline = new Timeline(
new KeyFrame(Duration.ZERO, new KeyValue(progressBar.progressProperty(), 0)),
new KeyFrame(Duration.millis(800), new KeyValue(progressBar.progressProperty(), 1))
);
timeline.setCycleCount(1);
timeline.play();
}
});
}
@Override
protected void layoutChildren() {
super.layoutChildren();
label.setVisible(label.prefWidth(-1) < getWidth() - getPadding().getLeft() - getPadding().getRight() - 8);
}
}
public static class SignatureProgressSegmentLabel extends Label {
public SignatureProgressSegmentLabel(String text) {
super(text);
setPadding(new Insets(10));
}
}
}
@@ -27,6 +27,7 @@ public class FontAwesome5 extends GlyphFont {
LOCK('\uf023'),
LOCK_OPEN('\uf3c1'),
PEN_FANCY('\uf5ac'),
QRCODE('\uf029'),
QUESTION_CIRCLE('\uf059'),
SD_CARD('\uf7c2'),
SEARCH('\uf002'),
@@ -4,13 +4,17 @@ import com.sparrowwallet.drongo.protocol.*;
import com.sparrowwallet.drongo.psbt.PSBT;
import com.sparrowwallet.drongo.psbt.PSBTInput;
import com.sparrowwallet.drongo.wallet.BlockTransaction;
import com.sparrowwallet.drongo.wallet.Keystore;
import com.sparrowwallet.drongo.wallet.KeystoreSource;
import com.sparrowwallet.drongo.wallet.Wallet;
import com.sparrowwallet.sparrow.AppController;
import com.sparrowwallet.sparrow.EventManager;
import com.sparrowwallet.sparrow.control.CoinLabel;
import com.sparrowwallet.sparrow.control.IdLabel;
import com.sparrowwallet.sparrow.control.CopyableLabel;
import com.sparrowwallet.sparrow.control.SignaturesProgressBar;
import com.sparrowwallet.sparrow.event.*;
import com.sparrowwallet.sparrow.glyphfont.FontAwesome5Brands;
import javafx.collections.FXCollections;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
@@ -18,6 +22,7 @@ import javafx.fxml.Initializable;
import javafx.scene.control.*;
import javafx.scene.input.Clipboard;
import javafx.scene.input.ClipboardContent;
import org.controlsfx.glyphfont.Glyph;
import tornadofx.control.DateTimePicker;
import tornadofx.control.Field;
import tornadofx.control.Fieldset;
@@ -27,9 +32,7 @@ import tornadofx.control.Form;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.time.*;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.*;
import java.util.stream.Collectors;
public class HeadersController extends TransactionFormController implements Initializable {
@@ -128,6 +131,12 @@ public class HeadersController extends TransactionFormController implements Init
@FXML
private Form signaturesForm;
@FXML
private SignaturesProgressBar signaturesProgressBar;
@FXML
private Button signButton;
@FXML
private Form broadcastForm;
@@ -303,6 +312,17 @@ public class HeadersController extends TransactionFormController implements Init
psbtInput.setSigHash(newValue);
}
});
headersForm.signingWalletProperty().addListener((observable, oldValue, signingWallet) -> {
initializeSignButton(signingWallet);
Map<PSBTInput, List<Keystore>> signedKeystoresMap = signingWallet.getSignedKeystores(headersForm.getPsbt());
Optional<List<Keystore>> optSignedKeystores = signedKeystoresMap.values().stream().filter(list -> !list.isEmpty()).min(Comparator.comparingInt(List::size));
optSignedKeystores.ifPresent(keystores -> headersForm.getSignedKeystores().setAll(keystores));
int threshold = signingWallet.getDefaultPolicy().getNumSignaturesRequired();
signaturesProgressBar.initialize(headersForm.getSignedKeystores(), threshold);
});
}
}
@@ -370,6 +390,18 @@ public class HeadersController extends TransactionFormController implements Init
}
}
private void initializeSignButton(Wallet signingWallet) {
Optional<Keystore> softwareKeystore = signingWallet.getKeystores().stream().filter(keystore -> keystore.getSource().equals(KeystoreSource.SW_SEED)).findAny();
Optional<Keystore> usbKeystore = signingWallet.getKeystores().stream().filter(keystore -> keystore.getSource().equals(KeystoreSource.HW_USB)).findAny();
if(softwareKeystore.isEmpty() && usbKeystore.isEmpty()) {
signButton.setDisable(true);
} else if(softwareKeystore.isEmpty()) {
Glyph usbGlyph = new Glyph(FontAwesome5Brands.FONT_NAME, FontAwesome5Brands.Glyph.USB);
usbGlyph.setFontSize(20);
signButton.setGraphic(usbGlyph);
}
}
private static class BlockHeightContextMenu extends ContextMenu {
public BlockHeightContextMenu(Sha256Hash blockHash) {
MenuItem copyBlockHash = new MenuItem("Copy Block Hash");
@@ -404,6 +436,18 @@ public class HeadersController extends TransactionFormController implements Init
EventManager.get().post(new FinalizePSBTEvent(headersForm.getPsbt(), signingWallet.getValue()));
}
public void showPSBT(ActionEvent event) {
}
public void savePSBT(ActionEvent event) {
}
public void signPSBT(ActionEvent event) {
headersForm.getSignedKeystores().add(headersForm.getSigningWallet().getKeystores().get(0));
}
@Subscribe
public void transactionChanged(TransactionChangedEvent event) {
if(headersForm.getTransaction().equals(event.getTransaction())) {
@@ -4,7 +4,11 @@ import com.sparrowwallet.drongo.protocol.Sha256Hash;
import com.sparrowwallet.drongo.protocol.Transaction;
import com.sparrowwallet.drongo.psbt.PSBT;
import com.sparrowwallet.drongo.wallet.BlockTransaction;
import com.sparrowwallet.drongo.wallet.Keystore;
import com.sparrowwallet.drongo.wallet.Wallet;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import java.util.List;
import java.util.Map;
@@ -21,7 +25,8 @@ public class TransactionData {
private int minOutputFetched;
private int maxOutputFetched;
private Wallet signingWallet;
private final SimpleObjectProperty<Wallet> signingWallet = new SimpleObjectProperty<>(this, "signingWallet", null);
private final ObservableList<Keystore> signedKeystores = FXCollections.observableArrayList();
public TransactionData(PSBT psbt) {
this.transaction = psbt.getTransaction();
@@ -114,10 +119,18 @@ public class TransactionData {
}
public Wallet getSigningWallet() {
return signingWallet.get();
}
public SimpleObjectProperty<Wallet> signingWalletProperty() {
return signingWallet;
}
public void setSigningWallet(Wallet signingWallet) {
this.signingWallet = signingWallet;
public void setSigningWallet(Wallet wallet) {
this.signingWallet.set(wallet);
}
public ObservableList<Keystore> getSignedKeystores() {
return signedKeystores;
}
}
@@ -4,7 +4,10 @@ import com.sparrowwallet.drongo.protocol.Sha256Hash;
import com.sparrowwallet.drongo.protocol.Transaction;
import com.sparrowwallet.drongo.psbt.PSBT;
import com.sparrowwallet.drongo.wallet.BlockTransaction;
import com.sparrowwallet.drongo.wallet.Keystore;
import com.sparrowwallet.drongo.wallet.Wallet;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.ObservableList;
import javafx.scene.Node;
import java.io.IOException;
@@ -58,10 +61,18 @@ public abstract class TransactionForm {
return txdata.getSigningWallet();
}
public SimpleObjectProperty<Wallet> signingWalletProperty() {
return txdata.signingWalletProperty();
}
public void setSigningWallet(Wallet signingWallet) {
txdata.setSigningWallet(signingWallet);
}
public ObservableList<Keystore> getSignedKeystores() {
return txdata.getSignedKeystores();
}
public boolean isEditable() {
if(getBlockTransaction() != null) {
return false;