add google guava for event bus, app control changes

This commit is contained in:
Craig Raw 2020-04-10 14:30:11 +02:00
parent 16bac7326e
commit fb9de5be56
16 changed files with 209 additions and 115 deletions

View file

@ -28,6 +28,7 @@ dependencies {
exclude group: 'org.hamcrest' exclude group: 'org.hamcrest'
exclude group: 'junit' exclude group: 'junit'
} }
implementation('com.google.guava:guava:28.2-jre')
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.controlsfx:controlsfx:11.0.1' ) { implementation('org.controlsfx:controlsfx:11.0.1' ) {
@ -56,7 +57,7 @@ jlink {
requires 'javafx.base'; requires 'javafx.base';
} }
options = ['--strip-debug', '--compress', '2', '--no-header-files', '--no-man-pages', '--ignore-signing-information'] options = ['--strip-debug', '--compress', '2', '--no-header-files', '--no-man-pages', '--ignore-signing-information', '--exclude-files', '**.png']
launcher { launcher {
name = 'sparrow' name = 'sparrow'
jvmArgs = ["--add-opens=javafx.graphics/com.sun.javafx.css=org.controlsfx.controls"] jvmArgs = ["--add-opens=javafx.graphics/com.sun.javafx.css=org.controlsfx.controls"]
@ -75,7 +76,6 @@ jlink {
} }
if (org.gradle.internal.os.OperatingSystem.current().macOsX) { if (org.gradle.internal.os.OperatingSystem.current().macOsX) {
imageOptions += ['--icon', 'src/main/resources/sparrow.icns'] imageOptions += ['--icon', 'src/main/resources/sparrow.icns']
installerType = "dmg" installerType = "dmg"
} }
} }

View file

