mirror of
https://github.com/sparrowwallet/sparrow.git
synced 2024-12-25 05:06:45 +00:00
wallet load, revert and save
This commit is contained in:
parent
e717589c9f
commit
72511fb184
13 changed files with 383 additions and 121 deletions
|
@ -29,6 +29,7 @@ dependencies {
|
||||||
exclude group: 'junit'
|
exclude group: 'junit'
|
||||||
}
|
}
|
||||||
implementation('com.google.guava:guava:28.2-jre')
|
implementation('com.google.guava:guava:28.2-jre')
|
||||||
|
implementation('com.google.code.gson:gson:2.8.6')
|
||||||
implementation('org.fxmisc.richtext:richtextfx:0.10.4')
|
implementation('org.fxmisc.richtext:richtextfx:0.10.4')
|
||||||
implementation('no.tornado:tornadofx-controls:1.0.4')
|
implementation('no.tornado:tornadofx-controls:1.0.4')
|
||||||
implementation('org.controlsfx:controlsfx:11.0.1' ) {
|
implementation('org.controlsfx:controlsfx:11.0.1' ) {
|
||||||
|
|
2
drongo
2
drongo
|
@ -1 +1 @@
|
||||||
Subproject commit 97cdd6217317d4f1a2238ca7d2c8161cb8534e10
|
Subproject commit 813781902b8914fbac20c5a36ef230a44639ecbc
|
|
@ -4,6 +4,8 @@ import com.google.common.base.Charsets;
|
||||||
import com.google.common.eventbus.Subscribe;
|
import com.google.common.eventbus.Subscribe;
|
||||||
import com.google.common.io.ByteSource;
|
import com.google.common.io.ByteSource;
|
||||||
import com.sparrowwallet.drongo.Utils;
|
import com.sparrowwallet.drongo.Utils;
|
||||||
|
import com.sparrowwallet.drongo.policy.PolicyType;
|
||||||
|
import com.sparrowwallet.drongo.protocol.ScriptType;
|
||||||
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;
|
||||||
|
@ -12,9 +14,12 @@ import com.sparrowwallet.sparrow.control.TextAreaDialog;
|
||||||
import com.sparrowwallet.sparrow.event.TabEvent;
|
import com.sparrowwallet.sparrow.event.TabEvent;
|
||||||
import com.sparrowwallet.sparrow.event.TransactionTabChangedEvent;
|
import com.sparrowwallet.sparrow.event.TransactionTabChangedEvent;
|
||||||
import com.sparrowwallet.sparrow.event.TransactionTabSelectedEvent;
|
import com.sparrowwallet.sparrow.event.TransactionTabSelectedEvent;
|
||||||
|
import com.sparrowwallet.sparrow.storage.Storage;
|
||||||
import com.sparrowwallet.sparrow.transaction.TransactionController;
|
import com.sparrowwallet.sparrow.transaction.TransactionController;
|
||||||
import com.sparrowwallet.sparrow.wallet.WalletController;
|
import com.sparrowwallet.sparrow.wallet.WalletController;
|
||||||
import com.sparrowwallet.sparrow.wallet.WalletForm;
|
import com.sparrowwallet.sparrow.wallet.WalletForm;
|
||||||
|
import javafx.beans.binding.Bindings;
|
||||||
|
import javafx.beans.binding.BooleanBinding;
|
||||||
import javafx.event.ActionEvent;
|
import javafx.event.ActionEvent;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.fxml.FXMLLoader;
|
import javafx.fxml.FXMLLoader;
|
||||||
|
@ -25,6 +30,10 @@ import javafx.scene.input.TransferMode;
|
||||||
import javafx.scene.layout.StackPane;
|
import javafx.scene.layout.StackPane;
|
||||||
import javafx.stage.FileChooser;
|
import javafx.stage.FileChooser;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
|
import org.controlsfx.validation.ValidationResult;
|
||||||
|
import org.controlsfx.validation.ValidationSupport;
|
||||||
|
import org.controlsfx.validation.Validator;
|
||||||
|
import org.controlsfx.validation.decoration.StyleClassValidationDecoration;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
@ -92,7 +101,7 @@ public class AppController implements Initializable {
|
||||||
showTxHex.setSelected(true);
|
showTxHex.setSelected(true);
|
||||||
showTxHexProperty = true;
|
showTxHexProperty = true;
|
||||||
|
|
||||||
addWalletTab(null, new Wallet());
|
//addWalletTab("newWallet", new Wallet(PolicyType.SINGLE, ScriptType.P2WPKH));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void openFromFile(ActionEvent event) {
|
public void openFromFile(ActionEvent event) {
|
||||||
|
@ -179,7 +188,7 @@ public class AppController implements Initializable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showErrorDialog(String title, String content) {
|
public static void showErrorDialog(String title, String content) {
|
||||||
Alert alert = new Alert(Alert.AlertType.ERROR);
|
Alert alert = new Alert(Alert.AlertType.ERROR);
|
||||||
alert.setTitle(title);
|
alert.setTitle(title);
|
||||||
alert.setHeaderText(title);
|
alert.setHeaderText(title);
|
||||||
|
@ -198,18 +207,53 @@ public class AppController implements Initializable {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void newWallet(ActionEvent event) {
|
public void newWallet(ActionEvent event) {
|
||||||
Tab tab = addWalletTab(null, new Wallet());
|
TextInputDialog dlg = new TextInputDialog("");
|
||||||
|
dlg.setTitle("New Wallet");
|
||||||
|
dlg.getDialogPane().setContentText("Wallet name:");
|
||||||
|
dlg.getDialogPane().getStylesheets().add(getClass().getResource("general.css").toExternalForm());
|
||||||
|
|
||||||
|
ValidationSupport validationSupport = new ValidationSupport();
|
||||||
|
validationSupport.registerValidator(dlg.getEditor(), Validator.combine(
|
||||||
|
Validator.createEmptyValidator("Wallet name is required"),
|
||||||
|
(Control c, String newValue) -> ValidationResult.fromErrorIf( c, "Wallet name is not unique", Storage.getStorage().getWalletFile(newValue).exists())
|
||||||
|
));
|
||||||
|
validationSupport.setValidationDecorator(new StyleClassValidationDecoration());
|
||||||
|
|
||||||
|
Button okButton = (Button)dlg.getDialogPane().lookupButton(ButtonType.OK);
|
||||||
|
BooleanBinding isInvalid = Bindings.createBooleanBinding(() ->
|
||||||
|
dlg.getEditor().getText().length() == 0 || Storage.getStorage().getWalletFile(dlg.getEditor().getText()).exists(), dlg.getEditor().textProperty());
|
||||||
|
okButton.disableProperty().bind(isInvalid);
|
||||||
|
|
||||||
|
Optional<String> walletName = dlg.showAndWait();
|
||||||
|
if(walletName.isPresent()) {
|
||||||
|
File walletFile = Storage.getStorage().getWalletFile(walletName.get());
|
||||||
|
Wallet wallet = new Wallet(PolicyType.SINGLE, ScriptType.P2WPKH);
|
||||||
|
Tab tab = addWalletTab(walletFile, wallet);
|
||||||
tabs.getSelectionModel().select(tab);
|
tabs.getSelectionModel().select(tab);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Tab addWalletTab(String name, Wallet wallet) {
|
|
||||||
try {
|
|
||||||
String tabName = name;
|
|
||||||
if(tabName == null || tabName.isEmpty()) {
|
|
||||||
tabName = "New wallet";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Tab tab = new Tab(tabName);
|
public void openWallet(ActionEvent event) {
|
||||||
|
Stage window = new Stage();
|
||||||
|
FileChooser fileChooser = new FileChooser();
|
||||||
|
fileChooser.setTitle("Open Wallet");
|
||||||
|
fileChooser.setInitialDirectory(Storage.getStorage().getWalletsDir());
|
||||||
|
|
||||||
|
File file = fileChooser.showOpenDialog(window);
|
||||||
|
if(file != null) {
|
||||||
|
try {
|
||||||
|
Wallet wallet = Storage.getStorage().loadWallet(file);
|
||||||
|
Tab tab = addWalletTab(file, wallet);
|
||||||
|
tabs.getSelectionModel().select(tab);
|
||||||
|
} catch (IOException e) {
|
||||||
|
showErrorDialog("Error opening wallet", e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Tab addWalletTab(File walletFile, Wallet wallet) {
|
||||||
|
try {
|
||||||
|
Tab tab = new Tab(walletFile.getName());
|
||||||
TabData tabData = new TabData(TabData.TabType.WALLET);
|
TabData tabData = new TabData(TabData.TabType.WALLET);
|
||||||
tab.setUserData(tabData);
|
tab.setUserData(tabData);
|
||||||
tab.setContextMenu(getTabContextMenu(tab));
|
tab.setContextMenu(getTabContextMenu(tab));
|
||||||
|
@ -217,7 +261,7 @@ public class AppController implements Initializable {
|
||||||
FXMLLoader walletLoader = new FXMLLoader(getClass().getResource("wallet/wallet.fxml"));
|
FXMLLoader walletLoader = new FXMLLoader(getClass().getResource("wallet/wallet.fxml"));
|
||||||
tab.setContent(walletLoader.load());
|
tab.setContent(walletLoader.load());
|
||||||
WalletController controller = walletLoader.getController();
|
WalletController controller = walletLoader.getController();
|
||||||
WalletForm walletForm = new WalletForm(wallet);
|
WalletForm walletForm = new WalletForm(walletFile, wallet);
|
||||||
controller.setWalletForm(walletForm);
|
controller.setWalletForm(walletForm);
|
||||||
|
|
||||||
tabs.getTabs().add(tab);
|
tabs.getTabs().add(tab);
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
package com.sparrowwallet.sparrow.event;
|
||||||
|
|
||||||
|
import com.sparrowwallet.drongo.wallet.Wallet;
|
||||||
|
|
||||||
|
public class SettingsChangedEvent {
|
||||||
|
private Wallet wallet;
|
||||||
|
|
||||||
|
public SettingsChangedEvent(Wallet wallet) {
|
||||||
|
this.wallet = wallet;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Wallet getWallet() {
|
||||||
|
return wallet;
|
||||||
|
}
|
||||||
|
}
|
81
src/main/java/com/sparrowwallet/sparrow/storage/Storage.java
Normal file
81
src/main/java/com/sparrowwallet/sparrow/storage/Storage.java
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
package com.sparrowwallet.sparrow.storage;
|
||||||
|
|
||||||
|
import com.google.gson.*;
|
||||||
|
import com.sparrowwallet.drongo.ExtendedPublicKey;
|
||||||
|
import com.sparrowwallet.drongo.wallet.Wallet;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
|
||||||
|
public class Storage {
|
||||||
|
public static final String SPARROW_DIR = ".sparrow";
|
||||||
|
public static final String WALLETS_DIR = "wallets";
|
||||||
|
|
||||||
|
private static Storage SINGLETON;
|
||||||
|
|
||||||
|
private final Gson gson;
|
||||||
|
|
||||||
|
private Storage() {
|
||||||
|
GsonBuilder gsonBuilder = new GsonBuilder();
|
||||||
|
gsonBuilder.registerTypeAdapter(ExtendedPublicKey.class, new ExtendedPublicKeySerializer());
|
||||||
|
gsonBuilder.registerTypeAdapter(ExtendedPublicKey.class, new ExtendedPublicKeyDeserializer());
|
||||||
|
gson = gsonBuilder.setPrettyPrinting().create();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Storage getStorage() {
|
||||||
|
if(SINGLETON == null) {
|
||||||
|
SINGLETON = new Storage();
|
||||||
|
}
|
||||||
|
|
||||||
|
return SINGLETON;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Wallet loadWallet(File file) throws IOException {
|
||||||
|
Reader reader = new FileReader(file);
|
||||||
|
Wallet wallet = gson.fromJson(reader, Wallet.class);
|
||||||
|
reader.close();
|
||||||
|
|
||||||
|
return wallet;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void storeWallet(File file, Wallet wallet) throws IOException {
|
||||||
|
File parent = file.getParentFile();
|
||||||
|
if(!parent.exists() && !parent.mkdirs()) {
|
||||||
|
throw new IOException("Could not create folder " + parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
Writer writer = new FileWriter(file);
|
||||||
|
gson.toJson(wallet, writer);
|
||||||
|
writer.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
public File getWalletFile(String walletName) {
|
||||||
|
return new File(getWalletsDir(), walletName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public File getWalletsDir() {
|
||||||
|
return new File(getSparrowDir(), WALLETS_DIR);
|
||||||
|
}
|
||||||
|
|
||||||
|
private File getSparrowDir() {
|
||||||
|
return new File(getHomeDir(), SPARROW_DIR);
|
||||||
|
}
|
||||||
|
|
||||||
|
private File getHomeDir() {
|
||||||
|
return new File(System.getProperty("user.home"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class ExtendedPublicKeySerializer implements JsonSerializer<ExtendedPublicKey> {
|
||||||
|
@Override
|
||||||
|
public JsonElement serialize(ExtendedPublicKey src, Type typeOfSrc, JsonSerializationContext context) {
|
||||||
|
return new JsonPrimitive(src.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class ExtendedPublicKeyDeserializer implements JsonDeserializer<ExtendedPublicKey> {
|
||||||
|
@Override
|
||||||
|
public ExtendedPublicKey deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
|
||||||
|
return ExtendedPublicKey.fromDescriptor(json.getAsJsonPrimitive().getAsString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,9 +4,9 @@ import com.sparrowwallet.drongo.ExtendedPublicKey;
|
||||||
import com.sparrowwallet.drongo.KeyDerivation;
|
import com.sparrowwallet.drongo.KeyDerivation;
|
||||||
import com.sparrowwallet.drongo.Utils;
|
import com.sparrowwallet.drongo.Utils;
|
||||||
import com.sparrowwallet.drongo.wallet.Keystore;
|
import com.sparrowwallet.drongo.wallet.Keystore;
|
||||||
|
import com.sparrowwallet.sparrow.EventManager;
|
||||||
|
import com.sparrowwallet.sparrow.event.SettingsChangedEvent;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.beans.value.ChangeListener;
|
|
||||||
import javafx.beans.value.ObservableValue;
|
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.fxml.Initializable;
|
import javafx.fxml.Initializable;
|
||||||
import javafx.scene.control.Control;
|
import javafx.scene.control.Control;
|
||||||
|
@ -36,7 +36,7 @@ public class KeystoreController extends WalletFormController implements Initiali
|
||||||
@FXML
|
@FXML
|
||||||
private TextField fingerprint;
|
private TextField fingerprint;
|
||||||
|
|
||||||
private ValidationSupport validationSupport;
|
private ValidationSupport validationSupport = new ValidationSupport();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void initialize(URL location, ResourceBundle resources) {
|
public void initialize(URL location, ResourceBundle resources) {
|
||||||
|
@ -61,21 +61,41 @@ public class KeystoreController extends WalletFormController implements Initiali
|
||||||
if(keystore.getKeyDerivation() != null) {
|
if(keystore.getKeyDerivation() != null) {
|
||||||
derivation.setText(keystore.getKeyDerivation().getDerivationPath());
|
derivation.setText(keystore.getKeyDerivation().getDerivationPath());
|
||||||
fingerprint.setText(keystore.getKeyDerivation().getMasterFingerprint());
|
fingerprint.setText(keystore.getKeyDerivation().getMasterFingerprint());
|
||||||
|
} else {
|
||||||
|
keystore.setKeyDerivation(new KeyDerivation("",""));
|
||||||
}
|
}
|
||||||
|
|
||||||
label.textProperty().addListener((observable, oldValue, newValue) -> keystore.setLabel(newValue));
|
label.textProperty().addListener((observable, oldValue, newValue) -> {
|
||||||
fingerprint.textProperty().addListener((observable, oldValue, newValue) -> keystore.setKeyDerivation(new KeyDerivation(newValue, keystore.getKeyDerivation().getDerivationPath())));
|
keystore.setLabel(newValue);
|
||||||
derivation.textProperty().addListener((observable, oldValue, newValue) -> keystore.setKeyDerivation(new KeyDerivation(keystore.getKeyDerivation().getMasterFingerprint(), newValue)));
|
EventManager.get().post(new SettingsChangedEvent(walletForm.getWallet()));
|
||||||
xpub.textProperty().addListener((observable, oldValue, newValue) -> keystore.setExtendedPublicKey(ExtendedPublicKey.fromDescriptor(newValue)));
|
});
|
||||||
|
fingerprint.textProperty().addListener((observable, oldValue, newValue) -> {
|
||||||
|
keystore.setKeyDerivation(new KeyDerivation(newValue, keystore.getKeyDerivation().getDerivationPath()));
|
||||||
|
EventManager.get().post(new SettingsChangedEvent(walletForm.getWallet()));
|
||||||
|
});
|
||||||
|
derivation.textProperty().addListener((observable, oldValue, newValue) -> {
|
||||||
|
if(KeyDerivation.isValid(newValue)) {
|
||||||
|
keystore.setKeyDerivation(new KeyDerivation(keystore.getKeyDerivation().getMasterFingerprint(), newValue));
|
||||||
|
EventManager.get().post(new SettingsChangedEvent(walletForm.getWallet()));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
xpub.textProperty().addListener((observable, oldValue, newValue) -> {
|
||||||
|
if(ExtendedPublicKey.isValid(newValue)) {
|
||||||
|
keystore.setExtendedPublicKey(ExtendedPublicKey.fromDescriptor(newValue));
|
||||||
|
EventManager.get().post(new SettingsChangedEvent(walletForm.getWallet()));
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public TextField getLabel() {
|
public TextField getLabel() {
|
||||||
return label;
|
return label;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setupValidation() {
|
public ValidationSupport getValidationSupport() {
|
||||||
validationSupport = new ValidationSupport();
|
return validationSupport;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupValidation() {
|
||||||
validationSupport.registerValidator(label, Validator.combine(
|
validationSupport.registerValidator(label, Validator.combine(
|
||||||
Validator.createEmptyValidator("Label is required"),
|
Validator.createEmptyValidator("Label is required"),
|
||||||
(Control c, String newValue) -> ValidationResult.fromErrorIf( c, "Label is not unique", walletForm.getWallet().getKeystores().stream().filter(k -> k != keystore).map(Keystore::getLabel).collect(Collectors.toList()).contains(newValue)),
|
(Control c, String newValue) -> ValidationResult.fromErrorIf( c, "Label is not unique", walletForm.getWallet().getKeystores().stream().filter(k -> k != keystore).map(Keystore::getLabel).collect(Collectors.toList()).contains(newValue)),
|
||||||
|
|
|
@ -9,7 +9,9 @@ import com.sparrowwallet.drongo.wallet.Wallet;
|
||||||
import com.sparrowwallet.sparrow.AppController;
|
import com.sparrowwallet.sparrow.AppController;
|
||||||
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.event.SettingsChangedEvent;
|
||||||
import com.sparrowwallet.sparrow.event.WalletChangedEvent;
|
import com.sparrowwallet.sparrow.event.WalletChangedEvent;
|
||||||
|
import javafx.application.Platform;
|
||||||
import javafx.beans.property.SimpleIntegerProperty;
|
import javafx.beans.property.SimpleIntegerProperty;
|
||||||
import javafx.collections.FXCollections;
|
import javafx.collections.FXCollections;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
|
@ -54,6 +56,12 @@ public class SettingsController extends WalletFormController implements Initiali
|
||||||
|
|
||||||
private TabPane keystoreTabs;
|
private TabPane keystoreTabs;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Button apply;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Button revert;
|
||||||
|
|
||||||
private final SimpleIntegerProperty totalKeystores = new SimpleIntegerProperty(0);
|
private final SimpleIntegerProperty totalKeystores = new SimpleIntegerProperty(0);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -63,15 +71,17 @@ public class SettingsController extends WalletFormController implements Initiali
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void initializeView() {
|
public void initializeView() {
|
||||||
Wallet wallet = walletForm.getWallet();
|
|
||||||
|
|
||||||
keystoreTabs = new TabPane();
|
keystoreTabs = new TabPane();
|
||||||
keystoreTabsPane.getChildren().add(Borders.wrap(keystoreTabs).etchedBorder().outerPadding(10, 5, 0 ,0).innerPadding(0).raised().buildAll());
|
keystoreTabsPane.getChildren().add(Borders.wrap(keystoreTabs).etchedBorder().outerPadding(10, 5, 0 ,0).innerPadding(0).raised().buildAll());
|
||||||
|
|
||||||
policyType.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, policyType) -> {
|
policyType.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, policyType) -> {
|
||||||
wallet.setPolicyType(policyType);
|
walletForm.getWallet().setPolicyType(policyType);
|
||||||
|
|
||||||
scriptType.setItems(FXCollections.observableArrayList(ScriptType.getScriptTypesForPolicyType(policyType)));
|
scriptType.setItems(FXCollections.observableArrayList(ScriptType.getScriptTypesForPolicyType(policyType)));
|
||||||
|
if(!ScriptType.getScriptTypesForPolicyType(policyType).contains(walletForm.getWallet().getScriptType())) {
|
||||||
scriptType.getSelectionModel().select(policyType.getDefaultScriptType());
|
scriptType.getSelectionModel().select(policyType.getDefaultScriptType());
|
||||||
|
}
|
||||||
|
|
||||||
multisigFieldset.setVisible(policyType.equals(PolicyType.MULTI));
|
multisigFieldset.setVisible(policyType.equals(PolicyType.MULTI));
|
||||||
if(policyType.equals(PolicyType.MULTI)) {
|
if(policyType.equals(PolicyType.MULTI)) {
|
||||||
totalKeystores.bind(multisigControl.highValueProperty());
|
totalKeystores.bind(multisigControl.highValueProperty());
|
||||||
|
@ -82,51 +92,74 @@ public class SettingsController extends WalletFormController implements Initiali
|
||||||
});
|
});
|
||||||
|
|
||||||
scriptType.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, scriptType) -> {
|
scriptType.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, scriptType) -> {
|
||||||
int threshold = wallet.getPolicyType().equals(PolicyType.MULTI) ? (int)multisigControl.lowValueProperty().get() : 1;
|
if(scriptType != null) {
|
||||||
wallet.setDefaultPolicy(Policy.getPolicy(wallet.getPolicyType(), scriptType, wallet.getKeystores(), threshold));
|
walletForm.getWallet().setScriptType(scriptType);
|
||||||
EventManager.get().post(new WalletChangedEvent(wallet));
|
}
|
||||||
|
|
||||||
|
EventManager.get().post(new SettingsChangedEvent(walletForm.getWallet()));
|
||||||
});
|
});
|
||||||
|
|
||||||
multisigLowLabel.textProperty().bind(multisigControl.lowValueProperty().asString("%.0f") );
|
multisigLowLabel.textProperty().bind(multisigControl.lowValueProperty().asString("%.0f") );
|
||||||
multisigHighLabel.textProperty().bind(multisigControl.highValueProperty().asString("%.0f"));
|
multisigHighLabel.textProperty().bind(multisigControl.highValueProperty().asString("%.0f"));
|
||||||
|
|
||||||
multisigControl.lowValueProperty().addListener((observable, oldValue, threshold) -> {
|
multisigControl.lowValueProperty().addListener((observable, oldValue, threshold) -> {
|
||||||
wallet.setDefaultPolicy(Policy.getPolicy(wallet.getPolicyType(), wallet.getScriptType(), wallet.getKeystores(), threshold.intValue()));
|
EventManager.get().post(new SettingsChangedEvent(walletForm.getWallet()));
|
||||||
EventManager.get().post(new WalletChangedEvent(wallet));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
multisigFieldset.managedProperty().bind(multisigFieldset.visibleProperty());
|
multisigFieldset.managedProperty().bind(multisigFieldset.visibleProperty());
|
||||||
|
|
||||||
totalKeystores.addListener((observable, oldValue, numCosigners) -> {
|
totalKeystores.addListener((observable, oldValue, numCosigners) -> {
|
||||||
int keystoreCount = wallet.getKeystores().size();
|
int keystoreCount = walletForm.getWallet().getKeystores().size();
|
||||||
int keystoreNameCount = keystoreCount;
|
int keystoreNameCount = keystoreCount + 1;
|
||||||
while(keystoreCount < numCosigners.intValue()) {
|
while(keystoreCount < numCosigners.intValue()) {
|
||||||
keystoreCount++;
|
keystoreCount++;
|
||||||
String name = "Keystore " + keystoreNameCount;
|
String name = "Keystore " + keystoreNameCount;
|
||||||
while(wallet.getKeystores().stream().map(Keystore::getLabel).collect(Collectors.toList()).contains(name)) {
|
while(walletForm.getWallet().getKeystores().stream().map(Keystore::getLabel).collect(Collectors.toList()).contains(name)) {
|
||||||
name = "Keystore " + (++keystoreNameCount);
|
name = "Keystore " + (++keystoreNameCount);
|
||||||
}
|
}
|
||||||
wallet.getKeystores().add(new Keystore(name));
|
walletForm.getWallet().getKeystores().add(new Keystore(name));
|
||||||
}
|
}
|
||||||
wallet.setKeystores(wallet.getKeystores().subList(0, numCosigners.intValue()));
|
walletForm.getWallet().setKeystores(walletForm.getWallet().getKeystores().subList(0, numCosigners.intValue()));
|
||||||
|
|
||||||
for(int i = 0; i < wallet.getKeystores().size(); i++) {
|
for(int i = 0; i < walletForm.getWallet().getKeystores().size(); i++) {
|
||||||
Keystore keystore = wallet.getKeystores().get(i);
|
Keystore keystore = walletForm.getWallet().getKeystores().get(i);
|
||||||
if(keystoreTabs.getTabs().size() == i) {
|
if(keystoreTabs.getTabs().size() == i) {
|
||||||
Tab tab = getKeystoreTab(wallet, keystore);
|
Tab tab = getKeystoreTab(walletForm.getWallet(), keystore);
|
||||||
keystoreTabs.getTabs().add(tab);
|
keystoreTabs.getTabs().add(tab);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
while(keystoreTabs.getTabs().size() > wallet.getKeystores().size()) {
|
while(keystoreTabs.getTabs().size() > walletForm.getWallet().getKeystores().size()) {
|
||||||
keystoreTabs.getTabs().remove(keystoreTabs.getTabs().size() - 1);
|
keystoreTabs.getTabs().remove(keystoreTabs.getTabs().size() - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(wallet.getPolicyType().equals(PolicyType.MULTI)) {
|
if(walletForm.getWallet().getPolicyType().equals(PolicyType.MULTI)) {
|
||||||
wallet.setDefaultPolicy(Policy.getPolicy(wallet.getPolicyType(), wallet.getScriptType(), wallet.getKeystores(), wallet.getDefaultPolicy().getNumSignaturesRequired()));
|
EventManager.get().post(new SettingsChangedEvent(walletForm.getWallet()));
|
||||||
EventManager.get().post(new WalletChangedEvent(wallet));
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
revert.setOnAction(event -> {
|
||||||
|
keystoreTabs.getTabs().removeAll(keystoreTabs.getTabs());
|
||||||
|
totalKeystores.unbind();
|
||||||
|
totalKeystores.setValue(0);
|
||||||
|
walletForm.revert();
|
||||||
|
setFieldsFromWallet(walletForm.getWallet());
|
||||||
|
});
|
||||||
|
|
||||||
|
apply.setOnAction(event -> {
|
||||||
|
try {
|
||||||
|
walletForm.save();
|
||||||
|
revert.setDisable(true);
|
||||||
|
apply.setDisable(true);
|
||||||
|
EventManager.get().post(new WalletChangedEvent(walletForm.getWallet()));
|
||||||
|
} catch (IOException e) {
|
||||||
|
AppController.showErrorDialog("Error saving file", e.getMessage());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
setFieldsFromWallet(walletForm.getWallet());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setFieldsFromWallet(Wallet wallet) {
|
||||||
if(wallet.getPolicyType() == null) {
|
if(wallet.getPolicyType() == null) {
|
||||||
wallet.setPolicyType(PolicyType.SINGLE);
|
wallet.setPolicyType(PolicyType.SINGLE);
|
||||||
wallet.setScriptType(ScriptType.P2WPKH);
|
wallet.setScriptType(ScriptType.P2WPKH);
|
||||||
|
@ -135,20 +168,23 @@ public class SettingsController extends WalletFormController implements Initiali
|
||||||
}
|
}
|
||||||
|
|
||||||
if(wallet.getPolicyType().equals(PolicyType.SINGLE)) {
|
if(wallet.getPolicyType().equals(PolicyType.SINGLE)) {
|
||||||
totalKeystores.setValue(wallet.getKeystores().size());
|
totalKeystores.setValue(1);
|
||||||
} else if(wallet.getPolicyType().equals(PolicyType.MULTI)) {
|
} else if(wallet.getPolicyType().equals(PolicyType.MULTI)) {
|
||||||
|
multisigControl.lowValueProperty().set(wallet.getDefaultPolicy().getNumSignaturesRequired());
|
||||||
multisigControl.highValueProperty().set(wallet.getKeystores().size());
|
multisigControl.highValueProperty().set(wallet.getKeystores().size());
|
||||||
|
totalKeystores.bind(multisigControl.highValueProperty());
|
||||||
}
|
}
|
||||||
|
|
||||||
if(wallet.getPolicyType() != null) {
|
if(wallet.getPolicyType() != null) {
|
||||||
policyType.getSelectionModel().select(walletForm.getWallet().getPolicyType());
|
policyType.getSelectionModel().select(walletForm.getWallet().getPolicyType());
|
||||||
} else {
|
|
||||||
policyType.getSelectionModel().select(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(wallet.getScriptType() != null) {
|
if(wallet.getScriptType() != null) {
|
||||||
scriptType.getSelectionModel().select(walletForm.getWallet().getScriptType());
|
scriptType.getSelectionModel().select(walletForm.getWallet().getScriptType());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
revert.setDisable(true);
|
||||||
|
apply.setDisable(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Tab getKeystoreTab(Wallet wallet, Keystore keystore) {
|
private Tab getKeystoreTab(Wallet wallet, Keystore keystore) {
|
||||||
|
@ -162,14 +198,47 @@ public class SettingsController extends WalletFormController implements Initiali
|
||||||
controller.setKeystore(getWalletForm(), keystore);
|
controller.setKeystore(getWalletForm(), keystore);
|
||||||
tab.textProperty().bind(controller.getLabel().textProperty());
|
tab.textProperty().bind(controller.getLabel().textProperty());
|
||||||
|
|
||||||
|
controller.getValidationSupport().validationResultProperty().addListener((o, oldValue, result) -> {
|
||||||
|
if(result.getErrors().isEmpty()) {
|
||||||
|
tab.getStyleClass().remove("tab-error");
|
||||||
|
tab.setTooltip(null);
|
||||||
|
apply.setDisable(false);
|
||||||
|
} else {
|
||||||
|
if(!tab.getStyleClass().contains("tab-error")) {
|
||||||
|
tab.getStyleClass().add("tab-error");
|
||||||
|
}
|
||||||
|
tab.setTooltip(new Tooltip(result.getErrors().iterator().next().getText()));
|
||||||
|
apply.setDisable(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return tab;
|
return tab;
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean tabsValidate() {
|
||||||
|
for(Tab tab : keystoreTabs.getTabs()) {
|
||||||
|
if(tab.getStyleClass().contains("tab-error")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
@Subscribe
|
@Subscribe
|
||||||
public void updateMiniscript(WalletChangedEvent event) {
|
public void update(SettingsChangedEvent event) {
|
||||||
|
Wallet wallet = event.getWallet();
|
||||||
|
if(wallet.getPolicyType() == PolicyType.SINGLE) {
|
||||||
|
wallet.setDefaultPolicy(Policy.getPolicy(wallet.getPolicyType(), wallet.getScriptType(), wallet.getKeystores(), 1));
|
||||||
|
} else if(wallet.getPolicyType() == PolicyType.MULTI) {
|
||||||
|
wallet.setDefaultPolicy(Policy.getPolicy(wallet.getPolicyType(), wallet.getScriptType(), wallet.getKeystores(), (int)multisigControl.getLowValue()));
|
||||||
|
}
|
||||||
|
|
||||||
spendingMiniscript.setText(event.getWallet().getDefaultPolicy().getMiniscript().getScript());
|
spendingMiniscript.setText(event.getWallet().getDefaultPolicy().getMiniscript().getScript());
|
||||||
|
revert.setDisable(false);
|
||||||
|
Platform.runLater(() -> apply.setDisable(!tabsValidate()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,19 +1,32 @@
|
||||||
package com.sparrowwallet.sparrow.wallet;
|
package com.sparrowwallet.sparrow.wallet;
|
||||||
|
|
||||||
import com.sparrowwallet.drongo.wallet.Wallet;
|
import com.sparrowwallet.drongo.wallet.Wallet;
|
||||||
|
import com.sparrowwallet.sparrow.storage.Storage;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
public class WalletForm {
|
public class WalletForm {
|
||||||
|
private File walletFile;
|
||||||
|
private Wallet oldWallet;
|
||||||
private Wallet wallet;
|
private Wallet wallet;
|
||||||
|
|
||||||
public WalletForm(Wallet wallet) {
|
public WalletForm(File walletFile, Wallet currentWallet) {
|
||||||
this.wallet = wallet;
|
this.walletFile = walletFile;
|
||||||
|
this.oldWallet = currentWallet;
|
||||||
|
this.wallet = currentWallet.copy();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Wallet getWallet() {
|
public Wallet getWallet() {
|
||||||
return wallet;
|
return wallet;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setWallet(Wallet wallet) {
|
public void revert() {
|
||||||
this.wallet = wallet;
|
this.wallet = oldWallet.copy();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void save() throws IOException {
|
||||||
|
Storage.getStorage().storeWallet(walletFile, wallet);
|
||||||
|
oldWallet = wallet.copy();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,5 +8,6 @@ open module com.sparrowwallet.sparrow {
|
||||||
requires com.sparrowwallet.drongo;
|
requires com.sparrowwallet.drongo;
|
||||||
requires com.google.common;
|
requires com.google.common;
|
||||||
requires flowless;
|
requires flowless;
|
||||||
|
requires com.google.gson;
|
||||||
requires javafx.swing;
|
requires javafx.swing;
|
||||||
}
|
}
|
|
@ -13,7 +13,8 @@
|
||||||
<Menu mnemonicParsing="false" text="File">
|
<Menu mnemonicParsing="false" text="File">
|
||||||
<items>
|
<items>
|
||||||
<MenuItem mnemonicParsing="false" text="New Wallet" onAction="#newWallet"/>
|
<MenuItem mnemonicParsing="false" text="New Wallet" onAction="#newWallet"/>
|
||||||
<Menu mnemonicParsing="false" text="Open">
|
<MenuItem mnemonicParsing="false" text="Open Wallet" onAction="#openWallet"/>
|
||||||
|
<Menu mnemonicParsing="false" text="Open Transaction">
|
||||||
<items>
|
<items>
|
||||||
<MenuItem text="File..." onAction="#openFromFile"/>
|
<MenuItem text="File..." onAction="#openFromFile"/>
|
||||||
<MenuItem text="From Text..." onAction="#openFromText"/>
|
<MenuItem text="From Text..." onAction="#openFromText"/>
|
||||||
|
|
|
@ -34,8 +34,12 @@
|
||||||
-fx-translate-x: -20px;
|
-fx-translate-x: -20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tab-error > .tab-container {
|
||||||
|
-fx-effect: dropshadow(three-pass-box, rgba(202, 18, 67, .6), 7, 0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
.error {
|
.error {
|
||||||
-fx-effect: dropshadow(three-pass-box, darkred, 7, 0, 0, 0);
|
-fx-effect: dropshadow(three-pass-box, rgb(202, 18, 67), 7, 0, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
.warning {
|
.warning {
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
</padding>
|
</padding>
|
||||||
<Form GridPane.columnIndex="0" GridPane.rowIndex="0">
|
<Form GridPane.columnIndex="0" GridPane.rowIndex="0">
|
||||||
<Fieldset inputGrow="SOMETIMES" text="">
|
<Fieldset inputGrow="SOMETIMES" text="">
|
||||||
<Field text="Name:">
|
<Field text="Label:">
|
||||||
<TextField fx:id="label" maxWidth="160"/>
|
<TextField fx:id="label" maxWidth="160"/>
|
||||||
</Field>
|
</Field>
|
||||||
<Field text="Master fingerprint:">
|
<Field text="Master fingerprint:">
|
||||||
|
|
|
@ -10,7 +10,9 @@
|
||||||
<?import com.sparrowwallet.drongo.policy.PolicyType?>
|
<?import com.sparrowwallet.drongo.policy.PolicyType?>
|
||||||
<?import com.sparrowwallet.drongo.protocol.ScriptType?>
|
<?import com.sparrowwallet.drongo.protocol.ScriptType?>
|
||||||
|
|
||||||
<GridPane hgap="10.0" vgap="10.0" stylesheets="@settings.css, @../general.css" xmlns="http://javafx.com/javafx/10.0.2-internal" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.sparrowwallet.sparrow.wallet.SettingsController">
|
<BorderPane stylesheets="@settings.css, @../general.css" xmlns="http://javafx.com/javafx/10.0.2-internal" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.sparrowwallet.sparrow.wallet.SettingsController">
|
||||||
|
<center>
|
||||||
|
<GridPane 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>
|
||||||
|
@ -80,4 +82,15 @@
|
||||||
<StackPane fx:id="keystoreTabsPane" />
|
<StackPane fx:id="keystoreTabsPane" />
|
||||||
</Fieldset>
|
</Fieldset>
|
||||||
</Form>
|
</Form>
|
||||||
</GridPane>
|
</GridPane>
|
||||||
|
</center>
|
||||||
|
<bottom>
|
||||||
|
<AnchorPane>
|
||||||
|
<padding>
|
||||||
|
<Insets left="25.0" right="25.0" bottom="25.0" />
|
||||||
|
</padding>
|
||||||
|
<Button fx:id="apply" text="Apply" defaultButton="true" AnchorPane.rightAnchor="10" />
|
||||||
|
<Button fx:id="revert" text="Revert" cancelButton="true" AnchorPane.rightAnchor="80" />
|
||||||
|
</AnchorPane>
|
||||||
|
</bottom>
|
||||||
|
</BorderPane>
|
||||||
|
|
Loading…
Reference in a new issue