mirror of
https://github.com/sparrowwallet/sparrow.git
synced 2024-11-05 05:46:44 +00:00
add utility buttons to import and export xpubs with qr codes, show slip132 versions
This commit is contained in:
parent
3a885b3a28
commit
eb8d66bf25
7 changed files with 165 additions and 13 deletions
2
drongo
2
drongo
|
@ -1 +1 @@
|
||||||
Subproject commit fee042679938316f034ceee137cfa056be566ffd
|
Subproject commit 3642ddc9581c4485b13d4d0fffee6290703a5768
|
|
@ -76,6 +76,28 @@ public class QRDisplayDialog extends Dialog<UR> {
|
||||||
setResultConverter(dialogButton -> dialogButton != cancelButtonType ? ur : null);
|
setResultConverter(dialogButton -> dialogButton != cancelButtonType ? ur : null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public QRDisplayDialog(String data) {
|
||||||
|
this.ur = null;
|
||||||
|
this.encoder = null;
|
||||||
|
|
||||||
|
final DialogPane dialogPane = getDialogPane();
|
||||||
|
AppController.setStageIcon(dialogPane.getScene().getWindow());
|
||||||
|
|
||||||
|
StackPane stackPane = new StackPane();
|
||||||
|
qrImageView = new ImageView();
|
||||||
|
stackPane.getChildren().add(qrImageView);
|
||||||
|
|
||||||
|
dialogPane.setContent(Borders.wrap(stackPane).lineBorder().buildAll());
|
||||||
|
qrImageView.setImage(getQrCode(data));
|
||||||
|
|
||||||
|
final ButtonType cancelButtonType = new javafx.scene.control.ButtonType("Cancel", ButtonBar.ButtonData.CANCEL_CLOSE);
|
||||||
|
dialogPane.getButtonTypes().addAll(cancelButtonType);
|
||||||
|
dialogPane.setPrefWidth(500);
|
||||||
|
dialogPane.setPrefHeight(550);
|
||||||
|
|
||||||
|
setResultConverter(dialogButton -> dialogButton != cancelButtonType ? ur : null);
|
||||||
|
}
|
||||||
|
|
||||||
private void nextPart() {
|
private void nextPart() {
|
||||||
String fragment = encoder.nextPart();
|
String fragment = encoder.nextPart();
|
||||||
currentPart = fragment.toUpperCase();
|
currentPart = fragment.toUpperCase();
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package com.sparrowwallet.sparrow.control;
|
package com.sparrowwallet.sparrow.control;
|
||||||
|
|
||||||
import com.github.sarxos.webcam.WebcamResolution;
|
import com.github.sarxos.webcam.WebcamResolution;
|
||||||
|
import com.sparrowwallet.drongo.ExtendedKey;
|
||||||
import com.sparrowwallet.drongo.Utils;
|
import com.sparrowwallet.drongo.Utils;
|
||||||
import com.sparrowwallet.drongo.address.Address;
|
import com.sparrowwallet.drongo.address.Address;
|
||||||
import com.sparrowwallet.drongo.protocol.Base43;
|
import com.sparrowwallet.drongo.protocol.Base43;
|
||||||
|
@ -106,6 +107,15 @@ public class QRScanDialog extends Dialog<QRScanDialog.Result> {
|
||||||
Transaction transaction;
|
Transaction transaction;
|
||||||
BitcoinURI bitcoinURI;
|
BitcoinURI bitcoinURI;
|
||||||
Address address;
|
Address address;
|
||||||
|
ExtendedKey extendedKey;
|
||||||
|
try {
|
||||||
|
extendedKey = ExtendedKey.fromDescriptor(qrtext);
|
||||||
|
result = new Result(extendedKey);
|
||||||
|
return;
|
||||||
|
} catch(Exception e) {
|
||||||
|
//Ignore, not a valid xpub
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
bitcoinURI = new BitcoinURI(qrtext);
|
bitcoinURI = new BitcoinURI(qrtext);
|
||||||
result = new Result(bitcoinURI);
|
result = new Result(bitcoinURI);
|
||||||
|
@ -180,6 +190,7 @@ public class QRScanDialog extends Dialog<QRScanDialog.Result> {
|
||||||
public final Transaction transaction;
|
public final Transaction transaction;
|
||||||
public final PSBT psbt;
|
public final PSBT psbt;
|
||||||
public final BitcoinURI uri;
|
public final BitcoinURI uri;
|
||||||
|
public final ExtendedKey extendedKey;
|
||||||
public final String error;
|
public final String error;
|
||||||
public final Throwable exception;
|
public final Throwable exception;
|
||||||
|
|
||||||
|
@ -187,6 +198,7 @@ public class QRScanDialog extends Dialog<QRScanDialog.Result> {
|
||||||
this.transaction = transaction;
|
this.transaction = transaction;
|
||||||
this.psbt = null;
|
this.psbt = null;
|
||||||
this.uri = null;
|
this.uri = null;
|
||||||
|
this.extendedKey = null;
|
||||||
this.error = null;
|
this.error = null;
|
||||||
this.exception = null;
|
this.exception = null;
|
||||||
}
|
}
|
||||||
|
@ -195,6 +207,7 @@ public class QRScanDialog extends Dialog<QRScanDialog.Result> {
|
||||||
this.transaction = null;
|
this.transaction = null;
|
||||||
this.psbt = psbt;
|
this.psbt = psbt;
|
||||||
this.uri = null;
|
this.uri = null;
|
||||||
|
this.extendedKey = null;
|
||||||
this.error = null;
|
this.error = null;
|
||||||
this.exception = null;
|
this.exception = null;
|
||||||
}
|
}
|
||||||
|
@ -203,6 +216,7 @@ public class QRScanDialog extends Dialog<QRScanDialog.Result> {
|
||||||
this.transaction = null;
|
this.transaction = null;
|
||||||
this.psbt = null;
|
this.psbt = null;
|
||||||
this.uri = uri;
|
this.uri = uri;
|
||||||
|
this.extendedKey = null;
|
||||||
this.error = null;
|
this.error = null;
|
||||||
this.exception = null;
|
this.exception = null;
|
||||||
}
|
}
|
||||||
|
@ -211,6 +225,16 @@ public class QRScanDialog extends Dialog<QRScanDialog.Result> {
|
||||||
this.transaction = null;
|
this.transaction = null;
|
||||||
this.psbt = null;
|
this.psbt = null;
|
||||||
this.uri = BitcoinURI.fromAddress(address);
|
this.uri = BitcoinURI.fromAddress(address);
|
||||||
|
this.extendedKey = null;
|
||||||
|
this.error = null;
|
||||||
|
this.exception = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Result(ExtendedKey extendedKey) {
|
||||||
|
this.transaction = null;
|
||||||
|
this.psbt = null;
|
||||||
|
this.uri = null;
|
||||||
|
this.extendedKey = extendedKey;
|
||||||
this.error = null;
|
this.error = null;
|
||||||
this.exception = null;
|
this.exception = null;
|
||||||
}
|
}
|
||||||
|
@ -219,6 +243,7 @@ public class QRScanDialog extends Dialog<QRScanDialog.Result> {
|
||||||
this.transaction = null;
|
this.transaction = null;
|
||||||
this.psbt = null;
|
this.psbt = null;
|
||||||
this.uri = null;
|
this.uri = null;
|
||||||
|
this.extendedKey = null;
|
||||||
this.error = error;
|
this.error = error;
|
||||||
this.exception = null;
|
this.exception = null;
|
||||||
}
|
}
|
||||||
|
@ -227,6 +252,7 @@ public class QRScanDialog extends Dialog<QRScanDialog.Result> {
|
||||||
this.transaction = null;
|
this.transaction = null;
|
||||||
this.psbt = null;
|
this.psbt = null;
|
||||||
this.uri = null;
|
this.uri = null;
|
||||||
|
this.extendedKey = null;
|
||||||
this.error = null;
|
this.error = null;
|
||||||
this.exception = exception;
|
this.exception = exception;
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,7 @@ public class FontAwesome5 extends GlyphFont {
|
||||||
CHECK_CIRCLE('\uf058'),
|
CHECK_CIRCLE('\uf058'),
|
||||||
CIRCLE('\uf111'),
|
CIRCLE('\uf111'),
|
||||||
COINS('\uf51e'),
|
COINS('\uf51e'),
|
||||||
|
EXCHANGE_ALT('\uf362'),
|
||||||
EXCLAMATION_CIRCLE('\uf06a'),
|
EXCLAMATION_CIRCLE('\uf06a'),
|
||||||
EXCLAMATION_TRIANGLE('\uf071'),
|
EXCLAMATION_TRIANGLE('\uf071'),
|
||||||
EXTERNAL_LINK_ALT('\uf35d'),
|
EXTERNAL_LINK_ALT('\uf35d'),
|
||||||
|
|
|
@ -7,6 +7,8 @@ import com.sparrowwallet.drongo.wallet.KeystoreSource;
|
||||||
import com.sparrowwallet.drongo.wallet.Wallet;
|
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.QRDisplayDialog;
|
||||||
|
import com.sparrowwallet.sparrow.control.QRScanDialog;
|
||||||
import com.sparrowwallet.sparrow.control.SeedDisplayDialog;
|
import com.sparrowwallet.sparrow.control.SeedDisplayDialog;
|
||||||
import com.sparrowwallet.sparrow.control.WalletPasswordDialog;
|
import com.sparrowwallet.sparrow.control.WalletPasswordDialog;
|
||||||
import com.sparrowwallet.sparrow.event.StorageEvent;
|
import com.sparrowwallet.sparrow.event.StorageEvent;
|
||||||
|
@ -29,6 +31,9 @@ import org.controlsfx.validation.ValidationResult;
|
||||||
import org.controlsfx.validation.ValidationSupport;
|
import org.controlsfx.validation.ValidationSupport;
|
||||||
import org.controlsfx.validation.Validator;
|
import org.controlsfx.validation.Validator;
|
||||||
import org.controlsfx.validation.decoration.StyleClassValidationDecoration;
|
import org.controlsfx.validation.decoration.StyleClassValidationDecoration;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import tornadofx.control.Field;
|
||||||
|
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
@ -36,6 +41,8 @@ import java.util.ResourceBundle;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
public class KeystoreController extends WalletFormController implements Initializable {
|
public class KeystoreController extends WalletFormController implements Initializable {
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(KeystoreController.class);
|
||||||
|
|
||||||
private Keystore keystore;
|
private Keystore keystore;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
|
@ -56,6 +63,9 @@ public class KeystoreController extends WalletFormController implements Initiali
|
||||||
@FXML
|
@FXML
|
||||||
private TextField label;
|
private TextField label;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Field xpubField;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private TextArea xpub;
|
private TextArea xpub;
|
||||||
|
|
||||||
|
@ -65,6 +75,15 @@ public class KeystoreController extends WalletFormController implements Initiali
|
||||||
@FXML
|
@FXML
|
||||||
private TextField fingerprint;
|
private TextField fingerprint;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Button scanXpubQR;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Button displayXpubQR;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Button switchXpubHeader;
|
||||||
|
|
||||||
private final ValidationSupport validationSupport = new ValidationSupport();
|
private final ValidationSupport validationSupport = new ValidationSupport();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -87,6 +106,9 @@ public class KeystoreController extends WalletFormController implements Initiali
|
||||||
}
|
}
|
||||||
|
|
||||||
viewSeedButton.managedProperty().bind(viewSeedButton.visibleProperty());
|
viewSeedButton.managedProperty().bind(viewSeedButton.visibleProperty());
|
||||||
|
scanXpubQR.managedProperty().bind(scanXpubQR.visibleProperty());
|
||||||
|
displayXpubQR.managedProperty().bind(displayXpubQR.visibleProperty());
|
||||||
|
displayXpubQR.visibleProperty().bind(scanXpubQR.visibleProperty().not());
|
||||||
|
|
||||||
updateType();
|
updateType();
|
||||||
|
|
||||||
|
@ -97,6 +119,8 @@ public class KeystoreController extends WalletFormController implements Initiali
|
||||||
if(keystore.getExtendedPublicKey() != null) {
|
if(keystore.getExtendedPublicKey() != null) {
|
||||||
xpub.setText(keystore.getExtendedPublicKey().toString());
|
xpub.setText(keystore.getExtendedPublicKey().toString());
|
||||||
setXpubContext(keystore.getExtendedPublicKey());
|
setXpubContext(keystore.getExtendedPublicKey());
|
||||||
|
} else {
|
||||||
|
switchXpubHeader.setDisable(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(keystore.getKeyDerivation() != null) {
|
if(keystore.getKeyDerivation() != null) {
|
||||||
|
@ -121,15 +145,19 @@ public class KeystoreController extends WalletFormController implements Initiali
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
xpub.textProperty().addListener((observable, oldValue, newValue) -> {
|
xpub.textProperty().addListener((observable, oldValue, newValue) -> {
|
||||||
if(ExtendedKey.isValid(newValue)) {
|
boolean valid = ExtendedKey.isValid(newValue);
|
||||||
|
if(valid) {
|
||||||
ExtendedKey extendedKey = ExtendedKey.fromDescriptor(newValue);
|
ExtendedKey extendedKey = ExtendedKey.fromDescriptor(newValue);
|
||||||
setXpubContext(extendedKey);
|
setXpubContext(extendedKey);
|
||||||
keystore.setExtendedPublicKey(extendedKey);
|
if(!extendedKey.equals(keystore.getExtendedPublicKey()) && extendedKey.getKey().isPubKeyOnly()) {
|
||||||
EventManager.get().post(new SettingsChangedEvent(walletForm.getWallet(), SettingsChangedEvent.Type.KEYSTORE_XPUB));
|
keystore.setExtendedPublicKey(extendedKey);
|
||||||
|
EventManager.get().post(new SettingsChangedEvent(walletForm.getWallet(), SettingsChangedEvent.Type.KEYSTORE_XPUB));
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
xpub.setTooltip(null);
|
|
||||||
xpub.setContextMenu(null);
|
xpub.setContextMenu(null);
|
||||||
|
switchXpubHeader.setDisable(true);
|
||||||
}
|
}
|
||||||
|
scanXpubQR.setVisible(!valid);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -148,7 +176,7 @@ public class KeystoreController extends WalletFormController implements Initiali
|
||||||
if(header != Network.get().getXpubHeader()) {
|
if(header != Network.get().getXpubHeader()) {
|
||||||
String otherPub = extendedKey.getExtendedKey(header);
|
String otherPub = extendedKey.getExtendedKey(header);
|
||||||
|
|
||||||
MenuItem copyOtherPub = new MenuItem("Copy " + header.getName().replace('p', 'P'));
|
MenuItem copyOtherPub = new MenuItem("Copy " + header.getDisplayName());
|
||||||
copyOtherPub.setOnAction(AE -> {
|
copyOtherPub.setOnAction(AE -> {
|
||||||
contextMenu.hide();
|
contextMenu.hide();
|
||||||
ClipboardContent content = new ClipboardContent();
|
ClipboardContent content = new ClipboardContent();
|
||||||
|
@ -157,13 +185,16 @@ public class KeystoreController extends WalletFormController implements Initiali
|
||||||
});
|
});
|
||||||
contextMenu.getItems().add(copyOtherPub);
|
contextMenu.getItems().add(copyOtherPub);
|
||||||
|
|
||||||
Tooltip tooltip = new Tooltip(otherPub);
|
xpubField.setText("xPub / " + header.getDisplayName() + ":");
|
||||||
xpub.setTooltip(tooltip);
|
switchXpubHeader.setDisable(false);
|
||||||
|
switchXpubHeader.setTooltip(new Tooltip("Show as " + header.getDisplayName()));
|
||||||
} else {
|
} else {
|
||||||
xpub.setTooltip(null);
|
xpubField.setText("xPub:");
|
||||||
|
switchXpubHeader.setDisable(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
xpub.setContextMenu(contextMenu);
|
xpub.setContextMenu(contextMenu);
|
||||||
|
scanXpubQR.setVisible(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void selectSource(ActionEvent event) {
|
public void selectSource(ActionEvent event) {
|
||||||
|
@ -223,6 +254,7 @@ public class KeystoreController extends WalletFormController implements Initiali
|
||||||
fingerprint.setEditable(editable);
|
fingerprint.setEditable(editable);
|
||||||
derivation.setEditable(editable);
|
derivation.setEditable(editable);
|
||||||
xpub.setEditable(editable);
|
xpub.setEditable(editable);
|
||||||
|
scanXpubQR.setVisible(editable);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getTypeLabel(Keystore keystore) {
|
private String getTypeLabel(Keystore keystore) {
|
||||||
|
@ -284,6 +316,7 @@ public class KeystoreController extends WalletFormController implements Initiali
|
||||||
|
|
||||||
if(keystore.getExtendedPublicKey() != null) {
|
if(keystore.getExtendedPublicKey() != null) {
|
||||||
xpub.setText(keystore.getExtendedPublicKey().toString());
|
xpub.setText(keystore.getExtendedPublicKey().toString());
|
||||||
|
setXpubContext(keystore.getExtendedPublicKey());
|
||||||
} else {
|
} else {
|
||||||
xpub.setText("");
|
xpub.setText("");
|
||||||
}
|
}
|
||||||
|
@ -321,6 +354,44 @@ public class KeystoreController extends WalletFormController implements Initiali
|
||||||
dlg.showAndWait();
|
dlg.showAndWait();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void scanXpubQR(ActionEvent event) {
|
||||||
|
QRScanDialog qrScanDialog = new QRScanDialog();
|
||||||
|
Optional<QRScanDialog.Result> optionalResult = qrScanDialog.showAndWait();
|
||||||
|
if(optionalResult.isPresent()) {
|
||||||
|
QRScanDialog.Result result = optionalResult.get();
|
||||||
|
if(result.extendedKey != null && result.extendedKey.getKey().isPubKeyOnly()) {
|
||||||
|
xpub.setText(result.extendedKey.getExtendedKey());
|
||||||
|
} else if(result.error != null) {
|
||||||
|
AppController.showErrorDialog("Invalid QR Code", result.error);
|
||||||
|
} else if(result.exception != null) {
|
||||||
|
log.error("Error opening webcam", result.exception);
|
||||||
|
AppController.showErrorDialog("Error opening webcam", result.exception.getMessage());
|
||||||
|
} else {
|
||||||
|
AppController.showErrorDialog("Invalid QR Code", "QR Code did not contain a valid xPub");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void displayXpubQR(ActionEvent event) {
|
||||||
|
QRDisplayDialog qrDisplayDialog = new QRDisplayDialog(xpub.getText());
|
||||||
|
qrDisplayDialog.showAndWait();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void switchXpubHeader(ActionEvent event) {
|
||||||
|
if(keystore.getExtendedPublicKey() != null) {
|
||||||
|
ExtendedKey.Header header = ExtendedKey.Header.fromScriptType(walletForm.getWallet().getScriptType(), false);
|
||||||
|
if(!xpub.getText().startsWith(header.getName())) {
|
||||||
|
String otherPub = keystore.getExtendedPublicKey().getExtendedKey(header);
|
||||||
|
xpub.setText(otherPub);
|
||||||
|
switchXpubHeader.setTooltip(new Tooltip("Show as xPub"));
|
||||||
|
} else {
|
||||||
|
String xPub = keystore.getExtendedPublicKey().getExtendedKey();
|
||||||
|
xpub.setText(xPub);
|
||||||
|
switchXpubHeader.setTooltip(new Tooltip("Show as " + header.getDisplayName()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Subscribe
|
@Subscribe
|
||||||
public void update(SettingsChangedEvent event) {
|
public void update(SettingsChangedEvent event) {
|
||||||
if(walletForm.getWallet().equals(event.getWallet()) && event.getType().equals(SettingsChangedEvent.Type.SCRIPT_TYPE)) {
|
if(walletForm.getWallet().equals(event.getWallet()) && event.getType().equals(SettingsChangedEvent.Type.SCRIPT_TYPE)) {
|
||||||
|
|
|
@ -21,3 +21,12 @@
|
||||||
#type {
|
#type {
|
||||||
-fx-padding: 0 17 0 0;
|
-fx-padding: 0 17 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.xpub-buttons {
|
||||||
|
-fx-max-width: 40;
|
||||||
|
-fx-padding: 0 0 0 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.xpub-buttons .button {
|
||||||
|
-fx-pref-width: 35;
|
||||||
|
}
|
|
@ -41,13 +41,36 @@
|
||||||
<TextField fx:id="label" maxWidth="160"/>
|
<TextField fx:id="label" maxWidth="160"/>
|
||||||
</Field>
|
</Field>
|
||||||
<Field text="Master fingerprint:">
|
<Field text="Master fingerprint:">
|
||||||
<TextField fx:id="fingerprint" maxWidth="80" promptText="ffffffff"/> <HelpLabel helpText="A master fingerprint is the first 4 bytes of the master public key hash. It is safe to use any valid value (ffffffff) for Watch Only Wallets." />
|
<TextField fx:id="fingerprint" maxWidth="80" promptText="00000000"/> <HelpLabel helpText="A master fingerprint is the first 4 bytes of the master public key hash. It is safe to use any valid value (00000000) for Watch Only Wallets." />
|
||||||
</Field>
|
</Field>
|
||||||
<Field text="Derivation:">
|
<Field text="Derivation:">
|
||||||
<TextField fx:id="derivation" maxWidth="200"/>
|
<TextField fx:id="derivation" maxWidth="200"/> <HelpLabel helpText="The derivation path to the xPub from the master private key. For safety, derivations that match defaults for other script types are not valid." />
|
||||||
</Field>
|
</Field>
|
||||||
<Field text="xPub:">
|
<Field fx:id="xpubField" text="xPub:">
|
||||||
<TextArea fx:id="xpub" wrapText="true" prefRowCount="2" maxHeight="50" />
|
<TextArea fx:id="xpub" wrapText="true" prefRowCount="2" maxHeight="52" />
|
||||||
|
<VBox styleClass="xpub-buttons" HBox.hgrow="NEVER">
|
||||||
|
<Button fx:id="scanXpubQR" onAction="#scanXpubQR">
|
||||||
|
<graphic>
|
||||||
|
<Glyph fontFamily="Font Awesome 5 Free Solid" fontSize="12" icon="CAMERA" />
|
||||||
|
</graphic>
|
||||||
|
<tooltip>
|
||||||
|
<Tooltip text="Scan a QR code" />
|
||||||
|
</tooltip>
|
||||||
|
</Button>
|
||||||
|
<Button fx:id="displayXpubQR" onAction="#displayXpubQR">
|
||||||
|
<graphic>
|
||||||
|
<Glyph fontFamily="Font Awesome 5 Free Solid" fontSize="12" icon="QRCODE" />
|
||||||
|
</graphic>
|
||||||
|
<tooltip>
|
||||||
|
<Tooltip text="Display as a QR code" />
|
||||||
|
</tooltip>
|
||||||
|
</Button>
|
||||||
|
<Button fx:id="switchXpubHeader" onAction="#switchXpubHeader">
|
||||||
|
<graphic>
|
||||||
|
<Glyph fontFamily="Font Awesome 5 Free Solid" fontSize="12" icon="EXCHANGE_ALT" />
|
||||||
|
</graphic>
|
||||||
|
</Button>
|
||||||
|
</VBox>
|
||||||
</Field>
|
</Field>
|
||||||
</Fieldset>
|
</Fieldset>
|
||||||
</Form>
|
</Form>
|
||||||
|
|
Loading…
Reference in a new issue