@ -1,18 +1,19 @@
package com.sparrowwallet.sparrow; package com.sparrowwallet.sparrow;
import com.google.common.eventbus.Subscribe;
import com.sparrowwallet.drongo.Utils; import com.sparrowwallet.drongo.Utils;
import com.sparrowwallet.drongo.protocol.Transaction; import com.sparrowwallet.drongo.protocol.Transaction;
import com.sparrowwallet.drongo.psbt.PSBT; import com.sparrowwallet.drongo.psbt.PSBT;
import com.sparrowwallet.drongo.psbt.PSBTParseException; import com.sparrowwallet.drongo.psbt.PSBTParseException;
import com.sparrowwallet.sparrow.event.TabEvent;
import com.sparrowwallet.sparrow.event.TransactionTabChangedEvent;
import com.sparrowwallet.sparrow.event.TransactionTabSelectedEvent;
import com.sparrowwallet.sparrow.transaction.TransactionController; import com.sparrowwallet.sparrow.transaction.TransactionController;
import com.sparrowwallet.sparrow.transaction.TransactionListener;
import javafx.event.ActionEvent; import javafx.event.ActionEvent;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader; import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable; import javafx.fxml.Initializable;
import javafx.scene.control.Alert; import javafx.scene.control.*;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.stage.FileChooser; import javafx.stage.FileChooser;
import javafx.stage.Stage; import javafx.stage.Stage;
@ -23,6 +24,10 @@ import java.net.URL;
import java.util.ResourceBundle; import java.util.ResourceBundle;
public class AppController implements Initializable { public class AppController implements Initializable {
private static final String TRANSACTION_TAB_TYPE = "transaction";
@FXML
private CheckMenuItem showTxHex;
@FXML @FXML
private TabPane tabs; private TabPane tabs;
@ -33,14 +38,15 @@ public class AppController implements Initializable {
} }
void initializeView() { void initializeView() {
tabs.getSelectionModel().selectedItemProperty().addListener((observable, old_val, new_val) -> { tabs.getSelectionModel().selectedItemProperty().addListener((observable, old_val, selectedTab) -> {
String tabName = new_val.getText(); String tabType = (String)selectedTab.getUserData();
if(tabs.getScene() != null) { if(tabType.equals(TRANSACTION_TAB_TYPE)) {
Stage tabStage = (Stage)tabs.getScene().getWindow(); EventManager.get().post(new TransactionTabSelectedEvent(selectedTab));
tabStage.setTitle("Sparrow - " + tabName);
} }
}); });
showTxHex.setSelected(true);
addExampleTxTabs(); addExampleTxTabs();
} }
@ -99,6 +105,11 @@ public class AppController implements Initializable {
} }
} }
public void showTxHex(ActionEvent event) {
CheckMenuItem item = (CheckMenuItem)event.getSource();
EventManager.get().post(new TransactionTabChangedEvent(tabs.getSelectionModel().getSelectedItem(), item.isSelected()));
}
private void addExampleTxTabs() { private void addExampleTxTabs() {
addTransactionTab("p2pkh", "01000000019c2e0f24a03e72002a96acedb12a632e72b6b74c05dc3ceab1fe78237f886c48010000006a47304402203da9d487be5302a6d69e02a861acff1da472885e43d7528ed9b1b537a8e2cac9022002d1bca03a1e9715a99971bafe3b1852b7a4f0168281cbd27a220380a01b3307012102c9950c622494c2e9ff5a003e33b690fe4832477d32c2d256c67eab8bf613b34effffffff02b6f50500000000001976a914bdf63990d6dc33d705b756e13dd135466c06b3b588ac845e0201000000001976a9145fb0e9755a3424efd2ba0587d20b1e98ee29814a88ac06241559", null); addTransactionTab("p2pkh", "01000000019c2e0f24a03e72002a96acedb12a632e72b6b74c05dc3ceab1fe78237f886c48010000006a47304402203da9d487be5302a6d69e02a861acff1da472885e43d7528ed9b1b537a8e2cac9022002d1bca03a1e9715a99971bafe3b1852b7a4f0168281cbd27a220380a01b3307012102c9950c622494c2e9ff5a003e33b690fe4832477d32c2d256c67eab8bf613b34effffffff02b6f50500000000001976a914bdf63990d6dc33d705b756e13dd135466c06b3b588ac845e0201000000001976a9145fb0e9755a3424efd2ba0587d20b1e98ee29814a88ac06241559", null);
addTransactionTab("p2sh", "0100000003a5ee1a0fd80dfbc3142df136ab56e082b799c13aa977c048bdf8f61bd158652c000000006b48304502203b0160de302cded63589a88214fe499a25aa1d86a2ea09129945cd632476a12c022100c77727daf0718307e184d55df620510cf96d4b5814ae3258519c0482c1ca82fa0121024f4102c1f1cf662bf99f2b034eb03edd4e6c96793cb9445ff519aab580649120ffffffff0fce901eb7b7551ba5f414735ff93b83a2a57403df11059ec88245fba2aaf1a0000000006a47304402204089adb8a1de1a9e22aa43b94d54f1e54dc9bea745d57df1a633e03dd9ede3c2022037d1e53e911ed7212186028f2e085f70524930e22eb6184af090ba4ab779a5b90121030644cb394bf381dbec91680bdf1be1986ad93cfb35603697353199fb285a119effffffff0fce901eb7b7551ba5f414735ff93b83a2a57403df11059ec88245fba2aaf1a0010000009300493046022100a07b2821f96658c938fa9c68950af0e69f3b2ce5f8258b3a6ad254d4bc73e11e022100e82fab8df3f7e7a28e91b3609f91e8ebf663af3a4dc2fd2abd954301a5da67e701475121022afc20bf379bc96a2f4e9e63ffceb8652b2b6a097f63fbee6ecec2a49a48010e2103a767c7221e9f15f870f1ad9311f5ab937d79fcaeee15bb2c722bca515581b4c052aeffffffff02a3b81b00000000001976a914ea00917f128f569cbdf79da5efcd9001671ab52c88ac80969800000000001976a9143dec0ead289be1afa8da127a7dbdd425a05e25f688ac00000000", null); addTransactionTab("p2sh", "0100000003a5ee1a0fd80dfbc3142df136ab56e082b799c13aa977c048bdf8f61bd158652c000000006b48304502203b0160de302cded63589a88214fe499a25aa1d86a2ea09129945cd632476a12c022100c77727daf0718307e184d55df620510cf96d4b5814ae3258519c0482c1ca82fa0121024f4102c1f1cf662bf99f2b034eb03edd4e6c96793cb9445ff519aab580649120ffffffff0fce901eb7b7551ba5f414735ff93b83a2a57403df11059ec88245fba2aaf1a0000000006a47304402204089adb8a1de1a9e22aa43b94d54f1e54dc9bea745d57df1a633e03dd9ede3c2022037d1e53e911ed7212186028f2e085f70524930e22eb6184af090ba4ab779a5b90121030644cb394bf381dbec91680bdf1be1986ad93cfb35603697353199fb285a119effffffff0fce901eb7b7551ba5f414735ff93b83a2a57403df11059ec88245fba2aaf1a0010000009300493046022100a07b2821f96658c938fa9c68950af0e69f3b2ce5f8258b3a6ad254d4bc73e11e022100e82fab8df3f7e7a28e91b3609f91e8ebf663af3a4dc2fd2abd954301a5da67e701475121022afc20bf379bc96a2f4e9e63ffceb8652b2b6a097f63fbee6ecec2a49a48010e2103a767c7221e9f15f870f1ad9311f5ab937d79fcaeee15bb2c722bca515581b4c052aeffffffff02a3b81b00000000001976a914ea00917f128f569cbdf79da5efcd9001671ab52c88ac80969800000000001976a9143dec0ead289be1afa8da127a7dbdd425a05e25f688ac00000000", null);
@ -131,6 +142,7 @@ public class AppController implements Initializable {
private Tab addTransactionTab(String name, Transaction transaction, PSBT psbt) { private Tab addTransactionTab(String name, Transaction transaction, PSBT psbt) {
try { try {
Tab tab = new Tab(name); Tab tab = new Tab(name);
tab.setUserData(TRANSACTION_TAB_TYPE);
tab.setClosable(true); tab.setClosable(true);
FXMLLoader transactionLoader = new FXMLLoader(getClass().getResource("transaction/transaction.fxml")); FXMLLoader transactionLoader = new FXMLLoader(getClass().getResource("transaction/transaction.fxml"));
tab.setContent(transactionLoader.load()); tab.setContent(transactionLoader.load());
@ -148,4 +160,16 @@ public class AppController implements Initializable {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
} }
@Subscribe
public void tabSelected(TabEvent event) {
Tab selectedTab = event.getTab();
String tabType = (String)selectedTab.getUserData();
String tabName = selectedTab.getText();
if(tabs.getScene() != null) {
Stage tabStage = (Stage)tabs.getScene().getWindow();
tabStage.setTitle("Sparrow - " + tabName);
}
}
} }

View file

@ -1,32 +1,13 @@
package com.sparrowwallet.sparrow; package com.sparrowwallet.sparrow;
import com.sparrowwallet.drongo.protocol.Transaction; import com.google.common.eventbus.EventBus;
import com.sparrowwallet.sparrow.transaction.TransactionListener;
import java.util.ArrayList;
import java.util.List;
public class EventManager { public class EventManager {
private List<TransactionListener> listenerList = new ArrayList<>(); private static EventBus SINGLETON = new EventBus();
private static EventManager SINGLETON = new EventManager();
private EventManager() {} private EventManager() {}
public void subscribe(TransactionListener listener) { public static EventBus get() {
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; return SINGLETON;
} }
} }

View file

@ -0,0 +1,15 @@
package com.sparrowwallet.sparrow.event;
import javafx.scene.control.Tab;
public class TabEvent {
private Tab tab;
public TabEvent(Tab tab) {
this.tab = tab;
}
public Tab getTab() {
return tab;
}
}

View file

@ -0,0 +1,15 @@
package com.sparrowwallet.sparrow.event;
import com.sparrowwallet.drongo.protocol.Transaction;
public class TransactionChangedEvent {
private Transaction transaction;
public TransactionChangedEvent(Transaction transaction) {
this.transaction = transaction;
}
public Transaction getTransaction() {
return transaction;
}
}

View file

@ -0,0 +1,16 @@
package com.sparrowwallet.sparrow.event;
import javafx.scene.control.Tab;
public class TransactionTabChangedEvent extends TabEvent {
private boolean txHexVisible;
public TransactionTabChangedEvent(Tab tab, boolean txHexVisible) {
super(tab);
this.txHexVisible = txHexVisible;
}
public boolean isTxHexVisible() {
return txHexVisible;
}
}

View file

@ -0,0 +1,9 @@
package com.sparrowwallet.sparrow.event;
import javafx.scene.control.Tab;
public class TransactionTabSelectedEvent extends TabEvent {
public TransactionTabSelectedEvent(Tab tab) {
super(tab);
}
}

View file

@ -4,18 +4,20 @@ import com.sparrowwallet.drongo.protocol.Transaction;
import com.sparrowwallet.sparrow.EventManager; import com.sparrowwallet.sparrow.EventManager;
import com.sparrowwallet.sparrow.control.IdLabel; import com.sparrowwallet.sparrow.control.IdLabel;
import com.sparrowwallet.sparrow.control.CopyableLabel; import com.sparrowwallet.sparrow.control.CopyableLabel;
import com.sparrowwallet.sparrow.event.TransactionChangedEvent;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.fxml.Initializable; import javafx.fxml.Initializable;
import javafx.scene.control.*; import javafx.scene.control.*;
import tornadofx.control.DateTimePicker; import tornadofx.control.DateTimePicker;
import tornadofx.control.Field; import tornadofx.control.Field;
import tornadofx.control.Fieldset; import tornadofx.control.Fieldset;
import com.google.common.eventbus.Subscribe;
import java.net.URL; import java.net.URL;
import java.time.*; import java.time.*;
import java.util.ResourceBundle; import java.util.ResourceBundle;
public class HeadersController extends TransactionFormController implements Initializable, TransactionListener { public class HeadersController extends TransactionFormController implements Initializable {
public static final String LOCKTIME_DATE_FORMAT = "yyyy-MM-dd HH:mm:ss"; public static final String LOCKTIME_DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";
private HeadersForm headersForm; private HeadersForm headersForm;
@ -76,7 +78,7 @@ public class HeadersController extends TransactionFormController implements Init
@Override @Override
public void initialize(URL location, ResourceBundle resources) { public void initialize(URL location, ResourceBundle resources) {
EventManager.get().subscribe(this); EventManager.get().register(this);
} }
void setModel(HeadersForm form) { void setModel(HeadersForm form) {
@ -92,7 +94,7 @@ public class HeadersController extends TransactionFormController implements Init
version.setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(1, 2, (int)tx.getVersion())); version.setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(1, 2, (int)tx.getVersion()));
version.valueProperty().addListener((obs, oldValue, newValue) -> { version.valueProperty().addListener((obs, oldValue, newValue) -> {
tx.setVersion(newValue); tx.setVersion(newValue);
EventManager.get().notify(tx); EventManager.get().post(new TransactionChangedEvent(tx));
}); });
String type = "Legacy"; String type = "Legacy";
@ -113,7 +115,7 @@ public class HeadersController extends TransactionFormController implements Init
locktimeFieldset.getChildren().remove(locktimeNoneField); locktimeFieldset.getChildren().remove(locktimeNoneField);
locktimeFieldset.getChildren().add(locktimeNoneField); locktimeFieldset.getChildren().add(locktimeNoneField);
tx.setLocktime(0); tx.setLocktime(0);
EventManager.get().notify(tx); EventManager.get().post(new TransactionChangedEvent(tx));
} else if(selection.equals("block")) { } else if(selection.equals("block")) {
locktimeFieldset.getChildren().remove(locktimeDateField); locktimeFieldset.getChildren().remove(locktimeDateField);
locktimeFieldset.getChildren().remove(locktimeBlockField); locktimeFieldset.getChildren().remove(locktimeBlockField);
@ -122,7 +124,7 @@ public class HeadersController extends TransactionFormController implements Init
Integer block = locktimeBlock.getValue(); Integer block = locktimeBlock.getValue();
if(block != null) { if(block != null) {
tx.setLocktime(block); tx.setLocktime(block);
EventManager.get().notify(tx); EventManager.get().post(new TransactionChangedEvent(tx));
} }
} else { } else {
locktimeFieldset.getChildren().remove(locktimeBlockField); locktimeFieldset.getChildren().remove(locktimeBlockField);
@ -133,7 +135,7 @@ public class HeadersController extends TransactionFormController implements Init
if(date != null) { if(date != null) {
locktimeDate.setDateTimeValue(date); locktimeDate.setDateTimeValue(date);
tx.setLocktime(date.toEpochSecond(OffsetDateTime.now(ZoneId.systemDefault()).getOffset())); tx.setLocktime(date.toEpochSecond(OffsetDateTime.now(ZoneId.systemDefault()).getOffset()));
EventManager.get().notify(tx); EventManager.get().post(new TransactionChangedEvent(tx));
} }
} }
} }
@ -158,13 +160,13 @@ public class HeadersController extends TransactionFormController implements Init
locktimeBlock.valueProperty().addListener((obs, oldValue, newValue) -> { locktimeBlock.valueProperty().addListener((obs, oldValue, newValue) -> {
tx.setLocktime(newValue); tx.setLocktime(newValue);
EventManager.get().notify(tx); EventManager.get().post(new TransactionChangedEvent(tx));
}); });
locktimeDate.setFormat(LOCKTIME_DATE_FORMAT); locktimeDate.setFormat(LOCKTIME_DATE_FORMAT);
locktimeDate.dateTimeValueProperty().addListener((obs, oldValue, newValue) -> { locktimeDate.dateTimeValueProperty().addListener((obs, oldValue, newValue) -> {
tx.setLocktime(newValue.toEpochSecond(OffsetDateTime.now(ZoneId.systemDefault()).getOffset())); tx.setLocktime(newValue.toEpochSecond(OffsetDateTime.now(ZoneId.systemDefault()).getOffset()));
EventManager.get().notify(tx); EventManager.get().post(new TransactionChangedEvent(tx));
}); });
size.setText(tx.getSize() + " B"); size.setText(tx.getSize() + " B");
@ -188,15 +190,16 @@ public class HeadersController extends TransactionFormController implements Init
id.setText(headersForm.getTransaction().calculateTxId(false).toString()); id.setText(headersForm.getTransaction().calculateTxId(false).toString());
} }
@Override @Subscribe
public void updated(Transaction transaction) { public void transactionChanged(TransactionChangedEvent event) {
updateTxId(); if(headersForm.getTransaction().equals(event.getTransaction())) {
updateTxId();
boolean locktimeEnabled = headersForm.getTransaction().isLocktimeSequenceEnabled(); boolean locktimeEnabled = headersForm.getTransaction().isLocktimeSequenceEnabled();
locktimeNoneType.setDisable(!locktimeEnabled); locktimeNoneType.setDisable(!locktimeEnabled);
locktimeBlockType.setDisable(!locktimeEnabled); locktimeBlockType.setDisable(!locktimeEnabled);
locktimeBlock.setDisable(!locktimeEnabled); locktimeBlock.setDisable(!locktimeEnabled);
locktimeDateType.setDisable(!locktimeEnabled); locktimeDateType.setDisable(!locktimeEnabled);
locktimeDate.setDisable(!locktimeEnabled); locktimeDate.setDisable(!locktimeEnabled);
}
} }
} }

