transaction headers pane first draft

This commit is contained in:
Craig Raw 2020-03-28 10:45:39 +02:00
parent 4826671fc3
commit b6b18a2a7c
33 changed files with 885 additions and 76 deletions

View file

@ -2,3 +2,8 @@
Bitcoin Transaction Editor
To clone this project, use `git clone --recursive git@github.com:craigraw/sparrow.git`
## Various ways to hex dump a file without spaces:
xxd -p file | tr -d '\n'
hexdump -ve '1/1 "%.2x"'
od -t x1 -An file | tr -d '\n '

View file

@ -19,6 +19,8 @@ javafx {
dependencies {
implementation project(':drongo')
compile group: 'org.fxmisc.richtext', name: 'richtextfx', version: '0.10.4'
compile group: 'no.tornado', name: 'tornadofx-controls', version: '1.0.4'
testCompile group: 'junit', name: 'junit', version: '4.12'
}

2
drongo

@ -1 +1 @@
Subproject commit 566aa2e95315291dd7c2092dfc1378bf2fa0d484
Subproject commit 7fb5601de38e79d5c0547ebc684ca137aa895134

View file

@ -1,11 +1,12 @@
package com.craigraw.sparrow;
import com.craigraw.drongo.Utils;
import com.craigraw.drongo.protocol.Transaction;
import com.craigraw.drongo.psbt.PSBT;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.Parent;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.stage.FileChooser;
@ -25,13 +26,20 @@ public class AppController implements Initializable {
@Override
public void initialize(URL location, ResourceBundle resources) {
addExampleTxTabs();
}
public void open(ActionEvent event) {
Stage window = new Stage();
FileChooser fileChooser = new FileChooser();
fileChooser.setTitle("Open Transaction");
fileChooser.getExtensionFilters().addAll(
new FileChooser.ExtensionFilter("All Files", "*.*"),
new FileChooser.ExtensionFilter("PSBT", "*.psbt"),
new FileChooser.ExtensionFilter("TXN", "*.txn")
);
File file = fileChooser.showOpenDialog(window);
if (file != null) {
System.out.println(file);
@ -61,4 +69,29 @@ public class AppController implements Initializable {
}
}
}
private void addExampleTxTabs() {
addTransactionTab("p2pkh", "01000000019c2e0f24a03e72002a96acedb12a632e72b6b74c05dc3ceab1fe78237f886c48010000006a47304402203da9d487be5302a6d69e02a861acff1da472885e43d7528ed9b1b537a8e2cac9022002d1bca03a1e9715a99971bafe3b1852b7a4f0168281cbd27a220380a01b3307012102c9950c622494c2e9ff5a003e33b690fe4832477d32c2d256c67eab8bf613b34effffffff02b6f50500000000001976a914bdf63990d6dc33d705b756e13dd135466c06b3b588ac845e0201000000001976a9145fb0e9755a3424efd2ba0587d20b1e98ee29814a88ac06241559");
addTransactionTab("p2sh-p2wpkh", "01000000000101db6b1b20aa0fd7b23880be2ecbd4a98130974cf4748fb66092ac4d3ceb1a5477010000001716001479091972186c449eb1ded22b78e40d009bdf0089feffffff02b8b4eb0b000000001976a914a457b684d7f0d539a46a45bbc043f35b59d0d96388ac0008af2f000000001976a914fd270b1ee6abcaea97fea7ad0402e8bd8ad6d77c88ac02473044022047ac8e878352d3ebbde1c94ce3a10d057c24175747116f8288e5d794d12d482f0220217f36a485cae903c713331d877c1f64677e3622ad4010726870540656fe9dcb012103ad1d8e89212f0b92c74d23bb710c00662ad1470198ac48c43f7d6f93a2a2687392040000");
addTransactionTab("p2wpkh", "01000000000102fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f00000000494830450221008b9d1dc26ba6a9cb62127b02742fa9d754cd3bebf337f7a55d114c8e5cdd30be022040529b194ba3f9281a99f2b1c0a19c0489bc22ede944ccf4ecbab4cc618ef3ed01eeffffffef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a0100000000ffffffff02202cb206000000001976a9148280b37df378db99f66f85c95a783a76ac7a6d5988ac9093510d000000001976a9143bde42dbee7e4dbe6a21b2d50ce2f0167faa815988ac000247304402203609e17b84f6a7d30c80bfa610b5b4542f32a8a0d5447a12fb1366d7f01cc44a0220573a954c4518331561406f90300e8f3358f51928d43c212a8caed02de67eebee0121025476c2e83188368da1ff3e292e7acafcdb3566bb0ad253f62fc70f07aeee635711000000");
addTransactionTab("p2wsh", "01000000000102fe3dc9208094f3ffd12645477b3dc56f60ec4fa8e6f5d67c565d1c6b9216b36e000000004847304402200af4e47c9b9629dbecc21f73af989bdaa911f7e6f6c2e9394588a3aa68f81e9902204f3fcf6ade7e5abb1295b6774c8e0abd94ae62217367096bc02ee5e435b67da201ffffffff0815cf020f013ed6cf91d29f4202e8a58726b1ac6c79da47c23d1bee0a6925f80000000000ffffffff0100f2052a010000001976a914a30741f8145e5acadf23f751864167f32e0963f788ac000347304402200de66acf4527789bfda55fc5459e214fa6083f936b430a762c629656216805ac0220396f550692cd347171cbc1ef1f51e15282e837bb2b30860dc77c8f78bc8501e503473044022027dc95ad6b740fe5129e7e62a75dd00f291a2aeb1200b84b09d9e3789406b6c002201a9ecd315dd6a0e632ab20bbb98948bc0c6fb204f2c286963bb48517a7058e27034721026dccc749adc2a9d0d89497ac511f760f45c47dc5ed9cf352a58ac706453880aeadab210255a9626aebf5e29c0e6538428ba0d1dcf6ca98ffdf086aa8ced5e0d0215ea465ac00000000");
addTransactionTab("test1", "02000000000102ba4dc5a4a14bfaa941b7d115b379b5e15f960635cf694c178b9116763cbd63b11600000017160014fc164cbcac023f5eacfcead2d17d8768c41949affeffffff074d44d2856beb68ba52e8832da60a1682768c2421c2d9a8109ef4e66babd1fd1e000000171600148c3098be6b430859115f5ee99c84c368afecd048feffffff02305310000000000017a914ffaf369c2212b178c7a2c21c9ccdd5d126e74c4187327f0300000000001976a914a7cda2e06b102a143ab606937a01d152e300cd3e88ac02473044022006da0ca227f765179219e08a33026b94e7cacff77f87b8cd8eb1b46d6dda11d6022064faa7912924fd23406b6ed3328f1bbbc3760dc51109a49c1b38bf57029d304f012103c6a2fcd030270427d4abe1041c8af929a9e2dbab07b243673453847ab842ee1f024730440220786316a16095105a0af28dccac5cf80f449dea2ea810a9559a89ecb989c2cb3d02205cbd9913d1217ffec144ae4f2bd895f16d778c2ec49ae9c929fdc8bcc2a2b1db0121024d4985241609d072a59be6418d700e87688f6c4d99a51ad68e66078211f076ee38820900");
}
private void addTransactionTab(String name, String hex) {
try {
byte[] txbytes = Utils.hexToBytes(hex);
Transaction transaction = new Transaction(txbytes);
Tab tab = new Tab(name);
FXMLLoader transactionLoader = new FXMLLoader(getClass().getResource("transaction.fxml"));
tab.setContent(transactionLoader.load());
TransactionController controller = transactionLoader.getController();
controller.setTransaction(transaction);
tabs.getTabs().add(tab);
} catch(IOException e) {
throw new RuntimeException(e);
}
}
}

View file

@ -0,0 +1,31 @@
package com.craigraw.sparrow;
import com.craigraw.drongo.protocol.Transaction;
import java.util.ArrayList;
import java.util.List;
public class EventManager {
private List<TransactionListener> listenerList = new ArrayList<>();
private static EventManager SINGLETON = new EventManager();
private EventManager() {}
public void subscribe(TransactionListener listener) {
listenerList.add(listener);
}
public void unsubscribe(TransactionListener listener) {
listenerList.remove(listener);
}
public void notify(Transaction transaction) {
for (TransactionListener listener : listenerList) {
listener.updated(transaction);
}
}
public static EventManager get() {
return SINGLETON;
}
}

View file

@ -2,46 +2,66 @@ package com.craigraw.sparrow;
import com.craigraw.drongo.protocol.*;
import com.craigraw.drongo.psbt.PSBT;
import com.craigraw.sparrow.form.*;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.TreeCell;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import javafx.scene.control.cell.TextFieldTreeCell;
import javafx.util.Callback;
import javafx.scene.layout.Pane;
import javafx.util.StringConverter;
import org.bouncycastle.util.encoders.Hex;
import org.fxmisc.richtext.CodeArea;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.URL;
import java.util.ResourceBundle;
public class TransactionController implements Initializable {
public class TransactionController implements Initializable, TransactionListener {
@FXML
private TreeView<TransactionPart> txtree;
private TreeView<Form> txtree;
@FXML
private Pane txpane;
@FXML
private CodeArea txhex;
private Transaction transaction;
private PSBT psbt;
@Override
public void initialize(URL location, ResourceBundle resources) {
EventManager.get().subscribe(this);
}
private void initialiseTxTree() {
TreeItem<TransactionPart> rootItem = new TreeItem<>(transaction);
private void initializeView() {
initializeTxTree();
refreshTxHex();
}
private void initializeTxTree() {
HeadersForm headersForm = new HeadersForm(transaction, psbt);
TreeItem<Form> rootItem = new TreeItem<>(headersForm);
rootItem.setExpanded(true);
InputsPart inputsPart = new InputsPart();
TreeItem<TransactionPart> inputsItem = new TreeItem<TransactionPart>(inputsPart);
for(TransactionInput input : transaction.getInputs()) {
TreeItem<TransactionPart> inputItem = new TreeItem<>(input);
InputsForm inputsForm = new InputsForm(transaction);
TreeItem<Form> inputsItem = new TreeItem<>(inputsForm);
inputsItem.setExpanded(true);
for(TransactionInput txInput : transaction.getInputs()) {
InputForm inputForm = new InputForm(txInput);
TreeItem<Form> inputItem = new TreeItem<>(inputForm);
inputsItem.getChildren().add(inputItem);
}
OutputsPart outputsPart = new OutputsPart();
TreeItem<TransactionPart> outputsItem = new TreeItem<TransactionPart>(outputsPart);
for(TransactionOutput output : transaction.getOutputs()) {
TreeItem<TransactionPart> outputItem = new TreeItem<>(output);
OutputsForm outputsForm = new OutputsForm(transaction);
TreeItem<Form> outputsItem = new TreeItem<>(outputsForm);
outputsItem.setExpanded(true);
for(TransactionOutput txOutput : transaction.getOutputs()) {
OutputForm outputForm = new OutputForm(txOutput);
TreeItem<Form> outputItem = new TreeItem<>(outputForm);
outputsItem.getChildren().add(outputItem);
}
@ -49,66 +69,113 @@ public class TransactionController implements Initializable {
rootItem.getChildren().add(outputsItem);
txtree.setRoot(rootItem);
txtree.setCellFactory(new Callback<TreeView<TransactionPart>, TreeCell<TransactionPart>>() {
txtree.setCellFactory(p -> new TextFieldTreeCell<>(new StringConverter<Form>(){
@Override
public TreeCell<TransactionPart> call(TreeView<TransactionPart> p) {
return new TextFieldTreeCell<TransactionPart>(new StringConverter<TransactionPart>(){
public String toString(Form form) {
return form.toString();
}
@Override
public String toString(TransactionPart part) {
if(part instanceof Transaction) {
Transaction transaction = (Transaction)part;
return "Tx " + transaction.getTxId().toString().substring(0, 6) + "...";
} else if(part instanceof InputsPart) {
return "Inputs";
} else if(part instanceof OutputsPart) {
return "Outputs";
} else if(part instanceof TransactionInput) {
TransactionInput input = (TransactionInput)part;
return "Input #" + input.getIndex();
} else if(part instanceof TransactionOutput) {
TransactionOutput output = (TransactionOutput)part;
return "Output #" + output.getIndex();
}
@Override
public Form fromString(String string) {
throw new IllegalStateException("No editing");
}
}));
return part.toString();
}
txtree.getSelectionModel().selectedItemProperty().addListener((observable, old_val, new_val) -> {
Form form = new_val.getValue();
@Override
public TransactionPart fromString(String string) {
throw new IllegalStateException("No fromString");
}
});
try {
txpane.getChildren().removeAll();
txpane.getChildren().add(form.getContents());
} catch (IOException e) {
throw new IllegalStateException("Can't find pane", e);
}
});
txtree.getSelectionModel().select(txtree.getRoot());
}
void refreshTxHex() {
txhex.clear();
String hex = "";
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
transaction.bitcoinSerializeToStream(baos);
hex = Hex.toHexString(baos.toByteArray());
} catch(IOException e) {
throw new IllegalStateException("Can't happen");
}
int cursor = 0;
//Version
cursor = addText(hex, cursor, 8, "version");
if(transaction.hasWitnesses()) {
//Segwit marker
cursor = addText(hex, cursor, 2, "segwit-marker");
//Segwit flag
cursor = addText(hex, cursor, 2, "segwit-flag");
}
//Number of inputs
cursor = addText(hex, cursor, 2, "num-inputs");
//Inputs
int totalInputLength = 0;
for(TransactionInput input : transaction.getInputs()) {
totalInputLength += input.getLength();
}
cursor = addText(hex, cursor, totalInputLength*2, "inputs");
//Number of outputs
cursor = addText(hex, cursor, 2, "num-outputs");
//Outputs
int totalOutputLength = 0;
for(TransactionOutput output : transaction.getOutputs()) {
totalOutputLength += output.getLength();
}
cursor = addText(hex, cursor, totalOutputLength*2, "outputs");
if(transaction.hasWitnesses()) {
int totalWitnessLength = 0;
for(TransactionInput input : transaction.getInputs()) {
totalWitnessLength += input.getWitness().getLength();
}
cursor = addText(hex, cursor, totalWitnessLength*2, "witnesses");
}
//Locktime
cursor = addText(hex, cursor, 8, "locktime");
if(cursor != hex.length()) {
throw new IllegalStateException("Cursor position does not match transaction serialisation " + cursor + ": " + hex.length());
}
}
private int addText(String hex, int cursor, int length, String description) {
txhex.append(hex.substring(cursor, cursor+=length), description + "-color");
return cursor;
}
public void setPSBT(PSBT psbt) {
this.psbt = psbt;
this.transaction = psbt.getTransaction();
initialiseTxTree();
initializeView();
}
private static class InputsPart extends TransactionPart {
public InputsPart() {
super(new byte[0], 0);
}
public void setTransaction(Transaction transaction) {
this.transaction = transaction;
@Override
protected void parse() throws ProtocolException {
}
initializeView();
}
private static class OutputsPart extends TransactionPart {
public OutputsPart() {
super(new byte[0], 0);
}
@Override
protected void parse() throws ProtocolException {
}
@Override
public void updated(Transaction transaction) {
refreshTxHex();
txtree.refresh();
}
}

View file

@ -0,0 +1,7 @@
package com.craigraw.sparrow;
import com.craigraw.drongo.protocol.Transaction;
public interface TransactionListener {
void updated(Transaction transaction);
}

View file

@ -0,0 +1,9 @@
package com.craigraw.sparrow.form;
import javafx.scene.Node;
import java.io.IOException;
public abstract class Form {
public abstract Node getContents() throws IOException;
}

View file

@ -0,0 +1,157 @@
package com.craigraw.sparrow.form;
import com.craigraw.drongo.protocol.Transaction;
import com.craigraw.sparrow.EventManager;
import com.craigraw.sparrow.TransactionListener;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.*;
import javafx.scene.layout.GridPane;
import tornadofx.control.DateTimePicker;
import java.net.URL;
import java.time.*;
import java.util.ResourceBundle;
public class HeadersController implements Initializable, TransactionListener {
private HeadersForm headersForm;
private static final long MAX_BLOCK_LOCKTIME = 500000000L;
@FXML
private GridPane layout;
@FXML
private TextField idField;
@FXML
private Spinner<Integer> versionField;
@FXML
private Label segwitField;
@FXML
private Label segwitVersionLabel;
@FXML
private Spinner<Integer> segwitVersionField;
@FXML
private ToggleGroup locktimeTypeField;
@FXML
private ToggleButton locktimeBlockTypeField;
@FXML
private ToggleButton locktimeDateTypeField;
@FXML
private Spinner<Integer> locktimeBlockField;
@FXML
private DateTimePicker locktimeDateField;
@FXML
private Label feeField;
@FXML
private Label sizeField;
@FXML
private Label virtualSizeField;
@Override
public void initialize(URL location, ResourceBundle resources) {
EventManager.get().subscribe(this);
}
void setModel(HeadersForm form) {
this.headersForm = form;
Transaction tx = headersForm.getTransaction();
updateTxId();
versionField.setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(1, 2, (int)tx.getVersion()));
versionField.valueProperty().addListener((obs, oldValue, newValue) -> {
tx.setVersion(newValue);
EventManager.get().notify(tx);
});
segwitField.setText(tx.isSegwit() ? "Segwit" : "Legacy" );
if(tx.isSegwit()) {
segwitVersionField.setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(1, 2, tx.getSegwitVersion()));
segwitVersionField.valueProperty().addListener((obs, oldValue, newValue) -> {
tx.setSegwitVersion(newValue);
EventManager.get().notify(tx);
});
} else {
layout.getChildren().removeAll(segwitVersionLabel, segwitVersionField);
}
locktimeTypeField.selectedToggleProperty().addListener((ov, old_toggle, new_toggle) -> {
if(locktimeTypeField.getSelectedToggle() != null) {
String selection = locktimeTypeField.getSelectedToggle().getUserData().toString();
if(selection.equals("block")) {
locktimeBlockField.setVisible(true);
locktimeDateField.setVisible(false);
Integer block = locktimeBlockField.getValue();
if(block != null) {
tx.setLockTime(block);
EventManager.get().notify(tx);
}
} else {
locktimeBlockField.setVisible(false);
locktimeDateField.setVisible(true);
LocalDateTime date = locktimeDateField.getDateTimeValue();
if(date != null) {
tx.setLockTime(date.toEpochSecond(OffsetDateTime.now(ZoneId.systemDefault()).getOffset()));
EventManager.get().notify(tx);
}
}
}
});
if(tx.getLockTime() < MAX_BLOCK_LOCKTIME) {
locktimeBlockField.setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(0, (int)MAX_BLOCK_LOCKTIME-1, (int)tx.getLockTime()));
locktimeBlockField.setVisible(true);
locktimeDateField.setVisible(false);
locktimeTypeField.selectToggle(locktimeBlockTypeField);
} else {
LocalDateTime date = Instant.ofEpochSecond(tx.getLockTime()).atZone(ZoneId.systemDefault()).toLocalDateTime();
locktimeDateField.setDateTimeValue(date);
locktimeBlockField.setVisible(false);
locktimeDateField.setVisible(true);
locktimeTypeField.selectToggle(locktimeDateTypeField);
}
locktimeBlockField.valueProperty().addListener((obs, oldValue, newValue) -> {
tx.setLockTime(newValue);
EventManager.get().notify(tx);
});
locktimeDateField.setFormat("yyyy-MM-dd HH:mm:ss");
locktimeDateField.dateTimeValueProperty().addListener((obs, oldValue, newValue) -> {
tx.setLockTime(newValue.toEpochSecond(OffsetDateTime.now(ZoneId.systemDefault()).getOffset()));
EventManager.get().notify(tx);
});
if(form.getPsbt() != null) {
feeField.setText(form.getPsbt().getFee().toString() + " sats");
} else {
feeField.setText("Unknown");
}
sizeField.setText(tx.getSize() + " B");
virtualSizeField.setText(tx.getVirtualSize() + " vB");
}
private void updateTxId() {
idField.setText(headersForm.getTransaction().calculateTxId(false).toString());
}
@Override
public void updated(Transaction transaction) {
updateTxId();
}
}

View file

@ -0,0 +1,38 @@
package com.craigraw.sparrow.form;
import com.craigraw.drongo.protocol.Transaction;
import com.craigraw.drongo.psbt.PSBT;
import javafx.fxml.FXMLLoader;
import javafx.scene.Node;
import java.io.IOException;
public class HeadersForm extends Form {
private Transaction transaction;
private PSBT psbt;
public HeadersForm(Transaction transaction, PSBT psbt) {
this.transaction = transaction;
this.psbt = psbt;
}
public Transaction getTransaction() {
return transaction;
}
public PSBT getPsbt() {
return psbt;
}
public Node getContents() throws IOException {
FXMLLoader loader = new FXMLLoader(getClass().getResource("headers.fxml"));
Node node = loader.load();
HeadersController controller = loader.getController();
controller.setModel(this);
return node;
}
public String toString() {
return "Tx [" + transaction.calculateTxId(false).toString().substring(0, 6) + "]";
}
}

View file

@ -0,0 +1,19 @@
package com.craigraw.sparrow.form;
import javafx.fxml.Initializable;
import java.net.URL;
import java.util.ResourceBundle;
public class InputController implements Initializable {
private InputForm inputForm;
@Override
public void initialize(URL location, ResourceBundle resources) {
}
public void setModel(InputForm form) {
this.inputForm = form;
}
}

View file

@ -0,0 +1,27 @@
package com.craigraw.sparrow.form;
import com.craigraw.drongo.protocol.TransactionInput;
import javafx.fxml.FXMLLoader;
import javafx.scene.Node;
import java.io.IOException;
public class InputForm extends Form {
private TransactionInput transactionInput;
public InputForm(TransactionInput transactionInput) {
this.transactionInput = transactionInput;
}
public Node getContents() throws IOException {
FXMLLoader loader = new FXMLLoader(getClass().getResource("input.fxml"));
Node node = loader.load();
InputController controller = loader.getController();
controller.setModel(this);
return node;
}
public String toString() {
return "Input #" + transactionInput.getIndex();
}
}

View file

@ -0,0 +1,19 @@
package com.craigraw.sparrow.form;
import javafx.fxml.Initializable;
import java.net.URL;
import java.util.ResourceBundle;
public class InputsController implements Initializable {
private InputsForm inputsForm;
@Override
public void initialize(URL location, ResourceBundle resources) {
}
public void setModel(InputsForm form) {
this.inputsForm = form;
}
}

View file

@ -0,0 +1,27 @@
package com.craigraw.sparrow.form;
import com.craigraw.drongo.protocol.Transaction;
import javafx.fxml.FXMLLoader;
import javafx.scene.Node;
import java.io.IOException;
public class InputsForm extends Form {
private Transaction transaction;
public InputsForm(Transaction transaction) {
this.transaction = transaction;
}
public Node getContents() throws IOException {
FXMLLoader loader = new FXMLLoader(getClass().getResource("inputs.fxml"));
Node node = loader.load();
InputsController controller = loader.getController();
controller.setModel(this);
return node;
}
public String toString() {
return "Inputs";
}
}

View file

@ -0,0 +1,19 @@
package com.craigraw.sparrow.form;
import javafx.fxml.Initializable;
import java.net.URL;
import java.util.ResourceBundle;
public class OutputController implements Initializable {
private OutputForm outputForm;
@Override
public void initialize(URL location, ResourceBundle resources) {
}
public void setModel(OutputForm form) {
this.outputForm = form;
}
}

View file

@ -0,0 +1,27 @@
package com.craigraw.sparrow.form;
import com.craigraw.drongo.protocol.TransactionOutput;
import javafx.fxml.FXMLLoader;
import javafx.scene.Node;
import java.io.IOException;
public class OutputForm extends Form {
private TransactionOutput transactionOutput;
public OutputForm(TransactionOutput transactionOutput) {
this.transactionOutput = transactionOutput;
}
public Node getContents() throws IOException {
FXMLLoader loader = new FXMLLoader(getClass().getResource("output.fxml"));
Node node = loader.load();
OutputController controller = loader.getController();
controller.setModel(this);
return node;
}
public String toString() {
return "Output #" + transactionOutput.getIndex();
}
}

View file

@ -0,0 +1,19 @@
package com.craigraw.sparrow.form;
import javafx.fxml.Initializable;
import java.net.URL;
import java.util.ResourceBundle;
public class OutputsController implements Initializable {
private OutputsForm outputsForm;
@Override
public void initialize(URL location, ResourceBundle resources) {
}
public void setModel(OutputsForm form) {
this.outputsForm = form;
}
}

View file

@ -0,0 +1,27 @@
package com.craigraw.sparrow.form;
import com.craigraw.drongo.protocol.Transaction;
import javafx.fxml.FXMLLoader;
import javafx.scene.Node;
import java.io.IOException;
public class OutputsForm extends Form {
private Transaction transaction;
public OutputsForm(Transaction transaction) {
this.transaction = transaction;
}
public Node getContents() throws IOException {
FXMLLoader loader = new FXMLLoader(getClass().getResource("outputs.fxml"));
Node node = loader.load();
OutputsController controller = loader.getController();
controller.setModel(this);
return node;
}
public String toString() {
return "Outputs";
}
}

View file

@ -0,0 +1,19 @@
package com.craigraw.sparrow.form;
import javafx.fxml.Initializable;
import java.net.URL;
import java.util.ResourceBundle;
public class PartialInputController implements Initializable {
private PartialInputForm partialInputForm;
@Override
public void initialize(URL location, ResourceBundle resources) {
}
public void setModel(PartialInputForm form) {
this.partialInputForm = form;
}
}

View file

@ -0,0 +1,23 @@
package com.craigraw.sparrow.form;
import com.craigraw.drongo.psbt.PSBTInput;
import javafx.fxml.FXMLLoader;
import javafx.scene.Node;
import java.io.IOException;
public class PartialInputForm extends Form {
private PSBTInput psbtInput;
public PartialInputForm(PSBTInput psbtInput) {
this.psbtInput = psbtInput;
}
public Node getContents() throws IOException {
FXMLLoader loader = new FXMLLoader(getClass().getResource("partialinput.fxml"));
Node node = loader.load();
PartialInputController controller = loader.getController();
controller.setModel(this);
return node;
}
}

View file

@ -0,0 +1,19 @@
package com.craigraw.sparrow.form;
import javafx.fxml.Initializable;
import java.net.URL;
import java.util.ResourceBundle;
public class PartialOutputController implements Initializable {
private PartialOutputForm partialOutputForm;
@Override
public void initialize(URL location, ResourceBundle resources) {
}
public void setModel(PartialOutputForm form) {
this.partialOutputForm = form;
}
}

View file

@ -0,0 +1,23 @@
package com.craigraw.sparrow.form;
import com.craigraw.drongo.psbt.PSBTOutput;
import javafx.fxml.FXMLLoader;
import javafx.scene.Node;
import java.io.IOException;
public class PartialOutputForm extends Form {
private PSBTOutput psbtOutput;
public PartialOutputForm(PSBTOutput psbtOutput) {
this.psbtOutput = psbtOutput;
}
public Node getContents() throws IOException {
FXMLLoader loader = new FXMLLoader(getClass().getResource("partialoutput.fxml"));
Node node = loader.load();
PartialOutputController controller = loader.getController();
controller.setModel(this);
return node;
}
}

View file

@ -3,7 +3,7 @@
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<VBox maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" fx:controller="com.craigraw.sparrow.AppController" xmlns="http://javafx.com/javafx/10.0.2-internal" xmlns:fx="http://javafx.com/fxml/1">
<VBox maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="700.0" prefWidth="900.0" fx:controller="com.craigraw.sparrow.AppController" xmlns="http://javafx.com/javafx/10.0.2-internal" xmlns:fx="http://javafx.com/fxml/1">
<children>
<MenuBar>
<menus>

View file

@ -0,0 +1,82 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.text.*?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.control.Spinner?>
<?import javafx.scene.control.ToggleButton?>
<?import javafx.scene.control.ToggleGroup?>
<?import javafx.scene.control.Separator?>
<?import tornadofx.control.DateTimePicker?>
<GridPane fx:id="layout" hgap="10.0" prefHeight="350.0" prefWidth="600.0" vgap="10.0" alignment="CENTER" xmlns="http://javafx.com/javafx/10.0.2-internal" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.craigraw.sparrow.form.HeadersController" stylesheets="@../general.css">
<padding>
<Insets bottom="25.0" left="25.0" right="25.0" top="25.0" />
</padding>
<columnConstraints>
<ColumnConstraints minWidth="100" prefWidth="100"
maxWidth="Infinity" halignment="RIGHT">
</ColumnConstraints>
<ColumnConstraints minWidth="200" prefWidth="200"
maxWidth="Infinity" hgrow="ALWAYS">
</ColumnConstraints>
</columnConstraints>
<rowConstraints>
<RowConstraints />
</rowConstraints>
<!-- Add Header Label -->
<Label text="Headers" GridPane.columnIndex="0"
GridPane.rowIndex="0" GridPane.columnSpan="2"
GridPane.rowSpan="1" GridPane.halignment="LEFT" >
<font>
<Font name="Arial" size="24" ></Font>
</font>
<GridPane.margin>
<Insets top="0" right="0" bottom="10" left="0"></Insets>
</GridPane.margin>
</Label>
<Label text="Transaction ID: " GridPane.columnIndex="0" GridPane.rowIndex="1" />
<TextField fx:id="idField" editable="false" styleClass="copyable-label" prefHeight="40" prefWidth="520" minWidth="520" GridPane.columnIndex="1" GridPane.rowIndex="1"/>
<Label text="Version: " GridPane.columnIndex="0" GridPane.rowIndex="2" />
<Spinner fx:id="versionField" prefHeight="40" prefWidth="60" editable="true" GridPane.columnIndex="1" GridPane.rowIndex="2" />
<Label text="Type: " GridPane.columnIndex="0" GridPane.rowIndex="3" />
<Label fx:id="segwitField" prefHeight="40" GridPane.columnIndex="1" GridPane.rowIndex="3"/>
<Label fx:id="segwitVersionLabel" text="Segwit Version: " GridPane.columnIndex="0" GridPane.rowIndex="4" />
<Spinner fx:id="segwitVersionField" prefWidth="60" prefHeight="40" GridPane.columnIndex="1" GridPane.rowIndex="4"/>
<Separator GridPane.columnIndex="0" GridPane.rowIndex="5" GridPane.columnSpan="2" GridPane.rowSpan="1" GridPane.halignment="LEFT" />
<Label text="Locktime Type: " GridPane.columnIndex="0" GridPane.rowIndex="6" />
<HBox fillHeight="true" GridPane.columnIndex="1" GridPane.rowIndex="6">
<children>
<ToggleButton fx:id="locktimeBlockTypeField" text="Block" userData="block">
<toggleGroup>
<ToggleGroup fx:id="locktimeTypeField" />
</toggleGroup>
</ToggleButton>
<ToggleButton fx:id="locktimeDateTypeField" text="Date" toggleGroup="$locktimeTypeField" userData="date" />
</children>
</HBox>
<Label text="Locktime: " GridPane.columnIndex="0" GridPane.rowIndex="7" />
<Spinner fx:id="locktimeBlockField" prefHeight="40" prefWidth="120" GridPane.columnIndex="1" GridPane.rowIndex="7"/>
<DateTimePicker fx:id="locktimeDateField" visible="false" prefHeight="40" GridPane.columnIndex="1" GridPane.rowIndex="7"/>
<Separator GridPane.columnIndex="0" GridPane.rowIndex="8" GridPane.columnSpan="2" GridPane.rowSpan="1" GridPane.halignment="LEFT" />
<Label text="Size: " GridPane.columnIndex="0" GridPane.rowIndex="9" />
<Label fx:id="sizeField" prefHeight="40" prefWidth="120" GridPane.columnIndex="1" GridPane.rowIndex="9"/>
<Label text="Virtual Size: " GridPane.columnIndex="0" GridPane.rowIndex="10" />
<Label fx:id="virtualSizeField" prefHeight="40" prefWidth="120" GridPane.columnIndex="1" GridPane.rowIndex="10"/>
<Label text="Fee: " GridPane.columnIndex="0" GridPane.rowIndex="11" />
<Label fx:id="feeField" prefHeight="40" prefWidth="120" GridPane.columnIndex="1" GridPane.rowIndex="11"/>
</GridPane>

View file

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<AnchorPane xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml"
fx:controller="com.craigraw.sparrow.form.InputController"
prefHeight="400.0" prefWidth="600.0">
</AnchorPane>

View file

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<AnchorPane xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml"
fx:controller="com.craigraw.sparrow.form.InputsController"
prefHeight="400.0" prefWidth="600.0">
</AnchorPane>

View file

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<AnchorPane xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml"
fx:controller="com.craigraw.sparrow.form.OutputController"
prefHeight="400.0" prefWidth="600.0">
</AnchorPane>

View file

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<AnchorPane xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml"
fx:controller="com.craigraw.sparrow.form.OutputsController"
prefHeight="400.0" prefWidth="600.0">
</AnchorPane>

View file

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<AnchorPane xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml"
fx:controller="com.craigraw.sparrow.form.PartialInputController"
prefHeight="400.0" prefWidth="600.0">
</AnchorPane>

View file

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<AnchorPane xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml"
fx:controller="com.craigraw.sparrow.form.PartialOutputController"
prefHeight="400.0" prefWidth="600.0">
</AnchorPane>

View file

@ -0,0 +1,6 @@
.copyable-label, .copyable-label:focused {
-fx-background-color: transparent ;
-fx-background-insets: 0px ;
-fx-border :none;
-fx-padding: 0;
}

View file

@ -0,0 +1,27 @@
#txhex {
-fx-background-color: #ffffff;
-fx-font: 14px Courier;
-fx-padding: 2;
}
.version-color { -fx-fill: #986801 }
.segwit-marker-color { -fx-fill: #000000 }
.segwit-flag-color { -fx-fill: #4078f2 }
.num-inputs-color { -fx-fill: #ca1243 }
.inputs-color { -fx-fill: #0184bc }
.num-outputs-color { -fx-fill: #ca1243 }
.outputs-color { -fx-fill: #50a14f }
.witnesses-color { -fx-fill: #a626a4 }
.locktime-color { -fx-fill: #986801 }
.color-0 { -fx-fill: #ca1243 }
.color-1 { -fx-fill: #d75f00 }
.color-2 { -fx-fill: #c18401 }
.color-3 { -fx-fill: #50a14f }
.color-4 { -fx-fill: #0184bc }
.color-5 { -fx-fill: #4078f2 }
.color-6 { -fx-fill: #a626a4 }
.color-7 { -fx-fill: #986801 }
.color-8 { -fx-fill: #000000 }
.color-grey { -fx-fill: #e5e5e6 }

View file

@ -6,23 +6,16 @@
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<SplitPane dividerPositions="0.7" orientation="VERTICAL" prefHeight="200.0" prefWidth="160.0" VBox.vgrow="ALWAYS" fx:controller="com.craigraw.sparrow.TransactionController" xmlns="http://javafx.com/javafx/10.0.2-internal" xmlns:fx="http://javafx.com/fxml/1">
<?import javafx.scene.text.TextFlow?>
<?import org.fxmisc.richtext.CodeArea?>
<SplitPane dividerPositions="0.7" orientation="VERTICAL" prefHeight="200.0" prefWidth="160.0" VBox.vgrow="ALWAYS" fx:controller="com.craigraw.sparrow.TransactionController" xmlns="http://javafx.com/javafx/10.0.2-internal" xmlns:fx="http://javafx.com/fxml/1" stylesheets="@transaction.css">
<items>
<SplitPane dividerPositions="0.4" prefHeight="160.0" prefWidth="200.0">
<SplitPane dividerPositions="0.2" prefHeight="160.0" prefWidth="200.0">
<items>
<TreeView prefHeight="154.0" prefWidth="174.0" fx:id="txtree" />
<ScrollPane fitToHeight="true" fitToWidth="true" prefHeight="200.0" prefWidth="200.0">
<content>
<TableView prefHeight="200.0" prefWidth="200.0" fx:id="txnode">
<columns>
<TableColumn prefWidth="75.0" text="Name" />
<TableColumn prefWidth="300.0" text="Value" />
</columns>
</TableView>
</content>
</ScrollPane>
<Pane fx:id="txpane" />
</items>
</SplitPane>
<TextArea />
<CodeArea fx:id="txhex" wrapText="true" editable="false" />
</items>
</SplitPane>