View file

@ -9,6 +9,7 @@ import com.sparrowwallet.sparrow.EventManager;
import com.sparrowwallet.sparrow.control.IdLabel; import com.sparrowwallet.sparrow.control.IdLabel;
import com.sparrowwallet.sparrow.control.CopyableLabel; import com.sparrowwallet.sparrow.control.CopyableLabel;
import com.sparrowwallet.sparrow.control.RelativeTimelockSpinner; import com.sparrowwallet.sparrow.control.RelativeTimelockSpinner;
import com.sparrowwallet.sparrow.event.TransactionChangedEvent;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.fxml.Initializable; import javafx.fxml.Initializable;
import javafx.scene.control.*; import javafx.scene.control.*;
@ -243,12 +244,12 @@ public class InputController extends TransactionFormController implements Initia
locktimeToggleGroup.selectToggle(locktimeAbsoluteType); locktimeToggleGroup.selectToggle(locktimeAbsoluteType);
} else if(txInput.isAbsoluteTimeLocked()) { } else if(txInput.isAbsoluteTimeLocked()) {
txInput.setSequenceNumber(TransactionInput.SEQUENCE_RBF_ENABLED); txInput.setSequenceNumber(TransactionInput.SEQUENCE_RBF_ENABLED);
EventManager.get().notify(transaction); EventManager.get().post(new TransactionChangedEvent(transaction));
} }
} else { } else {
if(txInput.isAbsoluteTimeLocked()) { if(txInput.isAbsoluteTimeLocked()) {
txInput.setSequenceNumber(TransactionInput.SEQUENCE_LOCKTIME_DISABLED - 1); txInput.setSequenceNumber(TransactionInput.SEQUENCE_LOCKTIME_DISABLED - 1);
EventManager.get().notify(transaction); EventManager.get().post(new TransactionChangedEvent(transaction));
} else if(txInput.isRelativeTimeLocked()) { } else if(txInput.isRelativeTimeLocked()) {
locktimeToggleGroup.selectToggle(locktimeAbsoluteType); locktimeToggleGroup.selectToggle(locktimeAbsoluteType);
} }
@ -268,7 +269,7 @@ public class InputController extends TransactionFormController implements Initia
locktimeAbsoluteField.setDisable(true); locktimeAbsoluteField.setDisable(true);
txInput.setSequenceNumber(TransactionInput.SEQUENCE_LOCKTIME_DISABLED); txInput.setSequenceNumber(TransactionInput.SEQUENCE_LOCKTIME_DISABLED);
rbf.setSelected(false); rbf.setSelected(false);
EventManager.get().notify(transaction); EventManager.get().post(new TransactionChangedEvent(transaction));
} else if(selection.equals("absolute")) { } else if(selection.equals("absolute")) {
locktimeFieldset.getChildren().removeAll(locktimeRelativeField, locktimeAbsoluteField); locktimeFieldset.getChildren().removeAll(locktimeRelativeField, locktimeAbsoluteField);
locktimeFieldset.getChildren().add(locktimeAbsoluteField); locktimeFieldset.getChildren().add(locktimeAbsoluteField);
@ -279,7 +280,7 @@ public class InputController extends TransactionFormController implements Initia
} else { } else {
txInput.setSequenceNumber(TransactionInput.SEQUENCE_LOCKTIME_DISABLED - 1); txInput.setSequenceNumber(TransactionInput.SEQUENCE_LOCKTIME_DISABLED - 1);
} }
EventManager.get().notify(transaction); EventManager.get().post(new TransactionChangedEvent(transaction));
} else { } else {
locktimeFieldset.getChildren().removeAll(locktimeRelativeField, locktimeAbsoluteField); locktimeFieldset.getChildren().removeAll(locktimeRelativeField, locktimeAbsoluteField);
locktimeFieldset.getChildren().add(locktimeRelativeField); locktimeFieldset.getChildren().add(locktimeRelativeField);
@ -348,7 +349,7 @@ public class InputController extends TransactionFormController implements Initia
long value = locktimeRelativeSeconds.getValue().toSeconds() / TransactionInput.RELATIVE_TIMELOCK_SECONDS_INCREMENT; long value = locktimeRelativeSeconds.getValue().toSeconds() / TransactionInput.RELATIVE_TIMELOCK_SECONDS_INCREMENT;
txInput.setSequenceNumber((value & TransactionInput.RELATIVE_TIMELOCK_VALUE_MASK) | TransactionInput.RELATIVE_TIMELOCK_TYPE_FLAG); txInput.setSequenceNumber((value & TransactionInput.RELATIVE_TIMELOCK_VALUE_MASK) | TransactionInput.RELATIVE_TIMELOCK_TYPE_FLAG);
} }
EventManager.get().notify(transaction); EventManager.get().post(new TransactionChangedEvent(transaction));
} }
public void setModel(InputForm form) { public void setModel(InputForm form) {

View file

@ -1,11 +1,15 @@
package com.sparrowwallet.sparrow.transaction; package com.sparrowwallet.sparrow.transaction;
import com.google.common.eventbus.Subscribe;
import com.sparrowwallet.drongo.Utils; import com.sparrowwallet.drongo.Utils;
import com.sparrowwallet.drongo.protocol.*; import com.sparrowwallet.drongo.protocol.*;
import com.sparrowwallet.drongo.psbt.PSBT; import com.sparrowwallet.drongo.psbt.PSBT;
import com.sparrowwallet.drongo.psbt.PSBTInput; import com.sparrowwallet.drongo.psbt.PSBTInput;
import com.sparrowwallet.drongo.psbt.PSBTOutput; import com.sparrowwallet.drongo.psbt.PSBTOutput;
import com.sparrowwallet.sparrow.EventManager; import com.sparrowwallet.sparrow.EventManager;
import com.sparrowwallet.sparrow.event.TransactionChangedEvent;
import com.sparrowwallet.sparrow.event.TransactionTabChangedEvent;
import com.sparrowwallet.sparrow.event.TransactionTabSelectedEvent;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.fxml.Initializable; import javafx.fxml.Initializable;
import javafx.scene.Node; import javafx.scene.Node;
@ -15,6 +19,7 @@ import javafx.scene.control.TreeView;
import javafx.scene.control.cell.TextFieldTreeCell; import javafx.scene.control.cell.TextFieldTreeCell;
import javafx.scene.layout.Pane; import javafx.scene.layout.Pane;
import javafx.util.StringConverter; import javafx.util.StringConverter;
import org.controlsfx.control.MasterDetailPane;
import org.fxmisc.richtext.CodeArea; import org.fxmisc.richtext.CodeArea;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
@ -22,7 +27,13 @@ import java.io.IOException;
import java.net.URL; import java.net.URL;
import java.util.ResourceBundle; import java.util.ResourceBundle;
public class TransactionController implements Initializable, TransactionListener { public class TransactionController implements Initializable {
@FXML
private Node tabContent;
@FXML
private MasterDetailPane transactionMasterDetail;
@FXML @FXML
private TreeView<TransactionForm> txtree; private TreeView<TransactionForm> txtree;
@ -40,7 +51,7 @@ public class TransactionController implements Initializable, TransactionListener
@Override @Override
public void initialize(URL location, ResourceBundle resources) { public void initialize(URL location, ResourceBundle resources) {
EventManager.get().subscribe(this); EventManager.get().register(this);
} }
private void initializeView() { private void initializeView() {
@ -56,9 +67,9 @@ public class TransactionController implements Initializable, TransactionListener
InputsForm inputsForm = (psbt == null ? new InputsForm(transaction) : new InputsForm(psbt)); InputsForm inputsForm = (psbt == null ? new InputsForm(transaction) : new InputsForm(psbt));
TreeItem<TransactionForm> inputsItem = new TreeItem<>(inputsForm); TreeItem<TransactionForm> inputsItem = new TreeItem<>(inputsForm);
inputsItem.setExpanded(true); inputsItem.setExpanded(true);
for(TransactionInput txInput : transaction.getInputs()) { for (TransactionInput txInput : transaction.getInputs()) {
PSBTInput psbtInput = null; PSBTInput psbtInput = null;
if(psbt != null && psbt.getPsbtInputs().size() > txInput.getIndex()) { if (psbt != null && psbt.getPsbtInputs().size() > txInput.getIndex()) {
psbtInput = psbt.getPsbtInputs().get(txInput.getIndex()); psbtInput = psbt.getPsbtInputs().get(txInput.getIndex());
} }
InputForm inputForm = (psbt == null ? new InputForm(transaction, txInput) : new InputForm(psbt, psbtInput)); InputForm inputForm = (psbt == null ? new InputForm(transaction, txInput) : new InputForm(psbt, psbtInput));
@ -69,9 +80,9 @@ public class TransactionController implements Initializable, TransactionListener
OutputsForm outputsForm = (psbt == null ? new OutputsForm(transaction) : new OutputsForm(psbt)); OutputsForm outputsForm = (psbt == null ? new OutputsForm(transaction) : new OutputsForm(psbt));
TreeItem<TransactionForm> outputsItem = new TreeItem<>(outputsForm); TreeItem<TransactionForm> outputsItem = new TreeItem<>(outputsForm);
outputsItem.setExpanded(true); outputsItem.setExpanded(true);
for(TransactionOutput txOutput : transaction.getOutputs()) { for (TransactionOutput txOutput : transaction.getOutputs()) {
PSBTOutput psbtOutput = null; PSBTOutput psbtOutput = null;
if(psbt != null && psbt.getPsbtOutputs().size() > txOutput.getIndex()) { if (psbt != null && psbt.getPsbtOutputs().size() > txOutput.getIndex()) {
psbtOutput = psbt.getPsbtOutputs().get(txOutput.getIndex()); psbtOutput = psbt.getPsbtOutputs().get(txOutput.getIndex());
} }
OutputForm outputForm = (psbt == null ? new OutputForm(transaction, txOutput) : new OutputForm(psbt, psbtOutput)); OutputForm outputForm = (psbt == null ? new OutputForm(transaction, txOutput) : new OutputForm(psbt, psbtOutput));
@ -83,7 +94,7 @@ public class TransactionController implements Initializable, TransactionListener
rootItem.getChildren().add(outputsItem); rootItem.getChildren().add(outputsItem);
txtree.setRoot(rootItem); txtree.setRoot(rootItem);
txtree.setCellFactory(p -> new TextFieldTreeCell<>(new StringConverter<TransactionForm>(){ txtree.setCellFactory(p -> new TextFieldTreeCell<>(new StringConverter<TransactionForm>() {
@Override @Override
public String toString(TransactionForm transactionForm) { public String toString(TransactionForm transactionForm) {
return transactionForm.toString(); return transactionForm.toString();
@ -102,18 +113,18 @@ public class TransactionController implements Initializable, TransactionListener
txpane.getChildren().clear(); txpane.getChildren().clear();
txpane.getChildren().add(node); txpane.getChildren().add(node);
if(node instanceof Parent) { if (node instanceof Parent) {
Parent parent = (Parent)node; Parent parent = (Parent) node;
txhex.getStylesheets().clear(); txhex.getStylesheets().clear();
txhex.getStylesheets().addAll(parent.getStylesheets()); txhex.getStylesheets().addAll(parent.getStylesheets());
selectedInputIndex = -1; selectedInputIndex = -1;
selectedOutputIndex = -1; selectedOutputIndex = -1;
if(transactionForm instanceof InputForm) { if (transactionForm instanceof InputForm) {
InputForm inputForm = (InputForm)transactionForm; InputForm inputForm = (InputForm) transactionForm;
selectedInputIndex = inputForm.getTransactionInput().getIndex(); selectedInputIndex = inputForm.getTransactionInput().getIndex();
} else if(transactionForm instanceof OutputForm) { } else if (transactionForm instanceof OutputForm) {
OutputForm outputForm = (OutputForm)transactionForm; OutputForm outputForm = (OutputForm) transactionForm;
selectedOutputIndex = outputForm.getTransactionOutput().getIndex(); selectedOutputIndex = outputForm.getTransactionOutput().getIndex();
} }
@ -135,7 +146,7 @@ public class TransactionController implements Initializable, TransactionListener
ByteArrayOutputStream baos = new ByteArrayOutputStream(); ByteArrayOutputStream baos = new ByteArrayOutputStream();
transaction.bitcoinSerializeToStream(baos); transaction.bitcoinSerializeToStream(baos);
hex = Utils.bytesToHex(baos.toByteArray()); hex = Utils.bytesToHex(baos.toByteArray());
} catch(IOException e) { } catch (IOException e) {
throw new IllegalStateException("Can't happen"); throw new IllegalStateException("Can't happen");
} }
@ -144,7 +155,7 @@ public class TransactionController implements Initializable, TransactionListener
//Version //Version
cursor = addText(hex, cursor, 8, "version"); cursor = addText(hex, cursor, 8, "version");
if(transaction.hasWitnesses()) { if (transaction.hasWitnesses()) {
//Segwit marker //Segwit marker
cursor = addText(hex, cursor, 2, "segwit-marker"); cursor = addText(hex, cursor, 2, "segwit-marker");
//Segwit flag //Segwit flag
@ -153,43 +164,43 @@ public class TransactionController implements Initializable, TransactionListener
//Number of inputs //Number of inputs
VarInt numInputs = new VarInt(transaction.getInputs().size()); VarInt numInputs = new VarInt(transaction.getInputs().size());
cursor = addText(hex, cursor, numInputs.getSizeInBytes()*2, "num-inputs"); cursor = addText(hex, cursor, numInputs.getSizeInBytes() * 2, "num-inputs");
//Inputs //Inputs
for (int i = 0; i < transaction.getInputs().size(); i++) { for (int i = 0; i < transaction.getInputs().size(); i++) {
TransactionInput input = transaction.getInputs().get(i); TransactionInput input = transaction.getInputs().get(i);
cursor = addText(hex, cursor, 32*2, "input-" + getIndexedStyleClass(i, selectedInputIndex, "hash")); cursor = addText(hex, cursor, 32 * 2, "input-" + getIndexedStyleClass(i, selectedInputIndex, "hash"));
cursor = addText(hex, cursor, 4*2, "input-" + getIndexedStyleClass(i, selectedInputIndex, "index")); cursor = addText(hex, cursor, 4 * 2, "input-" + getIndexedStyleClass(i, selectedInputIndex, "index"));
VarInt scriptLen = new VarInt(input.getScriptBytes().length); VarInt scriptLen = new VarInt(input.getScriptBytes().length);
cursor = addText(hex, cursor, scriptLen.getSizeInBytes()*2, "input-" + getIndexedStyleClass(i, selectedInputIndex, "sigscript-length")); cursor = addText(hex, cursor, scriptLen.getSizeInBytes() * 2, "input-" + getIndexedStyleClass(i, selectedInputIndex, "sigscript-length"));
cursor = addText(hex, cursor, (int)scriptLen.value*2, "input-" + getIndexedStyleClass(i, selectedInputIndex, "sigscript")); cursor = addText(hex, cursor, (int) scriptLen.value * 2, "input-" + getIndexedStyleClass(i, selectedInputIndex, "sigscript"));
cursor = addText(hex, cursor, 4*2, "input-" + getIndexedStyleClass(i, selectedInputIndex, "sequence")); cursor = addText(hex, cursor, 4 * 2, "input-" + getIndexedStyleClass(i, selectedInputIndex, "sequence"));
} }
//Number of outputs //Number of outputs
VarInt numOutputs = new VarInt(transaction.getOutputs().size()); VarInt numOutputs = new VarInt(transaction.getOutputs().size());
cursor = addText(hex, cursor, numOutputs.getSizeInBytes()*2, "num-outputs"); cursor = addText(hex, cursor, numOutputs.getSizeInBytes() * 2, "num-outputs");
//Outputs //Outputs
for (int i = 0; i < transaction.getOutputs().size(); i++) { for (int i = 0; i < transaction.getOutputs().size(); i++) {
TransactionOutput output = transaction.getOutputs().get(i); TransactionOutput output = transaction.getOutputs().get(i);
cursor = addText(hex, cursor, 8*2, "output-" + getIndexedStyleClass(i, selectedOutputIndex, "value")); cursor = addText(hex, cursor, 8 * 2, "output-" + getIndexedStyleClass(i, selectedOutputIndex, "value"));
VarInt scriptLen = new VarInt(output.getScriptBytes().length); VarInt scriptLen = new VarInt(output.getScriptBytes().length);
cursor = addText(hex, cursor, scriptLen.getSizeInBytes()*2, "output-" + getIndexedStyleClass(i, selectedOutputIndex, "pubkeyscript-length")); cursor = addText(hex, cursor, scriptLen.getSizeInBytes() * 2, "output-" + getIndexedStyleClass(i, selectedOutputIndex, "pubkeyscript-length"));
cursor = addText(hex, cursor, (int)scriptLen.value*2, "output-" + getIndexedStyleClass(i, selectedOutputIndex, "pubkeyscript")); cursor = addText(hex, cursor, (int) scriptLen.value * 2, "output-" + getIndexedStyleClass(i, selectedOutputIndex, "pubkeyscript"));
} }
if(transaction.hasWitnesses()) { if (transaction.hasWitnesses()) {
for (int i = 0; i < transaction.getInputs().size(); i++) { for (int i = 0; i < transaction.getInputs().size(); i++) {
TransactionInput input = transaction.getInputs().get(i); TransactionInput input = transaction.getInputs().get(i);
if(input.hasWitness()) { if (input.hasWitness()) {
TransactionWitness witness = input.getWitness(); TransactionWitness witness = input.getWitness();
VarInt witnessCount = new VarInt(witness.getPushCount()); VarInt witnessCount = new VarInt(witness.getPushCount());
cursor = addText(hex, cursor, witnessCount.getSizeInBytes()*2, "witness-" + getIndexedStyleClass(i, selectedInputIndex, "count")); cursor = addText(hex, cursor, witnessCount.getSizeInBytes() * 2, "witness-" + getIndexedStyleClass(i, selectedInputIndex, "count"));
for(byte[] push : witness.getPushes()) { for (byte[] push : witness.getPushes()) {
VarInt witnessLen = new VarInt(push.length); VarInt witnessLen = new VarInt(push.length);
cursor = addText(hex, cursor, witnessLen.getSizeInBytes()*2, "witness-" + getIndexedStyleClass(i, selectedInputIndex, "length")); cursor = addText(hex, cursor, witnessLen.getSizeInBytes() * 2, "witness-" + getIndexedStyleClass(i, selectedInputIndex, "length"));
cursor = addText(hex, cursor, (int)witnessLen.value*2, "witness-" + getIndexedStyleClass(i, selectedInputIndex, "data")); cursor = addText(hex, cursor, (int) witnessLen.value * 2, "witness-" + getIndexedStyleClass(i, selectedInputIndex, "data"));
} }
} }
} }
@ -198,13 +209,13 @@ public class TransactionController implements Initializable, TransactionListener
//Locktime //Locktime
cursor = addText(hex, cursor, 8, "locktime"); cursor = addText(hex, cursor, 8, "locktime");
if(cursor != hex.length()) { if (cursor != hex.length()) {
throw new IllegalStateException("Cursor position does not match transaction serialisation " + cursor + ": " + hex.length()); throw new IllegalStateException("Cursor position does not match transaction serialisation " + cursor + ": " + hex.length());
} }
} }
private String getIndexedStyleClass(int iterableIndex, int selectedIndex, String styleClass) { private String getIndexedStyleClass(int iterableIndex, int selectedIndex, String styleClass) {
if(selectedIndex == -1 || selectedIndex == iterableIndex) { if (selectedIndex == -1 || selectedIndex == iterableIndex) {
return styleClass; return styleClass;
} }
@ -212,7 +223,7 @@ public class TransactionController implements Initializable, TransactionListener
} }
private int addText(String hex, int cursor, int length, String styleClass) { private int addText(String hex, int cursor, int length, String styleClass) {
txhex.append(hex.substring(cursor, cursor+=length), styleClass); txhex.append(hex.substring(cursor, cursor += length), styleClass);
return cursor; return cursor;
} }
@ -229,9 +240,21 @@ public class TransactionController implements Initializable, TransactionListener
initializeView(); initializeView();
} }
@Override @Subscribe
public void updated(Transaction transaction) { public void transactionChanged(TransactionChangedEvent event) {
refreshTxHex(); if (event.getTransaction().equals(transaction)) {
txtree.refresh(); refreshTxHex();
txtree.refresh();
}
}
@Subscribe
public void tabSelected(TransactionTabSelectedEvent event) {
}
@Subscribe
public void tabChanged(TransactionTabChangedEvent event) {
transactionMasterDetail.setShowDetailNode(event.isTxHexVisible());
} }
} }

View file

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

View file

@ -6,5 +6,6 @@ open module com.sparrowwallet.sparrow {
requires org.fxmisc.richtext; requires org.fxmisc.richtext;
requires tornadofx.controls; requires tornadofx.controls;
requires com.sparrowwallet.drongo; requires com.sparrowwallet.drongo;
requires com.google.common;
requires flowless; requires flowless;
} }

View file

@ -2,6 +2,7 @@
<?import javafx.scene.control.*?> <?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?> <?import javafx.scene.layout.*?>
<?import org.controlsfx.control.StatusBar?>
<VBox maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="750.0" prefWidth="1100.0" fx:controller="com.sparrowwallet.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="750.0" prefWidth="1100.0" fx:controller="com.sparrowwallet.sparrow.AppController" xmlns="http://javafx.com/javafx/10.0.2-internal" xmlns:fx="http://javafx.com/fxml/1">
<children> <children>
@ -18,6 +19,11 @@
<MenuItem mnemonicParsing="false" text="Delete" /> <MenuItem mnemonicParsing="false" text="Delete" />
</items> </items>
</Menu> </Menu>
<Menu mnemonicParsing="false" text="View">
<items>
<CheckMenuItem fx:id="showTxHex" mnemonicParsing="false" text="Show Transaction Hex" onAction="#showTxHex" />
</items>
</Menu>
<Menu mnemonicParsing="false" text="Help"> <Menu mnemonicParsing="false" text="Help">
<items> <items>
<MenuItem mnemonicParsing="false" text="About" /> <MenuItem mnemonicParsing="false" text="About" />
@ -27,6 +33,6 @@
</MenuBar> </MenuBar>
<TabPane prefHeight="200.0" prefWidth="200.0" VBox.vgrow="ALWAYS" fx:id="tabs" /> <TabPane prefHeight="200.0" prefWidth="200.0" VBox.vgrow="ALWAYS" fx:id="tabs" />
<Label alignment="BOTTOM_LEFT" text="Label" /> <StatusBar text=""/>
</children> </children>
</VBox> </VBox>

View file

@ -3,20 +3,27 @@
<?import javafx.scene.control.*?> <?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?> <?import javafx.scene.layout.*?>
<?import org.fxmisc.richtext.*?> <?import org.fxmisc.richtext.*?>
<?import org.fxmisc.flowless.VirtualizedScrollPane?> <?import org.fxmisc.flowless.VirtualizedScrollPane?>
<SplitPane dividerPositions="0.82" orientation="VERTICAL" prefHeight="200.0" prefWidth="160.0" stylesheets="@transaction.css" VBox.vgrow="ALWAYS" xmlns="http://javafx.com/javafx/10.0.2-internal" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.sparrowwallet.sparrow.transaction.TransactionController"> <?import org.controlsfx.control.MasterDetailPane?>
<SplitPane fx:id="tabContent" dividerPositions="0.82" orientation="VERTICAL" prefHeight="200.0" prefWidth="160.0" stylesheets="@transaction.css" VBox.vgrow="ALWAYS" xmlns="http://javafx.com/javafx/10.0.2-internal" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.sparrowwallet.sparrow.transaction.TransactionController">
<items> <items>
<SplitPane dividerPositions="0.2" prefHeight="160.0" prefWidth="200.0"> <MasterDetailPane fx:id="transactionMasterDetail" detailSide="BOTTOM">
<items> <masterNode>
<TreeView fx:id="txtree" prefHeight="154.0" prefWidth="174.0" /> <SplitPane dividerPositions="0.2" prefHeight="160.0" prefWidth="200.0">
<Pane fx:id="txpane" /> <items>
</items> <TreeView fx:id="txtree" prefHeight="154.0" prefWidth="174.0" />
</SplitPane> <Pane fx:id="txpane" />
<VirtualizedScrollPane> </items>
<content> </SplitPane>
<CodeArea fx:id="txhex" editable="false" wrapText="true" /> </masterNode>
</content> <detailNode>
</VirtualizedScrollPane> <VirtualizedScrollPane>
<content>
<CodeArea fx:id="txhex" editable="false" wrapText="true" />
</content>
</VirtualizedScrollPane>
</detailNode>
</MasterDetailPane>
</items> </items>
</SplitPane> </SplitPane>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 690 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 248 KiB